* Purple is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA #include "backend-iface.h" #include <farstream/fs-conference.h> #include <farstream/fs-element-added-notifier.h> #include <farstream/fs-utils.h> #include <gst/gststructure.h> /** @copydoc _PurpleMediaBackendFs2Session */ typedef struct _PurpleMediaBackendFs2Session PurpleMediaBackendFs2Session; /** @copydoc _PurpleMediaBackendFs2Stream */ typedef struct _PurpleMediaBackendFs2Stream PurpleMediaBackendFs2Stream; static void purple_media_backend_iface_init(PurpleMediaBackendInterface *iface); gst_bus_cb(GstBus *bus, GstMessage *msg, PurpleMediaBackendFs2 *self); state_changed_cb(PurpleMedia *media, PurpleMediaState state, gchar *sid, gchar *name, PurpleMediaBackendFs2 *self); stream_info_cb(PurpleMedia *media, PurpleMediaInfoType type, gchar *sid, gchar *name, gboolean local, PurpleMediaBackendFs2 *self); static gboolean purple_media_backend_fs2_add_stream(PurpleMediaBackend *self, const gchar *sess_id, const gchar *who, PurpleMediaSessionType type, gboolean initiator, const gchar *transmitter, guint num_params, GParameter *params); static void purple_media_backend_fs2_add_remote_candidates( PurpleMediaBackend *self, const gchar *sess_id, const gchar *participant, GList *remote_candidates); static gboolean purple_media_backend_fs2_codecs_ready(PurpleMediaBackend *self, static GList *purple_media_backend_fs2_get_codecs(PurpleMediaBackend *self, static GList *purple_media_backend_fs2_get_local_candidates( PurpleMediaBackend *self, const gchar *sess_id, const gchar *participant); static gboolean purple_media_backend_fs2_set_encryption_parameters ( PurpleMediaBackend *self, const gchar *sess_id, const gchar *cipher, const gchar *auth, const gchar *key, gsize key_len); static gboolean purple_media_backend_fs2_set_decryption_parameters( PurpleMediaBackend *self, const gchar *sess_id, const gchar *participant, const gchar *cipher, const gchar *auth, const gchar *key, gsize key_len); static gboolean purple_media_backend_fs2_set_remote_codecs( PurpleMediaBackend *self, const gchar *sess_id, const gchar *participant, static gboolean purple_media_backend_fs2_set_send_codec( PurpleMediaBackend *self, const gchar *sess_id, PurpleMediaCodec *codec); static void purple_media_backend_fs2_set_params(PurpleMediaBackend *self, guint num_params, GParameter *params); static const gchar **purple_media_backend_fs2_get_available_params(void); static gboolean purple_media_backend_fs2_send_dtmf( PurpleMediaBackend *self, const gchar *sess_id, gchar dtmf, guint8 volume, guint16 duration); static gboolean purple_media_backend_fs2_set_send_rtcp_mux( PurpleMediaBackend *self, const gchar *sess_id, const gchar *participant, static void free_stream(PurpleMediaBackendFs2Stream *stream); static void free_session(PurpleMediaBackendFs2Session *session); * An opaque structure representing the Farstream media backend. struct _PurpleMediaBackendFs2 struct _PurpleMediaBackendFs2Stream PurpleMediaBackendFs2Session *session; GList *remote_candidates; struct _PurpleMediaBackendFs2Session PurpleMediaBackendFs2 *backend; PurpleMediaSessionType type; FsConference *conference; FsElementAddedNotifier *notifier; GHashTable *participants; gdouble silence_threshold; } PurpleMediaBackendFs2Private; G_DEFINE_TYPE_WITH_CODE(PurpleMediaBackendFs2, purple_media_backend_fs2, G_ADD_PRIVATE(PurpleMediaBackendFs2) G_IMPLEMENT_INTERFACE(PURPLE_TYPE_MEDIA_BACKEND, purple_media_backend_iface_init)); purple_media_backend_fs2_init(PurpleMediaBackendFs2 *self) purple_media_candidate_type_to_fs(PurpleMediaCandidateType type) case PURPLE_MEDIA_CANDIDATE_TYPE_HOST: return FS_CANDIDATE_TYPE_HOST; case PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX: return FS_CANDIDATE_TYPE_SRFLX; case PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX: return FS_CANDIDATE_TYPE_PRFLX; case PURPLE_MEDIA_CANDIDATE_TYPE_RELAY: return FS_CANDIDATE_TYPE_RELAY; case PURPLE_MEDIA_CANDIDATE_TYPE_MULTICAST: return FS_CANDIDATE_TYPE_MULTICAST; g_return_val_if_reached(FS_CANDIDATE_TYPE_HOST); static PurpleMediaCandidateType purple_media_candidate_type_from_fs(FsCandidateType type) case FS_CANDIDATE_TYPE_HOST: return PURPLE_MEDIA_CANDIDATE_TYPE_HOST; case FS_CANDIDATE_TYPE_SRFLX: return PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX; case FS_CANDIDATE_TYPE_PRFLX: return PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX; case FS_CANDIDATE_TYPE_RELAY: return PURPLE_MEDIA_CANDIDATE_TYPE_RELAY; case FS_CANDIDATE_TYPE_MULTICAST: return PURPLE_MEDIA_CANDIDATE_TYPE_MULTICAST; g_return_val_if_reached(PURPLE_MEDIA_CANDIDATE_TYPE_HOST); purple_media_network_protocol_to_fs(PurpleMediaNetworkProtocol protocol) case PURPLE_MEDIA_NETWORK_PROTOCOL_UDP: return FS_NETWORK_PROTOCOL_UDP; case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_PASSIVE: return FS_NETWORK_PROTOCOL_TCP_PASSIVE; case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_ACTIVE: return FS_NETWORK_PROTOCOL_TCP_ACTIVE; case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_SO: return FS_NETWORK_PROTOCOL_TCP_SO; g_return_val_if_reached(FS_NETWORK_PROTOCOL_TCP); static PurpleMediaNetworkProtocol purple_media_network_protocol_from_fs(FsNetworkProtocol protocol) case FS_NETWORK_PROTOCOL_UDP: return PURPLE_MEDIA_NETWORK_PROTOCOL_UDP; case FS_NETWORK_PROTOCOL_TCP_PASSIVE: return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_PASSIVE; case FS_NETWORK_PROTOCOL_TCP_ACTIVE: return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_ACTIVE; case FS_NETWORK_PROTOCOL_TCP_SO: return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_SO; g_return_val_if_reached(PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_PASSIVE); event_probe_cb(GstPad *srcpad, GstPadProbeInfo *info, gpointer unused) GstEvent *event = GST_PAD_PROBE_INFO_EVENT(info); if (GST_EVENT_TYPE(event) == GST_EVENT_CUSTOM_DOWNSTREAM && gst_event_has_name(event, "purple-unlink-tee")) { const GstStructure *s = gst_event_get_structure(event); gst_pad_unlink(srcpad, gst_pad_get_peer(srcpad)); gst_pad_remove_probe(srcpad, g_value_get_ulong(gst_structure_get_value(s, "handler-id"))); if (g_value_get_boolean(gst_structure_get_value(s, "release-pad"))) gst_element_release_request_pad(GST_ELEMENT_PARENT(srcpad), srcpad); return GST_PAD_PROBE_DROP; unlink_teepad_dynamic(GstPad *srcpad, gboolean release_pad) gulong id = gst_pad_add_probe(srcpad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, event_probe_cb, NULL, NULL); if (GST_IS_GHOST_PAD(srcpad)) srcpad = gst_ghost_pad_get_target(GST_GHOST_PAD(srcpad)); gst_element_send_event(gst_pad_get_parent_element(srcpad), gst_event_new_custom(GST_EVENT_CUSTOM_DOWNSTREAM, gst_structure_new("purple-unlink-tee", "release-pad", G_TYPE_BOOLEAN, release_pad, "handler-id", G_TYPE_ULONG, id, purple_media_backend_fs2_dispose(GObject *obj) PurpleMediaBackendFs2Private *priv = purple_media_backend_fs2_get_instance_private( PURPLE_MEDIA_BACKEND_FS2(obj)); purple_debug_info("backend-fs2", "purple_media_backend_fs2_dispose\n"); g_object_unref(priv->notifier); pipeline = purple_media_manager_get_pipeline( purple_media_get_manager(priv->media)); /* All connections to media sources should be blocked before confbin is * removed, to prevent freezing of any other simultaneously running GList *sessions = g_hash_table_get_values(priv->sessions); for (; sessions; sessions = g_list_delete_link(sessions, sessions)) { PurpleMediaBackendFs2Session *session = sessions->data; unlink_teepad_dynamic(session->srcpad, FALSE); gst_object_unref(session->srcpad); gst_element_set_locked_state(priv->confbin, TRUE); gst_element_set_state(GST_ELEMENT(priv->confbin), gst_bin_remove(GST_BIN(pipeline), priv->confbin); bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline)); g_signal_handlers_disconnect_matched(G_OBJECT(bus), 0, 0, 0, gst_bus_cb, obj); purple_debug_warning("backend-fs2", "Unable to " "properly dispose the conference. " "Couldn't get the pipeline.\n"); GList *sessions = g_hash_table_get_values(priv->sessions); for (; sessions; sessions = g_list_delete_link(sessions, sessions)) { PurpleMediaBackendFs2Session *session = g_object_unref(session->session); if (priv->participants) { g_hash_table_destroy(priv->participants); priv->participants = NULL; for (iter = priv->streams; iter; iter = g_list_next(iter)) { PurpleMediaBackendFs2Stream *stream = iter->data; g_object_unref(stream->stream); g_object_remove_weak_pointer(G_OBJECT(priv->media), (gpointer*)&priv->media); G_OBJECT_CLASS(purple_media_backend_fs2_parent_class)->dispose(obj); purple_media_backend_fs2_finalize(GObject *obj) PurpleMediaBackendFs2Private *priv = purple_media_backend_fs2_get_instance_private( PURPLE_MEDIA_BACKEND_FS2(obj)); purple_debug_info("backend-fs2", "purple_media_backend_fs2_finalize\n"); g_free(priv->conference_type); g_list_free_full(priv->streams, (GDestroyNotify)free_stream); GList *sessions = g_hash_table_get_values(priv->sessions); g_list_free_full(sessions, (GDestroyNotify)free_session); g_hash_table_destroy(priv->sessions); G_OBJECT_CLASS(purple_media_backend_fs2_parent_class)->finalize(obj); purple_media_backend_fs2_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) PurpleMediaBackendFs2Private *priv; g_return_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(object)); priv = purple_media_backend_fs2_get_instance_private( PURPLE_MEDIA_BACKEND_FS2(object)); case PROP_CONFERENCE_TYPE: priv->conference_type = g_value_dup_string(value); priv->media = g_value_get_object(value); g_object_add_weak_pointer(G_OBJECT(priv->media), (gpointer*)&priv->media); g_signal_connect(G_OBJECT(priv->media), G_CALLBACK(state_changed_cb), PURPLE_MEDIA_BACKEND_FS2(object)); g_signal_connect(G_OBJECT(priv->media), "stream-info", G_CALLBACK(stream_info_cb), PURPLE_MEDIA_BACKEND_FS2(object)); G_OBJECT_WARN_INVALID_PROPERTY_ID( purple_media_backend_fs2_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) PurpleMediaBackendFs2Private *priv; g_return_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(object)); priv = purple_media_backend_fs2_get_instance_private( PURPLE_MEDIA_BACKEND_FS2(object)); case PROP_CONFERENCE_TYPE: g_value_set_string(value, priv->conference_type); g_value_set_object(value, priv->media); G_OBJECT_WARN_INVALID_PROPERTY_ID( purple_media_backend_fs2_class_init(PurpleMediaBackendFs2Class *klass) GObjectClass *gobject_class = (GObjectClass*)klass; gobject_class->dispose = purple_media_backend_fs2_dispose; gobject_class->finalize = purple_media_backend_fs2_finalize; gobject_class->set_property = purple_media_backend_fs2_set_property; gobject_class->get_property = purple_media_backend_fs2_get_property; g_object_class_override_property(gobject_class, PROP_CONFERENCE_TYPE, g_object_class_override_property(gobject_class, PROP_MEDIA, "media"); /* VA-API elements aren't well supported in Farstream. Ignore them. */ features = gst_registry_get_feature_list_by_plugin(gst_registry_get(), g_list_foreach(features, (GFunc)gst_plugin_feature_set_rank, GINT_TO_POINTER(GST_RANK_NONE)); gst_plugin_feature_list_free(features); purple_media_backend_iface_init(PurpleMediaBackendInterface *iface) iface->add_stream = purple_media_backend_fs2_add_stream; iface->add_remote_candidates = purple_media_backend_fs2_add_remote_candidates; iface->codecs_ready = purple_media_backend_fs2_codecs_ready; iface->get_codecs = purple_media_backend_fs2_get_codecs; iface->get_local_candidates = purple_media_backend_fs2_get_local_candidates; iface->set_remote_codecs = purple_media_backend_fs2_set_remote_codecs; iface->set_send_codec = purple_media_backend_fs2_set_send_codec; iface->set_encryption_parameters = purple_media_backend_fs2_set_encryption_parameters; iface->set_decryption_parameters = purple_media_backend_fs2_set_decryption_parameters; iface->set_params = purple_media_backend_fs2_set_params; iface->get_available_params = purple_media_backend_fs2_get_available_params; iface->send_dtmf = purple_media_backend_fs2_send_dtmf; iface->set_send_rtcp_mux = purple_media_backend_fs2_set_send_rtcp_mux; session_type_to_fs_media_type(PurpleMediaSessionType type) if (type & PURPLE_MEDIA_AUDIO) return FS_MEDIA_TYPE_AUDIO; else if (type & PURPLE_MEDIA_VIDEO) return FS_MEDIA_TYPE_VIDEO; #ifdef HAVE_MEDIA_APPLICATION else if (type & PURPLE_MEDIA_APPLICATION) return FS_MEDIA_TYPE_APPLICATION; session_type_to_fs_stream_direction(PurpleMediaSessionType type) if ((type & PURPLE_MEDIA_AUDIO) == PURPLE_MEDIA_AUDIO || (type & PURPLE_MEDIA_VIDEO) == PURPLE_MEDIA_VIDEO) return FS_DIRECTION_BOTH; else if ((type & PURPLE_MEDIA_SEND_AUDIO) || (type & PURPLE_MEDIA_SEND_VIDEO)) return FS_DIRECTION_SEND; else if ((type & PURPLE_MEDIA_RECV_AUDIO) || (type & PURPLE_MEDIA_RECV_VIDEO)) return FS_DIRECTION_RECV; #ifdef HAVE_MEDIA_APPLICATION else if ((type & PURPLE_MEDIA_APPLICATION) == PURPLE_MEDIA_APPLICATION) return FS_DIRECTION_BOTH; else if (type & PURPLE_MEDIA_SEND_APPLICATION) return FS_DIRECTION_SEND; else if (type & PURPLE_MEDIA_RECV_APPLICATION) return FS_DIRECTION_RECV; return FS_DIRECTION_NONE; static PurpleMediaSessionType session_type_from_fs(FsMediaType type, FsStreamDirection direction) PurpleMediaSessionType result = PURPLE_MEDIA_NONE; if (type == FS_MEDIA_TYPE_AUDIO) { if (direction & FS_DIRECTION_SEND) result |= PURPLE_MEDIA_SEND_AUDIO; if (direction & FS_DIRECTION_RECV) result |= PURPLE_MEDIA_RECV_AUDIO; } else if (type == FS_MEDIA_TYPE_VIDEO) { if (direction & FS_DIRECTION_SEND) result |= PURPLE_MEDIA_SEND_VIDEO; if (direction & FS_DIRECTION_RECV) result |= PURPLE_MEDIA_RECV_VIDEO; #ifdef HAVE_MEDIA_APPLICATION } else if (type == FS_MEDIA_TYPE_APPLICATION) { if (direction & FS_DIRECTION_SEND) result |= PURPLE_MEDIA_SEND_APPLICATION; if (direction & FS_DIRECTION_RECV) result |= PURPLE_MEDIA_RECV_APPLICATION; candidate_to_fs(PurpleMediaCandidate *candidate) FsCandidate *fscandidate; PurpleMediaNetworkProtocol proto; PurpleMediaCandidateType type; g_object_get(G_OBJECT(candidate), "foundation", &foundation, "component-id", &component_id, fscandidate = fs_candidate_new(foundation, component_id, purple_media_candidate_type_to_fs(type), purple_media_network_protocol_to_fs(proto), ip, port); fscandidate->base_ip = base_ip; fscandidate->base_port = base_port; fscandidate->priority = priority; fscandidate->username = username; fscandidate->password = password; candidate_list_to_fs(GList *candidates) for (; candidates; candidates = g_list_next(candidates)) { new_list = g_list_prepend(new_list, candidate_to_fs(candidates->data)); new_list = g_list_reverse(new_list); static PurpleMediaCandidate * candidate_from_fs(FsCandidate *fscandidate) PurpleMediaCandidate *candidate; candidate = purple_media_candidate_new(fscandidate->foundation, fscandidate->component_id, purple_media_candidate_type_from_fs(fscandidate->type), purple_media_network_protocol_from_fs(fscandidate->proto), fscandidate->ip, fscandidate->port); "base-ip", fscandidate->base_ip, "base-port", fscandidate->base_port, "priority", fscandidate->priority, "username", fscandidate->username, "password", fscandidate->password, "ttl", fscandidate->ttl, NULL); candidate_list_from_fs(GList *candidates) for (; candidates; candidates = g_list_next(candidates)) { new_list = g_list_prepend(new_list, candidate_from_fs(candidates->data)); new_list = g_list_reverse(new_list); codec_to_fs(const PurpleMediaCodec *codec) PurpleMediaSessionType media_type; g_object_get(G_OBJECT(codec), "encoding-name", &encoding_name, "media-type", &media_type, "clock-rate", &clock_rate, "optional-params", &iter, new_codec = fs_codec_new(id, encoding_name, session_type_to_fs_media_type(media_type), new_codec->channels = channels; for (; iter; iter = g_list_next(iter)) { PurpleKeyValuePair *param = (PurpleKeyValuePair*)iter->data; fs_codec_add_optional_parameter(new_codec, param->key, param->value); static PurpleMediaCodec * codec_from_fs(const FsCodec *codec) PurpleMediaCodec *new_codec; new_codec = purple_media_codec_new(codec->id, codec->encoding_name, session_type_from_fs(codec->media_type, FS_DIRECTION_BOTH), codec->clock_rate); g_object_set(new_codec, "channels", codec->channels, NULL); for (iter = codec->optional_params; iter; iter = g_list_next(iter)) { FsCodecParameter *param = (FsCodecParameter*)iter->data; purple_media_codec_add_optional_parameter(new_codec, param->name, param->value); codec_list_from_fs(GList *codecs) for (; codecs; codecs = g_list_next(codecs)) { new_list = g_list_prepend(new_list, codec_from_fs(codecs->data)); new_list = g_list_reverse(new_list); codec_list_to_fs(GList *codecs) for (; codecs; codecs = g_list_next(codecs)) { new_list = g_list_prepend(new_list, codec_to_fs(codecs->data)); new_list = g_list_reverse(new_list); static PurpleMediaBackendFs2Session * get_session(PurpleMediaBackendFs2 *self, const gchar *sess_id) PurpleMediaBackendFs2Private *priv; PurpleMediaBackendFs2Session *session = NULL; g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL); priv = purple_media_backend_fs2_get_instance_private(self); if (priv->sessions != NULL) session = g_hash_table_lookup(priv->sessions, sess_id); get_participant(PurpleMediaBackendFs2 *self, const gchar *name) PurpleMediaBackendFs2Private *priv; FsParticipant *participant = NULL; g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL); priv = purple_media_backend_fs2_get_instance_private(self); if (priv->participants != NULL) participant = g_hash_table_lookup(priv->participants, name); static PurpleMediaBackendFs2Stream * get_stream(PurpleMediaBackendFs2 *self, const gchar *sess_id, const gchar *name) PurpleMediaBackendFs2Private *priv; g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL); priv = purple_media_backend_fs2_get_instance_private(self); for (; streams; streams = g_list_next(streams)) { PurpleMediaBackendFs2Stream *stream = streams->data; if (purple_strequal(stream->session->id, sess_id) && purple_strequal(stream->participant, name)) get_streams(PurpleMediaBackendFs2 *self, const gchar *sess_id, const gchar *name) PurpleMediaBackendFs2Private *priv; GList *streams, *ret = NULL; g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL); priv = purple_media_backend_fs2_get_instance_private(self); for (; streams; streams = g_list_next(streams)) { PurpleMediaBackendFs2Stream *stream = streams->data; if (sess_id != NULL && !purple_strequal(stream->session->id, sess_id)) else if (name != NULL && !purple_strequal(stream->participant, name)) ret = g_list_prepend(ret, stream); ret = g_list_reverse(ret); static PurpleMediaBackendFs2Session * get_session_from_fs_stream(PurpleMediaBackendFs2 *self, FsStream *stream) PurpleMediaBackendFs2Private *priv = purple_media_backend_fs2_get_instance_private(self); g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL); g_return_val_if_fail(FS_IS_STREAM(stream), NULL); g_object_get(stream, "session", &fssession, NULL); values = g_hash_table_get_values(priv->sessions); for (; values; values = g_list_delete_link(values, values)) { PurpleMediaBackendFs2Session *session = values->data; if (session->session == fssession) { g_object_unref(fssession); g_object_unref(fssession); gst_msg_db_to_percent(GstMessage *msg, gchar *value_name) list = gst_structure_get_value(gst_message_get_structure(msg), value_name); G_GNUC_BEGIN_IGNORE_DEPRECATIONS value = g_value_array_get_nth(g_value_get_boxed(list), 0); G_GNUC_END_IGNORE_DEPRECATIONS value_db = g_value_get_double(value); percent = pow(10, value_db / 20); return (percent > 1.0) ? 1.0 : percent; purple_media_error_fs(PurpleMedia *media, const gchar *error, const GstStructure *fs_error) const gchar *error_msg = gst_structure_get_string(fs_error, "error-msg"); purple_media_error(media, "%s%s%s", error, error_msg ? _("\n\nMessage from Farstream: ") : "", error_msg ? error_msg : ""); gst_handle_message_element(GstBus *bus, GstMessage *msg, PurpleMediaBackendFs2 *self) PurpleMediaBackendFs2Private *priv = purple_media_backend_fs2_get_instance_private(self); GstElement *src = GST_ELEMENT(GST_MESSAGE_SRC(msg)); static guint level_id = 0; const GstStructure *structure = gst_message_get_structure(msg); level_id = g_signal_lookup("level", PURPLE_TYPE_MEDIA); if (gst_structure_has_name(structure, "level")) { GstElement *src = GST_ELEMENT(GST_MESSAGE_SRC(msg)); gchar *participant = NULL; PurpleMediaBackendFs2Session *session = NULL; if (!PURPLE_IS_MEDIA(priv->media) || GST_ELEMENT_PARENT(src) != priv->confbin) name = gst_element_get_name(src); if (!strncmp(name, "sendlevel_", 10)) { session = get_session(self, name+10); if (priv->silence_threshold > 0) { percent = gst_msg_db_to_percent(msg, "decay"); g_object_set(session->srcvalve, "drop", (percent < priv->silence_threshold), NULL); if (!g_signal_has_handler_pending(priv->media, level_id, 0, FALSE)) GList *iter = priv->streams; PurpleMediaBackendFs2Stream *stream; for (; iter; iter = g_list_next(iter)) { if (stream->level == src) { session = stream->session; participant = stream->participant; percent = gst_msg_db_to_percent(msg, "rms"); g_signal_emit(priv->media, level_id, 0, session->id, participant, percent); if (!FS_IS_CONFERENCE(src) || !PURPLE_IS_MEDIA_BACKEND(self) || priv->conference != FS_CONFERENCE(src)) if (gst_structure_has_name(structure, "farstream-error")) { gboolean error_emitted = FALSE; gst_structure_get_enum(structure, "error-no", FS_TYPE_ERROR, (gint*)&error_no); case FS_ERROR_CONSTRUCTION: purple_media_error_fs(priv->media, _("Error initializing the call. " "This probably denotes problem in " "installation of GStreamer or Farstream."), purple_media_error_fs(priv->media, _("Network error."), purple_media_end(priv->media, NULL, NULL); case FS_ERROR_NEGOTIATION_FAILED: purple_media_error_fs(priv->media, _("Codec negotiation failed. " "This problem might be resolved by installing " "more GStreamer codecs."), purple_media_end(priv->media, NULL, NULL); purple_media_error(priv->media, "Install some GStreamer codecs found " "in GStreamer plugins packages.")); purple_media_end(priv->media, NULL, NULL); purple_debug_error("backend-fs2", "farstream-error: %i: %s\n", gst_structure_get_string(structure, "error-msg")); if (FS_ERROR_IS_FATAL(error_no)) { purple_media_error(priv->media, _("A non-recoverable Farstream error has occurred.")); purple_media_end(priv->media, NULL, NULL); } else if (gst_structure_has_name(structure, "farstream-new-local-candidate")) { FsCandidate *local_candidate; PurpleMediaCandidate *candidate; FsParticipant *participant; PurpleMediaBackendFs2Session *session; PurpleMediaBackendFs2Stream *media_stream; value = gst_structure_get_value(structure, "stream"); stream = g_value_get_object(value); value = gst_structure_get_value(structure, "candidate"); local_candidate = g_value_get_boxed(value); session = get_session_from_fs_stream(self, stream); purple_debug_info("backend-fs2", "got new local candidate: %s\n", local_candidate->foundation); g_object_get(stream, "participant", &participant, NULL); name = g_object_get_data(G_OBJECT(participant), "purple-name"); media_stream = get_stream(self, session->id, name); media_stream->local_candidates = g_list_append( media_stream->local_candidates, fs_candidate_copy(local_candidate)); candidate = candidate_from_fs(local_candidate); g_signal_emit_by_name(self, "new-candidate", session->id, name, candidate); g_object_unref(candidate); g_object_unref(participant); } else if (gst_structure_has_name(structure, "farstream-local-candidates-prepared")) { FsParticipant *participant; PurpleMediaBackendFs2Session *session; value = gst_structure_get_value(structure, "stream"); stream = g_value_get_object(value); session = get_session_from_fs_stream(self, stream); g_object_get(stream, "participant", &participant, NULL); g_signal_emit_by_name(self, "candidates-prepared", g_object_get_data(G_OBJECT(participant), "purple-name")); g_object_unref(participant); } else if (gst_structure_has_name(structure, "farstream-new-active-candidate-pair")) { FsCandidate *local_candidate; FsCandidate *remote_candidate; FsParticipant *participant; PurpleMediaBackendFs2Session *session; PurpleMediaCandidate *lcandidate, *rcandidate; value = gst_structure_get_value(structure, "stream"); stream = g_value_get_object(value); value = gst_structure_get_value(structure, "local-candidate"); local_candidate = g_value_get_boxed(value); value = gst_structure_get_value(structure, "remote-candidate"); remote_candidate = g_value_get_boxed(value); g_object_get(stream, "participant", &participant, NULL); session = get_session_from_fs_stream(self, stream); lcandidate = candidate_from_fs(local_candidate); rcandidate = candidate_from_fs(remote_candidate); g_signal_emit_by_name(self, "active-candidate-pair", g_object_get_data(G_OBJECT(participant), "purple-name"), g_object_unref(participant); g_object_unref(lcandidate); g_object_unref(rcandidate); } else if (gst_structure_has_name(structure, "farstream-recv-codecs-changed")) { value = gst_structure_get_value(structure, "codecs"); codecs = g_value_get_boxed(value); purple_debug_info("backend-fs2", "farstream-recv-codecs-changed: %s\n", } else if (gst_structure_has_name(structure, "farstream-component-state-changed")) { value = gst_structure_get_value(structure, "state"); fsstate = g_value_get_enum(value); value = gst_structure_get_value(structure, "component"); component = g_value_get_uint(value); case FS_STREAM_STATE_FAILED: case FS_STREAM_STATE_DISCONNECTED: case FS_STREAM_STATE_GATHERING: case FS_STREAM_STATE_CONNECTING: case FS_STREAM_STATE_CONNECTED: case FS_STREAM_STATE_READY: purple_debug_info("backend-fs2", "farstream-component-state-changed: " "component: %u state: %s\n", } else if (gst_structure_has_name(structure, "farstream-send-codec-changed")) { value = gst_structure_get_value(structure, "codec"); codec = g_value_get_boxed(value); codec_str = fs_codec_to_string(codec); purple_debug_info("backend-fs2", "farstream-send-codec-changed: codec: %s\n", } else if (gst_structure_has_name(structure, "farstream-codecs-changed")) { value = gst_structure_get_value(structure, "session"); fssession = g_value_get_object(value); sessions = g_hash_table_get_values(priv->sessions); for (; sessions; sessions = g_list_delete_link(sessions, sessions)) { PurpleMediaBackendFs2Session *session = sessions->data; if (session->session != fssession) session_id = g_strdup(session->id); g_signal_emit_by_name(self, "codecs-changed", gst_handle_message_error(GstBus *bus, GstMessage *msg, PurpleMediaBackendFs2 *self) PurpleMediaBackendFs2Private *priv = purple_media_backend_fs2_get_instance_private(self); GstElement *element = GST_ELEMENT(GST_MESSAGE_SRC(msg)); GstElement *lastElement = NULL; gst_message_parse_error(msg, &error, &debug_msg); purple_debug_error("backend-fs2", "gst error %s\ndebugging: %s\n", error->message, debug_msg); while (element && !GST_IS_PIPELINE(element)) { if (element == priv->confbin) element = GST_ELEMENT_PARENT(element); if (!element || !GST_IS_PIPELINE(element)) sessions = purple_media_get_session_ids(priv->media); for (; sessions; sessions = g_list_delete_link(sessions, sessions)) { if (purple_media_get_src(priv->media, sessions->data) if (purple_media_get_session_type(priv->media, sessions->data) purple_media_error(priv->media, _("Error with your microphone")); else if (purple_media_get_session_type(priv->media, sessions->data) & PURPLE_MEDIA_VIDEO) purple_media_error(priv->media, _("Error with your webcam")); purple_media_error(priv->media, _("Conference error")); purple_media_end(priv->media, NULL, NULL); gst_bus_cb(GstBus *bus, GstMessage *msg, PurpleMediaBackendFs2 *self) switch(GST_MESSAGE_TYPE(msg)) { case GST_MESSAGE_ELEMENT: gst_handle_message_element(bus, msg, self); gst_handle_message_error(bus, msg, self); remove_element(GstElement *element) gst_element_set_locked_state(element, TRUE); gst_element_set_state(element, GST_STATE_NULL); gst_bin_remove(GST_BIN(GST_ELEMENT_PARENT(element)), element); state_changed_cb(PurpleMedia *media, PurpleMediaState state, gchar *sid, gchar *name, PurpleMediaBackendFs2 *self) if (state == PURPLE_MEDIA_STATE_END) { PurpleMediaBackendFs2Private *priv = purple_media_backend_fs2_get_instance_private( PurpleMediaBackendFs2Stream *stream = get_stream(self, sid, name); gst_object_unref(stream->stream); priv->streams = g_list_remove(priv->streams, stream); remove_element(stream->src); remove_element(stream->tee); remove_element(stream->volume); remove_element(stream->level); remove_element(stream->fakesink); remove_element(stream->queue); } else if (sid && !name) { PurpleMediaBackendFs2Session *session = get_session(self, sid); g_object_get(session->session, "sink-pad", &pad, NULL); gst_pad_unlink(GST_PAD_PEER(pad), pad); gst_object_unref(session->session); g_hash_table_remove(priv->sessions, session->id); pad = gst_pad_get_peer(session->srcpad); gst_element_remove_pad(GST_ELEMENT_PARENT(pad), pad); gst_object_unref(session->srcpad); remove_element(session->srcvalve); remove_element(session->tee); purple_media_manager_remove_output_windows( purple_media_get_manager(media), media, sid, name); stream_info_cb(PurpleMedia *media, PurpleMediaInfoType type, gchar *sid, gchar *name, gboolean local, PurpleMediaBackendFs2 *self) if (type == PURPLE_MEDIA_INFO_ACCEPT && sid != NULL && name != NULL) { PurpleMediaBackendFs2Stream *stream = get_stream(self, sid, name); g_object_set(G_OBJECT(stream->stream), "direction", session_type_to_fs_stream_direction( stream->session->type), NULL); if (stream->remote_candidates == NULL || purple_media_is_initiator(media, sid, name)) if (stream->supports_add) fs_stream_add_remote_candidates(stream->stream, stream->remote_candidates, &err); fs_stream_force_remote_candidates(stream->stream, stream->remote_candidates, &err); purple_debug_error("backend-fs2", "Error adding " "remote candidates: %s\n", } else if (local == TRUE && (type == PURPLE_MEDIA_INFO_MUTE || type == PURPLE_MEDIA_INFO_UNMUTE)) { PurpleMediaBackendFs2Private *priv = purple_media_backend_fs2_get_instance_private( gboolean active = (type == PURPLE_MEDIA_INFO_MUTE); sessions = g_hash_table_get_values(priv->sessions); sessions = g_list_prepend(NULL, purple_debug_info("media", "Turning mute %s\n", for (; sessions; sessions = g_list_delete_link( PurpleMediaBackendFs2Session *session = if (session->type & PURPLE_MEDIA_SEND_AUDIO) { gchar *name = g_strdup_printf("volume_%s", GstElement *volume = gst_bin_get_by_name( GST_BIN(priv->confbin), name); g_object_set(volume, "mute", active, NULL); } else if (local == TRUE && (type == PURPLE_MEDIA_INFO_HOLD || type == PURPLE_MEDIA_INFO_UNHOLD)) { gboolean active = (type == PURPLE_MEDIA_INFO_HOLD); GList *streams = get_streams(self, sid, name); for (; streams; streams = g_list_delete_link(streams, streams)) { PurpleMediaBackendFs2Stream *stream = streams->data; if (stream->session->type & PURPLE_MEDIA_SEND_AUDIO) { g_object_set(stream->stream, "direction", session_type_to_fs_stream_direction( stream->session->type & ((active) ? ~PURPLE_MEDIA_SEND_AUDIO : PURPLE_MEDIA_AUDIO)), NULL); } else if (local == TRUE && (type == PURPLE_MEDIA_INFO_PAUSE || type == PURPLE_MEDIA_INFO_UNPAUSE)) { gboolean active = (type == PURPLE_MEDIA_INFO_PAUSE); GList *streams = get_streams(self, sid, name); for (; streams; streams = g_list_delete_link(streams, streams)) { PurpleMediaBackendFs2Stream *stream = streams->data; if (stream->session->type & PURPLE_MEDIA_SEND_VIDEO) { g_object_set(stream->stream, "direction", session_type_to_fs_stream_direction( stream->session->type & ((active) ? ~PURPLE_MEDIA_SEND_VIDEO : PURPLE_MEDIA_VIDEO)), NULL); init_conference(PurpleMediaBackendFs2 *self) PurpleMediaBackendFs2Private *priv = purple_media_backend_fs2_get_instance_private(self); priv->conference = FS_CONFERENCE( gst_element_factory_make(priv->conference_type, NULL)); if (priv->conference == NULL) { purple_debug_error("backend-fs2", "Conference == NULL\n"); if (purple_account_get_silence_suppression( purple_media_get_account(priv->media))) priv->silence_threshold = purple_prefs_get_int( "/purple/media/audio/silence_threshold") / 100.0; priv->silence_threshold = 0; pipeline = purple_media_manager_get_pipeline( purple_media_get_manager(priv->media)); purple_debug_error("backend-fs2", "Couldn't retrieve pipeline.\n"); name = g_strdup_printf("conf_%p", priv->conference); priv->confbin = gst_bin_new(name); if (priv->confbin == NULL) { purple_debug_error("backend-fs2", "Couldn't create confbin.\n"); bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline)); purple_debug_error("backend-fs2", "Couldn't get the pipeline's bus.\n"); default_props = fs_utils_get_default_element_properties(GST_ELEMENT(priv->conference)); if (default_props != NULL) { priv->notifier = fs_element_added_notifier_new(); fs_element_added_notifier_add(priv->notifier, fs_element_added_notifier_set_properties_from_keyfile(priv->notifier, default_props); g_signal_connect(G_OBJECT(bus), "message", G_CALLBACK(gst_bus_cb), self); if (!gst_bin_add(GST_BIN(pipeline), GST_ELEMENT(priv->confbin))) { purple_debug_error("backend-fs2", "Couldn't add confbin " "element to the pipeline\n"); if (!gst_bin_add(GST_BIN(priv->confbin), GST_ELEMENT(priv->conference))) { purple_debug_error("backend-fs2", "Couldn't add conference " "element to the confbin\n"); if (gst_element_set_state(GST_ELEMENT(priv->confbin), GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) { purple_debug_error("backend-fs2", "Failed to start conference.\n"); create_src(PurpleMediaBackendFs2 *self, const gchar *sess_id, PurpleMediaSessionType type) PurpleMediaBackendFs2Private *priv = purple_media_backend_fs2_get_instance_private(self); PurpleMediaBackendFs2Session *session; PurpleMediaSessionType session_type; FsMediaType media_type = session_type_to_fs_media_type(type); FsStreamDirection type_direction = session_type_to_fs_stream_direction(type); GstPad *sinkpad, *srcpad; if ((type_direction & FS_DIRECTION_SEND) == 0) session_type = session_type_from_fs( media_type, FS_DIRECTION_SEND); src = purple_media_manager_get_element( purple_media_get_manager(priv->media), session_type, priv->media, sess_id, NULL); if (!GST_IS_ELEMENT(src)) { purple_debug_error("backend-fs2", "Error creating src for session %s\n", session = get_session(self, sess_id); purple_debug_warning("backend-fs2", "purple_media_set_src: trying to set" " src on non-existent session\n"); gst_object_unref(session->src); gst_element_set_locked_state(session->src, TRUE); session->tee = gst_element_factory_make("tee", NULL); gst_bin_add(GST_BIN(priv->confbin), session->tee); /* This supposedly isn't necessary, but it silences some warnings */ if (GST_ELEMENT_PARENT(priv->confbin) == GST_ELEMENT_PARENT(session->src)) { GstPad *pad = gst_element_get_static_pad(session->tee, "sink"); ghost = gst_ghost_pad_new(NULL, pad); gst_pad_set_active(ghost, TRUE); gst_element_add_pad(priv->confbin, ghost); gst_element_set_state(session->tee, GST_STATE_PLAYING); gst_element_link(session->src, priv->confbin); session->srcpad = gst_pad_get_peer(ghost); g_object_get(session->session, "sink-pad", &sinkpad, NULL); if (session->type & PURPLE_MEDIA_SEND_AUDIO) { gchar *name = g_strdup_printf("volume_%s", session->id); GstElement *volume = gst_element_factory_make("volume", name); double input_volume = purple_prefs_get_int( "/purple/media/audio/volume/input")/10.0; name = g_strdup_printf("sendlevel_%s", session->id); level = gst_element_factory_make("level", name); session->srcvalve = gst_element_factory_make("valve", NULL); gst_bin_add(GST_BIN(priv->confbin), volume); gst_bin_add(GST_BIN(priv->confbin), level); gst_bin_add(GST_BIN(priv->confbin), session->srcvalve); gst_element_set_state(level, GST_STATE_PLAYING); gst_element_set_state(volume, GST_STATE_PLAYING); gst_element_set_state(session->srcvalve, GST_STATE_PLAYING); gst_element_link(level, session->srcvalve); gst_element_link(volume, level); gst_element_link(session->tee, volume); srcpad = gst_element_get_static_pad(session->srcvalve, "src"); g_object_set(volume, "volume", input_volume, NULL); srcpad = gst_element_get_request_pad(session->tee, "src_%u"); purple_debug_info("backend-fs2", "connecting pad: %s\n", gst_pad_link(srcpad, sinkpad) == GST_PAD_LINK_OK ? "success" : "failure"); gst_element_set_locked_state(session->src, FALSE); gst_object_unref(session->src); gst_object_unref(sinkpad); purple_media_manager_create_output_window(purple_media_get_manager( priv->media), priv->media, sess_id, NULL); purple_debug_info("backend-fs2", "create_src: setting source " "state to GST_STATE_PLAYING - it may hang here on win32\n"); gst_element_set_state(session->src, GST_STATE_PLAYING); purple_debug_info("backend-fs2", "create_src: state set\n"); create_session(PurpleMediaBackendFs2 *self, const gchar *sess_id, PurpleMediaSessionType type, gboolean initiator, const gchar *transmitter) PurpleMediaBackendFs2Private *priv = purple_media_backend_fs2_get_instance_private(self); PurpleMediaBackendFs2Session *session; GList *codec_conf = NULL; session = g_new0(PurpleMediaBackendFs2Session, 1); session->session = fs_conference_new_session(priv->conference, session_type_to_fs_media_type(type), &err); #ifdef HAVE_MEDIA_APPLICATION if (type == PURPLE_MEDIA_APPLICATION) { GObject *rtpsession = NULL; caps = gst_caps_new_empty_simple ("application/octet-stream"); fs_session_set_allowed_caps (session->session, caps, caps, NULL); g_object_get (session->session, "internal-session", &rtpsession, NULL); g_object_set (rtpsession, "probation", 0, NULL); g_object_unref (rtpsession); purple_media_error(priv->media, _("Error creating session: %s"), filename = g_build_filename(purple_config_dir(), "fs-codec.conf", NULL); codec_conf = fs_codec_list_from_keyfile(filename, &err); if (err->code == G_KEY_FILE_ERROR_NOT_FOUND) purple_debug_info("backend-fs2", "Couldn't read " purple_debug_error("backend-fs2", "Error reading " purple_debug_info("backend-fs2", "Loading default codec conf instead\n"); codec_conf = fs_utils_get_default_codec_preferences( GST_ELEMENT(priv->conference)); fs_session_set_codec_preferences(session->session, codec_conf, NULL); fs_codec_list_destroy(codec_conf); * Removes a 5-7 second delay before * receiving the src-pad-added signal. * Only works for non-multicast FsRtpSessions. if (!purple_strequal(transmitter, "multicast")) g_object_set(G_OBJECT(session->session), "no-rtcp-timeout", 0, NULL); session->id = g_strdup(sess_id); purple_debug_info("backend-fs2", "Creating hash table for sessions\n"); priv->sessions = g_hash_table_new_full(g_str_hash, g_str_equal, g_hash_table_insert(priv->sessions, g_strdup(session->id), session); if (!create_src(self, sess_id, type)) { purple_debug_info("backend-fs2", "Error creating the src\n"); free_session(PurpleMediaBackendFs2Session *session) create_participant(PurpleMediaBackendFs2 *self, const gchar *name) PurpleMediaBackendFs2Private *priv = purple_media_backend_fs2_get_instance_private(self); FsParticipant *participant; participant = fs_conference_new_participant( purple_debug_error("backend-fs2", "Error creating participant: %s\n", g_object_set_data_full(G_OBJECT(participant), "purple-name", if (g_object_class_find_property(G_OBJECT_GET_CLASS(participant), g_object_set(participant, "cname", name, NULL); if (!priv->participants) { purple_debug_info("backend-fs2", "Creating hash table for participants\n"); priv->participants = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_object_unref); g_hash_table_insert(priv->participants, g_strdup(name), participant); src_pad_added_cb_cb(PurpleMediaBackendFs2Stream *stream) PurpleMediaBackendFs2Private *priv; g_return_val_if_fail(stream != NULL, FALSE); priv = purple_media_backend_fs2_get_instance_private( stream->session->backend); stream->connected_cb_id = 0; purple_media_manager_create_output_window( purple_media_get_manager(priv->media), priv->media, stream->session->id, stream->participant); g_signal_emit_by_name(priv->media, "state-changed", PURPLE_MEDIA_STATE_CONNECTED, stream->session->id, stream->participant); src_pad_added_cb(FsStream *fsstream, GstPad *srcpad, FsCodec *codec, PurpleMediaBackendFs2Stream *stream) PurpleMediaBackendFs2Private *priv; g_return_if_fail(FS_IS_STREAM(fsstream)); g_return_if_fail(stream != NULL); priv = purple_media_backend_fs2_get_instance_private( stream->session->backend); if (stream->src == NULL) { if (codec->media_type == FS_MEDIA_TYPE_AUDIO) { double output_volume = purple_prefs_get_int( "/purple/media/audio/volume/output")/10.0; * Should this instead be: * audioconvert ! audioresample ! liveadder ! * audioresample ! audioconvert ! realsink stream->queue = gst_element_factory_make("queue", NULL); stream->volume = gst_element_factory_make("volume", NULL); g_object_set(stream->volume, "volume", output_volume, NULL); stream->level = gst_element_factory_make("level", NULL); stream->src = gst_element_factory_make("liveadder", NULL); sink = purple_media_manager_get_element( purple_media_get_manager(priv->media), PURPLE_MEDIA_RECV_AUDIO, priv->media, gst_bin_add(GST_BIN(priv->confbin), stream->queue); gst_bin_add(GST_BIN(priv->confbin), stream->volume); gst_bin_add(GST_BIN(priv->confbin), stream->level); gst_bin_add(GST_BIN(priv->confbin), sink); gst_element_set_state(sink, GST_STATE_PLAYING); gst_element_set_state(stream->level, GST_STATE_PLAYING); gst_element_set_state(stream->volume, GST_STATE_PLAYING); gst_element_set_state(stream->queue, GST_STATE_PLAYING); gst_element_link(stream->level, sink); gst_element_link(stream->volume, stream->level); gst_element_link(stream->queue, stream->volume); } else if (codec->media_type == FS_MEDIA_TYPE_VIDEO) { stream->src = gst_element_factory_make("funnel", NULL); sink = gst_element_factory_make("fakesink", NULL); g_object_set(G_OBJECT(sink), "async", FALSE, NULL); gst_bin_add(GST_BIN(priv->confbin), sink); gst_element_set_state(sink, GST_STATE_PLAYING); #ifdef HAVE_MEDIA_APPLICATION } else if (codec->media_type == FS_MEDIA_TYPE_APPLICATION) { stream->src = gst_element_factory_make("funnel", NULL); sink = purple_media_manager_get_element( purple_media_get_manager(priv->media), PURPLE_MEDIA_RECV_APPLICATION, priv->media, gst_bin_add(GST_BIN(priv->confbin), sink); gst_element_set_state(sink, GST_STATE_PLAYING); stream->tee = gst_element_factory_make("tee", NULL); gst_bin_add_many(GST_BIN(priv->confbin), stream->src, stream->tee, NULL); gst_element_set_state(stream->tee, GST_STATE_PLAYING); gst_element_set_state(stream->src, GST_STATE_PLAYING); gst_element_link_many(stream->src, stream->tee, sink, NULL); sinkpad = gst_element_get_request_pad(stream->src, "sink_%u"); gst_pad_link(srcpad, sinkpad); gst_object_unref(sinkpad); stream->connected_cb_id = g_timeout_add(0, (GSourceFunc)src_pad_added_cb_cb, stream); create_stream(PurpleMediaBackendFs2 *self, const gchar *sess_id, const gchar *who, PurpleMediaSessionType type, gboolean initiator, const gchar *transmitter, guint num_params, GParameter *params) PurpleMediaBackendFs2Private *priv = purple_media_backend_fs2_get_instance_private(self); FsStream *fsstream = NULL; const gchar *stun_ip = purple_network_get_stun_ip(); const gchar *turn_ip = purple_network_get_turn_ip(); guint _num_params = num_params; FsStreamDirection type_direction = session_type_to_fs_stream_direction(type); PurpleMediaBackendFs2Session *session; PurpleMediaBackendFs2Stream *stream; FsParticipant *participant; /* check if the protocol has already specified a relay-info we need to do this to allow them to override when using non-standard TURN modes, like Google f.ex. */ gboolean got_turn_from_protocol = FALSE; GPtrArray *relay_info = g_ptr_array_new_full (1, (GDestroyNotify) gst_structure_free); session = get_session(self, sess_id); purple_debug_error("backend-fs2", "Couldn't find session to create stream.\n"); participant = get_participant(self, who); if (participant == NULL) { purple_debug_error("backend-fs2", "Couldn't find " "participant to create stream.\n"); fsstream = fs_session_new_stream(session->session, participant, initiator == TRUE ? type_direction : (type_direction & FS_DIRECTION_RECV), &err); purple_debug_error("backend-fs2", "Error creating stream: %s\n", err->message ? err->message : "NULL"); purple_debug_error("backend-fs2", "Error creating stream\n"); for (i = 0 ; i < num_params ; i++) { if (purple_strequal(params[i].name, "relay-info")) { got_turn_from_protocol = TRUE; _params = g_new0(GParameter, num_params + 3); memcpy(_params, params, sizeof(GParameter) * num_params); /* set the controlling mode parameter */ _params[_num_params].name = "controlling-mode"; g_value_init(&_params[_num_params].value, G_TYPE_BOOLEAN); g_value_set_boolean(&_params[_num_params].value, initiator); purple_debug_info("backend-fs2", "Setting stun-ip on new stream: %s\n", stun_ip); _params[_num_params].name = "stun-ip"; g_value_init(&_params[_num_params].value, G_TYPE_STRING); g_value_set_string(&_params[_num_params].value, stun_ip); if (turn_ip && purple_strequal("nice", transmitter) && !got_turn_from_protocol) { const gchar *username = purple_prefs_get_string( "/purple/network/turn_username"); const gchar *password = purple_prefs_get_string( "/purple/network/turn_password"); port = purple_prefs_get_int("/purple/network/turn_port"); g_ptr_array_add (relay_info, gst_structure_new ("relay-info", "ip", G_TYPE_STRING, turn_ip, "port", G_TYPE_UINT, port, "username", G_TYPE_STRING, username, "password", G_TYPE_STRING, password, "relay-type", G_TYPE_STRING, "udp", port = purple_prefs_get_int("/purple/network/turn_port_tcp"); g_ptr_array_add (relay_info, gst_structure_new ("relay-info", "ip", G_TYPE_STRING, turn_ip, "port", G_TYPE_UINT, port, "username", G_TYPE_STRING, username, "password", G_TYPE_STRING, password, "relay-type", G_TYPE_STRING, "tcp", /* TURN over SSL is only supported by libnice for Google's "psuedo" SSL mode purple_debug_info("backend-fs2", "Setting relay-info on new stream\n"); _params[_num_params].name = "relay-info"; g_value_init(&_params[_num_params].value, G_TYPE_PTR_ARRAY); g_value_set_boxed(&_params[_num_params].value, relay_info); ret = fs_stream_set_transmitter(fsstream, transmitter, _params, _num_params, &err); for (i = 0 ; i < _num_params ; i++) g_value_unset (&_params[i].value); g_ptr_array_unref (relay_info); purple_debug_error("backend-fs2", "Could not set transmitter %s: %s.\n", transmitter, err ? err->message : NULL); stream = g_new0(PurpleMediaBackendFs2Stream, 1); stream->participant = g_strdup(who); stream->session = session; stream->stream = fsstream; stream->supports_add = purple_strequal(transmitter, "nice"); priv->streams = g_list_append(priv->streams, stream); g_signal_connect(G_OBJECT(fsstream), "src-pad-added", G_CALLBACK(src_pad_added_cb), stream); free_stream(PurpleMediaBackendFs2Stream *stream) /* Remove the connected_cb timeout */ if (stream->connected_cb_id != 0) g_source_remove(stream->connected_cb_id); g_free(stream->participant); if (stream->local_candidates) fs_candidate_list_destroy(stream->local_candidates); if (stream->remote_candidates) fs_candidate_list_destroy(stream->remote_candidates); purple_media_backend_fs2_add_stream(PurpleMediaBackend *self, const gchar *sess_id, const gchar *who, PurpleMediaSessionType type, gboolean initiator, const gchar *transmitter, guint num_params, GParameter *params) PurpleMediaBackendFs2 *backend = PURPLE_MEDIA_BACKEND_FS2(self); PurpleMediaBackendFs2Private *priv = purple_media_backend_fs2_get_instance_private(backend); PurpleMediaBackendFs2Stream *stream; if (priv->conference == NULL && !init_conference(backend)) { purple_debug_error("backend-fs2", "Error initializing the conference.\n"); if (get_session(backend, sess_id) == NULL && !create_session(backend, sess_id, type, initiator, transmitter)) { purple_debug_error("backend-fs2", "Error creating the session.\n"); if (get_participant(backend, who) == NULL && !create_participant(backend, who)) { purple_debug_error("backend-fs2", "Error creating the participant.\n"); stream = get_stream(backend, sess_id, who); FsStreamDirection type_direction = session_type_to_fs_stream_direction(type); if (session_type_to_fs_stream_direction( stream->session->type) != type_direction) { g_object_set(stream->stream, "direction", } else if (!create_stream(backend, sess_id, who, type, initiator, transmitter, num_params, params)) { purple_debug_error("backend-fs2", "Error creating the stream.\n"); purple_media_backend_fs2_add_remote_candidates(PurpleMediaBackend *self, const gchar *sess_id, const gchar *participant, GList *remote_candidates) PurpleMediaBackendFs2Private *priv; PurpleMediaBackendFs2Stream *stream; g_return_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self)); priv = purple_media_backend_fs2_get_instance_private( PURPLE_MEDIA_BACKEND_FS2(self)); stream = get_stream(PURPLE_MEDIA_BACKEND_FS2(self), purple_debug_error("backend-fs2", "purple_media_add_remote_candidates: " "couldn't find stream %s %s.\n", sess_id ? sess_id : "(null)", participant ? participant : "(null)"); stream->remote_candidates = g_list_concat(stream->remote_candidates, candidate_list_to_fs(remote_candidates)); if (purple_media_is_initiator(priv->media, sess_id, participant) || priv->media, sess_id, participant)) { if (stream->supports_add) fs_stream_add_remote_candidates(stream->stream, stream->remote_candidates, &err); fs_stream_force_remote_candidates(stream->stream, stream->remote_candidates, &err); purple_debug_error("backend-fs2", "Error adding remote" " candidates: %s\n", err->message); purple_media_backend_fs2_codecs_ready(PurpleMediaBackend *self, PurpleMediaBackendFs2Private *priv; g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE); priv = purple_media_backend_fs2_get_instance_private( PURPLE_MEDIA_BACKEND_FS2(self)); PurpleMediaBackendFs2Session *session = get_session( PURPLE_MEDIA_BACKEND_FS2(self), sess_id); if (session->type & (PURPLE_MEDIA_SEND_AUDIO | #ifdef HAVE_MEDIA_APPLICATION PURPLE_MEDIA_SEND_APPLICATION | PURPLE_MEDIA_SEND_VIDEO)) { g_object_get(session->session, "codecs", &codecs, NULL); fs_codec_list_destroy (codecs); GList *values = g_hash_table_get_values(priv->sessions); for (; values; values = g_list_delete_link(values, values)) { PurpleMediaBackendFs2Session *session = values->data; if (session->type & (PURPLE_MEDIA_SEND_AUDIO | #ifdef HAVE_MEDIA_APPLICATION PURPLE_MEDIA_SEND_APPLICATION | PURPLE_MEDIA_SEND_VIDEO)) { g_object_get(session->session, "codecs", &codecs, NULL); fs_codec_list_destroy (codecs); purple_media_backend_fs2_get_codecs(PurpleMediaBackend *self, PurpleMediaBackendFs2Session *session; g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL); session = get_session(PURPLE_MEDIA_BACKEND_FS2(self), sess_id); g_object_get(G_OBJECT(session->session), "codecs", &fscodecs, NULL); codecs = codec_list_from_fs(fscodecs); fs_codec_list_destroy(fscodecs); purple_media_backend_fs2_get_local_candidates(PurpleMediaBackend *self, const gchar *sess_id, const gchar *participant) PurpleMediaBackendFs2Stream *stream; GList *candidates = NULL; g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), NULL); stream = get_stream(PURPLE_MEDIA_BACKEND_FS2(self), candidates = candidate_list_from_fs( stream->local_candidates); purple_media_backend_fs2_set_remote_codecs(PurpleMediaBackend *self, const gchar *sess_id, const gchar *participant, PurpleMediaBackendFs2Stream *stream; g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE); stream = get_stream(PURPLE_MEDIA_BACKEND_FS2(self), fscodecs = codec_list_to_fs(codecs); fs_stream_set_remote_codecs(stream->stream, fscodecs, &err); fs_codec_list_destroy(fscodecs); purple_debug_error("backend-fs2", "Error setting remote codecs: %s\n", create_fs2_srtp_structure(const gchar *cipher, const gchar *auth, const gchar *key, gsize key_len) buffer = gst_buffer_new_allocate(NULL, key_len, NULL); gst_buffer_map(buffer, &info, GST_MAP_WRITE); memcpy(info.data, key, key_len); gst_buffer_unmap(buffer, &info); result = gst_structure_new("FarstreamSRTP", "cipher", G_TYPE_STRING, cipher, "auth", G_TYPE_STRING, auth, "key", GST_TYPE_BUFFER, buffer, gst_buffer_unref(buffer); purple_media_backend_fs2_set_encryption_parameters (PurpleMediaBackend *self, const gchar *sess_id, const gchar *cipher, const gchar *auth, const gchar *key, gsize key_len) PurpleMediaBackendFs2Session *session; g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE); session = get_session(PURPLE_MEDIA_BACKEND_FS2(self), sess_id); srtp = create_fs2_srtp_structure(cipher, auth, key, key_len); result = fs_session_set_encryption_parameters(session->session, srtp, purple_debug_error("backend-fs2", "Error setting encryption parameters: %s\n", err->message); gst_structure_free(srtp); purple_media_backend_fs2_set_decryption_parameters (PurpleMediaBackend *self, const gchar *sess_id, const gchar *participant, const gchar *cipher, const gchar *auth, const gchar *key, gsize key_len) PurpleMediaBackendFs2Stream *stream; g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE); stream = get_stream(PURPLE_MEDIA_BACKEND_FS2(self), sess_id, srtp = create_fs2_srtp_structure(cipher, auth, key, key_len); result = fs_stream_set_decryption_parameters(stream->stream, srtp, purple_debug_error("backend-fs2", "Error setting decryption parameters: %s\n", err->message); gst_structure_free(srtp); purple_media_backend_fs2_set_send_codec(PurpleMediaBackend *self, const gchar *sess_id, PurpleMediaCodec *codec) PurpleMediaBackendFs2Session *session; g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE); session = get_session(PURPLE_MEDIA_BACKEND_FS2(self), sess_id); fscodec = codec_to_fs(codec); fs_session_set_send_codec(session->session, fscodec, &err); fs_codec_destroy(fscodec); purple_debug_error("media", "Error setting send codec\n"); purple_media_backend_fs2_get_available_params(void) static const gchar *supported_params[] = { "sdes-cname", "sdes-email", "sdes-location", "sdes-name", "sdes-note", "sdes-phone", "sdes-tool", NULL param_to_sdes_type(const gchar *param) const gchar **supported = purple_media_backend_fs2_get_available_params(); static const gchar *sdes_types[] = { "cname", "email", "location", "name", "note", "phone", "tool", NULL for (i = 0; supported[i] != NULL; ++i) { if (purple_strequal(param, supported[i])) { purple_media_backend_fs2_set_params(PurpleMediaBackend *self, guint num_params, GParameter *params) PurpleMediaBackendFs2Private *priv; g_return_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self)); priv = purple_media_backend_fs2_get_instance_private( PURPLE_MEDIA_BACKEND_FS2(self)); if (priv->conference == NULL && !init_conference(PURPLE_MEDIA_BACKEND_FS2(self))) { purple_debug_error("backend-fs2", "Error initializing the conference.\n"); g_object_get(G_OBJECT(priv->conference), "sdes", &sdes, NULL); for (i = 0; i != num_params; ++i) { const gchar *sdes_type = param_to_sdes_type(params[i].name); gst_structure_set(sdes, sdes_type, G_TYPE_STRING, g_value_get_string(¶ms[i].value), g_object_set(G_OBJECT(priv->conference), "sdes", sdes, NULL); gst_structure_free(sdes); send_dtmf_callback(gpointer userdata) FsSession *session = userdata; fs_session_stop_telephony_event(session); purple_media_backend_fs2_send_dtmf(PurpleMediaBackend *self, const gchar *sess_id, gchar dtmf, guint8 volume, PurpleMediaBackendFs2Session *session; g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE); session = get_session(PURPLE_MEDIA_BACKEND_FS2(self), sess_id); /* Convert DTMF char into FsDTMFEvent enum */ case '0': event = FS_DTMF_EVENT_0; break; case '1': event = FS_DTMF_EVENT_1; break; case '2': event = FS_DTMF_EVENT_2; break; case '3': event = FS_DTMF_EVENT_3; break; case '4': event = FS_DTMF_EVENT_4; break; case '5': event = FS_DTMF_EVENT_5; break; case '6': event = FS_DTMF_EVENT_6; break; case '7': event = FS_DTMF_EVENT_7; break; case '8': event = FS_DTMF_EVENT_8; break; case '9': event = FS_DTMF_EVENT_9; break; case '*': event = FS_DTMF_EVENT_STAR; break; case '#': event = FS_DTMF_EVENT_POUND; break; case 'A': event = FS_DTMF_EVENT_A; break; case 'B': event = FS_DTMF_EVENT_B; break; case 'C': event = FS_DTMF_EVENT_C; break; case 'D': event = FS_DTMF_EVENT_D; break; if (!fs_session_start_telephony_event(session->session, fs_session_stop_telephony_event(session->session); g_timeout_add(duration, send_dtmf_callback, purple_media_backend_fs2_get_type(void) purple_media_backend_fs2_get_src(PurpleMediaBackendFs2 *self, PurpleMediaBackendFs2Session *session = get_session(self, sess_id); return session != NULL ? session->src : NULL; purple_media_backend_fs2_get_tee(PurpleMediaBackendFs2 *self, const gchar *sess_id, const gchar *who) if (sess_id != NULL && who == NULL) { PurpleMediaBackendFs2Session *session = get_session(self, sess_id); return (session != NULL) ? session->tee : NULL; } else if (sess_id != NULL && who != NULL) { PurpleMediaBackendFs2Stream *stream = get_stream(self, sess_id, who); return (stream != NULL) ? stream->tee : NULL; g_return_val_if_reached(NULL); purple_media_backend_fs2_set_input_volume(PurpleMediaBackendFs2 *self, const gchar *sess_id, double level) PurpleMediaBackendFs2Private *priv; g_return_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self)); priv = purple_media_backend_fs2_get_instance_private(self); purple_prefs_set_int("/purple/media/audio/volume/input", level); sessions = g_hash_table_get_values(priv->sessions); sessions = g_list_append(NULL, get_session(self, sess_id)); for (; sessions; sessions = g_list_delete_link(sessions, sessions)) { PurpleMediaBackendFs2Session *session = sessions->data; if (session->type & PURPLE_MEDIA_SEND_AUDIO) { gchar *name = g_strdup_printf("volume_%s", GstElement *volume = gst_bin_get_by_name( GST_BIN(priv->confbin), name); g_object_set(volume, "volume", level/10.0, NULL); purple_media_backend_fs2_set_output_volume(PurpleMediaBackendFs2 *self, const gchar *sess_id, const gchar *who, double level) g_return_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self)); purple_prefs_set_int("/purple/media/audio/volume/output", level); streams = get_streams(self, sess_id, who); for (; streams; streams = g_list_delete_link(streams, streams)) { PurpleMediaBackendFs2Stream *stream = streams->data; if (stream->session->type & PURPLE_MEDIA_RECV_AUDIO && GST_IS_ELEMENT(stream->volume)) { g_object_set(stream->volume, "volume", purple_media_backend_fs2_set_send_rtcp_mux(PurpleMediaBackend *self, const gchar *sess_id, const gchar *participant, PurpleMediaBackendFs2Stream *stream; g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE); stream = get_stream(PURPLE_MEDIA_BACKEND_FS2(self), g_object_class_find_property (G_OBJECT_GET_CLASS (stream->stream), "send-rtcp-mux") != NULL) { g_object_set (stream->stream, "send-rtcp-mux", send_rtcp_mux, NULL);