mirror of
https://gitlab.freedesktop.org/pulseaudio/pulseaudio.git
synced 2025-11-27 07:00:01 -05:00
sink, source: Allow reconfigure to change the complete sample spec
For the passthrough case, we allow the entire sink sample spec to be changed in reconfigure. This will be needed for high bitrate formats. We duplicate this for sources to keep things in sync as well. Relatedly, we also restore the original spec on leaving passthrough mode. We were getting away with not doing so in the past as, while incorrect, not restoring the rate was not disastrous. With the ability to change channel count, not restoring breaks the meaning of profiles entirely. The saving and restoration logic is restricted to sink/source reconfiguration code to allow it to be self-contained and easier to reason about. All this also applies to the channel map. We don't actually explicitly reconfigure the channel map at the moment, but since pa_sink/source_reconfigure() can now change the channel count, it seems to make sense to include the channel map along with that API change for future use.
This commit is contained in:
parent
9360dce76d
commit
0d85b18460
9 changed files with 242 additions and 76 deletions
|
|
@ -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));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue