[pulseaudio-discuss] Opus audio compression - improved

Submitted by Vadim Troshchinskiy on July 26, 2017, 9:18 a.m.

Details

Message ID 107221454.6NJgF5KPW9@snark
State New
Headers show
Series "Opus audio compression - improved" ( rev: 1 ) in PulseAudio

Not browsing as part of any series.

Commit Message

Vadim Troshchinskiy July 26, 2017, 9:18 a.m.
Hello,

This work is based on the Opus audio compression patch by Gerrit Wyen and 
Gavin Darkglider found at https://bugs.freedesktop.org/show_bug.cgi?id=56993.

The patch implements audio compression with Opus, which leads to great 
bandwidth savings. The main intended use is drastically reducing the amount of 
bandwidth required for remote desktop access. The ability to vary compression 
parameters at runtime makes it possible for the user to adjust quality and 
bandwidth usage as needed without reconnecting, and could be used 
automatically to adjust to network performance.



The following changes have been made:

* Adapt the code to the latest PA version.
* Add the ability to configure more Opus parameters.
* Make most parameters configurable at runtime. This means
  that for instance the bitrate can be varied at runtime as needed.
* Opus specific code has been moved out from tunnel-sink into
  transcode.c

Instructions:

1. Apply on client and server. libopus-dev is required.
2. On receiver: load-module module-native-protocol-tcp auth-ip-acl=SENDER_IP
3. On sender: load-module module-tunnel-sink-new sink_name=tunnel 
server=tcp:RECEIVER_IP:4713 sink="@DEFAULT_SINK@" compression="opus"

Recognized options:

compression: only "opus" so far.
compression-bitrate
compression-frame_size
compression-complexity
compression-max_bandwidth: narrowband, mediumband, wideband, superwideband or 
fullband
compression-signal: auto, voice, or music
compression-vbr
compression-vbr_constraint
compression-application: voip, audio or restricted_lowdelay
compression-lsb_depth
compression-dtx

The full explanation of these options can be found at:

https://mf4.xiph.org/jenkins/view/opus/job/opus/ws/doc/html/
group__opus__encoderctls.html


Parameters can be changed at runtime, eg:

update-sink-proplist tunnel compression-bitrate="32000"

Patch hide | download patch | download mbox

diff --git a/configure.ac b/configure.ac
index 77b5ff5d..29d75a96 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1169,6 +1169,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],
@@ -1588,6 +1603,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 }---
@@ -1645,6 +1661,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 3ff1139f..530b0b11 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -994,7 +994,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) $(AM_LIBLDFLAGS) -
avoid-version
@@ -1067,6 +1068,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 93a62b86..4d5cb0eb 100644
--- a/src/map-file
+++ b/src/map-file
@@ -333,7 +333,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 dd6c8866..fd7eed67 100644
--- a/src/modules/module-tunnel-sink-new.c
+++ b/src/modules/module-tunnel-sink-new.c
@@ -39,6 +39,8 @@ 
 #include <pulsecore/thread-mq.h>
 #include <pulsecore/poll.h>
 #include <pulsecore/proplist-util.h>
+#include <pulsecore/transcode.h>
+
 
 #include "module-tunnel-sink-new-symdef.h"
 
@@ -56,6 +58,9 @@  PA_MODULE_USAGE(
         "rate=<sample rate> "
         "channel_map=<channel map> "
         "cookie=<cookie file path>"
+        "compression=<compression format> "
+        "compression_frame_size=<frame_size of compression (opus: 120, 240, 
480, 960, 1920, 2880)> "
+        "compression_bitrate=<bitrate of compression> "
         );
 
 #define MAX_LATENCY_USEC (200 * PA_USEC_PER_MSEC)
@@ -67,12 +72,13 @@  static void stream_set_buffer_attr_cb(pa_stream *stream, 
int success, void *user
 static void context_state_cb(pa_context *c, void *userdata);
 static void sink_update_requested_latency_cb(pa_sink *s);
 
+
 struct userdata {
     pa_module *module;
     pa_sink *sink;
     pa_thread *thread;
     pa_thread_mq *thread_mq;
-    pa_mainloop *thread_mainloop;
+    pa_rtpoll *rtpoll;
     pa_mainloop_api *thread_mainloop_api;
 
     pa_context *context;
@@ -85,6 +91,9 @@  struct userdata {
     char *cookie_file;
     char *remote_server;
     char *remote_sink_name;
+
+    pa_transcode transcode;
+    pa_hook_slot *sink_changed_slot;
 };
 
 static const char* const valid_modargs[] = {
@@ -97,10 +106,15 @@  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,
 };
 
+static pa_hook_result_t sink_changed_cb(pa_core *c, pa_object *o, struct 
userdata *u);
+
 static void cork_stream(struct userdata *u, bool cork) {
     pa_operation *operation;
 
@@ -143,6 +157,10 @@  static pa_proplist* tunnel_new_proplist(struct userdata 
*u) {
 static void thread_func(void *userdata) {
     struct userdata *u = userdata;
     pa_proplist *proplist;
+    unsigned int frame_size;
+
+
+
     pa_assert(u);
 
     pa_log_debug("Thread starting up");
@@ -173,16 +191,15 @@  static void thread_func(void *userdata) {
         goto fail;
     }
 
+    u->transcode.proplist = u->sink->proplist;
+
+    // The parameter is limited internally and can't be negative
+    if ( u->transcode.encoding != -1 )
+        frame_size = (unsigned int)pa_transcode_get_param(&u->transcode, 
PA_PROP_COMPRESSION_FRAME_SIZE);
+
     for (;;) {
         int ret;
 
-        if (pa_mainloop_iterate(u->thread_mainloop, 1, &ret) < 0) {
-            if (ret == 0)
-                goto finish;
-            else
-                goto fail;
-        }
-
         if (PA_UNLIKELY(u->sink->thread_info.rewind_requested))
             pa_sink_process_rewind(u->sink, 0);
 
@@ -193,32 +210,78 @@  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, frame_size*u-
>transcode.channels*u->transcode.sample_size, &memchunk);
+
+                         pa_assert(memchunk.length > 0);
+                         pa_assert(memchunk.length >=  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, 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);
+                         }
+
                 }
 
             }
         }
+        if ((ret = pa_rtpoll_run(u->rtpoll)) < 0)
+            goto fail;
+
+        /* ret is zero only when the module is being unloaded, i.e. we're 
doing
+         * clean shutdown. */
+        if (ret == 0)
+            goto finish;
     }
 fail:
     pa_asyncmsgq_post(u->thread_mq->outq, PA_MSGOBJECT(u->module->core), 
PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL);
@@ -312,7 +375,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 +542,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);
@@ -487,21 +569,13 @@  int pa__init(pa_module *m) {
     u->module = m;
     m->userdata = u;
     u->remote_server = pa_xstrdup(remote_server);
-    u->thread_mainloop = pa_mainloop_new();
-    if (u->thread_mainloop == NULL) {
-        pa_log("Failed to create mainloop");
-        goto fail;
-    }
-    u->thread_mainloop_api = pa_mainloop_get_api(u->thread_mainloop);
-    u->cookie_file = pa_xstrdup(pa_modargs_get_value(ma, "cookie", NULL));
-    u->remote_sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL));
-
+    u->rtpoll = pa_rtpoll_new();
     u->thread_mq = pa_xnew0(pa_thread_mq, 1);
+    pa_thread_mq_init(u->thread_mq, m->core->mainloop, u->rtpoll);
+    u->thread_mainloop_api = pa_rtpoll_get_mainloop_api(u->rtpoll);
 
-    if (pa_thread_mq_init_thread_mainloop(u->thread_mq, m->core->mainloop, u-
>thread_mainloop_api) < 0) {
-        pa_log("pa_thread_mq_init_thread_mainloop() failed.");
-        goto fail;
-    }
+    u->cookie_file = pa_xstrdup(pa_modargs_get_value(ma, "cookie", NULL));
+    u->remote_sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL));
 
     /* Create sink */
     pa_sink_new_data_init(&sink_data);
@@ -511,6 +585,25 @@  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(pa_transcode_supported_byname(compression)){
+
+		pa_encoding_t encoding = pa_transcode_encoding_byname(compression);
+
+//             pa_proplist_sets(sink_data.proplist, "compression.algorithm", 
"opus");
+//             pa_transcode_copy_arguments(&u->transcode, ma, 
sink_data.proplist);
+
+
+             pa_transcode_init(&u->transcode, encoding, PA_TRANSCODE_ENCODER, 
NULL, &ss, ma, sink_data.proplist);
+	     u->sink_changed_slot = pa_hook_connect(&m->core-
>hooks[PA_CORE_HOOK_SINK_PROPLIST_CHANGED], PA_HOOK_LATE, 
(pa_hook_cb_t)sink_changed_cb, u);
+         }
+    }
+    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);
@@ -541,6 +634,7 @@  int pa__init(pa_module *m) {
 
     /* set thread message queue */
     pa_sink_set_asyncmsgq(u->sink, u->thread_mq->inq);
+    pa_sink_set_rtpoll(u->sink, u->rtpoll);
 
     if (!(u->thread = pa_thread_new("tunnel-sink", thread_func, u))) {
         pa_log("Failed to create thread.");
@@ -586,8 +680,8 @@  void pa__done(pa_module *m) {
         pa_xfree(u->thread_mq);
     }
 
-    if (u->thread_mainloop)
-        pa_mainloop_free(u->thread_mainloop);
+    if (u->rtpoll)
+        pa_rtpoll_free(u->rtpoll);
 
     if (u->cookie_file)
         pa_xfree(u->cookie_file);
@@ -601,5 +695,23 @@  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);
 }
+
+/* Runs in PA mainloop context */
+static pa_hook_result_t sink_changed_cb(pa_core *c, pa_object *o, struct 
userdata *u) {
+    pa_assert(c);
+    pa_object_assert_ref(o);
+
+    pa_mutex_lock(u->transcode.codec_mutex);
+
+    pa_transcode_verify_proplist(&u->transcode);
+    pa_transcode_update_encoder_options(&u->transcode);
+
+    pa_mutex_unlock(u->transcode.codec_mutex);
+
+    return PA_HOOK_OK;
+}
diff --git a/src/modules/module-tunnel-source-new.c b/src/modules/module-
tunnel-source-new.c
index 2db928c8..50417f38 100644
--- a/src/modules/module-tunnel-source-new.c
+++ b/src/modules/module-tunnel-source-new.c
@@ -70,7 +70,7 @@  struct userdata {
     pa_source *source;
     pa_thread *thread;
     pa_thread_mq *thread_mq;
-    pa_mainloop *thread_mainloop;
+    pa_rtpoll *rtpoll;
     pa_mainloop_api *thread_mainloop_api;
 
     pa_context *context;
@@ -225,15 +225,16 @@  static void thread_func(void *userdata) {
     for (;;) {
         int ret;
 
-        if (pa_mainloop_iterate(u->thread_mainloop, 1, &ret) < 0) {
-            if (ret == 0)
-                goto finish;
-            else
-                goto fail;
-        }
-
         if (u->new_data)
             read_new_samples(u);
+
+        if ((ret = pa_rtpoll_run(u->rtpoll)) < 0)
+            goto fail;
+
+        /* ret is zero only when the module is being unloaded, i.e. we're 
doing
+         * clean shutdown. */
+        if (ret == 0)
+            goto finish;
     }
 fail:
     pa_asyncmsgq_post(u->thread_mq->outq, PA_MSGOBJECT(u->module->core), 
PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL);
@@ -486,21 +487,13 @@  int pa__init(pa_module *m) {
     u->module = m;
     m->userdata = u;
     u->remote_server = pa_xstrdup(remote_server);
-    u->thread_mainloop = pa_mainloop_new();
-    if (u->thread_mainloop == NULL) {
-        pa_log("Failed to create mainloop");
-        goto fail;
-    }
-    u->thread_mainloop_api = pa_mainloop_get_api(u->thread_mainloop);
-    u->cookie_file = pa_xstrdup(pa_modargs_get_value(ma, "cookie", NULL));
-    u->remote_source_name = pa_xstrdup(pa_modargs_get_value(ma, "source", 
NULL));
-
+    u->rtpoll = pa_rtpoll_new();
     u->thread_mq = pa_xnew0(pa_thread_mq, 1);
+    pa_thread_mq_init(u->thread_mq, m->core->mainloop, u->rtpoll);
+    u->thread_mainloop_api = pa_rtpoll_get_mainloop_api(u->rtpoll);
 
-    if (pa_thread_mq_init_thread_mainloop(u->thread_mq, m->core->mainloop, u-
>thread_mainloop_api) < 0) {
-        pa_log("pa_thread_mq_init_thread_mainloop() failed.");
-        goto fail;
-    }
+    u->cookie_file = pa_xstrdup(pa_modargs_get_value(ma, "cookie", NULL));
+    u->remote_source_name = pa_xstrdup(pa_modargs_get_value(ma, "source", 
NULL));
 
     /* Create source */
     pa_source_new_data_init(&source_data);
@@ -538,6 +531,7 @@  int pa__init(pa_module *m) {
     u->source->update_requested_latency = source_update_requested_latency_cb;
 
     pa_source_set_asyncmsgq(u->source, u->thread_mq->inq);
+    pa_source_set_rtpoll(u->source, u->rtpoll);
 
     if (!(u->thread = pa_thread_new("tunnel-source", thread_func, u))) {
         pa_log("Failed to create thread.");
@@ -583,8 +577,8 @@  void pa__done(pa_module *m) {
         pa_xfree(u->thread_mq);
     }
 
-    if (u->thread_mainloop)
-        pa_mainloop_free(u->thread_mainloop);
+    if (u->rtpoll)
+        pa_rtpoll_free(u->rtpoll);
 
     if (u->cookie_file)
         pa_xfree(u->cookie_file);
diff --git a/src/pulse/format.h b/src/pulse/format.h
index f606b3b5..ecb43185 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 ee95757f..15ed2b4c 100644
--- a/src/pulse/stream.c
+++ b/src/pulse/stream.c
@@ -1471,14 +1471,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);
@@ -1495,7 +1495,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) {
@@ -1561,6 +1563,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;
@@ -1605,6 +1611,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,
@@ -1613,7 +1631,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 5dfdee1a..907258d0 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, must be in multiples of the stream's sample spec frame size 
*/,
         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 866e2c64..acf7002b 100644
--- a/src/pulsecore/protocol-native.c
+++ b/src/pulsecore/protocol-native.c
@@ -57,6 +57,8 @@ 
 #include <pulsecore/ipacl.h>
 #include <pulsecore/thread-mq.h>
 #include <pulsecore/mem.h>
+#include <pulsecore/core-format.h>
+#include <pulsecore/transcode.h>
 
 #include "protocol-native.h"
 
@@ -145,6 +147,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))
@@ -652,6 +656,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);
 }
@@ -971,6 +978,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);
@@ -1005,17 +1015,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;
@@ -1050,6 +1095,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, NULL, data.proplist);
+         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;
@@ -1316,6 +1367,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);
@@ -1335,11 +1390,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. */
@@ -4976,7 +5058,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/rtpoll.c b/src/pulsecore/rtpoll.c
index 98cf88ff..9829f7e7 100644
--- a/src/pulsecore/rtpoll.c
+++ b/src/pulsecore/rtpoll.c
@@ -27,6 +27,7 @@ 
 #include <string.h>
 #include <errno.h>
 
+#include <pulsecore/dynarray.h>
 #include <pulse/xmalloc.h>
 #include <pulse/timeval.h>
 
@@ -63,6 +64,18 @@  struct pa_rtpoll {
 #endif
 
     PA_LLIST_HEAD(pa_rtpoll_item, items);
+    
+    pa_mainloop_api mainloop_api;
+
+    pa_dynarray *io_events;
+
+    pa_dynarray *time_events;
+    pa_dynarray *enabled_time_events;
+    pa_dynarray *expired_time_events;
+    pa_time_event *cached_next_time_event;
+
+    pa_dynarray *defer_events;
+    pa_dynarray *enabled_defer_events;
 };
 
 struct pa_rtpoll_item {
@@ -82,8 +95,325 @@  struct pa_rtpoll_item {
     PA_LLIST_FIELDS(pa_rtpoll_item);
 };
 
+struct pa_io_event {
+    pa_rtpoll *rtpoll;
+    pa_rtpoll_item *rtpoll_item;
+    pa_io_event_flags_t events;
+    pa_io_event_cb_t callback;
+    pa_io_event_destroy_cb_t destroy_callback;
+    void *userdata;
+};
+
+static void io_event_enable(pa_io_event *event, pa_io_event_flags_t events);
+
+struct pa_time_event {
+    pa_rtpoll *rtpoll;
+    pa_usec_t time;
+    bool use_rtclock;
+    bool enabled;
+    pa_time_event_cb_t callback;
+    pa_time_event_destroy_cb_t destroy_callback;
+    void *userdata;
+};
+
+static void time_event_restart(pa_time_event *event, const struct timeval 
*tv);
+
+struct pa_defer_event {
+    pa_rtpoll *rtpoll;
+    bool enabled;
+    pa_defer_event_cb_t callback;
+    pa_defer_event_destroy_cb_t destroy_callback;
+    void *userdata;
+};
+
+static void defer_event_enable(pa_defer_event *event, int enable);
+
 PA_STATIC_FLIST_DECLARE(items, 0, pa_xfree);
 
+static short map_flags_to_libc(pa_io_event_flags_t flags) {
+    return (short)
+        ((flags & PA_IO_EVENT_INPUT ? POLLIN : 0) |
+         (flags & PA_IO_EVENT_OUTPUT ? POLLOUT : 0) |
+         (flags & PA_IO_EVENT_ERROR ? POLLERR : 0) |
+         (flags & PA_IO_EVENT_HANGUP ? POLLHUP : 0));
+}
+
+static pa_io_event_flags_t map_flags_from_libc(short flags) {
+    return
+        (flags & POLLIN ? PA_IO_EVENT_INPUT : 0) |
+        (flags & POLLOUT ? PA_IO_EVENT_OUTPUT : 0) |
+        (flags & POLLERR ? PA_IO_EVENT_ERROR : 0) |
+        (flags & POLLHUP ? PA_IO_EVENT_HANGUP : 0);
+}
+
+static int io_event_work_cb(pa_rtpoll_item *item) {
+    pa_io_event *event;
+    struct pollfd *pollfd;
+
+    pa_assert(item);
+
+    event = pa_rtpoll_item_get_userdata(item);
+    pollfd = pa_rtpoll_item_get_pollfd(item, NULL);
+    event->callback(&event->rtpoll->mainloop_api, event, pollfd->fd,
+map_flags_from_libc(pollfd->revents), event->userdata);
+
+    return 0;
+}
+
+static pa_io_event* io_event_new(pa_mainloop_api *api, int fd, 
pa_io_event_flags_t events,
+pa_io_event_cb_t callback,
+                                 void *userdata) {
+    pa_rtpoll *rtpoll;
+    pa_io_event *event;
+    struct pollfd *pollfd;
+
+    pa_assert(api);
+    pa_assert(api->userdata);
+    pa_assert(fd >= 0);
+    pa_assert(callback);
+
+    rtpoll = api->userdata;
+    pa_assert(api == &rtpoll->mainloop_api);
+
+    event = pa_xnew0(pa_io_event, 1);
+    event->rtpoll = rtpoll;
+    event->rtpoll_item = pa_rtpoll_item_new(rtpoll, PA_RTPOLL_NORMAL, 1);
+    pa_rtpoll_item_set_work_callback(event->rtpoll_item, io_event_work_cb);
+    pa_rtpoll_item_set_userdata(event->rtpoll_item, event);
+    pollfd = pa_rtpoll_item_get_pollfd(event->rtpoll_item, NULL);
+    pollfd->fd = fd;
+    event->callback = callback;
+    event->userdata = userdata;
+
+    pa_dynarray_append(rtpoll->io_events, event);
+    io_event_enable(event, events);
+
+    return event;
+}
+
+static void io_event_free(pa_io_event *event) {
+    pa_assert(event);
+
+    pa_dynarray_remove_by_data(event->rtpoll->io_events, event);
+
+    if (event->destroy_callback)
+        event->destroy_callback(&event->rtpoll->mainloop_api, event, event-
>userdata);
+
+    if (event->rtpoll_item)
+        pa_rtpoll_item_free(event->rtpoll_item);
+
+    pa_xfree(event);
+}
+
+static void io_event_enable(pa_io_event *event, pa_io_event_flags_t events) {
+    struct pollfd *pollfd;
+
+    pa_assert(event);
+
+    if (events == event->events)
+        return;
+
+    event->events = events;
+
+    pollfd = pa_rtpoll_item_get_pollfd(event->rtpoll_item, NULL);
+    pollfd->events = map_flags_to_libc(events);
+}
+
+static void io_event_set_destroy(pa_io_event *event, pa_io_event_destroy_cb_t 
callback) {
+    pa_assert(event);
+
+    event->destroy_callback = callback;
+}
+
+static pa_usec_t make_rt(const struct timeval *tv, bool *use_rtclock) {
+    struct timeval ttv;
+
+    if (!tv) {
+        *use_rtclock = false;
+        return PA_USEC_INVALID;
+    }
+
+    ttv = *tv;
+    *use_rtclock = !!(ttv.tv_usec & PA_TIMEVAL_RTCLOCK);
+
+    if (*use_rtclock)
+        ttv.tv_usec &= ~PA_TIMEVAL_RTCLOCK;
+    else
+        pa_rtclock_from_wallclock(&ttv);
+
+    return pa_timeval_load(&ttv);
+}
+
+static pa_time_event* time_event_new(pa_mainloop_api *api, const struct 
timeval *tv,
+pa_time_event_cb_t callback,
+                                     void *userdata) {
+    pa_rtpoll *rtpoll;
+    pa_time_event *event;
+
+    pa_assert(api);
+    pa_assert(api->userdata);
+    pa_assert(callback);
+
+    rtpoll = api->userdata;
+    pa_assert(api == &rtpoll->mainloop_api);
+
+    event = pa_xnew0(pa_time_event, 1);
+    event->rtpoll = rtpoll;
+    event->time = PA_USEC_INVALID;
+    event->callback = callback;
+    event->userdata = userdata;
+
+    pa_dynarray_append(rtpoll->time_events, event);
+    time_event_restart(event, tv);
+
+    return event;
+}
+
+static void time_event_free(pa_time_event *event) {
+    pa_assert(event);
+
+    time_event_restart(event, NULL);
+    pa_dynarray_remove_by_data(event->rtpoll->time_events, event);
+
+    if (event->destroy_callback)
+        event->destroy_callback(&event->rtpoll->mainloop_api, event, event-
>userdata);
+
+    pa_xfree(event);
+}
+
+static void time_event_restart(pa_time_event *event, const struct timeval 
*tv) {
+    pa_usec_t t;
+    bool use_rtclock;
+    bool enabled;
+    bool old_enabled;
+
+    pa_assert(event);
+
+    t = make_rt(tv, &use_rtclock);
+    enabled = (t != PA_USEC_INVALID);
+    old_enabled = event->enabled;
+
+    /* We return early only if the event stays disabled. If the event stays
+     * enabled, we can't return early, because the event time may change. */
+    if (!enabled && !old_enabled)
+        return;
+
+    event->enabled = enabled;
+    event->time = t;
+    event->use_rtclock = use_rtclock;
+
+    if (enabled && !old_enabled)
+        pa_dynarray_append(event->rtpoll->enabled_time_events, event);
+    else if (!enabled) {
+        pa_dynarray_remove_by_data(event->rtpoll->enabled_time_events, 
event);
+        pa_dynarray_remove_by_data(event->rtpoll->expired_time_events, 
event);
+    }
+
+    if (event->rtpoll->cached_next_time_event == event)
+        event->rtpoll->cached_next_time_event = NULL;
+
+    if (event->rtpoll->cached_next_time_event && enabled) {
+        pa_assert(event->rtpoll->cached_next_time_event->enabled);
+
+        if (t < event->rtpoll->cached_next_time_event->time)
+            event->rtpoll->cached_next_time_event = event;
+    }
+}
+
+static void time_event_set_destroy(pa_time_event *event, 
pa_time_event_destroy_cb_t callback) {
+    pa_assert(event);
+
+    event->destroy_callback = callback;
+}
+
+static pa_defer_event* defer_event_new(pa_mainloop_api *api, 
pa_defer_event_cb_t callback, void *userdata) {
+    pa_rtpoll *rtpoll;
+    pa_defer_event *event;
+
+    pa_assert(api);
+    pa_assert(api->userdata);
+    pa_assert(callback);
+
+    rtpoll = api->userdata;
+    pa_assert(api == &rtpoll->mainloop_api);
+
+    event = pa_xnew0(pa_defer_event, 1);
+    event->rtpoll = rtpoll;
+    event->callback = callback;
+    event->userdata = userdata;
+
+    pa_dynarray_append(rtpoll->defer_events, event);
+    defer_event_enable(event, true);
+
+    return event;
+}
+
+static void defer_event_free(pa_defer_event *event) {
+    pa_assert(event);
+
+    defer_event_enable(event, false);
+    pa_dynarray_remove_by_data(event->rtpoll->defer_events, event);
+
+    if (event->destroy_callback)
+        event->destroy_callback(&event->rtpoll->mainloop_api, event, event-
>userdata);
+
+    pa_xfree(event);
+}
+
+static void defer_event_enable(pa_defer_event *event, int enable) {
+    pa_assert(event);
+
+    if (enable == event->enabled)
+        return;
+
+    event->enabled = enable;
+
+    if (enable)
+        pa_dynarray_append(event->rtpoll->enabled_defer_events, event);
+    else
+        pa_dynarray_remove_by_data(event->rtpoll->enabled_defer_events, 
event);
+}
+
+static void defer_event_set_destroy(pa_defer_event *event, 
pa_defer_event_destroy_cb_t
+callback) {
+    pa_assert(event);
+
+    event->destroy_callback = callback;
+}
+
+static void mainloop_api_quit(pa_mainloop_api *api, int retval) {
+    pa_rtpoll *rtpoll;
+
+    pa_assert(api);
+    pa_assert(api->userdata);
+
+    rtpoll = api->userdata;
+    pa_assert(api == &rtpoll->mainloop_api);
+
+    //pa_rtpoll_quit(rtpoll);
+}
+
+static const pa_mainloop_api vtable = {
+    .userdata = NULL,
+
+    .io_new = io_event_new,
+    .io_enable = io_event_enable,
+    .io_free = io_event_free,
+    .io_set_destroy = io_event_set_destroy,
+
+    .time_new = time_event_new,
+    .time_restart = time_event_restart,
+    .time_free = time_event_free,
+    .time_set_destroy = time_event_set_destroy,
+
+    .defer_new = defer_event_new,
+    .defer_enable = defer_event_enable,
+    .defer_free = defer_event_free,
+    .defer_set_destroy = defer_event_set_destroy,
+
+    .quit = mainloop_api_quit,
+};
+
 pa_rtpoll *pa_rtpoll_new(void) {
     pa_rtpoll *p;
 
@@ -96,6 +426,14 @@  pa_rtpoll *pa_rtpoll_new(void) {
 #ifdef DEBUG_TIMING
     p->timestamp = pa_rtclock_now();
 #endif
+    p->mainloop_api = vtable;
+    p->mainloop_api.userdata = p;
+    p->io_events = pa_dynarray_new(NULL);
+    p->time_events = pa_dynarray_new(NULL);
+    p->enabled_time_events = pa_dynarray_new(NULL);
+    p->expired_time_events = pa_dynarray_new(NULL);
+    p->defer_events = pa_dynarray_new(NULL);
+    p->enabled_defer_events = pa_dynarray_new(NULL);
 
     return p;
 }
@@ -164,15 +502,105 @@  static void rtpoll_item_destroy(pa_rtpoll_item *i) {
 
 void pa_rtpoll_free(pa_rtpoll *p) {
     pa_assert(p);
+    if (p->defer_events) {
+        pa_defer_event *event;
+
+        while ((event = pa_dynarray_last(p->defer_events)))
+            defer_event_free(event);
+    }
+
+    if (p->time_events) {
+        pa_time_event *event;
+
+        while ((event = pa_dynarray_last(p->time_events)))
+            time_event_free(event);
+    }
+
+    if (p->io_events) {
+        pa_io_event *event;
+
+        while ((event = pa_dynarray_last(p->io_events)))
+            io_event_free(event);
+    }
 
     while (p->items)
         rtpoll_item_destroy(p->items);
+    if (p->enabled_defer_events) {
+        pa_assert(pa_dynarray_size(p->enabled_defer_events) == 0);
+        pa_dynarray_free(p->enabled_defer_events);
+    }
+
+    if (p->defer_events) {
+        pa_assert(pa_dynarray_size(p->defer_events) == 0);
+        pa_dynarray_free(p->defer_events);
+    }
+
+    if (p->expired_time_events) {
+        pa_assert(pa_dynarray_size(p->expired_time_events) == 0);
+        pa_dynarray_free(p->expired_time_events);
+    }
+
+    if (p->enabled_time_events) {
+        pa_assert(pa_dynarray_size(p->enabled_time_events) == 0);
+        pa_dynarray_free(p->enabled_time_events);
+    }
+
+    if (p->time_events) {
+        pa_assert(pa_dynarray_size(p->time_events) == 0);
+        pa_dynarray_free(p->time_events);
+    }
+
+    if (p->io_events) {
+        pa_assert(pa_dynarray_size(p->io_events) == 0);
+        pa_dynarray_free(p->io_events);
+    }
 
     pa_xfree(p->pollfd);
     pa_xfree(p->pollfd2);
 
     pa_xfree(p);
 }
+pa_mainloop_api *pa_rtpoll_get_mainloop_api(pa_rtpoll *rtpoll) {
+    pa_assert(rtpoll);
+
+    return &rtpoll->mainloop_api;
+}
+
+static void find_expired_time_events(pa_rtpoll *rtpoll) {
+    pa_usec_t now;
+    pa_time_event *event;
+    unsigned idx;
+
+    pa_assert(rtpoll);
+    pa_assert(pa_dynarray_size(rtpoll->expired_time_events) == 0);
+
+    now = pa_rtclock_now();
+
+    PA_DYNARRAY_FOREACH(event, rtpoll->enabled_time_events, idx) {
+        if (event->time <= now)
+            pa_dynarray_append(rtpoll->expired_time_events, event);
+    }
+}
+
+static pa_time_event *find_next_time_event(pa_rtpoll *rtpoll) {
+    pa_time_event *event;
+    pa_time_event *result = NULL;
+    unsigned idx;
+
+    pa_assert(rtpoll);
+
+    if (rtpoll->cached_next_time_event)
+        return rtpoll->cached_next_time_event;
+
+    PA_DYNARRAY_FOREACH(event, rtpoll->enabled_time_events, idx) {
+        if (!result || event->time < result->time)
+            result = event;
+    }
+
+    rtpoll->cached_next_time_event = result;
+
+    return result;
+}
 
 static void reset_revents(pa_rtpoll_item *i) {
     struct pollfd *f;
@@ -202,10 +630,14 @@  static void reset_all_revents(pa_rtpoll *p) {
 }
 
 int pa_rtpoll_run(pa_rtpoll *p) {
+    pa_defer_event *defer_event;
+    pa_time_event *time_event;
     pa_rtpoll_item *i;
     int r = 0;
     struct timeval timeout;
-
+    pa_time_event *next_time_event;
+    struct timeval next_time_event_elapse;
+    bool timer_enabled;
     pa_assert(p);
     pa_assert(!p->running);
 
@@ -216,7 +648,29 @@  int pa_rtpoll_run(pa_rtpoll *p) {
     p->running = true;
     p->timer_elapsed = false;
 
-    /* First, let's do some work */
+    /* Dispatch all enabled defer events. */
+    while ((defer_event = pa_dynarray_last(p->enabled_defer_events))) {
+        if (p->quit)
+            break;
+
+        defer_event->callback(&p->mainloop_api, defer_event, defer_event-
>userdata);
+    }
+
+    /* Dispatch all expired time events. */
+    find_expired_time_events(p);
+    while ((time_event = pa_dynarray_last(p->expired_time_events))) {
+        struct timeval tv;
+
+        if (p->quit)
+            break;
+
+        time_event_restart(time_event, NULL);
+        time_event->callback(&p->mainloop_api, time_event, 
pa_timeval_rtstore(&tv, time_event->time, time_event->use_rtclock),
+                             time_event->userdata);
+    }
+
+    /* Let's do some work */
+
     for (i = p->items; i && i->priority < PA_RTPOLL_NEVER; i = i->next) {
         int k;
 
@@ -280,15 +734,40 @@  int pa_rtpoll_run(pa_rtpoll *p) {
     if (p->rebuild_needed)
         rtpoll_rebuild(p);
 
+    /* Calculate timeout */
+
     pa_zero(timeout);
 
-    /* Calculate timeout */
-    if (!p->quit && p->timer_enabled) {
-        struct timeval now;
-        pa_rtclock_get(&now);
+    next_time_event = find_next_time_event(p);
+    if (next_time_event)
+        pa_timeval_rtstore(&next_time_event_elapse, next_time_event->time, 
next_time_event->use_rtclock);
+
+    /* p->timer_enabled and p->next_elapse are controlled by the rtpoll 
owner,
+     * while the time events can be created by anyone through 
pa_mainloop_api.
+     * It might be a good idea to merge p->timer_enabled and p->next_elapse
+     * with the time events so that we wouldn't need to handle them 
separately
+     * here. The reason why they are currently separate is that the
+     * pa_mainloop_api interface was bolted on pa_rtpoll as an afterthought. 
*/
+    timer_enabled = p->timer_enabled || next_time_event;
+
+    if (!p->quit && timer_enabled) {
+        struct timeval *next_elapse;
+         struct timeval now;
+
+        if (p->timer_enabled && next_time_event) {
+            if (pa_timeval_cmp(&p->next_elapse, &next_time_event_elapse) > 0)
+                next_elapse = &next_time_event_elapse;
+            else
+                next_elapse = &p->next_elapse;
+        } else if (p->timer_enabled)
+            next_elapse = &p->next_elapse;
+        else
+            next_elapse = &next_time_event_elapse;
 
-        if (pa_timeval_cmp(&p->next_elapse, &now) > 0)
-            pa_timeval_add(&timeout, pa_timeval_diff(&p->next_elapse, &now));
+        pa_rtclock_get(&now);
+        if (pa_timeval_cmp(next_elapse, &now) > 0)
+            pa_timeval_add(&timeout, pa_timeval_diff(next_elapse, &now));
+        
     }
 
 #ifdef DEBUG_TIMING
@@ -296,7 +775,7 @@  int pa_rtpoll_run(pa_rtpoll *p) {
         pa_usec_t now = pa_rtclock_now();
         p->awake = now - p->timestamp;
         p->timestamp = now;
-        if (!p->quit && p->timer_enabled)
+        if (!p->quit && timer_enabled)
             pa_log("poll timeout: %d ms ",(int) ((timeout.tv_sec*1000) + 
(timeout.tv_usec / 1000)));
         else if (p->quit)
             pa_log("poll timeout is ZERO");
@@ -311,12 +790,22 @@  int pa_rtpoll_run(pa_rtpoll *p) {
         struct timespec ts;
         ts.tv_sec = timeout.tv_sec;
         ts.tv_nsec = timeout.tv_usec * 1000;
-        r = ppoll(p->pollfd, p->n_pollfd_used, (p->quit || p->timer_enabled) 
? &ts : NULL, NULL);
+        r = ppoll(p->pollfd, p->n_pollfd_used, (p->quit || timer_enabled) ? 
&ts : NULL, NULL);
     }
 #else
-    r = pa_poll(p->pollfd, p->n_pollfd_used, (p->quit || p->timer_enabled) ? 
(int) ((timeout.tv_sec*1000) + (timeout.tv_usec / 1000)) : -1);
+    r = pa_poll(p->pollfd, p->n_pollfd_used, (p->quit || timer_enabled) ? 
(int) ((timeout.tv_sec*1000) +
+ (timeout.tv_usec / 1000)) : -1);
 #endif
 
+    /* FIXME: We don't know whether the pa_rtpoll owner's timer elapsed or 
one
+     * of the time events created by others through pa_mainloop_api. The alsa
+     * sink and source use pa_rtpoll_timer_elapsed() to check whether *their*
+     * timer elapsed, so this ambiguity is a problem for them in theory.
+     * However, currently the pa_rtpoll objects of the alsa sink and source 
are
+     * not being used through pa_mainloop_api, so in practice there's no
+     * ambiguity. We could use pa_rtclock_now() to check whether p-
>next_elapse
+     * is in the past, but we don't do that currently, because 
pa_rtclock_now()
+     * is somewhat expensive and this ambiguity isn't currently a big issue. 
*/
     p->timer_elapsed = r == 0;
 
 #ifdef DEBUG_TIMING
diff --git a/src/pulsecore/rtpoll.h b/src/pulsecore/rtpoll.h
index 8f0715ac..e8ceada1 100644
--- a/src/pulsecore/rtpoll.h
+++ b/src/pulsecore/rtpoll.h
@@ -23,6 +23,7 @@ 
 #include <sys/types.h>
 #include <limits.h>
 
+#include <pulse/mainloop-api.h>
 #include <pulse/sample.h>
 #include <pulsecore/asyncmsgq.h>
 #include <pulsecore/fdsem.h>
@@ -53,6 +54,7 @@  typedef enum pa_rtpoll_priority {
 
 pa_rtpoll *pa_rtpoll_new(void);
 void pa_rtpoll_free(pa_rtpoll *p);
+pa_mainloop_api *pa_rtpoll_get_mainloop_api(pa_rtpoll *rtpoll);
 
 /* Sleep on the rtpoll until the time event, or any of the fd events
  * is triggered. Returns negative on error, positive if the loop
diff --git a/src/pulsecore/transcode.c b/src/pulsecore/transcode.c
new file mode 100644
index 00000000..5d9bf05d
--- /dev/null
+++ b/src/pulsecore/transcode.c
@@ -0,0 +1,541 @@ 
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+
+#include <pulsecore/macro.h>
+#include <pulsecore/core-format.h>
+
+
+#include "transcode.h"
+
+
+
+#ifdef HAVE_OPUS
+
+#include <opus.h>
+
+#define OPUS_DEFAULT_SAMPLE_RATE 48000
+#define OPUS_DEFAULT_SAMPLE_SIZE 2
+#define OPUS_MAX_FRAME_SIZE 2880
+
+
+
+OpusEncoder *tc_opus_create_encoder(pa_transcode * params);
+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);
+
+#endif
+
+/* Conversion of string names to constant values */
+typedef struct tc_str_to_const {
+    const char *key;
+    int value;
+} tc_str_to_const_t;
+
+/* Flags for the parameter table */
+typedef enum tc_flags {
+    TC_NONE = 0x00,
+    TC_FIXED = 0x01             /* Parameter can only be set on module load 
*/
+} tc_flags_t;
+
+/* Parameter table
+ *
+ * name      : name of a property of the sink. PA_PROP_COMPRESSION constants.
+ * const_data: structure containing string to constant value mappings. 
Optional.
+ * min, max  : valid range for the parameter if const_data is NULL
+ */
+
+typedef struct tc_prop_data {
+    const char *name;
+    const char *default_value;
+    tc_str_to_const_t *const_data;
+    int min;
+    int max;
+    tc_flags_t flags;
+} tc_prop_data_t;
+
+/*
+ * tables are terminated with a NULL key with a value that will be 
interpreted
+ * as the default.
+ *
+ * This table lists the know algorithms. It is checked by 
pa_transcode_supported,
+ * so if something is listed here, it will be reported as being supported.
+ */
+
+
+static tc_str_to_const_t algo_map[] = {
+#ifdef HAVE_OPUS
+    { "opus", PA_ENCODING_OPUS    },
+#endif
+    { "none", PA_ENCODING_INVALID },
+    { NULL  , PA_ENCODING_INVALID }
+};
+
+#ifdef HAVE_OPUS
+static tc_str_to_const_t opus_application_map[] = {
+    { "voip"               , OPUS_APPLICATION_VOIP                },
+    { "audio"              , OPUS_APPLICATION_AUDIO               },
+    { "restricted_lowdelay", OPUS_APPLICATION_RESTRICTED_LOWDELAY },
+    { NULL                 , OPUS_APPLICATION_AUDIO               }
+};
+
+static tc_str_to_const_t opus_bandwidth_map[] = {
+    { "narrowband"   , OPUS_BANDWIDTH_NARROWBAND    },
+    { "mediumband"   , OPUS_BANDWIDTH_MEDIUMBAND    },
+    { "wideband"     , OPUS_BANDWIDTH_WIDEBAND      },
+    { "superwideband", OPUS_BANDWIDTH_SUPERWIDEBAND },
+    { "fullband"     , OPUS_BANDWIDTH_FULLBAND      },
+    { NULL           , OPUS_BANDWIDTH_FULLBAND      }
+};
+
+static tc_str_to_const_t opus_signal_map[] = {
+    { "auto" , OPUS_AUTO         },
+    { "voice", OPUS_SIGNAL_VOICE },
+    { "music", OPUS_SIGNAL_MUSIC },
+    { NULL   , OPUS_AUTO         }
+};
+#endif
+
+/* This table lists all the config values, their defaults, and limits */
+static tc_prop_data_t prop_data[] = {
+    { PA_PROP_COMPRESSION_ALGORITHM          , "none"    , algo_map            
, 0  , 0     , TC_FIXED },
+    { PA_PROP_COMPRESSION_BITRATE            , "64000"   , NULL                
, 500, 512000, TC_NONE  },
+    { PA_PROP_COMPRESSION_FRAME_SIZE         , "2880"    , NULL                
, 0  , 5000  , TC_FIXED },
+#ifdef HAVE_OPUS
+    { PA_PROP_COMPRESSION_OPUS_COMPLEXITY    , "10"      , NULL                
, 0  , 10    , TC_NONE  },
+    { PA_PROP_COMPRESSION_OPUS_MAX_BANDWIDTH , "fullband", opus_bandwidth_map  
, 0  , 0     , TC_NONE  },
+    { PA_PROP_COMPRESSION_OPUS_SIGNAL        , "auto"    , opus_signal_map     
, 0  , 0     , TC_NONE  },
+    { PA_PROP_COMPRESSION_OPUS_VBR           , "0"       , NULL                
, 0  , 1     , TC_NONE  },
+    { PA_PROP_COMPRESSION_OPUS_VBR_CONSTRAINT, "0"       , NULL                
, 0  , 1     , TC_NONE  },
+    { PA_PROP_COMPRESSION_OPUS_APPLICATION   , "audio"   , 
opus_application_map, 0  , 0     , TC_FIXED },
+    { PA_PROP_COMPRESSION_OPUS_LSB_DEPTH     , "24"      , NULL                
, 1  , 24    , TC_NONE  },
+    { PA_PROP_COMPRESSION_OPUS_DTX           , "0"       , NULL                
, 0  , 1     , TC_NONE  },
+#endif
+    { NULL                                   , NULL      , NULL                
, 0  , 0     , TC_NONE  }
+};
+
+
+int tc_string_to_const(tc_str_to_const_t map[], const char *key);
+const char *tc_const_to_string(tc_str_to_const_t map[], int value);
+void tc_copy_arg(pa_modargs * ma, const char *arg, pa_proplist * p, const 
char *name);
+
+
+
+int tc_string_to_const(tc_str_to_const_t map[], const char *key) {
+    tc_str_to_const_t *p = &map[0];
+
+    while (p->key != NULL) {
+        if (strcmp(p->key, key) == 0)
+            return p->value;
+
+        p++;
+    }
+
+    return p->value;
+}
+
+const char *tc_const_to_string(tc_str_to_const_t map[], int value) {
+    tc_str_to_const_t *p = &map[0];
+
+    while (p->key != NULL) {
+        if (p->value == value)
+            return p->key;
+
+        p++;
+    }
+
+    return NULL;
+}
+
+void tc_copy_arg(pa_modargs * ma, const char *arg, pa_proplist * p, const 
char *name) {
+    const char *val = NULL;
+    val = pa_modargs_get_value(ma, arg, NULL);
+
+    if (val)
+        pa_proplist_sets(p, name, val);
+}
+
+
+/*
+ *  This function returns the value of a parameter, in a numeric format
+ *  If it's an enum type, it gets converted to its value via the lookup 
table.
+ *  Otherwise it returns the numeric value as-is
+ */
+int pa_transcode_get_param(pa_transcode * transcode, const char *key) {
+    tc_prop_data_t *p = &prop_data[0];
+
+    while (p->name) {
+        if (strcmp(p->name, key) == 0) {
+
+            const char *value = pa_proplist_gets(transcode->proplist, key);
+
+            if (p->const_data) {
+                return tc_string_to_const(p->const_data, value);
+            } else {
+                return atoi(value);
+            }
+        }
+        p++;
+    }
+
+    /* This should never happen */
+    pa_log_error("BUG: Parameter '%s' not found", key);
+    return 0;
+}
+
+
+void pa_transcode_update_encoder_options(pa_transcode * transcode) {
+#ifdef HAVE_OPUS
+    int err;
+
+
+    if (transcode->encoding == PA_ENCODING_OPUS) {
+        err = opus_encoder_ctl(transcode->encoder,
+                               
OPUS_SET_BITRATE(pa_transcode_get_param(transcode, 
PA_PROP_COMPRESSION_BITRATE)));
+        if (err < 0)
+            pa_log_error("failed to set bitrate: %s", opus_strerror(err));
+
+
+        err = opus_encoder_ctl(transcode->encoder,
+                               
OPUS_SET_COMPLEXITY(pa_transcode_get_param(transcode, 
PA_PROP_COMPRESSION_OPUS_COMPLEXITY)));
+        if (err < 0)
+            pa_log_error("failed to set complexity: %s", opus_strerror(err));
+
+
+        err = opus_encoder_ctl(transcode->encoder,
+                               OPUS_SET_DTX(pa_transcode_get_param(transcode, 
PA_PROP_COMPRESSION_OPUS_DTX)));
+
+        if (err < 0)
+            pa_log_error("failed to set DTX: %s", opus_strerror(err));
+
+
+        err = opus_encoder_ctl(transcode->encoder,
+                               
OPUS_SET_MAX_BANDWIDTH(pa_transcode_get_param(transcode, 
PA_PROP_COMPRESSION_OPUS_MAX_BANDWIDTH)));
+
+        if (err < 0)
+            pa_log_error("failed to set max bandwidth: %s", 
opus_strerror(err));
+
+
+        err = opus_encoder_ctl(transcode->encoder,
+                               
OPUS_SET_SIGNAL(pa_transcode_get_param(transcode, 
PA_PROP_COMPRESSION_OPUS_SIGNAL)));
+        if (err < 0)
+            pa_log_error("failed to set signal: %s", opus_strerror(err));
+
+
+        err = opus_encoder_ctl(transcode->encoder, 
OPUS_SET_VBR(pa_transcode_get_param(transcode, 
PA_PROP_COMPRESSION_OPUS_VBR)));
+
+        if (err < 0)
+            pa_log_error("failed to set VBR: %s", opus_strerror(err));
+
+
+        err = opus_encoder_ctl(transcode->encoder,
+                               OPUS_SET_VBR_CONSTRAINT(pa_transcode_get_param
+                                                     (transcode, 
PA_PROP_COMPRESSION_OPUS_VBR_CONSTRAINT)));
+        if (err < 0)
+            pa_log_error("failed to set VBR constraint: %s", 
opus_strerror(err));
+
+
+        err = opus_encoder_ctl(transcode->encoder,
+                               
OPUS_SET_LSB_DEPTH(pa_transcode_get_param(transcode, 
PA_PROP_COMPRESSION_OPUS_LSB_DEPTH)));
+
+        if (err < 0)
+            pa_log_error("failed to set LSB depth: %s", opus_strerror(err));
+
+    }
+#endif
+
+
+}
+
+#ifdef HAVE_OPUS
+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(pa_transcode * params) {
+    int err;
+    int application = pa_transcode_get_param(params,
+                                             
PA_PROP_COMPRESSION_OPUS_APPLICATION);
+
+
+    OpusEncoder *encoder = opus_encoder_create(params->rate, params-
>channels, application,
+                                               &err);
+    if (err < 0) {
+        pa_log_error("failed to create an encoder: %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 < channels * frame_size; i++)
+        in[i] = pcm_bytes[2 * i + 1] << 8 | pcm_bytes[2 * i];
+
+    nbBytes = opus_encode(encoder, in, frame_size, cbits, frame_size * 
channels * 2);
+    if (nbBytes < 0) {
+        pa_log_error("encode failed: %s", opus_strerror(nbBytes));
+        return EXIT_FAILURE;
+    }
+
+    return nbBytes;
+}
+
+
+int
+tc_opus_decode(OpusDecoder * decoder, unsigned char *cbits, int nbBytes,
+               unsigned char *pcm_bytes, int channels, int max_frame_size) {
+    int err = 0, i;
+    opus_int16 out[max_frame_size * channels];
+
+    int frame_size = opus_decode(decoder, cbits, nbBytes, out, 
max_frame_size, 0);
+    if (frame_size < 0) {
+        pa_log_error("decoder failed: %s", opus_strerror(err));
+        return EXIT_FAILURE;
+    }
+
+    /* Convert to little-endian ordering. */
+    for (i = 0; i < channels * frame_size; i++) {
+        pcm_bytes[2 * i] = out[i] & 0xFF;
+        pcm_bytes[2 * i + 1] = (out[i] >> 8) & 0xFF;
+    }
+
+    return frame_size;
+}
+
+#endif
+
+
+
+/*
+ *  Copy module arguments into a property list.
+ *  Actual verification is done in pa_transcode_verify_proplist
+ */
+void pa_transcode_copy_arguments(pa_transcode * transcode, pa_modargs * ma, 
pa_proplist * prop) {
+    tc_copy_arg(ma, "compression", prop, PA_PROP_COMPRESSION_ALGORITHM);
+    tc_copy_arg(ma, "compression-bitrate", prop, 
PA_PROP_COMPRESSION_BITRATE);
+    tc_copy_arg(ma, "compression-frame_size", prop, 
PA_PROP_COMPRESSION_FRAME_SIZE);
+    tc_copy_arg(ma, "compression-complexity", prop, 
PA_PROP_COMPRESSION_OPUS_COMPLEXITY);
+    tc_copy_arg(ma, "compression-max_bandwidth", prop, 
PA_PROP_COMPRESSION_OPUS_MAX_BANDWIDTH);
+    tc_copy_arg(ma, "compression-signal", prop, 
PA_PROP_COMPRESSION_OPUS_SIGNAL);
+    tc_copy_arg(ma, "compression-vbr", prop, PA_PROP_COMPRESSION_OPUS_VBR);
+    tc_copy_arg(ma, "compression-vbr_constraint", prop, 
PA_PROP_COMPRESSION_OPUS_VBR_CONSTRAINT);
+    tc_copy_arg(ma, "compression-application", prop, 
PA_PROP_COMPRESSION_OPUS_APPLICATION);
+    tc_copy_arg(ma, "compression-lsb_depth", prop, 
PA_PROP_COMPRESSION_OPUS_LSB_DEPTH);
+    tc_copy_arg(ma, "compression-dtx", prop, PA_PROP_COMPRESSION_OPUS_DTX);
+
+}
+
+void pa_transcode_verify_proplist(pa_transcode * transcode) {
+
+    tc_prop_data_t *p = &prop_data[0];
+    const char *value = NULL;
+    int intval = 0;
+    bool found = false;
+    tc_str_to_const_t *cp = NULL;
+
+    while (p->name) {
+        if (!pa_proplist_contains(transcode->proplist, p->name)) {
+            pa_proplist_sets(transcode->proplist, p->name, p->default_value);
+
+        } else {
+            value = pa_proplist_gets(transcode->proplist, p->name);
+
+            if (p->const_data) {
+                /* Check if the value corresponds to one of the valid 
constants */
+                cp = p->const_data;
+                while (cp->key) {
+                    if (strcmp(cp->key, value) == 0) {
+                        found = true;
+                        break;
+                    }
+                    cp++;
+                }
+
+                if (!found) {
+                    /* We have the numeric default, so we look for the string 
name for it */
+                    pa_log_error("Parameter '%s' has an invalid value '%s'. 
Set to default.", p->name, value);
+                    pa_proplist_sets(transcode->proplist, p->name, 
tc_const_to_string(p->const_data, cp->value));
+                }
+
+            } else {
+                intval = atoi(value);
+
+                if (intval < p->min) {
+                    pa_proplist_setf(transcode->proplist, p->name, "%i", p-
>min);
+                    pa_log_error("Parameter '%s' out of range", p->name);
+                }
+
+                if (intval > p->max) {
+                    pa_proplist_setf(transcode->proplist, p->name, "%i", p-
>max);
+                    pa_log_error("Parameter '%s' out of range", p->name);
+                }
+
+
+            }
+        }
+
+        p++;
+    }
+
+}
+
+bool pa_transcode_supported(pa_encoding_t encoding) {
+    return (tc_const_to_string(algo_map, encoding) != NULL);
+}
+
+
+bool pa_transcode_supported_byname(const char *name) {
+    return (tc_string_to_const(algo_map, name) != PA_ENCODING_INVALID);
+}
+
+pa_encoding_t pa_transcode_encoding_byname(const char *name) {
+    return (pa_encoding_t) tc_string_to_const(algo_map, name);
+}
+
+void pa_transcode_set_format_info(pa_transcode * transcode, pa_format_info * 
f) {
+    pa_format_info_set_prop_int(f, "frame_size", 
pa_transcode_get_param(transcode, PA_PROP_COMPRESSION_FRAME_SIZE));
+    pa_format_info_set_prop_int(f, "bitrate", 
pa_transcode_get_param(transcode, PA_PROP_COMPRESSION_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_modargs * modargs, 
pa_proplist * proplist) {
+    int frame_size;
+
+    pa_assert((flags & PA_TRANSCODE_DECODER)
+              || (flags & PA_TRANSCODE_ENCODER));
+
+    transcode->flags = flags;
+    transcode->proplist = proplist;
+    transcode->codec_mutex = pa_mutex_new(false, false);
+
+
+    if (modargs) {
+        // Being called for creating an encoder, we receive parameters from a 
command line
+        pa_transcode_copy_arguments(transcode, modargs, proplist);
+        pa_transcode_verify_proplist(transcode);
+        transcode->encoding = pa_transcode_get_param(transcode, 
PA_PROP_COMPRESSION_ALGORITHM);
+    } else {
+        // Being called for creating a decoder, no arguments
+        transcode->encoding = encoding;
+    }
+
+    switch (transcode->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_MAX_FRAME_SIZE;
+            if (pa_format_info_get_prop_int(transcode_format_info, 
"frame_size", &frame_size) != 0)
+                pa_proplist_setf(transcode->proplist, 
PA_PROP_COMPRESSION_FRAME_SIZE, "%i", frame_size);
+
+            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 {
+            transcode->channels = transcode_sink_spec->channels;
+            transcode->rate = OPUS_DEFAULT_SAMPLE_RATE;
+            transcode->encoder = tc_opus_create_encoder(transcode);
+
+
+            transcode_sink_spec->rate = transcode->rate;
+            transcode_sink_spec->format = PA_SAMPLE_S16LE;
+
+            pa_transcode_update_encoder_options(transcode);
+
+        }
+
+        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;
+    int frame_size = pa_transcode_get_param(transcode, 
PA_PROP_COMPRESSION_FRAME_SIZE);
+
+    pa_mutex_lock(transcode->codec_mutex);
+    switch (transcode->encoding) {
+#ifdef HAVE_OPUS
+    case PA_ENCODING_OPUS:
+        *compressed_output = malloc(frame_size * transcode->channels * 
transcode->sample_size);
+        nbBytes = tc_opus_encode(transcode->encoder, pcm_input, 
*compressed_output, transcode->channels, frame_size);
+        break;
+#endif
+
+    }
+
+    pa_mutex_unlock(transcode->codec_mutex);
+
+    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;
+
+    pa_mutex_lock(transcode->codec_mutex);
+
+    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
+
+    }
+
+    pa_mutex_unlock(transcode->codec_mutex);
+
+    return frame_length;
+}
diff --git a/src/pulsecore/transcode.h b/src/pulsecore/transcode.h
new file mode 100644
index 00000000..43614a0c
--- /dev/null
+++ b/src/pulsecore/transcode.h
@@ -0,0 +1,70 @@ 
+#ifndef footranscodehfoo
+#define footranscodehfoo
+
+#include <pulsecore/core-format.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/proplist-util.h>
+#include <pulsecore/mutex.h>
+
+/*
+ * Bitrate and frame size are used externally, in module-tunnel-sink-new
+ * We consider these the universal parameters that are going to apply to
+ * any other codec that might be implemented in the future.
+ */
+#define PA_PROP_COMPRESSION_ALGORITHM           "compression.algorithm"
+#define PA_PROP_COMPRESSION_BITRATE             "compression.bitrate"
+#define PA_PROP_COMPRESSION_FRAME_SIZE          "compression.frame_size"
+
+/* The rest of the parameters are Opus specific. */
+#define PA_PROP_COMPRESSION_OPUS_COMPLEXITY     "compression.opus.complexity"
+#define PA_PROP_COMPRESSION_OPUS_MAX_BANDWIDTH  
"compression.opus.max_bandwidth"
+#define PA_PROP_COMPRESSION_OPUS_SIGNAL         "compression.opus.signal"
+#define PA_PROP_COMPRESSION_OPUS_VBR            "compression.opus.vbr"
+#define PA_PROP_COMPRESSION_OPUS_VBR_CONSTRAINT 
"compression.opus.vbr_constraint"
+#define PA_PROP_COMPRESSION_OPUS_APPLICATION    
"compression.opus.application"
+#define PA_PROP_COMPRESSION_OPUS_LSB_DEPTH      "compression.opus.lsb_depth"
+#define PA_PROP_COMPRESSION_OPUS_DTX            "compression.opus.dtx"
+
+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 max_frame_size;
+    uint32_t sample_size;
+    uint32_t rate;
+
+    pa_proplist *proplist;
+
+    pa_transcode_flags_t flags;
+    pa_mutex *codec_mutex;
+
+    union {
+        void *decoder;
+        void *encoder;
+    };
+} pa_transcode;
+
+void pa_transcode_copy_arguments(pa_transcode * transcode, pa_modargs * ma, 
pa_proplist * prop);
+void pa_transcode_verify_proplist(pa_transcode * transcode);
+void pa_transcode_update_encoder_options(pa_transcode * transcode);
+int pa_transcode_get_param(pa_transcode * transcode, const char *key);
+
+bool pa_transcode_supported(pa_encoding_t encoding);
+bool pa_transcode_supported_byname(const char *name);
+pa_encoding_t pa_transcode_encoding_byname(const char *name);
+
+
+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, pa_modargs * modargs,