#include #include #include #include #include #include #include "tox-session.h" #include "tox-marshal.h" G_DEFINE_TYPE(ToxSession, tox_session, G_TYPE_OBJECT) typedef struct _ToxSessionPrivate { FarsightSession *session; /* for now, one stream is enough */ FarsightStream *stream; int have_source, have_sink; gboolean dispose_has_run; } ToxSessionPrivate; gboolean tox_session_destroy(ToxSession *obj, GError **error); gboolean tox_session_set_default_audio_sink(ToxSession *obj, GError **error); gboolean tox_session_set_ogg_vorbis_audio_source(ToxSession *obj, char *filename, GError **error); gboolean tox_session_add_remote_candidate(ToxSession *obj, GPtrArray *candidate, GError **error); gboolean tox_session_set_remote_codecs(ToxSession *obj, GPtrArray *codecs, GError **error); gboolean tox_session_get_local_codecs(ToxSession *obj, GPtrArray **codecs, GError **error); gboolean tox_session_get_codec_intersection(ToxSession *obj, GPtrArray **codecs, GError **error); /* properties */ enum { DIRECTION = 1 }; static void tox_session_set_property(GObject *obj, guint property_id, const GValue *value, GParamSpec *pspec); /* signals */ enum { NEW_NATIVE_CANDIDATE, NATIVE_CANDIDATES_PREPARED, STATE_CHANGED, NEW_ACTIVE_CANDIDATE_PAIR, LAST_SIGNAL }; static guint signals[LAST_SIGNAL]; #include "tox-session-glue.h" static GstElement *prepare_source(const char *filename); static void new_pad(GstElement *, GstPad *, gpointer); static GstElement *prepare_sink(void); static void stream_done(ToxSession *); static GValueArray * candidate_list_to_dbus_array(const GList *candidates); static void tox_session_native_candidates_prepared(FarsightStream *stream, gpointer user_data); static void tox_session_new_native_candidate(FarsightStream *stream, gchar *candidate_id, ToxSession *self); static void tox_session_state_changed(FarsightStream *stream, gint state, gint direction, ToxSession *self); static void tox_session_new_active_candidate_pair(FarsightStream *stream, gchar *native_candidate_id, gchar *remote_candidate_id, ToxSession *self); void tox_session_init(ToxSession *obj) { ToxSessionPrivate *priv; priv = g_new0(ToxSessionPrivate, 1); obj->priv = priv; priv->session = farsight_session_factory_make("rtp"); g_assert(priv->session); /* we need to know the direction to create a stream, so we do that when that property is set. */ } static GObjectClass *parent_class = NULL; static void tox_session_dispose(GObject *obj) { ToxSession *self = (ToxSession *)obj; if (self->priv->dispose_has_run) { return; } g_debug("in tox_session_dispose\n"); self->priv->dispose_has_run = TRUE; if (self->priv->stream) g_object_unref(self->priv->stream); if (self->priv->session) g_object_unref(self->priv->session); self->priv->stream = NULL; self->priv->session = NULL; G_OBJECT_CLASS(parent_class)->dispose(obj); } static void tox_session_finalize(GObject *obj) { ToxSession *self = (ToxSession *)obj; g_debug("in tox_session_finalize\n"); g_free(self->priv); G_OBJECT_CLASS(parent_class)->finalize(obj); } void tox_session_class_init(ToxSessionClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS(klass); GParamSpec *direction_param_spec; gobject_class->dispose = tox_session_dispose; gobject_class->finalize = tox_session_finalize; gobject_class->set_property = tox_session_set_property; direction_param_spec = g_param_spec_uint("direction", "stream direction", "1 means send-only, 2 means receive-only, 3 means both", 1, 3, 1, G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE); g_object_class_install_property(gobject_class, DIRECTION, direction_param_spec); signals[NEW_NATIVE_CANDIDATE] = g_signal_new("new-native-candidate", G_OBJECT_CLASS_TYPE(klass), G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, 0, NULL, NULL, tox_marshal_VOID__BOXED, G_TYPE_NONE, 1, G_TYPE_VALUE_ARRAY); signals[NATIVE_CANDIDATES_PREPARED] = g_signal_new("native-candidates-prepared", G_OBJECT_CLASS_TYPE(klass), G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, 0, NULL, NULL, tox_marshal_VOID__BOXED, G_TYPE_NONE, 1, G_TYPE_VALUE_ARRAY); signals[STATE_CHANGED] = g_signal_new("state-changed", G_OBJECT_CLASS_TYPE(klass), G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, 0, NULL, NULL, tox_marshal_VOID__UCHAR_UCHAR, G_TYPE_NONE, 2, G_TYPE_UCHAR, G_TYPE_UCHAR); signals[NEW_ACTIVE_CANDIDATE_PAIR] = g_signal_new("new-active-candidate-pair", G_OBJECT_CLASS_TYPE(klass), G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, 0, NULL, NULL, tox_marshal_VOID__STRING_STRING, G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING); dbus_g_object_type_install_info(TOX_TYPE_SESSION, &dbus_glib_tox_session_object_info); parent_class = g_type_class_peek_parent (klass); } static void tox_session_set_property(GObject *obj, guint property_id, const GValue *value, GParamSpec *pspec) { ToxSession *self = (ToxSession *)obj; guint dir; switch(property_id) { case DIRECTION: if (self->priv->stream) g_object_unref(self->priv->stream); /* Now we know the direction, so we create a stream. */ dir = g_value_get_uint(value); self->priv->stream = farsight_session_create_stream(self->priv->session, FARSIGHT_MEDIA_TYPE_AUDIO, dir); g_assert(self->priv->stream); g_object_set(G_OBJECT(self->priv->stream), "transmitter", "libjingle", NULL); /* XXX: should we set null source/sink here? */ switch (dir) { case 1: /* send-only, we need no sink */ self->priv->have_sink = 1; break; case 2: /* receive-only, we need no source */ self->priv->have_source = 1; break; } /* start preparing native candidates */ g_debug("About to prepare native candidates...\n"); g_signal_connect(self->priv->stream, "new-native-candidate", (GCallback)tox_session_new_native_candidate, (gpointer)self); g_signal_connect(self->priv->stream, "native-candidates-prepared", (GCallback)tox_session_native_candidates_prepared, (gpointer)self); /* but we can't actually do it until we have a pipeline. so, we'll call stream_done when we do. */ /* Other signals we want to forward */ g_signal_connect(self->priv->stream, "state-changed", (GCallback)tox_session_state_changed, (gpointer)self); g_signal_connect(self->priv->stream, "new-active-candidate-pair", (GCallback)tox_session_new_active_candidate_pair, (gpointer)self); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec); } } gboolean tox_session_destroy(ToxSession *obj, GError **error) { g_object_unref(obj); return TRUE; } gboolean tox_session_set_default_audio_sink(ToxSession *obj, GError **error) { GstElement *sink = prepare_sink(); farsight_stream_set_sink(obj->priv->stream, sink); obj->priv->have_sink = 1; if (obj->priv->have_sink && obj->priv->have_source) stream_done(obj); return TRUE; } gboolean tox_session_set_ogg_vorbis_audio_source(ToxSession *obj, char *filename, GError **error) { GstElement *source = prepare_source(filename); farsight_stream_set_source(obj->priv->stream, source); obj->priv->have_source = 1; if (obj->priv->have_sink && obj->priv->have_source) stream_done(obj); return TRUE; } static GstElement * prepare_source(const char *filename) { GstElement *bin, *filesrc, *demux, *decode; bin = gst_bin_new("mysource"); filesrc = gst_element_factory_make("filesrc", "file-source"); g_object_set(G_OBJECT(filesrc), "location", filename, NULL); demux = gst_element_factory_make("oggdemux", "ogg-parser"); decode = gst_element_factory_make("vorbisdec", "vorbis-decoder"); gst_element_link(filesrc, demux); g_signal_connect(demux, "pad-added", G_CALLBACK(new_pad), decode); gst_bin_add_many(GST_BIN(bin), filesrc, demux, decode, NULL); return bin; } static void new_pad(GstElement *demux, GstPad *pad, gpointer data) { GstElement *decode = (GstElement*)data; GstPad *decoder_pad; decoder_pad = gst_element_get_pad(decode, "sink"); gst_pad_link(pad, decoder_pad); gst_object_unref(decoder_pad); } static GstElement * prepare_sink(void) { GstElement *bin, *converter, *audiosink; bin = gst_bin_new("mysink"); converter = gst_element_factory_make("audioconvert", "converter"); audiosink = gst_element_factory_make("autoaudiosink", "audiosink"); gst_element_link(converter, audiosink); gst_bin_add_many(GST_BIN(bin), converter, audiosink, NULL); return bin; } static void stream_done(ToxSession *self) { farsight_stream_prepare_transports(self->priv->stream); } gboolean tox_session_add_remote_candidate(ToxSession *self, GPtrArray *candidate, GError **error) { int i; guint n; GList *candidate_list; candidate_list = NULL; /* Here we convert the array of structs into a GList of FarsightTransportInfo. The argument list is described in tox-session.xml. */ for (i = 0; i < candidate->len; i++) { GValueArray *component; FarsightTransportInfo *info; gchar *s; component = g_ptr_array_index(candidate, i); info = g_new0(FarsightTransportInfo, 1); info->candidate_id = g_value_dup_string( g_value_array_get_nth(component, 0)); info->component = g_value_get_uint( g_value_array_get_nth(component, 1)); info->ip = g_value_dup_string( g_value_array_get_nth(component, 2)); info->port = g_value_get_uint( g_value_array_get_nth(component, 3)); s = g_value_dup_string(g_value_array_get_nth(component, 4)); if (strcmp(s, "tcp") == 0) info->proto = FARSIGHT_NETWORK_PROTOCOL_TCP; else if (strcmp(s, "udp") == 0) info->proto = FARSIGHT_NETWORK_PROTOCOL_UDP; else { g_set_error(error, DBUS_GERROR, DBUS_GERROR_REMOTE_EXCEPTION, "Unexpected protocol '%s'", s); g_free(s); g_free(info); goto fail; } g_free(s); /* this should be "RTP" */ info->proto_subtype = g_value_dup_string( g_value_array_get_nth(component, 5)); /* this should be "AVP" */ info->proto_profile = g_value_dup_string( g_value_array_get_nth(component, 6)); info->preference = g_value_get_uint( g_value_array_get_nth(component, 7)) * 0.01; n = g_value_get_uint(g_value_array_get_nth(component, 8)); switch (n) { case 0: info->type = FARSIGHT_CANDIDATE_TYPE_LOCAL; break; case 1: info->type = FARSIGHT_CANDIDATE_TYPE_DERIVED; break; case 2: info->type = FARSIGHT_CANDIDATE_TYPE_RELAY; break; default: g_set_error(error, DBUS_GERROR, DBUS_GERROR_REMOTE_EXCEPTION, "Unexpected type %u", n); g_free(info); goto fail; } info->username = g_value_dup_string( g_value_array_get_nth(component, 9)); info->password = g_value_dup_string( g_value_array_get_nth(component, 10)); candidate_list = g_list_append(candidate_list, info); } farsight_stream_add_remote_candidate(self->priv->stream, candidate_list); return TRUE; fail: farsight_transport_list_destroy(candidate_list); return FALSE; } gboolean tox_session_set_remote_codecs(ToxSession *obj, GPtrArray *codecs, GError **error) { GList *codec_list; int i; /* GList *j; */ codec_list = NULL; for (i = 0; i < codecs->len; i++) { GValueArray *codec_in; FarsightCodec *codec_out; codec_in = g_ptr_array_index(codecs, i); codec_out = g_new0(FarsightCodec, 1); codec_out->id = g_value_get_int(g_value_array_get_nth(codec_in, 0)); codec_out->encoding_name = g_value_dup_string(g_value_array_get_nth(codec_in, 1)); /* maybe check range of media_type... */ codec_out->media_type = g_value_get_uchar(g_value_array_get_nth(codec_in, 2)); codec_out->clock_rate = g_value_get_uint(g_value_array_get_nth(codec_in, 3)); codec_out->channels = g_value_get_uint(g_value_array_get_nth(codec_in, 4)); codec_list = g_list_append(codec_list, codec_out); } farsight_stream_set_remote_codecs(obj->priv->stream, codec_list); /* should the elements be freed, or just the list itself? */ /*for (j = codec_list; j; j = g_list_next(j)) { g_free(j->data); }*/ g_list_free(codec_list); return TRUE; } gboolean tox_session_get_local_codecs(ToxSession *obj, GPtrArray **codecs, GError **error) { FarsightStream *stream; const GList *codec_list; GType hash_string_string; stream = obj->priv->stream; codec_list = farsight_stream_get_local_codecs(stream); hash_string_string = dbus_g_type_get_map("GHashTable", G_TYPE_STRING, G_TYPE_STRING); *codecs = g_ptr_array_sized_new(g_list_length(codec_list)); for (; codec_list; codec_list = g_list_next(codec_list)) { GValueArray *codec_struct; const FarsightCodec *codec; GValue value; GHashTable *parameters; GList *p; memset(&value, 0, sizeof value); codec = (const FarsightCodec*)codec_list->data; codec_struct = g_value_array_new(2); g_value_init(&value, G_TYPE_INT); g_value_set_int(&value, codec->id); g_value_array_append(codec_struct, &value); g_value_unset(&value); g_value_init(&value, G_TYPE_STRING); g_value_set_string(&value, codec->encoding_name); g_value_array_append(codec_struct, &value); g_value_unset(&value); /* XXX: why is this the same as id? */ g_value_init(&value, G_TYPE_UCHAR); g_value_set_uchar(&value, codec->media_type); g_value_array_append(codec_struct, &value); g_value_unset(&value); g_value_init(&value, G_TYPE_UINT); g_value_set_uint(&value, codec->clock_rate); g_value_array_append(codec_struct, &value); g_value_unset(&value); g_value_init(&value, G_TYPE_UINT); g_value_set_uint(&value, codec->channels); g_value_array_append(codec_struct, &value); g_value_unset(&value); /* optional parameters - this should be a hash table, I think */ /* parameters = g_hash_table_new(g_str_hash, g_str_equal); */ parameters = dbus_g_type_specialized_construct(hash_string_string); for (p = codec->optional_params; p; p = g_list_next(p)) { FarsightCodecParameter *param = p->data; g_hash_table_insert(parameters, param->name, param->value); } g_value_init(&value, hash_string_string); g_value_set_boxed(&value, parameters); g_value_array_append(codec_struct, &value); g_value_unset(&value); g_hash_table_unref(parameters); g_assert(codec_struct->n_values == 6); g_ptr_array_add(*codecs, codec_struct); g_debug("Local codec: %s\n", codec->encoding_name); } return TRUE; } gboolean tox_session_get_codec_intersection(ToxSession *obj, GPtrArray **codecs, GError **error) { FarsightStream *stream; GList *codec_list; stream = obj->priv->stream; codec_list = farsight_stream_get_codec_intersection(stream); *codecs = g_ptr_array_sized_new(g_list_length(codec_list)); for (; codec_list; codec_list = g_list_next(codec_list)) { GValueArray *codec_struct; FarsightCodec *codec; GValue *value; codec = (FarsightCodec*)codec_list->data; codec_struct = g_value_array_new(6); value = g_new0(GValue, 1); g_value_init(value, G_TYPE_INT); g_value_set_int(value, codec->id); g_value_array_append(codec_struct, value); value = g_new0(GValue, 1); g_value_init(value, G_TYPE_STRING); g_value_set_string(value, codec->encoding_name); g_value_array_append(codec_struct, value); value = g_new0(GValue, 1); g_value_init(value, G_TYPE_UCHAR); g_value_set_uchar(value, codec->media_type); g_value_array_append(codec_struct, value); value = g_new0(GValue, 1); g_value_init(value, G_TYPE_UINT); g_value_set_uint(value, codec->clock_rate); g_value_array_append(codec_struct, value); value = g_new0(GValue, 1); g_value_init(value, G_TYPE_UINT); g_value_set_uint(value, codec->channels); g_value_array_append(codec_struct, value); /* XXX: do something about optional parameters */ value = g_new0(GValue, 1); g_value_init(value, DBUS_TYPE_G_STRING_STRING_HASHTABLE); g_value_set_boxed(value, g_hash_table_new(g_str_hash, g_str_equal)); g_value_array_append(codec_struct, value); g_assert(codec_struct->n_values == 6); g_ptr_array_add(*codecs, codec_struct); } return TRUE; } static void tox_session_native_candidates_prepared(FarsightStream *stream, gpointer user_data) { ToxSession *self = (ToxSession *)user_data; const GList *candidates; GValueArray *array; candidates = farsight_stream_get_native_candidate_list(stream); array = candidate_list_to_dbus_array(candidates); g_debug("Sending signal NativeCandidatesPrepared!\n"); g_signal_emit(self, signals[NATIVE_CANDIDATES_PREPARED], 0, array); } static void tox_session_new_native_candidate(FarsightStream *stream, gchar *candidate_id, ToxSession *self) { GList *candidate = farsight_stream_get_native_candidate (stream, candidate_id); FarsightTransportInfo *trans = candidate->data; GValueArray *array; g_debug ("tox_session_new_native_candidate: New native candidate" " with %d components, the first being: " "", g_list_length(candidate), trans->candidate_id, trans->component, trans->ip, trans->port, trans->proto, trans->proto_subtype, trans->proto_profile, trans->preference, trans->type, trans->username, trans->password); array = candidate_list_to_dbus_array(candidate); g_debug("Sending signal NewNativeCandidate!\n"); g_signal_emit(self, signals[NEW_NATIVE_CANDIDATE], 0, array); } static GValueArray * candidate_list_to_dbus_array(const GList *candidates) { GValueArray *array; array = g_value_array_new(1); for (; candidates; candidates = g_list_next(candidates)) { GValueArray *candidate; FarsightTransportInfo *info; GValue value; info = (FarsightTransportInfo*)candidates->data; candidate = g_value_array_new(11); memset(&value, 0, sizeof value); g_value_init(&value, G_TYPE_STRING); g_value_set_string(&value, info->candidate_id); g_value_array_append(candidate, &value); g_value_unset(&value); g_value_init(&value, G_TYPE_UINT); g_value_set_uint(&value, info->component); g_value_array_append(candidate, &value); g_value_unset(&value); g_value_init(&value, G_TYPE_STRING); g_value_set_string(&value, info->ip); g_value_array_append(candidate, &value); g_value_unset(&value); g_value_init(&value, G_TYPE_UINT); g_value_set_uint(&value, info->port); g_value_array_append(candidate, &value); g_value_unset(&value); g_value_init(&value, G_TYPE_STRING); switch(info->proto) { case FARSIGHT_NETWORK_PROTOCOL_UDP: g_value_set_static_string(&value, "udp"); break; case FARSIGHT_NETWORK_PROTOCOL_TCP: g_value_set_static_string(&value, "tcp"); break; default: g_error("Unknown protocol value %u\n", info->proto); } g_value_array_append(candidate, &value); g_value_unset(&value); g_value_init(&value, G_TYPE_STRING); g_value_set_string(&value, info->proto_subtype); g_value_array_append(candidate, &value); g_value_unset(&value); g_value_init(&value, G_TYPE_STRING); g_value_set_string(&value, info->proto_profile); g_value_array_append(candidate, &value); g_value_unset(&value); g_value_init(&value, G_TYPE_UINT); g_value_set_uint(&value, (guint)(info->preference * 100)); g_value_array_append(candidate, &value); g_value_unset(&value); g_value_init(&value, G_TYPE_UINT); switch(info->type) { case FARSIGHT_CANDIDATE_TYPE_LOCAL: g_value_set_uint(&value, 0); break; case FARSIGHT_CANDIDATE_TYPE_DERIVED: g_value_set_uint(&value, 1); break; case FARSIGHT_CANDIDATE_TYPE_RELAY: g_value_set_uint(&value, 2); break; default: g_error("Unknown candidate type %u\n", info->proto); } g_value_array_append(candidate, &value); g_value_unset(&value); g_value_init(&value, G_TYPE_STRING); g_value_set_string(&value, info->username); g_value_array_append(candidate, &value); g_value_unset(&value); g_value_init(&value, G_TYPE_STRING); g_value_set_string(&value, info->password); g_value_array_append(candidate, &value); g_value_unset(&value); g_assert(candidate->n_values == 11); g_value_init(&value, G_TYPE_VALUE_ARRAY); /* apparently GValueArray is a "boxed" type */ g_value_set_boxed(&value, candidate); g_value_array_append(array, &value); g_value_unset(&value); } return array; } static void tox_session_state_changed(FarsightStream *stream, gint state, gint direction, ToxSession *self) { g_signal_emit(self, signals[STATE_CHANGED], 0, state, direction); } static void tox_session_new_active_candidate_pair(FarsightStream *stream, gchar *native_candidate_id, gchar *remote_candidate_id, ToxSession *self) { g_signal_emit(self, signals[NEW_ACTIVE_CANDIDATE_PAIR], 0, native_candidate_id, remote_candidate_id); }