From 233ef98bf19ab311c1cb19867b1e8bcb4c36f66d Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Mon, 2 May 2011 10:08:27 +0530 Subject: filter-apply: Mark modules as being autoloaded (Based on Colin's review) We mark modules as being autoloaded so that they can handle this as a special case if needed (which is required by module-echo-cancel for now). This inverts how things were done and makes using these modules manually less error-prone. --- src/modules/echo-cancel/module-echo-cancel.c | 18 +++++++++--------- src/modules/module-equalizer-sink.c | 13 ++++++++++++- src/modules/module-filter-apply.c | 2 +- src/modules/module-virtual-sink.c | 3 +++ src/modules/module-virtual-source.c | 3 +++ 5 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/modules/echo-cancel/module-echo-cancel.c b/src/modules/echo-cancel/module-echo-cancel.c index 746028bb..37879629 100644 --- a/src/modules/echo-cancel/module-echo-cancel.c +++ b/src/modules/echo-cancel/module-echo-cancel.c @@ -78,7 +78,7 @@ PA_MODULE_USAGE( "aec_method= " "aec_args= " "save_aec= " - "manual_load= " + "autoloaded= " )); /* NOTE: Make sure the enum and ec_table are maintained in the correct order */ @@ -107,7 +107,7 @@ static const pa_echo_canceller ec_table[] = { #define DEFAULT_ADJUST_TIME_USEC (1*PA_USEC_PER_SEC) #define DEFAULT_SAVE_AEC 0 -#define DEFAULT_MANUAL_LOAD FALSE +#define DEFAULT_AUTOLOADED FALSE #define MEMBLOCKQ_MAXLENGTH (16*1024*1024) @@ -158,7 +158,7 @@ struct userdata { pa_core *core; pa_module *module; - pa_bool_t manual_load; + pa_bool_t autoloaded; uint32_t save_aec; pa_echo_canceller *ec; @@ -213,7 +213,7 @@ static const char* const valid_modargs[] = { "aec_method", "aec_args", "save_aec", - "manual_load", + "autoloaded", NULL }; @@ -1398,9 +1398,9 @@ int pa__init(pa_module*m) { goto fail; } - u->manual_load = DEFAULT_MANUAL_LOAD; - if (pa_modargs_get_value_boolean(ma, "manual_load", &u->manual_load) < 0) { - pa_log("Failed to parse manual_load value"); + u->autoloaded = DEFAULT_AUTOLOADED; + if (pa_modargs_get_value_boolean(ma, "autoloaded", &u->autoloaded) < 0) { + pa_log("Failed to parse autoloaded value"); goto fail; } @@ -1423,7 +1423,7 @@ int pa__init(pa_module*m) { pa_source_new_data_set_channel_map(&source_data, &source_map); pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, source_master->name); pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_CLASS, "filter"); - if (u->manual_load) + if (!u->autoloaded) pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone"); pa_proplist_sets(source_data.proplist, "device.echo-cancel.name", source_data.name); @@ -1471,7 +1471,7 @@ int pa__init(pa_module*m) { pa_sink_new_data_set_channel_map(&sink_data, &sink_map); pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, sink_master->name); pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter"); - if (u->manual_load) + if (!u->autoloaded) pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone"); pa_proplist_sets(sink_data.proplist, "device.echo-cancel.name", sink_data.name); diff --git a/src/modules/module-equalizer-sink.c b/src/modules/module-equalizer-sink.c index 0bbb23a8..9a85fe59 100644 --- a/src/modules/module-equalizer-sink.c +++ b/src/modules/module-equalizer-sink.c @@ -83,14 +83,18 @@ PA_MODULE_USAGE( "format= " "rate= " "channels= " - "channel_map=")); + "channel_map= " + "autoloaded= " + )); #define MEMBLOCKQ_MAXLENGTH (16*1024*1024) +#define DEFAULT_AUTOLOADED FALSE struct userdata { pa_module *module; pa_sink *sink; pa_sink_input *sink_input; + pa_bool_t autoloaded; size_t channels; size_t fft_size;//length (res) of fft @@ -138,6 +142,7 @@ static const char* const valid_modargs[] = { "rate", "channels", "channel_map", + "autoloaded", NULL }; @@ -1170,6 +1175,12 @@ int pa__init(pa_module*m) { goto fail; } + u->autoloaded = DEFAULT_AUTOLOADED; + if (pa_modargs_get_value_boolean(ma, "autoloaded", &u->autoloaded) < 0) { + pa_log("Failed to parse autoloaded value"); + goto fail; + } + u->sink = pa_sink_new(m->core, &sink_data, PA_SINK_HW_MUTE_CTRL|PA_SINK_HW_VOLUME_CTRL|PA_SINK_DECIBEL_VOLUME| (master->flags & (PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY))); diff --git a/src/modules/module-filter-apply.c b/src/modules/module-filter-apply.c index 79558f2e..e9c9f65b 100644 --- a/src/modules/module-filter-apply.c +++ b/src/modules/module-filter-apply.c @@ -314,7 +314,7 @@ static pa_hook_result_t process(struct userdata *u, pa_object *o, pa_bool_t is_s char *args; pa_module *m; - args = pa_sprintf_malloc("%s_master=%s", is_sink_input ? "sink" : "source", parent_name); + args = pa_sprintf_malloc("autoloaded=1 %s_master=%s", is_sink_input ? "sink" : "source", parent_name); pa_log_debug("Loading %s with arguments '%s'", module_name, args); if ((m = pa_module_load(u->core, module_name, args))) { diff --git a/src/modules/module-virtual-sink.c b/src/modules/module-virtual-sink.c index f7723148..9bcff8c3 100644 --- a/src/modules/module-virtual-sink.c +++ b/src/modules/module-virtual-sink.c @@ -66,6 +66,9 @@ PA_MODULE_USAGE( struct userdata { pa_module *module; + /* FIXME: Uncomment this and take "autoloaded" as a modarg if this is a filter */ + /* pa_bool_t autoloaded; */ + pa_sink *sink; pa_sink_input *sink_input; diff --git a/src/modules/module-virtual-source.c b/src/modules/module-virtual-source.c index b8f2ab06..835cf3ce 100644 --- a/src/modules/module-virtual-source.c +++ b/src/modules/module-virtual-source.c @@ -69,6 +69,9 @@ PA_MODULE_USAGE( struct userdata { pa_module *module; + /* FIXME: Uncomment this and take "autoloaded" as a modarg if this is a filter */ + /* pa_bool_t autoloaded; */ + pa_source *source; pa_source_output *source_output; -- cgit From 881074907e04b23d713815fe11093e9081fe71e8 Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Tue, 8 Mar 2011 15:12:56 +0530 Subject: sink-input: Don't resample passthrough inputs --- src/pulsecore/sink-input.c | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c index 1931d994..194ec99e 100644 --- a/src/pulsecore/sink-input.c +++ b/src/pulsecore/sink-input.c @@ -295,18 +295,20 @@ int pa_sink_input_new( !pa_sample_spec_equal(&data->sample_spec, &data->sink->sample_spec) || !pa_channel_map_equal(&data->channel_map, &data->sink->channel_map)) { - if (!(resampler = pa_resampler_new( - core->mempool, - &data->sample_spec, &data->channel_map, - &data->sink->sample_spec, &data->sink->channel_map, - data->resample_method, - ((data->flags & PA_SINK_INPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) | - ((data->flags & PA_SINK_INPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) | - (core->disable_remixing || (data->flags & PA_SINK_INPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0) | - (core->disable_lfe_remixing ? PA_RESAMPLER_NO_LFE : 0)))) { - pa_log_warn("Unsupported resampling operation."); - return -PA_ERR_NOTSUPPORTED; - } + /* Note: for passthrough content we need to adjust the output rate to that of the current sink-input */ + if (!(data->flags & PA_SINK_INPUT_PASSTHROUGH)) /* no resampler for passthrough content */ + if (!(resampler = pa_resampler_new( + core->mempool, + &data->sample_spec, &data->channel_map, + &data->sink->sample_spec, &data->sink->channel_map, + data->resample_method, + ((data->flags & PA_SINK_INPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) | + ((data->flags & PA_SINK_INPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) | + (core->disable_remixing || (data->flags & PA_SINK_INPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0) | + (core->disable_lfe_remixing ? PA_RESAMPLER_NO_LFE : 0)))) { + pa_log_warn("Unsupported resampling operation."); + return -PA_ERR_NOTSUPPORTED; + } } i = pa_msgobject_new(pa_sink_input); -- cgit From f4d1f2bdcfe5e598e926c190c80ec4c6d38dc9f4 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Fri, 25 Feb 2011 12:44:53 +0530 Subject: sink: Trivial typo fix --- src/pulsecore/sink.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h index ef698813..b61ba333 100644 --- a/src/pulsecore/sink.h +++ b/src/pulsecore/sink.h @@ -161,7 +161,7 @@ struct pa_sink { * s->real_volume and/or s->soft_volume so that they together * match the actual hardware volume that will be set later in the * write_volume callback. */ - void (*set_volume)(pa_sink *s); /* dito */ + void (*set_volume)(pa_sink *s); /* ditto */ /* Sink drivers that set PA_SINK_SYNC_VOLUME must provide this * callback. This callback is not used with sinks that do not set @@ -174,30 +174,30 @@ struct pa_sink { * not called automatically - it is the driver's responsibility to * schedule that function to be called at the right times in the * IO thread. */ - void (*write_volume)(pa_sink *s); /* dito */ + void (*write_volume)(pa_sink *s); /* ditto */ /* Called when the mute setting is queried. A PA_SINK_MESSAGE_GET_MUTE * message will also be sent. Called from IO thread if PA_SINK_SYNC_VOLUME * flag is set otherwise from main loop context. If refresh_mute is FALSE * neither this function is called nor a message is sent.*/ - void (*get_mute)(pa_sink *s); /* dito */ + void (*get_mute)(pa_sink *s); /* ditto */ /* Called when the mute setting shall be changed. A PA_SINK_MESSAGE_SET_MUTE * message will also be sent. Called from IO thread if PA_SINK_SYNC_VOLUME * flag is set otherwise from main loop context. */ - void (*set_mute)(pa_sink *s); /* dito */ + void (*set_mute)(pa_sink *s); /* ditto */ /* Called when a rewind request is issued. Called from IO thread * context. */ - void (*request_rewind)(pa_sink *s); /* dito */ + void (*request_rewind)(pa_sink *s); /* ditto */ /* Called when a the requested latency is changed. Called from IO * thread context. */ - void (*update_requested_latency)(pa_sink *s); /* dito */ + void (*update_requested_latency)(pa_sink *s); /* ditto */ /* Called whenever the port shall be changed. Called from main * thread. */ - int (*set_port)(pa_sink *s, pa_device_port *port); /* dito */ + int (*set_port)(pa_sink *s, pa_device_port *port); /* ditto */ /* Contains copies of the above data so that the real-time worker * thread can work without access locking */ -- cgit From 802c8b6fcc9349505d00c1f6f6132dd513904b68 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Thu, 24 Feb 2011 14:46:01 +0530 Subject: sample: Use PA_SAMPLE_INVALID instead of numeric value --- src/pulse/sample.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pulse/sample.c b/src/pulse/sample.c index 9698d8a5..50d55210 100644 --- a/src/pulse/sample.c +++ b/src/pulse/sample.c @@ -242,7 +242,7 @@ pa_sample_format_t pa_parse_sample_format(const char *format) { else if (strcasecmp(format, "s24-32re") == 0) return PA_SAMPLE_S24_32RE; - return -1; + return PA_SAMPLE_INVALID; } int pa_sample_format_is_le(pa_sample_format_t f) { -- cgit From c3839c76377ea7662f29364ae66718e8ac6233af Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Fri, 25 Feb 2011 12:35:14 +0530 Subject: core: Add a pa_format_info structure This will be used to represent the format of data provided by the client for both compressed and PCM formats in a new extended API. --- po/POTFILES.in | 1 + src/Makefile.am | 2 ++ src/map-file | 4 +++ src/pulse/format.c | 71 ++++++++++++++++++++++++++++++++++++++++++ src/pulse/format.h | 79 +++++++++++++++++++++++++++++++++++++++++++++++ src/pulse/pulseaudio.h | 1 + src/pulsecore/tagstruct.c | 42 +++++++++++++++++++++++++ src/pulsecore/tagstruct.h | 6 +++- 8 files changed, 205 insertions(+), 1 deletion(-) create mode 100644 src/pulse/format.c create mode 100644 src/pulse/format.h diff --git a/po/POTFILES.in b/po/POTFILES.in index ba7e9b71..35c20be4 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -177,6 +177,7 @@ src/pulse/thread-mainloop.c src/pulse/scache.c src/pulse/glib-mainloop.c src/pulse/timeval.c +src/pulse/format.c src/utils/pacat.c src/utils/pasuspender.c src/utils/pabrowse.c diff --git a/src/Makefile.am b/src/Makefile.am index 23d809d4..1b8b447e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -589,6 +589,7 @@ libpulsecommon_@PA_MAJORMINOR@_la_SOURCES = \ pulse/util.c pulse/util.h \ pulse/timeval.c pulse/timeval.h \ pulse/rtclock.c pulse/rtclock.h \ + pulse/format.c pulse/format.h \ pulsecore/atomic.h \ pulsecore/authkey.c pulsecore/authkey.h \ pulsecore/conf-parser.c pulsecore/conf-parser.h \ @@ -719,6 +720,7 @@ pulseinclude_HEADERS = \ pulse/error.h \ pulse/ext-device-manager.h \ pulse/ext-stream-restore.h \ + pulse/format.h \ pulse/gccmacro.h \ pulse/introspect.h \ pulse/mainloop-api.h \ diff --git a/src/map-file b/src/map-file index 1fffaff9..181af9ea 100644 --- a/src/map-file +++ b/src/map-file @@ -160,6 +160,10 @@ pa_ext_stream_restore_set_subscribe_cb; pa_ext_stream_restore_subscribe; pa_ext_stream_restore_test; pa_ext_stream_restore_write; +pa_format_info_copy; +pa_format_info_free; +pa_format_info_new; +pa_format_info_valid; pa_frame_size; pa_get_binary_name; pa_get_fqdn; diff --git a/src/pulse/format.c b/src/pulse/format.c new file mode 100644 index 00000000..372ae1c5 --- /dev/null +++ b/src/pulse/format.c @@ -0,0 +1,71 @@ +/*** + This file is part of PulseAudio. + + Copyright 2011 Intel Corporation + Copyright 2011 Collabora Multimedia + Copyright 2011 Arun Raghavan + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include +#include + +#include "format.h" + +pa_format_info* pa_format_info_new(void) { + pa_format_info *f = pa_xnew(pa_format_info, 1); + + f->encoding = PA_ENCODING_INVALID; + f->plist = pa_proplist_new(); + + return f; +} + +pa_format_info* pa_format_info_copy(const pa_format_info *src) { + pa_format_info *dest; + + pa_assert(src); + + dest = pa_xnew(pa_format_info, 1); + + dest->encoding = src->encoding; + + if (src->plist) + dest->plist = pa_proplist_copy(src->plist); + else + dest->plist = NULL; + + return dest; +} + +void pa_format_info_free(pa_format_info *f) { + pa_assert(f); + + pa_proplist_free(f->plist); + pa_xfree(f); +} + +int pa_format_info_valid(pa_format_info *f) { + return (f->encoding >= 0 && f->encoding < PA_ENCODING_MAX && f->plist != NULL); +} diff --git a/src/pulse/format.h b/src/pulse/format.h new file mode 100644 index 00000000..fdf4f377 --- /dev/null +++ b/src/pulse/format.h @@ -0,0 +1,79 @@ +#ifndef fooformathfoo +#define fooformathfoo + +/*** + This file is part of PulseAudio. + + Copyright 2011 Intel Corporation + Copyright 2011 Collabora Multimedia + Copyright 2011 Arun Raghavan + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include +#include + +PA_C_DECL_BEGIN + +/**< Represents the type of encoding used in a stream or accepted by a sink. \since 1.0 */ +typedef enum pa_encoding { + PA_ENCODING_ANY, + /**< Any encoding format, PCM or compressed */ + + PA_ENCODING_PCM, + /**< Any PCM format */ + + PA_ENCODING_AC3_IEC61937, + /**< AC3 data encapsulated in IEC 61937 header/padding */ + + PA_ENCODING_EAC3_IEC61937, + /**< EAC3 data encapsulated in IEC 61937 header/padding */ + + PA_ENCODING_MPEG_IEC61937, + /**< MPEG-1 or MPEG-2 (Part 3, not AAC) data encapsulated in IEC 61937 header/padding */ + + PA_ENCODING_MAX, + /**< Valid encoding types must be less than this value */ + + PA_ENCODING_INVALID = -1, + /**< Represents an invalid encoding */ +} pa_encoding_t; + +/**< Represents the format of data provided in a stream or processed by a sink. \since 1.0 */ +typedef struct pa_format_info { + pa_encoding_t encoding; + /**< The encoding used for the format */ + + pa_proplist *plist; + /**< Additional encoding-specific properties such as sample rate, bitrate, etc. */ +} pa_format_info; + +/**< Allocates a new \a pa_format_info structure. Clients must initialise at least the encoding field themselves. */ +pa_format_info* pa_format_info_new(void); + +/**< Returns a new \a pa_format_info struct and representing the same format as \a src */ +pa_format_info* pa_format_info_copy(const pa_format_info *src); + +/**< Frees a \a pa_format_info structure */ +void pa_format_info_free(pa_format_info *f); + +/** Returns non-zero when the format info structure is valid */ +int pa_format_info_valid(pa_format_info *f); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/pulseaudio.h b/src/pulse/pulseaudio.h index 793ba9b1..a399ed96 100644 --- a/src/pulse/pulseaudio.h +++ b/src/pulse/pulseaudio.h @@ -25,6 +25,7 @@ #include #include +#include #include #include #include diff --git a/src/pulsecore/tagstruct.c b/src/pulsecore/tagstruct.c index 804b9f90..5694a0da 100644 --- a/src/pulsecore/tagstruct.c +++ b/src/pulsecore/tagstruct.c @@ -291,6 +291,17 @@ void pa_tagstruct_put_proplist(pa_tagstruct *t, pa_proplist *p) { pa_tagstruct_puts(t, NULL); } +void pa_tagstruct_put_format_info(pa_tagstruct *t, pa_format_info *f) { + pa_assert(t); + pa_assert(f); + + extend(t, 1); + + t->data[t->length++] = PA_TAG_FORMAT_INFO; + pa_tagstruct_putu8(t, (uint8_t) f->encoding); + pa_tagstruct_put_proplist(t, f->plist); +} + int pa_tagstruct_gets(pa_tagstruct*t, const char **s) { int error = 0; size_t n; @@ -631,6 +642,37 @@ fail: return -1; } +int pa_tagstruct_get_format_info(pa_tagstruct *t, pa_format_info *f) { + size_t saved_rindex; + uint8_t encoding; + + pa_assert(t); + pa_assert(f); + + if (t->rindex+1 > t->length) + return -1; + + if (t->data[t->rindex] != PA_TAG_FORMAT_INFO) + return -1; + + saved_rindex = t->rindex; + t->rindex++; + + if (pa_tagstruct_getu8(t, &encoding) < 0) + goto fail; + + f->encoding = encoding; + + if (pa_tagstruct_get_proplist(t, f->plist) < 0) + goto fail; + + return 0; + +fail: + t->rindex = saved_rindex; + return -1; +} + void pa_tagstruct_put(pa_tagstruct *t, ...) { va_list va; pa_assert(t); diff --git a/src/pulsecore/tagstruct.h b/src/pulsecore/tagstruct.h index b6553ada..0091eeb9 100644 --- a/src/pulsecore/tagstruct.h +++ b/src/pulsecore/tagstruct.h @@ -27,6 +27,7 @@ #include #include +#include #include #include #include @@ -58,7 +59,8 @@ enum { PA_TAG_CHANNEL_MAP = 'm', PA_TAG_CVOLUME = 'v', PA_TAG_PROPLIST = 'P', - PA_TAG_VOLUME = 'V' + PA_TAG_VOLUME = 'V', + PA_TAG_FORMAT_INFO = 'f', }; pa_tagstruct *pa_tagstruct_new(const uint8_t* data, size_t length); @@ -84,6 +86,7 @@ void pa_tagstruct_put_channel_map(pa_tagstruct *t, const pa_channel_map *map); void pa_tagstruct_put_cvolume(pa_tagstruct *t, const pa_cvolume *cvolume); void pa_tagstruct_put_proplist(pa_tagstruct *t, pa_proplist *p); void pa_tagstruct_put_volume(pa_tagstruct *t, pa_volume_t volume); +void pa_tagstruct_put_format_info(pa_tagstruct *t, pa_format_info *f); int pa_tagstruct_get(pa_tagstruct *t, ...); @@ -101,5 +104,6 @@ int pa_tagstruct_get_channel_map(pa_tagstruct *t, pa_channel_map *map); int pa_tagstruct_get_cvolume(pa_tagstruct *t, pa_cvolume *v); int pa_tagstruct_get_proplist(pa_tagstruct *t, pa_proplist *p); int pa_tagstruct_get_volume(pa_tagstruct *t, pa_volume_t *v); +int pa_tagstruct_get_format_info(pa_tagstruct *t, pa_format_info *f); #endif -- cgit From 3767c9c4e8107a3e78f387466b45384d64644d94 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Mon, 28 Feb 2011 10:53:41 +0530 Subject: format: Add some properties and internal API The properties will be used by clients to set the sample format, sampling rate, etc. The functions will be used internally. --- src/pulse/format.c | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/pulse/internal.h | 5 +++ src/pulse/proplist.h | 12 ++++++++ 3 files changed, 104 insertions(+) diff --git a/src/pulse/format.c b/src/pulse/format.c index 372ae1c5..c3962de9 100644 --- a/src/pulse/format.c +++ b/src/pulse/format.c @@ -69,3 +69,90 @@ void pa_format_info_free(pa_format_info *f) { int pa_format_info_valid(pa_format_info *f) { return (f->encoding >= 0 && f->encoding < PA_ENCODING_MAX && f->plist != NULL); } + +pa_bool_t pa_format_info_is_compatible(pa_format_info *first, pa_format_info *second) { + const char *key; + void *state = NULL; + + pa_assert(first); + pa_assert(second); + + if (first->encoding != second->encoding) + return FALSE; + + while ((key = pa_proplist_iterate(first->plist, &state))) { + const char *value_one, *value_two; + + value_one = pa_proplist_gets(first->plist, key); + value_two = pa_proplist_gets(second->plist, key); + + if (!value_two || !pa_streq(value_one, value_two)) + return FALSE; + } + + return TRUE; +} + +pa_format_info* pa_format_info_from_sample_spec(pa_sample_spec *ss, pa_channel_map *map) { + char cm[PA_CHANNEL_MAP_SNPRINT_MAX]; + pa_format_info *f; + + pa_assert(ss && pa_sample_spec_valid(ss)); + pa_assert(!map || pa_channel_map_valid(map)); + + f = pa_format_info_new(); + f->encoding = PA_ENCODING_PCM; + + pa_proplist_sets(f->plist, PA_PROP_FORMAT_SAMPLE_FORMAT, pa_sample_format_to_string(ss->format)); + pa_proplist_setf(f->plist, PA_PROP_FORMAT_RATE, "%u", (unsigned int) ss->rate); + pa_proplist_setf(f->plist, PA_PROP_FORMAT_CHANNELS, "%u", (unsigned int) ss->channels); + + if (map) { + pa_channel_map_snprint(cm, sizeof(cm), map); + pa_proplist_setf(f->plist, PA_PROP_FORMAT_CHANNEL_MAP, "%s", cm); + } + + return f; +} + +/* For PCM streams */ +void pa_format_info_to_sample_spec(pa_format_info *f, pa_sample_spec *ss, pa_channel_map *map) { + const char *sf, *r, *ch; + uint32_t channels; + + pa_assert(f); + pa_assert(ss); + pa_assert(f->encoding == PA_ENCODING_PCM); + + pa_assert(sf = pa_proplist_gets(f->plist, PA_PROP_FORMAT_SAMPLE_FORMAT)); + pa_assert(r = pa_proplist_gets(f->plist, PA_PROP_FORMAT_RATE)); + pa_assert(ch = pa_proplist_gets(f->plist, PA_PROP_FORMAT_CHANNELS)); + + pa_assert((ss->format = pa_parse_sample_format(sf)) != PA_SAMPLE_INVALID); + pa_assert(pa_atou(r, &ss->rate) == 0); + pa_assert(pa_atou(ch, &channels) == 0); + ss->channels = (uint8_t) channels; + + if (map) { + const char *m = pa_proplist_gets(f->plist, PA_PROP_FORMAT_CHANNEL_MAP); + pa_channel_map_init(map); + + if (m) + pa_assert(pa_channel_map_parse(map, m) != NULL); + } +} + +/* For compressed streams */ +void pa_format_info_to_sample_spec_fake(pa_format_info *f, pa_sample_spec *ss) { + const char *r; + + pa_assert(f); + pa_assert(ss); + pa_assert(f->encoding != PA_ENCODING_PCM); + + ss->format = PA_SAMPLE_S16LE; + ss->channels = 2; + + pa_assert(r = pa_proplist_gets(f->plist, PA_PROP_FORMAT_RATE)); + pa_assert(pa_atou(r, &ss->rate) == 0); +} diff --git a/src/pulse/internal.h b/src/pulse/internal.h index ab702b99..53fcca60 100644 --- a/src/pulse/internal.h +++ b/src/pulse/internal.h @@ -291,6 +291,11 @@ pa_tagstruct *pa_tagstruct_command(pa_context *c, uint32_t command, uint32_t *ta void pa_ext_device_manager_command(pa_context *c, uint32_t tag, pa_tagstruct *t); void pa_ext_stream_restore_command(pa_context *c, uint32_t tag, pa_tagstruct *t); +pa_bool_t pa_format_info_is_compatible(pa_format_info *first, pa_format_info *second); +pa_format_info* pa_format_info_from_sample_spec(pa_sample_spec *ss, pa_channel_map *map); +void pa_format_info_to_sample_spec(pa_format_info *f, pa_sample_spec *ss, pa_channel_map *map); +void pa_format_info_to_sample_spec_fake(pa_format_info *f, pa_sample_spec *ss); + pa_bool_t pa_mainloop_is_our_api(pa_mainloop_api*m); #endif diff --git a/src/pulse/proplist.h b/src/pulse/proplist.h index 7d026997..a641f248 100644 --- a/src/pulse/proplist.h +++ b/src/pulse/proplist.h @@ -254,6 +254,18 @@ PA_C_DECL_BEGIN /** For modules: a version string for the module. e.g. "0.9.15" */ #define PA_PROP_MODULE_VERSION "module.version" +/** For PCM formats: the sample format used as returned by pa_sample_format_to_string() \since 1.0 */ +#define PA_PROP_FORMAT_SAMPLE_FORMAT "format.sample_format" + +/** For all formats: the sample rate (unsigned integer) \since 1.0 */ +#define PA_PROP_FORMAT_RATE "format.rate" + +/** For all formats: the number of channels (unsigned integer) \since 1.0 */ +#define PA_PROP_FORMAT_CHANNELS "format.channels" + +/** For PCM formats: the channel map of the stream as returned by pa_channel_map_snprint() \since 1.0 */ +#define PA_PROP_FORMAT_CHANNEL_MAP "format.channel_map" + /** A property list object. Basically a dictionary with ASCII strings * as keys and arbitrary data as values. \since 0.9.11 */ typedef struct pa_proplist pa_proplist; -- cgit From 47e0f91aa2ca6eb3ea8b7be8aa03cd03a28c3fbe Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Mon, 28 Feb 2011 13:00:20 +0530 Subject: sink: Extend API for compressed formats support This adds a get_formats() vfunc for sinks to provide a list of formats they can support. pa_sink_check_formats() can be used during or after routing to determine what formats from a stream the sink can support. --- src/pulsecore/sink.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/pulsecore/sink.h | 7 +++++++ 2 files changed, 58 insertions(+) diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c index 839b7d44..345d090c 100644 --- a/src/pulsecore/sink.c +++ b/src/pulsecore/sink.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -177,6 +178,7 @@ static void reset_callbacks(pa_sink *s) { s->request_rewind = NULL; s->update_requested_latency = NULL; s->set_port = NULL; + s->get_formats = NULL; } /* Called from main context */ @@ -3258,3 +3260,52 @@ static void pa_sink_volume_change_rewind(pa_sink *s, size_t nbytes) { } pa_sink_volume_change_apply(s, NULL); } + +/* Called from the main thread */ +/* Gets the list of formats supported by the sink. The members and idxset must + * be freed by the caller. */ +pa_idxset* pa_sink_get_formats(pa_sink *s) { + pa_idxset *ret; + + pa_assert(s); + + if (s->get_formats) { + /* Sink supports format query, all is good */ + ret = s->get_formats(s); + } else { + /* Sink doesn't support format query, so assume it does PCM */ + pa_format_info *f = pa_format_info_new(); + f->encoding = PA_ENCODING_PCM; + + ret = pa_idxset_new(NULL, NULL); + pa_idxset_put(ret, f, NULL); + } + + return ret; +} + +/* Called from the main thread */ +/* Calculates the intersection between formats supported by the sink and + * in_formats, and returns these, in the order of the sink's formats. */ +pa_idxset* pa_sink_check_formats(pa_sink *s, pa_idxset *in_formats) { + pa_idxset *out_formats = pa_idxset_new(NULL, NULL), *sink_formats; + pa_format_info *f_sink, *f_in; + uint32_t i, j; + + pa_assert(s); + + if (!in_formats || pa_idxset_isempty(in_formats)) + goto done; + + sink_formats = pa_sink_get_formats(s); + + PA_IDXSET_FOREACH(f_sink, sink_formats, i) { + PA_IDXSET_FOREACH(f_in, in_formats, j) { + if (pa_format_info_is_compatible(f_sink, f_in)) + pa_idxset_put(out_formats, pa_format_info_copy(f_in), NULL); + } + } + +done: + return out_formats; +} diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h index b61ba333..a96dd90a 100644 --- a/src/pulsecore/sink.h +++ b/src/pulsecore/sink.h @@ -199,6 +199,10 @@ struct pa_sink { * thread. */ int (*set_port)(pa_sink *s, pa_device_port *port); /* ditto */ + /* Called to get the list of formats supported by the sink, sorted + * in descending order of preference. */ + pa_idxset* (*get_formats)(pa_sink *s); /* ditto */ + /* Contains copies of the above data so that the real-time worker * thread can work without access locking */ struct { @@ -398,6 +402,9 @@ pa_queue *pa_sink_move_all_start(pa_sink *s, pa_queue *q); void pa_sink_move_all_finish(pa_sink *s, pa_queue *q, pa_bool_t save); void pa_sink_move_all_fail(pa_queue *q); +pa_idxset* pa_sink_get_formats(pa_sink *s); +pa_idxset* pa_sink_check_formats(pa_sink *s, pa_idxset *in_formats); + /*** To be called exclusively by the sink driver, from IO context */ void pa_sink_render(pa_sink*s, size_t length, pa_memchunk *result); -- cgit 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 --- PROTOCOL | 15 ++++ configure.ac | 2 +- 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 +- 25 files changed, 346 insertions(+), 81 deletions(-) diff --git a/PROTOCOL b/PROTOCOL index a15d1163..d06cb988 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 ec1099ba..9edae0ec 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 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 From 54c391e6db550c5519df0ebb37f2197eed440c92 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Tue, 1 Mar 2011 16:34:06 +0530 Subject: format: Add convenience API to check if a format is PCM or not --- src/map-file | 1 + src/pulse/format.c | 4 ++++ src/pulse/format.h | 3 +++ 3 files changed, 8 insertions(+) diff --git a/src/map-file b/src/map-file index b4196f15..3b1de105 100644 --- a/src/map-file +++ b/src/map-file @@ -162,6 +162,7 @@ pa_ext_stream_restore_test; pa_ext_stream_restore_write; pa_format_info_copy; pa_format_info_free; +pa_format_info_is_pcm; pa_format_info_new; pa_format_info_valid; pa_frame_size; diff --git a/src/pulse/format.c b/src/pulse/format.c index c3962de9..4ecd75ee 100644 --- a/src/pulse/format.c +++ b/src/pulse/format.c @@ -70,6 +70,10 @@ int pa_format_info_valid(pa_format_info *f) { return (f->encoding >= 0 && f->encoding < PA_ENCODING_MAX && f->plist != NULL); } +int pa_format_info_is_pcm(pa_format_info *f) { + return f->encoding == PA_ENCODING_PCM; +} + pa_bool_t pa_format_info_is_compatible(pa_format_info *first, pa_format_info *second) { const char *key; void *state = NULL; diff --git a/src/pulse/format.h b/src/pulse/format.h index fdf4f377..7e3ab08d 100644 --- a/src/pulse/format.h +++ b/src/pulse/format.h @@ -74,6 +74,9 @@ void pa_format_info_free(pa_format_info *f); /** Returns non-zero when the format info structure is valid */ int pa_format_info_valid(pa_format_info *f); +/** Returns non-zero when the format info structure represents a PCM (i.e. uncompressed data) format */ +int pa_format_info_is_pcm(pa_format_info *f); + PA_C_DECL_END #endif -- cgit From 71ec9577cf052558cfddb051c7d038c91b397bc5 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Wed, 2 Mar 2011 02:06:54 +0530 Subject: sink: Remove PASSTHROUGH flag This removes the passthrough flag from sinks since we will drop exclusively passthrough sinks in favour of providing a list of formats supported by each sink. We can still determine whether a sink is in passthrough mode by checking if any non-PCM streams are attached to it. --- src/modules/alsa/alsa-sink.c | 7 ------- src/pulse/def.h | 10 ++-------- src/pulsecore/sink-input.c | 41 ++++++++++++----------------------------- src/pulsecore/sink.c | 37 ++++++++++++++++++++++--------------- src/pulsecore/sink.h | 4 ++++ 5 files changed, 40 insertions(+), 59 deletions(-) diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c index ccbc0628..ec840afd 100644 --- a/src/modules/alsa/alsa-sink.c +++ b/src/modules/alsa/alsa-sink.c @@ -1707,13 +1707,6 @@ static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB, pa_bool_t sync_v return 0; } - /* FIXME: need automatic detection rather than hard-coded path */ - if (!strcmp(u->mixer_path->name, "iec958-passthrough-output")) { - u->sink->flags |= PA_SINK_PASSTHROUGH; - } else { - u->sink->flags &= ~PA_SINK_PASSTHROUGH; - } - if (!u->mixer_path->has_volume) pa_log_info("Driver does not support hardware volume control, falling back to software volume control."); else { diff --git a/src/pulse/def.h b/src/pulse/def.h index a3b86223..91a027e3 100644 --- a/src/pulse/def.h +++ b/src/pulse/def.h @@ -739,12 +739,7 @@ typedef enum pa_sink_flags { /**< The latency can be adjusted dynamically depending on the * needs of the connected streams. \since 0.9.15 */ - PA_SINK_PASSTHROUGH = 0x0100U, - /**< This sink has support for passthrough mode. The data will be left - * as is and not reformatted, resampled, mixed. - * \since 1.0 */ - - PA_SINK_SYNC_VOLUME = 0x0200U, + PA_SINK_SYNC_VOLUME = 0x0100U, /**< The HW volume changes are syncronized with SW volume. * \since 1.0 */ @@ -753,7 +748,7 @@ typedef enum pa_sink_flags { * The server will filter out these flags anyway, so you should never see * these flags in sinks. */ - PA_SINK_SHARE_VOLUME_WITH_MASTER = 0x0400U, + PA_SINK_SHARE_VOLUME_WITH_MASTER = 0x0200U, /**< This sink shares the volume with the master sink (used by some filter * sinks). */ /** \endcond */ @@ -769,7 +764,6 @@ typedef enum pa_sink_flags { #define PA_SINK_DECIBEL_VOLUME PA_SINK_DECIBEL_VOLUME #define PA_SINK_FLAT_VOLUME PA_SINK_FLAT_VOLUME #define PA_SINK_DYNAMIC_LATENCY PA_SINK_DYNAMIC_LATENCY -#define PA_SINK_PASSTHROUGH PA_SINK_PASSTHROUGH #define PA_SINK_SYNC_VOLUME PA_SINK_SYNC_VOLUME #define PA_SINK_SHARE_VOLUME_WITH_MASTER PA_SINK_SHARE_VOLUME_WITH_MASTER diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c index 3df499e4..b05373f8 100644 --- a/src/pulsecore/sink-input.c +++ b/src/pulsecore/sink-input.c @@ -50,36 +50,19 @@ PA_DEFINE_PUBLIC_CLASS(pa_sink_input, pa_msgobject); static void sink_input_free(pa_object *o); static void set_real_ratio(pa_sink_input *i, const pa_cvolume *v); -static int check_passthrough_connection(pa_sink_input_flags_t flags, pa_sink *dest) { +static int check_passthrough_connection(pa_format_info *format, pa_sink *dest) { - if (dest->flags & PA_SINK_PASSTHROUGH) { - - if (pa_idxset_size(dest->inputs) > 0) { - - pa_sink_input *alt_i; - uint32_t idx; - - alt_i = pa_idxset_first(dest->inputs, &idx); - - /* only need to check the first input is not PASSTHROUGH */ - if (alt_i->flags & PA_SINK_INPUT_PASSTHROUGH) { - pa_log_warn("Sink is already connected to PASSTHROUGH input"); - return -PA_ERR_BUSY; - } - - /* Current inputs are PCM, check new input is not PASSTHROUGH */ - if (flags & PA_SINK_INPUT_PASSTHROUGH) { - pa_log_warn("Sink is already connected, cannot accept new PASSTHROUGH INPUT"); - return -PA_ERR_BUSY; - } - } + if (pa_sink_is_passthrough(dest)) { + pa_log_warn("Sink is already connected to PASSTHROUGH input"); + return -PA_ERR_BUSY; + } - } else { - if (flags & PA_SINK_INPUT_PASSTHROUGH) { - pa_log_warn("Cannot connect PASSTHROUGH sink input to sink without PASSTHROUGH capabilities"); - return -PA_ERR_INVALID; - } + /* If current input(s) exist, check new input is not PASSTHROUGH */ + if (pa_idxset_size(dest->inputs) > 0 && !pa_format_info_is_pcm(format)) { + pa_log_warn("Sink is already connected, cannot accept new PASSTHROUGH INPUT"); + return -PA_ERR_BUSY; } + return PA_OK; } @@ -313,7 +296,7 @@ int pa_sink_input_new( pa_return_val_if_fail(PA_SINK_IS_LINKED(pa_sink_get_state(data->sink)), -PA_ERR_BADSTATE); pa_return_val_if_fail(!data->sync_base || (data->sync_base->sink == data->sink && pa_sink_input_get_state(data->sync_base) == PA_SINK_INPUT_CORKED), -PA_ERR_INVALID); - r = check_passthrough_connection(data->flags, data->sink); + r = check_passthrough_connection(data->format, data->sink); pa_return_val_if_fail(r == PA_OK, r); if (!data->sample_spec_is_set) @@ -1355,7 +1338,7 @@ pa_bool_t pa_sink_input_may_move_to(pa_sink_input *i, pa_sink *dest) { return FALSE; } - if (check_passthrough_connection(i->flags, dest) < 0) + if (check_passthrough_connection(i->format, dest) < 0) return FALSE; if (i->may_move_to) diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c index 345d090c..33923e1f 100644 --- a/src/pulsecore/sink.c +++ b/src/pulsecore/sink.c @@ -1241,6 +1241,24 @@ pa_bool_t pa_sink_flat_volume_enabled(pa_sink *s) { return (s->flags & PA_SINK_FLAT_VOLUME); } +/* Called from main context */ +pa_bool_t pa_sink_is_passthrough(pa_sink *s) { + pa_sink_input *alt_i; + uint32_t idx; + + pa_sink_assert_ref(s); + + /* one and only one PASSTHROUGH input can possibly be connected */ + if (pa_idxset_size(s->inputs) == 1) { + alt_i = pa_idxset_first(s->inputs, &idx); + + if (!pa_format_info_is_pcm(alt_i->format)) + return TRUE; + } + + return FALSE; +} + /* Called from main context. */ static void compute_reference_ratio(pa_sink_input *i) { unsigned c = 0; @@ -1632,21 +1650,10 @@ void pa_sink_set_volume( pa_assert(!volume || volume->channels == 1 || pa_cvolume_compatible(volume, &s->sample_spec)); /* make sure we don't change the volume when a PASSTHROUGH input is connected */ - if (s->flags & PA_SINK_PASSTHROUGH) { - pa_sink_input *alt_i; - uint32_t idx; - - /* one and only one PASSTHROUGH input can possibly be connected */ - if (pa_idxset_size(s->inputs) == 1) { - - alt_i = pa_idxset_first(s->inputs, &idx); - - if (alt_i->flags & PA_SINK_INPUT_PASSTHROUGH) { - /* FIXME: Need to notify client that volume control is disabled */ - pa_log_warn("Cannot change volume, Sink is connected to PASSTHROUGH input"); - return; - } - } + if (pa_sink_is_passthrough(s)) { + /* FIXME: Need to notify client that volume control is disabled */ + pa_log_warn("Cannot change volume, Sink is connected to PASSTHROUGH input"); + return; } /* In case of volume sharing, the volume is set for the root sink first, diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h index a96dd90a..492abf68 100644 --- a/src/pulsecore/sink.h +++ b/src/pulsecore/sink.h @@ -382,6 +382,10 @@ int pa_sink_suspend_all(pa_core *c, pa_bool_t suspend, pa_suspend_cause_t cause) /* Use this instead of checking s->flags & PA_SINK_FLAT_VOLUME directly. */ pa_bool_t pa_sink_flat_volume_enabled(pa_sink *s); +/* Is the sink in passthrough mode? (that is, is there a passthrough sink input + * connected to this sink? */ +pa_bool_t pa_sink_is_passthrough(pa_sink *s); + void pa_sink_set_volume(pa_sink *sink, const pa_cvolume *volume, pa_bool_t sendmsg, pa_bool_t save); const pa_cvolume *pa_sink_get_volume(pa_sink *sink, pa_bool_t force_refresh); -- cgit From e64f02ad9ec7d027e0360e3964e88a497d5df02f Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Wed, 2 Mar 2011 10:51:56 +0530 Subject: tests: Add a trivial test for the extended API This is just sync-playback.c modified to use the extended API. We need something more sophisticated for properly testing the compressed formats, but that is a non-trivial task in itself. --- src/Makefile.am | 6 ++ src/tests/extended-test.c | 197 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 203 insertions(+) create mode 100644 src/tests/extended-test.c diff --git a/src/Makefile.am b/src/Makefile.am index 1b8b447e..e3217043 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -274,6 +274,7 @@ TESTS_BINARIES = \ mcalign-test \ pacat-simple \ parec-simple \ + extended-test \ strlist-test \ close-test \ voltest \ @@ -439,6 +440,11 @@ parec_simple_LDADD = $(AM_LDADD) libpulse.la libpulse-simple.la libpulsecommon-@ parec_simple_CFLAGS = $(AM_CFLAGS) parec_simple_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) +extended_test_SOURCES = tests/extended-test.c +extended_test_LDADD = $(AM_LDADD) libpulse.la libpulsecommon-@PA_MAJORMINOR@.la +extended_test_CFLAGS = $(AM_CFLAGS) +extended_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + strlist_test_SOURCES = tests/strlist-test.c strlist_test_CFLAGS = $(AM_CFLAGS) strlist_test_LDADD = $(AM_LDADD) $(WINSOCK_LIBS) libpulsecore-@PA_MAJORMINOR@.la libpulse.la libpulsecommon-@PA_MAJORMINOR@.la diff --git a/src/tests/extended-test.c b/src/tests/extended-test.c new file mode 100644 index 00000000..ec3cc53c --- /dev/null +++ b/src/tests/extended-test.c @@ -0,0 +1,197 @@ +/*** + This file is part of PulseAudio. + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define NSTREAMS 4 +#define SINE_HZ 440 +#define SAMPLE_HZ 8000 + +static pa_context *context = NULL; +static pa_stream *streams[NSTREAMS]; +static pa_mainloop_api *mainloop_api = NULL; + +static float data[SAMPLE_HZ]; /* one second space */ + +static int n_streams_ready = 0; + +static const pa_buffer_attr buffer_attr = { + .maxlength = SAMPLE_HZ*sizeof(float)*NSTREAMS, /* exactly space for the entire play time */ + .tlength = (uint32_t) -1, + .prebuf = 0, /* Setting prebuf to 0 guarantees us the the streams will run synchronously, no matter what */ + .minreq = (uint32_t) -1, + .fragsize = 0 +}; + +static void nop_free_cb(void *p) {} + +static void underflow_cb(struct pa_stream *s, void *userdata) { + int i = (int) (long) userdata; + + fprintf(stderr, "Stream %i finished\n", i); + + if (++n_streams_ready >= 2*NSTREAMS) { + fprintf(stderr, "We're done\n"); + mainloop_api->quit(mainloop_api, 0); + } +} + +/* This routine is called whenever the stream state changes */ +static void stream_state_callback(pa_stream *s, void *userdata) { + assert(s); + + switch (pa_stream_get_state(s)) { + case PA_STREAM_UNCONNECTED: + case PA_STREAM_CREATING: + case PA_STREAM_TERMINATED: + break; + + case PA_STREAM_READY: { + + int r, i = (int) (long) userdata; + + fprintf(stderr, "Writing data to stream %i.\n", i); + + r = pa_stream_write(s, data, sizeof(data), nop_free_cb, (int64_t) sizeof(data) * (int64_t) i, PA_SEEK_ABSOLUTE); + assert(r == 0); + + /* Be notified when this stream is drained */ + pa_stream_set_underflow_callback(s, underflow_cb, userdata); + + /* All streams have been set up, let's go! */ + if (++n_streams_ready >= NSTREAMS) { + fprintf(stderr, "Uncorking\n"); + pa_operation_unref(pa_stream_cork(s, 0, NULL, NULL)); + } + + break; + } + + default: + case PA_STREAM_FAILED: + fprintf(stderr, "Stream error: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s)))); + abort(); + } +} + +/* This is called whenever the context status changes */ +static void context_state_callback(pa_context *c, void *userdata) { + assert(c); + + switch (pa_context_get_state(c)) { + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + + case PA_CONTEXT_READY: { + + int i; + fprintf(stderr, "Connection established.\n"); + + for (i = 0; i < NSTREAMS; i++) { + char name[64]; + pa_format_info *formats[2] = { NULL }; + + formats[0] = pa_format_info_new(); + formats[0]->encoding = PA_ENCODING_PCM; + pa_proplist_sets(formats[0]->plist, PA_PROP_FORMAT_SAMPLE_FORMAT, pa_sample_format_to_string(PA_SAMPLE_FLOAT32)); + pa_proplist_setf(formats[0]->plist, PA_PROP_FORMAT_RATE, "%u", SAMPLE_HZ); + pa_proplist_setf(formats[0]->plist, PA_PROP_FORMAT_CHANNELS, "%u", 1); + + fprintf(stderr, "Creating stream %i\n", i); + + snprintf(name, sizeof(name), "stream #%i", i); + + streams[i] = pa_stream_new_extended(c, name, formats, NULL); + assert(streams[i]); + pa_stream_set_state_callback(streams[i], stream_state_callback, (void*) (long) i); + pa_stream_connect_playback(streams[i], NULL, &buffer_attr, PA_STREAM_START_CORKED, NULL, i == 0 ? NULL : streams[0]); + } + + break; + } + + case PA_CONTEXT_TERMINATED: + mainloop_api->quit(mainloop_api, 0); + break; + + case PA_CONTEXT_FAILED: + default: + fprintf(stderr, "Context error: %s\n", pa_strerror(pa_context_errno(c))); + abort(); + } +} + +int main(int argc, char *argv[]) { + pa_mainloop* m = NULL; + int i, ret = 0; + + for (i = 0; i < SAMPLE_HZ; i++) + data[i] = (float) sin(((double) i/SAMPLE_HZ)*2*M_PI*SINE_HZ)/2; + + for (i = 0; i < NSTREAMS; i++) + streams[i] = NULL; + + /* Set up a new main loop */ + m = pa_mainloop_new(); + assert(m); + + mainloop_api = pa_mainloop_get_api(m); + + context = pa_context_new(mainloop_api, argv[0]); + assert(context); + + pa_context_set_state_callback(context, context_state_callback, NULL); + + /* Connect the context */ + if (pa_context_connect(context, NULL, 0, NULL) < 0) { + fprintf(stderr, "pa_context_connect() failed.\n"); + goto quit; + } + + if (pa_mainloop_run(m, &ret) < 0) + fprintf(stderr, "pa_mainloop_run() failed.\n"); + +quit: + pa_context_unref(context); + + for (i = 0; i < NSTREAMS; i++) + if (streams[i]) + pa_stream_unref(streams[i]); + + pa_mainloop_free(m); + + return ret; +} -- cgit From 5d5523604f03ac7d6b8f67569ed4ac6c06c72463 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Wed, 2 Mar 2011 10:53:45 +0530 Subject: sink-input: Minor cleanups Removes a couple of warnings and simplifies the assertion logic that verifies format negotiation was successful. --- src/pulsecore/sink-input.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c index b05373f8..92e364f8 100644 --- a/src/pulsecore/sink-input.c +++ b/src/pulsecore/sink-input.c @@ -185,9 +185,6 @@ pa_bool_t pa_sink_input_new_data_set_formats(pa_sink_input_new_data *data, pa_id } 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) @@ -273,11 +270,14 @@ int pa_sink_input_new( /* 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) + if (!data->format && data->nego_formats && !pa_idxset_isempty(data->nego_formats)) data->format = pa_format_info_copy(pa_idxset_first(data->nego_formats, NULL)); + + pa_return_val_if_fail(data->format, -PA_ERR_INVALID); + /* 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)) { -- cgit From 8ec0548f5f4c7ce3213e4639722d9cb78fc13b90 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Wed, 2 Mar 2011 11:00:49 +0530 Subject: sink-input: Return NOTSUPPORTED if format negotiation fails This is easier for clients to grok than INVALID. --- src/pulsecore/sink-input.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c index 92e364f8..1706a7fb 100644 --- a/src/pulsecore/sink-input.c +++ b/src/pulsecore/sink-input.c @@ -276,7 +276,7 @@ int pa_sink_input_new( if (!data->format && data->nego_formats && !pa_idxset_isempty(data->nego_formats)) data->format = pa_format_info_copy(pa_idxset_first(data->nego_formats, NULL)); - pa_return_val_if_fail(data->format, -PA_ERR_INVALID); + pa_return_val_if_fail(data->format, -PA_ERR_NOTSUPPORTED); /* Now populate the sample spec and format according to the final * format that we've negotiated */ -- cgit From 13229fb39e7b8cff3f114c98f8236d9123442243 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Wed, 2 Mar 2011 11:16:07 +0530 Subject: sink-input: Don't assert on bad formats Handles bad format input more gracefully and returns an error instead. --- src/pulse/format.c | 30 +++++++++++++++++------------- src/pulse/internal.h | 4 ++-- src/pulsecore/sink-input.c | 4 ++-- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/pulse/format.c b/src/pulse/format.c index 4ecd75ee..af24eb2e 100644 --- a/src/pulse/format.c +++ b/src/pulse/format.c @@ -120,21 +120,21 @@ pa_format_info* pa_format_info_from_sample_spec(pa_sample_spec *ss, pa_channel_m } /* For PCM streams */ -void pa_format_info_to_sample_spec(pa_format_info *f, pa_sample_spec *ss, pa_channel_map *map) { +pa_bool_t pa_format_info_to_sample_spec(pa_format_info *f, pa_sample_spec *ss, pa_channel_map *map) { const char *sf, *r, *ch; uint32_t channels; pa_assert(f); pa_assert(ss); - pa_assert(f->encoding == PA_ENCODING_PCM); + pa_return_val_if_fail(f->encoding == PA_ENCODING_PCM, FALSE); - pa_assert(sf = pa_proplist_gets(f->plist, PA_PROP_FORMAT_SAMPLE_FORMAT)); - pa_assert(r = pa_proplist_gets(f->plist, PA_PROP_FORMAT_RATE)); - pa_assert(ch = pa_proplist_gets(f->plist, PA_PROP_FORMAT_CHANNELS)); + pa_return_val_if_fail(sf = pa_proplist_gets(f->plist, PA_PROP_FORMAT_SAMPLE_FORMAT), FALSE); + pa_return_val_if_fail(r = pa_proplist_gets(f->plist, PA_PROP_FORMAT_RATE), FALSE); + pa_return_val_if_fail(ch = pa_proplist_gets(f->plist, PA_PROP_FORMAT_CHANNELS), FALSE); - pa_assert((ss->format = pa_parse_sample_format(sf)) != PA_SAMPLE_INVALID); - pa_assert(pa_atou(r, &ss->rate) == 0); - pa_assert(pa_atou(ch, &channels) == 0); + pa_return_val_if_fail((ss->format = pa_parse_sample_format(sf)) != PA_SAMPLE_INVALID, FALSE); + pa_return_val_if_fail(pa_atou(r, &ss->rate) == 0, FALSE); + pa_return_val_if_fail(pa_atou(ch, &channels) == 0, FALSE); ss->channels = (uint8_t) channels; if (map) { @@ -142,21 +142,25 @@ void pa_format_info_to_sample_spec(pa_format_info *f, pa_sample_spec *ss, pa_cha pa_channel_map_init(map); if (m) - pa_assert(pa_channel_map_parse(map, m) != NULL); + pa_return_val_if_fail(pa_channel_map_parse(map, m) != NULL, FALSE); } + + return TRUE; } /* For compressed streams */ -void pa_format_info_to_sample_spec_fake(pa_format_info *f, pa_sample_spec *ss) { +pa_bool_t pa_format_info_to_sample_spec_fake(pa_format_info *f, pa_sample_spec *ss) { const char *r; pa_assert(f); pa_assert(ss); - pa_assert(f->encoding != PA_ENCODING_PCM); + pa_return_val_if_fail(f->encoding != PA_ENCODING_PCM, FALSE); ss->format = PA_SAMPLE_S16LE; ss->channels = 2; - pa_assert(r = pa_proplist_gets(f->plist, PA_PROP_FORMAT_RATE)); - pa_assert(pa_atou(r, &ss->rate) == 0); + pa_return_val_if_fail(r = pa_proplist_gets(f->plist, PA_PROP_FORMAT_RATE), FALSE); + pa_return_val_if_fail(pa_atou(r, &ss->rate) == 0, FALSE); + + return TRUE; } diff --git a/src/pulse/internal.h b/src/pulse/internal.h index d7151653..228e8dc4 100644 --- a/src/pulse/internal.h +++ b/src/pulse/internal.h @@ -298,8 +298,8 @@ void pa_ext_stream_restore_command(pa_context *c, uint32_t tag, pa_tagstruct *t) pa_bool_t pa_format_info_is_compatible(pa_format_info *first, pa_format_info *second); pa_format_info* pa_format_info_from_sample_spec(pa_sample_spec *ss, pa_channel_map *map); -void pa_format_info_to_sample_spec(pa_format_info *f, pa_sample_spec *ss, pa_channel_map *map); -void pa_format_info_to_sample_spec_fake(pa_format_info *f, pa_sample_spec *ss); +pa_bool_t pa_format_info_to_sample_spec(pa_format_info *f, pa_sample_spec *ss, pa_channel_map *map); +pa_bool_t pa_format_info_to_sample_spec_fake(pa_format_info *f, pa_sample_spec *ss); pa_bool_t pa_mainloop_is_our_api(pa_mainloop_api*m); diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c index 1706a7fb..a4659558 100644 --- a/src/pulsecore/sink-input.c +++ b/src/pulsecore/sink-input.c @@ -281,12 +281,12 @@ int pa_sink_input_new( /* 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_return_val_if_fail(pa_format_info_to_sample_spec(data->format, &ss, &map), -PA_ERR_INVALID); 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_return_val_if_fail(pa_format_info_to_sample_spec_fake(data->format, &ss), -PA_ERR_INVALID); 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; -- cgit From e418e49ecbd643f3cac87438d00baaf86275927c Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Wed, 2 Mar 2011 11:31:51 +0530 Subject: format: Avoid some code duplication We frequently need to free an idxset containing pa_format_infos, so define an internal free function that can be used directly with this (instead of defining it once-per-file). --- src/pulse/format.c | 4 ++++ src/pulse/internal.h | 1 + src/pulsecore/protocol-native.c | 7 ++----- src/pulsecore/sink-input.c | 14 +++++--------- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/pulse/format.c b/src/pulse/format.c index af24eb2e..9bb76732 100644 --- a/src/pulse/format.c +++ b/src/pulse/format.c @@ -66,6 +66,10 @@ void pa_format_info_free(pa_format_info *f) { pa_xfree(f); } +void pa_format_info_free2(pa_format_info *f, void *userdata) { + pa_format_info_free(f); +} + int pa_format_info_valid(pa_format_info *f) { return (f->encoding >= 0 && f->encoding < PA_ENCODING_MAX && f->plist != NULL); } diff --git a/src/pulse/internal.h b/src/pulse/internal.h index 228e8dc4..351aeec9 100644 --- a/src/pulse/internal.h +++ b/src/pulse/internal.h @@ -296,6 +296,7 @@ pa_tagstruct *pa_tagstruct_command(pa_context *c, uint32_t command, uint32_t *ta void pa_ext_device_manager_command(pa_context *c, uint32_t tag, pa_tagstruct *t); void pa_ext_stream_restore_command(pa_context *c, uint32_t tag, pa_tagstruct *t); +void pa_format_info_free2(pa_format_info *f, void *userdata); pa_bool_t pa_format_info_is_compatible(pa_format_info *first, pa_format_info *second); pa_format_info* pa_format_info_from_sample_spec(pa_sample_spec *ss, pa_channel_map *map); pa_bool_t pa_format_info_to_sample_spec(pa_format_info *f, pa_sample_spec *ss, pa_channel_map *map); diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c index 83321790..ee2bc4f5 100644 --- a/src/pulsecore/protocol-native.c +++ b/src/pulsecore/protocol-native.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -1849,10 +1850,6 @@ 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; @@ -2110,7 +2107,7 @@ error: if (p) pa_proplist_free(p); if (formats) - pa_idxset_free(formats, (pa_free2_cb_t) free_format_info, NULL); + pa_idxset_free(formats, (pa_free2_cb_t) pa_format_info_free2, NULL); return; } diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c index a4659558..0f34e650 100644 --- a/src/pulsecore/sink-input.c +++ b/src/pulsecore/sink-input.c @@ -130,10 +130,6 @@ 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; @@ -154,12 +150,12 @@ pa_bool_t pa_sink_input_new_data_set_sink(pa_sink_input_new_data *data, pa_sink 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); + pa_idxset_free(data->nego_formats, (pa_free2_cb_t) pa_format_info_free2, 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); + pa_idxset_free(formats, (pa_free2_cb_t) pa_format_info_free2, NULL); ret = FALSE; } } @@ -172,7 +168,7 @@ pa_bool_t pa_sink_input_new_data_set_formats(pa_sink_input_new_data *data, pa_id pa_assert(formats); if (data->req_formats) - pa_idxset_free(formats, (pa_free2_cb_t) free_format_info, NULL); + pa_idxset_free(formats, (pa_free2_cb_t) pa_format_info_free2, NULL); data->req_formats = formats; @@ -188,10 +184,10 @@ void pa_sink_input_new_data_done(pa_sink_input_new_data *data) { pa_assert(data); if (data->req_formats) - pa_idxset_free(data->req_formats, (pa_free2_cb_t) free_format_info, NULL); + pa_idxset_free(data->req_formats, (pa_free2_cb_t) pa_format_info_free2, NULL); if (data->nego_formats) - pa_idxset_free(data->nego_formats, (pa_free2_cb_t) free_format_info, NULL); + pa_idxset_free(data->nego_formats, (pa_free2_cb_t) pa_format_info_free2, NULL); if (data->format) pa_format_info_free(data->format); -- cgit From 8b3e68a202084467086af6c83e89c07adb9aa18a Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Wed, 2 Mar 2011 11:16:48 +0530 Subject: sink: Fix leak in pa_sink_check_formats() We weren't freeing the sink formats idxset. --- src/pulsecore/sink.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c index 33923e1f..2b9402ab 100644 --- a/src/pulsecore/sink.c +++ b/src/pulsecore/sink.c @@ -3295,7 +3295,7 @@ pa_idxset* pa_sink_get_formats(pa_sink *s) { /* Calculates the intersection between formats supported by the sink and * in_formats, and returns these, in the order of the sink's formats. */ pa_idxset* pa_sink_check_formats(pa_sink *s, pa_idxset *in_formats) { - pa_idxset *out_formats = pa_idxset_new(NULL, NULL), *sink_formats; + pa_idxset *out_formats = pa_idxset_new(NULL, NULL), *sink_formats = NULL; pa_format_info *f_sink, *f_in; uint32_t i, j; @@ -3314,5 +3314,8 @@ pa_idxset* pa_sink_check_formats(pa_sink *s, pa_idxset *in_formats) { } done: + if (sink_formats) + pa_idxset_free(sink_formats, (pa_free2_cb_t) pa_format_info_free2, NULL); + return out_formats; } -- cgit From 658a9153f094db9a30ac94428f4e46985e7096eb Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Wed, 2 Mar 2011 11:38:01 +0530 Subject: sink-input: Kill passthrough streams if moving to an unsupported sink This will eventually be replaced by a hook to let clients know that the stream has moved so that they can gracefully reconnect and renegotiate a supported format. --- src/pulsecore/sink-input.c | 5 +++++ src/pulsecore/sink.c | 27 +++++++++++++++++++++++++++ src/pulsecore/sink.h | 1 + 3 files changed, 33 insertions(+) diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c index 0f34e650..6e1b81f4 100644 --- a/src/pulsecore/sink-input.c +++ b/src/pulsecore/sink-input.c @@ -1552,6 +1552,11 @@ int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, pa_bool_t save) { if (!pa_sink_input_may_move_to(i, dest)) return -PA_ERR_NOTSUPPORTED; + if (!pa_format_info_is_pcm(i->format) && !pa_sink_check_format(dest, i->format)) { + /* FIXME: Fire a message here so the client can renegotiate */ + return -PA_ERR_NOTSUPPORTED; + } + if (i->thread_info.resampler && pa_sample_spec_equal(pa_resampler_output_sample_spec(i->thread_info.resampler), &dest->sample_spec) && pa_channel_map_equal(pa_resampler_output_channel_map(i->thread_info.resampler), &dest->channel_map)) diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c index 2b9402ab..626c7278 100644 --- a/src/pulsecore/sink.c +++ b/src/pulsecore/sink.c @@ -3291,6 +3291,33 @@ pa_idxset* pa_sink_get_formats(pa_sink *s) { return ret; } +/* Called from the main thread */ +/* Checks if the sink can accept this format */ +pa_bool_t pa_sink_check_format(pa_sink *s, pa_format_info *f) +{ + pa_idxset *sink_formats = NULL; + pa_format_info *f_sink; + uint32_t i; + pa_bool_t ret = FALSE; + + pa_assert(s); + pa_assert(f); + + sink_formats = pa_sink_get_formats(s); + + PA_IDXSET_FOREACH(f_sink, sink_formats, i) { + if (pa_format_info_is_compatible(f_sink, f)) { + ret = TRUE; + break; + } + } + + if (sink_formats) + pa_idxset_free(sink_formats, (pa_free2_cb_t) pa_format_info_free2, NULL); + + return ret; +} + /* Called from the main thread */ /* Calculates the intersection between formats supported by the sink and * in_formats, and returns these, in the order of the sink's formats. */ diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h index 492abf68..cbff5cae 100644 --- a/src/pulsecore/sink.h +++ b/src/pulsecore/sink.h @@ -407,6 +407,7 @@ void pa_sink_move_all_finish(pa_sink *s, pa_queue *q, pa_bool_t save); void pa_sink_move_all_fail(pa_queue *q); pa_idxset* pa_sink_get_formats(pa_sink *s); +pa_bool_t pa_sink_check_format(pa_sink *s, pa_format_info *f); pa_idxset* pa_sink_check_formats(pa_sink *s, pa_idxset *in_formats); /*** To be called exclusively by the sink driver, from IO context */ -- cgit From e11770b64ffbe0a5e5d172244c175fa0bc4034db Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Wed, 2 Mar 2011 12:54:02 +0530 Subject: core: Fix some FIXMEs for the extended API This adds some checks that I'd postponed and adds a "should-be-good-enough" guess for tlength when using a compressed format. --- src/pulse/stream.c | 23 +++++++++++++++++------ src/pulsecore/protocol-native.c | 13 +++++++++---- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/pulse/stream.c b/src/pulse/stream.c index f5bf42c9..10e431cd 100644 --- a/src/pulse/stream.c +++ b/src/pulse/stream.c @@ -151,8 +151,16 @@ static pa_stream *pa_stream_new_with_proplist_internal( s->buffer_attr.maxlength = (uint32_t) -1; 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? */ + else { + /* FIXME: We assume a worst-case compressed format corresponding to + * 48000 Hz, 2 ch, S16 PCM, but this can very well be incorrect */ + pa_sample_spec tmp_ss = { + .format = PA_SAMPLE_S16NE, + .rate = 48000, + .channels = 2, + }; + s->buffer_attr.tlength = (uint32_t) pa_usec_to_bytes(250*PA_USEC_PER_MSEC, &tmp_ss); /* 250ms of buffering */ + } s->buffer_attr.minreq = (uint32_t) -1; s->buffer_attr.prebuf = (uint32_t) -1; s->buffer_attr.fragsize = (uint32_t) -1; @@ -224,8 +232,6 @@ pa_stream *pa_stream_new_extended( 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); } @@ -1029,7 +1035,6 @@ void pa_create_stream_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, (!(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; } @@ -1062,8 +1067,14 @@ void pa_create_stream_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, if (pa_format_info_valid(f)) s->format = f; - else + else { pa_format_info_free(f); + if (s->n_formats > 0) { + /* We used the extended API, so we should have got back a proper format */ + pa_context_fail(s->context, PA_ERR_PROTOCOL); + goto finish; + } + } } if (!pa_tagstruct_eof(t)) { diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c index ee2bc4f5..956670be 100644 --- a/src/pulsecore/protocol-native.c +++ b/src/pulsecore/protocol-native.c @@ -2008,10 +2008,15 @@ static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, u } } - 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 (n_formats == 0) { + CHECK_VALIDITY(c->pstream, pa_sample_spec_valid(&ss), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, map.channels == ss.channels && volume.channels == ss.channels, tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, pa_channel_map_valid(&map), tag, PA_ERR_INVALID); + } else { + PA_IDXSET_FOREACH(format, formats, i) { + CHECK_VALIDITY(c->pstream, pa_format_info_valid(format), tag, PA_ERR_INVALID); + } + } if (!pa_tagstruct_eof(t)) { protocol_error(c); -- cgit From 20f1fa17bebe086cb9cff09a5157dc7ca009ba61 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Thu, 3 Mar 2011 12:52:21 +0530 Subject: alsa-mixer: Remove passthrough profiles These aren't used any more - we handle passthrough mode in the iec958* profiles now. --- src/Makefile.am | 3 +-- .../alsa/mixer/paths/iec958-passthrough-output.conf | 19 ------------------- src/modules/alsa/mixer/profile-sets/default.conf | 7 ------- 3 files changed, 1 insertion(+), 28 deletions(-) delete mode 100644 src/modules/alsa/mixer/paths/iec958-passthrough-output.conf diff --git a/src/Makefile.am b/src/Makefile.am index e3217043..a68cdc26 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1166,8 +1166,7 @@ dist_alsapaths_DATA = \ modules/alsa/mixer/paths/analog-output-headphones-2.conf \ modules/alsa/mixer/paths/analog-output-lfe-on-mono.conf \ modules/alsa/mixer/paths/analog-output-mono.conf \ - modules/alsa/mixer/paths/iec958-stereo-output.conf \ - modules/alsa/mixer/paths/iec958-passthrough-output.conf + modules/alsa/mixer/paths/iec958-stereo-output.conf endif diff --git a/src/modules/alsa/mixer/paths/iec958-passthrough-output.conf b/src/modules/alsa/mixer/paths/iec958-passthrough-output.conf deleted file mode 100644 index 8506a580..00000000 --- a/src/modules/alsa/mixer/paths/iec958-passthrough-output.conf +++ /dev/null @@ -1,19 +0,0 @@ -# This file is part of PulseAudio. -# -# PulseAudio is free software; you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation; either version 2.1 of the -# License, or (at your option) any later version. -# -# PulseAudio is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with PulseAudio; if not, write to the Free Software Foundation, -# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. - - -[Element IEC958] -switch = mute diff --git a/src/modules/alsa/mixer/profile-sets/default.conf b/src/modules/alsa/mixer/profile-sets/default.conf index 9f7b5f2b..283edfb3 100644 --- a/src/modules/alsa/mixer/profile-sets/default.conf +++ b/src/modules/alsa/mixer/profile-sets/default.conf @@ -155,13 +155,6 @@ paths-input = iec958-stereo-input paths-output = iec958-stereo-output priority = 5 -[Mapping iec958-passthrough] -device-strings = iec958:%f -channel-map = left,right -direction = output -paths-output = iec958-passthrough-output -priority = 5 - [Mapping iec958-ac3-surround-40] device-strings = a52:%f channel-map = front-left,front-right,rear-left,rear-right -- cgit From 4c9d53f3f50162e19ad7934e62c8f35453bdbfbc Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Thu, 3 Mar 2011 18:35:14 +0530 Subject: sink: Trivial typo fix in comment --- src/pulsecore/sink.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c index 626c7278..5959a5ce 100644 --- a/src/pulsecore/sink.c +++ b/src/pulsecore/sink.c @@ -2153,7 +2153,7 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse /* If you change anything here, make sure to change the * sink input handling a few lines down at - * PA_SINK_MESSAGE_PREPAPRE_MOVE, too. */ + * PA_SINK_MESSAGE_START_MOVE, too. */ if (i->detach) i->detach(i); -- cgit From f94bcae6bd6a3021a9474de5703360817f1ec1a8 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Thu, 3 Mar 2011 19:02:45 +0530 Subject: core: Suspend monitor when a sink enters passthrough mode In most cases it is expected that clients cannot consume compressed data from monitor sources, so we suspend the monitor source when the sink goes into passthrough mode. Eventually, when the extended API includes client notifications for changed formats, we should emit a notification on the monitor so that clients can decide what they want to do when this happens (disconnect or consume the data anyway). --- src/pulsecore/core.h | 1 + src/pulsecore/sink-input.c | 16 ++++++++++++++++ src/pulsecore/source.c | 2 +- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h index 358b98d7..6b25fbad 100644 --- a/src/pulsecore/core.h +++ b/src/pulsecore/core.h @@ -35,6 +35,7 @@ typedef enum pa_suspend_cause { PA_SUSPEND_APPLICATION = 2, /* Used by the device reservation logic */ PA_SUSPEND_IDLE = 4, /* Used by module-suspend-on-idle */ PA_SUSPEND_SESSION = 8, /* Used by module-hal for mark inactive sessions */ + PA_SUSPEND_PASSTHROUGH = 16, /* Used to suspend monitor sources when the sink is in passthrough mode */ PA_SUSPEND_ALL = 0xFFFF /* Magic cause that can be used to resume forcibly */ } pa_suspend_cause_t; diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c index 6e1b81f4..d77eb2ca 100644 --- a/src/pulsecore/sink-input.c +++ b/src/pulsecore/sink-input.c @@ -599,6 +599,10 @@ void pa_sink_input_unlink(pa_sink_input *i) { if (i->sink->asyncmsgq) pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_REMOVE_INPUT, i, 0, NULL) == 0); + + /* We suspend the monitor if there was a passthrough sink, unsuspend now if required */ + if (!pa_format_info_is_pcm(i->format) && i->sink->monitor_source) + pa_source_suspend(i->sink->monitor_source, FALSE, PA_SUSPEND_PASSTHROUGH); } reset_callbacks(i); @@ -689,6 +693,10 @@ void pa_sink_input_put(pa_sink_input *i) { set_real_ratio(i, &i->volume); } + /* If we're entering passthrough mode, disable the monitor */ + if (!pa_format_info_is_pcm(i->format) && i->sink->monitor_source) + pa_source_suspend(i->sink->monitor_source, TRUE, PA_SUSPEND_PASSTHROUGH); + i->thread_info.soft_volume = i->soft_volume; i->thread_info.muted = i->muted; @@ -1380,6 +1388,10 @@ int pa_sink_input_start_move(pa_sink_input *i) { pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_START_MOVE, i, 0, NULL) == 0); + /* We suspend the monitor if there was a passthrough sink, unsuspend now if required */ + if (!pa_format_info_is_pcm(i->format) && i->sink->monitor_source) + pa_source_suspend(i->sink->monitor_source, FALSE, PA_SUSPEND_PASSTHROUGH); + pa_sink_update_status(i->sink); pa_cvolume_remap(&i->volume_factor_sink, &i->sink->channel_map, &i->channel_map); i->sink = NULL; @@ -1621,6 +1633,10 @@ int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, pa_bool_t save) { pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_FINISH_MOVE, i, 0, NULL) == 0); + /* If we're entering passthrough mode, disable the monitor */ + if (!pa_format_info_is_pcm(i->format) && i->sink->monitor_source) + pa_source_suspend(i->sink->monitor_source, TRUE, PA_SUSPEND_PASSTHROUGH); + pa_log_debug("Successfully moved sink input %i to %s.", i->index, dest->name); /* Notify everyone */ diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c index 92fb80e0..15a5b8d9 100644 --- a/src/pulsecore/source.c +++ b/src/pulsecore/source.c @@ -514,7 +514,7 @@ int pa_source_suspend(pa_source *s, pa_bool_t suspend, pa_suspend_cause_t cause) pa_assert(PA_SOURCE_IS_LINKED(s->state)); pa_assert(cause != 0); - if (s->monitor_of) + if (s->monitor_of && cause != PA_SUSPEND_PASSTHROUGH) return -PA_ERR_NOTSUPPORTED; if (suspend) -- cgit From 49b10ba6941ea61e551ccee482b12bb78db8a4b3 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Tue, 8 Mar 2011 14:22:24 +0530 Subject: alsa: Reconfigure sink sample rate for passthrough inputs When a passthrough sink-input is added, we need to reconfigure the sink's sample rate since no resampling occurs. We revert to the original rate when the passthrough sink-input is removed. --- src/modules/alsa/alsa-sink.c | 52 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c index ec840afd..3f8f6d20 100644 --- a/src/modules/alsa/alsa-sink.c +++ b/src/modules/alsa/alsa-sink.c @@ -108,6 +108,8 @@ struct userdata { pa_cvolume hardware_volume; + uint32_t old_rate; + size_t frame_size, fragment_size, @@ -1051,6 +1053,56 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse switch (code) { + case PA_SINK_MESSAGE_FINISH_MOVE: + case PA_SINK_MESSAGE_ADD_INPUT: { + pa_sink_input *i = PA_SINK_INPUT(data); + int r = 0; + + if (PA_LIKELY(pa_format_info_is_pcm(i->format))) + break; + + u->old_rate = u->sink->sample_spec.rate; + + /* Passthrough format, see if we need to reset sink sample rate */ + if (u->sink->sample_spec.rate == i->thread_info.sample_spec.rate) + break; + + /* .. we do */ + if ((r = suspend(u)) < 0) + return r; + + u->sink->sample_spec.rate = i->thread_info.sample_spec.rate; + + if ((r = unsuspend(u)) < 0) + return r; + + break; + } + + case PA_SINK_MESSAGE_START_MOVE: + case PA_SINK_MESSAGE_REMOVE_INPUT: { + pa_sink_input *i = PA_SINK_INPUT(data); + int r = 0; + + if (PA_LIKELY(pa_format_info_is_pcm(i->format))) + break; + + /* Passthrough format, see if we need to reset sink sample rate */ + if (u->sink->sample_spec.rate == u->old_rate) + break; + + /* .. we do */ + if ((r = suspend(u)) < 0) + return r; + + u->sink->sample_spec.rate = u->old_rate; + + if ((r = unsuspend(u)) < 0) + return r; + + break; + } + case PA_SINK_MESSAGE_GET_LATENCY: { pa_usec_t r = 0; -- cgit From a3a004214404c6f91a82c1e2164444e5e08c26cf Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Tue, 8 Mar 2011 20:14:46 +0530 Subject: format: Const-ify some parameters --- src/pulse/format.c | 4 ++-- src/pulse/format.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pulse/format.c b/src/pulse/format.c index 9bb76732..7ddfa7a1 100644 --- a/src/pulse/format.c +++ b/src/pulse/format.c @@ -70,11 +70,11 @@ void pa_format_info_free2(pa_format_info *f, void *userdata) { pa_format_info_free(f); } -int pa_format_info_valid(pa_format_info *f) { +int pa_format_info_valid(const pa_format_info *f) { return (f->encoding >= 0 && f->encoding < PA_ENCODING_MAX && f->plist != NULL); } -int pa_format_info_is_pcm(pa_format_info *f) { +int pa_format_info_is_pcm(const pa_format_info *f) { return f->encoding == PA_ENCODING_PCM; } diff --git a/src/pulse/format.h b/src/pulse/format.h index 7e3ab08d..03b34059 100644 --- a/src/pulse/format.h +++ b/src/pulse/format.h @@ -72,10 +72,10 @@ pa_format_info* pa_format_info_copy(const pa_format_info *src); void pa_format_info_free(pa_format_info *f); /** Returns non-zero when the format info structure is valid */ -int pa_format_info_valid(pa_format_info *f); +int pa_format_info_valid(const pa_format_info *f); /** Returns non-zero when the format info structure represents a PCM (i.e. uncompressed data) format */ -int pa_format_info_is_pcm(pa_format_info *f); +int pa_format_info_is_pcm(const pa_format_info *f); PA_C_DECL_END -- cgit From 8631f4e2c44b47db76795bebdbab54914a1f3ea0 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Tue, 8 Mar 2011 20:15:36 +0530 Subject: format: Add some convenience functions for printing --- src/map-file | 2 ++ src/pulse/format.c | 36 ++++++++++++++++++++++++++++++++++++ src/pulse/format.h | 13 +++++++++++++ 3 files changed, 51 insertions(+) diff --git a/src/map-file b/src/map-file index 3b1de105..5283a5e3 100644 --- a/src/map-file +++ b/src/map-file @@ -146,6 +146,7 @@ pa_cvolume_set_fade; pa_cvolume_set_position; pa_cvolume_snprint; pa_cvolume_valid; +pa_encoding_to_string; pa_ext_device_manager_delete; pa_ext_device_manager_enable_role_device_priority_routing; pa_ext_device_manager_read; @@ -164,6 +165,7 @@ pa_format_info_copy; pa_format_info_free; pa_format_info_is_pcm; pa_format_info_new; +pa_format_info_snprint; pa_format_info_valid; pa_frame_size; pa_get_binary_name; diff --git a/src/pulse/format.c b/src/pulse/format.c index 7ddfa7a1..0c5c24b1 100644 --- a/src/pulse/format.c +++ b/src/pulse/format.c @@ -27,12 +27,28 @@ #include #include +#include #include #include #include "format.h" +const char *pa_encoding_to_string(pa_encoding_t e) { + static const char* const table[]= { + [PA_ENCODING_PCM] = "pcm", + [PA_ENCODING_AC3_IEC61937] = "ac3-iec61937", + [PA_ENCODING_EAC3_IEC61937] = "eac3-iec61937", + [PA_ENCODING_MPEG_IEC61937] = "mpeg-iec61937", + [PA_ENCODING_ANY] = "any", + }; + + if (e < 0 || e >= PA_ENCODING_MAX) + return NULL; + + return table[e]; +} + pa_format_info* pa_format_info_new(void) { pa_format_info *f = pa_xnew(pa_format_info, 1); @@ -78,6 +94,26 @@ int pa_format_info_is_pcm(const pa_format_info *f) { return f->encoding == PA_ENCODING_PCM; } +char *pa_format_info_snprint(char *s, size_t l, const pa_format_info *f) { + char *tmp; + + pa_assert(s); + pa_assert(l > 0); + pa_assert(f); + + pa_init_i18n(); + + if (!pa_format_info_valid(f)) + pa_snprintf(s, l, _("(invalid)")); + else { + tmp = pa_proplist_to_string_sep(f->plist, ", "); + pa_snprintf(s, l, _("%s, %s"), pa_encoding_to_string(f->encoding), tmp[0] ? tmp : _("(no properties)")); + pa_xfree(tmp); + } + + return s; +} + pa_bool_t pa_format_info_is_compatible(pa_format_info *first, pa_format_info *second) { const char *key; void *state = NULL; diff --git a/src/pulse/format.h b/src/pulse/format.h index 03b34059..51c9426c 100644 --- a/src/pulse/format.h +++ b/src/pulse/format.h @@ -53,6 +53,9 @@ typedef enum pa_encoding { /**< Represents an invalid encoding */ } pa_encoding_t; +/** Returns a printable string representing the given encoding type. \since 1.0 */ +const char *pa_encoding_to_string(pa_encoding_t e) PA_GCC_CONST; + /**< Represents the format of data provided in a stream or processed by a sink. \since 1.0 */ typedef struct pa_format_info { pa_encoding_t encoding; @@ -77,6 +80,16 @@ int pa_format_info_valid(const pa_format_info *f); /** Returns non-zero when the format info structure represents a PCM (i.e. uncompressed data) format */ int pa_format_info_is_pcm(const pa_format_info *f); +/** Maximum required string length for + * pa_format_info_snprint(). Please note that this value can change + * with any release without warning and without being considered API + * or ABI breakage. You should not use this definition anywhere where + * it might become part of an ABI. \since 1.0 */ +#define PA_FORMAT_INFO_SNPRINT_MAX 256 + +/** Return a human-readable string representing the given format. \since 1.0 */ +char *pa_format_info_snprint(char *s, size_t l, const pa_format_info *f); + PA_C_DECL_END #endif -- cgit From dedbc942ab2090320856c36d6673db12026efdc1 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Tue, 8 Mar 2011 20:16:00 +0530 Subject: stream: Add API to get a stream's pa_format_info --- src/map-file | 1 + src/pulse/stream.c | 10 ++++++++++ src/pulse/stream.h | 3 +++ 3 files changed, 14 insertions(+) diff --git a/src/map-file b/src/map-file index 5283a5e3..1acc6e98 100644 --- a/src/map-file +++ b/src/map-file @@ -258,6 +258,7 @@ pa_stream_get_channel_map; pa_stream_get_context; pa_stream_get_device_index; pa_stream_get_device_name; +pa_stream_get_format_info; pa_stream_get_index; pa_stream_get_latency; pa_stream_get_monitor_stream; diff --git a/src/pulse/stream.c b/src/pulse/stream.c index 10e431cd..bd2e6ca1 100644 --- a/src/pulse/stream.c +++ b/src/pulse/stream.c @@ -2470,6 +2470,16 @@ const pa_channel_map* pa_stream_get_channel_map(pa_stream *s) { return &s->channel_map; } +const pa_format_info* pa_stream_get_format_info(pa_stream *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + /* We don't have the format till routing is done */ + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, !pa_detect_fork(), PA_ERR_FORKED); + + return s->format; +} const pa_buffer_attr* pa_stream_get_buffer_attr(pa_stream *s) { pa_assert(s); pa_assert(PA_REFCNT_VALUE(s) >= 1); diff --git a/src/pulse/stream.h b/src/pulse/stream.h index 48bf09df..b265fae6 100644 --- a/src/pulse/stream.h +++ b/src/pulse/stream.h @@ -709,6 +709,9 @@ const pa_sample_spec* pa_stream_get_sample_spec(pa_stream *s); /** Return a pointer to the stream's channel map. */ const pa_channel_map* pa_stream_get_channel_map(pa_stream *s); +/** Return a pointer to the stream's format \since 1.0 */ +const pa_format_info* pa_stream_get_format_info(pa_stream *s); + /** Return the per-stream server-side buffer metrics of the * stream. Only valid after the stream has been connected successfuly * and if the server is at least PulseAudio 0.9. This will return the -- cgit From 322980e2e3844abf837dcc8cc5317406b3d8cb94 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Tue, 8 Mar 2011 23:30:24 +0530 Subject: introspect: Get formats for sinks This gets the list of supported formats for a sink in pa_context_get_sink_info*(). Also prints these in 'pactl list'. --- PROTOCOL | 8 ++++++++ src/pulse/introspect.c | 34 ++++++++++++++++++++++++++++++++++ src/pulse/introspect.h | 3 +++ src/pulsecore/protocol-native.c | 13 +++++++++++++ src/utils/pactl.c | 11 ++++++++++- 5 files changed, 68 insertions(+), 1 deletion(-) diff --git a/PROTOCOL b/PROTOCOL index d06cb988..419f9936 100644 --- a/PROTOCOL +++ b/PROTOCOL @@ -228,3 +228,11 @@ New fields PA_COMMAND_CREATE_PLAYBACK_STREAM: One new field in reply from PA_COMMAND_CREATE_PLAYBACK_STREAM: format_info format + +New fields in reply from PA_COMMAND_GET_SINK_INFO (and thus +PA_COMMAND_GET_SINK_INFO_LIST) + + uint8_t n_formats + format_info format1 + ... + format_info formatn diff --git a/src/pulse/introspect.c b/src/pulse/introspect.c index c93fb062..28a6bf4a 100644 --- a/src/pulse/introspect.c +++ b/src/pulse/introspect.c @@ -240,6 +240,33 @@ static void context_get_sink_info_callback(pa_pdispatch *pd, uint32_t command, u } } + if (o->context->version >= 21) { + i.formats = NULL; + + if (pa_tagstruct_getu8(t, &i.n_formats)) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + pa_proplist_free(i.proplist); + goto finish; + } + + pa_assert(i.n_formats > 0); + i.formats = pa_xnew0(pa_format_info*, i.n_formats); + + for (j = 0; j < i.n_formats; j++) { + i.formats[j] = pa_format_info_new(); + if (pa_tagstruct_get_format_info(t, i.formats[j]) < 0) { + do { + pa_format_info_free(i.formats[j]); + } while (j--); + pa_xfree(i.formats); + + pa_context_fail(o->context, PA_ERR_PROTOCOL); + pa_proplist_free(i.proplist); + goto finish; + } + } + } + i.mute = (int) mute; i.flags = (pa_sink_flags_t) flags; i.state = (pa_sink_state_t) state; @@ -253,6 +280,13 @@ static void context_get_sink_info_callback(pa_pdispatch *pd, uint32_t command, u pa_xfree(i.ports[0]); pa_xfree(i.ports); } + + if (i.formats) { + for (j = 0; j < i.n_formats; j++) + pa_format_info_free(i.formats[j]); + pa_xfree(i.formats); + } + pa_proplist_free(i.proplist); } } diff --git a/src/pulse/introspect.h b/src/pulse/introspect.h index 297b4bac..49c1de63 100644 --- a/src/pulse/introspect.h +++ b/src/pulse/introspect.h @@ -32,6 +32,7 @@ #include #include #include +#include #include /** \page introspect Server Query and Control @@ -230,6 +231,8 @@ typedef struct pa_sink_info { uint32_t n_ports; /**< Number of entries in port array \since 0.9.16 */ pa_sink_port_info** ports; /**< Array of available ports, or NULL. Array is terminated by an entry set to NULL. The number of entries is stored in n_ports \since 0.9.16 */ pa_sink_port_info* active_port; /**< Pointer to active port in the array, or NULL \since 0.9.16 */ + uint8_t n_formats; /**< Number of formats supported by the sink. \since 1.0 */ + pa_format_info **formats; /**< Array of formats supported by the sink. \since 1.0 */ } pa_sink_info; /** Callback prototype for pa_context_get_sink_info_by_name() and friends */ diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c index 956670be..f151bd21 100644 --- a/src/pulsecore/protocol-native.c +++ b/src/pulsecore/protocol-native.c @@ -2984,6 +2984,19 @@ static void sink_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_sin pa_tagstruct_puts(t, sink->active_port ? sink->active_port->name : NULL); } + + if (c->version >= 21) { + uint32_t i; + pa_format_info *f; + pa_idxset *formats = pa_sink_get_formats(sink); + + pa_tagstruct_putu8(t, (uint8_t) pa_idxset_size(formats)); + PA_IDXSET_FOREACH(f, formats, i) { + pa_tagstruct_put_format_info(t, f); + } + + pa_idxset_free(formats, (pa_free2_cb_t) pa_format_info_free2, NULL); + } } static void source_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_source *source) { diff --git a/src/utils/pactl.c b/src/utils/pactl.c index 194183d4..109d31b9 100644 --- a/src/utils/pactl.c +++ b/src/utils/pactl.c @@ -222,7 +222,8 @@ static void get_sink_info_callback(pa_context *c, const pa_sink_info *i, int is_ cvdb[PA_SW_CVOLUME_SNPRINT_DB_MAX], v[PA_VOLUME_SNPRINT_MAX], vdb[PA_SW_VOLUME_SNPRINT_DB_MAX], - cm[PA_CHANNEL_MAP_SNPRINT_MAX]; + cm[PA_CHANNEL_MAP_SNPRINT_MAX], + f[PA_FORMAT_INFO_SNPRINT_MAX]; char *pl; if (is_last < 0) { @@ -307,6 +308,14 @@ static void get_sink_info_callback(pa_context *c, const pa_sink_info *i, int is_ if (i->active_port) printf(_("\tActive Port: %s\n"), i->active_port->name); + + if (i->formats) { + uint8_t j; + + printf(_("\tFormats:\n")); + for (j = 0; j < i->n_formats; j++) + printf("\t\t%s\n", pa_format_info_snprint(f, sizeof(f), i->formats[j])); + } } static void get_source_info_callback(pa_context *c, const pa_source_info *i, int is_last, void *userdata) { -- cgit From 7aa84e82089a88a542f15cbf6f38c808b4f04db1 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Tue, 8 Mar 2011 23:31:59 +0530 Subject: introspect: Get format of sink input This gets the negotiated format of sink inputs in pa_context_get_sink_input*(). Also prints the format in 'pactl list'. --- PROTOCOL | 5 +++++ src/pulse/introspect.c | 6 +++++- src/pulse/introspect.h | 1 + src/pulsecore/protocol-native.c | 2 ++ src/utils/pactl.c | 4 +++- 5 files changed, 16 insertions(+), 2 deletions(-) diff --git a/PROTOCOL b/PROTOCOL index 419f9936..4178274f 100644 --- a/PROTOCOL +++ b/PROTOCOL @@ -236,3 +236,8 @@ PA_COMMAND_GET_SINK_INFO_LIST) format_info format1 ... format_info formatn + +One new field in reply from PA_COMMAND_GET_SINK_INPUT_INFO (and thus +PA_COMMAND_GET_SINK_INPUT_INFO_LIST) + + format_info format diff --git a/src/pulse/introspect.c b/src/pulse/introspect.c index 28a6bf4a..e28a78c4 100644 --- a/src/pulse/introspect.c +++ b/src/pulse/introspect.c @@ -1033,6 +1033,7 @@ static void context_get_sink_input_info_callback(pa_pdispatch *pd, uint32_t comm pa_zero(i); i.proplist = pa_proplist_new(); + i.format = pa_format_info_new(); if (pa_tagstruct_getu32(t, &i.index) < 0 || pa_tagstruct_gets(t, &i.name) < 0 || @@ -1050,10 +1051,12 @@ static void context_get_sink_input_info_callback(pa_pdispatch *pd, uint32_t comm (o->context->version >= 13 && pa_tagstruct_get_proplist(t, i.proplist) < 0) || (o->context->version >= 19 && pa_tagstruct_get_boolean(t, &corked) < 0) || (o->context->version >= 20 && (pa_tagstruct_get_boolean(t, &has_volume) < 0 || - pa_tagstruct_get_boolean(t, &volume_writable) < 0))) { + pa_tagstruct_get_boolean(t, &volume_writable) < 0)) || + (o->context->version >= 21 && pa_tagstruct_get_format_info(t, i.format) < 0)) { pa_context_fail(o->context, PA_ERR_PROTOCOL); pa_proplist_free(i.proplist); + pa_format_info_free(i.format); goto finish; } @@ -1068,6 +1071,7 @@ static void context_get_sink_input_info_callback(pa_pdispatch *pd, uint32_t comm } pa_proplist_free(i.proplist); + pa_format_info_free(i.format); } } diff --git a/src/pulse/introspect.h b/src/pulse/introspect.h index 49c1de63..a6b4a801 100644 --- a/src/pulse/introspect.h +++ b/src/pulse/introspect.h @@ -508,6 +508,7 @@ typedef struct pa_sink_input_info { int corked; /**< Stream corked \since 1.0 */ int has_volume; /**< Stream has volume. If not set, then the meaning of this struct's volume member is unspecified. \since 1.0 */ int volume_writable; /**< The volume can be set. If not set, the volume can still change even though clients can't control the volume. \since 1.0 */ + pa_format_info *format; /**< Stream format information. \since 1.0 */ } pa_sink_input_info; /** Callback prototype for pa_context_get_sink_input_info() and friends*/ diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c index f151bd21..59b87242 100644 --- a/src/pulsecore/protocol-native.c +++ b/src/pulsecore/protocol-native.c @@ -3153,6 +3153,8 @@ static void sink_input_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_tagstruct_put_boolean(t, has_volume); pa_tagstruct_put_boolean(t, s->volume_writable); } + if (c->version >= 21) + pa_tagstruct_put_format_info(t, s->format); } static void source_output_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_source_output *s) { diff --git a/src/utils/pactl.c b/src/utils/pactl.c index 109d31b9..7be7049f 100644 --- a/src/utils/pactl.c +++ b/src/utils/pactl.c @@ -560,7 +560,7 @@ static void get_card_info_callback(pa_context *c, const pa_card_info *i, int is_ } static void get_sink_input_info_callback(pa_context *c, const pa_sink_input_info *i, int is_last, void *userdata) { - char t[32], k[32], s[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX], cvdb[PA_SW_CVOLUME_SNPRINT_DB_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX]; + char t[32], k[32], s[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX], cvdb[PA_SW_CVOLUME_SNPRINT_DB_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX], f[PA_FORMAT_INFO_SNPRINT_MAX]; char *pl; if (is_last < 0) { @@ -600,6 +600,7 @@ static void get_sink_input_info_callback(pa_context *c, const pa_sink_input_info "\tSink: %u\n" "\tSample Specification: %s\n" "\tChannel Map: %s\n" + "\tFormat: %s\n" "\tMute: %s\n" "\tVolume: %s\n" "\t %s\n" @@ -615,6 +616,7 @@ static void get_sink_input_info_callback(pa_context *c, const pa_sink_input_info i->sink, pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec), pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map), + pa_format_info_snprint(f, sizeof(f), i->format), pa_yes_no(i->mute), pa_cvolume_snprint(cv, sizeof(cv), &i->volume), pa_sw_cvolume_snprint_dB(cvdb, sizeof(cvdb), &i->volume), -- cgit From 9a39a3df10c3874bf0b5b091f721a6d8ab8750a5 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Wed, 9 Mar 2011 20:50:25 +0530 Subject: format: Add a type for DTS --- src/pulse/format.c | 1 + src/pulse/format.h | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/pulse/format.c b/src/pulse/format.c index 0c5c24b1..e601d902 100644 --- a/src/pulse/format.c +++ b/src/pulse/format.c @@ -40,6 +40,7 @@ const char *pa_encoding_to_string(pa_encoding_t e) { [PA_ENCODING_AC3_IEC61937] = "ac3-iec61937", [PA_ENCODING_EAC3_IEC61937] = "eac3-iec61937", [PA_ENCODING_MPEG_IEC61937] = "mpeg-iec61937", + [PA_ENCODING_DTS_IEC61937] = "dts-iec61937", [PA_ENCODING_ANY] = "any", }; diff --git a/src/pulse/format.h b/src/pulse/format.h index 51c9426c..bd32ba92 100644 --- a/src/pulse/format.h +++ b/src/pulse/format.h @@ -46,6 +46,9 @@ typedef enum pa_encoding { PA_ENCODING_MPEG_IEC61937, /**< MPEG-1 or MPEG-2 (Part 3, not AAC) data encapsulated in IEC 61937 header/padding */ + PA_ENCODING_DTS_IEC61937, + /**< DTS data encapsulated in IEC 61937 header/padding */ + PA_ENCODING_MAX, /**< Valid encoding types must be less than this value */ -- cgit From 4fb68b91acef3cb37c014814d9e9de8ca9f22bf4 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Wed, 16 Mar 2011 16:08:23 +0530 Subject: core: Factor out passthrough checks into their own functions Since we currently have two mechanisms to signal a passthrough connection (non-PCM format or PA_SINK_INPUT_PASSTHROUGH flag), we move all the related checks into functions and use those everywhere. This makes things more consistent, and should we decide to get rid of the flag, we only need to change pa_sink_input_*_is_passthrough() accordingly. --- src/modules/alsa/alsa-sink.c | 4 ++-- src/pulsecore/sink-input.c | 50 +++++++++++++++++++++++++++++++------------- src/pulsecore/sink-input.h | 2 ++ src/pulsecore/sink.c | 2 +- 4 files changed, 41 insertions(+), 17 deletions(-) diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c index 3f8f6d20..b98340b7 100644 --- a/src/modules/alsa/alsa-sink.c +++ b/src/modules/alsa/alsa-sink.c @@ -1058,7 +1058,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse pa_sink_input *i = PA_SINK_INPUT(data); int r = 0; - if (PA_LIKELY(pa_format_info_is_pcm(i->format))) + if (PA_LIKELY(!pa_sink_input_is_passthrough(i))) break; u->old_rate = u->sink->sample_spec.rate; @@ -1084,7 +1084,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse pa_sink_input *i = PA_SINK_INPUT(data); int r = 0; - if (PA_LIKELY(pa_format_info_is_pcm(i->format))) + if (PA_LIKELY(!pa_sink_input_is_passthrough(i))) break; /* Passthrough format, see if we need to reset sink sample rate */ diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c index d77eb2ca..5e7cfd13 100644 --- a/src/pulsecore/sink-input.c +++ b/src/pulsecore/sink-input.c @@ -50,15 +50,14 @@ PA_DEFINE_PUBLIC_CLASS(pa_sink_input, pa_msgobject); static void sink_input_free(pa_object *o); static void set_real_ratio(pa_sink_input *i, const pa_cvolume *v); -static int check_passthrough_connection(pa_format_info *format, pa_sink *dest) { - +static int check_passthrough_connection(pa_bool_t passthrough, pa_sink *dest) { if (pa_sink_is_passthrough(dest)) { pa_log_warn("Sink is already connected to PASSTHROUGH input"); return -PA_ERR_BUSY; } /* If current input(s) exist, check new input is not PASSTHROUGH */ - if (pa_idxset_size(dest->inputs) > 0 && !pa_format_info_is_pcm(format)) { + if (pa_idxset_size(dest->inputs) > 0 && passthrough) { pa_log_warn("Sink is already connected, cannot accept new PASSTHROUGH INPUT"); return -PA_ERR_BUSY; } @@ -91,6 +90,18 @@ void pa_sink_input_new_data_set_channel_map(pa_sink_input_new_data *data, const data->channel_map = *map; } +pa_bool_t pa_sink_input_new_data_is_passthrough(pa_sink_input_new_data *data) { + pa_assert(data); + + if (PA_LIKELY(data->format) && PA_UNLIKELY(!pa_format_info_is_pcm(data->format))) + return TRUE; + + if (PA_UNLIKELY(data->flags & PA_SINK_INPUT_PASSTHROUGH)) + return TRUE; + + return FALSE; +} + void pa_sink_input_new_data_set_volume(pa_sink_input_new_data *data, const pa_cvolume *volume) { pa_assert(data); pa_assert(data->volume_writable); @@ -284,15 +295,13 @@ int pa_sink_input_new( } else { pa_return_val_if_fail(pa_format_info_to_sample_spec_fake(data->format, &ss), -PA_ERR_INVALID); 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); pa_return_val_if_fail(PA_SINK_IS_LINKED(pa_sink_get_state(data->sink)), -PA_ERR_BADSTATE); pa_return_val_if_fail(!data->sync_base || (data->sync_base->sink == data->sink && pa_sink_input_get_state(data->sync_base) == PA_SINK_INPUT_CORKED), -PA_ERR_INVALID); - r = check_passthrough_connection(data->format, data->sink); + r = check_passthrough_connection(pa_sink_input_new_data_is_passthrough(data), data->sink); pa_return_val_if_fail(r == PA_OK, r); if (!data->sample_spec_is_set) @@ -373,7 +382,7 @@ int pa_sink_input_new( !pa_channel_map_equal(&data->channel_map, &data->sink->channel_map)) { /* Note: for passthrough content we need to adjust the output rate to that of the current sink-input */ - if (!(data->flags & PA_SINK_INPUT_PASSTHROUGH)) /* no resampler for passthrough content */ + if (!pa_sink_input_new_data_is_passthrough(data)) /* no resampler for passthrough content */ if (!(resampler = pa_resampler_new( core->mempool, &data->sample_spec, &data->channel_map, @@ -601,7 +610,7 @@ void pa_sink_input_unlink(pa_sink_input *i) { pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_REMOVE_INPUT, i, 0, NULL) == 0); /* We suspend the monitor if there was a passthrough sink, unsuspend now if required */ - if (!pa_format_info_is_pcm(i->format) && i->sink->monitor_source) + if (pa_sink_input_is_passthrough(i) && i->sink->monitor_source) pa_source_suspend(i->sink->monitor_source, FALSE, PA_SUSPEND_PASSTHROUGH); } @@ -694,7 +703,7 @@ void pa_sink_input_put(pa_sink_input *i) { } /* If we're entering passthrough mode, disable the monitor */ - if (!pa_format_info_is_pcm(i->format) && i->sink->monitor_source) + if (pa_sink_input_is_passthrough(i) && i->sink->monitor_source) pa_source_suspend(i->sink->monitor_source, TRUE, PA_SUSPEND_PASSTHROUGH); i->thread_info.soft_volume = i->soft_volume; @@ -1178,12 +1187,25 @@ static void set_real_ratio(pa_sink_input *i, const pa_cvolume *v) { /* We don't copy the data to the thread_info data. That's left for someone else to do */ } +/* Called from main or I/O context */ +pa_bool_t pa_sink_input_is_passthrough(pa_sink_input *i) { + pa_sink_input_assert_ref(i); + + if (PA_UNLIKELY(!pa_format_info_is_pcm(i->format))) + return TRUE; + + if (PA_UNLIKELY(i->flags & PA_SINK_INPUT_PASSTHROUGH)) + return TRUE; + + return FALSE; +} + /* Called from main context */ pa_bool_t pa_sink_input_is_volume_readable(pa_sink_input *i) { pa_sink_input_assert_ref(i); pa_assert_ctl_context(); - return !(i->flags & PA_SINK_INPUT_PASSTHROUGH); + return !pa_sink_input_is_passthrough(i); } /* Called from main context */ @@ -1342,7 +1364,7 @@ pa_bool_t pa_sink_input_may_move_to(pa_sink_input *i, pa_sink *dest) { return FALSE; } - if (check_passthrough_connection(i->format, dest) < 0) + if (check_passthrough_connection(pa_sink_input_is_passthrough(i), dest) < 0) return FALSE; if (i->may_move_to) @@ -1389,7 +1411,7 @@ int pa_sink_input_start_move(pa_sink_input *i) { pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_START_MOVE, i, 0, NULL) == 0); /* We suspend the monitor if there was a passthrough sink, unsuspend now if required */ - if (!pa_format_info_is_pcm(i->format) && i->sink->monitor_source) + if (pa_sink_input_is_passthrough(i) && i->sink->monitor_source) pa_source_suspend(i->sink->monitor_source, FALSE, PA_SUSPEND_PASSTHROUGH); pa_sink_update_status(i->sink); @@ -1564,7 +1586,7 @@ int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, pa_bool_t save) { if (!pa_sink_input_may_move_to(i, dest)) return -PA_ERR_NOTSUPPORTED; - if (!pa_format_info_is_pcm(i->format) && !pa_sink_check_format(dest, i->format)) { + if (pa_sink_input_is_passthrough(i) && !pa_sink_check_format(dest, i->format)) { /* FIXME: Fire a message here so the client can renegotiate */ return -PA_ERR_NOTSUPPORTED; } @@ -1634,7 +1656,7 @@ int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, pa_bool_t save) { pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_FINISH_MOVE, i, 0, NULL) == 0); /* If we're entering passthrough mode, disable the monitor */ - if (!pa_format_info_is_pcm(i->format) && i->sink->monitor_source) + if (pa_sink_input_is_passthrough(i) && i->sink->monitor_source) pa_source_suspend(i->sink->monitor_source, TRUE, PA_SUSPEND_PASSTHROUGH); pa_log_debug("Successfully moved sink input %i to %s.", i->index, dest->name); diff --git a/src/pulsecore/sink-input.h b/src/pulsecore/sink-input.h index 72a1d5a2..0ebb74a9 100644 --- a/src/pulsecore/sink-input.h +++ b/src/pulsecore/sink-input.h @@ -304,6 +304,7 @@ typedef struct pa_sink_input_new_data { pa_sink_input_new_data* pa_sink_input_new_data_init(pa_sink_input_new_data *data); void pa_sink_input_new_data_set_sample_spec(pa_sink_input_new_data *data, const pa_sample_spec *spec); void pa_sink_input_new_data_set_channel_map(pa_sink_input_new_data *data, const pa_channel_map *map); +pa_bool_t pa_sink_input_new_data_is_passthrough(pa_sink_input_new_data *data); void pa_sink_input_new_data_set_volume(pa_sink_input_new_data *data, const pa_cvolume *volume); 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); @@ -350,6 +351,7 @@ void pa_sink_input_kill(pa_sink_input*i); pa_usec_t pa_sink_input_get_latency(pa_sink_input *i, pa_usec_t *sink_latency); +pa_bool_t pa_sink_input_is_passthrough(pa_sink_input *i); pa_bool_t pa_sink_input_is_volume_readable(pa_sink_input *i); void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume, pa_bool_t save, pa_bool_t absolute); pa_cvolume *pa_sink_input_get_volume(pa_sink_input *i, pa_cvolume *volume, pa_bool_t absolute); diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c index 5959a5ce..bc4ddd21 100644 --- a/src/pulsecore/sink.c +++ b/src/pulsecore/sink.c @@ -1252,7 +1252,7 @@ pa_bool_t pa_sink_is_passthrough(pa_sink *s) { if (pa_idxset_size(s->inputs) == 1) { alt_i = pa_idxset_first(s->inputs, &idx); - if (!pa_format_info_is_pcm(alt_i->format)) + if (pa_sink_input_is_passthrough(alt_i)) return TRUE; } -- cgit From a199bfb765147b3938d268a67f671646a16845c3 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Mon, 28 Mar 2011 08:45:31 +0530 Subject: sink-input: Don't print an error if a passthrough connection fails The assertion message is misleading, since the passthrough connection can fail for reasons the client has no control over (like other sink inputs being connected). --- src/pulsecore/sink-input.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c index 5e7cfd13..979dc76e 100644 --- a/src/pulsecore/sink-input.c +++ b/src/pulsecore/sink-input.c @@ -302,7 +302,8 @@ int pa_sink_input_new( pa_return_val_if_fail(!data->sync_base || (data->sync_base->sink == data->sink && pa_sink_input_get_state(data->sync_base) == PA_SINK_INPUT_CORKED), -PA_ERR_INVALID); r = check_passthrough_connection(pa_sink_input_new_data_is_passthrough(data), data->sink); - pa_return_val_if_fail(r == PA_OK, r); + if (r != PA_OK) + return r; if (!data->sample_spec_is_set) data->sample_spec = data->sink->sample_spec; -- cgit From cb3dcb14f868e10c0fde582e9284561af974fab4 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Mon, 28 Mar 2011 08:46:20 +0530 Subject: sink-input: Don't restore volume for passthrough streams --- src/pulsecore/sink-input.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c index 979dc76e..196d7712 100644 --- a/src/pulsecore/sink-input.c +++ b/src/pulsecore/sink-input.c @@ -319,6 +319,12 @@ int pa_sink_input_new( pa_return_val_if_fail(pa_channel_map_compatible(&data->channel_map, &data->sample_spec), -PA_ERR_INVALID); + /* Don't restore (or save) stream volume for passthrough streams */ + if (!pa_format_info_is_pcm(data->format)) { + data->volume_is_set = FALSE; + data->volume_factor_is_set = FALSE; + } + if (!data->volume_is_set) { pa_cvolume_reset(&data->volume, data->sample_spec.channels); data->volume_is_absolute = FALSE; -- cgit From 53091cc5f0a766e0bff36b59600f99cd2771bbd6 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Mon, 28 Mar 2011 08:46:40 +0530 Subject: sink-input: Add a format-lost event This event is emitted if the sink-input could not be moved to a new sink because it doesn't support the format of the sink-input. Clients can reconnect their stream with a different format if they wish or gracefully exit. --- src/pulse/def.h | 7 +++++++ src/pulsecore/sink-input.c | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/pulse/def.h b/src/pulse/def.h index 91a027e3..e01e667f 100644 --- a/src/pulse/def.h +++ b/src/pulse/def.h @@ -912,6 +912,13 @@ typedef void (*pa_free_cb_t)(void *p); * information, \since 0.9.15 */ #define PA_STREAM_EVENT_REQUEST_UNCORK "request-uncork" +/** A stream event notifying that the stream is going to be + * disconnected because the underlying sink changed and no longer + * supports the format that was originally negotiated. Clients need + * to connect a new stream to renegotiate a format and continue + * playback, \since 1.0 */ +#define PA_STREAM_EVENT_FORMAT_LOST "format-lost" + PA_C_DECL_END #endif diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c index 196d7712..f0558f40 100644 --- a/src/pulsecore/sink-input.c +++ b/src/pulsecore/sink-input.c @@ -1594,7 +1594,8 @@ int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, pa_bool_t save) { return -PA_ERR_NOTSUPPORTED; if (pa_sink_input_is_passthrough(i) && !pa_sink_check_format(dest, i->format)) { - /* FIXME: Fire a message here so the client can renegotiate */ + pa_log_debug("New sink doesn't support stream format, sending format-changed and killing"); + pa_sink_input_send_event(i, PA_STREAM_EVENT_FORMAT_LOST, NULL); return -PA_ERR_NOTSUPPORTED; } -- cgit From 13a33abf45f31417076f283ca7da9d9f74892286 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Tue, 29 Mar 2011 17:16:08 +0530 Subject: format: Export pa_format_info_is_compatible in API This allows clients to perform checks between formats as well. --- src/map-file | 1 + src/pulse/format.c | 2 +- src/pulse/format.h | 9 +++++++++ src/pulse/internal.h | 1 - 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/map-file b/src/map-file index 1acc6e98..8d683405 100644 --- a/src/map-file +++ b/src/map-file @@ -163,6 +163,7 @@ pa_ext_stream_restore_test; pa_ext_stream_restore_write; pa_format_info_copy; pa_format_info_free; +pa_format_info_is_compatible; pa_format_info_is_pcm; pa_format_info_new; pa_format_info_snprint; diff --git a/src/pulse/format.c b/src/pulse/format.c index e601d902..a634b27b 100644 --- a/src/pulse/format.c +++ b/src/pulse/format.c @@ -115,7 +115,7 @@ char *pa_format_info_snprint(char *s, size_t l, const pa_format_info *f) { return s; } -pa_bool_t pa_format_info_is_compatible(pa_format_info *first, pa_format_info *second) { +int pa_format_info_is_compatible(pa_format_info *first, pa_format_info *second) { const char *key; void *state = NULL; diff --git a/src/pulse/format.h b/src/pulse/format.h index bd32ba92..b0efe502 100644 --- a/src/pulse/format.h +++ b/src/pulse/format.h @@ -83,6 +83,15 @@ int pa_format_info_valid(const pa_format_info *f); /** Returns non-zero when the format info structure represents a PCM (i.e. uncompressed data) format */ int pa_format_info_is_pcm(const pa_format_info *f); +/** Returns non-zero if the format represented \a first is a subset of + * the format represented by \second. This means that \a second must + * have all the fields that \a first does, but the reverse need not + * be true. This is typically expected to be used to check if a + * stream's format is compatible with a given sink. In such a case, + * \a first would be the sink's format and \a second would be the + * stream's.*/ +int pa_format_info_is_compatible(pa_format_info *first, pa_format_info *second); + /** Maximum required string length for * pa_format_info_snprint(). Please note that this value can change * with any release without warning and without being considered API diff --git a/src/pulse/internal.h b/src/pulse/internal.h index 351aeec9..a659576a 100644 --- a/src/pulse/internal.h +++ b/src/pulse/internal.h @@ -297,7 +297,6 @@ void pa_ext_device_manager_command(pa_context *c, uint32_t tag, pa_tagstruct *t) void pa_ext_stream_restore_command(pa_context *c, uint32_t tag, pa_tagstruct *t); void pa_format_info_free2(pa_format_info *f, void *userdata); -pa_bool_t pa_format_info_is_compatible(pa_format_info *first, pa_format_info *second); pa_format_info* pa_format_info_from_sample_spec(pa_sample_spec *ss, pa_channel_map *map); pa_bool_t pa_format_info_to_sample_spec(pa_format_info *f, pa_sample_spec *ss, pa_channel_map *map); pa_bool_t pa_format_info_to_sample_spec_fake(pa_format_info *f, pa_sample_spec *ss); -- cgit From d1f13fa781ec69e4dbed000902123d255baaba5f Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Sat, 9 Apr 2011 09:36:25 +0530 Subject: format: Add correct sample spec conversion for E-AC3 IEC61937-encapsulated E-AC3 frames contain 6 audio blocks per substream, which corresponds to 1536 samples contained a 24576-byte frame. To cope with this, we maintain the s16le stereo sample spec, but quadruple the sample rate so that the conversion remains accurate. --- src/pulse/format.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pulse/format.c b/src/pulse/format.c index a634b27b..84df76b4 100644 --- a/src/pulse/format.c +++ b/src/pulse/format.c @@ -203,5 +203,8 @@ pa_bool_t pa_format_info_to_sample_spec_fake(pa_format_info *f, pa_sample_spec * pa_return_val_if_fail(r = pa_proplist_gets(f->plist, PA_PROP_FORMAT_RATE), FALSE); pa_return_val_if_fail(pa_atou(r, &ss->rate) == 0, FALSE); + if (f->encoding == PA_ENCODING_EAC3_IEC61937) + ss->rate *= 4; + return TRUE; } -- cgit From 62f56a9f6b01c277a8c4f15625473df4b73bd208 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Sun, 10 Apr 2011 16:24:33 +0530 Subject: sink-input: Provide more information to client when format is lost When the sink format changes and we kill the stream, clients need a way to know (a) what device they should reconnect to, and (b) what the stream running time was when the stream got killed (pa_stream_get_time() won't work after the stream has been killed). This adds these two bits of information in the event callback's proplist parameter. --- src/pulse/stream.c | 8 ++++++++ src/pulsecore/sink-input.c | 6 +++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/pulse/stream.c b/src/pulse/stream.c index bd2e6ca1..6c055a5c 100644 --- a/src/pulse/stream.c +++ b/src/pulse/stream.c @@ -766,6 +766,14 @@ void pa_command_stream_event(pa_pdispatch *pd, uint32_t command, uint32_t tag, p if (s->state != PA_STREAM_READY) goto finish; + if (pa_streq(event, PA_STREAM_EVENT_FORMAT_LOST)) { + /* Let client know what the running time was when the stream had to be + * killed */ + pa_usec_t time; + if (pa_stream_get_time(s, &time) == 0) + pa_proplist_setf(pl, "stream-time", "%llu", (unsigned long long) time); + } + if (s->event_callback) s->event_callback(s, event, pl, s->event_userdata); diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c index f0558f40..b21cfae0 100644 --- a/src/pulsecore/sink-input.c +++ b/src/pulsecore/sink-input.c @@ -1594,8 +1594,12 @@ int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, pa_bool_t save) { return -PA_ERR_NOTSUPPORTED; if (pa_sink_input_is_passthrough(i) && !pa_sink_check_format(dest, i->format)) { + pa_proplist *p = pa_proplist_new(); pa_log_debug("New sink doesn't support stream format, sending format-changed and killing"); - pa_sink_input_send_event(i, PA_STREAM_EVENT_FORMAT_LOST, NULL); + /* Tell the client what device we want to be on if it is going to + * reconnect */ + pa_proplist_sets(p, "device", dest->name); + pa_sink_input_send_event(i, PA_STREAM_EVENT_FORMAT_LOST, p); return -PA_ERR_NOTSUPPORTED; } -- cgit From 8d076d09902fe618e69f3d71e42299bffe2af437 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Wed, 13 Apr 2011 14:05:18 +0530 Subject: format: Extend properties to handle lists/ranges This replaces the simple string used by pa_format_info's proplist with a JSON string (accessed via new API only). This allows us to express lists and ranges more cleanly, and embed type information for future extensibility. We use json-c for JSON parsing. This is a lightweight depdency (32 KB on my system) and avoids the hassle of having to reinvent a JSON parser. Also included is a test which verifies functionality and is valgrind-clean. --- configure.ac | 6 ++ src/Makefile.am | 11 +- src/map-file | 5 + src/pulse/format.c | 270 ++++++++++++++++++++++++++++++++++++++++++++---- src/pulse/format.h | 11 ++ src/pulse/internal.h | 2 + src/tests/format-test.c | 106 +++++++++++++++++++ 7 files changed, 389 insertions(+), 22 deletions(-) create mode 100644 src/tests/format-test.c diff --git a/configure.ac b/configure.ac index 9edae0ec..9d388ef3 100644 --- a/configure.ac +++ b/configure.ac @@ -603,6 +603,12 @@ fi AC_CHECK_HEADERS_ONCE([valgrind/memcheck.h]) +#### json parsing #### + +PKG_CHECK_MODULES(LIBJSON, [ json >= 0.9 ]) +AC_SUBST(LIBJSON_CFLAGS) +AC_SUBST(LIBJSON_LIBS) + #### Sound file #### PKG_CHECK_MODULES(LIBSNDFILE, [ sndfile >= 1.0.20 ]) diff --git a/src/Makefile.am b/src/Makefile.am index a68cdc26..7a4a32d6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -252,6 +252,7 @@ TESTS = \ channelmap-test \ thread-mainloop-test \ utf8-test \ + format-test \ get-binary-name-test \ ipacl-test \ hook-list-test \ @@ -285,6 +286,7 @@ TESTS_BINARIES = \ channelmap-test \ thread-mainloop-test \ utf8-test \ + format-test \ get-binary-name-test \ ipacl-test \ hook-list-test \ @@ -370,6 +372,11 @@ utf8_test_CFLAGS = $(AM_CFLAGS) utf8_test_LDADD = $(AM_LDADD) libpulsecore-@PA_MAJORMINOR@.la libpulse.la libpulsecommon-@PA_MAJORMINOR@.la utf8_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) +format_test_SOURCES = tests/format-test.c +format_test_CFLAGS = $(AM_CFLAGS) +format_test_LDADD = $(AM_LDADD) libpulsecore-@PA_MAJORMINOR@.la libpulse.la libpulsecommon-@PA_MAJORMINOR@.la +format_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + get_binary_name_test_SOURCES = tests/get-binary-name-test.c get_binary_name_test_CFLAGS = $(AM_CFLAGS) get_binary_name_test_LDADD = $(AM_LDADD) libpulse.la libpulsecommon-@PA_MAJORMINOR@.la @@ -652,9 +659,9 @@ libpulsecommon_@PA_MAJORMINOR@_la_SOURCES = \ pulsecore/sndfile-util.c pulsecore/sndfile-util.h \ pulsecore/socket.h -libpulsecommon_@PA_MAJORMINOR@_la_CFLAGS = $(AM_CFLAGS) $(LIBSAMPLERATE_CFLAGS) $(LIBSNDFILE_CFLAGS) +libpulsecommon_@PA_MAJORMINOR@_la_CFLAGS = $(AM_CFLAGS) $(LIBSAMPLERATE_CFLAGS) $(LIBSNDFILE_CFLAGS) $(LIBJSON_CFLAGS) libpulsecommon_@PA_MAJORMINOR@_la_LDFLAGS = $(AM_LDFLAGS) -avoid-version -libpulsecommon_@PA_MAJORMINOR@_la_LIBADD = $(AM_LIBADD) $(LIBWRAP_LIBS) $(WINSOCK_LIBS) $(LTLIBICONV) $(LIBSAMPLERATE_LIBS) $(LIBSNDFILE_LIBS) +libpulsecommon_@PA_MAJORMINOR@_la_LIBADD = $(AM_LIBADD) $(LIBWRAP_LIBS) $(WINSOCK_LIBS) $(LTLIBICONV) $(LIBSAMPLERATE_LIBS) $(LIBSNDFILE_LIBS) $(LIBJSON_LIBS) if HAVE_X11 libpulsecommon_@PA_MAJORMINOR@_la_SOURCES += pulse/client-conf-x11.c pulse/client-conf-x11.h diff --git a/src/map-file b/src/map-file index 8d683405..a12f30d1 100644 --- a/src/map-file +++ b/src/map-file @@ -166,6 +166,11 @@ pa_format_info_free; pa_format_info_is_compatible; pa_format_info_is_pcm; pa_format_info_new; +pa_format_info_set_prop_int; +pa_format_info_set_prop_int_array; +pa_format_info_set_prop_int_range; +pa_format_info_set_prop_string; +pa_format_info_set_prop_string_array; pa_format_info_snprint; pa_format_info_valid; pa_frame_size; diff --git a/src/pulse/format.c b/src/pulse/format.c index 84df76b4..1cb804ef 100644 --- a/src/pulse/format.c +++ b/src/pulse/format.c @@ -25,6 +25,8 @@ #include #endif +#include + #include #include #include @@ -34,6 +36,11 @@ #include "format.h" +#define PA_JSON_MIN_KEY "min" +#define PA_JSON_MAX_KEY "max" + +static int pa_format_info_prop_compatible(const char *one, const char *two); + const char *pa_encoding_to_string(pa_encoding_t e) { static const char* const table[]= { [PA_ENCODING_PCM] = "pcm", @@ -131,7 +138,7 @@ int pa_format_info_is_compatible(pa_format_info *first, pa_format_info *second) value_one = pa_proplist_gets(first->plist, key); value_two = pa_proplist_gets(second->plist, key); - if (!value_two || !pa_streq(value_one, value_two)) + if (!value_two || !pa_format_info_prop_compatible(value_one, value_two)) return FALSE; } @@ -148,13 +155,13 @@ pa_format_info* pa_format_info_from_sample_spec(pa_sample_spec *ss, pa_channel_m f = pa_format_info_new(); f->encoding = PA_ENCODING_PCM; - pa_proplist_sets(f->plist, PA_PROP_FORMAT_SAMPLE_FORMAT, pa_sample_format_to_string(ss->format)); - pa_proplist_setf(f->plist, PA_PROP_FORMAT_RATE, "%u", (unsigned int) ss->rate); - pa_proplist_setf(f->plist, PA_PROP_FORMAT_CHANNELS, "%u", (unsigned int) ss->channels); + pa_format_info_set_prop_string(f, PA_PROP_FORMAT_SAMPLE_FORMAT, pa_sample_format_to_string(ss->format)); + pa_format_info_set_prop_int(f, PA_PROP_FORMAT_RATE, ss->rate); + pa_format_info_set_prop_int(f, PA_PROP_FORMAT_CHANNELS, ss->channels); if (map) { pa_channel_map_snprint(cm, sizeof(cm), map); - pa_proplist_setf(f->plist, PA_PROP_FORMAT_CHANNEL_MAP, "%s", cm); + pa_format_info_set_prop_string(f, PA_PROP_FORMAT_CHANNEL_MAP, cm); } return f; @@ -162,36 +169,51 @@ pa_format_info* pa_format_info_from_sample_spec(pa_sample_spec *ss, pa_channel_m /* For PCM streams */ pa_bool_t pa_format_info_to_sample_spec(pa_format_info *f, pa_sample_spec *ss, pa_channel_map *map) { - const char *sf, *r, *ch; - uint32_t channels; + char *sf = NULL, *m = NULL; + int rate, channels; + pa_bool_t ret = FALSE; pa_assert(f); pa_assert(ss); pa_return_val_if_fail(f->encoding == PA_ENCODING_PCM, FALSE); - pa_return_val_if_fail(sf = pa_proplist_gets(f->plist, PA_PROP_FORMAT_SAMPLE_FORMAT), FALSE); - pa_return_val_if_fail(r = pa_proplist_gets(f->plist, PA_PROP_FORMAT_RATE), FALSE); - pa_return_val_if_fail(ch = pa_proplist_gets(f->plist, PA_PROP_FORMAT_CHANNELS), FALSE); + if (!pa_format_info_get_prop_string(f, PA_PROP_FORMAT_SAMPLE_FORMAT, &sf)) + goto out; + if (!pa_format_info_get_prop_int(f, PA_PROP_FORMAT_RATE, &rate)) + goto out; + if (!pa_format_info_get_prop_int(f, PA_PROP_FORMAT_CHANNELS, &channels)) + goto out; - pa_return_val_if_fail((ss->format = pa_parse_sample_format(sf)) != PA_SAMPLE_INVALID, FALSE); - pa_return_val_if_fail(pa_atou(r, &ss->rate) == 0, FALSE); - pa_return_val_if_fail(pa_atou(ch, &channels) == 0, FALSE); + if ((ss->format = pa_parse_sample_format(sf)) == PA_SAMPLE_INVALID) + goto out; + + ss->rate = (uint32_t) rate; ss->channels = (uint8_t) channels; if (map) { - const char *m = pa_proplist_gets(f->plist, PA_PROP_FORMAT_CHANNEL_MAP); pa_channel_map_init(map); - if (m) - pa_return_val_if_fail(pa_channel_map_parse(map, m) != NULL, FALSE); + if (!pa_format_info_get_prop_string(f, PA_PROP_FORMAT_CHANNEL_MAP, &m)) + goto out; + + if (m && pa_channel_map_parse(map, m) == NULL) + goto out; } - return TRUE; + ret = TRUE; + +out: + if (sf) + pa_xfree(sf); + if (m) + pa_xfree(m); + + return ret; } /* For compressed streams */ pa_bool_t pa_format_info_to_sample_spec_fake(pa_format_info *f, pa_sample_spec *ss) { - const char *r; + int rate; pa_assert(f); pa_assert(ss); @@ -200,11 +222,219 @@ pa_bool_t pa_format_info_to_sample_spec_fake(pa_format_info *f, pa_sample_spec * ss->format = PA_SAMPLE_S16LE; ss->channels = 2; - pa_return_val_if_fail(r = pa_proplist_gets(f->plist, PA_PROP_FORMAT_RATE), FALSE); - pa_return_val_if_fail(pa_atou(r, &ss->rate) == 0, FALSE); + pa_return_val_if_fail(pa_format_info_get_prop_int(f, PA_PROP_FORMAT_RATE, &rate), FALSE); + ss->rate = (uint32_t) rate; if (f->encoding == PA_ENCODING_EAC3_IEC61937) ss->rate *= 4; return TRUE; } + +pa_bool_t pa_format_info_get_prop_int(pa_format_info *f, const char *key, int *v) { + const char *str; + json_object *o; + + pa_assert(f); + pa_assert(key); + pa_assert(v); + + pa_return_val_if_fail(str = pa_proplist_gets(f->plist, key), FALSE); + o = json_tokener_parse(str); + pa_return_val_if_fail(!is_error(o), FALSE); + if (json_object_get_type(o) != json_type_int) { + json_object_put(o); + return FALSE; + } + + *v = json_object_get_int(o); + json_object_put(o); + + return TRUE; +} + +pa_bool_t pa_format_info_get_prop_string(pa_format_info *f, const char *key, char **v) { + const char *str = NULL; + json_object *o; + + pa_assert(f); + pa_assert(key); + pa_assert(v); + + pa_return_val_if_fail(str = pa_proplist_gets(f->plist, key), FALSE); + o = json_tokener_parse(str); + pa_return_val_if_fail(!is_error(o), FALSE); + if (json_object_get_type(o) != json_type_string) { + json_object_put(o); + return FALSE; + } + + *v = pa_xstrdup(json_object_get_string(o)); + json_object_put(o); + + return TRUE; +} + +void pa_format_info_set_prop_int(pa_format_info *f, const char *key, int value) { + json_object *o; + + pa_assert(f); + pa_assert(key); + + o = json_object_new_int(value); + + pa_proplist_sets(f->plist, key, json_object_to_json_string(o)); + + json_object_put(o); +} + +void pa_format_info_set_prop_int_array(pa_format_info *f, const char *key, const int *values, int n_values) { + json_object *o; + int i; + + pa_assert(f); + pa_assert(key); + + o = json_object_new_array(); + + for (i = 0; i < n_values; i++) + json_object_array_add(o, json_object_new_int(values[i])); + + pa_proplist_sets(f->plist, key, json_object_to_json_string(o)); + + json_object_put(o); +} + +void pa_format_info_set_prop_int_range(pa_format_info *f, const char *key, int min, int max) { + json_object *o; + + pa_assert(f); + pa_assert(key); + + o = json_object_new_object(); + + json_object_object_add(o, PA_JSON_MIN_KEY, json_object_new_int(min)); + json_object_object_add(o, PA_JSON_MAX_KEY, json_object_new_int(max)); + + pa_proplist_sets(f->plist, key, json_object_to_json_string(o)); + + json_object_put(o); +} + +void pa_format_info_set_prop_string(pa_format_info *f, const char *key, const char *value) { + json_object *o; + + pa_assert(f); + pa_assert(key); + + o = json_object_new_string(value); + + pa_proplist_sets(f->plist, key, json_object_to_json_string(o)); + + json_object_put(o); +} + +void pa_format_info_set_prop_string_array(pa_format_info *f, const char *key, const char **values, int n_values) { + json_object *o; + int i; + + pa_assert(f); + pa_assert(key); + + o = json_object_new_array(); + + for (i = 0; i < n_values; i++) + json_object_array_add(o, json_object_new_string(values[i])); + + pa_proplist_sets(f->plist, key, json_object_to_json_string(o)); + + json_object_put(o); +} + +static pa_bool_t pa_json_is_fixed_type(json_object *o) +{ + switch(json_object_get_type(o)) { + case json_type_object: + case json_type_array: + return FALSE; + + default: + return TRUE; + } +} + +static int pa_json_value_equal(json_object *o1, json_object *o2) { + return (json_object_get_type(o1) == json_object_get_type(o2)) && + pa_streq(json_object_to_json_string(o1), json_object_to_json_string(o2)); +} + +static int pa_format_info_prop_compatible(const char *one, const char *two) { + json_object *o1 = NULL, *o2 = NULL; + int i, ret = 0; + + o1 = json_tokener_parse(one); + if (is_error(o1)) + goto out; + + o2 = json_tokener_parse(two); + if (is_error(o2)) + goto out; + + /* We don't deal with both values being non-fixed - just because there is no immediate need (FIXME) */ + pa_return_val_if_fail(pa_json_is_fixed_type(o1) || pa_json_is_fixed_type(o2), FALSE); + + if (pa_json_is_fixed_type(o1) && pa_json_is_fixed_type(o2)) { + ret = pa_json_value_equal(o1, o2); + goto out; + } + + if (pa_json_is_fixed_type(o1)) { + json_object *tmp = o2; + o2 = o1; + o1 = tmp; + } + + /* o2 is now a fixed type, and o1 is not */ + + if (json_object_get_type(o1) == json_type_array) { + for (i = 0; i < json_object_array_length(o1); i++) { + if (pa_json_value_equal(json_object_array_get_idx(o1, i), o2)) { + ret = 1; + break; + } + } + } else if (json_object_get_type(o1) == json_type_object) { + /* o1 should be a range type */ + int min, max, v; + json_object *o_min = NULL, *o_max = NULL; + + if (json_object_get_type(o2) != json_type_int) { + /* We don't support non-integer ranges */ + goto out; + } + + o_min = json_object_object_get(o1, PA_JSON_MIN_KEY); + if (!o_min || json_object_get_type(o_min) != json_type_int) + goto out; + + o_max = json_object_object_get(o1, PA_JSON_MAX_KEY); + if (!o_max || json_object_get_type(o_max) != json_type_int) + goto out; + + v = json_object_get_int(o2); + min = json_object_get_int(o_min); + max = json_object_get_int(o_max); + + ret = v >= min && v <= max; + } else { + pa_log_warn("Got a format type that we don't support"); + } + +out: + if (o1) + json_object_put(o1); + if (o2) + json_object_put(o2); + + return ret; +} diff --git a/src/pulse/format.h b/src/pulse/format.h index b0efe502..0498e68a 100644 --- a/src/pulse/format.h +++ b/src/pulse/format.h @@ -102,6 +102,17 @@ int pa_format_info_is_compatible(pa_format_info *first, pa_format_info *second); /** Return a human-readable string representing the given format. \since 1.0 */ char *pa_format_info_snprint(char *s, size_t l, const pa_format_info *f); +/** Sets an integer property on the given format info */ +void pa_format_info_set_prop_int(pa_format_info *f, const char *key, int value); +/** Sets a property with a list of integer values on the given format info */ +void pa_format_info_set_prop_int_array(pa_format_info *f, const char *key, const int *values, int n_values); +/** Sets a property which can have any value in a given integer range on the given format info */ +void pa_format_info_set_prop_int_range(pa_format_info *f, const char *key, int min, int max); +/** Sets a string property on the given format info */ +void pa_format_info_set_prop_string(pa_format_info *f, const char *key, const char *value); +/** Sets a property with a list of string values on the given format info */ +void pa_format_info_set_prop_string_array(pa_format_info *f, const char *key, const char **values, int n_values); + PA_C_DECL_END #endif diff --git a/src/pulse/internal.h b/src/pulse/internal.h index a659576a..40f6804a 100644 --- a/src/pulse/internal.h +++ b/src/pulse/internal.h @@ -300,6 +300,8 @@ void pa_format_info_free2(pa_format_info *f, void *userdata); pa_format_info* pa_format_info_from_sample_spec(pa_sample_spec *ss, pa_channel_map *map); pa_bool_t pa_format_info_to_sample_spec(pa_format_info *f, pa_sample_spec *ss, pa_channel_map *map); pa_bool_t pa_format_info_to_sample_spec_fake(pa_format_info *f, pa_sample_spec *ss); +pa_bool_t pa_format_info_get_prop_int(pa_format_info *f, const char *key, int *v); +pa_bool_t pa_format_info_get_prop_string(pa_format_info *f, const char *key, char **v); pa_bool_t pa_mainloop_is_our_api(pa_mainloop_api*m); diff --git a/src/tests/format-test.c b/src/tests/format-test.c new file mode 100644 index 00000000..888db8c9 --- /dev/null +++ b/src/tests/format-test.c @@ -0,0 +1,106 @@ +/*** + This file is part of PulseAudio. + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include +#include + +#define INIT(f) f = pa_format_info_new() +#define DEINIT(f) pa_format_info_free(f); +#define REINIT(f) { DEINIT(f); INIT(f); } + +int main(int argc, char *argv[]) { + pa_format_info *f1 = NULL, *f2 = NULL; + int rates1[] = { 32000, 44100, 48000 }; + const char *strings[] = { "thing1", "thing2", "thing3" }; + + /* 1. Simple fixed format int check */ + INIT(f1); INIT(f2); + f1->encoding = PA_ENCODING_AC3_IEC61937; + pa_format_info_set_prop_int(f1, PA_PROP_FORMAT_RATE, 32000); + f2->encoding = PA_ENCODING_AC3_IEC61937; + pa_format_info_set_prop_int(f2, PA_PROP_FORMAT_RATE, 44100); + pa_assert(!pa_format_info_is_compatible(f1, f2)); + + /* 2. Check int array membership - positive */ + REINIT(f1); REINIT(f2); + f1->encoding = PA_ENCODING_AC3_IEC61937; + pa_format_info_set_prop_int_array(f1, PA_PROP_FORMAT_RATE, rates1, PA_ELEMENTSOF(rates1)); + f2->encoding = PA_ENCODING_AC3_IEC61937; + pa_format_info_set_prop_int(f2, PA_PROP_FORMAT_RATE, 44100); + pa_assert(pa_format_info_is_compatible(f1, f2)); + pa_assert(pa_format_info_is_compatible(f2, f1)); + + /* 3. Check int array memebership - negative */ + REINIT(f2); + f2->encoding = PA_ENCODING_AC3_IEC61937; + pa_format_info_set_prop_int(f2, PA_PROP_FORMAT_RATE, 96000); + pa_assert(!pa_format_info_is_compatible(f1, f2)); + pa_assert(!pa_format_info_is_compatible(f2, f1)); + + /* 4. Check int range - positive */ + REINIT(f1); REINIT(f2); + f1->encoding = PA_ENCODING_AC3_IEC61937; + pa_format_info_set_prop_int_range(f1, PA_PROP_FORMAT_RATE, 32000, 48000); + f2->encoding = PA_ENCODING_AC3_IEC61937; + pa_format_info_set_prop_int(f2, PA_PROP_FORMAT_RATE, 44100); + pa_assert(pa_format_info_is_compatible(f1, f2)); + pa_assert(pa_format_info_is_compatible(f2, f1)); + + /* 5. Check int range - negative */ + REINIT(f2); + f2->encoding = PA_ENCODING_AC3_IEC61937; + pa_format_info_set_prop_int(f2, PA_PROP_FORMAT_RATE, 96000); + pa_assert(!pa_format_info_is_compatible(f1, f2)); + pa_assert(!pa_format_info_is_compatible(f2, f1)); + + /* 6. Simple fixed format string check */ + REINIT(f1); REINIT(f2); + f1->encoding = PA_ENCODING_AC3_IEC61937; + pa_format_info_set_prop_string(f1, "format.test_string", "thing1"); + f2->encoding = PA_ENCODING_AC3_IEC61937; + pa_format_info_set_prop_string(f2, "format.test_string", "notthing1"); + pa_assert(!pa_format_info_is_compatible(f1, f2)); + + /* 7. Check string array membership - positive */ + REINIT(f1); REINIT(f2); + f1->encoding = PA_ENCODING_AC3_IEC61937; + pa_format_info_set_prop_string_array(f1, "format.test_string", strings, PA_ELEMENTSOF(strings)); + f2->encoding = PA_ENCODING_AC3_IEC61937; + pa_format_info_set_prop_string(f2, "format.test_string", "thing3"); + pa_assert(pa_format_info_is_compatible(f1, f2)); + pa_assert(pa_format_info_is_compatible(f2, f1)); + + /* 8. Check string array memebership - negative */ + REINIT(f2); + f2->encoding = PA_ENCODING_AC3_IEC61937; + pa_format_info_set_prop_string(f2, "format.test_string", "thing5"); + pa_assert(!pa_format_info_is_compatible(f1, f2)); + pa_assert(!pa_format_info_is_compatible(f2, f1)); + + DEINIT(f1); + DEINIT(f2); + + return 0; +} -- cgit From 1404db3d47c77ef3a360feed4ae8932cc8e443a0 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Wed, 13 Apr 2011 15:37:25 +0530 Subject: format: Add some convenience API for setting properties Adds functions to set sample format, rate, channels and channel map on a format to make life easier for users of the API. --- src/map-file | 4 ++++ src/pulse/format.c | 26 +++++++++++++++++++++++--- src/pulse/format.h | 11 +++++++++++ 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/map-file b/src/map-file index a12f30d1..026ac10f 100644 --- a/src/map-file +++ b/src/map-file @@ -166,11 +166,15 @@ pa_format_info_free; pa_format_info_is_compatible; pa_format_info_is_pcm; pa_format_info_new; +pa_format_info_set_channel_map; +pa_format_info_set_channels; pa_format_info_set_prop_int; pa_format_info_set_prop_int_array; pa_format_info_set_prop_int_range; pa_format_info_set_prop_string; pa_format_info_set_prop_string_array; +pa_format_info_set_rate; +pa_format_info_set_sample_format; pa_format_info_snprint; pa_format_info_valid; pa_frame_size; diff --git a/src/pulse/format.c b/src/pulse/format.c index 1cb804ef..a1a0981b 100644 --- a/src/pulse/format.c +++ b/src/pulse/format.c @@ -155,9 +155,9 @@ pa_format_info* pa_format_info_from_sample_spec(pa_sample_spec *ss, pa_channel_m f = pa_format_info_new(); f->encoding = PA_ENCODING_PCM; - pa_format_info_set_prop_string(f, PA_PROP_FORMAT_SAMPLE_FORMAT, pa_sample_format_to_string(ss->format)); - pa_format_info_set_prop_int(f, PA_PROP_FORMAT_RATE, ss->rate); - pa_format_info_set_prop_int(f, PA_PROP_FORMAT_CHANNELS, ss->channels); + pa_format_info_set_sample_format(f, ss->format); + pa_format_info_set_rate(f, ss->rate); + pa_format_info_set_channels(f, ss->channels); if (map) { pa_channel_map_snprint(cm, sizeof(cm), map); @@ -231,6 +231,26 @@ pa_bool_t pa_format_info_to_sample_spec_fake(pa_format_info *f, pa_sample_spec * return TRUE; } +void pa_format_info_set_sample_format(pa_format_info *f, pa_sample_format_t sf) { + pa_format_info_set_prop_string(f, PA_PROP_FORMAT_SAMPLE_FORMAT, pa_sample_format_to_string(sf)); +} + +void pa_format_info_set_rate(pa_format_info *f, int rate) { + pa_format_info_set_prop_int(f, PA_PROP_FORMAT_RATE, rate); +} + +void pa_format_info_set_channels(pa_format_info *f, int channels) { + pa_format_info_set_prop_int(f, PA_PROP_FORMAT_CHANNELS, channels); +} + +void pa_format_info_set_channel_map(pa_format_info *f, const pa_channel_map *map) { + char map_str[PA_CHANNEL_MAP_SNPRINT_MAX]; + + pa_channel_map_snprint(map_str, sizeof(map_str), map); + + pa_format_info_set_prop_string(f, PA_PROP_FORMAT_CHANNEL_MAP, map_str); +} + pa_bool_t pa_format_info_get_prop_int(pa_format_info *f, const char *key, int *v) { const char *str; json_object *o; diff --git a/src/pulse/format.h b/src/pulse/format.h index 0498e68a..06e1fe62 100644 --- a/src/pulse/format.h +++ b/src/pulse/format.h @@ -26,6 +26,8 @@ #include #include +#include +#include PA_C_DECL_BEGIN @@ -113,6 +115,15 @@ void pa_format_info_set_prop_string(pa_format_info *f, const char *key, const ch /** Sets a property with a list of string values on the given format info */ void pa_format_info_set_prop_string_array(pa_format_info *f, const char *key, const char **values, int n_values); +/** Convenience method to set the sample format as a property on the given format */ +void pa_format_info_set_sample_format(pa_format_info *f, pa_sample_format_t sf); +/** Convenience method to set the sampling rate as a property on the given format */ +void pa_format_info_set_rate(pa_format_info *f, int rate); +/** Convenience method to set the number of channels as a property on the given format */ +void pa_format_info_set_channels(pa_format_info *f, int channels); +/** Convenience method to set the channel map as a property on the given format */ +void pa_format_info_set_channel_map(pa_format_info *f, const pa_channel_map *map); + PA_C_DECL_END #endif -- cgit From cebb4e031d3ec5c99b1d633d6582d35b3379a6cc Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Thu, 5 May 2011 15:58:46 +0530 Subject: doxygen: generate documentation for format.h --- doxygen/doxygen.conf.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doxygen/doxygen.conf.in b/doxygen/doxygen.conf.in index 7dc0f8b1..0306114b 100644 --- a/doxygen/doxygen.conf.in +++ b/doxygen/doxygen.conf.in @@ -417,7 +417,7 @@ WARN_LOGFILE = # directories like "/usr/src/myproject". Separate the files or directories # with spaces. -INPUT = ../src/pulse/context.h ../src/pulse/stream.h ../src/pulse/pulseaudio.h ../src/pulse/sample.h ../src/pulse/def.h ../src/pulse/subscribe.h ../src/pulse/introspect.h ../src/pulse/scache.h ../src/pulse/mainloop-api.h ../src/pulse/glib-mainloop.h ../src/pulse/mainloop.h ../src/pulse/mainloop-signal.h ../src/pulse/error.h ../src/pulse/operation.h ../src/pulse/simple.h ../src/pulse/version.h ../src/pulse/volume.h ../src/pulse/channelmap.h ../src/pulse/thread-mainloop.h ../src/pulse/xmalloc.h ../src/pulse/utf8.h ../src/pulse/util.h ../src/pulse/timeval.h ../src/pulse/proplist.h ../src/pulse/gccmacro.h ../src/pulse/ext-stream-restore.h ../src/pulse/rtclock.h +INPUT = ../src/pulse/context.h ../src/pulse/stream.h ../src/pulse/pulseaudio.h ../src/pulse/sample.h ../src/pulse/def.h ../src/pulse/subscribe.h ../src/pulse/introspect.h ../src/pulse/scache.h ../src/pulse/mainloop-api.h ../src/pulse/glib-mainloop.h ../src/pulse/mainloop.h ../src/pulse/mainloop-signal.h ../src/pulse/error.h ../src/pulse/operation.h ../src/pulse/simple.h ../src/pulse/version.h ../src/pulse/volume.h ../src/pulse/channelmap.h ../src/pulse/thread-mainloop.h ../src/pulse/xmalloc.h ../src/pulse/utf8.h ../src/pulse/util.h ../src/pulse/timeval.h ../src/pulse/proplist.h ../src/pulse/gccmacro.h ../src/pulse/ext-stream-restore.h ../src/pulse/rtclock.h ../src/pulse/format.h # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp -- cgit From 7ebc5033637d7f0ca8ece80259d8a5dc6b30557b Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Sun, 15 May 2011 09:54:17 +0530 Subject: module-tunnel: Update for recent protocol changes This updates the tunnel module for protocol version >= 19. module-tunnel-sink does not proxy server-side passthrough support (yet). This would require a few more changes, namely keeping track of what formats are available and if any other sink inputs are connected on the server-side. --- src/modules/module-tunnel.c | 59 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/src/modules/module-tunnel.c b/src/modules/module-tunnel.c index 6cb22e03..1b2d3a1a 100644 --- a/src/modules/module-tunnel.c +++ b/src/modules/module-tunnel.c @@ -1095,6 +1095,23 @@ static void sink_info_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_t } } + if (u->version >= 21) { + uint8_t n_formats; + pa_format_info format; + + if (pa_tagstruct_getu8(t, &n_formats) < 0) { /* no. of formats */ + pa_log("Parse failure"); + goto fail; + } + + for (uint8_t j = 0; j < n_formats; j++) { + if (pa_tagstruct_get_format_info(t, &format)) { /* format info */ + pa_log("Parse failure"); + goto fail; + } + } + } + if (!pa_tagstruct_eof(t)) { pa_log("Packet too long"); goto fail; @@ -1128,6 +1145,7 @@ static void sink_input_info_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag pa_channel_map channel_map; pa_cvolume volume; pa_proplist *pl; + pa_bool_t b; pa_assert(pd); pa_assert(u); @@ -1175,6 +1193,33 @@ static void sink_input_info_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag } } + if (u->version >= 19) { + if (pa_tagstruct_get_boolean(t, &b) < 0) { + + pa_log("Parse failure"); + goto fail; + } + } + + if (u->version >= 20) { + if (pa_tagstruct_get_boolean(t, &b) < 0 || + pa_tagstruct_get_boolean(t, &b) < 0) { + + pa_log("Parse failure"); + goto fail; + } + } + + if (u->version >= 21) { + pa_format_info format; + + if (pa_tagstruct_get_format_info(t, &format) < 0) { + + pa_log("Parse failure"); + goto fail; + } + } + if (!pa_tagstruct_eof(t)) { pa_log("Packet too long"); goto fail; @@ -1491,6 +1536,13 @@ static void create_stream_callback(pa_pdispatch *pd, uint32_t command, uint32_t /* #endif */ } + if (u->version >= 21) { + pa_format_info format; + + if (pa_tagstruct_get_format_info(t, &format) < 0) + goto parse_error; + } + if (!pa_tagstruct_eof(t)) goto parse_error; @@ -1693,6 +1745,13 @@ static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t pa_tagstruct_put_boolean(reply, FALSE); /* passthrough stream */ #endif +#ifdef TUNNEL_SINK + if (u->version >= 21) { + /* We're not using the extended API, so n_formats = 0 and that's that */ + pa_tagstruct_putu8(t, 0); + } +#endif + pa_pstream_send_tagstruct(u->pstream, reply); pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, create_stream_callback, u, NULL); -- cgit