elisp-vcs/tox/tox-session.c
2009-03-29 20:44:54 +02:00

753 lines
21 KiB
C

#include <glib.h>
#include <farsight/farsight.h>
#include <farsight/farsight-transport.h>
#include <dbus/dbus-glib.h>
#include <glib/gprintf.h>
#include <string.h>
#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: "
"<id: %s, "
"component: %d, "
"ip: %s port: %d "
"proto: %d, "
"proto_subtype: %s, "
"proto_profile: %s, "
"preference: %f, "
"type: %d "
"username: %s password: %s>",
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);
}