From 0ac2cfce6d1a3d7ab5af6aca659e46625c32d3c4 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Mon, 28 Feb 2011 13:23:23 +0530 Subject: core: Add extended stream API to support compressed formats 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 --- src/map-file | 1 + src/modules/echo-cancel/module-echo-cancel.c | 2 +- src/modules/module-combine-sink.c | 2 +- src/modules/module-device-manager.c | 4 +- src/modules/module-equalizer-sink.c | 2 +- src/modules/module-intended-roles.c | 10 +-- src/modules/module-ladspa-sink.c | 2 +- src/modules/module-loopback.c | 2 +- src/modules/module-remap-sink.c | 2 +- src/modules/module-sine.c | 2 +- src/modules/module-stream-restore.c | 8 +- src/modules/module-virtual-sink.c | 2 +- src/modules/rtp/module-rtp-recv.c | 2 +- src/pulse/internal.h | 5 ++ src/pulse/stream.c | 123 ++++++++++++++++++++++----- src/pulse/stream.h | 11 +++ src/pulsecore/play-memblockq.c | 2 +- src/pulsecore/protocol-esound.c | 2 +- src/pulsecore/protocol-native.c | 107 ++++++++++++++++------- src/pulsecore/protocol-simple.c | 2 +- src/pulsecore/sink-input.c | 108 ++++++++++++++++++++++- src/pulsecore/sink-input.h | 7 ++ src/pulsecore/sound-file-stream.c | 2 +- 23 files changed, 330 insertions(+), 80 deletions(-) (limited to 'src') diff --git a/src/map-file b/src/map-file index 181af9ea..b4196f15 100644 --- a/src/map-file +++ b/src/map-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; diff --git a/src/modules/echo-cancel/module-echo-cancel.c b/src/modules/echo-cancel/module-echo-cancel.c index 37879629..a06e481d 100644 --- a/src/modules/echo-cancel/module-echo-cancel.c +++ b/src/modules/echo-cancel/module-echo-cancel.c @@ -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"); diff --git a/src/modules/module-combine-sink.c b/src/modules/module-combine-sink.c index 09af942d..f6d64531 100644 --- a/src/modules/module-combine-sink.c +++ b/src/modules/module-combine-sink.c @@ -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"); diff --git a/src/modules/module-device-manager.c b/src/modules/module-device-manager.c index 47469b06..c28affd1 100644 --- a/src/modules/module-device-manager.c +++ b/src/modules/module-device-manager.c @@ -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"); } } } diff --git a/src/modules/module-equalizer-sink.c b/src/modules/module-equalizer-sink.c index 9a85fe59..e20ee4ab 100644 --- a/src/modules/module-equalizer-sink.c +++ b/src/modules/module-equalizer-sink.c @@ -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"); diff --git a/src/modules/module-intended-roles.c b/src/modules/module-intended-roles.c index d19444d2..90385622 100644 --- a/src/modules/module-intended-roles.c +++ b/src/modules/module-intended-roles.c @@ -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; diff --git a/src/modules/module-ladspa-sink.c b/src/modules/module-ladspa-sink.c index f6430f29..6489f3f7 100644 --- a/src/modules/module-ladspa-sink.c +++ b/src/modules/module-ladspa-sink.c @@ -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"); diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c index 9a8640b1..ca813b00 100644 --- a/src/modules/module-loopback.c +++ b/src/modules/module-loopback.c @@ -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); diff --git a/src/modules/module-remap-sink.c b/src/modules/module-remap-sink.c index 7f64f306..79627f7a 100644 --- a/src/modules/module-remap-sink.c +++ b/src/modules/module-remap-sink.c @@ -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"); diff --git a/src/modules/module-sine.c b/src/modules/module-sine.c index 69b20028..cee01f1a 100644 --- a/src/modules/module-sine.c +++ b/src/modules/module-sine.c @@ -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); diff --git a/src/modules/module-stream-restore.c b/src/modules/module-stream-restore.c index 77b6949d..fa2c0210 100644 --- a/src/modules/module-stream-restore.c +++ b/src/modules/module-stream-restore.c @@ -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); } diff --git a/src/modules/module-virtual-sink.c b/src/modules/module-virtual-sink.c index 9bcff8c3..fe269304 100644 --- a/src/modules/module-virtual-sink.c +++ b/src/modules/module-virtual-sink.c @@ -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"); diff --git a/src/modules/rtp/module-rtp-recv.c b/src/modules/rtp/module-rtp-recv.c index 1144169b..fb3bccb4 100644 --- a/src/modules/rtp/module-rtp-recv.c +++ b/src/modules/rtp/module-rtp-recv.c @@ -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, diff --git a/src/pulse/internal.h b/src/pulse/internal.h index 53fcca60..d7151653 100644 --- a/src/pulse/internal.h +++ b/src/pulse/internal.h @@ -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; diff --git a/src/pulse/stream.c b/src/pulse/stream.c index aac18a31..f5bf42c9 100644 --- a/src/pulse/stream.c +++ b/src/pulse/stream.c @@ -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); diff --git a/src/pulse/stream.h b/src/pulse/stream.h index dd67db73..48bf09df 100644 --- a/src/pulse/stream.h +++ b/src/pulse/stream.h @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -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); diff --git a/src/pulsecore/play-memblockq.c b/src/pulsecore/play-memblockq.c index 66e47ea4..9455340d 100644 --- a/src/pulsecore/play-memblockq.c +++ b/src/pulsecore/play-memblockq.c @@ -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); diff --git a/src/pulsecore/protocol-esound.c b/src/pulsecore/protocol-esound.c index 66fd73c8..c2af77c2 100644 --- a/src/pulsecore/protocol-esound.c +++ b/src/pulsecore/protocol-esound.c @@ -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); diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c index 4952ee41..83321790 100644 --- a/src/pulsecore/protocol-native.c +++ b/src/pulsecore/protocol-native.c @@ -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) { diff --git a/src/pulsecore/protocol-simple.c b/src/pulsecore/protocol-simple.c index 77277e13..c1aaa813 100644 --- a/src/pulsecore/protocol-simple.c +++ b/src/pulsecore/protocol-simple.c @@ -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); diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c index 194ec99e..3df499e4 100644 --- a/src/pulsecore/sink-input.c +++ b/src/pulsecore/sink-input.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -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); diff --git a/src/pulsecore/sink-input.h b/src/pulsecore/sink-input.h index 11f6608f..72a1d5a2 100644 --- a/src/pulsecore/sink-input.h +++ b/src/pulsecore/sink-input.h @@ -28,6 +28,7 @@ typedef struct pa_sink_input pa_sink_input; #include +#include #include #include #include @@ -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 */ diff --git a/src/pulsecore/sound-file-stream.c b/src/pulsecore/sound-file-stream.c index 1ec19425..d33eca5a 100644 --- a/src/pulsecore/sound-file-stream.c +++ b/src/pulsecore/sound-file-stream.c @@ -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); -- cgit