diff options
-rw-r--r-- | src/Makefile.am | 4 | ||||
-rw-r--r-- | src/audio.c | 205 | ||||
-rw-r--r-- | src/audio.h | 40 | ||||
-rw-r--r-- | src/cmumble.c | 193 | ||||
-rw-r--r-- | src/cmumble.h | 17 |
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; |