diff options
| author | Colin Guthrie <colin@mageia.org> | 2011-05-15 13:14:33 +0100 | 
|---|---|---|
| committer | Colin Guthrie <colin@mageia.org> | 2011-05-15 13:14:33 +0100 | 
| commit | 26b4bd74aa92ed51a33a59af46ec32d7e5b66628 (patch) | |
| tree | ba0105baedff9f796f89b31b10953748a119d644 | |
| parent | be4208d0799f21663f58f50430b4883e87709480 (diff) | |
| parent | 7ebc5033637d7f0ca8ece80259d8a5dc6b30557b (diff) | |
Merge branch 'passthrough'
49 files changed, 1795 insertions, 200 deletions
| @@ -213,3 +213,31 @@ 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 + +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 + +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/configure.ac b/configure.ac index ec1099ba..9d388ef3 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 @@ -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/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 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..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 \ @@ -274,6 +275,7 @@ TESTS_BINARIES = \  		mcalign-test \  		pacat-simple \  		parec-simple \ +		extended-test \  		strlist-test \  		close-test \  		voltest \ @@ -284,6 +286,7 @@ TESTS_BINARIES = \  		channelmap-test \  		thread-mainloop-test \  		utf8-test \ +		format-test \  		get-binary-name-test \  		ipacl-test \  		hook-list-test \ @@ -369,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 @@ -439,6 +447,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 @@ -589,6 +602,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 \ @@ -645,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 @@ -719,6 +733,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 \ @@ -1158,8 +1173,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/map-file b/src/map-file index 1fffaff9..026ac10f 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; @@ -160,6 +161,22 @@ 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_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;  pa_get_binary_name;  pa_get_fqdn; @@ -251,6 +268,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; @@ -261,6 +279,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/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c index ccbc0628..b98340b7 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_sink_input_is_passthrough(i))) +                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_sink_input_is_passthrough(i))) +                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; @@ -1707,13 +1759,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/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 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-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); 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/def.h b/src/pulse/def.h index a3b86223..e01e667f 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 @@ -918,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/pulse/format.c b/src/pulse/format.c new file mode 100644 index 00000000..a1a0981b --- /dev/null +++ b/src/pulse/format.c @@ -0,0 +1,460 @@ +/*** +  This file is part of PulseAudio. + +  Copyright 2011 Intel Corporation +  Copyright 2011 Collabora Multimedia +  Copyright 2011 Arun Raghavan <arun.raghavan@collabora.co.uk> + +  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 <config.h> +#endif + +#include <json.h> + +#include <pulse/internal.h> +#include <pulse/xmalloc.h> +#include <pulse/i18n.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> + +#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", +        [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", +    }; + +    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); + +    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); +} + +void pa_format_info_free2(pa_format_info *f, void *userdata) { +    pa_format_info_free(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(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; +} + +int 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_format_info_prop_compatible(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_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); +        pa_format_info_set_prop_string(f, PA_PROP_FORMAT_CHANNEL_MAP, cm); +    } + +    return f; +} + +/* For PCM streams */ +pa_bool_t pa_format_info_to_sample_spec(pa_format_info *f, pa_sample_spec *ss, pa_channel_map *map) { +    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); + +    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; + +    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) { +        pa_channel_map_init(map); + +        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; +    } + +    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) { +    int rate; + +    pa_assert(f); +    pa_assert(ss); +    pa_return_val_if_fail(f->encoding != PA_ENCODING_PCM, FALSE); + +    ss->format = PA_SAMPLE_S16LE; +    ss->channels = 2; + +    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; +} + +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; + +    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 new file mode 100644 index 00000000..06e1fe62 --- /dev/null +++ b/src/pulse/format.h @@ -0,0 +1,129 @@ +#ifndef fooformathfoo +#define fooformathfoo + +/*** +  This file is part of PulseAudio. + +  Copyright 2011 Intel Corporation +  Copyright 2011 Collabora Multimedia +  Copyright 2011 Arun Raghavan <arun.raghavan@collabora.co.uk> + +  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 <pulse/cdecl.h> +#include <pulse/proplist.h> +#include <pulse/sample.h> +#include <pulse/channelmap.h> + +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_DTS_IEC61937, +    /**< DTS 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; + +/** 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; +    /**< 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(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 + * 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); + +/** 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); + +/** 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 diff --git a/src/pulse/internal.h b/src/pulse/internal.h index ab702b99..40f6804a 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; @@ -291,6 +296,13 @@ 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_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);  #endif diff --git a/src/pulse/introspect.c b/src/pulse/introspect.c index c93fb062..e28a78c4 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);          }      } @@ -999,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 || @@ -1016,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;              } @@ -1034,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 297b4bac..a6b4a801 100644 --- a/src/pulse/introspect.h +++ b/src/pulse/introspect.h @@ -32,6 +32,7 @@  #include <pulse/channelmap.h>  #include <pulse/volume.h>  #include <pulse/proplist.h> +#include <pulse/format.h>  #include <pulse/version.h>  /** \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 */ @@ -505,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/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; 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 <pulse/mainloop-api.h>  #include <pulse/sample.h> +#include <pulse/format.h>  #include <pulse/def.h>  #include <pulse/context.h>  #include <pulse/stream.h> 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) { diff --git a/src/pulse/stream.c b/src/pulse/stream.c index aac18a31..6c055a5c 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,18 @@ 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 { +        /* 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; @@ -179,6 +203,38 @@ 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); + +    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 +276,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 +302,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);  } @@ -705,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); @@ -970,9 +1039,10 @@ 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))))) {              pa_context_fail(s->context, PA_ERR_PROTOCOL);              goto finish;          } @@ -999,6 +1069,22 @@ 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 (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)) {          pa_context_fail(s->context, PA_ERR_PROTOCOL);          goto finish; @@ -1039,6 +1125,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 +1166,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 +1234,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 +1309,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); @@ -2374,6 +2478,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 dd67db73..b265fae6 100644 --- a/src/pulse/stream.h +++ b/src/pulse/stream.h @@ -26,6 +26,7 @@  #include <sys/types.h>  #include <pulse/sample.h> +#include <pulse/format.h>  #include <pulse/channelmap.h>  #include <pulse/volume.h>  #include <pulse/def.h> @@ -356,6 +357,16 @@ pa_stream* pa_stream_new_with_proplist(          const pa_channel_map *map         /**< The desired channel map, or NULL for default */,          pa_proplist *p                    /**< The initial property list */); +/* Create a new, unconnected stream with the specified name, the set of formats + * this client can provide, and an initial list of properties. While + * connecting, the server will select the most appropriate format which the + * client must then provide. \since 1.0 */ +pa_stream *pa_stream_new_extended( +        pa_context *c                     /**< The context to create this stream in */, +        const char *name                  /**< A name for this stream */, +        pa_format_info * const * formats  /**< The list of formats that can be provided */, +        pa_proplist *p                    /**< The initial property list */); +  /** Decrease the reference counter by one */  void pa_stream_unref(pa_stream *s); @@ -698,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 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/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..59b87242 100644 --- a/src/pulsecore/protocol-native.c +++ b/src/pulsecore/protocol-native.c @@ -35,6 +35,7 @@  #include <pulse/utf8.h>  #include <pulse/util.h>  #include <pulse/xmalloc.h> +#include <pulse/internal.h>  #include <pulsecore/native-common.h>  #include <pulsecore/packet.h> @@ -1014,6 +1015,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 +1069,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; @@ -1876,9 +1880,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 +1909,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 +1935,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 +1944,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 +1954,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 +1965,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 +1984,57 @@ 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); +        } +    } + +    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); -        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 +2055,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 +2094,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) pa_format_info_free2, NULL); +    return;  }  static void command_delete_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { @@ -2935,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) { @@ -3091,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/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 1931d994..b21cfae0 100644 --- a/src/pulsecore/sink-input.c +++ b/src/pulsecore/sink-input.c @@ -31,6 +31,7 @@  #include <pulse/utf8.h>  #include <pulse/xmalloc.h>  #include <pulse/util.h> +#include <pulse/internal.h>  #include <pulsecore/sample-util.h>  #include <pulsecore/core-subscribe.h> @@ -49,36 +50,18 @@ 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) { - -    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; -            } -        } +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; +    } -    } 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 && passthrough) { +        pa_log_warn("Sink is already connected, cannot accept new PASSTHROUGH INPUT"); +        return -PA_ERR_BUSY;      } +      return PA_OK;  } @@ -107,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); @@ -146,9 +141,68 @@ void pa_sink_input_new_data_set_muted(pa_sink_input_new_data *data, pa_bool_t mu      data->muted = !!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 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) 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) pa_format_info_free2, 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) pa_format_info_free2, 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_assert(data); +    if (data->req_formats) +        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) pa_format_info_free2, NULL); + +    if (data->format) +        pa_format_info_free(data->format); +      pa_proplist_free(data->proplist);  } @@ -189,6 +243,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,22 +257,53 @@ 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 */ + +    /* 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->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_NOTSUPPORTED); + +    /* 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_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_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);      }      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->flags, data->sink); -    pa_return_val_if_fail(r == PA_OK, r); +    r = check_passthrough_connection(pa_sink_input_new_data_is_passthrough(data), data->sink); +    if (r != PA_OK) +        return r;      if (!data->sample_spec_is_set)          data->sample_spec = data->sink->sample_spec; @@ -232,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; @@ -295,18 +388,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 (!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, +                          &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); @@ -327,6 +422,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; @@ -519,6 +615,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_sink_input_is_passthrough(i) && i->sink->monitor_source) +            pa_source_suspend(i->sink->monitor_source, FALSE, PA_SUSPEND_PASSTHROUGH);      }      reset_callbacks(i); @@ -562,6 +662,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); @@ -606,6 +709,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_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;      i->thread_info.muted = i->muted; @@ -1087,12 +1194,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 */ @@ -1251,7 +1371,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(pa_sink_input_is_passthrough(i), dest) < 0)          return FALSE;      if (i->may_move_to) @@ -1297,6 +1417,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_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);      pa_cvolume_remap(&i->volume_factor_sink, &i->sink->channel_map, &i->channel_map);      i->sink = NULL; @@ -1469,6 +1593,16 @@ 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_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"); +        /* 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; +    } +      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)) @@ -1533,6 +1667,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_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);      /* Notify everyone */ diff --git a/src/pulsecore/sink-input.h b/src/pulsecore/sink-input.h index 11f6608f..0ebb74a9 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 <pulse/sample.h> +#include <pulse/format.h>  #include <pulsecore/hook-list.h>  #include <pulsecore/memblockq.h>  #include <pulsecore/resampler.h> @@ -92,6 +93,7 @@ struct pa_sink_input {      pa_sample_spec sample_spec;      pa_channel_map channel_map; +    pa_format_info *format;      pa_sink_input *sync_prev, *sync_next; @@ -279,6 +281,9 @@ typedef struct pa_sink_input_new_data {      pa_sample_spec sample_spec;      pa_channel_map channel_map; +    pa_format_info *format; +    pa_idxset *req_formats; +    pa_idxset *nego_formats;      pa_cvolume volume, volume_factor, volume_factor_sink;      pa_bool_t muted:1; @@ -299,10 +304,13 @@ 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);  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 */ @@ -343,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 839b7d44..bc4ddd21 100644 --- a/src/pulsecore/sink.c +++ b/src/pulsecore/sink.c @@ -35,6 +35,7 @@  #include <pulse/util.h>  #include <pulse/i18n.h>  #include <pulse/rtclock.h> +#include <pulse/internal.h>  #include <pulsecore/sink-input.h>  #include <pulsecore/namereg.h> @@ -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 */ @@ -1239,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_sink_input_is_passthrough(alt_i)) +            return TRUE; +    } + +    return FALSE; +} +  /* Called from main context. */  static void compute_reference_ratio(pa_sink_input *i) {      unsigned c = 0; @@ -1630,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, @@ -2144,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); @@ -3258,3 +3267,82 @@ 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 */ +/* 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. */ +pa_idxset* pa_sink_check_formats(pa_sink *s, pa_idxset *in_formats) { +    pa_idxset *out_formats = pa_idxset_new(NULL, NULL), *sink_formats = NULL; +    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: +    if (sink_formats) +        pa_idxset_free(sink_formats, (pa_free2_cb_t) pa_format_info_free2, NULL); + +    return out_formats; +} diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h index ef698813..cbff5cae 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,34 @@ 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 */ + +    /* 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 */ @@ -378,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); @@ -398,6 +406,10 @@ 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_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 */  void pa_sink_render(pa_sink*s, size_t length, pa_memchunk *result); 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); 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) 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 <sys/time.h>  #include <pulse/sample.h> +#include <pulse/format.h>  #include <pulse/channelmap.h>  #include <pulse/volume.h>  #include <pulse/proplist.h> @@ -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 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 <config.h> +#endif + +#include <signal.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <getopt.h> +#include <math.h> + +#include <pulse/pulseaudio.h> +#include <pulse/mainloop.h> + +#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; +} 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 <config.h> +#endif + +#include <stdlib.h> + +#include <pulsecore/macro.h> +#include <pulse/format.h> + +#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; +} diff --git a/src/utils/pactl.c b/src/utils/pactl.c index 194183d4..7be7049f 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) { @@ -551,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) { @@ -591,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" @@ -606,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), | 
