On Wed, 2013-12-18 at 19:28 +0200, Tanu Kaskinen wrote:
> Changes in v2:
> - Support clients that want to set the stream volume while at the
> same leaving the stream channel map unspecified.
> - Support setting mono volume even if the stream has multiple
> channels (the volume is copied to all channels).
> - Constify some format info related function parameters.
> - More error logging.
> - Removed sample spec and channel map validity checking from
> pa_format_info_from_sample_spec2(). It doesn't really make sense
> to prepare everywhere for invalid sample specs and channel maps.
> Preparing for that should be a special occasion, such as
> receiving those structs directly from an untrusted source (i.e.
> clients).
> - Removed a volume validity check from create_stream() in
> pulse/stream.c.
In case someone is interested, I attached the diff between v1 and v2.
--
Tanu
diff --git a/src/Makefile.am b/src/Makefile.am
index 7b544a9..f625326 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -890,6 +890,7 @@ libpulsecore_@PA_MAJORMINOR@_la_SOURCES = \
pulsecore/remap_mmx.c pulsecore/remap_sse.c \
pulsecore/resampler.c pulsecore/resampler.h \
pulsecore/rtpoll.c pulsecore/rtpoll.h \
+ pulsecore/stream-util.c pulsecore/stream-util.h \
pulsecore/mix.c pulsecore/mix.h \
pulsecore/cpu.h \
pulsecore/cpu-arm.c pulsecore/cpu-arm.h \
diff --git a/src/map-file b/src/map-file
index fbad1a4..8283a03 100644
--- a/src/map-file
+++ b/src/map-file
@@ -321,6 +321,7 @@ pa_stream_set_started_callback;
pa_stream_set_state_callback;
pa_stream_set_suspended_callback;
pa_stream_set_underflow_callback;
+pa_stream_set_volume_channel_map;
pa_stream_set_write_callback;
pa_stream_trigger;
pa_stream_unref;
diff --git a/src/pulse/format.c b/src/pulse/format.c
index 9b42cdb..729c2d2 100644
--- a/src/pulse/format.c
+++ b/src/pulse/format.c
@@ -171,7 +171,7 @@ error:
goto out;
}
-int pa_format_info_is_compatible(pa_format_info *first, pa_format_info *second) {
+int pa_format_info_is_compatible(const pa_format_info *first, const pa_format_info *second) {
const char *key;
void *state = NULL;
@@ -194,7 +194,7 @@ int pa_format_info_is_compatible(pa_format_info *first, pa_format_info *second)
return true;
}
-pa_format_info* pa_format_info_from_sample_spec(pa_sample_spec *ss, pa_channel_map *map) {
+pa_format_info* pa_format_info_from_sample_spec(const pa_sample_spec *ss, const pa_channel_map *map) {
char cm[PA_CHANNEL_MAP_SNPRINT_MAX];
pa_format_info *f;
@@ -217,7 +217,7 @@ pa_format_info* pa_format_info_from_sample_spec(pa_sample_spec *ss, pa_channel_m
}
/* For PCM streams */
-int pa_format_info_to_sample_spec(pa_format_info *f, pa_sample_spec *ss, pa_channel_map *map) {
+int pa_format_info_to_sample_spec(const pa_format_info *f, pa_sample_spec *ss, pa_channel_map *map) {
pa_assert(f);
pa_assert(ss);
@@ -236,7 +236,7 @@ int pa_format_info_to_sample_spec(pa_format_info *f, pa_sample_spec *ss, pa_chan
return 0;
}
-pa_prop_type_t pa_format_info_get_prop_type(pa_format_info *f, const char *key) {
+pa_prop_type_t pa_format_info_get_prop_type(const pa_format_info *f, const char *key) {
const char *str;
json_object *o, *o1;
pa_prop_type_t type;
@@ -310,7 +310,7 @@ pa_prop_type_t pa_format_info_get_prop_type(pa_format_info *f, const char *key)
return type;
}
-int pa_format_info_get_prop_int(pa_format_info *f, const char *key, int *v) {
+int pa_format_info_get_prop_int(const pa_format_info *f, const char *key, int *v) {
const char *str;
json_object *o;
@@ -323,10 +323,13 @@ int pa_format_info_get_prop_int(pa_format_info *f, const char *key, int *v) {
return -PA_ERR_NOENTITY;
o = json_tokener_parse(str);
- if (is_error(o))
+ if (is_error(o)) {
+ pa_log_debug("Failed to parse format info property '%s'.", key);
return -PA_ERR_INVALID;
+ }
if (json_object_get_type(o) != json_type_int) {
+ pa_log_debug("Format info property '%s' type is not int.", key);
json_object_put(o);
return -PA_ERR_INVALID;
}
@@ -337,7 +340,7 @@ int pa_format_info_get_prop_int(pa_format_info *f, const char *key, int *v) {
return 0;
}
-int pa_format_info_get_prop_int_range(pa_format_info *f, const char *key, int *min, int *max) {
+int pa_format_info_get_prop_int_range(const pa_format_info *f, const char *key, int *min, int *max) {
const char *str;
json_object *o, *o1;
int ret = -PA_ERR_INVALID;
@@ -352,8 +355,10 @@ int pa_format_info_get_prop_int_range(pa_format_info *f, const char *key, int *m
return -PA_ERR_NOENTITY;
o = json_tokener_parse(str);
- if (is_error(o))
+ if (is_error(o)) {
+ pa_log_debug("Failed to parse format info property '%s'.", key);
return -PA_ERR_INVALID;
+ }
if (json_object_get_type(o) != json_type_object)
goto out;
@@ -373,11 +378,14 @@ int pa_format_info_get_prop_int_range(pa_format_info *f, const char *key, int *m
ret = 0;
out:
+ if (ret < 0)
+ pa_log_debug("Format info property '%s' is not a valid int range.", key);
+
json_object_put(o);
return ret;
}
-int pa_format_info_get_prop_int_array(pa_format_info *f, const char *key, int **values, int *n_values) {
+int pa_format_info_get_prop_int_array(const pa_format_info *f, const char *key, int **values, int *n_values) {
const char *str;
json_object *o, *o1;
int i, ret = -PA_ERR_INVALID;
@@ -392,8 +400,10 @@ int pa_format_info_get_prop_int_array(pa_format_info *f, const char *key, int **
return -PA_ERR_NOENTITY;
o = json_tokener_parse(str);
- if (is_error(o))
+ if (is_error(o)) {
+ pa_log_debug("Failed to parse format info property '%s'.", key);
return -PA_ERR_INVALID;
+ }
if (json_object_get_type(o) != json_type_array)
goto out;
@@ -416,11 +426,14 @@ int pa_format_info_get_prop_int_array(pa_format_info *f, const char *key, int **
ret = 0;
out:
+ if (ret < 0)
+ pa_log_debug("Format info property '%s' is not a valid int array.", key);
+
json_object_put(o);
return ret;
}
-int pa_format_info_get_prop_string(pa_format_info *f, const char *key, char **v) {
+int pa_format_info_get_prop_string(const pa_format_info *f, const char *key, char **v) {
const char *str = NULL;
json_object *o;
@@ -433,10 +446,13 @@ int pa_format_info_get_prop_string(pa_format_info *f, const char *key, char **v)
return -PA_ERR_NOENTITY;
o = json_tokener_parse(str);
- if (is_error(o))
+ if (is_error(o)) {
+ pa_log_debug("Failed to parse format info property '%s'.", key);
return -PA_ERR_INVALID;
+ }
if (json_object_get_type(o) != json_type_string) {
+ pa_log_debug("Format info property '%s' type is not string.", key);
json_object_put(o);
return -PA_ERR_INVALID;
}
@@ -447,7 +463,7 @@ int pa_format_info_get_prop_string(pa_format_info *f, const char *key, char **v)
return 0;
}
-int pa_format_info_get_prop_string_array(pa_format_info *f, const char *key, char ***values, int *n_values) {
+int pa_format_info_get_prop_string_array(const pa_format_info *f, const char *key, char ***values, int *n_values) {
const char *str;
json_object *o, *o1;
int i, ret = -PA_ERR_INVALID;
@@ -462,8 +478,10 @@ int pa_format_info_get_prop_string_array(pa_format_info *f, const char *key, cha
return -PA_ERR_NOENTITY;
o = json_tokener_parse(str);
- if (is_error(o))
+ if (is_error(o)) {
+ pa_log_debug("Failed to parse format info property '%s'.", key);
return -PA_ERR_INVALID;
+ }
if (json_object_get_type(o) != json_type_array)
goto out;
@@ -486,6 +504,9 @@ int pa_format_info_get_prop_string_array(pa_format_info *f, const char *key, cha
ret = 0;
out:
+ if (ret < 0)
+ pa_log_debug("Format info property '%s' is not a valid string array.", key);
+
json_object_put(o);
return ret;
}
diff --git a/src/pulse/format.h b/src/pulse/format.h
index abd21fb..7284642 100644
--- a/src/pulse/format.h
+++ b/src/pulse/format.h
@@ -114,7 +114,7 @@ int pa_format_info_is_pcm(const pa_format_info *f);
* 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. \since 1.0 */
-int pa_format_info_is_compatible(pa_format_info *first, pa_format_info *second);
+int pa_format_info_is_compatible(const pa_format_info *first, const pa_format_info *second);
/** Maximum required string length for
* pa_format_info_snprint(). Please note that this value can change
@@ -142,14 +142,14 @@ pa_format_info* pa_format_info_from_string(const char *str);
* channel map will be left unspecified, allowing the server to choose it.
*
* \since 2.0 */
-pa_format_info* pa_format_info_from_sample_spec(pa_sample_spec *ss, pa_channel_map *map);
+pa_format_info* pa_format_info_from_sample_spec(const pa_sample_spec *ss, const pa_channel_map *map);
/** Utility function to generate a \a pa_sample_spec and \a pa_channel_map corresponding to a given \a pa_format_info. The
* conversion for PCM formats is straight-forward. For non-PCM formats, if there is a fixed size-time conversion (i.e. all
* IEC61937-encapsulated formats), a "fake" sample spec whose size-time conversion corresponds to this format is provided and
* the channel map argument is ignored. For formats with variable size-time conversion, this function will fail. Returns a
* negative integer if conversion failed and 0 on success. \since 2.0 */
-int pa_format_info_to_sample_spec(pa_format_info *f, pa_sample_spec *ss, pa_channel_map *map);
+int pa_format_info_to_sample_spec(const pa_format_info *f, pa_sample_spec *ss, pa_channel_map *map);
/** Represents the type of value type of a property on a \ref pa_format_info. \since 2.0 */
typedef enum pa_prop_type_t {
@@ -182,24 +182,24 @@ typedef enum pa_prop_type_t {
/** \endcond */
/** Gets the type of property \a key in a given \ref pa_format_info. \since 2.0 */
-pa_prop_type_t pa_format_info_get_prop_type(pa_format_info *f, const char *key);
+pa_prop_type_t pa_format_info_get_prop_type(const pa_format_info *f, const char *key);
/** Gets an integer property from the given format info. Returns 0 on success and a negative integer on failure. \since 2.0 */
-int pa_format_info_get_prop_int(pa_format_info *f, const char *key, int *v);
+int pa_format_info_get_prop_int(const pa_format_info *f, const char *key, int *v);
/** Gets an integer range property from the given format info. Returns 0 on success and a negative integer on failure.
* \since 2.0 */
-int pa_format_info_get_prop_int_range(pa_format_info *f, const char *key, int *min, int *max);
+int pa_format_info_get_prop_int_range(const pa_format_info *f, const char *key, int *min, int *max);
/** Gets an integer array property from the given format info. \a values contains the values and \a n_values contains the
* number of elements. The caller must free \a values using \ref pa_xfree. Returns 0 on success and a negative integer on
* failure. \since 2.0 */
-int pa_format_info_get_prop_int_array(pa_format_info *f, const char *key, int **values, int *n_values);
+int pa_format_info_get_prop_int_array(const pa_format_info *f, const char *key, int **values, int *n_values);
/** Gets a string property from the given format info. The caller must free the returned string using \ref pa_xfree. Returns
* 0 on success and a negative integer on failure. \since 2.0 */
-int pa_format_info_get_prop_string(pa_format_info *f, const char *key, char **v);
+int pa_format_info_get_prop_string(const pa_format_info *f, const char *key, char **v);
/** Gets a string array property from the given format info. \a values contains the values and \a n_values contains
* the number of elements. The caller must free \a values using \ref pa_format_info_free_string_array. Returns 0 on success and
* a negative integer on failure. \since 2.0 */
-int pa_format_info_get_prop_string_array(pa_format_info *f, const char *key, char ***values, int *n_values);
+int pa_format_info_get_prop_string_array(const pa_format_info *f, const char *key, char ***values, int *n_values);
/** Frees a string array returned by \ref pa_format_info_get_prop_string_array. \since 2.0 */
void pa_format_info_free_string_array(char **values, int n_values);
diff --git a/src/pulse/internal.h b/src/pulse/internal.h
index c5084d5..1577e8c 100644
--- a/src/pulse/internal.h
+++ b/src/pulse/internal.h
@@ -144,6 +144,7 @@ struct pa_stream {
pa_sample_spec sample_spec;
pa_channel_map channel_map;
+ bool channel_map_is_set;
uint8_t n_formats;
pa_format_info *req_formats[PA_MAX_FORMATS];
pa_format_info *format;
diff --git a/src/pulse/stream.c b/src/pulse/stream.c
index d376326..5b0cce7 100644
--- a/src/pulse/stream.c
+++ b/src/pulse/stream.c
@@ -102,7 +102,7 @@ static pa_stream *pa_stream_new_with_proplist_internal(
PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED);
PA_CHECK_VALIDITY_RETURN_NULL(c, name || (p && pa_proplist_contains(p, PA_PROP_MEDIA_NAME)), PA_ERR_INVALID);
- s = pa_xnew(pa_stream, 1);
+ s = pa_xnew0(pa_stream, 1);
PA_REFCNT_INIT(s);
s->context = c;
s->mainloop = c->mainloop;
@@ -116,9 +116,10 @@ static pa_stream *pa_stream_new_with_proplist_internal(
else
pa_sample_spec_init(&s->sample_spec);
- if (map)
+ if (map) {
s->channel_map = *map;
- else
+ s->channel_map_is_set = true;
+ } else
pa_channel_map_init(&s->channel_map);
s->n_formats = 0;
@@ -913,6 +914,27 @@ finish:
pa_context_unref(c);
}
+int pa_stream_set_volume_channel_map(pa_stream *s, const pa_channel_map *map) {
+ pa_assert(s);
+ pa_assert(map);
+ pa_assert(pa_channel_map_valid(map));
+
+ if (s->state != PA_STREAM_UNCONNECTED) {
+ pa_log_debug("Stream state is not unconnected.");
+ return -PA_ERR_BADSTATE;
+ }
+
+ if (s->channel_map_is_set && !pa_channel_map_equal(map, &s->channel_map)) {
+ pa_log_debug("Channel map is already set for the stream, changing it is not allowed.");
+ return -PA_ERR_INVALID;
+ }
+
+ s->channel_map = *map;
+ s->channel_map_is_set = true;
+
+ return 0;
+}
+
static void invalidate_indexes(pa_stream *s, bool r, bool w) {
pa_assert(s);
pa_assert(PA_REFCNT_VALUE(s) >= 1);
@@ -1224,7 +1246,6 @@ static int create_stream(
* client development easier */
PA_CHECK_VALIDITY(s->context, direction == PA_STREAM_RECORD || !(flags & (PA_STREAM_PEAK_DETECT)), PA_ERR_INVALID);
- PA_CHECK_VALIDITY(s->context, !volume || s->n_formats || (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);
diff --git a/src/pulse/stream.h b/src/pulse/stream.h
index 9009fba..f80dd7b 100644
--- a/src/pulse/stream.h
+++ b/src/pulse/stream.h
@@ -423,6 +423,37 @@ int pa_stream_is_suspended(pa_stream *s);
* not, and a negative value on error. \since 0.9.11 */
int pa_stream_is_corked(pa_stream *s);
+/** Set the volume channel map. The stream volume is passed to
+ * pa_stream_connect_playback() or pa_stream_connect_record(), but those
+ * functions don't have a parameter for specifying the channel map for the
+ * volume. That's usually not an issue, because normally when creating the
+ * stream object, the stream channel map is passed already at that time.
+ * However, with pa_stream_new_extended() it's possible that no channel map is
+ * specified, in case the client wants the server to decide the stream channel
+ * map. In that situation the server still needs to know how the client
+ * intended the volume to be interpreted, i.e. the server needs a channel map
+ * for the volume (unless the volume has only one channel, in which case that
+ * volume is applied to all channels and no channel map information is needed
+ * from the client).
+ *
+ * This function exists to handle the specific case where all these conditions
+ * apply (in other cases this function does not need to be called):
+ *
+ * 1) The client creates the stream using pa_stream_new_extended().
+ * 2) The client doesn't specify a channel map for the stream (so the server
+ * will decide the stream channel map).
+ * 3) The client specifies a volume for the stream.
+ * 4) The specified volume has more than one channel.
+ *
+ * This function must be called before calling pa_stream_connect_playback() or
+ * pa_stream_connect_record(). Returns a negative error code on failure.
+ *
+ * \since 5.0 */
+int pa_stream_set_volume_channel_map(
+ pa_stream *s, /**< The stream for which to set the volume channel map. */
+ const pa_channel_map *map /**< The volume channel map. */
+);
+
/** Connect the stream to a sink. It is strongly recommended to pass
* NULL in both \a dev and \a volume and not to set either
* PA_STREAM_START_MUTED nor PA_STREAM_START_UNMUTED -- unless these
@@ -440,7 +471,16 @@ int pa_stream_is_corked(pa_stream *s);
* an absolute device volume. Since 0.9.20 it is an absolute volume when
* the sink is in flat volume mode, and relative otherwise, thus
* making sure the volume passed here has always the same semantics as
- * the volume passed to pa_context_set_sink_input_volume(). */
+ * the volume passed to pa_context_set_sink_input_volume().
+ *
+ * Since 5.0, it's possible to specify a single-channel volume even if the
+ * stream has multiple channels. In that case the same volume is applied to all
+ * channels.
+ *
+ * If you create the stream with pa_stream_new_extended() and you don't specify
+ * the channel map for the stream, and you specify a multi-channel volume here,
+ * then you have to use pa_stream_set_volume_channel_map() so that the server
+ * can figure out what your multi-channel volume actually means. */
int pa_stream_connect_playback(
pa_stream *s /**< The stream to connect to a sink */,
const char *dev /**< Name of the sink to connect to, or NULL for default */ ,
diff --git a/src/pulsecore/core-format.c b/src/pulsecore/core-format.c
index 34976b7..f74dc79 100644
--- a/src/pulsecore/core-format.c
+++ b/src/pulsecore/core-format.c
@@ -28,7 +28,7 @@
#include <pulsecore/macro.h>
-int pa_format_info_get_sample_format(pa_format_info *f, pa_sample_format_t *sf) {
+int pa_format_info_get_sample_format(const pa_format_info *f, pa_sample_format_t *sf) {
int r;
char *sf_str;
pa_sample_format_t sf_local;
@@ -44,15 +44,17 @@ int pa_format_info_get_sample_format(pa_format_info *f, pa_sample_format_t *sf)
sf_local = pa_parse_sample_format(sf_str);
pa_xfree(sf_str);
- if (!pa_sample_format_valid(sf_local))
+ if (!pa_sample_format_valid(sf_local)) {
+ pa_log_debug("Invalid sample format.");
return -PA_ERR_INVALID;
+ }
*sf = sf_local;
return 0;
}
-int pa_format_info_get_rate(pa_format_info *f, uint32_t *rate) {
+int pa_format_info_get_rate(const pa_format_info *f, uint32_t *rate) {
int r;
int rate_local;
@@ -64,15 +66,17 @@ int pa_format_info_get_rate(pa_format_info *f, uint32_t *rate) {
if (r < 0)
return r;
- if (!pa_sample_rate_valid(rate_local))
+ if (!pa_sample_rate_valid(rate_local)) {
+ pa_log_debug("Invalid sample rate: %i", rate_local);
return -PA_ERR_INVALID;
+ }
*rate = rate_local;
return 0;
}
-int pa_format_info_get_channels(pa_format_info *f, uint8_t *channels) {
+int pa_format_info_get_channels(const pa_format_info *f, uint8_t *channels) {
int r;
int channels_local;
@@ -84,15 +88,17 @@ int pa_format_info_get_channels(pa_format_info *f, uint8_t *channels) {
if (r < 0)
return r;
- if (!pa_channels_valid(channels_local))
+ if (!pa_channels_valid(channels_local)) {
+ pa_log_debug("Invalid channel count: %i", channels_local);
return -PA_ERR_INVALID;
+ }
*channels = channels_local;
return 0;
}
-int pa_format_info_get_channel_map(pa_format_info *f, pa_channel_map *map) {
+int pa_format_info_get_channel_map(const pa_format_info *f, pa_channel_map *map) {
int r;
char *map_str;
@@ -107,8 +113,10 @@ int pa_format_info_get_channel_map(pa_format_info *f, pa_channel_map *map) {
map = pa_channel_map_parse(map, map_str);
pa_xfree(map_str);
- if (!map)
+ if (!map) {
+ pa_log_debug("Failed to parse channel map.");
return -PA_ERR_INVALID;
+ }
return 0;
}
@@ -122,32 +130,20 @@ pa_format_info *pa_format_info_from_sample_spec2(const pa_sample_spec *ss, const
format = pa_format_info_new();
format->encoding = PA_ENCODING_PCM;
- if (set_format) {
- if (!pa_sample_format_valid(ss->format))
- goto fail;
-
+ if (set_format)
pa_format_info_set_sample_format(format, ss->format);
- }
-
- if (set_rate) {
- if (!pa_sample_rate_valid(ss->rate))
- goto fail;
+ if (set_rate)
pa_format_info_set_rate(format, ss->rate);
- }
if (set_channels) {
- if (!pa_channels_valid(ss->channels))
- goto fail;
-
pa_format_info_set_channels(format, ss->channels);
if (map) {
- if (!pa_channel_map_valid(map))
- goto fail;
-
- if (map->channels != ss->channels)
+ if (map->channels != ss->channels) {
+ pa_log_debug("Channel map is incompatible with the sample spec.");
goto fail;
+ }
pa_format_info_set_channel_map(format, map);
}
@@ -162,8 +158,8 @@ fail:
return NULL;
}
-int pa_format_info_to_sample_spec2(pa_format_info *f, pa_sample_spec *ss, pa_channel_map *map, pa_sample_spec *fallback_ss,
- pa_channel_map *fallback_map) {
+int pa_format_info_to_sample_spec2(const pa_format_info *f, pa_sample_spec *ss, pa_channel_map *map,
+ const pa_sample_spec *fallback_ss, const pa_channel_map *fallback_map) {
int r, r2;
pa_sample_spec ss_local;
pa_channel_map map_local;
@@ -207,8 +203,10 @@ int pa_format_info_to_sample_spec2(pa_format_info *f, pa_sample_spec *ss, pa_cha
pa_assert(pa_channels_valid(ss_local.channels));
- if (r2 >= 0 && map_local.channels != ss_local.channels)
+ if (r2 >= 0 && map_local.channels != ss_local.channels) {
+ pa_log_debug("Channel map is not compatible with the sample spec.");
return -PA_ERR_INVALID;
+ }
if (r2 == -PA_ERR_NOENTITY) {
if (fallback_map->channels == ss_local.channels)
@@ -227,7 +225,7 @@ int pa_format_info_to_sample_spec2(pa_format_info *f, pa_sample_spec *ss, pa_cha
return 0;
}
-int pa_format_info_to_sample_spec_fake(pa_format_info *f, pa_sample_spec *ss, pa_channel_map *map) {
+int pa_format_info_to_sample_spec_fake(const pa_format_info *f, pa_sample_spec *ss, pa_channel_map *map) {
int rate;
pa_assert(f);
diff --git a/src/pulsecore/core-format.h b/src/pulsecore/core-format.h
index ce3923e..f2fa12e 100644
--- a/src/pulsecore/core-format.h
+++ b/src/pulsecore/core-format.h
@@ -27,22 +27,22 @@
/* Gets the sample format stored in the format info. Returns a negative error
* code on failure. If the sample format property is not set at all, returns
* -PA_ERR_NOENTITY. */
-int pa_format_info_get_sample_format(pa_format_info *f, pa_sample_format_t *sf);
+int pa_format_info_get_sample_format(const pa_format_info *f, pa_sample_format_t *sf);
/* Gets the sample rate stored in the format info. Returns a negative error
* code on failure. If the sample rate property is not set at all, returns
* -PA_ERR_NOENTITY. */
-int pa_format_info_get_rate(pa_format_info *f, uint32_t *rate);
+int pa_format_info_get_rate(const pa_format_info *f, uint32_t *rate);
/* Gets the channel count stored in the format info. Returns a negative error
* code on failure. If the channels property is not set at all, returns
* -PA_ERR_NOENTITY. */
-int pa_format_info_get_channels(pa_format_info *f, uint8_t *channels);
+int pa_format_info_get_channels(const pa_format_info *f, uint8_t *channels);
/* Gets the channel map stored in the format info. Returns a negative error
* code on failure. If the channel map property is not set at all, returns
* -PA_ERR_NOENTITY. */
-int pa_format_info_get_channel_map(pa_format_info *f, pa_channel_map *map);
+int pa_format_info_get_channel_map(const pa_format_info *f, pa_channel_map *map);
/* Convert a sample spec and an optional channel map to a new PCM format info
* object (remember to free it). If map is NULL, then the channel map will be
@@ -69,13 +69,13 @@ pa_format_info *pa_format_info_from_sample_spec2(const pa_sample_spec *ss, const
* a fallback sample spec and channel map. That functionality can't be added to
* the original function, because the function is part of the public API and
* adding parameters to it would break the API. */
-int pa_format_info_to_sample_spec2(pa_format_info *f, pa_sample_spec *ss, pa_channel_map *map, pa_sample_spec *fallback_ss,
- pa_channel_map *fallback_map);
+int pa_format_info_to_sample_spec2(const pa_format_info *f, pa_sample_spec *ss, pa_channel_map *map,
+ const pa_sample_spec *fallback_ss, const pa_channel_map *fallback_map);
/* For compressed formats. Converts the format info into a sample spec and a
* channel map that an ALSA device can use as its configuration parameters when
* playing back the compressed data. That is, the returned sample spec doesn't
* describe the audio content, but the device parameters. */
-int pa_format_info_to_sample_spec_fake(pa_format_info *f, pa_sample_spec *ss, pa_channel_map *map);
+int pa_format_info_to_sample_spec_fake(const pa_format_info *f, pa_sample_spec *ss, pa_channel_map *map);
#endif
diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c
index 439b860..a7f90bc 100644
--- a/src/pulsecore/sink-input.c
+++ b/src/pulsecore/sink-input.c
@@ -34,6 +34,7 @@
#include <pulsecore/core-format.h>
#include <pulsecore/mix.h>
+#include <pulsecore/stream-util.h>
#include <pulsecore/core-subscribe.h>
#include <pulsecore/log.h>
#include <pulsecore/play-memblockq.h>
@@ -287,6 +288,7 @@ int pa_sink_input_new(
pa_sink_input *i;
pa_resampler *resampler = NULL;
char st[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX], fmt[PA_FORMAT_INFO_SNPRINT_MAX];
+ pa_channel_map volume_map;
int r;
char *pt;
char *memblockq_name;
@@ -358,6 +360,17 @@ int pa_sink_input_new(
return -PA_ERR_NOTSUPPORTED;
}
+ if (data->volume_is_set && pa_format_info_is_pcm(data->format)) {
+ /* If volume is set, we need to save the original data->channel_map,
+ * so that we can remap the volume from the original channel map to the
+ * final channel map of the stream in case data->channel_map gets
+ * modified in pa_format_info_to_sample_spec2(). */
+ r = pa_stream_get_volume_channel_map(&data->volume, data->channel_map_is_set ? &data->channel_map : NULL, data->format, &volume_map);
+
+ if (r < 0)
+ return r;
+ }
+
/* Now populate the sample spec and channel map according to the final
* format that we've negotiated */
r = pa_format_info_to_sample_spec2(data->format, &data->sample_spec, &data->channel_map, &data->sink->sample_spec,
@@ -388,7 +401,10 @@ int pa_sink_input_new(
if (!data->volume_writable)
data->save_volume = false;
- pa_return_val_if_fail(pa_cvolume_compatible(&data->volume, &data->sample_spec), -PA_ERR_INVALID);
+ if (data->volume_is_set)
+ /* The original volume channel map may be different than the final
+ * stream channel map, so remapping may be needed. */
+ pa_cvolume_remap(&data->volume, &volume_map, &data->channel_map);
if (!data->muted_is_set)
data->muted = false;
diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c
index b7637df..575b56b 100644
--- a/src/pulsecore/source-output.c
+++ b/src/pulsecore/source-output.c
@@ -34,6 +34,7 @@
#include <pulsecore/core-format.h>
#include <pulsecore/mix.h>
+#include <pulsecore/stream-util.h>
#include <pulsecore/core-subscribe.h>
#include <pulsecore/log.h>
#include <pulsecore/namereg.h>
@@ -222,6 +223,7 @@ int pa_source_output_new(
pa_source_output *o;
pa_resampler *resampler = NULL;
char st[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX], fmt[PA_FORMAT_INFO_SNPRINT_MAX];
+ pa_channel_map volume_map;
int r;
char *pt;
@@ -298,6 +300,17 @@ int pa_source_output_new(
return -PA_ERR_NOTSUPPORTED;
}
+ if (data->volume_is_set && pa_format_info_is_pcm(data->format)) {
+ /* If volume is set, we need to save the original data->channel_map,
+ * so that we can remap the volume from the original channel map to the
+ * final channel map of the stream in case data->channel_map gets
+ * modified in pa_format_info_to_sample_spec2(). */
+ r = pa_stream_get_volume_channel_map(&data->volume, data->channel_map_is_set ? &data->channel_map : NULL, data->format, &volume_map);
+
+ if (r < 0)
+ return r;
+ }
+
/* Now populate the sample spec and channel map according to the final
* format that we've negotiated */
r = pa_format_info_to_sample_spec2(data->format, &data->sample_spec, &data->channel_map, &data->source->sample_spec,
@@ -324,7 +337,10 @@ int pa_source_output_new(
if (!data->volume_writable)
data->save_volume = false;
- pa_return_val_if_fail(pa_cvolume_compatible(&data->volume, &data->sample_spec), -PA_ERR_INVALID);
+ if (data->volume_is_set)
+ /* The original volume channel map may be different than the final
+ * stream channel map, so remapping may be needed. */
+ pa_cvolume_remap(&data->volume, &volume_map, &data->channel_map);
if (!data->volume_factor_is_set)
pa_cvolume_reset(&data->volume_factor, data->sample_spec.channels);
diff --git a/src/pulsecore/stream-util.c b/src/pulsecore/stream-util.c
new file mode 100644
index 0000000..66def0c
--- /dev/null
+++ b/src/pulsecore/stream-util.c
@@ -0,0 +1,87 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2013 Intel Corporation
+
+ 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 "stream-util.h"
+
+#include <pulse/def.h>
+
+#include <pulsecore/core-format.h>
+#include <pulsecore/macro.h>
+
+int pa_stream_get_volume_channel_map(const pa_cvolume *volume, const pa_channel_map *original_map, const pa_format_info *format,
+ pa_channel_map *volume_map) {
+ int r;
+ pa_channel_map volume_map_local;
+
+ pa_assert(volume);
+ pa_assert(format);
+ pa_assert(volume_map);
+
+ if (original_map) {
+ if (volume->channels == original_map->channels) {
+ *volume_map = *original_map;
+ return 0;
+ }
+
+ if (volume->channels == 1) {
+ pa_channel_map_init_mono(volume_map);
+ return 0;
+ }
+
+ pa_log_info("Invalid stream parameters: the volume is incompatible with the channel map.");
+ return -PA_ERR_INVALID;
+ }
+
+ r = pa_format_info_get_channel_map(format, &volume_map_local);
+
+ if (r == -PA_ERR_NOENTITY) {
+ if (volume->channels == 1) {
+ pa_channel_map_init_mono(volume_map);
+ return 0;
+ }
+
+ pa_log_info("Invalid stream parameters: multi-channel volume is set, but channel map is not.");
+ return -PA_ERR_INVALID;
+ }
+
+ if (r < 0) {
+ pa_log_info("Invalid channel map.");
+ return -PA_ERR_INVALID;
+ }
+
+ if (volume->channels == volume_map_local.channels) {
+ *volume_map = volume_map_local;
+ return 0;
+ }
+
+ if (volume->channels == 1) {
+ pa_channel_map_init_mono(volume_map);
+ return 0;
+ }
+
+ pa_log_info("Invalid stream parameters: the volume is incompatible with the channel map.");
+
+ return -PA_ERR_INVALID;
+}
diff --git a/src/pulsecore/stream-util.h b/src/pulsecore/stream-util.h
new file mode 100644
index 0000000..fd22ab3
--- /dev/null
+++ b/src/pulsecore/stream-util.h
@@ -0,0 +1,50 @@
+#ifndef foostreamutilhfoo
+#define foostreamutilhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2013 Intel Corporation
+
+ 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/format.h>
+#include <pulse/volume.h>
+
+/* This is a helper function that is called from pa_sink_input_new() and
+ * pa_source_output_new(). The job of this function is to figure out what
+ * channel map should be used for interpreting the volume that was set for the
+ * stream. The channel map that the client intended for the volume may be
+ * different than the final stream channel map, because the client may want the
+ * server to decide the stream channel map.
+ *
+ * volume is the volume for which the channel map should be figured out.
+ *
+ * original_map is the channel map that is set in the new data struct's
+ * channel_map field. If the channel map hasn't been set in the new data, then
+ * original_map should be NULL.
+ *
+ * format is the negotiated format for the stream. It's used as a fallback if
+ * original_map is not available.
+ *
+ * On success, the result is saved in volume_map. It's possible that this
+ * function fails to figure out the right channel map for the volume, in which
+ * case a negative error code is returned. */
+int pa_stream_get_volume_channel_map(const pa_cvolume *volume, const pa_channel_map *original_map, const pa_format_info *format,
+ pa_channel_map *volume_map);
+
+#endif
_______________________________________________
pulseaudio-discuss mailing list
[email protected]
http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss