summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/Makefile.am4
-rw-r--r--src/audio.c205
-rw-r--r--src/audio.h40
-rw-r--r--src/cmumble.c193
-rw-r--r--src/cmumble.h17
5 files changed, 254 insertions, 205 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 7004c5d..3dec946 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,9 +1,9 @@
bin_PROGRAMS = cmumble
-noinst_HEADERS = cmumble.h message.h varint.h io.h connection.h
+noinst_HEADERS = cmumble.h message.h varint.h io.h connection.h audio.h
nodist_noinst_HEADERS = mumble.pb-c.h message_list.h
-cmumble_SOURCES = cmumble.c message.c varint.c io.c connection.c
+cmumble_SOURCES = cmumble.c message.c varint.c io.c connection.c audio.c
nodist_cmumble_SOURCES = mumble.pb-c.c
cmumble_LDADD = $(PROTOBUF_LIBS) $(GLIB_LIBS) $(GIO_LIBS) \
diff --git a/src/audio.c b/src/audio.c
new file mode 100644
index 0000000..519dc04
--- /dev/null
+++ b/src/audio.c
@@ -0,0 +1,205 @@
+#include "audio.h"
+#include "varint.h"
+#include "cmumble.h"
+#include <string.h>
+
+#define SAMPLERATE 48000
+#define CHANNELS 1
+
+void
+cmumble_audio_push(struct context *ctx, struct user *user,
+ const uint8_t *data, gsize size)
+{
+ GstBuffer *gstbuf;
+
+ gstbuf = gst_app_buffer_new(g_memdup(data, size), size, g_free, NULL);
+ gst_app_src_push_buffer(user->src, gstbuf);
+}
+
+static GstFlowReturn
+pull_buffer(GstAppSink *sink, gpointer user_data)
+{
+ struct context *ctx = user_data;
+ GstBuffer *buf;
+ uint8_t data[1024];
+ uint32_t write = 0, pos = 0;
+ MumbleProto__UDPTunnel tunnel;
+ static int seq = 0;
+
+ buf = gst_app_sink_pull_buffer(ctx->audio.sink);
+
+ if (++seq <= 2) {
+ gst_buffer_unref(buf);
+ return GST_FLOW_OK;
+ }
+ if (GST_BUFFER_SIZE(buf) > 127) {
+ g_printerr("GOT TOO BIG BUFFER\n");
+ return GST_FLOW_ERROR;
+ }
+
+ data[pos++] = (udp_voice_celt_alpha) | (0 << 4);
+
+ encode_varint(&data[pos], &write, ++ctx->sequence, 1024-pos);
+ pos += write;
+
+ data[pos++] = 0x00 /*: 0x80 */ | (GST_BUFFER_SIZE(buf) & 0x7F);
+ memcpy(&data[pos], GST_BUFFER_DATA(buf), GST_BUFFER_SIZE(buf));
+ pos += GST_BUFFER_SIZE(buf);
+
+ gst_buffer_unref(buf);
+
+ mumble_proto__udptunnel__init(&tunnel);
+ tunnel.packet.data = data;
+ tunnel.packet.len = pos;
+ cmumble_send_msg(ctx, &tunnel.base);
+
+ return GST_FLOW_OK;
+}
+
+static int
+setup_recording_gst_pipeline(struct context *ctx)
+{
+ GstElement *pipeline, *cutter, *sink;
+ GError *error = NULL;
+ GstCaps *caps;
+
+ char *desc = "autoaudiosrc ! cutter name=cutter ! audioresample ! audioconvert ! "
+ "audio/x-raw-int,channels=1,depth=16,rate=48000,signed=TRUE,width=16 ! "
+ "celtenc ! appsink name=sink";
+
+ pipeline = gst_parse_launch(desc, &error);
+ if (error) {
+ g_printerr("Failed to create pipeline: %s\n", error->message);
+ return -1;
+ }
+ sink = gst_bin_get_by_name(GST_BIN(pipeline), "sink");
+ ctx->audio.sink = GST_APP_SINK(sink);
+ ctx->audio.record_pipeline = pipeline;
+
+ cutter = gst_bin_get_by_name(GST_BIN(pipeline), "cutter");
+ g_object_set(G_OBJECT(cutter),
+ "threshold_dB", -45.0, "leaky", TRUE, NULL);
+
+ gst_app_sink_set_emit_signals(ctx->audio.sink, TRUE);
+ gst_app_sink_set_drop(ctx->audio.sink, FALSE);;
+ g_signal_connect(sink, "new-buffer", G_CALLBACK(pull_buffer), ctx);
+
+ caps = gst_caps_new_simple("audio/x-celt",
+ "rate", G_TYPE_INT, SAMPLERATE,
+ "channels", G_TYPE_INT, 1,
+ "frame-size", G_TYPE_INT, SAMPLERATE/100,
+ NULL);
+ gst_app_sink_set_caps(ctx->audio.sink, caps);
+ gst_caps_unref(caps);
+
+ gst_element_set_state(pipeline, GST_STATE_PLAYING);
+
+ ctx->sequence = 0;
+
+ return 0;
+}
+
+static void
+set_pulse_states(gpointer data, gpointer user_data)
+{
+ GstElement *elm = data;
+ struct 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;
+
+ /* configure pulseaudio to use:
+ * load-module module-device-manager "do_routing=1"
+ * or new users may join to default output which is not headset?
+ * Also consider setting device.intended_roles = "phone" for your
+ * wanted default output (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));
+}
+
+int
+cmumble_audio_create_playback_pipeline(struct context *ctx, struct user *user)
+{
+ GstElement *pipeline, *sink_bin;
+ GError *error = NULL;
+ char *desc = "appsrc name=src ! celtdec ! audioconvert ! autoaudiosink name=sink";
+
+ 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"));
+
+ /* Important! */
+ gst_base_src_set_live(GST_BASE_SRC(user->src), TRUE);
+ gst_base_src_set_do_timestamp(GST_BASE_SRC(user->src), TRUE);
+ gst_base_src_set_format(GST_BASE_SRC(user->src), GST_FORMAT_TIME);
+
+ gst_app_src_set_stream_type(user->src, GST_APP_STREAM_TYPE_STREAM);
+
+ gst_element_set_state(pipeline, GST_STATE_PLAYING);
+
+ sink_bin = gst_bin_get_by_name(GST_BIN(pipeline), "sink");
+ GstIterator *iter = gst_bin_iterate_sinks(GST_BIN(sink_bin));
+ gst_iterator_foreach(iter, set_pulse_states, user);
+ gst_iterator_free(iter);
+
+ /* Setup Celt Decoder */
+ cmumble_audio_push(ctx, user,
+ ctx->audio.celt_header_packet, sizeof(CELTHeader));
+ /* fake vorbiscomment buffer */
+ cmumble_audio_push(ctx, user, NULL, 0);
+
+ return 0;
+}
+
+static int
+setup_playback_gst_pipeline(struct context *ctx)
+{
+ ctx->audio.celt_mode = celt_mode_create(SAMPLERATE,
+ SAMPLERATE / 100, NULL);
+ celt_header_init(&ctx->audio.celt_header, ctx->audio.celt_mode, CHANNELS);
+ celt_header_to_packet(&ctx->audio.celt_header,
+ ctx->audio.celt_header_packet, sizeof(CELTHeader));
+
+ return 0;
+}
+
+int
+cmumble_audio_init(struct context *ctx)
+{
+ if (setup_playback_gst_pipeline(ctx) < 0)
+ return -1;
+
+ if (setup_recording_gst_pipeline(ctx) < 0)
+ return -1;
+
+ return 0;
+}
+
+int
+cmumble_audio_fini(struct context *ctx)
+{
+
+ return 0;
+}
+
diff --git a/src/audio.h b/src/audio.h
new file mode 100644
index 0000000..b31c77f
--- /dev/null
+++ b/src/audio.h
@@ -0,0 +1,40 @@
+#ifndef _AUDIO_H_
+#define _AUDIO_H_
+
+#include <glib.h>
+
+#include <gst/gst.h>
+#include <gst/app/gstappsrc.h>
+#include <gst/app/gstappsink.h>
+#include <gst/app/gstappbuffer.h>
+
+#include <celt/celt.h>
+#include <celt/celt_header.h>
+
+struct cmumble_audio {
+ GstElement *record_pipeline;
+ GstAppSink *sink;
+
+ uint8_t celt_header_packet[sizeof(CELTHeader)];
+ CELTHeader celt_header;
+ CELTMode *celt_mode;
+};
+
+struct context;
+struct user;
+
+int
+cmumble_audio_init(struct context *ctx);
+
+int
+cmumble_audio_fini(struct context *ctx);
+
+int
+cmumble_audio_create_playback_pipeline(struct context *ctx,
+ struct user *user);
+
+void
+cmumble_audio_push(struct context *ctx, struct user *user,
+ const uint8_t *data, gsize size);
+
+#endif /* _AUDIO_H_ */
diff --git a/src/cmumble.c b/src/cmumble.c
index 3a40e21..3d060ce 100644
--- a/src/cmumble.c
+++ b/src/cmumble.c
@@ -23,55 +23,6 @@ find_user(struct context *ctx, uint32_t session)
}
static void
-appsrc_push(GstAppSrc *src, const void *mem, size_t size)
-{
- GstBuffer *gstbuf;
-
- gstbuf = gst_app_buffer_new(g_memdup(mem, size), size, g_free, NULL);
- gst_app_src_push_buffer(src, gstbuf);
-}
-
-static GstFlowReturn
-pull_buffer(GstAppSink *sink, gpointer user_data)
-{
- struct context *ctx = user_data;
- GstBuffer *buf;
- uint8_t data[1024];
- uint32_t write = 0, pos = 0;
- MumbleProto__UDPTunnel tunnel;
- static int seq = 0;
-
- buf = gst_app_sink_pull_buffer(ctx->sink);
-
- if (++seq <= 2) {
- gst_buffer_unref(buf);
- return GST_FLOW_OK;
- }
- if (GST_BUFFER_SIZE(buf) > 127) {
- g_printerr("GOT TOO BIG BUFFER\n");
- return GST_FLOW_ERROR;
- }
-
- data[pos++] = (udp_voice_celt_alpha) | (0 << 4);
-
- encode_varint(&data[pos], &write, ++ctx->sequence, 1024-pos);
- pos += write;
-
- data[pos++] = 0x00 /*: 0x80 */ | (GST_BUFFER_SIZE(buf) & 0x7F);
- memcpy(&data[pos], GST_BUFFER_DATA(buf), GST_BUFFER_SIZE(buf));
- pos += GST_BUFFER_SIZE(buf);
-
- gst_buffer_unref(buf);
-
- mumble_proto__udptunnel__init(&tunnel);
- tunnel.packet.data = data;
- tunnel.packet.len = pos;
- cmumble_send_msg(ctx, &tunnel.base);
-
- return GST_FLOW_OK;
-}
-
-static void
recv_udp_tunnel(MumbleProto__UDPTunnel *tunnel, struct context *ctx)
{
int64_t session, sequence;
@@ -100,7 +51,7 @@ recv_udp_tunnel(MumbleProto__UDPTunnel *tunnel, struct context *ctx)
if (frame_len == 0 || frame_len > len-pos)
break;
- appsrc_push(user->src, &data[pos], frame_len);
+ cmumble_audio_push(ctx, user, &data[pos], frame_len);
pos += frame_len;
sequence++;
@@ -177,9 +128,6 @@ recv_user_remove(MumbleProto__UserRemove *remove, struct context *ctx)
}
}
-static int
-user_create_playback_pipeline(struct context *ctx, struct user *user);
-
static void
recv_user_state(MumbleProto__UserState *state, struct context *ctx)
{
@@ -200,7 +148,8 @@ recv_user_state(MumbleProto__UserState *state, struct context *ctx)
user->name = g_strdup(state->name);
user->user_id = state->user_id;
- user_create_playback_pipeline(ctx, user);
+
+ cmumble_audio_create_playback_pipeline(ctx, user);
g_print("receive user: %s\n", user->name);
ctx->users = g_list_prepend(ctx->users, user);
}
@@ -239,135 +188,6 @@ static const struct {
.SuggestConfig = NULL,
};
-static void
-set_pulse_states(gpointer data, gpointer user_data)
-{
- GstElement *elm = data;
- struct 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;
-
- /* configure pulseaudio to use:
- * load-module module-device-manager "do_routing=1"
- * or new users may join to default output which is not headset?
- * Also consider setting device.intended_roles = "phone" for your
- * wanted default output (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 int
-user_create_playback_pipeline(struct context *ctx, struct user *user)
-{
- GstElement *pipeline, *sink_bin;
- GError *error = NULL;
- char *desc = "appsrc name=src ! celtdec ! audioconvert ! autoaudiosink name=sink";
-
- 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"));
-
- /* Important! */
- gst_base_src_set_live(GST_BASE_SRC(user->src), TRUE);
- gst_base_src_set_do_timestamp(GST_BASE_SRC(user->src), TRUE);
- gst_base_src_set_format(GST_BASE_SRC(user->src), GST_FORMAT_TIME);
-
- gst_app_src_set_stream_type(user->src, GST_APP_STREAM_TYPE_STREAM);
-
- gst_element_set_state(pipeline, GST_STATE_PLAYING);
-
- sink_bin = gst_bin_get_by_name(GST_BIN(pipeline), "sink");
- GstIterator *iter = gst_bin_iterate_sinks(GST_BIN(sink_bin));
- gst_iterator_foreach(iter, set_pulse_states, user);
- gst_iterator_free(iter);
-
- /* Setup Celt Decoder */
- appsrc_push(user->src, ctx->celt_header_packet, sizeof(CELTHeader));
- /* fake vorbiscomment buffer */
- appsrc_push(user->src, NULL, 0);
-
- return 0;
-}
-
-static int
-setup_playback_gst_pipeline(struct context *ctx)
-{
-#define SAMPLERATE 48000
-#define CHANNELS 1
- ctx->celt_mode = celt_mode_create(SAMPLERATE,
- SAMPLERATE / 100, NULL);
- celt_header_init(&ctx->celt_header, ctx->celt_mode, CHANNELS);
- celt_header_to_packet(&ctx->celt_header,
- ctx->celt_header_packet, sizeof(CELTHeader));
-
- return 0;
-}
-
-static int
-setup_recording_gst_pipeline(struct context *ctx)
-{
- GstElement *pipeline, *cutter, *sink;
- GError *error = NULL;
- GstCaps *caps;
-
- char *desc = "autoaudiosrc ! cutter name=cutter ! audioresample ! audioconvert ! "
- "audio/x-raw-int,channels=1,depth=16,rate=48000,signed=TRUE,width=16 ! "
- "celtenc ! appsink name=sink";
-
- pipeline = gst_parse_launch(desc, &error);
- if (error) {
- g_printerr("Failed to create pipeline: %s\n", error->message);
- return -1;
- }
- sink = gst_bin_get_by_name(GST_BIN(pipeline), "sink");
- ctx->sink = GST_APP_SINK(sink);
- ctx->record_pipeline = pipeline;
-
- cutter = gst_bin_get_by_name(GST_BIN(pipeline), "cutter");
- g_object_set(G_OBJECT(cutter),
- "threshold_dB", -45.0, "leaky", TRUE, NULL);
-
- gst_app_sink_set_emit_signals(ctx->sink, TRUE);
- gst_app_sink_set_drop(ctx->sink, FALSE);;
- g_signal_connect(sink, "new-buffer", G_CALLBACK(pull_buffer), ctx);
-
- caps = gst_caps_new_simple("audio/x-celt",
- "rate", G_TYPE_INT, SAMPLERATE,
- "channels", G_TYPE_INT, 1,
- "frame-size", G_TYPE_INT, SAMPLERATE/100,
- NULL);
- gst_app_sink_set_caps(ctx->sink, caps);
- gst_caps_unref(caps);
-
- gst_element_set_state(pipeline, GST_STATE_PLAYING);
-
- ctx->sequence = 0;
-
- return 0;
-}
-
int main(int argc, char **argv)
{
char *host = "localhost";
@@ -411,12 +231,8 @@ int main(int argc, char **argv)
gst_init(&argc, &argv);
- if (setup_playback_gst_pipeline(&ctx) < 0)
+ if (cmumble_audio_init(&ctx) < 0)
return 1;
-
- if (setup_recording_gst_pipeline(&ctx) < 0)
- return 1;
-
cmumble_io_init(&ctx);
g_main_loop_run(ctx.loop);
@@ -424,6 +240,7 @@ int main(int argc, char **argv)
g_main_loop_unref(ctx.loop);
cmumble_io_fini(&ctx);
+ cmumble_audio_init(&ctx);
cmumble_connection_fini(&ctx);
return 0;
diff --git a/src/cmumble.h b/src/cmumble.h
index 5de3fb1..2ea1203 100644
--- a/src/cmumble.h
+++ b/src/cmumble.h
@@ -1,41 +1,28 @@
#ifndef _CMUMBLE_H_
#define _CMUMBLE_H_
-#include <gst/gst.h>
-#include <gst/app/gstappsrc.h>
-#include <gst/app/gstappsink.h>
-#include <gst/app/gstappbuffer.h>
-
#include <glib.h>
#include <glib-object.h>
#include <gio/gio.h>
-#include <celt/celt.h>
-#include <celt/celt_header.h>
-
#include "mumble.pb-c.h"
#include "message.h"
#include "io.h"
#include "connection.h"
+#include "audio.h"
typedef void (*callback_t)(ProtobufCMessage *msg, struct context *);
struct context {
struct cmumble_connection con;
struct cmumble_io io;
+ struct cmumble_audio audio;
const callback_t *callbacks;
GMainLoop *loop;
uint32_t session;
gboolean authenticated;
- uint8_t celt_header_packet[sizeof(CELTHeader)];
- CELTHeader celt_header;
- CELTMode *celt_mode;
-
- GstElement *record_pipeline;
- GstAppSink *sink;
-
int64_t sequence;
GList *users;