From: Arun Raghavan Date: Mon, 28 Feb 2011 07:53:23 +0000 (+0530) Subject: core: Add extended stream API to support compressed formats X-Git-Tag: 1.0_branch~586^2~31 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=0ac2cfce6d1a3d7ab5af6aca659e46625c32d3c4;p=profile%2Fivi%2Fpulseaudio.git 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 --- diff --git a/PROTOCOL b/PROTOCOL index a15d116..d06cb98 100644 --- 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 diff --git a/configure.ac b/configure.ac index ec1099b..9edae0e 100644 --- a/configure.ac +++ b/configure.ac @@ -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 diff --git a/src/map-file b/src/map-file index 181af9e..b4196f1 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 3787962..a06e481 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 09af942..f6d6453 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 47469b0..c28affd 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 9a85fe5..e20ee4a 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 d19444d..9038562 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 f6430f2..6489f3f 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 9a8640b..ca813b0 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 7f64f30..79627f7 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 69b2002..cee01f1 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 77b6949..fa2c021 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 9bcff8c..fe26930 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 1144169..fb3bccb 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 53fcca6..d715165 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 aac18a3..f5bf42c 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 dd67db7..48bf09d 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 66e47ea..9455340 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 66fd73c..c2af77c 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 4952ee4..8332179 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 77277e1..c1aaa81 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 194ec99..3df499e 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 11f6608..72a1d5a 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 1ec1942..d33eca5 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);