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