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

Reply via email to