miniaudio icon indicating copy to clipboard operation
miniaudio copied to clipboard

Alsa backend restricts some multichannel devices to stereo

Open ideoforms opened this issue 11 months ago • 5 comments

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.

ideoforms avatar Feb 12 '25 14:02 ideoforms

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}

ideoforms avatar Feb 12 '25 14:02 ideoforms

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.

mackron avatar Feb 16 '25 06:02 mackron

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?

mackron avatar Mar 12 '25 06:03 mackron

Yes, aplay is able to play out of all 8 speakers, and same with Python sounddevice (which I was using previously).

ideoforms avatar Mar 13 '25 10:03 ideoforms

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.

ideoforms avatar Nov 04 '25 22:11 ideoforms