From 7634cbeb224a064868084639aa557be0f521743e Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 8 Aug 2018 18:26:05 +0200 Subject: Updated mini_al Corrected issue with sound playing (pop sound at the end) --- src/external/mini_al.c | 6 +- src/external/mini_al.h | 10430 +++++++++++++++++++++++++++-------------------- 2 files changed, 6107 insertions(+), 4329 deletions(-) (limited to 'src') diff --git a/src/external/mini_al.c b/src/external/mini_al.c index 1ce94419..eabb6365 100644 --- a/src/external/mini_al.c +++ b/src/external/mini_al.c @@ -1,4 +1,8 @@ // The implementation of mini_al needs to #include windows.h which means it needs to go into // it's own translation unit. Not doing this will cause conflicts with CloseWindow(), etc. #define MINI_AL_IMPLEMENTATION -#include "mini_al.h" \ No newline at end of file +#define MAL_NO_JACK +#define MAL_NO_OPENAL +#define MAL_NO_SDL +#define MAL_NO_NULL +#include "mini_al.h" diff --git a/src/external/mini_al.h b/src/external/mini_al.h index 4314343d..829a7396 100644 --- a/src/external/mini_al.h +++ b/src/external/mini_al.h @@ -1,5 +1,5 @@ // Audio playback and capture library. Public domain. See "unlicense" statement at the end of this file. -// mini_al - v0.8.3 - 2018-07-15 +// mini_al - v0.8.5-rc - 2018-xx-xx // // David Reid - davidreidsoftware@gmail.com @@ -20,7 +20,9 @@ // - ALSA // - PulseAudio // - JACK -// - OSS +// - sndio (OpenBSD) +// - audioio (NetBSD) +// - OSS (FreeBSD) // - OpenSL|ES (Android only) // - OpenAL // - SDL @@ -65,8 +67,8 @@ // // Building for BSD // ---------------- -// The BSD build uses OSS. Requires linking to -lpthread and -lm. Also requires linking to -lossaudio on {Open,Net}BSD, -// but not FreeBSD. +// The BSD build only requires linking to -ldl, -lpthread and -lm. NetBSD uses audio(4), OpenBSD uses sndio and +// FreeBSD uses OSS. // // Building for Android // -------------------- @@ -116,8 +118,9 @@ // - If mal_device_init() is called with a device that's not aligned to the platform's natural alignment // boundary (4 bytes on 32-bit, 8 bytes on 64-bit), it will _not_ be thread-safe. The reason for this // is that it depends on members of mal_device being correctly aligned for atomic assignments. -// - Sample data is always little-endian and interleaved. For example, mal_format_s16 means signed 16-bit +// - Sample data is always native-endian and interleaved. For example, mal_format_s16 means signed 16-bit // integer samples, interleaved. Let me know if you need non-interleaved and I'll look into it. +// - The sndio backend is currently only enabled on OpenBSD builds. // // // @@ -180,6 +183,12 @@ // #define MAL_NO_COREAUDIO // Disables the Core Audio backend. // +// #define MAL_NO_SNDIO +// Disables the sndio backend. +// +// #define MAL_NO_AUDIOIO +// Disables the audioio backend. +// // #define MAL_NO_OSS // Disables the OSS backend. // @@ -207,6 +216,10 @@ // #define MAL_NO_DECODING // Disables the decoding APIs. // +// #define MAL_NO_DEVICE_IO +// Disables playback and recording. This will disable mal_context and mal_device APIs. This is useful if you only want to +// use mini_al's data conversion and/or decoding APIs. +// // #define MAL_NO_STDIO // Disables file IO APIs. // @@ -275,82 +288,6 @@ extern "C" { #endif #endif -// Some backends are only supported on certain platforms. -#if defined(MAL_WIN32) - #define MAL_SUPPORT_WASAPI - #if defined(MAL_WIN32_DESKTOP) // DirectSound and WinMM backends are only supported on desktop's. - #define MAL_SUPPORT_DSOUND - #define MAL_SUPPORT_WINMM - #define MAL_SUPPORT_JACK // JACK is technically supported on Windows, but I don't know how many people use it in practice... - #endif -#endif -#if defined(MAL_UNIX) - #if defined(MAL_LINUX) - #if !defined(MAL_ANDROID) // ALSA is not supported on Android. - #define MAL_SUPPORT_ALSA - #endif - #endif - #if !defined(MAL_BSD) && !defined(MAL_ANDROID) && !defined(MAL_EMSCRIPTEN) - #define MAL_SUPPORT_PULSEAUDIO - #define MAL_SUPPORT_JACK - #endif - #if defined(MAL_ANDROID) - #define MAL_SUPPORT_OPENSL - #endif - #if !defined(MAL_LINUX) && !defined(MAL_APPLE) && !defined(MAL_ANDROID) && !defined(MAL_EMSCRIPTEN) - #define MAL_SUPPORT_OSS - #endif -#endif -#if defined(MAL_APPLE) - #define MAL_SUPPORT_COREAUDIO -#endif - -#define MAL_SUPPORT_SDL // All platforms support SDL. - -// Explicitly disable OpenAL and Null backends for Emscripten because they both use a background thread which is not properly supported right now. -#if !defined(MAL_EMSCRIPTEN) -#define MAL_SUPPORT_OPENAL -#define MAL_SUPPORT_NULL // All platforms support the null backend. -#endif - - -#if !defined(MAL_NO_WASAPI) && defined(MAL_SUPPORT_WASAPI) - #define MAL_ENABLE_WASAPI -#endif -#if !defined(MAL_NO_DSOUND) && defined(MAL_SUPPORT_DSOUND) - #define MAL_ENABLE_DSOUND -#endif -#if !defined(MAL_NO_WINMM) && defined(MAL_SUPPORT_WINMM) - #define MAL_ENABLE_WINMM -#endif -#if !defined(MAL_NO_ALSA) && defined(MAL_SUPPORT_ALSA) - #define MAL_ENABLE_ALSA -#endif -#if !defined(MAL_NO_PULSEAUDIO) && defined(MAL_SUPPORT_PULSEAUDIO) - #define MAL_ENABLE_PULSEAUDIO -#endif -#if !defined(MAL_NO_JACK) && defined(MAL_SUPPORT_JACK) - #define MAL_ENABLE_JACK -#endif -#if !defined(MAL_NO_COREAUDIO) && defined(MAL_SUPPORT_COREAUDIO) - #define MAL_ENABLE_COREAUDIO -#endif -#if !defined(MAL_NO_OSS) && defined(MAL_SUPPORT_OSS) - #define MAL_ENABLE_OSS -#endif -#if !defined(MAL_NO_OPENSL) && defined(MAL_SUPPORT_OPENSL) - #define MAL_ENABLE_OPENSL -#endif -#if !defined(MAL_NO_OPENAL) && defined(MAL_SUPPORT_OPENAL) - #define MAL_ENABLE_OPENAL -#endif -#if !defined(MAL_NO_SDL) && defined(MAL_SUPPORT_SDL) - #define MAL_ENABLE_SDL -#endif -#if !defined(MAL_NO_NULL) && defined(MAL_SUPPORT_NULL) - #define MAL_ENABLE_NULL -#endif - #include // For size_t. #ifndef MAL_HAS_STDINT @@ -479,122 +416,6 @@ typedef mal_uint16 wchar_t; #endif -// Thread priorties should be ordered such that the default priority of the worker thread is 0. -typedef enum -{ - mal_thread_priority_idle = -5, - mal_thread_priority_lowest = -4, - mal_thread_priority_low = -3, - mal_thread_priority_normal = -2, - mal_thread_priority_high = -1, - mal_thread_priority_highest = 0, - mal_thread_priority_realtime = 1, - mal_thread_priority_default = 0 -} mal_thread_priority; - -typedef struct -{ - mal_context* pContext; - - union - { -#ifdef MAL_WIN32 - struct - { - /*HANDLE*/ mal_handle hThread; - } win32; -#endif -#ifdef MAL_POSIX - struct - { - pthread_t thread; - } posix; -#endif - - int _unused; - }; -} mal_thread; - -typedef struct -{ - mal_context* pContext; - - union - { -#ifdef MAL_WIN32 - struct - { - /*HANDLE*/ mal_handle hMutex; - } win32; -#endif -#ifdef MAL_POSIX - struct - { - pthread_mutex_t mutex; - } posix; -#endif - - int _unused; - }; -} mal_mutex; - -typedef struct -{ - mal_context* pContext; - - union - { -#ifdef MAL_WIN32 - struct - { - /*HANDLE*/ mal_handle hEvent; - } win32; -#endif -#ifdef MAL_POSIX - struct - { - pthread_mutex_t mutex; - pthread_cond_t condition; - mal_uint32 value; - } posix; -#endif - - int _unused; - }; -} mal_event; - - -#define MAL_MAX_PERIODS_DSOUND 4 -#define MAL_MAX_PERIODS_OPENAL 4 - -// Standard sample rates. -#define MAL_SAMPLE_RATE_8000 8000 -#define MAL_SAMPLE_RATE_11025 11025 -#define MAL_SAMPLE_RATE_16000 16000 -#define MAL_SAMPLE_RATE_22050 22050 -#define MAL_SAMPLE_RATE_24000 24000 -#define MAL_SAMPLE_RATE_32000 32000 -#define MAL_SAMPLE_RATE_44100 44100 -#define MAL_SAMPLE_RATE_48000 48000 -#define MAL_SAMPLE_RATE_88200 88200 -#define MAL_SAMPLE_RATE_96000 96000 -#define MAL_SAMPLE_RATE_176400 176400 -#define MAL_SAMPLE_RATE_192000 192000 -#define MAL_SAMPLE_RATE_352800 352800 -#define MAL_SAMPLE_RATE_384000 384000 - -#define MAL_MIN_PCM_SAMPLE_SIZE_IN_BYTES 1 // For simplicity, mini_al does not support PCM samples that are not byte aligned. -#define MAL_MAX_PCM_SAMPLE_SIZE_IN_BYTES 8 -#define MAL_MIN_CHANNELS 1 -#define MAL_MAX_CHANNELS 32 -#define MAL_MIN_SAMPLE_RATE MAL_SAMPLE_RATE_8000 -#define MAL_MAX_SAMPLE_RATE MAL_SAMPLE_RATE_384000 -#define MAL_SRC_SINC_MIN_WINDOW_WIDTH 2 -#define MAL_SRC_SINC_MAX_WINDOW_WIDTH 32 -#define MAL_SRC_SINC_DEFAULT_WINDOW_WIDTH 16 -#define MAL_SRC_SINC_LOOKUP_TABLE_RESOLUTION 8 -#define MAL_SRC_INPUT_BUFFER_SIZE_IN_SAMPLES 256 - typedef mal_uint8 mal_channel; #define MAL_CHANNEL_NONE 0 #define MAL_CHANNEL_MONO 1 @@ -688,38 +509,33 @@ typedef int mal_result; #define MAL_ACCESS_DENIED -32 #define MAL_TOO_LARGE -33 -typedef void (* mal_log_proc) (mal_context* pContext, mal_device* pDevice, const char* message); -typedef void (* mal_recv_proc)(mal_device* pDevice, mal_uint32 frameCount, const void* pSamples); -typedef mal_uint32 (* mal_send_proc)(mal_device* pDevice, mal_uint32 frameCount, void* pSamples); -typedef void (* mal_stop_proc)(mal_device* pDevice); - -typedef enum -{ - mal_backend_null, - mal_backend_wasapi, - mal_backend_dsound, - mal_backend_winmm, - mal_backend_alsa, - mal_backend_pulseaudio, - mal_backend_jack, - mal_backend_coreaudio, - mal_backend_oss, - mal_backend_opensl, - mal_backend_openal, - mal_backend_sdl -} mal_backend; - -typedef enum -{ - mal_device_type_playback, - mal_device_type_capture -} mal_device_type; +// Standard sample rates. +#define MAL_SAMPLE_RATE_8000 8000 +#define MAL_SAMPLE_RATE_11025 11025 +#define MAL_SAMPLE_RATE_16000 16000 +#define MAL_SAMPLE_RATE_22050 22050 +#define MAL_SAMPLE_RATE_24000 24000 +#define MAL_SAMPLE_RATE_32000 32000 +#define MAL_SAMPLE_RATE_44100 44100 +#define MAL_SAMPLE_RATE_48000 48000 +#define MAL_SAMPLE_RATE_88200 88200 +#define MAL_SAMPLE_RATE_96000 96000 +#define MAL_SAMPLE_RATE_176400 176400 +#define MAL_SAMPLE_RATE_192000 192000 +#define MAL_SAMPLE_RATE_352800 352800 +#define MAL_SAMPLE_RATE_384000 384000 -typedef enum -{ - mal_share_mode_shared = 0, - mal_share_mode_exclusive, -} mal_share_mode; +#define MAL_MIN_PCM_SAMPLE_SIZE_IN_BYTES 1 // For simplicity, mini_al does not support PCM samples that are not byte aligned. +#define MAL_MAX_PCM_SAMPLE_SIZE_IN_BYTES 8 +#define MAL_MIN_CHANNELS 1 +#define MAL_MAX_CHANNELS 32 +#define MAL_MIN_SAMPLE_RATE MAL_SAMPLE_RATE_8000 +#define MAL_MAX_SAMPLE_RATE MAL_SAMPLE_RATE_384000 +#define MAL_SRC_SINC_MIN_WINDOW_WIDTH 2 +#define MAL_SRC_SINC_MAX_WINDOW_WIDTH 32 +#define MAL_SRC_SINC_DEFAULT_WINDOW_WIDTH 32 +#define MAL_SRC_SINC_LOOKUP_TABLE_RESOLUTION 8 +#define MAL_SRC_INPUT_BUFFER_SIZE_IN_SAMPLES 256 typedef enum { @@ -766,6 +582,7 @@ typedef enum mal_standard_channel_map_rfc3551, // Based off AIFF. mal_standard_channel_map_flac, mal_standard_channel_map_vorbis, + mal_standard_channel_map_sndio, // www.sndio.org/tips.html mal_standard_channel_map_default = mal_standard_channel_map_microsoft } mal_standard_channel_map; @@ -775,78 +592,12 @@ typedef enum mal_performance_profile_conservative } mal_performance_profile; -typedef union -{ -#ifdef MAL_SUPPORT_WASAPI - wchar_t wasapi[64]; // WASAPI uses a wchar_t string for identification. -#endif -#ifdef MAL_SUPPORT_DSOUND - mal_uint8 dsound[16]; // DirectSound uses a GUID for identification. -#endif -#ifdef MAL_SUPPORT_WINMM - /*UINT_PTR*/ mal_uint32 winmm; // When creating a device, WinMM expects a Win32 UINT_PTR for device identification. In practice it's actually just a UINT. -#endif -#ifdef MAL_SUPPORT_ALSA - char alsa[256]; // ALSA uses a name string for identification. -#endif -#ifdef MAL_SUPPORT_PULSEAUDIO - char pulse[256]; // PulseAudio uses a name string for identification. -#endif -#ifdef MAL_SUPPORT_JACK - int jack; // JACK always uses default devices. -#endif -#ifdef MAL_SUPPORT_COREAUDIO - char coreaudio[256]; // Core Audio uses a string for identification. -#endif -#ifdef MAL_SUPPORT_OSS - char oss[64]; // "dev/dsp0", etc. "dev/dsp" for the default device. -#endif -#ifdef MAL_SUPPORT_OPENSL - mal_uint32 opensl; // OpenSL|ES uses a 32-bit unsigned integer for identification. -#endif -#ifdef MAL_SUPPORT_OPENAL - char openal[256]; // OpenAL seems to use human-readable device names as the ID. -#endif -#ifdef MAL_SUPPORT_SDL - int sdl; // SDL devices are identified with an index. -#endif -#ifdef MAL_SUPPORT_NULL - int nullbackend; // The null backend uses an integer for device IDs. -#endif -} mal_device_id; - -typedef struct -{ - // Basic info. This is the only information guaranteed to be filled in during device enumeration. - mal_device_id id; - char name[256]; - - // Detailed info. As much of this is filled as possible with mal_context_get_device_info(). Note that you are allowed to initialize - // a device with settings outside of this range, but it just means the data will be converted using mini_al's data conversion - // pipeline before sending the data to/from the device. Most programs will need to not worry about these values, but it's provided - // here mainly for informational purposes or in the rare case that someone might find it useful. - // - // These will be set to 0 when returned by mal_context_enumerate_devices() or mal_context_get_devices(). - mal_uint32 formatCount; - mal_format formats[mal_format_count]; - mal_uint32 minChannels; - mal_uint32 maxChannels; - mal_uint32 minSampleRate; - mal_uint32 maxSampleRate; -} mal_device_info; - -typedef struct -{ - mal_int64 counter; -} mal_timer; - - - -typedef struct mal_format_converter mal_format_converter; -typedef mal_uint32 (* mal_format_converter_read_proc) (mal_format_converter* pConverter, mal_uint32 frameCount, void* pFramesOut, void* pUserData); -typedef mal_uint32 (* mal_format_converter_read_deinterleaved_proc)(mal_format_converter* pConverter, mal_uint32 frameCount, void** ppSamplesOut, void* pUserData); - -typedef struct + +typedef struct mal_format_converter mal_format_converter; +typedef mal_uint32 (* mal_format_converter_read_proc) (mal_format_converter* pConverter, mal_uint32 frameCount, void* pFramesOut, void* pUserData); +typedef mal_uint32 (* mal_format_converter_read_deinterleaved_proc)(mal_format_converter* pConverter, mal_uint32 frameCount, void** ppSamplesOut, void* pUserData); + +typedef struct { mal_format formatIn; mal_format formatOut; @@ -929,6 +680,12 @@ typedef enum mal_src_sinc_window_function_default = mal_src_sinc_window_function_hann } mal_src_sinc_window_function; +typedef struct +{ + mal_src_sinc_window_function windowFunction; + mal_uint32 windowWidth; +} mal_src_config_sinc; + typedef struct { mal_uint32 sampleRateIn; @@ -944,11 +701,7 @@ typedef struct void* pUserData; union { - struct - { - mal_src_sinc_window_function windowFunction; - mal_uint32 windowWidth; - } sinc; + mal_src_config_sinc sinc; }; } mal_src_config; @@ -1007,11 +760,7 @@ typedef struct void* pUserData; union { - struct - { - mal_src_sinc_window_function windowFunction; - mal_uint32 windowWidth; - } sinc; + mal_src_config_sinc sinc; }; } mal_dsp_config; @@ -1033,159 +782,790 @@ MAL_ALIGNED_STRUCT(MAL_SIMD_ALIGNMENT) mal_dsp }; -typedef struct -{ - mal_format format; - mal_uint32 channels; - mal_uint32 sampleRate; - mal_channel channelMap[MAL_MAX_CHANNELS]; - mal_uint32 bufferSizeInFrames; - mal_uint32 periods; - mal_share_mode shareMode; - mal_performance_profile performanceProfile; - mal_recv_proc onRecvCallback; - mal_send_proc onSendCallback; - mal_stop_proc onStopCallback; +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// DATA CONVERSION +// =============== +// +// This section contains the APIs for data conversion. You will find everything here for channel mapping, sample format conversion, resampling, etc. +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - struct - { - mal_bool32 noMMap; // Disables MMap mode. - } alsa; +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Channel Maps +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - struct - { - const char* pStreamName; - } pulse; -} mal_device_config; +// Helper for retrieving a standard channel map. +void mal_get_standard_channel_map(mal_standard_channel_map standardChannelMap, mal_uint32 channels, mal_channel channelMap[MAL_MAX_CHANNELS]); -typedef struct -{ - mal_log_proc onLog; - mal_thread_priority threadPriority; +// Copies a channel map. +void mal_channel_map_copy(mal_channel* pOut, const mal_channel* pIn, mal_uint32 channels); - struct - { - mal_bool32 useVerboseDeviceEnumeration; - } alsa; - struct - { - const char* pApplicationName; - const char* pServerName; - mal_bool32 tryAutoSpawn; // Enables autospawning of the PulseAudio daemon if necessary. - } pulse; +// Determines whether or not a channel map is valid. +// +// A blank channel map is valid (all channels set to MAL_CHANNEL_NONE). The way a blank channel map is handled is context specific, but +// is usually treated as a passthrough. +// +// Invalid channel maps: +// - A channel map with no channels +// - A channel map with more than one channel and a mono channel +mal_bool32 mal_channel_map_valid(mal_uint32 channels, const mal_channel channelMap[MAL_MAX_CHANNELS]); - struct - { - const char* pClientName; - mal_bool32 tryStartServer; - } jack; -} mal_context_config; +// Helper for comparing two channel maps for equality. +// +// This assumes the channel count is the same between the two. +mal_bool32 mal_channel_map_equal(mal_uint32 channels, const mal_channel channelMapA[MAL_MAX_CHANNELS], const mal_channel channelMapB[MAL_MAX_CHANNELS]); -typedef mal_bool32 (* mal_enum_devices_callback_proc)(mal_context* pContext, mal_device_type type, const mal_device_info* pInfo, void* pUserData); +// Helper for determining if a channel map is blank (all channels set to MAL_CHANNEL_NONE). +mal_bool32 mal_channel_map_blank(mal_uint32 channels, const mal_channel channelMap[MAL_MAX_CHANNELS]); -struct mal_context -{ - mal_backend backend; // DirectSound, ALSA, etc. - mal_context_config config; - mal_mutex deviceEnumLock; // Used to make mal_context_get_devices() thread safe. - mal_mutex deviceInfoLock; // Used to make mal_context_get_device_info() thread safe. - mal_uint32 deviceInfoCapacity; // Total capacity of pDeviceInfos. - mal_uint32 playbackDeviceInfoCount; - mal_uint32 captureDeviceInfoCount; - mal_device_info* pDeviceInfos; // Playback devices first, then capture. - mal_bool32 isBackendAsynchronous : 1; // Set when the context is initialized. Set to 1 for asynchronous backends such as Core Audio and JACK. Do not modify. +// Helper for determining whether or not a channel is present in the given channel map. +mal_bool32 mal_channel_map_contains_channel_position(mal_uint32 channels, const mal_channel channelMap[MAL_MAX_CHANNELS], mal_channel channelPosition); - mal_result (* onUninit )(mal_context* pContext); - mal_bool32 (* onDeviceIDEqual )(mal_context* pContext, const mal_device_id* pID0, const mal_device_id* pID1); - mal_result (* onEnumDevices )(mal_context* pContext, mal_enum_devices_callback_proc callback, void* pUserData); // Return false from the callback to stop enumeration. - mal_result (* onGetDeviceInfo )(mal_context* pContext, mal_device_type type, const mal_device_id* pDeviceID, mal_share_mode shareMode, mal_device_info* pDeviceInfo); - mal_result (* onDeviceInit )(mal_context* pContext, mal_device_type type, const mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice); - void (* onDeviceUninit )(mal_device* pDevice); - mal_result (* onDeviceStart )(mal_device* pDevice); - mal_result (* onDeviceStop )(mal_device* pDevice); - mal_result (* onDeviceBreakMainLoop)(mal_device* pDevice); - mal_result (* onDeviceMainLoop )(mal_device* pDevice); - union - { -#ifdef MAL_SUPPORT_WASAPI - struct - { - int _unused; - } wasapi; -#endif -#ifdef MAL_SUPPORT_DSOUND - struct - { - /*HMODULE*/ mal_handle hDSoundDLL; - mal_proc DirectSoundCreate; - mal_proc DirectSoundEnumerateA; - mal_proc DirectSoundCaptureCreate; - mal_proc DirectSoundCaptureEnumerateA; - } dsound; -#endif -#ifdef MAL_SUPPORT_WINMM - struct - { - /*HMODULE*/ mal_handle hWinMM; - mal_proc waveOutGetNumDevs; - mal_proc waveOutGetDevCapsA; - mal_proc waveOutOpen; - mal_proc waveOutClose; - mal_proc waveOutPrepareHeader; - mal_proc waveOutUnprepareHeader; - mal_proc waveOutWrite; - mal_proc waveOutReset; - mal_proc waveInGetNumDevs; - mal_proc waveInGetDevCapsA; - mal_proc waveInOpen; - mal_proc waveInClose; - mal_proc waveInPrepareHeader; - mal_proc waveInUnprepareHeader; - mal_proc waveInAddBuffer; - mal_proc waveInStart; - mal_proc waveInReset; - } winmm; -#endif -#ifdef MAL_SUPPORT_ALSA - struct - { - mal_handle asoundSO; - mal_proc snd_pcm_open; - mal_proc snd_pcm_close; - mal_proc snd_pcm_hw_params_sizeof; - mal_proc snd_pcm_hw_params_any; - mal_proc snd_pcm_hw_params_set_format; - mal_proc snd_pcm_hw_params_set_format_first; - mal_proc snd_pcm_hw_params_get_format_mask; - mal_proc snd_pcm_hw_params_set_channels_near; - mal_proc snd_pcm_hw_params_set_rate_resample; - mal_proc snd_pcm_hw_params_set_rate_near; - mal_proc snd_pcm_hw_params_set_buffer_size_near; - mal_proc snd_pcm_hw_params_set_periods_near; - mal_proc snd_pcm_hw_params_set_access; - mal_proc snd_pcm_hw_params_get_format; - mal_proc snd_pcm_hw_params_get_channels; - mal_proc snd_pcm_hw_params_get_channels_min; - mal_proc snd_pcm_hw_params_get_channels_max; - mal_proc snd_pcm_hw_params_get_rate; - mal_proc snd_pcm_hw_params_get_rate_min; - mal_proc snd_pcm_hw_params_get_rate_max; - mal_proc snd_pcm_hw_params_get_buffer_size; - mal_proc snd_pcm_hw_params_get_periods; - mal_proc snd_pcm_hw_params_get_access; - mal_proc snd_pcm_hw_params; - mal_proc snd_pcm_sw_params_sizeof; - mal_proc snd_pcm_sw_params_current; - mal_proc snd_pcm_sw_params_set_avail_min; - mal_proc snd_pcm_sw_params_set_start_threshold; - mal_proc snd_pcm_sw_params; - mal_proc snd_pcm_format_mask_sizeof; - mal_proc snd_pcm_format_mask_test; - mal_proc snd_pcm_get_chmap; - mal_proc snd_pcm_prepare; - mal_proc snd_pcm_start; +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Format Conversion +// ================= +// The format converter serves two purposes: +// 1) Conversion between data formats (u8 to f32, etc.) +// 2) Interleaving and deinterleaving +// +// When initializing a converter, you specify the input and output formats (u8, s16, etc.) and read callbacks. There are two read callbacks - one for +// interleaved input data (onRead) and another for deinterleaved input data (onReadDeinterleaved). You implement whichever is most convenient for you. You +// can implement both, but it's not recommended as it just introduces unnecessary complexity. +// +// To read data as interleaved samples, use mal_format_converter_read(). Otherwise use mal_format_converter_read_deinterleaved(). +// +// Dithering +// --------- +// The format converter also supports dithering. Dithering can be set using ditherMode variable in the config, like so. +// +// pConfig->ditherMode = mal_dither_mode_rectangle; +// +// The different dithering modes include the following, in order of efficiency: +// - None: mal_dither_mode_none +// - Rectangle: mal_dither_mode_rectangle +// - Triangle: mal_dither_mode_triangle +// +// Note that even if the dither mode is set to something other than mal_dither_mode_none, it will be ignored for conversions where dithering is not needed. +// Dithering is available for the following conversions: +// - s16 -> u8 +// - s24 -> u8 +// - s32 -> u8 +// - f32 -> u8 +// - s24 -> s16 +// - s32 -> s16 +// - f32 -> s16 +// +// Note that it is not an error to pass something other than mal_dither_mode_none for conversions where dither is not used. It will just be ignored. +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// Initializes a format converter. +mal_result mal_format_converter_init(const mal_format_converter_config* pConfig, mal_format_converter* pConverter); + +// Reads data from the format converter as interleaved channels. +mal_uint64 mal_format_converter_read(mal_format_converter* pConverter, mal_uint64 frameCount, void* pFramesOut, void* pUserData); + +// Reads data from the format converter as deinterleaved channels. +mal_uint64 mal_format_converter_read_deinterleaved(mal_format_converter* pConverter, mal_uint64 frameCount, void** ppSamplesOut, void* pUserData); + + +// Helper for initializing a format converter config. +mal_format_converter_config mal_format_converter_config_init_new(void); +mal_format_converter_config mal_format_converter_config_init(mal_format formatIn, mal_format formatOut, mal_uint32 channels, mal_format_converter_read_proc onRead, void* pUserData); +mal_format_converter_config mal_format_converter_config_init_deinterleaved(mal_format formatIn, mal_format formatOut, mal_uint32 channels, mal_format_converter_read_deinterleaved_proc onReadDeinterleaved, void* pUserData); + + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Channel Routing +// =============== +// There are two main things you can do with the channel router: +// 1) Rearrange channels +// 2) Convert from one channel count to another +// +// Channel Rearrangement +// --------------------- +// A simple example of channel rearrangement may be swapping the left and right channels in a stereo stream. To do this you just pass in the same channel +// count for both the input and output with channel maps that contain the same channels (in a different order). +// +// Channel Conversion +// ------------------ +// The channel router can also convert from one channel count to another, such as converting a 5.1 stream to stero. When changing the channel count, the +// router will first perform a 1:1 mapping of channel positions that are present in both the input and output channel maps. The second thing it will do +// is distribute the input mono channel (if any) across all output channels, excluding any None and LFE channels. If there is an output mono channel, all +// input channels will be averaged, excluding any None and LFE channels. +// +// The last case to consider is when a channel position in the input channel map is not present in the output channel map, and vice versa. In this case the +// channel router will perform a blend of other related channels to produce an audible channel. There are several blending modes. +// 1) Simple +// Unmatched channels are silenced. +// 2) Planar Blending +// Channels are blended based on a set of planes that each speaker emits audio from. +// +// Planar Blending +// --------------- +// In this mode, channel positions are associated with a set of planes where the channel conceptually emits audio from. An example is the front/left speaker. +// This speaker is positioned to the front of the listener, so you can think of it as emitting audio from the front plane. It is also positioned to the left +// of the listener so you can think of it as also emitting audio from the left plane. Now consider the (unrealistic) situation where the input channel map +// contains only the front/left channel position, but the output channel map contains both the front/left and front/center channel. When deciding on the audio +// data to send to the front/center speaker (which has no 1:1 mapping with an input channel) we need to use some logic based on our available input channel +// positions. +// +// As mentioned earlier, our front/left speaker is, conceptually speaking, emitting audio from the front _and_ the left planes. Similarly, the front/center +// speaker is emitting audio from _only_ the front plane. What these two channels have in common is that they are both emitting audio from the front plane. +// Thus, it makes sense that the front/center speaker should receive some contribution from the front/left channel. How much contribution depends on their +// planar relationship (thus the name of this blending technique). +// +// Because the front/left channel is emitting audio from two planes (front and left), you can think of it as though it's willing to dedicate 50% of it's total +// volume to each of it's planes (a channel position emitting from 1 plane would be willing to given 100% of it's total volume to that plane, and a channel +// position emitting from 3 planes would be willing to given 33% of it's total volume to each plane). Similarly, the front/center speaker is emitting audio +// from only one plane so you can think of it as though it's willing to _take_ 100% of it's volume from front plane emissions. Now, since the front/left +// channel is willing to _give_ 50% of it's total volume to the front plane, and the front/center speaker is willing to _take_ 100% of it's total volume +// from the front, you can imagine that 50% of the front/left speaker will be given to the front/center speaker. +// +// Usage +// ----- +// To use the channel router you need to specify three things: +// 1) The input channel count and channel map +// 2) The output channel count and channel map +// 3) The mixing mode to use in the case where a 1:1 mapping is unavailable +// +// Note that input and output data is always deinterleaved 32-bit floating point. +// +// Initialize the channel router with mal_channel_router_init(). You will need to pass in a config object which specifies the input and output configuration, +// mixing mode and a callback for sending data to the router. This callback will be called when input data needs to be sent to the router for processing. +// +// Read data from the channel router with mal_channel_router_read_deinterleaved(). Output data is always 32-bit floating point. +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// Initializes a channel router where it is assumed that the input data is non-interleaved. +mal_result mal_channel_router_init(const mal_channel_router_config* pConfig, mal_channel_router* pRouter); + +// Reads data from the channel router as deinterleaved channels. +mal_uint64 mal_channel_router_read_deinterleaved(mal_channel_router* pRouter, mal_uint64 frameCount, void** ppSamplesOut, void* pUserData); + +// Helper for initializing a channel router config. +mal_channel_router_config mal_channel_router_config_init(mal_uint32 channelsIn, const mal_channel channelMapIn[MAL_MAX_CHANNELS], mal_uint32 channelsOut, const mal_channel channelMapOut[MAL_MAX_CHANNELS], mal_channel_mix_mode mixingMode, mal_channel_router_read_deinterleaved_proc onRead, void* pUserData); + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Sample Rate Conversion +// ====================== +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// Initializes a sample rate conversion object. +mal_result mal_src_init(const mal_src_config* pConfig, mal_src* pSRC); + +// Dynamically adjusts the input sample rate. +// +// DEPRECATED. Use mal_src_set_sample_rate() instead. +mal_result mal_src_set_input_sample_rate(mal_src* pSRC, mal_uint32 sampleRateIn); + +// Dynamically adjusts the output sample rate. +// +// This is useful for dynamically adjust pitch. Keep in mind, however, that this will speed up or slow down the sound. If this +// is not acceptable you will need to use your own algorithm. +// +// DEPRECATED. Use mal_src_set_sample_rate() instead. +mal_result mal_src_set_output_sample_rate(mal_src* pSRC, mal_uint32 sampleRateOut); + +// Dynamically adjusts the sample rate. +// +// This is useful for dynamically adjust pitch. Keep in mind, however, that this will speed up or slow down the sound. If this +// is not acceptable you will need to use your own algorithm. +mal_result mal_src_set_sample_rate(mal_src* pSRC, mal_uint32 sampleRateIn, mal_uint32 sampleRateOut); + +// Reads a number of frames. +// +// Returns the number of frames actually read. +mal_uint64 mal_src_read_deinterleaved(mal_src* pSRC, mal_uint64 frameCount, void** ppSamplesOut, void* pUserData); + + +// Helper for creating a sample rate conversion config. +mal_src_config mal_src_config_init_new(void); +mal_src_config mal_src_config_init(mal_uint32 sampleRateIn, mal_uint32 sampleRateOut, mal_uint32 channels, mal_src_read_deinterleaved_proc onReadDeinterleaved, void* pUserData); + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// DSP +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// Initializes a DSP object. +mal_result mal_dsp_init(const mal_dsp_config* pConfig, mal_dsp* pDSP); + +// Dynamically adjusts the input sample rate. +// +// This will fail is the DSP was not initialized with allowDynamicSampleRate. +// +// DEPRECATED. Use mal_dsp_set_sample_rate() instead. +mal_result mal_dsp_set_input_sample_rate(mal_dsp* pDSP, mal_uint32 sampleRateOut); + +// Dynamically adjusts the output sample rate. +// +// This is useful for dynamically adjust pitch. Keep in mind, however, that this will speed up or slow down the sound. If this +// is not acceptable you will need to use your own algorithm. +// +// This will fail is the DSP was not initialized with allowDynamicSampleRate. +// +// DEPRECATED. Use mal_dsp_set_sample_rate() instead. +mal_result mal_dsp_set_output_sample_rate(mal_dsp* pDSP, mal_uint32 sampleRateOut); + +// Dynamically adjusts the output sample rate. +// +// This is useful for dynamically adjust pitch. Keep in mind, however, that this will speed up or slow down the sound. If this +// is not acceptable you will need to use your own algorithm. +// +// This will fail is the DSP was not initialized with allowDynamicSampleRate. +mal_result mal_dsp_set_sample_rate(mal_dsp* pDSP, mal_uint32 sampleRateIn, mal_uint32 sampleRateOut); + + +// Reads a number of frames and runs them through the DSP processor. +mal_uint64 mal_dsp_read(mal_dsp* pDSP, mal_uint64 frameCount, void* pFramesOut, void* pUserData); + +// Helper for initializing a mal_dsp_config object. +mal_dsp_config mal_dsp_config_init_new(void); +mal_dsp_config mal_dsp_config_init(mal_format formatIn, mal_uint32 channelsIn, mal_uint32 sampleRateIn, mal_format formatOut, mal_uint32 channelsOut, mal_uint32 sampleRateOut, mal_dsp_read_proc onRead, void* pUserData); +mal_dsp_config mal_dsp_config_init_ex(mal_format formatIn, mal_uint32 channelsIn, mal_uint32 sampleRateIn, mal_channel channelMapIn[MAL_MAX_CHANNELS], mal_format formatOut, mal_uint32 channelsOut, mal_uint32 sampleRateOut, mal_channel channelMapOut[MAL_MAX_CHANNELS], mal_dsp_read_proc onRead, void* pUserData); + + +// High-level helper for doing a full format conversion in one go. Returns the number of output frames. Call this with pOut set to NULL to +// determine the required size of the output buffer. +// +// A return value of 0 indicates an error. +// +// This function is useful for one-off bulk conversions, but if you're streaming data you should use the DSP APIs instead. +mal_uint64 mal_convert_frames(void* pOut, mal_format formatOut, mal_uint32 channelsOut, mal_uint32 sampleRateOut, const void* pIn, mal_format formatIn, mal_uint32 channelsIn, mal_uint32 sampleRateIn, mal_uint64 frameCountIn); +mal_uint64 mal_convert_frames_ex(void* pOut, mal_format formatOut, mal_uint32 channelsOut, mal_uint32 sampleRateOut, mal_channel channelMapOut[MAL_MAX_CHANNELS], const void* pIn, mal_format formatIn, mal_uint32 channelsIn, mal_uint32 sampleRateIn, mal_channel channelMapIn[MAL_MAX_CHANNELS], mal_uint64 frameCountIn); + + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Miscellaneous Helpers +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// malloc(). Calls MAL_MALLOC(). +void* mal_malloc(size_t sz); + +// realloc(). Calls MAL_REALLOC(). +void* mal_realloc(void* p, size_t sz); + +// free(). Calls MAL_FREE(). +void mal_free(void* p); + +// Performs an aligned malloc, with the assumption that the alignment is a power of 2. +void* mal_aligned_malloc(size_t sz, size_t alignment); + +// Free's an aligned malloc'd buffer. +void mal_aligned_free(void* p); + +// Retrieves a friendly name for a format. +const char* mal_get_format_name(mal_format format); + +// Blends two frames in floating point format. +void mal_blend_f32(float* pOut, float* pInA, float* pInB, float factor, mal_uint32 channels); + +// Retrieves the size of a sample in bytes for the given format. +// +// This API is efficient and is implemented using a lookup table. +// +// Thread Safety: SAFE +// This is API is pure. +mal_uint32 mal_get_bytes_per_sample(mal_format format); +static MAL_INLINE mal_uint32 mal_get_bytes_per_frame(mal_format format, mal_uint32 channels) { return mal_get_bytes_per_sample(format) * channels; } + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Format Conversion +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void mal_pcm_u8_to_s16(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); +void mal_pcm_u8_to_s24(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); +void mal_pcm_u8_to_s32(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); +void mal_pcm_u8_to_f32(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); +void mal_pcm_s16_to_u8(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); +void mal_pcm_s16_to_s24(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); +void mal_pcm_s16_to_s32(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); +void mal_pcm_s16_to_f32(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); +void mal_pcm_s24_to_u8(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); +void mal_pcm_s24_to_s16(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); +void mal_pcm_s24_to_s32(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); +void mal_pcm_s24_to_f32(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); +void mal_pcm_s32_to_u8(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); +void mal_pcm_s32_to_s16(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); +void mal_pcm_s32_to_s24(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); +void mal_pcm_s32_to_f32(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); +void mal_pcm_f32_to_u8(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); +void mal_pcm_f32_to_s16(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); +void mal_pcm_f32_to_s24(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); +void mal_pcm_f32_to_s32(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); +void mal_pcm_convert(void* pOut, mal_format formatOut, const void* pIn, mal_format formatIn, mal_uint64 sampleCount, mal_dither_mode ditherMode); + + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// DEVICE I/O +// ========== +// +// This section contains the APIs for device playback and capture. Here is where you'll find mal_device_init(), etc. +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#ifndef MAL_NO_DEVICE_IO +// Some backends are only supported on certain platforms. +#if defined(MAL_WIN32) + #define MAL_SUPPORT_WASAPI + #if defined(MAL_WIN32_DESKTOP) // DirectSound and WinMM backends are only supported on desktop's. + #define MAL_SUPPORT_DSOUND + #define MAL_SUPPORT_WINMM + #define MAL_SUPPORT_JACK // JACK is technically supported on Windows, but I don't know how many people use it in practice... + #endif +#endif +#if defined(MAL_UNIX) + #if defined(MAL_LINUX) + #if !defined(MAL_ANDROID) // ALSA is not supported on Android. + #define MAL_SUPPORT_ALSA + #endif + #endif + #if !defined(MAL_BSD) && !defined(MAL_ANDROID) && !defined(MAL_EMSCRIPTEN) + #define MAL_SUPPORT_PULSEAUDIO + #define MAL_SUPPORT_JACK + #endif + #if defined(MAL_ANDROID) + #define MAL_SUPPORT_OPENSL + #endif + #if defined(__OpenBSD__) // <-- Change this to "#if defined(MAL_BSD)" to enable sndio on all BSD flavors. + #define MAL_SUPPORT_SNDIO // sndio is only supported on OpenBSD for now. May be expanded later if there's demand. + #endif + #if defined(__NetBSD__) + #define MAL_SUPPORT_AUDIOIO // Only support audioio on platforms with known support. + #endif + #if defined(__FreeBSD__) || defined(__DragonFly__) + #define MAL_SUPPORT_OSS // Only support OSS on specific platforms with known support. + #endif +#endif +#if defined(MAL_APPLE) + #define MAL_SUPPORT_COREAUDIO +#endif + +#define MAL_SUPPORT_SDL // All platforms support SDL. + +// Explicitly disable OpenAL and Null backends for Emscripten because they both use a background thread which is not properly supported right now. +#if !defined(MAL_EMSCRIPTEN) +#define MAL_SUPPORT_OPENAL +#define MAL_SUPPORT_NULL // All platforms support the null backend. +#endif + + +#if !defined(MAL_NO_WASAPI) && defined(MAL_SUPPORT_WASAPI) + #define MAL_ENABLE_WASAPI +#endif +#if !defined(MAL_NO_DSOUND) && defined(MAL_SUPPORT_DSOUND) + #define MAL_ENABLE_DSOUND +#endif +#if !defined(MAL_NO_WINMM) && defined(MAL_SUPPORT_WINMM) + #define MAL_ENABLE_WINMM +#endif +#if !defined(MAL_NO_ALSA) && defined(MAL_SUPPORT_ALSA) + #define MAL_ENABLE_ALSA +#endif +#if !defined(MAL_NO_PULSEAUDIO) && defined(MAL_SUPPORT_PULSEAUDIO) + #define MAL_ENABLE_PULSEAUDIO +#endif +#if !defined(MAL_NO_JACK) && defined(MAL_SUPPORT_JACK) + #define MAL_ENABLE_JACK +#endif +#if !defined(MAL_NO_COREAUDIO) && defined(MAL_SUPPORT_COREAUDIO) + #define MAL_ENABLE_COREAUDIO +#endif +#if !defined(MAL_NO_SNDIO) && defined(MAL_SUPPORT_SNDIO) + #define MAL_ENABLE_SNDIO +#endif +#if !defined(MAL_NO_AUDIOIO) && defined(MAL_SUPPORT_AUDIOIO) + #define MAL_ENABLE_AUDIOIO +#endif +#if !defined(MAL_NO_OSS) && defined(MAL_SUPPORT_OSS) + #define MAL_ENABLE_OSS +#endif +#if !defined(MAL_NO_OPENSL) && defined(MAL_SUPPORT_OPENSL) + #define MAL_ENABLE_OPENSL +#endif +#if !defined(MAL_NO_OPENAL) && defined(MAL_SUPPORT_OPENAL) + #define MAL_ENABLE_OPENAL +#endif +#if !defined(MAL_NO_SDL) && defined(MAL_SUPPORT_SDL) + #define MAL_ENABLE_SDL +#endif +#if !defined(MAL_NO_NULL) && defined(MAL_SUPPORT_NULL) + #define MAL_ENABLE_NULL +#endif + + +typedef enum +{ + mal_backend_null, + mal_backend_wasapi, + mal_backend_dsound, + mal_backend_winmm, + mal_backend_alsa, + mal_backend_pulseaudio, + mal_backend_jack, + mal_backend_coreaudio, + mal_backend_sndio, + mal_backend_audioio, + mal_backend_oss, + mal_backend_opensl, + mal_backend_openal, + mal_backend_sdl +} mal_backend; + +// Thread priorties should be ordered such that the default priority of the worker thread is 0. +typedef enum +{ + mal_thread_priority_idle = -5, + mal_thread_priority_lowest = -4, + mal_thread_priority_low = -3, + mal_thread_priority_normal = -2, + mal_thread_priority_high = -1, + mal_thread_priority_highest = 0, + mal_thread_priority_realtime = 1, + mal_thread_priority_default = 0 +} mal_thread_priority; + +typedef struct +{ + mal_context* pContext; + + union + { +#ifdef MAL_WIN32 + struct + { + /*HANDLE*/ mal_handle hThread; + } win32; +#endif +#ifdef MAL_POSIX + struct + { + pthread_t thread; + } posix; +#endif + + int _unused; + }; +} mal_thread; + +typedef struct +{ + mal_context* pContext; + + union + { +#ifdef MAL_WIN32 + struct + { + /*HANDLE*/ mal_handle hMutex; + } win32; +#endif +#ifdef MAL_POSIX + struct + { + pthread_mutex_t mutex; + } posix; +#endif + + int _unused; + }; +} mal_mutex; + +typedef struct +{ + mal_context* pContext; + + union + { +#ifdef MAL_WIN32 + struct + { + /*HANDLE*/ mal_handle hEvent; + } win32; +#endif +#ifdef MAL_POSIX + struct + { + pthread_mutex_t mutex; + pthread_cond_t condition; + mal_uint32 value; + } posix; +#endif + + int _unused; + }; +} mal_event; + + +#define MAL_MAX_PERIODS_DSOUND 4 +#define MAL_MAX_PERIODS_OPENAL 4 + +typedef void (* mal_log_proc) (mal_context* pContext, mal_device* pDevice, const char* message); +typedef void (* mal_recv_proc)(mal_device* pDevice, mal_uint32 frameCount, const void* pSamples); +typedef mal_uint32 (* mal_send_proc)(mal_device* pDevice, mal_uint32 frameCount, void* pSamples); +typedef void (* mal_stop_proc)(mal_device* pDevice); + +typedef enum +{ + mal_device_type_playback, + mal_device_type_capture +} mal_device_type; + +typedef enum +{ + mal_share_mode_shared = 0, + mal_share_mode_exclusive, +} mal_share_mode; + +typedef union +{ +#ifdef MAL_SUPPORT_WASAPI + wchar_t wasapi[64]; // WASAPI uses a wchar_t string for identification. +#endif +#ifdef MAL_SUPPORT_DSOUND + mal_uint8 dsound[16]; // DirectSound uses a GUID for identification. +#endif +#ifdef MAL_SUPPORT_WINMM + /*UINT_PTR*/ mal_uint32 winmm; // When creating a device, WinMM expects a Win32 UINT_PTR for device identification. In practice it's actually just a UINT. +#endif +#ifdef MAL_SUPPORT_ALSA + char alsa[256]; // ALSA uses a name string for identification. +#endif +#ifdef MAL_SUPPORT_PULSEAUDIO + char pulse[256]; // PulseAudio uses a name string for identification. +#endif +#ifdef MAL_SUPPORT_JACK + int jack; // JACK always uses default devices. +#endif +#ifdef MAL_SUPPORT_COREAUDIO + char coreaudio[256]; // Core Audio uses a string for identification. +#endif +#ifdef MAL_SUPPORT_SNDIO + char sndio[256]; // "snd/0", etc. +#endif +#ifdef MAL_SUPPORT_AUDIOIO + char audioio[256]; // "/dev/audio", etc. +#endif +#ifdef MAL_SUPPORT_OSS + char oss[64]; // "dev/dsp0", etc. "dev/dsp" for the default device. +#endif +#ifdef MAL_SUPPORT_OPENSL + mal_uint32 opensl; // OpenSL|ES uses a 32-bit unsigned integer for identification. +#endif +#ifdef MAL_SUPPORT_OPENAL + char openal[256]; // OpenAL seems to use human-readable device names as the ID. +#endif +#ifdef MAL_SUPPORT_SDL + int sdl; // SDL devices are identified with an index. +#endif +#ifdef MAL_SUPPORT_NULL + int nullbackend; // The null backend uses an integer for device IDs. +#endif +} mal_device_id; + +typedef struct +{ + // Basic info. This is the only information guaranteed to be filled in during device enumeration. + mal_device_id id; + char name[256]; + + // Detailed info. As much of this is filled as possible with mal_context_get_device_info(). Note that you are allowed to initialize + // a device with settings outside of this range, but it just means the data will be converted using mini_al's data conversion + // pipeline before sending the data to/from the device. Most programs will need to not worry about these values, but it's provided + // here mainly for informational purposes or in the rare case that someone might find it useful. + // + // These will be set to 0 when returned by mal_context_enumerate_devices() or mal_context_get_devices(). + mal_uint32 formatCount; + mal_format formats[mal_format_count]; + mal_uint32 minChannels; + mal_uint32 maxChannels; + mal_uint32 minSampleRate; + mal_uint32 maxSampleRate; +} mal_device_info; + +typedef struct +{ + mal_int64 counter; +} mal_timer; + +typedef struct +{ + mal_format format; + mal_uint32 channels; + mal_uint32 sampleRate; + mal_channel channelMap[MAL_MAX_CHANNELS]; + mal_uint32 bufferSizeInFrames; + mal_uint32 periods; + mal_share_mode shareMode; + mal_performance_profile performanceProfile; + mal_recv_proc onRecvCallback; + mal_send_proc onSendCallback; + mal_stop_proc onStopCallback; + + struct + { + mal_bool32 noMMap; // Disables MMap mode. + } alsa; + + struct + { + const char* pStreamName; + } pulse; +} mal_device_config; + +typedef struct +{ + mal_log_proc onLog; + mal_thread_priority threadPriority; + + struct + { + mal_bool32 useVerboseDeviceEnumeration; + } alsa; + + struct + { + const char* pApplicationName; + const char* pServerName; + mal_bool32 tryAutoSpawn; // Enables autospawning of the PulseAudio daemon if necessary. + } pulse; + + struct + { + const char* pClientName; + mal_bool32 tryStartServer; + } jack; +} mal_context_config; + +typedef mal_bool32 (* mal_enum_devices_callback_proc)(mal_context* pContext, mal_device_type type, const mal_device_info* pInfo, void* pUserData); + +struct mal_context +{ + mal_backend backend; // DirectSound, ALSA, etc. + mal_context_config config; + mal_mutex deviceEnumLock; // Used to make mal_context_get_devices() thread safe. + mal_mutex deviceInfoLock; // Used to make mal_context_get_device_info() thread safe. + mal_uint32 deviceInfoCapacity; // Total capacity of pDeviceInfos. + mal_uint32 playbackDeviceInfoCount; + mal_uint32 captureDeviceInfoCount; + mal_device_info* pDeviceInfos; // Playback devices first, then capture. + mal_bool32 isBackendAsynchronous : 1; // Set when the context is initialized. Set to 1 for asynchronous backends such as Core Audio and JACK. Do not modify. + + mal_result (* onUninit )(mal_context* pContext); + mal_bool32 (* onDeviceIDEqual )(mal_context* pContext, const mal_device_id* pID0, const mal_device_id* pID1); + mal_result (* onEnumDevices )(mal_context* pContext, mal_enum_devices_callback_proc callback, void* pUserData); // Return false from the callback to stop enumeration. + mal_result (* onGetDeviceInfo )(mal_context* pContext, mal_device_type type, const mal_device_id* pDeviceID, mal_share_mode shareMode, mal_device_info* pDeviceInfo); + mal_result (* onDeviceInit )(mal_context* pContext, mal_device_type type, const mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice); + void (* onDeviceUninit )(mal_device* pDevice); + mal_result (* onDeviceStart )(mal_device* pDevice); + mal_result (* onDeviceStop )(mal_device* pDevice); + mal_result (* onDeviceBreakMainLoop)(mal_device* pDevice); + mal_result (* onDeviceMainLoop )(mal_device* pDevice); + + union + { +#ifdef MAL_SUPPORT_WASAPI + struct + { + int _unused; + } wasapi; +#endif +#ifdef MAL_SUPPORT_DSOUND + struct + { + /*HMODULE*/ mal_handle hDSoundDLL; + mal_proc DirectSoundCreate; + mal_proc DirectSoundEnumerateA; + mal_proc DirectSoundCaptureCreate; + mal_proc DirectSoundCaptureEnumerateA; + } dsound; +#endif +#ifdef MAL_SUPPORT_WINMM + struct + { + /*HMODULE*/ mal_handle hWinMM; + mal_proc waveOutGetNumDevs; + mal_proc waveOutGetDevCapsA; + mal_proc waveOutOpen; + mal_proc waveOutClose; + mal_proc waveOutPrepareHeader; + mal_proc waveOutUnprepareHeader; + mal_proc waveOutWrite; + mal_proc waveOutReset; + mal_proc waveInGetNumDevs; + mal_proc waveInGetDevCapsA; + mal_proc waveInOpen; + mal_proc waveInClose; + mal_proc waveInPrepareHeader; + mal_proc waveInUnprepareHeader; + mal_proc waveInAddBuffer; + mal_proc waveInStart; + mal_proc waveInReset; + } winmm; +#endif +#ifdef MAL_SUPPORT_ALSA + struct + { + mal_handle asoundSO; + mal_proc snd_pcm_open; + mal_proc snd_pcm_close; + mal_proc snd_pcm_hw_params_sizeof; + mal_proc snd_pcm_hw_params_any; + mal_proc snd_pcm_hw_params_set_format; + mal_proc snd_pcm_hw_params_set_format_first; + mal_proc snd_pcm_hw_params_get_format_mask; + mal_proc snd_pcm_hw_params_set_channels_near; + mal_proc snd_pcm_hw_params_set_rate_resample; + mal_proc snd_pcm_hw_params_set_rate_near; + mal_proc snd_pcm_hw_params_set_buffer_size_near; + mal_proc snd_pcm_hw_params_set_periods_near; + mal_proc snd_pcm_hw_params_set_access; + mal_proc snd_pcm_hw_params_get_format; + mal_proc snd_pcm_hw_params_get_channels; + mal_proc snd_pcm_hw_params_get_channels_min; + mal_proc snd_pcm_hw_params_get_channels_max; + mal_proc snd_pcm_hw_params_get_rate; + mal_proc snd_pcm_hw_params_get_rate_min; + mal_proc snd_pcm_hw_params_get_rate_max; + mal_proc snd_pcm_hw_params_get_buffer_size; + mal_proc snd_pcm_hw_params_get_periods; + mal_proc snd_pcm_hw_params_get_access; + mal_proc snd_pcm_hw_params; + mal_proc snd_pcm_sw_params_sizeof; + mal_proc snd_pcm_sw_params_current; + mal_proc snd_pcm_sw_params_set_avail_min; + mal_proc snd_pcm_sw_params_set_start_threshold; + mal_proc snd_pcm_sw_params; + mal_proc snd_pcm_format_mask_sizeof; + mal_proc snd_pcm_format_mask_test; + mal_proc snd_pcm_get_chmap; + mal_proc snd_pcm_prepare; + mal_proc snd_pcm_start; mal_proc snd_pcm_drop; mal_proc snd_device_name_hint; mal_proc snd_device_name_get_hint; @@ -1299,6 +1679,35 @@ struct mal_context mal_proc AudioUnitRender; } coreaudio; #endif +#ifdef MAL_SUPPORT_SNDIO + struct + { + mal_handle sndioSO; + mal_proc sio_open; + mal_proc sio_close; + mal_proc sio_setpar; + mal_proc sio_getpar; + mal_proc sio_getcap; + mal_proc sio_start; + mal_proc sio_stop; + mal_proc sio_read; + mal_proc sio_write; + mal_proc sio_onmove; + mal_proc sio_nfds; + mal_proc sio_pollfd; + mal_proc sio_revents; + mal_proc sio_eof; + mal_proc sio_setvol; + mal_proc sio_onvol; + mal_proc sio_initpar; + } sndio; +#endif +#ifdef MAL_SUPPORT_AUDIOIO + struct + { + int _unused; + } audioio; +#endif #ifdef MAL_SUPPORT_OSS struct { @@ -1591,6 +2000,24 @@ MAL_ALIGNED_STRUCT(MAL_SIMD_ALIGNMENT) mal_device /*AudioBufferList**/ mal_ptr pAudioBufferList; // Only used for input devices. } coreaudio; #endif +#ifdef MAL_SUPPORT_SNDIO + struct + { + mal_ptr handle; + mal_uint32 fragmentSizeInFrames; + mal_bool32 breakFromMainLoop; + void* pIntermediaryBuffer; + } sndio; +#endif +#ifdef MAL_SUPPORT_AUDIOIO + struct + { + int fd; + mal_uint32 fragmentSizeInFrames; + mal_bool32 breakFromMainLoop; + void* pIntermediaryBuffer; + } audioio; +#endif #ifdef MAL_SUPPORT_OSS struct { @@ -1663,7 +2090,9 @@ MAL_ALIGNED_STRUCT(MAL_SIMD_ALIGNMENT) mal_device // - WASAPI // - DirectSound // - WinMM -// - Core Audio (macOS, iOS) +// - Core Audio (Apple) +// - sndio +// - audioio // - OSS // - PulseAudio // - ALSA @@ -1941,15 +2370,6 @@ mal_bool32 mal_device_is_started(mal_device* pDevice); // This is calculated from constant values which are set at initialization time and never change. mal_uint32 mal_device_get_buffer_size_in_bytes(mal_device* pDevice); -// Retrieves the size of a sample in bytes for the given format. -// -// This API is efficient and is implemented using a lookup table. -// -// Thread Safety: SAFE -// This is API is pure. -mal_uint32 mal_get_bytes_per_sample(mal_format format); -static MAL_INLINE mal_uint32 mal_get_bytes_per_frame(mal_format format, mal_uint32 channels) { return mal_get_bytes_per_sample(format) * channels; } - // Helper function for initializing a mal_context_config object. mal_context_config mal_context_config_init(mal_log_proc onLog); @@ -2002,276 +2422,46 @@ mal_device_config mal_device_config_init_default_playback(mal_send_proc onSendCa // | | 2: MAL_CHANNEL_FRONT_CENTER | // | | 3: MAL_CHANNEL_LFE | // | | 4: MAL_CHANNEL_SIDE_LEFT | -// | | 5: MAL_CHANNEL_SIDE_RIGHT | -// |---------------|------------------------------| -// | 7 | 0: MAL_CHANNEL_FRONT_LEFT | -// | | 1: MAL_CHANNEL_FRONT_RIGHT | -// | | 2: MAL_CHANNEL_FRONT_CENTER | -// | | 3: MAL_CHANNEL_LFE | -// | | 4: MAL_CHANNEL_BACK_CENTER | -// | | 4: MAL_CHANNEL_SIDE_LEFT | -// | | 5: MAL_CHANNEL_SIDE_RIGHT | -// |---------------|------------------------------| -// | 8 (7.1) | 0: MAL_CHANNEL_FRONT_LEFT | -// | | 1: MAL_CHANNEL_FRONT_RIGHT | -// | | 2: MAL_CHANNEL_FRONT_CENTER | -// | | 3: MAL_CHANNEL_LFE | -// | | 4: MAL_CHANNEL_BACK_LEFT | -// | | 5: MAL_CHANNEL_BACK_RIGHT | -// | | 6: MAL_CHANNEL_SIDE_LEFT | -// | | 7: MAL_CHANNEL_SIDE_RIGHT | -// |---------------|------------------------------| -// | Other | All channels set to 0. This | -// | | is equivalent to the same | -// | | mapping as the device. | -// |---------------|------------------------------| -// -// Thread Safety: SAFE -// -// Efficiency: HIGH -// This just returns a stack allocated object and consists of just a few assignments. -mal_device_config mal_device_config_init_ex(mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_channel channelMap[MAL_MAX_CHANNELS], mal_recv_proc onRecvCallback, mal_send_proc onSendCallback); - -// A simplified version of mal_device_config_init_ex(). -static MAL_INLINE mal_device_config mal_device_config_init(mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_recv_proc onRecvCallback, mal_send_proc onSendCallback) { return mal_device_config_init_ex(format, channels, sampleRate, NULL, onRecvCallback, onSendCallback); } - -// A simplified version of mal_device_config_init() for capture devices. -static MAL_INLINE mal_device_config mal_device_config_init_capture_ex(mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_channel channelMap[MAL_MAX_CHANNELS], mal_recv_proc onRecvCallback) { return mal_device_config_init_ex(format, channels, sampleRate, channelMap, onRecvCallback, NULL); } -static MAL_INLINE mal_device_config mal_device_config_init_capture(mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_recv_proc onRecvCallback) { return mal_device_config_init_capture_ex(format, channels, sampleRate, NULL, onRecvCallback); } - -// A simplified version of mal_device_config_init() for playback devices. -static MAL_INLINE mal_device_config mal_device_config_init_playback_ex(mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_channel channelMap[MAL_MAX_CHANNELS], mal_send_proc onSendCallback) { return mal_device_config_init_ex(format, channels, sampleRate, channelMap, NULL, onSendCallback); } -static MAL_INLINE mal_device_config mal_device_config_init_playback(mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_send_proc onSendCallback) { return mal_device_config_init_playback_ex(format, channels, sampleRate, NULL, onSendCallback); } - - -// Helper for retrieving a standard channel map. -void mal_get_standard_channel_map(mal_standard_channel_map standardChannelMap, mal_uint32 channels, mal_channel channelMap[MAL_MAX_CHANNELS]); - -// Copies a channel map. -void mal_channel_map_copy(mal_channel* pOut, const mal_channel* pIn, mal_uint32 channels); - - -// Determines whether or not a channel map is valid. -// -// A blank channel map is valid (all channels set to MAL_CHANNEL_NONE). The way a blank channel map is handled is context specific, but -// is usually treated as a passthrough. -// -// Invalid channel maps: -// - A channel map with no channels -// - A channel map with more than one channel and a mono channel -mal_bool32 mal_channel_map_valid(mal_uint32 channels, const mal_channel channelMap[MAL_MAX_CHANNELS]); - -// Helper for comparing two channel maps for equality. -// -// This assumes the channel count is the same between the two. -mal_bool32 mal_channel_map_equal(mal_uint32 channels, const mal_channel channelMapA[MAL_MAX_CHANNELS], const mal_channel channelMapB[MAL_MAX_CHANNELS]); - -// Helper for determining if a channel map is blank (all channels set to MAL_CHANNEL_NONE). -mal_bool32 mal_channel_map_blank(mal_uint32 channels, const mal_channel channelMap[MAL_MAX_CHANNELS]); - -// Helper for determining whether or not a channel is present in the given channel map. -mal_bool32 mal_channel_map_contains_channel_position(mal_uint32 channels, const mal_channel channelMap[MAL_MAX_CHANNELS], mal_channel channelPosition); - - -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// -// Format Conversion -// ================= -// The format converter serves two purposes: -// 1) Conversion between data formats (u8 to f32, etc.) -// 2) Interleaving and deinterleaving -// -// When initializing a converter, you specify the input and output formats (u8, s16, etc.) and read callbacks. There are two read callbacks - one for -// interleaved input data (onRead) and another for deinterleaved input data (onReadDeinterleaved). You implement whichever is most convenient for you. You -// can implement both, but it's not recommended as it just introduces unnecessary complexity. -// -// To read data as interleaved samples, use mal_format_converter_read(). Otherwise use mal_format_converter_read_deinterleaved(). -// -// Dithering -// --------- -// The format converter also supports dithering. Dithering can be set using ditherMode variable in the config, like so. -// -// pConfig->ditherMode = mal_dither_mode_rectangle; -// -// The different dithering modes include the following, in order of efficiency: -// - None: mal_dither_mode_none -// - Rectangle: mal_dither_mode_rectangle -// - Triangle: mal_dither_mode_triangle -// -// Note that even if the dither mode is set to something other than mal_dither_mode_none, it will be ignored for conversions where dithering is not needed. -// Dithering is available for the following conversions: -// - s16 -> u8 -// - s24 -> u8 -// - s32 -> u8 -// - f32 -> u8 -// - s24 -> s16 -// - s32 -> s16 -// - f32 -> s16 -// -// Note that it is not an error to pass something other than mal_dither_mode_none for conversions where dither is not used. It will just be ignored. -// -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -// Initializes a format converter. -mal_result mal_format_converter_init(const mal_format_converter_config* pConfig, mal_format_converter* pConverter); - -// Reads data from the format converter as interleaved channels. -mal_uint64 mal_format_converter_read(mal_format_converter* pConverter, mal_uint64 frameCount, void* pFramesOut, void* pUserData); - -// Reads data from the format converter as deinterleaved channels. -mal_uint64 mal_format_converter_read_deinterleaved(mal_format_converter* pConverter, mal_uint64 frameCount, void** ppSamplesOut, void* pUserData); - - -// Helper for initializing a format converter config. -mal_format_converter_config mal_format_converter_config_init_new(void); -mal_format_converter_config mal_format_converter_config_init(mal_format formatIn, mal_format formatOut, mal_uint32 channels, mal_format_converter_read_proc onRead, void* pUserData); -mal_format_converter_config mal_format_converter_config_init_deinterleaved(mal_format formatIn, mal_format formatOut, mal_uint32 channels, mal_format_converter_read_deinterleaved_proc onReadDeinterleaved, void* pUserData); - - - -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// -// Channel Routing -// =============== -// There are two main things you can do with the channel router: -// 1) Rearrange channels -// 2) Convert from one channel count to another -// -// Channel Rearrangement -// --------------------- -// A simple example of channel rearrangement may be swapping the left and right channels in a stereo stream. To do this you just pass in the same channel -// count for both the input and output with channel maps that contain the same channels (in a different order). -// -// Channel Conversion -// ------------------ -// The channel router can also convert from one channel count to another, such as converting a 5.1 stream to stero. When changing the channel count, the -// router will first perform a 1:1 mapping of channel positions that are present in both the input and output channel maps. The second thing it will do -// is distribute the input mono channel (if any) across all output channels, excluding any None and LFE channels. If there is an output mono channel, all -// input channels will be averaged, excluding any None and LFE channels. -// -// The last case to consider is when a channel position in the input channel map is not present in the output channel map, and vice versa. In this case the -// channel router will perform a blend of other related channels to produce an audible channel. There are several blending modes. -// 1) Simple -// Unmatched channels are silenced. -// 2) Planar Blending -// Channels are blended based on a set of planes that each speaker emits audio from. -// -// Planar Blending -// --------------- -// In this mode, channel positions are associated with a set of planes where the channel conceptually emits audio from. An example is the front/left speaker. -// This speaker is positioned to the front of the listener, so you can think of it as emitting audio from the front plane. It is also positioned to the left -// of the listener so you can think of it as also emitting audio from the left plane. Now consider the (unrealistic) situation where the input channel map -// contains only the front/left channel position, but the output channel map contains both the front/left and front/center channel. When deciding on the audio -// data to send to the front/center speaker (which has no 1:1 mapping with an input channel) we need to use some logic based on our available input channel -// positions. -// -// As mentioned earlier, our front/left speaker is, conceptually speaking, emitting audio from the front _and_ the left planes. Similarly, the front/center -// speaker is emitting audio from _only_ the front plane. What these two channels have in common is that they are both emitting audio from the front plane. -// Thus, it makes sense that the front/center speaker should receive some contribution from the front/left channel. How much contribution depends on their -// planar relationship (thus the name of this blending technique). -// -// Because the front/left channel is emitting audio from two planes (front and left), you can think of it as though it's willing to dedicate 50% of it's total -// volume to each of it's planes (a channel position emitting from 1 plane would be willing to given 100% of it's total volume to that plane, and a channel -// position emitting from 3 planes would be willing to given 33% of it's total volume to each plane). Similarly, the front/center speaker is emitting audio -// from only one plane so you can think of it as though it's willing to _take_ 100% of it's volume from front plane emissions. Now, since the front/left -// channel is willing to _give_ 50% of it's total volume to the front plane, and the front/center speaker is willing to _take_ 100% of it's total volume -// from the front, you can imagine that 50% of the front/left speaker will be given to the front/center speaker. -// -// Usage -// ----- -// To use the channel router you need to specify three things: -// 1) The input channel count and channel map -// 2) The output channel count and channel map -// 3) The mixing mode to use in the case where a 1:1 mapping is unavailable -// -// Note that input and output data is always deinterleaved 32-bit floating point. -// -// Initialize the channel router with mal_channel_router_init(). You will need to pass in a config object which specifies the input and output configuration, -// mixing mode and a callback for sending data to the router. This callback will be called when input data needs to be sent to the router for processing. -// -// Read data from the channel router with mal_channel_router_read_deinterleaved(). Output data is always 32-bit floating point. -// -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -// Initializes a channel router where it is assumed that the input data is non-interleaved. -mal_result mal_channel_router_init(const mal_channel_router_config* pConfig, mal_channel_router* pRouter); - -// Reads data from the channel router as deinterleaved channels. -mal_uint64 mal_channel_router_read_deinterleaved(mal_channel_router* pRouter, mal_uint64 frameCount, void** ppSamplesOut, void* pUserData); - -// Helper for initializing a channel router config. -mal_channel_router_config mal_channel_router_config_init(mal_uint32 channelsIn, const mal_channel channelMapIn[MAL_MAX_CHANNELS], mal_uint32 channelsOut, const mal_channel channelMapOut[MAL_MAX_CHANNELS], mal_channel_mix_mode mixingMode, mal_channel_router_read_deinterleaved_proc onRead, void* pUserData); - - -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// -// Sample Rate Conversion -// ====================== -// -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -// Initializes a sample rate conversion object. -mal_result mal_src_init(const mal_src_config* pConfig, mal_src* pSRC); - -// Dynamically adjusts the input sample rate. -mal_result mal_src_set_input_sample_rate(mal_src* pSRC, mal_uint32 sampleRateIn); - -// Dynamically adjusts the output sample rate. -// -// This is useful for dynamically adjust pitch. Keep in mind, however, that this will speed up or slow down the sound. If this -// is not acceptable you will need to use your own algorithm. -mal_result mal_src_set_output_sample_rate(mal_src* pSRC, mal_uint32 sampleRateOut); - -// Reads a number of frames. -// -// Returns the number of frames actually read. -mal_uint64 mal_src_read_deinterleaved(mal_src* pSRC, mal_uint64 frameCount, void** ppSamplesOut, void* pUserData); - - -// Helper for creating a sample rate conversion config. -mal_src_config mal_src_config_init_new(void); -mal_src_config mal_src_config_init(mal_uint32 sampleRateIn, mal_uint32 sampleRateOut, mal_uint32 channels, mal_src_read_deinterleaved_proc onReadDeinterleaved, void* pUserData); - - -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// -// DSP -// -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -// Initializes a DSP object. -mal_result mal_dsp_init(const mal_dsp_config* pConfig, mal_dsp* pDSP); - -// Dynamically adjusts the input sample rate. -// -// This will fail is the DSP was not initialized with allowDynamicSampleRate. -mal_result mal_dsp_set_input_sample_rate(mal_dsp* pDSP, mal_uint32 sampleRateOut); - -// Dynamically adjusts the output sample rate. +// | | 5: MAL_CHANNEL_SIDE_RIGHT | +// |---------------|------------------------------| +// | 7 | 0: MAL_CHANNEL_FRONT_LEFT | +// | | 1: MAL_CHANNEL_FRONT_RIGHT | +// | | 2: MAL_CHANNEL_FRONT_CENTER | +// | | 3: MAL_CHANNEL_LFE | +// | | 4: MAL_CHANNEL_BACK_CENTER | +// | | 4: MAL_CHANNEL_SIDE_LEFT | +// | | 5: MAL_CHANNEL_SIDE_RIGHT | +// |---------------|------------------------------| +// | 8 (7.1) | 0: MAL_CHANNEL_FRONT_LEFT | +// | | 1: MAL_CHANNEL_FRONT_RIGHT | +// | | 2: MAL_CHANNEL_FRONT_CENTER | +// | | 3: MAL_CHANNEL_LFE | +// | | 4: MAL_CHANNEL_BACK_LEFT | +// | | 5: MAL_CHANNEL_BACK_RIGHT | +// | | 6: MAL_CHANNEL_SIDE_LEFT | +// | | 7: MAL_CHANNEL_SIDE_RIGHT | +// |---------------|------------------------------| +// | Other | All channels set to 0. This | +// | | is equivalent to the same | +// | | mapping as the device. | +// |---------------|------------------------------| // -// This is useful for dynamically adjust pitch. Keep in mind, however, that this will speed up or slow down the sound. If this -// is not acceptable you will need to use your own algorithm. +// Thread Safety: SAFE // -// This will fail is the DSP was not initialized with allowDynamicSampleRate. -mal_result mal_dsp_set_output_sample_rate(mal_dsp* pDSP, mal_uint32 sampleRateOut); - -// Reads a number of frames and runs them through the DSP processor. -mal_uint64 mal_dsp_read(mal_dsp* pDSP, mal_uint64 frameCount, void* pFramesOut, void* pUserData); +// Efficiency: HIGH +// This just returns a stack allocated object and consists of just a few assignments. +mal_device_config mal_device_config_init_ex(mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_channel channelMap[MAL_MAX_CHANNELS], mal_recv_proc onRecvCallback, mal_send_proc onSendCallback); -// Helper for initializing a mal_dsp_config object. -mal_dsp_config mal_dsp_config_init_new(void); -mal_dsp_config mal_dsp_config_init(mal_format formatIn, mal_uint32 channelsIn, mal_uint32 sampleRateIn, mal_format formatOut, mal_uint32 channelsOut, mal_uint32 sampleRateOut, mal_dsp_read_proc onRead, void* pUserData); -mal_dsp_config mal_dsp_config_init_ex(mal_format formatIn, mal_uint32 channelsIn, mal_uint32 sampleRateIn, mal_channel channelMapIn[MAL_MAX_CHANNELS], mal_format formatOut, mal_uint32 channelsOut, mal_uint32 sampleRateOut, mal_channel channelMapOut[MAL_MAX_CHANNELS], mal_dsp_read_proc onRead, void* pUserData); +// A simplified version of mal_device_config_init_ex(). +static MAL_INLINE mal_device_config mal_device_config_init(mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_recv_proc onRecvCallback, mal_send_proc onSendCallback) { return mal_device_config_init_ex(format, channels, sampleRate, NULL, onRecvCallback, onSendCallback); } +// A simplified version of mal_device_config_init() for capture devices. +static MAL_INLINE mal_device_config mal_device_config_init_capture_ex(mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_channel channelMap[MAL_MAX_CHANNELS], mal_recv_proc onRecvCallback) { return mal_device_config_init_ex(format, channels, sampleRate, channelMap, onRecvCallback, NULL); } +static MAL_INLINE mal_device_config mal_device_config_init_capture(mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_recv_proc onRecvCallback) { return mal_device_config_init_capture_ex(format, channels, sampleRate, NULL, onRecvCallback); } -// High-level helper for doing a full format conversion in one go. Returns the number of output frames. Call this with pOut set to NULL to -// determine the required size of the output buffer. -// -// A return value of 0 indicates an error. -// -// This function is useful for one-off bulk conversions, but if you're streaming data you should use the DSP APIs instead. -mal_uint64 mal_convert_frames(void* pOut, mal_format formatOut, mal_uint32 channelsOut, mal_uint32 sampleRateOut, const void* pIn, mal_format formatIn, mal_uint32 channelsIn, mal_uint32 sampleRateIn, mal_uint64 frameCountIn); -mal_uint64 mal_convert_frames_ex(void* pOut, mal_format formatOut, mal_uint32 channelsOut, mal_uint32 sampleRateOut, mal_channel channelMapOut[MAL_MAX_CHANNELS], const void* pIn, mal_format formatIn, mal_uint32 channelsIn, mal_uint32 sampleRateIn, mal_channel channelMapIn[MAL_MAX_CHANNELS], mal_uint64 frameCountIn); +// A simplified version of mal_device_config_init() for playback devices. +static MAL_INLINE mal_device_config mal_device_config_init_playback_ex(mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_channel channelMap[MAL_MAX_CHANNELS], mal_send_proc onSendCallback) { return mal_device_config_init_ex(format, channels, sampleRate, channelMap, NULL, onSendCallback); } +static MAL_INLINE mal_device_config mal_device_config_init_playback(mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_send_proc onSendCallback) { return mal_device_config_init_playback_ex(format, channels, sampleRate, NULL, onSendCallback); } @@ -2296,37 +2486,9 @@ void mal_mutex_lock(mal_mutex* pMutex); void mal_mutex_unlock(mal_mutex* pMutex); - -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// -// Miscellaneous Helpers -// -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -// malloc(). Calls MAL_MALLOC(). -void* mal_malloc(size_t sz); - -// realloc(). Calls MAL_REALLOC(). -void* mal_realloc(void* p, size_t sz); - -// free(). Calls MAL_FREE(). -void mal_free(void* p); - -// Performs an aligned malloc, with the assumption that the alignment is a power of 2. -void* mal_aligned_malloc(size_t sz, size_t alignment); - -// Free's an aligned malloc'd buffer. -void mal_aligned_free(void* p); - // Retrieves a friendly name for a backend. const char* mal_get_backend_name(mal_backend backend); -// Retrieves a friendly name for a format. -const char* mal_get_format_name(mal_format format); - -// Blends two frames in floating point format. -void mal_blend_f32(float* pOut, float* pInA, float* pInB, float factor, mal_uint32 channels); - // Calculates a scaling factor relative to speed of the system. // // This could be useful for dynamically determining the size of a device's internal buffer based on the speed of the system. @@ -2342,35 +2504,9 @@ mal_uint32 mal_scale_buffer_size(mal_uint32 baseBufferSize, float scale); // Calculates a buffer size in frames for the specified performance profile and scale factor. mal_uint32 mal_calculate_default_buffer_size_in_frames(mal_performance_profile performanceProfile, mal_uint32 sampleRate, float scale); +#endif // MAL_NO_DEVICE_IO -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// -// Format Conversion -// -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -void mal_pcm_u8_to_s16(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); -void mal_pcm_u8_to_s24(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); -void mal_pcm_u8_to_s32(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); -void mal_pcm_u8_to_f32(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); -void mal_pcm_s16_to_u8(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); -void mal_pcm_s16_to_s24(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); -void mal_pcm_s16_to_s32(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); -void mal_pcm_s16_to_f32(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); -void mal_pcm_s24_to_u8(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); -void mal_pcm_s24_to_s16(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); -void mal_pcm_s24_to_s32(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); -void mal_pcm_s24_to_f32(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); -void mal_pcm_s32_to_u8(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); -void mal_pcm_s32_to_s16(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); -void mal_pcm_s32_to_s24(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); -void mal_pcm_s32_to_f32(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); -void mal_pcm_f32_to_u8(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); -void mal_pcm_f32_to_s16(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); -void mal_pcm_f32_to_s24(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); -void mal_pcm_f32_to_s32(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); -void mal_pcm_convert(void* pOut, mal_format formatOut, const void* pIn, mal_format formatIn, mal_uint64 sampleCount, mal_dither_mode ditherMode); - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -2395,10 +2531,17 @@ typedef mal_result (* mal_decoder_uninit_proc) (mal_decoder* pDecoder); typedef struct { - mal_format format; // Set to 0 or mal_format_unknown to use the stream's internal format. - mal_uint32 channels; // Set to 0 to use the stream's internal channels. - mal_uint32 sampleRate; // Set to 0 to use the stream's internal sample rate. + mal_format format; // Set to 0 or mal_format_unknown to use the stream's internal format. + mal_uint32 channels; // Set to 0 to use the stream's internal channels. + mal_uint32 sampleRate; // Set to 0 to use the stream's internal sample rate. mal_channel channelMap[MAL_MAX_CHANNELS]; + mal_channel_mix_mode channelMixMode; + mal_dither_mode ditherMode; + mal_src_algorithm srcAlgorithm; + union + { + mal_src_config_sinc sinc; + } src; } mal_decoder_config; struct mal_decoder @@ -2660,9 +2803,23 @@ mal_uint64 mal_sine_wave_read(mal_sine_wave* pSignWave, mal_uint64 count, float* #elif (defined(__GNUC__) || defined(__clang__)) && !defined(MAL_ANDROID) static MAL_INLINE void mal_cpuid(int info[4], int fid) { - __asm__ __volatile__ ( - "cpuid" : "=a"(info[0]), "=b"(info[1]), "=c"(info[2]), "=d"(info[3]) : "a"(fid), "c"(0) - ); + // It looks like the -fPIC option uses the ebx register which GCC complains about. We can work around this by just using a different register, the + // specific register of which I'm letting the compiler decide on. The "k" prefix is used to specify a 32-bit register. The {...} syntax is for + // supporting different assembly dialects. + // + // What's basically happening is that we're saving and restoring the ebx register manually. + #if defined(DRFLAC_X86) && defined(__PIC__) + __asm__ __volatile__ ( + "xchg{l} {%%}ebx, %k1;" + "cpuid;" + "xchg{l} {%%}ebx, %k1;" + : "=a"(info[0]), "=&r"(info[1]), "=c"(info[2]), "=d"(info[3]) : "a"(fid), "c"(0) + ); + #else + __asm__ __volatile__ ( + "cpuid" : "=a"(info[0]), "=b"(info[1]), "=c"(info[2]), "=d"(info[3]) : "a"(fid), "c"(0) + ); + #endif } static MAL_INLINE unsigned long long mal_xgetbv(int reg) @@ -2797,185 +2954,72 @@ static MAL_INLINE mal_bool32 mal_has_avx512f() mal_cpuid(info7, 7); if (((info1[2] & (1 << 27)) != 0) && ((info7[1] & (1 << 16)) != 0)) { mal_uint64 xrc = mal_xgetbv(0); - if ((xrc & 0xE6) == 0xE6) { - return MAL_TRUE; - } else { - return MAL_FALSE; - } - } else { - return MAL_FALSE; - } - #endif - #endif - #else - return MAL_FALSE; // AVX-512F is only supported on x86 and x64 architectures. - #endif -#else - return MAL_FALSE; // No compiler support. -#endif -} - -static MAL_INLINE mal_bool32 mal_has_neon() -{ -#if defined(MAL_SUPPORT_NEON) - #if defined(MAL_ARM) && !defined(MAL_NO_NEON) - #if (defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64)) - return MAL_TRUE; // If the compiler is allowed to freely generate NEON code we can assume support. - #else - // TODO: Runtime check. - return MAL_FALSE; - #endif - #else - return MAL_FALSE; // NEON is only supported on ARM architectures. - #endif -#else - return MAL_FALSE; // No compiler support. -#endif -} - - -#ifndef MAL_PI -#define MAL_PI 3.14159265358979323846264f -#endif -#ifndef MAL_PI_D -#define MAL_PI_D 3.14159265358979323846264 -#endif -#ifndef MAL_TAU -#define MAL_TAU 6.28318530717958647693f -#endif -#ifndef MAL_TAU_D -#define MAL_TAU_D 6.28318530717958647693 -#endif - -// Unfortunately using runtime linking for pthreads causes problems. This has occurred for me when testing on FreeBSD. When -// using runtime linking, deadlocks can occur (for me it happens when loading data from fread()). It turns out that doing -// compile-time linking fixes this. I'm not sure why this happens, but the safest way I can think of to fix this is to simply -// disable runtime linking by default. To enable runtime linking, #define this before the implementation of this file. I am -// not officially supporting this, but I'm leaving it here in case it's useful for somebody, somewhere. -//#define MAL_USE_RUNTIME_LINKING_FOR_PTHREAD - -// Disable run-time linking on certain backends. -#ifndef MAL_NO_RUNTIME_LINKING - #if defined(MAL_ANDROID) || defined(MAL_EMSCRIPTEN) - #define MAL_NO_RUNTIME_LINKING - #endif -#endif - -// Check if we have the necessary development packages for each backend at the top so we can use this to determine whether or not -// certain unused functions and variables can be excluded from the build to avoid warnings. -#ifdef MAL_ENABLE_WASAPI - #define MAL_HAS_WASAPI // Every compiler should support WASAPI -#endif -#ifdef MAL_ENABLE_DSOUND - #define MAL_HAS_DSOUND // Every compiler should support DirectSound. -#endif -#ifdef MAL_ENABLE_WINMM - #define MAL_HAS_WINMM // Every compiler I'm aware of supports WinMM. -#endif -#ifdef MAL_ENABLE_ALSA - #define MAL_HAS_ALSA - #ifdef MAL_NO_RUNTIME_LINKING - #ifdef __has_include - #if !__has_include() - #undef MAL_HAS_ALSA - #endif - #endif - #endif -#endif -#ifdef MAL_ENABLE_PULSEAUDIO - #define MAL_HAS_PULSEAUDIO // Development packages are unnecessary for PulseAudio. - #ifdef MAL_NO_RUNTIME_LINKING - #ifdef __has_include - #if !__has_include() - #undef MAL_HAS_PULSEAUDIO - #endif - #endif - #endif -#endif -#ifdef MAL_ENABLE_JACK - #define MAL_HAS_JACK - #ifdef MAL_NO_RUNTIME_LINKING - #ifdef __has_include - #if !__has_include() - #undef MAL_HAS_JACK - #endif - #endif - #endif -#endif -#ifdef MAL_ENABLE_COREAUDIO - #define MAL_HAS_COREAUDIO -#endif -#ifdef MAL_ENABLE_OSS - #define MAL_HAS_OSS // OSS is the only supported backend for Unix and BSD, so it must be present else this library is useless. -#endif -#ifdef MAL_ENABLE_OPENSL - #define MAL_HAS_OPENSL // OpenSL is the only supported backend for Android. It must be present. -#endif -#ifdef MAL_ENABLE_OPENAL - #define MAL_HAS_OPENAL - #ifdef MAL_NO_RUNTIME_LINKING - #ifdef __has_include - #if !__has_include() - #undef MAL_HAS_OPENAL - #endif - #endif - #endif -#endif -#ifdef MAL_ENABLE_SDL - #define MAL_HAS_SDL - - // SDL headers are necessary if using compile-time linking. - #ifdef MAL_NO_RUNTIME_LINKING - #ifdef __has_include - #ifdef MAL_EMSCRIPTEN - #if !__has_include() - #undef MAL_HAS_SDL - #endif - #else - #if !__has_include() - #undef MAL_HAS_SDL - #endif + if ((xrc & 0xE6) == 0xE6) { + return MAL_TRUE; + } else { + return MAL_FALSE; + } + } else { + return MAL_FALSE; + } #endif #endif + #else + return MAL_FALSE; // AVX-512F is only supported on x86 and x64 architectures. #endif +#else + return MAL_FALSE; // No compiler support. #endif -#ifdef MAL_ENABLE_NULL - #define MAL_HAS_NULL // Everything supports the null backend. +} + +static MAL_INLINE mal_bool32 mal_has_neon() +{ +#if defined(MAL_SUPPORT_NEON) + #if defined(MAL_ARM) && !defined(MAL_NO_NEON) + #if (defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64)) + return MAL_TRUE; // If the compiler is allowed to freely generate NEON code we can assume support. + #else + // TODO: Runtime check. + return MAL_FALSE; + #endif + #else + return MAL_FALSE; // NEON is only supported on ARM architectures. + #endif +#else + return MAL_FALSE; // No compiler support. #endif +} -#ifdef MAL_WIN32 - #define MAL_THREADCALL WINAPI - typedef unsigned long mal_thread_result; +static MAL_INLINE mal_bool32 mal_is_little_endian() +{ +#if defined(MAL_X86) || defined(MAL_X64) + return MAL_TRUE; #else - #define MAL_THREADCALL - typedef void* mal_thread_result; + int n = 1; + return (*(char*)&n) == 1; #endif -typedef mal_thread_result (MAL_THREADCALL * mal_thread_entry_proc)(void* pData); - -#ifdef MAL_WIN32 -typedef HRESULT (WINAPI * MAL_PFN_CoInitializeEx)(LPVOID pvReserved, DWORD dwCoInit); -typedef void (WINAPI * MAL_PFN_CoUninitialize)(); -typedef HRESULT (WINAPI * MAL_PFN_CoCreateInstance)(REFCLSID rclsid, LPUNKNOWN pUnkOuter, DWORD dwClsContext, REFIID riid, LPVOID *ppv); -typedef void (WINAPI * MAL_PFN_CoTaskMemFree)(LPVOID pv); -typedef HRESULT (WINAPI * MAL_PFN_PropVariantClear)(PROPVARIANT *pvar); -typedef int (WINAPI * MAL_PFN_StringFromGUID2)(const GUID* const rguid, LPOLESTR lpsz, int cchMax); +} -typedef HWND (WINAPI * MAL_PFN_GetForegroundWindow)(); -typedef HWND (WINAPI * MAL_PFN_GetDesktopWindow)(); +static MAL_INLINE mal_bool32 mal_is_big_endian() +{ + return !mal_is_little_endian(); +} -// Microsoft documents these APIs as returning LSTATUS, but the Win32 API shipping with some compilers do not define it. It's just a LONG. -typedef LONG (WINAPI * MAL_PFN_RegOpenKeyExA)(HKEY hKey, LPCSTR lpSubKey, DWORD ulOptions, REGSAM samDesired, PHKEY phkResult); -typedef LONG (WINAPI * MAL_PFN_RegCloseKey)(HKEY hKey); -typedef LONG (WINAPI * MAL_PFN_RegQueryValueExA)(HKEY hKey, LPCSTR lpValueName, LPDWORD lpReserved, LPDWORD lpType, LPBYTE lpData, LPDWORD lpcbData); -#endif -#define MAL_STATE_UNINITIALIZED 0 -#define MAL_STATE_STOPPED 1 // The device's default state after initialization. -#define MAL_STATE_STARTED 2 // The worker thread is in it's main loop waiting for the driver to request or deliver audio data. -#define MAL_STATE_STARTING 3 // Transitioning from a stopped state to started. -#define MAL_STATE_STOPPING 4 // Transitioning from a started state to stopped. +#ifndef MAL_PI +#define MAL_PI 3.14159265358979323846264f +#endif +#ifndef MAL_PI_D +#define MAL_PI_D 3.14159265358979323846264 +#endif +#ifndef MAL_TAU +#define MAL_TAU 6.28318530717958647693f +#endif +#ifndef MAL_TAU_D +#define MAL_TAU_D 6.28318530717958647693 +#endif // The default format when mal_format_unknown (0) is requested when initializing a device. @@ -3032,8 +3076,8 @@ mal_uint32 g_malStandardSampleRatePriorities[] = { }; mal_format g_malFormatPriorities[] = { - mal_format_f32, // Most common - mal_format_s16, + mal_format_s16, // Most common + mal_format_f32, //mal_format_s24_32, // Clean alignment mal_format_s32, @@ -3043,8 +3087,7 @@ mal_format g_malFormatPriorities[] = { mal_format_u8 // Low quality }; -#define MAL_DEFAULT_PLAYBACK_DEVICE_NAME "Default Playback Device" -#define MAL_DEFAULT_CAPTURE_DEVICE_NAME "Default Capture Device" + /////////////////////////////////////////////////////////////////////////////// // @@ -3504,69 +3547,270 @@ static MAL_INLINE mal_int32 mal_dither_s32(mal_dither_mode ditherMode, mal_int32 } -// Splits a buffer into parts of equal length and of the given alignment. The returned size of the split buffers will be a -// multiple of the alignment. The alignment must be a power of 2. -void mal_split_buffer(void* pBuffer, size_t bufferSize, size_t splitCount, size_t alignment, void** ppBuffersOut, size_t* pSplitSizeOut) +// Splits a buffer into parts of equal length and of the given alignment. The returned size of the split buffers will be a +// multiple of the alignment. The alignment must be a power of 2. +void mal_split_buffer(void* pBuffer, size_t bufferSize, size_t splitCount, size_t alignment, void** ppBuffersOut, size_t* pSplitSizeOut) +{ + if (pSplitSizeOut) { + *pSplitSizeOut = 0; + } + + if (pBuffer == NULL || bufferSize == 0 || splitCount == 0) { + return; + } + + if (alignment == 0) { + alignment = 1; + } + + mal_uintptr pBufferUnaligned = (mal_uintptr)pBuffer; + mal_uintptr pBufferAligned = (pBufferUnaligned + (alignment-1)) & ~(alignment-1); + size_t unalignedBytes = (size_t)(pBufferAligned - pBufferUnaligned); + + size_t splitSize = 0; + if (bufferSize >= unalignedBytes) { + splitSize = (bufferSize - unalignedBytes) / splitCount; + splitSize = splitSize & ~(alignment-1); + } + + if (ppBuffersOut != NULL) { + for (size_t i = 0; i < splitCount; ++i) { + ppBuffersOut[i] = (mal_uint8*)(pBufferAligned + (splitSize*i)); + } + } + + if (pSplitSizeOut) { + *pSplitSizeOut = splitSize; + } +} + + +/////////////////////////////////////////////////////////////////////////////// +// +// Atomics +// +/////////////////////////////////////////////////////////////////////////////// +#if defined(_WIN32) && !defined(__GNUC__) +#define mal_memory_barrier() MemoryBarrier() +#define mal_atomic_exchange_32(a, b) InterlockedExchange((LONG*)a, (LONG)b) +#define mal_atomic_exchange_64(a, b) InterlockedExchange64((LONGLONG*)a, (LONGLONG)b) +#define mal_atomic_increment_32(a) InterlockedIncrement((LONG*)a) +#define mal_atomic_decrement_32(a) InterlockedDecrement((LONG*)a) +#else +#define mal_memory_barrier() __sync_synchronize() +#define mal_atomic_exchange_32(a, b) (void)__sync_lock_test_and_set(a, b); __sync_synchronize() +#define mal_atomic_exchange_64(a, b) (void)__sync_lock_test_and_set(a, b); __sync_synchronize() +#define mal_atomic_increment_32(a) __sync_add_and_fetch(a, 1) +#define mal_atomic_decrement_32(a) __sync_sub_and_fetch(a, 1) +#endif + +#ifdef MAL_64BIT +#define mal_atomic_exchange_ptr mal_atomic_exchange_64 +#endif +#ifdef MAL_32BIT +#define mal_atomic_exchange_ptr mal_atomic_exchange_32 +#endif + + +mal_uint32 mal_get_standard_sample_rate_priority_index(mal_uint32 sampleRate) // Lower = higher priority +{ + for (mal_uint32 i = 0; i < mal_countof(g_malStandardSampleRatePriorities); ++i) { + if (g_malStandardSampleRatePriorities[i] == sampleRate) { + return i; + } + } + + return (mal_uint32)-1; +} + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// DEVICE I/O +// ========== +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#ifndef MAL_NO_DEVICE_IO +// Unfortunately using runtime linking for pthreads causes problems. This has occurred for me when testing on FreeBSD. When +// using runtime linking, deadlocks can occur (for me it happens when loading data from fread()). It turns out that doing +// compile-time linking fixes this. I'm not sure why this happens, but the safest way I can think of to fix this is to simply +// disable runtime linking by default. To enable runtime linking, #define this before the implementation of this file. I am +// not officially supporting this, but I'm leaving it here in case it's useful for somebody, somewhere. +//#define MAL_USE_RUNTIME_LINKING_FOR_PTHREAD + +// Disable run-time linking on certain backends. +#ifndef MAL_NO_RUNTIME_LINKING + #if defined(MAL_ANDROID) || defined(MAL_EMSCRIPTEN) + #define MAL_NO_RUNTIME_LINKING + #endif +#endif + +// Check if we have the necessary development packages for each backend at the top so we can use this to determine whether or not +// certain unused functions and variables can be excluded from the build to avoid warnings. +#ifdef MAL_ENABLE_WASAPI + #define MAL_HAS_WASAPI // Every compiler should support WASAPI +#endif +#ifdef MAL_ENABLE_DSOUND + #define MAL_HAS_DSOUND // Every compiler should support DirectSound. +#endif +#ifdef MAL_ENABLE_WINMM + #define MAL_HAS_WINMM // Every compiler I'm aware of supports WinMM. +#endif +#ifdef MAL_ENABLE_ALSA + #define MAL_HAS_ALSA + #ifdef MAL_NO_RUNTIME_LINKING + #ifdef __has_include + #if !__has_include() + #undef MAL_HAS_ALSA + #endif + #endif + #endif +#endif +#ifdef MAL_ENABLE_PULSEAUDIO + #define MAL_HAS_PULSEAUDIO // Development packages are unnecessary for PulseAudio. + #ifdef MAL_NO_RUNTIME_LINKING + #ifdef __has_include + #if !__has_include() + #undef MAL_HAS_PULSEAUDIO + #endif + #endif + #endif +#endif +#ifdef MAL_ENABLE_JACK + #define MAL_HAS_JACK + #ifdef MAL_NO_RUNTIME_LINKING + #ifdef __has_include + #if !__has_include() + #undef MAL_HAS_JACK + #endif + #endif + #endif +#endif +#ifdef MAL_ENABLE_COREAUDIO + #define MAL_HAS_COREAUDIO +#endif +#ifdef MAL_ENABLE_SNDIO + #define MAL_HAS_SNDIO +#endif +#ifdef MAL_ENABLE_AUDIOIO + #define MAL_HAS_AUDIOIO // When enabled, always assume audioio is available. +#endif +#ifdef MAL_ENABLE_OSS + #define MAL_HAS_OSS // OSS is the only supported backend for Unix and BSD, so it must be present else this library is useless. +#endif +#ifdef MAL_ENABLE_OPENSL + #define MAL_HAS_OPENSL // OpenSL is the only supported backend for Android. It must be present. +#endif +#ifdef MAL_ENABLE_OPENAL + #define MAL_HAS_OPENAL + #ifdef MAL_NO_RUNTIME_LINKING + #ifdef __has_include + #if !__has_include() + #undef MAL_HAS_OPENAL + #endif + #endif + #endif +#endif +#ifdef MAL_ENABLE_SDL + #define MAL_HAS_SDL + + // SDL headers are necessary if using compile-time linking. + #ifdef MAL_NO_RUNTIME_LINKING + #ifdef __has_include + #ifdef MAL_EMSCRIPTEN + #if !__has_include() + #undef MAL_HAS_SDL + #endif + #else + #if !__has_include() + #undef MAL_HAS_SDL + #endif + #endif + #endif + #endif +#endif +#ifdef MAL_ENABLE_NULL + #define MAL_HAS_NULL // Everything supports the null backend. +#endif + +const mal_backend g_malDefaultBackends[] = { + mal_backend_wasapi, + mal_backend_dsound, + mal_backend_winmm, + mal_backend_coreaudio, + mal_backend_sndio, + mal_backend_audioio, + mal_backend_oss, + mal_backend_pulseaudio, + mal_backend_alsa, + mal_backend_jack, + mal_backend_opensl, + mal_backend_openal, + mal_backend_sdl, + mal_backend_null +}; + +const char* mal_get_backend_name(mal_backend backend) { - if (pSplitSizeOut) { - *pSplitSizeOut = 0; + switch (backend) + { + case mal_backend_null: return "Null"; + case mal_backend_wasapi: return "WASAPI"; + case mal_backend_dsound: return "DirectSound"; + case mal_backend_winmm: return "WinMM"; + case mal_backend_alsa: return "ALSA"; + case mal_backend_pulseaudio: return "PulseAudio"; + case mal_backend_jack: return "JACK"; + case mal_backend_coreaudio: return "Core Audio"; + case mal_backend_sndio: return "sndio"; + case mal_backend_audioio: return "audioio"; + case mal_backend_oss: return "OSS"; + case mal_backend_opensl: return "OpenSL|ES"; + case mal_backend_openal: return "OpenAL"; + case mal_backend_sdl: return "SDL"; + default: return "Unknown"; } +} - if (pBuffer == NULL || bufferSize == 0 || splitCount == 0) { - return; - } - if (alignment == 0) { - alignment = 1; - } - mal_uintptr pBufferUnaligned = (mal_uintptr)pBuffer; - mal_uintptr pBufferAligned = (pBufferUnaligned + (alignment-1)) & ~(alignment-1); - size_t unalignedBytes = (size_t)(pBufferAligned - pBufferUnaligned); +#ifdef MAL_WIN32 + #define MAL_THREADCALL WINAPI + typedef unsigned long mal_thread_result; +#else + #define MAL_THREADCALL + typedef void* mal_thread_result; +#endif +typedef mal_thread_result (MAL_THREADCALL * mal_thread_entry_proc)(void* pData); - size_t splitSize = 0; - if (bufferSize >= unalignedBytes) { - splitSize = (bufferSize - unalignedBytes) / splitCount; - splitSize = splitSize & ~(alignment-1); - } +#ifdef MAL_WIN32 +typedef HRESULT (WINAPI * MAL_PFN_CoInitializeEx)(LPVOID pvReserved, DWORD dwCoInit); +typedef void (WINAPI * MAL_PFN_CoUninitialize)(); +typedef HRESULT (WINAPI * MAL_PFN_CoCreateInstance)(REFCLSID rclsid, LPUNKNOWN pUnkOuter, DWORD dwClsContext, REFIID riid, LPVOID *ppv); +typedef void (WINAPI * MAL_PFN_CoTaskMemFree)(LPVOID pv); +typedef HRESULT (WINAPI * MAL_PFN_PropVariantClear)(PROPVARIANT *pvar); +typedef int (WINAPI * MAL_PFN_StringFromGUID2)(const GUID* const rguid, LPOLESTR lpsz, int cchMax); - if (ppBuffersOut != NULL) { - for (size_t i = 0; i < splitCount; ++i) { - ppBuffersOut[i] = (mal_uint8*)(pBufferAligned + (splitSize*i)); - } - } +typedef HWND (WINAPI * MAL_PFN_GetForegroundWindow)(); +typedef HWND (WINAPI * MAL_PFN_GetDesktopWindow)(); - if (pSplitSizeOut) { - *pSplitSizeOut = splitSize; - } -} +// Microsoft documents these APIs as returning LSTATUS, but the Win32 API shipping with some compilers do not define it. It's just a LONG. +typedef LONG (WINAPI * MAL_PFN_RegOpenKeyExA)(HKEY hKey, LPCSTR lpSubKey, DWORD ulOptions, REGSAM samDesired, PHKEY phkResult); +typedef LONG (WINAPI * MAL_PFN_RegCloseKey)(HKEY hKey); +typedef LONG (WINAPI * MAL_PFN_RegQueryValueExA)(HKEY hKey, LPCSTR lpValueName, LPDWORD lpReserved, LPDWORD lpType, LPBYTE lpData, LPDWORD lpcbData); +#endif -/////////////////////////////////////////////////////////////////////////////// -// -// Atomics -// -/////////////////////////////////////////////////////////////////////////////// -#if defined(_WIN32) && !defined(__GNUC__) -#define mal_memory_barrier() MemoryBarrier() -#define mal_atomic_exchange_32(a, b) InterlockedExchange((LONG*)a, (LONG)b) -#define mal_atomic_exchange_64(a, b) InterlockedExchange64((LONGLONG*)a, (LONGLONG)b) -#define mal_atomic_increment_32(a) InterlockedIncrement((LONG*)a) -#define mal_atomic_decrement_32(a) InterlockedDecrement((LONG*)a) -#else -#define mal_memory_barrier() __sync_synchronize() -#define mal_atomic_exchange_32(a, b) (void)__sync_lock_test_and_set(a, b); __sync_synchronize() -#define mal_atomic_exchange_64(a, b) (void)__sync_lock_test_and_set(a, b); __sync_synchronize() -#define mal_atomic_increment_32(a) __sync_add_and_fetch(a, 1) -#define mal_atomic_decrement_32(a) __sync_sub_and_fetch(a, 1) -#endif +#define MAL_STATE_UNINITIALIZED 0 +#define MAL_STATE_STOPPED 1 // The device's default state after initialization. +#define MAL_STATE_STARTED 2 // The worker thread is in it's main loop waiting for the driver to request or deliver audio data. +#define MAL_STATE_STARTING 3 // Transitioning from a stopped state to started. +#define MAL_STATE_STOPPING 4 // Transitioning from a started state to stopped. -#ifdef MAL_64BIT -#define mal_atomic_exchange_ptr mal_atomic_exchange_64 -#endif -#ifdef MAL_32BIT -#define mal_atomic_exchange_ptr mal_atomic_exchange_32 -#endif +#define MAL_DEFAULT_PLAYBACK_DEVICE_NAME "Default Playback Device" +#define MAL_DEFAULT_CAPTURE_DEVICE_NAME "Default Capture Device" /////////////////////////////////////////////////////////////////////////////// @@ -4154,6 +4398,136 @@ mal_uint32 mal_get_closest_standard_sample_rate(mal_uint32 sampleRateIn) } +typedef struct +{ + mal_uint8* pInputFrames; + mal_uint32 framesRemaining; +} mal_calculate_cpu_speed_factor_data; + +mal_uint32 mal_calculate_cpu_speed_factor__on_read(mal_dsp* pDSP, mal_uint32 framesToRead, void* pFramesOut, void* pUserData) +{ + mal_calculate_cpu_speed_factor_data* pData = (mal_calculate_cpu_speed_factor_data*)pUserData; + mal_assert(pData != NULL); + + if (framesToRead > pData->framesRemaining) { + framesToRead = pData->framesRemaining; + } + + mal_copy_memory(pFramesOut, pData->pInputFrames, framesToRead*pDSP->formatConverterIn.config.channels * sizeof(*pData->pInputFrames)); + + pData->pInputFrames += framesToRead; + pData->framesRemaining -= framesToRead; + + return framesToRead; +} + +float mal_calculate_cpu_speed_factor() +{ + // Our profiling test is based on how quick it can process 1 second worth of samples through mini_al's data conversion pipeline. + + // This factor is multiplied with the profiling time. May need to fiddle with this to get an accurate value. + double f = 1000; + + // Experiment: Reduce the factor a little when debug mode is used to reduce a blowout. +#if !defined(NDEBUG) || defined(_DEBUG) + f /= 2; +#endif + + mal_uint32 sampleRateIn = 44100; + mal_uint32 sampleRateOut = 48000; + mal_uint32 channelsIn = 2; + mal_uint32 channelsOut = 6; + + // Using the heap here to avoid an unnecessary static memory allocation. Also too big for the stack. + mal_uint8* pInputFrames = NULL; + float* pOutputFrames = NULL; + + size_t inputDataSize = sampleRateIn * channelsIn * sizeof(*pInputFrames); + size_t outputDataSize = sampleRateOut * channelsOut * sizeof(*pOutputFrames); + + void* pData = mal_malloc(inputDataSize + outputDataSize); + if (pData == NULL) { + return 1; + } + + pInputFrames = (mal_uint8*)pData; + pOutputFrames = (float*)(pInputFrames + inputDataSize); + + + + + mal_calculate_cpu_speed_factor_data data; + data.pInputFrames = pInputFrames; + data.framesRemaining = sampleRateIn; + + mal_dsp_config config = mal_dsp_config_init(mal_format_u8, channelsIn, sampleRateIn, mal_format_f32, channelsOut, sampleRateOut, mal_calculate_cpu_speed_factor__on_read, &data); + + // Use linear sample rate conversion because it's the simplest and least likely to cause skewing as a result of tweaks to default + // configurations in the future. + config.srcAlgorithm = mal_src_algorithm_linear; + + // Experiment: Disable SIMD extensions when profiling just to try and keep things a bit more consistent. The idea is to get a general + // indication on the speed of the system, but SIMD is used more heavily in the DSP pipeline than in the general case which may make + // the results a little less realistic. + config.noSSE2 = MAL_TRUE; + config.noAVX2 = MAL_TRUE; + config.noAVX512 = MAL_TRUE; + config.noNEON = MAL_TRUE; + + mal_dsp dsp; + mal_result result = mal_dsp_init(&config, &dsp); + if (result != MAL_SUCCESS) { + mal_free(pData); + return 1; + } + + + int iterationCount = 2; + + mal_timer timer; + mal_timer_init(&timer); + double startTime = mal_timer_get_time_in_seconds(&timer); + { + for (int i = 0; i < iterationCount; ++i) { + mal_dsp_read(&dsp, sampleRateOut, pOutputFrames, &data); + data.pInputFrames = pInputFrames; + data.framesRemaining = sampleRateIn; + } + } + double executionTimeInSeconds = mal_timer_get_time_in_seconds(&timer) - startTime; + executionTimeInSeconds /= iterationCount; + + + mal_free(pData); + + // Guard against extreme blowouts. + return (float)mal_clamp(executionTimeInSeconds * f, 0.1, 100.0); +} + +mal_uint32 mal_scale_buffer_size(mal_uint32 baseBufferSize, float scale) +{ + return mal_max(1, (mal_uint32)(baseBufferSize*scale)); +} + +mal_uint32 mal_calculate_default_buffer_size_in_frames(mal_performance_profile performanceProfile, mal_uint32 sampleRate, float scale) +{ + mal_uint32 baseLatency; + if (performanceProfile == mal_performance_profile_low_latency) { + baseLatency = MAL_BASE_BUFFER_SIZE_IN_MILLISECONDS_LOW_LATENCY; + } else { + baseLatency = MAL_BASE_BUFFER_SIZE_IN_MILLISECONDS_CONSERVATIVE; + } + + mal_uint32 sampleRateMS = (sampleRate/1000); + + mal_uint32 minBufferSize = sampleRateMS * mal_min(baseLatency / 5, 1); // <-- Guard against multiply by zero. + mal_uint32 maxBufferSize = sampleRateMS * (baseLatency * 40); + + mal_uint32 bufferSize = mal_scale_buffer_size((sampleRate/1000) * baseLatency, scale); + return mal_clamp(bufferSize, minBufferSize, maxBufferSize); +} + + const char* mal_log_level_to_string(mal_uint32 logLevel) { switch (logLevel) @@ -6191,7 +6565,7 @@ mal_uint32 mal_device__get_available_frames__wasapi(mal_device* pDevice) { mal_assert(pDevice != NULL); -#if 1 +#if 0 if (pDevice->type == mal_device_type_playback) { mal_uint32 paddingFramesCount; HRESULT hr = mal_IAudioClient_GetCurrentPadding((mal_IAudioClient*)pDevice->wasapi.pAudioClient, &paddingFramesCount); @@ -6215,15 +6589,20 @@ mal_uint32 mal_device__get_available_frames__wasapi(mal_device* pDevice) } #else mal_uint32 paddingFramesCount; - HRESULT hr = mal_IAudioClient_GetCurrentPadding(pDevice->wasapi.pAudioClient, &paddingFramesCount); + HRESULT hr = mal_IAudioClient_GetCurrentPadding((mal_IAudioClient*)pDevice->wasapi.pAudioClient, &paddingFramesCount); if (FAILED(hr)) { return 0; } + // Slightly different rules for exclusive and shared modes. if (pDevice->exclusiveMode) { return paddingFramesCount; } else { - return pDevice->bufferSizeInFrames - paddingFramesCount; + if (pDevice->type == mal_device_type_playback) { + return pDevice->bufferSizeInFrames - paddingFramesCount; + } else { + return paddingFramesCount; + } } #endif } @@ -8843,16 +9222,6 @@ typedef size_t (* mal_snd_pcm_info_sizeof_proc) typedef const char* (* mal_snd_pcm_info_get_name_proc) (const mal_snd_pcm_info_t* info); typedef int (* mal_snd_config_update_free_global_proc) (); -mal_snd_pcm_format_t g_mal_ALSAFormats[] = { - MAL_SND_PCM_FORMAT_UNKNOWN, // mal_format_unknown - MAL_SND_PCM_FORMAT_U8, // mal_format_u8 - MAL_SND_PCM_FORMAT_S16_LE, // mal_format_s16 - MAL_SND_PCM_FORMAT_S24_3LE, // mal_format_s24 - //MAL_SND_PCM_FORMAT_S24_LE, // mal_format_s24_32 - MAL_SND_PCM_FORMAT_S32_LE, // mal_format_s32 - MAL_SND_PCM_FORMAT_FLOAT_LE // mal_format_f32 -}; - // This array specifies each of the common devices that can be used for both playback and capture. const char* g_malCommonDeviceNamesALSA[] = { "default", @@ -8900,20 +9269,52 @@ float mal_find_default_buffer_size_scale__alsa(const char* deviceName) mal_snd_pcm_format_t mal_convert_mal_format_to_alsa_format(mal_format format) { - return g_mal_ALSAFormats[format]; + mal_snd_pcm_format_t ALSAFormats[] = { + MAL_SND_PCM_FORMAT_UNKNOWN, // mal_format_unknown + MAL_SND_PCM_FORMAT_U8, // mal_format_u8 + MAL_SND_PCM_FORMAT_S16_LE, // mal_format_s16 + MAL_SND_PCM_FORMAT_S24_3LE, // mal_format_s24 + MAL_SND_PCM_FORMAT_S32_LE, // mal_format_s32 + MAL_SND_PCM_FORMAT_FLOAT_LE // mal_format_f32 + }; + + if (mal_is_big_endian()) { + ALSAFormats[0] = MAL_SND_PCM_FORMAT_UNKNOWN; + ALSAFormats[1] = MAL_SND_PCM_FORMAT_U8; + ALSAFormats[2] = MAL_SND_PCM_FORMAT_S16_BE; + ALSAFormats[3] = MAL_SND_PCM_FORMAT_S24_3BE; + ALSAFormats[4] = MAL_SND_PCM_FORMAT_S32_BE; + ALSAFormats[5] = MAL_SND_PCM_FORMAT_FLOAT_BE; + } + + + return ALSAFormats[format]; } mal_format mal_convert_alsa_format_to_mal_format(mal_snd_pcm_format_t formatALSA) { - switch (formatALSA) - { - case MAL_SND_PCM_FORMAT_U8: return mal_format_u8; - case MAL_SND_PCM_FORMAT_S16_LE: return mal_format_s16; - case MAL_SND_PCM_FORMAT_S24_3LE: return mal_format_s24; - //case MAL_SND_PCM_FORMAT_S24_LE: return mal_format_s24_32 - case MAL_SND_PCM_FORMAT_S32_LE: return mal_format_s32; - case MAL_SND_PCM_FORMAT_FLOAT_LE: return mal_format_f32; - default: return mal_format_unknown; + if (mal_is_little_endian()) { + switch (formatALSA) { + case MAL_SND_PCM_FORMAT_S16_LE: return mal_format_s16; + case MAL_SND_PCM_FORMAT_S24_3LE: return mal_format_s24; + case MAL_SND_PCM_FORMAT_S32_LE: return mal_format_s32; + case MAL_SND_PCM_FORMAT_FLOAT_LE: return mal_format_f32; + default: break; + } + } else { + switch (formatALSA) { + case MAL_SND_PCM_FORMAT_S16_BE: return mal_format_s16; + case MAL_SND_PCM_FORMAT_S24_3BE: return mal_format_s24; + case MAL_SND_PCM_FORMAT_S32_BE: return mal_format_s32; + case MAL_SND_PCM_FORMAT_FLOAT_BE: return mal_format_f32; + default: break; + } + } + + // Endian agnostic. + switch (formatALSA) { + case MAL_SND_PCM_FORMAT_U8: return mal_format_u8; + default: return mal_format_unknown; } } @@ -9933,11 +10334,19 @@ mal_result mal_device_init__alsa(mal_context* pContext, mal_device_type type, co MAL_SND_PCM_FORMAT_FLOAT_LE, // mal_format_f32 MAL_SND_PCM_FORMAT_S32_LE, // mal_format_s32 MAL_SND_PCM_FORMAT_S24_3LE, // mal_format_s24 - //MAL_SND_PCM_FORMAT_S24_LE, // mal_format_s24_32 MAL_SND_PCM_FORMAT_S16_LE, // mal_format_s16 MAL_SND_PCM_FORMAT_U8 // mal_format_u8 }; + if (mal_is_big_endian()) { + preferredFormatsALSA[0] = MAL_SND_PCM_FORMAT_FLOAT_BE; + preferredFormatsALSA[1] = MAL_SND_PCM_FORMAT_S32_BE; + preferredFormatsALSA[2] = MAL_SND_PCM_FORMAT_S24_3BE; + preferredFormatsALSA[3] = MAL_SND_PCM_FORMAT_S16_BE; + preferredFormatsALSA[4] = MAL_SND_PCM_FORMAT_U8; + } + + formatALSA = MAL_SND_PCM_FORMAT_UNKNOWN; for (size_t i = 0; i < (sizeof(preferredFormatsALSA) / sizeof(preferredFormatsALSA[0])); ++i) { if (((mal_snd_pcm_format_mask_test_proc)pContext->alsa.snd_pcm_format_mask_test)(pFormatMask, preferredFormatsALSA[i])) { @@ -10637,3932 +11046,5239 @@ typedef int mal_pa_stream_flags_t; #define MAL_PA_STREAM_RELATIVE_VOLUME 0x00040000 #define MAL_PA_STREAM_PASSTHROUGH 0x00080000 -typedef int mal_pa_sink_flags_t; -#define MAL_PA_SINK_NOFLAGS 0x00000000 -#define MAL_PA_SINK_HW_VOLUME_CTRL 0x00000001 -#define MAL_PA_SINK_LATENCY 0x00000002 -#define MAL_PA_SINK_HARDWARE 0x00000004 -#define MAL_PA_SINK_NETWORK 0x00000008 -#define MAL_PA_SINK_HW_MUTE_CTRL 0x00000010 -#define MAL_PA_SINK_DECIBEL_VOLUME 0x00000020 -#define MAL_PA_SINK_FLAT_VOLUME 0x00000040 -#define MAL_PA_SINK_DYNAMIC_LATENCY 0x00000080 -#define MAL_PA_SINK_SET_FORMATS 0x00000100 +typedef int mal_pa_sink_flags_t; +#define MAL_PA_SINK_NOFLAGS 0x00000000 +#define MAL_PA_SINK_HW_VOLUME_CTRL 0x00000001 +#define MAL_PA_SINK_LATENCY 0x00000002 +#define MAL_PA_SINK_HARDWARE 0x00000004 +#define MAL_PA_SINK_NETWORK 0x00000008 +#define MAL_PA_SINK_HW_MUTE_CTRL 0x00000010 +#define MAL_PA_SINK_DECIBEL_VOLUME 0x00000020 +#define MAL_PA_SINK_FLAT_VOLUME 0x00000040 +#define MAL_PA_SINK_DYNAMIC_LATENCY 0x00000080 +#define MAL_PA_SINK_SET_FORMATS 0x00000100 + +typedef int mal_pa_source_flags_t; +#define MAL_PA_SOURCE_NOFLAGS 0x00000000 +#define MAL_PA_SOURCE_HW_VOLUME_CTRL 0x00000001 +#define MAL_PA_SOURCE_LATENCY 0x00000002 +#define MAL_PA_SOURCE_HARDWARE 0x00000004 +#define MAL_PA_SOURCE_NETWORK 0x00000008 +#define MAL_PA_SOURCE_HW_MUTE_CTRL 0x00000010 +#define MAL_PA_SOURCE_DECIBEL_VOLUME 0x00000020 +#define MAL_PA_SOURCE_DYNAMIC_LATENCY 0x00000040 +#define MAL_PA_SOURCE_FLAT_VOLUME 0x00000080 + +typedef int mal_pa_context_state_t; +#define MAL_PA_CONTEXT_UNCONNECTED 0 +#define MAL_PA_CONTEXT_CONNECTING 1 +#define MAL_PA_CONTEXT_AUTHORIZING 2 +#define MAL_PA_CONTEXT_SETTING_NAME 3 +#define MAL_PA_CONTEXT_READY 4 +#define MAL_PA_CONTEXT_FAILED 5 +#define MAL_PA_CONTEXT_TERMINATED 6 + +typedef int mal_pa_stream_state_t; +#define MAL_PA_STREAM_UNCONNECTED 0 +#define MAL_PA_STREAM_CREATING 1 +#define MAL_PA_STREAM_READY 2 +#define MAL_PA_STREAM_FAILED 3 +#define MAL_PA_STREAM_TERMINATED 4 + +typedef int mal_pa_operation_state_t; +#define MAL_PA_OPERATION_RUNNING 0 +#define MAL_PA_OPERATION_DONE 1 +#define MAL_PA_OPERATION_CANCELLED 2 + +typedef int mal_pa_sink_state_t; +#define MAL_PA_SINK_INVALID_STATE -1 +#define MAL_PA_SINK_RUNNING 0 +#define MAL_PA_SINK_IDLE 1 +#define MAL_PA_SINK_SUSPENDED 2 + +typedef int mal_pa_source_state_t; +#define MAL_PA_SOURCE_INVALID_STATE -1 +#define MAL_PA_SOURCE_RUNNING 0 +#define MAL_PA_SOURCE_IDLE 1 +#define MAL_PA_SOURCE_SUSPENDED 2 + +typedef int mal_pa_seek_mode_t; +#define MAL_PA_SEEK_RELATIVE 0 +#define MAL_PA_SEEK_ABSOLUTE 1 +#define MAL_PA_SEEK_RELATIVE_ON_READ 2 +#define MAL_PA_SEEK_RELATIVE_END 3 + +typedef int mal_pa_channel_position_t; +#define MAL_PA_CHANNEL_POSITION_INVALID -1 +#define MAL_PA_CHANNEL_POSITION_MONO 0 +#define MAL_PA_CHANNEL_POSITION_FRONT_LEFT 1 +#define MAL_PA_CHANNEL_POSITION_FRONT_RIGHT 2 +#define MAL_PA_CHANNEL_POSITION_FRONT_CENTER 3 +#define MAL_PA_CHANNEL_POSITION_REAR_CENTER 4 +#define MAL_PA_CHANNEL_POSITION_REAR_LEFT 5 +#define MAL_PA_CHANNEL_POSITION_REAR_RIGHT 6 +#define MAL_PA_CHANNEL_POSITION_LFE 7 +#define MAL_PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER 8 +#define MAL_PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER 9 +#define MAL_PA_CHANNEL_POSITION_SIDE_LEFT 10 +#define MAL_PA_CHANNEL_POSITION_SIDE_RIGHT 11 +#define MAL_PA_CHANNEL_POSITION_AUX0 12 +#define MAL_PA_CHANNEL_POSITION_AUX1 13 +#define MAL_PA_CHANNEL_POSITION_AUX2 14 +#define MAL_PA_CHANNEL_POSITION_AUX3 15 +#define MAL_PA_CHANNEL_POSITION_AUX4 16 +#define MAL_PA_CHANNEL_POSITION_AUX5 17 +#define MAL_PA_CHANNEL_POSITION_AUX6 18 +#define MAL_PA_CHANNEL_POSITION_AUX7 19 +#define MAL_PA_CHANNEL_POSITION_AUX8 20 +#define MAL_PA_CHANNEL_POSITION_AUX9 21 +#define MAL_PA_CHANNEL_POSITION_AUX10 22 +#define MAL_PA_CHANNEL_POSITION_AUX11 23 +#define MAL_PA_CHANNEL_POSITION_AUX12 24 +#define MAL_PA_CHANNEL_POSITION_AUX13 25 +#define MAL_PA_CHANNEL_POSITION_AUX14 26 +#define MAL_PA_CHANNEL_POSITION_AUX15 27 +#define MAL_PA_CHANNEL_POSITION_AUX16 28 +#define MAL_PA_CHANNEL_POSITION_AUX17 29 +#define MAL_PA_CHANNEL_POSITION_AUX18 30 +#define MAL_PA_CHANNEL_POSITION_AUX19 31 +#define MAL_PA_CHANNEL_POSITION_AUX20 32 +#define MAL_PA_CHANNEL_POSITION_AUX21 33 +#define MAL_PA_CHANNEL_POSITION_AUX22 34 +#define MAL_PA_CHANNEL_POSITION_AUX23 35 +#define MAL_PA_CHANNEL_POSITION_AUX24 36 +#define MAL_PA_CHANNEL_POSITION_AUX25 37 +#define MAL_PA_CHANNEL_POSITION_AUX26 38 +#define MAL_PA_CHANNEL_POSITION_AUX27 39 +#define MAL_PA_CHANNEL_POSITION_AUX28 40 +#define MAL_PA_CHANNEL_POSITION_AUX29 41 +#define MAL_PA_CHANNEL_POSITION_AUX30 42 +#define MAL_PA_CHANNEL_POSITION_AUX31 43 +#define MAL_PA_CHANNEL_POSITION_TOP_CENTER 44 +#define MAL_PA_CHANNEL_POSITION_TOP_FRONT_LEFT 45 +#define MAL_PA_CHANNEL_POSITION_TOP_FRONT_RIGHT 46 +#define MAL_PA_CHANNEL_POSITION_TOP_FRONT_CENTER 47 +#define MAL_PA_CHANNEL_POSITION_TOP_REAR_LEFT 48 +#define MAL_PA_CHANNEL_POSITION_TOP_REAR_RIGHT 49 +#define MAL_PA_CHANNEL_POSITION_TOP_REAR_CENTER 50 +#define MAL_PA_CHANNEL_POSITION_LEFT MAL_PA_CHANNEL_POSITION_FRONT_LEFT +#define MAL_PA_CHANNEL_POSITION_RIGHT MAL_PA_CHANNEL_POSITION_FRONT_RIGHT +#define MAL_PA_CHANNEL_POSITION_CENTER MAL_PA_CHANNEL_POSITION_FRONT_CENTER +#define MAL_PA_CHANNEL_POSITION_SUBWOOFER MAL_PA_CHANNEL_POSITION_LFE + +typedef int mal_pa_channel_map_def_t; +#define MAL_PA_CHANNEL_MAP_AIFF 0 +#define MAL_PA_CHANNEL_MAP_ALSA 1 +#define MAL_PA_CHANNEL_MAP_AUX 2 +#define MAL_PA_CHANNEL_MAP_WAVEEX 3 +#define MAL_PA_CHANNEL_MAP_OSS 4 +#define MAL_PA_CHANNEL_MAP_DEFAULT MAL_PA_CHANNEL_MAP_AIFF + +typedef int mal_pa_sample_format_t; +#define MAL_PA_SAMPLE_INVALID -1 +#define MAL_PA_SAMPLE_U8 0 +#define MAL_PA_SAMPLE_ALAW 1 +#define MAL_PA_SAMPLE_ULAW 2 +#define MAL_PA_SAMPLE_S16LE 3 +#define MAL_PA_SAMPLE_S16BE 4 +#define MAL_PA_SAMPLE_FLOAT32LE 5 +#define MAL_PA_SAMPLE_FLOAT32BE 6 +#define MAL_PA_SAMPLE_S32LE 7 +#define MAL_PA_SAMPLE_S32BE 8 +#define MAL_PA_SAMPLE_S24LE 9 +#define MAL_PA_SAMPLE_S24BE 10 +#define MAL_PA_SAMPLE_S24_32LE 11 +#define MAL_PA_SAMPLE_S24_32BE 12 + +typedef struct mal_pa_mainloop mal_pa_mainloop; +typedef struct mal_pa_mainloop_api mal_pa_mainloop_api; +typedef struct mal_pa_context mal_pa_context; +typedef struct mal_pa_operation mal_pa_operation; +typedef struct mal_pa_stream mal_pa_stream; +typedef struct mal_pa_spawn_api mal_pa_spawn_api; + +typedef struct +{ + mal_uint32 maxlength; + mal_uint32 tlength; + mal_uint32 prebuf; + mal_uint32 minreq; + mal_uint32 fragsize; +} mal_pa_buffer_attr; + +typedef struct +{ + mal_uint8 channels; + mal_pa_channel_position_t map[MAL_PA_CHANNELS_MAX]; +} mal_pa_channel_map; + +typedef struct +{ + mal_uint8 channels; + mal_uint32 values[MAL_PA_CHANNELS_MAX]; +} mal_pa_cvolume; + +typedef struct +{ + mal_pa_sample_format_t format; + mal_uint32 rate; + mal_uint8 channels; +} mal_pa_sample_spec; + +typedef struct +{ + const char* name; + mal_uint32 index; + const char* description; + mal_pa_sample_spec sample_spec; + mal_pa_channel_map channel_map; + mal_uint32 owner_module; + mal_pa_cvolume volume; + int mute; + mal_uint32 monitor_source; + const char* monitor_source_name; + mal_uint64 latency; + const char* driver; + mal_pa_sink_flags_t flags; + void* proplist; + mal_uint64 configured_latency; + mal_uint32 base_volume; + mal_pa_sink_state_t state; + mal_uint32 n_volume_steps; + mal_uint32 card; + mal_uint32 n_ports; + void** ports; + void* active_port; + mal_uint8 n_formats; + void** formats; +} mal_pa_sink_info; + +typedef struct +{ + const char *name; + mal_uint32 index; + const char *description; + mal_pa_sample_spec sample_spec; + mal_pa_channel_map channel_map; + mal_uint32 owner_module; + mal_pa_cvolume volume; + int mute; + mal_uint32 monitor_of_sink; + const char *monitor_of_sink_name; + mal_uint64 latency; + const char *driver; + mal_pa_source_flags_t flags; + void* proplist; + mal_uint64 configured_latency; + mal_uint32 base_volume; + mal_pa_source_state_t state; + mal_uint32 n_volume_steps; + mal_uint32 card; + mal_uint32 n_ports; + void** ports; + void* active_port; + mal_uint8 n_formats; + void** formats; +} mal_pa_source_info; + +typedef void (* mal_pa_context_notify_cb_t)(mal_pa_context* c, void* userdata); +typedef void (* mal_pa_sink_info_cb_t) (mal_pa_context* c, const mal_pa_sink_info* i, int eol, void* userdata); +typedef void (* mal_pa_source_info_cb_t) (mal_pa_context* c, const mal_pa_source_info* i, int eol, void* userdata); +typedef void (* mal_pa_stream_success_cb_t)(mal_pa_stream* s, int success, void* userdata); +typedef void (* mal_pa_stream_request_cb_t)(mal_pa_stream* s, size_t nbytes, void* userdata); +typedef void (* mal_pa_free_cb_t) (void* p); +#endif + -typedef int mal_pa_source_flags_t; -#define MAL_PA_SOURCE_NOFLAGS 0x00000000 -#define MAL_PA_SOURCE_HW_VOLUME_CTRL 0x00000001 -#define MAL_PA_SOURCE_LATENCY 0x00000002 -#define MAL_PA_SOURCE_HARDWARE 0x00000004 -#define MAL_PA_SOURCE_NETWORK 0x00000008 -#define MAL_PA_SOURCE_HW_MUTE_CTRL 0x00000010 -#define MAL_PA_SOURCE_DECIBEL_VOLUME 0x00000020 -#define MAL_PA_SOURCE_DYNAMIC_LATENCY 0x00000040 -#define MAL_PA_SOURCE_FLAT_VOLUME 0x00000080 +typedef mal_pa_mainloop* (* mal_pa_mainloop_new_proc) (); +typedef void (* mal_pa_mainloop_free_proc) (mal_pa_mainloop* m); +typedef mal_pa_mainloop_api* (* mal_pa_mainloop_get_api_proc) (mal_pa_mainloop* m); +typedef int (* mal_pa_mainloop_iterate_proc) (mal_pa_mainloop* m, int block, int* retval); +typedef void (* mal_pa_mainloop_wakeup_proc) (mal_pa_mainloop* m); +typedef mal_pa_context* (* mal_pa_context_new_proc) (mal_pa_mainloop_api* mainloop, const char* name); +typedef void (* mal_pa_context_unref_proc) (mal_pa_context* c); +typedef int (* mal_pa_context_connect_proc) (mal_pa_context* c, const char* server, mal_pa_context_flags_t flags, const mal_pa_spawn_api* api); +typedef void (* mal_pa_context_disconnect_proc) (mal_pa_context* c); +typedef void (* mal_pa_context_set_state_callback_proc) (mal_pa_context* c, mal_pa_context_notify_cb_t cb, void* userdata); +typedef mal_pa_context_state_t (* mal_pa_context_get_state_proc) (mal_pa_context* c); +typedef mal_pa_operation* (* mal_pa_context_get_sink_info_list_proc) (mal_pa_context* c, mal_pa_sink_info_cb_t cb, void* userdata); +typedef mal_pa_operation* (* mal_pa_context_get_source_info_list_proc) (mal_pa_context* c, mal_pa_source_info_cb_t cb, void* userdata); +typedef mal_pa_operation* (* mal_pa_context_get_sink_info_by_name_proc) (mal_pa_context* c, const char* name, mal_pa_sink_info_cb_t cb, void* userdata); +typedef mal_pa_operation* (* mal_pa_context_get_source_info_by_name_proc)(mal_pa_context* c, const char* name, mal_pa_source_info_cb_t cb, void* userdata); +typedef void (* mal_pa_operation_unref_proc) (mal_pa_operation* o); +typedef mal_pa_operation_state_t (* mal_pa_operation_get_state_proc) (mal_pa_operation* o); +typedef mal_pa_channel_map* (* mal_pa_channel_map_init_extend_proc) (mal_pa_channel_map* m, unsigned channels, mal_pa_channel_map_def_t def); +typedef int (* mal_pa_channel_map_valid_proc) (const mal_pa_channel_map* m); +typedef int (* mal_pa_channel_map_compatible_proc) (const mal_pa_channel_map* m, const mal_pa_sample_spec* ss); +typedef mal_pa_stream* (* mal_pa_stream_new_proc) (mal_pa_context* c, const char* name, const mal_pa_sample_spec* ss, const mal_pa_channel_map* map); +typedef void (* mal_pa_stream_unref_proc) (mal_pa_stream* s); +typedef int (* mal_pa_stream_connect_playback_proc) (mal_pa_stream* s, const char* dev, const mal_pa_buffer_attr* attr, mal_pa_stream_flags_t flags, const mal_pa_cvolume* volume, mal_pa_stream* sync_stream); +typedef int (* mal_pa_stream_connect_record_proc) (mal_pa_stream* s, const char* dev, const mal_pa_buffer_attr* attr, mal_pa_stream_flags_t flags); +typedef int (* mal_pa_stream_disconnect_proc) (mal_pa_stream* s); +typedef mal_pa_stream_state_t (* mal_pa_stream_get_state_proc) (mal_pa_stream* s); +typedef const mal_pa_sample_spec* (* mal_pa_stream_get_sample_spec_proc) (mal_pa_stream* s); +typedef const mal_pa_channel_map* (* mal_pa_stream_get_channel_map_proc) (mal_pa_stream* s); +typedef const mal_pa_buffer_attr* (* mal_pa_stream_get_buffer_attr_proc) (mal_pa_stream* s); +typedef const char* (* mal_pa_stream_get_device_name_proc) (mal_pa_stream* s); +typedef void (* mal_pa_stream_set_write_callback_proc) (mal_pa_stream* s, mal_pa_stream_request_cb_t cb, void* userdata); +typedef void (* mal_pa_stream_set_read_callback_proc) (mal_pa_stream* s, mal_pa_stream_request_cb_t cb, void* userdata); +typedef mal_pa_operation* (* mal_pa_stream_flush_proc) (mal_pa_stream* s, mal_pa_stream_success_cb_t cb, void* userdata); +typedef mal_pa_operation* (* mal_pa_stream_drain_proc) (mal_pa_stream* s, mal_pa_stream_success_cb_t cb, void* userdata); +typedef mal_pa_operation* (* mal_pa_stream_cork_proc) (mal_pa_stream* s, int b, mal_pa_stream_success_cb_t cb, void* userdata); +typedef mal_pa_operation* (* mal_pa_stream_trigger_proc) (mal_pa_stream* s, mal_pa_stream_success_cb_t cb, void* userdata); +typedef int (* mal_pa_stream_begin_write_proc) (mal_pa_stream* s, void** data, size_t* nbytes); +typedef int (* mal_pa_stream_write_proc) (mal_pa_stream* s, const void* data, size_t nbytes, mal_pa_free_cb_t free_cb, int64_t offset, mal_pa_seek_mode_t seek); +typedef int (* mal_pa_stream_peek_proc) (mal_pa_stream* s, const void** data, size_t* nbytes); +typedef int (* mal_pa_stream_drop_proc) (mal_pa_stream* s); -typedef int mal_pa_context_state_t; -#define MAL_PA_CONTEXT_UNCONNECTED 0 -#define MAL_PA_CONTEXT_CONNECTING 1 -#define MAL_PA_CONTEXT_AUTHORIZING 2 -#define MAL_PA_CONTEXT_SETTING_NAME 3 -#define MAL_PA_CONTEXT_READY 4 -#define MAL_PA_CONTEXT_FAILED 5 -#define MAL_PA_CONTEXT_TERMINATED 6 +typedef struct +{ + mal_uint32 count; + mal_uint32 capacity; + mal_device_info* pInfo; +} mal_pulse_device_enum_data; -typedef int mal_pa_stream_state_t; -#define MAL_PA_STREAM_UNCONNECTED 0 -#define MAL_PA_STREAM_CREATING 1 -#define MAL_PA_STREAM_READY 2 -#define MAL_PA_STREAM_FAILED 3 -#define MAL_PA_STREAM_TERMINATED 4 +mal_result mal_result_from_pulse(int result) +{ + switch (result) { + case MAL_PA_OK: return MAL_SUCCESS; + case MAL_PA_ERR_ACCESS: return MAL_ACCESS_DENIED; + case MAL_PA_ERR_INVALID: return MAL_INVALID_ARGS; + case MAL_PA_ERR_NOENTITY: return MAL_NO_DEVICE; + default: return MAL_ERROR; + } +} -typedef int mal_pa_operation_state_t; -#define MAL_PA_OPERATION_RUNNING 0 -#define MAL_PA_OPERATION_DONE 1 -#define MAL_PA_OPERATION_CANCELLED 2 +#if 0 +mal_pa_sample_format_t mal_format_to_pulse(mal_format format) +{ + if (mal_is_little_endian()) { + switch (format) { + case mal_format_s16: return MAL_PA_SAMPLE_S16LE; + case mal_format_s24: return MAL_PA_SAMPLE_S24LE; + case mal_format_s32: return MAL_PA_SAMPLE_S32LE; + case mal_format_f32: return MAL_PA_SAMPLE_FLOAT32LE; + default: break; + } + } else { + switch (format) { + case mal_format_s16: return MAL_PA_SAMPLE_S16BE; + case mal_format_s24: return MAL_PA_SAMPLE_S24BE; + case mal_format_s32: return MAL_PA_SAMPLE_S32BE; + case mal_format_f32: return MAL_PA_SAMPLE_FLOAT32BE; + default: break; + } + } -typedef int mal_pa_sink_state_t; -#define MAL_PA_SINK_INVALID_STATE -1 -#define MAL_PA_SINK_RUNNING 0 -#define MAL_PA_SINK_IDLE 1 -#define MAL_PA_SINK_SUSPENDED 2 + // Endian agnostic. + switch (format) { + case mal_format_u8: return MAL_PA_SAMPLE_U8; + default: return MAL_PA_SAMPLE_INVALID; + } +} +#endif -typedef int mal_pa_source_state_t; -#define MAL_PA_SOURCE_INVALID_STATE -1 -#define MAL_PA_SOURCE_RUNNING 0 -#define MAL_PA_SOURCE_IDLE 1 -#define MAL_PA_SOURCE_SUSPENDED 2 +mal_format mal_format_from_pulse(mal_pa_sample_format_t format) +{ + if (mal_is_little_endian()) { + switch (format) { + case MAL_PA_SAMPLE_S16LE: return mal_format_s16; + case MAL_PA_SAMPLE_S24LE: return mal_format_s24; + case MAL_PA_SAMPLE_S32LE: return mal_format_s32; + case MAL_PA_SAMPLE_FLOAT32LE: return mal_format_f32; + default: break; + } + } else { + switch (format) { + case MAL_PA_SAMPLE_S16BE: return mal_format_s16; + case MAL_PA_SAMPLE_S24BE: return mal_format_s24; + case MAL_PA_SAMPLE_S32BE: return mal_format_s32; + case MAL_PA_SAMPLE_FLOAT32BE: return mal_format_f32; + default: break; + } + } -typedef int mal_pa_seek_mode_t; -#define MAL_PA_SEEK_RELATIVE 0 -#define MAL_PA_SEEK_ABSOLUTE 1 -#define MAL_PA_SEEK_RELATIVE_ON_READ 2 -#define MAL_PA_SEEK_RELATIVE_END 3 + // Endian agnostic. + switch (format) { + case MAL_PA_SAMPLE_U8: return mal_format_u8; + default: return mal_format_unknown; + } +} -typedef int mal_pa_channel_position_t; -#define MAL_PA_CHANNEL_POSITION_INVALID -1 -#define MAL_PA_CHANNEL_POSITION_MONO 0 -#define MAL_PA_CHANNEL_POSITION_FRONT_LEFT 1 -#define MAL_PA_CHANNEL_POSITION_FRONT_RIGHT 2 -#define MAL_PA_CHANNEL_POSITION_FRONT_CENTER 3 -#define MAL_PA_CHANNEL_POSITION_REAR_CENTER 4 -#define MAL_PA_CHANNEL_POSITION_REAR_LEFT 5 -#define MAL_PA_CHANNEL_POSITION_REAR_RIGHT 6 -#define MAL_PA_CHANNEL_POSITION_LFE 7 -#define MAL_PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER 8 -#define MAL_PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER 9 -#define MAL_PA_CHANNEL_POSITION_SIDE_LEFT 10 -#define MAL_PA_CHANNEL_POSITION_SIDE_RIGHT 11 -#define MAL_PA_CHANNEL_POSITION_AUX0 12 -#define MAL_PA_CHANNEL_POSITION_AUX1 13 -#define MAL_PA_CHANNEL_POSITION_AUX2 14 -#define MAL_PA_CHANNEL_POSITION_AUX3 15 -#define MAL_PA_CHANNEL_POSITION_AUX4 16 -#define MAL_PA_CHANNEL_POSITION_AUX5 17 -#define MAL_PA_CHANNEL_POSITION_AUX6 18 -#define MAL_PA_CHANNEL_POSITION_AUX7 19 -#define MAL_PA_CHANNEL_POSITION_AUX8 20 -#define MAL_PA_CHANNEL_POSITION_AUX9 21 -#define MAL_PA_CHANNEL_POSITION_AUX10 22 -#define MAL_PA_CHANNEL_POSITION_AUX11 23 -#define MAL_PA_CHANNEL_POSITION_AUX12 24 -#define MAL_PA_CHANNEL_POSITION_AUX13 25 -#define MAL_PA_CHANNEL_POSITION_AUX14 26 -#define MAL_PA_CHANNEL_POSITION_AUX15 27 -#define MAL_PA_CHANNEL_POSITION_AUX16 28 -#define MAL_PA_CHANNEL_POSITION_AUX17 29 -#define MAL_PA_CHANNEL_POSITION_AUX18 30 -#define MAL_PA_CHANNEL_POSITION_AUX19 31 -#define MAL_PA_CHANNEL_POSITION_AUX20 32 -#define MAL_PA_CHANNEL_POSITION_AUX21 33 -#define MAL_PA_CHANNEL_POSITION_AUX22 34 -#define MAL_PA_CHANNEL_POSITION_AUX23 35 -#define MAL_PA_CHANNEL_POSITION_AUX24 36 -#define MAL_PA_CHANNEL_POSITION_AUX25 37 -#define MAL_PA_CHANNEL_POSITION_AUX26 38 -#define MAL_PA_CHANNEL_POSITION_AUX27 39 -#define MAL_PA_CHANNEL_POSITION_AUX28 40 -#define MAL_PA_CHANNEL_POSITION_AUX29 41 -#define MAL_PA_CHANNEL_POSITION_AUX30 42 -#define MAL_PA_CHANNEL_POSITION_AUX31 43 -#define MAL_PA_CHANNEL_POSITION_TOP_CENTER 44 -#define MAL_PA_CHANNEL_POSITION_TOP_FRONT_LEFT 45 -#define MAL_PA_CHANNEL_POSITION_TOP_FRONT_RIGHT 46 -#define MAL_PA_CHANNEL_POSITION_TOP_FRONT_CENTER 47 -#define MAL_PA_CHANNEL_POSITION_TOP_REAR_LEFT 48 -#define MAL_PA_CHANNEL_POSITION_TOP_REAR_RIGHT 49 -#define MAL_PA_CHANNEL_POSITION_TOP_REAR_CENTER 50 -#define MAL_PA_CHANNEL_POSITION_LEFT MAL_PA_CHANNEL_POSITION_FRONT_LEFT -#define MAL_PA_CHANNEL_POSITION_RIGHT MAL_PA_CHANNEL_POSITION_FRONT_RIGHT -#define MAL_PA_CHANNEL_POSITION_CENTER MAL_PA_CHANNEL_POSITION_FRONT_CENTER -#define MAL_PA_CHANNEL_POSITION_SUBWOOFER MAL_PA_CHANNEL_POSITION_LFE +mal_channel mal_channel_position_from_pulse(mal_pa_channel_position_t position) +{ + switch (position) + { + case MAL_PA_CHANNEL_POSITION_INVALID: return MAL_CHANNEL_NONE; + case MAL_PA_CHANNEL_POSITION_MONO: return MAL_CHANNEL_MONO; + case MAL_PA_CHANNEL_POSITION_FRONT_LEFT: return MAL_CHANNEL_FRONT_LEFT; + case MAL_PA_CHANNEL_POSITION_FRONT_RIGHT: return MAL_CHANNEL_FRONT_RIGHT; + case MAL_PA_CHANNEL_POSITION_FRONT_CENTER: return MAL_CHANNEL_FRONT_CENTER; + case MAL_PA_CHANNEL_POSITION_REAR_CENTER: return MAL_CHANNEL_BACK_CENTER; + case MAL_PA_CHANNEL_POSITION_REAR_LEFT: return MAL_CHANNEL_BACK_LEFT; + case MAL_PA_CHANNEL_POSITION_REAR_RIGHT: return MAL_CHANNEL_BACK_RIGHT; + case MAL_PA_CHANNEL_POSITION_LFE: return MAL_CHANNEL_LFE; + case MAL_PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: return MAL_CHANNEL_FRONT_LEFT_CENTER; + case MAL_PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: return MAL_CHANNEL_FRONT_RIGHT_CENTER; + case MAL_PA_CHANNEL_POSITION_SIDE_LEFT: return MAL_CHANNEL_SIDE_LEFT; + case MAL_PA_CHANNEL_POSITION_SIDE_RIGHT: return MAL_CHANNEL_SIDE_RIGHT; + case MAL_PA_CHANNEL_POSITION_AUX0: return MAL_CHANNEL_AUX_0; + case MAL_PA_CHANNEL_POSITION_AUX1: return MAL_CHANNEL_AUX_1; + case MAL_PA_CHANNEL_POSITION_AUX2: return MAL_CHANNEL_AUX_2; + case MAL_PA_CHANNEL_POSITION_AUX3: return MAL_CHANNEL_AUX_3; + case MAL_PA_CHANNEL_POSITION_AUX4: return MAL_CHANNEL_AUX_4; + case MAL_PA_CHANNEL_POSITION_AUX5: return MAL_CHANNEL_AUX_5; + case MAL_PA_CHANNEL_POSITION_AUX6: return MAL_CHANNEL_AUX_6; + case MAL_PA_CHANNEL_POSITION_AUX7: return MAL_CHANNEL_AUX_7; + case MAL_PA_CHANNEL_POSITION_AUX8: return MAL_CHANNEL_AUX_8; + case MAL_PA_CHANNEL_POSITION_AUX9: return MAL_CHANNEL_AUX_9; + case MAL_PA_CHANNEL_POSITION_AUX10: return MAL_CHANNEL_AUX_10; + case MAL_PA_CHANNEL_POSITION_AUX11: return MAL_CHANNEL_AUX_11; + case MAL_PA_CHANNEL_POSITION_AUX12: return MAL_CHANNEL_AUX_12; + case MAL_PA_CHANNEL_POSITION_AUX13: return MAL_CHANNEL_AUX_13; + case MAL_PA_CHANNEL_POSITION_AUX14: return MAL_CHANNEL_AUX_14; + case MAL_PA_CHANNEL_POSITION_AUX15: return MAL_CHANNEL_AUX_15; + case MAL_PA_CHANNEL_POSITION_AUX16: return MAL_CHANNEL_AUX_16; + case MAL_PA_CHANNEL_POSITION_AUX17: return MAL_CHANNEL_AUX_17; + case MAL_PA_CHANNEL_POSITION_AUX18: return MAL_CHANNEL_AUX_18; + case MAL_PA_CHANNEL_POSITION_AUX19: return MAL_CHANNEL_AUX_19; + case MAL_PA_CHANNEL_POSITION_AUX20: return MAL_CHANNEL_AUX_20; + case MAL_PA_CHANNEL_POSITION_AUX21: return MAL_CHANNEL_AUX_21; + case MAL_PA_CHANNEL_POSITION_AUX22: return MAL_CHANNEL_AUX_22; + case MAL_PA_CHANNEL_POSITION_AUX23: return MAL_CHANNEL_AUX_23; + case MAL_PA_CHANNEL_POSITION_AUX24: return MAL_CHANNEL_AUX_24; + case MAL_PA_CHANNEL_POSITION_AUX25: return MAL_CHANNEL_AUX_25; + case MAL_PA_CHANNEL_POSITION_AUX26: return MAL_CHANNEL_AUX_26; + case MAL_PA_CHANNEL_POSITION_AUX27: return MAL_CHANNEL_AUX_27; + case MAL_PA_CHANNEL_POSITION_AUX28: return MAL_CHANNEL_AUX_28; + case MAL_PA_CHANNEL_POSITION_AUX29: return MAL_CHANNEL_AUX_29; + case MAL_PA_CHANNEL_POSITION_AUX30: return MAL_CHANNEL_AUX_30; + case MAL_PA_CHANNEL_POSITION_AUX31: return MAL_CHANNEL_AUX_31; + case MAL_PA_CHANNEL_POSITION_TOP_CENTER: return MAL_CHANNEL_TOP_CENTER; + case MAL_PA_CHANNEL_POSITION_TOP_FRONT_LEFT: return MAL_CHANNEL_TOP_FRONT_LEFT; + case MAL_PA_CHANNEL_POSITION_TOP_FRONT_RIGHT: return MAL_CHANNEL_TOP_FRONT_RIGHT; + case MAL_PA_CHANNEL_POSITION_TOP_FRONT_CENTER: return MAL_CHANNEL_TOP_FRONT_CENTER; + case MAL_PA_CHANNEL_POSITION_TOP_REAR_LEFT: return MAL_CHANNEL_TOP_BACK_LEFT; + case MAL_PA_CHANNEL_POSITION_TOP_REAR_RIGHT: return MAL_CHANNEL_TOP_BACK_RIGHT; + case MAL_PA_CHANNEL_POSITION_TOP_REAR_CENTER: return MAL_CHANNEL_TOP_BACK_CENTER; + default: return MAL_CHANNEL_NONE; + } +} + +#if 0 +mal_pa_channel_position_t mal_channel_position_to_pulse(mal_channel position) +{ + switch (position) + { + case MAL_CHANNEL_NONE: return MAL_PA_CHANNEL_POSITION_INVALID; + case MAL_CHANNEL_FRONT_LEFT: return MAL_PA_CHANNEL_POSITION_FRONT_LEFT; + case MAL_CHANNEL_FRONT_RIGHT: return MAL_PA_CHANNEL_POSITION_FRONT_RIGHT; + case MAL_CHANNEL_FRONT_CENTER: return MAL_PA_CHANNEL_POSITION_FRONT_CENTER; + case MAL_CHANNEL_LFE: return MAL_PA_CHANNEL_POSITION_LFE; + case MAL_CHANNEL_BACK_LEFT: return MAL_PA_CHANNEL_POSITION_REAR_LEFT; + case MAL_CHANNEL_BACK_RIGHT: return MAL_PA_CHANNEL_POSITION_REAR_RIGHT; + case MAL_CHANNEL_FRONT_LEFT_CENTER: return MAL_PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER; + case MAL_CHANNEL_FRONT_RIGHT_CENTER: return MAL_PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER; + case MAL_CHANNEL_BACK_CENTER: return MAL_PA_CHANNEL_POSITION_REAR_CENTER; + case MAL_CHANNEL_SIDE_LEFT: return MAL_PA_CHANNEL_POSITION_SIDE_LEFT; + case MAL_CHANNEL_SIDE_RIGHT: return MAL_PA_CHANNEL_POSITION_SIDE_RIGHT; + case MAL_CHANNEL_TOP_CENTER: return MAL_PA_CHANNEL_POSITION_TOP_CENTER; + case MAL_CHANNEL_TOP_FRONT_LEFT: return MAL_PA_CHANNEL_POSITION_TOP_FRONT_LEFT; + case MAL_CHANNEL_TOP_FRONT_CENTER: return MAL_PA_CHANNEL_POSITION_TOP_FRONT_CENTER; + case MAL_CHANNEL_TOP_FRONT_RIGHT: return MAL_PA_CHANNEL_POSITION_TOP_FRONT_RIGHT; + case MAL_CHANNEL_TOP_BACK_LEFT: return MAL_PA_CHANNEL_POSITION_TOP_REAR_LEFT; + case MAL_CHANNEL_TOP_BACK_CENTER: return MAL_PA_CHANNEL_POSITION_TOP_REAR_CENTER; + case MAL_CHANNEL_TOP_BACK_RIGHT: return MAL_PA_CHANNEL_POSITION_TOP_REAR_RIGHT; + case MAL_CHANNEL_19: return MAL_PA_CHANNEL_POSITION_AUX18; + case MAL_CHANNEL_20: return MAL_PA_CHANNEL_POSITION_AUX19; + case MAL_CHANNEL_21: return MAL_PA_CHANNEL_POSITION_AUX20; + case MAL_CHANNEL_22: return MAL_PA_CHANNEL_POSITION_AUX21; + case MAL_CHANNEL_23: return MAL_PA_CHANNEL_POSITION_AUX22; + case MAL_CHANNEL_24: return MAL_PA_CHANNEL_POSITION_AUX23; + case MAL_CHANNEL_25: return MAL_PA_CHANNEL_POSITION_AUX24; + case MAL_CHANNEL_26: return MAL_PA_CHANNEL_POSITION_AUX25; + case MAL_CHANNEL_27: return MAL_PA_CHANNEL_POSITION_AUX26; + case MAL_CHANNEL_28: return MAL_PA_CHANNEL_POSITION_AUX27; + case MAL_CHANNEL_29: return MAL_PA_CHANNEL_POSITION_AUX28; + case MAL_CHANNEL_30: return MAL_PA_CHANNEL_POSITION_AUX29; + case MAL_CHANNEL_31: return MAL_PA_CHANNEL_POSITION_AUX30; + case MAL_CHANNEL_32: return MAL_PA_CHANNEL_POSITION_AUX31; + default: return (mal_pa_channel_position_t)position; + } +} +#endif -typedef int mal_pa_channel_map_def_t; -#define MAL_PA_CHANNEL_MAP_AIFF 0 -#define MAL_PA_CHANNEL_MAP_ALSA 1 -#define MAL_PA_CHANNEL_MAP_AUX 2 -#define MAL_PA_CHANNEL_MAP_WAVEEX 3 -#define MAL_PA_CHANNEL_MAP_OSS 4 -#define MAL_PA_CHANNEL_MAP_DEFAULT MAL_PA_CHANNEL_MAP_AIFF -typedef int mal_pa_sample_format_t; -#define MAL_PA_SAMPLE_INVALID -1 -#define MAL_PA_SAMPLE_U8 0 -#define MAL_PA_SAMPLE_ALAW 1 -#define MAL_PA_SAMPLE_ULAW 2 -#define MAL_PA_SAMPLE_S16LE 3 -#define MAL_PA_SAMPLE_S16BE 4 -#define MAL_PA_SAMPLE_FLOAT32LE 5 -#define MAL_PA_SAMPLE_FLOAT32BE 6 -#define MAL_PA_SAMPLE_S32LE 7 -#define MAL_PA_SAMPLE_S32BE 8 -#define MAL_PA_SAMPLE_S24LE 9 -#define MAL_PA_SAMPLE_S24BE 10 -#define MAL_PA_SAMPLE_S24_32LE 11 -#define MAL_PA_SAMPLE_S24_32BE 12 +mal_result mal_wait_for_operation__pulse(mal_context* pContext, mal_pa_mainloop* pMainLoop, mal_pa_operation* pOP) +{ + mal_assert(pContext != NULL); + mal_assert(pMainLoop != NULL); + mal_assert(pOP != NULL); -typedef struct mal_pa_mainloop mal_pa_mainloop; -typedef struct mal_pa_mainloop_api mal_pa_mainloop_api; -typedef struct mal_pa_context mal_pa_context; -typedef struct mal_pa_operation mal_pa_operation; -typedef struct mal_pa_stream mal_pa_stream; -typedef struct mal_pa_spawn_api mal_pa_spawn_api; + while (((mal_pa_operation_get_state_proc)pContext->pulse.pa_operation_get_state)(pOP) != MAL_PA_OPERATION_DONE) { + int error = ((mal_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)(pMainLoop, 1, NULL); + if (error < 0) { + return mal_result_from_pulse(error); + } + } -typedef struct + return MAL_SUCCESS; +} + +mal_result mal_device__wait_for_operation__pulse(mal_device* pDevice, mal_pa_operation* pOP) { - mal_uint32 maxlength; - mal_uint32 tlength; - mal_uint32 prebuf; - mal_uint32 minreq; - mal_uint32 fragsize; -} mal_pa_buffer_attr; + mal_assert(pDevice != NULL); + mal_assert(pOP != NULL); -typedef struct + return mal_wait_for_operation__pulse(pDevice->pContext, (mal_pa_mainloop*)pDevice->pulse.pMainLoop, pOP); +} + + +mal_bool32 mal_context_is_device_id_equal__pulse(mal_context* pContext, const mal_device_id* pID0, const mal_device_id* pID1) { - mal_uint8 channels; - mal_pa_channel_position_t map[MAL_PA_CHANNELS_MAX]; -} mal_pa_channel_map; + mal_assert(pContext != NULL); + mal_assert(pID0 != NULL); + mal_assert(pID1 != NULL); + (void)pContext; + + return mal_strcmp(pID0->pulse, pID1->pulse) == 0; +} + typedef struct { - mal_uint8 channels; - mal_uint32 values[MAL_PA_CHANNELS_MAX]; -} mal_pa_cvolume; + mal_context* pContext; + mal_enum_devices_callback_proc callback; + void* pUserData; + mal_bool32 isTerminated; +} mal_context_enumerate_devices_callback_data__pulse; -typedef struct +void mal_context_enumerate_devices_sink_callback__pulse(mal_pa_context* pPulseContext, const mal_pa_sink_info* pSinkInfo, int endOfList, void* pUserData) { - mal_pa_sample_format_t format; - mal_uint32 rate; - mal_uint8 channels; -} mal_pa_sample_spec; + mal_context_enumerate_devices_callback_data__pulse* pData = (mal_context_enumerate_devices_callback_data__pulse*)pUserData; + mal_assert(pData != NULL); -typedef struct + if (endOfList || pData->isTerminated) { + return; + } + + mal_device_info deviceInfo; + mal_zero_object(&deviceInfo); + + // The name from PulseAudio is the ID for mini_al. + if (pSinkInfo->name != NULL) { + mal_strncpy_s(deviceInfo.id.pulse, sizeof(deviceInfo.id.pulse), pSinkInfo->name, (size_t)-1); + } + + // The description from PulseAudio is the name for mini_al. + if (pSinkInfo->description != NULL) { + mal_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), pSinkInfo->description, (size_t)-1); + } + + pData->isTerminated = !pData->callback(pData->pContext, mal_device_type_playback, &deviceInfo, pData->pUserData); +} + +void mal_context_enumerate_devices_source_callback__pulse(mal_pa_context* pPulseContext, const mal_pa_source_info* pSinkInfo, int endOfList, void* pUserData) { - const char* name; - mal_uint32 index; - const char* description; - mal_pa_sample_spec sample_spec; - mal_pa_channel_map channel_map; - mal_uint32 owner_module; - mal_pa_cvolume volume; - int mute; - mal_uint32 monitor_source; - const char* monitor_source_name; - mal_uint64 latency; - const char* driver; - mal_pa_sink_flags_t flags; - void* proplist; - mal_uint64 configured_latency; - mal_uint32 base_volume; - mal_pa_sink_state_t state; - mal_uint32 n_volume_steps; - mal_uint32 card; - mal_uint32 n_ports; - void** ports; - void* active_port; - mal_uint8 n_formats; - void** formats; -} mal_pa_sink_info; + mal_context_enumerate_devices_callback_data__pulse* pData = (mal_context_enumerate_devices_callback_data__pulse*)pUserData; + mal_assert(pData != NULL); -typedef struct + if (endOfList || pData->isTerminated) { + return; + } + + mal_device_info deviceInfo; + mal_zero_object(&deviceInfo); + + // The name from PulseAudio is the ID for mini_al. + if (pSinkInfo->name != NULL) { + mal_strncpy_s(deviceInfo.id.pulse, sizeof(deviceInfo.id.pulse), pSinkInfo->name, (size_t)-1); + } + + // The description from PulseAudio is the name for mini_al. + if (pSinkInfo->description != NULL) { + mal_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), pSinkInfo->description, (size_t)-1); + } + + pData->isTerminated = !pData->callback(pData->pContext, mal_device_type_capture, &deviceInfo, pData->pUserData); +} + +mal_result mal_context_enumerate_devices__pulse(mal_context* pContext, mal_enum_devices_callback_proc callback, void* pUserData) { - const char *name; - mal_uint32 index; - const char *description; - mal_pa_sample_spec sample_spec; - mal_pa_channel_map channel_map; - mal_uint32 owner_module; - mal_pa_cvolume volume; - int mute; - mal_uint32 monitor_of_sink; - const char *monitor_of_sink_name; - mal_uint64 latency; - const char *driver; - mal_pa_source_flags_t flags; - void* proplist; - mal_uint64 configured_latency; - mal_uint32 base_volume; - mal_pa_source_state_t state; - mal_uint32 n_volume_steps; - mal_uint32 card; - mal_uint32 n_ports; - void** ports; - void* active_port; - mal_uint8 n_formats; - void** formats; -} mal_pa_source_info; + mal_assert(pContext != NULL); + mal_assert(callback != NULL); + + mal_result result = MAL_SUCCESS; + + mal_context_enumerate_devices_callback_data__pulse callbackData; + callbackData.pContext = pContext; + callbackData.callback = callback; + callbackData.pUserData = pUserData; + callbackData.isTerminated = MAL_FALSE; + + mal_pa_operation* pOP = NULL; + + mal_pa_mainloop* pMainLoop = ((mal_pa_mainloop_new_proc)pContext->pulse.pa_mainloop_new)(); + if (pMainLoop == NULL) { + return MAL_FAILED_TO_INIT_BACKEND; + } + + mal_pa_mainloop_api* pAPI = ((mal_pa_mainloop_get_api_proc)pContext->pulse.pa_mainloop_get_api)(pMainLoop); + if (pAPI == NULL) { + ((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); + return MAL_FAILED_TO_INIT_BACKEND; + } + + mal_pa_context* pPulseContext = ((mal_pa_context_new_proc)pContext->pulse.pa_context_new)(pAPI, pContext->config.pulse.pApplicationName); + if (pPulseContext == NULL) { + ((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); + return MAL_FAILED_TO_INIT_BACKEND; + } + + int error = ((mal_pa_context_connect_proc)pContext->pulse.pa_context_connect)(pPulseContext, pContext->config.pulse.pServerName, 0, NULL); + if (error != MAL_PA_OK) { + ((mal_pa_context_unref_proc)pContext->pulse.pa_context_unref)(pPulseContext); + ((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); + return mal_result_from_pulse(error); + } + + while (((mal_pa_context_get_state_proc)pContext->pulse.pa_context_get_state)(pPulseContext) != MAL_PA_CONTEXT_READY) { + error = ((mal_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)(pMainLoop, 1, NULL); + if (error < 0) { + result = mal_result_from_pulse(error); + goto done; + } + } -typedef void (* mal_pa_context_notify_cb_t)(mal_pa_context* c, void* userdata); -typedef void (* mal_pa_sink_info_cb_t) (mal_pa_context* c, const mal_pa_sink_info* i, int eol, void* userdata); -typedef void (* mal_pa_source_info_cb_t) (mal_pa_context* c, const mal_pa_source_info* i, int eol, void* userdata); -typedef void (* mal_pa_stream_success_cb_t)(mal_pa_stream* s, int success, void* userdata); -typedef void (* mal_pa_stream_request_cb_t)(mal_pa_stream* s, size_t nbytes, void* userdata); -typedef void (* mal_pa_free_cb_t) (void* p); -#endif + // Playback. + if (!callbackData.isTerminated) { + pOP = ((mal_pa_context_get_sink_info_list_proc)pContext->pulse.pa_context_get_sink_info_list)(pPulseContext, mal_context_enumerate_devices_sink_callback__pulse, &callbackData); + if (pOP == NULL) { + result = MAL_ERROR; + goto done; + } + + result = mal_wait_for_operation__pulse(pContext, pMainLoop, pOP); + ((mal_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); + if (result != MAL_SUCCESS) { + goto done; + } + } + + + // Capture. + if (!callbackData.isTerminated) { + pOP = ((mal_pa_context_get_source_info_list_proc)pContext->pulse.pa_context_get_source_info_list)(pPulseContext, mal_context_enumerate_devices_source_callback__pulse, &callbackData); + if (pOP == NULL) { + result = MAL_ERROR; + goto done; + } + + result = mal_wait_for_operation__pulse(pContext, pMainLoop, pOP); + ((mal_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); + if (result != MAL_SUCCESS) { + goto done; + } + } + +done: + ((mal_pa_context_disconnect_proc)pContext->pulse.pa_context_disconnect)(pPulseContext); + ((mal_pa_context_unref_proc)pContext->pulse.pa_context_unref)(pPulseContext); + ((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); + return result; +} -typedef mal_pa_mainloop* (* mal_pa_mainloop_new_proc) (); -typedef void (* mal_pa_mainloop_free_proc) (mal_pa_mainloop* m); -typedef mal_pa_mainloop_api* (* mal_pa_mainloop_get_api_proc) (mal_pa_mainloop* m); -typedef int (* mal_pa_mainloop_iterate_proc) (mal_pa_mainloop* m, int block, int* retval); -typedef void (* mal_pa_mainloop_wakeup_proc) (mal_pa_mainloop* m); -typedef mal_pa_context* (* mal_pa_context_new_proc) (mal_pa_mainloop_api* mainloop, const char* name); -typedef void (* mal_pa_context_unref_proc) (mal_pa_context* c); -typedef int (* mal_pa_context_connect_proc) (mal_pa_context* c, const char* server, mal_pa_context_flags_t flags, const mal_pa_spawn_api* api); -typedef void (* mal_pa_context_disconnect_proc) (mal_pa_context* c); -typedef void (* mal_pa_context_set_state_callback_proc) (mal_pa_context* c, mal_pa_context_notify_cb_t cb, void* userdata); -typedef mal_pa_context_state_t (* mal_pa_context_get_state_proc) (mal_pa_context* c); -typedef mal_pa_operation* (* mal_pa_context_get_sink_info_list_proc) (mal_pa_context* c, mal_pa_sink_info_cb_t cb, void* userdata); -typedef mal_pa_operation* (* mal_pa_context_get_source_info_list_proc) (mal_pa_context* c, mal_pa_source_info_cb_t cb, void* userdata); -typedef mal_pa_operation* (* mal_pa_context_get_sink_info_by_name_proc) (mal_pa_context* c, const char* name, mal_pa_sink_info_cb_t cb, void* userdata); -typedef mal_pa_operation* (* mal_pa_context_get_source_info_by_name_proc)(mal_pa_context* c, const char* name, mal_pa_source_info_cb_t cb, void* userdata); -typedef void (* mal_pa_operation_unref_proc) (mal_pa_operation* o); -typedef mal_pa_operation_state_t (* mal_pa_operation_get_state_proc) (mal_pa_operation* o); -typedef mal_pa_channel_map* (* mal_pa_channel_map_init_extend_proc) (mal_pa_channel_map* m, unsigned channels, mal_pa_channel_map_def_t def); -typedef int (* mal_pa_channel_map_valid_proc) (const mal_pa_channel_map* m); -typedef int (* mal_pa_channel_map_compatible_proc) (const mal_pa_channel_map* m, const mal_pa_sample_spec* ss); -typedef mal_pa_stream* (* mal_pa_stream_new_proc) (mal_pa_context* c, const char* name, const mal_pa_sample_spec* ss, const mal_pa_channel_map* map); -typedef void (* mal_pa_stream_unref_proc) (mal_pa_stream* s); -typedef int (* mal_pa_stream_connect_playback_proc) (mal_pa_stream* s, const char* dev, const mal_pa_buffer_attr* attr, mal_pa_stream_flags_t flags, const mal_pa_cvolume* volume, mal_pa_stream* sync_stream); -typedef int (* mal_pa_stream_connect_record_proc) (mal_pa_stream* s, const char* dev, const mal_pa_buffer_attr* attr, mal_pa_stream_flags_t flags); -typedef int (* mal_pa_stream_disconnect_proc) (mal_pa_stream* s); -typedef mal_pa_stream_state_t (* mal_pa_stream_get_state_proc) (mal_pa_stream* s); -typedef const mal_pa_sample_spec* (* mal_pa_stream_get_sample_spec_proc) (mal_pa_stream* s); -typedef const mal_pa_channel_map* (* mal_pa_stream_get_channel_map_proc) (mal_pa_stream* s); -typedef const mal_pa_buffer_attr* (* mal_pa_stream_get_buffer_attr_proc) (mal_pa_stream* s); -typedef const char* (* mal_pa_stream_get_device_name_proc) (mal_pa_stream* s); -typedef void (* mal_pa_stream_set_write_callback_proc) (mal_pa_stream* s, mal_pa_stream_request_cb_t cb, void* userdata); -typedef void (* mal_pa_stream_set_read_callback_proc) (mal_pa_stream* s, mal_pa_stream_request_cb_t cb, void* userdata); -typedef mal_pa_operation* (* mal_pa_stream_flush_proc) (mal_pa_stream* s, mal_pa_stream_success_cb_t cb, void* userdata); -typedef mal_pa_operation* (* mal_pa_stream_drain_proc) (mal_pa_stream* s, mal_pa_stream_success_cb_t cb, void* userdata); -typedef mal_pa_operation* (* mal_pa_stream_cork_proc) (mal_pa_stream* s, int b, mal_pa_stream_success_cb_t cb, void* userdata); -typedef mal_pa_operation* (* mal_pa_stream_trigger_proc) (mal_pa_stream* s, mal_pa_stream_success_cb_t cb, void* userdata); -typedef int (* mal_pa_stream_begin_write_proc) (mal_pa_stream* s, void** data, size_t* nbytes); -typedef int (* mal_pa_stream_write_proc) (mal_pa_stream* s, const void* data, size_t nbytes, mal_pa_free_cb_t free_cb, int64_t offset, mal_pa_seek_mode_t seek); -typedef int (* mal_pa_stream_peek_proc) (mal_pa_stream* s, const void** data, size_t* nbytes); -typedef int (* mal_pa_stream_drop_proc) (mal_pa_stream* s); typedef struct { - mal_uint32 count; - mal_uint32 capacity; - mal_device_info* pInfo; -} mal_pulse_device_enum_data; + mal_device_info* pDeviceInfo; + mal_bool32 foundDevice; +} mal_context_get_device_info_callback_data__pulse; -mal_result mal_result_from_pulse(int result) +void mal_context_get_device_info_sink_callback__pulse(mal_pa_context* pPulseContext, const mal_pa_sink_info* pInfo, int endOfList, void* pUserData) { - switch (result) { - case MAL_PA_OK: return MAL_SUCCESS; - case MAL_PA_ERR_ACCESS: return MAL_ACCESS_DENIED; - case MAL_PA_ERR_INVALID: return MAL_INVALID_ARGS; - case MAL_PA_ERR_NOENTITY: return MAL_NO_DEVICE; - default: return MAL_ERROR; + if (endOfList > 0) { + return; } -} -#if 0 -mal_pa_sample_format_t mal_format_to_pulse(mal_format format) -{ - switch (format) - { - case mal_format_u8: return MAL_PA_SAMPLE_U8; - case mal_format_s16: return MAL_PA_SAMPLE_S16LE; - //case mal_format_s16be: return MAL_PA_SAMPLE_S16BE; - case mal_format_s24: return MAL_PA_SAMPLE_S24LE; - //case mal_format_s24be: return MAL_PA_SAMPLE_S24BE; - //case mal_format_s24_32: return MAL_PA_SAMPLE_S24_32LE; - //case mal_format_s24_32be: return MAL_PA_SAMPLE_S24_32BE; - case mal_format_s32: return MAL_PA_SAMPLE_S32LE; - //case mal_format_s32be: return MAL_PA_SAMPLE_S32BE; - case mal_format_f32: return MAL_PA_SAMPLE_FLOAT32LE; - //case mal_format_f32be: return PA_SAMPLE_FLOAT32BE; + mal_context_get_device_info_callback_data__pulse* pData = (mal_context_get_device_info_callback_data__pulse*)pUserData; + mal_assert(pData != NULL); + pData->foundDevice = MAL_TRUE; - default: return MAL_PA_SAMPLE_INVALID; + if (pInfo->name != NULL) { + mal_strncpy_s(pData->pDeviceInfo->id.pulse, sizeof(pData->pDeviceInfo->id.pulse), pInfo->name, (size_t)-1); + } + + if (pInfo->description != NULL) { + mal_strncpy_s(pData->pDeviceInfo->name, sizeof(pData->pDeviceInfo->name), pInfo->description, (size_t)-1); } + + pData->pDeviceInfo->minChannels = pInfo->sample_spec.channels; + pData->pDeviceInfo->maxChannels = pInfo->sample_spec.channels; + pData->pDeviceInfo->minSampleRate = pInfo->sample_spec.rate; + pData->pDeviceInfo->maxSampleRate = pInfo->sample_spec.rate; + pData->pDeviceInfo->formatCount = 1; + pData->pDeviceInfo->formats[0] = mal_format_from_pulse(pInfo->sample_spec.format); } -#endif -mal_format mal_format_from_pulse(mal_pa_sample_format_t format) +void mal_context_get_device_info_source_callback__pulse(mal_pa_context* pPulseContext, const mal_pa_source_info* pInfo, int endOfList, void* pUserData) { - switch (format) - { - case MAL_PA_SAMPLE_U8: return mal_format_u8; - case MAL_PA_SAMPLE_S16LE: return mal_format_s16; - //case MAL_PA_SAMPLE_S16BE: return mal_format_s16be; - case MAL_PA_SAMPLE_S24LE: return mal_format_s24; - //case MAL_PA_SAMPLE_S24BE: return mal_format_s24be; - //case MAL_PA_SAMPLE_S24_32LE: return mal_format_s24_32; - //case MAL_PA_SAMPLE_S24_32BE: return mal_format_s24_32be; - case MAL_PA_SAMPLE_S32LE: return mal_format_s32; - //case MAL_PA_SAMPLE_S32BE: return mal_format_s32be; - case MAL_PA_SAMPLE_FLOAT32LE: return mal_format_f32; - //case MAL_PA_SAMPLE_FLOAT32BE: return mal_format_f32be; + if (endOfList > 0) { + return; + } - default: return mal_format_unknown; + mal_context_get_device_info_callback_data__pulse* pData = (mal_context_get_device_info_callback_data__pulse*)pUserData; + mal_assert(pData != NULL); + pData->foundDevice = MAL_TRUE; + + if (pInfo->name != NULL) { + mal_strncpy_s(pData->pDeviceInfo->id.pulse, sizeof(pData->pDeviceInfo->id.pulse), pInfo->name, (size_t)-1); + } + + if (pInfo->description != NULL) { + mal_strncpy_s(pData->pDeviceInfo->name, sizeof(pData->pDeviceInfo->name), pInfo->description, (size_t)-1); } + + pData->pDeviceInfo->minChannels = pInfo->sample_spec.channels; + pData->pDeviceInfo->maxChannels = pInfo->sample_spec.channels; + pData->pDeviceInfo->minSampleRate = pInfo->sample_spec.rate; + pData->pDeviceInfo->maxSampleRate = pInfo->sample_spec.rate; + pData->pDeviceInfo->formatCount = 1; + pData->pDeviceInfo->formats[0] = mal_format_from_pulse(pInfo->sample_spec.format); } -mal_channel mal_channel_position_from_pulse(mal_pa_channel_position_t position) +mal_result mal_context_get_device_info__pulse(mal_context* pContext, mal_device_type deviceType, const mal_device_id* pDeviceID, mal_share_mode shareMode, mal_device_info* pDeviceInfo) { - switch (position) - { - case MAL_PA_CHANNEL_POSITION_INVALID: return MAL_CHANNEL_NONE; - case MAL_PA_CHANNEL_POSITION_MONO: return MAL_CHANNEL_MONO; - case MAL_PA_CHANNEL_POSITION_FRONT_LEFT: return MAL_CHANNEL_FRONT_LEFT; - case MAL_PA_CHANNEL_POSITION_FRONT_RIGHT: return MAL_CHANNEL_FRONT_RIGHT; - case MAL_PA_CHANNEL_POSITION_FRONT_CENTER: return MAL_CHANNEL_FRONT_CENTER; - case MAL_PA_CHANNEL_POSITION_REAR_CENTER: return MAL_CHANNEL_BACK_CENTER; - case MAL_PA_CHANNEL_POSITION_REAR_LEFT: return MAL_CHANNEL_BACK_LEFT; - case MAL_PA_CHANNEL_POSITION_REAR_RIGHT: return MAL_CHANNEL_BACK_RIGHT; - case MAL_PA_CHANNEL_POSITION_LFE: return MAL_CHANNEL_LFE; - case MAL_PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: return MAL_CHANNEL_FRONT_LEFT_CENTER; - case MAL_PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: return MAL_CHANNEL_FRONT_RIGHT_CENTER; - case MAL_PA_CHANNEL_POSITION_SIDE_LEFT: return MAL_CHANNEL_SIDE_LEFT; - case MAL_PA_CHANNEL_POSITION_SIDE_RIGHT: return MAL_CHANNEL_SIDE_RIGHT; - case MAL_PA_CHANNEL_POSITION_AUX0: return MAL_CHANNEL_AUX_0; - case MAL_PA_CHANNEL_POSITION_AUX1: return MAL_CHANNEL_AUX_1; - case MAL_PA_CHANNEL_POSITION_AUX2: return MAL_CHANNEL_AUX_2; - case MAL_PA_CHANNEL_POSITION_AUX3: return MAL_CHANNEL_AUX_3; - case MAL_PA_CHANNEL_POSITION_AUX4: return MAL_CHANNEL_AUX_4; - case MAL_PA_CHANNEL_POSITION_AUX5: return MAL_CHANNEL_AUX_5; - case MAL_PA_CHANNEL_POSITION_AUX6: return MAL_CHANNEL_AUX_6; - case MAL_PA_CHANNEL_POSITION_AUX7: return MAL_CHANNEL_AUX_7; - case MAL_PA_CHANNEL_POSITION_AUX8: return MAL_CHANNEL_AUX_8; - case MAL_PA_CHANNEL_POSITION_AUX9: return MAL_CHANNEL_AUX_9; - case MAL_PA_CHANNEL_POSITION_AUX10: return MAL_CHANNEL_AUX_10; - case MAL_PA_CHANNEL_POSITION_AUX11: return MAL_CHANNEL_AUX_11; - case MAL_PA_CHANNEL_POSITION_AUX12: return MAL_CHANNEL_AUX_12; - case MAL_PA_CHANNEL_POSITION_AUX13: return MAL_CHANNEL_AUX_13; - case MAL_PA_CHANNEL_POSITION_AUX14: return MAL_CHANNEL_AUX_14; - case MAL_PA_CHANNEL_POSITION_AUX15: return MAL_CHANNEL_AUX_15; - case MAL_PA_CHANNEL_POSITION_AUX16: return MAL_CHANNEL_AUX_16; - case MAL_PA_CHANNEL_POSITION_AUX17: return MAL_CHANNEL_AUX_17; - case MAL_PA_CHANNEL_POSITION_AUX18: return MAL_CHANNEL_AUX_18; - case MAL_PA_CHANNEL_POSITION_AUX19: return MAL_CHANNEL_AUX_19; - case MAL_PA_CHANNEL_POSITION_AUX20: return MAL_CHANNEL_AUX_20; - case MAL_PA_CHANNEL_POSITION_AUX21: return MAL_CHANNEL_AUX_21; - case MAL_PA_CHANNEL_POSITION_AUX22: return MAL_CHANNEL_AUX_22; - case MAL_PA_CHANNEL_POSITION_AUX23: return MAL_CHANNEL_AUX_23; - case MAL_PA_CHANNEL_POSITION_AUX24: return MAL_CHANNEL_AUX_24; - case MAL_PA_CHANNEL_POSITION_AUX25: return MAL_CHANNEL_AUX_25; - case MAL_PA_CHANNEL_POSITION_AUX26: return MAL_CHANNEL_AUX_26; - case MAL_PA_CHANNEL_POSITION_AUX27: return MAL_CHANNEL_AUX_27; - case MAL_PA_CHANNEL_POSITION_AUX28: return MAL_CHANNEL_AUX_28; - case MAL_PA_CHANNEL_POSITION_AUX29: return MAL_CHANNEL_AUX_29; - case MAL_PA_CHANNEL_POSITION_AUX30: return MAL_CHANNEL_AUX_30; - case MAL_PA_CHANNEL_POSITION_AUX31: return MAL_CHANNEL_AUX_31; - case MAL_PA_CHANNEL_POSITION_TOP_CENTER: return MAL_CHANNEL_TOP_CENTER; - case MAL_PA_CHANNEL_POSITION_TOP_FRONT_LEFT: return MAL_CHANNEL_TOP_FRONT_LEFT; - case MAL_PA_CHANNEL_POSITION_TOP_FRONT_RIGHT: return MAL_CHANNEL_TOP_FRONT_RIGHT; - case MAL_PA_CHANNEL_POSITION_TOP_FRONT_CENTER: return MAL_CHANNEL_TOP_FRONT_CENTER; - case MAL_PA_CHANNEL_POSITION_TOP_REAR_LEFT: return MAL_CHANNEL_TOP_BACK_LEFT; - case MAL_PA_CHANNEL_POSITION_TOP_REAR_RIGHT: return MAL_CHANNEL_TOP_BACK_RIGHT; - case MAL_PA_CHANNEL_POSITION_TOP_REAR_CENTER: return MAL_CHANNEL_TOP_BACK_CENTER; - default: return MAL_CHANNEL_NONE; + mal_assert(pContext != NULL); + (void)shareMode; + + mal_result result = MAL_SUCCESS; + + mal_context_get_device_info_callback_data__pulse callbackData; + callbackData.pDeviceInfo = pDeviceInfo; + callbackData.foundDevice = MAL_FALSE; + + mal_pa_operation* pOP = NULL; + + mal_pa_mainloop* pMainLoop = ((mal_pa_mainloop_new_proc)pContext->pulse.pa_mainloop_new)(); + if (pMainLoop == NULL) { + return MAL_FAILED_TO_INIT_BACKEND; + } + + mal_pa_mainloop_api* pAPI = ((mal_pa_mainloop_get_api_proc)pContext->pulse.pa_mainloop_get_api)(pMainLoop); + if (pAPI == NULL) { + ((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); + return MAL_FAILED_TO_INIT_BACKEND; + } + + mal_pa_context* pPulseContext = ((mal_pa_context_new_proc)pContext->pulse.pa_context_new)(pAPI, pContext->config.pulse.pApplicationName); + if (pPulseContext == NULL) { + ((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); + return MAL_FAILED_TO_INIT_BACKEND; + } + + int error = ((mal_pa_context_connect_proc)pContext->pulse.pa_context_connect)(pPulseContext, pContext->config.pulse.pServerName, 0, NULL); + if (error != MAL_PA_OK) { + ((mal_pa_context_unref_proc)pContext->pulse.pa_context_unref)(pPulseContext); + ((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); + return mal_result_from_pulse(error); + } + + while (((mal_pa_context_get_state_proc)pContext->pulse.pa_context_get_state)(pPulseContext) != MAL_PA_CONTEXT_READY) { + error = ((mal_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)(pMainLoop, 1, NULL); + if (error < 0) { + result = mal_result_from_pulse(error); + goto done; + } + } + + if (deviceType == mal_device_type_playback) { + pOP = ((mal_pa_context_get_sink_info_by_name_proc)pContext->pulse.pa_context_get_sink_info_by_name)(pPulseContext, pDeviceID->pulse, mal_context_get_device_info_sink_callback__pulse, &callbackData); + } else { + pOP = ((mal_pa_context_get_source_info_by_name_proc)pContext->pulse.pa_context_get_source_info_by_name)(pPulseContext, pDeviceID->pulse, mal_context_get_device_info_source_callback__pulse, &callbackData); } -} -#if 0 -mal_pa_channel_position_t mal_channel_position_to_pulse(mal_channel position) -{ - switch (position) - { - case MAL_CHANNEL_NONE: return MAL_PA_CHANNEL_POSITION_INVALID; - case MAL_CHANNEL_FRONT_LEFT: return MAL_PA_CHANNEL_POSITION_FRONT_LEFT; - case MAL_CHANNEL_FRONT_RIGHT: return MAL_PA_CHANNEL_POSITION_FRONT_RIGHT; - case MAL_CHANNEL_FRONT_CENTER: return MAL_PA_CHANNEL_POSITION_FRONT_CENTER; - case MAL_CHANNEL_LFE: return MAL_PA_CHANNEL_POSITION_LFE; - case MAL_CHANNEL_BACK_LEFT: return MAL_PA_CHANNEL_POSITION_REAR_LEFT; - case MAL_CHANNEL_BACK_RIGHT: return MAL_PA_CHANNEL_POSITION_REAR_RIGHT; - case MAL_CHANNEL_FRONT_LEFT_CENTER: return MAL_PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER; - case MAL_CHANNEL_FRONT_RIGHT_CENTER: return MAL_PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER; - case MAL_CHANNEL_BACK_CENTER: return MAL_PA_CHANNEL_POSITION_REAR_CENTER; - case MAL_CHANNEL_SIDE_LEFT: return MAL_PA_CHANNEL_POSITION_SIDE_LEFT; - case MAL_CHANNEL_SIDE_RIGHT: return MAL_PA_CHANNEL_POSITION_SIDE_RIGHT; - case MAL_CHANNEL_TOP_CENTER: return MAL_PA_CHANNEL_POSITION_TOP_CENTER; - case MAL_CHANNEL_TOP_FRONT_LEFT: return MAL_PA_CHANNEL_POSITION_TOP_FRONT_LEFT; - case MAL_CHANNEL_TOP_FRONT_CENTER: return MAL_PA_CHANNEL_POSITION_TOP_FRONT_CENTER; - case MAL_CHANNEL_TOP_FRONT_RIGHT: return MAL_PA_CHANNEL_POSITION_TOP_FRONT_RIGHT; - case MAL_CHANNEL_TOP_BACK_LEFT: return MAL_PA_CHANNEL_POSITION_TOP_REAR_LEFT; - case MAL_CHANNEL_TOP_BACK_CENTER: return MAL_PA_CHANNEL_POSITION_TOP_REAR_CENTER; - case MAL_CHANNEL_TOP_BACK_RIGHT: return MAL_PA_CHANNEL_POSITION_TOP_REAR_RIGHT; - case MAL_CHANNEL_19: return MAL_PA_CHANNEL_POSITION_AUX18; - case MAL_CHANNEL_20: return MAL_PA_CHANNEL_POSITION_AUX19; - case MAL_CHANNEL_21: return MAL_PA_CHANNEL_POSITION_AUX20; - case MAL_CHANNEL_22: return MAL_PA_CHANNEL_POSITION_AUX21; - case MAL_CHANNEL_23: return MAL_PA_CHANNEL_POSITION_AUX22; - case MAL_CHANNEL_24: return MAL_PA_CHANNEL_POSITION_AUX23; - case MAL_CHANNEL_25: return MAL_PA_CHANNEL_POSITION_AUX24; - case MAL_CHANNEL_26: return MAL_PA_CHANNEL_POSITION_AUX25; - case MAL_CHANNEL_27: return MAL_PA_CHANNEL_POSITION_AUX26; - case MAL_CHANNEL_28: return MAL_PA_CHANNEL_POSITION_AUX27; - case MAL_CHANNEL_29: return MAL_PA_CHANNEL_POSITION_AUX28; - case MAL_CHANNEL_30: return MAL_PA_CHANNEL_POSITION_AUX29; - case MAL_CHANNEL_31: return MAL_PA_CHANNEL_POSITION_AUX30; - case MAL_CHANNEL_32: return MAL_PA_CHANNEL_POSITION_AUX31; - default: return (mal_pa_channel_position_t)position; + if (pOP != NULL) { + mal_wait_for_operation__pulse(pContext, pMainLoop, pOP); + ((mal_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); + } else { + result = MAL_ERROR; + goto done; + } + + if (!callbackData.foundDevice) { + result = MAL_NO_DEVICE; + goto done; } + + +done: + ((mal_pa_context_disconnect_proc)pContext->pulse.pa_context_disconnect)(pPulseContext); + ((mal_pa_context_unref_proc)pContext->pulse.pa_context_unref)(pPulseContext); + ((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); + return result; } -#endif -mal_result mal_wait_for_operation__pulse(mal_context* pContext, mal_pa_mainloop* pMainLoop, mal_pa_operation* pOP) +void mal_pulse_device_state_callback(mal_pa_context* pPulseContext, void* pUserData) { + mal_device* pDevice = (mal_device*)pUserData; + mal_assert(pDevice != NULL); + + mal_context* pContext = pDevice->pContext; mal_assert(pContext != NULL); - mal_assert(pMainLoop != NULL); - mal_assert(pOP != NULL); - while (((mal_pa_operation_get_state_proc)pContext->pulse.pa_operation_get_state)(pOP) != MAL_PA_OPERATION_DONE) { - int error = ((mal_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)(pMainLoop, 1, NULL); + pDevice->pulse.pulseContextState = ((mal_pa_context_get_state_proc)pContext->pulse.pa_context_get_state)(pPulseContext); +} + +void mal_pulse_device_write_callback(mal_pa_stream* pStream, size_t sizeInBytes, void* pUserData) +{ + mal_device* pDevice = (mal_device*)pUserData; + mal_assert(pDevice != NULL); + + mal_context* pContext = pDevice->pContext; + mal_assert(pContext != NULL); + + size_t bytesRemaining = sizeInBytes; + while (bytesRemaining > 0) { + size_t bytesToReadFromClient = bytesRemaining; + if (bytesToReadFromClient > 0xFFFFFFFF) { + bytesToReadFromClient = 0xFFFFFFFF; + } + + void* pBuffer = NULL; + int error = ((mal_pa_stream_begin_write_proc)pContext->pulse.pa_stream_begin_write)((mal_pa_stream*)pDevice->pulse.pStream, &pBuffer, &bytesToReadFromClient); if (error < 0) { - return mal_result_from_pulse(error); + mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to retrieve write buffer for sending data to the device.", mal_result_from_pulse(error)); + return; } - } - return MAL_SUCCESS; + if (pBuffer != NULL && bytesToReadFromClient > 0) { + mal_uint32 framesToReadFromClient = (mal_uint32)bytesToReadFromClient / (pDevice->internalChannels*mal_get_bytes_per_sample(pDevice->internalFormat)); + if (framesToReadFromClient > 0) { + mal_device__read_frames_from_client(pDevice, framesToReadFromClient, pBuffer); + + error = ((mal_pa_stream_write_proc)pContext->pulse.pa_stream_write)((mal_pa_stream*)pDevice->pulse.pStream, pBuffer, bytesToReadFromClient, NULL, 0, MAL_PA_SEEK_RELATIVE); + if (error < 0) { + mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to write data to the PulseAudio stream.", mal_result_from_pulse(error)); + return; + } + } + + bytesRemaining -= bytesToReadFromClient; + } + } } -mal_result mal_device__wait_for_operation__pulse(mal_device* pDevice, mal_pa_operation* pOP) +void mal_pulse_device_read_callback(mal_pa_stream* pStream, size_t sizeInBytes, void* pUserData) { + mal_device* pDevice = (mal_device*)pUserData; mal_assert(pDevice != NULL); - mal_assert(pOP != NULL); - return mal_wait_for_operation__pulse(pDevice->pContext, (mal_pa_mainloop*)pDevice->pulse.pMainLoop, pOP); -} + mal_context* pContext = pDevice->pContext; + mal_assert(pContext != NULL); + size_t bytesRemaining = sizeInBytes; + while (bytesRemaining > 0) { + size_t bytesToSendToClient = bytesRemaining; + if (bytesToSendToClient > 0xFFFFFFFF) { + bytesToSendToClient = 0xFFFFFFFF; + } -mal_bool32 mal_context_is_device_id_equal__pulse(mal_context* pContext, const mal_device_id* pID0, const mal_device_id* pID1) -{ - mal_assert(pContext != NULL); - mal_assert(pID0 != NULL); - mal_assert(pID1 != NULL); - (void)pContext; + const void* pBuffer = NULL; + int error = ((mal_pa_stream_peek_proc)pContext->pulse.pa_stream_peek)((mal_pa_stream*)pDevice->pulse.pStream, &pBuffer, &sizeInBytes); + if (error < 0) { + mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to retrieve read buffer for reading data from the device.", mal_result_from_pulse(error)); + return; + } - return mal_strcmp(pID0->pulse, pID1->pulse) == 0; -} + if (pBuffer != NULL) { + mal_uint32 framesToSendToClient = (mal_uint32)bytesToSendToClient / (pDevice->internalChannels*mal_get_bytes_per_sample(pDevice->internalFormat)); + if (framesToSendToClient > 0) { + mal_device__send_frames_to_client(pDevice, framesToSendToClient, pBuffer); + } + } + error = ((mal_pa_stream_drop_proc)pContext->pulse.pa_stream_drop)((mal_pa_stream*)pDevice->pulse.pStream); + if (error < 0) { + mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to drop fragment from the PulseAudio stream.", mal_result_from_pulse(error)); + } -typedef struct -{ - mal_context* pContext; - mal_enum_devices_callback_proc callback; - void* pUserData; - mal_bool32 isTerminated; -} mal_context_enumerate_devices_callback_data__pulse; + bytesRemaining -= bytesToSendToClient; + } +} -void mal_context_enumerate_devices_sink_callback__pulse(mal_pa_context* pPulseContext, const mal_pa_sink_info* pSinkInfo, int endOfList, void* pUserData) +void mal_device_sink_info_callback(mal_pa_context* pPulseContext, const mal_pa_sink_info* pInfo, int endOfList, void* pUserData) { - mal_context_enumerate_devices_callback_data__pulse* pData = (mal_context_enumerate_devices_callback_data__pulse*)pUserData; - mal_assert(pData != NULL); - - if (endOfList || pData->isTerminated) { + if (endOfList > 0) { return; } - mal_device_info deviceInfo; - mal_zero_object(&deviceInfo); + mal_pa_sink_info* pInfoOut = (mal_pa_sink_info*)pUserData; + mal_assert(pInfoOut != NULL); - // The name from PulseAudio is the ID for mini_al. - if (pSinkInfo->name != NULL) { - mal_strncpy_s(deviceInfo.id.pulse, sizeof(deviceInfo.id.pulse), pSinkInfo->name, (size_t)-1); - } + *pInfoOut = *pInfo; +} - // The description from PulseAudio is the name for mini_al. - if (pSinkInfo->description != NULL) { - mal_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), pSinkInfo->description, (size_t)-1); +void mal_device_source_info_callback(mal_pa_context* pPulseContext, const mal_pa_source_info* pInfo, int endOfList, void* pUserData) +{ + if (endOfList > 0) { + return; } - pData->isTerminated = !pData->callback(pData->pContext, mal_device_type_playback, &deviceInfo, pData->pUserData); + mal_pa_source_info* pInfoOut = (mal_pa_source_info*)pUserData; + mal_assert(pInfoOut != NULL); + + *pInfoOut = *pInfo; } -void mal_context_enumerate_devices_source_callback__pulse(mal_pa_context* pPulseContext, const mal_pa_source_info* pSinkInfo, int endOfList, void* pUserData) +void mal_device_sink_name_callback(mal_pa_context* pPulseContext, const mal_pa_sink_info* pInfo, int endOfList, void* pUserData) { - mal_context_enumerate_devices_callback_data__pulse* pData = (mal_context_enumerate_devices_callback_data__pulse*)pUserData; - mal_assert(pData != NULL); - - if (endOfList || pData->isTerminated) { + if (endOfList > 0) { return; } - mal_device_info deviceInfo; - mal_zero_object(&deviceInfo); + mal_device* pDevice = (mal_device*)pUserData; + mal_assert(pDevice != NULL); - // The name from PulseAudio is the ID for mini_al. - if (pSinkInfo->name != NULL) { - mal_strncpy_s(deviceInfo.id.pulse, sizeof(deviceInfo.id.pulse), pSinkInfo->name, (size_t)-1); - } + mal_strncpy_s(pDevice->name, sizeof(pDevice->name), pInfo->description, (size_t)-1); +} - // The description from PulseAudio is the name for mini_al. - if (pSinkInfo->description != NULL) { - mal_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), pSinkInfo->description, (size_t)-1); +void mal_device_source_name_callback(mal_pa_context* pPulseContext, const mal_pa_source_info* pInfo, int endOfList, void* pUserData) +{ + if (endOfList > 0) { + return; } - pData->isTerminated = !pData->callback(pData->pContext, mal_device_type_capture, &deviceInfo, pData->pUserData); + mal_device* pDevice = (mal_device*)pUserData; + mal_assert(pDevice != NULL); + + mal_strncpy_s(pDevice->name, sizeof(pDevice->name), pInfo->description, (size_t)-1); } -mal_result mal_context_enumerate_devices__pulse(mal_context* pContext, mal_enum_devices_callback_proc callback, void* pUserData) +void mal_device_uninit__pulse(mal_device* pDevice) { + mal_assert(pDevice != NULL); + + mal_context* pContext = pDevice->pContext; mal_assert(pContext != NULL); - mal_assert(callback != NULL); - mal_result result = MAL_SUCCESS; + ((mal_pa_stream_disconnect_proc)pContext->pulse.pa_stream_disconnect)((mal_pa_stream*)pDevice->pulse.pStream); + ((mal_pa_stream_unref_proc)pContext->pulse.pa_stream_unref)((mal_pa_stream*)pDevice->pulse.pStream); + ((mal_pa_context_disconnect_proc)pContext->pulse.pa_context_disconnect)((mal_pa_context*)pDevice->pulse.pPulseContext); + ((mal_pa_context_unref_proc)pContext->pulse.pa_context_unref)((mal_pa_context*)pDevice->pulse.pPulseContext); + ((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((mal_pa_mainloop*)pDevice->pulse.pMainLoop); +} + +mal_result mal_device_init__pulse(mal_context* pContext, mal_device_type type, const mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice) +{ + (void)pContext; + + mal_assert(pDevice != NULL); + mal_zero_object(&pDevice->pulse); + + mal_result result = MAL_SUCCESS; + int error = 0; + + const char* dev = NULL; + if (pDeviceID != NULL) { + dev = pDeviceID->pulse; + } + + mal_uint32 bufferSizeInFrames = pConfig->bufferSizeInFrames; + + mal_pa_sink_info sinkInfo; + mal_pa_source_info sourceInfo; + mal_pa_operation* pOP = NULL; + + mal_pa_sample_spec ss; + mal_pa_channel_map cmap; + mal_pa_buffer_attr attr; + + const mal_pa_sample_spec* pActualSS = NULL; + const mal_pa_channel_map* pActualCMap = NULL; + const mal_pa_buffer_attr* pActualAttr = NULL; - mal_context_enumerate_devices_callback_data__pulse callbackData; - callbackData.pContext = pContext; - callbackData.callback = callback; - callbackData.pUserData = pUserData; - callbackData.isTerminated = MAL_FALSE; - mal_pa_operation* pOP = NULL; - mal_pa_mainloop* pMainLoop = ((mal_pa_mainloop_new_proc)pContext->pulse.pa_mainloop_new)(); - if (pMainLoop == NULL) { - return MAL_FAILED_TO_INIT_BACKEND; + pDevice->pulse.pMainLoop = ((mal_pa_mainloop_new_proc)pContext->pulse.pa_mainloop_new)(); + if (pDevice->pulse.pMainLoop == NULL) { + result = mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create main loop for device.", MAL_FAILED_TO_INIT_BACKEND); + goto on_error0; } - mal_pa_mainloop_api* pAPI = ((mal_pa_mainloop_get_api_proc)pContext->pulse.pa_mainloop_get_api)(pMainLoop); - if (pAPI == NULL) { - ((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); - return MAL_FAILED_TO_INIT_BACKEND; + pDevice->pulse.pAPI = ((mal_pa_mainloop_get_api_proc)pContext->pulse.pa_mainloop_get_api)((mal_pa_mainloop*)pDevice->pulse.pMainLoop); + if (pDevice->pulse.pAPI == NULL) { + result = mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to retrieve PulseAudio main loop.", MAL_FAILED_TO_INIT_BACKEND); + goto on_error1; } - mal_pa_context* pPulseContext = ((mal_pa_context_new_proc)pContext->pulse.pa_context_new)(pAPI, pContext->config.pulse.pApplicationName); - if (pPulseContext == NULL) { - ((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); - return MAL_FAILED_TO_INIT_BACKEND; + pDevice->pulse.pPulseContext = ((mal_pa_context_new_proc)pContext->pulse.pa_context_new)((mal_pa_mainloop_api*)pDevice->pulse.pAPI, pContext->config.pulse.pApplicationName); + if (pDevice->pulse.pPulseContext == NULL) { + result = mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio context for device.", MAL_FAILED_TO_INIT_BACKEND); + goto on_error1; } - int error = ((mal_pa_context_connect_proc)pContext->pulse.pa_context_connect)(pPulseContext, pContext->config.pulse.pServerName, 0, NULL); + error = ((mal_pa_context_connect_proc)pContext->pulse.pa_context_connect)((mal_pa_context*)pDevice->pulse.pPulseContext, pContext->config.pulse.pServerName, (pContext->config.pulse.tryAutoSpawn) ? 0 : MAL_PA_CONTEXT_NOAUTOSPAWN, NULL); if (error != MAL_PA_OK) { - ((mal_pa_context_unref_proc)pContext->pulse.pa_context_unref)(pPulseContext); - ((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); - return mal_result_from_pulse(error); + result = mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio context.", mal_result_from_pulse(error)); + goto on_error2; } - while (((mal_pa_context_get_state_proc)pContext->pulse.pa_context_get_state)(pPulseContext) != MAL_PA_CONTEXT_READY) { - error = ((mal_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)(pMainLoop, 1, NULL); + + pDevice->pulse.pulseContextState = MAL_PA_CONTEXT_UNCONNECTED; + ((mal_pa_context_set_state_callback_proc)pContext->pulse.pa_context_set_state_callback)((mal_pa_context*)pDevice->pulse.pPulseContext, mal_pulse_device_state_callback, pDevice); + + // Wait for PulseAudio to get itself ready before returning. + for (;;) { + if (pDevice->pulse.pulseContextState == MAL_PA_CONTEXT_READY) { + break; + } else { + error = ((mal_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((mal_pa_mainloop*)pDevice->pulse.pMainLoop, 1, NULL); // 1 = block. + if (error < 0) { + result = mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] The PulseAudio main loop returned an error while connecting the PulseAudio context.", mal_result_from_pulse(error)); + goto on_error3; + } + continue; + } + + // An error may have occurred. + if (pDevice->pulse.pulseContextState == MAL_PA_CONTEXT_FAILED || pDevice->pulse.pulseContextState == MAL_PA_CONTEXT_TERMINATED) { + result = mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while connecting the PulseAudio context.", MAL_ERROR); + goto on_error3; + } + + error = ((mal_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((mal_pa_mainloop*)pDevice->pulse.pMainLoop, 1, NULL); if (error < 0) { - result = mal_result_from_pulse(error); - goto done; + result = mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] The PulseAudio main loop returned an error while connecting the PulseAudio context.", mal_result_from_pulse(error)); + goto on_error3; } } - // Playback. - if (!callbackData.isTerminated) { - pOP = ((mal_pa_context_get_sink_info_list_proc)pContext->pulse.pa_context_get_sink_info_list)(pPulseContext, mal_context_enumerate_devices_sink_callback__pulse, &callbackData); - if (pOP == NULL) { - result = MAL_ERROR; - goto done; - } + if (type == mal_device_type_playback) { + pOP = ((mal_pa_context_get_sink_info_by_name_proc)pContext->pulse.pa_context_get_sink_info_by_name)((mal_pa_context*)pDevice->pulse.pPulseContext, dev, mal_device_sink_info_callback, &sinkInfo); + } else { + pOP = ((mal_pa_context_get_source_info_by_name_proc)pContext->pulse.pa_context_get_source_info_by_name)((mal_pa_context*)pDevice->pulse.pPulseContext, dev, mal_device_source_info_callback, &sourceInfo); + } - result = mal_wait_for_operation__pulse(pContext, pMainLoop, pOP); + if (pOP != NULL) { + mal_device__wait_for_operation__pulse(pDevice, pOP); ((mal_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); - if (result != MAL_SUCCESS) { - goto done; - } } - // Capture. - if (!callbackData.isTerminated) { - pOP = ((mal_pa_context_get_source_info_list_proc)pContext->pulse.pa_context_get_source_info_list)(pPulseContext, mal_context_enumerate_devices_source_callback__pulse, &callbackData); - if (pOP == NULL) { - result = MAL_ERROR; - goto done; +#if 0 + mal_pa_sample_spec deviceSS; + mal_pa_channel_map deviceCMap; + if (type == mal_device_type_playback) { + deviceSS = sinkInfo.sample_spec; + deviceCMap = sinkInfo.channel_map; + } else { + deviceSS = sourceInfo.sample_spec; + deviceCMap = sourceInfo.channel_map; + } + + if (pDevice->usingDefaultFormat) { + ss.format = deviceSS.format; + } else { + ss.format = mal_format_to_pulse(pConfig->format); + } + if (ss.format == MAL_PA_SAMPLE_INVALID) { + ss.format = MAL_PA_SAMPLE_S16LE; + } + + if (pDevice->usingDefaultChannels) { + ss.channels = deviceSS.channels; + } else { + ss.channels = pConfig->channels; + } + + if (pDevice->usingDefaultSampleRate) { + ss.rate = deviceSS.rate; + } else { + ss.rate = pConfig->sampleRate; + } + + + if (pDevice->usingDefaultChannelMap) { + cmap = deviceCMap; + } else { + cmap.channels = pConfig->channels; + for (mal_uint32 iChannel = 0; iChannel < pConfig->channels; ++iChannel) { + cmap.map[iChannel] = mal_channel_position_to_pulse(pConfig->channelMap[iChannel]); } - result = mal_wait_for_operation__pulse(pContext, pMainLoop, pOP); - ((mal_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); - if (result != MAL_SUCCESS) { - goto done; + if (((mal_pa_channel_map_valid_proc)pContext->pulse.pa_channel_map_valid)(&cmap) == 0 || ((mal_pa_channel_map_compatible_proc)pContext->pulse.pa_channel_map_compatible)(&cmap, &ss) == 0) { + ((mal_pa_channel_map_init_extend_proc)pContext->pulse.pa_channel_map_init_extend)(&cmap, ss.channels, MAL_PA_CHANNEL_MAP_DEFAULT); // The channel map is invalid, so just fall back to the default. } } +#else + if (type == mal_device_type_playback) { + ss = sinkInfo.sample_spec; + cmap = sinkInfo.channel_map; + } else { + ss = sourceInfo.sample_spec; + cmap = sourceInfo.channel_map; + } +#endif -done: - ((mal_pa_context_disconnect_proc)pContext->pulse.pa_context_disconnect)(pPulseContext); - ((mal_pa_context_unref_proc)pContext->pulse.pa_context_unref)(pPulseContext); - ((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); - return result; -} + // If using the default buffer size try to find an appropriate default. + if (pDevice->usingDefaultBufferSize) { + // CPU speed is a factor to consider when determine how large of a buffer we need. + float fCPUSpeed = mal_calculate_cpu_speed_factor(); + // In my testing, capture seems to have worse latency than playback for some reason. + float fType; + if (type == mal_device_type_playback) { + fType = 1.0f; + } else { + fType = 2.0f; + } -typedef struct -{ - mal_device_info* pDeviceInfo; - mal_bool32 foundDevice; -} mal_context_get_device_info_callback_data__pulse; + // Backend tax. Need to fiddle with this. + float fBackend = 1.2; -void mal_context_get_device_info_sink_callback__pulse(mal_pa_context* pPulseContext, const mal_pa_sink_info* pInfo, int endOfList, void* pUserData) -{ - if (endOfList > 0) { - return; + bufferSizeInFrames = mal_calculate_default_buffer_size_in_frames(pConfig->performanceProfile, pConfig->sampleRate, fCPUSpeed*fType*fBackend); } - mal_context_get_device_info_callback_data__pulse* pData = (mal_context_get_device_info_callback_data__pulse*)pUserData; - mal_assert(pData != NULL); - pData->foundDevice = MAL_TRUE; + attr.maxlength = bufferSizeInFrames * mal_get_bytes_per_sample(mal_format_from_pulse(ss.format))*ss.channels; + attr.tlength = attr.maxlength / pConfig->periods; + attr.prebuf = (mal_uint32)-1; + attr.minreq = attr.tlength; + attr.fragsize = attr.tlength; - if (pInfo->name != NULL) { - mal_strncpy_s(pData->pDeviceInfo->id.pulse, sizeof(pData->pDeviceInfo->id.pulse), pInfo->name, (size_t)-1); + char streamName[256]; + if (pConfig->pulse.pStreamName != NULL) { + mal_strncpy_s(streamName, sizeof(streamName), pConfig->pulse.pStreamName, (size_t)-1); + } else { + static int g_StreamCounter = 0; + mal_strcpy_s(streamName, sizeof(streamName), "mini_al:"); + mal_itoa_s(g_StreamCounter, streamName + 8, sizeof(streamName)-8, 10); // 8 = strlen("mini_al:") + g_StreamCounter += 1; } - if (pInfo->description != NULL) { - mal_strncpy_s(pData->pDeviceInfo->name, sizeof(pData->pDeviceInfo->name), pInfo->description, (size_t)-1); + pDevice->pulse.pStream = ((mal_pa_stream_new_proc)pContext->pulse.pa_stream_new)((mal_pa_context*)pDevice->pulse.pPulseContext, streamName, &ss, &cmap); + if (pDevice->pulse.pStream == NULL) { + result = mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio stream.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + goto on_error3; } - pData->pDeviceInfo->minChannels = pInfo->sample_spec.channels; - pData->pDeviceInfo->maxChannels = pInfo->sample_spec.channels; - pData->pDeviceInfo->minSampleRate = pInfo->sample_spec.rate; - pData->pDeviceInfo->maxSampleRate = pInfo->sample_spec.rate; - pData->pDeviceInfo->formatCount = 1; - pData->pDeviceInfo->formats[0] = mal_format_from_pulse(pInfo->sample_spec.format); -} - -void mal_context_get_device_info_source_callback__pulse(mal_pa_context* pPulseContext, const mal_pa_source_info* pInfo, int endOfList, void* pUserData) -{ - if (endOfList > 0) { - return; - } - mal_context_get_device_info_callback_data__pulse* pData = (mal_context_get_device_info_callback_data__pulse*)pUserData; - mal_assert(pData != NULL); - pData->foundDevice = MAL_TRUE; - if (pInfo->name != NULL) { - mal_strncpy_s(pData->pDeviceInfo->id.pulse, sizeof(pData->pDeviceInfo->id.pulse), pInfo->name, (size_t)-1); + if (type == mal_device_type_playback) { + error = ((mal_pa_stream_connect_playback_proc)pContext->pulse.pa_stream_connect_playback)((mal_pa_stream*)pDevice->pulse.pStream, dev, &attr, MAL_PA_STREAM_START_CORKED, NULL, NULL); + } else { + error = ((mal_pa_stream_connect_record_proc)pContext->pulse.pa_stream_connect_record)((mal_pa_stream*)pDevice->pulse.pStream, dev, &attr, MAL_PA_STREAM_START_CORKED); } - if (pInfo->description != NULL) { - mal_strncpy_s(pData->pDeviceInfo->name, sizeof(pData->pDeviceInfo->name), pInfo->description, (size_t)-1); + if (error != MAL_PA_OK) { + result = mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio stream.", mal_result_from_pulse(error)); + goto on_error4; } - pData->pDeviceInfo->minChannels = pInfo->sample_spec.channels; - pData->pDeviceInfo->maxChannels = pInfo->sample_spec.channels; - pData->pDeviceInfo->minSampleRate = pInfo->sample_spec.rate; - pData->pDeviceInfo->maxSampleRate = pInfo->sample_spec.rate; - pData->pDeviceInfo->formatCount = 1; - pData->pDeviceInfo->formats[0] = mal_format_from_pulse(pInfo->sample_spec.format); -} + while (((mal_pa_stream_get_state_proc)pContext->pulse.pa_stream_get_state)((mal_pa_stream*)pDevice->pulse.pStream) != MAL_PA_STREAM_READY) { + error = ((mal_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((mal_pa_mainloop*)pDevice->pulse.pMainLoop, 1, NULL); + if (error < 0) { + result = mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] The PulseAudio main loop returned an error while connecting the PulseAudio stream.", mal_result_from_pulse(error)); + goto on_error5; + } + } -mal_result mal_context_get_device_info__pulse(mal_context* pContext, mal_device_type deviceType, const mal_device_id* pDeviceID, mal_share_mode shareMode, mal_device_info* pDeviceInfo) -{ - mal_assert(pContext != NULL); - (void)shareMode; - mal_result result = MAL_SUCCESS; + // Internal format. + pActualSS = ((mal_pa_stream_get_sample_spec_proc)pContext->pulse.pa_stream_get_sample_spec)((mal_pa_stream*)pDevice->pulse.pStream); + if (pActualSS != NULL) { + ss = *pActualSS; + } - mal_context_get_device_info_callback_data__pulse callbackData; - callbackData.pDeviceInfo = pDeviceInfo; - callbackData.foundDevice = MAL_FALSE; + pDevice->internalFormat = mal_format_from_pulse(ss.format); + pDevice->internalChannels = ss.channels; + pDevice->internalSampleRate = ss.rate; - mal_pa_operation* pOP = NULL; - mal_pa_mainloop* pMainLoop = ((mal_pa_mainloop_new_proc)pContext->pulse.pa_mainloop_new)(); - if (pMainLoop == NULL) { - return MAL_FAILED_TO_INIT_BACKEND; + // Internal channel map. + pActualCMap = ((mal_pa_stream_get_channel_map_proc)pContext->pulse.pa_stream_get_channel_map)((mal_pa_stream*)pDevice->pulse.pStream); + if (pActualCMap != NULL) { + cmap = *pActualCMap; } - mal_pa_mainloop_api* pAPI = ((mal_pa_mainloop_get_api_proc)pContext->pulse.pa_mainloop_get_api)(pMainLoop); - if (pAPI == NULL) { - ((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); - return MAL_FAILED_TO_INIT_BACKEND; + for (mal_uint32 iChannel = 0; iChannel < pDevice->internalChannels; ++iChannel) { + pDevice->internalChannelMap[iChannel] = mal_channel_position_from_pulse(cmap.map[iChannel]); } - mal_pa_context* pPulseContext = ((mal_pa_context_new_proc)pContext->pulse.pa_context_new)(pAPI, pContext->config.pulse.pApplicationName); - if (pPulseContext == NULL) { - ((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); - return MAL_FAILED_TO_INIT_BACKEND; - } - int error = ((mal_pa_context_connect_proc)pContext->pulse.pa_context_connect)(pPulseContext, pContext->config.pulse.pServerName, 0, NULL); - if (error != MAL_PA_OK) { - ((mal_pa_context_unref_proc)pContext->pulse.pa_context_unref)(pPulseContext); - ((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); - return mal_result_from_pulse(error); + // Buffer size. + pActualAttr = ((mal_pa_stream_get_buffer_attr_proc)pContext->pulse.pa_stream_get_buffer_attr)((mal_pa_stream*)pDevice->pulse.pStream); + if (pActualAttr != NULL) { + attr = *pActualAttr; } - while (((mal_pa_context_get_state_proc)pContext->pulse.pa_context_get_state)(pPulseContext) != MAL_PA_CONTEXT_READY) { - error = ((mal_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)(pMainLoop, 1, NULL); - if (error < 0) { - result = mal_result_from_pulse(error); - goto done; + pDevice->bufferSizeInFrames = attr.maxlength / (mal_get_bytes_per_sample(pDevice->internalFormat)*pDevice->internalChannels); + pDevice->periods = attr.maxlength / attr.tlength; + + + // Grab the name of the device if we can. + dev = ((mal_pa_stream_get_device_name_proc)pContext->pulse.pa_stream_get_device_name)((mal_pa_stream*)pDevice->pulse.pStream); + if (dev != NULL) { + mal_pa_operation* pOP = NULL; + if (type == mal_device_type_playback) { + pOP = ((mal_pa_context_get_sink_info_by_name_proc)pContext->pulse.pa_context_get_sink_info_by_name)((mal_pa_context*)pDevice->pulse.pPulseContext, dev, mal_device_sink_name_callback, pDevice); + } else { + pOP = ((mal_pa_context_get_source_info_by_name_proc)pContext->pulse.pa_context_get_source_info_by_name)((mal_pa_context*)pDevice->pulse.pPulseContext, dev, mal_device_source_name_callback, pDevice); } - } - if (deviceType == mal_device_type_playback) { - pOP = ((mal_pa_context_get_sink_info_by_name_proc)pContext->pulse.pa_context_get_sink_info_by_name)(pPulseContext, pDeviceID->pulse, mal_context_get_device_info_sink_callback__pulse, &callbackData); - } else { - pOP = ((mal_pa_context_get_source_info_by_name_proc)pContext->pulse.pa_context_get_source_info_by_name)(pPulseContext, pDeviceID->pulse, mal_context_get_device_info_source_callback__pulse, &callbackData); + if (pOP != NULL) { + mal_device__wait_for_operation__pulse(pDevice, pOP); + ((mal_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); + } } - if (pOP != NULL) { - mal_wait_for_operation__pulse(pContext, pMainLoop, pOP); - ((mal_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); + + // Set callbacks for reading and writing data to/from the PulseAudio stream. + if (type == mal_device_type_playback) { + ((mal_pa_stream_set_write_callback_proc)pContext->pulse.pa_stream_set_write_callback)((mal_pa_stream*)pDevice->pulse.pStream, mal_pulse_device_write_callback, pDevice); } else { - result = MAL_ERROR; - goto done; + ((mal_pa_stream_set_read_callback_proc)pContext->pulse.pa_stream_set_read_callback)((mal_pa_stream*)pDevice->pulse.pStream, mal_pulse_device_read_callback, pDevice); } - if (!callbackData.foundDevice) { - result = MAL_NO_DEVICE; - goto done; - } + pDevice->pulse.fragmentSizeInBytes = attr.tlength; + + return MAL_SUCCESS; -done: - ((mal_pa_context_disconnect_proc)pContext->pulse.pa_context_disconnect)(pPulseContext); - ((mal_pa_context_unref_proc)pContext->pulse.pa_context_unref)(pPulseContext); - ((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); + +on_error5: ((mal_pa_stream_disconnect_proc)pContext->pulse.pa_stream_disconnect)((mal_pa_stream*)pDevice->pulse.pStream); +on_error4: ((mal_pa_stream_unref_proc)pContext->pulse.pa_stream_unref)((mal_pa_stream*)pDevice->pulse.pStream); +on_error3: ((mal_pa_context_disconnect_proc)pContext->pulse.pa_context_disconnect)((mal_pa_context*)pDevice->pulse.pPulseContext); +on_error2: ((mal_pa_context_unref_proc)pContext->pulse.pa_context_unref)((mal_pa_context*)pDevice->pulse.pPulseContext); +on_error1: ((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((mal_pa_mainloop*)pDevice->pulse.pMainLoop); +on_error0: return result; } -void mal_pulse_device_state_callback(mal_pa_context* pPulseContext, void* pUserData) +void mal_pulse_operation_complete_callback(mal_pa_stream* pStream, int success, void* pUserData) { - mal_device* pDevice = (mal_device*)pUserData; - mal_assert(pDevice != NULL); - - mal_context* pContext = pDevice->pContext; - mal_assert(pContext != NULL); + mal_bool32* pIsSuccessful = (mal_bool32*)pUserData; + mal_assert(pIsSuccessful != NULL); - pDevice->pulse.pulseContextState = ((mal_pa_context_get_state_proc)pContext->pulse.pa_context_get_state)(pPulseContext); + *pIsSuccessful = (mal_bool32)success; } -void mal_pulse_device_write_callback(mal_pa_stream* pStream, size_t sizeInBytes, void* pUserData) +mal_result mal_device__cork_stream__pulse(mal_device* pDevice, int cork) { - mal_device* pDevice = (mal_device*)pUserData; - mal_assert(pDevice != NULL); - mal_context* pContext = pDevice->pContext; mal_assert(pContext != NULL); - size_t bytesRemaining = sizeInBytes; - while (bytesRemaining > 0) { - size_t bytesToReadFromClient = bytesRemaining; - if (bytesToReadFromClient > 0xFFFFFFFF) { - bytesToReadFromClient = 0xFFFFFFFF; - } - - void* pBuffer = NULL; - int error = ((mal_pa_stream_begin_write_proc)pContext->pulse.pa_stream_begin_write)((mal_pa_stream*)pDevice->pulse.pStream, &pBuffer, &bytesToReadFromClient); - if (error < 0) { - mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to retrieve write buffer for sending data to the device.", mal_result_from_pulse(error)); - return; - } + mal_bool32 wasSuccessful = MAL_FALSE; + mal_pa_operation* pOP = ((mal_pa_stream_cork_proc)pContext->pulse.pa_stream_cork)((mal_pa_stream*)pDevice->pulse.pStream, cork, mal_pulse_operation_complete_callback, &wasSuccessful); + if (pOP == NULL) { + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to cork PulseAudio stream.", (cork == 0) ? MAL_FAILED_TO_START_BACKEND_DEVICE : MAL_FAILED_TO_STOP_BACKEND_DEVICE); + } - if (pBuffer != NULL && bytesToReadFromClient > 0) { - mal_uint32 framesToReadFromClient = (mal_uint32)bytesToReadFromClient / (pDevice->internalChannels*mal_get_bytes_per_sample(pDevice->internalFormat)); - if (framesToReadFromClient > 0) { - mal_device__read_frames_from_client(pDevice, framesToReadFromClient, pBuffer); + mal_result result = mal_device__wait_for_operation__pulse(pDevice, pOP); + ((mal_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); - error = ((mal_pa_stream_write_proc)pContext->pulse.pa_stream_write)((mal_pa_stream*)pDevice->pulse.pStream, pBuffer, bytesToReadFromClient, NULL, 0, MAL_PA_SEEK_RELATIVE); - if (error < 0) { - mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to write data to the PulseAudio stream.", mal_result_from_pulse(error)); - return; - } - } + if (result != MAL_SUCCESS) { + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while waiting for the PulseAudio stream to cork.", result); + } - bytesRemaining -= bytesToReadFromClient; + if (!wasSuccessful) { + if (cork) { + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to stop PulseAudio stream.", MAL_FAILED_TO_STOP_BACKEND_DEVICE); + } else { + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to start PulseAudio stream.", MAL_FAILED_TO_START_BACKEND_DEVICE); } } + + return MAL_SUCCESS; } -void mal_pulse_device_read_callback(mal_pa_stream* pStream, size_t sizeInBytes, void* pUserData) +mal_result mal_device__start_backend__pulse(mal_device* pDevice) { - mal_device* pDevice = (mal_device*)pUserData; mal_assert(pDevice != NULL); mal_context* pContext = pDevice->pContext; mal_assert(pContext != NULL); - size_t bytesRemaining = sizeInBytes; - while (bytesRemaining > 0) { - size_t bytesToSendToClient = bytesRemaining; - if (bytesToSendToClient > 0xFFFFFFFF) { - bytesToSendToClient = 0xFFFFFFFF; - } - - const void* pBuffer = NULL; - int error = ((mal_pa_stream_peek_proc)pContext->pulse.pa_stream_peek)((mal_pa_stream*)pDevice->pulse.pStream, &pBuffer, &sizeInBytes); - if (error < 0) { - mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to retrieve read buffer for reading data from the device.", mal_result_from_pulse(error)); - return; - } + // For both playback and capture we need to uncork the stream. Afterwards, for playback we need to fill in an initial chunk + // of data, equal to the trigger length. That should then start actual playback. + mal_result result = mal_device__cork_stream__pulse(pDevice, 0); + if (result != MAL_SUCCESS) { + return result; + } - if (pBuffer != NULL) { - mal_uint32 framesToSendToClient = (mal_uint32)bytesToSendToClient / (pDevice->internalChannels*mal_get_bytes_per_sample(pDevice->internalFormat)); - if (framesToSendToClient > 0) { - mal_device__send_frames_to_client(pDevice, framesToSendToClient, pBuffer); - } - } + // A playback device is started by simply writing data to it. For capture we do nothing. + if (pDevice->type == mal_device_type_playback) { + // Playback. + mal_pulse_device_write_callback((mal_pa_stream*)pDevice->pulse.pStream, pDevice->pulse.fragmentSizeInBytes, pDevice); - error = ((mal_pa_stream_drop_proc)pContext->pulse.pa_stream_drop)((mal_pa_stream*)pDevice->pulse.pStream); - if (error < 0) { - mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to drop fragment from the PulseAudio stream.", mal_result_from_pulse(error)); + // Force an immediate start of the device just to be sure. + mal_pa_operation* pOP = ((mal_pa_stream_trigger_proc)pContext->pulse.pa_stream_trigger)((mal_pa_stream*)pDevice->pulse.pStream, NULL, NULL); + if (pOP != NULL) { + mal_device__wait_for_operation__pulse(pDevice, pOP); + ((mal_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); } - - bytesRemaining -= bytesToSendToClient; - } -} - -void mal_device_sink_info_callback(mal_pa_context* pPulseContext, const mal_pa_sink_info* pInfo, int endOfList, void* pUserData) -{ - if (endOfList > 0) { - return; + } else { + // Capture. Do nothing. } - mal_pa_sink_info* pInfoOut = (mal_pa_sink_info*)pUserData; - mal_assert(pInfoOut != NULL); - - *pInfoOut = *pInfo; + return MAL_SUCCESS; } -void mal_device_source_info_callback(mal_pa_context* pPulseContext, const mal_pa_source_info* pInfo, int endOfList, void* pUserData) +mal_result mal_device__stop_backend__pulse(mal_device* pDevice) { - if (endOfList > 0) { - return; - } + mal_assert(pDevice != NULL); - mal_pa_source_info* pInfoOut = (mal_pa_source_info*)pUserData; - mal_assert(pInfoOut != NULL); + mal_context* pContext = pDevice->pContext; + mal_assert(pContext != NULL); - *pInfoOut = *pInfo; -} + mal_result result = mal_device__cork_stream__pulse(pDevice, 1); + if (result != MAL_SUCCESS) { + return result; + } -void mal_device_sink_name_callback(mal_pa_context* pPulseContext, const mal_pa_sink_info* pInfo, int endOfList, void* pUserData) -{ - if (endOfList > 0) { - return; + // For playback, buffers need to be flushed. For capture they need to be drained. + mal_bool32 wasSuccessful; + mal_pa_operation* pOP = NULL; + if (pDevice->type == mal_device_type_playback) { + pOP = ((mal_pa_stream_flush_proc)pContext->pulse.pa_stream_flush)((mal_pa_stream*)pDevice->pulse.pStream, mal_pulse_operation_complete_callback, &wasSuccessful); + } else { + pOP = ((mal_pa_stream_drain_proc)pContext->pulse.pa_stream_drain)((mal_pa_stream*)pDevice->pulse.pStream, mal_pulse_operation_complete_callback, &wasSuccessful); } - mal_device* pDevice = (mal_device*)pUserData; - mal_assert(pDevice != NULL); + if (pOP == NULL) { + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to flush buffers after stopping PulseAudio stream.", MAL_ERROR); + } - mal_strncpy_s(pDevice->name, sizeof(pDevice->name), pInfo->description, (size_t)-1); -} + result = mal_device__wait_for_operation__pulse(pDevice, pOP); + ((mal_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); -void mal_device_source_name_callback(mal_pa_context* pPulseContext, const mal_pa_source_info* pInfo, int endOfList, void* pUserData) -{ - if (endOfList > 0) { - return; + if (result != MAL_SUCCESS) { + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while waiting for the PulseAudio stream to flush.", result); } - mal_device* pDevice = (mal_device*)pUserData; - mal_assert(pDevice != NULL); + if (!wasSuccessful) { + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to flush buffers after stopping PulseAudio stream.", MAL_ERROR); + } - mal_strncpy_s(pDevice->name, sizeof(pDevice->name), pInfo->description, (size_t)-1); + return MAL_SUCCESS; } -void mal_device_uninit__pulse(mal_device* pDevice) +mal_result mal_device__break_main_loop__pulse(mal_device* pDevice) { mal_assert(pDevice != NULL); mal_context* pContext = pDevice->pContext; mal_assert(pContext != NULL); - ((mal_pa_stream_disconnect_proc)pContext->pulse.pa_stream_disconnect)((mal_pa_stream*)pDevice->pulse.pStream); - ((mal_pa_stream_unref_proc)pContext->pulse.pa_stream_unref)((mal_pa_stream*)pDevice->pulse.pStream); - ((mal_pa_context_disconnect_proc)pContext->pulse.pa_context_disconnect)((mal_pa_context*)pDevice->pulse.pPulseContext); - ((mal_pa_context_unref_proc)pContext->pulse.pa_context_unref)((mal_pa_context*)pDevice->pulse.pPulseContext); - ((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((mal_pa_mainloop*)pDevice->pulse.pMainLoop); + pDevice->pulse.breakFromMainLoop = MAL_TRUE; + ((mal_pa_mainloop_wakeup_proc)pContext->pulse.pa_mainloop_wakeup)((mal_pa_mainloop*)pDevice->pulse.pMainLoop); + + return MAL_SUCCESS; } -mal_result mal_device_init__pulse(mal_context* pContext, mal_device_type type, const mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice) +mal_result mal_device__main_loop__pulse(mal_device* pDevice) { - (void)pContext; - mal_assert(pDevice != NULL); - mal_zero_object(&pDevice->pulse); - mal_result result = MAL_SUCCESS; - int error = 0; + mal_context* pContext = pDevice->pContext; + mal_assert(pContext != NULL); - const char* dev = NULL; - if (pDeviceID != NULL) { - dev = pDeviceID->pulse; + pDevice->pulse.breakFromMainLoop = MAL_FALSE; + while (!pDevice->pulse.breakFromMainLoop) { + // Break from the main loop if the device isn't started anymore. Likely what's happened is the application + // has requested that the device be stopped. + if (!mal_device_is_started(pDevice)) { + break; + } + + int resultPA = ((mal_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((mal_pa_mainloop*)pDevice->pulse.pMainLoop, 1, NULL); + if (resultPA < 0) { + break; // Some error occurred. + } } - mal_uint32 bufferSizeInFrames = pConfig->bufferSizeInFrames; + return MAL_SUCCESS; +} - mal_pa_sink_info sinkInfo; - mal_pa_source_info sourceInfo; - mal_pa_operation* pOP = NULL; - mal_pa_sample_spec ss; - mal_pa_channel_map cmap; - mal_pa_buffer_attr attr; +mal_result mal_context_uninit__pulse(mal_context* pContext) +{ + mal_assert(pContext != NULL); + mal_assert(pContext->backend == mal_backend_pulseaudio); - const mal_pa_sample_spec* pActualSS = NULL; - const mal_pa_channel_map* pActualCMap = NULL; - const mal_pa_buffer_attr* pActualAttr = NULL; +#ifndef MAL_NO_RUNTIME_LINKING + mal_dlclose(pContext->pulse.pulseSO); +#endif + return MAL_SUCCESS; +} +mal_result mal_context_init__pulse(mal_context* pContext) +{ + mal_assert(pContext != NULL); - pDevice->pulse.pMainLoop = ((mal_pa_mainloop_new_proc)pContext->pulse.pa_mainloop_new)(); - if (pDevice->pulse.pMainLoop == NULL) { - result = mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create main loop for device.", MAL_FAILED_TO_INIT_BACKEND); - goto on_error0; - } +#ifndef MAL_NO_RUNTIME_LINKING + // libpulse.so + const char* libpulseNames[] = { + "libpulse.so", + "libpulse.so.0" + }; - pDevice->pulse.pAPI = ((mal_pa_mainloop_get_api_proc)pContext->pulse.pa_mainloop_get_api)((mal_pa_mainloop*)pDevice->pulse.pMainLoop); - if (pDevice->pulse.pAPI == NULL) { - result = mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to retrieve PulseAudio main loop.", MAL_FAILED_TO_INIT_BACKEND); - goto on_error1; + for (size_t i = 0; i < mal_countof(libpulseNames); ++i) { + pContext->pulse.pulseSO = mal_dlopen(libpulseNames[i]); + if (pContext->pulse.pulseSO != NULL) { + break; + } } - pDevice->pulse.pPulseContext = ((mal_pa_context_new_proc)pContext->pulse.pa_context_new)((mal_pa_mainloop_api*)pDevice->pulse.pAPI, pContext->config.pulse.pApplicationName); - if (pDevice->pulse.pPulseContext == NULL) { - result = mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio context for device.", MAL_FAILED_TO_INIT_BACKEND); - goto on_error1; + if (pContext->pulse.pulseSO == NULL) { + return MAL_NO_BACKEND; } - error = ((mal_pa_context_connect_proc)pContext->pulse.pa_context_connect)((mal_pa_context*)pDevice->pulse.pPulseContext, pContext->config.pulse.pServerName, (pContext->config.pulse.tryAutoSpawn) ? 0 : MAL_PA_CONTEXT_NOAUTOSPAWN, NULL); - if (error != MAL_PA_OK) { - result = mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio context.", mal_result_from_pulse(error)); - goto on_error2; - } + pContext->pulse.pa_mainloop_new = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_mainloop_new"); + pContext->pulse.pa_mainloop_free = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_mainloop_free"); + pContext->pulse.pa_mainloop_get_api = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_mainloop_get_api"); + pContext->pulse.pa_mainloop_iterate = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_mainloop_iterate"); + pContext->pulse.pa_mainloop_wakeup = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_mainloop_wakeup"); + pContext->pulse.pa_context_new = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_context_new"); + pContext->pulse.pa_context_unref = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_context_unref"); + pContext->pulse.pa_context_connect = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_context_connect"); + pContext->pulse.pa_context_disconnect = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_context_disconnect"); + pContext->pulse.pa_context_set_state_callback = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_context_set_state_callback"); + pContext->pulse.pa_context_get_state = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_context_get_state"); + pContext->pulse.pa_context_get_sink_info_list = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_context_get_sink_info_list"); + pContext->pulse.pa_context_get_source_info_list = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_context_get_source_info_list"); + pContext->pulse.pa_context_get_sink_info_by_name = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_context_get_sink_info_by_name"); + pContext->pulse.pa_context_get_source_info_by_name = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_context_get_source_info_by_name"); + pContext->pulse.pa_operation_unref = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_operation_unref"); + pContext->pulse.pa_operation_get_state = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_operation_get_state"); + pContext->pulse.pa_channel_map_init_extend = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_channel_map_init_extend"); + pContext->pulse.pa_channel_map_valid = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_channel_map_valid"); + pContext->pulse.pa_channel_map_compatible = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_channel_map_compatible"); + pContext->pulse.pa_stream_new = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_new"); + pContext->pulse.pa_stream_unref = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_unref"); + pContext->pulse.pa_stream_connect_playback = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_connect_playback"); + pContext->pulse.pa_stream_connect_record = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_connect_record"); + pContext->pulse.pa_stream_disconnect = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_disconnect"); + pContext->pulse.pa_stream_get_state = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_get_state"); + pContext->pulse.pa_stream_get_sample_spec = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_get_sample_spec"); + pContext->pulse.pa_stream_get_channel_map = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_get_channel_map"); + pContext->pulse.pa_stream_get_buffer_attr = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_get_buffer_attr"); + pContext->pulse.pa_stream_get_device_name = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_get_device_name"); + pContext->pulse.pa_stream_set_write_callback = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_set_write_callback"); + pContext->pulse.pa_stream_set_read_callback = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_set_read_callback"); + pContext->pulse.pa_stream_flush = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_flush"); + pContext->pulse.pa_stream_drain = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_drain"); + pContext->pulse.pa_stream_cork = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_cork"); + pContext->pulse.pa_stream_trigger = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_trigger"); + pContext->pulse.pa_stream_begin_write = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_begin_write"); + pContext->pulse.pa_stream_write = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_write"); + pContext->pulse.pa_stream_peek = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_peek"); + pContext->pulse.pa_stream_drop = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_drop"); +#else + // This strange assignment system is just for type safety. + mal_pa_mainloop_new_proc _pa_mainloop_new = pa_mainloop_new; + mal_pa_mainloop_free_proc _pa_mainloop_free = pa_mainloop_free; + mal_pa_mainloop_get_api_proc _pa_mainloop_get_api = pa_mainloop_get_api; + mal_pa_mainloop_iterate_proc _pa_mainloop_iterate = pa_mainloop_iterate; + mal_pa_mainloop_wakeup_proc _pa_mainloop_wakeup = pa_mainloop_wakeup; + mal_pa_context_new_proc _pa_context_new = pa_context_new; + mal_pa_context_unref_proc _pa_context_unref = pa_context_unref; + mal_pa_context_connect_proc _pa_context_connect = pa_context_connect; + mal_pa_context_disconnect_proc _pa_context_disconnect = pa_context_disconnect; + mal_pa_context_set_state_callback_proc _pa_context_set_state_callback = pa_context_set_state_callback; + mal_pa_context_get_state_proc _pa_context_get_state = pa_context_get_state; + mal_pa_context_get_sink_info_list_proc _pa_context_get_sink_info_list = pa_context_get_sink_info_list; + mal_pa_context_get_source_info_list_proc _pa_context_get_source_info_list = pa_context_get_source_info_list; + mal_pa_context_get_sink_info_by_name_proc _pa_context_get_sink_info_by_name = pa_context_get_sink_info_by_name; + mal_pa_context_get_source_info_by_name_proc _pa_context_get_source_info_by_name= pa_context_get_source_info_by_name; + mal_pa_operation_unref_proc _pa_operation_unref = pa_operation_unref; + mal_pa_operation_get_state_proc _pa_operation_get_state = pa_operation_get_state; + mal_pa_channel_map_init_extend_proc _pa_channel_map_init_extend = pa_channel_map_init_extend; + mal_pa_channel_map_valid_proc _pa_channel_map_valid = pa_channel_map_valid; + mal_pa_channel_map_compatible_proc _pa_channel_map_compatible = pa_channel_map_compatible; + mal_pa_stream_new_proc _pa_stream_new = pa_stream_new; + mal_pa_stream_unref_proc _pa_stream_unref = pa_stream_unref; + mal_pa_stream_connect_playback_proc _pa_stream_connect_playback = pa_stream_connect_playback; + mal_pa_stream_connect_record_proc _pa_stream_connect_record = pa_stream_connect_record; + mal_pa_stream_disconnect_proc _pa_stream_disconnect = pa_stream_disconnect; + mal_pa_stream_get_state_proc _pa_stream_get_state = pa_stream_get_state; + mal_pa_stream_get_sample_spec_proc _pa_stream_get_sample_spec = pa_stream_get_sample_spec; + mal_pa_stream_get_channel_map_proc _pa_stream_get_channel_map = pa_stream_get_channel_map; + mal_pa_stream_get_buffer_attr_proc _pa_stream_get_buffer_attr = pa_stream_get_buffer_attr; + mal_pa_stream_get_device_name_proc _pa_stream_get_device_name = pa_stream_get_device_name; + mal_pa_stream_set_write_callback_proc _pa_stream_set_write_callback = pa_stream_set_write_callback; + mal_pa_stream_set_read_callback_proc _pa_stream_set_read_callback = pa_stream_set_read_callback; + mal_pa_stream_flush_proc _pa_stream_flush = pa_stream_flush; + mal_pa_stream_drain_proc _pa_stream_drain = pa_stream_drain; + mal_pa_stream_cork_proc _pa_stream_cork = pa_stream_cork; + mal_pa_stream_trigger_proc _pa_stream_trigger = pa_stream_trigger; + mal_pa_stream_begin_write_proc _pa_stream_begin_write = pa_stream_begin_write; + mal_pa_stream_write_proc _pa_stream_write = pa_stream_write; + mal_pa_stream_peek_proc _pa_stream_peek = pa_stream_peek; + mal_pa_stream_drop_proc _pa_stream_drop = pa_stream_drop; + + pContext->pulse.pa_mainloop_new = (mal_proc)_pa_mainloop_new; + pContext->pulse.pa_mainloop_free = (mal_proc)_pa_mainloop_free; + pContext->pulse.pa_mainloop_get_api = (mal_proc)_pa_mainloop_get_api; + pContext->pulse.pa_mainloop_iterate = (mal_proc)_pa_mainloop_iterate; + pContext->pulse.pa_mainloop_wakeup = (mal_proc)_pa_mainloop_wakeup; + pContext->pulse.pa_context_new = (mal_proc)_pa_context_new; + pContext->pulse.pa_context_unref = (mal_proc)_pa_context_unref; + pContext->pulse.pa_context_connect = (mal_proc)_pa_context_connect; + pContext->pulse.pa_context_disconnect = (mal_proc)_pa_context_disconnect; + pContext->pulse.pa_context_set_state_callback = (mal_proc)_pa_context_set_state_callback; + pContext->pulse.pa_context_get_state = (mal_proc)_pa_context_get_state; + pContext->pulse.pa_context_get_sink_info_list = (mal_proc)_pa_context_get_sink_info_list; + pContext->pulse.pa_context_get_source_info_list = (mal_proc)_pa_context_get_source_info_list; + pContext->pulse.pa_context_get_sink_info_by_name = (mal_proc)_pa_context_get_sink_info_by_name; + pContext->pulse.pa_context_get_source_info_by_name = (mal_proc)_pa_context_get_source_info_by_name; + pContext->pulse.pa_operation_unref = (mal_proc)_pa_operation_unref; + pContext->pulse.pa_operation_get_state = (mal_proc)_pa_operation_get_state; + pContext->pulse.pa_channel_map_init_extend = (mal_proc)_pa_channel_map_init_extend; + pContext->pulse.pa_channel_map_valid = (mal_proc)_pa_channel_map_valid; + pContext->pulse.pa_channel_map_compatible = (mal_proc)_pa_channel_map_compatible; + pContext->pulse.pa_stream_new = (mal_proc)_pa_stream_new; + pContext->pulse.pa_stream_unref = (mal_proc)_pa_stream_unref; + pContext->pulse.pa_stream_connect_playback = (mal_proc)_pa_stream_connect_playback; + pContext->pulse.pa_stream_connect_record = (mal_proc)_pa_stream_connect_record; + pContext->pulse.pa_stream_disconnect = (mal_proc)_pa_stream_disconnect; + pContext->pulse.pa_stream_get_state = (mal_proc)_pa_stream_get_state; + pContext->pulse.pa_stream_get_sample_spec = (mal_proc)_pa_stream_get_sample_spec; + pContext->pulse.pa_stream_get_channel_map = (mal_proc)_pa_stream_get_channel_map; + pContext->pulse.pa_stream_get_buffer_attr = (mal_proc)_pa_stream_get_buffer_attr; + pContext->pulse.pa_stream_get_device_name = (mal_proc)_pa_stream_get_device_name; + pContext->pulse.pa_stream_set_write_callback = (mal_proc)_pa_stream_set_write_callback; + pContext->pulse.pa_stream_set_read_callback = (mal_proc)_pa_stream_set_read_callback; + pContext->pulse.pa_stream_flush = (mal_proc)_pa_stream_flush; + pContext->pulse.pa_stream_drain = (mal_proc)_pa_stream_drain; + pContext->pulse.pa_stream_cork = (mal_proc)_pa_stream_cork; + pContext->pulse.pa_stream_trigger = (mal_proc)_pa_stream_trigger; + pContext->pulse.pa_stream_begin_write = (mal_proc)_pa_stream_begin_write; + pContext->pulse.pa_stream_write = (mal_proc)_pa_stream_write; + pContext->pulse.pa_stream_peek = (mal_proc)_pa_stream_peek; + pContext->pulse.pa_stream_drop = (mal_proc)_pa_stream_drop; +#endif + pContext->onUninit = mal_context_uninit__pulse; + pContext->onDeviceIDEqual = mal_context_is_device_id_equal__pulse; + pContext->onEnumDevices = mal_context_enumerate_devices__pulse; + pContext->onGetDeviceInfo = mal_context_get_device_info__pulse; + pContext->onDeviceInit = mal_device_init__pulse; + pContext->onDeviceUninit = mal_device_uninit__pulse; + pContext->onDeviceStart = mal_device__start_backend__pulse; + pContext->onDeviceStop = mal_device__stop_backend__pulse; + pContext->onDeviceBreakMainLoop = mal_device__break_main_loop__pulse; + pContext->onDeviceMainLoop = mal_device__main_loop__pulse; - pDevice->pulse.pulseContextState = MAL_PA_CONTEXT_UNCONNECTED; - ((mal_pa_context_set_state_callback_proc)pContext->pulse.pa_context_set_state_callback)((mal_pa_context*)pDevice->pulse.pPulseContext, mal_pulse_device_state_callback, pDevice); + + // Although we have found the libpulse library, it doesn't necessarily mean PulseAudio is useable. We need to initialize + // and connect a dummy PulseAudio context to test PulseAudio's usability. + mal_pa_mainloop* pMainLoop = ((mal_pa_mainloop_new_proc)pContext->pulse.pa_mainloop_new)(); + if (pMainLoop == NULL) { + return MAL_NO_BACKEND; + } - // Wait for PulseAudio to get itself ready before returning. - for (;;) { - if (pDevice->pulse.pulseContextState == MAL_PA_CONTEXT_READY) { - break; - } else { - error = ((mal_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((mal_pa_mainloop*)pDevice->pulse.pMainLoop, 1, NULL); // 1 = block. - if (error < 0) { - result = mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] The PulseAudio main loop returned an error while connecting the PulseAudio context.", mal_result_from_pulse(error)); - goto on_error3; - } - continue; - } + mal_pa_mainloop_api* pAPI = ((mal_pa_mainloop_get_api_proc)pContext->pulse.pa_mainloop_get_api)(pMainLoop); + if (pAPI == NULL) { + ((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); + return MAL_NO_BACKEND; + } - // An error may have occurred. - if (pDevice->pulse.pulseContextState == MAL_PA_CONTEXT_FAILED || pDevice->pulse.pulseContextState == MAL_PA_CONTEXT_TERMINATED) { - result = mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while connecting the PulseAudio context.", MAL_ERROR); - goto on_error3; - } + mal_pa_context* pPulseContext = ((mal_pa_context_new_proc)pContext->pulse.pa_context_new)(pAPI, pContext->config.pulse.pApplicationName); + if (pPulseContext == NULL) { + ((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); + return MAL_NO_BACKEND; + } - error = ((mal_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((mal_pa_mainloop*)pDevice->pulse.pMainLoop, 1, NULL); - if (error < 0) { - result = mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] The PulseAudio main loop returned an error while connecting the PulseAudio context.", mal_result_from_pulse(error)); - goto on_error3; - } + int error = ((mal_pa_context_connect_proc)pContext->pulse.pa_context_connect)(pPulseContext, pContext->config.pulse.pServerName, 0, NULL); + if (error != MAL_PA_OK) { + ((mal_pa_context_unref_proc)pContext->pulse.pa_context_unref)(pPulseContext); + ((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); + return MAL_NO_BACKEND; } + ((mal_pa_context_disconnect_proc)pContext->pulse.pa_context_disconnect)(pPulseContext); + ((mal_pa_context_unref_proc)pContext->pulse.pa_context_unref)(pPulseContext); + ((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); + return MAL_SUCCESS; +} +#endif - if (type == mal_device_type_playback) { - pOP = ((mal_pa_context_get_sink_info_by_name_proc)pContext->pulse.pa_context_get_sink_info_by_name)((mal_pa_context*)pDevice->pulse.pPulseContext, dev, mal_device_sink_info_callback, &sinkInfo); - } else { - pOP = ((mal_pa_context_get_source_info_by_name_proc)pContext->pulse.pa_context_get_source_info_by_name)((mal_pa_context*)pDevice->pulse.pPulseContext, dev, mal_device_source_info_callback, &sourceInfo); - } - if (pOP != NULL) { - mal_device__wait_for_operation__pulse(pDevice, pOP); - ((mal_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); - } +/////////////////////////////////////////////////////////////////////////////// +// +// JACK Backend +// +/////////////////////////////////////////////////////////////////////////////// +#ifdef MAL_HAS_JACK +// It is assumed jack.h is available when compile-time linking is being used. +#ifdef MAL_NO_RUNTIME_LINKING +#include -#if 0 - mal_pa_sample_spec deviceSS; - mal_pa_channel_map deviceCMap; - if (type == mal_device_type_playback) { - deviceSS = sinkInfo.sample_spec; - deviceCMap = sinkInfo.channel_map; - } else { - deviceSS = sourceInfo.sample_spec; - deviceCMap = sourceInfo.channel_map; - } +typedef jack_nframes_t mal_jack_nframes_t; +typedef jack_options_t mal_jack_options_t; +typedef jack_status_t mal_jack_status_t; +typedef jack_client_t mal_jack_client_t; +typedef jack_port_t mal_jack_port_t; +typedef JackProcessCallback mal_JackProcessCallback; +typedef JackBufferSizeCallback mal_JackBufferSizeCallback; +typedef JackShutdownCallback mal_JackShutdownCallback; +#define MAL_JACK_DEFAULT_AUDIO_TYPE JACK_DEFAULT_AUDIO_TYPE +#define mal_JackNoStartServer JackNoStartServer +#define mal_JackPortIsInput JackPortIsInput +#define mal_JackPortIsOutput JackPortIsOutput +#define mal_JackPortIsPhysical JackPortIsPhysical +#else +typedef mal_uint32 mal_jack_nframes_t; +typedef int mal_jack_options_t; +typedef int mal_jack_status_t; +typedef struct mal_jack_client_t mal_jack_client_t; +typedef struct mal_jack_port_t mal_jack_port_t; +typedef int (* mal_JackProcessCallback) (mal_jack_nframes_t nframes, void* arg); +typedef int (* mal_JackBufferSizeCallback)(mal_jack_nframes_t nframes, void* arg); +typedef void (* mal_JackShutdownCallback) (void* arg); +#define MAL_JACK_DEFAULT_AUDIO_TYPE "32 bit float mono audio" +#define mal_JackNoStartServer 1 +#define mal_JackPortIsInput 1 +#define mal_JackPortIsOutput 2 +#define mal_JackPortIsPhysical 4 +#endif - if (pDevice->usingDefaultFormat) { - ss.format = deviceSS.format; - } else { - ss.format = mal_format_to_pulse(pConfig->format); - } - if (ss.format == MAL_PA_SAMPLE_INVALID) { - ss.format = MAL_PA_SAMPLE_S16LE; - } +typedef mal_jack_client_t* (* mal_jack_client_open_proc) (const char* client_name, mal_jack_options_t options, mal_jack_status_t* status, ...); +typedef int (* mal_jack_client_close_proc) (mal_jack_client_t* client); +typedef int (* mal_jack_client_name_size_proc) (); +typedef int (* mal_jack_set_process_callback_proc) (mal_jack_client_t* client, mal_JackProcessCallback process_callback, void* arg); +typedef int (* mal_jack_set_buffer_size_callback_proc)(mal_jack_client_t* client, mal_JackBufferSizeCallback bufsize_callback, void* arg); +typedef void (* mal_jack_on_shutdown_proc) (mal_jack_client_t* client, mal_JackShutdownCallback function, void* arg); +typedef mal_jack_nframes_t (* mal_jack_get_sample_rate_proc) (mal_jack_client_t* client); +typedef mal_jack_nframes_t (* mal_jack_get_buffer_size_proc) (mal_jack_client_t* client); +typedef const char** (* mal_jack_get_ports_proc) (mal_jack_client_t* client, const char* port_name_pattern, const char* type_name_pattern, unsigned long flags); +typedef int (* mal_jack_activate_proc) (mal_jack_client_t* client); +typedef int (* mal_jack_deactivate_proc) (mal_jack_client_t* client); +typedef int (* mal_jack_connect_proc) (mal_jack_client_t* client, const char* source_port, const char* destination_port); +typedef mal_jack_port_t* (* mal_jack_port_register_proc) (mal_jack_client_t* client, const char* port_name, const char* port_type, unsigned long flags, unsigned long buffer_size); +typedef const char* (* mal_jack_port_name_proc) (const mal_jack_port_t* port); +typedef void* (* mal_jack_port_get_buffer_proc) (mal_jack_port_t* port, mal_jack_nframes_t nframes); +typedef void (* mal_jack_free_proc) (void* ptr); - if (pDevice->usingDefaultChannels) { - ss.channels = deviceSS.channels; - } else { - ss.channels = pConfig->channels; - } +mal_result mal_context_open_client__jack(mal_context* pContext, mal_jack_client_t** ppClient) +{ + mal_assert(pContext != NULL); + mal_assert(ppClient != NULL); - if (pDevice->usingDefaultSampleRate) { - ss.rate = deviceSS.rate; - } else { - ss.rate = pConfig->sampleRate; + if (ppClient) { + *ppClient = NULL; } + size_t maxClientNameSize = ((mal_jack_client_name_size_proc)pContext->jack.jack_client_name_size)(); // Includes null terminator. - if (pDevice->usingDefaultChannelMap) { - cmap = deviceCMap; - } else { - cmap.channels = pConfig->channels; - for (mal_uint32 iChannel = 0; iChannel < pConfig->channels; ++iChannel) { - cmap.map[iChannel] = mal_channel_position_to_pulse(pConfig->channelMap[iChannel]); - } + char clientName[256]; + mal_strncpy_s(clientName, mal_min(sizeof(clientName), maxClientNameSize), (pContext->config.jack.pClientName != NULL) ? pContext->config.jack.pClientName : "mini_al", (size_t)-1); - if (((mal_pa_channel_map_valid_proc)pContext->pulse.pa_channel_map_valid)(&cmap) == 0 || ((mal_pa_channel_map_compatible_proc)pContext->pulse.pa_channel_map_compatible)(&cmap, &ss) == 0) { - ((mal_pa_channel_map_init_extend_proc)pContext->pulse.pa_channel_map_init_extend)(&cmap, ss.channels, MAL_PA_CHANNEL_MAP_DEFAULT); // The channel map is invalid, so just fall back to the default. - } + mal_jack_status_t status; + mal_jack_client_t* pClient = ((mal_jack_client_open_proc)pContext->jack.jack_client_open)(clientName, (pContext->config.jack.tryStartServer) ? 0 : mal_JackNoStartServer, &status, NULL); + if (pClient == NULL) { + return MAL_FAILED_TO_OPEN_BACKEND_DEVICE; } -#else - if (type == mal_device_type_playback) { - ss = sinkInfo.sample_spec; - cmap = sinkInfo.channel_map; - } else { - ss = sourceInfo.sample_spec; - cmap = sourceInfo.channel_map; + + if (ppClient) { + *ppClient = pClient; } -#endif - // If using the default buffer size try to find an appropriate default. - if (pDevice->usingDefaultBufferSize) { - // CPU speed is a factor to consider when determine how large of a buffer we need. - float fCPUSpeed = mal_calculate_cpu_speed_factor(); + return MAL_SUCCESS; +} - // In my testing, capture seems to have worse latency than playback for some reason. - float fType; - if (type == mal_device_type_playback) { - fType = 1.0f; - } else { - fType = 2.0f; - } +mal_bool32 mal_context_is_device_id_equal__jack(mal_context* pContext, const mal_device_id* pID0, const mal_device_id* pID1) +{ + mal_assert(pContext != NULL); + mal_assert(pID0 != NULL); + mal_assert(pID1 != NULL); + (void)pContext; - // Backend tax. Need to fiddle with this. - float fBackend = 1.2; + return pID0->jack == pID1->jack; +} - bufferSizeInFrames = mal_calculate_default_buffer_size_in_frames(pConfig->performanceProfile, pConfig->sampleRate, fCPUSpeed*fType*fBackend); - } +mal_result mal_context_enumerate_devices__jack(mal_context* pContext, mal_enum_devices_callback_proc callback, void* pUserData) +{ + mal_assert(pContext != NULL); + mal_assert(callback != NULL); - attr.maxlength = bufferSizeInFrames * mal_get_bytes_per_sample(mal_format_from_pulse(ss.format))*ss.channels; - attr.tlength = attr.maxlength / pConfig->periods; - attr.prebuf = (mal_uint32)-1; - attr.minreq = attr.tlength; - attr.fragsize = attr.tlength; + mal_bool32 cbResult = MAL_TRUE; - char streamName[256]; - if (pConfig->pulse.pStreamName != NULL) { - mal_strncpy_s(streamName, sizeof(streamName), pConfig->pulse.pStreamName, (size_t)-1); - } else { - static int g_StreamCounter = 0; - mal_strcpy_s(streamName, sizeof(streamName), "mini_al:"); - mal_itoa_s(g_StreamCounter, streamName + 8, sizeof(streamName)-8, 10); // 8 = strlen("mini_al:") - g_StreamCounter += 1; + // Playback. + if (cbResult) { + mal_device_info deviceInfo; + mal_zero_object(&deviceInfo); + mal_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MAL_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + cbResult = callback(pContext, mal_device_type_playback, &deviceInfo, pUserData); } - pDevice->pulse.pStream = ((mal_pa_stream_new_proc)pContext->pulse.pa_stream_new)((mal_pa_context*)pDevice->pulse.pPulseContext, streamName, &ss, &cmap); - if (pDevice->pulse.pStream == NULL) { - result = mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio stream.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); - goto on_error3; + // Capture. + if (cbResult) { + mal_device_info deviceInfo; + mal_zero_object(&deviceInfo); + mal_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MAL_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + cbResult = callback(pContext, mal_device_type_capture, &deviceInfo, pUserData); } + return MAL_SUCCESS; +} +mal_result mal_context_get_device_info__jack(mal_context* pContext, mal_device_type deviceType, const mal_device_id* pDeviceID, mal_share_mode shareMode, mal_device_info* pDeviceInfo) +{ + mal_assert(pContext != NULL); - if (type == mal_device_type_playback) { - error = ((mal_pa_stream_connect_playback_proc)pContext->pulse.pa_stream_connect_playback)((mal_pa_stream*)pDevice->pulse.pStream, dev, &attr, MAL_PA_STREAM_START_CORKED, NULL, NULL); - } else { - error = ((mal_pa_stream_connect_record_proc)pContext->pulse.pa_stream_connect_record)((mal_pa_stream*)pDevice->pulse.pStream, dev, &attr, MAL_PA_STREAM_START_CORKED); - } + (void)pContext; + (void)shareMode; - if (error != MAL_PA_OK) { - result = mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio stream.", mal_result_from_pulse(error)); - goto on_error4; + if (pDeviceID != NULL && pDeviceID->jack != 0) { + return MAL_NO_DEVICE; // Don't know the device. } - while (((mal_pa_stream_get_state_proc)pContext->pulse.pa_stream_get_state)((mal_pa_stream*)pDevice->pulse.pStream) != MAL_PA_STREAM_READY) { - error = ((mal_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((mal_pa_mainloop*)pDevice->pulse.pMainLoop, 1, NULL); - if (error < 0) { - result = mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] The PulseAudio main loop returned an error while connecting the PulseAudio stream.", mal_result_from_pulse(error)); - goto on_error5; - } + // Name / Description + if (deviceType == mal_device_type_playback) { + mal_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MAL_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + } else { + mal_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MAL_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); } + // Jack only supports f32 and has a specific channel count and sample rate. + pDeviceInfo->formatCount = 1; + pDeviceInfo->formats[0] = mal_format_f32; - // Internal format. - pActualSS = ((mal_pa_stream_get_sample_spec_proc)pContext->pulse.pa_stream_get_sample_spec)((mal_pa_stream*)pDevice->pulse.pStream); - if (pActualSS != NULL) { - ss = *pActualSS; + // The channel count and sample rate can only be determined by opening the device. + mal_jack_client_t* pClient; + mal_result result = mal_context_open_client__jack(pContext, &pClient); + if (result != MAL_SUCCESS) { + return mal_context_post_error(pContext, NULL, MAL_LOG_LEVEL_ERROR, "[JACK] Failed to open client.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); } - pDevice->internalFormat = mal_format_from_pulse(ss.format); - pDevice->internalChannels = ss.channels; - pDevice->internalSampleRate = ss.rate; + pDeviceInfo->minSampleRate = ((mal_jack_get_sample_rate_proc)pContext->jack.jack_get_sample_rate)((mal_jack_client_t*)pClient); + pDeviceInfo->maxSampleRate = pDeviceInfo->minSampleRate; + + pDeviceInfo->minChannels = 0; + pDeviceInfo->maxChannels = 0; + const char** ppPorts = ((mal_jack_get_ports_proc)pContext->jack.jack_get_ports)((mal_jack_client_t*)pClient, NULL, NULL, mal_JackPortIsPhysical | ((deviceType == mal_device_type_playback) ? mal_JackPortIsInput : mal_JackPortIsOutput)); + if (ppPorts == NULL) { + ((mal_jack_client_close_proc)pContext->jack.jack_client_close)((mal_jack_client_t*)pClient); + return mal_context_post_error(pContext, NULL, MAL_LOG_LEVEL_ERROR, "[JACK] Failed to query physical ports.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + } - // Internal channel map. - pActualCMap = ((mal_pa_stream_get_channel_map_proc)pContext->pulse.pa_stream_get_channel_map)((mal_pa_stream*)pDevice->pulse.pStream); - if (pActualCMap != NULL) { - cmap = *pActualCMap; + while (ppPorts[pDeviceInfo->minChannels] != NULL) { + pDeviceInfo->minChannels += 1; + pDeviceInfo->maxChannels += 1; } - for (mal_uint32 iChannel = 0; iChannel < pDevice->internalChannels; ++iChannel) { - pDevice->internalChannelMap[iChannel] = mal_channel_position_from_pulse(cmap.map[iChannel]); - } + ((mal_jack_free_proc)pContext->jack.jack_free)((void*)ppPorts); + ((mal_jack_client_close_proc)pContext->jack.jack_client_close)((mal_jack_client_t*)pClient); + + return MAL_SUCCESS; +} - // Buffer size. - pActualAttr = ((mal_pa_stream_get_buffer_attr_proc)pContext->pulse.pa_stream_get_buffer_attr)((mal_pa_stream*)pDevice->pulse.pStream); - if (pActualAttr != NULL) { - attr = *pActualAttr; +void mal_device_uninit__jack(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + mal_context* pContext = pDevice->pContext; + mal_assert(pContext != NULL); + + if (pDevice->jack.pClient != NULL) { + ((mal_jack_client_close_proc)pContext->jack.jack_client_close)((mal_jack_client_t*)pDevice->jack.pClient); } +} - pDevice->bufferSizeInFrames = attr.maxlength / (mal_get_bytes_per_sample(pDevice->internalFormat)*pDevice->internalChannels); - pDevice->periods = attr.maxlength / attr.tlength; +void mal_device__jack_shutdown_callback(void* pUserData) +{ + // JACK died. Stop the device. + mal_device* pDevice = (mal_device*)pUserData; + mal_assert(pDevice != NULL); + mal_device_stop(pDevice); +} - // Grab the name of the device if we can. - dev = ((mal_pa_stream_get_device_name_proc)pContext->pulse.pa_stream_get_device_name)((mal_pa_stream*)pDevice->pulse.pStream); - if (dev != NULL) { - mal_pa_operation* pOP = NULL; - if (type == mal_device_type_playback) { - pOP = ((mal_pa_context_get_sink_info_by_name_proc)pContext->pulse.pa_context_get_sink_info_by_name)((mal_pa_context*)pDevice->pulse.pPulseContext, dev, mal_device_sink_name_callback, pDevice); - } else { - pOP = ((mal_pa_context_get_source_info_by_name_proc)pContext->pulse.pa_context_get_source_info_by_name)((mal_pa_context*)pDevice->pulse.pPulseContext, dev, mal_device_source_name_callback, pDevice); - } +int mal_device__jack_buffer_size_callback(mal_jack_nframes_t frameCount, void* pUserData) +{ + mal_device* pDevice = (mal_device*)pUserData; + mal_assert(pDevice != NULL); - if (pOP != NULL) { - mal_device__wait_for_operation__pulse(pDevice, pOP); - ((mal_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); - } + float* pNewBuffer = (float*)mal_realloc(pDevice->jack.pIntermediaryBuffer, frameCount * (pDevice->internalChannels*mal_get_bytes_per_sample(pDevice->internalFormat))); + if (pNewBuffer == NULL) { + return MAL_OUT_OF_MEMORY; } + pDevice->jack.pIntermediaryBuffer = pNewBuffer; + pDevice->bufferSizeInFrames = frameCount * pDevice->periods; - // Set callbacks for reading and writing data to/from the PulseAudio stream. - if (type == mal_device_type_playback) { - ((mal_pa_stream_set_write_callback_proc)pContext->pulse.pa_stream_set_write_callback)((mal_pa_stream*)pDevice->pulse.pStream, mal_pulse_device_write_callback, pDevice); - } else { - ((mal_pa_stream_set_read_callback_proc)pContext->pulse.pa_stream_set_read_callback)((mal_pa_stream*)pDevice->pulse.pStream, mal_pulse_device_read_callback, pDevice); - } + return 0; +} +int mal_device__jack_process_callback(mal_jack_nframes_t frameCount, void* pUserData) +{ + mal_device* pDevice = (mal_device*)pUserData; + mal_assert(pDevice != NULL); - pDevice->pulse.fragmentSizeInBytes = attr.tlength; + mal_context* pContext = pDevice->pContext; + mal_assert(pContext != NULL); - return MAL_SUCCESS; + if (pDevice->type == mal_device_type_playback) { + mal_device__read_frames_from_client(pDevice, frameCount, pDevice->jack.pIntermediaryBuffer); + // Channels need to be deinterleaved. + for (mal_uint32 iChannel = 0; iChannel < pDevice->internalChannels; ++iChannel) { + float* pDst = (float*)((mal_jack_port_get_buffer_proc)pContext->jack.jack_port_get_buffer)((mal_jack_port_t*)pDevice->jack.pPorts[iChannel], frameCount); + if (pDst != NULL) { + const float* pSrc = pDevice->jack.pIntermediaryBuffer + iChannel; + for (mal_jack_nframes_t iFrame = 0; iFrame < frameCount; ++iFrame) { + *pDst = *pSrc; -on_error5: ((mal_pa_stream_disconnect_proc)pContext->pulse.pa_stream_disconnect)((mal_pa_stream*)pDevice->pulse.pStream); -on_error4: ((mal_pa_stream_unref_proc)pContext->pulse.pa_stream_unref)((mal_pa_stream*)pDevice->pulse.pStream); -on_error3: ((mal_pa_context_disconnect_proc)pContext->pulse.pa_context_disconnect)((mal_pa_context*)pDevice->pulse.pPulseContext); -on_error2: ((mal_pa_context_unref_proc)pContext->pulse.pa_context_unref)((mal_pa_context*)pDevice->pulse.pPulseContext); -on_error1: ((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((mal_pa_mainloop*)pDevice->pulse.pMainLoop); -on_error0: - return result; -} + pDst += 1; + pSrc += pDevice->internalChannels; + } + } + } + } else { + // Channels need to be interleaved. + for (mal_uint32 iChannel = 0; iChannel < pDevice->internalChannels; ++iChannel) { + const float* pSrc = (const float*)((mal_jack_port_get_buffer_proc)pContext->jack.jack_port_get_buffer)((mal_jack_port_t*)pDevice->jack.pPorts[iChannel], frameCount); + if (pSrc != NULL) { + float* pDst = pDevice->jack.pIntermediaryBuffer + iChannel; + for (mal_jack_nframes_t iFrame = 0; iFrame < frameCount; ++iFrame) { + *pDst = *pSrc; + pDst += pDevice->internalChannels; + pSrc += 1; + } + } + } -void mal_pulse_operation_complete_callback(mal_pa_stream* pStream, int success, void* pUserData) -{ - mal_bool32* pIsSuccessful = (mal_bool32*)pUserData; - mal_assert(pIsSuccessful != NULL); + mal_device__send_frames_to_client(pDevice, frameCount, pDevice->jack.pIntermediaryBuffer); + } - *pIsSuccessful = (mal_bool32)success; + return 0; } -mal_result mal_device__cork_stream__pulse(mal_device* pDevice, int cork) +mal_result mal_device_init__jack(mal_context* pContext, mal_device_type type, const mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice) { - mal_context* pContext = pDevice->pContext; mal_assert(pContext != NULL); + mal_assert(pConfig != NULL); + mal_assert(pDevice != NULL); - mal_bool32 wasSuccessful = MAL_FALSE; - mal_pa_operation* pOP = ((mal_pa_stream_cork_proc)pContext->pulse.pa_stream_cork)((mal_pa_stream*)pDevice->pulse.pStream, cork, mal_pulse_operation_complete_callback, &wasSuccessful); - if (pOP == NULL) { - return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to cork PulseAudio stream.", (cork == 0) ? MAL_FAILED_TO_START_BACKEND_DEVICE : MAL_FAILED_TO_STOP_BACKEND_DEVICE); + (void)pContext; + (void)pConfig; + + // Only supporting default devices with JACK. + if (pDeviceID != NULL && pDeviceID->jack != 0) { + return MAL_NO_DEVICE; } - mal_result result = mal_device__wait_for_operation__pulse(pDevice, pOP); - ((mal_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); + // Open the client. + mal_result result = mal_context_open_client__jack(pContext, (mal_jack_client_t**)&pDevice->jack.pClient); if (result != MAL_SUCCESS) { - return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while waiting for the PulseAudio stream to cork.", result); + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[JACK] Failed to open client.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); } - if (!wasSuccessful) { - if (cork) { - return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to stop PulseAudio stream.", MAL_FAILED_TO_STOP_BACKEND_DEVICE); - } else { - return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to start PulseAudio stream.", MAL_FAILED_TO_START_BACKEND_DEVICE); - } + // Callbacks. + if (((mal_jack_set_process_callback_proc)pContext->jack.jack_set_process_callback)((mal_jack_client_t*)pDevice->jack.pClient, mal_device__jack_process_callback, pDevice) != 0) { + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[JACK] Failed to set process callback.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + } + if (((mal_jack_set_buffer_size_callback_proc)pContext->jack.jack_set_buffer_size_callback)((mal_jack_client_t*)pDevice->jack.pClient, mal_device__jack_buffer_size_callback, pDevice) != 0) { + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[JACK] Failed to set buffer size callback.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); } - return MAL_SUCCESS; -} + ((mal_jack_on_shutdown_proc)pContext->jack.jack_on_shutdown)((mal_jack_client_t*)pDevice->jack.pClient, mal_device__jack_shutdown_callback, pDevice); -mal_result mal_device__start_backend__pulse(mal_device* pDevice) -{ - mal_assert(pDevice != NULL); - mal_context* pContext = pDevice->pContext; - mal_assert(pContext != NULL); + // The format is always f32. + pDevice->internalFormat = mal_format_f32; - // For both playback and capture we need to uncork the stream. Afterwards, for playback we need to fill in an initial chunk - // of data, equal to the trigger length. That should then start actual playback. - mal_result result = mal_device__cork_stream__pulse(pDevice, 0); - if (result != MAL_SUCCESS) { - return result; + // A port is a channel. + unsigned long serverPortFlags; + unsigned long clientPortFlags; + if (type == mal_device_type_playback) { + serverPortFlags = mal_JackPortIsInput; + clientPortFlags = mal_JackPortIsOutput; + } else { + serverPortFlags = mal_JackPortIsOutput; + clientPortFlags = mal_JackPortIsInput; } - // A playback device is started by simply writing data to it. For capture we do nothing. - if (pDevice->type == mal_device_type_playback) { - // Playback. - mal_pulse_device_write_callback((mal_pa_stream*)pDevice->pulse.pStream, pDevice->pulse.fragmentSizeInBytes, pDevice); + const char** ppPorts = ((mal_jack_get_ports_proc)pContext->jack.jack_get_ports)((mal_jack_client_t*)pDevice->jack.pClient, NULL, NULL, mal_JackPortIsPhysical | serverPortFlags); + if (ppPorts == NULL) { + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[JACK] Failed to query physical ports.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + } - // Force an immediate start of the device just to be sure. - mal_pa_operation* pOP = ((mal_pa_stream_trigger_proc)pContext->pulse.pa_stream_trigger)((mal_pa_stream*)pDevice->pulse.pStream, NULL, NULL); - if (pOP != NULL) { - mal_device__wait_for_operation__pulse(pDevice, pOP); - ((mal_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); + pDevice->internalChannels = 0; + while (ppPorts[pDevice->internalChannels] != NULL) { + char name[64]; + if (type == mal_device_type_playback) { + mal_strcpy_s(name, sizeof(name), "playback"); + mal_itoa_s((int)pDevice->internalChannels, name+8, sizeof(name)-8, 10); // 8 = length of "playback" + } else { + mal_strcpy_s(name, sizeof(name), "capture"); + mal_itoa_s((int)pDevice->internalChannels, name+7, sizeof(name)-7, 10); // 7 = length of "capture" } - } else { - // Capture. Do nothing. + + pDevice->jack.pPorts[pDevice->internalChannels] = ((mal_jack_port_register_proc)pContext->jack.jack_port_register)((mal_jack_client_t*)pDevice->jack.pClient, name, MAL_JACK_DEFAULT_AUDIO_TYPE, clientPortFlags, 0); + if (pDevice->jack.pPorts[pDevice->internalChannels] == NULL) { + ((mal_jack_free_proc)pContext->jack.jack_free)((void*)ppPorts); + mal_device_uninit__jack(pDevice); + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[JACK] Failed to register ports.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + pDevice->internalChannels += 1; + } + + ((mal_jack_free_proc)pContext->jack.jack_free)((void*)ppPorts); + ppPorts = NULL; + + // We set the sample rate here, but apparently this can change. This is incompatible with mini_al, so changing sample rates will not be supported. + pDevice->internalSampleRate = ((mal_jack_get_sample_rate_proc)pContext->jack.jack_get_sample_rate)((mal_jack_client_t*)pDevice->jack.pClient); + + // I don't think the channel map can be queried, so just use defaults for now. + mal_get_standard_channel_map(mal_standard_channel_map_alsa, pDevice->internalChannels, pDevice->internalChannelMap); + + // The buffer size in frames can change. + pDevice->periods = 2; + pDevice->bufferSizeInFrames = ((mal_jack_get_buffer_size_proc)pContext->jack.jack_get_buffer_size)((mal_jack_client_t*)pDevice->jack.pClient) * pDevice->periods; + + // Initial allocation for the intermediary buffer. + pDevice->jack.pIntermediaryBuffer = (float*)mal_malloc((pDevice->bufferSizeInFrames/pDevice->periods)*(pDevice->internalChannels*mal_get_bytes_per_sample(pDevice->internalFormat))); + if (pDevice->jack.pIntermediaryBuffer == NULL) { + mal_device_uninit__jack(pDevice); + return MAL_OUT_OF_MEMORY; } return MAL_SUCCESS; } -mal_result mal_device__stop_backend__pulse(mal_device* pDevice) + +mal_result mal_device__start_backend__jack(mal_device* pDevice) { mal_assert(pDevice != NULL); mal_context* pContext = pDevice->pContext; mal_assert(pContext != NULL); - mal_result result = mal_device__cork_stream__pulse(pDevice, 1); - if (result != MAL_SUCCESS) { - return result; + int resultJACK = ((mal_jack_activate_proc)pContext->jack.jack_activate)((mal_jack_client_t*)pDevice->jack.pClient); + if (resultJACK != 0) { + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[JACK] Failed to activate the JACK client.", MAL_FAILED_TO_START_BACKEND_DEVICE); } - // For playback, buffers need to be flushed. For capture they need to be drained. - mal_bool32 wasSuccessful; - mal_pa_operation* pOP = NULL; + const char** ppServerPorts; if (pDevice->type == mal_device_type_playback) { - pOP = ((mal_pa_stream_flush_proc)pContext->pulse.pa_stream_flush)((mal_pa_stream*)pDevice->pulse.pStream, mal_pulse_operation_complete_callback, &wasSuccessful); + ppServerPorts = ((mal_jack_get_ports_proc)pContext->jack.jack_get_ports)((mal_jack_client_t*)pDevice->jack.pClient, NULL, NULL, mal_JackPortIsPhysical | mal_JackPortIsInput); } else { - pOP = ((mal_pa_stream_drain_proc)pContext->pulse.pa_stream_drain)((mal_pa_stream*)pDevice->pulse.pStream, mal_pulse_operation_complete_callback, &wasSuccessful); - } - - if (pOP == NULL) { - return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to flush buffers after stopping PulseAudio stream.", MAL_ERROR); + ppServerPorts = ((mal_jack_get_ports_proc)pContext->jack.jack_get_ports)((mal_jack_client_t*)pDevice->jack.pClient, NULL, NULL, mal_JackPortIsPhysical | mal_JackPortIsOutput); } - result = mal_device__wait_for_operation__pulse(pDevice, pOP); - ((mal_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); - - if (result != MAL_SUCCESS) { - return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while waiting for the PulseAudio stream to flush.", result); + if (ppServerPorts == NULL) { + ((mal_jack_deactivate_proc)pContext->jack.jack_deactivate)((mal_jack_client_t*)pDevice->jack.pClient); + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[JACK] Failed to retrieve physical ports.", MAL_ERROR); } - if (!wasSuccessful) { - return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to flush buffers after stopping PulseAudio stream.", MAL_ERROR); - } + for (size_t i = 0; ppServerPorts[i] != NULL; ++i) { + const char* pServerPort = ppServerPorts[i]; + mal_assert(pServerPort != NULL); - return MAL_SUCCESS; -} + const char* pClientPort = ((mal_jack_port_name_proc)pContext->jack.jack_port_name)((mal_jack_port_t*)pDevice->jack.pPorts[i]); + mal_assert(pClientPort != NULL); -mal_result mal_device__break_main_loop__pulse(mal_device* pDevice) -{ - mal_assert(pDevice != NULL); + if (pDevice->type == mal_device_type_playback) { + resultJACK = ((mal_jack_connect_proc)pContext->jack.jack_connect)((mal_jack_client_t*)pDevice->jack.pClient, pClientPort, pServerPort); + } else { + resultJACK = ((mal_jack_connect_proc)pContext->jack.jack_connect)((mal_jack_client_t*)pDevice->jack.pClient, pServerPort, pClientPort); + } - mal_context* pContext = pDevice->pContext; - mal_assert(pContext != NULL); + if (resultJACK != 0) { + ((mal_jack_free_proc)pContext->jack.jack_free)((void*)ppServerPorts); + ((mal_jack_deactivate_proc)pContext->jack.jack_deactivate)((mal_jack_client_t*)pDevice->jack.pClient); + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[JACK] Failed to connect ports.", MAL_ERROR); + } + } - pDevice->pulse.breakFromMainLoop = MAL_TRUE; - ((mal_pa_mainloop_wakeup_proc)pContext->pulse.pa_mainloop_wakeup)((mal_pa_mainloop*)pDevice->pulse.pMainLoop); + ((mal_jack_free_proc)pContext->jack.jack_free)((void*)ppServerPorts); return MAL_SUCCESS; } -mal_result mal_device__main_loop__pulse(mal_device* pDevice) +mal_result mal_device__stop_backend__jack(mal_device* pDevice) { mal_assert(pDevice != NULL); mal_context* pContext = pDevice->pContext; mal_assert(pContext != NULL); - pDevice->pulse.breakFromMainLoop = MAL_FALSE; - while (!pDevice->pulse.breakFromMainLoop) { - // Break from the main loop if the device isn't started anymore. Likely what's happened is the application - // has requested that the device be stopped. - if (!mal_device_is_started(pDevice)) { - break; - } - - int resultPA = ((mal_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((mal_pa_mainloop*)pDevice->pulse.pMainLoop, 1, NULL); - if (resultPA < 0) { - break; // Some error occurred. - } + if (((mal_jack_deactivate_proc)pContext->jack.jack_deactivate)((mal_jack_client_t*)pDevice->jack.pClient) != 0) { + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[JACK] An error occurred when deactivating the JACK client.", MAL_ERROR); + } + + mal_device__set_state(pDevice, MAL_STATE_STOPPED); + mal_stop_proc onStop = pDevice->onStop; + if (onStop) { + onStop(pDevice); } return MAL_SUCCESS; } -mal_result mal_context_uninit__pulse(mal_context* pContext) +mal_result mal_context_uninit__jack(mal_context* pContext) { mal_assert(pContext != NULL); - mal_assert(pContext->backend == mal_backend_pulseaudio); + mal_assert(pContext->backend == mal_backend_jack); #ifndef MAL_NO_RUNTIME_LINKING - mal_dlclose(pContext->pulse.pulseSO); + mal_dlclose(pContext->jack.jackSO); #endif return MAL_SUCCESS; } -mal_result mal_context_init__pulse(mal_context* pContext) +mal_result mal_context_init__jack(mal_context* pContext) { mal_assert(pContext != NULL); #ifndef MAL_NO_RUNTIME_LINKING - // libpulse.so - const char* libpulseNames[] = { - "libpulse.so", - "libpulse.so.0" + // libjack.so + const char* libjackNames[] = { +#ifdef MAL_WIN32 + "libjack.dll" +#else + "libjack.so", + "libjack.so.0" +#endif }; - for (size_t i = 0; i < mal_countof(libpulseNames); ++i) { - pContext->pulse.pulseSO = mal_dlopen(libpulseNames[i]); - if (pContext->pulse.pulseSO != NULL) { + for (size_t i = 0; i < mal_countof(libjackNames); ++i) { + pContext->jack.jackSO = mal_dlopen(libjackNames[i]); + if (pContext->jack.jackSO != NULL) { break; } } - if (pContext->pulse.pulseSO == NULL) { + if (pContext->jack.jackSO == NULL) { return MAL_NO_BACKEND; } - pContext->pulse.pa_mainloop_new = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_mainloop_new"); - pContext->pulse.pa_mainloop_free = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_mainloop_free"); - pContext->pulse.pa_mainloop_get_api = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_mainloop_get_api"); - pContext->pulse.pa_mainloop_iterate = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_mainloop_iterate"); - pContext->pulse.pa_mainloop_wakeup = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_mainloop_wakeup"); - pContext->pulse.pa_context_new = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_context_new"); - pContext->pulse.pa_context_unref = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_context_unref"); - pContext->pulse.pa_context_connect = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_context_connect"); - pContext->pulse.pa_context_disconnect = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_context_disconnect"); - pContext->pulse.pa_context_set_state_callback = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_context_set_state_callback"); - pContext->pulse.pa_context_get_state = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_context_get_state"); - pContext->pulse.pa_context_get_sink_info_list = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_context_get_sink_info_list"); - pContext->pulse.pa_context_get_source_info_list = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_context_get_source_info_list"); - pContext->pulse.pa_context_get_sink_info_by_name = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_context_get_sink_info_by_name"); - pContext->pulse.pa_context_get_source_info_by_name = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_context_get_source_info_by_name"); - pContext->pulse.pa_operation_unref = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_operation_unref"); - pContext->pulse.pa_operation_get_state = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_operation_get_state"); - pContext->pulse.pa_channel_map_init_extend = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_channel_map_init_extend"); - pContext->pulse.pa_channel_map_valid = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_channel_map_valid"); - pContext->pulse.pa_channel_map_compatible = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_channel_map_compatible"); - pContext->pulse.pa_stream_new = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_new"); - pContext->pulse.pa_stream_unref = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_unref"); - pContext->pulse.pa_stream_connect_playback = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_connect_playback"); - pContext->pulse.pa_stream_connect_record = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_connect_record"); - pContext->pulse.pa_stream_disconnect = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_disconnect"); - pContext->pulse.pa_stream_get_state = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_get_state"); - pContext->pulse.pa_stream_get_sample_spec = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_get_sample_spec"); - pContext->pulse.pa_stream_get_channel_map = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_get_channel_map"); - pContext->pulse.pa_stream_get_buffer_attr = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_get_buffer_attr"); - pContext->pulse.pa_stream_get_device_name = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_get_device_name"); - pContext->pulse.pa_stream_set_write_callback = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_set_write_callback"); - pContext->pulse.pa_stream_set_read_callback = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_set_read_callback"); - pContext->pulse.pa_stream_flush = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_flush"); - pContext->pulse.pa_stream_drain = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_drain"); - pContext->pulse.pa_stream_cork = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_cork"); - pContext->pulse.pa_stream_trigger = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_trigger"); - pContext->pulse.pa_stream_begin_write = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_begin_write"); - pContext->pulse.pa_stream_write = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_write"); - pContext->pulse.pa_stream_peek = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_peek"); - pContext->pulse.pa_stream_drop = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_drop"); + pContext->jack.jack_client_open = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_client_open"); + pContext->jack.jack_client_close = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_client_close"); + pContext->jack.jack_client_name_size = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_client_name_size"); + pContext->jack.jack_set_process_callback = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_set_process_callback"); + pContext->jack.jack_set_buffer_size_callback = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_set_buffer_size_callback"); + pContext->jack.jack_on_shutdown = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_on_shutdown"); + pContext->jack.jack_get_sample_rate = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_get_sample_rate"); + pContext->jack.jack_get_buffer_size = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_get_buffer_size"); + pContext->jack.jack_get_ports = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_get_ports"); + pContext->jack.jack_activate = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_activate"); + pContext->jack.jack_deactivate = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_deactivate"); + pContext->jack.jack_connect = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_connect"); + pContext->jack.jack_port_register = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_port_register"); + pContext->jack.jack_port_name = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_port_name"); + pContext->jack.jack_port_get_buffer = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_port_get_buffer"); + pContext->jack.jack_free = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_free"); #else - // This strange assignment system is just for type safety. - mal_pa_mainloop_new_proc _pa_mainloop_new = pa_mainloop_new; - mal_pa_mainloop_free_proc _pa_mainloop_free = pa_mainloop_free; - mal_pa_mainloop_get_api_proc _pa_mainloop_get_api = pa_mainloop_get_api; - mal_pa_mainloop_iterate_proc _pa_mainloop_iterate = pa_mainloop_iterate; - mal_pa_mainloop_wakeup_proc _pa_mainloop_wakeup = pa_mainloop_wakeup; - mal_pa_context_new_proc _pa_context_new = pa_context_new; - mal_pa_context_unref_proc _pa_context_unref = pa_context_unref; - mal_pa_context_connect_proc _pa_context_connect = pa_context_connect; - mal_pa_context_disconnect_proc _pa_context_disconnect = pa_context_disconnect; - mal_pa_context_set_state_callback_proc _pa_context_set_state_callback = pa_context_set_state_callback; - mal_pa_context_get_state_proc _pa_context_get_state = pa_context_get_state; - mal_pa_context_get_sink_info_list_proc _pa_context_get_sink_info_list = pa_context_get_sink_info_list; - mal_pa_context_get_source_info_list_proc _pa_context_get_source_info_list = pa_context_get_source_info_list; - mal_pa_context_get_sink_info_by_name_proc _pa_context_get_sink_info_by_name = pa_context_get_sink_info_by_name; - mal_pa_context_get_source_info_by_name_proc _pa_context_get_source_info_by_name= pa_context_get_source_info_by_name; - mal_pa_operation_unref_proc _pa_operation_unref = pa_operation_unref; - mal_pa_operation_get_state_proc _pa_operation_get_state = pa_operation_get_state; - mal_pa_channel_map_init_extend_proc _pa_channel_map_init_extend = pa_channel_map_init_extend; - mal_pa_channel_map_valid_proc _pa_channel_map_valid = pa_channel_map_valid; - mal_pa_channel_map_compatible_proc _pa_channel_map_compatible = pa_channel_map_compatible; - mal_pa_stream_new_proc _pa_stream_new = pa_stream_new; - mal_pa_stream_unref_proc _pa_stream_unref = pa_stream_unref; - mal_pa_stream_connect_playback_proc _pa_stream_connect_playback = pa_stream_connect_playback; - mal_pa_stream_connect_record_proc _pa_stream_connect_record = pa_stream_connect_record; - mal_pa_stream_disconnect_proc _pa_stream_disconnect = pa_stream_disconnect; - mal_pa_stream_get_state_proc _pa_stream_get_state = pa_stream_get_state; - mal_pa_stream_get_sample_spec_proc _pa_stream_get_sample_spec = pa_stream_get_sample_spec; - mal_pa_stream_get_channel_map_proc _pa_stream_get_channel_map = pa_stream_get_channel_map; - mal_pa_stream_get_buffer_attr_proc _pa_stream_get_buffer_attr = pa_stream_get_buffer_attr; - mal_pa_stream_get_device_name_proc _pa_stream_get_device_name = pa_stream_get_device_name; - mal_pa_stream_set_write_callback_proc _pa_stream_set_write_callback = pa_stream_set_write_callback; - mal_pa_stream_set_read_callback_proc _pa_stream_set_read_callback = pa_stream_set_read_callback; - mal_pa_stream_flush_proc _pa_stream_flush = pa_stream_flush; - mal_pa_stream_drain_proc _pa_stream_drain = pa_stream_drain; - mal_pa_stream_cork_proc _pa_stream_cork = pa_stream_cork; - mal_pa_stream_trigger_proc _pa_stream_trigger = pa_stream_trigger; - mal_pa_stream_begin_write_proc _pa_stream_begin_write = pa_stream_begin_write; - mal_pa_stream_write_proc _pa_stream_write = pa_stream_write; - mal_pa_stream_peek_proc _pa_stream_peek = pa_stream_peek; - mal_pa_stream_drop_proc _pa_stream_drop = pa_stream_drop; + // This strange assignment system is here just to ensure type safety of mini_al's function pointer + // types. If anything differs slightly the compiler should throw a warning. + mal_jack_client_open_proc _jack_client_open = jack_client_open; + mal_jack_client_close_proc _jack_client_close = jack_client_close; + mal_jack_client_name_size_proc _jack_client_name_size = jack_client_name_size; + mal_jack_set_process_callback_proc _jack_set_process_callback = jack_set_process_callback; + mal_jack_set_buffer_size_callback_proc _jack_set_buffer_size_callback = jack_set_buffer_size_callback; + mal_jack_on_shutdown_proc _jack_on_shutdown = jack_on_shutdown; + mal_jack_get_sample_rate_proc _jack_get_sample_rate = jack_get_sample_rate; + mal_jack_get_buffer_size_proc _jack_get_buffer_size = jack_get_buffer_size; + mal_jack_get_ports_proc _jack_get_ports = jack_get_ports; + mal_jack_activate_proc _jack_activate = jack_activate; + mal_jack_deactivate_proc _jack_deactivate = jack_deactivate; + mal_jack_connect_proc _jack_connect = jack_connect; + mal_jack_port_register_proc _jack_port_register = jack_port_register; + mal_jack_port_name_proc _jack_port_name = jack_port_name; + mal_jack_port_get_buffer_proc _jack_port_get_buffer = jack_port_get_buffer; + mal_jack_free_proc _jack_free = jack_free; + + pContext->jack.jack_client_open = (mal_proc)_jack_client_open; + pContext->jack.jack_client_close = (mal_proc)_jack_client_close; + pContext->jack.jack_client_name_size = (mal_proc)_jack_client_name_size; + pContext->jack.jack_set_process_callback = (mal_proc)_jack_set_process_callback; + pContext->jack.jack_set_buffer_size_callback = (mal_proc)_jack_set_buffer_size_callback; + pContext->jack.jack_on_shutdown = (mal_proc)_jack_on_shutdown; + pContext->jack.jack_get_sample_rate = (mal_proc)_jack_get_sample_rate; + pContext->jack.jack_get_buffer_size = (mal_proc)_jack_get_buffer_size; + pContext->jack.jack_get_ports = (mal_proc)_jack_get_ports; + pContext->jack.jack_activate = (mal_proc)_jack_activate; + pContext->jack.jack_deactivate = (mal_proc)_jack_deactivate; + pContext->jack.jack_connect = (mal_proc)_jack_connect; + pContext->jack.jack_port_register = (mal_proc)_jack_port_register; + pContext->jack.jack_port_name = (mal_proc)_jack_port_name; + pContext->jack.jack_port_get_buffer = (mal_proc)_jack_port_get_buffer; + pContext->jack.jack_free = (mal_proc)_jack_free; +#endif + + pContext->isBackendAsynchronous = MAL_TRUE; + + pContext->onUninit = mal_context_uninit__jack; + pContext->onDeviceIDEqual = mal_context_is_device_id_equal__jack; + pContext->onEnumDevices = mal_context_enumerate_devices__jack; + pContext->onGetDeviceInfo = mal_context_get_device_info__jack; + pContext->onDeviceInit = mal_device_init__jack; + pContext->onDeviceUninit = mal_device_uninit__jack; + pContext->onDeviceStart = mal_device__start_backend__jack; + pContext->onDeviceStop = mal_device__stop_backend__jack; + + + // Getting here means the JACK library is installed, but it doesn't necessarily mean it's usable. We need to quickly test this by connecting + // a temporary client. + mal_jack_client_t* pDummyClient; + mal_result result = mal_context_open_client__jack(pContext, &pDummyClient); + if (result != MAL_SUCCESS) { + return MAL_NO_BACKEND; + } + + ((mal_jack_client_close_proc)pContext->jack.jack_client_close)((mal_jack_client_t*)pDummyClient); + return MAL_SUCCESS; +} +#endif // JACK + + + +/////////////////////////////////////////////////////////////////////////////// +// +// Core Audio Backend +// +/////////////////////////////////////////////////////////////////////////////// +#ifdef MAL_HAS_COREAUDIO +#include + +#if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1 + #define MAL_APPLE_MOBILE +#else + #define MAL_APPLE_DESKTOP +#endif + +#if defined(MAL_APPLE_DESKTOP) +#include +#else +#include +#endif + +#include + +// CoreFoundation +typedef Boolean (* mal_CFStringGetCString_proc)(CFStringRef theString, char* buffer, CFIndex bufferSize, CFStringEncoding encoding); - pContext->pulse.pa_mainloop_new = (mal_proc)_pa_mainloop_new; - pContext->pulse.pa_mainloop_free = (mal_proc)_pa_mainloop_free; - pContext->pulse.pa_mainloop_get_api = (mal_proc)_pa_mainloop_get_api; - pContext->pulse.pa_mainloop_iterate = (mal_proc)_pa_mainloop_iterate; - pContext->pulse.pa_mainloop_wakeup = (mal_proc)_pa_mainloop_wakeup; - pContext->pulse.pa_context_new = (mal_proc)_pa_context_new; - pContext->pulse.pa_context_unref = (mal_proc)_pa_context_unref; - pContext->pulse.pa_context_connect = (mal_proc)_pa_context_connect; - pContext->pulse.pa_context_disconnect = (mal_proc)_pa_context_disconnect; - pContext->pulse.pa_context_set_state_callback = (mal_proc)_pa_context_set_state_callback; - pContext->pulse.pa_context_get_state = (mal_proc)_pa_context_get_state; - pContext->pulse.pa_context_get_sink_info_list = (mal_proc)_pa_context_get_sink_info_list; - pContext->pulse.pa_context_get_source_info_list = (mal_proc)_pa_context_get_source_info_list; - pContext->pulse.pa_context_get_sink_info_by_name = (mal_proc)_pa_context_get_sink_info_by_name; - pContext->pulse.pa_context_get_source_info_by_name = (mal_proc)_pa_context_get_source_info_by_name; - pContext->pulse.pa_operation_unref = (mal_proc)_pa_operation_unref; - pContext->pulse.pa_operation_get_state = (mal_proc)_pa_operation_get_state; - pContext->pulse.pa_channel_map_init_extend = (mal_proc)_pa_channel_map_init_extend; - pContext->pulse.pa_channel_map_valid = (mal_proc)_pa_channel_map_valid; - pContext->pulse.pa_channel_map_compatible = (mal_proc)_pa_channel_map_compatible; - pContext->pulse.pa_stream_new = (mal_proc)_pa_stream_new; - pContext->pulse.pa_stream_unref = (mal_proc)_pa_stream_unref; - pContext->pulse.pa_stream_connect_playback = (mal_proc)_pa_stream_connect_playback; - pContext->pulse.pa_stream_connect_record = (mal_proc)_pa_stream_connect_record; - pContext->pulse.pa_stream_disconnect = (mal_proc)_pa_stream_disconnect; - pContext->pulse.pa_stream_get_state = (mal_proc)_pa_stream_get_state; - pContext->pulse.pa_stream_get_sample_spec = (mal_proc)_pa_stream_get_sample_spec; - pContext->pulse.pa_stream_get_channel_map = (mal_proc)_pa_stream_get_channel_map; - pContext->pulse.pa_stream_get_buffer_attr = (mal_proc)_pa_stream_get_buffer_attr; - pContext->pulse.pa_stream_get_device_name = (mal_proc)_pa_stream_get_device_name; - pContext->pulse.pa_stream_set_write_callback = (mal_proc)_pa_stream_set_write_callback; - pContext->pulse.pa_stream_set_read_callback = (mal_proc)_pa_stream_set_read_callback; - pContext->pulse.pa_stream_flush = (mal_proc)_pa_stream_flush; - pContext->pulse.pa_stream_drain = (mal_proc)_pa_stream_drain; - pContext->pulse.pa_stream_cork = (mal_proc)_pa_stream_cork; - pContext->pulse.pa_stream_trigger = (mal_proc)_pa_stream_trigger; - pContext->pulse.pa_stream_begin_write = (mal_proc)_pa_stream_begin_write; - pContext->pulse.pa_stream_write = (mal_proc)_pa_stream_write; - pContext->pulse.pa_stream_peek = (mal_proc)_pa_stream_peek; - pContext->pulse.pa_stream_drop = (mal_proc)_pa_stream_drop; +// CoreAudio +#if defined(MAL_APPLE_DESKTOP) +typedef OSStatus (* mal_AudioObjectGetPropertyData_proc)(AudioObjectID inObjectID, const AudioObjectPropertyAddress* inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32* ioDataSize, void* outData); +typedef OSStatus (* mal_AudioObjectGetPropertyDataSize_proc)(AudioObjectID inObjectID, const AudioObjectPropertyAddress* inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32* outDataSize); +typedef OSStatus (* mal_AudioObjectSetPropertyData_proc)(AudioObjectID inObjectID, const AudioObjectPropertyAddress* inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, const void* inData); #endif - pContext->onUninit = mal_context_uninit__pulse; - pContext->onDeviceIDEqual = mal_context_is_device_id_equal__pulse; - pContext->onEnumDevices = mal_context_enumerate_devices__pulse; - pContext->onGetDeviceInfo = mal_context_get_device_info__pulse; - pContext->onDeviceInit = mal_device_init__pulse; - pContext->onDeviceUninit = mal_device_uninit__pulse; - pContext->onDeviceStart = mal_device__start_backend__pulse; - pContext->onDeviceStop = mal_device__stop_backend__pulse; - pContext->onDeviceBreakMainLoop = mal_device__break_main_loop__pulse; - pContext->onDeviceMainLoop = mal_device__main_loop__pulse; +// AudioToolbox +typedef AudioComponent (* mal_AudioComponentFindNext_proc)(AudioComponent inComponent, const AudioComponentDescription* inDesc); +typedef OSStatus (* mal_AudioComponentInstanceDispose_proc)(AudioComponentInstance inInstance); +typedef OSStatus (* mal_AudioComponentInstanceNew_proc)(AudioComponent inComponent, AudioComponentInstance* outInstance); +typedef OSStatus (* mal_AudioOutputUnitStart_proc)(AudioUnit inUnit); +typedef OSStatus (* mal_AudioOutputUnitStop_proc)(AudioUnit inUnit); +typedef OSStatus (* mal_AudioUnitAddPropertyListener_proc)(AudioUnit inUnit, AudioUnitPropertyID inID, AudioUnitPropertyListenerProc inProc, void* inProcUserData); +typedef OSStatus (* mal_AudioUnitGetProperty_proc)(AudioUnit inUnit, AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, void* outData, UInt32* ioDataSize); +typedef OSStatus (* mal_AudioUnitSetProperty_proc)(AudioUnit inUnit, AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, const void* inData, UInt32 inDataSize); +typedef OSStatus (* mal_AudioUnitInitialize_proc)(AudioUnit inUnit); +typedef OSStatus (* mal_AudioUnitRender_proc)(AudioUnit inUnit, AudioUnitRenderActionFlags* ioActionFlags, const AudioTimeStamp* inTimeStamp, UInt32 inOutputBusNumber, UInt32 inNumberFrames, AudioBufferList* ioData); - - // Although we have found the libpulse library, it doesn't necessarily mean PulseAudio is useable. We need to initialize - // and connect a dummy PulseAudio context to test PulseAudio's usability. - mal_pa_mainloop* pMainLoop = ((mal_pa_mainloop_new_proc)pContext->pulse.pa_mainloop_new)(); - if (pMainLoop == NULL) { - return MAL_NO_BACKEND; + +#define MAL_COREAUDIO_OUTPUT_BUS 0 +#define MAL_COREAUDIO_INPUT_BUS 1 + + +// Core Audio +// +// So far, Core Audio has been the worst backend to work with due to being both unintuitive and having almost no documentation +// apart from comments in the headers (which admittedly are quite good). For my own purposes, and for anybody out there whose +// needing to figure out how this darn thing works, I'm going to outline a few things here. +// +// Since mini_al is a fairly low-level API, one of the things it needs is control over specific devices, and it needs to be +// able to identify whether or not it can be used as playback and/or capture. The AudioObject API is the only one I've seen +// that supports this level of detail. There was some public domain sample code I stumbled across that used the AudioComponent +// and AudioUnit APIs, but I couldn't see anything that gave low-level control over device selection and capabilities (the +// distinction between playback and capture in particular). Therefore, mini_al is using the AudioObject API. +// +// Most (all?) functions in the AudioObject API take a AudioObjectID as it's input. This is the device identifier. When +// retrieving global information, such as the device list, you use kAudioObjectSystemObject. When retrieving device-specific +// data, you pass in the ID for that device. In order to retrieve device-specific IDs you need to enumerate over each of the +// devices. This is done using the AudioObjectGetPropertyDataSize() and AudioObjectGetPropertyData() APIs which seem to be +// the central APIs for retrieving information about the system and specific devices. +// +// To use the AudioObjectGetPropertyData() API you need to use the notion of a property address. A property address is a +// structure with three variables and is used to identify which property you are getting or setting. The first is the "selector" +// which is basically the specific property that you're wanting to retrieve or set. The second is the "scope", which is +// typically set to kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyScopeInput for input-specific properties and +// kAudioObjectPropertyScopeOutput for output-specific properties. The last is the "element" which is always set to +// kAudioObjectPropertyElementMaster in mini_al's case. I don't know of any cases where this would be set to anything different. +// +// Back to the earlier issue of device retrieval, you first use the AudioObjectGetPropertyDataSize() API to retrieve the size +// of the raw data which is just a list of AudioDeviceID's. You use the kAudioObjectSystemObject AudioObjectID, and a property +// address with the kAudioHardwarePropertyDevices selector and the kAudioObjectPropertyScopeGlobal scope. Once you have the +// size, allocate a block of memory of that size and then call AudioObjectGetPropertyData(). The data is just a list of +// AudioDeviceID's so just do "dataSize/sizeof(AudioDeviceID)" to know the device count. + +mal_result mal_result_from_OSStatus(OSStatus status) +{ + switch (status) + { + case noErr: return MAL_SUCCESS; + #if defined(MAL_APPLE_DESKTOP) + case kAudioHardwareNotRunningError: return MAL_DEVICE_NOT_STARTED; + case kAudioHardwareUnspecifiedError: return MAL_ERROR; + case kAudioHardwareUnknownPropertyError: return MAL_INVALID_ARGS; + case kAudioHardwareBadPropertySizeError: return MAL_INVALID_OPERATION; + case kAudioHardwareIllegalOperationError: return MAL_INVALID_OPERATION; + case kAudioHardwareBadObjectError: return MAL_INVALID_ARGS; + case kAudioHardwareBadDeviceError: return MAL_INVALID_ARGS; + case kAudioHardwareBadStreamError: return MAL_INVALID_ARGS; + case kAudioHardwareUnsupportedOperationError: return MAL_INVALID_OPERATION; + case kAudioDeviceUnsupportedFormatError: return MAL_FORMAT_NOT_SUPPORTED; + case kAudioDevicePermissionsError: return MAL_ACCESS_DENIED; + #endif + default: return MAL_ERROR; + } +} + +#if 0 +mal_channel mal_channel_from_AudioChannelBitmap(AudioChannelBitmap bit) +{ + switch (bit) + { + case kAudioChannelBit_Left: return MAL_CHANNEL_LEFT; + case kAudioChannelBit_Right: return MAL_CHANNEL_RIGHT; + case kAudioChannelBit_Center: return MAL_CHANNEL_FRONT_CENTER; + case kAudioChannelBit_LFEScreen: return MAL_CHANNEL_LFE; + case kAudioChannelBit_LeftSurround: return MAL_CHANNEL_BACK_LEFT; + case kAudioChannelBit_RightSurround: return MAL_CHANNEL_BACK_RIGHT; + case kAudioChannelBit_LeftCenter: return MAL_CHANNEL_FRONT_LEFT_CENTER; + case kAudioChannelBit_RightCenter: return MAL_CHANNEL_FRONT_RIGHT_CENTER; + case kAudioChannelBit_CenterSurround: return MAL_CHANNEL_BACK_CENTER; + case kAudioChannelBit_LeftSurroundDirect: return MAL_CHANNEL_SIDE_LEFT; + case kAudioChannelBit_RightSurroundDirect: return MAL_CHANNEL_SIDE_RIGHT; + case kAudioChannelBit_TopCenterSurround: return MAL_CHANNEL_TOP_CENTER; + case kAudioChannelBit_VerticalHeightLeft: return MAL_CHANNEL_TOP_FRONT_LEFT; + case kAudioChannelBit_VerticalHeightCenter: return MAL_CHANNEL_TOP_FRONT_CENTER; + case kAudioChannelBit_VerticalHeightRight: return MAL_CHANNEL_TOP_FRONT_RIGHT; + case kAudioChannelBit_TopBackLeft: return MAL_CHANNEL_TOP_BACK_LEFT; + case kAudioChannelBit_TopBackCenter: return MAL_CHANNEL_TOP_BACK_CENTER; + case kAudioChannelBit_TopBackRight: return MAL_CHANNEL_TOP_BACK_RIGHT; + default: return MAL_CHANNEL_NONE; + } +} +#endif + +mal_channel mal_channel_from_AudioChannelLabel(AudioChannelLabel label) +{ + switch (label) + { + case kAudioChannelLabel_Unknown: return MAL_CHANNEL_NONE; + case kAudioChannelLabel_Unused: return MAL_CHANNEL_NONE; + case kAudioChannelLabel_UseCoordinates: return MAL_CHANNEL_NONE; + case kAudioChannelLabel_Left: return MAL_CHANNEL_LEFT; + case kAudioChannelLabel_Right: return MAL_CHANNEL_RIGHT; + case kAudioChannelLabel_Center: return MAL_CHANNEL_FRONT_CENTER; + case kAudioChannelLabel_LFEScreen: return MAL_CHANNEL_LFE; + case kAudioChannelLabel_LeftSurround: return MAL_CHANNEL_BACK_LEFT; + case kAudioChannelLabel_RightSurround: return MAL_CHANNEL_BACK_RIGHT; + case kAudioChannelLabel_LeftCenter: return MAL_CHANNEL_FRONT_LEFT_CENTER; + case kAudioChannelLabel_RightCenter: return MAL_CHANNEL_FRONT_RIGHT_CENTER; + case kAudioChannelLabel_CenterSurround: return MAL_CHANNEL_BACK_CENTER; + case kAudioChannelLabel_LeftSurroundDirect: return MAL_CHANNEL_SIDE_LEFT; + case kAudioChannelLabel_RightSurroundDirect: return MAL_CHANNEL_SIDE_RIGHT; + case kAudioChannelLabel_TopCenterSurround: return MAL_CHANNEL_TOP_CENTER; + case kAudioChannelLabel_VerticalHeightLeft: return MAL_CHANNEL_TOP_FRONT_LEFT; + case kAudioChannelLabel_VerticalHeightCenter: return MAL_CHANNEL_TOP_FRONT_CENTER; + case kAudioChannelLabel_VerticalHeightRight: return MAL_CHANNEL_TOP_FRONT_RIGHT; + case kAudioChannelLabel_TopBackLeft: return MAL_CHANNEL_TOP_BACK_LEFT; + case kAudioChannelLabel_TopBackCenter: return MAL_CHANNEL_TOP_BACK_CENTER; + case kAudioChannelLabel_TopBackRight: return MAL_CHANNEL_TOP_BACK_RIGHT; + case kAudioChannelLabel_RearSurroundLeft: return MAL_CHANNEL_BACK_LEFT; + case kAudioChannelLabel_RearSurroundRight: return MAL_CHANNEL_BACK_RIGHT; + case kAudioChannelLabel_LeftWide: return MAL_CHANNEL_SIDE_LEFT; + case kAudioChannelLabel_RightWide: return MAL_CHANNEL_SIDE_RIGHT; + case kAudioChannelLabel_LFE2: return MAL_CHANNEL_LFE; + case kAudioChannelLabel_LeftTotal: return MAL_CHANNEL_LEFT; + case kAudioChannelLabel_RightTotal: return MAL_CHANNEL_RIGHT; + case kAudioChannelLabel_HearingImpaired: return MAL_CHANNEL_NONE; + case kAudioChannelLabel_Narration: return MAL_CHANNEL_MONO; + case kAudioChannelLabel_Mono: return MAL_CHANNEL_MONO; + case kAudioChannelLabel_DialogCentricMix: return MAL_CHANNEL_MONO; + case kAudioChannelLabel_CenterSurroundDirect: return MAL_CHANNEL_BACK_CENTER; + case kAudioChannelLabel_Haptic: return MAL_CHANNEL_NONE; + case kAudioChannelLabel_Ambisonic_W: return MAL_CHANNEL_NONE; + case kAudioChannelLabel_Ambisonic_X: return MAL_CHANNEL_NONE; + case kAudioChannelLabel_Ambisonic_Y: return MAL_CHANNEL_NONE; + case kAudioChannelLabel_Ambisonic_Z: return MAL_CHANNEL_NONE; + case kAudioChannelLabel_MS_Mid: return MAL_CHANNEL_LEFT; + case kAudioChannelLabel_MS_Side: return MAL_CHANNEL_RIGHT; + case kAudioChannelLabel_XY_X: return MAL_CHANNEL_LEFT; + case kAudioChannelLabel_XY_Y: return MAL_CHANNEL_RIGHT; + case kAudioChannelLabel_HeadphonesLeft: return MAL_CHANNEL_LEFT; + case kAudioChannelLabel_HeadphonesRight: return MAL_CHANNEL_RIGHT; + case kAudioChannelLabel_ClickTrack: return MAL_CHANNEL_NONE; + case kAudioChannelLabel_ForeignLanguage: return MAL_CHANNEL_NONE; + case kAudioChannelLabel_Discrete: return MAL_CHANNEL_NONE; + case kAudioChannelLabel_Discrete_0: return MAL_CHANNEL_AUX_0; + case kAudioChannelLabel_Discrete_1: return MAL_CHANNEL_AUX_1; + case kAudioChannelLabel_Discrete_2: return MAL_CHANNEL_AUX_2; + case kAudioChannelLabel_Discrete_3: return MAL_CHANNEL_AUX_3; + case kAudioChannelLabel_Discrete_4: return MAL_CHANNEL_AUX_4; + case kAudioChannelLabel_Discrete_5: return MAL_CHANNEL_AUX_5; + case kAudioChannelLabel_Discrete_6: return MAL_CHANNEL_AUX_6; + case kAudioChannelLabel_Discrete_7: return MAL_CHANNEL_AUX_7; + case kAudioChannelLabel_Discrete_8: return MAL_CHANNEL_AUX_8; + case kAudioChannelLabel_Discrete_9: return MAL_CHANNEL_AUX_9; + case kAudioChannelLabel_Discrete_10: return MAL_CHANNEL_AUX_10; + case kAudioChannelLabel_Discrete_11: return MAL_CHANNEL_AUX_11; + case kAudioChannelLabel_Discrete_12: return MAL_CHANNEL_AUX_12; + case kAudioChannelLabel_Discrete_13: return MAL_CHANNEL_AUX_13; + case kAudioChannelLabel_Discrete_14: return MAL_CHANNEL_AUX_14; + case kAudioChannelLabel_Discrete_15: return MAL_CHANNEL_AUX_15; + case kAudioChannelLabel_Discrete_65535: return MAL_CHANNEL_NONE; + + #if 0 // Introduced in a later version of macOS. + case kAudioChannelLabel_HOA_ACN: return MAL_CHANNEL_NONE; + case kAudioChannelLabel_HOA_ACN_0: return MAL_CHANNEL_AUX_0; + case kAudioChannelLabel_HOA_ACN_1: return MAL_CHANNEL_AUX_1; + case kAudioChannelLabel_HOA_ACN_2: return MAL_CHANNEL_AUX_2; + case kAudioChannelLabel_HOA_ACN_3: return MAL_CHANNEL_AUX_3; + case kAudioChannelLabel_HOA_ACN_4: return MAL_CHANNEL_AUX_4; + case kAudioChannelLabel_HOA_ACN_5: return MAL_CHANNEL_AUX_5; + case kAudioChannelLabel_HOA_ACN_6: return MAL_CHANNEL_AUX_6; + case kAudioChannelLabel_HOA_ACN_7: return MAL_CHANNEL_AUX_7; + case kAudioChannelLabel_HOA_ACN_8: return MAL_CHANNEL_AUX_8; + case kAudioChannelLabel_HOA_ACN_9: return MAL_CHANNEL_AUX_9; + case kAudioChannelLabel_HOA_ACN_10: return MAL_CHANNEL_AUX_10; + case kAudioChannelLabel_HOA_ACN_11: return MAL_CHANNEL_AUX_11; + case kAudioChannelLabel_HOA_ACN_12: return MAL_CHANNEL_AUX_12; + case kAudioChannelLabel_HOA_ACN_13: return MAL_CHANNEL_AUX_13; + case kAudioChannelLabel_HOA_ACN_14: return MAL_CHANNEL_AUX_14; + case kAudioChannelLabel_HOA_ACN_15: return MAL_CHANNEL_AUX_15; + case kAudioChannelLabel_HOA_ACN_65024: return MAL_CHANNEL_NONE; + #endif + + default: return MAL_CHANNEL_NONE; + } +} + +mal_result mal_format_from_AudioStreamBasicDescription(const AudioStreamBasicDescription* pDescription, mal_format* pFormatOut) +{ + mal_assert(pDescription != NULL); + mal_assert(pFormatOut != NULL); + + *pFormatOut = mal_format_unknown; // Safety. + + // There's a few things mini_al doesn't support. + if (pDescription->mFormatID != kAudioFormatLinearPCM) { + return MAL_FORMAT_NOT_SUPPORTED; } - - mal_pa_mainloop_api* pAPI = ((mal_pa_mainloop_get_api_proc)pContext->pulse.pa_mainloop_get_api)(pMainLoop); - if (pAPI == NULL) { - ((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); - return MAL_NO_BACKEND; + + // We don't support any non-packed formats that are aligned high. + if ((pDescription->mFormatFlags & kLinearPCMFormatFlagIsAlignedHigh) != 0) { + return MAL_FORMAT_NOT_SUPPORTED; } - mal_pa_context* pPulseContext = ((mal_pa_context_new_proc)pContext->pulse.pa_context_new)(pAPI, pContext->config.pulse.pApplicationName); - if (pPulseContext == NULL) { - ((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); - return MAL_NO_BACKEND; + // Only supporting native-endian. + if ((mal_is_little_endian() && (pDescription->mFormatFlags & kAudioFormatFlagIsBigEndian) != 0) || (mal_is_big_endian() && (pDescription->mFormatFlags & kAudioFormatFlagIsBigEndian) == 0)) { + return MAL_FORMAT_NOT_SUPPORTED; } + + // We are not currently supporting non-interleaved formats (this will be added in a future version of mini_al). + //if ((pDescription->mFormatFlags & kAudioFormatFlagIsNonInterleaved) != 0) { + // return MAL_FORMAT_NOT_SUPPORTED; + //} - int error = ((mal_pa_context_connect_proc)pContext->pulse.pa_context_connect)(pPulseContext, pContext->config.pulse.pServerName, 0, NULL); - if (error != MAL_PA_OK) { - ((mal_pa_context_unref_proc)pContext->pulse.pa_context_unref)(pPulseContext); - ((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); - return MAL_NO_BACKEND; + if ((pDescription->mFormatFlags & kLinearPCMFormatFlagIsFloat) != 0) { + if (pDescription->mBitsPerChannel == 32) { + *pFormatOut = mal_format_f32; + return MAL_SUCCESS; + } + } else { + if ((pDescription->mFormatFlags & kLinearPCMFormatFlagIsSignedInteger) != 0) { + if (pDescription->mBitsPerChannel == 16) { + *pFormatOut = mal_format_s16; + return MAL_SUCCESS; + } else if (pDescription->mBitsPerChannel == 24) { + if (pDescription->mBytesPerFrame == (pDescription->mBitsPerChannel/8 * pDescription->mChannelsPerFrame)) { + *pFormatOut = mal_format_s24; + return MAL_SUCCESS; + } else { + if (pDescription->mBytesPerFrame/pDescription->mChannelsPerFrame == sizeof(mal_int32)) { + // TODO: Implement mal_format_s24_32. + //*pFormatOut = mal_format_s24_32; + //return MAL_SUCCESS; + return MAL_FORMAT_NOT_SUPPORTED; + } + } + } else if (pDescription->mBitsPerChannel == 32) { + *pFormatOut = mal_format_s32; + return MAL_SUCCESS; + } + } else { + if (pDescription->mBitsPerChannel == 8) { + *pFormatOut = mal_format_u8; + return MAL_SUCCESS; + } + } } - - ((mal_pa_context_disconnect_proc)pContext->pulse.pa_context_disconnect)(pPulseContext); - ((mal_pa_context_unref_proc)pContext->pulse.pa_context_unref)(pPulseContext); - ((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); - return MAL_SUCCESS; + + // Getting here means the format is not supported. + return MAL_FORMAT_NOT_SUPPORTED; } -#endif - - -/////////////////////////////////////////////////////////////////////////////// -// -// JACK Backend -// -/////////////////////////////////////////////////////////////////////////////// -#ifdef MAL_HAS_JACK - -// It is assumed jack.h is available when compile-time linking is being used. -#ifdef MAL_NO_RUNTIME_LINKING -#include - -typedef jack_nframes_t mal_jack_nframes_t; -typedef jack_options_t mal_jack_options_t; -typedef jack_status_t mal_jack_status_t; -typedef jack_client_t mal_jack_client_t; -typedef jack_port_t mal_jack_port_t; -typedef JackProcessCallback mal_JackProcessCallback; -typedef JackBufferSizeCallback mal_JackBufferSizeCallback; -typedef JackShutdownCallback mal_JackShutdownCallback; -#define MAL_JACK_DEFAULT_AUDIO_TYPE JACK_DEFAULT_AUDIO_TYPE -#define mal_JackNoStartServer JackNoStartServer -#define mal_JackPortIsInput JackPortIsInput -#define mal_JackPortIsOutput JackPortIsOutput -#define mal_JackPortIsPhysical JackPortIsPhysical -#else -typedef mal_uint32 mal_jack_nframes_t; -typedef int mal_jack_options_t; -typedef int mal_jack_status_t; -typedef struct mal_jack_client_t mal_jack_client_t; -typedef struct mal_jack_port_t mal_jack_port_t; -typedef int (* mal_JackProcessCallback) (mal_jack_nframes_t nframes, void* arg); -typedef int (* mal_JackBufferSizeCallback)(mal_jack_nframes_t nframes, void* arg); -typedef void (* mal_JackShutdownCallback) (void* arg); -#define MAL_JACK_DEFAULT_AUDIO_TYPE "32 bit float mono audio" -#define mal_JackNoStartServer 1 -#define mal_JackPortIsInput 1 -#define mal_JackPortIsOutput 2 -#define mal_JackPortIsPhysical 4 -#endif - -typedef mal_jack_client_t* (* mal_jack_client_open_proc) (const char* client_name, mal_jack_options_t options, mal_jack_status_t* status, ...); -typedef int (* mal_jack_client_close_proc) (mal_jack_client_t* client); -typedef int (* mal_jack_client_name_size_proc) (); -typedef int (* mal_jack_set_process_callback_proc) (mal_jack_client_t* client, mal_JackProcessCallback process_callback, void* arg); -typedef int (* mal_jack_set_buffer_size_callback_proc)(mal_jack_client_t* client, mal_JackBufferSizeCallback bufsize_callback, void* arg); -typedef void (* mal_jack_on_shutdown_proc) (mal_jack_client_t* client, mal_JackShutdownCallback function, void* arg); -typedef mal_jack_nframes_t (* mal_jack_get_sample_rate_proc) (mal_jack_client_t* client); -typedef mal_jack_nframes_t (* mal_jack_get_buffer_size_proc) (mal_jack_client_t* client); -typedef const char** (* mal_jack_get_ports_proc) (mal_jack_client_t* client, const char* port_name_pattern, const char* type_name_pattern, unsigned long flags); -typedef int (* mal_jack_activate_proc) (mal_jack_client_t* client); -typedef int (* mal_jack_deactivate_proc) (mal_jack_client_t* client); -typedef int (* mal_jack_connect_proc) (mal_jack_client_t* client, const char* source_port, const char* destination_port); -typedef mal_jack_port_t* (* mal_jack_port_register_proc) (mal_jack_client_t* client, const char* port_name, const char* port_type, unsigned long flags, unsigned long buffer_size); -typedef const char* (* mal_jack_port_name_proc) (const mal_jack_port_t* port); -typedef void* (* mal_jack_port_get_buffer_proc) (mal_jack_port_t* port, mal_jack_nframes_t nframes); -typedef void (* mal_jack_free_proc) (void* ptr); -mal_result mal_context_open_client__jack(mal_context* pContext, mal_jack_client_t** ppClient) +#if defined(MAL_APPLE_DESKTOP) +mal_result mal_get_device_object_ids__coreaudio(mal_context* pContext, UInt32* pDeviceCount, AudioObjectID** ppDeviceObjectIDs) // NOTE: Free the returned buffer with mal_free(). { mal_assert(pContext != NULL); - mal_assert(ppClient != NULL); - - if (ppClient) { - *ppClient = NULL; - } - - size_t maxClientNameSize = ((mal_jack_client_name_size_proc)pContext->jack.jack_client_name_size)(); // Includes null terminator. + mal_assert(pDeviceCount != NULL); + mal_assert(ppDeviceObjectIDs != NULL); + (void)pContext; - char clientName[256]; - mal_strncpy_s(clientName, mal_min(sizeof(clientName), maxClientNameSize), (pContext->config.jack.pClientName != NULL) ? pContext->config.jack.pClientName : "mini_al", (size_t)-1); + // Safety. + *pDeviceCount = 0; + *ppDeviceObjectIDs = NULL; + + AudioObjectPropertyAddress propAddressDevices; + propAddressDevices.mSelector = kAudioHardwarePropertyDevices; + propAddressDevices.mScope = kAudioObjectPropertyScopeGlobal; + propAddressDevices.mElement = kAudioObjectPropertyElementMaster; - mal_jack_status_t status; - mal_jack_client_t* pClient = ((mal_jack_client_open_proc)pContext->jack.jack_client_open)(clientName, (pContext->config.jack.tryStartServer) ? 0 : mal_JackNoStartServer, &status, NULL); - if (pClient == NULL) { - return MAL_FAILED_TO_OPEN_BACKEND_DEVICE; + UInt32 deviceObjectsDataSize; + OSStatus status = ((mal_AudioObjectGetPropertyDataSize_proc)pContext->coreaudio.AudioObjectGetPropertyDataSize)(kAudioObjectSystemObject, &propAddressDevices, 0, NULL, &deviceObjectsDataSize); + if (status != noErr) { + return mal_result_from_OSStatus(status); } - - if (ppClient) { - *ppClient = pClient; + + AudioObjectID* pDeviceObjectIDs = (AudioObjectID*)mal_malloc(deviceObjectsDataSize); + if (pDeviceObjectIDs == NULL) { + return MAL_OUT_OF_MEMORY; } - + + status = ((mal_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(kAudioObjectSystemObject, &propAddressDevices, 0, NULL, &deviceObjectsDataSize, pDeviceObjectIDs); + if (status != noErr) { + mal_free(pDeviceObjectIDs); + return mal_result_from_OSStatus(status); + } + + *pDeviceCount = deviceObjectsDataSize / sizeof(AudioObjectID); + *ppDeviceObjectIDs = pDeviceObjectIDs; return MAL_SUCCESS; } -mal_bool32 mal_context_is_device_id_equal__jack(mal_context* pContext, const mal_device_id* pID0, const mal_device_id* pID1) +mal_result mal_get_AudioObject_uid_as_CFStringRef(mal_context* pContext, AudioObjectID objectID, CFStringRef* pUID) { mal_assert(pContext != NULL); - mal_assert(pID0 != NULL); - mal_assert(pID1 != NULL); - (void)pContext; - return pID0->jack == pID1->jack; + AudioObjectPropertyAddress propAddress; + propAddress.mSelector = kAudioDevicePropertyDeviceUID; + propAddress.mScope = kAudioObjectPropertyScopeGlobal; + propAddress.mElement = kAudioObjectPropertyElementMaster; + + UInt32 dataSize = sizeof(*pUID); + OSStatus status = ((mal_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(objectID, &propAddress, 0, NULL, &dataSize, pUID); + if (status != noErr) { + return mal_result_from_OSStatus(status); + } + + return MAL_SUCCESS; } -mal_result mal_context_enumerate_devices__jack(mal_context* pContext, mal_enum_devices_callback_proc callback, void* pUserData) +mal_result mal_get_AudioObject_uid(mal_context* pContext, AudioObjectID objectID, size_t bufferSize, char* bufferOut) { mal_assert(pContext != NULL); - mal_assert(callback != NULL); - - mal_bool32 cbResult = MAL_TRUE; - // Playback. - if (cbResult) { - mal_device_info deviceInfo; - mal_zero_object(&deviceInfo); - mal_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MAL_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); - cbResult = callback(pContext, mal_device_type_playback, &deviceInfo, pUserData); + CFStringRef uid; + mal_result result = mal_get_AudioObject_uid_as_CFStringRef(pContext, objectID, &uid); + if (result != MAL_SUCCESS) { + return result; } - - // Capture. - if (cbResult) { - mal_device_info deviceInfo; - mal_zero_object(&deviceInfo); - mal_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MAL_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); - cbResult = callback(pContext, mal_device_type_capture, &deviceInfo, pUserData); + + if (!((mal_CFStringGetCString_proc)pContext->coreaudio.CFStringGetCString)(uid, bufferOut, bufferSize, kCFStringEncodingUTF8)) { + return MAL_ERROR; } - + return MAL_SUCCESS; } -mal_result mal_context_get_device_info__jack(mal_context* pContext, mal_device_type deviceType, const mal_device_id* pDeviceID, mal_share_mode shareMode, mal_device_info* pDeviceInfo) +mal_result mal_get_AudioObject_name(mal_context* pContext, AudioObjectID objectID, size_t bufferSize, char* bufferOut) { mal_assert(pContext != NULL); - (void)pContext; - (void)shareMode; - - if (pDeviceID != NULL && pDeviceID->jack != 0) { - return MAL_NO_DEVICE; // Don't know the device. - } + AudioObjectPropertyAddress propAddress; + propAddress.mSelector = kAudioDevicePropertyDeviceNameCFString; + propAddress.mScope = kAudioObjectPropertyScopeGlobal; + propAddress.mElement = kAudioObjectPropertyElementMaster; - // Name / Description - if (deviceType == mal_device_type_playback) { - mal_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MAL_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); - } else { - mal_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MAL_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + CFStringRef deviceName = NULL; + UInt32 dataSize = sizeof(deviceName); + OSStatus status = ((mal_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(objectID, &propAddress, 0, NULL, &dataSize, &deviceName); + if (status != noErr) { + return mal_result_from_OSStatus(status); } - - // Jack only supports f32 and has a specific channel count and sample rate. - pDeviceInfo->formatCount = 1; - pDeviceInfo->formats[0] = mal_format_f32; - - // The channel count and sample rate can only be determined by opening the device. - mal_jack_client_t* pClient; - mal_result result = mal_context_open_client__jack(pContext, &pClient); - if (result != MAL_SUCCESS) { - return mal_context_post_error(pContext, NULL, MAL_LOG_LEVEL_ERROR, "[JACK] Failed to open client.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + + if (!((mal_CFStringGetCString_proc)pContext->coreaudio.CFStringGetCString)(deviceName, bufferOut, bufferSize, kCFStringEncodingUTF8)) { + return MAL_ERROR; } + + return MAL_SUCCESS; +} - pDeviceInfo->minSampleRate = ((mal_jack_get_sample_rate_proc)pContext->jack.jack_get_sample_rate)((mal_jack_client_t*)pClient); - pDeviceInfo->maxSampleRate = pDeviceInfo->minSampleRate; - - pDeviceInfo->minChannels = 0; - pDeviceInfo->maxChannels = 0; +mal_bool32 mal_does_AudioObject_support_scope(mal_context* pContext, AudioObjectID deviceObjectID, AudioObjectPropertyScope scope) +{ + mal_assert(pContext != NULL); - const char** ppPorts = ((mal_jack_get_ports_proc)pContext->jack.jack_get_ports)((mal_jack_client_t*)pClient, NULL, NULL, mal_JackPortIsPhysical | ((deviceType == mal_device_type_playback) ? mal_JackPortIsInput : mal_JackPortIsOutput)); - if (ppPorts == NULL) { - ((mal_jack_client_close_proc)pContext->jack.jack_client_close)((mal_jack_client_t*)pClient); - return mal_context_post_error(pContext, NULL, MAL_LOG_LEVEL_ERROR, "[JACK] Failed to query physical ports.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + // To know whether or not a device is an input device we need ot look at the stream configuration. If it has an output channel it's a + // playback device. + AudioObjectPropertyAddress propAddress; + propAddress.mSelector = kAudioDevicePropertyStreamConfiguration; + propAddress.mScope = scope; + propAddress.mElement = kAudioObjectPropertyElementMaster; + + UInt32 dataSize; + OSStatus status = ((mal_AudioObjectGetPropertyDataSize_proc)pContext->coreaudio.AudioObjectGetPropertyDataSize)(deviceObjectID, &propAddress, 0, NULL, &dataSize); + if (status != noErr) { + return MAL_FALSE; + } + + AudioBufferList* pBufferList = (AudioBufferList*)mal_malloc(dataSize); + if (pBufferList == NULL) { + return MAL_FALSE; // Out of memory. + } + + status = ((mal_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(deviceObjectID, &propAddress, 0, NULL, &dataSize, pBufferList); + if (status != noErr) { + mal_free(pBufferList); + return MAL_FALSE; } - while (ppPorts[pDeviceInfo->minChannels] != NULL) { - pDeviceInfo->minChannels += 1; - pDeviceInfo->maxChannels += 1; + mal_bool32 isSupported = MAL_FALSE; + if (pBufferList->mNumberBuffers > 0) { + isSupported = MAL_TRUE; } + + mal_free(pBufferList); + return isSupported; +} - ((mal_jack_free_proc)pContext->jack.jack_free)((void*)ppPorts); - ((mal_jack_client_close_proc)pContext->jack.jack_client_close)((mal_jack_client_t*)pClient); +mal_bool32 mal_does_AudioObject_support_playback(mal_context* pContext, AudioObjectID deviceObjectID) +{ + return mal_does_AudioObject_support_scope(pContext, deviceObjectID, kAudioObjectPropertyScopeOutput); +} - return MAL_SUCCESS; +mal_bool32 mal_does_AudioObject_support_capture(mal_context* pContext, AudioObjectID deviceObjectID) +{ + return mal_does_AudioObject_support_scope(pContext, deviceObjectID, kAudioObjectPropertyScopeInput); } -void mal_device_uninit__jack(mal_device* pDevice) +mal_result mal_get_AudioObject_stream_descriptions(mal_context* pContext, AudioObjectID deviceObjectID, mal_device_type deviceType, UInt32* pDescriptionCount, AudioStreamRangedDescription** ppDescriptions) // NOTE: Free the returned pointer with mal_free(). { - mal_assert(pDevice != NULL); - - mal_context* pContext = pDevice->pContext; mal_assert(pContext != NULL); - - if (pDevice->jack.pClient != NULL) { - ((mal_jack_client_close_proc)pContext->jack.jack_client_close)((mal_jack_client_t*)pDevice->jack.pClient); + mal_assert(pDescriptionCount != NULL); + mal_assert(ppDescriptions != NULL); + + // TODO: Experiment with kAudioStreamPropertyAvailablePhysicalFormats instead of (or in addition to) kAudioStreamPropertyAvailableVirtualFormats. My + // MacBook Pro uses s24/32 format, however, which mini_al does not currently support. + AudioObjectPropertyAddress propAddress; + propAddress.mSelector = kAudioStreamPropertyAvailableVirtualFormats; //kAudioStreamPropertyAvailablePhysicalFormats; + propAddress.mScope = (deviceType == mal_device_type_playback) ? kAudioObjectPropertyScopeOutput : kAudioObjectPropertyScopeInput; + propAddress.mElement = kAudioObjectPropertyElementMaster; + + UInt32 dataSize; + OSStatus status = ((mal_AudioObjectGetPropertyDataSize_proc)pContext->coreaudio.AudioObjectGetPropertyDataSize)(deviceObjectID, &propAddress, 0, NULL, &dataSize); + if (status != noErr) { + return mal_result_from_OSStatus(status); } + + AudioStreamRangedDescription* pDescriptions = (AudioStreamRangedDescription*)mal_malloc(dataSize); + if (pDescriptions == NULL) { + return MAL_OUT_OF_MEMORY; + } + + status = ((mal_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(deviceObjectID, &propAddress, 0, NULL, &dataSize, pDescriptions); + if (status != noErr) { + mal_free(pDescriptions); + return mal_result_from_OSStatus(status); + } + + *pDescriptionCount = dataSize / sizeof(*pDescriptions); + *ppDescriptions = pDescriptions; + return MAL_SUCCESS; } -void mal_device__jack_shutdown_callback(void* pUserData) -{ - // JACK died. Stop the device. - mal_device* pDevice = (mal_device*)pUserData; - mal_assert(pDevice != NULL); - mal_device_stop(pDevice); -} -int mal_device__jack_buffer_size_callback(mal_jack_nframes_t frameCount, void* pUserData) +mal_result mal_get_AudioObject_channel_layout(mal_context* pContext, AudioObjectID deviceObjectID, mal_device_type deviceType, AudioChannelLayout** ppChannelLayout) // NOTE: Free the returned pointer with mal_free(). { - mal_device* pDevice = (mal_device*)pUserData; - mal_assert(pDevice != NULL); - - float* pNewBuffer = (float*)mal_realloc(pDevice->jack.pIntermediaryBuffer, frameCount * (pDevice->internalChannels*mal_get_bytes_per_sample(pDevice->internalFormat))); - if (pNewBuffer == NULL) { + mal_assert(pContext != NULL); + mal_assert(ppChannelLayout != NULL); + + *ppChannelLayout = NULL; // Safety. + + AudioObjectPropertyAddress propAddress; + propAddress.mSelector = kAudioDevicePropertyPreferredChannelLayout; + propAddress.mScope = (deviceType == mal_device_type_playback) ? kAudioObjectPropertyScopeOutput : kAudioObjectPropertyScopeInput; + propAddress.mElement = kAudioObjectPropertyElementMaster; + + UInt32 dataSize; + OSStatus status = ((mal_AudioObjectGetPropertyDataSize_proc)pContext->coreaudio.AudioObjectGetPropertyDataSize)(deviceObjectID, &propAddress, 0, NULL, &dataSize); + if (status != noErr) { + return mal_result_from_OSStatus(status); + } + + AudioChannelLayout* pChannelLayout = (AudioChannelLayout*)mal_malloc(dataSize); + if (pChannelLayout == NULL) { return MAL_OUT_OF_MEMORY; } - - pDevice->jack.pIntermediaryBuffer = pNewBuffer; - pDevice->bufferSizeInFrames = frameCount * pDevice->periods; - - return 0; + + status = ((mal_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(deviceObjectID, &propAddress, 0, NULL, &dataSize, pChannelLayout); + if (status != noErr) { + mal_free(pChannelLayout); + return mal_result_from_OSStatus(status); + } + + *ppChannelLayout = pChannelLayout; + return MAL_SUCCESS; } -int mal_device__jack_process_callback(mal_jack_nframes_t frameCount, void* pUserData) +mal_result mal_get_AudioObject_channel_count(mal_context* pContext, AudioObjectID deviceObjectID, mal_device_type deviceType, mal_uint32* pChannelCount) { - mal_device* pDevice = (mal_device*)pUserData; - mal_assert(pDevice != NULL); - - mal_context* pContext = pDevice->pContext; mal_assert(pContext != NULL); + mal_assert(pChannelCount != NULL); + + *pChannelCount = 0; // Safety. - if (pDevice->type == mal_device_type_playback) { - mal_device__read_frames_from_client(pDevice, frameCount, pDevice->jack.pIntermediaryBuffer); - - // Channels need to be deinterleaved. - for (mal_uint32 iChannel = 0; iChannel < pDevice->internalChannels; ++iChannel) { - float* pDst = (float*)((mal_jack_port_get_buffer_proc)pContext->jack.jack_port_get_buffer)((mal_jack_port_t*)pDevice->jack.pPorts[iChannel], frameCount); - if (pDst != NULL) { - const float* pSrc = pDevice->jack.pIntermediaryBuffer + iChannel; - for (mal_jack_nframes_t iFrame = 0; iFrame < frameCount; ++iFrame) { - *pDst = *pSrc; - - pDst += 1; - pSrc += pDevice->internalChannels; - } - } - } + AudioChannelLayout* pChannelLayout; + mal_result result = mal_get_AudioObject_channel_layout(pContext, deviceObjectID, deviceType, &pChannelLayout); + if (result != MAL_SUCCESS) { + return result; + } + + if (pChannelLayout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelDescriptions) { + *pChannelCount = pChannelLayout->mNumberChannelDescriptions; + } else if (pChannelLayout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelBitmap) { + *pChannelCount = mal_count_set_bits(pChannelLayout->mChannelBitmap); } else { - // Channels need to be interleaved. - for (mal_uint32 iChannel = 0; iChannel < pDevice->internalChannels; ++iChannel) { - const float* pSrc = (const float*)((mal_jack_port_get_buffer_proc)pContext->jack.jack_port_get_buffer)((mal_jack_port_t*)pDevice->jack.pPorts[iChannel], frameCount); - if (pSrc != NULL) { - float* pDst = pDevice->jack.pIntermediaryBuffer + iChannel; - for (mal_jack_nframes_t iFrame = 0; iFrame < frameCount; ++iFrame) { - *pDst = *pSrc; + *pChannelCount = AudioChannelLayoutTag_GetNumberOfChannels(pChannelLayout->mChannelLayoutTag); + } + + mal_free(pChannelLayout); + return MAL_SUCCESS; +} - pDst += pDevice->internalChannels; - pSrc += 1; - } +mal_result mal_get_channel_map_from_AudioChannelLayout(AudioChannelLayout* pChannelLayout, mal_channel channelMap[MAL_MAX_CHANNELS]) +{ + mal_assert(pChannelLayout != NULL); + + if (pChannelLayout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelDescriptions) { + for (UInt32 iChannel = 0; iChannel < pChannelLayout->mNumberChannelDescriptions; ++iChannel) { + channelMap[iChannel] = mal_channel_from_AudioChannelLabel(pChannelLayout->mChannelDescriptions[iChannel].mChannelLabel); + } + } else +#if 0 + if (pChannelLayout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelBitmap) { + // This is the same kind of system that's used by Windows audio APIs. + UInt32 iChannel = 0; + AudioChannelBitmap bitmap = pChannelLayout->mChannelBitmap; + for (UInt32 iBit = 0; iBit < 32; ++iBit) { + AudioChannelBitmap bit = bitmap & (1 << iBit); + if (bit != 0) { + channelMap[iChannel++] = mal_channel_from_AudioChannelBit(bit); } } - - mal_device__send_frames_to_client(pDevice, frameCount, pDevice->jack.pIntermediaryBuffer); + } else +#endif + { + // Need to use the tag to determine the channel map. For now I'm just assuming a default channel map, but later on this should + // be updated to determine the mapping based on the tag. + UInt32 channelCount = AudioChannelLayoutTag_GetNumberOfChannels(pChannelLayout->mChannelLayoutTag); + switch (pChannelLayout->mChannelLayoutTag) + { + case kAudioChannelLayoutTag_Mono: + case kAudioChannelLayoutTag_Stereo: + case kAudioChannelLayoutTag_StereoHeadphones: + case kAudioChannelLayoutTag_MatrixStereo: + case kAudioChannelLayoutTag_MidSide: + case kAudioChannelLayoutTag_XY: + case kAudioChannelLayoutTag_Binaural: + case kAudioChannelLayoutTag_Ambisonic_B_Format: + { + mal_get_standard_channel_map(mal_standard_channel_map_default, channelCount, channelMap); + } break; + + case kAudioChannelLayoutTag_Octagonal: + { + channelMap[7] = MAL_CHANNEL_SIDE_RIGHT; + channelMap[6] = MAL_CHANNEL_SIDE_LEFT; + } // Intentional fallthrough. + case kAudioChannelLayoutTag_Hexagonal: + { + channelMap[5] = MAL_CHANNEL_BACK_CENTER; + } // Intentional fallthrough. + case kAudioChannelLayoutTag_Pentagonal: + { + channelMap[4] = MAL_CHANNEL_FRONT_CENTER; + } // Intentional fallghrough. + case kAudioChannelLayoutTag_Quadraphonic: + { + channelMap[3] = MAL_CHANNEL_BACK_RIGHT; + channelMap[2] = MAL_CHANNEL_BACK_LEFT; + channelMap[1] = MAL_CHANNEL_RIGHT; + channelMap[0] = MAL_CHANNEL_LEFT; + } break; + + // TODO: Add support for more tags here. + + default: + { + mal_get_standard_channel_map(mal_standard_channel_map_default, channelCount, channelMap); + } break; + } } - - return 0; + + return MAL_SUCCESS; } -mal_result mal_device_init__jack(mal_context* pContext, mal_device_type type, const mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice) +mal_result mal_get_AudioObject_channel_map(mal_context* pContext, AudioObjectID deviceObjectID, mal_device_type deviceType, mal_channel channelMap[MAL_MAX_CHANNELS]) { mal_assert(pContext != NULL); - mal_assert(pConfig != NULL); - mal_assert(pDevice != NULL); - - (void)pContext; - (void)pConfig; - - // Only supporting default devices with JACK. - if (pDeviceID != NULL && pDeviceID->jack != 0) { - return MAL_NO_DEVICE; + + AudioChannelLayout* pChannelLayout; + mal_result result = mal_get_AudioObject_channel_layout(pContext, deviceObjectID, deviceType, &pChannelLayout); + if (result != MAL_SUCCESS) { + return result; // Rather than always failing here, would it be more robust to simply assume a default? } - - - // Open the client. - mal_result result = mal_context_open_client__jack(pContext, (mal_jack_client_t**)&pDevice->jack.pClient); + + result = mal_get_channel_map_from_AudioChannelLayout(pChannelLayout, channelMap); if (result != MAL_SUCCESS) { - return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[JACK] Failed to open client.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + return result; } + + return result; +} - // Callbacks. - if (((mal_jack_set_process_callback_proc)pContext->jack.jack_set_process_callback)((mal_jack_client_t*)pDevice->jack.pClient, mal_device__jack_process_callback, pDevice) != 0) { - return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[JACK] Failed to set process callback.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); +mal_result mal_get_AudioObject_sample_rates(mal_context* pContext, AudioObjectID deviceObjectID, mal_device_type deviceType, UInt32* pSampleRateRangesCount, AudioValueRange** ppSampleRateRanges) // NOTE: Free the returned pointer with mal_free(). +{ + mal_assert(pContext != NULL); + mal_assert(pSampleRateRangesCount != NULL); + mal_assert(ppSampleRateRanges != NULL); + + // Safety. + *pSampleRateRangesCount = 0; + *ppSampleRateRanges = NULL; + + AudioObjectPropertyAddress propAddress; + propAddress.mSelector = kAudioDevicePropertyAvailableNominalSampleRates; + propAddress.mScope = (deviceType == mal_device_type_playback) ? kAudioObjectPropertyScopeOutput : kAudioObjectPropertyScopeInput; + propAddress.mElement = kAudioObjectPropertyElementMaster; + + UInt32 dataSize; + OSStatus status = ((mal_AudioObjectGetPropertyDataSize_proc)pContext->coreaudio.AudioObjectGetPropertyDataSize)(deviceObjectID, &propAddress, 0, NULL, &dataSize); + if (status != noErr) { + return mal_result_from_OSStatus(status); } - if (((mal_jack_set_buffer_size_callback_proc)pContext->jack.jack_set_buffer_size_callback)((mal_jack_client_t*)pDevice->jack.pClient, mal_device__jack_buffer_size_callback, pDevice) != 0) { - return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[JACK] Failed to set buffer size callback.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + + AudioValueRange* pSampleRateRanges = (AudioValueRange*)mal_malloc(dataSize); + if (pSampleRateRanges == NULL) { + return MAL_OUT_OF_MEMORY; } - - ((mal_jack_on_shutdown_proc)pContext->jack.jack_on_shutdown)((mal_jack_client_t*)pDevice->jack.pClient, mal_device__jack_shutdown_callback, pDevice); - - - // The format is always f32. - pDevice->internalFormat = mal_format_f32; - - // A port is a channel. - unsigned long serverPortFlags; - unsigned long clientPortFlags; - if (type == mal_device_type_playback) { - serverPortFlags = mal_JackPortIsInput; - clientPortFlags = mal_JackPortIsOutput; - } else { - serverPortFlags = mal_JackPortIsOutput; - clientPortFlags = mal_JackPortIsInput; + + status = ((mal_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(deviceObjectID, &propAddress, 0, NULL, &dataSize, pSampleRateRanges); + if (status != noErr) { + mal_free(pSampleRateRanges); + return mal_result_from_OSStatus(status); } + + *pSampleRateRangesCount = dataSize / sizeof(*pSampleRateRanges); + *ppSampleRateRanges = pSampleRateRanges; + return MAL_SUCCESS; +} - const char** ppPorts = ((mal_jack_get_ports_proc)pContext->jack.jack_get_ports)((mal_jack_client_t*)pDevice->jack.pClient, NULL, NULL, mal_JackPortIsPhysical | serverPortFlags); - if (ppPorts == NULL) { - return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[JACK] Failed to query physical ports.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); +mal_result mal_get_AudioObject_get_closest_sample_rate(mal_context* pContext, AudioObjectID deviceObjectID, mal_device_type deviceType, mal_uint32 sampleRateIn, mal_uint32* pSampleRateOut) +{ + mal_assert(pContext != NULL); + mal_assert(pSampleRateOut != NULL); + + *pSampleRateOut = 0; // Safety. + + UInt32 sampleRateRangeCount; + AudioValueRange* pSampleRateRanges; + mal_result result = mal_get_AudioObject_sample_rates(pContext, deviceObjectID, deviceType, &sampleRateRangeCount, &pSampleRateRanges); + if (result != MAL_SUCCESS) { + return result; } - - pDevice->internalChannels = 0; - while (ppPorts[pDevice->internalChannels] != NULL) { - char name[64]; - if (type == mal_device_type_playback) { - mal_strcpy_s(name, sizeof(name), "playback"); - mal_itoa_s((int)pDevice->internalChannels, name+8, sizeof(name)-8, 10); // 8 = length of "playback" - } else { - mal_strcpy_s(name, sizeof(name), "capture"); - mal_itoa_s((int)pDevice->internalChannels, name+7, sizeof(name)-7, 10); // 7 = length of "capture" + + if (sampleRateRangeCount == 0) { + mal_free(pSampleRateRanges); + return MAL_ERROR; // Should never hit this case should we? + } + + if (sampleRateIn == 0) { + // Search in order of mini_al's preferred priority. + for (UInt32 iMALSampleRate = 0; iMALSampleRate < mal_countof(g_malStandardSampleRatePriorities); ++iMALSampleRate) { + mal_uint32 malSampleRate = g_malStandardSampleRatePriorities[iMALSampleRate]; + for (UInt32 iCASampleRate = 0; iCASampleRate < sampleRateRangeCount; ++iCASampleRate) { + AudioValueRange caSampleRate = pSampleRateRanges[iCASampleRate]; + if (caSampleRate.mMinimum <= malSampleRate && caSampleRate.mMaximum >= malSampleRate) { + *pSampleRateOut = malSampleRate; + mal_free(pSampleRateRanges); + return MAL_SUCCESS; + } + } } - - pDevice->jack.pPorts[pDevice->internalChannels] = ((mal_jack_port_register_proc)pContext->jack.jack_port_register)((mal_jack_client_t*)pDevice->jack.pClient, name, MAL_JACK_DEFAULT_AUDIO_TYPE, clientPortFlags, 0); - if (pDevice->jack.pPorts[pDevice->internalChannels] == NULL) { - ((mal_jack_free_proc)pContext->jack.jack_free)((void*)ppPorts); - mal_device_uninit__jack(pDevice); - return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[JACK] Failed to register ports.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + + // If we get here it means none of mini_al's standard sample rates matched any of the supported sample rates from the device. In this + // case we just fall back to the first one reported by Core Audio. + mal_assert(sampleRateRangeCount > 0); + + *pSampleRateOut = pSampleRateRanges[0].mMinimum; + mal_free(pSampleRateRanges); + return MAL_SUCCESS; + } else { + // Find the closest match to this sample rate. + UInt32 currentAbsoluteDifference = INT32_MAX; + UInt32 iCurrentClosestRange = (UInt32)-1; + for (UInt32 iRange = 0; iRange < sampleRateRangeCount; ++iRange) { + if (pSampleRateRanges[iRange].mMinimum <= sampleRateIn && pSampleRateRanges[iRange].mMaximum >= sampleRateIn) { + *pSampleRateOut = sampleRateIn; + mal_free(pSampleRateRanges); + return MAL_SUCCESS; + } else { + UInt32 absoluteDifference; + if (pSampleRateRanges[iRange].mMinimum > sampleRateIn) { + absoluteDifference = pSampleRateRanges[iRange].mMinimum - sampleRateIn; + } else { + absoluteDifference = sampleRateIn - pSampleRateRanges[iRange].mMaximum; + } + + if (currentAbsoluteDifference > absoluteDifference) { + currentAbsoluteDifference = absoluteDifference; + iCurrentClosestRange = iRange; + } + } } - - pDevice->internalChannels += 1; + + mal_assert(iCurrentClosestRange != (UInt32)-1); + + *pSampleRateOut = pSampleRateRanges[iCurrentClosestRange].mMinimum; + mal_free(pSampleRateRanges); + return MAL_SUCCESS; } + + // Should never get here, but it would mean we weren't able to find any suitable sample rates. + //mal_free(pSampleRateRanges); + //return MAL_ERROR; +} - ((mal_jack_free_proc)pContext->jack.jack_free)((void*)ppPorts); - ppPorts = NULL; - - // We set the sample rate here, but apparently this can change. This is incompatible with mini_al, so changing sample rates will not be supported. - pDevice->internalSampleRate = ((mal_jack_get_sample_rate_proc)pContext->jack.jack_get_sample_rate)((mal_jack_client_t*)pDevice->jack.pClient); - - // I don't think the channel map can be queried, so just use defaults for now. - mal_get_standard_channel_map(mal_standard_channel_map_alsa, pDevice->internalChannels, pDevice->internalChannelMap); - // The buffer size in frames can change. - pDevice->periods = 2; - pDevice->bufferSizeInFrames = ((mal_jack_get_buffer_size_proc)pContext->jack.jack_get_buffer_size)((mal_jack_client_t*)pDevice->jack.pClient) * pDevice->periods; +mal_result mal_get_AudioObject_closest_buffer_size_in_frames(mal_context* pContext, AudioObjectID deviceObjectID, mal_device_type deviceType, mal_uint32 bufferSizeInFramesIn, mal_uint32* pBufferSizeInFramesOut) +{ + mal_assert(pContext != NULL); + mal_assert(pBufferSizeInFramesOut != NULL); + + *pBufferSizeInFramesOut = 0; // Safety. + + AudioObjectPropertyAddress propAddress; + propAddress.mSelector = kAudioDevicePropertyBufferFrameSizeRange; + propAddress.mScope = (deviceType == mal_device_type_playback) ? kAudioObjectPropertyScopeOutput : kAudioObjectPropertyScopeInput; + propAddress.mElement = kAudioObjectPropertyElementMaster; - // Initial allocation for the intermediary buffer. - pDevice->jack.pIntermediaryBuffer = (float*)mal_malloc((pDevice->bufferSizeInFrames/pDevice->periods)*(pDevice->internalChannels*mal_get_bytes_per_sample(pDevice->internalFormat))); - if (pDevice->jack.pIntermediaryBuffer == NULL) { - mal_device_uninit__jack(pDevice); - return MAL_OUT_OF_MEMORY; + AudioValueRange bufferSizeRange; + UInt32 dataSize = sizeof(bufferSizeRange); + OSStatus status = ((mal_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(deviceObjectID, &propAddress, 0, NULL, &dataSize, &bufferSizeRange); + if (status != noErr) { + return mal_result_from_OSStatus(status); + } + + // This is just a clamp. + if (bufferSizeInFramesIn < bufferSizeRange.mMinimum) { + *pBufferSizeInFramesOut = (mal_uint32)bufferSizeRange.mMinimum; + } else if (bufferSizeInFramesIn > bufferSizeRange.mMaximum) { + *pBufferSizeInFramesOut = (mal_uint32)bufferSizeRange.mMaximum; + } else { + *pBufferSizeInFramesOut = bufferSizeInFramesIn; } return MAL_SUCCESS; } - -mal_result mal_device__start_backend__jack(mal_device* pDevice) +mal_result mal_set_AudioObject_buffer_size_in_frames(mal_context* pContext, AudioObjectID deviceObjectID, mal_device_type deviceType, mal_uint32* pBufferSizeInOut) { - mal_assert(pDevice != NULL); - - mal_context* pContext = pDevice->pContext; mal_assert(pContext != NULL); - int resultJACK = ((mal_jack_activate_proc)pContext->jack.jack_activate)((mal_jack_client_t*)pDevice->jack.pClient); - if (resultJACK != 0) { - return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[JACK] Failed to activate the JACK client.", MAL_FAILED_TO_START_BACKEND_DEVICE); - } - - const char** ppServerPorts; - if (pDevice->type == mal_device_type_playback) { - ppServerPorts = ((mal_jack_get_ports_proc)pContext->jack.jack_get_ports)((mal_jack_client_t*)pDevice->jack.pClient, NULL, NULL, mal_JackPortIsPhysical | mal_JackPortIsInput); - } else { - ppServerPorts = ((mal_jack_get_ports_proc)pContext->jack.jack_get_ports)((mal_jack_client_t*)pDevice->jack.pClient, NULL, NULL, mal_JackPortIsPhysical | mal_JackPortIsOutput); + mal_uint32 chosenBufferSizeInFrames; + mal_result result = mal_get_AudioObject_closest_buffer_size_in_frames(pContext, deviceObjectID, deviceType, *pBufferSizeInOut, &chosenBufferSizeInFrames); + if (result != MAL_SUCCESS) { + return result; } - if (ppServerPorts == NULL) { - ((mal_jack_deactivate_proc)pContext->jack.jack_deactivate)((mal_jack_client_t*)pDevice->jack.pClient); - return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[JACK] Failed to retrieve physical ports.", MAL_ERROR); + // Try setting the size of the buffer... If this fails we just use whatever is currently set. + AudioObjectPropertyAddress propAddress; + propAddress.mSelector = kAudioDevicePropertyBufferFrameSize; + propAddress.mScope = (deviceType == mal_device_type_playback) ? kAudioObjectPropertyScopeOutput : kAudioObjectPropertyScopeInput; + propAddress.mElement = kAudioObjectPropertyElementMaster; + + ((mal_AudioObjectSetPropertyData_proc)pContext->coreaudio.AudioObjectSetPropertyData)(deviceObjectID, &propAddress, 0, NULL, sizeof(chosenBufferSizeInFrames), &chosenBufferSizeInFrames); + + // Get the actual size of the buffer. + UInt32 dataSize = sizeof(*pBufferSizeInOut); + OSStatus status = ((mal_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(deviceObjectID, &propAddress, 0, NULL, &dataSize, &chosenBufferSizeInFrames); + if (status != noErr) { + return mal_result_from_OSStatus(status); } + + *pBufferSizeInOut = chosenBufferSizeInFrames; + return MAL_SUCCESS; +} - for (size_t i = 0; ppServerPorts[i] != NULL; ++i) { - const char* pServerPort = ppServerPorts[i]; - mal_assert(pServerPort != NULL); - const char* pClientPort = ((mal_jack_port_name_proc)pContext->jack.jack_port_name)((mal_jack_port_t*)pDevice->jack.pPorts[i]); - mal_assert(pClientPort != NULL); +mal_result mal_find_AudioObjectID(mal_context* pContext, mal_device_type type, const mal_device_id* pDeviceID, AudioObjectID* pDeviceObjectID) +{ + mal_assert(pContext != NULL); + mal_assert(pDeviceObjectID != NULL); - if (pDevice->type == mal_device_type_playback) { - resultJACK = ((mal_jack_connect_proc)pContext->jack.jack_connect)((mal_jack_client_t*)pDevice->jack.pClient, pClientPort, pServerPort); + // Safety. + *pDeviceObjectID = 0; + + if (pDeviceID == NULL) { + // Default device. + AudioObjectPropertyAddress propAddressDefaultDevice; + propAddressDefaultDevice.mScope = kAudioObjectPropertyScopeGlobal; + propAddressDefaultDevice.mElement = kAudioObjectPropertyElementMaster; + if (type == mal_device_type_playback) { + propAddressDefaultDevice.mSelector = kAudioHardwarePropertyDefaultOutputDevice; } else { - resultJACK = ((mal_jack_connect_proc)pContext->jack.jack_connect)((mal_jack_client_t*)pDevice->jack.pClient, pServerPort, pClientPort); + propAddressDefaultDevice.mSelector = kAudioHardwarePropertyDefaultInputDevice; } - - if (resultJACK != 0) { - ((mal_jack_free_proc)pContext->jack.jack_free)((void*)ppServerPorts); - ((mal_jack_deactivate_proc)pContext->jack.jack_deactivate)((mal_jack_client_t*)pDevice->jack.pClient); - return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[JACK] Failed to connect ports.", MAL_ERROR); + + UInt32 defaultDeviceObjectIDSize = sizeof(AudioObjectID); + AudioObjectID defaultDeviceObjectID; + OSStatus status = ((mal_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(kAudioObjectSystemObject, &propAddressDefaultDevice, 0, NULL, &defaultDeviceObjectIDSize, &defaultDeviceObjectID); + if (status == noErr) { + *pDeviceObjectID = defaultDeviceObjectID; + return MAL_SUCCESS; + } + } else { + // Explicit device. + UInt32 deviceCount; + AudioObjectID* pDeviceObjectIDs; + mal_result result = mal_get_device_object_ids__coreaudio(pContext, &deviceCount, &pDeviceObjectIDs); + if (result != MAL_SUCCESS) { + return result; + } + + for (UInt32 iDevice = 0; iDevice < deviceCount; ++iDevice) { + AudioObjectID deviceObjectID = pDeviceObjectIDs[iDevice]; + + char uid[256]; + if (mal_get_AudioObject_uid(pContext, deviceObjectID, sizeof(uid), uid) != MAL_SUCCESS) { + continue; + } + + if (type == mal_device_type_playback) { + if (mal_does_AudioObject_support_playback(pContext, deviceObjectID)) { + if (strcmp(uid, pDeviceID->coreaudio) == 0) { + *pDeviceObjectID = deviceObjectID; + return MAL_SUCCESS; + } + } + } else { + if (mal_does_AudioObject_support_capture(pContext, deviceObjectID)) { + if (strcmp(uid, pDeviceID->coreaudio) == 0) { + *pDeviceObjectID = deviceObjectID; + return MAL_SUCCESS; + } + } + } } } - - ((mal_jack_free_proc)pContext->jack.jack_free)((void*)ppServerPorts); - - return MAL_SUCCESS; + + // If we get here it means we couldn't find the device. + return MAL_NO_DEVICE; } -mal_result mal_device__stop_backend__jack(mal_device* pDevice) + +mal_result mal_device_find_best_format__coreaudio(const mal_device* pDevice, AudioStreamBasicDescription* pFormat) { mal_assert(pDevice != NULL); - - mal_context* pContext = pDevice->pContext; - mal_assert(pContext != NULL); - - if (((mal_jack_deactivate_proc)pContext->jack.jack_deactivate)((mal_jack_client_t*)pDevice->jack.pClient) != 0) { - return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[JACK] An error occurred when deactivating the JACK client.", MAL_ERROR); + + AudioObjectID deviceObjectID = (AudioObjectID)pDevice->coreaudio.deviceObjectID; + + UInt32 deviceFormatDescriptionCount; + AudioStreamRangedDescription* pDeviceFormatDescriptions; + mal_result result = mal_get_AudioObject_stream_descriptions(pDevice->pContext, deviceObjectID, pDevice->type, &deviceFormatDescriptionCount, &pDeviceFormatDescriptions); + if (result != MAL_SUCCESS) { + return result; + } + + mal_uint32 desiredSampleRate = pDevice->sampleRate; + if (pDevice->usingDefaultSampleRate) { + // When using the device's default sample rate, we get the highest priority standard rate supported by the device. Otherwise + // we just use the pre-set rate. + for (mal_uint32 iStandardRate = 0; iStandardRate < mal_countof(g_malStandardSampleRatePriorities); ++iStandardRate) { + mal_uint32 standardRate = g_malStandardSampleRatePriorities[iStandardRate]; + + mal_bool32 foundRate = MAL_FALSE; + for (UInt32 iDeviceRate = 0; iDeviceRate < deviceFormatDescriptionCount; ++iDeviceRate) { + mal_uint32 deviceRate = (mal_uint32)pDeviceFormatDescriptions[iDeviceRate].mFormat.mSampleRate; + + if (deviceRate == standardRate) { + desiredSampleRate = standardRate; + foundRate = MAL_TRUE; + break; + } + } + + if (foundRate) { + break; + } + } + } + + mal_uint32 desiredChannelCount = pDevice->channels; + if (pDevice->usingDefaultChannels) { + mal_get_AudioObject_channel_count(pDevice->pContext, deviceObjectID, pDevice->type, &desiredChannelCount); // <-- Not critical if this fails. + } + + mal_format desiredFormat = pDevice->format; + if (pDevice->usingDefaultFormat) { + desiredFormat = g_malFormatPriorities[0]; + } + + // If we get here it means we don't have an exact match to what the client is asking for. We'll need to find the closest one. The next + // loop will check for formats that have the same sample rate to what we're asking for. If there is, we prefer that one in all cases. + AudioStreamBasicDescription bestDeviceFormatSoFar; + mal_zero_object(&bestDeviceFormatSoFar); + + mal_bool32 hasSupportedFormat = MAL_FALSE; + for (UInt32 iFormat = 0; iFormat < deviceFormatDescriptionCount; ++iFormat) { + mal_format format; + mal_result formatResult = mal_format_from_AudioStreamBasicDescription(&pDeviceFormatDescriptions[iFormat].mFormat, &format); + if (formatResult == MAL_SUCCESS && format != mal_format_unknown) { + hasSupportedFormat = MAL_TRUE; + bestDeviceFormatSoFar = pDeviceFormatDescriptions[iFormat].mFormat; + break; + } + } + + if (!hasSupportedFormat) { + return MAL_FORMAT_NOT_SUPPORTED; + } + + + for (UInt32 iFormat = 0; iFormat < deviceFormatDescriptionCount; ++iFormat) { + AudioStreamBasicDescription thisDeviceFormat = pDeviceFormatDescriptions[iFormat].mFormat; + + // If the format is not supported by mini_al we need to skip this one entirely. + mal_format thisSampleFormat; + mal_result formatResult = mal_format_from_AudioStreamBasicDescription(&pDeviceFormatDescriptions[iFormat].mFormat, &thisSampleFormat); + if (formatResult != MAL_SUCCESS || thisSampleFormat == mal_format_unknown) { + continue; // The format is not supported by mini_al. Skip. + } + + mal_format bestSampleFormatSoFar; + mal_format_from_AudioStreamBasicDescription(&bestDeviceFormatSoFar, &bestSampleFormatSoFar); + + + // Getting here means the format is supported by mini_al which makes this format a candidate. + if (thisDeviceFormat.mSampleRate != desiredSampleRate) { + // The sample rate does not match, but this format could still be usable, although it's a very low priority. If the best format + // so far has an equal sample rate we can just ignore this one. + if (bestDeviceFormatSoFar.mSampleRate == desiredSampleRate) { + continue; // The best sample rate so far has the same sample rate as what we requested which means it's still the best so far. Skip this format. + } else { + // In this case, neither the best format so far nor this one have the same sample rate. Check the channel count next. + if (thisDeviceFormat.mChannelsPerFrame != desiredChannelCount) { + // This format has a different sample rate _and_ a different channel count. + if (bestDeviceFormatSoFar.mChannelsPerFrame == desiredChannelCount) { + continue; // No change to the best format. + } else { + // Both this format and the best so far have different sample rates and different channel counts. Whichever has the + // best format is the new best. + if (mal_get_format_priority_index(thisSampleFormat) < mal_get_format_priority_index(bestSampleFormatSoFar)) { + bestDeviceFormatSoFar = thisDeviceFormat; + continue; + } else { + continue; // No change to the best format. + } + } + } else { + // This format has a different sample rate but the desired channel count. + if (bestDeviceFormatSoFar.mChannelsPerFrame == desiredChannelCount) { + // Both this format and the best so far have the desired channel count. Whichever has the best format is the new best. + if (mal_get_format_priority_index(thisSampleFormat) < mal_get_format_priority_index(bestSampleFormatSoFar)) { + bestDeviceFormatSoFar = thisDeviceFormat; + continue; + } else { + continue; // No change to the best format for now. + } + } else { + // This format has the desired channel count, but the best so far does not. We have a new best. + bestDeviceFormatSoFar = thisDeviceFormat; + continue; + } + } + } + } else { + // The sample rates match which makes this format a very high priority contender. If the best format so far has a different + // sample rate it needs to be replaced with this one. + if (bestDeviceFormatSoFar.mSampleRate != desiredSampleRate) { + bestDeviceFormatSoFar = thisDeviceFormat; + continue; + } else { + // In this case both this format and the best format so far have the same sample rate. Check the channel count next. + if (thisDeviceFormat.mChannelsPerFrame == desiredChannelCount) { + // In this case this format has the same channel count as what the client is requesting. If the best format so far has + // a different count, this one becomes the new best. + if (bestDeviceFormatSoFar.mChannelsPerFrame != desiredChannelCount) { + bestDeviceFormatSoFar = thisDeviceFormat; + continue; + } else { + // In this case both this format and the best so far have the ideal sample rate and channel count. Check the format. + if (thisSampleFormat == desiredFormat) { + bestDeviceFormatSoFar = thisDeviceFormat; + break; // Found the exact match. + } else { + // The formats are different. The new best format is the one with the highest priority format according to mini_al. + if (mal_get_format_priority_index(thisSampleFormat) < mal_get_format_priority_index(bestSampleFormatSoFar)) { + bestDeviceFormatSoFar = thisDeviceFormat; + continue; + } else { + continue; // No change to the best format for now. + } + } + } + } else { + // In this case the channel count is different to what the client has requested. If the best so far has the same channel + // count as the requested count then it remains the best. + if (bestDeviceFormatSoFar.mChannelsPerFrame == desiredChannelCount) { + continue; + } else { + // This is the case where both have the same sample rate (good) but different channel counts. Right now both have about + // the same priority, but we need to compare the format now. + if (thisSampleFormat == bestSampleFormatSoFar) { + if (mal_get_format_priority_index(thisSampleFormat) < mal_get_format_priority_index(bestSampleFormatSoFar)) { + bestDeviceFormatSoFar = thisDeviceFormat; + continue; + } else { + continue; // No change to the best format for now. + } + } + } + } + } + } } - mal_device__set_state(pDevice, MAL_STATE_STOPPED); - mal_stop_proc onStop = pDevice->onStop; - if (onStop) { - onStop(pDevice); - } - + *pFormat = bestDeviceFormatSoFar; return MAL_SUCCESS; } +#endif -mal_result mal_context_uninit__jack(mal_context* pContext) + +mal_bool32 mal_context_is_device_id_equal__coreaudio(mal_context* pContext, const mal_device_id* pID0, const mal_device_id* pID1) { mal_assert(pContext != NULL); - mal_assert(pContext->backend == mal_backend_jack); - -#ifndef MAL_NO_RUNTIME_LINKING - mal_dlclose(pContext->jack.jackSO); -#endif + mal_assert(pID0 != NULL); + mal_assert(pID1 != NULL); + (void)pContext; - return MAL_SUCCESS; + return strcmp(pID0->coreaudio, pID1->coreaudio) == 0; } -mal_result mal_context_init__jack(mal_context* pContext) +mal_result mal_context_enumerate_devices__coreaudio(mal_context* pContext, mal_enum_devices_callback_proc callback, void* pUserData) { mal_assert(pContext != NULL); - -#ifndef MAL_NO_RUNTIME_LINKING - // libjack.so - const char* libjackNames[] = { -#ifdef MAL_WIN32 - "libjack.dll" -#else - "libjack.so", - "libjack.so.0" -#endif - }; - - for (size_t i = 0; i < mal_countof(libjackNames); ++i) { - pContext->jack.jackSO = mal_dlopen(libjackNames[i]); - if (pContext->jack.jackSO != NULL) { - break; - } - } - - if (pContext->jack.jackSO == NULL) { - return MAL_NO_BACKEND; - } - - pContext->jack.jack_client_open = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_client_open"); - pContext->jack.jack_client_close = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_client_close"); - pContext->jack.jack_client_name_size = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_client_name_size"); - pContext->jack.jack_set_process_callback = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_set_process_callback"); - pContext->jack.jack_set_buffer_size_callback = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_set_buffer_size_callback"); - pContext->jack.jack_on_shutdown = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_on_shutdown"); - pContext->jack.jack_get_sample_rate = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_get_sample_rate"); - pContext->jack.jack_get_buffer_size = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_get_buffer_size"); - pContext->jack.jack_get_ports = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_get_ports"); - pContext->jack.jack_activate = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_activate"); - pContext->jack.jack_deactivate = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_deactivate"); - pContext->jack.jack_connect = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_connect"); - pContext->jack.jack_port_register = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_port_register"); - pContext->jack.jack_port_name = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_port_name"); - pContext->jack.jack_port_get_buffer = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_port_get_buffer"); - pContext->jack.jack_free = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_free"); -#else - // This strange assignment system is here just to ensure type safety of mini_al's function pointer - // types. If anything differs slightly the compiler should throw a warning. - mal_jack_client_open_proc _jack_client_open = jack_client_open; - mal_jack_client_close_proc _jack_client_close = jack_client_close; - mal_jack_client_name_size_proc _jack_client_name_size = jack_client_name_size; - mal_jack_set_process_callback_proc _jack_set_process_callback = jack_set_process_callback; - mal_jack_set_buffer_size_callback_proc _jack_set_buffer_size_callback = jack_set_buffer_size_callback; - mal_jack_on_shutdown_proc _jack_on_shutdown = jack_on_shutdown; - mal_jack_get_sample_rate_proc _jack_get_sample_rate = jack_get_sample_rate; - mal_jack_get_buffer_size_proc _jack_get_buffer_size = jack_get_buffer_size; - mal_jack_get_ports_proc _jack_get_ports = jack_get_ports; - mal_jack_activate_proc _jack_activate = jack_activate; - mal_jack_deactivate_proc _jack_deactivate = jack_deactivate; - mal_jack_connect_proc _jack_connect = jack_connect; - mal_jack_port_register_proc _jack_port_register = jack_port_register; - mal_jack_port_name_proc _jack_port_name = jack_port_name; - mal_jack_port_get_buffer_proc _jack_port_get_buffer = jack_port_get_buffer; - mal_jack_free_proc _jack_free = jack_free; - - pContext->jack.jack_client_open = (mal_proc)_jack_client_open; - pContext->jack.jack_client_close = (mal_proc)_jack_client_close; - pContext->jack.jack_client_name_size = (mal_proc)_jack_client_name_size; - pContext->jack.jack_set_process_callback = (mal_proc)_jack_set_process_callback; - pContext->jack.jack_set_buffer_size_callback = (mal_proc)_jack_set_buffer_size_callback; - pContext->jack.jack_on_shutdown = (mal_proc)_jack_on_shutdown; - pContext->jack.jack_get_sample_rate = (mal_proc)_jack_get_sample_rate; - pContext->jack.jack_get_buffer_size = (mal_proc)_jack_get_buffer_size; - pContext->jack.jack_get_ports = (mal_proc)_jack_get_ports; - pContext->jack.jack_activate = (mal_proc)_jack_activate; - pContext->jack.jack_deactivate = (mal_proc)_jack_deactivate; - pContext->jack.jack_connect = (mal_proc)_jack_connect; - pContext->jack.jack_port_register = (mal_proc)_jack_port_register; - pContext->jack.jack_port_name = (mal_proc)_jack_port_name; - pContext->jack.jack_port_get_buffer = (mal_proc)_jack_port_get_buffer; - pContext->jack.jack_free = (mal_proc)_jack_free; -#endif - - pContext->isBackendAsynchronous = MAL_TRUE; - - pContext->onUninit = mal_context_uninit__jack; - pContext->onDeviceIDEqual = mal_context_is_device_id_equal__jack; - pContext->onEnumDevices = mal_context_enumerate_devices__jack; - pContext->onGetDeviceInfo = mal_context_get_device_info__jack; - pContext->onDeviceInit = mal_device_init__jack; - pContext->onDeviceUninit = mal_device_uninit__jack; - pContext->onDeviceStart = mal_device__start_backend__jack; - pContext->onDeviceStop = mal_device__stop_backend__jack; - - - // Getting here means the JACK library is installed, but it doesn't necessarily mean it's usable. We need to quickly test this by connecting - // a temporary client. - mal_jack_client_t* pDummyClient; - mal_result result = mal_context_open_client__jack(pContext, &pDummyClient); + mal_assert(callback != NULL); + +#if defined(MAL_APPLE_DESKTOP) + UInt32 deviceCount; + AudioObjectID* pDeviceObjectIDs; + mal_result result = mal_get_device_object_ids__coreaudio(pContext, &deviceCount, &pDeviceObjectIDs); if (result != MAL_SUCCESS) { - return MAL_NO_BACKEND; + return result; } + + for (UInt32 iDevice = 0; iDevice < deviceCount; ++iDevice) { + AudioObjectID deviceObjectID = pDeviceObjectIDs[iDevice]; - ((mal_jack_client_close_proc)pContext->jack.jack_client_close)((mal_jack_client_t*)pDummyClient); - return MAL_SUCCESS; -} -#endif // JACK - - - -/////////////////////////////////////////////////////////////////////////////// -// -// Core Audio Backend -// -/////////////////////////////////////////////////////////////////////////////// -#ifdef MAL_HAS_COREAUDIO -#include - -#if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1 - #define MAL_APPLE_MOBILE -#else - #define MAL_APPLE_DESKTOP -#endif + mal_device_info info; + mal_zero_object(&info); + if (mal_get_AudioObject_uid(pContext, deviceObjectID, sizeof(info.id.coreaudio), info.id.coreaudio) != MAL_SUCCESS) { + continue; + } + if (mal_get_AudioObject_name(pContext, deviceObjectID, sizeof(info.name), info.name) != MAL_SUCCESS) { + continue; + } -#if defined(MAL_APPLE_DESKTOP) -#include + if (mal_does_AudioObject_support_playback(pContext, deviceObjectID)) { + if (!callback(pContext, mal_device_type_playback, &info, pUserData)) { + break; + } + } + if (mal_does_AudioObject_support_capture(pContext, deviceObjectID)) { + if (!callback(pContext, mal_device_type_capture, &info, pUserData)) { + break; + } + } + } + + mal_free(pDeviceObjectIDs); #else -#include -#endif - -#include - -// CoreFoundation -typedef Boolean (* mal_CFStringGetCString_proc)(CFStringRef theString, char* buffer, CFIndex bufferSize, CFStringEncoding encoding); - -// CoreAudio -#if defined(MAL_APPLE_DESKTOP) -typedef OSStatus (* mal_AudioObjectGetPropertyData_proc)(AudioObjectID inObjectID, const AudioObjectPropertyAddress* inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32* ioDataSize, void* outData); -typedef OSStatus (* mal_AudioObjectGetPropertyDataSize_proc)(AudioObjectID inObjectID, const AudioObjectPropertyAddress* inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32* outDataSize); -typedef OSStatus (* mal_AudioObjectSetPropertyData_proc)(AudioObjectID inObjectID, const AudioObjectPropertyAddress* inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, const void* inData); -#endif - -// AudioToolbox -typedef AudioComponent (* mal_AudioComponentFindNext_proc)(AudioComponent inComponent, const AudioComponentDescription* inDesc); -typedef OSStatus (* mal_AudioComponentInstanceDispose_proc)(AudioComponentInstance inInstance); -typedef OSStatus (* mal_AudioComponentInstanceNew_proc)(AudioComponent inComponent, AudioComponentInstance* outInstance); -typedef OSStatus (* mal_AudioOutputUnitStart_proc)(AudioUnit inUnit); -typedef OSStatus (* mal_AudioOutputUnitStop_proc)(AudioUnit inUnit); -typedef OSStatus (* mal_AudioUnitAddPropertyListener_proc)(AudioUnit inUnit, AudioUnitPropertyID inID, AudioUnitPropertyListenerProc inProc, void* inProcUserData); -typedef OSStatus (* mal_AudioUnitGetProperty_proc)(AudioUnit inUnit, AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, void* outData, UInt32* ioDataSize); -typedef OSStatus (* mal_AudioUnitSetProperty_proc)(AudioUnit inUnit, AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, const void* inData, UInt32 inDataSize); -typedef OSStatus (* mal_AudioUnitInitialize_proc)(AudioUnit inUnit); -typedef OSStatus (* mal_AudioUnitRender_proc)(AudioUnit inUnit, AudioUnitRenderActionFlags* ioActionFlags, const AudioTimeStamp* inTimeStamp, UInt32 inOutputBusNumber, UInt32 inNumberFrames, AudioBufferList* ioData); - - -#define MAL_COREAUDIO_OUTPUT_BUS 0 -#define MAL_COREAUDIO_INPUT_BUS 1 - - -// Core Audio -// -// So far, Core Audio has been the worst backend to work with due to being both unintuitive and having almost no documentation -// apart from comments in the headers (which admittedly are quite good). For my own purposes, and for anybody out there whose -// needing to figure out how this darn thing works, I'm going to outline a few things here. -// -// Since mini_al is a fairly low-level API, one of the things it needs is control over specific devices, and it needs to be -// able to identify whether or not it can be used as playback and/or capture. The AudioObject API is the only one I've seen -// that supports this level of detail. There was some public domain sample code I stumbled across that used the AudioComponent -// and AudioUnit APIs, but I couldn't see anything that gave low-level control over device selection and capabilities (the -// distinction between playback and capture in particular). Therefore, mini_al is using the AudioObject API. -// -// Most (all?) functions in the AudioObject API take a AudioObjectID as it's input. This is the device identifier. When -// retrieving global information, such as the device list, you use kAudioObjectSystemObject. When retrieving device-specific -// data, you pass in the ID for that device. In order to retrieve device-specific IDs you need to enumerate over each of the -// devices. This is done using the AudioObjectGetPropertyDataSize() and AudioObjectGetPropertyData() APIs which seem to be -// the central APIs for retrieving information about the system and specific devices. -// -// To use the AudioObjectGetPropertyData() API you need to use the notion of a property address. A property address is a -// structure with three variables and is used to identify which property you are getting or setting. The first is the "selector" -// which is basically the specific property that you're wanting to retrieve or set. The second is the "scope", which is -// typically set to kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyScopeInput for input-specific properties and -// kAudioObjectPropertyScopeOutput for output-specific properties. The last is the "element" which is always set to -// kAudioObjectPropertyElementMaster in mini_al's case. I don't know of any cases where this would be set to anything different. -// -// Back to the earlier issue of device retrieval, you first use the AudioObjectGetPropertyDataSize() API to retrieve the size -// of the raw data which is just a list of AudioDeviceID's. You use the kAudioObjectSystemObject AudioObjectID, and a property -// address with the kAudioHardwarePropertyDevices selector and the kAudioObjectPropertyScopeGlobal scope. Once you have the -// size, allocate a block of memory of that size and then call AudioObjectGetPropertyData(). The data is just a list of -// AudioDeviceID's so just do "dataSize/sizeof(AudioDeviceID)" to know the device count. - -mal_result mal_result_from_OSStatus(OSStatus status) -{ - switch (status) - { - case noErr: return MAL_SUCCESS; - #if defined(MAL_APPLE_DESKTOP) - case kAudioHardwareNotRunningError: return MAL_DEVICE_NOT_STARTED; - case kAudioHardwareUnspecifiedError: return MAL_ERROR; - case kAudioHardwareUnknownPropertyError: return MAL_INVALID_ARGS; - case kAudioHardwareBadPropertySizeError: return MAL_INVALID_OPERATION; - case kAudioHardwareIllegalOperationError: return MAL_INVALID_OPERATION; - case kAudioHardwareBadObjectError: return MAL_INVALID_ARGS; - case kAudioHardwareBadDeviceError: return MAL_INVALID_ARGS; - case kAudioHardwareBadStreamError: return MAL_INVALID_ARGS; - case kAudioHardwareUnsupportedOperationError: return MAL_INVALID_OPERATION; - case kAudioDeviceUnsupportedFormatError: return MAL_FORMAT_NOT_SUPPORTED; - case kAudioDevicePermissionsError: return MAL_ACCESS_DENIED; - #endif - default: return MAL_ERROR; + // Only supporting default devices on non-Desktop platforms. + mal_device_info info; + + mal_zero_object(&info); + mal_strncpy_s(info.name, sizeof(info.name), MAL_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + if (!callback(pContext, mal_device_type_playback, &info, pUserData)) { + return MAL_SUCCESS; } -} - -#if 0 -mal_channel mal_channel_from_AudioChannelBitmap(AudioChannelBitmap bit) -{ - switch (bit) - { - case kAudioChannelBit_Left: return MAL_CHANNEL_LEFT; - case kAudioChannelBit_Right: return MAL_CHANNEL_RIGHT; - case kAudioChannelBit_Center: return MAL_CHANNEL_FRONT_CENTER; - case kAudioChannelBit_LFEScreen: return MAL_CHANNEL_LFE; - case kAudioChannelBit_LeftSurround: return MAL_CHANNEL_BACK_LEFT; - case kAudioChannelBit_RightSurround: return MAL_CHANNEL_BACK_RIGHT; - case kAudioChannelBit_LeftCenter: return MAL_CHANNEL_FRONT_LEFT_CENTER; - case kAudioChannelBit_RightCenter: return MAL_CHANNEL_FRONT_RIGHT_CENTER; - case kAudioChannelBit_CenterSurround: return MAL_CHANNEL_BACK_CENTER; - case kAudioChannelBit_LeftSurroundDirect: return MAL_CHANNEL_SIDE_LEFT; - case kAudioChannelBit_RightSurroundDirect: return MAL_CHANNEL_SIDE_RIGHT; - case kAudioChannelBit_TopCenterSurround: return MAL_CHANNEL_TOP_CENTER; - case kAudioChannelBit_VerticalHeightLeft: return MAL_CHANNEL_TOP_FRONT_LEFT; - case kAudioChannelBit_VerticalHeightCenter: return MAL_CHANNEL_TOP_FRONT_CENTER; - case kAudioChannelBit_VerticalHeightRight: return MAL_CHANNEL_TOP_FRONT_RIGHT; - case kAudioChannelBit_TopBackLeft: return MAL_CHANNEL_TOP_BACK_LEFT; - case kAudioChannelBit_TopBackCenter: return MAL_CHANNEL_TOP_BACK_CENTER; - case kAudioChannelBit_TopBackRight: return MAL_CHANNEL_TOP_BACK_RIGHT; - default: return MAL_CHANNEL_NONE; + + mal_zero_object(&info); + mal_strncpy_s(info.name, sizeof(info.name), MAL_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + if (!callback(pContext, mal_device_type_capture, &info, pUserData)) { + return MAL_SUCCESS; } -} #endif - -mal_channel mal_channel_from_AudioChannelLabel(AudioChannelLabel label) -{ - switch (label) - { - case kAudioChannelLabel_Unknown: return MAL_CHANNEL_NONE; - case kAudioChannelLabel_Unused: return MAL_CHANNEL_NONE; - case kAudioChannelLabel_UseCoordinates: return MAL_CHANNEL_NONE; - case kAudioChannelLabel_Left: return MAL_CHANNEL_LEFT; - case kAudioChannelLabel_Right: return MAL_CHANNEL_RIGHT; - case kAudioChannelLabel_Center: return MAL_CHANNEL_FRONT_CENTER; - case kAudioChannelLabel_LFEScreen: return MAL_CHANNEL_LFE; - case kAudioChannelLabel_LeftSurround: return MAL_CHANNEL_BACK_LEFT; - case kAudioChannelLabel_RightSurround: return MAL_CHANNEL_BACK_RIGHT; - case kAudioChannelLabel_LeftCenter: return MAL_CHANNEL_FRONT_LEFT_CENTER; - case kAudioChannelLabel_RightCenter: return MAL_CHANNEL_FRONT_RIGHT_CENTER; - case kAudioChannelLabel_CenterSurround: return MAL_CHANNEL_BACK_CENTER; - case kAudioChannelLabel_LeftSurroundDirect: return MAL_CHANNEL_SIDE_LEFT; - case kAudioChannelLabel_RightSurroundDirect: return MAL_CHANNEL_SIDE_RIGHT; - case kAudioChannelLabel_TopCenterSurround: return MAL_CHANNEL_TOP_CENTER; - case kAudioChannelLabel_VerticalHeightLeft: return MAL_CHANNEL_TOP_FRONT_LEFT; - case kAudioChannelLabel_VerticalHeightCenter: return MAL_CHANNEL_TOP_FRONT_CENTER; - case kAudioChannelLabel_VerticalHeightRight: return MAL_CHANNEL_TOP_FRONT_RIGHT; - case kAudioChannelLabel_TopBackLeft: return MAL_CHANNEL_TOP_BACK_LEFT; - case kAudioChannelLabel_TopBackCenter: return MAL_CHANNEL_TOP_BACK_CENTER; - case kAudioChannelLabel_TopBackRight: return MAL_CHANNEL_TOP_BACK_RIGHT; - case kAudioChannelLabel_RearSurroundLeft: return MAL_CHANNEL_BACK_LEFT; - case kAudioChannelLabel_RearSurroundRight: return MAL_CHANNEL_BACK_RIGHT; - case kAudioChannelLabel_LeftWide: return MAL_CHANNEL_SIDE_LEFT; - case kAudioChannelLabel_RightWide: return MAL_CHANNEL_SIDE_RIGHT; - case kAudioChannelLabel_LFE2: return MAL_CHANNEL_LFE; - case kAudioChannelLabel_LeftTotal: return MAL_CHANNEL_LEFT; - case kAudioChannelLabel_RightTotal: return MAL_CHANNEL_RIGHT; - case kAudioChannelLabel_HearingImpaired: return MAL_CHANNEL_NONE; - case kAudioChannelLabel_Narration: return MAL_CHANNEL_MONO; - case kAudioChannelLabel_Mono: return MAL_CHANNEL_MONO; - case kAudioChannelLabel_DialogCentricMix: return MAL_CHANNEL_MONO; - case kAudioChannelLabel_CenterSurroundDirect: return MAL_CHANNEL_BACK_CENTER; - case kAudioChannelLabel_Haptic: return MAL_CHANNEL_NONE; - case kAudioChannelLabel_Ambisonic_W: return MAL_CHANNEL_NONE; - case kAudioChannelLabel_Ambisonic_X: return MAL_CHANNEL_NONE; - case kAudioChannelLabel_Ambisonic_Y: return MAL_CHANNEL_NONE; - case kAudioChannelLabel_Ambisonic_Z: return MAL_CHANNEL_NONE; - case kAudioChannelLabel_MS_Mid: return MAL_CHANNEL_LEFT; - case kAudioChannelLabel_MS_Side: return MAL_CHANNEL_RIGHT; - case kAudioChannelLabel_XY_X: return MAL_CHANNEL_LEFT; - case kAudioChannelLabel_XY_Y: return MAL_CHANNEL_RIGHT; - case kAudioChannelLabel_HeadphonesLeft: return MAL_CHANNEL_LEFT; - case kAudioChannelLabel_HeadphonesRight: return MAL_CHANNEL_RIGHT; - case kAudioChannelLabel_ClickTrack: return MAL_CHANNEL_NONE; - case kAudioChannelLabel_ForeignLanguage: return MAL_CHANNEL_NONE; - case kAudioChannelLabel_Discrete: return MAL_CHANNEL_NONE; - case kAudioChannelLabel_Discrete_0: return MAL_CHANNEL_AUX_0; - case kAudioChannelLabel_Discrete_1: return MAL_CHANNEL_AUX_1; - case kAudioChannelLabel_Discrete_2: return MAL_CHANNEL_AUX_2; - case kAudioChannelLabel_Discrete_3: return MAL_CHANNEL_AUX_3; - case kAudioChannelLabel_Discrete_4: return MAL_CHANNEL_AUX_4; - case kAudioChannelLabel_Discrete_5: return MAL_CHANNEL_AUX_5; - case kAudioChannelLabel_Discrete_6: return MAL_CHANNEL_AUX_6; - case kAudioChannelLabel_Discrete_7: return MAL_CHANNEL_AUX_7; - case kAudioChannelLabel_Discrete_8: return MAL_CHANNEL_AUX_8; - case kAudioChannelLabel_Discrete_9: return MAL_CHANNEL_AUX_9; - case kAudioChannelLabel_Discrete_10: return MAL_CHANNEL_AUX_10; - case kAudioChannelLabel_Discrete_11: return MAL_CHANNEL_AUX_11; - case kAudioChannelLabel_Discrete_12: return MAL_CHANNEL_AUX_12; - case kAudioChannelLabel_Discrete_13: return MAL_CHANNEL_AUX_13; - case kAudioChannelLabel_Discrete_14: return MAL_CHANNEL_AUX_14; - case kAudioChannelLabel_Discrete_15: return MAL_CHANNEL_AUX_15; - case kAudioChannelLabel_Discrete_65535: return MAL_CHANNEL_NONE; - - #if 0 // Introduced in a later version of macOS. - case kAudioChannelLabel_HOA_ACN: return MAL_CHANNEL_NONE; - case kAudioChannelLabel_HOA_ACN_0: return MAL_CHANNEL_AUX_0; - case kAudioChannelLabel_HOA_ACN_1: return MAL_CHANNEL_AUX_1; - case kAudioChannelLabel_HOA_ACN_2: return MAL_CHANNEL_AUX_2; - case kAudioChannelLabel_HOA_ACN_3: return MAL_CHANNEL_AUX_3; - case kAudioChannelLabel_HOA_ACN_4: return MAL_CHANNEL_AUX_4; - case kAudioChannelLabel_HOA_ACN_5: return MAL_CHANNEL_AUX_5; - case kAudioChannelLabel_HOA_ACN_6: return MAL_CHANNEL_AUX_6; - case kAudioChannelLabel_HOA_ACN_7: return MAL_CHANNEL_AUX_7; - case kAudioChannelLabel_HOA_ACN_8: return MAL_CHANNEL_AUX_8; - case kAudioChannelLabel_HOA_ACN_9: return MAL_CHANNEL_AUX_9; - case kAudioChannelLabel_HOA_ACN_10: return MAL_CHANNEL_AUX_10; - case kAudioChannelLabel_HOA_ACN_11: return MAL_CHANNEL_AUX_11; - case kAudioChannelLabel_HOA_ACN_12: return MAL_CHANNEL_AUX_12; - case kAudioChannelLabel_HOA_ACN_13: return MAL_CHANNEL_AUX_13; - case kAudioChannelLabel_HOA_ACN_14: return MAL_CHANNEL_AUX_14; - case kAudioChannelLabel_HOA_ACN_15: return MAL_CHANNEL_AUX_15; - case kAudioChannelLabel_HOA_ACN_65024: return MAL_CHANNEL_NONE; - #endif - - default: return MAL_CHANNEL_NONE; - } + + return MAL_SUCCESS; } -mal_result mal_format_from_AudioStreamBasicDescription(const AudioStreamBasicDescription* pDescription, mal_format* pFormatOut) +mal_result mal_context_get_device_info__coreaudio(mal_context* pContext, mal_device_type deviceType, const mal_device_id* pDeviceID, mal_share_mode shareMode, mal_device_info* pDeviceInfo) { - mal_assert(pDescription != NULL); - mal_assert(pFormatOut != NULL); + mal_assert(pContext != NULL); + (void)shareMode; + (void)pDeviceInfo; - *pFormatOut = mal_format_unknown; // Safety. +#if defined(MAL_APPLE_DESKTOP) + // Desktop + // ======= + AudioObjectID deviceObjectID; + mal_result result = mal_find_AudioObjectID(pContext, deviceType, pDeviceID, &deviceObjectID); + if (result != MAL_SUCCESS) { + return result; + } - // There's a few things mini_al doesn't support. - if (pDescription->mFormatID != kAudioFormatLinearPCM) { - return MAL_FORMAT_NOT_SUPPORTED; + result = mal_get_AudioObject_uid(pContext, deviceObjectID, sizeof(pDeviceInfo->id.coreaudio), pDeviceInfo->id.coreaudio); + if (result != MAL_SUCCESS) { + return result; } - // We don't support any non-packed formats that are aligned high. - if ((pDescription->mFormatFlags & kLinearPCMFormatFlagIsAlignedHigh) != 0) { - return MAL_FORMAT_NOT_SUPPORTED; + result = mal_get_AudioObject_name(pContext, deviceObjectID, sizeof(pDeviceInfo->name), pDeviceInfo->name); + if (result != MAL_SUCCESS) { + return result; } - // Big-endian formats are not currently supported, but will be added in a future version of mini_al. - if ((pDescription->mFormatFlags & kLinearPCMFormatFlagIsAlignedHigh) != 0) { - return MAL_FORMAT_NOT_SUPPORTED; + // Formats. + UInt32 streamDescriptionCount; + AudioStreamRangedDescription* pStreamDescriptions; + result = mal_get_AudioObject_stream_descriptions(pContext, deviceObjectID, deviceType, &streamDescriptionCount, &pStreamDescriptions); + if (result != MAL_SUCCESS) { + return result; } - // We are not currently supporting non-interleaved formats (this will be added in a future version of mini_al). - //if ((pDescription->mFormatFlags & kAudioFormatFlagIsNonInterleaved) != 0) { - // return MAL_FORMAT_NOT_SUPPORTED; - //} - - if ((pDescription->mFormatFlags & kLinearPCMFormatFlagIsFloat) != 0) { - if (pDescription->mBitsPerChannel == 32) { - *pFormatOut = mal_format_f32; - return MAL_SUCCESS; + for (UInt32 iStreamDescription = 0; iStreamDescription < streamDescriptionCount; ++iStreamDescription) { + mal_format format; + result = mal_format_from_AudioStreamBasicDescription(&pStreamDescriptions[iStreamDescription].mFormat, &format); + if (result != MAL_SUCCESS) { + continue; } - } else { - if ((pDescription->mFormatFlags & kLinearPCMFormatFlagIsSignedInteger) != 0) { - if (pDescription->mBitsPerChannel == 16) { - *pFormatOut = mal_format_s16; - return MAL_SUCCESS; - } else if (pDescription->mBitsPerChannel == 24) { - if (pDescription->mBytesPerFrame == (pDescription->mBitsPerChannel/8 * pDescription->mChannelsPerFrame)) { - *pFormatOut = mal_format_s24; - return MAL_SUCCESS; - } else { - if (pDescription->mBytesPerFrame/pDescription->mChannelsPerFrame == sizeof(mal_int32)) { - // TODO: Implement mal_format_s24_32. - //*pFormatOut = mal_format_s24_32; - //return MAL_SUCCESS; - return MAL_FORMAT_NOT_SUPPORTED; - } - } - } else if (pDescription->mBitsPerChannel == 32) { - *pFormatOut = mal_format_s32; - return MAL_SUCCESS; + + mal_assert(format != mal_format_unknown); + + // Make sure the format isn't already in the output list. + mal_bool32 exists = MAL_FALSE; + for (mal_uint32 iOutputFormat = 0; iOutputFormat < pDeviceInfo->formatCount; ++iOutputFormat) { + if (pDeviceInfo->formats[iOutputFormat] == format) { + exists = MAL_TRUE; + break; } - } else { - if (pDescription->mBitsPerChannel == 8) { - *pFormatOut = mal_format_u8; - return MAL_SUCCESS; + } + + if (!exists) { + pDeviceInfo->formats[pDeviceInfo->formatCount++] = format; + } + } + + mal_free(pStreamDescriptions); + + + // Channels. + result = mal_get_AudioObject_channel_count(pContext, deviceObjectID, deviceType, &pDeviceInfo->minChannels); + if (result != MAL_SUCCESS) { + return result; + } + pDeviceInfo->maxChannels = pDeviceInfo->minChannels; + + + // Sample rates. + UInt32 sampleRateRangeCount; + AudioValueRange* pSampleRateRanges; + result = mal_get_AudioObject_sample_rates(pContext, deviceObjectID, deviceType, &sampleRateRangeCount, &pSampleRateRanges); + if (result != MAL_SUCCESS) { + return result; + } + + if (sampleRateRangeCount > 0) { + pDeviceInfo->minSampleRate = UINT32_MAX; + pDeviceInfo->maxSampleRate = 0; + for (UInt32 iSampleRate = 0; iSampleRate < sampleRateRangeCount; ++iSampleRate) { + if (pDeviceInfo->minSampleRate > pSampleRateRanges[iSampleRate].mMinimum) { + pDeviceInfo->minSampleRate = pSampleRateRanges[iSampleRate].mMinimum; + } + if (pDeviceInfo->maxSampleRate < pSampleRateRanges[iSampleRate].mMaximum) { + pDeviceInfo->maxSampleRate = pSampleRateRanges[iSampleRate].mMaximum; } } } +#else + // Mobile + // ====== + if (deviceType == mal_device_type_playback) { + mal_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MAL_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + } else { + mal_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MAL_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + } - // Getting here means the format is not supported. - return MAL_FORMAT_NOT_SUPPORTED; -} - -#if defined(MAL_APPLE_DESKTOP) -mal_result mal_get_device_object_ids__coreaudio(mal_context* pContext, UInt32* pDeviceCount, AudioObjectID** ppDeviceObjectIDs) // NOTE: Free the returned buffer with mal_free(). -{ - mal_assert(pContext != NULL); - mal_assert(pDeviceCount != NULL); - mal_assert(ppDeviceObjectIDs != NULL); - (void)pContext; - - // Safety. - *pDeviceCount = 0; - *ppDeviceObjectIDs = NULL; - - AudioObjectPropertyAddress propAddressDevices; - propAddressDevices.mSelector = kAudioHardwarePropertyDevices; - propAddressDevices.mScope = kAudioObjectPropertyScopeGlobal; - propAddressDevices.mElement = kAudioObjectPropertyElementMaster; - - UInt32 deviceObjectsDataSize; - OSStatus status = ((mal_AudioObjectGetPropertyDataSize_proc)pContext->coreaudio.AudioObjectGetPropertyDataSize)(kAudioObjectSystemObject, &propAddressDevices, 0, NULL, &deviceObjectsDataSize); - if (status != noErr) { - return mal_result_from_OSStatus(status); - } + // Retrieving device information is more annoying on mobile than desktop. For simplicity I'm locking this down to whatever format is + // reported on a temporary I/O unit. The problem, however, is that this doesn't return a value for the sample rate which we need to + // retrieve from the AVAudioSession shared instance. + AudioComponentDescription desc; + desc.componentType = kAudioUnitType_Output; + desc.componentSubType = kAudioUnitSubType_RemoteIO; + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + desc.componentFlags = 0; + desc.componentFlagsMask = 0; - AudioObjectID* pDeviceObjectIDs = (AudioObjectID*)mal_malloc(deviceObjectsDataSize); - if (pDeviceObjectIDs == NULL) { - return MAL_OUT_OF_MEMORY; + AudioComponent component = ((mal_AudioComponentFindNext_proc)pContext->coreaudio.AudioComponentFindNext)(NULL, &desc); + if (component == NULL) { + return MAL_FAILED_TO_INIT_BACKEND; } - status = ((mal_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(kAudioObjectSystemObject, &propAddressDevices, 0, NULL, &deviceObjectsDataSize, pDeviceObjectIDs); + AudioUnit audioUnit; + OSStatus status = ((mal_AudioComponentInstanceNew_proc)pContext->coreaudio.AudioComponentInstanceNew)(component, &audioUnit); if (status != noErr) { - mal_free(pDeviceObjectIDs); return mal_result_from_OSStatus(status); } - *pDeviceCount = deviceObjectsDataSize / sizeof(AudioObjectID); - *ppDeviceObjectIDs = pDeviceObjectIDs; - return MAL_SUCCESS; -} - -mal_result mal_get_AudioObject_uid_as_CFStringRef(mal_context* pContext, AudioObjectID objectID, CFStringRef* pUID) -{ - mal_assert(pContext != NULL); - - AudioObjectPropertyAddress propAddress; - propAddress.mSelector = kAudioDevicePropertyDeviceUID; - propAddress.mScope = kAudioObjectPropertyScopeGlobal; - propAddress.mElement = kAudioObjectPropertyElementMaster; - - UInt32 dataSize = sizeof(*pUID); - OSStatus status = ((mal_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(objectID, &propAddress, 0, NULL, &dataSize, pUID); + AudioUnitScope formatScope = (deviceType == mal_device_type_playback) ? kAudioUnitScope_Input : kAudioUnitScope_Output; + AudioUnitElement formatElement = (deviceType == mal_device_type_playback) ? MAL_COREAUDIO_OUTPUT_BUS : MAL_COREAUDIO_INPUT_BUS; + + AudioStreamBasicDescription bestFormat; + UInt32 propSize = sizeof(bestFormat); + status = ((mal_AudioUnitGetProperty_proc)pContext->coreaudio.AudioUnitGetProperty)(audioUnit, kAudioUnitProperty_StreamFormat, formatScope, formatElement, &bestFormat, &propSize); if (status != noErr) { + ((mal_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(audioUnit); return mal_result_from_OSStatus(status); } - return MAL_SUCCESS; -} - -mal_result mal_get_AudioObject_uid(mal_context* pContext, AudioObjectID objectID, size_t bufferSize, char* bufferOut) -{ - mal_assert(pContext != NULL); - - CFStringRef uid; - mal_result result = mal_get_AudioObject_uid_as_CFStringRef(pContext, objectID, &uid); + ((mal_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(audioUnit); + audioUnit = NULL; + + + pDeviceInfo->minChannels = bestFormat.mChannelsPerFrame; + pDeviceInfo->maxChannels = bestFormat.mChannelsPerFrame; + + pDeviceInfo->formatCount = 1; + mal_result result = mal_format_from_AudioStreamBasicDescription(&bestFormat, &pDeviceInfo->formats[0]); if (result != MAL_SUCCESS) { return result; } - if (!((mal_CFStringGetCString_proc)pContext->coreaudio.CFStringGetCString)(uid, bufferOut, bufferSize, kCFStringEncodingUTF8)) { - return MAL_ERROR; + // It looks like Apple are wanting to push the whole AVAudioSession thing. Thus, we need to use that to determine device settings. To do + // this we just get the shared instance and inspect. + @autoreleasepool { + AVAudioSession* pAudioSession = [AVAudioSession sharedInstance]; + mal_assert(pAudioSession != NULL); + + pDeviceInfo->minSampleRate = (mal_uint32)pAudioSession.sampleRate; + pDeviceInfo->maxSampleRate = pDeviceInfo->minSampleRate; } +#endif return MAL_SUCCESS; } -mal_result mal_get_AudioObject_name(mal_context* pContext, AudioObjectID objectID, size_t bufferSize, char* bufferOut) + +void mal_device_uninit__coreaudio(mal_device* pDevice) { - mal_assert(pContext != NULL); + mal_assert(pDevice != NULL); + mal_assert(mal_device__get_state(pDevice) == MAL_STATE_UNINITIALIZED); + + ((mal_AudioComponentInstanceDispose_proc)pDevice->pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnit); + + if (pDevice->coreaudio.pAudioBufferList) { + mal_free(pDevice->coreaudio.pAudioBufferList); + } +} - AudioObjectPropertyAddress propAddress; - propAddress.mSelector = kAudioDevicePropertyDeviceNameCFString; - propAddress.mScope = kAudioObjectPropertyScopeGlobal; - propAddress.mElement = kAudioObjectPropertyElementMaster; - CFStringRef deviceName = NULL; - UInt32 dataSize = sizeof(deviceName); - OSStatus status = ((mal_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(objectID, &propAddress, 0, NULL, &dataSize, &deviceName); - if (status != noErr) { - return mal_result_from_OSStatus(status); - } +OSStatus mal_on_output__coreaudio(void* pUserData, AudioUnitRenderActionFlags* pActionFlags, const AudioTimeStamp* pTimeStamp, UInt32 busNumber, UInt32 frameCount, AudioBufferList* pBufferList) +{ + (void)pActionFlags; + (void)pTimeStamp; + (void)busNumber; + + mal_device* pDevice = (mal_device*)pUserData; + mal_assert(pDevice != NULL); - if (!((mal_CFStringGetCString_proc)pContext->coreaudio.CFStringGetCString)(deviceName, bufferOut, bufferSize, kCFStringEncodingUTF8)) { - return MAL_ERROR; +#if defined(MAL_DEBUG_OUTPUT) + printf("INFO: Output Callback: busNumber=%d, frameCount=%d, mNumberBuffers=%d\n", busNumber, frameCount, pBufferList->mNumberBuffers); +#endif + + // For now we can assume everything is interleaved. + for (UInt32 iBuffer = 0; iBuffer < pBufferList->mNumberBuffers; ++iBuffer) { + if (pBufferList->mBuffers[iBuffer].mNumberChannels == pDevice->internalChannels) { + mal_uint32 frameCountForThisBuffer = pBufferList->mBuffers[iBuffer].mDataByteSize / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels); + if (frameCountForThisBuffer > 0) { + mal_device__read_frames_from_client(pDevice, frameCountForThisBuffer, pBufferList->mBuffers[iBuffer].mData); + } + + #if defined(MAL_DEBUG_OUTPUT) + printf(" frameCount=%d, mNumberChannels=%d, mDataByteSize=%d\n", frameCount, pBufferList->mBuffers[iBuffer].mNumberChannels, pBufferList->mBuffers[iBuffer].mDataByteSize); + #endif + } else { + // This case is where the number of channels in the output buffer do not match our internal channels. It could mean that it's + // not interleaved, in which case we can't handle right now since mini_al does not yet support non-interleaved streams. We just + // output silence here. + mal_zero_memory(pBufferList->mBuffers[iBuffer].mData, pBufferList->mBuffers[iBuffer].mDataByteSize); + + #if defined(MAL_DEBUG_OUTPUT) + printf(" WARNING: Outputting silence. frameCount=%d, mNumberChannels=%d, mDataByteSize=%d\n", frameCount, pBufferList->mBuffers[iBuffer].mNumberChannels, pBufferList->mBuffers[iBuffer].mDataByteSize); + #endif + } } - return MAL_SUCCESS; + return noErr; } -mal_bool32 mal_does_AudioObject_support_scope(mal_context* pContext, AudioObjectID deviceObjectID, AudioObjectPropertyScope scope) +OSStatus mal_on_input__coreaudio(void* pUserData, AudioUnitRenderActionFlags* pActionFlags, const AudioTimeStamp* pTimeStamp, UInt32 busNumber, UInt32 frameCount, AudioBufferList* pUnusedBufferList) { - mal_assert(pContext != NULL); - - // To know whether or not a device is an input device we need ot look at the stream configuration. If it has an output channel it's a - // playback device. - AudioObjectPropertyAddress propAddress; - propAddress.mSelector = kAudioDevicePropertyStreamConfiguration; - propAddress.mScope = scope; - propAddress.mElement = kAudioObjectPropertyElementMaster; + (void)pActionFlags; + (void)pTimeStamp; + (void)busNumber; + (void)frameCount; + (void)pUnusedBufferList; - UInt32 dataSize; - OSStatus status = ((mal_AudioObjectGetPropertyDataSize_proc)pContext->coreaudio.AudioObjectGetPropertyDataSize)(deviceObjectID, &propAddress, 0, NULL, &dataSize); - if (status != noErr) { - return MAL_FALSE; - } + mal_device* pDevice = (mal_device*)pUserData; + mal_assert(pDevice != NULL); - AudioBufferList* pBufferList = (AudioBufferList*)mal_malloc(dataSize); - if (pBufferList == NULL) { - return MAL_FALSE; // Out of memory. - } + AudioBufferList* pRenderedBufferList = (AudioBufferList*)pDevice->coreaudio.pAudioBufferList; + mal_assert(pRenderedBufferList); - status = ((mal_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(deviceObjectID, &propAddress, 0, NULL, &dataSize, pBufferList); +#if defined(MAL_DEBUG_OUTPUT) + printf("INFO: Input Callback: busNumber=%d, frameCount=%d, mNumberBuffers=%d\n", busNumber, frameCount, pRenderedBufferList->mNumberBuffers); +#endif + + OSStatus status = ((mal_AudioUnitRender_proc)pDevice->pContext->coreaudio.AudioUnitRender)((AudioUnit)pDevice->coreaudio.audioUnit, pActionFlags, pTimeStamp, busNumber, frameCount, pRenderedBufferList); if (status != noErr) { - mal_free(pBufferList); - return MAL_FALSE; - } - - mal_bool32 isSupported = MAL_FALSE; - if (pBufferList->mNumberBuffers > 0) { - isSupported = MAL_TRUE; + #if defined(MAL_DEBUG_OUTPUT) + printf(" ERROR: AudioUnitRender() failed with %d\n", status); + #endif + return status; } - mal_free(pBufferList); - return isSupported; -} + // For now we can assume everything is interleaved. + for (UInt32 iBuffer = 0; iBuffer < pRenderedBufferList->mNumberBuffers; ++iBuffer) { + if (pRenderedBufferList->mBuffers[iBuffer].mNumberChannels == pDevice->internalChannels) { + mal_device__send_frames_to_client(pDevice, frameCount, pRenderedBufferList->mBuffers[iBuffer].mData); + #if defined(MAL_DEBUG_OUTPUT) + printf(" mDataByteSize=%d\n", pRenderedBufferList->mBuffers[iBuffer].mDataByteSize); + #endif + } else { + // This case is where the number of channels in the output buffer do not match our internal channels. It could mean that it's + // not interleaved, in which case we can't handle right now since mini_al does not yet support non-interleaved streams. + } + } -mal_bool32 mal_does_AudioObject_support_playback(mal_context* pContext, AudioObjectID deviceObjectID) -{ - return mal_does_AudioObject_support_scope(pContext, deviceObjectID, kAudioObjectPropertyScopeOutput); + return noErr; } -mal_bool32 mal_does_AudioObject_support_capture(mal_context* pContext, AudioObjectID deviceObjectID) +void on_start_stop__coreaudio(void* pUserData, AudioUnit audioUnit, AudioUnitPropertyID propertyID, AudioUnitScope scope, AudioUnitElement element) { - return mal_does_AudioObject_support_scope(pContext, deviceObjectID, kAudioObjectPropertyScopeInput); + (void)propertyID; + + mal_device* pDevice = (mal_device*)pUserData; + mal_assert(pDevice != NULL); + + UInt32 isRunning; + UInt32 isRunningSize = sizeof(isRunning); + OSStatus status = ((mal_AudioUnitGetProperty_proc)pDevice->pContext->coreaudio.AudioUnitGetProperty)(audioUnit, kAudioOutputUnitProperty_IsRunning, scope, element, &isRunning, &isRunningSize); + if (status != noErr) { + return; // Don't really know what to do in this case... just ignore it, I suppose... + } + + if (!isRunning) { + mal_stop_proc onStop = pDevice->onStop; + if (onStop) { + onStop(pDevice); + } + } } -mal_result mal_get_AudioObject_stream_descriptions(mal_context* pContext, AudioObjectID deviceObjectID, mal_device_type deviceType, UInt32* pDescriptionCount, AudioStreamRangedDescription** ppDescriptions) // NOTE: Free the returned pointer with mal_free(). +mal_result mal_device_init__coreaudio(mal_context* pContext, mal_device_type deviceType, const mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice) { mal_assert(pContext != NULL); - mal_assert(pDescriptionCount != NULL); - mal_assert(ppDescriptions != NULL); + mal_assert(pConfig != NULL); + mal_assert(pDevice != NULL); + mal_assert(deviceType == mal_device_type_playback || deviceType == mal_device_type_capture); - // TODO: Experiment with kAudioStreamPropertyAvailablePhysicalFormats instead of (or in addition to) kAudioStreamPropertyAvailableVirtualFormats. My - // MacBook Pro uses s24/32 format, however, which mini_al does not currently support. - AudioObjectPropertyAddress propAddress; - propAddress.mSelector = kAudioStreamPropertyAvailableVirtualFormats; //kAudioStreamPropertyAvailablePhysicalFormats; - propAddress.mScope = (deviceType == mal_device_type_playback) ? kAudioObjectPropertyScopeOutput : kAudioObjectPropertyScopeInput; - propAddress.mElement = kAudioObjectPropertyElementMaster; + mal_result result; - UInt32 dataSize; - OSStatus status = ((mal_AudioObjectGetPropertyDataSize_proc)pContext->coreaudio.AudioObjectGetPropertyDataSize)(deviceObjectID, &propAddress, 0, NULL, &dataSize); - if (status != noErr) { - return mal_result_from_OSStatus(status); +#if defined(MAL_APPLE_DESKTOP) + AudioObjectID deviceObjectID; + result = mal_find_AudioObjectID(pContext, deviceType, pDeviceID, &deviceObjectID); + if (result != MAL_SUCCESS) { + return result; } - AudioStreamRangedDescription* pDescriptions = (AudioStreamRangedDescription*)mal_malloc(dataSize); - if (pDescriptions == NULL) { - return MAL_OUT_OF_MEMORY; - } + pDevice->coreaudio.deviceObjectID = deviceObjectID; +#endif - status = ((mal_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(deviceObjectID, &propAddress, 0, NULL, &dataSize, pDescriptions); - if (status != noErr) { - mal_free(pDescriptions); - return mal_result_from_OSStatus(status); + // Core audio doesn't really use the notion of a period so we can leave this unmodified, but not too over the top. + if (pDevice->periods < 1) { + pDevice->periods = 1; + } + if (pDevice->periods > 16) { + pDevice->periods = 16; } - *pDescriptionCount = dataSize / sizeof(*pDescriptions); - *ppDescriptions = pDescriptions; - return MAL_SUCCESS; -} - - -mal_result mal_get_AudioObject_channel_layout(mal_context* pContext, AudioObjectID deviceObjectID, mal_device_type deviceType, AudioChannelLayout** ppChannelLayout) // NOTE: Free the returned pointer with mal_free(). -{ - mal_assert(pContext != NULL); - mal_assert(ppChannelLayout != NULL); + // Audio component. + AudioComponentDescription desc; + desc.componentType = kAudioUnitType_Output; +#if defined(MAL_APPLE_DESKTOP) + desc.componentSubType = kAudioUnitSubType_HALOutput; +#else + desc.componentSubType = kAudioUnitSubType_RemoteIO; +#endif + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + desc.componentFlags = 0; + desc.componentFlagsMask = 0; - *ppChannelLayout = NULL; // Safety. + pDevice->coreaudio.component = ((mal_AudioComponentFindNext_proc)pContext->coreaudio.AudioComponentFindNext)(NULL, &desc); + if (pDevice->coreaudio.component == NULL) { + return MAL_FAILED_TO_INIT_BACKEND; + } - AudioObjectPropertyAddress propAddress; - propAddress.mSelector = kAudioDevicePropertyPreferredChannelLayout; - propAddress.mScope = (deviceType == mal_device_type_playback) ? kAudioObjectPropertyScopeOutput : kAudioObjectPropertyScopeInput; - propAddress.mElement = kAudioObjectPropertyElementMaster; - UInt32 dataSize; - OSStatus status = ((mal_AudioObjectGetPropertyDataSize_proc)pContext->coreaudio.AudioObjectGetPropertyDataSize)(deviceObjectID, &propAddress, 0, NULL, &dataSize); + // Audio unit. + OSStatus status = ((mal_AudioComponentInstanceNew_proc)pContext->coreaudio.AudioComponentInstanceNew)((AudioComponent)pDevice->coreaudio.component, (AudioUnit*)&pDevice->coreaudio.audioUnit); if (status != noErr) { return mal_result_from_OSStatus(status); } - AudioChannelLayout* pChannelLayout = (AudioChannelLayout*)mal_malloc(dataSize); - if (pChannelLayout == NULL) { - return MAL_OUT_OF_MEMORY; + + // The input/output buses need to be explicitly enabled and disabled. We set the flag based on the output unit first, then we just swap it for input. + UInt32 enableIOFlag = 1; + if (deviceType == mal_device_type_capture) { + enableIOFlag = 0; } - status = ((mal_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(deviceObjectID, &propAddress, 0, NULL, &dataSize, pChannelLayout); + status = ((mal_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)((AudioUnit)pDevice->coreaudio.audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, MAL_COREAUDIO_OUTPUT_BUS, &enableIOFlag, sizeof(enableIOFlag)); if (status != noErr) { - mal_free(pChannelLayout); + ((mal_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnit); return mal_result_from_OSStatus(status); } - *ppChannelLayout = pChannelLayout; - return MAL_SUCCESS; -} - -mal_result mal_get_AudioObject_channel_count(mal_context* pContext, AudioObjectID deviceObjectID, mal_device_type deviceType, mal_uint32* pChannelCount) -{ - mal_assert(pContext != NULL); - mal_assert(pChannelCount != NULL); - - *pChannelCount = 0; // Safety. - - AudioChannelLayout* pChannelLayout; - mal_result result = mal_get_AudioObject_channel_layout(pContext, deviceObjectID, deviceType, &pChannelLayout); - if (result != MAL_SUCCESS) { - return result; + enableIOFlag = (enableIOFlag == 0) ? 1 : 0; + status = ((mal_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)((AudioUnit)pDevice->coreaudio.audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, MAL_COREAUDIO_INPUT_BUS, &enableIOFlag, sizeof(enableIOFlag)); + if (status != noErr) { + ((mal_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnit); + return mal_result_from_OSStatus(status); } - if (pChannelLayout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelDescriptions) { - *pChannelCount = pChannelLayout->mNumberChannelDescriptions; - } else if (pChannelLayout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelBitmap) { - *pChannelCount = mal_count_set_bits(pChannelLayout->mChannelBitmap); - } else { - *pChannelCount = AudioChannelLayoutTag_GetNumberOfChannels(pChannelLayout->mChannelLayoutTag); + + // Set the device to use with this audio unit. This is only used on desktop since we are using defaults on mobile. +#if defined(MAL_APPLE_DESKTOP) + status = ((mal_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)((AudioUnit)pDevice->coreaudio.audioUnit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, (deviceType == mal_device_type_playback) ? MAL_COREAUDIO_OUTPUT_BUS : MAL_COREAUDIO_INPUT_BUS, &deviceObjectID, sizeof(AudioDeviceID)); + if (status != noErr) { + ((mal_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnit); + return mal_result_from_OSStatus(result); } +#endif - mal_free(pChannelLayout); - return MAL_SUCCESS; -} - -mal_result mal_get_channel_map_from_AudioChannelLayout(AudioChannelLayout* pChannelLayout, mal_channel channelMap[MAL_MAX_CHANNELS]) -{ - mal_assert(pChannelLayout != NULL); + // Format. This is the hardest part of initialization because there's a few variables to take into account. + // 1) The format must be supported by the device. + // 2) The format must be supported mini_al. + // 3) There's a priority that mini_al prefers. + // + // Ideally we would like to use a format that's as close to the hardware as possible so we can get as close to a passthrough as possible. The + // most important property is the sample rate. mini_al can do format conversion for any sample rate and channel count, but cannot do the same + // for the sample data format. If the sample data format is not supported by mini_al it must be ignored completely. + // + // On mobile platforms this is a bit different. We just force the use of whatever the audio unit's current format is set to. + AudioStreamBasicDescription bestFormat; + { + AudioUnitScope formatScope = (deviceType == mal_device_type_playback) ? kAudioUnitScope_Input : kAudioUnitScope_Output; + AudioUnitElement formatElement = (deviceType == mal_device_type_playback) ? MAL_COREAUDIO_OUTPUT_BUS : MAL_COREAUDIO_INPUT_BUS; - if (pChannelLayout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelDescriptions) { - for (UInt32 iChannel = 0; iChannel < pChannelLayout->mNumberChannelDescriptions; ++iChannel) { - channelMap[iChannel] = mal_channel_from_AudioChannelLabel(pChannelLayout->mChannelDescriptions[iChannel].mChannelLabel); + #if defined(MAL_APPLE_DESKTOP) + result = mal_device_find_best_format__coreaudio(pDevice, &bestFormat); + if (result != MAL_SUCCESS) { + ((mal_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnit); + return result; } - } else -#if 0 - if (pChannelLayout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelBitmap) { - // This is the same kind of system that's used by Windows audio APIs. - UInt32 iChannel = 0; - AudioChannelBitmap bitmap = pChannelLayout->mChannelBitmap; - for (UInt32 iBit = 0; iBit < 32; ++iBit) { - AudioChannelBitmap bit = bitmap & (1 << iBit); - if (bit != 0) { - channelMap[iChannel++] = mal_channel_from_AudioChannelBit(bit); - } + + // From what I can see, Apple's documentation implies that we should keep the sample rate consistent. + AudioStreamBasicDescription origFormat; + UInt32 origFormatSize = sizeof(origFormat); + if (deviceType == mal_device_type_playback) { + status = ((mal_AudioUnitGetProperty_proc)pContext->coreaudio.AudioUnitGetProperty)((AudioUnit)pDevice->coreaudio.audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, MAL_COREAUDIO_OUTPUT_BUS, &origFormat, &origFormatSize); + } else { + status = ((mal_AudioUnitGetProperty_proc)pContext->coreaudio.AudioUnitGetProperty)((AudioUnit)pDevice->coreaudio.audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, MAL_COREAUDIO_INPUT_BUS, &origFormat, &origFormatSize); } - } else -#endif - { - // Need to use the tag to determine the channel map. For now I'm just assuming a default channel map, but later on this should - // be updated to determine the mapping based on the tag. - UInt32 channelCount = AudioChannelLayoutTag_GetNumberOfChannels(pChannelLayout->mChannelLayoutTag); - switch (pChannelLayout->mChannelLayoutTag) - { - case kAudioChannelLayoutTag_Mono: - case kAudioChannelLayoutTag_Stereo: - case kAudioChannelLayoutTag_StereoHeadphones: - case kAudioChannelLayoutTag_MatrixStereo: - case kAudioChannelLayoutTag_MidSide: - case kAudioChannelLayoutTag_XY: - case kAudioChannelLayoutTag_Binaural: - case kAudioChannelLayoutTag_Ambisonic_B_Format: - { - mal_get_standard_channel_map(mal_standard_channel_map_default, channelCount, channelMap); - } break; - - case kAudioChannelLayoutTag_Octagonal: - { - channelMap[7] = MAL_CHANNEL_SIDE_RIGHT; - channelMap[6] = MAL_CHANNEL_SIDE_LEFT; - } // Intentional fallthrough. - case kAudioChannelLayoutTag_Hexagonal: - { - channelMap[5] = MAL_CHANNEL_BACK_CENTER; - } // Intentional fallthrough. - case kAudioChannelLayoutTag_Pentagonal: - { - channelMap[4] = MAL_CHANNEL_FRONT_CENTER; - } // Intentional fallghrough. - case kAudioChannelLayoutTag_Quadraphonic: - { - channelMap[3] = MAL_CHANNEL_BACK_RIGHT; - channelMap[2] = MAL_CHANNEL_BACK_LEFT; - channelMap[1] = MAL_CHANNEL_RIGHT; - channelMap[0] = MAL_CHANNEL_LEFT; - } break; + + if (status != noErr) { + ((mal_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnit); + return result; + } + + bestFormat.mSampleRate = origFormat.mSampleRate; + + status = ((mal_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)((AudioUnit)pDevice->coreaudio.audioUnit, kAudioUnitProperty_StreamFormat, formatScope, formatElement, &bestFormat, sizeof(bestFormat)); + if (status != noErr) { + // We failed to set the format, so fall back to the current format of the audio unit. + bestFormat = origFormat; + } + #else + UInt32 propSize = sizeof(bestFormat); + status = ((mal_AudioUnitGetProperty_proc)pContext->coreaudio.AudioUnitGetProperty)((AudioUnit)pDevice->coreaudio.audioUnit, kAudioUnitProperty_StreamFormat, formatScope, formatElement, &bestFormat, &propSize); + if (status != noErr) { + ((mal_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnit); + return mal_result_from_OSStatus(status); + } + + // Sample rate is a little different here because for some reason kAudioUnitProperty_StreamFormat returns 0... Oh well. We need to instead try + // setting the sample rate to what the user has requested and then just see the results of it. Need to use some Objective-C here for this since + // it depends on Apple's AVAudioSession API. To do this we just get the shared AVAudioSession instance and then set it. Note that from what I + // can tell, it looks like the sample rate is shared between playback and capture for everything. + @autoreleasepool { + AVAudioSession* pAudioSession = [AVAudioSession sharedInstance]; + mal_assert(pAudioSession != NULL); - // TODO: Add support for more tags here. + [pAudioSession setPreferredSampleRate:(double)pDevice->sampleRate error:nil]; + bestFormat.mSampleRate = pAudioSession.sampleRate; + } - default: - { - mal_get_standard_channel_map(mal_standard_channel_map_default, channelCount, channelMap); - } break; + status = ((mal_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)((AudioUnit)pDevice->coreaudio.audioUnit, kAudioUnitProperty_StreamFormat, formatScope, formatElement, &bestFormat, sizeof(bestFormat)); + if (status != noErr) { + ((mal_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnit); + return mal_result_from_OSStatus(status); + } + #endif + + result = mal_format_from_AudioStreamBasicDescription(&bestFormat, &pDevice->internalFormat); + if (result != MAL_SUCCESS) { + ((mal_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnit); + return result; + } + + if (pDevice->internalFormat == mal_format_unknown) { + ((mal_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnit); + return MAL_FORMAT_NOT_SUPPORTED; } + + pDevice->internalChannels = bestFormat.mChannelsPerFrame; + pDevice->internalSampleRate = bestFormat.mSampleRate; + } + + + // Internal channel map. +#if defined(MAL_APPLE_DESKTOP) + result = mal_get_AudioObject_channel_map(pContext, deviceObjectID, deviceType, pDevice->internalChannelMap); + if (result != MAL_SUCCESS) { + return result; } +#else + // TODO: Figure out how to get the channel map using AVAudioSession. + mal_get_standard_channel_map(mal_standard_channel_map_default, pDevice->internalChannels, pDevice->internalChannelMap); +#endif - return MAL_SUCCESS; -} - -mal_result mal_get_AudioObject_channel_map(mal_context* pContext, AudioObjectID deviceObjectID, mal_device_type deviceType, mal_channel channelMap[MAL_MAX_CHANNELS]) -{ - mal_assert(pContext != NULL); - AudioChannelLayout* pChannelLayout; - mal_result result = mal_get_AudioObject_channel_layout(pContext, deviceObjectID, deviceType, &pChannelLayout); - if (result != MAL_SUCCESS) { - return result; // Rather than always failing here, would it be more robust to simply assume a default? + // Buffer size. Not allowing this to be configurable on iOS. + mal_uint32 actualBufferSizeInFrames = pDevice->bufferSizeInFrames; + if (actualBufferSizeInFrames < pDevice->periods) { + actualBufferSizeInFrames = pDevice->periods; } - result = mal_get_channel_map_from_AudioChannelLayout(pChannelLayout, channelMap); +#if defined(MAL_APPLE_DESKTOP) + if (pDevice->usingDefaultBufferSize) { + // CPU speed is a factor to consider when determine how large of a buffer we need. + float fCPUSpeed = mal_calculate_cpu_speed_factor(); + + // In my admittedly limited testing, capture latency seems to be about the same as playback with Core Audio, at least on my MacBook Pro. On other + // backends, however, this is often different. I am therefore leaving the logic below in place just in case I need to do some capture/playback + // specific tweaking. + float fDeviceType; + if (deviceType == mal_device_type_playback) { + fDeviceType = 1.0f; + } else { + fDeviceType = 6.0f; + } + + // Backend tax. Need to fiddle with this. + float fBackend = 1.0f; + + actualBufferSizeInFrames = mal_calculate_default_buffer_size_in_frames(pConfig->performanceProfile, pConfig->sampleRate, fCPUSpeed*fDeviceType*fBackend); + if (actualBufferSizeInFrames < pDevice->periods) { + actualBufferSizeInFrames = pDevice->periods; + } + } + + actualBufferSizeInFrames = actualBufferSizeInFrames / pDevice->periods; + result = mal_set_AudioObject_buffer_size_in_frames(pContext, deviceObjectID, deviceType, &actualBufferSizeInFrames); if (result != MAL_SUCCESS) { return result; } - - return result; -} +#else + actualBufferSizeInFrames = 4096; +#endif -mal_result mal_get_AudioObject_sample_rates(mal_context* pContext, AudioObjectID deviceObjectID, mal_device_type deviceType, UInt32* pSampleRateRangesCount, AudioValueRange** ppSampleRateRanges) // NOTE: Free the returned pointer with mal_free(). -{ - mal_assert(pContext != NULL); - mal_assert(pSampleRateRangesCount != NULL); - mal_assert(ppSampleRateRanges != NULL); - - // Safety. - *pSampleRateRangesCount = 0; - *ppSampleRateRanges = NULL; + pDevice->bufferSizeInFrames = actualBufferSizeInFrames * pDevice->periods; - AudioObjectPropertyAddress propAddress; - propAddress.mSelector = kAudioDevicePropertyAvailableNominalSampleRates; - propAddress.mScope = (deviceType == mal_device_type_playback) ? kAudioObjectPropertyScopeOutput : kAudioObjectPropertyScopeInput; - propAddress.mElement = kAudioObjectPropertyElementMaster; + // During testing I discovered that the buffer size can be too big. You'll get an error like this: + // + // kAudioUnitErr_TooManyFramesToProcess : inFramesToProcess=4096, mMaxFramesPerSlice=512 + // + // Note how inFramesToProcess is smaller than mMaxFramesPerSlice. To fix, we need to set kAudioUnitProperty_MaximumFramesPerSlice to that + // of the size of our buffer, or do it the other way around and set our buffer size to the kAudioUnitProperty_MaximumFramesPerSlice. + { + /*AudioUnitScope propScope = (deviceType == mal_device_type_playback) ? kAudioUnitScope_Input : kAudioUnitScope_Output; + AudioUnitElement propBus = (deviceType == mal_device_type_playback) ? MAL_COREAUDIO_OUTPUT_BUS : MAL_COREAUDIO_INPUT_BUS; - UInt32 dataSize; - OSStatus status = ((mal_AudioObjectGetPropertyDataSize_proc)pContext->coreaudio.AudioObjectGetPropertyDataSize)(deviceObjectID, &propAddress, 0, NULL, &dataSize); - if (status != noErr) { - return mal_result_from_OSStatus(status); + status = ((mal_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)((AudioUnit)pDevice->coreaudio.audioUnit, kAudioUnitProperty_MaximumFramesPerSlice, propScope, propBus, &actualBufferSizeInFrames, sizeof(actualBufferSizeInFrames)); + if (status != noErr) { + ((mal_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnit); + return mal_result_from_OSStatus(status); + }*/ + + status = ((mal_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)((AudioUnit)pDevice->coreaudio.audioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &actualBufferSizeInFrames, sizeof(actualBufferSizeInFrames)); + if (status != noErr) { + ((mal_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnit); + return mal_result_from_OSStatus(status); + } } - AudioValueRange* pSampleRateRanges = (AudioValueRange*)mal_malloc(dataSize); - if (pSampleRateRanges == NULL) { - return MAL_OUT_OF_MEMORY; + + // Callbacks. + AURenderCallbackStruct callbackInfo; + callbackInfo.inputProcRefCon = pDevice; + if (deviceType == mal_device_type_playback) { + callbackInfo.inputProc = mal_on_output__coreaudio; + status = ((mal_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)((AudioUnit)pDevice->coreaudio.audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, MAL_COREAUDIO_OUTPUT_BUS, &callbackInfo, sizeof(callbackInfo)); + if (status != noErr) { + ((mal_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnit); + return mal_result_from_OSStatus(status); + } + } else { + callbackInfo.inputProc = mal_on_input__coreaudio; + status = ((mal_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)((AudioUnit)pDevice->coreaudio.audioUnit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, MAL_COREAUDIO_INPUT_BUS, &callbackInfo, sizeof(callbackInfo)); + if (status != noErr) { + ((mal_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnit); + return mal_result_from_OSStatus(status); + } } - status = ((mal_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(deviceObjectID, &propAddress, 0, NULL, &dataSize, pSampleRateRanges); + // We need to listen for stop events. + status = ((mal_AudioUnitAddPropertyListener_proc)pContext->coreaudio.AudioUnitAddPropertyListener)((AudioUnit)pDevice->coreaudio.audioUnit, kAudioOutputUnitProperty_IsRunning, on_start_stop__coreaudio, pDevice); if (status != noErr) { - mal_free(pSampleRateRanges); + ((mal_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnit); return mal_result_from_OSStatus(status); } - *pSampleRateRangesCount = dataSize / sizeof(*pSampleRateRanges); - *ppSampleRateRanges = pSampleRateRanges; - return MAL_SUCCESS; -} - -mal_result mal_get_AudioObject_get_closest_sample_rate(mal_context* pContext, AudioObjectID deviceObjectID, mal_device_type deviceType, mal_uint32 sampleRateIn, mal_uint32* pSampleRateOut) -{ - mal_assert(pContext != NULL); - mal_assert(pSampleRateOut != NULL); - - *pSampleRateOut = 0; // Safety. - - UInt32 sampleRateRangeCount; - AudioValueRange* pSampleRateRanges; - mal_result result = mal_get_AudioObject_sample_rates(pContext, deviceObjectID, deviceType, &sampleRateRangeCount, &pSampleRateRanges); - if (result != MAL_SUCCESS) { - return result; - } - - if (sampleRateRangeCount == 0) { - mal_free(pSampleRateRanges); - return MAL_ERROR; // Should never hit this case should we? - } - if (sampleRateIn == 0) { - // Search in order of mini_al's preferred priority. - for (UInt32 iMALSampleRate = 0; iMALSampleRate < mal_countof(g_malStandardSampleRatePriorities); ++iMALSampleRate) { - mal_uint32 malSampleRate = g_malStandardSampleRatePriorities[iMALSampleRate]; - for (UInt32 iCASampleRate = 0; iCASampleRate < sampleRateRangeCount; ++iCASampleRate) { - AudioValueRange caSampleRate = pSampleRateRanges[iCASampleRate]; - if (caSampleRate.mMinimum <= malSampleRate && caSampleRate.mMaximum >= malSampleRate) { - *pSampleRateOut = malSampleRate; - mal_free(pSampleRateRanges); - return MAL_SUCCESS; - } - } + // We need a buffer list if this is an input device. We render into this in the input callback. + if (deviceType == mal_device_type_capture) { + mal_bool32 isInterleaved = (bestFormat.mFormatFlags & kAudioFormatFlagIsNonInterleaved) == 0; + + size_t allocationSize = sizeof(AudioBufferList) - sizeof(AudioBuffer); // Subtract sizeof(AudioBuffer) because that part is dynamically sized. + if (isInterleaved) { + // Interleaved case. This is the simple case because we just have one buffer. + allocationSize += sizeof(AudioBuffer) * 1; + allocationSize += actualBufferSizeInFrames * mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels); + } else { + // Non-interleaved case. This is the more complex case because there's more than one buffer. + allocationSize += sizeof(AudioBuffer) * pDevice->internalChannels; + allocationSize += actualBufferSizeInFrames * mal_get_bytes_per_sample(pDevice->internalFormat) * pDevice->internalChannels; } - // If we get here it means none of mini_al's standard sample rates matched any of the supported sample rates from the device. In this - // case we just fall back to the first one reported by Core Audio. - mal_assert(sampleRateRangeCount > 0); + AudioBufferList* pBufferList = (AudioBufferList*)mal_malloc(allocationSize); + if (pBufferList == NULL) { + ((mal_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnit); + return MAL_OUT_OF_MEMORY; + } - *pSampleRateOut = pSampleRateRanges[0].mMinimum; - mal_free(pSampleRateRanges); - return MAL_SUCCESS; - } else { - // Find the closest match to this sample rate. - UInt32 currentAbsoluteDifference = INT32_MAX; - UInt32 iCurrentClosestRange = (UInt32)-1; - for (UInt32 iRange = 0; iRange < sampleRateRangeCount; ++iRange) { - if (pSampleRateRanges[iRange].mMinimum <= sampleRateIn && pSampleRateRanges[iRange].mMaximum >= sampleRateIn) { - *pSampleRateOut = sampleRateIn; - mal_free(pSampleRateRanges); - return MAL_SUCCESS; - } else { - UInt32 absoluteDifference; - if (pSampleRateRanges[iRange].mMinimum > sampleRateIn) { - absoluteDifference = pSampleRateRanges[iRange].mMinimum - sampleRateIn; - } else { - absoluteDifference = sampleRateIn - pSampleRateRanges[iRange].mMaximum; - } - - if (currentAbsoluteDifference > absoluteDifference) { - currentAbsoluteDifference = absoluteDifference; - iCurrentClosestRange = iRange; - } + if (isInterleaved) { + pBufferList->mNumberBuffers = 1; + pBufferList->mBuffers[0].mNumberChannels = pDevice->internalChannels; + pBufferList->mBuffers[0].mDataByteSize = actualBufferSizeInFrames * mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels); + pBufferList->mBuffers[0].mData = (mal_uint8*)pBufferList + sizeof(AudioBufferList); + } else { + pBufferList->mNumberBuffers = pDevice->internalChannels; + for (mal_uint32 iBuffer = 0; iBuffer < pBufferList->mNumberBuffers; ++iBuffer) { + pBufferList->mBuffers[iBuffer].mNumberChannels = 1; + pBufferList->mBuffers[iBuffer].mDataByteSize = actualBufferSizeInFrames * mal_get_bytes_per_sample(pDevice->internalFormat); + pBufferList->mBuffers[iBuffer].mData = (mal_uint8*)pBufferList + ((sizeof(AudioBufferList) - sizeof(AudioBuffer)) + (sizeof(AudioBuffer) * pDevice->internalChannels)) + (actualBufferSizeInFrames * mal_get_bytes_per_sample(pDevice->internalFormat) * iBuffer); } } - mal_assert(iCurrentClosestRange != (UInt32)-1); - - *pSampleRateOut = pSampleRateRanges[iCurrentClosestRange].mMinimum; - mal_free(pSampleRateRanges); - return MAL_SUCCESS; + pDevice->coreaudio.pAudioBufferList = pBufferList; } - // Should never get here, but it would mean we weren't able to find any suitable sample rates. - //mal_free(pSampleRateRanges); - //return MAL_ERROR; -} - - -mal_result mal_get_AudioObject_closest_buffer_size_in_frames(mal_context* pContext, AudioObjectID deviceObjectID, mal_device_type deviceType, mal_uint32 bufferSizeInFramesIn, mal_uint32* pBufferSizeInFramesOut) -{ - mal_assert(pContext != NULL); - mal_assert(pBufferSizeInFramesOut != NULL); - - *pBufferSizeInFramesOut = 0; // Safety. - AudioObjectPropertyAddress propAddress; - propAddress.mSelector = kAudioDevicePropertyBufferFrameSizeRange; - propAddress.mScope = (deviceType == mal_device_type_playback) ? kAudioObjectPropertyScopeOutput : kAudioObjectPropertyScopeInput; - propAddress.mElement = kAudioObjectPropertyElementMaster; - - AudioValueRange bufferSizeRange; - UInt32 dataSize = sizeof(bufferSizeRange); - OSStatus status = ((mal_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(deviceObjectID, &propAddress, 0, NULL, &dataSize, &bufferSizeRange); + // Initialize the audio unit. + status = ((mal_AudioUnitInitialize_proc)pContext->coreaudio.AudioUnitInitialize)((AudioUnit)pDevice->coreaudio.audioUnit); if (status != noErr) { + mal_free(pDevice->coreaudio.pAudioBufferList); + ((mal_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnit); return mal_result_from_OSStatus(status); } - // This is just a clamp. - if (bufferSizeInFramesIn < bufferSizeRange.mMinimum) { - *pBufferSizeInFramesOut = (mal_uint32)bufferSizeRange.mMinimum; - } else if (bufferSizeInFramesIn > bufferSizeRange.mMaximum) { - *pBufferSizeInFramesOut = (mal_uint32)bufferSizeRange.mMaximum; - } else { - *pBufferSizeInFramesOut = bufferSizeInFramesIn; - } return MAL_SUCCESS; } -mal_result mal_set_AudioObject_buffer_size_in_frames(mal_context* pContext, AudioObjectID deviceObjectID, mal_device_type deviceType, mal_uint32* pBufferSizeInOut) +mal_result mal_device__start_backend__coreaudio(mal_device* pDevice) { - mal_assert(pContext != NULL); - - mal_uint32 chosenBufferSizeInFrames; - mal_result result = mal_get_AudioObject_closest_buffer_size_in_frames(pContext, deviceObjectID, deviceType, *pBufferSizeInOut, &chosenBufferSizeInFrames); - if (result != MAL_SUCCESS) { - return result; + mal_assert(pDevice != NULL); + + OSStatus status = ((mal_AudioOutputUnitStart_proc)pDevice->pContext->coreaudio.AudioOutputUnitStart)((AudioUnit)pDevice->coreaudio.audioUnit); + if (status != noErr) { + return mal_result_from_OSStatus(status); } - - // Try setting the size of the buffer... If this fails we just use whatever is currently set. - AudioObjectPropertyAddress propAddress; - propAddress.mSelector = kAudioDevicePropertyBufferFrameSize; - propAddress.mScope = (deviceType == mal_device_type_playback) ? kAudioObjectPropertyScopeOutput : kAudioObjectPropertyScopeInput; - propAddress.mElement = kAudioObjectPropertyElementMaster; - ((mal_AudioObjectSetPropertyData_proc)pContext->coreaudio.AudioObjectSetPropertyData)(deviceObjectID, &propAddress, 0, NULL, sizeof(chosenBufferSizeInFrames), &chosenBufferSizeInFrames); + return MAL_SUCCESS; +} + +mal_result mal_device__stop_backend__coreaudio(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); - // Get the actual size of the buffer. - UInt32 dataSize = sizeof(*pBufferSizeInOut); - OSStatus status = ((mal_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(deviceObjectID, &propAddress, 0, NULL, &dataSize, &chosenBufferSizeInFrames); + OSStatus status = ((mal_AudioOutputUnitStop_proc)pDevice->pContext->coreaudio.AudioOutputUnitStop)((AudioUnit)pDevice->coreaudio.audioUnit); if (status != noErr) { return mal_result_from_OSStatus(status); } - *pBufferSizeInOut = chosenBufferSizeInFrames; return MAL_SUCCESS; } -mal_result mal_find_AudioObjectID(mal_context* pContext, mal_device_type type, const mal_device_id* pDeviceID, AudioObjectID* pDeviceObjectID) +mal_result mal_context_uninit__coreaudio(mal_context* pContext) { mal_assert(pContext != NULL); - mal_assert(pDeviceObjectID != NULL); - - // Safety. - *pDeviceObjectID = 0; - - if (pDeviceID == NULL) { - // Default device. - AudioObjectPropertyAddress propAddressDefaultDevice; - propAddressDefaultDevice.mScope = kAudioObjectPropertyScopeGlobal; - propAddressDefaultDevice.mElement = kAudioObjectPropertyElementMaster; - if (type == mal_device_type_playback) { - propAddressDefaultDevice.mSelector = kAudioHardwarePropertyDefaultOutputDevice; - } else { - propAddressDefaultDevice.mSelector = kAudioHardwarePropertyDefaultInputDevice; - } - - UInt32 defaultDeviceObjectIDSize = sizeof(AudioObjectID); - AudioObjectID defaultDeviceObjectID; - OSStatus status = ((mal_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(kAudioObjectSystemObject, &propAddressDefaultDevice, 0, NULL, &defaultDeviceObjectIDSize, &defaultDeviceObjectID); - if (status == noErr) { - *pDeviceObjectID = defaultDeviceObjectID; - return MAL_SUCCESS; - } - } else { - // Explicit device. - UInt32 deviceCount; - AudioObjectID* pDeviceObjectIDs; - mal_result result = mal_get_device_object_ids__coreaudio(pContext, &deviceCount, &pDeviceObjectIDs); - if (result != MAL_SUCCESS) { - return result; - } - - for (UInt32 iDevice = 0; iDevice < deviceCount; ++iDevice) { - AudioObjectID deviceObjectID = pDeviceObjectIDs[iDevice]; - - char uid[256]; - if (mal_get_AudioObject_uid(pContext, deviceObjectID, sizeof(uid), uid) != MAL_SUCCESS) { - continue; - } - - if (type == mal_device_type_playback) { - if (mal_does_AudioObject_support_playback(pContext, deviceObjectID)) { - if (strcmp(uid, pDeviceID->coreaudio) == 0) { - *pDeviceObjectID = deviceObjectID; - return MAL_SUCCESS; - } - } - } else { - if (mal_does_AudioObject_support_capture(pContext, deviceObjectID)) { - if (strcmp(uid, pDeviceID->coreaudio) == 0) { - *pDeviceObjectID = deviceObjectID; - return MAL_SUCCESS; - } - } - } - } - } + mal_assert(pContext->backend == mal_backend_coreaudio); - // If we get here it means we couldn't find the device. - return MAL_NO_DEVICE; -} +#if !defined(MAL_NO_RUNTIME_LINKING) && !defined(MAL_APPLE_MOBILE) + mal_dlclose(pContext->coreaudio.hAudioUnit); + mal_dlclose(pContext->coreaudio.hCoreAudio); + mal_dlclose(pContext->coreaudio.hCoreFoundation); +#endif + (void)pContext; + return MAL_SUCCESS; +} -mal_result mal_device_find_best_format__coreaudio(const mal_device* pDevice, AudioStreamBasicDescription* pFormat) +mal_result mal_context_init__coreaudio(mal_context* pContext) { - mal_assert(pDevice != NULL); + mal_assert(pContext != NULL); - AudioObjectID deviceObjectID = (AudioObjectID)pDevice->coreaudio.deviceObjectID; +#if !defined(MAL_NO_RUNTIME_LINKING) && !defined(MAL_APPLE_MOBILE) + pContext->coreaudio.hCoreFoundation = mal_dlopen("CoreFoundation.framework/CoreFoundation"); + if (pContext->coreaudio.hCoreFoundation == NULL) { + return MAL_API_NOT_FOUND; + } - UInt32 deviceFormatDescriptionCount; - AudioStreamRangedDescription* pDeviceFormatDescriptions; - mal_result result = mal_get_AudioObject_stream_descriptions(pDevice->pContext, deviceObjectID, pDevice->type, &deviceFormatDescriptionCount, &pDeviceFormatDescriptions); - if (result != MAL_SUCCESS) { - return result; + pContext->coreaudio.CFStringGetCString = mal_dlsym(pContext->coreaudio.hCoreFoundation, "CFStringGetCString"); + + + pContext->coreaudio.hCoreAudio = mal_dlopen("CoreAudio.framework/CoreAudio"); + if (pContext->coreaudio.hCoreAudio == NULL) { + mal_dlclose(pContext->coreaudio.hCoreFoundation); + return MAL_API_NOT_FOUND; } - mal_uint32 desiredSampleRate = pDevice->sampleRate; - if (pDevice->usingDefaultSampleRate) { - // When using the device's default sample rate, we get the highest priority standard rate supported by the device. Otherwise - // we just use the pre-set rate. - for (mal_uint32 iStandardRate = 0; iStandardRate < mal_countof(g_malStandardSampleRatePriorities); ++iStandardRate) { - mal_uint32 standardRate = g_malStandardSampleRatePriorities[iStandardRate]; - - mal_bool32 foundRate = MAL_FALSE; - for (UInt32 iDeviceRate = 0; iDeviceRate < deviceFormatDescriptionCount; ++iDeviceRate) { - mal_uint32 deviceRate = (mal_uint32)pDeviceFormatDescriptions[iDeviceRate].mFormat.mSampleRate; - - if (deviceRate == standardRate) { - desiredSampleRate = standardRate; - foundRate = MAL_TRUE; - break; - } - } - - if (foundRate) { - break; - } + pContext->coreaudio.AudioObjectGetPropertyData = mal_dlsym(pContext->coreaudio.hCoreAudio, "AudioObjectGetPropertyData"); + pContext->coreaudio.AudioObjectGetPropertyDataSize = mal_dlsym(pContext->coreaudio.hCoreAudio, "AudioObjectGetPropertyDataSize"); + pContext->coreaudio.AudioObjectSetPropertyData = mal_dlsym(pContext->coreaudio.hCoreAudio, "AudioObjectSetPropertyData"); + + + // It looks like Apple has moved some APIs from AudioUnit into AudioToolbox on more recent versions of macOS. They are still + // defined in AudioUnit, but just in case they decide to remove them from there entirely I'm going to implement a fallback. + // The way it'll work is that it'll first try AudioUnit, and if the required symbols are not present there we'll fall back to + // AudioToolbox. + pContext->coreaudio.hAudioUnit = mal_dlopen("AudioUnit.framework/AudioUnit"); + if (pContext->coreaudio.hAudioUnit == NULL) { + mal_dlclose(pContext->coreaudio.hCoreAudio); + mal_dlclose(pContext->coreaudio.hCoreFoundation); + return MAL_API_NOT_FOUND; + } + + if (mal_dlsym(pContext->coreaudio.hAudioUnit, "AudioComponentFindNext") == NULL) { + // Couldn't find the required symbols in AudioUnit, so fall back to AudioToolbox. + mal_dlclose(pContext->coreaudio.hAudioUnit); + pContext->coreaudio.hAudioUnit = mal_dlopen("AudioToolbox.framework/AudioToolbox"); + if (pContext->coreaudio.hAudioUnit == NULL) { + mal_dlclose(pContext->coreaudio.hCoreAudio); + mal_dlclose(pContext->coreaudio.hCoreFoundation); + return MAL_API_NOT_FOUND; } } - mal_uint32 desiredChannelCount = pDevice->channels; - if (pDevice->usingDefaultChannels) { - mal_get_AudioObject_channel_count(pDevice->pContext, deviceObjectID, pDevice->type, &desiredChannelCount); // <-- Not critical if this fails. + pContext->coreaudio.AudioComponentFindNext = mal_dlsym(pContext->coreaudio.hAudioUnit, "AudioComponentFindNext"); + pContext->coreaudio.AudioComponentInstanceDispose = mal_dlsym(pContext->coreaudio.hAudioUnit, "AudioComponentInstanceDispose"); + pContext->coreaudio.AudioComponentInstanceNew = mal_dlsym(pContext->coreaudio.hAudioUnit, "AudioComponentInstanceNew"); + pContext->coreaudio.AudioOutputUnitStart = mal_dlsym(pContext->coreaudio.hAudioUnit, "AudioOutputUnitStart"); + pContext->coreaudio.AudioOutputUnitStop = mal_dlsym(pContext->coreaudio.hAudioUnit, "AudioOutputUnitStop"); + pContext->coreaudio.AudioUnitAddPropertyListener = mal_dlsym(pContext->coreaudio.hAudioUnit, "AudioUnitAddPropertyListener"); + pContext->coreaudio.AudioUnitGetProperty = mal_dlsym(pContext->coreaudio.hAudioUnit, "AudioUnitGetProperty"); + pContext->coreaudio.AudioUnitSetProperty = mal_dlsym(pContext->coreaudio.hAudioUnit, "AudioUnitSetProperty"); + pContext->coreaudio.AudioUnitInitialize = mal_dlsym(pContext->coreaudio.hAudioUnit, "AudioUnitInitialize"); + pContext->coreaudio.AudioUnitRender = mal_dlsym(pContext->coreaudio.hAudioUnit, "AudioUnitRender"); +#else + pContext->coreaudio.CFStringGetCString = (mal_proc)CFStringGetCString; + + #if defined(MAL_APPLE_DESKTOP) + pContext->coreaudio.AudioObjectGetPropertyData = (mal_proc)AudioObjectGetPropertyData; + pContext->coreaudio.AudioObjectGetPropertyDataSize = (mal_proc)AudioObjectGetPropertyDataSize; + pContext->coreaudio.AudioObjectSetPropertyData = (mal_proc)AudioObjectSetPropertyData; + #endif + + pContext->coreaudio.AudioComponentFindNext = (mal_proc)AudioComponentFindNext; + pContext->coreaudio.AudioComponentInstanceDispose = (mal_proc)AudioComponentInstanceDispose; + pContext->coreaudio.AudioComponentInstanceNew = (mal_proc)AudioComponentInstanceNew; + pContext->coreaudio.AudioOutputUnitStart = (mal_proc)AudioOutputUnitStart; + pContext->coreaudio.AudioOutputUnitStop = (mal_proc)AudioOutputUnitStop; + pContext->coreaudio.AudioUnitAddPropertyListener = (mal_proc)AudioUnitAddPropertyListener; + pContext->coreaudio.AudioUnitGetProperty = (mal_proc)AudioUnitGetProperty; + pContext->coreaudio.AudioUnitSetProperty = (mal_proc)AudioUnitSetProperty; + pContext->coreaudio.AudioUnitInitialize = (mal_proc)AudioUnitInitialize; + pContext->coreaudio.AudioUnitRender = (mal_proc)AudioUnitRender; +#endif + + pContext->isBackendAsynchronous = MAL_TRUE; + + pContext->onUninit = mal_context_uninit__coreaudio; + pContext->onDeviceIDEqual = mal_context_is_device_id_equal__coreaudio; + pContext->onEnumDevices = mal_context_enumerate_devices__coreaudio; + pContext->onGetDeviceInfo = mal_context_get_device_info__coreaudio; + pContext->onDeviceInit = mal_device_init__coreaudio; + pContext->onDeviceUninit = mal_device_uninit__coreaudio; + pContext->onDeviceStart = mal_device__start_backend__coreaudio; + pContext->onDeviceStop = mal_device__stop_backend__coreaudio; + + return MAL_SUCCESS; +} +#endif // Core Audio + + + +/////////////////////////////////////////////////////////////////////////////// +// +// sndio Backend +// +/////////////////////////////////////////////////////////////////////////////// +#ifdef MAL_HAS_SNDIO +#include +#include + +// Only supporting OpenBSD. This did not work very well at all on FreeBSD when I tried it. Not sure if this is due +// to mini_al's implementation or if it's some kind of system configuration issue, but basically the default device +// just doesn't emit any sound, or at times you'll hear tiny pieces. I will consider enabling this when there's +// demand for it or if I can get it tested and debugged more thoroughly. + +//#if defined(__NetBSD__) || defined(__OpenBSD__) +//#include +//#endif +//#if defined(__FreeBSD__) || defined(__DragonFly__) +//#include +//#endif + +#define MAL_SIO_DEVANY "default" +#define MAL_SIO_PLAY 1 +#define MAL_SIO_REC 2 +#define MAL_SIO_NENC 8 +#define MAL_SIO_NCHAN 8 +#define MAL_SIO_NRATE 16 +#define MAL_SIO_NCONF 4 + +struct mal_sio_hdl; // <-- Opaque + +struct mal_sio_par +{ + unsigned int bits; + unsigned int bps; + unsigned int sig; + unsigned int le; + unsigned int msb; + unsigned int rchan; + unsigned int pchan; + unsigned int rate; + unsigned int bufsz; + unsigned int xrun; + unsigned int round; + unsigned int appbufsz; + int __pad[3]; + unsigned int __magic; +}; + +struct mal_sio_enc +{ + unsigned int bits; + unsigned int bps; + unsigned int sig; + unsigned int le; + unsigned int msb; +}; + +struct mal_sio_conf +{ + unsigned int enc; + unsigned int rchan; + unsigned int pchan; + unsigned int rate; +}; + +struct mal_sio_cap +{ + struct mal_sio_enc enc[MAL_SIO_NENC]; + unsigned int rchan[MAL_SIO_NCHAN]; + unsigned int pchan[MAL_SIO_NCHAN]; + unsigned int rate[MAL_SIO_NRATE]; + int __pad[7]; + unsigned int nconf; + struct mal_sio_conf confs[MAL_SIO_NCONF]; +}; + +typedef struct mal_sio_hdl* (* mal_sio_open_proc) (const char*, unsigned int, int); +typedef void (* mal_sio_close_proc) (struct mal_sio_hdl*); +typedef int (* mal_sio_setpar_proc) (struct mal_sio_hdl*, struct mal_sio_par*); +typedef int (* mal_sio_getpar_proc) (struct mal_sio_hdl*, struct mal_sio_par*); +typedef int (* mal_sio_getcap_proc) (struct mal_sio_hdl*, struct mal_sio_cap*); +typedef size_t (* mal_sio_write_proc) (struct mal_sio_hdl*, const void*, size_t); +typedef size_t (* mal_sio_read_proc) (struct mal_sio_hdl*, void*, size_t); +typedef int (* mal_sio_start_proc) (struct mal_sio_hdl*); +typedef int (* mal_sio_stop_proc) (struct mal_sio_hdl*); +typedef int (* mal_sio_initpar_proc)(struct mal_sio_par*); + +mal_format mal_format_from_sio_enc__sndio(unsigned int bits, unsigned int bps, unsigned int sig, unsigned int le, unsigned int msb) +{ + // We only support native-endian right now. + if ((mal_is_little_endian() && le == 0) || (mal_is_big_endian() && le == 1)) { + return mal_format_unknown; } - mal_format desiredFormat = pDevice->format; - if (pDevice->usingDefaultFormat) { - desiredFormat = g_malFormatPriorities[0]; + if (bits == 8 && bps == 1 && sig == 0) { + return mal_format_u8; + } + if (bits == 16 && bps == 2 && sig == 1) { + return mal_format_s16; + } + if (bits == 24 && bps == 3 && sig == 1) { + return mal_format_s24; + } + if (bits == 24 && bps == 4 && sig == 1 && msb == 0) { + //return mal_format_s24_32; + } + if (bits == 32 && bps == 4 && sig == 1) { + return mal_format_s32; } - // If we get here it means we don't have an exact match to what the client is asking for. We'll need to find the closest one. The next - // loop will check for formats that have the same sample rate to what we're asking for. If there is, we prefer that one in all cases. - AudioStreamBasicDescription bestDeviceFormatSoFar; - mal_zero_object(&bestDeviceFormatSoFar); + return mal_format_unknown; +} + +mal_format mal_find_best_format_from_sio_cap__sndio(struct mal_sio_cap* caps) +{ + mal_assert(caps != NULL); - mal_bool32 hasSupportedFormat = MAL_FALSE; - for (UInt32 iFormat = 0; iFormat < deviceFormatDescriptionCount; ++iFormat) { - mal_format format; - mal_result formatResult = mal_format_from_AudioStreamBasicDescription(&pDeviceFormatDescriptions[iFormat].mFormat, &format); - if (formatResult == MAL_SUCCESS && format != mal_format_unknown) { - hasSupportedFormat = MAL_TRUE; - bestDeviceFormatSoFar = pDeviceFormatDescriptions[iFormat].mFormat; - break; + mal_format bestFormat = mal_format_unknown; + for (unsigned int iConfig = 0; iConfig < caps->nconf; iConfig += 1) { + for (unsigned int iEncoding = 0; iEncoding < MAL_SIO_NENC; iEncoding += 1) { + if ((caps->confs[iConfig].enc & (1UL << iEncoding)) == 0) { + continue; + } + + unsigned int bits = caps->enc[iEncoding].bits; + unsigned int bps = caps->enc[iEncoding].bps; + unsigned int sig = caps->enc[iEncoding].sig; + unsigned int le = caps->enc[iEncoding].le; + unsigned int msb = caps->enc[iEncoding].msb; + mal_format format = mal_format_from_sio_enc__sndio(bits, bps, sig, le, msb); + if (format == mal_format_unknown) { + continue; // Format not supported. + } + + if (bestFormat == mal_format_unknown) { + bestFormat = format; + } else { + if (mal_get_format_priority_index(bestFormat) > mal_get_format_priority_index(format)) { // <-- Lower = better. + bestFormat = format; + } + } } } - if (!hasSupportedFormat) { - return MAL_FORMAT_NOT_SUPPORTED; - } + return mal_format_unknown; +} + +mal_uint32 mal_find_best_channels_from_sio_cap__sndio(struct mal_sio_cap* caps, mal_device_type deviceType, mal_format requiredFormat) +{ + mal_assert(caps != NULL); + mal_assert(requiredFormat != mal_format_unknown); + // Just pick whatever configuration has the most channels. + mal_uint32 maxChannels = 0; + for (unsigned int iConfig = 0; iConfig < caps->nconf; iConfig += 1) { + // The encoding should be of requiredFormat. + for (unsigned int iEncoding = 0; iEncoding < MAL_SIO_NENC; iEncoding += 1) { + if ((caps->confs[iConfig].enc & (1UL << iEncoding)) == 0) { + continue; + } + + unsigned int bits = caps->enc[iEncoding].bits; + unsigned int bps = caps->enc[iEncoding].bps; + unsigned int sig = caps->enc[iEncoding].sig; + unsigned int le = caps->enc[iEncoding].le; + unsigned int msb = caps->enc[iEncoding].msb; + mal_format format = mal_format_from_sio_enc__sndio(bits, bps, sig, le, msb); + if (format != requiredFormat) { + continue; + } + + // Getting here means the format is supported. Iterate over each channel count and grab the biggest one. + for (unsigned int iChannel = 0; iChannel < MAL_SIO_NCHAN; iChannel += 1) { + unsigned int chan = 0; + if (deviceType == mal_device_type_playback) { + chan = caps->confs[iConfig].pchan; + } else { + chan = caps->confs[iConfig].rchan; + } + + if ((chan & (1UL << iChannel)) == 0) { + continue; + } + + unsigned int channels; + if (deviceType == mal_device_type_playback) { + channels = caps->pchan[iChannel]; + } else { + channels = caps->rchan[iChannel]; + } + + if (maxChannels < channels) { + maxChannels = channels; + } + } + } + } - for (UInt32 iFormat = 0; iFormat < deviceFormatDescriptionCount; ++iFormat) { - AudioStreamBasicDescription thisDeviceFormat = pDeviceFormatDescriptions[iFormat].mFormat; + return maxChannels; +} + +mal_uint32 mal_find_best_sample_rate_from_sio_cap__sndio(struct mal_sio_cap* caps, mal_device_type deviceType, mal_format requiredFormat, mal_uint32 requiredChannels) +{ + mal_assert(caps != NULL); + mal_assert(requiredFormat != mal_format_unknown); + mal_assert(requiredChannels > 0); + mal_assert(requiredChannels <= MAL_MAX_CHANNELS); - // If the format is not supported by mini_al we need to skip this one entirely. - mal_format thisSampleFormat; - mal_result formatResult = mal_format_from_AudioStreamBasicDescription(&pDeviceFormatDescriptions[iFormat].mFormat, &thisSampleFormat); - if (formatResult != MAL_SUCCESS || thisSampleFormat == mal_format_unknown) { - continue; // The format is not supported by mini_al. Skip. - } - - mal_format bestSampleFormatSoFar; - mal_format_from_AudioStreamBasicDescription(&bestDeviceFormatSoFar, &bestSampleFormatSoFar); - + mal_uint32 firstSampleRate = 0; // <-- If the device does not support a standard rate we'll fall back to the first one that's found. - // Getting here means the format is supported by mini_al which makes this format a candidate. - if (thisDeviceFormat.mSampleRate != desiredSampleRate) { - // The sample rate does not match, but this format could still be usable, although it's a very low priority. If the best format - // so far has an equal sample rate we can just ignore this one. - if (bestDeviceFormatSoFar.mSampleRate == desiredSampleRate) { - continue; // The best sample rate so far has the same sample rate as what we requested which means it's still the best so far. Skip this format. - } else { - // In this case, neither the best format so far nor this one have the same sample rate. Check the channel count next. - if (thisDeviceFormat.mChannelsPerFrame != desiredChannelCount) { - // This format has a different sample rate _and_ a different channel count. - if (bestDeviceFormatSoFar.mChannelsPerFrame == desiredChannelCount) { - continue; // No change to the best format. - } else { - // Both this format and the best so far have different sample rates and different channel counts. Whichever has the - // best format is the new best. - if (mal_get_format_priority_index(thisSampleFormat) < mal_get_format_priority_index(bestSampleFormatSoFar)) { - bestDeviceFormatSoFar = thisDeviceFormat; - continue; - } else { - continue; // No change to the best format. - } - } - } else { - // This format has a different sample rate but the desired channel count. - if (bestDeviceFormatSoFar.mChannelsPerFrame == desiredChannelCount) { - // Both this format and the best so far have the desired channel count. Whichever has the best format is the new best. - if (mal_get_format_priority_index(thisSampleFormat) < mal_get_format_priority_index(bestSampleFormatSoFar)) { - bestDeviceFormatSoFar = thisDeviceFormat; - continue; - } else { - continue; // No change to the best format for now. - } - } else { - // This format has the desired channel count, but the best so far does not. We have a new best. - bestDeviceFormatSoFar = thisDeviceFormat; - continue; - } - } + mal_uint32 bestSampleRate = 0; + for (unsigned int iConfig = 0; iConfig < caps->nconf; iConfig += 1) { + // The encoding should be of requiredFormat. + for (unsigned int iEncoding = 0; iEncoding < MAL_SIO_NENC; iEncoding += 1) { + if ((caps->confs[iConfig].enc & (1UL << iEncoding)) == 0) { + continue; } - } else { - // The sample rates match which makes this format a very high priority contender. If the best format so far has a different - // sample rate it needs to be replaced with this one. - if (bestDeviceFormatSoFar.mSampleRate != desiredSampleRate) { - bestDeviceFormatSoFar = thisDeviceFormat; + + unsigned int bits = caps->enc[iEncoding].bits; + unsigned int bps = caps->enc[iEncoding].bps; + unsigned int sig = caps->enc[iEncoding].sig; + unsigned int le = caps->enc[iEncoding].le; + unsigned int msb = caps->enc[iEncoding].msb; + mal_format format = mal_format_from_sio_enc__sndio(bits, bps, sig, le, msb); + if (format != requiredFormat) { continue; - } else { - // In this case both this format and the best format so far have the same sample rate. Check the channel count next. - if (thisDeviceFormat.mChannelsPerFrame == desiredChannelCount) { - // In this case this format has the same channel count as what the client is requesting. If the best format so far has - // a different count, this one becomes the new best. - if (bestDeviceFormatSoFar.mChannelsPerFrame != desiredChannelCount) { - bestDeviceFormatSoFar = thisDeviceFormat; - continue; - } else { - // In this case both this format and the best so far have the ideal sample rate and channel count. Check the format. - if (thisSampleFormat == desiredFormat) { - bestDeviceFormatSoFar = thisDeviceFormat; - break; // Found the exact match. - } else { - // The formats are different. The new best format is the one with the highest priority format according to mini_al. - if (mal_get_format_priority_index(thisSampleFormat) < mal_get_format_priority_index(bestSampleFormatSoFar)) { - bestDeviceFormatSoFar = thisDeviceFormat; - continue; - } else { - continue; // No change to the best format for now. - } - } - } + } + + // Getting here means the format is supported. Iterate over each channel count and grab the biggest one. + for (unsigned int iChannel = 0; iChannel < MAL_SIO_NCHAN; iChannel += 1) { + unsigned int chan = 0; + if (deviceType == mal_device_type_playback) { + chan = caps->confs[iConfig].pchan; } else { - // In this case the channel count is different to what the client has requested. If the best so far has the same channel - // count as the requested count then it remains the best. - if (bestDeviceFormatSoFar.mChannelsPerFrame == desiredChannelCount) { + chan = caps->confs[iConfig].rchan; + } + + if ((chan & (1UL << iChannel)) == 0) { + continue; + } + + unsigned int channels; + if (deviceType == mal_device_type_playback) { + channels = caps->pchan[iChannel]; + } else { + channels = caps->rchan[iChannel]; + } + + if (channels != requiredChannels) { + continue; + } + + // Getting here means we have found a compatible encoding/channel pair. + for (unsigned int iRate = 0; iRate < MAL_SIO_NRATE; iRate += 1) { + mal_uint32 rate = (mal_uint32)caps->rate[iRate]; + + if (firstSampleRate == 0) { + firstSampleRate = rate; + } + + // Disregard this rate if it's not a standard one. + mal_uint32 ratePriority = mal_get_standard_sample_rate_priority_index(rate); + if (ratePriority == (mal_uint32)-1) { continue; - } else { - // This is the case where both have the same sample rate (good) but different channel counts. Right now both have about - // the same priority, but we need to compare the format now. - if (thisSampleFormat == bestSampleFormatSoFar) { - if (mal_get_format_priority_index(thisSampleFormat) < mal_get_format_priority_index(bestSampleFormatSoFar)) { - bestDeviceFormatSoFar = thisDeviceFormat; - continue; - } else { - continue; // No change to the best format for now. - } - } + } + + if (mal_get_standard_sample_rate_priority_index(bestSampleRate) > ratePriority) { // Lower = better. + bestSampleRate = rate; } } } } } - *pFormat = bestDeviceFormatSoFar; - return MAL_SUCCESS; + // If a standard sample rate was not found just fall back to the first one that was iterated. + if (bestSampleRate == 0) { + bestSampleRate = firstSampleRate; + } + + return bestSampleRate; } -#endif - -mal_bool32 mal_context_is_device_id_equal__coreaudio(mal_context* pContext, const mal_device_id* pID0, const mal_device_id* pID1) +mal_bool32 mal_context_is_device_id_equal__sndio(mal_context* pContext, const mal_device_id* pID0, const mal_device_id* pID1) { mal_assert(pContext != NULL); mal_assert(pID0 != NULL); mal_assert(pID1 != NULL); (void)pContext; - return strcmp(pID0->coreaudio, pID1->coreaudio) == 0; + return mal_strcmp(pID0->sndio, pID1->sndio) == 0; } -mal_result mal_context_enumerate_devices__coreaudio(mal_context* pContext, mal_enum_devices_callback_proc callback, void* pUserData) +mal_result mal_context_enumerate_devices__sndio(mal_context* pContext, mal_enum_devices_callback_proc callback, void* pUserData) { mal_assert(pContext != NULL); mal_assert(callback != NULL); -#if defined(MAL_APPLE_DESKTOP) - UInt32 deviceCount; - AudioObjectID* pDeviceObjectIDs; - mal_result result = mal_get_device_object_ids__coreaudio(pContext, &deviceCount, &pDeviceObjectIDs); - if (result != MAL_SUCCESS) { - return result; - } - - for (UInt32 iDevice = 0; iDevice < deviceCount; ++iDevice) { - AudioObjectID deviceObjectID = pDeviceObjectIDs[iDevice]; - - mal_device_info info; - mal_zero_object(&info); - if (mal_get_AudioObject_uid(pContext, deviceObjectID, sizeof(info.id.coreaudio), info.id.coreaudio) != MAL_SUCCESS) { - continue; - } - if (mal_get_AudioObject_name(pContext, deviceObjectID, sizeof(info.name), info.name) != MAL_SUCCESS) { - continue; - } - - if (mal_does_AudioObject_support_playback(pContext, deviceObjectID)) { - if (!callback(pContext, mal_device_type_playback, &info, pUserData)) { - break; - } - } - if (mal_does_AudioObject_support_capture(pContext, deviceObjectID)) { - if (!callback(pContext, mal_device_type_capture, &info, pUserData)) { - break; - } - } - } - - mal_free(pDeviceObjectIDs); -#else - // Only supporting default devices on non-Desktop platforms. - mal_device_info info; + // sndio doesn't seem to have a good device enumeration API, so I'm therefore only enumerating + // over default devices for now. + mal_bool32 isTerminating = MAL_FALSE; + struct mal_sio_hdl* handle; - mal_zero_object(&info); - mal_strncpy_s(info.name, sizeof(info.name), MAL_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); - if (!callback(pContext, mal_device_type_playback, &info, pUserData)) { - return MAL_SUCCESS; + // Playback. + if (!isTerminating) { + handle = ((mal_sio_open_proc)pContext->sndio.sio_open)(MAL_SIO_DEVANY, MAL_SIO_PLAY, 0); + if (handle != NULL) { + // Supports playback. + mal_device_info deviceInfo; + mal_zero_object(&deviceInfo); + mal_strcpy_s(deviceInfo.id.sndio, sizeof(deviceInfo.id.sndio), MAL_SIO_DEVANY); + mal_strcpy_s(deviceInfo.name, sizeof(deviceInfo.name), MAL_DEFAULT_PLAYBACK_DEVICE_NAME); + + isTerminating = !callback(pContext, mal_device_type_playback, &deviceInfo, pUserData); + + ((mal_sio_close_proc)pContext->sndio.sio_close)(handle); + } } - mal_zero_object(&info); - mal_strncpy_s(info.name, sizeof(info.name), MAL_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); - if (!callback(pContext, mal_device_type_capture, &info, pUserData)) { - return MAL_SUCCESS; + // Capture. + if (!isTerminating) { + handle = ((mal_sio_open_proc)pContext->sndio.sio_open)(MAL_SIO_DEVANY, MAL_SIO_REC, 0); + if (handle != NULL) { + // Supports capture. + mal_device_info deviceInfo; + mal_zero_object(&deviceInfo); + mal_strcpy_s(deviceInfo.id.sndio, sizeof(deviceInfo.id.sndio), "default"); + mal_strcpy_s(deviceInfo.name, sizeof(deviceInfo.name), MAL_DEFAULT_CAPTURE_DEVICE_NAME); + + isTerminating = !callback(pContext, mal_device_type_capture, &deviceInfo, pUserData); + + ((mal_sio_close_proc)pContext->sndio.sio_close)(handle); + } } -#endif return MAL_SUCCESS; } -mal_result mal_context_get_device_info__coreaudio(mal_context* pContext, mal_device_type deviceType, const mal_device_id* pDeviceID, mal_share_mode shareMode, mal_device_info* pDeviceInfo) +mal_result mal_context_get_device_info__sndio(mal_context* pContext, mal_device_type deviceType, const mal_device_id* pDeviceID, mal_share_mode shareMode, mal_device_info* pDeviceInfo) { mal_assert(pContext != NULL); (void)shareMode; - (void)pDeviceInfo; - -#if defined(MAL_APPLE_DESKTOP) - // Desktop - // ======= - AudioObjectID deviceObjectID; - mal_result result = mal_find_AudioObjectID(pContext, deviceType, pDeviceID, &deviceObjectID); - if (result != MAL_SUCCESS) { - return result; - } - result = mal_get_AudioObject_uid(pContext, deviceObjectID, sizeof(pDeviceInfo->id.coreaudio), pDeviceInfo->id.coreaudio); - if (result != MAL_SUCCESS) { - return result; + // We need to open the device before we can get information about it. + char devid[256]; + if (pDeviceID == NULL) { + mal_strcpy_s(devid, sizeof(devid), MAL_SIO_DEVANY); + mal_strcpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), (deviceType == mal_device_type_playback) ? MAL_DEFAULT_PLAYBACK_DEVICE_NAME : MAL_DEFAULT_CAPTURE_DEVICE_NAME); + } else { + mal_strcpy_s(devid, sizeof(devid), pDeviceID->sndio); + mal_strcpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), devid); } - result = mal_get_AudioObject_name(pContext, deviceObjectID, sizeof(pDeviceInfo->name), pDeviceInfo->name); - if (result != MAL_SUCCESS) { - return result; + struct mal_sio_hdl* handle = ((mal_sio_open_proc)pContext->sndio.sio_open)(devid, (deviceType == mal_device_type_playback) ? MAL_SIO_PLAY : MAL_SIO_REC, 0); + if (handle == NULL) { + return MAL_NO_DEVICE; } - // Formats. - UInt32 streamDescriptionCount; - AudioStreamRangedDescription* pStreamDescriptions; - result = mal_get_AudioObject_stream_descriptions(pContext, deviceObjectID, deviceType, &streamDescriptionCount, &pStreamDescriptions); - if (result != MAL_SUCCESS) { - return result; + struct mal_sio_cap caps; + if (((mal_sio_getcap_proc)pContext->sndio.sio_getcap)(handle, &caps) == 0) { + return MAL_ERROR; } - for (UInt32 iStreamDescription = 0; iStreamDescription < streamDescriptionCount; ++iStreamDescription) { - mal_format format; - result = mal_format_from_AudioStreamBasicDescription(&pStreamDescriptions[iStreamDescription].mFormat, &format); - if (result != MAL_SUCCESS) { - continue; + for (unsigned int iConfig = 0; iConfig < caps.nconf; iConfig += 1) { + // The main thing we care about is that the encoding is supported by mini_al. If it is, we want to give + // preference to some formats over others. + for (unsigned int iEncoding = 0; iEncoding < MAL_SIO_NENC; iEncoding += 1) { + if ((caps.confs[iConfig].enc & (1UL << iEncoding)) == 0) { + continue; + } + + unsigned int bits = caps.enc[iEncoding].bits; + unsigned int bps = caps.enc[iEncoding].bps; + unsigned int sig = caps.enc[iEncoding].sig; + unsigned int le = caps.enc[iEncoding].le; + unsigned int msb = caps.enc[iEncoding].msb; + mal_format format = mal_format_from_sio_enc__sndio(bits, bps, sig, le, msb); + if (format == mal_format_unknown) { + continue; // Format not supported. + } + + // Add this format if it doesn't already exist. + mal_bool32 formatExists = MAL_FALSE; + for (mal_uint32 iExistingFormat = 0; iExistingFormat < pDeviceInfo->formatCount; iExistingFormat += 1) { + if (pDeviceInfo->formats[iExistingFormat] == format) { + formatExists = MAL_TRUE; + break; + } + } + + if (!formatExists) { + pDeviceInfo->formats[pDeviceInfo->formatCount++] = format; + } } - mal_assert(format != mal_format_unknown); + // Channels. + for (unsigned int iChannel = 0; iChannel < MAL_SIO_NCHAN; iChannel += 1) { + unsigned int chan = 0; + if (deviceType == mal_device_type_playback) { + chan = caps.confs[iConfig].pchan; + } else { + chan = caps.confs[iConfig].rchan; + } - // Make sure the format isn't already in the output list. - mal_bool32 exists = MAL_FALSE; - for (mal_uint32 iOutputFormat = 0; iOutputFormat < pDeviceInfo->formatCount; ++iOutputFormat) { - if (pDeviceInfo->formats[iOutputFormat] == format) { - exists = MAL_TRUE; - break; + if ((chan & (1UL << iChannel)) == 0) { + continue; + } + + unsigned int channels; + if (deviceType == mal_device_type_playback) { + channels = caps.pchan[iChannel]; + } else { + channels = caps.rchan[iChannel]; + } + + if (pDeviceInfo->minChannels > channels) { + pDeviceInfo->minChannels = channels; + } + if (pDeviceInfo->maxChannels < channels) { + pDeviceInfo->maxChannels = channels; } } - if (!exists) { - pDeviceInfo->formats[pDeviceInfo->formatCount++] = format; + // Sample rates. + for (unsigned int iRate = 0; iRate < MAL_SIO_NRATE; iRate += 1) { + if ((caps.confs[iConfig].rate & (1UL << iRate)) != 0) { + unsigned int rate = caps.rate[iRate]; + if (pDeviceInfo->minSampleRate > rate) { + pDeviceInfo->minSampleRate = rate; + } + if (pDeviceInfo->maxSampleRate < rate) { + pDeviceInfo->maxSampleRate = rate; + } + } } } + + ((mal_sio_close_proc)pContext->sndio.sio_close)(handle); + return MAL_SUCCESS; +} + +void mal_device_uninit__sndio(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + ((mal_sio_close_proc)pDevice->pContext->sndio.sio_close)((struct mal_sio_hdl*)pDevice->sndio.handle); + mal_free(pDevice->sndio.pIntermediaryBuffer); +} + +mal_result mal_device_init__sndio(mal_context* pContext, mal_device_type deviceType, const mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice) +{ + (void)pContext; + + mal_assert(pDevice != NULL); + mal_zero_object(&pDevice->sndio); - mal_free(pStreamDescriptions); + const char* deviceName = MAL_SIO_DEVANY; +//#if defined(__FreeBSD__) || defined(__DragonFly__) +// deviceName = "rsnd/0"; +//#else + if (pDeviceID != NULL) { + deviceName = pDeviceID->sndio; + } + pDevice->sndio.handle = (mal_ptr)((mal_sio_open_proc)pContext->sndio.sio_open)(deviceName, (deviceType == mal_device_type_playback) ? MAL_SIO_PLAY : MAL_SIO_REC, 0); + if (pDevice->sndio.handle == NULL) { + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[sndio] Failed to open device.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + } - // Channels. - result = mal_get_AudioObject_channel_count(pContext, deviceObjectID, deviceType, &pDeviceInfo->minChannels); - if (result != MAL_SUCCESS) { - return result; + // We need to retrieve the device caps to determine the most appropriate format to use. + struct mal_sio_cap caps; + if (((mal_sio_getcap_proc)pContext->sndio.sio_getcap)((struct mal_sio_hdl*)pDevice->sndio.handle, &caps) == 0) { + ((mal_sio_close_proc)pContext->sndio.sio_close)((struct mal_sio_hdl*)pDevice->sndio.handle); + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[sndio] Failed to retrieve device caps.", MAL_ERROR); } - pDeviceInfo->maxChannels = pDeviceInfo->minChannels; + mal_format desiredFormat = pDevice->format; + if (pDevice->usingDefaultFormat) { + desiredFormat = mal_find_best_format_from_sio_cap__sndio(&caps); + } - // Sample rates. - UInt32 sampleRateRangeCount; - AudioValueRange* pSampleRateRanges; - result = mal_get_AudioObject_sample_rates(pContext, deviceObjectID, deviceType, &sampleRateRangeCount, &pSampleRateRanges); - if (result != MAL_SUCCESS) { - return result; + if (desiredFormat == mal_format_unknown) { + desiredFormat = pDevice->format; } - if (sampleRateRangeCount > 0) { - pDeviceInfo->minSampleRate = UINT32_MAX; - pDeviceInfo->maxSampleRate = 0; - for (UInt32 iSampleRate = 0; iSampleRate < sampleRateRangeCount; ++iSampleRate) { - if (pDeviceInfo->minSampleRate > pSampleRateRanges[iSampleRate].mMinimum) { - pDeviceInfo->minSampleRate = pSampleRateRanges[iSampleRate].mMinimum; - } - if (pDeviceInfo->maxSampleRate < pSampleRateRanges[iSampleRate].mMaximum) { - pDeviceInfo->maxSampleRate = pSampleRateRanges[iSampleRate].mMaximum; - } + + // Note: sndio reports a huge range of available channels. This is inconvenient for us because there's no real + // way, as far as I can tell, to get the _actual_ channel count of the device. I'm therefore restricting this + // to the requested channels, regardless of whether or not the default channel count is requested. + // + // For hardware devices, I'm suspecting only a single channel count will be reported and we can safely use the + // value returned by mal_find_best_channels_from_sio_cap__sndio(). + mal_uint32 desiredChannels = pDevice->channels; + if (pDevice->usingDefaultChannels) { + if (strlen(deviceName) > strlen("rsnd/") && strncmp(deviceName, "rsnd/", strlen("rsnd/")) == 0) { + desiredChannels = mal_find_best_channels_from_sio_cap__sndio(&caps, deviceType, desiredFormat); } } -#else - // Mobile - // ====== - if (deviceType == mal_device_type_playback) { - mal_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MAL_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); - } else { - mal_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MAL_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + + if (desiredChannels == 0) { + desiredChannels = pDevice->channels; } + - // Retrieving device information is more annoying on mobile than desktop. For simplicity I'm locking this down to whatever format is - // reported on a temporary I/O unit. The problem, however, is that this doesn't return a value for the sample rate which we need to - // retrieve from the AVAudioSession shared instance. - AudioComponentDescription desc; - desc.componentType = kAudioUnitType_Output; - desc.componentSubType = kAudioUnitSubType_RemoteIO; - desc.componentManufacturer = kAudioUnitManufacturer_Apple; - desc.componentFlags = 0; - desc.componentFlagsMask = 0; + mal_uint32 desiredSampleRate = pDevice->sampleRate; + if (pDevice->usingDefaultSampleRate) { + desiredSampleRate = mal_find_best_sample_rate_from_sio_cap__sndio(&caps, deviceType, desiredFormat, desiredChannels); + } - AudioComponent component = ((mal_AudioComponentFindNext_proc)pContext->coreaudio.AudioComponentFindNext)(NULL, &desc); - if (component == NULL) { - return MAL_FAILED_TO_INIT_BACKEND; + if (desiredSampleRate == 0) { + desiredSampleRate = pDevice->sampleRate; } - AudioUnit audioUnit; - OSStatus status = ((mal_AudioComponentInstanceNew_proc)pContext->coreaudio.AudioComponentInstanceNew)(component, &audioUnit); - if (status != noErr) { - return mal_result_from_OSStatus(status); + + struct mal_sio_par par; + ((mal_sio_initpar_proc)pDevice->pContext->sndio.sio_initpar)(&par); + par.msb = 0; + par.le = mal_is_little_endian(); + + switch (desiredFormat) { + case mal_format_u8: + { + par.bits = 8; + par.bps = 1; + par.sig = 0; + } break; + + case mal_format_s24: + { + par.bits = 24; + par.bps = 3; + par.sig = 1; + } break; + + case mal_format_s32: + { + par.bits = 32; + par.bps = 4; + par.sig = 1; + } break; + + case mal_format_s16: + case mal_format_f32: + default: + { + par.bits = 16; + par.bps = 2; + par.sig = 1; + } break; } - AudioUnitScope formatScope = (deviceType == mal_device_type_playback) ? kAudioUnitScope_Input : kAudioUnitScope_Output; - AudioUnitElement formatElement = (deviceType == mal_device_type_playback) ? MAL_COREAUDIO_OUTPUT_BUS : MAL_COREAUDIO_INPUT_BUS; + if (deviceType == mal_device_type_playback) { + par.pchan = desiredChannels; + } else { + par.rchan = desiredChannels; + } + + par.rate = desiredSampleRate; - AudioStreamBasicDescription bestFormat; - UInt32 propSize = sizeof(bestFormat); - status = ((mal_AudioUnitGetProperty_proc)pContext->coreaudio.AudioUnitGetProperty)(audioUnit, kAudioUnitProperty_StreamFormat, formatScope, formatElement, &bestFormat, &propSize); - if (status != noErr) { - ((mal_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(audioUnit); - return mal_result_from_OSStatus(status); + // Try calculating an appropriate default buffer size after we have the sample rate. + mal_uint32 desiredBufferSizeInFrames = pDevice->bufferSizeInFrames; + if (pDevice->usingDefaultBufferSize) { + // CPU speed factor. + float fCPUSpeed = mal_calculate_cpu_speed_factor(); + + // Playback vs capture latency. + float fDeviceType = 1; + + // Backend tax. + float fBackend = 1; + + desiredBufferSizeInFrames = mal_calculate_default_buffer_size_in_frames(pConfig->performanceProfile, par.rate, fCPUSpeed*fDeviceType*fBackend); } - ((mal_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(audioUnit); - audioUnit = NULL; + par.round = desiredBufferSizeInFrames / pDevice->periods; + par.appbufsz = par.round * pDevice->periods; + if (((mal_sio_setpar_proc)pContext->sndio.sio_setpar)((struct mal_sio_hdl*)pDevice->sndio.handle, &par) == 0) { + ((mal_sio_close_proc)pContext->sndio.sio_close)((struct mal_sio_hdl*)pDevice->sndio.handle); + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[sndio] Failed to set buffer size.", MAL_FORMAT_NOT_SUPPORTED); + } + if (((mal_sio_getpar_proc)pContext->sndio.sio_getpar)((struct mal_sio_hdl*)pDevice->sndio.handle, &par) == 0) { + ((mal_sio_close_proc)pContext->sndio.sio_close)((struct mal_sio_hdl*)pDevice->sndio.handle); + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[sndio] Failed to retrieve buffer size.", MAL_FORMAT_NOT_SUPPORTED); + } - pDeviceInfo->minChannels = bestFormat.mChannelsPerFrame; - pDeviceInfo->maxChannels = bestFormat.mChannelsPerFrame; + pDevice->internalFormat = mal_format_from_sio_enc__sndio(par.bits, par.bps, par.sig, par.le, par.msb); - pDeviceInfo->formatCount = 1; - mal_result result = mal_format_from_AudioStreamBasicDescription(&bestFormat, &pDeviceInfo->formats[0]); - if (result != MAL_SUCCESS) { - return result; + if (deviceType == mal_device_type_playback) { + pDevice->internalChannels = par.pchan; + } else { + pDevice->internalChannels = par.rchan; } - // It looks like Apple are wanting to push the whole AVAudioSession thing. Thus, we need to use that to determine device settings. To do - // this we just get the shared instance and inspect. - @autoreleasepool { - AVAudioSession* pAudioSession = [AVAudioSession sharedInstance]; - mal_assert(pAudioSession != NULL); + pDevice->internalSampleRate = par.rate; - pDeviceInfo->minSampleRate = (mal_uint32)pAudioSession.sampleRate; - pDeviceInfo->maxSampleRate = pDeviceInfo->minSampleRate; + pDevice->periods = par.appbufsz / par.round; + if (pDevice->periods < 2) { + pDevice->periods = 2; + } + pDevice->bufferSizeInFrames = par.round * pDevice->periods; + pDevice->sndio.fragmentSizeInFrames = par.round; + + mal_get_standard_channel_map(mal_standard_channel_map_sndio, pDevice->internalChannels, pDevice->internalChannelMap); + + pDevice->sndio.pIntermediaryBuffer = mal_malloc(pDevice->sndio.fragmentSizeInFrames * mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels)); + if (pDevice->sndio.pIntermediaryBuffer == NULL) { + ((mal_sio_close_proc)pContext->sndio.sio_close)((struct mal_sio_hdl*)pDevice->sndio.handle); + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[sndio] Failed to allocate memory for intermediary buffer.", MAL_OUT_OF_MEMORY); } + +#ifdef MAL_DEBUG_OUTPUT + printf("DEVICE INFO\n"); + printf(" Format: %s\n", mal_get_format_name(pDevice->internalFormat)); + printf(" Channels: %d\n", pDevice->internalChannels); + printf(" Sample Rate: %d\n", pDevice->internalSampleRate); + printf(" Buffer Size: %d\n", pDevice->bufferSizeInFrames); + printf(" Periods: %d\n", pDevice->periods); + printf(" appbufsz: %d\n", par.appbufsz); + printf(" round: %d\n", par.round); #endif return MAL_SUCCESS; } - -void mal_device_uninit__coreaudio(mal_device* pDevice) +mal_result mal_device__start_backend__sndio(mal_device* pDevice) { mal_assert(pDevice != NULL); - mal_assert(mal_device__get_state(pDevice) == MAL_STATE_UNINITIALIZED); - - ((mal_AudioComponentInstanceDispose_proc)pDevice->pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnit); - if (pDevice->coreaudio.pAudioBufferList) { - mal_free(pDevice->coreaudio.pAudioBufferList); + if (((mal_sio_start_proc)pDevice->pContext->sndio.sio_start)((struct mal_sio_hdl*)pDevice->sndio.handle) == 0) { + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[sndio] Failed to start backend device.", MAL_FAILED_TO_START_BACKEND_DEVICE); } -} + // The device is started by the next calls to read() and write(). For playback it's simple - just read + // data from the client, then write it to the device with write() which will in turn start the device. + // For capture it's a bit less intuitive - we do nothing (it'll be started automatically by the first + // call to read(). + if (pDevice->type == mal_device_type_playback) { + // Playback. Need to load the entire buffer, which means we need to write a fragment for each period. + for (mal_uint32 iPeriod = 0; iPeriod < pDevice->periods; iPeriod += 1) { + mal_device__read_frames_from_client(pDevice, pDevice->sndio.fragmentSizeInFrames, pDevice->sndio.pIntermediaryBuffer); -OSStatus mal_on_output__coreaudio(void* pUserData, AudioUnitRenderActionFlags* pActionFlags, const AudioTimeStamp* pTimeStamp, UInt32 busNumber, UInt32 frameCount, AudioBufferList* pBufferList) + int bytesWritten = ((mal_sio_write_proc)pDevice->pContext->sndio.sio_write)((struct mal_sio_hdl*)pDevice->sndio.handle, pDevice->sndio.pIntermediaryBuffer, pDevice->sndio.fragmentSizeInFrames * mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels)); + if (bytesWritten == 0) { + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[sndio] Failed to send initial chunk of data to the device.", MAL_FAILED_TO_SEND_DATA_TO_DEVICE); + } + } + } else { + // Capture. Do nothing. + } + + return MAL_SUCCESS; +} + +mal_result mal_device__stop_backend__sndio(mal_device* pDevice) { - (void)pActionFlags; - (void)pTimeStamp; - (void)busNumber; + mal_assert(pDevice != NULL); - mal_device* pDevice = (mal_device*)pUserData; + ((mal_sio_stop_proc)pDevice->pContext->sndio.sio_stop)((struct mal_sio_hdl*)pDevice->sndio.handle); + return MAL_SUCCESS; +} + +mal_result mal_device__break_main_loop__sndio(mal_device* pDevice) +{ mal_assert(pDevice != NULL); - -#if defined(MAL_DEBUG_OUTPUT) - printf("INFO: Output Callback: busNumber=%d, frameCount=%d, mNumberBuffers=%d\n", busNumber, frameCount, pBufferList->mNumberBuffers); -#endif - - // For now we can assume everything is interleaved. - for (UInt32 iBuffer = 0; iBuffer < pBufferList->mNumberBuffers; ++iBuffer) { - if (pBufferList->mBuffers[iBuffer].mNumberChannels == pDevice->internalChannels) { - mal_uint32 frameCountForThisBuffer = pBufferList->mBuffers[iBuffer].mDataByteSize / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels); - if (frameCountForThisBuffer > 0) { - mal_device__read_frames_from_client(pDevice, frameCountForThisBuffer, pBufferList->mBuffers[iBuffer].mData); - } - - #if defined(MAL_DEBUG_OUTPUT) - printf(" frameCount=%d, mNumberChannels=%d, mDataByteSize=%d\n", frameCount, pBufferList->mBuffers[iBuffer].mNumberChannels, pBufferList->mBuffers[iBuffer].mDataByteSize); - #endif - } else { - // This case is where the number of channels in the output buffer do not match our internal channels. It could mean that it's - // not interleaved, in which case we can't handle right now since mini_al does not yet support non-interleaved streams. We just - // output silence here. - mal_zero_memory(pBufferList->mBuffers[iBuffer].mData, pBufferList->mBuffers[iBuffer].mDataByteSize); - #if defined(MAL_DEBUG_OUTPUT) - printf(" WARNING: Outputting silence. frameCount=%d, mNumberChannels=%d, mDataByteSize=%d\n", frameCount, pBufferList->mBuffers[iBuffer].mNumberChannels, pBufferList->mBuffers[iBuffer].mDataByteSize); - #endif - } - } - - return noErr; + pDevice->sndio.breakFromMainLoop = MAL_TRUE; + return MAL_SUCCESS; } -OSStatus mal_on_input__coreaudio(void* pUserData, AudioUnitRenderActionFlags* pActionFlags, const AudioTimeStamp* pTimeStamp, UInt32 busNumber, UInt32 frameCount, AudioBufferList* pUnusedBufferList) +mal_result mal_device__main_loop__sndio(mal_device* pDevice) { - (void)pActionFlags; - (void)pTimeStamp; - (void)busNumber; - (void)frameCount; - (void)pUnusedBufferList; - - mal_device* pDevice = (mal_device*)pUserData; mal_assert(pDevice != NULL); - - AudioBufferList* pRenderedBufferList = (AudioBufferList*)pDevice->coreaudio.pAudioBufferList; - mal_assert(pRenderedBufferList); - -#if defined(MAL_DEBUG_OUTPUT) - printf("INFO: Input Callback: busNumber=%d, frameCount=%d, mNumberBuffers=%d\n", busNumber, frameCount, pRenderedBufferList->mNumberBuffers); -#endif - - OSStatus status = ((mal_AudioUnitRender_proc)pDevice->pContext->coreaudio.AudioUnitRender)((AudioUnit)pDevice->coreaudio.audioUnit, pActionFlags, pTimeStamp, busNumber, frameCount, pRenderedBufferList); - if (status != noErr) { - #if defined(MAL_DEBUG_OUTPUT) - printf(" ERROR: AudioUnitRender() failed with %d\n", status); - #endif - return status; - } - - // For now we can assume everything is interleaved. - for (UInt32 iBuffer = 0; iBuffer < pRenderedBufferList->mNumberBuffers; ++iBuffer) { - if (pRenderedBufferList->mBuffers[iBuffer].mNumberChannels == pDevice->internalChannels) { - mal_device__send_frames_to_client(pDevice, frameCount, pRenderedBufferList->mBuffers[iBuffer].mData); - #if defined(MAL_DEBUG_OUTPUT) - printf(" mDataByteSize=%d\n", pRenderedBufferList->mBuffers[iBuffer].mDataByteSize); - #endif + + pDevice->sndio.breakFromMainLoop = MAL_FALSE; + while (!pDevice->sndio.breakFromMainLoop) { + // Break from the main loop if the device isn't started anymore. Likely what's happened is the application + // has requested that the device be stopped. + if (!mal_device_is_started(pDevice)) { + break; + } + + if (pDevice->type == mal_device_type_playback) { + // Playback. + mal_device__read_frames_from_client(pDevice, pDevice->sndio.fragmentSizeInFrames, pDevice->sndio.pIntermediaryBuffer); + + int bytesWritten = ((mal_sio_write_proc)pDevice->pContext->sndio.sio_write)((struct mal_sio_hdl*)pDevice->sndio.handle, pDevice->sndio.pIntermediaryBuffer, pDevice->sndio.fragmentSizeInFrames * pDevice->internalChannels * mal_get_bytes_per_sample(pDevice->internalFormat)); + if (bytesWritten == 0) { + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[sndio] Failed to send data from the client to the device.", MAL_FAILED_TO_SEND_DATA_TO_DEVICE); + } } else { - // This case is where the number of channels in the output buffer do not match our internal channels. It could mean that it's - // not interleaved, in which case we can't handle right now since mini_al does not yet support non-interleaved streams. + // Capture. + int bytesRead = ((mal_sio_read_proc)pDevice->pContext->sndio.sio_read)((struct mal_sio_hdl*)pDevice->sndio.handle, pDevice->sndio.pIntermediaryBuffer, pDevice->sndio.fragmentSizeInFrames * mal_get_bytes_per_sample(pDevice->internalFormat)); + if (bytesRead == 0) { + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[sndio] Failed to read data from the device to be sent to the client.", MAL_FAILED_TO_READ_DATA_FROM_DEVICE); + } + + mal_uint32 framesRead = (mal_uint32)bytesRead / pDevice->internalChannels / mal_get_bytes_per_sample(pDevice->internalFormat); + mal_device__send_frames_to_client(pDevice, framesRead, pDevice->sndio.pIntermediaryBuffer); } } - return noErr; + return MAL_SUCCESS; } -void on_start_stop__coreaudio(void* pUserData, AudioUnit audioUnit, AudioUnitPropertyID propertyID, AudioUnitScope scope, AudioUnitElement element) +mal_result mal_context_uninit__sndio(mal_context* pContext) { - (void)propertyID; - - mal_device* pDevice = (mal_device*)pUserData; - mal_assert(pDevice != NULL); - - UInt32 isRunning; - UInt32 isRunningSize = sizeof(isRunning); - OSStatus status = ((mal_AudioUnitGetProperty_proc)pDevice->pContext->coreaudio.AudioUnitGetProperty)(audioUnit, kAudioOutputUnitProperty_IsRunning, scope, element, &isRunning, &isRunningSize); - if (status != noErr) { - return; // Don't really know what to do in this case... just ignore it, I suppose... - } + mal_assert(pContext != NULL); + mal_assert(pContext->backend == mal_backend_sndio); + + (void)pContext; + return MAL_SUCCESS; +} + +mal_result mal_context_init__sndio(mal_context* pContext) +{ + mal_assert(pContext != NULL); - if (!isRunning) { - mal_stop_proc onStop = pDevice->onStop; - if (onStop) { - onStop(pDevice); +#ifndef MAL_NO_RUNTIME_LINKING + // libpulse.so + const char* libsndioNames[] = { + "libsndio.so" + }; + + for (size_t i = 0; i < mal_countof(libsndioNames); ++i) { + pContext->sndio.sndioSO = mal_dlopen(libsndioNames[i]); + if (pContext->sndio.sndioSO != NULL) { + break; } } + + if (pContext->sndio.sndioSO == NULL) { + return MAL_NO_BACKEND; + } + + pContext->sndio.sio_open = (mal_proc)mal_dlsym(pContext->sndio.sndioSO, "sio_open"); + pContext->sndio.sio_close = (mal_proc)mal_dlsym(pContext->sndio.sndioSO, "sio_close"); + pContext->sndio.sio_setpar = (mal_proc)mal_dlsym(pContext->sndio.sndioSO, "sio_setpar"); + pContext->sndio.sio_getpar = (mal_proc)mal_dlsym(pContext->sndio.sndioSO, "sio_getpar"); + pContext->sndio.sio_getcap = (mal_proc)mal_dlsym(pContext->sndio.sndioSO, "sio_getcap"); + pContext->sndio.sio_write = (mal_proc)mal_dlsym(pContext->sndio.sndioSO, "sio_write"); + pContext->sndio.sio_read = (mal_proc)mal_dlsym(pContext->sndio.sndioSO, "sio_read"); + pContext->sndio.sio_start = (mal_proc)mal_dlsym(pContext->sndio.sndioSO, "sio_start"); + pContext->sndio.sio_stop = (mal_proc)mal_dlsym(pContext->sndio.sndioSO, "sio_stop"); + pContext->sndio.sio_initpar = (mal_proc)mal_dlsym(pContext->sndio.sndioSO, "sio_initpar"); +#else + pContext->sndio.sio_open = sio_open; + pContext->sndio.sio_close = sio_close; + pContext->sndio.sio_setpar = sio_setpar; + pContext->sndio.sio_getpar = sio_getpar; + pContext->sndio.sio_getcap = sio_getcap; + pContext->sndio.sio_write = sio_write; + pContext->sndio.sio_read = sio_read; + pContext->sndio.sio_start = sio_start; + pContext->sndio.sio_stop = sio_stop; + pContext->sndio.sio_initpar = sio_initpar; +#endif + + pContext->onUninit = mal_context_uninit__sndio; + pContext->onDeviceIDEqual = mal_context_is_device_id_equal__sndio; + pContext->onEnumDevices = mal_context_enumerate_devices__sndio; + pContext->onGetDeviceInfo = mal_context_get_device_info__sndio; + pContext->onDeviceInit = mal_device_init__sndio; + pContext->onDeviceUninit = mal_device_uninit__sndio; + pContext->onDeviceStart = mal_device__start_backend__sndio; + pContext->onDeviceStop = mal_device__stop_backend__sndio; + pContext->onDeviceBreakMainLoop = mal_device__break_main_loop__sndio; + pContext->onDeviceMainLoop = mal_device__main_loop__sndio; + + return MAL_SUCCESS; } +#endif // sndio -mal_result mal_device_init__coreaudio(mal_context* pContext, mal_device_type deviceType, const mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice) + +/////////////////////////////////////////////////////////////////////////////// +// +// audioio Backend +// +/////////////////////////////////////////////////////////////////////////////// +#ifdef MAL_HAS_AUDIOIO +#include +#include +#include +#include +#include + +void mal_construct_device_id__audioio(char* id, size_t idSize, const char* base, int deviceIndex) { - mal_assert(pContext != NULL); - mal_assert(pConfig != NULL); - mal_assert(pDevice != NULL); - mal_assert(deviceType == mal_device_type_playback || deviceType == mal_device_type_capture); + mal_assert(id != NULL); + mal_assert(idSize > 0); + mal_assert(deviceIndex >= 0); - mal_result result; + size_t baseLen = strlen(base); + mal_assert(idSize > baseLen); -#if defined(MAL_APPLE_DESKTOP) - AudioObjectID deviceObjectID; - result = mal_find_AudioObjectID(pContext, deviceType, pDeviceID, &deviceObjectID); - if (result != MAL_SUCCESS) { - return result; + mal_strcpy_s(id, idSize, base); + mal_itoa_s(deviceIndex, id+baseLen, idSize-baseLen, 10); +} + +mal_result mal_extract_device_index_from_id__audioio(const char* id, const char* base, int* pIndexOut) +{ + mal_assert(id != NULL); + mal_assert(base != NULL); + mal_assert(pIndexOut != NULL); + + size_t idLen = strlen(id); + size_t baseLen = strlen(base); + if (idLen <= baseLen) { + return MAL_ERROR; // Doesn't look like the id starts with the base. } - pDevice->coreaudio.deviceObjectID = deviceObjectID; -#endif + if (strncmp(id, base, baseLen) != 0) { + return MAL_ERROR; // ID does not begin with base. + } - // Core audio doesn't really use the notion of a period so we can leave this unmodified, but not too over the top. - if (pDevice->periods < 1) { - pDevice->periods = 1; + const char* deviceIndexStr = id + baseLen; + if (deviceIndexStr[0] == '\0') { + return MAL_ERROR; // No index specified in the ID. } - if (pDevice->periods > 16) { - pDevice->periods = 16; + + if (pIndexOut) { + *pIndexOut = atoi(deviceIndexStr); } + return MAL_SUCCESS; +} - // Audio component. - AudioComponentDescription desc; - desc.componentType = kAudioUnitType_Output; -#if defined(MAL_APPLE_DESKTOP) - desc.componentSubType = kAudioUnitSubType_HALOutput; -#else - desc.componentSubType = kAudioUnitSubType_RemoteIO; -#endif - desc.componentManufacturer = kAudioUnitManufacturer_Apple; - desc.componentFlags = 0; - desc.componentFlagsMask = 0; - - pDevice->coreaudio.component = ((mal_AudioComponentFindNext_proc)pContext->coreaudio.AudioComponentFindNext)(NULL, &desc); - if (pDevice->coreaudio.component == NULL) { - return MAL_FAILED_TO_INIT_BACKEND; +mal_bool32 mal_context_is_device_id_equal__audioio(mal_context* pContext, const mal_device_id* pID0, const mal_device_id* pID1) +{ + mal_assert(pContext != NULL); + mal_assert(pID0 != NULL); + mal_assert(pID1 != NULL); + (void)pContext; + + return mal_strcmp(pID0->audioio, pID1->audioio) == 0; +} + +mal_format mal_format_from_encoding__audioio(unsigned int encoding, unsigned int precision) +{ + if (precision == 8 && (encoding == AUDIO_ENCODING_ULINEAR || encoding == AUDIO_ENCODING_ULINEAR || encoding == AUDIO_ENCODING_ULINEAR_LE || encoding == AUDIO_ENCODING_ULINEAR_BE)) { + return mal_format_u8; + } else { + if (mal_is_little_endian() && encoding == AUDIO_ENCODING_SLINEAR_LE) { + if (precision == 16) { + return mal_format_s16; + } else if (precision == 24) { + return mal_format_s24; + } else if (precision == 32) { + return mal_format_s32; + } + } else if (mal_is_big_endian() && encoding == AUDIO_ENCODING_SLINEAR_BE) { + if (precision == 16) { + return mal_format_s16; + } else if (precision == 24) { + return mal_format_s24; + } else if (precision == 32) { + return mal_format_s32; + } + } } + + return mal_format_unknown; // Encoding not supported. +} + +mal_format mal_format_from_prinfo__audioio(struct audio_prinfo* prinfo) +{ + return mal_format_from_encoding__audioio(prinfo->encoding, prinfo->precision); +} + +mal_result mal_context_get_device_info_from_fd__audioio(mal_context* pContext, mal_device_type deviceType, int fd, mal_device_info* pInfoOut) +{ + mal_assert(pContext != NULL); + mal_assert(fd >= 0); + mal_assert(pInfoOut != NULL); - - // Audio unit. - OSStatus status = ((mal_AudioComponentInstanceNew_proc)pContext->coreaudio.AudioComponentInstanceNew)((AudioComponent)pDevice->coreaudio.component, (AudioUnit*)&pDevice->coreaudio.audioUnit); - if (status != noErr) { - return mal_result_from_OSStatus(status); + (void)pContext; + (void)deviceType; + + audio_device_t fdDevice; + if (ioctl(fd, AUDIO_GETDEV, &fdDevice) < 0) { + return MAL_ERROR; // Failed to retrieve device info. } - - - // The input/output buses need to be explicitly enabled and disabled. We set the flag based on the output unit first, then we just swap it for input. - UInt32 enableIOFlag = 1; - if (deviceType == mal_device_type_capture) { - enableIOFlag = 0; + + // Name. + mal_strcpy_s(pInfoOut->name, sizeof(pInfoOut->name), fdDevice.name); + + // Supported formats. We get this by looking at the encodings. + int counter = 0; + for (;;) { + audio_encoding_t encoding; + mal_zero_object(&encoding); + encoding.index = counter; + if (ioctl(fd, AUDIO_GETENC, &encoding) < 0) { + break; + } + + mal_format format = mal_format_from_encoding__audioio(encoding.encoding, encoding.precision); + if (format != mal_format_unknown) { + pInfoOut->formats[pInfoOut->formatCount++] = format; + } + + counter += 1; } - - status = ((mal_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)((AudioUnit)pDevice->coreaudio.audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, MAL_COREAUDIO_OUTPUT_BUS, &enableIOFlag, sizeof(enableIOFlag)); - if (status != noErr) { - ((mal_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnit); - return mal_result_from_OSStatus(status); + + audio_info_t fdInfo; + if (ioctl(fd, AUDIO_GETINFO, &fdInfo) < 0) { + return MAL_ERROR; } - - enableIOFlag = (enableIOFlag == 0) ? 1 : 0; - status = ((mal_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)((AudioUnit)pDevice->coreaudio.audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, MAL_COREAUDIO_INPUT_BUS, &enableIOFlag, sizeof(enableIOFlag)); - if (status != noErr) { - ((mal_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnit); - return mal_result_from_OSStatus(status); + + if (deviceType == mal_device_type_playback) { + pInfoOut->minChannels = fdInfo.play.channels; + pInfoOut->maxChannels = fdInfo.play.channels; + pInfoOut->minSampleRate = fdInfo.play.sample_rate; + pInfoOut->maxSampleRate = fdInfo.play.sample_rate; + } else { + pInfoOut->minChannels = fdInfo.record.channels; + pInfoOut->maxChannels = fdInfo.record.channels; + pInfoOut->minSampleRate = fdInfo.record.sample_rate; + pInfoOut->maxSampleRate = fdInfo.record.sample_rate; } + return MAL_SUCCESS; +} + +mal_result mal_context_enumerate_devices__audioio(mal_context* pContext, mal_enum_devices_callback_proc callback, void* pUserData) +{ + mal_assert(pContext != NULL); + mal_assert(callback != NULL); - // Set the device to use with this audio unit. This is only used on desktop since we are using defaults on mobile. -#if defined(MAL_APPLE_DESKTOP) - status = ((mal_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)((AudioUnit)pDevice->coreaudio.audioUnit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, (deviceType == mal_device_type_playback) ? MAL_COREAUDIO_OUTPUT_BUS : MAL_COREAUDIO_INPUT_BUS, &deviceObjectID, sizeof(AudioDeviceID)); - if (status != noErr) { - ((mal_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnit); - return mal_result_from_OSStatus(result); - } -#endif + const int maxDevices = 64; - // Format. This is the hardest part of initialization because there's a few variables to take into account. - // 1) The format must be supported by the device. - // 2) The format must be supported mini_al. - // 3) There's a priority that mini_al prefers. - // - // Ideally we would like to use a format that's as close to the hardware as possible so we can get as close to a passthrough as possible. The - // most important property is the sample rate. mini_al can do format conversion for any sample rate and channel count, but cannot do the same - // for the sample data format. If the sample data format is not supported by mini_al it must be ignored completely. - // - // On mobile platforms this is a bit different. We just force the use of whatever the audio unit's current format is set to. - AudioStreamBasicDescription bestFormat; - { - AudioUnitScope formatScope = (deviceType == mal_device_type_playback) ? kAudioUnitScope_Input : kAudioUnitScope_Output; - AudioUnitElement formatElement = (deviceType == mal_device_type_playback) ? MAL_COREAUDIO_OUTPUT_BUS : MAL_COREAUDIO_INPUT_BUS; + // Every device will be named "/dev/audioN", with a "/dev/audioctlN" equivalent. We use the "/dev/audioctlN" + // version here since we can open it even when another process has control of the "/dev/audioN" device. + char devpath[256]; + for (int iDevice = 0; iDevice < maxDevices; ++iDevice) { + mal_strcpy_s(devpath, sizeof(devpath), "/dev/audioctl"); + mal_itoa_s(iDevice, devpath+strlen(devpath), sizeof(devpath)-strlen(devpath), 10); - #if defined(MAL_APPLE_DESKTOP) - result = mal_device_find_best_format__coreaudio(pDevice, &bestFormat); - if (result != MAL_SUCCESS) { - ((mal_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnit); - return result; - } - - // From what I can see, Apple's documentation implies that we should keep the sample rate consistent. - AudioStreamBasicDescription origFormat; - UInt32 origFormatSize = sizeof(origFormat); - if (deviceType == mal_device_type_playback) { - status = ((mal_AudioUnitGetProperty_proc)pContext->coreaudio.AudioUnitGetProperty)((AudioUnit)pDevice->coreaudio.audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, MAL_COREAUDIO_OUTPUT_BUS, &origFormat, &origFormatSize); - } else { - status = ((mal_AudioUnitGetProperty_proc)pContext->coreaudio.AudioUnitGetProperty)((AudioUnit)pDevice->coreaudio.audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, MAL_COREAUDIO_INPUT_BUS, &origFormat, &origFormatSize); - } - - if (status != noErr) { - ((mal_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnit); - return result; + struct stat st; + if (stat(devpath, &st) < 0) { + break; } + + // The device exists, but we need to check if it's usable as playback and/or capture. + int fd; + mal_bool32 isTerminating = MAL_FALSE; - bestFormat.mSampleRate = origFormat.mSampleRate; - - status = ((mal_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)((AudioUnit)pDevice->coreaudio.audioUnit, kAudioUnitProperty_StreamFormat, formatScope, formatElement, &bestFormat, sizeof(bestFormat)); - if (status != noErr) { - // We failed to set the format, so fall back to the current format of the audio unit. - bestFormat = origFormat; - } - #else - UInt32 propSize = sizeof(bestFormat); - status = ((mal_AudioUnitGetProperty_proc)pContext->coreaudio.AudioUnitGetProperty)((AudioUnit)pDevice->coreaudio.audioUnit, kAudioUnitProperty_StreamFormat, formatScope, formatElement, &bestFormat, &propSize); - if (status != noErr) { - ((mal_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnit); - return mal_result_from_OSStatus(status); + // Playback. + if (!isTerminating) { + fd = open(devpath, O_RDONLY, 0); + if (fd >= 0) { + // Supports playback. + mal_device_info deviceInfo; + mal_zero_object(&deviceInfo); + mal_construct_device_id__audioio(deviceInfo.id.audioio, sizeof(deviceInfo.id.audioio), "/dev/audio", iDevice); + if (mal_context_get_device_info_from_fd__audioio(pContext, mal_device_type_playback, fd, &deviceInfo) == MAL_SUCCESS) { + isTerminating = !callback(pContext, mal_device_type_playback, &deviceInfo, pUserData); + } + + close(fd); + } } - // Sample rate is a little different here because for some reason kAudioUnitProperty_StreamFormat returns 0... Oh well. We need to instead try - // setting the sample rate to what the user has requested and then just see the results of it. Need to use some Objective-C here for this since - // it depends on Apple's AVAudioSession API. To do this we just get the shared AVAudioSession instance and then set it. Note that from what I - // can tell, it looks like the sample rate is shared between playback and capture for everything. - @autoreleasepool { - AVAudioSession* pAudioSession = [AVAudioSession sharedInstance]; - mal_assert(pAudioSession != NULL); - - [pAudioSession setPreferredSampleRate:(double)pDevice->sampleRate error:nil]; - bestFormat.mSampleRate = pAudioSession.sampleRate; + // Capture. + if (!isTerminating) { + fd = open(devpath, O_WRONLY, 0); + if (fd >= 0) { + // Supports capture. + mal_device_info deviceInfo; + mal_zero_object(&deviceInfo); + mal_construct_device_id__audioio(deviceInfo.id.audioio, sizeof(deviceInfo.id.audioio), "/dev/audio", iDevice); + if (mal_context_get_device_info_from_fd__audioio(pContext, mal_device_type_capture, fd, &deviceInfo) == MAL_SUCCESS) { + isTerminating = !callback(pContext, mal_device_type_capture, &deviceInfo, pUserData); + } + + close(fd); + } } - status = ((mal_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)((AudioUnit)pDevice->coreaudio.audioUnit, kAudioUnitProperty_StreamFormat, formatScope, formatElement, &bestFormat, sizeof(bestFormat)); - if (status != noErr) { - ((mal_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnit); - return mal_result_from_OSStatus(status); + if (isTerminating) { + break; } - #endif - - result = mal_format_from_AudioStreamBasicDescription(&bestFormat, &pDevice->internalFormat); + } + + return MAL_SUCCESS; +} + +mal_result mal_context_get_device_info__audioio(mal_context* pContext, mal_device_type deviceType, const mal_device_id* pDeviceID, mal_share_mode shareMode, mal_device_info* pDeviceInfo) +{ + mal_assert(pContext != NULL); + (void)shareMode; + + // We need to open the "/dev/audioctlN" device to get the info. To do this we need to extract the number + // from the device ID which will be in "/dev/audioN" format. + int fd = -1; + int deviceIndex = -1; + char ctlid[256]; + if (pDeviceID == NULL) { + // Default device. + mal_strcpy_s(ctlid, sizeof(ctlid), "/dev/audioctl"); + } else { + // Specific device. We need to convert from "/dev/audioN" to "/dev/audioctlN". + mal_result result = mal_extract_device_index_from_id__audioio(pDeviceID->audioio, "/dev/audio", &deviceIndex); if (result != MAL_SUCCESS) { - ((mal_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnit); return result; } - if (pDevice->internalFormat == mal_format_unknown) { - ((mal_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnit); - return MAL_FORMAT_NOT_SUPPORTED; - } - - pDevice->internalChannels = bestFormat.mChannelsPerFrame; - pDevice->internalSampleRate = bestFormat.mSampleRate; + mal_construct_device_id__audioio(ctlid, sizeof(ctlid), "/dev/audioctl", deviceIndex); } + fd = open(ctlid, (deviceType == mal_device_type_playback) ? O_WRONLY : O_RDONLY, 0); + if (fd == -1) { + return MAL_NO_DEVICE; + } - // Internal channel map. -#if defined(MAL_APPLE_DESKTOP) - result = mal_get_AudioObject_channel_map(pContext, deviceObjectID, deviceType, pDevice->internalChannelMap); - if (result != MAL_SUCCESS) { - return result; + if (deviceIndex == -1) { + mal_strcpy_s(pDeviceInfo->id.audioio, sizeof(pDeviceInfo->id.audioio), "/dev/audio"); + } else { + mal_construct_device_id__audioio(pDeviceInfo->id.audioio, sizeof(pDeviceInfo->id.audioio), "/dev/audio", deviceIndex); } -#else - // TODO: Figure out how to get the channel map using AVAudioSession. - mal_get_standard_channel_map(mal_standard_channel_map_default, pDevice->internalChannels, pDevice->internalChannelMap); -#endif + mal_result result = mal_context_get_device_info_from_fd__audioio(pContext, deviceType, fd, pDeviceInfo); - // Buffer size. Not allowing this to be configurable on iOS. - mal_uint32 actualBufferSizeInFrames = pDevice->bufferSizeInFrames; - if (actualBufferSizeInFrames < pDevice->periods) { - actualBufferSizeInFrames = pDevice->periods; + close(fd); + return result; +} + +void mal_device_uninit__audioio(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + close(pDevice->audioio.fd); + mal_free(pDevice->audioio.pIntermediaryBuffer); +} + +mal_result mal_device_init__audioio(mal_context* pContext, mal_device_type deviceType, const mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice) +{ + (void)pContext; + + mal_assert(pDevice != NULL); + mal_zero_object(&pDevice->audioio); + + // The first thing to do is open the file. + const char* deviceName = "/dev/audio"; + if (pDeviceID != NULL) { + deviceName = pDeviceID->audioio; } -#if defined(MAL_APPLE_DESKTOP) - if (pDevice->usingDefaultBufferSize) { - // CPU speed is a factor to consider when determine how large of a buffer we need. - float fCPUSpeed = mal_calculate_cpu_speed_factor(); + pDevice->audioio.fd = open(deviceName, (deviceType == mal_device_type_playback) ? O_WRONLY : O_RDONLY, 0); + if (pDevice->audioio.fd == -1) { + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audioio] Failed to open device.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + } - // In my admittedly limited testing, capture latency seems to be about the same as playback with Core Audio, at least on my MacBook Pro. On other - // backends, however, this is often different. I am therefore leaving the logic below in place just in case I need to do some capture/playback - // specific tweaking. - float fDeviceType; - if (deviceType == mal_device_type_playback) { - fDeviceType = 1.0f; - } else { - fDeviceType = 6.0f; - } + audio_info_t fdInfo; + AUDIO_INITINFO(&fdInfo); - // Backend tax. Need to fiddle with this. - float fBackend = 1.0f; + struct audio_prinfo* prinfo; + if (deviceType == mal_device_type_playback) { + prinfo = &fdInfo.play; + fdInfo.mode = AUMODE_PLAY; + } else { + prinfo = &fdInfo.record; + fdInfo.mode = AUMODE_RECORD; + } - actualBufferSizeInFrames = mal_calculate_default_buffer_size_in_frames(pConfig->performanceProfile, pConfig->sampleRate, fCPUSpeed*fDeviceType*fBackend); - if (actualBufferSizeInFrames < pDevice->periods) { - actualBufferSizeInFrames = pDevice->periods; - } + // Format. Note that it looks like audioio does not support floating point formats. In this case + // we just fall back to s16. + switch (pDevice->format) + { + case mal_format_u8: + { + prinfo->encoding = AUDIO_ENCODING_ULINEAR; + prinfo->precision = 8; + } break; + + case mal_format_s24: + { + prinfo->encoding = (mal_is_little_endian()) ? AUDIO_ENCODING_SLINEAR_LE : AUDIO_ENCODING_SLINEAR_BE; + prinfo->precision = 24; + } break; + + case mal_format_s32: + { + prinfo->encoding = (mal_is_little_endian()) ? AUDIO_ENCODING_SLINEAR_LE : AUDIO_ENCODING_SLINEAR_BE; + prinfo->precision = 32; + } break; + + case mal_format_s16: + case mal_format_f32: + default: + { + prinfo->encoding = (mal_is_little_endian()) ? AUDIO_ENCODING_SLINEAR_LE : AUDIO_ENCODING_SLINEAR_BE; + prinfo->precision = 16; + } break; } - - actualBufferSizeInFrames = actualBufferSizeInFrames / pDevice->periods; - result = mal_set_AudioObject_buffer_size_in_frames(pContext, deviceObjectID, deviceType, &actualBufferSizeInFrames); + + // We always want to the use the devices native channel count and sample rate. + mal_device_info nativeInfo; + mal_result result = mal_context_get_device_info(pContext, deviceType, pDeviceID, pConfig->shareMode, &nativeInfo); if (result != MAL_SUCCESS) { - return result; + close(pDevice->audioio.fd); + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audioio] Failed to retrieve device format.", result); } -#else - actualBufferSizeInFrames = 4096; -#endif - pDevice->bufferSizeInFrames = actualBufferSizeInFrames * pDevice->periods; + prinfo->channels = nativeInfo.maxChannels; + prinfo->sample_rate = nativeInfo.maxSampleRate; - // During testing I discovered that the buffer size can be too big. You'll get an error like this: - // - // kAudioUnitErr_TooManyFramesToProcess : inFramesToProcess=4096, mMaxFramesPerSlice=512 - // - // Note how inFramesToProcess is smaller than mMaxFramesPerSlice. To fix, we need to set kAudioUnitProperty_MaximumFramesPerSlice to that - // of the size of our buffer, or do it the other way around and set our buffer size to the kAudioUnitProperty_MaximumFramesPerSlice. - { - /*AudioUnitScope propScope = (deviceType == mal_device_type_playback) ? kAudioUnitScope_Input : kAudioUnitScope_Output; - AudioUnitElement propBus = (deviceType == mal_device_type_playback) ? MAL_COREAUDIO_OUTPUT_BUS : MAL_COREAUDIO_INPUT_BUS; + // We need to apply the settings so far so we can get back the actual sample rate which we need for calculating + // the default buffer size below. + if (ioctl(pDevice->audioio.fd, AUDIO_SETINFO, &fdInfo) < 0) { + close(pDevice->audioio.fd); + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audioio] Failed to set device format. AUDIO_SETINFO failed.", MAL_FORMAT_NOT_SUPPORTED); + } - status = ((mal_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)((AudioUnit)pDevice->coreaudio.audioUnit, kAudioUnitProperty_MaximumFramesPerSlice, propScope, propBus, &actualBufferSizeInFrames, sizeof(actualBufferSizeInFrames)); - if (status != noErr) { - ((mal_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnit); - return mal_result_from_OSStatus(status); - }*/ - - status = ((mal_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)((AudioUnit)pDevice->coreaudio.audioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &actualBufferSizeInFrames, sizeof(actualBufferSizeInFrames)); - if (status != noErr) { - ((mal_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnit); - return mal_result_from_OSStatus(status); - } + if (ioctl(pDevice->audioio.fd, AUDIO_GETINFO, &fdInfo) < 0) { + close(pDevice->audioio.fd); + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audioio] AUDIO_GETINFO failed.", MAL_FORMAT_NOT_SUPPORTED); } + pDevice->internalFormat = mal_format_from_prinfo__audioio(prinfo); + if (pDevice->internalFormat == mal_format_unknown) { + close(pDevice->audioio.fd); + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audioio] The device's internal device format is not supported by mini_al. The device is unusable.", MAL_FORMAT_NOT_SUPPORTED); + } - // Callbacks. - AURenderCallbackStruct callbackInfo; - callbackInfo.inputProcRefCon = pDevice; - if (deviceType == mal_device_type_playback) { - callbackInfo.inputProc = mal_on_output__coreaudio; - status = ((mal_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)((AudioUnit)pDevice->coreaudio.audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, MAL_COREAUDIO_OUTPUT_BUS, &callbackInfo, sizeof(callbackInfo)); - if (status != noErr) { - ((mal_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnit); - return mal_result_from_OSStatus(status); - } - } else { - callbackInfo.inputProc = mal_on_input__coreaudio; - status = ((mal_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)((AudioUnit)pDevice->coreaudio.audioUnit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, MAL_COREAUDIO_INPUT_BUS, &callbackInfo, sizeof(callbackInfo)); - if (status != noErr) { - ((mal_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnit); - return mal_result_from_OSStatus(status); - } + pDevice->internalChannels = prinfo->channels; + pDevice->internalSampleRate = prinfo->sample_rate; + + + + // Try calculating an appropriate default buffer size. + if (pDevice->usingDefaultBufferSize) { + // CPU speed factor. + float fCPUSpeed = mal_calculate_cpu_speed_factor(); + + // Playback vs capture latency. + float fDeviceType = 1; + + // Backend tax. + float fBackend = 1; + + pDevice->bufferSizeInFrames = mal_calculate_default_buffer_size_in_frames(pConfig->performanceProfile, pDevice->internalSampleRate, fCPUSpeed*fDeviceType*fBackend); + } + + // What mini_al calls a fragment, audioio calls a block. + mal_uint32 fragmentSizeInBytes = pDevice->bufferSizeInFrames * mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels); + if (fragmentSizeInBytes < 16) { + fragmentSizeInBytes = 16; } + - // We need to listen for stop events. - status = ((mal_AudioUnitAddPropertyListener_proc)pContext->coreaudio.AudioUnitAddPropertyListener)((AudioUnit)pDevice->coreaudio.audioUnit, kAudioOutputUnitProperty_IsRunning, on_start_stop__coreaudio, pDevice); - if (status != noErr) { - ((mal_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnit); - return mal_result_from_OSStatus(status); + AUDIO_INITINFO(&fdInfo); + fdInfo.blocksize = fragmentSizeInBytes; + fdInfo.hiwat = mal_max(pDevice->periods, 5); + fdInfo.lowat = (unsigned int)(fdInfo.hiwat * 0.75); + if (ioctl(pDevice->audioio.fd, AUDIO_SETINFO, &fdInfo) < 0) { + close(pDevice->audioio.fd); + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audioio] Failed to set internal buffer size. AUDIO_SETINFO failed.", MAL_FORMAT_NOT_SUPPORTED); } + pDevice->periods = fdInfo.hiwat; + pDevice->bufferSizeInFrames = (fdInfo.blocksize * fdInfo.hiwat) / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels); + + pDevice->audioio.fragmentSizeInFrames = fdInfo.blocksize / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels); + + + // For the channel map, I'm not sure how to query the channel map (or if it's even possible). I'm just + // using mini_al's default channel map for now. + mal_get_standard_channel_map(mal_standard_channel_map_default, pDevice->internalChannels, pDevice->internalChannelMap); + - // We need a buffer list if this is an input device. We render into this in the input callback. - if (deviceType == mal_device_type_capture) { - mal_bool32 isInterleaved = (bestFormat.mFormatFlags & kAudioFormatFlagIsNonInterleaved) == 0; - - size_t allocationSize = sizeof(AudioBufferList) - sizeof(AudioBuffer); // Subtract sizeof(AudioBuffer) because that part is dynamically sized. - if (isInterleaved) { - // Interleaved case. This is the simple case because we just have one buffer. - allocationSize += sizeof(AudioBuffer) * 1; - allocationSize += actualBufferSizeInFrames * mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels); - } else { - // Non-interleaved case. This is the more complex case because there's more than one buffer. - allocationSize += sizeof(AudioBuffer) * pDevice->internalChannels; - allocationSize += actualBufferSizeInFrames * mal_get_bytes_per_sample(pDevice->internalFormat) * pDevice->internalChannels; - } - - AudioBufferList* pBufferList = (AudioBufferList*)mal_malloc(allocationSize); - if (pBufferList == NULL) { - ((mal_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnit); - return MAL_OUT_OF_MEMORY; - } - - if (isInterleaved) { - pBufferList->mNumberBuffers = 1; - pBufferList->mBuffers[0].mNumberChannels = pDevice->internalChannels; - pBufferList->mBuffers[0].mDataByteSize = actualBufferSizeInFrames * mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels); - pBufferList->mBuffers[0].mData = (mal_uint8*)pBufferList + sizeof(AudioBufferList); - } else { - pBufferList->mNumberBuffers = pDevice->internalChannels; - for (mal_uint32 iBuffer = 0; iBuffer < pBufferList->mNumberBuffers; ++iBuffer) { - pBufferList->mBuffers[iBuffer].mNumberChannels = 1; - pBufferList->mBuffers[iBuffer].mDataByteSize = actualBufferSizeInFrames * mal_get_bytes_per_sample(pDevice->internalFormat); - pBufferList->mBuffers[iBuffer].mData = (mal_uint8*)pBufferList + ((sizeof(AudioBufferList) - sizeof(AudioBuffer)) + (sizeof(AudioBuffer) * pDevice->internalChannels)) + (actualBufferSizeInFrames * mal_get_bytes_per_sample(pDevice->internalFormat) * iBuffer); + // When not using MMAP mode we need to use an intermediary buffer to the data transfer between the client + // and device. Everything is done by the size of a fragment. + pDevice->audioio.pIntermediaryBuffer = mal_malloc(fdInfo.blocksize); + if (pDevice->audioio.pIntermediaryBuffer == NULL) { + close(pDevice->audioio.fd); + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audioio] Failed to allocate memory for intermediary buffer.", MAL_OUT_OF_MEMORY); + } + + + return MAL_SUCCESS; +} + +mal_result mal_device__start_backend__audioio(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + // The device is started by the next calls to read() and write(). For playback it's simple - just read + // data from the client, then write it to the device with write() which will in turn start the device. + // For capture it's a bit less intuitive - we do nothing (it'll be started automatically by the first + // call to read(). + if (pDevice->type == mal_device_type_playback) { + // Playback. Need to load the entire buffer, which means we need to write a fragment for each period. + for (mal_uint32 iPeriod = 0; iPeriod < pDevice->periods; iPeriod += 1) { + mal_device__read_frames_from_client(pDevice, pDevice->audioio.fragmentSizeInFrames, pDevice->audioio.pIntermediaryBuffer); + + int bytesWritten = write(pDevice->audioio.fd, pDevice->audioio.pIntermediaryBuffer, pDevice->audioio.fragmentSizeInFrames * mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels)); + if (bytesWritten == -1) { + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audioio] Failed to send initial chunk of data to the device.", MAL_FAILED_TO_SEND_DATA_TO_DEVICE); } } - - pDevice->coreaudio.pAudioBufferList = pBufferList; + } else { + // Capture. Do nothing. } - - - // Initialize the audio unit. - status = ((mal_AudioUnitInitialize_proc)pContext->coreaudio.AudioUnitInitialize)((AudioUnit)pDevice->coreaudio.audioUnit); - if (status != noErr) { - mal_free(pDevice->coreaudio.pAudioBufferList); - ((mal_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnit); - return mal_result_from_OSStatus(status); + + return MAL_SUCCESS; +} + +mal_result mal_device__stop_backend__audioio(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + if (ioctl(pDevice->audioio.fd, AUDIO_FLUSH, 0) < 0) { + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audioio] Failed to stop device. AUDIO_FLUSH failed.", MAL_FAILED_TO_STOP_BACKEND_DEVICE); } - return MAL_SUCCESS; } -mal_result mal_device__start_backend__coreaudio(mal_device* pDevice) -{ - mal_assert(pDevice != NULL); - - OSStatus status = ((mal_AudioOutputUnitStart_proc)pDevice->pContext->coreaudio.AudioOutputUnitStart)((AudioUnit)pDevice->coreaudio.audioUnit); - if (status != noErr) { - return mal_result_from_OSStatus(status); +mal_result mal_device__break_main_loop__audioio(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + pDevice->audioio.breakFromMainLoop = MAL_TRUE; + return MAL_SUCCESS; +} + +mal_result mal_device__main_loop__audioio(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + pDevice->audioio.breakFromMainLoop = MAL_FALSE; + while (!pDevice->audioio.breakFromMainLoop) { + // Break from the main loop if the device isn't started anymore. Likely what's happened is the application + // has requested that the device be stopped. + if (!mal_device_is_started(pDevice)) { + break; + } + + if (pDevice->type == mal_device_type_playback) { + // Playback. + mal_device__read_frames_from_client(pDevice, pDevice->audioio.fragmentSizeInFrames, pDevice->audioio.pIntermediaryBuffer); + + int bytesWritten = write(pDevice->audioio.fd, pDevice->audioio.pIntermediaryBuffer, pDevice->audioio.fragmentSizeInFrames * pDevice->internalChannels * mal_get_bytes_per_sample(pDevice->internalFormat)); + if (bytesWritten < 0) { + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audioio] Failed to send data from the client to the device.", MAL_FAILED_TO_SEND_DATA_TO_DEVICE); + } + } else { + // Capture. + int bytesRead = read(pDevice->audioio.fd, pDevice->audioio.pIntermediaryBuffer, pDevice->audioio.fragmentSizeInFrames * mal_get_bytes_per_sample(pDevice->internalFormat)); + if (bytesRead < 0) { + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[audioio] Failed to read data from the device to be sent to the client.", MAL_FAILED_TO_READ_DATA_FROM_DEVICE); + } + + mal_uint32 framesRead = (mal_uint32)bytesRead / pDevice->internalChannels / mal_get_bytes_per_sample(pDevice->internalFormat); + mal_device__send_frames_to_client(pDevice, framesRead, pDevice->audioio.pIntermediaryBuffer); + } } - - return MAL_SUCCESS; -} -mal_result mal_device__stop_backend__coreaudio(mal_device* pDevice) -{ - mal_assert(pDevice != NULL); - - OSStatus status = ((mal_AudioOutputUnitStop_proc)pDevice->pContext->coreaudio.AudioOutputUnitStop)((AudioUnit)pDevice->coreaudio.audioUnit); - if (status != noErr) { - return mal_result_from_OSStatus(status); - } - return MAL_SUCCESS; } - -mal_result mal_context_uninit__coreaudio(mal_context* pContext) +mal_result mal_context_uninit__audioio(mal_context* pContext) { mal_assert(pContext != NULL); - mal_assert(pContext->backend == mal_backend_coreaudio); - -#if !defined(MAL_NO_RUNTIME_LINKING) && !defined(MAL_APPLE_MOBILE) - mal_dlclose(pContext->coreaudio.hAudioUnit); - mal_dlclose(pContext->coreaudio.hCoreAudio); - mal_dlclose(pContext->coreaudio.hCoreFoundation); -#endif + mal_assert(pContext->backend == mal_backend_audioio); (void)pContext; return MAL_SUCCESS; } -mal_result mal_context_init__coreaudio(mal_context* pContext) +mal_result mal_context_init__audioio(mal_context* pContext) { mal_assert(pContext != NULL); - -#if !defined(MAL_NO_RUNTIME_LINKING) && !defined(MAL_APPLE_MOBILE) - pContext->coreaudio.hCoreFoundation = mal_dlopen("CoreFoundation.framework/CoreFoundation"); - if (pContext->coreaudio.hCoreFoundation == NULL) { - return MAL_API_NOT_FOUND; - } - - pContext->coreaudio.CFStringGetCString = mal_dlsym(pContext->coreaudio.hCoreFoundation, "CFStringGetCString"); - - - pContext->coreaudio.hCoreAudio = mal_dlopen("CoreAudio.framework/CoreAudio"); - if (pContext->coreaudio.hCoreAudio == NULL) { - mal_dlclose(pContext->coreaudio.hCoreFoundation); - return MAL_API_NOT_FOUND; - } - - pContext->coreaudio.AudioObjectGetPropertyData = mal_dlsym(pContext->coreaudio.hCoreAudio, "AudioObjectGetPropertyData"); - pContext->coreaudio.AudioObjectGetPropertyDataSize = mal_dlsym(pContext->coreaudio.hCoreAudio, "AudioObjectGetPropertyDataSize"); - pContext->coreaudio.AudioObjectSetPropertyData = mal_dlsym(pContext->coreaudio.hCoreAudio, "AudioObjectSetPropertyData"); - - - // It looks like Apple has moved some APIs from AudioUnit into AudioToolbox on more recent versions of macOS. They are still - // defined in AudioUnit, but just in case they decide to remove them from there entirely I'm going to implement a fallback. - // The way it'll work is that it'll first try AudioUnit, and if the required symbols are not present there we'll fall back to - // AudioToolbox. - pContext->coreaudio.hAudioUnit = mal_dlopen("AudioUnit.framework/AudioUnit"); - if (pContext->coreaudio.hAudioUnit == NULL) { - mal_dlclose(pContext->coreaudio.hCoreAudio); - mal_dlclose(pContext->coreaudio.hCoreFoundation); - return MAL_API_NOT_FOUND; - } - - if (mal_dlsym(pContext->coreaudio.hAudioUnit, "AudioComponentFindNext") == NULL) { - // Couldn't find the required symbols in AudioUnit, so fall back to AudioToolbox. - mal_dlclose(pContext->coreaudio.hAudioUnit); - pContext->coreaudio.hAudioUnit = mal_dlopen("AudioToolbox.framework/AudioToolbox"); - if (pContext->coreaudio.hAudioUnit == NULL) { - mal_dlclose(pContext->coreaudio.hCoreAudio); - mal_dlclose(pContext->coreaudio.hCoreFoundation); - return MAL_API_NOT_FOUND; - } - } - - pContext->coreaudio.AudioComponentFindNext = mal_dlsym(pContext->coreaudio.hAudioUnit, "AudioComponentFindNext"); - pContext->coreaudio.AudioComponentInstanceDispose = mal_dlsym(pContext->coreaudio.hAudioUnit, "AudioComponentInstanceDispose"); - pContext->coreaudio.AudioComponentInstanceNew = mal_dlsym(pContext->coreaudio.hAudioUnit, "AudioComponentInstanceNew"); - pContext->coreaudio.AudioOutputUnitStart = mal_dlsym(pContext->coreaudio.hAudioUnit, "AudioOutputUnitStart"); - pContext->coreaudio.AudioOutputUnitStop = mal_dlsym(pContext->coreaudio.hAudioUnit, "AudioOutputUnitStop"); - pContext->coreaudio.AudioUnitAddPropertyListener = mal_dlsym(pContext->coreaudio.hAudioUnit, "AudioUnitAddPropertyListener"); - pContext->coreaudio.AudioUnitGetProperty = mal_dlsym(pContext->coreaudio.hAudioUnit, "AudioUnitGetProperty"); - pContext->coreaudio.AudioUnitSetProperty = mal_dlsym(pContext->coreaudio.hAudioUnit, "AudioUnitSetProperty"); - pContext->coreaudio.AudioUnitInitialize = mal_dlsym(pContext->coreaudio.hAudioUnit, "AudioUnitInitialize"); - pContext->coreaudio.AudioUnitRender = mal_dlsym(pContext->coreaudio.hAudioUnit, "AudioUnitRender"); -#else - pContext->coreaudio.CFStringGetCString = (mal_proc)CFStringGetCString; - - #if defined(MAL_APPLE_DESKTOP) - pContext->coreaudio.AudioObjectGetPropertyData = (mal_proc)AudioObjectGetPropertyData; - pContext->coreaudio.AudioObjectGetPropertyDataSize = (mal_proc)AudioObjectGetPropertyDataSize; - pContext->coreaudio.AudioObjectSetPropertyData = (mal_proc)AudioObjectSetPropertyData; - #endif - - pContext->coreaudio.AudioComponentFindNext = (mal_proc)AudioComponentFindNext; - pContext->coreaudio.AudioComponentInstanceDispose = (mal_proc)AudioComponentInstanceDispose; - pContext->coreaudio.AudioComponentInstanceNew = (mal_proc)AudioComponentInstanceNew; - pContext->coreaudio.AudioOutputUnitStart = (mal_proc)AudioOutputUnitStart; - pContext->coreaudio.AudioOutputUnitStop = (mal_proc)AudioOutputUnitStop; - pContext->coreaudio.AudioUnitAddPropertyListener = (mal_proc)AudioUnitAddPropertyListener; - pContext->coreaudio.AudioUnitGetProperty = (mal_proc)AudioUnitGetProperty; - pContext->coreaudio.AudioUnitSetProperty = (mal_proc)AudioUnitSetProperty; - pContext->coreaudio.AudioUnitInitialize = (mal_proc)AudioUnitInitialize; - pContext->coreaudio.AudioUnitRender = (mal_proc)AudioUnitRender; -#endif - pContext->isBackendAsynchronous = MAL_TRUE; - - pContext->onUninit = mal_context_uninit__coreaudio; - pContext->onDeviceIDEqual = mal_context_is_device_id_equal__coreaudio; - pContext->onEnumDevices = mal_context_enumerate_devices__coreaudio; - pContext->onGetDeviceInfo = mal_context_get_device_info__coreaudio; - pContext->onDeviceInit = mal_device_init__coreaudio; - pContext->onDeviceUninit = mal_device_uninit__coreaudio; - pContext->onDeviceStart = mal_device__start_backend__coreaudio; - pContext->onDeviceStop = mal_device__stop_backend__coreaudio; + pContext->onUninit = mal_context_uninit__audioio; + pContext->onDeviceIDEqual = mal_context_is_device_id_equal__audioio; + pContext->onEnumDevices = mal_context_enumerate_devices__audioio; + pContext->onGetDeviceInfo = mal_context_get_device_info__audioio; + pContext->onDeviceInit = mal_device_init__audioio; + pContext->onDeviceUninit = mal_device_uninit__audioio; + pContext->onDeviceStart = mal_device__start_backend__audioio; + pContext->onDeviceStop = mal_device__stop_backend__audioio; + pContext->onDeviceBreakMainLoop = mal_device__break_main_loop__audioio; + pContext->onDeviceMainLoop = mal_device__main_loop__audioio; return MAL_SUCCESS; } -#endif // Core Audio - +#endif // audioio /////////////////////////////////////////////////////////////////////////////// @@ -14747,10 +16463,10 @@ mal_result mal_context_get_device_info__oss(mal_context* pContext, mal_device_ty if ((formatMask & AFMT_U8) != 0) { pDeviceInfo->formats[pDeviceInfo->formatCount++] = mal_format_u8; } - if ((formatMask & AFMT_S16_LE) != 0) { + if (((formatMask & AFMT_S16_LE) != 0 && mal_is_little_endian()) || (AFMT_S16_BE && mal_is_big_endian())) { pDeviceInfo->formats[pDeviceInfo->formatCount++] = mal_format_s16; } - if ((formatMask & AFMT_S32_LE) != 0) { + if (((formatMask & AFMT_S32_LE) != 0 && mal_is_little_endian()) || (AFMT_S32_BE && mal_is_big_endian())) { pDeviceInfo->formats[pDeviceInfo->formatCount++] = mal_format_s32; } @@ -14804,10 +16520,10 @@ mal_result mal_device_init__oss(mal_context* pContext, mal_device_type type, con // Format. int ossFormat = AFMT_U8; switch (pDevice->format) { - case mal_format_s16: ossFormat = AFMT_S16_LE; break; - case mal_format_s24: ossFormat = AFMT_S32_LE; break; - case mal_format_s32: ossFormat = AFMT_S32_LE; break; - case mal_format_f32: ossFormat = AFMT_S32_LE; break; + case mal_format_s16: ossFormat = (mal_is_little_endian()) ? AFMT_S16_LE : AFMT_S16_BE; break; + case mal_format_s24: ossFormat = (mal_is_little_endian()) ? AFMT_S32_LE : AFMT_S32_BE; break; + case mal_format_s32: ossFormat = (mal_is_little_endian()) ? AFMT_S32_LE : AFMT_S32_BE; break; + case mal_format_f32: ossFormat = (mal_is_little_endian()) ? AFMT_S32_LE : AFMT_S32_BE; break; case mal_format_u8: default: ossFormat = AFMT_U8; break; } @@ -14817,14 +16533,24 @@ mal_result mal_device_init__oss(mal_context* pContext, mal_device_type type, con return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[OSS] Failed to set format.", MAL_FORMAT_NOT_SUPPORTED); } - switch (ossFormat) { - case AFMT_U8: pDevice->internalFormat = mal_format_u8; break; - case AFMT_S16_LE: pDevice->internalFormat = mal_format_s16; break; - case AFMT_S32_LE: pDevice->internalFormat = mal_format_s32; break; - default: mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[OSS] The device's internal format is not supported by mini_al.", MAL_FORMAT_NOT_SUPPORTED); + if (ossFormat == AFMT_U8) { + pDevice->internalFormat = mal_format_u8; + } else { + if (mal_is_little_endian()) { + switch (ossFormat) { + case AFMT_S16_LE: pDevice->internalFormat = mal_format_s16; break; + case AFMT_S32_LE: pDevice->internalFormat = mal_format_s32; break; + default: mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[OSS] The device's internal format is not supported by mini_al.", MAL_FORMAT_NOT_SUPPORTED); + } + } else { + switch (ossFormat) { + case AFMT_S16_BE: pDevice->internalFormat = mal_format_s16; break; + case AFMT_S32_BE: pDevice->internalFormat = mal_format_s32; break; + default: mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[OSS] The device's internal format is not supported by mini_al.", MAL_FORMAT_NOT_SUPPORTED); + } + } } - // Channels. int ossChannels = (int)pConfig->channels; ossResult = ioctl(pDevice->oss.fd, SNDCTL_DSP_CHANNELS, &ossChannels); @@ -15538,7 +17264,7 @@ mal_result mal_device_init__opensl(mal_context* pContext, mal_device_type type, pFormat->bitsPerSample = mal_get_bytes_per_sample(pDevice->format)*8; pFormat->containerSize = pFormat->bitsPerSample; // Always tightly packed for now. pFormat->channelMask = mal_channel_map_to_channel_mask__opensl(pConfig->channelMap, pFormat->numChannels); - pFormat->endianness = SL_BYTEORDER_LITTLEENDIAN; + pFormat->endianness = (mal_is_little_endian()) ? SL_BYTEORDER_LITTLEENDIAN : SL_BYTEORDER_BIGENDIAN; // Android has a few restrictions on the format as documented here: https://developer.android.com/ndk/guides/audio/opensl-for-android.html // - Only mono and stereo is supported. @@ -15591,7 +17317,8 @@ mal_result mal_device_init__opensl(mal_context* pContext, mal_device_type type, // Set the output device. if (pDeviceID != NULL) { - MAL_OPENSL_OUTPUTMIX(pDevice->opensl.pOutputMix)->ReRoute((SLOutputMixItf)pDevice->opensl.pOutputMix, 1, &pDeviceID->opensl); + SLuint32 deviceID_OpenSL = pDeviceID->opensl; + MAL_OPENSL_OUTPUTMIX(pDevice->opensl.pOutputMix)->ReRoute((SLOutputMixItf)pDevice->opensl.pOutputMix, 1, &deviceID_OpenSL); } SLDataSource source; @@ -17449,6 +19176,62 @@ mal_thread_result MAL_THREADCALL mal_worker_thread(void* pData) mal_CoInitializeEx(pDevice->pContext, NULL, 0); // 0 = COINIT_MULTITHREADED #endif +#if 1 + // When the device is being initialized it's initial state is set to MAL_STATE_UNINITIALIZED. Before returning from + // mal_device_init(), the state needs to be set to something valid. In mini_al the device's default state immediately + // after initialization is stopped, so therefore we need to mark the device as such. mini_al will wait on the worker + // thread to signal an event to know when the worker thread is ready for action. + mal_device__set_state(pDevice, MAL_STATE_STOPPED); + mal_event_signal(&pDevice->stopEvent); + + for (;;) { + // We wait on an event to know when something has requested that the device be started and the main loop entered. + mal_event_wait(&pDevice->wakeupEvent); + + // Default result code. + pDevice->workResult = MAL_SUCCESS; + + // If the reason for the wake up is that we are terminating, just break from the loop. + if (mal_device__get_state(pDevice) == MAL_STATE_UNINITIALIZED) { + break; + } + + // Getting to this point means the device is wanting to get started. The function that has requested that the device + // be started will be waiting on an event (pDevice->startEvent) which means we need to make sure we signal the event + // in both the success and error case. It's important that the state of the device is set _before_ signaling the event. + mal_assert(mal_device__get_state(pDevice) == MAL_STATE_STARTING); + + pDevice->workResult = pDevice->pContext->onDeviceStart(pDevice); + if (pDevice->workResult != MAL_SUCCESS) { + mal_device__set_state(pDevice, MAL_STATE_STOPPED); + mal_event_signal(&pDevice->startEvent); + continue; + } + + // At this point the device should be started. + mal_device__set_state(pDevice, MAL_STATE_STARTED); + mal_event_signal(&pDevice->startEvent); + + + // Now we just enter the main loop. When the main loop is terminated the device needs to be marked as stopped. This can + // be broken with mal_device__break_main_loop(). + pDevice->pContext->onDeviceMainLoop(pDevice); + + + // Getting here means we have broken from the main loop which happens the application has requested that device be stopped. + pDevice->pContext->onDeviceStop(pDevice); + + // After the device has stopped, make sure an event is posted. + mal_stop_proc onStop = pDevice->onStop; + if (onStop) { + onStop(pDevice); + } + + // A function somewhere is waiting for the device to have stopped for real so we need to signal an event to allow it to continue. + mal_device__set_state(pDevice, MAL_STATE_STOPPED); + mal_event_signal(&pDevice->stopEvent); + } +#else // This is only used to prevent posting onStop() when the device is first initialized. mal_bool32 skipNextStopEvent = MAL_TRUE; @@ -17500,6 +19283,7 @@ mal_thread_result MAL_THREADCALL mal_worker_thread(void* pData) // Now we just enter the main loop. The main loop can be broken with mal_device__break_main_loop(). pDevice->pContext->onDeviceMainLoop(pDevice); } +#endif // Make sure we aren't continuously waiting on a stop event. mal_event_signal(&pDevice->stopEvent); // <-- Is this still needed? @@ -17668,20 +19452,6 @@ mal_result mal_context_uninit_backend_apis(mal_context* pContext) return result; } -const mal_backend g_malDefaultBackends[] = { - mal_backend_wasapi, - mal_backend_dsound, - mal_backend_winmm, - mal_backend_coreaudio, - mal_backend_oss, - mal_backend_pulseaudio, - mal_backend_alsa, - mal_backend_jack, - mal_backend_opensl, - mal_backend_openal, - mal_backend_sdl, - mal_backend_null -}; mal_bool32 mal_context_is_backend_asynchronous(mal_context* pContext) { @@ -17765,6 +19535,18 @@ mal_result mal_context_init(const mal_backend backends[], mal_uint32 backendCoun result = mal_context_init__coreaudio(pContext); } break; #endif + #ifdef MAL_HAS_SNDIO + case mal_backend_sndio: + { + result = mal_context_init__sndio(pContext); + } break; + #endif + #ifdef MAL_HAS_AUDIOIO + case mal_backend_audioio: + { + result = mal_context_init__audioio(pContext); + } break; + #endif #ifdef MAL_HAS_OSS case mal_backend_oss: { @@ -18387,19 +20169,6 @@ mal_uint32 mal_device_get_buffer_size_in_bytes(mal_device* pDevice) return pDevice->bufferSizeInFrames * pDevice->channels * mal_get_bytes_per_sample(pDevice->format); } -mal_uint32 mal_get_bytes_per_sample(mal_format format) -{ - mal_uint32 sizes[] = { - 0, // unknown - 1, // u8 - 2, // s16 - 3, // s24 - 4, // s32 - 4, // f32 - }; - return sizes[format]; -} - mal_context_config mal_context_config_init(mal_log_proc onLog) { mal_context_config config; @@ -18462,6 +20231,7 @@ mal_device_config mal_device_config_init_ex(mal_format format, mal_uint32 channe return config; } +#endif // MAL_NO_DEVICE_IO void mal_get_standard_channel_map_microsoft(mal_uint32 channels, mal_channel channelMap[MAL_MAX_CHANNELS]) @@ -18820,45 +20590,104 @@ void mal_get_standard_channel_map_vorbis(mal_uint32 channels, mal_channel channe channelMap[4] = MAL_CHANNEL_BACK_RIGHT; } break; - case 6: + case 6: + { + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_CENTER; + channelMap[2] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[3] = MAL_CHANNEL_BACK_LEFT; + channelMap[4] = MAL_CHANNEL_BACK_RIGHT; + channelMap[5] = MAL_CHANNEL_LFE; + } break; + + case 7: + { + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_CENTER; + channelMap[2] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[3] = MAL_CHANNEL_SIDE_LEFT; + channelMap[4] = MAL_CHANNEL_SIDE_RIGHT; + channelMap[5] = MAL_CHANNEL_BACK_CENTER; + channelMap[6] = MAL_CHANNEL_LFE; + } break; + + case 8: + default: + { + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_CENTER; + channelMap[2] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[3] = MAL_CHANNEL_SIDE_LEFT; + channelMap[4] = MAL_CHANNEL_SIDE_RIGHT; + channelMap[5] = MAL_CHANNEL_BACK_LEFT; + channelMap[6] = MAL_CHANNEL_BACK_RIGHT; + channelMap[7] = MAL_CHANNEL_LFE; + } break; + } + + // Remainder. + if (channels > 8) { + for (mal_uint32 iChannel = 8; iChannel < MAL_MAX_CHANNELS; ++iChannel) { + channelMap[iChannel] = (mal_channel)(MAL_CHANNEL_AUX_0 + (iChannel-8)); + } + } +} + +void mal_get_standard_channel_map_sndio(mal_uint32 channels, mal_channel channelMap[MAL_MAX_CHANNELS]) +{ + switch (channels) + { + case 1: + { + channelMap[0] = MAL_CHANNEL_MONO; + } break; + + case 2: + { + channelMap[0] = MAL_CHANNEL_LEFT; + channelMap[1] = MAL_CHANNEL_RIGHT; + } break; + + case 3: + { + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[2] = MAL_CHANNEL_FRONT_CENTER; + } break; + + case 4: { channelMap[0] = MAL_CHANNEL_FRONT_LEFT; - channelMap[1] = MAL_CHANNEL_FRONT_CENTER; - channelMap[2] = MAL_CHANNEL_FRONT_RIGHT; - channelMap[3] = MAL_CHANNEL_BACK_LEFT; - channelMap[4] = MAL_CHANNEL_BACK_RIGHT; - channelMap[5] = MAL_CHANNEL_LFE; + channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[2] = MAL_CHANNEL_BACK_LEFT; + channelMap[3] = MAL_CHANNEL_BACK_RIGHT; } break; - case 7: + case 5: { channelMap[0] = MAL_CHANNEL_FRONT_LEFT; - channelMap[1] = MAL_CHANNEL_FRONT_CENTER; - channelMap[2] = MAL_CHANNEL_FRONT_RIGHT; - channelMap[3] = MAL_CHANNEL_SIDE_LEFT; - channelMap[4] = MAL_CHANNEL_SIDE_RIGHT; - channelMap[5] = MAL_CHANNEL_BACK_CENTER; - channelMap[6] = MAL_CHANNEL_LFE; + channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[2] = MAL_CHANNEL_BACK_LEFT; + channelMap[3] = MAL_CHANNEL_BACK_RIGHT; + channelMap[4] = MAL_CHANNEL_FRONT_CENTER; } break; - case 8: + case 6: default: { channelMap[0] = MAL_CHANNEL_FRONT_LEFT; - channelMap[1] = MAL_CHANNEL_FRONT_CENTER; - channelMap[2] = MAL_CHANNEL_FRONT_RIGHT; - channelMap[3] = MAL_CHANNEL_SIDE_LEFT; - channelMap[4] = MAL_CHANNEL_SIDE_RIGHT; - channelMap[5] = MAL_CHANNEL_BACK_LEFT; - channelMap[6] = MAL_CHANNEL_BACK_RIGHT; - channelMap[7] = MAL_CHANNEL_LFE; + channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[2] = MAL_CHANNEL_BACK_LEFT; + channelMap[3] = MAL_CHANNEL_BACK_RIGHT; + channelMap[4] = MAL_CHANNEL_FRONT_CENTER; + channelMap[5] = MAL_CHANNEL_LFE; } break; } // Remainder. - if (channels > 8) { - for (mal_uint32 iChannel = 8; iChannel < MAL_MAX_CHANNELS; ++iChannel) { - channelMap[iChannel] = (mal_channel)(MAL_CHANNEL_AUX_0 + (iChannel-8)); + if (channels > 6) { + for (mal_uint32 iChannel = 6; iChannel < MAL_MAX_CHANNELS; ++iChannel) { + channelMap[iChannel] = (mal_channel)(MAL_CHANNEL_AUX_0 + (iChannel-6)); } } } @@ -18886,6 +20715,11 @@ void mal_get_standard_channel_map(mal_standard_channel_map standardChannelMap, m { mal_get_standard_channel_map_vorbis(channels, channelMap); } break; + + case mal_standard_channel_map_sndio: + { + mal_get_standard_channel_map_sndio(channels, channelMap); + } break; case mal_standard_channel_map_microsoft: default: @@ -22555,6 +24389,23 @@ mal_result mal_src_set_output_sample_rate(mal_src* pSRC, mal_uint32 sampleRateOu return MAL_SUCCESS; } +mal_result mal_src_set_sample_rate(mal_src* pSRC, mal_uint32 sampleRateIn, mal_uint32 sampleRateOut) +{ + if (pSRC == NULL) { + return MAL_INVALID_ARGS; + } + + // Must have a sample rate of > 0. + if (sampleRateIn == 0 || sampleRateOut == 0) { + return MAL_INVALID_ARGS; + } + + mal_atomic_exchange_32(&pSRC->config.sampleRateIn, sampleRateIn); + mal_atomic_exchange_32(&pSRC->config.sampleRateOut, sampleRateOut); + + return MAL_SUCCESS; +} + mal_uint64 mal_src_read_deinterleaved(mal_src* pSRC, mal_uint64 frameCount, void** ppSamplesOut, void* pUserData) { if (pSRC == NULL || frameCount == 0 || ppSamplesOut == NULL) { @@ -23056,8 +24907,8 @@ mal_uint64 mal_src_read_deinterleaved__sinc(mal_src* pSRC, mal_uint64 frameCount maxInputSamplesAvailableInCache = pSRC->sinc.inputFrameCount; } - // If the last of the input data has been loaded, we need to ensure we don't read into it if it's configured such. - if (pSRC->config.neverConsumeEndOfInput && pSRC->isEndOfInputLoaded) { + // Never consume the tail end of the input data if requested. + if (pSRC->config.neverConsumeEndOfInput) { if (maxInputSamplesAvailableInCache >= pSRC->config.sinc.windowWidth) { maxInputSamplesAvailableInCache -= pSRC->config.sinc.windowWidth; } else { @@ -23249,6 +25100,15 @@ mal_uint64 mal_src_read_deinterleaved__sinc(mal_src* pSRC, mal_uint64 frameCount } // Read more data from the client if required. + if (pSRC->isEndOfInputLoaded) { + pSRC->isEndOfInputLoaded = MAL_FALSE; + break; + } + + // Everything beyond this point is reloading. If we're at the end of the input data we do _not_ want to try reading any more in this function call. If the + // caller wants to keep trying, they can reload their internal data sources and call this function again. We should never be + mal_assert(pSRC->isEndOfInputLoaded == MAL_FALSE); + if (pSRC->sinc.inputFrameCount <= pSRC->config.sinc.windowWidth || availableOutputFrames == 0) { float* ppInputDst[MAL_MAX_CHANNELS] = {0}; for (mal_uint32 iChannel = 0; iChannel < pSRC->config.channels; iChannel += 1) { @@ -23257,20 +25117,29 @@ mal_uint64 mal_src_read_deinterleaved__sinc(mal_src* pSRC, mal_uint64 frameCount // Now read data from the client. mal_uint32 framesToReadFromClient = mal_countof(pSRC->sinc.input[0]) - (pSRC->config.sinc.windowWidth + pSRC->sinc.inputFrameCount); + + mal_uint32 framesReadFromClient = 0; if (framesToReadFromClient > 0) { - mal_uint32 framesReadFromClient = pSRC->config.onReadDeinterleaved(pSRC, framesToReadFromClient, (void**)ppInputDst, pUserData); - if (framesReadFromClient != framesToReadFromClient) { - pSRC->isEndOfInputLoaded = MAL_TRUE; - } else { - pSRC->isEndOfInputLoaded = MAL_FALSE; - } + framesReadFromClient = pSRC->config.onReadDeinterleaved(pSRC, framesToReadFromClient, (void**)ppInputDst, pUserData); + } + + if (framesReadFromClient != framesToReadFromClient) { + pSRC->isEndOfInputLoaded = MAL_TRUE; + } else { + pSRC->isEndOfInputLoaded = MAL_FALSE; + } - if (framesReadFromClient != 0) { - pSRC->sinc.inputFrameCount += framesReadFromClient; + if (framesReadFromClient != 0) { + pSRC->sinc.inputFrameCount += framesReadFromClient; + } else { + // We couldn't get anything more from the client. If no more output samples can be computed from the available input samples + // we need to return. + if (pSRC->config.neverConsumeEndOfInput) { + if ((pSRC->sinc.inputFrameCount * inverseFactor) <= pSRC->config.sinc.windowWidth) { + break; + } } else { - // We couldn't get anything more from the client. If no more output samples can be computed from the available input samples - // we need to return. - if (((pSRC->sinc.timeIn - pSRC->sinc.inputFrameCount) * inverseFactor) < 1) { + if ((pSRC->sinc.inputFrameCount * inverseFactor) < 1) { break; } } @@ -23712,6 +25581,28 @@ mal_result mal_dsp_set_output_sample_rate(mal_dsp* pDSP, mal_uint32 sampleRateOu return mal_dsp_refresh_sample_rate(pDSP); } +mal_result mal_dsp_set_sample_rate(mal_dsp* pDSP, mal_uint32 sampleRateIn, mal_uint32 sampleRateOut) +{ + if (pDSP == NULL) { + return MAL_INVALID_ARGS; + } + + // Must have a sample rate of > 0. + if (sampleRateIn == 0 || sampleRateOut == 0) { + return MAL_INVALID_ARGS; + } + + // Must have been initialized with allowDynamicSampleRate. + if (!pDSP->isDynamicSampleRateAllowed) { + return MAL_INVALID_OPERATION; + } + + mal_atomic_exchange_32(&pDSP->src.config.sampleRateIn, sampleRateIn); + mal_atomic_exchange_32(&pDSP->src.config.sampleRateOut, sampleRateOut); + + return mal_dsp_refresh_sample_rate(pDSP); +} + mal_uint64 mal_dsp_read(mal_dsp* pDSP, mal_uint64 frameCount, void* pFramesOut, void* pUserData) { if (pDSP == NULL || pFramesOut == NULL) return 0; @@ -23970,26 +25861,6 @@ void mal_aligned_free(void* p) mal_free(((void**)p)[-1]); } -const char* mal_get_backend_name(mal_backend backend) -{ - switch (backend) - { - case mal_backend_null: return "Null"; - case mal_backend_wasapi: return "WASAPI"; - case mal_backend_dsound: return "DirectSound"; - case mal_backend_winmm: return "WinMM"; - case mal_backend_alsa: return "ALSA"; - case mal_backend_pulseaudio: return "PulseAudio"; - case mal_backend_jack: return "JACK"; - case mal_backend_coreaudio: return "Core Audio"; - case mal_backend_oss: return "OSS"; - case mal_backend_opensl: return "OpenSL|ES"; - case mal_backend_openal: return "OpenAL"; - case mal_backend_sdl: return "SDL"; - default: return "Unknown"; - } -} - const char* mal_get_format_name(mal_format format) { switch (format) @@ -24012,137 +25883,20 @@ void mal_blend_f32(float* pOut, float* pInA, float* pInB, float factor, mal_uint } -typedef struct -{ - mal_uint8* pInputFrames; - mal_uint32 framesRemaining; -} mal_calculate_cpu_speed_factor_data; - -mal_uint32 mal_calculate_cpu_speed_factor__on_read(mal_dsp* pDSP, mal_uint32 framesToRead, void* pFramesOut, void* pUserData) -{ - mal_calculate_cpu_speed_factor_data* pData = (mal_calculate_cpu_speed_factor_data*)pUserData; - mal_assert(pData != NULL); - - if (framesToRead > pData->framesRemaining) { - framesToRead = pData->framesRemaining; - } - - mal_copy_memory(pFramesOut, pData->pInputFrames, framesToRead*pDSP->formatConverterIn.config.channels * sizeof(*pData->pInputFrames)); - - pData->pInputFrames += framesToRead; - pData->framesRemaining -= framesToRead; - - return framesToRead; -} - -float mal_calculate_cpu_speed_factor() -{ - // Our profiling test is based on how quick it can process 1 second worth of samples through mini_al's data conversion pipeline. - - // This factor is multiplied with the profiling time. May need to fiddle with this to get an accurate value. - double f = 1000; - - // Experiment: Reduce the factor a little when debug mode is used to reduce a blowout. -#if !defined(NDEBUG) || defined(_DEBUG) - f /= 2; -#endif - - mal_uint32 sampleRateIn = 44100; - mal_uint32 sampleRateOut = 48000; - mal_uint32 channelsIn = 2; - mal_uint32 channelsOut = 6; - - // Using the heap here to avoid an unnecessary static memory allocation. Also too big for the stack. - mal_uint8* pInputFrames = NULL; - float* pOutputFrames = NULL; - - size_t inputDataSize = sampleRateIn * channelsIn * sizeof(*pInputFrames); - size_t outputDataSize = sampleRateOut * channelsOut * sizeof(*pOutputFrames); - - void* pData = mal_malloc(inputDataSize + outputDataSize); - if (pData == NULL) { - return 1; - } - - pInputFrames = (mal_uint8*)pData; - pOutputFrames = (float*)(pInputFrames + inputDataSize); - - - - - mal_calculate_cpu_speed_factor_data data; - data.pInputFrames = pInputFrames; - data.framesRemaining = sampleRateIn; - - mal_dsp_config config = mal_dsp_config_init(mal_format_u8, channelsIn, sampleRateIn, mal_format_f32, channelsOut, sampleRateOut, mal_calculate_cpu_speed_factor__on_read, &data); - - // Use linear sample rate conversion because it's the simplest and least likely to cause skewing as a result of tweaks to default - // configurations in the future. - config.srcAlgorithm = mal_src_algorithm_linear; - - // Experiment: Disable SIMD extensions when profiling just to try and keep things a bit more consistent. The idea is to get a general - // indication on the speed of the system, but SIMD is used more heavily in the DSP pipeline than in the general case which may make - // the results a little less realistic. - config.noSSE2 = MAL_TRUE; - config.noAVX2 = MAL_TRUE; - config.noAVX512 = MAL_TRUE; - config.noNEON = MAL_TRUE; - - mal_dsp dsp; - mal_result result = mal_dsp_init(&config, &dsp); - if (result != MAL_SUCCESS) { - mal_free(pData); - return 1; - } - - - int iterationCount = 2; - - mal_timer timer; - mal_timer_init(&timer); - double startTime = mal_timer_get_time_in_seconds(&timer); - { - for (int i = 0; i < iterationCount; ++i) { - mal_dsp_read(&dsp, sampleRateOut, pOutputFrames, &data); - data.pInputFrames = pInputFrames; - data.framesRemaining = sampleRateIn; - } - } - double executionTimeInSeconds = mal_timer_get_time_in_seconds(&timer) - startTime; - executionTimeInSeconds /= iterationCount; - - - mal_free(pData); - - // Guard against extreme blowouts. - return (float)mal_clamp(executionTimeInSeconds * f, 0.1, 100.0); -} - -mal_uint32 mal_scale_buffer_size(mal_uint32 baseBufferSize, float scale) -{ - return mal_max(1, (mal_uint32)(baseBufferSize*scale)); -} - -mal_uint32 mal_calculate_default_buffer_size_in_frames(mal_performance_profile performanceProfile, mal_uint32 sampleRate, float scale) +mal_uint32 mal_get_bytes_per_sample(mal_format format) { - mal_uint32 baseLatency; - if (performanceProfile == mal_performance_profile_low_latency) { - baseLatency = MAL_BASE_BUFFER_SIZE_IN_MILLISECONDS_LOW_LATENCY; - } else { - baseLatency = MAL_BASE_BUFFER_SIZE_IN_MILLISECONDS_CONSERVATIVE; - } - - mal_uint32 sampleRateMS = (sampleRate/1000); - - mal_uint32 minBufferSize = sampleRateMS * mal_min(baseLatency / 5, 1); // <-- Guard against multiply by zero. - mal_uint32 maxBufferSize = sampleRateMS * (baseLatency * 40); - - mal_uint32 bufferSize = mal_scale_buffer_size((sampleRate/1000) * baseLatency, scale); - return mal_clamp(bufferSize, minBufferSize, maxBufferSize); + mal_uint32 sizes[] = { + 0, // unknown + 1, // u8 + 2, // s16 + 3, // s24 + 4, // s32 + 4, // f32 + }; + return sizes[format]; } - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // @@ -24211,6 +25965,11 @@ mal_result mal_decoder__init_dsp(mal_decoder* pDecoder, const mal_decoder_config pDecoder->internalFormat, pDecoder->internalChannels, pDecoder->internalSampleRate, pDecoder->internalChannelMap, pDecoder->outputFormat, pDecoder->outputChannels, pDecoder->outputSampleRate, pDecoder->outputChannelMap, onRead, pDecoder); + dspConfig.channelMixMode = pConfig->channelMixMode; + dspConfig.ditherMode = pConfig->ditherMode; + dspConfig.srcAlgorithm = pConfig->srcAlgorithm; + dspConfig.sinc = pConfig->src.sinc; + return mal_dsp_init(&dspConfig, &pDecoder->dsp); } @@ -25624,6 +27383,21 @@ mal_uint64 mal_sine_wave_read(mal_sine_wave* pSineWave, mal_uint64 count, float* // REVISION HISTORY // ================ // +// v0.8.5-rc - 2018-xx-xx +// - Fix a bug where an incorrect number of samples is returned from sinc resampling. +// +// v0.8.4 - 2018-08-06 +// - Add sndio backend for OpenBSD. +// - Add audioio backend for NetBSD. +// - Drop support for the OSS backend on everything except FreeBSD and DragonFly BSD. +// - Formats are now native-endian (were previously little-endian). +// - Mark some APIs as deprecated: +// - mal_src_set_input_sample_rate() and mal_src_set_output_sample_rate() are replaced with mal_src_set_sample_rate(). +// - mal_dsp_set_input_sample_rate() and mal_dsp_set_output_sample_rate() are replaced with mal_dsp_set_sample_rate(). +// - Fix a bug when capturing using the WASAPI backend. +// - Fix some aliasing issues with resampling, specifically when increasing the sample rate. +// - Fix warnings. +// // v0.8.3 - 2018-07-15 // - Fix a crackling bug when resampling in capture mode. // - Core Audio: Fix a bug where capture does not work. -- cgit v1.2.3