1 /*
2  * call-handler.c
3  * Copyright (C) 2011 Collabora Ltd.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  */
19 
20 #include "config.h"
21 
22 #include <gst/gst.h>
23 #include <telepathy-glib/telepathy-glib.h>
24 #include <telepathy-glib/telepathy-glib-dbus.h>
25 #include <farstream/fs-element-added-notifier.h>
26 #include <farstream/fs-utils.h>
27 #include <telepathy-farstream/telepathy-farstream.h>
28 
29 typedef struct {
30   GstElement *pipeline;
31   guint buswatch;
32   TpChannel *proxy;
33   TfChannel *channel;
34   GList *notifiers;
35 
36   guint input_volume;
37   guint output_volume;
38 
39   gboolean has_audio_src;
40   gboolean has_video_src;
41 
42   GstElement *video_input;
43   GstElement *video_capsfilter;
44 
45   guint width;
46   guint height;
47   guint framerate;
48 } ChannelContext;
49 
50 GMainLoop *loop;
51 
52 static gboolean
bus_watch_cb(GstBus * bus,GstMessage * message,gpointer user_data)53 bus_watch_cb (GstBus *bus,
54     GstMessage *message,
55     gpointer user_data)
56 {
57   ChannelContext *context = user_data;
58 
59   if (context->channel != NULL)
60     tf_channel_bus_message (context->channel, message);
61 
62   if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ERROR)
63     {
64       GError *error = NULL;
65       gchar *debug = NULL;
66       gst_message_parse_error (message, &error, &debug);
67       g_printerr ("ERROR from element %s: %s\n",
68           GST_OBJECT_NAME (message->src), error->message);
69       g_printerr ("Debugging info: %s\n", (debug) ? debug : "none");
70       g_error_free (error);
71       g_free (debug);
72     }
73 
74   return TRUE;
75 }
76 
77 static void
on_audio_output_volume_changed(TfContent * content,GParamSpec * spec,GstElement * volume)78 on_audio_output_volume_changed (TfContent *content,
79   GParamSpec *spec,
80   GstElement *volume)
81 {
82   guint output_volume = 0;
83 
84   g_object_get (content, "requested-output-volume", &output_volume, NULL);
85 
86   if (output_volume == 0)
87     return;
88 
89   g_object_set (volume, "volume", (double)output_volume / 255.0, NULL);
90 }
91 
92 static void
src_pad_added_cb(TfContent * content,TpHandle handle,FsStream * stream,GstPad * pad,FsCodec * codec,gpointer user_data)93 src_pad_added_cb (TfContent *content,
94     TpHandle handle,
95     FsStream *stream,
96     GstPad *pad,
97     FsCodec *codec,
98     gpointer user_data)
99 {
100   ChannelContext *context = user_data;
101   gchar *cstr = fs_codec_to_string (codec);
102   FsMediaType mtype;
103   GstPad *sinkpad;
104   GstElement *element;
105   GstStateChangeReturn ret;
106 
107   g_debug ("New src pad: %s", cstr);
108   g_object_get (content, "media-type", &mtype, NULL);
109 
110   switch (mtype)
111     {
112       case FS_MEDIA_TYPE_AUDIO:
113         {
114           GstElement *volume = NULL;
115           gchar *tmp_str = g_strdup_printf ("audioconvert ! audioresample "
116               "! volume name=\"output_volume%s\" "
117               "! audioconvert ! autoaudiosink", cstr);
118           element = gst_parse_bin_from_description (tmp_str,
119               TRUE, NULL);
120           g_free (tmp_str);
121 
122           tmp_str = g_strdup_printf ("output_volume%s", cstr);
123           volume = gst_bin_get_by_name (GST_BIN (element), tmp_str);
124           g_free (tmp_str);
125 
126           tp_g_signal_connect_object (content, "notify::output-volume",
127               G_CALLBACK (on_audio_output_volume_changed),
128               volume, 0);
129 
130           gst_object_unref (volume);
131 
132           break;
133         }
134       case FS_MEDIA_TYPE_VIDEO:
135         element = gst_parse_bin_from_description (
136           "videoconvert ! videoscale ! autovideosink",
137           TRUE, NULL);
138         break;
139       default:
140         g_warning ("Unknown media type");
141         return;
142     }
143 
144   gst_bin_add (GST_BIN (context->pipeline), element);
145   sinkpad = gst_element_get_static_pad (element, "sink");
146   ret = gst_element_set_state (element, GST_STATE_PLAYING);
147   if (ret == GST_STATE_CHANGE_FAILURE)
148     {
149       tp_channel_close_async (TP_CHANNEL (context->proxy), NULL, NULL);
150       g_warning ("Failed to start sink pipeline !?");
151       return;
152     }
153 
154   if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sinkpad)))
155     {
156       tp_channel_close_async (TP_CHANNEL (context->proxy), NULL, NULL);
157       g_warning ("Couldn't link sink pipeline !?");
158       return;
159     }
160 
161   g_object_unref (sinkpad);
162 }
163 
164 static void
update_video_parameters(ChannelContext * context,gboolean restart)165 update_video_parameters (ChannelContext *context, gboolean restart)
166 {
167   GstCaps *caps;
168   GstClock *clock;
169 
170   if (restart)
171     {
172       /* Assuming the pipeline is in playing state */
173       gst_element_set_locked_state (context->video_input, TRUE);
174       gst_element_set_state (context->video_input, GST_STATE_NULL);
175     }
176 
177   g_object_get (context->video_capsfilter, "caps", &caps, NULL);
178   caps = gst_caps_make_writable (caps);
179 
180   gst_caps_set_simple (caps,
181       "framerate", GST_TYPE_FRACTION, context->framerate, 1,
182       "width", G_TYPE_INT, context->width,
183       "height", G_TYPE_INT, context->height,
184       NULL);
185 
186   g_object_set (context->video_capsfilter, "caps", caps, NULL);
187 
188   if (restart)
189     {
190       clock = gst_pipeline_get_clock (GST_PIPELINE (context->pipeline));
191       /* Need to reset the clock if we set the pipeline back to ready by hand */
192       if (clock != NULL)
193         {
194           gst_element_set_clock (context->video_input, clock);
195           g_object_unref (clock);
196         }
197 
198       gst_element_set_locked_state (context->video_input, FALSE);
199       gst_element_sync_state_with_parent (context->video_input);
200     }
201 }
202 
203 static void
on_video_framerate_changed(TfContent * content,GParamSpec * spec,ChannelContext * context)204 on_video_framerate_changed (TfContent *content,
205   GParamSpec *spec,
206   ChannelContext *context)
207 {
208   guint framerate;
209 
210   g_object_get (content, "framerate", &framerate, NULL);
211 
212   if (framerate != 0)
213     context->framerate = framerate;
214 
215   update_video_parameters (context, FALSE);
216 }
217 
218 static void
on_video_resolution_changed(TfContent * content,guint width,guint height,ChannelContext * context)219 on_video_resolution_changed (TfContent *content,
220    guint width,
221    guint height,
222    ChannelContext *context)
223 {
224   g_assert (width > 0 && height > 0);
225 
226   context->width = width;
227   context->height = height;
228 
229   update_video_parameters (context, TRUE);
230 }
231 
232 static void
on_audio_input_volume_changed(TfContent * content,GParamSpec * spec,ChannelContext * context)233 on_audio_input_volume_changed (TfContent *content,
234   GParamSpec *spec,
235   ChannelContext *context)
236 {
237   GstElement *volume;
238   guint input_volume = 0;
239 
240   g_object_get (content, "requested-input-volume", &input_volume, NULL);
241 
242   if (input_volume == 0)
243     return;
244 
245   volume = gst_bin_get_by_name (GST_BIN (context->pipeline), "input_volume");
246   g_object_set (volume, "volume", (double)input_volume / 255.0, NULL);
247   gst_object_unref (volume);
248 }
249 
250 static GstElement *
setup_audio_source(ChannelContext * context,TfContent * content)251 setup_audio_source (ChannelContext *context, TfContent *content)
252 {
253   GstElement *result;
254   GstElement *volume;
255   gint input_volume = 0;
256 
257   result = gst_parse_bin_from_description (
258       "pulsesrc ! audio/x-raw, rate=8000 ! queue"
259       " ! audioconvert ! audioresample"
260       " ! volume name=input_volume ! audioconvert ",
261       TRUE, NULL);
262 
263   /* FIXME Need to handle both requested/reported */
264   /* TODO Volume control should be handled in FsIo */
265   g_object_get (content,
266       "requested-input-volume", &input_volume,
267       NULL);
268 
269   if (input_volume >= 0)
270     {
271       volume = gst_bin_get_by_name (GST_BIN (result), "input_volume");
272       g_debug ("Requested volume is: %i", input_volume);
273       g_object_set (volume, "volume", (double)input_volume / 255.0, NULL);
274       gst_object_unref (volume);
275     }
276 
277   g_signal_connect (content, "notify::requested-input-volume",
278       G_CALLBACK (on_audio_input_volume_changed),
279       context);
280 
281   return result;
282 }
283 
284 static GstElement *
setup_video_source(ChannelContext * context,TfContent * content)285 setup_video_source (ChannelContext *context, TfContent *content)
286 {
287   GstElement *result, *capsfilter;
288   GstCaps *caps;
289   guint framerate = 0, width = 0, height = 0;
290 
291   result = gst_parse_bin_from_description_full (
292       "autovideosrc ! videorate drop-only=1 average-period=20000000000 ! videoscale ! videoconvert ! capsfilter name=c",
293       TRUE, NULL, GST_PARSE_FLAG_FATAL_ERRORS, NULL);
294 
295   g_assert (result);
296   capsfilter = gst_bin_get_by_name (GST_BIN (result), "c");
297 
298   g_object_get (content,
299       "framerate", &framerate,
300       "width", &width,
301       "height", &height,
302       NULL);
303 
304   if (framerate == 0)
305     framerate = 15;
306 
307   if (width == 0 || height == 0)
308     {
309       width = 320;
310       height = 240;
311     }
312 
313   context->framerate = framerate;
314   context->width = width;
315   context->height = height;
316 
317   caps = gst_caps_new_simple ("video/x-raw",
318       "width", G_TYPE_INT, width,
319       "height", G_TYPE_INT, height,
320       "framerate", GST_TYPE_FRACTION, framerate, 1,
321       NULL);
322 
323   g_object_set (G_OBJECT (capsfilter), "caps", caps, NULL);
324 
325   gst_caps_unref (caps);
326 
327   context->video_input = result;
328   context->video_capsfilter = capsfilter;
329 
330   g_signal_connect (content, "notify::framerate",
331     G_CALLBACK (on_video_framerate_changed),
332     context);
333 
334   g_signal_connect (content, "resolution-changed",
335     G_CALLBACK (on_video_resolution_changed),
336     context);
337 
338   return result;
339 }
340 
341 static gboolean
start_sending_cb(TfContent * content,gpointer user_data)342 start_sending_cb (TfContent *content, gpointer user_data)
343 {
344   ChannelContext *context = user_data;
345   GstPad *srcpad, *sinkpad;
346   FsMediaType mtype;
347   GstElement *element;
348   GstStateChangeReturn ret;
349   gboolean res = FALSE;
350 
351   g_debug ("Start sending");
352 
353   g_object_get (content,
354     "sink-pad", &sinkpad,
355     "media-type", &mtype,
356     NULL);
357 
358   switch (mtype)
359     {
360       case FS_MEDIA_TYPE_AUDIO:
361         if (context->has_audio_src)
362           goto out;
363 
364         element = setup_audio_source (context, content);
365         context->has_audio_src = TRUE;
366         break;
367       case FS_MEDIA_TYPE_VIDEO:
368         if (context->has_video_src)
369           goto out;
370 
371         element = setup_video_source (context, content);
372         context->has_video_src = TRUE;
373         break;
374       default:
375         g_warning ("Unknown media type");
376         goto out;
377     }
378 
379 
380   gst_bin_add (GST_BIN (context->pipeline), element);
381   srcpad = gst_element_get_static_pad (element, "src");
382 
383   if (GST_PAD_LINK_FAILED (gst_pad_link (srcpad, sinkpad)))
384     {
385       tp_channel_close_async (TP_CHANNEL (context->proxy), NULL, NULL);
386       g_warning ("Couldn't link source pipeline !?");
387       goto out2;
388     }
389 
390   ret = gst_element_set_state (element, GST_STATE_PLAYING);
391   if (ret == GST_STATE_CHANGE_FAILURE)
392     {
393       tp_channel_close_async (TP_CHANNEL (context->proxy), NULL, NULL);
394       g_warning ("source pipeline failed to start!?");
395       goto out2;
396     }
397 
398   res = TRUE;
399 
400 out2:
401   g_object_unref (srcpad);
402 out:
403   g_object_unref (sinkpad);
404 
405   return res;
406 }
407 
408 static void
content_added_cb(TfChannel * channel,TfContent * content,gpointer user_data)409 content_added_cb (TfChannel *channel,
410     TfContent *content,
411     gpointer user_data)
412 {
413   ChannelContext *context = user_data;
414 
415   g_debug ("Content added");
416 
417   g_signal_connect (content, "src-pad-added",
418     G_CALLBACK (src_pad_added_cb), context);
419   g_signal_connect (content, "start-sending",
420       G_CALLBACK (start_sending_cb), context);
421 }
422 
423 static void
conference_added_cb(TfChannel * channel,GstElement * conference,gpointer user_data)424 conference_added_cb (TfChannel *channel,
425   GstElement *conference,
426   gpointer user_data)
427 {
428   ChannelContext *context = user_data;
429   GKeyFile *keyfile;
430 
431   g_debug ("Conference added");
432 
433   /* Add notifier to set the various element properties as needed */
434   keyfile = fs_utils_get_default_element_properties (conference);
435   if (keyfile != NULL)
436     {
437       FsElementAddedNotifier *notifier;
438       g_debug ("Loaded default codecs for %s", GST_ELEMENT_NAME (conference));
439 
440       notifier = fs_element_added_notifier_new ();
441       fs_element_added_notifier_set_properties_from_keyfile (notifier, keyfile);
442       fs_element_added_notifier_add (notifier, GST_BIN (context->pipeline));
443 
444       context->notifiers = g_list_prepend (context->notifiers, notifier);
445     }
446 
447 
448   gst_bin_add (GST_BIN (context->pipeline), conference);
449   gst_element_set_state (conference, GST_STATE_PLAYING);
450 }
451 
452 
453 static void
conference_removed_cb(TfChannel * channel,GstElement * conference,gpointer user_data)454 conference_removed_cb (TfChannel *channel,
455   GstElement *conference,
456   gpointer user_data)
457 {
458   ChannelContext *context = user_data;
459 
460   gst_element_set_locked_state (conference, TRUE);
461   gst_element_set_state (conference, GST_STATE_NULL);
462   gst_bin_remove (GST_BIN (context->pipeline), conference);
463 }
464 
465 static gboolean
dump_pipeline_cb(gpointer data)466 dump_pipeline_cb (gpointer data)
467 {
468   ChannelContext *context = data;
469 
470   GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (context->pipeline),
471     GST_DEBUG_GRAPH_SHOW_ALL,
472     "call-handler");
473 
474   return TRUE;
475 }
476 
477 static void
new_tf_channel_cb(GObject * source,GAsyncResult * result,gpointer user_data)478 new_tf_channel_cb (GObject *source,
479   GAsyncResult *result,
480   gpointer user_data)
481 {
482   ChannelContext *context = user_data;
483   GError *error = NULL;
484 
485   g_debug ("New TfChannel");
486 
487   context->channel = tf_channel_new_finish (source, result, &error);
488 
489   if (context->channel == NULL)
490     {
491       g_error ("Failed to create channel: %s", error->message);
492       g_clear_error (&error);
493     }
494 
495   g_debug ("Adding timeout");
496   g_timeout_add_seconds (5, dump_pipeline_cb, context);
497 
498   g_signal_connect (context->channel, "fs-conference-added",
499     G_CALLBACK (conference_added_cb), context);
500 
501 
502   g_signal_connect (context->channel, "fs-conference-removed",
503     G_CALLBACK (conference_removed_cb), context);
504 
505   g_signal_connect (context->channel, "content-added",
506     G_CALLBACK (content_added_cb), context);
507 }
508 
509 static void
proxy_invalidated_cb(TpProxy * proxy,guint domain,gint code,gchar * message,gpointer user_data)510 proxy_invalidated_cb (TpProxy *proxy,
511     guint domain,
512     gint code,
513     gchar *message,
514     gpointer user_data)
515 {
516   ChannelContext *context = user_data;
517 
518   g_debug ("Channel closed");
519   if (context->pipeline != NULL)
520     {
521       gst_element_set_state (context->pipeline, GST_STATE_NULL);
522       g_object_unref (context->pipeline);
523     }
524 
525   if (context->channel != NULL)
526     g_object_unref (context->channel);
527 
528   g_list_foreach (context->notifiers, (GFunc) g_object_unref, NULL);
529   g_list_free (context->notifiers);
530 
531   g_object_unref (context->proxy);
532 
533   g_slice_free (ChannelContext, context);
534 
535   g_main_loop_quit (loop);
536 }
537 
538 static void
new_call_channel_cb(TpSimpleHandler * handler,TpAccount * account,TpConnection * connection,GList * channels,GList * requests_satisfied,gint64 user_action_time,TpHandleChannelsContext * handler_context,gpointer user_data)539 new_call_channel_cb (TpSimpleHandler *handler,
540     TpAccount *account,
541     TpConnection *connection,
542     GList *channels,
543     GList *requests_satisfied,
544     gint64 user_action_time,
545     TpHandleChannelsContext *handler_context,
546     gpointer user_data)
547 {
548   ChannelContext *context;
549   TpChannel *proxy;
550   GstBus *bus;
551   GstElement *pipeline;
552   GstStateChangeReturn ret;
553 
554   g_debug ("New channel");
555 
556   proxy = channels->data;
557 
558   pipeline = gst_pipeline_new (NULL);
559 
560   ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
561 
562   if (ret == GST_STATE_CHANGE_FAILURE)
563     {
564       tp_channel_close_async (TP_CHANNEL (proxy), NULL, NULL);
565       g_object_unref (pipeline);
566       g_warning ("Failed to start an empty pipeline !?");
567       return;
568     }
569 
570   context = g_slice_new0 (ChannelContext);
571   context->pipeline = pipeline;
572 
573   bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
574   context->buswatch = gst_bus_add_watch (bus, bus_watch_cb, context);
575   g_object_unref (bus);
576 
577   tf_channel_new_async (proxy, new_tf_channel_cb, context);
578 
579   tp_handle_channels_context_accept (handler_context);
580 
581   tp_cli_channel_type_call_call_accept (proxy, -1,
582       NULL, NULL, NULL, NULL);
583 
584   context->proxy = g_object_ref (proxy);
585   g_signal_connect (proxy, "invalidated",
586     G_CALLBACK (proxy_invalidated_cb),
587     context);
588 }
589 
590 int
main(int argc,char ** argv)591 main (int argc, char **argv)
592 {
593   TpBaseClient *client;
594   TpAccountManager *am;
595 
596   g_type_init ();
597   gst_init (&argc, &argv);
598 
599   loop = g_main_loop_new (NULL, FALSE);
600 
601   am = tp_account_manager_dup ();
602 
603   client = tp_simple_handler_new_with_am (am,
604     FALSE,
605     FALSE,
606     "TpFsCallHandlerDemo",
607     TRUE,
608     new_call_channel_cb,
609     NULL,
610     NULL);
611 
612   tp_base_client_take_handler_filter (client,
613     tp_asv_new (
614        TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
615           TP_IFACE_CHANNEL_TYPE_CALL,
616        TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT,
617           TP_HANDLE_TYPE_CONTACT,
618         TP_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO, G_TYPE_BOOLEAN,
619           TRUE,
620        NULL));
621 
622   tp_base_client_take_handler_filter (client,
623     tp_asv_new (
624        TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
625           TP_IFACE_CHANNEL_TYPE_CALL,
626        TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT,
627           TP_HANDLE_TYPE_CONTACT,
628         TP_PROP_CHANNEL_TYPE_CALL_INITIAL_VIDEO, G_TYPE_BOOLEAN,
629           TRUE,
630        NULL));
631 
632   tp_base_client_add_handler_capabilities_varargs (client,
633     TP_IFACE_CHANNEL_TYPE_CALL "/video/h264",
634     TP_IFACE_CHANNEL_TYPE_CALL "/shm",
635     TP_IFACE_CHANNEL_TYPE_CALL "/ice",
636     TP_IFACE_CHANNEL_TYPE_CALL "/gtalk-p2p",
637     NULL);
638 
639   tp_base_client_register (client, NULL);
640 
641   g_main_loop_run (loop);
642 
643   g_object_unref (am);
644   g_object_unref (client);
645   g_main_loop_unref (loop);
646 
647   return 0;
648 }
649