"Refactor" generation of payload-type elements
[emacs-jabber-tox.git] / tox-session.c
blobc76ab0423e2710d0bf259827808a889dc3d6aefd
1 #include <glib.h>
2 #include <farsight/farsight.h>
3 #include <farsight/farsight-transport.h>
4 #include <dbus/dbus-glib.h>
5 #include <glib/gprintf.h>
6 #include <string.h>
7 #include "tox-session.h"
8 #include "tox-marshal.h"
10 G_DEFINE_TYPE(ToxSession, tox_session, G_TYPE_OBJECT)
12 typedef struct _ToxSessionPrivate {
13 FarsightSession *session;
14 /* for now, one stream is enough */
15 FarsightStream *stream;
17 int have_source, have_sink;
19 gboolean dispose_has_run;
20 } ToxSessionPrivate;
22 gboolean tox_session_destroy(ToxSession *obj, GError **error);
23 gboolean tox_session_set_default_audio_sink(ToxSession *obj, GError **error);
24 gboolean tox_session_set_ogg_vorbis_audio_source(ToxSession *obj, char *filename, GError **error);
25 gboolean tox_session_add_remote_candidate(ToxSession *obj, GPtrArray *candidate, GError **error);
26 gboolean tox_session_set_remote_codecs(ToxSession *obj, GPtrArray *codecs, GError **error);
27 gboolean tox_session_get_local_codecs(ToxSession *obj, GPtrArray **codecs, GError **error);
28 gboolean tox_session_get_codec_intersection(ToxSession *obj, GPtrArray **codecs, GError **error);
30 /* properties */
31 enum {
32 DIRECTION = 1
34 static void tox_session_set_property(GObject *obj, guint property_id, const GValue *value, GParamSpec *pspec);
36 /* signals */
37 enum {
38 NEW_NATIVE_CANDIDATE,
39 NATIVE_CANDIDATES_PREPARED,
40 LAST_SIGNAL
43 static guint signals[LAST_SIGNAL];
45 #include "tox-session-glue.h"
47 static GstElement *prepare_source(const char *filename);
48 static void new_pad(GstElement *, GstPad *, gpointer);
49 static GstElement *prepare_sink(void);
50 static void stream_done(ToxSession *);
52 static GValueArray * candidate_list_to_dbus_array(const GList *candidates);
54 static void tox_session_native_candidates_prepared(FarsightStream *stream, gpointer user_data);
55 static void tox_session_new_native_candidate(FarsightStream *stream, gchar *candidate_id, ToxSession *self);
57 void
58 tox_session_init(ToxSession *obj)
60 ToxSessionPrivate *priv;
61 priv = g_new0(ToxSessionPrivate, 1);
62 obj->priv = priv;
64 priv->session = farsight_session_factory_make("rtp");
65 g_assert(priv->session);
67 /* we need to know the direction to create a stream,
68 so we do that when that property is set.
73 static GObjectClass *parent_class = NULL;
75 static void
76 tox_session_dispose(GObject *obj)
78 ToxSession *self = (ToxSession *)obj;
80 if (self->priv->dispose_has_run) {
81 return;
83 g_debug("in tox_session_dispose\n");
84 self->priv->dispose_has_run = TRUE;
86 if (self->priv->stream)
87 g_object_unref(self->priv->stream);
88 if (self->priv->session)
89 g_object_unref(self->priv->session);
91 self->priv->stream = NULL;
92 self->priv->session = NULL;
94 G_OBJECT_CLASS(parent_class)->dispose(obj);
97 static void
98 tox_session_finalize(GObject *obj)
100 ToxSession *self = (ToxSession *)obj;
102 g_debug("in tox_session_finalize\n");
103 g_free(self->priv);
105 G_OBJECT_CLASS(parent_class)->finalize(obj);
108 void
109 tox_session_class_init(ToxSessionClass *klass)
111 GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
112 GParamSpec *direction_param_spec;
114 gobject_class->dispose = tox_session_dispose;
115 gobject_class->finalize = tox_session_finalize;
117 gobject_class->set_property = tox_session_set_property;
119 direction_param_spec = g_param_spec_uint("direction",
120 "stream direction",
121 "1 means send-only, 2 means receive-only, 3 means both",
125 G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE);
126 g_object_class_install_property(gobject_class,
127 DIRECTION,
128 direction_param_spec);
130 signals[NEW_NATIVE_CANDIDATE] =
131 g_signal_new("new-native-candidate",
132 G_OBJECT_CLASS_TYPE(klass),
133 G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
135 NULL, NULL,
136 tox_marshal_VOID__BOXED,
137 G_TYPE_NONE,
139 G_TYPE_VALUE_ARRAY);
141 signals[NATIVE_CANDIDATES_PREPARED] =
142 g_signal_new("native-candidates-prepared",
143 G_OBJECT_CLASS_TYPE(klass),
144 G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
146 NULL, NULL,
147 tox_marshal_VOID__BOXED,
148 G_TYPE_NONE,
150 G_TYPE_VALUE_ARRAY);
152 dbus_g_object_type_install_info(TOX_TYPE_SESSION, &dbus_glib_tox_session_object_info);
154 parent_class = g_type_class_peek_parent (klass);
157 static void
158 tox_session_set_property(GObject *obj, guint property_id, const GValue *value, GParamSpec *pspec)
160 ToxSession *self = (ToxSession *)obj;
161 guint dir;
163 switch(property_id) {
164 case DIRECTION:
165 if (self->priv->stream)
166 g_object_unref(self->priv->stream);
167 /* Now we know the direction, so we create a stream. */
168 dir = g_value_get_uint(value);
169 self->priv->stream = farsight_session_create_stream(self->priv->session,
170 FARSIGHT_MEDIA_TYPE_AUDIO,
171 dir);
172 g_assert(self->priv->stream);
174 g_object_set(G_OBJECT(self->priv->stream), "transmitter", "libjingle", NULL);
176 /* XXX: should we set null source/sink here? */
177 switch (dir) {
178 case 1:
179 /* send-only, we need no sink */
180 self->priv->have_sink = 1;
181 break;
182 case 2:
183 /* receive-only, we need no source */
184 self->priv->have_source = 1;
185 break;
188 /* start preparing native candidates */
189 g_debug("About to prepare native candidates...\n");
190 g_signal_connect(self->priv->stream, "new-native-candidate",
191 (GCallback)tox_session_new_native_candidate, (gpointer)self);
192 g_signal_connect(self->priv->stream, "native-candidates-prepared",
193 (GCallback)tox_session_native_candidates_prepared, (gpointer)self);
194 /* but we can't actually do it until we have a pipeline.
195 so, we'll call stream_done when we do. */
196 break;
197 default:
198 G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
202 gboolean
203 tox_session_destroy(ToxSession *obj, GError **error)
205 g_object_unref(obj);
206 return TRUE;
209 gboolean
210 tox_session_set_default_audio_sink(ToxSession *obj, GError **error)
212 GstElement *sink = prepare_sink();
213 farsight_stream_set_sink(obj->priv->stream, sink);
214 obj->priv->have_sink = 1;
216 if (obj->priv->have_sink && obj->priv->have_source)
217 stream_done(obj);
219 return TRUE;
222 gboolean
223 tox_session_set_ogg_vorbis_audio_source(ToxSession *obj, char *filename, GError **error)
225 GstElement *source = prepare_source(filename);
226 farsight_stream_set_source(obj->priv->stream, source);
227 obj->priv->have_source = 1;
229 if (obj->priv->have_sink && obj->priv->have_source)
230 stream_done(obj);
232 return TRUE;
235 static GstElement *
236 prepare_source(const char *filename)
238 GstElement *bin, *filesrc, *demux, *decode;
240 bin = gst_bin_new("mysource");
241 filesrc = gst_element_factory_make("filesrc", "file-source");
242 g_object_set(G_OBJECT(filesrc), "location", filename, NULL);
244 demux = gst_element_factory_make("oggdemux", "ogg-parser");
245 decode = gst_element_factory_make("vorbisdec", "vorbis-decoder");
247 gst_element_link(filesrc, demux);
248 g_signal_connect(demux, "pad-added", G_CALLBACK(new_pad), decode);
250 gst_bin_add_many(GST_BIN(bin), filesrc, demux, decode, NULL);
252 return bin;
255 static void
256 new_pad(GstElement *demux, GstPad *pad, gpointer data)
258 GstElement *decode = (GstElement*)data;
259 GstPad *decoder_pad;
261 decoder_pad = gst_element_get_pad(decode, "sink");
262 gst_pad_link(pad, decoder_pad);
264 gst_object_unref(decoder_pad);
267 static GstElement *
268 prepare_sink(void)
270 GstElement *bin, *converter, *audiosink;
272 bin = gst_bin_new("mysink");
273 converter = gst_element_factory_make("audioconvert", "converter");
274 audiosink = gst_element_factory_make("autoaudiosink", "audiosink");
275 gst_element_link(converter, audiosink);
277 gst_bin_add_many(GST_BIN(bin), converter, audiosink, NULL);
279 return bin;
282 static void
283 stream_done(ToxSession *self)
285 farsight_stream_prepare_transports(self->priv->stream);
288 gboolean
289 tox_session_add_remote_candidate(ToxSession *self, GPtrArray *candidate, GError **error)
291 int i;
292 guint n;
293 GList *candidate_list;
295 candidate_list = NULL;
297 /* Here we convert the array of structs into a GList of
298 FarsightTransportInfo. The argument list is described in
299 tox-session.xml. */
300 for (i = 0; i < candidate->len; i++) {
301 GValueArray *component;
302 FarsightTransportInfo *info;
303 gchar *s;
305 component = g_ptr_array_index(candidate, i);
306 info = g_new0(FarsightTransportInfo, 1);
308 info->candidate_id =
309 g_value_dup_string(
310 g_value_array_get_nth(component, 0));
311 info->component =
312 g_value_get_uint(
313 g_value_array_get_nth(component, 1));
314 info->ip =
315 g_value_dup_string(
316 g_value_array_get_nth(component, 2));
317 info->port =
318 g_value_get_uint(
319 g_value_array_get_nth(component, 3));
321 s = g_value_dup_string(g_value_array_get_nth(component, 4));
322 if (strcmp(s, "tcp") == 0)
323 info->proto = FARSIGHT_NETWORK_PROTOCOL_TCP;
324 else if (strcmp(s, "udp") == 0)
325 info->proto = FARSIGHT_NETWORK_PROTOCOL_UDP;
326 else {
327 g_set_error(error, DBUS_GERROR,
328 DBUS_GERROR_REMOTE_EXCEPTION,
329 "Unexpected protocol '%s'",
331 g_free(s);
332 g_free(info);
333 goto fail;
335 g_free(s);
337 /* this should be "RTP" */
338 info->proto_subtype =
339 g_value_dup_string(
340 g_value_array_get_nth(component, 5));
341 /* this should be "AVP" */
342 info->proto_profile =
343 g_value_dup_string(
344 g_value_array_get_nth(component, 6));
346 info->preference =
347 g_value_get_uint(
348 g_value_array_get_nth(component, 7))
349 * 0.01;
351 n = g_value_get_uint(g_value_array_get_nth(component, 8));
352 switch (n) {
353 case 0:
354 info->type = FARSIGHT_CANDIDATE_TYPE_LOCAL;
355 break;
356 case 1:
357 info->type = FARSIGHT_CANDIDATE_TYPE_DERIVED;
358 break;
359 case 2:
360 info->type = FARSIGHT_CANDIDATE_TYPE_RELAY;
361 break;
362 default:
363 g_set_error(error, DBUS_GERROR,
364 DBUS_GERROR_REMOTE_EXCEPTION,
365 "Unexpected type %u",
367 g_free(info);
368 goto fail;
371 info->username = g_value_dup_string(
372 g_value_array_get_nth(component, 9));
373 info->password = g_value_dup_string(
374 g_value_array_get_nth(component, 10));
376 candidate_list = g_list_append(candidate_list, info);
379 farsight_stream_add_remote_candidate(self->priv->stream, candidate_list);
380 return TRUE;
382 fail:
383 farsight_transport_list_destroy(candidate_list);
384 return FALSE;
387 gboolean
388 tox_session_set_remote_codecs(ToxSession *obj, GPtrArray *codecs, GError **error)
390 GList *codec_list;
391 int i;
392 /* GList *j; */
394 codec_list = NULL;
396 for (i = 0; i < codecs->len; i++) {
397 GValueArray *codec_in;
398 FarsightCodec *codec_out;
400 codec_in = g_ptr_array_index(codecs, i);
401 codec_out = g_new0(FarsightCodec, 1);
403 codec_out->id = g_value_get_int(g_value_array_get_nth(codec_in, 0));
404 codec_out->encoding_name = g_value_dup_string(g_value_array_get_nth(codec_in, 1));
405 /* maybe check range of media_type... */
406 codec_out->media_type = g_value_get_int(g_value_array_get_nth(codec_in, 2));
407 codec_out->clock_rate = g_value_get_uint(g_value_array_get_nth(codec_in, 3));
408 codec_out->channels = g_value_get_uint(g_value_array_get_nth(codec_in, 4));
410 codec_list = g_list_append(codec_list, codec_out);
413 farsight_stream_set_remote_candidate_list(obj->priv->stream, codec_list);
415 /* should the elements be freed, or just the list itself? */
416 /*for (j = codec_list; j; j = g_list_next(j)) {
417 g_free(j->data);
419 g_list_free(codec_list);
421 return TRUE;
424 gboolean
425 tox_session_get_local_codecs(ToxSession *obj, GPtrArray **codecs, GError **error)
427 FarsightStream *stream;
428 const GList *codec_list;
430 GType hash_string_string;
432 stream = obj->priv->stream;
433 codec_list = farsight_stream_get_local_codecs(stream);
435 hash_string_string = dbus_g_type_get_map("GHashTable", G_TYPE_STRING, G_TYPE_STRING);
437 *codecs = g_ptr_array_sized_new(g_list_length(codec_list));
438 for (; codec_list; codec_list = g_list_next(codec_list)) {
439 GValueArray *codec_struct;
440 const FarsightCodec *codec;
441 GValue value;
442 GHashTable *parameters;
443 GList *p;
445 memset(&value, 0, sizeof value);
447 codec = (const FarsightCodec*)codec_list->data;
448 codec_struct = g_value_array_new(2);
450 g_value_init(&value, G_TYPE_INT);
451 g_value_set_int(&value, codec->id);
452 g_value_array_append(codec_struct, &value);
453 g_value_unset(&value);
455 g_value_init(&value, G_TYPE_STRING);
456 g_value_set_string(&value, codec->encoding_name);
457 g_value_array_append(codec_struct, &value);
458 g_value_unset(&value);
460 /* XXX: why is this the same as id? */
461 g_value_init(&value, G_TYPE_UCHAR);
462 g_value_set_uchar(&value, codec->media_type);
463 g_value_array_append(codec_struct, &value);
464 g_value_unset(&value);
466 g_value_init(&value, G_TYPE_UINT);
467 g_value_set_uint(&value, codec->clock_rate);
468 g_value_array_append(codec_struct, &value);
469 g_value_unset(&value);
471 g_value_init(&value, G_TYPE_UINT);
472 g_value_set_uint(&value, codec->channels);
473 g_value_array_append(codec_struct, &value);
474 g_value_unset(&value);
476 /* optional parameters - this should be a hash table, I think */
477 /* parameters = g_hash_table_new(g_str_hash, g_str_equal); */
478 parameters = dbus_g_type_specialized_construct(hash_string_string);
479 for (p = codec->optional_params; p; p = g_list_next(p)) {
480 FarsightCodecParameter *param = p->data;
482 g_hash_table_insert(parameters, param->name, param->value);
484 g_value_init(&value, hash_string_string);
485 g_value_set_boxed(&value, parameters);
486 g_value_array_append(codec_struct, &value);
487 g_value_unset(&value);
488 g_hash_table_unref(parameters);
490 g_assert(codec_struct->n_values == 6);
492 g_ptr_array_add(*codecs, codec_struct);
494 g_debug("Local codec: %s\n", codec->encoding_name);
497 return TRUE;
500 gboolean
501 tox_session_get_codec_intersection(ToxSession *obj, GPtrArray **codecs, GError **error)
503 FarsightStream *stream;
504 GList *codec_list;
506 stream = obj->priv->stream;
507 codec_list = farsight_stream_get_codec_intersection(stream);
509 *codecs = g_ptr_array_sized_new(g_list_length(codec_list));
510 for (; codec_list; codec_list = g_list_next(codec_list)) {
511 GValueArray *codec_struct;
512 FarsightCodec *codec;
513 GValue *value;
515 codec = (FarsightCodec*)codec_list->data;
516 codec_struct = g_value_array_new(6);
518 value = g_new(GValue, 1);
519 g_value_init(value, G_TYPE_INT);
520 g_value_set_int(value, codec->id);
521 g_value_array_append(codec_struct, value);
523 value = g_new(GValue, 1);
524 g_value_init(value, G_TYPE_STRING);
525 g_value_set_string(value, codec->encoding_name);
526 g_value_array_append(codec_struct, value);
528 value = g_new(GValue, 1);
529 g_value_init(value, G_TYPE_UCHAR);
530 g_value_set_uchar(value, codec->media_type);
531 g_value_array_append(codec_struct, value);
533 value = g_new(GValue, 1);
534 g_value_init(value, G_TYPE_UINT);
535 g_value_set_uint(value, codec->clock_rate);
536 g_value_array_append(codec_struct, value);
538 value = g_new(GValue, 1);
539 g_value_init(value, G_TYPE_UINT);
540 g_value_set_uint(value, codec->channels);
541 g_value_array_append(codec_struct, value);
543 /* XXX: do something about optional parameters */
544 value = g_new(GValue, 1);
545 g_value_init(value, G_TYPE_HASH_TABLE);
546 g_value_set_boxed(value, g_hash_table_new(g_str_hash, g_str_equal));
547 g_value_array_append(codec_struct, value);
549 g_assert(codec_struct->n_values == 6);
551 g_ptr_array_add(*codecs, codec_struct);
554 return TRUE;
557 static void
558 tox_session_native_candidates_prepared(FarsightStream *stream, gpointer user_data)
560 ToxSession *self = (ToxSession *)user_data;
561 const GList *candidates;
562 GValueArray *array;
564 candidates = farsight_stream_get_native_candidate_list(stream);
566 array = candidate_list_to_dbus_array(candidates);
568 g_debug("Sending signal NativeCandidatesPrepared!\n");
569 g_signal_emit(self, signals[NATIVE_CANDIDATES_PREPARED], 0, array);
572 static void
573 tox_session_new_native_candidate(FarsightStream *stream, gchar *candidate_id, ToxSession *self)
575 GList *candidate =
576 farsight_stream_get_native_candidate (stream, candidate_id);
577 FarsightTransportInfo *trans = candidate->data;
578 GValueArray *array;
580 g_debug ("tox_session_new_native_candidate: New native candidate"
581 " with %d components, the first being: "
582 "<id: %s, "
583 "component: %d, "
584 "ip: %s port: %d "
585 "proto: %d, "
586 "proto_subtype: %s, "
587 "proto_profile: %s, "
588 "preference: %f, "
589 "type: %d "
590 "username: %s password: %s>",
591 g_list_length(candidate),
592 trans->candidate_id, trans->component,
593 trans->ip, trans->port, trans->proto, trans->proto_subtype,
594 trans->proto_profile, trans->preference,
595 trans->type, trans->username, trans->password);
597 array = candidate_list_to_dbus_array(candidate);
598 g_debug("Sending signal NewNativeCandidate!\n");
599 g_signal_emit(self, signals[NEW_NATIVE_CANDIDATE], 0, array);
602 static GValueArray *
603 candidate_list_to_dbus_array(const GList *candidates)
605 GValueArray *array;
607 array = g_value_array_new(1);
609 for (; candidates; candidates = g_list_next(candidates)) {
610 GValueArray *candidate;
611 FarsightTransportInfo *info;
612 GValue value;
614 info = (FarsightTransportInfo*)candidates->data;
615 candidate = g_value_array_new(11);
617 memset(&value, 0, sizeof value);
619 g_value_init(&value, G_TYPE_STRING);
620 g_value_set_string(&value, info->candidate_id);
621 g_value_array_append(candidate, &value);
622 g_value_unset(&value);
624 g_value_init(&value, G_TYPE_UINT);
625 g_value_set_uint(&value, info->component);
626 g_value_array_append(candidate, &value);
627 g_value_unset(&value);
629 g_value_init(&value, G_TYPE_STRING);
630 g_value_set_string(&value, info->ip);
631 g_value_array_append(candidate, &value);
632 g_value_unset(&value);
634 g_value_init(&value, G_TYPE_UINT);
635 g_value_set_uint(&value, info->port);
636 g_value_array_append(candidate, &value);
637 g_value_unset(&value);
639 g_value_init(&value, G_TYPE_STRING);
640 switch(info->proto) {
641 case FARSIGHT_NETWORK_PROTOCOL_UDP:
642 g_value_set_static_string(&value, "udp");
643 break;
644 case FARSIGHT_NETWORK_PROTOCOL_TCP:
645 g_value_set_static_string(&value, "tcp");
646 break;
647 default:
648 g_error("Unknown protocol value %u\n", info->proto);
650 g_value_array_append(candidate, &value);
651 g_value_unset(&value);
653 g_value_init(&value, G_TYPE_STRING);
654 g_value_set_string(&value, info->proto_subtype);
655 g_value_array_append(candidate, &value);
656 g_value_unset(&value);
658 g_value_init(&value, G_TYPE_STRING);
659 g_value_set_string(&value, info->proto_profile);
660 g_value_array_append(candidate, &value);
661 g_value_unset(&value);
663 g_value_init(&value, G_TYPE_UINT);
664 g_value_set_uint(&value, (guint)(info->preference * 100));
665 g_value_array_append(candidate, &value);
666 g_value_unset(&value);
668 g_value_init(&value, G_TYPE_UINT);
669 switch(info->type) {
670 case FARSIGHT_CANDIDATE_TYPE_LOCAL:
671 g_value_set_uint(&value, 0);
672 break;
673 case FARSIGHT_CANDIDATE_TYPE_DERIVED:
674 g_value_set_uint(&value, 1);
675 break;
676 case FARSIGHT_CANDIDATE_TYPE_RELAY:
677 g_value_set_uint(&value, 2);
678 break;
679 default:
680 g_error("Unknown candidate type %u\n", info->proto);
682 g_value_array_append(candidate, &value);
683 g_value_unset(&value);
685 g_value_init(&value, G_TYPE_STRING);
686 g_value_set_string(&value, info->username);
687 g_value_array_append(candidate, &value);
688 g_value_unset(&value);
690 g_value_init(&value, G_TYPE_STRING);
691 g_value_set_string(&value, info->password);
692 g_value_array_append(candidate, &value);
693 g_value_unset(&value);
695 g_assert(candidate->n_values == 11);
697 g_value_init(&value, G_TYPE_VALUE_ARRAY);
698 /* apparently GValueArray is a "boxed" type */
699 g_value_set_boxed(&value, candidate);
700 g_value_array_append(array, &value);
701 g_value_unset(&value);
704 return array;