Alsa backend restricts some multichannel devices to stereo
Discussed in Discord, and adding report here.
Using the Alsa backend with latest Miniaudio (2025-02-12), some multichannel devices (specifically ESI Gigaport EX and a Startech 7.1 interface) only appear as having 2 channels in Miniaudio, even when explicitly specifying config.playback.channels = 8.
A third device (a custom class-compliant audio interface) works successfully out of the box.
Both of the failing devices appear as supporting either 2 or 8 channels when I run aplay -D hw:CARD=ICUSBAUDIO7D,DEV=0 --dump-hw-params /dev/zero, so this info must be available somewhere in the Alsa stack.
From inspecting /proc/asound/card0/stream0 with one of the failing devices, I found that the default USB/Alsa USB Altset uses stereo output, but there are 3 other altsets including 8-channel support. I assume that the Alsa driver should interrogate these and present them to higher layers, but perhaps explains why it is stereo by default, rather than 8-channel.
Debug output when I force channels = 8:
DEBUG: Attempting to initialize ALSA backend...
DEBUG: Loading library: libasound.so.2
DEBUG: Loading symbol: snd_pcm_open
DEBUG: Loading symbol: snd_pcm_close
DEBUG: Loading symbol: snd_pcm_hw_params_sizeof
DEBUG: Loading symbol: snd_pcm_hw_params_any
DEBUG: Loading symbol: snd_pcm_hw_params_set_format
DEBUG: Loading symbol: snd_pcm_hw_params_set_format_first
DEBUG: Loading symbol: snd_pcm_hw_params_get_format_mask
DEBUG: Loading symbol: snd_pcm_hw_params_set_channels
DEBUG: Loading symbol: snd_pcm_hw_params_set_channels_near
DEBUG: Loading symbol: snd_pcm_hw_params_set_channels_minmax
DEBUG: Loading symbol: snd_pcm_hw_params_set_rate_resample
DEBUG: Loading symbol: snd_pcm_hw_params_set_rate
DEBUG: Loading symbol: snd_pcm_hw_params_set_rate_near
DEBUG: Loading symbol: snd_pcm_hw_params_set_buffer_size_near
DEBUG: Loading symbol: snd_pcm_hw_params_set_periods_near
DEBUG: Loading symbol: snd_pcm_hw_params_set_access
DEBUG: Loading symbol: snd_pcm_hw_params_get_format
DEBUG: Loading symbol: snd_pcm_hw_params_get_channels
DEBUG: Loading symbol: snd_pcm_hw_params_get_channels_min
DEBUG: Loading symbol: snd_pcm_hw_params_get_channels_max
DEBUG: Loading symbol: snd_pcm_hw_params_get_rate
DEBUG: Loading symbol: snd_pcm_hw_params_get_rate_min
DEBUG: Loading symbol: snd_pcm_hw_params_get_rate_max
DEBUG: Loading symbol: snd_pcm_hw_params_get_buffer_size
DEBUG: Loading symbol: snd_pcm_hw_params_get_periods
DEBUG: Loading symbol: snd_pcm_hw_params_get_access
DEBUG: Loading symbol: snd_pcm_hw_params_test_format
DEBUG: Loading symbol: snd_pcm_hw_params_test_channels
DEBUG: Loading symbol: snd_pcm_hw_params_test_rate
DEBUG: Loading symbol: snd_pcm_hw_params
DEBUG: Loading symbol: snd_pcm_sw_params_sizeof
DEBUG: Loading symbol: snd_pcm_sw_params_current
DEBUG: Loading symbol: snd_pcm_sw_params_get_boundary
DEBUG: Loading symbol: snd_pcm_sw_params_set_avail_min
DEBUG: Loading symbol: snd_pcm_sw_params_set_start_threshold
DEBUG: Loading symbol: snd_pcm_sw_params_set_stop_threshold
DEBUG: Loading symbol: snd_pcm_sw_params
DEBUG: Loading symbol: snd_pcm_format_mask_sizeof
DEBUG: Loading symbol: snd_pcm_format_mask_test
DEBUG: Loading symbol: snd_pcm_get_chmap
DEBUG: Loading symbol: snd_pcm_state
DEBUG: Loading symbol: snd_pcm_prepare
DEBUG: Loading symbol: snd_pcm_start
DEBUG: Loading symbol: snd_pcm_drop
DEBUG: Loading symbol: snd_pcm_drain
DEBUG: Loading symbol: snd_pcm_reset
DEBUG: Loading symbol: snd_device_name_hint
DEBUG: Loading symbol: snd_device_name_get_hint
DEBUG: Loading symbol: snd_card_get_index
DEBUG: Loading symbol: snd_device_name_free_hint
DEBUG: Loading symbol: snd_pcm_mmap_begin
DEBUG: Loading symbol: snd_pcm_mmap_commit
DEBUG: Loading symbol: snd_pcm_recover
DEBUG: Loading symbol: snd_pcm_readi
DEBUG: Loading symbol: snd_pcm_writei
DEBUG: Loading symbol: snd_pcm_avail
DEBUG: Loading symbol: snd_pcm_avail_update
DEBUG: Loading symbol: snd_pcm_wait
DEBUG: Loading symbol: snd_pcm_nonblock
DEBUG: Loading symbol: snd_pcm_info
DEBUG: Loading symbol: snd_pcm_info_sizeof
DEBUG: Loading symbol: snd_pcm_info_get_name
DEBUG: Loading symbol: snd_pcm_poll_descriptors
DEBUG: Loading symbol: snd_pcm_poll_descriptors_count
DEBUG: Loading symbol: snd_pcm_poll_descriptors_revents
DEBUG: Loading symbol: snd_config_update_free_global
DEBUG: System Architecture:
DEBUG: Endian: LE
DEBUG: SSE2: NO
DEBUG: AVX2: NO
DEBUG: NEON: YES
INFO: [ALSA]
INFO: GIGAPORT eX, USB Audio (Playback)
INFO: Format: 32-bit IEEE Floating Point -> 16-bit Signed Integer
INFO: Channels: 8 -> 2
INFO: Sample Rate: 44100 -> 48000
INFO: Buffer Size: 1024*3 (3072)
INFO: Conversion:
INFO: Pre Format Conversion: YES
INFO: Post Format Conversion: NO
INFO: Channel Routing: YES
INFO: Resampling: YES
INFO: Passthrough: NO
INFO: Channel Map In: {CHANNEL_FRONT_LEFT CHANNEL_FRONT_RIGHT CHANNEL_FRONT_CENTER CHANNEL_LFE CHANNEL_BACK_LEFT CHANNEL_BACK_RIGHT CHANNEL_SIDE_LEFT CHANNEL_SIDE_RIGHT}
INFO: Channel Map Out: {CHANNEL_FRONT_LEFT CHANNEL_FRONT_RIGHT}
Thanks. Just acknowledging that I've seen this. I'll have to take a look at what dplay is doing different to miniaudio. No timeframe on this - it probably won't be making the next release. Will report back.
I sent a message to you on Discord but forgot to follow it up here. Copy/paste:
I've had a look at the aplay code and it looks pretty standard to me. I'm not sure what miniaudio is doing differently where it'd matter. The format output you're seeing is from a function called snd_pcm_hw_params_dump() so I'm still not sure how it's actually retrieving the info.
What happens if you try playing an 8-channel sound from aplay? Does it play out of all 8 speakers?
Yes, aplay is able to play out of all 8 speakers, and same with Python sounddevice (which I was using previously).
Hi there, I've just encountered a similar bug on another multichannel audio interface, the HiFiBerry DAC8x on the Raspberry Pi. This isn't USB so it doesn't have altsets. But, like the USB devices above, when I run dump-hw-params, I can see that it supports either 2 or 8 channel configurations.
I've made more progress this time around, and made a new finding. If I create an ma_context, enumerate the devices, and specify the playback device name with deviceConfig.playback.pDeviceID when I create the device, I can never get 8-channel playback working, regardless of whether I attempt to request 8-channel playback with config.playback.channels = 8.
This is the list of available devices it finds:
Available playback devices:
0: Discard all samples (playback) or generate zero samples (capture)
1: snd_rpi_hifiberry_dac8x, HifiBerry DAC8x HiFi snd-soc-dummy-dai-0
2: vc4-hdmi-0, MAI PCM i2s-hifi-0
3: vc4-hdmi-1, MAI PCM i2s-hifi-0
However, if I leave the playback pDeviceID unspecified, but request config.playback.channels = 8, I now can play from all 8 channels. Querying device.playback.name, I appear to be using a device called "Default Playback Device".
I still haven't found a successful route to querying the number of channels actually available on the device, but I can at least hard-code 8 channels for now as a workaround.
I'll try to see if I can replicate this on those USB devices at some point.