mackron / miniaudio

Audio playback and capture library written in C, in a single source file.
https://miniaud.io
Other
4.07k stars 361 forks source link

No output when miniaudio does 2 -> 4 channel converting #480

Closed emoon closed 2 years ago

emoon commented 2 years ago

Hi,

I'm having trouble getting correct output with this program

#include <stdio.h>

#define MINIAUDIO_IMPLEMENTATION
#define MA_DEBUG_OUTPUT
#include "miniaudio.h"

static float angle = 0.0f;

#define STEREO

void data_callback(ma_device* device, void* output, const void* input, ma_uint32 frame_count) {
    float* t = (float*)output;
    float a = angle;

    for (int i = 0; i < frame_count; ++i) {
        *t++ = sin(a);
#ifdef STEREO
        *t++ = sin(a);
#endif
        a += 0.01f;
    }

    angle += 0.01f;
}

int main() {
    ma_result result;
    ma_device_config deviceConfig;
    ma_device device;

    deviceConfig = ma_device_config_init(ma_device_type_playback);
    deviceConfig.playback.format = ma_format_f32;
#ifdef STEREO
    deviceConfig.playback.channels = 2;
#else
    deviceConfig.playback.channels = 1;
#endif
    deviceConfig.sampleRate = 48000;
    deviceConfig.dataCallback = data_callback;
    deviceConfig.pUserData = 0;

    if (ma_device_init(NULL, &deviceConfig, &device) != MA_SUCCESS) {
        printf("Failed to open playback device.\n");
        return -3;
    }

    if (ma_device_start(&device) != MA_SUCCESS) {
        printf("Failed to start playback device.\n");
        ma_device_uninit(&device);
        return -4;
    }

    printf("Press Enter to quit...");
    getchar();

    ma_device_uninit(&device);

    return 0;
}

It will work correct with //#define STEREO (i.e) mono, but with 2 channels it doesn't work correctly and I only get silence as output. My device seems to have 4 channels by default (this is the output with MA_DEBUG_OUTPUT enabled)

DEBUG: Failed to initialize WASAPI backend.
DEBUG: Failed to initialize DirectSound backend.
DEBUG: Failed to initialize WinMM backend.
DEBUG: Failed to initialize Core Audio backend.
DEBUG: Failed to initialize sndio backend.
DEBUG: Failed to initialize audio(4) backend.
DEBUG: Failed to initialize OSS backend.
DEBUG: Attempting to initialize PulseAudio backend...
DEBUG: Loading library: libpulse.so
INFO: Failed to load library: libpulse.so
DEBUG: Loading library: libpulse.so.0
DEBUG: Loading symbol: pa_mainloop_new
DEBUG: Loading symbol: pa_mainloop_free
DEBUG: Loading symbol: pa_mainloop_quit
DEBUG: Loading symbol: pa_mainloop_get_api
DEBUG: Loading symbol: pa_mainloop_iterate
DEBUG: Loading symbol: pa_mainloop_wakeup
DEBUG: Loading symbol: pa_threaded_mainloop_new
DEBUG: Loading symbol: pa_threaded_mainloop_free
DEBUG: Loading symbol: pa_threaded_mainloop_start
DEBUG: Loading symbol: pa_threaded_mainloop_stop
DEBUG: Loading symbol: pa_threaded_mainloop_lock
DEBUG: Loading symbol: pa_threaded_mainloop_unlock
DEBUG: Loading symbol: pa_threaded_mainloop_wait
DEBUG: Loading symbol: pa_threaded_mainloop_signal
DEBUG: Loading symbol: pa_threaded_mainloop_accept
DEBUG: Loading symbol: pa_threaded_mainloop_get_retval
DEBUG: Loading symbol: pa_threaded_mainloop_get_api
DEBUG: Loading symbol: pa_threaded_mainloop_in_thread
DEBUG: Loading symbol: pa_threaded_mainloop_set_name
DEBUG: Loading symbol: pa_context_new
DEBUG: Loading symbol: pa_context_unref
DEBUG: Loading symbol: pa_context_connect
DEBUG: Loading symbol: pa_context_disconnect
DEBUG: Loading symbol: pa_context_set_state_callback
DEBUG: Loading symbol: pa_context_get_state
DEBUG: Loading symbol: pa_context_get_sink_info_list
DEBUG: Loading symbol: pa_context_get_source_info_list
DEBUG: Loading symbol: pa_context_get_sink_info_by_name
DEBUG: Loading symbol: pa_context_get_source_info_by_name
DEBUG: Loading symbol: pa_operation_unref
DEBUG: Loading symbol: pa_operation_get_state
DEBUG: Loading symbol: pa_channel_map_init_extend
DEBUG: Loading symbol: pa_channel_map_valid
DEBUG: Loading symbol: pa_channel_map_compatible
DEBUG: Loading symbol: pa_stream_new
DEBUG: Loading symbol: pa_stream_unref
DEBUG: Loading symbol: pa_stream_connect_playback
DEBUG: Loading symbol: pa_stream_connect_record
DEBUG: Loading symbol: pa_stream_disconnect
DEBUG: Loading symbol: pa_stream_get_state
DEBUG: Loading symbol: pa_stream_get_sample_spec
DEBUG: Loading symbol: pa_stream_get_channel_map
DEBUG: Loading symbol: pa_stream_get_buffer_attr
DEBUG: Loading symbol: pa_stream_set_buffer_attr
DEBUG: Loading symbol: pa_stream_get_device_name
DEBUG: Loading symbol: pa_stream_set_write_callback
DEBUG: Loading symbol: pa_stream_set_read_callback
DEBUG: Loading symbol: pa_stream_set_suspended_callback
DEBUG: Loading symbol: pa_stream_set_moved_callback
DEBUG: Loading symbol: pa_stream_is_suspended
DEBUG: Loading symbol: pa_stream_flush
DEBUG: Loading symbol: pa_stream_drain
DEBUG: Loading symbol: pa_stream_is_corked
DEBUG: Loading symbol: pa_stream_cork
DEBUG: Loading symbol: pa_stream_trigger
DEBUG: Loading symbol: pa_stream_begin_write
DEBUG: Loading symbol: pa_stream_write
DEBUG: Loading symbol: pa_stream_peek
DEBUG: Loading symbol: pa_stream_drop
DEBUG: Loading symbol: pa_stream_writable_size
DEBUG: Loading symbol: pa_stream_readable_size
DEBUG: System Architecture:
DEBUG:   Endian: LE
DEBUG:   SSE2:   YES
DEBUG:   AVX2:   NO
DEBUG:   NEON:   NO
INFO: [PulseAudio] Playback attr: maxlength=26448, tlength=8816, prebuf=-1, minreq=-1, fragsize=8816; periodSizeInFrames=1102
INFO: [PulseAudio] Playback sample spec: format=16-bit Signed Integer, channels=4, rate=44100
INFO: [PulseAudio] Playback actual attr: maxlength=26448, tlength=6608, prebuf=4416, minreq=2200, fragsize=8816; internalPeriodSizeInFrames=826
INFO: [PulseAudio]
INFO:   EVO4 Analog Surround 4.0 (Playback)
INFO:     Format:      32-bit IEEE Floating Point -> 16-bit Signed Integer
INFO:     Channels:    2 -> 4
INFO:     Sample Rate: 48000 -> 44100
INFO:     Buffer Size: 826*4 (3304)
INFO:     Conversion:
INFO:       Pre Format Conversion:  YES
INFO:       Post Format Conversion: NO
INFO:       Channel Routing:        YES
INFO:       Resampling:             YES
INFO:       Passthrough:            NO

Also if I modify my program to write to 4 channels it works correctly as well, it's only when the 2 -> 4 conversion is supposed to take place it seems to break.

I built the code like this (on Linux) gcc mini_test.c -o test -lm -lpthread -ldl with version 0.11.9 of miniaudio

ibrumby commented 2 years ago

I get a similar problem. I'm generating 8 channels of data (96k, f32) and get no output when the output device has 2 channels (wasapi).

Miniaudio 0.10.x works greats. Miniaudio 0.11.x has no output.

mackron commented 2 years ago

Sorry for the delay with this one. I've updated the dev branch to add some debug output to show the input and output channel maps. Are you able to try that and post the new output?

Also, I've fixed a channel map bug a while ago - not sure if that might have coincidentally fixed this issue as well (unlikely).

emoon commented 2 years ago

Sorry for the delay. The issue remains. If I run with #define STEREO I get this log (and no audio)

DEBUG: Failed to initialize WASAPI backend.
DEBUG: Failed to initialize DirectSound backend.
DEBUG: Failed to initialize WinMM backend.
DEBUG: Failed to initialize Core Audio backend.
DEBUG: Failed to initialize sndio backend.
DEBUG: Failed to initialize audio(4) backend.
DEBUG: Failed to initialize OSS backend.
DEBUG: Attempting to initialize PulseAudio backend...
DEBUG: Loading library: libpulse.so
INFO: Failed to load library: libpulse.so
DEBUG: Loading library: libpulse.so.0
DEBUG: Loading symbol: pa_mainloop_new
DEBUG: Loading symbol: pa_mainloop_free
DEBUG: Loading symbol: pa_mainloop_quit
DEBUG: Loading symbol: pa_mainloop_get_api
DEBUG: Loading symbol: pa_mainloop_iterate
DEBUG: Loading symbol: pa_mainloop_wakeup
DEBUG: Loading symbol: pa_threaded_mainloop_new
DEBUG: Loading symbol: pa_threaded_mainloop_free
DEBUG: Loading symbol: pa_threaded_mainloop_start
DEBUG: Loading symbol: pa_threaded_mainloop_stop
DEBUG: Loading symbol: pa_threaded_mainloop_lock
DEBUG: Loading symbol: pa_threaded_mainloop_unlock
DEBUG: Loading symbol: pa_threaded_mainloop_wait
DEBUG: Loading symbol: pa_threaded_mainloop_signal
DEBUG: Loading symbol: pa_threaded_mainloop_accept
DEBUG: Loading symbol: pa_threaded_mainloop_get_retval
DEBUG: Loading symbol: pa_threaded_mainloop_get_api
DEBUG: Loading symbol: pa_threaded_mainloop_in_thread
DEBUG: Loading symbol: pa_threaded_mainloop_set_name
DEBUG: Loading symbol: pa_context_new
DEBUG: Loading symbol: pa_context_unref
DEBUG: Loading symbol: pa_context_connect
DEBUG: Loading symbol: pa_context_disconnect
DEBUG: Loading symbol: pa_context_set_state_callback
DEBUG: Loading symbol: pa_context_get_state
DEBUG: Loading symbol: pa_context_get_sink_info_list
DEBUG: Loading symbol: pa_context_get_source_info_list
DEBUG: Loading symbol: pa_context_get_sink_info_by_name
DEBUG: Loading symbol: pa_context_get_source_info_by_name
DEBUG: Loading symbol: pa_operation_unref
DEBUG: Loading symbol: pa_operation_get_state
DEBUG: Loading symbol: pa_channel_map_init_extend
DEBUG: Loading symbol: pa_channel_map_valid
DEBUG: Loading symbol: pa_channel_map_compatible
DEBUG: Loading symbol: pa_stream_new
DEBUG: Loading symbol: pa_stream_unref
DEBUG: Loading symbol: pa_stream_connect_playback
DEBUG: Loading symbol: pa_stream_connect_record
DEBUG: Loading symbol: pa_stream_disconnect
DEBUG: Loading symbol: pa_stream_get_state
DEBUG: Loading symbol: pa_stream_get_sample_spec
DEBUG: Loading symbol: pa_stream_get_channel_map
DEBUG: Loading symbol: pa_stream_get_buffer_attr
DEBUG: Loading symbol: pa_stream_set_buffer_attr
DEBUG: Loading symbol: pa_stream_get_device_name
DEBUG: Loading symbol: pa_stream_set_write_callback
DEBUG: Loading symbol: pa_stream_set_read_callback
DEBUG: Loading symbol: pa_stream_set_suspended_callback
DEBUG: Loading symbol: pa_stream_set_moved_callback
DEBUG: Loading symbol: pa_stream_is_suspended
DEBUG: Loading symbol: pa_stream_flush
DEBUG: Loading symbol: pa_stream_drain
DEBUG: Loading symbol: pa_stream_is_corked
DEBUG: Loading symbol: pa_stream_cork
DEBUG: Loading symbol: pa_stream_trigger
DEBUG: Loading symbol: pa_stream_begin_write
DEBUG: Loading symbol: pa_stream_write
DEBUG: Loading symbol: pa_stream_peek
DEBUG: Loading symbol: pa_stream_drop
DEBUG: Loading symbol: pa_stream_writable_size
DEBUG: Loading symbol: pa_stream_readable_size
DEBUG: System Architecture:
DEBUG:   Endian: LE
DEBUG:   SSE2:   YES
DEBUG:   AVX2:   NO
DEBUG:   NEON:   NO
INFO: [PulseAudio] Playback attr: maxlength=26448, tlength=8816, prebuf=-1, minreq=-1, fragsize=8816; periodSizeInFrames=1102
INFO: [PulseAudio] Playback sample spec: format=16-bit Signed Integer, channels=4, rate=44100
INFO: [PulseAudio] Playback actual attr: maxlength=26448, tlength=10048, prebuf=7856, minreq=2200, fragsize=8816; internalPeriodSizeInFrames=1653
INFO: [PulseAudio]
INFO:   EVO4 Analog Surround 4.0 (Playback)
INFO:     Format:      32-bit IEEE Floating Point -> 16-bit Signed Integer
INFO:     Channels:    2 -> 4
INFO:     Sample Rate: 48000 -> 44100
INFO:     Buffer Size: 1653*2 (3306)
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}
INFO:       Channel Map Out:        {CHANNEL_FRONT_LEFT CHANNEL_FRONT_RIGHT CHANNEL_BACK_LEFT CHANNEL_BACK_RIGHT}

If I switch disable the define I will get this log (and audio output works)

DEBUG: Failed to initialize WASAPI backend.
DEBUG: Failed to initialize DirectSound backend.
DEBUG: Failed to initialize WinMM backend.
DEBUG: Failed to initialize Core Audio backend.
DEBUG: Failed to initialize sndio backend.
DEBUG: Failed to initialize audio(4) backend.
DEBUG: Failed to initialize OSS backend.
DEBUG: Attempting to initialize PulseAudio backend...
DEBUG: Loading library: libpulse.so
INFO: Failed to load library: libpulse.so
DEBUG: Loading library: libpulse.so.0
DEBUG: Loading symbol: pa_mainloop_new
DEBUG: Loading symbol: pa_mainloop_free
DEBUG: Loading symbol: pa_mainloop_quit
DEBUG: Loading symbol: pa_mainloop_get_api
DEBUG: Loading symbol: pa_mainloop_iterate
DEBUG: Loading symbol: pa_mainloop_wakeup
DEBUG: Loading symbol: pa_threaded_mainloop_new
DEBUG: Loading symbol: pa_threaded_mainloop_free
DEBUG: Loading symbol: pa_threaded_mainloop_start
DEBUG: Loading symbol: pa_threaded_mainloop_stop
DEBUG: Loading symbol: pa_threaded_mainloop_lock
DEBUG: Loading symbol: pa_threaded_mainloop_unlock
DEBUG: Loading symbol: pa_threaded_mainloop_wait
DEBUG: Loading symbol: pa_threaded_mainloop_signal
DEBUG: Loading symbol: pa_threaded_mainloop_accept
DEBUG: Loading symbol: pa_threaded_mainloop_get_retval
DEBUG: Loading symbol: pa_threaded_mainloop_get_api
DEBUG: Loading symbol: pa_threaded_mainloop_in_thread
DEBUG: Loading symbol: pa_threaded_mainloop_set_name
DEBUG: Loading symbol: pa_context_new
DEBUG: Loading symbol: pa_context_unref
DEBUG: Loading symbol: pa_context_connect
DEBUG: Loading symbol: pa_context_disconnect
DEBUG: Loading symbol: pa_context_set_state_callback
DEBUG: Loading symbol: pa_context_get_state
DEBUG: Loading symbol: pa_context_get_sink_info_list
DEBUG: Loading symbol: pa_context_get_source_info_list
DEBUG: Loading symbol: pa_context_get_sink_info_by_name
DEBUG: Loading symbol: pa_context_get_source_info_by_name
DEBUG: Loading symbol: pa_operation_unref
DEBUG: Loading symbol: pa_operation_get_state
DEBUG: Loading symbol: pa_channel_map_init_extend
DEBUG: Loading symbol: pa_channel_map_valid
DEBUG: Loading symbol: pa_channel_map_compatible
DEBUG: Loading symbol: pa_stream_new
DEBUG: Loading symbol: pa_stream_unref
DEBUG: Loading symbol: pa_stream_connect_playback
DEBUG: Loading symbol: pa_stream_connect_record
DEBUG: Loading symbol: pa_stream_disconnect
DEBUG: Loading symbol: pa_stream_get_state
DEBUG: Loading symbol: pa_stream_get_sample_spec
DEBUG: Loading symbol: pa_stream_get_channel_map
DEBUG: Loading symbol: pa_stream_get_buffer_attr
DEBUG: Loading symbol: pa_stream_set_buffer_attr
DEBUG: Loading symbol: pa_stream_get_device_name
DEBUG: Loading symbol: pa_stream_set_write_callback
DEBUG: Loading symbol: pa_stream_set_read_callback
DEBUG: Loading symbol: pa_stream_set_suspended_callback
DEBUG: Loading symbol: pa_stream_set_moved_callback
DEBUG: Loading symbol: pa_stream_is_suspended
DEBUG: Loading symbol: pa_stream_flush
DEBUG: Loading symbol: pa_stream_drain
DEBUG: Loading symbol: pa_stream_is_corked
DEBUG: Loading symbol: pa_stream_cork
DEBUG: Loading symbol: pa_stream_trigger
DEBUG: Loading symbol: pa_stream_begin_write
DEBUG: Loading symbol: pa_stream_write
DEBUG: Loading symbol: pa_stream_peek
DEBUG: Loading symbol: pa_stream_drop
DEBUG: Loading symbol: pa_stream_writable_size
DEBUG: Loading symbol: pa_stream_readable_size
DEBUG: System Architecture:
DEBUG:   Endian: LE
DEBUG:   SSE2:   YES
DEBUG:   AVX2:   NO
DEBUG:   NEON:   NO
INFO: [PulseAudio] Playback attr: maxlength=26448, tlength=8816, prebuf=-1, minreq=-1, fragsize=8816; periodSizeInFrames=1102
INFO: [PulseAudio] Playback sample spec: format=16-bit Signed Integer, channels=4, rate=44100
INFO: [PulseAudio] Playback actual attr: maxlength=26448, tlength=10048, prebuf=7856, minreq=2200, fragsize=8816; internalPeriodSizeInFrames=1653
INFO: [PulseAudio]
INFO:   EVO4 Analog Surround 4.0 (Playback)
INFO:     Format:      32-bit IEEE Floating Point -> 16-bit Signed Integer
INFO:     Channels:    1 -> 4
INFO:     Sample Rate: 48000 -> 44100
INFO:     Buffer Size: 1653*2 (3306)
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_MONO}
INFO:       Channel Map Out:        {CHANNEL_FRONT_LEFT CHANNEL_FRONT_RIGHT CHANNEL_BACK_LEFT CHANNEL_BACK_RIGHT}
mackron commented 2 years ago

Thanks for the update. Your channel maps look fine and I would expect them to work. I will put together a test and try to replicate this.

tycho commented 2 years ago

I already talked with @mackron via Discord about this, but I'm recording notes of what I've observed for channel mapping weirdness.

I've seen similar channel mapping issues on both Linux (Pipewire via the Pulseaudio backend) and Windows (via WASAPI backend).

It seems like the weights that ma_channel_mix_mode_rectangular is generating must be wrong somehow, because these two do not expand correctly:


For the first scenario, 6-channel (5.1) audio only plays on the RL/RR channels on Pipewire, or only to the SL/SR channels on Windows. All the other output channels are receiving silence.

The channel map looks correct, which is why I suspect the calculated rectangular weights are the problem:

Channel Map In:         {CHANNEL_FRONT_LEFT CHANNEL_FRONT_RIGHT CHANNEL_FRONT_CENTER CHANNEL_LFE CHANNEL_SIDE_LEFT CHANNEL_SIDE_RIGHT}
Channel Map Out:        {CHANNEL_FRONT_LEFT CHANNEL_FRONT_RIGHT CHANNEL_FRONT_CENTER CHANNEL_LFE CHANNEL_BACK_LEFT CHANNEL_BACK_RIGHT CHANNEL_SIDE_LEFT CHANNEL_SIDE_RIGHT}
2022-08-10-0341-Orban_Loudness_Meter_dbT4kw7rnP

For the second scenario, 2-channel audio gets mapped to FC, SL, SR, RL, RR.

Again the channel maps look reasonable:

Channel Map In:         {CHANNEL_FRONT_LEFT CHANNEL_FRONT_RIGHT}
Channel Map Out:        {CHANNEL_FRONT_LEFT CHANNEL_FRONT_RIGHT CHANNEL_FRONT_CENTER CHANNEL_LFE CHANNEL_BACK_LEFT CHANNEL_BACK_RIGHT CHANNEL_SIDE_LEFT CHANNEL_SIDE_RIGHT}
2022-08-10-0340-Orban_Loudness_Meter_yqLIjatMa9

On both OSes, running with ma_channel_mix_mode_simple does work around this and maps to the expected output channels. But as @mackron pointed out to me over Discord, the simple mode is probably not a good idea to use long-term, as it only has a few special cases for unusual device channel maps. For example, it has a special case for devices with stereo implemented as SIDE_LEFT + SIDE_RIGHT instead of FRONT_LEFT + FRONT_RIGHT, but it won't have a way to deal with the other more unusual channel maps.

tycho commented 2 years ago

Also I added some debug code to dump the channel mapping weight table for the 2-channel and 6-channel configs, that does appear to be the source of the problem:

image

mackron commented 2 years ago

Very sorry for the late response on this. I've pushed a potential fix to the dev branch. Are you able to give that a try?

ibrumby commented 2 years ago

For me at least this has resolved the issue.

emoon commented 2 years ago

This fixes the issue for me as well.

mackron commented 2 years ago

Thanks everyone for testing. This is affecting a few people now so I'll be getting this out very soon.

mackron commented 2 years ago

I've finally got this released. Version 0.11.10.