alsa: Implement reconfiguration of channel count/map

This is needed at the moment for passthrough support of high bitrate
formats (such as DTS-HD and Dolby TrueHD), which, in addition to setting
a higher sample rate also set channel count to 8. It would be clunky to
require users to reset the HDMI profile based on the format being
played, so for consistency we continue to only allow setting formats on
the stereo profile, and reconfigure channels as needed for the duration
of pakssthrough playback.

We implement the source side as well for consistency.
This commit is contained in:
Arun Raghavan 2022-01-12 13:22:36 -05:00
parent 4693028b9b
commit 628c0c0b74
4 changed files with 111 additions and 0 deletions

View file

@ -124,6 +124,7 @@ struct userdata {
pa_sample_spec verified_sample_spec;
pa_sample_format_t *supported_formats;
unsigned int *supported_rates;
unsigned int *supported_channels;
struct {
size_t fragment_size;
size_t nfrags;
@ -1822,6 +1823,7 @@ static int sink_reconfigure_cb(pa_sink *s, pa_sample_spec *spec, pa_channel_map
int i;
bool format_supported = false;
bool rate_supported = false;
bool channels_supported = false;
#ifdef USE_SMOOTHER_2
pa_sample_spec effective_spec;
#endif
@ -1875,6 +1877,27 @@ static int sink_reconfigure_cb(pa_sink *s, pa_sample_spec *spec, pa_channel_map
pa_smoother_2_set_sample_spec(u->smoother, pa_rtclock_now(), &effective_spec);
#endif
for (i = 0; u->supported_channels[i]; i++) {
if (u->supported_channels[i] == spec->channels) {
pa_sink_set_channels(u->sink, spec->channels);
channels_supported = true;
break;
}
}
if (!channels_supported) {
pa_log_info("Sink does not support %u channels, set it to a verified value", spec->channels);
pa_sink_set_channels(u->sink, u->verified_sample_spec.channels);
}
if (map) {
pa_sink_set_channel_map(s, map);
} else {
pa_channel_map def_map;
pa_channel_map_init_auto(&def_map, spec->channels, PA_CHANNEL_MAP_DEFAULT);
pa_sink_set_channel_map(s, &def_map);
}
/* Passthrough status change is handled during unsuspend */
return 0;
@ -2614,6 +2637,12 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
goto fail;
}
u->supported_channels = pa_alsa_get_supported_channels(u->pcm_handle, ss.channels);
if (!u->supported_channels) {
pa_log_error("Failed to find any supported channel counts.");
goto fail;
}
/* ALSA might tweak the sample spec, so recalculate the frame size */
frame_size = pa_frame_size(&ss);
@ -2936,6 +2965,9 @@ static void userdata_free(struct userdata *u) {
if (u->supported_rates)
pa_xfree(u->supported_rates);
if (u->supported_channels)
pa_xfree(u->supported_channels);
reserve_done(u);
monitor_done(u);

View file

@ -112,6 +112,7 @@ struct userdata {
pa_sample_spec verified_sample_spec;
pa_sample_format_t *supported_formats;
unsigned int *supported_rates;
unsigned int *supported_channels;
struct {
size_t fragment_size;
size_t nfrags;
@ -1637,6 +1638,7 @@ static int source_reconfigure_cb(pa_source *s, pa_sample_spec *spec, pa_channel_
int i;
bool format_supported = false;
bool rate_supported = false;
bool channels_supported = false;
#ifdef USE_SMOOTHER_2
pa_sample_spec effective_spec;
#endif
@ -1690,6 +1692,27 @@ static int source_reconfigure_cb(pa_source *s, pa_sample_spec *spec, pa_channel_
pa_smoother_2_set_sample_spec(u->smoother, pa_rtclock_now(), &effective_spec);
#endif
for (i = 0; u->supported_channels[i]; i++) {
if (u->supported_channels[i] == spec->channels) {
pa_source_set_channels(u->source, spec->channels);
channels_supported = true;
break;
}
}
if (!channels_supported) {
pa_log_info("Sink does not support %u channels, set it to a verified value", spec->channels);
pa_source_set_channels(u->source, u->verified_sample_spec.channels);
}
if (map) {
pa_source_set_channel_map(s, map);
} else {
pa_channel_map def_map;
pa_channel_map_init_auto(&def_map, spec->channels, PA_CHANNEL_MAP_DEFAULT);
pa_source_set_channel_map(s, &def_map);
}
return 0;
}
@ -2294,6 +2317,12 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
goto fail;
}
u->supported_channels = pa_alsa_get_supported_channels(u->pcm_handle, ss.channels);
if (!u->supported_channels) {
pa_log_error("Failed to find any supported channel counts.");
goto fail;
}
/* ALSA might tweak the sample spec, so recalculate the frame size */
frame_size = pa_frame_size(&ss);
@ -2555,6 +2584,9 @@ static void userdata_free(struct userdata *u) {
if (u->supported_rates)
pa_xfree(u->supported_rates);
if (u->supported_channels)
pa_xfree(u->supported_channels);
reserve_done(u);
monitor_done(u);

View file

@ -1535,6 +1535,52 @@ pa_sample_format_t *pa_alsa_get_supported_formats(snd_pcm_t *pcm, pa_sample_form
return formats;
}
unsigned int *pa_alsa_get_supported_channels(snd_pcm_t *pcm, unsigned int fallback_channels) {
/* Index 0 is unused as "no channels" is meaningless */
bool supported[PA_CHANNELS_MAX + 1] = { false, };
snd_pcm_hw_params_t *hwparams;
unsigned int i, j, n, *channels = NULL;
int ret;
snd_pcm_hw_params_alloca(&hwparams);
if ((ret = snd_pcm_hw_params_any(pcm, hwparams)) < 0) {
pa_log_debug("snd_pcm_hw_params_any() failed: %s", pa_alsa_strerror(ret));
return NULL;
}
for (i = 1, n = 0; i <= PA_CHANNELS_MAX; i++) {
if (snd_pcm_hw_params_test_channels(pcm, hwparams, i) == 0) {
supported[i] = true;
n++;
}
}
if (n > 0) {
channels = pa_xnew(unsigned int, n + 1);
for (i = 1, j = 0; i <= PA_CHANNELS_MAX; i++) {
if (supported[i])
channels[j++] = i;
}
channels[j] = 0;
} else {
channels = pa_xnew(unsigned int, 2);
channels[0] = fallback_channels;
if ((ret = snd_pcm_hw_params_set_channels_near(pcm, hwparams, &channels[0])) < 0) {
pa_log_debug("snd_pcm_hw_params_set_channels_near() failed: %s", pa_alsa_strerror(ret));
pa_xfree(channels);
return NULL;
}
channels[1] = 0;
}
return channels;
}
bool pa_alsa_pcm_is_hw(snd_pcm_t *pcm) {
snd_pcm_info_t* info;
snd_pcm_info_alloca(&info);

View file

@ -140,6 +140,7 @@ char *pa_alsa_get_reserve_name(const char *device);
unsigned int *pa_alsa_get_supported_rates(snd_pcm_t *pcm, unsigned int fallback_rate);
pa_sample_format_t *pa_alsa_get_supported_formats(snd_pcm_t *pcm, pa_sample_format_t fallback_format);
unsigned int *pa_alsa_get_supported_channels(snd_pcm_t *pcm, unsigned int fallback_channels);
bool pa_alsa_pcm_is_hw(snd_pcm_t *pcm);
bool pa_alsa_pcm_is_modem(snd_pcm_t *pcm);