pa_node_put() will tell the router module, if there is one loaded, that a new node is being created. The router module can then set the initial routing for the new node. To set the initial routing for a stream, the device pointer needs to be set and the format needs to be negotiated. This patch modifies pa_sink_input_new() and pa_source_output_new() so that those functions handle it correctly if i->sink and i->format (or o->source and o->format) get changed during pa_node_put(). After the complex changes, the rest of the changes are only about replacing data->sink with i->sink, data->format with i->format etc. These changes are not mandatory, because the new data is kept in sync with the final routing, but once i->sink has been set to its final value, I think it's cleaner to only use i->sink in the rest of the function. --- src/pulsecore/sink-input.c | 135 ++++++++++++++++++++++++----------------- src/pulsecore/source-output.c | 138 ++++++++++++++++++++++++------------------ 2 files changed, 159 insertions(+), 114 deletions(-) diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c index 0469a15..d045402 100644 --- a/src/pulsecore/sink-input.c +++ b/src/pulsecore/sink-input.c @@ -291,7 +291,7 @@ int pa_sink_input_new( pa_sink_input *i = NULL; pa_resampler *resampler = NULL; - char st[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX], fmt[PA_FORMAT_INFO_SNPRINT_MAX]; + char st[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX]; pa_channel_map original_cm; int r; char *pt; @@ -345,8 +345,28 @@ int pa_sink_input_new( goto fail; } + /* If something didn't pick a format for us, pick the top-most format since + * we assume this is sorted in priority order */ + if (!data->format && data->nego_formats && !pa_idxset_isempty(data->nego_formats)) + data->format = pa_format_info_copy(pa_idxset_first(data->nego_formats, NULL)); + + /* If the sink has been set, then also the format should have been + * negotiated. If the sink hasn't been set, no format negotiation should + * have happened either. */ + pa_assert((data->sink && data->format) || (!data->sink && !data->format)); + + /* From now on, routing will be done with the sink input object instead of + * the new data, because the routing code that is in pa_node_put() doesn't + * have access to the new data. Therefore, let's copy data->req_formats, + * data->format and data->sink to i, because the routing code needs access + * to those. */ i->req_formats = pa_idxset_copy(data->req_formats, (pa_copy_func_t) pa_format_info_copy); + if (data->format) + i->format = pa_format_info_copy(data->format); + + i->sink = data->sink; + if (data->driver && !pa_utf8_valid(data->driver)) { ret = -PA_ERR_INVALID; goto fail; @@ -365,9 +385,10 @@ int pa_sink_input_new( i->node->owner = i; + /* This may update i->sink and i->format. */ pa_node_put(i->node); - if (!data->sink) { + if (!i->sink) { pa_sink *sink = pa_namereg_get(core, NULL, PA_NAMEREG_SINK); if (!sink) { @@ -375,61 +396,65 @@ int pa_sink_input_new( goto fail; } - pa_sink_input_new_data_set_sink(data, sink, false); - } + r = pa_sink_input_set_initial_sink(i, sink); - /* Routing's done, we have a sink. Now let's fix the format and set up the - * sample spec */ - - /* If something didn't pick a format for us, pick the top-most format since - * we assume this is sorted in priority order */ - if (!data->format && data->nego_formats && !pa_idxset_isempty(data->nego_formats)) - data->format = pa_format_info_copy(pa_idxset_first(data->nego_formats, NULL)); + if (r < 0) { + pa_log_info("Format negotiation between sink input \"%s\" and the default sink (%s) failed.", + pa_sink_input_get_description(i), sink->name); + ret = r; + goto fail; + } + } - if (PA_LIKELY(data->format)) { - pa_log_debug("Negotiated format: %s", pa_format_info_snprint(fmt, sizeof(fmt), data->format)); - } else { - pa_format_info *format; - uint32_t idx; + pa_assert(i->sink); + pa_assert(i->format); + + /* Modules may want to look at the sink and format in the FIXATE hook, so + * let's make sure that the new data is in sync with the sink input. Also, + * pa_sink_input_new_data_is_passthrough() uses data->format too, and we + * call that function a few times. (XXX: It would probably be better to + * pass the sink input object to the FIXATE hook instead of the new data, + * and replace the pa_sink_input_new_data_is_passthrough() usage with + * something that uses the sink input object instead of the new data. Then + * this extra syncing could be removed.) */ + data->sink = i->sink; - pa_log_info("Sink does not support any requested format:"); - PA_IDXSET_FOREACH(format, data->req_formats, idx) - pa_log_info(" -- %s", pa_format_info_snprint(fmt, sizeof(fmt), format)); + if (data->format) + pa_format_info_free(data->format); - ret = -PA_ERR_NOTSUPPORTED; - goto fail; - } + data->format = pa_format_info_copy(i->format); - /* Now populate the sample spec and format according to the final - * format that we've negotiated */ + /* Routing's done, we have a sink and a format. Now populate the sample + * spec and channel map according to the final format that we've + * negotiated. */ - if (pa_format_info_to_sample_spec(data->format, &ss, &map) < 0) { + if (pa_format_info_to_sample_spec(i->format, &ss, &map) < 0) { ret = -PA_ERR_INVALID; goto fail; } pa_sink_input_new_data_set_sample_spec(data, &ss); - if (pa_format_info_is_pcm(data->format) && pa_channel_map_valid(&map)) + if (pa_format_info_is_pcm(i->format) && pa_channel_map_valid(&map)) pa_sink_input_new_data_set_channel_map(data, &map); - if (!PA_SINK_IS_LINKED(pa_sink_get_state(data->sink))) { + if (!PA_SINK_IS_LINKED(pa_sink_get_state(i->sink))) { ret = -PA_ERR_BADSTATE; goto fail; } - if (data->sync_base && !(data->sync_base->sink == data->sink && pa_sink_input_get_state(data->sync_base) == PA_SINK_INPUT_CORKED)) { + if (data->sync_base && !(data->sync_base->sink == i->sink && pa_sink_input_get_state(data->sync_base) == PA_SINK_INPUT_CORKED)) { ret = -PA_ERR_INVALID; goto fail; } - r = check_passthrough_connection(pa_sink_input_new_data_is_passthrough(data), data->sink); + r = check_passthrough_connection(pa_sink_input_new_data_is_passthrough(data), i->sink); if (r != PA_OK) { ret = r; goto fail; } if (!data->sample_spec_is_set) - data->sample_spec = data->sink->sample_spec; + data->sample_spec = i->sink->sample_spec; if (!pa_sample_spec_valid(&data->sample_spec)) { ret = -PA_ERR_INVALID; @@ -437,8 +462,8 @@ int pa_sink_input_new( } if (!data->channel_map_is_set) { - if (pa_channel_map_compatible(&data->sink->channel_map, &data->sample_spec)) - data->channel_map = data->sink->channel_map; + if (pa_channel_map_compatible(&i->sink->channel_map, &data->sample_spec)) + data->channel_map = i->sink->channel_map; else pa_channel_map_init_extend(&data->channel_map, data->sample_spec.channels, PA_CHANNEL_MAP_DEFAULT); } @@ -475,54 +500,54 @@ int pa_sink_input_new( data->muted = false; if (data->flags & PA_SINK_INPUT_FIX_FORMAT) { - if (!pa_format_info_is_pcm(data->format)) { + if (!pa_format_info_is_pcm(i->format)) { ret = -PA_ERR_INVALID; goto fail; } - data->sample_spec.format = data->sink->sample_spec.format; - pa_format_info_set_sample_format(data->format, data->sample_spec.format); + data->sample_spec.format = i->sink->sample_spec.format; + pa_format_info_set_sample_format(i->format, data->sample_spec.format); } if (data->flags & PA_SINK_INPUT_FIX_RATE) { - if (!pa_format_info_is_pcm(data->format)) { + if (!pa_format_info_is_pcm(i->format)) { ret = -PA_ERR_INVALID; goto fail; } - data->sample_spec.rate = data->sink->sample_spec.rate; - pa_format_info_set_rate(data->format, data->sample_spec.rate); + data->sample_spec.rate = i->sink->sample_spec.rate; + pa_format_info_set_rate(i->format, data->sample_spec.rate); } original_cm = data->channel_map; if (data->flags & PA_SINK_INPUT_FIX_CHANNELS) { - if (!pa_format_info_is_pcm(data->format)) { + if (!pa_format_info_is_pcm(i->format)) { ret = -PA_ERR_INVALID; goto fail; } - data->sample_spec.channels = data->sink->sample_spec.channels; - data->channel_map = data->sink->channel_map; - pa_format_info_set_channels(data->format, data->sample_spec.channels); - pa_format_info_set_channel_map(data->format, &data->channel_map); + data->sample_spec.channels = i->sink->sample_spec.channels; + data->channel_map = i->sink->channel_map; + pa_format_info_set_channels(i->format, data->sample_spec.channels); + pa_format_info_set_channel_map(i->format, &data->channel_map); } pa_assert(pa_sample_spec_valid(&data->sample_spec)); pa_assert(pa_channel_map_valid(&data->channel_map)); if (!(data->flags & PA_SINK_INPUT_VARIABLE_RATE) && - !pa_sample_spec_equal(&data->sample_spec, &data->sink->sample_spec)) { + !pa_sample_spec_equal(&data->sample_spec, &i->sink->sample_spec)) { /* try to change sink rate. This is done before the FIXATE hook since module-suspend-on-idle can resume a sink */ pa_log_info("Trying to change sample rate"); - if (pa_sink_update_rate(data->sink, data->sample_spec.rate, pa_sink_input_new_data_is_passthrough(data)) >= 0) - pa_log_info("Rate changed to %u Hz", data->sink->sample_spec.rate); + if (pa_sink_update_rate(i->sink, data->sample_spec.rate, pa_sink_input_new_data_is_passthrough(data)) >= 0) + pa_log_info("Rate changed to %u Hz", i->sink->sample_spec.rate); } if (pa_sink_input_new_data_is_passthrough(data) && - !pa_sample_spec_equal(&data->sample_spec, &data->sink->sample_spec)) { + !pa_sample_spec_equal(&data->sample_spec, &i->sink->sample_spec)) { /* rate update failed, or other parts of sample spec didn't match */ pa_log_debug("Could not update sink sample spec to match passthrough stream"); @@ -547,28 +572,28 @@ int pa_sink_input_new( } if ((data->flags & PA_SINK_INPUT_NO_CREATE_ON_SUSPEND) && - pa_sink_get_state(data->sink) == PA_SINK_SUSPENDED) { + pa_sink_get_state(i->sink) == PA_SINK_SUSPENDED) { pa_log_warn("Failed to create sink input: sink is suspended."); ret = -PA_ERR_BADSTATE; goto fail; } - if (pa_idxset_size(data->sink->inputs) >= PA_MAX_INPUTS_PER_SINK) { + if (pa_idxset_size(i->sink->inputs) >= PA_MAX_INPUTS_PER_SINK) { pa_log_warn("Failed to create sink input: too many inputs per sink."); ret = -PA_ERR_TOOLARGE; goto fail; } if ((data->flags & PA_SINK_INPUT_VARIABLE_RATE) || - !pa_sample_spec_equal(&data->sample_spec, &data->sink->sample_spec) || - !pa_channel_map_equal(&data->channel_map, &data->sink->channel_map)) { + !pa_sample_spec_equal(&data->sample_spec, &i->sink->sample_spec) || + !pa_channel_map_equal(&data->channel_map, &i->sink->channel_map)) { /* Note: for passthrough content we need to adjust the output rate to that of the current sink-input */ if (!pa_sink_input_new_data_is_passthrough(data)) /* no resampler for passthrough content */ if (!(resampler = pa_resampler_new( core->mempool, &data->sample_spec, &data->channel_map, - &data->sink->sample_spec, &data->sink->channel_map, + &i->sink->sample_spec, &i->sink->channel_map, data->resample_method, ((data->flags & PA_SINK_INPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) | ((data->flags & PA_SINK_INPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) | @@ -581,20 +606,18 @@ int pa_sink_input_new( } i->driver = pa_xstrdup(pa_path_get_filename(data->driver)); - i->sink = data->sink; i->requested_resample_method = data->resample_method; i->actual_resample_method = resampler ? pa_resampler_get_method(resampler) : PA_RESAMPLER_INVALID; i->sample_spec = data->sample_spec; i->channel_map = data->channel_map; - i->format = pa_format_info_copy(data->format); if (!data->volume_is_absolute && pa_sink_flat_volume_enabled(i->sink)) { pa_cvolume remapped; /* When the 'absolute' bool is not set then we'll treat the volume * as relative to the sink volume even in flat volume mode */ - remapped = data->sink->reference_volume; - pa_cvolume_remap(&remapped, &data->sink->channel_map, &data->channel_map); + remapped = i->sink->reference_volume; + pa_cvolume_remap(&remapped, &i->sink->channel_map, &data->channel_map); pa_sw_cvolume_multiply(&i->volume, &data->volume, &remapped); } else i->volume = data->volume; diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c index 28381f5..c050292 100644 --- a/src/pulsecore/source-output.c +++ b/src/pulsecore/source-output.c @@ -227,7 +227,7 @@ int pa_source_output_new( pa_source_output *o = NULL; pa_resampler *resampler = NULL; - char st[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX], fmt[PA_FORMAT_INFO_SNPRINT_MAX]; + char st[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX]; pa_channel_map original_cm; int r; char *pt; @@ -278,8 +278,28 @@ int pa_source_output_new( goto fail; } + /* If something didn't pick a format for us, pick the top-most format since + * we assume this is sorted in priority order */ + if (!data->format && data->nego_formats && !pa_idxset_isempty(data->nego_formats)) + data->format = pa_format_info_copy(pa_idxset_first(data->nego_formats, NULL)); + + /* If the source has been set, then also the format should have been + * negotiated. If the source hasn't been set, no format negotiation should + * have happened either. */ + pa_assert((data->source && data->format) || (!data->source && !data->format)); + + /* From now on, routing will be done with the source output object instead + * of the new data, because the routing code that is in pa_node_put() + * doesn't have access to the new data. Therefore, let's copy + * data->req_formats, data->format and data->source to o, because the + * routing code needs access to those. */ o->req_formats = pa_idxset_copy(data->req_formats, (pa_copy_func_t) pa_format_info_copy); + if (data->format) + o->format = pa_format_info_copy(data->format); + + o->source = data->source; + if (data->driver && !pa_utf8_valid(data->driver)) { ret = -PA_ERR_INVALID; goto fail; @@ -298,9 +318,10 @@ int pa_source_output_new( o->node->owner = o; + /* This may update o->source and o->format. */ pa_node_put(o->node); - if (!data->source) { + if (!o->source) { pa_source *source; if (data->direct_on_input) { @@ -319,55 +340,59 @@ int pa_source_output_new( } } - pa_source_output_new_data_set_source(data, source, false); - } - - /* Routing's done, we have a source. Now let's fix the format and set up the - * sample spec */ + r = pa_source_output_set_initial_source(o, source); - /* If something didn't pick a format for us, pick the top-most format since - * we assume this is sorted in priority order */ - if (!data->format && data->nego_formats && !pa_idxset_isempty(data->nego_formats)) - data->format = pa_format_info_copy(pa_idxset_first(data->nego_formats, NULL)); + if (r < 0) { + pa_log_info("Format negotiation between source output \"%s\" and the default source (%s) failed.", + pa_source_output_get_description(o), source->name); + ret = r; + goto fail; + } + } - if (PA_LIKELY(data->format)) { - pa_log_debug("Negotiated format: %s", pa_format_info_snprint(fmt, sizeof(fmt), data->format)); - } else { - pa_format_info *format; - uint32_t idx; + pa_assert(o->source); + pa_assert(o->format); + + /* Modules may want to look at the source and format in the FIXATE hook, so + * let's make sure that the new data is in sync with the source output. + * Also, pa_source_output_new_data_is_passthrough() uses data->format too, + * and we call that function a few times. (XXX: It would probably be better + * to pass the source output object to the FIXATE hook instead of the new + * data, and replace the pa_source_output_new_data_is_passthrough() usage + * with something that uses the source output object instead of the new + * data. Then this extra syncing could be removed.) */ + data->source = o->source; - pa_log_info("Source does not support any requested format:"); - PA_IDXSET_FOREACH(format, data->req_formats, idx) - pa_log_info(" -- %s", pa_format_info_snprint(fmt, sizeof(fmt), format)); + if (data->format) + pa_format_info_free(data->format); - ret = -PA_ERR_NOTSUPPORTED; - goto fail; - } + data->format = pa_format_info_copy(o->format); - /* Now populate the sample spec and format according to the final - * format that we've negotiated */ + /* Routing's done, we have a source and a format. Now populate the sample + * spec and channel map according to the final format that we've + * negotiated. */ - if (pa_format_info_to_sample_spec(data->format, &ss, &map) < 0) { + if (pa_format_info_to_sample_spec(o->format, &ss, &map) < 0) { ret = -PA_ERR_INVALID; goto fail; } pa_source_output_new_data_set_sample_spec(data, &ss); - if (pa_format_info_is_pcm(data->format) && pa_channel_map_valid(&map)) + if (pa_format_info_is_pcm(o->format) && pa_channel_map_valid(&map)) pa_source_output_new_data_set_channel_map(data, &map); - if (!PA_SOURCE_IS_LINKED(pa_source_get_state(data->source))) { + if (!PA_SOURCE_IS_LINKED(pa_source_get_state(o->source))) { ret = -PA_ERR_BADSTATE; goto fail; } - if (data->direct_on_input && data->direct_on_input->sink != data->source->monitor_of) { + if (data->direct_on_input && data->direct_on_input->sink != o->source->monitor_of) { ret = -PA_ERR_INVALID; goto fail; } if (!data->sample_spec_is_set) - data->sample_spec = data->source->sample_spec; + data->sample_spec = o->source->sample_spec; if (!pa_sample_spec_valid(&data->sample_spec)) { ret = -PA_ERR_INVALID; @@ -375,8 +400,8 @@ int pa_source_output_new( } if (!data->channel_map_is_set) { - if (pa_channel_map_compatible(&data->source->channel_map, &data->sample_spec)) - data->channel_map = data->source->channel_map; + if (pa_channel_map_compatible(&o->source->channel_map, &data->sample_spec)) + data->channel_map = o->source->channel_map; else pa_channel_map_init_extend(&data->channel_map, data->sample_spec.channels, PA_CHANNEL_MAP_DEFAULT); } @@ -418,9 +443,9 @@ int pa_source_output_new( } if (!data->volume_factor_source_is_set) - pa_cvolume_reset(&data->volume_factor_source, data->source->sample_spec.channels); + pa_cvolume_reset(&data->volume_factor_source, o->source->sample_spec.channels); - if (!pa_cvolume_compatible(&data->volume_factor_source, &data->source->sample_spec)) { + if (!pa_cvolume_compatible(&data->volume_factor_source, &o->source->sample_spec)) { ret = -PA_ERR_INVALID; goto fail; } @@ -429,54 +454,54 @@ int pa_source_output_new( data->muted = false; if (data->flags & PA_SOURCE_OUTPUT_FIX_FORMAT) { - if (!pa_format_info_is_pcm(data->format)) { + if (!pa_format_info_is_pcm(o->format)) { ret = -PA_ERR_INVALID; goto fail; } - data->sample_spec.format = data->source->sample_spec.format; - pa_format_info_set_sample_format(data->format, data->sample_spec.format); + data->sample_spec.format = o->source->sample_spec.format; + pa_format_info_set_sample_format(o->format, data->sample_spec.format); } if (data->flags & PA_SOURCE_OUTPUT_FIX_RATE) { - if (!pa_format_info_is_pcm(data->format)) { + if (!pa_format_info_is_pcm(o->format)) { ret = -PA_ERR_INVALID; goto fail; } - pa_format_info_set_rate(data->format, data->sample_spec.rate); - data->sample_spec.rate = data->source->sample_spec.rate; + pa_format_info_set_rate(o->format, data->sample_spec.rate); + data->sample_spec.rate = o->source->sample_spec.rate; } original_cm = data->channel_map; if (data->flags & PA_SOURCE_OUTPUT_FIX_CHANNELS) { - if (!pa_format_info_is_pcm(data->format)) { + if (!pa_format_info_is_pcm(o->format)) { ret = -PA_ERR_INVALID; goto fail; } - data->sample_spec.channels = data->source->sample_spec.channels; - data->channel_map = data->source->channel_map; - pa_format_info_set_channels(data->format, data->sample_spec.channels); - pa_format_info_set_channel_map(data->format, &data->channel_map); + data->sample_spec.channels = o->source->sample_spec.channels; + data->channel_map = o->source->channel_map; + pa_format_info_set_channels(o->format, data->sample_spec.channels); + pa_format_info_set_channel_map(o->format, &data->channel_map); } pa_assert(pa_sample_spec_valid(&data->sample_spec)); pa_assert(pa_channel_map_valid(&data->channel_map)); if (!(data->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) && - !pa_sample_spec_equal(&data->sample_spec, &data->source->sample_spec)) { + !pa_sample_spec_equal(&data->sample_spec, &o->source->sample_spec)) { /* try to change source rate. This is done before the FIXATE hook since module-suspend-on-idle can resume a source */ pa_log_info("Trying to change sample rate"); - if (pa_source_update_rate(data->source, data->sample_spec.rate, pa_source_output_new_data_is_passthrough(data)) >= 0) - pa_log_info("Rate changed to %u Hz", data->source->sample_spec.rate); + if (pa_source_update_rate(o->source, data->sample_spec.rate, pa_source_output_new_data_is_passthrough(data)) >= 0) + pa_log_info("Rate changed to %u Hz", o->source->sample_spec.rate); } if (pa_source_output_new_data_is_passthrough(data) && - !pa_sample_spec_equal(&data->sample_spec, &data->source->sample_spec)) { + !pa_sample_spec_equal(&data->sample_spec, &o->source->sample_spec)) { /* rate update failed, or other parts of sample spec didn't match */ pa_log_debug("Could not update source sample spec to match passthrough stream"); @@ -501,26 +526,26 @@ int pa_source_output_new( } if ((data->flags & PA_SOURCE_OUTPUT_NO_CREATE_ON_SUSPEND) && - pa_source_get_state(data->source) == PA_SOURCE_SUSPENDED) { + pa_source_get_state(o->source) == PA_SOURCE_SUSPENDED) { pa_log("Failed to create source output: source is suspended."); ret = -PA_ERR_BADSTATE; goto fail; } - if (pa_idxset_size(data->source->outputs) >= PA_MAX_OUTPUTS_PER_SOURCE) { + if (pa_idxset_size(o->source->outputs) >= PA_MAX_OUTPUTS_PER_SOURCE) { pa_log("Failed to create source output: too many outputs per source."); ret = -PA_ERR_TOOLARGE; goto fail; } if ((data->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) || - !pa_sample_spec_equal(&data->sample_spec, &data->source->sample_spec) || - !pa_channel_map_equal(&data->channel_map, &data->source->channel_map)) { + !pa_sample_spec_equal(&data->sample_spec, &o->source->sample_spec) || + !pa_channel_map_equal(&data->channel_map, &o->source->channel_map)) { if (!pa_source_output_new_data_is_passthrough(data)) /* no resampler for passthrough content */ if (!(resampler = pa_resampler_new( core->mempool, - &data->source->sample_spec, &data->source->channel_map, + &o->source->sample_spec, &o->source->channel_map, &data->sample_spec, &data->channel_map, data->resample_method, ((data->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) | @@ -534,21 +559,18 @@ int pa_source_output_new( } o->driver = pa_xstrdup(pa_path_get_filename(data->driver)); - o->source = data->source; - o->requested_resample_method = data->resample_method; o->actual_resample_method = resampler ? pa_resampler_get_method(resampler) : PA_RESAMPLER_INVALID; o->sample_spec = data->sample_spec; o->channel_map = data->channel_map; - o->format = pa_format_info_copy(data->format); if (!data->volume_is_absolute && pa_source_flat_volume_enabled(o->source)) { pa_cvolume remapped; /* When the 'absolute' bool is not set then we'll treat the volume * as relative to the source volume even in flat volume mode */ - remapped = data->source->reference_volume; - pa_cvolume_remap(&remapped, &data->source->channel_map, &data->channel_map); + remapped = o->source->reference_volume; + pa_cvolume_remap(&remapped, &o->source->channel_map, &data->channel_map); pa_sw_cvolume_multiply(&o->volume, &data->volume, &remapped); } else o->volume = data->volume; -- 1.8.3.1