ecfbf850126324ef030318e9af9b5172783e1659 diff --git a/configure.ac b/configure.ac index 9e65684..2c613ed 100644 --- a/configure.ac +++ b/configure.ac @@ -1139,6 +1139,21 @@ AS_IF([test "x$with_speex" = "xyes" && test "x$HAVE_SPEEX" = "x0"], AM_CONDITIONAL([HAVE_SPEEX], [test "x$HAVE_SPEEX" = "x1"]) AS_IF([test "x$HAVE_SPEEX" = "x1"], AC_DEFINE([HAVE_SPEEX], 1, [Have speex])) +#### opus (optional) #### + +AC_ARG_WITH([opus], + AS_HELP_STRING([--without-opus],[Omit opus (transcoding, tunnel)])) + +AS_IF([test "x$with_opus" != "xno"], + [PKG_CHECK_MODULES(LIBOPUS, [ opus >= 1.1 ], HAVE_OPUS=1, HAVE_OPUS=0)], + HAVE_OPUS=0) + +AS_IF([test "x$with_opus" = "xyes" && test "x$HAVE_OPUS" = "x0"], + [AC_MSG_ERROR([*** opus support not found])]) + +AM_CONDITIONAL([HAVE_OPUS], [test "x$HAVE_OPUS" = "x1"]) +AS_IF([test "x$HAVE_OPUS" = "x1"], AC_DEFINE([HAVE_OPUS], 1, [Have opus])) + #### soxr (optional) #### AC_ARG_WITH([soxr], @@ -1565,6 +1580,7 @@ AS_IF([test "x$HAVE_ESOUND" = "x1" -a "x$USE_PER_USER_ESOUND_SOCKET" = "x1"], EN AS_IF([test "x$HAVE_GCOV" = "x1"], ENABLE_GCOV=yes, ENABLE_GCOV=no) AS_IF([test "x$HAVE_LIBCHECK" = "x1"], ENABLE_TESTS=yes, ENABLE_TESTS=no) AS_IF([test "x$enable_legacy_database_entry_format" != "xno"], ENABLE_LEGACY_DATABASE_ENTRY_FORMAT=yes, ENABLE_LEGACY_DATABASE_ENTRY_FORMAT=no) +AS_IF([test "x$HAVE_OPUS" = "x1"], ENABLE_OPUS=yes, ENABLE_OPUS=no) echo " ---{ $PACKAGE_NAME $VERSION }--- @@ -1622,6 +1638,7 @@ echo " Enable WebRTC echo canceller: ${ENABLE_WEBRTC} Enable gcov coverage: ${ENABLE_GCOV} Enable unit tests: ${ENABLE_TESTS} + Enable opus (transcoding): ${ENABLE_OPUS} Database tdb: ${ENABLE_TDB} gdbm: ${ENABLE_GDBM} diff --git a/src/Makefile.am b/src/Makefile.am index cef3114..fa41e52 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -969,7 +969,8 @@ libpulsecore_@PA_MAJORMINOR@_la_SOURCES = \ pulsecore/source.c pulsecore/source.h \ pulsecore/start-child.c pulsecore/start-child.h \ pulsecore/thread-mq.c pulsecore/thread-mq.h \ - pulsecore/database.h + pulsecore/database.h \ + pulsecore/transcode.c pulsecore/transcode.h libpulsecore_@PA_MAJORMINOR@_la_CFLAGS = $(AM_CFLAGS) $(SERVER_CFLAGS) $(LIBSNDFILE_CFLAGS) $(WINSOCK_CFLAGS) libpulsecore_@PA_MAJORMINOR@_la_LDFLAGS = $(AM_LDFLAGS) -avoid-version @@ -1042,6 +1043,11 @@ libpulsecore_@PA_MAJORMINOR@_la_CFLAGS += $(LIBSAMPLERATE_CFLAGS) libpulsecore_@PA_MAJORMINOR@_la_LIBADD += $(LIBSAMPLERATE_LIBS) endif +if HAVE_OPUS +libpulsecore_@PA_MAJORMINOR@_la_CFLAGS += $(LIBOPUS_CFLAGS) +libpulsecore_@PA_MAJORMINOR@_la_LIBADD += $(LIBOPUS_LIBS) +endif + # We split the foreign code off to not be annoyed by warnings we don't care about noinst_LTLIBRARIES += libpulsecore-foreign.la diff --git a/src/map-file b/src/map-file index 5159829..2d0c9ba 100644 --- a/src/map-file +++ b/src/map-file @@ -330,7 +330,9 @@ pa_stream_update_sample_rate; pa_stream_update_timing_info; pa_stream_writable_size; pa_stream_write; +pa_stream_write_compressed; pa_stream_write_ext_free; +pa_stream_write_ext_compressed_free; pa_strerror; pa_sw_cvolume_divide; pa_sw_cvolume_divide_scalar; diff --git a/src/modules/module-tunnel-sink-new.c b/src/modules/module-tunnel-sink-new.c index 7f83543..3ac6a11 100644 --- a/src/modules/module-tunnel-sink-new.c +++ b/src/modules/module-tunnel-sink-new.c @@ -39,6 +39,8 @@ #include #include #include +#include + #include "module-tunnel-sink-new-symdef.h" @@ -56,6 +58,9 @@ PA_MODULE_USAGE( "rate= " "channel_map= " "cookie=" + "compression= " + "compression_frame_size= " + "compression_bitrate= " ); #define MAX_LATENCY_USEC (200 * PA_USEC_PER_MSEC) @@ -85,6 +90,8 @@ struct userdata { char *cookie_file; char *remote_server; char *remote_sink_name; + + pa_transcode transcode; }; static const char* const valid_modargs[] = { @@ -97,6 +104,9 @@ static const char* const valid_modargs[] = { "rate", "channel_map", "cookie", + "compression", + "compression-bitrate", + "compression-frame_size", /* "reconnect", reconnect if server comes back again - unimplemented */ NULL, }; @@ -193,28 +203,67 @@ static void thread_func(void *userdata) { writable = pa_stream_writable_size(u->stream); if (writable > 0) { - pa_memchunk memchunk; - const void *p; - - pa_sink_render_full(u->sink, writable, &memchunk); - - pa_assert(memchunk.length > 0); - - /* we have new data to write */ - p = pa_memblock_acquire(memchunk.memblock); - /* TODO: Use pa_stream_begin_write() to reduce copying. */ - ret = pa_stream_write(u->stream, - (uint8_t*) p + memchunk.index, - memchunk.length, - NULL, /**< A cleanup routine for the data or NULL to request an internal copy */ - 0, /** offset */ - PA_SEEK_RELATIVE); - pa_memblock_release(memchunk.memblock); - pa_memblock_unref(memchunk.memblock); - - if (ret != 0) { - pa_log_error("Could not write data into the stream ... ret = %i", ret); - u->thread_mainloop_api->quit(u->thread_mainloop_api, TUNNEL_THREAD_FAILED_MAINLOOP); + + if(u->transcode.encoding != -1) { + pa_memchunk memchunk; + const void *p; + size_t nbBytes; + unsigned char *cbits; + + pa_sink_render_full(u->sink, u->transcode.frame_size*u->transcode.channels*u->transcode.sample_size, &memchunk); + + pa_assert(memchunk.length > 0); + pa_assert(memchunk.length >= u->transcode.frame_size*u->transcode.channels); + + + pa_log_debug("received memchunk length: %zu bytes", memchunk.length ); + /* we have new data to write */ + p = pa_memblock_acquire(memchunk.memblock); + + nbBytes = pa_transcode_encode(&u->transcode, (uint8_t*) p + memchunk.index, &cbits); + pa_log_debug("encoded length: %zu bytes", nbBytes); + + /* TODO: Use pa_stream_begin_write() to reduce copying. */ + ret = pa_stream_write_compressed(u->stream, + (uint8_t*) cbits, + nbBytes, + NULL, /**< A cleanup routine for the data or NULL to request an internal copy */ + 0, /** offset */ + PA_SEEK_RELATIVE, u->transcode.frame_size*u->transcode.channels*u->transcode.sample_size); + pa_memblock_release(memchunk.memblock); + pa_memblock_unref(memchunk.memblock); + if(nbBytes > 0) free(cbits); + + if (ret != 0) { + pa_log_error("Could not write data into the stream ... ret = %i", ret); + u->thread_mainloop_api->quit(u->thread_mainloop_api, TUNNEL_THREAD_FAILED_MAINLOOP); + } + } + else { + pa_memchunk memchunk; + const void *p; + + pa_sink_render_full(u->sink, writable, &memchunk); + + pa_assert(memchunk.length > 0); + + /* we have new data to write */ + p = pa_memblock_acquire(memchunk.memblock); + /* TODO: Use pa_stream_begin_write() to reduce copying. */ + ret = pa_stream_write(u->stream, + (uint8_t*) p + memchunk.index, + memchunk.length, + NULL, /**< A cleanup routine for the data or NULL to request an internal copy */ + 0, /** offset */ + PA_SEEK_RELATIVE); + pa_memblock_release(memchunk.memblock); + pa_memblock_unref(memchunk.memblock); + + if (ret != 0) { + pa_log_error("Could not write data into the stream ... ret = %i", ret); + u->thread_mainloop_api->quit(u->thread_mainloop_api, TUNNEL_THREAD_FAILED_MAINLOOP); + } + } } @@ -312,7 +361,25 @@ static void context_state_cb(pa_context *c, void *userdata) { pa_assert(!u->stream); proplist = tunnel_new_proplist(u); - u->stream = pa_stream_new_with_proplist(u->context, + + if(u->transcode.encoding != -1) { + + unsigned int n_formats = 1; + pa_format_info *formats[1]; + + formats[0] = pa_format_info_new(); + formats[0]->encoding = u->transcode.encoding; + pa_format_info_set_sample_format(formats[0], u->sink->sample_spec.format); + pa_format_info_set_rate(formats[0], u->sink->sample_spec.rate); + pa_format_info_set_channels(formats[0], u->sink->sample_spec.channels); + pa_format_info_set_channel_map(formats[0], &u->sink->channel_map); + pa_transcode_set_format_info(&u->transcode, formats[0]); + + u->stream = pa_stream_new_extended(u->context, stream_name, formats, n_formats, proplist); + + } + else + u->stream = pa_stream_new_with_proplist(u->context, stream_name, &u->sink->sample_spec, &u->sink->channel_map, @@ -461,6 +528,7 @@ int pa__init(pa_module *m) { pa_channel_map map; const char *remote_server = NULL; const char *sink_name = NULL; + const char *compression = NULL; char *default_sink_name = NULL; pa_assert(m); @@ -507,6 +575,24 @@ int pa__init(pa_module *m) { default_sink_name = pa_sprintf_malloc("tunnel-sink-new.%s", remote_server); sink_name = pa_modargs_get_value(ma, "sink_name", default_sink_name); + compression = pa_modargs_get_value(ma, "compression", NULL); + if (compression) { + pa_log("compression activated"); + memset(&u->transcode, 0, sizeof(pa_transcode)); + + if(strcmp(compression, "opus") == 0 && pa_transcode_supported(PA_ENCODING_OPUS)){ + + compression = pa_modargs_get_value(ma, "compression-frame_size", NULL); + if(compression) u->transcode.frame_size = atoi(compression); + compression = pa_modargs_get_value(ma, "compression-bitrate", NULL); + if(compression) u->transcode.bitrate = atoi(compression); + + pa_transcode_init(&u->transcode, PA_ENCODING_OPUS, PA_TRANSCODE_ENCODER, NULL, &ss); + + } + } + else u->transcode.encoding = -1; + pa_sink_new_data_set_name(&sink_data, sink_name); pa_sink_new_data_set_sample_spec(&sink_data, &ss); pa_sink_new_data_set_channel_map(&sink_data, &map); @@ -597,5 +683,8 @@ void pa__done(pa_module *m) { if (u->sink) pa_sink_unref(u->sink); + if(u->transcode.encoding != -1) + pa_transcode_free(&u->transcode); + pa_xfree(u); } diff --git a/src/pulse/format.h b/src/pulse/format.h index f606b3b..ecb4318 100644 --- a/src/pulse/format.h +++ b/src/pulse/format.h @@ -56,6 +56,9 @@ typedef enum pa_encoding { PA_ENCODING_MPEG2_AAC_IEC61937, /**< MPEG-2 AAC data encapsulated in IEC 61937 header/padding. \since 4.0 */ + PA_ENCODING_OPUS, + /**< Opus encoding (used for network tunnels) */ + PA_ENCODING_MAX, /**< Valid encoding types must be less than this value */ diff --git a/src/pulse/stream.c b/src/pulse/stream.c index 6fea996..3e6f712 100644 --- a/src/pulse/stream.c +++ b/src/pulse/stream.c @@ -1472,14 +1472,14 @@ int pa_stream_cancel_write( return 0; } -int pa_stream_write_ext_free( +int pa_stream_write_ext_compressed_free( pa_stream *s, const void *data, size_t length, pa_free_cb_t free_cb, void *free_cb_data, int64_t offset, - pa_seek_mode_t seek) { + pa_seek_mode_t seek, size_t decoded_length) { pa_assert(s); pa_assert(PA_REFCNT_VALUE(s) >= 1); @@ -1496,7 +1496,9 @@ int pa_stream_write_ext_free( ((const char*) data + length <= (const char*) s->write_data + pa_memblock_get_length(s->write_memblock))), PA_ERR_INVALID); PA_CHECK_VALIDITY(s->context, offset % pa_frame_size(&s->sample_spec) == 0, PA_ERR_INVALID); - PA_CHECK_VALIDITY(s->context, length % pa_frame_size(&s->sample_spec) == 0, PA_ERR_INVALID); + if(decoded_length==0) { + PA_CHECK_VALIDITY(s->context, length % pa_frame_size(&s->sample_spec) == 0, PA_ERR_INVALID); + } PA_CHECK_VALIDITY(s->context, !free_cb || !s->write_memblock, PA_ERR_INVALID); if (s->write_memblock) { @@ -1558,6 +1560,10 @@ int pa_stream_write_ext_free( free_cb(free_cb_data); } + /* use uncompressed length in the following */ + if(decoded_length != 0) + length = decoded_length; + /* This is obviously wrong since we ignore the seeking index . But * that's OK, the server side applies the same error */ s->requested_bytes -= (seek == PA_SEEK_RELATIVE ? offset : 0) + (int64_t) length; @@ -1602,6 +1608,18 @@ int pa_stream_write_ext_free( return 0; } +int pa_stream_write_ext_free( + pa_stream *s, + const void *data, + size_t length, + pa_free_cb_t free_cb, + void *free_cb_data, + int64_t offset, + pa_seek_mode_t seek) { + + return pa_stream_write_ext_compressed_free(s, data, length, free_cb, (void*) data, offset, seek, 0); +} + int pa_stream_write( pa_stream *s, const void *data, @@ -1610,7 +1628,19 @@ int pa_stream_write( int64_t offset, pa_seek_mode_t seek) { - return pa_stream_write_ext_free(s, data, length, free_cb, (void*) data, offset, seek); + return pa_stream_write_ext_compressed_free(s, data, length, free_cb, (void*) data, offset, seek, 0); +} + +int pa_stream_write_compressed( + pa_stream *s, + const void *data, + size_t length, + pa_free_cb_t free_cb, + int64_t offset, + pa_seek_mode_t seek, + size_t decoded_length) { + + return pa_stream_write_ext_compressed_free(s, data, length, free_cb, (void*) data, offset, seek, decoded_length); } int pa_stream_peek(pa_stream *s, const void **data, size_t *length) { diff --git a/src/pulse/stream.h b/src/pulse/stream.h index 70fa415..e931894 100644 --- a/src/pulse/stream.h +++ b/src/pulse/stream.h @@ -552,8 +552,27 @@ int pa_stream_write( int64_t offset, /**< Offset for seeking, must be 0 for upload streams */ pa_seek_mode_t seek /**< Seek mode, must be PA_SEEK_RELATIVE for upload streams */); +int pa_stream_write_compressed( + pa_stream *s, + const void *data, + size_t length, + pa_free_cb_t free_cb, + int64_t offset, + pa_seek_mode_t seek, + size_t decoded_length /**< Length of data without compression */); + /** Function does exactly the same as pa_stream_write() with the difference * that free_cb_data is passed to free_cb instead of data. \since 6.0 */ +int pa_stream_write_ext_compressed_free( + pa_stream *p /**< The stream to use */, + const void *data /**< The data to write */, + size_t nbytes /**< The length of the data to write in bytes */, + pa_free_cb_t free_cb /**< A cleanup routine for the data or NULL to request an internal copy */, + void *free_cb_data /**< Argument passed to free_cb function */, + int64_t offset /**< Offset for seeking, must be 0 for upload streams */, + pa_seek_mode_t seek /**< Seek mode, must be PA_SEEK_RELATIVE for upload streams */, + size_t decoded_length /**< Length of data without compression */); + int pa_stream_write_ext_free( pa_stream *p /**< The stream to use */, const void *data /**< The data to write */, diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c index ec223be..68fcc8c 100644 --- a/src/pulsecore/protocol-native.c +++ b/src/pulsecore/protocol-native.c @@ -55,6 +55,8 @@ #include #include #include +#include +#include #include "protocol-native.h" @@ -143,6 +145,8 @@ typedef struct playback_stream { size_t render_memblockq_length; pa_usec_t current_sink_latency; uint64_t playing_for, underrun_for; + + pa_transcode transcode; } playback_stream; #define PLAYBACK_STREAM(o) (playback_stream_cast(o)) @@ -793,6 +797,9 @@ static void playback_stream_free(pa_object* o) { playback_stream_unlink(s); + if(s->transcode.encoding != -1) + pa_transcode_free(&s->transcode); + pa_memblockq_free(s->memblockq); pa_xfree(s); } @@ -1112,6 +1119,9 @@ static playback_stream* playback_stream_new( int64_t start_index; pa_sink_input_new_data data; char *memblockq_name; + pa_encoding_t transcode_encoding = -1; + pa_format_info *f_in, *transcode_format_info; + uint32_t j; pa_assert(c); pa_assert(ss); @@ -1146,17 +1156,52 @@ static playback_stream* playback_stream_new( data.driver = __FILE__; data.module = c->options->module; data.client = c->client; - if (sink) - pa_sink_input_new_data_set_sink(&data, sink, false); + if (pa_sample_spec_valid(ss)) pa_sink_input_new_data_set_sample_spec(&data, ss); if (pa_channel_map_valid(map)) pa_sink_input_new_data_set_channel_map(&data, map); + + if (!sink){ + sink = pa_namereg_get(c->protocol->core, NULL, PA_NAMEREG_SINK); + } + if (formats) { pa_sink_input_new_data_set_formats(&data, formats); /* Ownership transferred to new_data, so we don't free it ourselves */ formats = NULL; } + + *ret = pa_sink_input_new_data_set_sink(&data, sink, false); + + if(*ret == false) + { + PA_IDXSET_FOREACH(f_in, data.req_formats, j) { + if(pa_transcode_supported(f_in->encoding)){ + + transcode_encoding = f_in->encoding; + + f_in->encoding = PA_ENCODING_PCM; + + pa_sink_input_new_data_set_sink(&data, sink, false); + + #ifdef PROTOCOL_NATIVE_DEBUG + pa_log("enabling transcoding"); + #endif + + transcode_format_info = pa_format_info_copy(f_in); + + break; + } + } + } + + + + + + + if (volume) { pa_sink_input_new_data_set_volume(&data, volume); data.volume_is_absolute = !relative_volume; @@ -1191,6 +1236,12 @@ static playback_stream* playback_stream_new( pa_atomic_store(&s->seek_or_post_in_queue, 0); s->seek_windex = -1; + s->transcode.encoding = transcode_encoding; + if(transcode_encoding != -1) { + pa_transcode_init(&s->transcode, transcode_encoding, PA_TRANSCODE_DECODER, transcode_format_info, NULL); + pa_format_info_free(transcode_format_info); + } + s->sink_input->parent.process_msg = sink_input_process_msg; s->sink_input->pop = sink_input_pop_cb; s->sink_input->process_underrun = sink_input_process_underrun_cb; @@ -1459,6 +1510,10 @@ static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int pa_sink_input *i = PA_SINK_INPUT(o); playback_stream *s; + struct pa_memchunk transcode_chunk; + void *input_encoded_bytes, *output_pcm_bytes; + int32_t frame_size; + pa_sink_input_assert_ref(i); s = PLAYBACK_STREAM(i->userdata); playback_stream_assert_ref(s); @@ -1478,11 +1533,38 @@ static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int windex = PA_MIN(windex, pa_memblockq_get_write_index(s->memblockq)); } - if (chunk && pa_memblockq_push_align(s->memblockq, chunk) < 0) { - if (pa_log_ratelimit(PA_LOG_WARN)) - pa_log_warn("Failed to push data into queue"); - pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_OVERFLOW, NULL, 0, NULL, NULL); - pa_memblockq_seek(s->memblockq, (int64_t) chunk->length, PA_SEEK_RELATIVE, true); + if(s->transcode.encoding != -1) { + input_encoded_bytes = pa_memblock_acquire(chunk->memblock); + transcode_chunk.memblock = pa_memblock_new( pa_memblock_get_pool(chunk->memblock), s->transcode.max_frame_size*s->transcode.channels*s->transcode.sample_size); + transcode_chunk.index = transcode_chunk.length = 0; + + output_pcm_bytes = pa_memblock_acquire(transcode_chunk.memblock); + + frame_size = pa_transcode_decode(&s->transcode, input_encoded_bytes, chunk->length, output_pcm_bytes); + transcode_chunk.length = frame_size*s->transcode.channels*s->transcode.sample_size; + + pa_log_info("transcode: decoded frame (framesize: %d total length: %d)", frame_size, (int)transcode_chunk.length); + + pa_memblock_release(chunk->memblock); + pa_memblock_release(transcode_chunk.memblock); + + + if (pa_memblockq_push_align(s->memblockq, &transcode_chunk) < 0) { + if (pa_log_ratelimit(PA_LOG_WARN)) + pa_log_warn("Failed to push data into queue"); + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_OVERFLOW, NULL, 0, NULL, NULL); + pa_memblockq_seek(s->memblockq, (int64_t) transcode_chunk.length, PA_SEEK_RELATIVE, true); + } + pa_memblock_unref(transcode_chunk.memblock); + + } + else { + if (chunk && pa_memblockq_push_align(s->memblockq, chunk) < 0) { + if (pa_log_ratelimit(PA_LOG_WARN)) + pa_log_warn("Failed to push data into queue"); + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_OVERFLOW, NULL, 0, NULL, NULL); + pa_memblockq_seek(s->memblockq, (int64_t) chunk->length, PA_SEEK_RELATIVE, true); + } } /* If more data is in queue, we rewind later instead. */ @@ -4922,7 +5004,7 @@ static void pstream_memblock_callback(pa_pstream *p, uint32_t channel, int64_t o playback_stream *ps = PLAYBACK_STREAM(stream); size_t frame_size = pa_frame_size(&ps->sink_input->sample_spec); - if (chunk->index % frame_size != 0 || chunk->length % frame_size != 0) { + if ((ps->transcode.encoding == -1) && (chunk->index % frame_size != 0 || chunk->length % frame_size != 0)) { pa_log_warn("Client sent non-aligned memblock: index %d, length %d, frame size: %d", (int) chunk->index, (int) chunk->length, (int) frame_size); return; diff --git a/src/pulsecore/transcode.c b/src/pulsecore/transcode.c new file mode 100644 index 0000000..2159472 --- /dev/null +++ b/src/pulsecore/transcode.c @@ -0,0 +1,227 @@ + +#ifdef HAVE_CONFIG_H +#include +#endif + + +#include +#include + + +#include "transcode.h" + + + +#ifdef HAVE_OPUS + +#include + +#define OPUS_DEFAULT_FRAME_SIZE 2880 +#define OPUS_DEFAULT_BITRATE 64000 + +#define OPUS_DEFAULT_SAMPLE_RATE 48000 +#define OPUS_DEFAULT_SAMPLE_SIZE 2 + + + +OpusEncoder *tc_opus_create_encoder(int sample_rate, int channels, int bitrate); +OpusDecoder *tc_opus_create_decoder(int sample_rate, int channels); +int tc_opus_encode(OpusEncoder *encoder, unsigned char *pcm_bytes, unsigned char *cbits, int channels, int frame_size); +int tc_opus_decode(OpusDecoder *decoder, unsigned char *cbits, int nbBytes, unsigned char *pcm_bytes, int channels, int max_frame_size); + + +OpusDecoder *tc_opus_create_decoder(int sample_rate, int channels) +{ + int err; + + OpusDecoder * decoder = opus_decoder_create(sample_rate, channels, &err); + if (err<0) { + pa_log_error("failed to create decoder: %s", opus_strerror(err)); + return NULL; + } + + return decoder; +} + + + +OpusEncoder *tc_opus_create_encoder(int sample_rate, int channels, int bitrate) +{ + int err; + + OpusEncoder *encoder = opus_encoder_create(sample_rate, channels, OPUS_APPLICATION_AUDIO, &err); + if (err<0){ + pa_log_error("failed to create an encoder: %s", opus_strerror(err)); + return NULL; + } + + err = opus_encoder_ctl(encoder, OPUS_SET_BITRATE(bitrate)); + if (err<0) + { + pa_log_error("failed to set bitrate: %s", opus_strerror(err)); + return NULL; + } + + return encoder; +} + + +int tc_opus_encode(OpusEncoder *encoder, unsigned char *pcm_bytes, unsigned char *cbits, int channels, int frame_size) +{ + int i; + int nbBytes; + opus_int16 in[frame_size*channels]; + + /* Convert from little-endian ordering. */ + for (i=0;i>8)&0xFF; + } + + return frame_size; +} + +#endif + + +bool pa_transcode_supported(pa_encoding_t encoding) { + #ifdef HAVE_OPUS + if(encoding == PA_ENCODING_OPUS) + return 1; + #endif + + return 0; +} + +void pa_transcode_set_format_info(pa_transcode *transcode, pa_format_info *f) { + pa_format_info_set_prop_int(f, "frame_size", transcode->frame_size); + pa_format_info_set_prop_int(f, "bitrate", transcode->bitrate); +} + + +void pa_transcode_init(pa_transcode *transcode, pa_encoding_t encoding, pa_transcode_flags_t flags, pa_format_info *transcode_format_info, pa_sample_spec *transcode_sink_spec) { + + pa_assert((flags & PA_TRANSCODE_DECODER) || (flags & PA_TRANSCODE_ENCODER)); + + transcode->flags = flags; + transcode->encoding = encoding; + + switch(encoding) { + #ifdef HAVE_OPUS + case PA_ENCODING_OPUS: + + transcode->sample_size = OPUS_DEFAULT_SAMPLE_SIZE; + + + if(flags & PA_TRANSCODE_DECODER) { + if(pa_format_info_get_prop_int(transcode_format_info, "max_frame_size", (int *)&transcode->max_frame_size) != 0) + transcode->max_frame_size = OPUS_DEFAULT_FRAME_SIZE; + if(pa_format_info_get_prop_int(transcode_format_info, "frame_size", (int *)&transcode->frame_size) != 0) + transcode->frame_size = OPUS_DEFAULT_FRAME_SIZE; + if(pa_format_info_get_prop_int(transcode_format_info, "bitrate", (int *)&transcode->bitrate) != 0) + transcode->bitrate = OPUS_DEFAULT_BITRATE; + + pa_format_info_get_rate(transcode_format_info, &transcode->rate); + pa_format_info_get_channels(transcode_format_info, &transcode->channels); + + transcode->decoder = tc_opus_create_decoder(transcode->rate, transcode->channels); + + } + else { + if(transcode->bitrate == 0) + transcode->bitrate = OPUS_DEFAULT_BITRATE; + if(transcode->frame_size == 0) + transcode->frame_size = OPUS_DEFAULT_FRAME_SIZE; + + transcode->channels = transcode_sink_spec->channels; + transcode->rate = OPUS_DEFAULT_SAMPLE_RATE; + transcode->encoder = tc_opus_create_encoder(transcode->rate, transcode->channels, transcode->bitrate); + + + transcode_sink_spec->rate = transcode->rate; + transcode_sink_spec->format = PA_SAMPLE_S16LE; + } + + break; + #endif + default: + transcode->decoder = NULL; + transcode->encoder = NULL; + } + +} + +void pa_transcode_free(pa_transcode *transcode) { + + switch(transcode->encoding) { + #ifdef HAVE_OPUS + case PA_ENCODING_OPUS: + opus_decoder_destroy(transcode->decoder); + break; + #endif + default: + transcode->decoder = NULL; + } + +} + + +int32_t pa_transcode_encode(pa_transcode *transcode, unsigned char *pcm_input, unsigned char **compressed_output) { + int nbBytes=0; + + switch(transcode->encoding) { + #ifdef HAVE_OPUS + case PA_ENCODING_OPUS: + *compressed_output = malloc(transcode->frame_size*transcode->channels*transcode->sample_size); + nbBytes = tc_opus_encode(transcode->encoder, pcm_input, *compressed_output, transcode->channels, transcode->frame_size); + break; + #endif + + } + + return nbBytes; +} + +int32_t pa_transcode_decode(pa_transcode *transcode, unsigned char *compressed_input, int input_length, unsigned char *pcm_output) { + int32_t frame_length=0; + + switch(transcode->encoding) { + #ifdef HAVE_OPUS + case PA_ENCODING_OPUS: + frame_length = tc_opus_decode(transcode->decoder, compressed_input, input_length, pcm_output, transcode->channels, transcode->max_frame_size); + break; + #endif + + } + + return frame_length; +} diff --git a/src/pulsecore/transcode.h b/src/pulsecore/transcode.h new file mode 100644 index 0000000..c9e260e --- /dev/null +++ b/src/pulsecore/transcode.h @@ -0,0 +1,36 @@ +#ifndef footranscodehfoo +#define footranscodehfoo + +#include + + + + +typedef enum pa_transcode_flags { + PA_TRANSCODE_DECODER=(1<<0), + PA_TRANSCODE_ENCODER=(1<<1) + } pa_transcode_flags_t; + +typedef struct pa_transcode { + int32_t encoding; + uint8_t channels; + uint32_t frame_size; + uint32_t max_frame_size; + uint32_t sample_size; + uint32_t rate; + uint32_t bitrate; + pa_transcode_flags_t flags; + union { + void *decoder; + void *encoder; + }; +} pa_transcode; + +bool pa_transcode_supported(pa_encoding_t encoding); +void pa_transcode_set_format_info(pa_transcode *transcode, pa_format_info *f); +void pa_transcode_init(pa_transcode *transcode, pa_encoding_t encoding, pa_transcode_flags_t flags, pa_format_info *transcode_format_info, pa_sample_spec *transcode_sink_spec); +void pa_transcode_free(pa_transcode *transcode); +int32_t pa_transcode_encode(pa_transcode *transcode, unsigned char *pcm_input, unsigned char **compressed_output); +int32_t pa_transcode_decode(pa_transcode *transcode, unsigned char *compressed_input, int input_length, unsigned char *pcm_output); + +#endif