Merge branch 'dev' into dev-0.12

This commit is contained in:
David Reid
2025-02-18 18:03:01 +10:00
32 changed files with 11546 additions and 250 deletions
+131 -55
View File
@@ -12,15 +12,18 @@ GitHub: https://github.com/mackron/miniaudio
/*
1. Introduction
===============
miniaudio is a single file library for audio playback and capture. To use it, do the following in
one .c file:
To use miniaudio, include "miniaudio.h":
```c
#define MINIAUDIO_IMPLEMENTATION
#include "miniaudio.h"
```
You can do `#include "miniaudio.h"` in other parts of the program just like any other header.
The implementation is contained in "miniaudio.c". Just compile this like any other source file. You
can include miniaudio.c if you want to compile your project as a single translation unit:
```c
#include "miniaudio.c"
```
miniaudio includes both low level and high level APIs. The low level API is good for those who want
to do all of their mixing themselves and only require a light weight interface to the underlying
@@ -483,21 +486,12 @@ link the relevant frameworks but should compile cleanly out of the box with Xcod
through the command line requires linking to `-lpthread` and `-lm`.
Due to the way miniaudio links to frameworks at runtime, your application may not pass Apple's
notarization process. To fix this there are two options. The first is to use the
`MA_NO_RUNTIME_LINKING` option, like so:
```c
#ifdef __APPLE__
#define MA_NO_RUNTIME_LINKING
#endif
#define MINIAUDIO_IMPLEMENTATION
#include "miniaudio.h"
```
This will require linking with `-framework CoreFoundation -framework CoreAudio -framework AudioToolbox`.
If you get errors about AudioToolbox, try with `-framework AudioUnit` instead. You may get this when
using older versions of iOS. Alternatively, if you would rather keep using runtime linking you can
add the following to your entitlements.xcent file:
notarization process. To fix this there are two options. The first is to compile with
`-DMA_NO_RUNTIME_LINKING` which in turn will require linking with
`-framework CoreFoundation -framework CoreAudio -framework AudioToolbox`. If you get errors about
AudioToolbox, try with `-framework AudioUnit` instead. You may get this when using older versions
of iOS. Alternatively, if you would rather keep using runtime linking you can add the following to
your entitlements.xcent file:
```
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
@@ -555,7 +549,7 @@ To run locally, you'll need to use emrun:
2.7. Build Options
------------------
`#define` these options before including miniaudio.h.
`#define` these options before including miniaudio.c, or pass them as compiler flags:
+----------------------------------+--------------------------------------------------------------------+
| Option | Description |
@@ -586,6 +580,8 @@ To run locally, you'll need to use emrun:
+----------------------------------+--------------------------------------------------------------------+
| MA_NO_WEBAUDIO | Disables the Web Audio backend. |
+----------------------------------+--------------------------------------------------------------------+
| MA_NO_CUSTOM | Disables support for custom backends. |
+----------------------------------+--------------------------------------------------------------------+
| MA_NO_NULL | Disables the null backend. |
+----------------------------------+--------------------------------------------------------------------+
| MA_ENABLE_ONLY_SPECIFIC_BACKENDS | Disables all backends by default and requires `MA_ENABLE_*` to |
@@ -630,6 +626,9 @@ To run locally, you'll need to use emrun:
| MA_ENABLE_WEBAUDIO | Used in conjunction with MA_ENABLE_ONLY_SPECIFIC_BACKENDS to |
| | enable the Web Audio backend. |
+----------------------------------+--------------------------------------------------------------------+
| MA_ENABLE_CUSTOM | Used in conjunction with MA_ENABLE_ONLY_SPECIFIC_BACKENDS to |
| | enable custom backends. |
+----------------------------------+--------------------------------------------------------------------+
| MA_ENABLE_NULL | Used in conjunction with MA_ENABLE_ONLY_SPECIFIC_BACKENDS to |
| | enable the null backend. |
+----------------------------------+--------------------------------------------------------------------+
@@ -693,11 +692,30 @@ To run locally, you'll need to use emrun:
| | You may need to enable this if your target platform does not allow |
| | runtime linking via `dlopen()`. |
+----------------------------------+--------------------------------------------------------------------+
| MA_USE_STDINT | (Pass this in as a compiler flag. Do not `#define` this before |
| | miniaudio.c) Forces the use of stdint.h for sized types. |
+----------------------------------+--------------------------------------------------------------------+
| MA_DEBUG_OUTPUT | Enable `printf()` output of debug logs (`MA_LOG_LEVEL_DEBUG`). |
+----------------------------------+--------------------------------------------------------------------+
| MA_COINIT_VALUE | Windows only. The value to pass to internal calls to |
| | `CoInitializeEx()`. Defaults to `COINIT_MULTITHREADED`. |
+----------------------------------+--------------------------------------------------------------------+
| MA_FORCE_UWP | Windows only. Affects only the WASAPI backend. Will force the |
| | WASAPI backend to use the UWP code path instead of the regular |
| | desktop path. This is normally auto-detected and should rarely be |
| | needed to be used explicitly, but can be useful for debugging. |
+----------------------------------+--------------------------------------------------------------------+
| MA_ON_THREAD_ENTRY | Defines some code that will be executed as soon as an internal |
| | miniaudio-managed thread is created. This will be the first thing |
| | to be executed by the thread entry point. |
+----------------------------------+--------------------------------------------------------------------+
| MA_ON_THREAD_EXIT | Defines some code that will be executed from the entry point of an |
| | internal miniaudio-managed thread upon exit. This will be the last |
| | thing to be executed before the thread's entry point exits. |
+----------------------------------+--------------------------------------------------------------------+
| MA_THREAD_DEFAULT_STACK_SIZE | If set, specifies the default stack size used by miniaudio-managed |
| | threads. |
+----------------------------------+--------------------------------------------------------------------+
| MA_API | Controls how public APIs should be decorated. Default is `extern`. |
+----------------------------------+--------------------------------------------------------------------+
@@ -9840,7 +9858,7 @@ Utilities
************************************************************************************************************************************************************/
/*
Calculates a buffer size in milliseconds from the specified number of frames and sample rate.
Calculates a buffer size in milliseconds (rounded up) from the specified number of frames and sample rate.
*/
MA_API ma_uint32 ma_calculate_buffer_size_in_milliseconds_from_frames(ma_uint32 bufferSizeInFrames, ma_uint32 sampleRate);
@@ -18020,7 +18038,16 @@ MA_API void ma_dlclose(ma_log* pLog, ma_handle handle)
#ifdef MA_WIN32
FreeLibrary((HMODULE)handle);
#else
dlclose((void*)handle);
/* Hack for Android bug (see https://github.com/android/ndk/issues/360). Calling dlclose() pre-API 28 may segfault. */
#if !defined(MA_ANDROID) || (defined(__ANDROID_API__) && __ANDROID_API__ >= 28)
{
dlclose((void*)handle);
}
#else
{
(void)handle;
}
#endif
#endif
(void)pLog;
@@ -23396,6 +23423,14 @@ static ma_result ma_device_stop__wasapi_nolock(ma_device* pDevice)
}
if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex || pDevice->type == ma_device_type_loopback) {
/* If we have a mapped buffer we need to release it. */
if (pDevice->wasapi.pMappedBufferCapture != NULL) {
ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, pDevice->wasapi.mappedBufferCaptureCap);
pDevice->wasapi.pMappedBufferCapture = NULL;
pDevice->wasapi.mappedBufferCaptureCap = 0;
pDevice->wasapi.mappedBufferCaptureLen = 0;
}
hr = ma_IAudioClient_Stop((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture);
if (FAILED(hr)) {
ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to stop internal capture device.");
@@ -23409,31 +23444,34 @@ static ma_result ma_device_stop__wasapi_nolock(ma_device* pDevice)
return ma_result_from_HRESULT(hr);
}
/* If we have a mapped buffer we need to release it. */
if (pDevice->wasapi.pMappedBufferCapture != NULL) {
ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, pDevice->wasapi.mappedBufferCaptureCap);
pDevice->wasapi.pMappedBufferCapture = NULL;
pDevice->wasapi.mappedBufferCaptureCap = 0;
pDevice->wasapi.mappedBufferCaptureLen = 0;
}
ma_atomic_bool32_set(&pDevice->wasapi.isStartedCapture, MA_FALSE);
}
if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) {
if (pDevice->wasapi.pMappedBufferPlayback != NULL) {
ma_silence_pcm_frames(
ma_offset_pcm_frames_ptr(pDevice->wasapi.pMappedBufferPlayback, pDevice->wasapi.mappedBufferPlaybackLen, pDevice->playback.internalFormat, pDevice->playback.internalChannels),
pDevice->wasapi.mappedBufferPlaybackCap - pDevice->wasapi.mappedBufferPlaybackLen,
pDevice->playback.internalFormat, pDevice->playback.internalChannels
);
ma_IAudioRenderClient_ReleaseBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, pDevice->wasapi.mappedBufferPlaybackCap, 0);
pDevice->wasapi.pMappedBufferPlayback = NULL;
pDevice->wasapi.mappedBufferPlaybackCap = 0;
pDevice->wasapi.mappedBufferPlaybackLen = 0;
}
/*
The buffer needs to be drained before stopping the device. Not doing this will result in the last few frames not getting output to
the speakers. This is a problem for very short sounds because it'll result in a significant portion of it not getting played.
*/
if (ma_atomic_bool32_get(&pDevice->wasapi.isStartedPlayback)) {
/* We need to make sure we put a timeout here or else we'll risk getting stuck in a deadlock in some cases. */
DWORD waitTime = pDevice->wasapi.actualBufferSizeInFramesPlayback / pDevice->playback.internalSampleRate;
DWORD waitTime = (pDevice->wasapi.actualBufferSizeInFramesPlayback * 1000) / pDevice->playback.internalSampleRate;
if (pDevice->playback.shareMode == ma_share_mode_exclusive) {
WaitForSingleObject((HANDLE)pDevice->wasapi.hEventPlayback, waitTime);
}
else {
ma_uint32 prevFramesAvaialablePlayback = (ma_uint32)-1;
} else {
ma_uint32 prevFramesAvailablePlayback = (ma_uint32)-1;
ma_uint32 framesAvailablePlayback;
for (;;) {
result = ma_device__get_available_frames__wasapi(pDevice, (ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, &framesAvailablePlayback);
@@ -23449,13 +23487,13 @@ static ma_result ma_device_stop__wasapi_nolock(ma_device* pDevice)
Just a safety check to avoid an infinite loop. If this iteration results in a situation where the number of available frames
has not changed, get out of the loop. I don't think this should ever happen, but I think it's nice to have just in case.
*/
if (framesAvailablePlayback == prevFramesAvaialablePlayback) {
if (framesAvailablePlayback == prevFramesAvailablePlayback) {
break;
}
prevFramesAvaialablePlayback = framesAvailablePlayback;
prevFramesAvailablePlayback = framesAvailablePlayback;
WaitForSingleObject((HANDLE)pDevice->wasapi.hEventPlayback, waitTime * 1000);
ResetEvent((HANDLE)pDevice->wasapi.hEventPlayback); /* Manual reset. */
WaitForSingleObject((HANDLE)pDevice->wasapi.hEventPlayback, waitTime);
}
}
}
@@ -23467,19 +23505,20 @@ static ma_result ma_device_stop__wasapi_nolock(ma_device* pDevice)
}
/* The audio client needs to be reset otherwise restarting will fail. */
hr = ma_IAudioClient_Reset((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback);
{
ma_int32 retries = 5;
while ((hr = ma_IAudioClient_Reset((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback)) == MA_AUDCLNT_E_BUFFER_OPERATION_PENDING && retries > 0) {
ma_sleep(10);
retries -= 1;
}
}
if (FAILED(hr)) {
ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to reset internal playback device.");
return ma_result_from_HRESULT(hr);
}
if (pDevice->wasapi.pMappedBufferPlayback != NULL) {
ma_IAudioRenderClient_ReleaseBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, pDevice->wasapi.mappedBufferPlaybackCap, 0);
pDevice->wasapi.pMappedBufferPlayback = NULL;
pDevice->wasapi.mappedBufferPlaybackCap = 0;
pDevice->wasapi.mappedBufferPlaybackLen = 0;
}
ma_atomic_bool32_set(&pDevice->wasapi.isStartedPlayback, MA_FALSE);
}
@@ -30438,16 +30477,18 @@ static ma_pa_buffer_attr ma_device__pa_buffer_attr_new(ma_uint32 periodSizeInFra
static ma_pa_stream* ma_device__pa_stream_new__pulse(ma_device* pDevice, const char* pStreamName, const ma_pa_sample_spec* ss, const ma_pa_channel_map* cmap)
{
static int g_StreamCounter = 0;
static ma_atomic_uint32 g_StreamCounter = { 0 };
char actualStreamName[256];
if (pStreamName != NULL) {
ma_strncpy_s(actualStreamName, sizeof(actualStreamName), pStreamName, (size_t)-1);
} else {
ma_strcpy_s(actualStreamName, sizeof(actualStreamName), "miniaudio:");
ma_itoa_s(g_StreamCounter, actualStreamName + 8, sizeof(actualStreamName)-8, 10); /* 8 = strlen("miniaudio:") */
const char* pBaseName = "miniaudio:";
size_t baseNameLen = strlen(pBaseName);
ma_strcpy_s(actualStreamName, sizeof(actualStreamName), pBaseName);
ma_itoa_s((int)ma_atomic_uint32_get(&g_StreamCounter), actualStreamName + baseNameLen, sizeof(actualStreamName)-baseNameLen, 10);
}
g_StreamCounter += 1;
ma_atomic_uint32_fetch_add(&g_StreamCounter, 1);
return ((ma_pa_stream_new_proc)pDevice->pContext->pulse.pa_stream_new)((ma_pa_context*)pDevice->pulse.pPulseContext, actualStreamName, ss, cmap);
}
@@ -37797,7 +37838,9 @@ AAudio Backend
******************************************************************************/
#ifdef MA_HAS_AAUDIO
/*#include <AAudio/AAudio.h>*/
#ifdef MA_NO_RUNTIME_LINKING
#include <AAudio/AAudio.h>
#endif
typedef int32_t ma_aaudio_result_t;
typedef int32_t ma_aaudio_direction_t;
@@ -38741,6 +38784,7 @@ static ma_result ma_context_uninit__aaudio(ma_context* pContext)
static ma_result ma_context_init__aaudio(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks)
{
#if !defined(MA_NO_RUNTIME_LINKING)
size_t i;
const char* libNames[] = {
"libaaudio.so"
@@ -38786,7 +38830,39 @@ static ma_result ma_context_init__aaudio(ma_context* pContext, const ma_context_
pContext->aaudio.AAudioStream_getFramesPerBurst = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->aaudio.hAAudio, "AAudioStream_getFramesPerBurst");
pContext->aaudio.AAudioStream_requestStart = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->aaudio.hAAudio, "AAudioStream_requestStart");
pContext->aaudio.AAudioStream_requestStop = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->aaudio.hAAudio, "AAudioStream_requestStop");
#else
pContext->aaudio.AAudio_createStreamBuilder = (ma_proc)AAudio_createStreamBuilder;
pContext->aaudio.AAudioStreamBuilder_delete = (ma_proc)AAudioStreamBuilder_delete;
pContext->aaudio.AAudioStreamBuilder_setDeviceId = (ma_proc)AAudioStreamBuilder_setDeviceId;
pContext->aaudio.AAudioStreamBuilder_setDirection = (ma_proc)AAudioStreamBuilder_setDirection;
pContext->aaudio.AAudioStreamBuilder_setSharingMode = (ma_proc)AAudioStreamBuilder_setSharingMode;
pContext->aaudio.AAudioStreamBuilder_setFormat = (ma_proc)AAudioStreamBuilder_setFormat;
pContext->aaudio.AAudioStreamBuilder_setChannelCount = (ma_proc)AAudioStreamBuilder_setChannelCount;
pContext->aaudio.AAudioStreamBuilder_setSampleRate = (ma_proc)AAudioStreamBuilder_setSampleRate;
pContext->aaudio.AAudioStreamBuilder_setBufferCapacityInFrames = (ma_proc)AAudioStreamBuilder_setBufferCapacityInFrames;
pContext->aaudio.AAudioStreamBuilder_setFramesPerDataCallback = (ma_proc)AAudioStreamBuilder_setFramesPerDataCallback;
pContext->aaudio.AAudioStreamBuilder_setDataCallback = (ma_proc)AAudioStreamBuilder_setDataCallback;
pContext->aaudio.AAudioStreamBuilder_setErrorCallback = (ma_proc)AAudioStreamBuilder_setErrorCallback;
pContext->aaudio.AAudioStreamBuilder_setPerformanceMode = (ma_proc)AAudioStreamBuilder_setPerformanceMode;
pContext->aaudio.AAudioStreamBuilder_setUsage = (ma_proc)AAudioStreamBuilder_setUsage;
pContext->aaudio.AAudioStreamBuilder_setContentType = (ma_proc)AAudioStreamBuilder_setContentType;
pContext->aaudio.AAudioStreamBuilder_setInputPreset = (ma_proc)AAudioStreamBuilder_setInputPreset;
#if defined(__ANDROID_API__) && __ANDROID_API__ >= 29
pContext->aaudio.AAudioStreamBuilder_setAllowedCapturePolicy = (ma_proc)AAudioStreamBuilder_setAllowedCapturePolicy;
#endif
pContext->aaudio.AAudioStreamBuilder_openStream = (ma_proc)AAudioStreamBuilder_openStream;
pContext->aaudio.AAudioStream_close = (ma_proc)AAudioStream_close;
pContext->aaudio.AAudioStream_getState = (ma_proc)AAudioStream_getState;
pContext->aaudio.AAudioStream_waitForStateChange = (ma_proc)AAudioStream_waitForStateChange;
pContext->aaudio.AAudioStream_getFormat = (ma_proc)AAudioStream_getFormat;
pContext->aaudio.AAudioStream_getChannelCount = (ma_proc)AAudioStream_getChannelCount;
pContext->aaudio.AAudioStream_getSampleRate = (ma_proc)AAudioStream_getSampleRate;
pContext->aaudio.AAudioStream_getBufferCapacityInFrames = (ma_proc)AAudioStream_getBufferCapacityInFrames;
pContext->aaudio.AAudioStream_getFramesPerDataCallback = (ma_proc)AAudioStream_getFramesPerDataCallback;
pContext->aaudio.AAudioStream_getFramesPerBurst = (ma_proc)AAudioStream_getFramesPerBurst;
pContext->aaudio.AAudioStream_requestStart = (ma_proc)AAudioStream_requestStart;
pContext->aaudio.AAudioStream_requestStop = (ma_proc)AAudioStream_requestStop;
#endif
pCallbacks->onContextInit = ma_context_init__aaudio;
pCallbacks->onContextUninit = ma_context_uninit__aaudio;
@@ -43123,7 +43199,7 @@ MA_API ma_uint32 ma_calculate_buffer_size_in_milliseconds_from_frames(ma_uint32
return 0;
}
return bufferSizeInFrames*1000 / sampleRate;
return (bufferSizeInFrames*1000 + (sampleRate - 1)) / sampleRate;
}
MA_API ma_uint32 ma_calculate_buffer_size_in_frames_from_milliseconds(ma_uint32 bufferSizeInMilliseconds, ma_uint32 sampleRate)
@@ -47951,7 +48027,7 @@ static ma_result ma_bpf_get_heap_layout(const ma_bpf_config* pConfig, ma_bpf_hea
return MA_INVALID_ARGS;
}
bpf2Count = pConfig->channels / 2;
bpf2Count = pConfig->order / 2;
pHeapLayout->sizeInBytes = 0;
@@ -56487,7 +56563,7 @@ MA_API const char* ma_channel_position_to_string(ma_channel channel)
case MA_CHANNEL_LFE : return "CHANNEL_LFE";
case MA_CHANNEL_BACK_LEFT : return "CHANNEL_BACK_LEFT";
case MA_CHANNEL_BACK_RIGHT : return "CHANNEL_BACK_RIGHT";
case MA_CHANNEL_FRONT_LEFT_CENTER : return "CHANNEL_FRONT_LEFT_CENTER ";
case MA_CHANNEL_FRONT_LEFT_CENTER : return "CHANNEL_FRONT_LEFT_CENTER";
case MA_CHANNEL_FRONT_RIGHT_CENTER: return "CHANNEL_FRONT_RIGHT_CENTER";
case MA_CHANNEL_BACK_CENTER : return "CHANNEL_BACK_CENTER";
case MA_CHANNEL_SIDE_LEFT : return "CHANNEL_SIDE_LEFT";
@@ -74730,7 +74806,7 @@ static void ma_engine_node_process_pcm_frames__sound(ma_node* pNode, const float
ma_sound_set_at_end(pSound, MA_TRUE); /* This will be set to false in ma_sound_start(). */
}
pRunningFramesOut = ma_offset_pcm_frames_ptr_f32(ppFramesOut[0], totalFramesRead, ma_engine_get_channels(ma_sound_get_engine(pSound)));
pRunningFramesOut = ma_offset_pcm_frames_ptr_f32(ppFramesOut[0], totalFramesRead, ma_node_get_output_channels(pNode, 0));
frameCountIn = (ma_uint32)framesJustRead;
frameCountOut = framesRemaining;