diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c index b249df680..b2c142bf3 100644 --- a/src/modules/alsa/alsa-sink.c +++ b/src/modules/alsa/alsa-sink.c @@ -1817,7 +1817,7 @@ static bool sink_set_formats(pa_sink *s, pa_idxset *formats) { return true; } -static void sink_reconfigure_cb(pa_sink *s, pa_sample_spec *spec, bool passthrough) { +static int sink_reconfigure_cb(pa_sink *s, pa_sample_spec *spec, pa_channel_map *map, bool passthrough) { struct userdata *u = s->userdata; int i; bool format_supported = false; @@ -1876,6 +1876,8 @@ static void sink_reconfigure_cb(pa_sink *s, pa_sample_spec *spec, bool passthrou #endif /* Passthrough status change is handled during unsuspend */ + + return 0; } static int process_rewind(struct userdata *u) { diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c index ef8b12c32..24fae97c0 100644 --- a/src/modules/alsa/alsa-source.c +++ b/src/modules/alsa/alsa-source.c @@ -1632,7 +1632,7 @@ static void source_update_requested_latency_cb(pa_source *s) { update_sw_params(u); } -static void source_reconfigure_cb(pa_source *s, pa_sample_spec *spec, bool passthrough) { +static int source_reconfigure_cb(pa_source *s, pa_sample_spec *spec, pa_channel_map *map, bool passthrough) { struct userdata *u = s->userdata; int i; bool format_supported = false; @@ -1690,6 +1690,7 @@ static void source_reconfigure_cb(pa_source *s, pa_sample_spec *spec, bool passt pa_smoother_2_set_sample_spec(u->smoother, pa_rtclock_now(), &effective_spec); #endif + return 0; } static void thread_func(void *userdata) { diff --git a/src/modules/module-null-sink.c b/src/modules/module-null-sink.c index 714a81a63..a349bbb20 100644 --- a/src/modules/module-null-sink.c +++ b/src/modules/module-null-sink.c @@ -168,9 +168,16 @@ static void sink_update_requested_latency_cb(pa_sink *s) { sink_recalculate_max_request_and_rewind(s); } -static void sink_reconfigure_cb(pa_sink *s, pa_sample_spec *spec, bool passthrough) { +static int sink_reconfigure_cb(pa_sink *s, pa_sample_spec *spec, pa_channel_map *map, bool passthrough) { /* We don't need to do anything */ s->sample_spec = *spec; + + if (map) + s->channel_map = *map; + else + pa_channel_map_init_auto(&s->channel_map, spec->channels, PA_CHANNEL_MAP_DEFAULT); + + return 0; } static bool sink_set_formats_cb(pa_sink *s, pa_idxset *formats) { diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c index 4380087ca..be05b3e32 100644 --- a/src/pulsecore/sink-input.c +++ b/src/pulsecore/sink-input.c @@ -493,7 +493,8 @@ int pa_sink_input_new( module-suspend-on-idle can resume a sink */ pa_log_info("Trying to change sample spec"); - pa_sink_reconfigure(data->sink, &data->sample_spec, pa_sink_input_new_data_is_passthrough(data)); + pa_sink_reconfigure(data->sink, &data->sample_spec, &data->channel_map, pa_sink_input_new_data_is_passthrough(data), + false); } if (pa_sink_input_new_data_is_passthrough(data) && @@ -709,7 +710,7 @@ static void sink_input_set_state(pa_sink_input *i, pa_sink_input_state_t state) !pa_sample_spec_equal(&i->sample_spec, &i->sink->sample_spec)) { /* We were uncorked and the sink was not playing anything -- let's try * to update the sample format and rate to avoid resampling */ - pa_sink_reconfigure(i->sink, &i->sample_spec, pa_sink_input_is_passthrough(i)); + pa_sink_reconfigure(i->sink, &i->sample_spec, &i->channel_map, pa_sink_input_is_passthrough(i), false); } pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_STATE, PA_UINT_TO_PTR(state), 0, NULL) == 0); @@ -812,9 +813,15 @@ void pa_sink_input_unlink(pa_sink_input *i) { reset_callbacks(i); if (i->sink) { - if (PA_SINK_IS_LINKED(i->sink->state)) + if (PA_SINK_IS_LINKED(i->sink->state)) { pa_sink_update_status(i->sink); + if (pa_sink_input_is_passthrough(i)) { + pa_log_debug("Leaving passthrough, trying to restore previous configuration"); + pa_sink_reconfigure(i->sink, NULL, NULL, false, true); + } + } + i->sink = NULL; } @@ -1901,6 +1908,11 @@ int pa_sink_input_start_move(pa_sink_input *i) { pa_sink_update_status(i->sink); + if (pa_sink_input_is_passthrough(i)) { + pa_log_debug("Leaving passthrough, trying to restore previous configuration"); + pa_sink_reconfigure(i->sink, NULL, NULL, false, true); + } + PA_HASHMAP_FOREACH(v, i->volume_factor_sink_items, state) pa_cvolume_remap(&v->volume, &i->sink->channel_map, &i->channel_map); @@ -2176,7 +2188,7 @@ int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, bool save) { SINK_INPUT_MOVE_FINISH hook */ pa_log_info("Trying to change sample spec"); - pa_sink_reconfigure(dest, &i->sample_spec, pa_sink_input_is_passthrough(i)); + pa_sink_reconfigure(dest, &i->sample_spec, &i->channel_map, pa_sink_input_is_passthrough(i), false); } if (i->moving) diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c index 0f0dc56fc..7196ba11d 100644 --- a/src/pulsecore/sink.c +++ b/src/pulsecore/sink.c @@ -273,6 +273,8 @@ pa_sink* pa_sink_new( s->sample_spec = data->sample_spec; s->channel_map = data->channel_map; s->default_sample_rate = s->sample_spec.rate; + pa_sample_spec_init(&s->saved_spec); + pa_channel_map_init(&s->saved_map); if (data->alternate_sample_rate_is_set) s->alternate_sample_rate = data->alternate_sample_rate; @@ -1479,7 +1481,8 @@ void pa_sink_render_full(pa_sink *s, size_t length, pa_memchunk *result) { } /* Called from main thread */ -void pa_sink_reconfigure(pa_sink *s, pa_sample_spec *spec, bool passthrough) { +int pa_sink_reconfigure(pa_sink *s, pa_sample_spec *spec, pa_channel_map *map, bool passthrough, bool restore) { + int ret = -1; pa_sample_spec desired_spec; uint32_t default_rate = s->default_sample_rate; uint32_t alternate_rate = s->alternate_sample_rate; @@ -1488,55 +1491,69 @@ void pa_sink_reconfigure(pa_sink *s, pa_sample_spec *spec, bool passthrough) { bool default_rate_is_usable = false; bool alternate_rate_is_usable = false; bool avoid_resampling = s->avoid_resampling; + pa_channel_map old_map, *new_map; - if (pa_sample_spec_equal(spec, &s->sample_spec)) - return; + pa_assert(restore || (spec != NULL)); + pa_assert(!restore || (spec == NULL && map == NULL && pa_sample_spec_valid(&s->saved_spec))); + + if (!restore && pa_sample_spec_equal(spec, &s->sample_spec)) + return 0; if (!s->reconfigure) - return; + return -1; - if (PA_UNLIKELY(default_rate == alternate_rate && !passthrough && !avoid_resampling)) { + if (PA_UNLIKELY(default_rate == alternate_rate && !passthrough && !restore && !avoid_resampling)) { pa_log_debug("Default and alternate sample rates are the same, so there is no point in switching."); - return; + return -1; } if (PA_SINK_IS_RUNNING(s->state)) { - pa_log_info("Cannot update sample spec, SINK_IS_RUNNING, will keep using %s and %u Hz", - pa_sample_format_to_string(s->sample_spec.format), s->sample_spec.rate); - return; + pa_log_info("Cannot update sample spec, SINK_IS_RUNNING, will keep using %s, %u ch and %u Hz", + pa_sample_format_to_string(s->sample_spec.format), s->sample_spec.channels, s->sample_spec.rate); + return -1; } if (s->monitor_source) { if (PA_SOURCE_IS_RUNNING(s->monitor_source->state) == true) { pa_log_info("Cannot update sample spec, monitor source is RUNNING"); - return; + return -1; } } - if (PA_UNLIKELY(!pa_sample_spec_valid(spec))) - return; - - desired_spec = s->sample_spec; + if (PA_UNLIKELY(!restore && !pa_sample_spec_valid(spec))) + return -1; if (passthrough) { - /* We have to try to use the sink input format and rate */ - desired_spec.format = spec->format; - desired_spec.rate = spec->rate; + /* Save the previous sample spec and channel map, we will try to restore it when leaving passthrough */ + s->saved_spec = s->sample_spec; + s->saved_map = s->channel_map; + } + + if (restore) { + /* We try to restore the saved spec */ + desired_spec = s->saved_spec; + + } else if (passthrough) { + /* We have to try to use the sink input spec */ + desired_spec = *spec; } else if (avoid_resampling) { /* We just try to set the sink input's sample rate if it's not too low */ + desired_spec = s->sample_spec; if (spec->rate >= default_rate || spec->rate >= alternate_rate) desired_spec.rate = spec->rate; + /* FIXME: don't set this if it's too low */ desired_spec.format = spec->format; } else if (default_rate == spec->rate || alternate_rate == spec->rate) { /* We can directly try to use this rate */ + desired_spec = s->sample_spec; desired_spec.rate = spec->rate; - } - - if (desired_spec.rate != spec->rate) { + } else { /* See if we can pick a rate that results in less resampling effort */ + desired_spec = s->sample_spec; + if (default_rate % 11025 == 0 && spec->rate % 11025 == 0) default_rate_is_usable = true; if (default_rate % 4000 == 0 && spec->rate % 4000 == 0) @@ -1553,28 +1570,65 @@ void pa_sink_reconfigure(pa_sink *s, pa_sample_spec *spec, bool passthrough) { } if (pa_sample_spec_equal(&desired_spec, &s->sample_spec) && passthrough == pa_sink_is_passthrough(s)) - return; + return 0; if (!passthrough && pa_sink_used_by(s) > 0) - return; + return -1; pa_log_debug("Suspending sink %s due to changing format, desired format = %s rate = %u", s->name, pa_sample_format_to_string(desired_spec.format), desired_spec.rate); pa_sink_suspend(s, true, PA_SUSPEND_INTERNAL); - s->reconfigure(s, &desired_spec, passthrough); + /* Keep the old channel map in case it changes */ + old_map = s->channel_map; - /* update monitor source as well */ - if (s->monitor_source && !passthrough) - pa_source_reconfigure(s->monitor_source, &s->sample_spec, false); - pa_log_info("Reconfigured successfully"); + if (restore) { + /* Restore the previous channel map as well */ + new_map = &s->saved_map; + } else if (map) { + /* Set the requested channel map */ + new_map = map; + } else if (desired_spec.channels == s->sample_spec.channels) { + /* No requested channel map, but channel count is unchanged so don't change */ + new_map = &s->channel_map; + } else { + /* No requested channel map, let the device decide */ + new_map = NULL; + } + + if (s->reconfigure(s, &desired_spec, new_map, passthrough) >= 0) { + char spec_str[PA_SAMPLE_SPEC_SNPRINT_MAX]; + + /* update monitor source as well */ + if (s->monitor_source && !passthrough) + pa_source_reconfigure(s->monitor_source, &desired_spec, new_map, false, false); + + pa_log_info("Reconfigured successfully to: %s", + pa_sample_spec_snprint(spec_str, sizeof(spec_str), &desired_spec)); + } PA_IDXSET_FOREACH(i, s->inputs, idx) { if (i->state == PA_SINK_INPUT_CORKED) pa_sink_input_update_resampler(i, true); } + if (!pa_channel_map_equal(&old_map, &s->channel_map)) { + /* Remap stored volumes to the new channel map */ + pa_cvolume_remap(&s->reference_volume, &old_map, &s->channel_map); + pa_cvolume_remap(&s->real_volume, &old_map, &s->channel_map); + pa_cvolume_remap(&s->soft_volume, &old_map, &s->channel_map); + } + pa_sink_suspend(s, false, PA_SUSPEND_INTERNAL); + + if (restore) { + /* Reset saved spec and channel map to bail early if we inadvertently + * use them (which is not expected after this) */ + pa_sample_spec_init(&s->saved_spec); + pa_channel_map_init(&s->saved_map); + } + + return ret; } /* Called from main thread */ @@ -4032,7 +4086,8 @@ void pa_sink_set_reference_volume_direct(pa_sink *s, const pa_cvolume *volume) { s->reference_volume = *volume; pa_log_debug("The reference volume of sink %s changed from %s to %s.", s->name, - pa_cvolume_snprint_verbose(old_volume_str, sizeof(old_volume_str), &old_volume, &s->channel_map, + /* we don't print old volume channel map as it might have changed */ + pa_cvolume_snprint_verbose(old_volume_str, sizeof(old_volume_str), &old_volume, NULL, s->flags & PA_SINK_DECIBEL_VOLUME), pa_cvolume_snprint_verbose(new_volume_str, sizeof(new_volume_str), volume, &s->channel_map, s->flags & PA_SINK_DECIBEL_VOLUME)); diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h index 383edacb5..c4c0bfd39 100644 --- a/src/pulsecore/sink.h +++ b/src/pulsecore/sink.h @@ -107,7 +107,9 @@ struct pa_sink { bool save_muted:1; bool port_changing:1; - /* Saved volume state while we're in passthrough mode */ + /* Saved state while we're in passthrough mode */ + pa_sample_spec saved_spec; + pa_channel_map saved_map; pa_cvolume saved_volume; bool saved_save_volume:1; @@ -268,7 +270,7 @@ struct pa_sink { /* Called whenever device parameters need to be changed. Called from * main thread. */ - void (*reconfigure)(pa_sink *s, pa_sample_spec *spec, bool passthrough); + int (*reconfigure)(pa_sink *s, pa_sample_spec *spec, pa_channel_map *map, bool passthrough); /* Contains copies of the above data so that the real-time worker * thread can work without access locking */ @@ -450,7 +452,7 @@ unsigned pa_device_init_priority(pa_proplist *p); /**** May be called by everyone, from main context */ -void pa_sink_reconfigure(pa_sink *s, pa_sample_spec *spec, bool passthrough); +int pa_sink_reconfigure(pa_sink *s, pa_sample_spec *spec, pa_channel_map *map, bool passthrough, bool restore); void pa_sink_set_port_latency_offset(pa_sink *s, int64_t offset); /* The returned value is supposed to be in the time domain of the sound card! */ diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c index 2e2b7a274..8b6fd0493 100644 --- a/src/pulsecore/source-output.c +++ b/src/pulsecore/source-output.c @@ -380,7 +380,8 @@ int pa_source_output_new( module-suspend-on-idle can resume a source */ pa_log_info("Trying to change sample spec"); - pa_source_reconfigure(data->source, &data->sample_spec, pa_source_output_new_data_is_passthrough(data)); + pa_source_reconfigure(data->source, &data->sample_spec, &data->channel_map, + pa_source_output_new_data_is_passthrough(data), false); } if (pa_source_output_new_data_is_passthrough(data) && @@ -559,7 +560,7 @@ static void source_output_set_state(pa_source_output *o, pa_source_output_state_ !pa_sample_spec_equal(&o->sample_spec, &o->source->sample_spec)) { /* We were uncorked and the source was not playing anything -- let's try * to update the sample format and rate to avoid resampling */ - pa_source_reconfigure(o->source, &o->sample_spec, pa_source_output_is_passthrough(o)); + pa_source_reconfigure(o->source, &o->sample_spec, &o->channel_map, pa_source_output_is_passthrough(o), false); } pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_SET_STATE, PA_UINT_TO_PTR(state), 0, NULL) == 0); @@ -628,9 +629,15 @@ void pa_source_output_unlink(pa_source_output*o) { reset_callbacks(o); if (o->source) { - if (PA_SOURCE_IS_LINKED(o->source->state)) + if (PA_SOURCE_IS_LINKED(o->source->state)) { pa_source_update_status(o->source); + if (pa_source_output_is_passthrough(o)) { + pa_log_debug("Leaving passthrough, trying to restore previous configuration"); + pa_source_reconfigure(o->source, NULL, NULL, false, true); + } + } + o->source = NULL; } @@ -1412,6 +1419,11 @@ int pa_source_output_start_move(pa_source_output *o) { pa_source_update_status(o->source); + if (pa_source_output_is_passthrough(o)) { + pa_log_debug("Leaving passthrough, trying to restore previous configuration"); + pa_source_reconfigure(o->source, NULL, NULL, false, true); + } + pa_cvolume_remap(&o->volume_factor_source, &o->source->channel_map, &o->channel_map); o->source = NULL; @@ -1605,7 +1617,7 @@ int pa_source_output_finish_move(pa_source_output *o, pa_source *dest, bool save SOURCE_OUTPUT_MOVE_FINISH hook */ pa_log_info("Trying to change sample spec"); - pa_source_reconfigure(dest, &o->sample_spec, pa_source_output_is_passthrough(o)); + pa_source_reconfigure(dest, &o->sample_spec, &o->channel_map, pa_source_output_is_passthrough(o), false); } if (o->moving) diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c index 99d8dde6e..67f2fb624 100644 --- a/src/pulsecore/source.c +++ b/src/pulsecore/source.c @@ -259,6 +259,8 @@ pa_source* pa_source_new( s->sample_spec = data->sample_spec; s->channel_map = data->channel_map; s->default_sample_rate = s->sample_spec.rate; + pa_sample_spec_init(&s->saved_spec); + pa_channel_map_init(&s->saved_map); if (data->alternate_sample_rate_is_set) s->alternate_sample_rate = data->alternate_sample_rate; @@ -1045,64 +1047,77 @@ void pa_source_post_direct(pa_source*s, pa_source_output *o, const pa_memchunk * } /* Called from main thread */ -void pa_source_reconfigure(pa_source *s, pa_sample_spec *spec, bool passthrough) { - uint32_t idx; - pa_source_output *o; +int pa_source_reconfigure(pa_source *s, pa_sample_spec *spec, pa_channel_map *map, bool passthrough, bool restore) { + int ret; pa_sample_spec desired_spec; uint32_t default_rate = s->default_sample_rate; uint32_t alternate_rate = s->alternate_sample_rate; bool default_rate_is_usable = false; bool alternate_rate_is_usable = false; bool avoid_resampling = s->avoid_resampling; + pa_channel_map old_map, *new_map; - if (pa_sample_spec_equal(spec, &s->sample_spec)) - return; + pa_assert(restore || (spec != NULL)); + pa_assert(!restore || (spec == NULL && map == NULL && pa_sample_spec_valid(&s->saved_spec))); + + if (!restore && pa_sample_spec_equal(spec, &s->sample_spec)) + return 0; if (!s->reconfigure && !s->monitor_of) - return; + return -1; - if (PA_UNLIKELY(default_rate == alternate_rate && !passthrough && !avoid_resampling)) { + if (PA_UNLIKELY(default_rate == alternate_rate && !passthrough && !restore && !avoid_resampling)) { pa_log_debug("Default and alternate sample rates are the same, so there is no point in switching."); - return; + return -1; } if (PA_SOURCE_IS_RUNNING(s->state)) { - pa_log_info("Cannot update sample spec, SOURCE_IS_RUNNING, will keep using %s and %u Hz", - pa_sample_format_to_string(s->sample_spec.format), s->sample_spec.rate); - return; + pa_log_info("Cannot update sample spec, SOURCE_IS_RUNNING, will keep using %s, %u ch and %u Hz", + pa_sample_format_to_string(s->sample_spec.format), s->sample_spec.channels, s->sample_spec.rate); + return -1; } if (s->monitor_of) { if (PA_SINK_IS_RUNNING(s->monitor_of->state)) { pa_log_info("Cannot update sample spec, this is a monitor source and the sink is running."); - return; + return -1; } } - if (PA_UNLIKELY(!pa_sample_spec_valid(spec))) - return; - - desired_spec = s->sample_spec; + if (PA_UNLIKELY(!restore && !pa_sample_spec_valid(spec))) + return -1; if (passthrough) { - /* We have to try to use the source output format and rate */ - desired_spec.format = spec->format; - desired_spec.rate = spec->rate; + /* Save the previous sample spec and channel map, we will try to restore it when leaving passthrough */ + s->saved_spec = s->sample_spec; + s->saved_map = s->channel_map; + } + + if (restore) { + /* We try to restore the saved spec */ + desired_spec = s->saved_spec; + + } else if (passthrough) { + /* We have to try to use the source output spec */ + desired_spec = *spec; } else if (avoid_resampling) { /* We just try to set the source output's sample rate if it's not too low */ + desired_spec = s->sample_spec; if (spec->rate >= default_rate || spec->rate >= alternate_rate) desired_spec.rate = spec->rate; + /* FIXME: don't set this if it's too low */ desired_spec.format = spec->format; } else if (default_rate == spec->rate || alternate_rate == spec->rate) { /* We can directly try to use this rate */ + desired_spec = s->sample_spec; desired_spec.rate = spec->rate; - } - - if (desired_spec.rate != spec->rate) { + } else { /* See if we can pick a rate that results in less resampling effort */ + desired_spec = s->sample_spec; + if (default_rate % 11025 == 0 && spec->rate % 11025 == 0) default_rate_is_usable = true; if (default_rate % 4000 == 0 && spec->rate % 4000 == 0) @@ -1119,17 +1134,35 @@ void pa_source_reconfigure(pa_source *s, pa_sample_spec *spec, bool passthrough) } if (pa_sample_spec_equal(&desired_spec, &s->sample_spec) && passthrough == pa_source_is_passthrough(s)) - return; + return 0; if (!passthrough && pa_source_used_by(s) > 0) - return; + return -1; pa_log_debug("Suspending source %s due to changing format, desired format = %s rate = %u", s->name, pa_sample_format_to_string(desired_spec.format), desired_spec.rate); + pa_source_suspend(s, true, PA_SUSPEND_INTERNAL); + /* Keep the old channel map in case it changes */ + old_map = s->channel_map; + + if (restore) { + /* Restore the previous channel map as well */ + new_map = &s->saved_map; + } else if (map) { + /* Set the requested channel map */ + new_map = map; + } else if (desired_spec.channels == s->sample_spec.channels) { + /* No requested channel map, but channel count is unchanged so don't change */ + new_map = &s->channel_map; + } else { + /* No requested channel map, let the device decide */ + new_map = NULL; + } + if (s->reconfigure) - s->reconfigure(s, &desired_spec, passthrough); + ret = s->reconfigure(s, &desired_spec, new_map, passthrough); else { /* This is a monitor source. */ @@ -1137,22 +1170,61 @@ void pa_source_reconfigure(pa_source *s, pa_sample_spec *spec, bool passthrough) * have no idea whether the behaviour with passthrough streams is * sensible. */ if (!passthrough) { + pa_sample_spec old_spec = s->sample_spec; s->sample_spec = desired_spec; - pa_sink_reconfigure(s->monitor_of, &desired_spec, false); - s->sample_spec = s->monitor_of->sample_spec; + ret = pa_sink_reconfigure(s->monitor_of, &desired_spec, new_map, false, false); + + if (ret < 0) { + /* Changing the sink rate failed, roll back the old rate for + * the monitor source. Why did we set the source rate before + * calling pa_sink_reconfigure(), you may ask. The reason is + * that pa_sink_reconfigure() tries to update the monitor + * source rate, but we are already in the process of updating + * the monitor source rate, so there's a risk of entering an + * infinite loop. Setting the source rate before calling + * pa_sink_reconfigure() makes the rate == s->sample_spec.rate + * check in the beginning of this function return early, so we + * avoid looping. */ + s->sample_spec = old_spec; + } } else goto unsuspend; } - PA_IDXSET_FOREACH(o, s->outputs, idx) { - if (o->state == PA_SOURCE_OUTPUT_CORKED) - pa_source_output_update_resampler(o); + if (ret >= 0) { + uint32_t idx; + pa_source_output *o; + char spec_str[PA_SAMPLE_SPEC_SNPRINT_MAX]; + + pa_log_info("Changed source format successfully to: %s", + pa_sample_spec_snprint(spec_str, sizeof(spec_str), &desired_spec)); + + PA_IDXSET_FOREACH(o, s->outputs, idx) { + if (o->state == PA_SOURCE_OUTPUT_CORKED) + pa_source_output_update_resampler(o); + } + } + + if (!pa_channel_map_equal(&old_map, &s->channel_map)) { + /* Remap stored volumes to the new channel map */ + pa_cvolume_remap(&s->reference_volume, &old_map, &s->channel_map); + pa_cvolume_remap(&s->real_volume, &old_map, &s->channel_map); + pa_cvolume_remap(&s->soft_volume, &old_map, &s->channel_map); } pa_log_info("Reconfigured successfully"); + if (restore) { + /* Reset saved spec and channel map to bail early if we inadvertently + * use them (which is not expected after this) */ + pa_sample_spec_init(&s->saved_spec); + pa_channel_map_init(&s->saved_map); + } + unsuspend: pa_source_suspend(s, false, PA_SUSPEND_INTERNAL); + + return ret; } /* Called from main thread */ @@ -3001,7 +3073,8 @@ void pa_source_set_reference_volume_direct(pa_source *s, const pa_cvolume *volum s->reference_volume = *volume; pa_log_debug("The reference volume of source %s changed from %s to %s.", s->name, - pa_cvolume_snprint_verbose(old_volume_str, sizeof(old_volume_str), &old_volume, &s->channel_map, + /* we don't print old volume channel map as it might have changed */ + pa_cvolume_snprint_verbose(old_volume_str, sizeof(old_volume_str), &old_volume, NULL, s->flags & PA_SOURCE_DECIBEL_VOLUME), pa_cvolume_snprint_verbose(new_volume_str, sizeof(new_volume_str), volume, &s->channel_map, s->flags & PA_SOURCE_DECIBEL_VOLUME)); diff --git a/src/pulsecore/source.h b/src/pulsecore/source.h index aa71ee829..57ed77bc6 100644 --- a/src/pulsecore/source.h +++ b/src/pulsecore/source.h @@ -108,7 +108,9 @@ struct pa_source { bool save_muted:1; bool port_changing:1; - /* Saved volume state while we're in passthrough mode */ + /* Saved state while we're in passthrough mode */ + pa_sample_spec saved_spec; + pa_channel_map saved_map; pa_cvolume saved_volume; bool saved_save_volume:1; @@ -226,7 +228,7 @@ struct pa_source { /* Called whenever device parameters need to be changed. Called from * main thread. */ - void (*reconfigure)(pa_source *s, pa_sample_spec *spec, bool passthrough); + int (*reconfigure)(pa_source *s, pa_sample_spec *spec, pa_channel_map *map, bool passthrough); /* Contains copies of the above data so that the real-time worker * thread can work without access locking */ @@ -419,7 +421,7 @@ bool pa_source_update_proplist(pa_source *s, pa_update_mode_t mode, pa_proplist int pa_source_set_port(pa_source *s, const char *name, bool save); -void pa_source_reconfigure(pa_source *s, pa_sample_spec *spec, bool passthrough); +int pa_source_reconfigure(pa_source *s, pa_sample_spec *spec, pa_channel_map *map, bool passthrough, bool restore); unsigned pa_source_linked_by(pa_source *s); /* Number of connected streams */ unsigned pa_source_used_by(pa_source *s); /* Number of connected streams that are not corked */