diff options
-rw-r--r-- | src/audio.c | 265 | ||||
-rw-r--r-- | src/cmumble.c | 5 |
2 files changed, 153 insertions, 117 deletions
diff --git a/src/audio.c b/src/audio.c index b532870..e0bb6b0 100644 --- a/src/audio.c +++ b/src/audio.c @@ -14,6 +14,116 @@ #define AUDIO_CAPS "audio/x-raw,format=S16LE,channels=" \ G_STRINGIFY(CHANNELS) ",rate=" G_STRINGIFY(SAMPLERATE) +static void +set_pulse_states(const GValue *item, gpointer user_data) +{ + GstElement *elm = g_value_get_object(item); + struct cmumble_user *user = user_data; + GstStructure *props; + gchar *name; + + if (g_strcmp0(G_OBJECT_TYPE_NAME(elm), "GstPulseSink") != 0 || + g_object_class_find_property(G_OBJECT_GET_CLASS(elm), + "stream-properties") == NULL) + goto out; + + /* FIXME: Move this into a man-page or so: + * Dear User: Add the following to the pulseaudio configuration: + * load-module module-device-manager "do_routing=1" + * This is to let new join users default to e.g. a headset output. + * Also consider setting device.intended_roles = "phone" for your + * output to be marked as headset (if you dont have a usb headset dev). */ + + name = g_strdup_printf("cmumble [%s]", user->name); + + props = gst_structure_new("props", + "application.name", G_TYPE_STRING, name, + "media.role", G_TYPE_STRING, "phone", + NULL); + + g_object_set(elm, "stream-properties", props, NULL); + gst_structure_free(props); + g_free(name); + +out: + g_object_unref(G_OBJECT(elm)); +} + + +static void +add_celt_streamheader(struct cmumble *cm, GstAppSrc *src) +{ + GValue streamheader = { 0, }, val = { 0, }; + GstTagList *tags; + GstBuffer *buf[2]; + GstStructure *s; + GstCaps *caps; + gint i; + + buf[0] = gst_buffer_new_allocate(NULL, sizeof(CELTHeader), NULL); + gst_buffer_fill(buf[0], 0, cm->audio.celt_header_packet, + sizeof(CELTHeader)); + + tags = gst_tag_list_new_empty(); + buf[1] = gst_tag_list_to_vorbiscomment_buffer(tags, NULL, 0, "mumble"); + gst_tag_list_unref(tags); + + g_value_init(&streamheader, GST_TYPE_ARRAY); + for (i = 0; i < G_N_ELEMENTS(buf); ++i) { + GST_BUFFER_FLAG_SET(buf[i], GST_BUFFER_FLAG_HEADER); + GST_BUFFER_OFFSET(buf[i]) = 0; + GST_BUFFER_OFFSET_END(buf[i]) = 0; + g_value_init(&val, GST_TYPE_BUFFER); + gst_value_take_buffer(&val, buf[i]); + gst_value_array_append_value(&streamheader, &val); + g_value_unset(&val); + } + + caps = gst_app_src_get_caps(src); + caps = gst_caps_make_writable(caps); + s = gst_caps_get_structure(caps, 0); + gst_structure_set_value(s, "streamheader", &streamheader); + g_value_unset(&streamheader); + + gst_app_src_set_caps(src, caps); + gst_caps_unref(caps); +} + +int +cmumble_audio_create_playback_pipeline(struct cmumble *cm, + struct cmumble_user *user) +{ + GstElement *pipeline, *sink_bin; + GError *error = NULL; + char *desc = "appsrc name=src format=GST_FORMAT_TIME caps="CELT_CAPS" " + "! celtdec name=dec " + "! audioresample ! audioconvert ! autoaudiosink name=sink"; + GstIterator *it; + + pipeline = gst_parse_launch(desc, &error); + if (error) { + g_printerr("Failed to create pipeline: %s\n", error->message); + return -1; + } + + user->pipeline = pipeline; + user->src = GST_APP_SRC(gst_bin_get_by_name(GST_BIN(pipeline), "src")); + add_celt_streamheader(cm, user->src); + + gst_element_set_state(pipeline, GST_STATE_PLAYING); + + /* FIXME: Use a recursive name for sink-actual-sink-pluse instead? like: + * gst_bin_get_by_name(GST_BIN(pipeline), "sink-actual-sink-pulse"); */ + sink_bin = gst_bin_get_by_name(GST_BIN(pipeline), "sink"); + it = gst_bin_iterate_sinks(GST_BIN(sink_bin)); + gst_iterator_foreach(it, set_pulse_states, user); + gst_iterator_free(it); + + user->last_sequence = -1; + + return 0; +} + void cmumble_audio_push(struct cmumble *cm, struct cmumble_user *user, const guint8 *data, gsize size, gint64 sequence) @@ -23,6 +133,10 @@ cmumble_audio_push(struct cmumble *cm, struct cmumble_user *user, GstClockTime time = 0; GstClockTime base, now = 0; + if (user->src == NULL) + if (cmumble_audio_create_playback_pipeline(cm, user) < 0) + return; + if (cm->verbose) g_print("%s: sequence: %ld\n", __func__, sequence); @@ -320,115 +434,6 @@ setup_recording_gst_pipeline(struct cmumble *cm) return 0; } -static void -set_pulse_states(const GValue *item, gpointer user_data) -{ - GstElement *elm = g_value_get_object(item); - struct cmumble_user *user = user_data; - GstStructure *props; - gchar *name; - - if (g_strcmp0(G_OBJECT_TYPE_NAME(elm), "GstPulseSink") != 0 || - g_object_class_find_property(G_OBJECT_GET_CLASS(elm), - "stream-properties") == NULL) - goto out; - - /* FIXME: Move this into a man-page or so: - * Dear User: Add the following to the pulseaudio configuration: - * load-module module-device-manager "do_routing=1" - * This is to let new join users default to e.g. a headset output. - * Also consider setting device.intended_roles = "phone" for your - * output to be marked as headset (if you dont have a usb headset dev). */ - - name = g_strdup_printf("cmumble [%s]", user->name); - - props = gst_structure_new("props", - "application.name", G_TYPE_STRING, name, - "media.role", G_TYPE_STRING, "phone", - NULL); - - g_object_set(elm, "stream-properties", props, NULL); - gst_structure_free(props); - g_free(name); - -out: - g_object_unref(G_OBJECT(elm)); -} - -static void -add_celt_streamheader(struct cmumble *cm, GstAppSrc *src) -{ - GValue streamheader = { 0, }, val = { 0, }; - GstTagList *tags; - GstBuffer *buf[2]; - GstStructure *s; - GstCaps *caps; - gint i; - - buf[0] = gst_buffer_new_allocate(NULL, sizeof(CELTHeader), NULL); - gst_buffer_fill(buf[0], 0, cm->audio.celt_header_packet, - sizeof(CELTHeader)); - - tags = gst_tag_list_new_empty(); - buf[1] = gst_tag_list_to_vorbiscomment_buffer(tags, NULL, 0, "mumble"); - gst_tag_list_unref(tags); - - g_value_init(&streamheader, GST_TYPE_ARRAY); - for (i = 0; i < G_N_ELEMENTS(buf); ++i) { - GST_BUFFER_FLAG_SET(buf[i], GST_BUFFER_FLAG_HEADER); - GST_BUFFER_OFFSET(buf[i]) = 0; - GST_BUFFER_OFFSET_END(buf[i]) = 0; - g_value_init(&val, GST_TYPE_BUFFER); - gst_value_take_buffer(&val, buf[i]); - gst_value_array_append_value(&streamheader, &val); - g_value_unset(&val); - } - - caps = gst_app_src_get_caps(src); - caps = gst_caps_make_writable(caps); - s = gst_caps_get_structure(caps, 0); - gst_structure_set_value(s, "streamheader", &streamheader); - g_value_unset(&streamheader); - - gst_app_src_set_caps(src, caps); - gst_caps_unref(caps); -} - -int -cmumble_audio_create_playback_pipeline(struct cmumble *cm, - struct cmumble_user *user) -{ - GstElement *pipeline, *sink_bin; - GError *error = NULL; - char *desc = "appsrc name=src format=GST_FORMAT_TIME caps="CELT_CAPS" " - "! celtdec name=dec " - "! audioresample ! audioconvert ! autoaudiosink name=sink"; - GstIterator *it; - - pipeline = gst_parse_launch(desc, &error); - if (error) { - g_printerr("Failed to create pipeline: %s\n", error->message); - return -1; - } - - user->pipeline = pipeline; - user->src = GST_APP_SRC(gst_bin_get_by_name(GST_BIN(pipeline), "src")); - add_celt_streamheader(cm, user->src); - - gst_element_set_state(pipeline, GST_STATE_PLAYING); - - /* FIXME: Use a recursive name for sink-actual-sink-pluse instead? like: - * gst_bin_get_by_name(GST_BIN(pipeline), "sink-actual-sink-pulse"); */ - sink_bin = gst_bin_get_by_name(GST_BIN(pipeline), "sink"); - it = gst_bin_iterate_sinks(GST_BIN(sink_bin)); - gst_iterator_foreach(it, set_pulse_states, user); - gst_iterator_free(it); - - user->last_sequence = -1; - - return 0; -} - static int setup_playback_gst_pipeline(struct cmumble *cm) { @@ -456,12 +461,48 @@ cmumble_audio_init(struct cmumble *cm) return 0; } +static void +destroy_record_pipeline(struct cmumble *cm) +{ + if (cm->audio.record_pipeline == NULL) + return; + + g_source_remove(cm->audio.bus_watch_id); + if (cm->audio.silence_timestamps) + g_queue_free_full(cm->audio.silence_timestamps, g_free); + if (cm->audio.silence_timestamps) + g_queue_free_full(cm->audio.buffer_queue, (GDestroyNotify) gst_sample_unref); + + gst_element_set_state(cm->audio.record_pipeline, GST_STATE_NULL); + gst_object_unref(GST_OBJECT(cm->audio.record_pipeline)); + gst_object_unref(GST_OBJECT(cm->audio.sink)); + gst_object_unref(GST_OBJECT(cm->audio.src)); + gst_object_unref(GST_OBJECT(cm->audio.cutter)); +} + +static void +destroy_playback_pipelines(struct cmumble *cm) +{ + struct cmumble_user *user = NULL; + GList *l; + + for (l = cm->users; l; l = l->next) { + user = l->data; + if (user->pipeline == NULL || user->src == NULL) + continue; + gst_element_set_state(user->pipeline, GST_STATE_NULL); + gst_object_unref(GST_OBJECT(user->pipeline)); + user->pipeline = NULL; + gst_object_unref(GST_OBJECT(user->src)); + user->src = NULL; + } +} + int cmumble_audio_fini(struct cmumble *cm) { - g_source_remove(cm->audio.bus_watch_id); - g_queue_free_full(cm->audio.silence_timestamps, g_free); - g_queue_free_full(cm->audio.buffer_queue, (GDestroyNotify) gst_sample_unref); + destroy_record_pipeline(cm); + destroy_playback_pipelines(cm); return 0; } diff --git a/src/cmumble.c b/src/cmumble.c index 228f28b..8267834 100644 --- a/src/cmumble.c +++ b/src/cmumble.c @@ -197,11 +197,6 @@ recv_user_state(mumble_user_state_t *state, struct cmumble *cm) if (cm->session == user->session) cm->user = user; - /* FIXME: Rather than doing this ugly check by name here, - * we should rather create the pipeline ondemand? - */ - if (g_strcmp0(user->name, cm->user_name) != 0) - cmumble_audio_create_playback_pipeline(cm, user); if (cm->verbose) g_print("receive user: %s\n", user->name); cm->users = g_list_prepend(cm->users, user); |