diff options
Diffstat (limited to 'src/pulse')
-rw-r--r-- | src/pulse/def.h | 17 | ||||
-rw-r--r-- | src/pulse/format.c | 460 | ||||
-rw-r--r-- | src/pulse/format.h | 129 | ||||
-rw-r--r-- | src/pulse/internal.h | 12 | ||||
-rw-r--r-- | src/pulse/introspect.c | 40 | ||||
-rw-r--r-- | src/pulse/introspect.h | 4 | ||||
-rw-r--r-- | src/pulse/proplist.h | 12 | ||||
-rw-r--r-- | src/pulse/pulseaudio.h | 1 | ||||
-rw-r--r-- | src/pulse/sample.c | 2 | ||||
-rw-r--r-- | src/pulse/stream.c | 152 | ||||
-rw-r--r-- | src/pulse/stream.h | 14 |
11 files changed, 814 insertions, 29 deletions
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 |