]> code.delx.au - pulseaudio/commitdiff
core: Add extended stream API to support compressed formats
authorArun Raghavan <arun.raghavan@collabora.co.uk>
Mon, 28 Feb 2011 07:53:23 +0000 (13:23 +0530)
committerArun Raghavan <arun.raghavan@collabora.co.uk>
Mon, 2 May 2011 06:24:43 +0000 (11:54 +0530)
This is the beginning of work to support compressed formats natively in
PulseAudio. This adds a pa_stream_new_extended() that takes a format
structure, sends it to the server (=> protocol extension) and has the
server negotiate with the appropropriate sink to figure out what format
it should use.

This is work in progress, and works only with PCM streams. Actual
compressed format support in some sink needs to be implemented, and
extensive testing is required.

More details on how this is supposed to work is available at:
http://pulseaudio.org/wiki/PassthroughSupport

25 files changed:
PROTOCOL
configure.ac
src/map-file
src/modules/echo-cancel/module-echo-cancel.c
src/modules/module-combine-sink.c
src/modules/module-device-manager.c
src/modules/module-equalizer-sink.c
src/modules/module-intended-roles.c
src/modules/module-ladspa-sink.c
src/modules/module-loopback.c
src/modules/module-remap-sink.c
src/modules/module-sine.c
src/modules/module-stream-restore.c
src/modules/module-virtual-sink.c
src/modules/rtp/module-rtp-recv.c
src/pulse/internal.h
src/pulse/stream.c
src/pulse/stream.h
src/pulsecore/play-memblockq.c
src/pulsecore/protocol-esound.c
src/pulsecore/protocol-native.c
src/pulsecore/protocol-simple.c
src/pulsecore/sink-input.c
src/pulsecore/sink-input.h
src/pulsecore/sound-file-stream.c

index a15d1163e070946779d26cdb3bc2dda573f2f585..d06cb9886ab992ec1a6fce50d3b1ffbcb55c9434 100644 (file)
--- a/PROTOCOL
+++ b/PROTOCOL
@@ -213,3 +213,18 @@ Two new flags at the end of sink input introspection data:
 
     bool has_volume
     bool volume_writable
+
+## v21, implemented by >= 1.0
+
+Changes for format negotiation in the extended API.
+
+New fields PA_COMMAND_CREATE_PLAYBACK_STREAM:
+
+    uint8_t n_formats
+    format_info format1
+    ...
+    format_info formatn
+
+One new field in reply from PA_COMMAND_CREATE_PLAYBACK_STREAM:
+
+    format_info format
index ec1099ba8277af227f4e60f43b3afc245f1e78a2..9edae0ec6b55b4abf9f1b5e5cabac61daa7cbb8b 100644 (file)
@@ -37,7 +37,7 @@ AC_SUBST(PA_MAJORMINOR, pa_major.pa_minor)
 AC_SUBST(PACKAGE_URL, [http://pulseaudio.org/])
 
 AC_SUBST(PA_API_VERSION, 12)
-AC_SUBST(PA_PROTOCOL_VERSION, 20)
+AC_SUBST(PA_PROTOCOL_VERSION, 21)
 
 # The stable ABI for client applications, for the version info x:y:z
 # always will hold y=z
index 181af9ea635424ad26c224c24c449a50f19fe379..b4196f150ddb777cdbbeb3c030b945156c1d5518 100644 (file)
@@ -265,6 +265,7 @@ pa_stream_get_timing_info;
 pa_stream_is_corked;
 pa_stream_is_suspended;
 pa_stream_new;
+pa_stream_new_extended;
 pa_stream_new_with_proplist;
 pa_stream_peek;
 pa_stream_prebuf;
index 37879629b7ad449d8c2fbd8af2e2afbe843678a2..a06e481d827afca0581b1c5583e7baab49cdce32 100644 (file)
@@ -1549,7 +1549,7 @@ int pa__init(pa_module*m) {
     pa_sink_input_new_data_init(&sink_input_data);
     sink_input_data.driver = __FILE__;
     sink_input_data.module = m;
-    sink_input_data.sink = sink_master;
+    pa_sink_input_new_data_set_sink(&sink_input_data, sink_master, FALSE);
     sink_input_data.origin_sink = u->sink;
     pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "Echo-Cancel Sink Stream");
     pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter");
index 09af942d88160213fee67230bdbdac6cbe469b28..f6d645312120972372622d19d1b79017568359cb 100644 (file)
@@ -845,7 +845,7 @@ static int output_create_sink_input(struct output *o) {
         return 0;
 
     pa_sink_input_new_data_init(&data);
-    data.sink = o->sink;
+    pa_sink_input_new_data_set_sink(&data, o->sink, FALSE);
     data.driver = __FILE__;
     pa_proplist_setf(data.proplist, PA_PROP_MEDIA_NAME, "Simultaneous output on %s", pa_strnull(pa_proplist_gets(o->sink->proplist, PA_PROP_DEVICE_DESCRIPTION)));
     pa_proplist_sets(data.proplist, PA_PROP_MEDIA_ROLE, "filter");
index 47469b06c7a744e0d50380b94bba63303faae4cb..c28affd1ee07fd85bcf7c781e491e9e69b1d2e3a 100644 (file)
@@ -832,8 +832,8 @@ static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_n
                 pa_sink *sink;
 
                 if ((sink = pa_idxset_get_by_index(u->core->sinks, device_index))) {
-                    new_data->sink = sink;
-                    new_data->save_sink = FALSE;
+                    if (!pa_sink_input_new_data_set_sink(new_data, sink, FALSE))
+                        pa_log_debug("Not restoring device for stream because no supported format was found");
                 }
             }
         }
index 9a85fe5938c7fa2d7d8fa04bdaafe993dfc1d0c8..e20ee4ab58d0f81f3fbcf83c28b36b46ac5265ca 100644 (file)
@@ -1212,7 +1212,7 @@ int pa__init(pa_module*m) {
     pa_sink_input_new_data_init(&sink_input_data);
     sink_input_data.driver = __FILE__;
     sink_input_data.module = m;
-    sink_input_data.sink = master;
+    pa_sink_input_new_data_set_sink(&sink_input_data, master, FALSE);
     sink_input_data.origin_sink = u->sink;
     pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "Equalized Stream");
     pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter");
index d19444d204444b11e45ddd24bb4c9336706d491d..90385622b90397309640a58efa8cb8ea59508032 100644 (file)
@@ -117,11 +117,8 @@ static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_n
 
     /* Prefer the default sink over any other sink, just in case... */
     if ((def = pa_namereg_get_default_sink(c)))
-        if (role_match(def->proplist, role)) {
-            new_data->sink = def;
-            new_data->save_sink = FALSE;
+        if (role_match(def->proplist, role) && pa_sink_input_new_data_set_sink(new_data, def, FALSE))
             return PA_HOOK_OK;
-        }
 
     /* @todo: favour the highest priority device, not the first one we find? */
     PA_IDXSET_FOREACH(s, c->sinks, idx) {
@@ -131,11 +128,8 @@ static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_n
         if (!PA_SINK_IS_LINKED(pa_sink_get_state(s)))
             continue;
 
-        if (role_match(s->proplist, role)) {
-            new_data->sink = s;
-            new_data->save_sink = FALSE;
+        if (role_match(s->proplist, role) && pa_sink_input_new_data_set_sink(new_data, s, FALSE))
             return PA_HOOK_OK;
-        }
     }
 
     return PA_HOOK_OK;
index f6430f299fe6c91c933bf77b785146510ce7b4b6..6489f3f7fe862e147abf81a004dcbf709cfc65fa 100644 (file)
@@ -907,7 +907,7 @@ int pa__init(pa_module*m) {
     pa_sink_input_new_data_init(&sink_input_data);
     sink_input_data.driver = __FILE__;
     sink_input_data.module = m;
-    sink_input_data.sink = master;
+    pa_sink_input_new_data_set_sink(&sink_input_data, master, FALSE);
     sink_input_data.origin_sink = u->sink;
     pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "LADSPA Stream");
     pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter");
index 9a8640b11af0749bde3d4bd57b4432b962bcafbf..ca813b0031e84673b7bf5bcba5d25ff97d9a8b54 100644 (file)
@@ -695,7 +695,7 @@ int pa__init(pa_module *m) {
     pa_sink_input_new_data_init(&sink_input_data);
     sink_input_data.driver = __FILE__;
     sink_input_data.module = m;
-    sink_input_data.sink = sink;
+    pa_sink_input_new_data_set_sink(&sink_input_data, sink, FALSE);
 
     if ((n = pa_modargs_get_value(ma, "sink_input_name", NULL)))
         pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_NAME, n);
index 7f64f3067e1f758b036ac00cd2064065fcc7f902..79627f7a15b68aad696b4f15b61f3fdd0c0d70c7 100644 (file)
@@ -419,7 +419,7 @@ int pa__init(pa_module*m) {
     pa_sink_input_new_data_init(&sink_input_data);
     sink_input_data.driver = __FILE__;
     sink_input_data.module = m;
-    sink_input_data.sink = master;
+    pa_sink_input_new_data_set_sink(&sink_input_data, master, FALSE);
     sink_input_data.origin_sink = u->sink;
     pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "Remapped Stream");
     pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter");
index 69b20028c509884e3eb4ebf88c715f5112ce9ab4..cee01f1accd7110178b03810a6f50b4ef493f604 100644 (file)
@@ -157,7 +157,7 @@ int pa__init(pa_module*m) {
     pa_sink_input_new_data_init(&data);
     data.driver = __FILE__;
     data.module = m;
-    data.sink = sink;
+    pa_sink_input_new_data_set_sink(&data, sink, FALSE);
     pa_proplist_setf(data.proplist, PA_PROP_MEDIA_NAME, "%u Hz Sine", frequency);
     pa_proplist_sets(data.proplist, PA_PROP_MEDIA_ROLE, "abstract");
     pa_proplist_setf(data.proplist, "sine.hz", "%u", frequency);
index 77b6949db92be02ba7f0462414e3ad91952ce05b..fa2c0210c27b851491ae799fc78658d36abe97da 100644 (file)
@@ -1301,11 +1301,9 @@ static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_n
         /* It might happen that a stream and a sink are set up at the
            same time, in which case we want to make sure we don't
            interfere with that */
-        if (s && PA_SINK_IS_LINKED(pa_sink_get_state(s))) {
-            pa_log_info("Restoring device for stream %s.", name);
-            new_data->sink = s;
-            new_data->save_sink = TRUE;
-        }
+        if (s && PA_SINK_IS_LINKED(pa_sink_get_state(s)))
+            if (pa_sink_input_new_data_set_sink(new_data, s, TRUE))
+                pa_log_info("Restoring device for stream %s.", name);
 
         pa_xfree(e);
     }
index 9bcff8c3f6ede778523f8c0a13cc0f23dff3c0c9..fe269304581b84d5732caca9645dd806cdb39bae 100644 (file)
@@ -579,7 +579,7 @@ int pa__init(pa_module*m) {
     pa_sink_input_new_data_init(&sink_input_data);
     sink_input_data.driver = __FILE__;
     sink_input_data.module = m;
-    sink_input_data.sink = master;
+    pa_sink_input_new_data_set_sink(&sink_input_data, master, FALSE);
     sink_input_data.origin_sink = u->sink;
     pa_proplist_setf(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "Virtual Sink Stream from %s", pa_proplist_gets(u->sink->proplist, PA_PROP_DEVICE_DESCRIPTION));
     pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter");
index 1144169b957a220ab2b3270aa3adb98da9032ad6..fb3bccb4e66937263f9efdf640263eeb519731e4 100644 (file)
@@ -512,7 +512,7 @@ static struct session *session_new(struct userdata *u, const pa_sdp_info *sdp_in
         goto fail;
 
     pa_sink_input_new_data_init(&data);
-    data.sink = sink;
+    pa_sink_input_new_data_set_sink(&data, sink, FALSE);
     data.driver = __FILE__;
     pa_proplist_sets(data.proplist, PA_PROP_MEDIA_ROLE, "stream");
     pa_proplist_setf(data.proplist, PA_PROP_MEDIA_NAME,
index 53fcca60c76b412b307a667b211baf9e9688ceab..d7151653f001718237a2ebce5238b32d1baf0762 100644 (file)
@@ -122,6 +122,8 @@ typedef struct pa_index_correction {
     pa_bool_t corrupt:1;
 } pa_index_correction;
 
+#define PA_MAX_FORMATS (PA_ENCODING_MAX)
+
 struct pa_stream {
     PA_REFCNT_DECLARE;
     PA_LLIST_FIELDS(pa_stream);
@@ -137,6 +139,9 @@ struct pa_stream {
 
     pa_sample_spec sample_spec;
     pa_channel_map channel_map;
+    uint8_t n_formats;
+    pa_format_info *req_formats[PA_MAX_FORMATS];
+    pa_format_info *format;
 
     pa_proplist *proplist;
 
index aac18a314130618b02679fbd1cb5b6b35c6f34c0..f5bf42c95d1feb4008f308bc19a4495ae15d1e1d 100644 (file)
@@ -80,31 +80,24 @@ static void reset_callbacks(pa_stream *s) {
     s->buffer_attr_userdata = NULL;
 }
 
-pa_stream *pa_stream_new_with_proplist(
+static pa_stream *pa_stream_new_with_proplist_internal(
         pa_context *c,
         const char *name,
         const pa_sample_spec *ss,
         const pa_channel_map *map,
+        pa_format_info * const *formats,
         pa_proplist *p) {
 
     pa_stream *s;
     int i;
-    pa_channel_map tmap;
 
     pa_assert(c);
     pa_assert(PA_REFCNT_VALUE(c) >= 1);
+    pa_assert((ss == NULL && map == NULL) || formats == NULL);
 
     PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED);
-    PA_CHECK_VALIDITY_RETURN_NULL(c, ss && pa_sample_spec_valid(ss), PA_ERR_INVALID);
-    PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 12 || (ss->format != PA_SAMPLE_S32LE && ss->format != PA_SAMPLE_S32BE), PA_ERR_NOTSUPPORTED);
-    PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 15 || (ss->format != PA_SAMPLE_S24LE && ss->format != PA_SAMPLE_S24BE), PA_ERR_NOTSUPPORTED);
-    PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 15 || (ss->format != PA_SAMPLE_S24_32LE && ss->format != PA_SAMPLE_S24_32BE), PA_ERR_NOTSUPPORTED);
-    PA_CHECK_VALIDITY_RETURN_NULL(c, !map || (pa_channel_map_valid(map) && map->channels == ss->channels), PA_ERR_INVALID);
     PA_CHECK_VALIDITY_RETURN_NULL(c, name || (p && pa_proplist_contains(p, PA_PROP_MEDIA_NAME)), PA_ERR_INVALID);
 
-    if (!map)
-        PA_CHECK_VALIDITY_RETURN_NULL(c, map = pa_channel_map_init_auto(&tmap, ss->channels, PA_CHANNEL_MAP_DEFAULT), PA_ERR_INVALID);
-
     s = pa_xnew(pa_stream, 1);
     PA_REFCNT_INIT(s);
     s->context = c;
@@ -114,8 +107,28 @@ pa_stream *pa_stream_new_with_proplist(
     s->state = PA_STREAM_UNCONNECTED;
     s->flags = 0;
 
-    s->sample_spec = *ss;
-    s->channel_map = *map;
+    if (ss)
+        s->sample_spec = *ss;
+    else
+        s->sample_spec.format = PA_SAMPLE_INVALID;
+
+    if (map)
+        s->channel_map = *map;
+    else
+        pa_channel_map_init(&s->channel_map);
+
+    s->n_formats = 0;
+    if (formats) {
+        for (i = 0; formats[i] && i < PA_MAX_FORMATS; i++) {
+            s->n_formats++;
+            s->req_formats[i] = pa_format_info_copy(formats[i]);
+        }
+        /* Make sure the input array was NULL-terminated */
+        pa_assert(formats[i] == NULL);
+    }
+
+    /* We'll get the final negotiated format after connecting */
+    s->format = NULL;
 
     s->direct_on_input = PA_INVALID_INDEX;
 
@@ -136,7 +149,10 @@ pa_stream *pa_stream_new_with_proplist(
      * what older PA versions provided. */
 
     s->buffer_attr.maxlength = (uint32_t) -1;
-    s->buffer_attr.tlength = (uint32_t) pa_usec_to_bytes(250*PA_USEC_PER_MSEC, ss); /* 250ms of buffering */
+    if (ss)
+        s->buffer_attr.tlength = (uint32_t) pa_usec_to_bytes(250*PA_USEC_PER_MSEC, ss); /* 250ms of buffering */
+    else
+        /* XXX: How do we apply worst case conversion here? */
     s->buffer_attr.minreq = (uint32_t) -1;
     s->buffer_attr.prebuf = (uint32_t) -1;
     s->buffer_attr.fragsize = (uint32_t) -1;
@@ -179,6 +195,40 @@ pa_stream *pa_stream_new_with_proplist(
     return s;
 }
 
+pa_stream *pa_stream_new_with_proplist(
+        pa_context *c,
+        const char *name,
+        const pa_sample_spec *ss,
+        const pa_channel_map *map,
+        pa_proplist *p) {
+
+    pa_channel_map tmap;
+
+    PA_CHECK_VALIDITY_RETURN_NULL(c, ss && pa_sample_spec_valid(ss), PA_ERR_INVALID);
+    PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 12 || (ss->format != PA_SAMPLE_S32LE && ss->format != PA_SAMPLE_S32BE), PA_ERR_NOTSUPPORTED);
+    PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 15 || (ss->format != PA_SAMPLE_S24LE && ss->format != PA_SAMPLE_S24BE), PA_ERR_NOTSUPPORTED);
+    PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 15 || (ss->format != PA_SAMPLE_S24_32LE && ss->format != PA_SAMPLE_S24_32BE), PA_ERR_NOTSUPPORTED);
+    PA_CHECK_VALIDITY_RETURN_NULL(c, !map || (pa_channel_map_valid(map) && map->channels == ss->channels), PA_ERR_INVALID);
+
+    if (!map)
+        PA_CHECK_VALIDITY_RETURN_NULL(c, map = pa_channel_map_init_auto(&tmap, ss->channels, PA_CHANNEL_MAP_DEFAULT), PA_ERR_INVALID);
+
+    return pa_stream_new_with_proplist_internal(c, name, ss, map, NULL, p);
+}
+
+pa_stream *pa_stream_new_extended(
+        pa_context *c,
+        const char *name,
+        pa_format_info * const *formats,
+        pa_proplist *p) {
+
+    PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 21, PA_ERR_NOTSUPPORTED);
+
+    /* XXX: For the single-format PCM case, pass ss/map instead of formats */
+
+    return pa_stream_new_with_proplist_internal(c, name, NULL, NULL, formats, p);
+}
+
 static void stream_unlink(pa_stream *s) {
     pa_operation *o, *n;
     pa_assert(s);
@@ -220,6 +270,8 @@ static void stream_unlink(pa_stream *s) {
 }
 
 static void stream_free(pa_stream *s) {
+    unsigned int i;
+
     pa_assert(s);
 
     stream_unlink(s);
@@ -244,6 +296,9 @@ static void stream_free(pa_stream *s) {
     if (s->smoother)
         pa_smoother_free(s->smoother);
 
+    for (i = 0; i < s->n_formats; i++)
+        pa_xfree(s->req_formats[i]);
+
     pa_xfree(s->device_name);
     pa_xfree(s);
 }
@@ -970,9 +1025,11 @@ void pa_create_stream_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag,
             ss.channels != cm.channels ||
             !pa_channel_map_valid(&cm) ||
             !pa_sample_spec_valid(&ss) ||
-            (!(s->flags & PA_STREAM_FIX_FORMAT) && ss.format != s->sample_spec.format) ||
-            (!(s->flags & PA_STREAM_FIX_RATE) && ss.rate != s->sample_spec.rate) ||
-            (!(s->flags & PA_STREAM_FIX_CHANNELS) && !pa_channel_map_equal(&cm, &s->channel_map))) {
+            (s->n_formats == 0 && (
+                (!(s->flags & PA_STREAM_FIX_FORMAT) && ss.format != s->sample_spec.format) ||
+                (!(s->flags & PA_STREAM_FIX_RATE) && ss.rate != s->sample_spec.rate) ||
+                (!(s->flags & PA_STREAM_FIX_CHANNELS) && !pa_channel_map_equal(&cm, &s->channel_map))))) {
+            /* XXX: checks for the n_formats > 0 case? */
             pa_context_fail(s->context, PA_ERR_PROTOCOL);
             goto finish;
         }
@@ -999,6 +1056,16 @@ void pa_create_stream_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag,
             s->timing_info.configured_sink_usec = usec;
     }
 
+    if (s->context->version >= 21 && s->direction == PA_STREAM_PLAYBACK) {
+        pa_format_info *f = pa_format_info_new();
+        pa_tagstruct_get_format_info(t, f);
+
+        if (pa_format_info_valid(f))
+            s->format = f;
+        else
+            pa_format_info_free(f);
+    }
+
     if (!pa_tagstruct_eof(t)) {
         pa_context_fail(s->context, PA_ERR_PROTOCOL);
         goto finish;
@@ -1039,6 +1106,7 @@ static int create_stream(
     pa_tagstruct *t;
     uint32_t tag;
     pa_bool_t volume_set = FALSE;
+    uint32_t i;
 
     pa_assert(s);
     pa_assert(PA_REFCNT_VALUE(s) >= 1);
@@ -1079,7 +1147,7 @@ static int create_stream(
 
     PA_CHECK_VALIDITY(s->context, direction == PA_STREAM_PLAYBACK || !(flags & (PA_STREAM_START_MUTED)), PA_ERR_INVALID);
     PA_CHECK_VALIDITY(s->context, direction == PA_STREAM_RECORD || !(flags & (PA_STREAM_PEAK_DETECT)), PA_ERR_INVALID);
-    PA_CHECK_VALIDITY(s->context, !volume || volume->channels == s->sample_spec.channels, PA_ERR_INVALID);
+    PA_CHECK_VALIDITY(s->context, !volume || (pa_sample_spec_valid(&s->sample_spec) && volume->channels == s->sample_spec.channels), PA_ERR_INVALID);
     PA_CHECK_VALIDITY(s->context, !sync_stream || (direction == PA_STREAM_PLAYBACK && sync_stream->direction == PA_STREAM_PLAYBACK), PA_ERR_INVALID);
     PA_CHECK_VALIDITY(s->context, (flags & (PA_STREAM_ADJUST_LATENCY|PA_STREAM_EARLY_REQUESTS)) != (PA_STREAM_ADJUST_LATENCY|PA_STREAM_EARLY_REQUESTS), PA_ERR_INVALID);
 
@@ -1147,8 +1215,16 @@ static int create_stream(
 
         volume_set = !!volume;
 
-        if (!volume)
-            volume = pa_cvolume_reset(&cv, s->sample_spec.channels);
+        if (!volume) {
+            if (pa_sample_spec_valid(&s->sample_spec))
+                volume = pa_cvolume_reset(&cv, s->sample_spec.channels);
+            else {
+                /* This is not really relevant, since no volume was set, and
+                 * the real number of channels is embedded in the format_info
+                 * structure */
+                volume = pa_cvolume_reset(&cv, PA_CHANNELS_MAX);
+            }
+        }
 
         pa_tagstruct_put_cvolume(t, volume);
     } else
@@ -1214,6 +1290,15 @@ static int create_stream(
             pa_tagstruct_put_boolean(t, flags & (PA_STREAM_PASSTHROUGH));
     }
 
+    if (s->context->version >= 21) {
+
+        if (s->direction == PA_STREAM_PLAYBACK) {
+            pa_tagstruct_putu8(t, s->n_formats);
+            for (i = 0; i < s->n_formats; i++)
+                pa_tagstruct_put_format_info(t, s->req_formats[i]);
+        }
+    }
+
     pa_pstream_send_tagstruct(s->context->pstream, t);
     pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_create_stream_callback, s, NULL);
 
index dd67db738cadaa5efa68ecde0d3b62e7a9915cba..48bf09df667467765b7cc184b4cb82bc13cdbb7e 100644 (file)
@@ -26,6 +26,7 @@
 #include <sys/types.h>
 
 #include <pulse/sample.h>
+#include <pulse/format.h>
 #include <pulse/channelmap.h>
 #include <pulse/volume.h>
 #include <pulse/def.h>
@@ -356,6 +357,16 @@ pa_stream* pa_stream_new_with_proplist(
         const pa_channel_map *map         /**< The desired channel map, or NULL for default */,
         pa_proplist *p                    /**< The initial property list */);
 
+/* Create a new, unconnected stream with the specified name, the set of formats
+ * this client can provide, and an initial list of properties. While
+ * connecting, the server will select the most appropriate format which the
+ * client must then provide. \since 1.0 */
+pa_stream *pa_stream_new_extended(
+        pa_context *c                     /**< The context to create this stream in */,
+        const char *name                  /**< A name for this stream */,
+        pa_format_info * const * formats  /**< The list of formats that can be provided */,
+        pa_proplist *p                    /**< The initial property list */);
+
 /** Decrease the reference counter by one */
 void pa_stream_unref(pa_stream *s);
 
index 66e47ea491c2e879c1ba07c9134cc6222825ff59..9455340d1d228c4479a7dfec06aaac14e99326d5 100644 (file)
@@ -202,7 +202,7 @@ pa_sink_input* pa_memblockq_sink_input_new(
     u->memblockq = NULL;
 
     pa_sink_input_new_data_init(&data);
-    data.sink = sink;
+    pa_sink_input_new_data_set_sink(&data, sink, FALSE);
     data.driver = __FILE__;
     pa_sink_input_new_data_set_sample_spec(&data, ss);
     pa_sink_input_new_data_set_channel_map(&data, map);
index 66fd73c87c8185632eedb4247354c80bea7087f6..c2af77c2ce1ba7421440783ba9399bd39642f2f8 100644 (file)
@@ -426,7 +426,7 @@ static int esd_proto_stream_play(connection *c, esd_proto_t request, const void
     sdata.driver = __FILE__;
     sdata.module = c->options->module;
     sdata.client = c->client;
-    sdata.sink = sink;
+    pa_sink_input_new_data_set_sink(&sdata, sink, FALSE);
     pa_sink_input_new_data_set_sample_spec(&sdata, &ss);
 
     pa_sink_input_new(&c->sink_input, c->protocol->core, &sdata);
index 4952ee41505532861c36da8b8669a7732a517355..833217904398c1b7d4024e7f3da14fdffc8a4ad0 100644 (file)
@@ -1014,6 +1014,7 @@ static playback_stream* playback_stream_new(
         pa_sink *sink,
         pa_sample_spec *ss,
         pa_channel_map *map,
+        pa_idxset *formats,
         pa_buffer_attr *a,
         pa_cvolume *volume,
         pa_bool_t muted,
@@ -1067,12 +1068,14 @@ static playback_stream* playback_stream_new(
     data.driver = __FILE__;
     data.module = c->options->module;
     data.client = c->client;
-    if (sink) {
-        data.sink = sink;
-        data.save_sink = TRUE;
-    }
-    pa_sink_input_new_data_set_sample_spec(&data, ss);
-    pa_sink_input_new_data_set_channel_map(&data, map);
+    if (sink)
+        pa_sink_input_new_data_set_sink(&data, sink, TRUE);
+    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 (formats)
+        pa_sink_input_new_data_set_formats(&data, formats);
     if (volume) {
         pa_sink_input_new_data_set_volume(&data, volume);
         data.volume_is_absolute = !relative_volume;
@@ -1846,6 +1849,10 @@ static pa_tagstruct *reply_new(uint32_t tag) {
     return reply;
 }
 
+static void free_format_info(pa_format_info *f, void *userdata) {
+    pa_format_info_free(f);
+}
+
 static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
     pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
     playback_stream *s;
@@ -1876,9 +1883,13 @@ static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, u
         passthrough = FALSE;
 
     pa_sink_input_flags_t flags = 0;
-    pa_proplist *p;
+    pa_proplist *p = NULL;
     pa_bool_t volume_set = TRUE;
     int ret = PA_ERR_INVALID;
+    uint8_t n_formats = 0;
+    pa_format_info *format;
+    pa_idxset *formats = NULL;
+    uint32_t i;
 
     pa_native_connection_assert_ref(c);
     pa_assert(t);
@@ -1901,17 +1912,14 @@ static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, u
                 PA_TAG_INVALID) < 0) {
 
         protocol_error(c);
-        return;
+        goto error;
     }
 
     CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
     CHECK_VALIDITY(c->pstream, !sink_name || pa_namereg_is_valid_name_or_wildcard(sink_name, PA_NAMEREG_SINK), tag, PA_ERR_INVALID);
     CHECK_VALIDITY(c->pstream, sink_index == PA_INVALID_INDEX || !sink_name, tag, PA_ERR_INVALID);
     CHECK_VALIDITY(c->pstream, !sink_name || sink_index == PA_INVALID_INDEX, tag, PA_ERR_INVALID);
-    CHECK_VALIDITY(c->pstream, pa_channel_map_valid(&map), tag, PA_ERR_INVALID);
-    CHECK_VALIDITY(c->pstream, pa_sample_spec_valid(&ss), tag, PA_ERR_INVALID);
     CHECK_VALIDITY(c->pstream, pa_cvolume_valid(&volume), tag, PA_ERR_INVALID);
-    CHECK_VALIDITY(c->pstream, map.channels == ss.channels && volume.channels == ss.channels, tag, PA_ERR_INVALID);
 
     p = pa_proplist_new();
 
@@ -1930,8 +1938,7 @@ static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, u
             pa_tagstruct_get_boolean(t, &variable_rate) < 0) {
 
             protocol_error(c);
-            pa_proplist_free(p);
-            return;
+            goto error;
         }
     }
 
@@ -1940,9 +1947,9 @@ static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, u
         if (pa_tagstruct_get_boolean(t, &muted) < 0 ||
             pa_tagstruct_get_boolean(t, &adjust_latency) < 0 ||
             pa_tagstruct_get_proplist(t, p) < 0) {
+
             protocol_error(c);
-            pa_proplist_free(p);
-            return;
+            goto error;
         }
     }
 
@@ -1950,9 +1957,9 @@ static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, u
 
         if (pa_tagstruct_get_boolean(t, &volume_set) < 0 ||
             pa_tagstruct_get_boolean(t, &early_requests) < 0) {
+
             protocol_error(c);
-            pa_proplist_free(p);
-            return;
+            goto error;
         }
     }
 
@@ -1961,18 +1968,18 @@ static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, u
         if (pa_tagstruct_get_boolean(t, &muted_set) < 0 ||
             pa_tagstruct_get_boolean(t, &dont_inhibit_auto_suspend) < 0 ||
             pa_tagstruct_get_boolean(t, &fail_on_suspend) < 0) {
+
             protocol_error(c);
-            pa_proplist_free(p);
-            return;
+            goto error;
         }
     }
 
     if (c->version >= 17) {
 
         if (pa_tagstruct_get_boolean(t, &relative_volume) < 0) {
+
             protocol_error(c);
-            pa_proplist_free(p);
-            return;
+            goto error;
         }
     }
 
@@ -1980,31 +1987,52 @@ static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, u
 
         if (pa_tagstruct_get_boolean(t, &passthrough) < 0 ) {
             protocol_error(c);
-            pa_proplist_free(p);
-            return;
+            goto error;
+        }
+    }
+
+    if (c->version >= 21) {
+
+        if (pa_tagstruct_getu8(t, &n_formats) < 0) {
+            protocol_error(c);
+            goto error;
+        }
+
+        if (n_formats)
+            formats = pa_idxset_new(NULL, NULL);
+
+        for (i = 0; i < n_formats; i++) {
+            format = pa_format_info_new();
+            if (pa_tagstruct_get_format_info(t, format) < 0) {
+                protocol_error(c);
+                goto error;
+            }
+            pa_idxset_put(formats, format, NULL);
         }
     }
 
+    CHECK_VALIDITY(c->pstream, n_formats > 0 || pa_sample_spec_valid(&ss), tag, PA_ERR_INVALID);
+    CHECK_VALIDITY(c->pstream, n_formats > 0 || (map.channels == ss.channels && volume.channels == ss.channels), tag, PA_ERR_INVALID);
+    CHECK_VALIDITY(c->pstream, n_formats > 0 || pa_channel_map_valid(&map), tag, PA_ERR_INVALID);
+    /* XXX: add checks on formats. At least inverse checks of the 3 above */
+
     if (!pa_tagstruct_eof(t)) {
         protocol_error(c);
-        pa_proplist_free(p);
-        return;
+        goto error;
     }
 
     if (sink_index != PA_INVALID_INDEX) {
 
         if (!(sink = pa_idxset_get_by_index(c->protocol->core->sinks, sink_index))) {
             pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY);
-            pa_proplist_free(p);
-            return;
+            goto error;
         }
 
     } else if (sink_name) {
 
         if (!(sink = pa_namereg_get(c->protocol->core, sink_name, PA_NAMEREG_SINK))) {
             pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY);
-            pa_proplist_free(p);
-            return;
+            goto error;
         }
     }
 
@@ -2025,7 +2053,7 @@ static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, u
      * flag. For older versions we synthesize it here */
     muted_set = muted_set || muted;
 
-    s = playback_stream_new(c, sink, &ss, &map, &attr, volume_set ? &volume : NULL, muted, muted_set, syncid, &missing, flags, p, adjust_latency, early_requests, relative_volume, &ret);
+    s = playback_stream_new(c, sink, &ss, &map, formats, &attr, volume_set ? &volume : NULL, muted, muted_set, syncid, &missing, flags, p, adjust_latency, early_requests, relative_volume, &ret);
     pa_proplist_free(p);
 
     CHECK_VALIDITY(c->pstream, s, tag, ret);
@@ -2064,7 +2092,26 @@ static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, u
     if (c->version >= 13)
         pa_tagstruct_put_usec(reply, s->configured_sink_latency);
 
+    if (c->version >= 21) {
+        /* Send back the format we negotiated */
+        if (s->sink_input->format)
+            pa_tagstruct_put_format_info(reply, s->sink_input->format);
+        else {
+            pa_format_info *f = pa_format_info_new();
+            pa_tagstruct_put_format_info(reply, f);
+            pa_format_info_free(f);
+        }
+    }
+
     pa_pstream_send_tagstruct(c->pstream, reply);
+    return;
+
+error:
+    if (p)
+        pa_proplist_free(p);
+    if (formats)
+        pa_idxset_free(formats, (pa_free2_cb_t) free_format_info, NULL);
+    return;
 }
 
 static void command_delete_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
index 77277e1349325158629708c1b1e9bd5d1517b5d1..c1aaa81333c3c3732a2c3a755398f2a037d6ea87 100644 (file)
@@ -538,7 +538,7 @@ void pa_simple_protocol_connect(pa_simple_protocol *p, pa_iochannel *io, pa_simp
         data.driver = __FILE__;
         data.module = o->module;
         data.client = c->client;
-        data.sink = sink;
+        pa_sink_input_new_data_set_sink(&data, sink, FALSE);
         pa_proplist_update(data.proplist, PA_UPDATE_MERGE, c->client->proplist);
         pa_sink_input_new_data_set_sample_spec(&data, &o->sample_spec);
 
index 194ec99e7c2b686382d9152aa6870f2197212a98..3df499e4097e66bd1063f4a9564efb9796b442ef 100644 (file)
@@ -31,6 +31,7 @@
 #include <pulse/utf8.h>
 #include <pulse/xmalloc.h>
 #include <pulse/util.h>
+#include <pulse/internal.h>
 
 #include <pulsecore/sample-util.h>
 #include <pulsecore/core-subscribe.h>
@@ -146,9 +147,75 @@ void pa_sink_input_new_data_set_muted(pa_sink_input_new_data *data, pa_bool_t mu
     data->muted = !!mute;
 }
 
+static void free_format_info(pa_format_info *f, void *userdata) {
+    pa_format_info_free(f);
+}
+
+pa_bool_t pa_sink_input_new_data_set_sink(pa_sink_input_new_data *data, pa_sink *s, pa_bool_t save) {
+    pa_bool_t ret = TRUE;
+    pa_idxset *formats = NULL;
+
+    pa_assert(data);
+    pa_assert(s);
+
+    if (!data->req_formats) {
+        /* We're not working with the extended API */
+        data->sink = s;
+        data->save_sink = save;
+    } else {
+        /* Extended API: let's see if this sink supports the formats the client can provide */
+        formats = pa_sink_check_formats(s, data->req_formats);
+
+        if (formats && !pa_idxset_isempty(formats)) {
+            /* Sink supports at least one of the requested formats */
+            data->sink = s;
+            data->save_sink = save;
+            if (data->nego_formats)
+                pa_idxset_free(data->nego_formats, (pa_free2_cb_t) free_format_info, NULL);
+            data->nego_formats = formats;
+        } else {
+            /* Sink doesn't support any of the formats requested by the client */
+            if (formats)
+                pa_idxset_free(formats, (pa_free2_cb_t) free_format_info, NULL);
+            ret = FALSE;
+        }
+    }
+
+    return ret;
+}
+
+pa_bool_t pa_sink_input_new_data_set_formats(pa_sink_input_new_data *data, pa_idxset *formats) {
+    pa_assert(data);
+    pa_assert(formats);
+
+    if (data->req_formats)
+        pa_idxset_free(formats, (pa_free2_cb_t) free_format_info, NULL);
+
+    data->req_formats = formats;
+
+    if (data->sink) {
+        /* Trigger format negotiation */
+        return pa_sink_input_new_data_set_sink(data, data->sink, data->save_sink);
+    }
+
+    return TRUE;
+}
+
 void pa_sink_input_new_data_done(pa_sink_input_new_data *data) {
+    pa_format_info *f;
+    int i;
+
     pa_assert(data);
 
+    if (data->req_formats)
+        pa_idxset_free(data->req_formats, (pa_free2_cb_t) free_format_info, NULL);
+
+    if (data->nego_formats)
+        pa_idxset_free(data->nego_formats, (pa_free2_cb_t) free_format_info, NULL);
+
+    if (data->format)
+        pa_format_info_free(data->format);
+
     pa_proplist_free(data->proplist);
 }
 
@@ -189,6 +256,8 @@ int pa_sink_input_new(
     pa_channel_map original_cm;
     int r;
     char *pt;
+    pa_sample_spec ss;
+    pa_channel_map map;
 
     pa_assert(_i);
     pa_assert(core);
@@ -201,14 +270,43 @@ int pa_sink_input_new(
     if (data->origin_sink && (data->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER))
         data->volume_writable = FALSE;
 
+    if (!data->req_formats) {
+        /* From this point on, we want to work only with formats, and get back
+         * to using the sample spec and channel map after all decisions w.r.t.
+         * routing are complete. */
+        pa_idxset *tmp = pa_idxset_new(NULL, NULL);
+        pa_format_info *f = pa_format_info_from_sample_spec(&data->sample_spec, &data->channel_map);
+        pa_idxset_put(tmp, f, NULL);
+        pa_sink_input_new_data_set_formats(data, tmp);
+    }
+
     if ((r = pa_hook_fire(&core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], data)) < 0)
         return r;
 
     pa_return_val_if_fail(!data->driver || pa_utf8_valid(data->driver), -PA_ERR_INVALID);
 
-    if (!data->sink) {
-        data->sink = pa_namereg_get(core, NULL, PA_NAMEREG_SINK);
-        data->save_sink = FALSE;
+    if (!data->sink)
+        pa_sink_input_new_data_set_sink(data, pa_namereg_get(core, NULL, PA_NAMEREG_SINK), FALSE);
+
+    /* Routing's done, we have a sink. Now let's fix the format and set up the
+     * sample spec */
+    pa_return_val_if_fail(data->format || (data->nego_formats && !pa_idxset_isempty(data->nego_formats)), -PA_ERR_INVALID);
+    /* If something didn't pick a format for us, pick the top-most format since
+     * we assume this is sorted in priority order */
+    if (!data->format)
+        data->format = pa_format_info_copy(pa_idxset_first(data->nego_formats, NULL));
+    /* Now populate the sample spec and format according to the final
+     * format that we've negotiated */
+    if (PA_LIKELY(data->format->encoding == PA_ENCODING_PCM)) {
+        pa_format_info_to_sample_spec(data->format, &ss, &map);
+        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);
+    } else {
+        pa_format_info_to_sample_spec_fake(data->format, &ss);
+        pa_sink_input_new_data_set_sample_spec(data, &ss);
+        /* XXX: this is redundant - we can just check the encoding */
+        data->flags |= PA_SINK_INPUT_PASSTHROUGH;
     }
 
     pa_return_val_if_fail(data->sink, -PA_ERR_NOENTITY);
@@ -329,6 +427,7 @@ int pa_sink_input_new(
     i->actual_resample_method = resampler ? pa_resampler_get_method(resampler) : PA_RESAMPLER_INVALID;
     i->sample_spec = data->sample_spec;
     i->channel_map = data->channel_map;
+    i->format = pa_format_info_copy(data->format);
 
     if (!data->volume_is_absolute && pa_sink_flat_volume_enabled(i->sink)) {
         pa_cvolume remapped;
@@ -564,6 +663,9 @@ static void sink_input_free(pa_object *o) {
     if (i->thread_info.resampler)
         pa_resampler_free(i->thread_info.resampler);
 
+    if (i->format)
+        pa_format_info_free(i->format);
+
     if (i->proplist)
         pa_proplist_free(i->proplist);
 
index 11f6608f367ad8aa9efea555dc454a3a30a8dbe7..72a1d5a2b68c75c77c03f3989affa9a3e7dc7313 100644 (file)
@@ -28,6 +28,7 @@
 typedef struct pa_sink_input pa_sink_input;
 
 #include <pulse/sample.h>
+#include <pulse/format.h>
 #include <pulsecore/hook-list.h>
 #include <pulsecore/memblockq.h>
 #include <pulsecore/resampler.h>
@@ -92,6 +93,7 @@ struct pa_sink_input {
 
     pa_sample_spec sample_spec;
     pa_channel_map channel_map;
+    pa_format_info *format;
 
     pa_sink_input *sync_prev, *sync_next;
 
@@ -279,6 +281,9 @@ typedef struct pa_sink_input_new_data {
 
     pa_sample_spec sample_spec;
     pa_channel_map channel_map;
+    pa_format_info *format;
+    pa_idxset *req_formats;
+    pa_idxset *nego_formats;
 
     pa_cvolume volume, volume_factor, volume_factor_sink;
     pa_bool_t muted:1;
@@ -303,6 +308,8 @@ void pa_sink_input_new_data_set_volume(pa_sink_input_new_data *data, const pa_cv
 void pa_sink_input_new_data_apply_volume_factor(pa_sink_input_new_data *data, const pa_cvolume *volume_factor);
 void pa_sink_input_new_data_apply_volume_factor_sink(pa_sink_input_new_data *data, const pa_cvolume *volume_factor);
 void pa_sink_input_new_data_set_muted(pa_sink_input_new_data *data, pa_bool_t mute);
+pa_bool_t pa_sink_input_new_data_set_sink(pa_sink_input_new_data *data, pa_sink *s, pa_bool_t save);
+pa_bool_t pa_sink_input_new_data_set_formats(pa_sink_input_new_data *data, pa_idxset *formats);
 void pa_sink_input_new_data_done(pa_sink_input_new_data *data);
 
 /* To be called by the implementing module only */
index 1ec1942553bed1be8183d1a61748d4bab72c3b73..d33eca5a8a84e0045389207560be5e233c6c361c 100644 (file)
@@ -299,7 +299,7 @@ int pa_play_file(
     u->readf_function = pa_sndfile_readf_function(&ss);
 
     pa_sink_input_new_data_init(&data);
-    data.sink = sink;
+    pa_sink_input_new_data_set_sink(&data, sink, FALSE);
     data.driver = __FILE__;
     pa_sink_input_new_data_set_sample_spec(&data, &ss);
     pa_sink_input_new_data_set_channel_map(&data, &cm);