From 628c0c0b746254a7f7054827c67a2182fa761c54 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Wed, 12 Jan 2022 13:22:36 -0500 Subject: [PATCH] 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. --- src/modules/alsa/alsa-sink.c | 32 +++++++++++++++++++++++ src/modules/alsa/alsa-source.c | 32 +++++++++++++++++++++++ src/modules/alsa/alsa-util.c | 46 ++++++++++++++++++++++++++++++++++ src/modules/alsa/alsa-util.h | 1 + 4 files changed, 111 insertions(+) diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c index 6ffdf6ea7..6f9a71888 100644 --- a/src/modules/alsa/alsa-sink.c +++ b/src/modules/alsa/alsa-sink.c @@ -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); diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c index ee2d04d9a..9be79d37d 100644 --- a/src/modules/alsa/alsa-source.c +++ b/src/modules/alsa/alsa-source.c @@ -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); diff --git a/src/modules/alsa/alsa-util.c b/src/modules/alsa/alsa-util.c index 9d464a8ac..d9aa8e329 100644 --- a/src/modules/alsa/alsa-util.c +++ b/src/modules/alsa/alsa-util.c @@ -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); diff --git a/src/modules/alsa/alsa-util.h b/src/modules/alsa/alsa-util.h index 2eed3eac3..50a049adc 100644 --- a/src/modules/alsa/alsa-util.h +++ b/src/modules/alsa/alsa-util.h @@ -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);