summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/audio.c265
-rw-r--r--src/cmumble.c5
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);