* 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 "mediamanager.h" #include <media/backend-fs2.h> #include <farstream/fs-element-added-notifier.h> #include <gst/video/videooverlay.h> #ifdef HAVE_MEDIA_APPLICATION /** @copydoc _PurpleMediaOutputWindow */ typedef struct _PurpleMediaOutputWindow PurpleMediaOutputWindow; /** @copydoc _PurpleMediaManagerPrivate */ typedef struct _PurpleMediaElementInfoPrivate PurpleMediaElementInfoPrivate; struct _PurpleMediaOutputWindow struct _PurpleMediaManagerPrivate gulong next_output_window_id; PurpleMediaElementInfo *video_src; PurpleMediaElementInfo *video_sink; PurpleMediaElementInfo *audio_src; PurpleMediaElementInfo *audio_sink; #ifdef HAVE_MEDIA_APPLICATION /* Application data streams */ GList *appdata_info; /* holds PurpleMediaAppDataInfo */ guint appdata_cb_token; /* last used read/write callback token */ #ifdef HAVE_MEDIA_APPLICATION PurpleMediaAppDataCallbacks callbacks; GstSample *current_sample; } PurpleMediaAppDataInfo; #define PURPLE_MEDIA_MANAGER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), PURPLE_TYPE_MEDIA_MANAGER, PurpleMediaManagerPrivate)) #define PURPLE_MEDIA_ELEMENT_INFO_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), PURPLE_TYPE_MEDIA_ELEMENT_INFO, PurpleMediaElementInfoPrivate)) static void purple_media_manager_class_init (PurpleMediaManagerClass *klass); static void purple_media_manager_init (PurpleMediaManager *media); static void purple_media_manager_finalize (GObject *object); #ifdef HAVE_MEDIA_APPLICATION static void free_appdata_info_locked (PurpleMediaAppDataInfo *info); static GObjectClass *parent_class = NULL; static guint purple_media_manager_signals[LAST_SIGNAL] = {0}; purple_media_manager_get_type() static const GTypeInfo info = { sizeof(PurpleMediaManagerClass), (GClassInitFunc) purple_media_manager_class_init, sizeof(PurpleMediaManager), (GInstanceInitFunc) purple_media_manager_init, type = g_type_register_static(G_TYPE_OBJECT, "PurpleMediaManager", &info, 0); purple_media_manager_class_init (PurpleMediaManagerClass *klass) GObjectClass *gobject_class = (GObjectClass*)klass; parent_class = g_type_class_peek_parent(klass); gobject_class->finalize = purple_media_manager_finalize; purple_media_manager_signals[INIT_MEDIA] = g_signal_new ("init-media", G_TYPE_FROM_CLASS (klass), purple_smarshal_BOOLEAN__OBJECT_POINTER_STRING, G_TYPE_BOOLEAN, 3, PURPLE_TYPE_MEDIA, G_TYPE_POINTER, G_TYPE_STRING); purple_media_manager_signals[INIT_PRIVATE_MEDIA] = g_signal_new ("init-private-media", G_TYPE_FROM_CLASS (klass), purple_smarshal_BOOLEAN__OBJECT_POINTER_STRING, G_TYPE_BOOLEAN, 3, PURPLE_TYPE_MEDIA, G_TYPE_POINTER, G_TYPE_STRING); purple_media_manager_signals[UI_CAPS_CHANGED] = g_signal_new ("ui-caps-changed", G_TYPE_FROM_CLASS (klass), purple_smarshal_VOID__FLAGS_FLAGS, G_TYPE_NONE, 2, PURPLE_MEDIA_TYPE_CAPS, g_type_class_add_private(klass, sizeof(PurpleMediaManagerPrivate)); purple_media_manager_init (PurpleMediaManager *media) media->priv = PURPLE_MEDIA_MANAGER_GET_PRIVATE(media); media->priv->medias = NULL; media->priv->private_medias = NULL; media->priv->next_output_window_id = 1; media->priv->backend_type = PURPLE_TYPE_MEDIA_BACKEND_FS2; #ifdef HAVE_MEDIA_APPLICATION media->priv->appdata_info = NULL; g_mutex_init (&media->priv->appdata_mutex); purple_prefs_add_none("/purple/media"); purple_prefs_add_none("/purple/media/audio"); purple_prefs_add_int("/purple/media/audio/silence_threshold", 5); purple_prefs_add_none("/purple/media/audio/volume"); purple_prefs_add_int("/purple/media/audio/volume/input", 10); purple_prefs_add_int("/purple/media/audio/volume/output", 10); purple_media_manager_finalize (GObject *media) PurpleMediaManagerPrivate *priv = PURPLE_MEDIA_MANAGER_GET_PRIVATE(media); for (; priv->medias; priv->medias = g_list_delete_link(priv->medias, priv->medias)) { g_object_unref(priv->medias->data); for (; priv->private_medias; priv->private_medias = g_list_delete_link(priv->private_medias, priv->private_medias)) { g_object_unref(priv->private_medias->data); for (; priv->elements; priv->elements = g_list_delete_link(priv->elements, priv->elements)) { g_object_unref(priv->elements->data); gst_caps_unref(priv->video_caps); #ifdef HAVE_MEDIA_APPLICATION g_list_free_full (priv->appdata_info, (GDestroyNotify) free_appdata_info_locked); g_mutex_clear (&priv->appdata_mutex); parent_class->finalize(media); purple_media_manager_get() static PurpleMediaManager *manager = NULL; manager = PURPLE_MEDIA_MANAGER(g_object_new(purple_media_manager_get_type(), NULL)); pipeline_bus_call(GstBus *bus, GstMessage *msg, PurpleMediaManager *manager) switch(GST_MESSAGE_TYPE(msg)) { purple_debug_info("mediamanager", "End of Stream\n"); case GST_MESSAGE_ERROR: { gst_message_parse_error(msg, &err, &debug); purple_debug_error("mediamanager", "gst pipeline error: %s\n", purple_debug_error("mediamanager", "Debug details: %s\n", debug); purple_media_manager_get_pipeline(PurpleMediaManager *manager) g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), NULL); if (manager->priv->pipeline == NULL) { FsElementAddedNotifier *notifier; manager->priv->pipeline = gst_pipeline_new(NULL); bus = gst_pipeline_get_bus( GST_PIPELINE(manager->priv->pipeline)); gst_bus_add_signal_watch(GST_BUS(bus)); g_signal_connect(G_OBJECT(bus), "message", G_CALLBACK(pipeline_bus_call), manager); gst_bus_set_sync_handler(bus, gst_bus_sync_signal_handler, NULL, NULL); filename = g_build_filename(purple_user_dir(), "fs-element.conf", NULL); keyfile = g_key_file_new(); if (!g_key_file_load_from_file(keyfile, filename, G_KEY_FILE_NONE, &err)) { purple_debug_info("mediamanager", purple_debug_error("mediamanager", /* Hack to make alsasrc stop messing up audio timestamps */ if (!g_key_file_has_key(keyfile, "alsasrc", "slave-method", NULL)) { g_key_file_set_integer(keyfile, "alsasrc", "slave-method", 2); notifier = fs_element_added_notifier_new(); fs_element_added_notifier_add(notifier, GST_BIN(manager->priv->pipeline)); fs_element_added_notifier_set_properties_from_keyfile( gst_element_set_state(manager->priv->pipeline, return manager->priv->pipeline; #endif /* USE_GSTREAMER */ create_media(PurpleMediaManager *manager, const char *conference_type, media = PURPLE_MEDIA(g_object_new(purple_media_get_type(), "conference-type", conference_type, purple_media_manager_signals[INIT_PRIVATE_MEDIA] : purple_media_manager_signals[INIT_MEDIA]; if (g_signal_has_handler_pending(manager, signal_id, 0, FALSE)) { g_signal_emit(manager, signal_id, 0, media, account, remote_user, if (signal_ret == FALSE) { manager->priv->private_medias = g_list_append( manager->priv->private_medias, media); manager->priv->medias = g_list_append(manager->priv->medias, media); get_media(PurpleMediaManager *manager, gboolean private) return manager->priv->private_medias; return manager->priv->medias; get_media_by_account(PurpleMediaManager *manager, PurpleAccount *account, gboolean private) g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), NULL); iter = manager->priv->private_medias; iter = manager->priv->medias; for (; iter; iter = g_list_next(iter)) { if (purple_media_get_account(iter->data) == account) { media = g_list_prepend(media, iter->data); purple_media_manager_remove_media(PurpleMediaManager *manager, PurpleMedia *media) g_return_if_fail(manager != NULL); if ((list = g_list_find(manager->priv->medias, media))) { medias = &manager->priv->medias; } else if ((list = g_list_find(manager->priv->private_medias, media))) { medias = &manager->priv->private_medias; *medias = g_list_delete_link(*medias, list); #ifdef HAVE_MEDIA_APPLICATION g_mutex_lock (&manager->priv->appdata_mutex); for (list = manager->priv->appdata_info; list; list = list->next) { PurpleMediaAppDataInfo *info = list->data; if (info->media == media) { manager->priv->appdata_info = g_list_delete_link ( manager->priv->appdata_info, list); free_appdata_info_locked (info); g_mutex_unlock (&manager->priv->appdata_mutex); purple_media_manager_create_media(PurpleMediaManager *manager, const char *conference_type, return create_media (manager, account, conference_type, remote_user, initiator, FALSE); purple_media_manager_get_media(PurpleMediaManager *manager) return get_media (manager, FALSE); purple_media_manager_get_media_by_account(PurpleMediaManager *manager, return get_media_by_account (manager, account, FALSE); purple_media_manager_create_private_media(PurpleMediaManager *manager, const char *conference_type, return create_media (manager, account, conference_type, remote_user, initiator, TRUE); purple_media_manager_get_private_media(PurpleMediaManager *manager) return get_media (manager, TRUE); purple_media_manager_get_private_media_by_account(PurpleMediaManager *manager, return get_media_by_account (manager, account, TRUE); #ifdef HAVE_MEDIA_APPLICATION free_appdata_info_locked (PurpleMediaAppDataInfo *info) info->notify (info->user_data); /* Make sure no other thread is using the structure */ g_free (info->session_id); g_free (info->participant); /* This lets the potential read or write callbacks waiting for appdata_mutex * know the info structure has been destroyed. */ info->readable_cb_token = 0; info->writable_cb_token = 0; if (info->readable_timer_id) { purple_timeout_remove (info->readable_timer_id); info->readable_timer_id = 0; if (info->writable_timer_id) { purple_timeout_remove (info->writable_timer_id); info->writable_timer_id = 0; if (info->current_sample) gst_sample_unref (info->current_sample); info->current_sample = NULL; /* Unblock any reading thread before destroying the GCond */ g_cond_broadcast (&info->readable_cond); g_cond_clear (&info->readable_cond); g_slice_free (PurpleMediaAppDataInfo, info); * Get an app data info struct associated with a session and lock the mutex * We don't want to return an info struct and unlock then it gets destroyed * so we need to return it with the lock still taken static PurpleMediaAppDataInfo * get_app_data_info_and_lock (PurpleMediaManager *manager, PurpleMedia *media, const gchar *session_id, const gchar *participant) g_mutex_lock (&manager->priv->appdata_mutex); for (i = manager->priv->appdata_info; i; i = i->next) { PurpleMediaAppDataInfo *info = i->data; if (info->media == media && g_strcmp0 (info->session_id, session_id) == 0 && g_strcmp0 (info->participant, participant) == 0)) { * Get an app data info struct associated with a session and lock the mutex * if it doesn't exist, we create it. static PurpleMediaAppDataInfo * ensure_app_data_info_and_lock (PurpleMediaManager *manager, PurpleMedia *media, const gchar *session_id, const gchar *participant) PurpleMediaAppDataInfo * info = get_app_data_info_and_lock (manager, media, session_id, participant); info = g_slice_new0 (PurpleMediaAppDataInfo); g_weak_ref_init (&info->media_ref, media); info->session_id = g_strdup (session_id); info->participant = g_strdup (participant); g_cond_init (&info->readable_cond); manager->priv->appdata_info = g_list_prepend ( manager->priv->appdata_info, info); request_pad_unlinked_cb(GstPad *pad, GstPad *peer, gpointer user_data) GstElement *parent = GST_ELEMENT_PARENT(pad); GValue tmp = G_VALUE_INIT; GstIteratorResult result; gst_element_release_request_pad(parent, pad); iter = gst_element_iterate_src_pads(parent); result = gst_iterator_next(iter, &tmp); if (result == GST_ITERATOR_DONE) { gst_element_set_locked_state(parent, TRUE); gst_element_set_state(parent, GST_STATE_NULL); gst_bin_remove(GST_BIN(GST_ELEMENT_PARENT(parent)), parent); } else if (result == GST_ITERATOR_OK) { remaining_pad = g_value_get_object(&tmp); gst_object_unref(remaining_pad); nonunique_src_unlinked_cb(GstPad *pad, GstPad *peer, gpointer user_data) GstElement *element = GST_ELEMENT_PARENT(pad); 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); purple_media_manager_set_video_caps(PurpleMediaManager *manager, GstCaps *caps) if (manager->priv->video_caps) gst_caps_unref(manager->priv->video_caps); manager->priv->video_caps = caps; if (manager->priv->pipeline && manager->priv->video_src) { gchar *id = purple_media_element_info_get_id(manager->priv->video_src); GstElement *src = gst_bin_get_by_name(GST_BIN(manager->priv->pipeline), id); GstElement *capsfilter = gst_bin_get_by_name(GST_BIN(src), "protocol_video_caps"); g_object_set(G_OBJECT(capsfilter), "caps", caps, NULL); gst_object_unref (capsfilter); purple_media_manager_get_video_caps(PurpleMediaManager *manager) if (manager->priv->video_caps == NULL) manager->priv->video_caps = gst_caps_from_string("video/x-raw," "width=[250,352], height=[200,288], framerate=[1/1,20/1]"); return manager->priv->video_caps; #ifdef HAVE_MEDIA_APPLICATION * Calls the appdata writable callback from the main thread. * This needs to grab the appdata lock and make sure it didn't get destroyed * before calling the callback. appsrc_writable (gpointer user_data) PurpleMediaManager *manager = purple_media_manager_get (); PurpleMediaAppDataInfo *info = user_data; void (*writable_cb) (PurpleMediaManager *manager, PurpleMedia *media, const gchar *session_id, const gchar *participant, gboolean writable, guint *cb_token_ptr = &info->writable_cb_token; guint cb_token = *cb_token_ptr; g_mutex_lock (&manager->priv->appdata_mutex); if (cb_token == 0 || cb_token != *cb_token_ptr) { /* In case info was freed while we were waiting for the mutex to unlock * we still have a pointer to the cb_token which should still be * accessible since it's in the Glib slice allocator. It gets set to 0 * just after the timeout is canceled which happens also before the * AppDataInfo is freed, so even if that memory slice gets reused, the * cb_token would be different from its previous value (unless * extremely unlucky). So checking if the value for the cb_token changed * should be enough to prevent any kind of race condition in which the * media/AppDataInfo gets destroyed in one thread while the timeout was * triggered and is waiting on the mutex to get unlocked in this thread g_mutex_unlock (&manager->priv->appdata_mutex); writable_cb = info->callbacks.writable; media = g_weak_ref_get (&info->media_ref); session_id = g_strdup (info->session_id); participant = g_strdup (info->participant); writable = info->writable && info->connected; cb_data = info->user_data; info->writable_cb_token = 0; g_mutex_unlock (&manager->priv->appdata_mutex); if (writable_cb && media) writable_cb (manager, media, session_id, participant, writable, * Schedule a writable callback to be called from the main thread. * We need to do this because need-data/enough-data signals from appsrc * will come from the streaming thread and we need to create * a source that we attach to the main context but we can't use * g_main_context_invoke since we need to be able to cancel the source if the * We use a timeout source instead of idle source, so the callback gets a higher call_appsrc_writable_locked (PurpleMediaAppDataInfo *info) PurpleMediaManager *manager = purple_media_manager_get (); /* We already have a writable callback scheduled, don't create another one */ if (info->writable_cb_token || info->callbacks.writable == NULL) /* We can't use writable_timer_id as a token, because the timeout is added * into libpurple's main event loop, which runs in a different thread than * from where call_appsrc_writable_locked() was called. Consequently, the * callback may run even before purple_timeout_add() returns the timer ID info->writable_cb_token = ++manager->priv->appdata_cb_token; info->writable_timer_id = purple_timeout_add (0, appsrc_writable, info); appsrc_need_data (GstAppSrc *appsrc, guint length, gpointer user_data) PurpleMediaAppDataInfo *info = user_data; PurpleMediaManager *manager = purple_media_manager_get (); g_mutex_lock (&manager->priv->appdata_mutex); /* Only signal writable if we also established a connection */ call_appsrc_writable_locked (info); g_mutex_unlock (&manager->priv->appdata_mutex); appsrc_enough_data (GstAppSrc *appsrc, gpointer user_data) PurpleMediaAppDataInfo *info = user_data; PurpleMediaManager *manager = purple_media_manager_get (); g_mutex_lock (&manager->priv->appdata_mutex); call_appsrc_writable_locked (info); g_mutex_unlock (&manager->priv->appdata_mutex); appsrc_seek_data (GstAppSrc *appsrc, guint64 offset, gpointer user_data) appsrc_destroyed (PurpleMediaAppDataInfo *info) PurpleMediaManager *manager = purple_media_manager_get (); g_mutex_lock (&manager->priv->appdata_mutex); call_appsrc_writable_locked (info); g_mutex_unlock (&manager->priv->appdata_mutex); media_established_cb (PurpleMedia *media,const gchar *session_id, const gchar *participant, PurpleMediaCandidate *local_candidate, PurpleMediaCandidate *remote_candidate, PurpleMediaAppDataInfo *info) PurpleMediaManager *manager = purple_media_manager_get (); g_mutex_lock (&manager->priv->appdata_mutex); /* We established the connection, if we were writable, then we need to call_appsrc_writable_locked (info); g_mutex_unlock (&manager->priv->appdata_mutex); create_send_appsrc(PurpleMedia *media, const gchar *session_id, const gchar *participant) PurpleMediaManager *manager = purple_media_manager_get (); PurpleMediaAppDataInfo * info = ensure_app_data_info_and_lock (manager, media, session_id, participant); GstElement *appsrc = (GstElement *)info->appsrc; GstAppSrcCallbacks callbacks = {appsrc_need_data, appsrc_enough_data, appsrc_seek_data, {NULL}}; GstCaps *caps = gst_caps_new_empty_simple ("application/octet-stream"); appsrc = gst_element_factory_make("appsrc", NULL); info->appsrc = (GstAppSrc *)appsrc; gst_app_src_set_caps (info->appsrc, caps); gst_app_src_set_callbacks (info->appsrc, &callbacks, info, (GDestroyNotify) appsrc_destroyed); g_signal_connect (media, "candidate-pair-established", (GCallback) media_established_cb, info); g_mutex_unlock (&manager->priv->appdata_mutex); appsink_eos (GstAppSink *appsink, gpointer user_data) appsink_new_preroll (GstAppSink *appsink, gpointer user_data) appsink_readable (gpointer user_data) PurpleMediaManager *manager = purple_media_manager_get (); PurpleMediaAppDataInfo *info = user_data; void (*readable_cb) (PurpleMediaManager *manager, PurpleMedia *media, const gchar *session_id, const gchar *participant, gpointer user_data); guint *cb_token_ptr = &info->readable_cb_token; guint cb_token = *cb_token_ptr; gboolean run_again = FALSE; g_mutex_lock (&manager->priv->appdata_mutex); if (cb_token == 0 || cb_token != *cb_token_ptr) { /* Avoided a race condition (see writable callback) */ g_mutex_unlock (&manager->priv->appdata_mutex); if (info->callbacks.readable && (info->num_samples > 0 || info->current_sample != NULL)) { readable_cb = info->callbacks.readable; media = g_weak_ref_get (&info->media_ref); session_id = g_strdup (info->session_id); participant = g_strdup (info->participant); cb_data = info->user_data; g_mutex_unlock (&manager->priv->appdata_mutex); readable_cb (manager, media, session_id, participant, cb_data); g_mutex_lock (&manager->priv->appdata_mutex); if (cb_token == 0 || cb_token != *cb_token_ptr) { g_mutex_unlock (&manager->priv->appdata_mutex); /* Do we still have samples? Schedule appsink_readable again. We break here * so that other events get a chance to be processed too. */ if (info->num_samples > 0 || info->current_sample != NULL) { info->readable_cb_token = 0; g_mutex_unlock (&manager->priv->appdata_mutex); call_appsink_readable_locked (PurpleMediaAppDataInfo *info) PurpleMediaManager *manager = purple_media_manager_get (); /* We must signal that a new sample has arrived to release blocking reads */ g_cond_broadcast (&info->readable_cond); /* We already have a writable callback scheduled, don't create another one */ if (info->readable_cb_token || info->callbacks.readable == NULL) info->readable_cb_token = ++manager->priv->appdata_cb_token; info->readable_timer_id = purple_timeout_add (0, appsink_readable, info); appsink_new_sample (GstAppSink *appsink, gpointer user_data) PurpleMediaManager *manager = purple_media_manager_get (); PurpleMediaAppDataInfo *info = user_data; g_mutex_lock (&manager->priv->appdata_mutex); call_appsink_readable_locked (info); g_mutex_unlock (&manager->priv->appdata_mutex); appsink_destroyed (PurpleMediaAppDataInfo *info) PurpleMediaManager *manager = purple_media_manager_get (); g_mutex_lock (&manager->priv->appdata_mutex); g_mutex_unlock (&manager->priv->appdata_mutex); create_recv_appsink(PurpleMedia *media, const gchar *session_id, const gchar *participant) PurpleMediaManager *manager = purple_media_manager_get (); PurpleMediaAppDataInfo * info = ensure_app_data_info_and_lock (manager, media, session_id, participant); GstElement *appsink = (GstElement *)info->appsink; GstAppSinkCallbacks callbacks = {appsink_eos, appsink_new_preroll, appsink_new_sample, {NULL}}; GstCaps *caps = gst_caps_new_empty_simple ("application/octet-stream"); appsink = gst_element_factory_make("appsink", NULL); info->appsink = (GstAppSink *)appsink; gst_app_sink_set_caps (info->appsink, caps); gst_app_sink_set_callbacks (info->appsink, &callbacks, info, (GDestroyNotify) appsink_destroyed); g_mutex_unlock (&manager->priv->appdata_mutex); static PurpleMediaElementInfo * get_send_application_element_info () static PurpleMediaElementInfo *info = NULL; #ifdef HAVE_MEDIA_APPLICATION info = g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO, "name", "Pidgin Application Source", "type", PURPLE_MEDIA_ELEMENT_APPLICATION | PURPLE_MEDIA_ELEMENT_SRC | PURPLE_MEDIA_ELEMENT_ONE_SRC, "create-cb", create_send_appsrc, NULL); static PurpleMediaElementInfo * get_recv_application_element_info () static PurpleMediaElementInfo *info = NULL; #ifdef HAVE_MEDIA_APPLICATION info = g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO, "name", "Pidgin Application Sink", "type", PURPLE_MEDIA_ELEMENT_APPLICATION | PURPLE_MEDIA_ELEMENT_SINK | PURPLE_MEDIA_ELEMENT_ONE_SINK, "create-cb", create_recv_appsink, NULL); purple_media_manager_get_element(PurpleMediaManager *manager, PurpleMediaSessionType type, PurpleMedia *media, const gchar *session_id, const gchar *participant) PurpleMediaElementInfo *info = NULL; PurpleMediaElementType element_type; if (type & PURPLE_MEDIA_SEND_AUDIO) info = manager->priv->audio_src; else if (type & PURPLE_MEDIA_RECV_AUDIO) info = manager->priv->audio_sink; else if (type & PURPLE_MEDIA_SEND_VIDEO) info = manager->priv->video_src; else if (type & PURPLE_MEDIA_RECV_VIDEO) info = manager->priv->video_sink; else if (type & PURPLE_MEDIA_SEND_APPLICATION) info = get_send_application_element_info (); else if (type & PURPLE_MEDIA_RECV_APPLICATION) info = get_recv_application_element_info (); element_type = purple_media_element_info_get_element_type(info); if (element_type & PURPLE_MEDIA_ELEMENT_UNIQUE && element_type & PURPLE_MEDIA_ELEMENT_SRC) { gchar *id = purple_media_element_info_get_id(info); ret = gst_bin_get_by_name(GST_BIN( purple_media_manager_get_pipeline( GstElement *bin, *fakesink; ret = purple_media_element_info_call_create(info, media, session_id, participant); tee = gst_element_factory_make("tee", "tee"); gst_bin_add_many(GST_BIN(bin), ret, tee, NULL); if (type & PURPLE_MEDIA_SEND_VIDEO) { videoscale = gst_element_factory_make("videoscale", NULL); capsfilter = gst_element_factory_make("capsfilter", "protocol_video_caps"); g_object_set(G_OBJECT(capsfilter), "caps", purple_media_manager_get_video_caps(manager), NULL); gst_bin_add_many(GST_BIN(bin), videoscale, capsfilter, NULL); gst_element_link_many(ret, videoscale, capsfilter, tee, NULL); gst_element_link(ret, tee); * This shouldn't be necessary, but it stops it from * giving a not-linked error upon destruction fakesink = gst_element_factory_make("fakesink", NULL); g_object_set(fakesink, "sync", FALSE, NULL); gst_bin_add(GST_BIN(bin), fakesink); gst_element_link(tee, fakesink); gst_bin_add(GST_BIN(purple_media_manager_get_pipeline( tee = gst_bin_get_by_name(GST_BIN(ret), "tee"); pad = gst_element_get_request_pad(tee, "src_%u"); ghost = gst_ghost_pad_new(NULL, pad); g_signal_connect(GST_PAD(ghost), "unlinked", G_CALLBACK(request_pad_unlinked_cb), NULL); gst_pad_set_active(ghost, TRUE); gst_element_add_pad(ret, ghost); ret = purple_media_element_info_call_create(info, media, session_id, participant); if (element_type & PURPLE_MEDIA_ELEMENT_SRC) { GstPad *pad = gst_element_get_static_pad(ret, "src"); g_signal_connect(pad, "unlinked", G_CALLBACK(nonunique_src_unlinked_cb), NULL); gst_bin_add(GST_BIN(purple_media_manager_get_pipeline(manager)), purple_debug_error("media", "Error creating source or sink\n"); purple_media_manager_get_element_info(PurpleMediaManager *manager, g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), NULL); iter = manager->priv->elements; for (; iter; iter = g_list_next(iter)) { purple_media_element_info_get_id(iter->data); if (!strcmp(element_id, id)) { g_object_ref(iter->data); purple_media_manager_register_element(PurpleMediaManager *manager, PurpleMediaElementInfo *info) PurpleMediaElementInfo *info2; g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), FALSE); g_return_val_if_fail(info != NULL, FALSE); id = purple_media_element_info_get_id(info); info2 = purple_media_manager_get_element_info(manager, id); manager->priv->elements = g_list_prepend(manager->priv->elements, info); purple_media_manager_unregister_element(PurpleMediaManager *manager, PurpleMediaElementInfo *info; g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), FALSE); info = purple_media_manager_get_element_info(manager, id); if (manager->priv->audio_src == info) manager->priv->audio_src = NULL; if (manager->priv->audio_sink == info) manager->priv->audio_sink = NULL; if (manager->priv->video_src == info) manager->priv->video_src = NULL; if (manager->priv->video_sink == info) manager->priv->video_sink = NULL; manager->priv->elements = g_list_remove( manager->priv->elements, info); purple_media_manager_set_active_element(PurpleMediaManager *manager, PurpleMediaElementInfo *info) PurpleMediaElementInfo *info2; PurpleMediaElementType type; g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), FALSE); g_return_val_if_fail(info != NULL, FALSE); id = purple_media_element_info_get_id(info); info2 = purple_media_manager_get_element_info(manager, id); purple_media_manager_register_element(manager, info); type = purple_media_element_info_get_element_type(info); if (type & PURPLE_MEDIA_ELEMENT_SRC) { if (type & PURPLE_MEDIA_ELEMENT_AUDIO) { manager->priv->audio_src = info; if (type & PURPLE_MEDIA_ELEMENT_VIDEO) { manager->priv->video_src = info; if (type & PURPLE_MEDIA_ELEMENT_SINK) { if (type & PURPLE_MEDIA_ELEMENT_AUDIO) { manager->priv->audio_sink = info; if (type & PURPLE_MEDIA_ELEMENT_VIDEO) { manager->priv->video_sink = info; purple_media_manager_get_active_element(PurpleMediaManager *manager, PurpleMediaElementType type) g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), NULL); if (type & PURPLE_MEDIA_ELEMENT_SRC) { if (type & PURPLE_MEDIA_ELEMENT_AUDIO) return manager->priv->audio_src; else if (type & PURPLE_MEDIA_ELEMENT_VIDEO) return manager->priv->video_src; else if (type & PURPLE_MEDIA_ELEMENT_APPLICATION) return get_send_application_element_info (); } else if (type & PURPLE_MEDIA_ELEMENT_SINK) { if (type & PURPLE_MEDIA_ELEMENT_AUDIO) return manager->priv->audio_sink; else if (type & PURPLE_MEDIA_ELEMENT_VIDEO) return manager->priv->video_sink; else if (type & PURPLE_MEDIA_ELEMENT_APPLICATION) return get_recv_application_element_info (); #endif /* USE_GSTREAMER */ window_id_cb(GstBus *bus, GstMessage *msg, PurpleMediaOutputWindow *ow) if (GST_MESSAGE_TYPE(msg) != GST_MESSAGE_ELEMENT || !gst_is_video_overlay_prepare_window_handle_message(msg)) sink = GST_ELEMENT(GST_MESSAGE_SRC(msg)); while (sink != ow->sink) { sink = GST_ELEMENT_PARENT(sink); g_signal_handlers_disconnect_matched(bus, G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, 0, 0, NULL, gst_video_overlay_set_window_handle(GST_VIDEO_OVERLAY(GST_MESSAGE_SRC(msg)), purple_media_manager_create_output_window(PurpleMediaManager *manager, PurpleMedia *media, const gchar *session_id, const gchar *participant) g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE); iter = manager->priv->output_windows; for(; iter; iter = g_list_next(iter)) { PurpleMediaOutputWindow *ow = iter->data; if (ow->sink == NULL && ow->media == media && ow->participant != NULL && !strcmp(participant, ow->participant)) || (participant == ow->participant)) && !strcmp(session_id, ow->session_id)) { GstElement *queue, *convert; GstElement *tee = purple_media_get_tee(media, session_id, participant); queue = gst_element_factory_make("queue", NULL); convert = gst_element_factory_make("videoconvert", NULL); ow->sink = purple_media_manager_get_element( manager, PURPLE_MEDIA_RECV_VIDEO, ow->media, ow->session_id, if (participant == NULL) { /* aka this is a preview sink */ G_OBJECT_GET_CLASS(ow->sink); if (g_object_class_find_property(klass, g_object_set(G_OBJECT(ow->sink), if (g_object_class_find_property(klass, g_object_set(G_OBJECT(ow->sink), gst_bin_add_many(GST_BIN(GST_ELEMENT_PARENT(tee)), queue, convert, ow->sink, NULL); bus = gst_pipeline_get_bus(GST_PIPELINE( manager->priv->pipeline)); g_signal_connect(bus, "sync-message::element", G_CALLBACK(window_id_cb), ow); gst_element_set_state(ow->sink, GST_STATE_PLAYING); gst_element_set_state(convert, GST_STATE_PLAYING); gst_element_set_state(queue, GST_STATE_PLAYING); gst_element_link(convert, ow->sink); gst_element_link(queue, convert); gst_element_link(tee, queue); purple_media_manager_set_output_window(PurpleMediaManager *manager, PurpleMedia *media, const gchar *session_id, const gchar *participant, gulong window_id) PurpleMediaOutputWindow *output_window; g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), FALSE); g_return_val_if_fail(PURPLE_IS_MEDIA(media), FALSE); output_window = g_new0(PurpleMediaOutputWindow, 1); output_window->id = manager->priv->next_output_window_id++; output_window->media = media; output_window->session_id = g_strdup(session_id); output_window->participant = g_strdup(participant); output_window->window_id = window_id; manager->priv->output_windows = g_list_prepend( manager->priv->output_windows, output_window); if (purple_media_get_tee(media, session_id, participant) != NULL) purple_media_manager_create_output_window(manager, media, session_id, participant); return output_window->id; purple_media_manager_remove_output_window(PurpleMediaManager *manager, PurpleMediaOutputWindow *output_window = NULL; g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), FALSE); iter = manager->priv->output_windows; for (; iter; iter = g_list_next(iter)) { PurpleMediaOutputWindow *ow = iter->data; if (ow->id == output_window_id) { manager->priv->output_windows = g_list_delete_link( manager->priv->output_windows, iter); if (output_window == NULL) if (output_window->sink != NULL) { GstPad *pad = gst_element_get_static_pad( output_window->sink, "sink"); GstPad *peer = gst_pad_get_peer(pad); GstElement *colorspace = GST_ELEMENT_PARENT(peer), *queue; pad = gst_element_get_static_pad(colorspace, "sink"); peer = gst_pad_get_peer(pad); queue = GST_ELEMENT_PARENT(peer); pad = gst_element_get_static_pad(queue, "sink"); peer = gst_pad_get_peer(pad); gst_element_release_request_pad(GST_ELEMENT_PARENT(peer), peer); gst_element_set_locked_state(queue, TRUE); gst_element_set_state(queue, GST_STATE_NULL); gst_bin_remove(GST_BIN(GST_ELEMENT_PARENT(queue)), queue); gst_element_set_locked_state(colorspace, TRUE); gst_element_set_state(colorspace, GST_STATE_NULL); gst_bin_remove(GST_BIN(GST_ELEMENT_PARENT(colorspace)), colorspace); gst_element_set_locked_state(output_window->sink, TRUE); gst_element_set_state(output_window->sink, GST_STATE_NULL); gst_bin_remove(GST_BIN(GST_ELEMENT_PARENT(output_window->sink)), g_free(output_window->session_id); g_free(output_window->participant); purple_media_manager_remove_output_windows(PurpleMediaManager *manager, PurpleMedia *media, const gchar *session_id, const gchar *participant) g_return_if_fail(PURPLE_IS_MEDIA(media)); iter = manager->priv->output_windows; PurpleMediaOutputWindow *ow = iter->data; iter = g_list_next(iter); if (media == ow->media && ((session_id != NULL && ow->session_id != NULL && !strcmp(session_id, ow->session_id)) || (session_id == ow->session_id)) && ((participant != NULL && ow->participant != NULL && !strcmp(participant, ow->participant)) || (participant == ow->participant))) purple_media_manager_remove_output_window( purple_media_manager_set_ui_caps(PurpleMediaManager *manager, g_return_if_fail(PURPLE_IS_MEDIA_MANAGER(manager)); oldcaps = manager->priv->ui_caps; manager->priv->ui_caps = caps; purple_media_manager_signals[UI_CAPS_CHANGED], purple_media_manager_get_ui_caps(PurpleMediaManager *manager) g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), return manager->priv->ui_caps; return PURPLE_MEDIA_CAPS_NONE; purple_media_manager_set_backend_type(PurpleMediaManager *manager, g_return_if_fail(PURPLE_IS_MEDIA_MANAGER(manager)); manager->priv->backend_type = backend_type; purple_media_manager_get_backend_type(PurpleMediaManager *manager) g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), return manager->priv->backend_type; purple_media_manager_set_application_data_callbacks(PurpleMediaManager *manager, PurpleMedia *media, const gchar *session_id, const gchar *participant, PurpleMediaAppDataCallbacks *callbacks, gpointer user_data, GDestroyNotify notify) #ifdef HAVE_MEDIA_APPLICATION PurpleMediaAppDataInfo * info = ensure_app_data_info_and_lock (manager, media, session_id, participant); info->notify (info->user_data); if (info->readable_cb_token) { purple_timeout_remove (info->readable_timer_id); info->readable_cb_token = 0; if (info->writable_cb_token) { purple_timeout_remove (info->writable_timer_id); info->writable_cb_token = 0; info->callbacks = *callbacks; info->callbacks.writable = NULL; info->callbacks.readable = NULL; info->user_data = user_data; call_appsrc_writable_locked (info); if (info->num_samples > 0 || info->current_sample != NULL) call_appsink_readable_locked (info); g_mutex_unlock (&manager->priv->appdata_mutex); purple_media_manager_send_application_data ( PurpleMediaManager *manager, PurpleMedia *media, const gchar *session_id, const gchar *participant, gpointer buffer, guint size, gboolean blocking) #ifdef HAVE_MEDIA_APPLICATION PurpleMediaAppDataInfo * info = get_app_data_info_and_lock (manager, media, session_id, participant); if (info && info->appsrc && info->connected) { GstBuffer *gstbuffer = gst_buffer_new_wrapped (g_memdup (buffer, size), GstAppSrc *appsrc = gst_object_ref (info->appsrc); g_mutex_unlock (&manager->priv->appdata_mutex); if (gst_app_src_push_buffer (appsrc, gstbuffer) == GST_FLOW_OK) { srcpad = gst_element_get_static_pad (GST_ELEMENT (appsrc), gst_pad_peer_query (srcpad, gst_query_new_drain ()); gst_object_unref (srcpad); gst_object_unref (appsrc); gst_object_unref (appsrc); g_mutex_unlock (&manager->priv->appdata_mutex); purple_media_manager_receive_application_data ( PurpleMediaManager *manager, PurpleMedia *media, const gchar *session_id, const gchar *participant, gpointer buffer, guint max_size, #ifdef HAVE_MEDIA_APPLICATION PurpleMediaAppDataInfo * info = get_app_data_info_and_lock (manager, media, session_id, participant); /* If we are in a blocking read, we need to loop until max_size data * is read into the buffer, if we're not, then we need to read as much if (!info->current_sample && info->appsink && info->num_samples > 0) { info->current_sample = gst_app_sink_pull_sample (info->appsink); if (info->current_sample) if (info->current_sample) { GstBuffer *gstbuffer = gst_sample_get_buffer ( gst_buffer_map (gstbuffer, &mapinfo, GST_MAP_READ); /* We must copy only the data remaining in the buffer without * overflowing the buffer */ bytes_to_copy = max_size - bytes_read; if (bytes_to_copy > mapinfo.size - info->sample_offset) bytes_to_copy = mapinfo.size - info->sample_offset; memcpy ((guint8 *)buffer + bytes_read, mapinfo.data + info->sample_offset, bytes_to_copy); gst_buffer_unmap (gstbuffer, &mapinfo); info->sample_offset += bytes_to_copy; bytes_read += bytes_to_copy; if (info->sample_offset == mapinfo.size) { gst_sample_unref (info->current_sample); info->current_sample = NULL; /* In case there's no buffer in the sample (should never * happen), we need to at least unref it */ gst_sample_unref (info->current_sample); info->current_sample = NULL; /* If blocking, wait until there's an available sample */ while (bytes_read < max_size && blocking && info->current_sample == NULL && info->num_samples == 0) { g_cond_wait (&info->readable_cond, &manager->priv->appdata_mutex); /* We've been signaled, we need to unlock and regrab the info * struct to make sure nothing changed */ g_mutex_unlock (&manager->priv->appdata_mutex); info = get_app_data_info_and_lock (manager, media, session_id, participant); if (info == NULL || info->appsink == NULL) { /* The session was destroyed while we were waiting, we g_mutex_unlock (&manager->priv->appdata_mutex); } while (bytes_read < max_size && (blocking || info->num_samples > 0)); g_mutex_unlock (&manager->priv->appdata_mutex); g_mutex_unlock (&manager->priv->appdata_mutex); purple_media_element_type_get_type() static const GFlagsValue values[] = { { PURPLE_MEDIA_ELEMENT_NONE, "PURPLE_MEDIA_ELEMENT_NONE", "none" }, { PURPLE_MEDIA_ELEMENT_AUDIO, "PURPLE_MEDIA_ELEMENT_AUDIO", "audio" }, { PURPLE_MEDIA_ELEMENT_VIDEO, "PURPLE_MEDIA_ELEMENT_VIDEO", "video" }, { PURPLE_MEDIA_ELEMENT_AUDIO_VIDEO, "PURPLE_MEDIA_ELEMENT_AUDIO_VIDEO", { PURPLE_MEDIA_ELEMENT_NO_SRCS, "PURPLE_MEDIA_ELEMENT_NO_SRCS", "no-srcs" }, { PURPLE_MEDIA_ELEMENT_ONE_SRC, "PURPLE_MEDIA_ELEMENT_ONE_SRC", "one-src" }, { PURPLE_MEDIA_ELEMENT_MULTI_SRC, "PURPLE_MEDIA_ELEMENT_MULTI_SRC", { PURPLE_MEDIA_ELEMENT_REQUEST_SRC, "PURPLE_MEDIA_ELEMENT_REQUEST_SRC", { PURPLE_MEDIA_ELEMENT_NO_SINKS, "PURPLE_MEDIA_ELEMENT_NO_SINKS", "no-sinks" }, { PURPLE_MEDIA_ELEMENT_ONE_SINK, "PURPLE_MEDIA_ELEMENT_ONE_SINK", "one-sink" }, { PURPLE_MEDIA_ELEMENT_MULTI_SINK, "PURPLE_MEDIA_ELEMENT_MULTI_SINK", { PURPLE_MEDIA_ELEMENT_REQUEST_SINK, "PURPLE_MEDIA_ELEMENT_REQUEST_SINK", { PURPLE_MEDIA_ELEMENT_UNIQUE, "PURPLE_MEDIA_ELEMENT_UNIQUE", "unique" }, { PURPLE_MEDIA_ELEMENT_SRC, "PURPLE_MEDIA_ELEMENT_SRC", "src" }, { PURPLE_MEDIA_ELEMENT_SINK, "PURPLE_MEDIA_ELEMENT_SINK", "sink" }, { PURPLE_MEDIA_ELEMENT_APPLICATION, "PURPLE_MEDIA_ELEMENT_APPLICATION", "application" }, type = g_flags_register_static( "PurpleMediaElementType", values); struct _PurpleMediaElementInfoClass GObjectClass parent_class; struct _PurpleMediaElementInfo struct _PurpleMediaElementInfoPrivate PurpleMediaElementType type; PurpleMediaElementCreateCallback create; purple_media_element_info_init(PurpleMediaElementInfo *info) PurpleMediaElementInfoPrivate *priv = PURPLE_MEDIA_ELEMENT_INFO_GET_PRIVATE(info); priv->type = PURPLE_MEDIA_ELEMENT_NONE; purple_media_element_info_finalize(GObject *info) PurpleMediaElementInfoPrivate *priv = PURPLE_MEDIA_ELEMENT_INFO_GET_PRIVATE(info); purple_media_element_info_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) PurpleMediaElementInfoPrivate *priv; g_return_if_fail(PURPLE_IS_MEDIA_ELEMENT_INFO(object)); priv = PURPLE_MEDIA_ELEMENT_INFO_GET_PRIVATE(object); priv->id = g_value_dup_string(value); priv->name = g_value_dup_string(value); priv->type = g_value_get_flags(value); priv->create = g_value_get_pointer(value); G_OBJECT_WARN_INVALID_PROPERTY_ID( purple_media_element_info_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) PurpleMediaElementInfoPrivate *priv; g_return_if_fail(PURPLE_IS_MEDIA_ELEMENT_INFO(object)); priv = PURPLE_MEDIA_ELEMENT_INFO_GET_PRIVATE(object); g_value_set_string(value, priv->id); g_value_set_string(value, priv->name); g_value_set_flags(value, priv->type); g_value_set_pointer(value, priv->create); G_OBJECT_WARN_INVALID_PROPERTY_ID( purple_media_element_info_class_init(PurpleMediaElementInfoClass *klass) GObjectClass *gobject_class = (GObjectClass*)klass; gobject_class->finalize = purple_media_element_info_finalize; gobject_class->set_property = purple_media_element_info_set_property; gobject_class->get_property = purple_media_element_info_get_property; g_object_class_install_property(gobject_class, PROP_ID, g_param_spec_string("id", "The unique identifier of the element.", G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property(gobject_class, PROP_NAME, g_param_spec_string("name", "The friendly/display name of this element.", G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property(gobject_class, PROP_TYPE, g_param_spec_flags("type", "The type of element this is.", PURPLE_TYPE_MEDIA_ELEMENT_TYPE, PURPLE_MEDIA_ELEMENT_NONE, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property(gobject_class, PROP_CREATE_CB, g_param_spec_pointer("create-cb", "The function called to create this element.", G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_type_class_add_private(klass, sizeof(PurpleMediaElementInfoPrivate)); G_DEFINE_TYPE(PurpleMediaElementInfo, purple_media_element_info, G_TYPE_OBJECT); purple_media_element_info_get_type() purple_media_element_info_get_id(PurpleMediaElementInfo *info) #if GLIB_CHECK_VERSION(2, 37, 3) /* Silence a warning. This could be anywhere below G_DEFINE_TYPE */ (void)purple_media_element_info_get_instance_private; g_return_val_if_fail(PURPLE_IS_MEDIA_ELEMENT_INFO(info), NULL); g_object_get(info, "id", &id, NULL); purple_media_element_info_get_name(PurpleMediaElementInfo *info) g_return_val_if_fail(PURPLE_IS_MEDIA_ELEMENT_INFO(info), NULL); g_object_get(info, "name", &name, NULL); purple_media_element_info_get_element_type(PurpleMediaElementInfo *info) PurpleMediaElementType type; g_return_val_if_fail(PURPLE_IS_MEDIA_ELEMENT_INFO(info), PURPLE_MEDIA_ELEMENT_NONE); g_object_get(info, "type", &type, NULL); return PURPLE_MEDIA_ELEMENT_NONE; purple_media_element_info_call_create(PurpleMediaElementInfo *info, PurpleMedia *media, const gchar *session_id, const gchar *participant) PurpleMediaElementCreateCallback create; g_return_val_if_fail(PURPLE_IS_MEDIA_ELEMENT_INFO(info), NULL); g_object_get(info, "create-cb", &create, NULL); return create(media, session_id, participant); #endif /* USE_GSTREAMER */