Merge branch 'dev' into dev-0.12

This commit is contained in:
David Reid
2025-02-24 16:35:53 +10:00
12 changed files with 1424 additions and 558 deletions
+2 -2
View File
@@ -1,4 +1,4 @@
Code of Conduct Code of Conduct
=============== ===============
We don't believe we need a document telling fully grown adults how to conduct themselves within an open source I don't believe we need a document telling fully grown adults how to conduct themselves within an open source
community. All we ask is that you just don't be unpleasant and keep everything on topic. community. All I ask is that you just don't be unpleasant and keep everything on topic.
+25 -3
View File
@@ -1,21 +1,43 @@
v0.11.22 - TBD v0.11.22 - 2025-02-24
===================== =====================
* Starting from version 0.12, miniaudio will be switching to a split .c/h pair, away from a single header. In preparation for this, a file named "miniaudio.c" has been added to repository. Currently this is just a simple wrapper around miniaudio.h and `MINIAUDIO_IMPLEMENTATION`. Nothing has changed in miniaudio.h, however when version 0.12 is released you will need to use miniaudio.c for the implementation. It's recommended you start the transition away from `MINIAUDIO_IMPLEMENTATION` and towards miniaudio.c. If you want to keep building your project as a single translation unit, you can do `#include "miniaudio.c"` which will continue to be supported with version 0.12 and beyond. * Starting from version 0.12, miniaudio will be switching to a split .c/h pair, away from a single header. In preparation for this, a file named "miniaudio.c" has been added to repository. Currently this is just a simple wrapper around miniaudio.h and `MINIAUDIO_IMPLEMENTATION`. Nothing has changed in miniaudio.h, however when version 0.12 is released you will need to use miniaudio.c for the implementation. It's recommended you start the transition away from `MINIAUDIO_IMPLEMENTATION` and towards miniaudio.c. If you want to keep building your project as a single translation unit, you can do `#include "miniaudio.c"` which will continue to be supported with version 0.12 and beyond.
* In the extras folder, the `miniaudio_libvorbis.h` and `miniaudio_libopus.h` files have been deprecated. They have been replaced with versions in the `extras/decoders` folder. They are now split into a separate .c and .h files. The old files still exist for compatibility, but you need to transition over to the new versions. The transition should be trivial. Compile the .c files like a normal source file, and include the .h file like a normal header.
* Add `MA_SOUND_FLAG_LOOPING` and `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING` flags. These can be used to initialize sounds and resource managed data sources to loop by default. This is the recommended way to enable looping for streams. The `isLooping` config option in `ma_sound_config` and `ma_resource_manager_data_source_config` has been deprecated. If you are using those, you should switch to the new flag or else you'll get compiler errors when upgrading to a future version. * Add `MA_SOUND_FLAG_LOOPING` and `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING` flags. These can be used to initialize sounds and resource managed data sources to loop by default. This is the recommended way to enable looping for streams. The `isLooping` config option in `ma_sound_config` and `ma_resource_manager_data_source_config` has been deprecated. If you are using those, you should switch to the new flag or else you'll get compiler errors when upgrading to a future version.
* `ma_rb_commit_read()`, `ma_rb_commit_write()`, `ma_pcm_rb_commit_read()` and `ma_pcm_rb_commit_write()` no longer return `MA_AT_END`. The reason for this change is that there's no real notion of an "end" in a ring buffer which makes this result code confusing. In addition, it's possible for these functions to return something other than `MA_SUCCESS` even when the operation completed successfully which adds to the confusion. The correct way to check if there is any more room in the ring buffer is to look at the frame count returned by `*rb_acquire_read/write()`. * `ma_rb_commit_read()`, `ma_rb_commit_write()`, `ma_pcm_rb_commit_read()` and `ma_pcm_rb_commit_write()` no longer return `MA_AT_END`. The reason for this change is that there's no real notion of an "end" in a ring buffer which makes this result code confusing. In addition, it's possible for these functions to return something other than `MA_SUCCESS` even when the operation completed successfully which adds to the confusion. The correct way to check if there is any more room in the ring buffer is to look at the frame count returned by `*rb_acquire_read/write()`.
* The `ma_pcm_rb` data source implementation has been modified to pad output data with silence if there is not enough data in the ring buffer to fill the request. What this means is for an `ma_pcm_rb`, `ma_data_source_read_pcm_frames()` should no longer return a frame count of less than what you requested, and will therefore never return `MA_AT_END` which does not make sense for a ring buffer since it does not have the notion of an end. This change should make it much easier to use a ring buffer as the data source for a `ma_sound`. * The `ma_pcm_rb` data source implementation has been modified to pad output data with silence if there is not enough data in the ring buffer to fill the request. What this means is for an `ma_pcm_rb`, `ma_data_source_read_pcm_frames()` should no longer return a frame count of less than what you requested, and will therefore never return `MA_AT_END` which does not make sense for a ring buffer since it does not have the notion of an end. This change should make it much easier to use a ring buffer as the data source for a `ma_sound`.
* There has been a minor change to `ma_calculate_buffer_size_in_milliseconds_from_frames()` to have it return a value rounded up to the nearest integer.
* When initialization of a decoder fails, it will now return the first error code encountered rather than always returning `MA_NO_BACKEND` regardless of the error.
* Add `ma_device_id_equal()` for comparing device IDs.
* Add support for `MA_NO_RUNTIME_LINKING` to the AAudio backend.
* Fix a buffer overrun bug with `ma_sound` processing.
* Fix a bug relating to node detachment. * Fix a bug relating to node detachment.
* Fix a bug where amplification with `ma_device_set_master_volume()` does not work. * Fix a bug where amplification with `ma_device_set_master_volume()` does not work.
* Fix a bug where sounds loaded with `MA_SOUND_FLAG_DECODE` do not loop. * Fix a bug where sounds loaded with `MA_SOUND_FLAG_DECODE` do not loop.
* ALSA: Fix some warnings relating to unhandled return value of `read()`. * Fix a bug with initialization of the band pass biquad filter.
* Fix a bug where a device would output distorted audio if initialized without a data callback.
* Fix various compiler and static analysis warnings.
* Various documentation updates.
* WASAPI: Fix an error when stopping the device. The "Failed to reset internal playback device." error should be significantly reduced in frequency.
* WASAPI: Fix an error when stopping the device where it was possible miniaudio would not wait for the device to be drained due to an error with the wait time calculation.
* WASAPI: Fix a COM related crash with device rerouting.
* DirectSound: Add support for specifying an explicit window handle for SetCooperativeLevel(). * DirectSound: Add support for specifying an explicit window handle for SetCooperativeLevel().
* ALSA: Fix a bug where a playback device can fail to start.
* ALSA: Fix some warnings relating to unhandled return value of `read()`.
* Web: Fix ScriptProcessorNode path when compiling with `--closure=1`. Note that the Audio Worklets path is not currently working due to the callback specified in `emscripten_create_wasm_audio_worklet_processor_async` never getting fired. * Web: Fix ScriptProcessorNode path when compiling with `--closure=1`. Note that the Audio Worklets path is not currently working due to the callback specified in `emscripten_create_wasm_audio_worklet_processor_async` never getting fired.
* Web: Fix an error with the unlocked notification when compiling as C++. * Web: Fix an error with the unlocked notification when compiling as C++.
* Web: Fix a JavaScript error when initializing and then uninitializing a context before any interactivity. * Web: Fix a JavaScript error when initializing and then uninitializing a context before any interactivity.
* Web: miniaudio will now enable threading when the `-pthread` command line flag is used.
* Web: Infrastructure has been added to support configurable buffer sizes. In practice this is still restricted to 128 frames, but once Emscripten adds full support for configuration of the buffer size, it will be trivial to add support to miniaudio.
* AAudio: Fix some crashes relating to stream rerouting.
* AAudio: Fix an error where the device is silenced after rerouting. With this change, miniaudio will no longer give AAudio a hint to use your supplied period size which will therefore result in AAudio using its default latency configuration. If you want AAudio to try to use the period size you supply in the device config, which is the old behaviour, set `aaudio.allowSetBufferCapacity` to true in the device config. Note, however, if you do this you may end up with errors when rerouting between devices. * AAudio: Fix an error where the device is silenced after rerouting. With this change, miniaudio will no longer give AAudio a hint to use your supplied period size which will therefore result in AAudio using its default latency configuration. If you want AAudio to try to use the period size you supply in the device config, which is the old behaviour, set `aaudio.allowSetBufferCapacity` to true in the device config. Note, however, if you do this you may end up with errors when rerouting between devices.
* AAudio: The default minimum SDK version has been increased from 26 to 27 when enabling AAudio. If you need to support version 26, you can use `#define MA_AAUDIO_MIN_ANDROID_SDK_VERSION 26`. * AAudio: The default minimum SDK version has been increased from 26 to 27 when enabling AAudio. If you need to support version 26, you can use `#define MA_AAUDIO_MIN_ANDROID_SDK_VERSION 26`.
* AAudio: Fix ma_device_get_info() implementation. * AAudio: Fix ma_device_get_info() implementation.
* PulseAudio: Allow setting the channel map requested from PulseAudio in device configs. * AAudio: Fix an error when an assertion can trigger due to AAudio reporting a frame count of 0.
* PulseAudio: Add a configuration option to control the PulseAudio-defined channel map to use.
* PulseAudio: Fix an extremely unlikely race condition with device initialization.
* PulseAudio: Fix a bug with the miniaudio-generated stream name. Previously this would create names like "miniaudi0" when it was supposed to be "miniaudio:0".
* iOS: Fix an error when trying to capture audio from the simulator with iOS version 16 and newer.
* sndio: Fix a crash with device uninitialization.
v0.11.21 - 2023-11-15 v0.11.21 - 2023-11-15
+3 -2
View File
@@ -206,8 +206,9 @@ Backends
Security Security
======== ========
I deal with all security related issues publicly and transparently, and it can sometimes take a while before I I deal with all security related issues publicly and transparently, and it can sometimes take a while before I
get a chance to address it. If this is an issue for you, you need to use another library. Please post any get a chance to address it. If this is an issue for you, you need to use another library. The fastest way to get
security related bugs on the public GitHub issue tracker. a bug fixed is to submit a pull request, but if this is unpractical for you please post a ticket to the public
GitHub issue tracker.
License License
+1 -6
View File
@@ -24,14 +24,9 @@ the decoder via the decoder config (`ma_decoder_config`). You need to implement
of your custom decoders. See `ma_decoding_backend_vtable` for the functions you need to implement. of your custom decoders. See `ma_decoding_backend_vtable` for the functions you need to implement.
The `onInitFile`, `onInitFileW` and `onInitMemory` functions are optional. The `onInitFile`, `onInitFileW` and `onInitMemory` functions are optional.
*/ */
#include "../miniaudio.c"
/*
For now these need to be declared before miniaudio.c due to some compatibility issues with the old
MINIAUDIO_IMPLEMENTATION system. This will change from version 0.12.
*/
#include "../extras/decoders/libvorbis/miniaudio_libvorbis.c" #include "../extras/decoders/libvorbis/miniaudio_libvorbis.c"
#include "../extras/decoders/libopus/miniaudio_libopus.c" #include "../extras/decoders/libopus/miniaudio_libopus.c"
#include "../miniaudio.c"
#include <stdio.h> #include <stdio.h>
+1 -6
View File
@@ -5,14 +5,9 @@ This is the same as the custom_decoder example, only it's used with the high lev
rather than the low level decoding API. You can use this to add support for Opus to your games, for rather than the low level decoding API. You can use this to add support for Opus to your games, for
example (via libopus). example (via libopus).
*/ */
#include "../miniaudio.c"
/*
For now these need to be declared before miniaudio.c due to some compatibility issues with the old
MINIAUDIO_IMPLEMENTATION system. This will change from version 0.12.
*/
#include "../extras/decoders/libvorbis/miniaudio_libvorbis.c" #include "../extras/decoders/libvorbis/miniaudio_libvorbis.c"
#include "../extras/decoders/libopus/miniaudio_libopus.c" #include "../extras/decoders/libopus/miniaudio_libopus.c"
#include "../miniaudio.c"
#include <stdio.h> #include <stdio.h>
+12 -1
View File
@@ -40,6 +40,10 @@ static ma_result ma_data_source_read_pcm_frames_f32_ex(ma_data_source* pDataSour
ma_uint64 totalFramesRead; ma_uint64 totalFramesRead;
ma_uint8 temp[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; ma_uint8 temp[MA_DATA_CONVERTER_STACK_BUFFER_SIZE];
ma_uint64 tempCapInFrames = sizeof(temp) / ma_get_bytes_per_frame(dataSourceFormat, dataSourceChannels); ma_uint64 tempCapInFrames = sizeof(temp) / ma_get_bytes_per_frame(dataSourceFormat, dataSourceChannels);
if (pFramesRead != NULL) {
*pFramesRead = 0;
}
totalFramesRead = 0; totalFramesRead = 0;
while (totalFramesRead < frameCount) { while (totalFramesRead < frameCount) {
@@ -49,7 +53,10 @@ static ma_result ma_data_source_read_pcm_frames_f32_ex(ma_data_source* pDataSour
framesToRead = tempCapInFrames; framesToRead = tempCapInFrames;
} }
result = ma_data_source_read_pcm_frames(pDataSource, pFramesOut, framesToRead, &framesJustRead); result = ma_data_source_read_pcm_frames(pDataSource, temp, framesToRead, &framesJustRead);
if (result != MA_SUCCESS) {
break;
}
ma_convert_pcm_frames_format(ma_offset_pcm_frames_ptr_f32(pFramesOut, totalFramesRead, dataSourceChannels), ma_format_f32, temp, dataSourceFormat, framesJustRead, dataSourceChannels, ma_dither_mode_none); ma_convert_pcm_frames_format(ma_offset_pcm_frames_ptr_f32(pFramesOut, totalFramesRead, dataSourceChannels), ma_format_f32, temp, dataSourceFormat, framesJustRead, dataSourceChannels, ma_dither_mode_none);
totalFramesRead += framesJustRead; totalFramesRead += framesJustRead;
@@ -59,6 +66,10 @@ static ma_result ma_data_source_read_pcm_frames_f32_ex(ma_data_source* pDataSour
} }
} }
if (pFramesRead != NULL) {
*pFramesRead = totalFramesRead;
}
return MA_SUCCESS; return MA_SUCCESS;
} }
} }
+112 -81
View File
@@ -646,6 +646,10 @@ FS_API fs_result fs_stream_read(fs_stream* pStream, void* pDst, size_t bytesToRe
size_t bytesRead; size_t bytesRead;
fs_result result; fs_result result;
if (pBytesRead != NULL) {
*pBytesRead = 0;
}
if (pStream == NULL) { if (pStream == NULL) {
return FS_INVALID_ARGS; return FS_INVALID_ARGS;
} }
@@ -669,6 +673,10 @@ FS_API fs_result fs_stream_write(fs_stream* pStream, const void* pSrc, size_t by
size_t bytesWritten; size_t bytesWritten;
fs_result result; fs_result result;
if (pBytesWritten != NULL) {
*pBytesWritten = 0;
}
if (pStream == NULL) { if (pStream == NULL) {
return FS_INVALID_ARGS; return FS_INVALID_ARGS;
} }
@@ -2105,8 +2113,15 @@ FS_API fs_result fs_mkdir(fs* pFS, const char* pPath)
char* pRunningPath = pRunningPathStack; char* pRunningPath = pRunningPathStack;
size_t runningPathLen = 0; size_t runningPathLen = 0;
fs_path_iterator iSegment; fs_path_iterator iSegment;
const fs_backend* pBackend;
if (pFS == NULL || pPath == NULL) { pBackend = fs_get_backend_or_default(pFS);
if (pBackend == NULL) {
return FS_INVALID_ARGS;
}
if (pPath == NULL) {
return FS_INVALID_ARGS; return FS_INVALID_ARGS;
} }
@@ -2144,7 +2159,7 @@ FS_API fs_result fs_mkdir(fs* pFS, const char* pPath)
runningPathLen += iSegment.segmentLength; runningPathLen += iSegment.segmentLength;
pRunningPath[runningPathLen] = '\0'; pRunningPath[runningPathLen] = '\0';
result = fs_backend_mkdir(pFS->pBackend, pFS, pRunningPath); result = fs_backend_mkdir(pBackend, pFS, pRunningPath);
/* We just pretend to be successful if the directory already exists. */ /* We just pretend to be successful if the directory already exists. */
if (result == FS_ALREADY_EXISTS) { if (result == FS_ALREADY_EXISTS) {
@@ -3040,94 +3055,110 @@ FS_API fs_result fs_file_open_or_info(fs* pFS, const char* pFilePath, int openMo
We'll need to iterate over every mount point and keep track of the mount point with the longest We'll need to iterate over every mount point and keep track of the mount point with the longest
prefix that matches the start of the file path. prefix that matches the start of the file path.
*/ */
fs_mount_list_iterator iMountPoint; if (pFS != NULL) {
fs_mount_point* pBestMountPoint = NULL; fs_mount_list_iterator iMountPoint;
const char* pBestMountPointPath = NULL; fs_mount_point* pBestMountPoint = NULL;
const char* pBestMountPointFileSubPath = NULL; const char* pBestMountPointPath = NULL;
const char* pBestMountPointFileSubPath = NULL;
for (mountPointIerationResult = fs_mount_list_first(pFS->pWriteMountPoints, &iMountPoint); mountPointIerationResult == FS_SUCCESS; mountPointIerationResult = fs_mount_list_next(&iMountPoint)) {
const char* pFileSubPath = fs_path_trim_base(pFilePath, FS_NULL_TERMINATED, iMountPoint.pMountPointPath, FS_NULL_TERMINATED); for (mountPointIerationResult = fs_mount_list_first(pFS->pWriteMountPoints, &iMountPoint); mountPointIerationResult == FS_SUCCESS; mountPointIerationResult = fs_mount_list_next(&iMountPoint)) {
if (pFileSubPath == NULL) { const char* pFileSubPath = fs_path_trim_base(pFilePath, FS_NULL_TERMINATED, iMountPoint.pMountPointPath, FS_NULL_TERMINATED);
continue; /* The file path doesn't start with this mount point so skip. */ if (pFileSubPath == NULL) {
} continue; /* The file path doesn't start with this mount point so skip. */
if (pBestMountPointFileSubPath == NULL || strlen(pFileSubPath) < strlen(pBestMountPointFileSubPath)) {
pBestMountPoint = iMountPoint.internal.pMountPoint;
pBestMountPointPath = iMountPoint.pPath;
pBestMountPointFileSubPath = pFileSubPath;
}
}
if (pBestMountPoint != NULL) {
char pActualPathStack[1024];
char* pActualPathHeap = NULL;
char* pActualPath = pActualPathStack;
int actualPathLen;
char pActualPathCleanStack[1024];
char* pActualPathCleanHeap = NULL;
char* pActualPathClean = pActualPathCleanStack;
int actualPathCleanLen;
unsigned int cleanOptions = (openMode & FS_NO_ABOVE_ROOT_NAVIGATION);
/* If the mount point starts with a root segment, i.e. "/", we cannot allow navigation above that. */
if (pBestMountPointPath[0] == '/' || pBestMountPointPath[0] == '\\') {
cleanOptions |= FS_NO_ABOVE_ROOT_NAVIGATION;
}
/* Here is where we append the cleaned sub-path to the mount points actual path. */
actualPathLen = fs_path_append(pActualPathStack, sizeof(pActualPathStack), pBestMountPointPath, pBestMountPoint->pathLen, pBestMountPointFileSubPath, FS_NULL_TERMINATED);
if (actualPathLen > 0 && (size_t)actualPathLen >= sizeof(pActualPathStack)) {
/* Not enough room on the stack. Allocate on the heap. */
pActualPathHeap = (char*)fs_malloc(actualPathLen + 1, fs_get_allocation_callbacks(pFS));
if (pActualPathHeap == NULL) {
return FS_OUT_OF_MEMORY;
} }
fs_path_append(pActualPathHeap, actualPathLen + 1, pBestMountPointPath, pBestMountPoint->pathLen, pBestMountPointFileSubPath, FS_NULL_TERMINATED); /* <-- This should never fail. */ if (pBestMountPointFileSubPath == NULL || strlen(pFileSubPath) < strlen(pBestMountPointFileSubPath)) {
pActualPath = pActualPathHeap; pBestMountPoint = iMountPoint.internal.pMountPoint;
} else { pBestMountPointPath = iMountPoint.pPath;
pActualPath = pActualPathStack; pBestMountPointFileSubPath = pFileSubPath;
}
} }
if (pBestMountPoint != NULL) {
char pActualPathStack[1024];
char* pActualPathHeap = NULL;
char* pActualPath;
int actualPathLen;
char pActualPathCleanStack[1024];
char* pActualPathCleanHeap = NULL;
char* pActualPathClean;
int actualPathCleanLen;
unsigned int cleanOptions = (openMode & FS_NO_ABOVE_ROOT_NAVIGATION);
/* Now we need to clean the path. */ /* If the mount point starts with a root segment, i.e. "/", we cannot allow navigation above that. */
actualPathCleanLen = fs_path_normalize(pActualPathCleanStack, sizeof(pActualPathCleanStack), pActualPath, FS_NULL_TERMINATED, cleanOptions); if (pBestMountPointPath[0] == '/' || pBestMountPointPath[0] == '\\') {
if (actualPathCleanLen < 0) { cleanOptions |= FS_NO_ABOVE_ROOT_NAVIGATION;
fs_free(pActualPathHeap, fs_get_allocation_callbacks(pFS)); }
return FS_INVALID_OPERATION; /* Most likely violating FS_NO_ABOVE_ROOT_NAVIGATION. */
}
if (actualPathCleanLen >= (int)sizeof(pActualPathCleanStack)) {
pActualPathCleanHeap = (char*)fs_malloc(actualPathCleanLen + 1, fs_get_allocation_callbacks(pFS)); /* Here is where we append the cleaned sub-path to the mount points actual path. */
if (pActualPathCleanHeap == NULL) { actualPathLen = fs_path_append(pActualPathStack, sizeof(pActualPathStack), pBestMountPointPath, pBestMountPoint->pathLen, pBestMountPointFileSubPath, FS_NULL_TERMINATED);
if (actualPathLen > 0 && (size_t)actualPathLen >= sizeof(pActualPathStack)) {
/* Not enough room on the stack. Allocate on the heap. */
pActualPathHeap = (char*)fs_malloc(actualPathLen + 1, fs_get_allocation_callbacks(pFS));
if (pActualPathHeap == NULL) {
return FS_OUT_OF_MEMORY;
}
fs_path_append(pActualPathHeap, actualPathLen + 1, pBestMountPointPath, pBestMountPoint->pathLen, pBestMountPointFileSubPath, FS_NULL_TERMINATED); /* <-- This should never fail. */
pActualPath = pActualPathHeap;
} else {
pActualPath = pActualPathStack;
}
/* Now we need to clean the path. */
actualPathCleanLen = fs_path_normalize(pActualPathCleanStack, sizeof(pActualPathCleanStack), pActualPath, FS_NULL_TERMINATED, cleanOptions);
if (actualPathCleanLen < 0) {
fs_free(pActualPathHeap, fs_get_allocation_callbacks(pFS)); fs_free(pActualPathHeap, fs_get_allocation_callbacks(pFS));
return FS_OUT_OF_MEMORY; return FS_INVALID_OPERATION; /* Most likely violating FS_NO_ABOVE_ROOT_NAVIGATION. */
} }
fs_path_normalize(pActualPathCleanHeap, actualPathCleanLen + 1, pActualPath, FS_NULL_TERMINATED, cleanOptions); /* <-- This should never fail. */ if (actualPathCleanLen >= (int)sizeof(pActualPathCleanStack)) {
pActualPathClean = pActualPathCleanHeap; pActualPathCleanHeap = (char*)fs_malloc(actualPathCleanLen + 1, fs_get_allocation_callbacks(pFS));
if (pActualPathCleanHeap == NULL) {
fs_free(pActualPathHeap, fs_get_allocation_callbacks(pFS));
return FS_OUT_OF_MEMORY;
}
fs_path_normalize(pActualPathCleanHeap, actualPathCleanLen + 1, pActualPath, FS_NULL_TERMINATED, cleanOptions); /* <-- This should never fail. */
pActualPathClean = pActualPathCleanHeap;
} else {
pActualPathClean = pActualPathCleanStack;
}
fs_free(pActualPathHeap, fs_get_allocation_callbacks(pFS));
pActualPathHeap = NULL;
/* We now have enough information to open the file. */
result = fs_file_alloc_if_necessary_and_open_or_info(pFS, pActualPathClean, openMode, ppFile, pInfo);
fs_free(pActualPathCleanHeap, fs_get_allocation_callbacks(pFS));
pActualPathCleanHeap = NULL;
if (result == FS_SUCCESS) {
return FS_SUCCESS;
} else {
return FS_DOES_NOT_EXIST; /* Couldn't find the file from the best mount point. */
}
} else { } else {
pActualPathClean = pActualPathCleanStack; return FS_DOES_NOT_EXIST; /* Couldn't find an appropriate mount point. */
}
fs_free(pActualPathHeap, fs_get_allocation_callbacks(pFS));
pActualPathHeap = NULL;
/* We now have enough information to open the file. */
result = fs_file_alloc_if_necessary_and_open_or_info(pFS, pActualPathClean, openMode, ppFile, pInfo);
fs_free(pActualPathCleanHeap, fs_get_allocation_callbacks(pFS));
pActualPathCleanHeap = NULL;
if (result == FS_SUCCESS) {
return FS_SUCCESS;
} else {
return FS_DOES_NOT_EXIST; /* Couldn't find the file from the best mount point. */
} }
} else { } else {
return FS_DOES_NOT_EXIST; /* Couldn't find an appropriate mount point. */ /*
No "fs" object was supplied. Open using the default backend without using mount points. This is as if you were
opening a file using `fopen()`.
*/
if ((openMode & FS_ONLY_MOUNTS) == 0) {
return fs_file_alloc_if_necessary_and_open_or_info(pFS, pFilePath, openMode, ppFile, pInfo);
} else {
/*
Getting here means only the mount points can be used to open the file (cannot open straight from
the file system natively).
*/
return FS_DOES_NOT_EXIST;
}
} }
} else { } else {
/* Opening in read mode. */ /* Opening in read mode. */
@@ -3201,7 +3232,7 @@ FS_API fs_result fs_file_open_or_info(fs* pFS, const char* pFilePath, int openMo
/* The mount point is a directory. We need to combine the sub-path with the mount point's original path and then load the file. */ /* The mount point is a directory. We need to combine the sub-path with the mount point's original path and then load the file. */
char pActualPathStack[1024]; char pActualPathStack[1024];
char* pActualPathHeap = NULL; char* pActualPathHeap = NULL;
char* pActualPath = pActualPathStack; char* pActualPath;
int actualPathLen; int actualPathLen;
actualPathLen = fs_path_append(pActualPathStack, sizeof(pActualPathStack), iMountPoint.pPath, FS_NULL_TERMINATED, pFileSubPathClean, fileSubPathCleanLen); actualPathLen = fs_path_append(pActualPathStack, sizeof(pActualPathStack), iMountPoint.pPath, FS_NULL_TERMINATED, pFileSubPathClean, fileSubPathCleanLen);
@@ -4129,7 +4160,7 @@ FS_API fs_result fs_unmount(fs* pFS, const char* pPathToMount_NotMountPoint)
return FS_INVALID_ARGS; return FS_INVALID_ARGS;
} }
for (iteratorResult = fs_mount_list_first(pFS->pReadMountPoints, &iterator); iteratorResult == FS_SUCCESS; iteratorResult = fs_mount_list_next(&iterator)) { for (iteratorResult = fs_mount_list_first(pFS->pReadMountPoints, &iterator); iteratorResult == FS_SUCCESS; /*iteratorResult = fs_mount_list_next(&iterator)*/) {
if (strcmp(pPathToMount_NotMountPoint, iterator.pPath) == 0) { if (strcmp(pPathToMount_NotMountPoint, iterator.pPath) == 0) {
if (iterator.internal.pMountPoint->closeArchiveOnUnmount) { if (iterator.internal.pMountPoint->closeArchiveOnUnmount) {
fs_close_archive(iterator.pArchive); fs_close_archive(iterator.pArchive);
@@ -4258,7 +4289,7 @@ FS_API fs_result fs_unmount_write(fs* pFS, const char* pPathToMount_NotMountPoin
return FS_INVALID_ARGS; return FS_INVALID_ARGS;
} }
for (iteratorResult = fs_mount_list_first(pFS->pWriteMountPoints, &iterator); iteratorResult == FS_SUCCESS; iteratorResult = fs_mount_list_next(&iterator)) { for (iteratorResult = fs_mount_list_first(pFS->pWriteMountPoints, &iterator); iteratorResult == FS_SUCCESS; /*iteratorResult = fs_mount_list_next(&iterator)*/) {
if (strcmp(pPathToMount_NotMountPoint, iterator.pPath) == 0) { if (strcmp(pPathToMount_NotMountPoint, iterator.pPath) == 0) {
fs_mount_list_remove(pFS->pWriteMountPoints, iterator.internal.pMountPoint); fs_mount_list_remove(pFS->pWriteMountPoints, iterator.internal.pMountPoint);
+6 -1
View File
@@ -267,9 +267,14 @@ be saved:
```c ```c
fs_file_open(pFS, "config/game.cfg", FS_WRITE, &pFile); // Prefixed with "config", so will use the "config" mount point. fs_file_open(pFS, "config/game.cfg", FS_WRITE, &pFile); // Prefixed with "config", so will use the "config" mount point.
fs_file_open(pFs, "saves/save0.sav", FS_WRITE, &pFile); // Prefixed with "saves", so will use the "saves" mount point. fs_file_open(pFS, "saves/save0.sav", FS_WRITE, &pFile); // Prefixed with "saves", so will use the "saves" mount point.
``` ```
When opening a file for writing, if you pass in NULL for the `pFS` parameter it will open the file
like normal using the standard file system. That is it'll work exactly as if you were using stdio
`fopen()` and you will not be able use mount points. Keep in mind that there is no notion of a
"current directory" in this library so you'll be stuck with the initial working directory.
By default, you can move outside the mount point with ".." segments. If you want to disable this By default, you can move outside the mount point with ".." segments. If you want to disable this
functionality, you can use the `FS_NO_ABOVE_ROOT_NAVIGATION` flag: functionality, you can use the `FS_NO_ABOVE_ROOT_NAVIGATION` flag:
File diff suppressed because it is too large Load Diff
+83 -44
View File
@@ -1,6 +1,6 @@
/* /*
Audio playback and capture library. Choice of public domain or MIT-0. See license statements at the end of this file. Audio playback and capture library. Choice of public domain or MIT-0. See license statements at the end of this file.
miniaudio - v0.11.22 - TBD miniaudio - v0.11.22 - 2025-02-24
David Reid - mackron@gmail.com David Reid - mackron@gmail.com
@@ -37,8 +37,7 @@ extern "C" {
#endif #endif
#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined(_M_IA64) || defined(__aarch64__) || defined(_M_ARM64) || defined(__powerpc64__) || defined(__ppc64__)
#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined(_M_IA64) || defined(__aarch64__) || defined(_M_ARM64) || defined(__powerpc64__)
#define MA_SIZEOF_PTR 8 #define MA_SIZEOF_PTR 8
#else #else
#define MA_SIZEOF_PTR 4 #define MA_SIZEOF_PTR 4
@@ -102,7 +101,7 @@ typedef void* ma_handle;
typedef void* ma_ptr; typedef void* ma_ptr;
/* /*
ma_proc is annoying because when compiling with GCC we get pendantic warnings about converting ma_proc is annoying because when compiling with GCC we get pedantic warnings about converting
between `void*` and `void (*)()`. We can't use `void (*)()` with MSVC however, because we'll get between `void*` and `void (*)()`. We can't use `void (*)()` with MSVC however, because we'll get
warning C4191 about "type cast between incompatible function types". To work around this I'm going warning C4191 about "type cast between incompatible function types". To work around this I'm going
to use a different data type depending on the compiler. to use a different data type depending on the compiler.
@@ -296,7 +295,7 @@ Special wchar_t type to ensure any structures in the public sections that refere
consistent size across all platforms. consistent size across all platforms.
On Windows, wchar_t is 2 bytes, whereas everywhere else it's 4 bytes. Since Windows likes to use On Windows, wchar_t is 2 bytes, whereas everywhere else it's 4 bytes. Since Windows likes to use
wchar_t for it's IDs, we need a special explicitly sized wchar type that is always 2 bytes on all wchar_t for its IDs, we need a special explicitly sized wchar type that is always 2 bytes on all
platforms. platforms.
*/ */
#if !defined(MA_POSIX) && defined(MA_WIN32) #if !defined(MA_POSIX) && defined(MA_WIN32)
@@ -322,7 +321,7 @@ MA_LOG_LEVEL_INFO
callback. callback.
MA_LOG_LEVEL_WARNING MA_LOG_LEVEL_WARNING
Warnings. You should enable this in you development builds and action them when encounted. These Warnings. You should enable this in you development builds and action them when encountered. These
logs usually indicate a potential problem or misconfiguration, but still allow you to keep logs usually indicate a potential problem or misconfiguration, but still allow you to keep
running. This will never be called from within the data callback. running. This will never be called from within the data callback.
@@ -1754,7 +1753,7 @@ input frames.
MA_API ma_result ma_resampler_get_expected_output_frame_count(const ma_resampler* pResampler, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount); MA_API ma_result ma_resampler_get_expected_output_frame_count(const ma_resampler* pResampler, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount);
/* /*
Resets the resampler's timer and clears it's internal cache. Resets the resampler's timer and clears its internal cache.
*/ */
MA_API ma_result ma_resampler_reset(ma_resampler* pResampler); MA_API ma_result ma_resampler_reset(ma_resampler* pResampler);
@@ -1975,7 +1974,7 @@ MA_API void ma_channel_map_init_standard(ma_standard_channel_map standardChannel
/* /*
Copies a channel map. Copies a channel map.
Both input and output channel map buffers must have a capacity of at at least `channels`. Both input and output channel map buffers must have a capacity of at least `channels`.
*/ */
MA_API void ma_channel_map_copy(ma_channel* pOut, const ma_channel* pIn, ma_uint32 channels); MA_API void ma_channel_map_copy(ma_channel* pOut, const ma_channel* pIn, ma_uint32 channels);
@@ -2114,6 +2113,8 @@ MA_API void ma_data_source_uninit(ma_data_source* pDataSource);
MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); /* Must support pFramesOut = NULL in which case a forward seek should be performed. */ MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); /* Must support pFramesOut = NULL in which case a forward seek should be performed. */
MA_API ma_result ma_data_source_seek_pcm_frames(ma_data_source* pDataSource, ma_uint64 frameCount, ma_uint64* pFramesSeeked); /* Can only seek forward. Equivalent to ma_data_source_read_pcm_frames(pDataSource, NULL, frameCount, &framesRead); */ MA_API ma_result ma_data_source_seek_pcm_frames(ma_data_source* pDataSource, ma_uint64 frameCount, ma_uint64* pFramesSeeked); /* Can only seek forward. Equivalent to ma_data_source_read_pcm_frames(pDataSource, NULL, frameCount, &framesRead); */
MA_API ma_result ma_data_source_seek_to_pcm_frame(ma_data_source* pDataSource, ma_uint64 frameIndex); MA_API ma_result ma_data_source_seek_to_pcm_frame(ma_data_source* pDataSource, ma_uint64 frameIndex);
MA_API ma_result ma_data_source_seek_seconds(ma_data_source* pDataSource, float secondCount, float* pSecondsSeeked); /* Can only seek forward. Abstraction to ma_data_source_seek_pcm_frames() */
MA_API ma_result ma_data_source_seek_to_second(ma_data_source* pDataSource, float seekPointInSeconds); /* Abstraction to ma_data_source_seek_to_pcm_frame() */
MA_API ma_result ma_data_source_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap); MA_API ma_result ma_data_source_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap);
MA_API ma_result ma_data_source_get_cursor_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pCursor); MA_API ma_result ma_data_source_get_cursor_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pCursor);
MA_API ma_result ma_data_source_get_length_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pLength); /* Returns MA_NOT_IMPLEMENTED if the length is unknown or cannot be determined. Decoders can return this. */ MA_API ma_result ma_data_source_get_length_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pLength); /* Returns MA_NOT_IMPLEMENTED if the length is unknown or cannot be determined. Decoders can return this. */
@@ -2479,6 +2480,12 @@ MA_API ma_result ma_event_wait(ma_event* pEvent);
Signals the specified auto-reset event. Signals the specified auto-reset event.
*/ */
MA_API ma_result ma_event_signal(ma_event* pEvent); MA_API ma_result ma_event_signal(ma_event* pEvent);
MA_API ma_result ma_semaphore_init(int initialValue, ma_semaphore* pSemaphore);
MA_API void ma_semaphore_uninit(ma_semaphore* pSemaphore);
MA_API ma_result ma_semaphore_wait(ma_semaphore* pSemaphore);
MA_API ma_result ma_semaphore_release(ma_semaphore* pSemaphore);
#endif /* MA_NO_THREADING */ #endif /* MA_NO_THREADING */
@@ -2570,7 +2577,7 @@ Job Queue
/* /*
Slot Allocator Slot Allocator
-------------- --------------
The idea of the slot allocator is for it to be used in conjunction with a fixed sized buffer. You use the slot allocator to allocator an index that can be used The idea of the slot allocator is for it to be used in conjunction with a fixed sized buffer. You use the slot allocator to allocate an index that can be used
as the insertion point for an object. as the insertion point for an object.
Slots are reference counted to help mitigate the ABA problem in the lock-free queue we use for tracking jobs. Slots are reference counted to help mitigate the ABA problem in the lock-free queue we use for tracking jobs.
@@ -3303,6 +3310,8 @@ typedef union
int nullbackend; /* The null backend uses an integer for device IDs. */ int nullbackend; /* The null backend uses an integer for device IDs. */
} ma_device_id; } ma_device_id;
MA_API ma_bool32 ma_device_id_equal(const ma_device_id* pA, const ma_device_id* pB);
typedef struct ma_context_config ma_context_config; typedef struct ma_context_config ma_context_config;
typedef struct ma_device_config ma_device_config; typedef struct ma_device_config ma_device_config;
@@ -3390,6 +3399,7 @@ struct ma_device_config
{ {
const char* pStreamNamePlayback; const char* pStreamNamePlayback;
const char* pStreamNameCapture; const char* pStreamNameCapture;
int channelMap;
} pulse; } pulse;
struct struct
{ {
@@ -3409,6 +3419,7 @@ struct ma_device_config
ma_aaudio_allowed_capture_policy allowedCapturePolicy; ma_aaudio_allowed_capture_policy allowedCapturePolicy;
ma_bool32 noAutoStartAfterReroute; ma_bool32 noAutoStartAfterReroute;
ma_bool32 enableCompatibilityWorkarounds; ma_bool32 enableCompatibilityWorkarounds;
ma_bool32 allowSetBufferCapacity;
} aaudio; } aaudio;
}; };
@@ -3481,7 +3492,7 @@ and on output returns detailed information about the device in `ma_device_info`.
case when the device ID is NULL, in which case information about the default device needs to be retrieved. case when the device ID is NULL, in which case information about the default device needs to be retrieved.
Once the context has been created and the device ID retrieved (if using anything other than the default device), the device can be created. Once the context has been created and the device ID retrieved (if using anything other than the default device), the device can be created.
This is a little bit more complicated than initialization of the context due to it's more complicated configuration. When initializing a This is a little bit more complicated than initialization of the context due to its more complicated configuration. When initializing a
device, a duplex device may be requested. This means a separate data format needs to be specified for both playback and capture. On input, device, a duplex device may be requested. This means a separate data format needs to be specified for both playback and capture. On input,
the data format is set to what the application wants. On output it's set to the native format which should match as closely as possible to the data format is set to what the application wants. On output it's set to the native format which should match as closely as possible to
the requested format. The conversion between the format requested by the application and the device's native format will be handled the requested format. The conversion between the format requested by the application and the device's native format will be handled
@@ -3502,10 +3513,10 @@ asynchronous reading and writing, `onDeviceStart()` and `onDeviceStop()` should
The handling of data delivery between the application and the device is the most complicated part of the process. To make this a bit The handling of data delivery between the application and the device is the most complicated part of the process. To make this a bit
easier, some helper callbacks are available. If the backend uses a blocking read/write style of API, the `onDeviceRead()` and easier, some helper callbacks are available. If the backend uses a blocking read/write style of API, the `onDeviceRead()` and
`onDeviceWrite()` callbacks can optionally be implemented. These are blocking and work just like reading and writing from a file. If the `onDeviceWrite()` callbacks can optionally be implemented. These are blocking and work just like reading and writing from a file. If the
backend uses a callback for data delivery, that callback must call `ma_device_handle_backend_data_callback()` from within it's callback. backend uses a callback for data delivery, that callback must call `ma_device_handle_backend_data_callback()` from within its callback.
This allows miniaudio to then process any necessary data conversion and then pass it to the miniaudio data callback. This allows miniaudio to then process any necessary data conversion and then pass it to the miniaudio data callback.
If the backend requires absolute flexibility with it's data delivery, it can optionally implement the `onDeviceDataLoop()` callback If the backend requires absolute flexibility with its data delivery, it can optionally implement the `onDeviceDataLoop()` callback
which will allow it to implement the logic that will run on the audio thread. This is much more advanced and is completely optional. which will allow it to implement the logic that will run on the audio thread. This is much more advanced and is completely optional.
The audio thread should run data delivery logic in a loop while `ma_device_get_state() == ma_device_state_started` and no errors have been The audio thread should run data delivery logic in a loop while `ma_device_get_state() == ma_device_state_started` and no errors have been
@@ -4244,6 +4255,7 @@ struct ma_device
{ {
/*AAudioStream**/ ma_ptr pStreamPlayback; /*AAudioStream**/ ma_ptr pStreamPlayback;
/*AAudioStream**/ ma_ptr pStreamCapture; /*AAudioStream**/ ma_ptr pStreamCapture;
ma_mutex rerouteLock;
ma_aaudio_usage usage; ma_aaudio_usage usage;
ma_aaudio_content_type contentType; ma_aaudio_content_type contentType;
ma_aaudio_input_preset inputPreset; ma_aaudio_input_preset inputPreset;
@@ -4667,6 +4679,10 @@ Retrieves basic information about every active playback and/or capture device.
This function will allocate memory internally for the device lists and return a pointer to them through the `ppPlaybackDeviceInfos` and `ppCaptureDeviceInfos` This function will allocate memory internally for the device lists and return a pointer to them through the `ppPlaybackDeviceInfos` and `ppCaptureDeviceInfos`
parameters. If you do not want to incur the overhead of these allocations consider using `ma_context_enumerate_devices()` which will instead use a callback. parameters. If you do not want to incur the overhead of these allocations consider using `ma_context_enumerate_devices()` which will instead use a callback.
Note that this only retrieves the ID and name/description of the device. The reason for only retrieving basic information is that it would otherwise require
opening the backend device in order to probe it for more detailed information which can be inefficient. Consider using `ma_context_get_device_info()` for this,
but don't call it from within the enumeration callback.
Parameters Parameters
---------- ----------
@@ -4708,7 +4724,7 @@ The returned pointers will become invalid upon the next call this this function,
See Also See Also
-------- --------
ma_context_get_devices() ma_context_enumerate_devices()
*/ */
MA_API ma_result ma_context_get_devices(ma_context* pContext, ma_device_info** ppPlaybackDeviceInfos, ma_uint32* pPlaybackDeviceCount, ma_device_info** ppCaptureDeviceInfos, ma_uint32* pCaptureDeviceCount); MA_API ma_result ma_context_get_devices(ma_context* pContext, ma_device_info** ppPlaybackDeviceInfos, ma_uint32* pPlaybackDeviceCount, ma_device_info** ppCaptureDeviceInfos, ma_uint32* pCaptureDeviceCount);
@@ -4847,7 +4863,7 @@ from a microphone. Whether or not you should send or receive data from the devic
playback, capture, full-duplex or loopback. (Note that loopback mode is only supported on select backends.) Sending and receiving audio data to and from the playback, capture, full-duplex or loopback. (Note that loopback mode is only supported on select backends.) Sending and receiving audio data to and from the
device is done via a callback which is fired by miniaudio at periodic time intervals. device is done via a callback which is fired by miniaudio at periodic time intervals.
The frequency at which data is delivered to and from a device depends on the size of it's period. The size of the period can be defined in terms of PCM frames The frequency at which data is delivered to and from a device depends on the size of its period. The size of the period can be defined in terms of PCM frames
or milliseconds, whichever is more convenient. Generally speaking, the smaller the period, the lower the latency at the expense of higher CPU usage and or milliseconds, whichever is more convenient. Generally speaking, the smaller the period, the lower the latency at the expense of higher CPU usage and
increased risk of glitching due to the more frequent and granular data deliver intervals. The size of a period will depend on your requirements, but increased risk of glitching due to the more frequent and granular data deliver intervals. The size of a period will depend on your requirements, but
miniaudio's defaults should work fine for most scenarios. If you're building a game you should leave this fairly small, whereas if you're building a simple miniaudio's defaults should work fine for most scenarios. If you're building a game you should leave this fairly small, whereas if you're building a simple
@@ -4921,7 +4937,7 @@ then be set directly on the structure. Below are the members of the `ma_device_c
performanceProfile performanceProfile
A hint to miniaudio as to the performance requirements of your program. Can be either `ma_performance_profile_low_latency` (default) or A hint to miniaudio as to the performance requirements of your program. Can be either `ma_performance_profile_low_latency` (default) or
`ma_performance_profile_conservative`. This mainly affects the size of default buffers and can usually be left at it's default value. `ma_performance_profile_conservative`. This mainly affects the size of default buffers and can usually be left at its default value.
noPreSilencedOutputBuffer noPreSilencedOutputBuffer
When set to true, the contents of the output buffer passed into the data callback will be left undefined. When set to false (default), the contents of When set to true, the contents of the output buffer passed into the data callback will be left undefined. When set to false (default), the contents of
@@ -4961,7 +4977,7 @@ then be set directly on the structure. Below are the members of the `ma_device_c
A pointer that will passed to callbacks in pBackendVTable. A pointer that will passed to callbacks in pBackendVTable.
resampling.linear.lpfOrder resampling.linear.lpfOrder
The linear resampler applies a low-pass filter as part of it's processing for anti-aliasing. This setting controls the order of the filter. The higher The linear resampler applies a low-pass filter as part of its processing for anti-aliasing. This setting controls the order of the filter. The higher
the value, the better the quality, in general. Setting this to 0 will disable low-pass filtering altogether. The maximum value is the value, the better the quality, in general. Setting this to 0 will disable low-pass filtering altogether. The maximum value is
`MA_MAX_FILTER_ORDER`. The default value is `min(4, MA_MAX_FILTER_ORDER)`. `MA_MAX_FILTER_ORDER`. The default value is `min(4, MA_MAX_FILTER_ORDER)`.
@@ -5038,6 +5054,9 @@ then be set directly on the structure. Below are the members of the `ma_device_c
pulse.pStreamNameCapture pulse.pStreamNameCapture
PulseAudio only. Sets the stream name for capture. PulseAudio only. Sets the stream name for capture.
pulse.channelMap
PulseAudio only. Sets the channel map that is requested from PulseAudio. See MA_PA_CHANNEL_MAP_* constants. Defaults to MA_PA_CHANNEL_MAP_AIFF.
coreaudio.allowNominalSampleRateChange coreaudio.allowNominalSampleRateChange
Core Audio only. Desktop only. When enabled, allows the sample rate of the device to be changed at the operating system level. This Core Audio only. Desktop only. When enabled, allows the sample rate of the device to be changed at the operating system level. This
is disabled by default in order to prevent intrusive changes to the user's system. This is useful if you want to use a sample rate is disabled by default in order to prevent intrusive changes to the user's system. This is useful if you want to use a sample rate
@@ -5211,7 +5230,7 @@ Unsafe. It is not safe to call this inside any callback.
Remarks Remarks
------- -------
You only need to use this function if you want to configure the context differently to it's defaults. You should never use this function if you want to manage You only need to use this function if you want to configure the context differently to its defaults. You should never use this function if you want to manage
your own context. your own context.
See the documentation for `ma_context_init()` for information on the different context configuration options. See the documentation for `ma_context_init()` for information on the different context configuration options.
@@ -5976,7 +5995,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); MA_API ma_uint32 ma_calculate_buffer_size_in_milliseconds_from_frames(ma_uint32 bufferSizeInFrames, ma_uint32 sampleRate);
@@ -6233,7 +6252,7 @@ struct ma_decoder
void* pInputCache; /* In input format. Can be null if it's not needed. */ void* pInputCache; /* In input format. Can be null if it's not needed. */
ma_uint64 inputCacheCap; /* The capacity of the input cache. */ ma_uint64 inputCacheCap; /* The capacity of the input cache. */
ma_uint64 inputCacheConsumed; /* The number of frames that have been consumed in the cache. Used for determining the next valid frame. */ ma_uint64 inputCacheConsumed; /* The number of frames that have been consumed in the cache. Used for determining the next valid frame. */
ma_uint64 inputCacheRemaining; /* The number of valid frames remaining in the cahce. */ ma_uint64 inputCacheRemaining; /* The number of valid frames remaining in the cache. */
ma_allocation_callbacks allocationCallbacks; ma_allocation_callbacks allocationCallbacks;
union union
{ {
@@ -6274,7 +6293,7 @@ This is not thread safe without your own synchronization.
MA_API ma_result ma_decoder_read_pcm_frames(ma_decoder* pDecoder, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); MA_API ma_result ma_decoder_read_pcm_frames(ma_decoder* pDecoder, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead);
/* /*
Seeks to a PCM frame based on it's absolute index. Seeks to a PCM frame based on its absolute index.
This is not thread safe without your own synchronization. This is not thread safe without your own synchronization.
*/ */
@@ -6537,7 +6556,8 @@ typedef enum
MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE = 0x00000002, /* Decode data before storing in memory. When set, decoding is done at the resource manager level rather than the mixing thread. Results in faster mixing, but higher memory usage. */ MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE = 0x00000002, /* Decode data before storing in memory. When set, decoding is done at the resource manager level rather than the mixing thread. Results in faster mixing, but higher memory usage. */
MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC = 0x00000004, /* When set, the resource manager will load the data source asynchronously. */ MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC = 0x00000004, /* When set, the resource manager will load the data source asynchronously. */
MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT = 0x00000008, /* When set, waits for initialization of the underlying data source before returning from ma_resource_manager_data_source_init(). */ MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT = 0x00000008, /* When set, waits for initialization of the underlying data source before returning from ma_resource_manager_data_source_init(). */
MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_UNKNOWN_LENGTH = 0x00000010 /* Gives the resource manager a hint that the length of the data source is unknown and calling `ma_data_source_get_length_in_pcm_frames()` should be avoided. */ MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_UNKNOWN_LENGTH = 0x00000010, /* Gives the resource manager a hint that the length of the data source is unknown and calling `ma_data_source_get_length_in_pcm_frames()` should be avoided. */
MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING = 0x00000020 /* When set, configures the data source to loop by default. */
} ma_resource_manager_data_source_flags; } ma_resource_manager_data_source_flags;
@@ -6605,8 +6625,8 @@ typedef struct
ma_uint64 rangeEndInPCMFrames; ma_uint64 rangeEndInPCMFrames;
ma_uint64 loopPointBegInPCMFrames; ma_uint64 loopPointBegInPCMFrames;
ma_uint64 loopPointEndInPCMFrames; ma_uint64 loopPointEndInPCMFrames;
ma_bool32 isLooping;
ma_uint32 flags; ma_uint32 flags;
ma_bool32 isLooping; /* Deprecated. Use the MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING flag in `flags` instead. */
} ma_resource_manager_data_source_config; } ma_resource_manager_data_source_config;
MA_API ma_resource_manager_data_source_config ma_resource_manager_data_source_config_init(void); MA_API ma_resource_manager_data_source_config ma_resource_manager_data_source_config_init(void);
@@ -6849,6 +6869,16 @@ Node Graph
/* Use this when the bus count is determined by the node instance rather than the vtable. */ /* Use this when the bus count is determined by the node instance rather than the vtable. */
#define MA_NODE_BUS_COUNT_UNKNOWN 255 #define MA_NODE_BUS_COUNT_UNKNOWN 255
/* For some internal memory management of ma_node_graph. */
typedef struct
{
size_t offset;
size_t sizeInBytes;
unsigned char _data[1];
} ma_stack;
typedef struct ma_node_graph ma_node_graph; typedef struct ma_node_graph ma_node_graph;
typedef void ma_node; typedef void ma_node;
@@ -6888,7 +6918,7 @@ typedef struct
void (* onProcess)(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut); void (* onProcess)(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut);
/* /*
A callback for retrieving the number of a input frames that are required to output the A callback for retrieving the number of input frames that are required to output the
specified number of output frames. You would only want to implement this when the node performs specified number of output frames. You would only want to implement this when the node performs
resampling. This is optional, even for nodes that perform resampling, but it does offer a resampling. This is optional, even for nodes that perform resampling, but it does offer a
small reduction in latency as it allows miniaudio to calculate the exact number of input frames small reduction in latency as it allows miniaudio to calculate the exact number of input frames
@@ -6973,10 +7003,14 @@ typedef struct ma_node_base ma_node_base;
struct ma_node_base struct ma_node_base
{ {
/* These variables are set once at startup. */ /* These variables are set once at startup. */
ma_node_graph* pNodeGraph; /* The graph this node belongs to. */ ma_node_graph* pNodeGraph; /* The graph this node belongs to. */
const ma_node_vtable* vtable; const ma_node_vtable* vtable;
float* pCachedData; /* Allocated on the heap. Fixed size. Needs to be stored on the heap because reading from output buses is done in separate function calls. */ ma_uint32 inputBusCount;
ma_uint16 cachedDataCapInFramesPerBus; /* The capacity of the input data cache in frames, per bus. */ ma_uint32 outputBusCount;
ma_node_input_bus* pInputBuses;
ma_node_output_bus* pOutputBuses;
float* pCachedData; /* Allocated on the heap. Fixed size. Needs to be stored on the heap because reading from output buses is done in separate function calls. */
ma_uint16 cachedDataCapInFramesPerBus; /* The capacity of the input data cache in frames, per bus. */
/* These variables are read and written only from the audio thread. */ /* These variables are read and written only from the audio thread. */
ma_uint16 cachedFrameCountOut; ma_uint16 cachedFrameCountOut;
@@ -6984,13 +7018,9 @@ struct ma_node_base
ma_uint16 consumedFrameCountIn; ma_uint16 consumedFrameCountIn;
/* These variables are read and written between different threads. */ /* These variables are read and written between different threads. */
MA_ATOMIC(4, ma_node_state) state; /* When set to stopped, nothing will be read, regardless of the times in stateTimes. */ MA_ATOMIC(4, ma_node_state) state; /* When set to stopped, nothing will be read, regardless of the times in stateTimes. */
MA_ATOMIC(8, ma_uint64) stateTimes[2]; /* Indexed by ma_node_state. Specifies the time based on the global clock that a node should be considered to be in the relevant state. */ MA_ATOMIC(8, ma_uint64) stateTimes[2]; /* Indexed by ma_node_state. Specifies the time based on the global clock that a node should be considered to be in the relevant state. */
MA_ATOMIC(8, ma_uint64) localTime; /* The node's local clock. This is just a running sum of the number of output frames that have been processed. Can be modified by any thread with `ma_node_set_time()`. */ MA_ATOMIC(8, ma_uint64) localTime; /* The node's local clock. This is just a running sum of the number of output frames that have been processed. Can be modified by any thread with `ma_node_set_time()`. */
ma_uint32 inputBusCount;
ma_uint32 outputBusCount;
ma_node_input_bus* pInputBuses;
ma_node_output_bus* pOutputBuses;
/* Memory management. */ /* Memory management. */
ma_node_input_bus _inputBuses[MA_MAX_NODE_LOCAL_BUS_COUNT]; ma_node_input_bus _inputBuses[MA_MAX_NODE_LOCAL_BUS_COUNT];
@@ -7026,7 +7056,8 @@ MA_API ma_result ma_node_set_time(ma_node* pNode, ma_uint64 localTime);
typedef struct typedef struct
{ {
ma_uint32 channels; ma_uint32 channels;
ma_uint16 nodeCacheCapInFrames; ma_uint32 processingSizeInFrames; /* This is the preferred processing size for node processing callbacks unless overridden by a node itself. Can be 0 in which case it will be based on the frame count passed into ma_node_graph_read_pcm_frames(), but will not be well defined. */
size_t preMixStackSizeInBytes; /* Defaults to 512KB per channel. Reducing this will save memory, but the depth of your node graph will be more restricted. */
} ma_node_graph_config; } ma_node_graph_config;
MA_API ma_node_graph_config ma_node_graph_config_init(ma_uint32 channels); MA_API ma_node_graph_config ma_node_graph_config_init(ma_uint32 channels);
@@ -7037,10 +7068,15 @@ struct ma_node_graph
/* Immutable. */ /* Immutable. */
ma_node_base base; /* The node graph itself is a node so it can be connected as an input to different node graph. This has zero inputs and calls ma_node_graph_read_pcm_frames() to generate it's output. */ ma_node_base base; /* The node graph itself is a node so it can be connected as an input to different node graph. This has zero inputs and calls ma_node_graph_read_pcm_frames() to generate it's output. */
ma_node_base endpoint; /* Special node that all nodes eventually connect to. Data is read from this node in ma_node_graph_read_pcm_frames(). */ ma_node_base endpoint; /* Special node that all nodes eventually connect to. Data is read from this node in ma_node_graph_read_pcm_frames(). */
ma_uint16 nodeCacheCapInFrames; float* pProcessingCache; /* This will be allocated when processingSizeInFrames is non-zero. This is needed because ma_node_graph_read_pcm_frames() can be called with a variable number of frames, and we may need to do some buffering in situations where the caller requests a frame count that's not a multiple of processingSizeInFrames. */
ma_uint32 processingCacheFramesRemaining;
ma_uint32 processingSizeInFrames;
/* Read and written by multiple threads. */ /* Read and written by multiple threads. */
MA_ATOMIC(4, ma_bool32) isReading; MA_ATOMIC(4, ma_bool32) isReading;
/* Modified only by the audio thread. */
ma_stack* pPreMixStack;
}; };
MA_API ma_result ma_node_graph_init(const ma_node_graph_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_node_graph* pNodeGraph); MA_API ma_result ma_node_graph_init(const ma_node_graph_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_node_graph* pNodeGraph);
@@ -7325,6 +7361,7 @@ typedef enum
MA_SOUND_FLAG_ASYNC = 0x00000004, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC */ MA_SOUND_FLAG_ASYNC = 0x00000004, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC */
MA_SOUND_FLAG_WAIT_INIT = 0x00000008, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT */ MA_SOUND_FLAG_WAIT_INIT = 0x00000008, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT */
MA_SOUND_FLAG_UNKNOWN_LENGTH = 0x00000010, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_UNKNOWN_LENGTH */ MA_SOUND_FLAG_UNKNOWN_LENGTH = 0x00000010, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_UNKNOWN_LENGTH */
MA_SOUND_FLAG_LOOPING = 0x00000020, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING */
/* ma_sound specific flags. */ /* ma_sound specific flags. */
MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT = 0x00001000, /* Do not attach to the endpoint by default. Useful for when setting up nodes in a complex graph system. */ MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT = 0x00001000, /* Do not attach to the endpoint by default. Useful for when setting up nodes in a complex graph system. */
@@ -7364,7 +7401,7 @@ MA_API ma_engine_node_config ma_engine_node_config_init(ma_engine* pEngine, ma_e
/* Base node object for both ma_sound and ma_sound_group. */ /* Base node object for both ma_sound and ma_sound_group. */
typedef struct typedef struct
{ {
ma_node_base baseNode; /* Must be the first member for compatiblity with the ma_node API. */ ma_node_base baseNode; /* Must be the first member for compatibility with the ma_node API. */
ma_engine* pEngine; /* A pointer to the engine. Set based on the value from the config. */ ma_engine* pEngine; /* A pointer to the engine. Set based on the value from the config. */
ma_uint32 sampleRate; /* The sample rate of the input data. For sounds backed by a data source, this will be the data source's sample rate. Otherwise it'll be the engine's sample rate. */ ma_uint32 sampleRate; /* The sample rate of the input data. For sounds backed by a data source, this will be the data source's sample rate. Otherwise it'll be the engine's sample rate. */
ma_uint32 volumeSmoothTimeInPCMFrames; ma_uint32 volumeSmoothTimeInPCMFrames;
@@ -7424,13 +7461,13 @@ typedef struct
ma_uint64 rangeEndInPCMFrames; ma_uint64 rangeEndInPCMFrames;
ma_uint64 loopPointBegInPCMFrames; ma_uint64 loopPointBegInPCMFrames;
ma_uint64 loopPointEndInPCMFrames; ma_uint64 loopPointEndInPCMFrames;
ma_bool32 isLooping;
ma_sound_end_proc endCallback; /* Fired when the sound reaches the end. Will be fired from the audio thread. Do not restart, uninitialize or otherwise change the state of the sound from here. Instead fire an event or set a variable to indicate to a different thread to change the start of the sound. Will not be fired in response to a scheduled stop with ma_sound_set_stop_time_*(). */ ma_sound_end_proc endCallback; /* Fired when the sound reaches the end. Will be fired from the audio thread. Do not restart, uninitialize or otherwise change the state of the sound from here. Instead fire an event or set a variable to indicate to a different thread to change the start of the sound. Will not be fired in response to a scheduled stop with ma_sound_set_stop_time_*(). */
void* pEndCallbackUserData; void* pEndCallbackUserData;
#ifndef MA_NO_RESOURCE_MANAGER #ifndef MA_NO_RESOURCE_MANAGER
ma_resource_manager_pipeline_notifications initNotifications; ma_resource_manager_pipeline_notifications initNotifications;
#endif #endif
ma_fence* pDoneFence; /* Deprecated. Use initNotifications instead. Released when the resource manager has finished decoding the entire sound. Not used with streams. */ ma_fence* pDoneFence; /* Deprecated. Use initNotifications instead. Released when the resource manager has finished decoding the entire sound. Not used with streams. */
ma_bool32 isLooping; /* Deprecated. Use the MA_SOUND_FLAG_LOOPING flag in `flags` instead. */
} ma_sound_config; } ma_sound_config;
MA_API ma_sound_config ma_sound_config_init(void); /* Deprecated. Will be removed in version 0.12. Use ma_sound_config_2() instead. */ MA_API ma_sound_config ma_sound_config_init(void); /* Deprecated. Will be removed in version 0.12. Use ma_sound_config_2() instead. */
@@ -7494,6 +7531,7 @@ typedef struct
ma_uint32 gainSmoothTimeInFrames; /* The number of frames to interpolate the gain of spatialized sounds across. If set to 0, will use gainSmoothTimeInMilliseconds. */ ma_uint32 gainSmoothTimeInFrames; /* The number of frames to interpolate the gain of spatialized sounds across. If set to 0, will use gainSmoothTimeInMilliseconds. */
ma_uint32 gainSmoothTimeInMilliseconds; /* When set to 0, gainSmoothTimeInFrames will be used. If both are set to 0, a default value will be used. */ ma_uint32 gainSmoothTimeInMilliseconds; /* When set to 0, gainSmoothTimeInFrames will be used. If both are set to 0, a default value will be used. */
ma_uint32 defaultVolumeSmoothTimeInPCMFrames; /* Defaults to 0. Controls the default amount of smoothing to apply to volume changes to sounds. High values means more smoothing at the expense of high latency (will take longer to reach the new volume). */ ma_uint32 defaultVolumeSmoothTimeInPCMFrames; /* Defaults to 0. Controls the default amount of smoothing to apply to volume changes to sounds. High values means more smoothing at the expense of high latency (will take longer to reach the new volume). */
ma_uint32 preMixStackSizeInBytes; /* A stack is used for internal processing in the node graph. This allows you to configure the size of this stack. Smaller values will reduce the maximum depth of your node graph. You should rarely need to modify this. */
ma_allocation_callbacks allocationCallbacks; ma_allocation_callbacks allocationCallbacks;
ma_bool32 noAutoStart; /* When set to true, requires an explicit call to ma_engine_start(). This is false by default, meaning the engine will be started automatically in ma_engine_init(). */ ma_bool32 noAutoStart; /* When set to true, requires an explicit call to ma_engine_start(). This is false by default, meaning the engine will be started automatically in ma_engine_init(). */
ma_bool32 noDevice; /* When set to true, don't create a default device. ma_engine_read_pcm_frames() can be called manually to read data. */ ma_bool32 noDevice; /* When set to true, don't create a default device. ma_engine_read_pcm_frames() can be called manually to read data. */
@@ -7508,12 +7546,12 @@ MA_API ma_engine_config ma_engine_config_init(void);
struct ma_engine struct ma_engine
{ {
ma_node_graph nodeGraph; /* An engine is a node graph. It should be able to be plugged into any ma_node_graph API (with a cast) which means this must be the first member of this struct. */ ma_node_graph nodeGraph; /* An engine is a node graph. It should be able to be plugged into any ma_node_graph API (with a cast) which means this must be the first member of this struct. */
#if !defined(MA_NO_RESOURCE_MANAGER) #if !defined(MA_NO_RESOURCE_MANAGER)
ma_resource_manager* pResourceManager; ma_resource_manager* pResourceManager;
#endif #endif
#if !defined(MA_NO_DEVICE_IO) #if !defined(MA_NO_DEVICE_IO)
ma_device* pDevice; /* Optionally set via the config, otherwise allocated by the engine in ma_engine_init(). */ ma_device* pDevice; /* Optionally set via the config, otherwise allocated by the engine in ma_engine_init(). */
#endif #endif
ma_log* pLog; ma_log* pLog;
ma_uint32 sampleRate; ma_uint32 sampleRate;
@@ -7522,10 +7560,10 @@ struct ma_engine
ma_allocation_callbacks allocationCallbacks; ma_allocation_callbacks allocationCallbacks;
ma_bool8 ownsResourceManager; ma_bool8 ownsResourceManager;
ma_bool8 ownsDevice; ma_bool8 ownsDevice;
ma_spinlock inlinedSoundLock; /* For synchronizing access so the inlined sound list. */ ma_spinlock inlinedSoundLock; /* For synchronizing access to the inlined sound list. */
ma_sound_inlined* pInlinedSoundHead; /* The first inlined sound. Inlined sounds are tracked in a linked list. */ ma_sound_inlined* pInlinedSoundHead; /* The first inlined sound. Inlined sounds are tracked in a linked list. */
MA_ATOMIC(4, ma_uint32) inlinedSoundCount; /* The total number of allocated inlined sound objects. Used for debugging. */ MA_ATOMIC(4, ma_uint32) inlinedSoundCount; /* The total number of allocated inlined sound objects. Used for debugging. */
ma_uint32 gainSmoothTimeInFrames; /* The number of frames to interpolate the gain of spatialized sounds across. */ ma_uint32 gainSmoothTimeInFrames; /* The number of frames to interpolate the gain of spatialized sounds across. */
ma_uint32 defaultVolumeSmoothTimeInPCMFrames; ma_uint32 defaultVolumeSmoothTimeInPCMFrames;
ma_mono_expansion_mode monoExpansionMode; ma_mono_expansion_mode monoExpansionMode;
ma_engine_process_proc onProcess; ma_engine_process_proc onProcess;
@@ -7650,6 +7688,7 @@ MA_API void ma_sound_set_looping(ma_sound* pSound, ma_bool32 isLooping);
MA_API ma_bool32 ma_sound_is_looping(const ma_sound* pSound); MA_API ma_bool32 ma_sound_is_looping(const ma_sound* pSound);
MA_API ma_bool32 ma_sound_at_end(const ma_sound* pSound); MA_API ma_bool32 ma_sound_at_end(const ma_sound* pSound);
MA_API ma_result ma_sound_seek_to_pcm_frame(ma_sound* pSound, ma_uint64 frameIndex); /* Just a wrapper around ma_data_source_seek_to_pcm_frame(). */ MA_API ma_result ma_sound_seek_to_pcm_frame(ma_sound* pSound, ma_uint64 frameIndex); /* Just a wrapper around ma_data_source_seek_to_pcm_frame(). */
MA_API ma_result ma_sound_seek_to_second(ma_sound* pSound, float seekPointInSeconds); /* Abstraction to ma_sound_seek_to_pcm_frame() */
MA_API ma_result ma_sound_get_data_format(ma_sound* pSound, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap); MA_API ma_result ma_sound_get_data_format(ma_sound* pSound, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap);
MA_API ma_result ma_sound_get_cursor_in_pcm_frames(ma_sound* pSound, ma_uint64* pCursor); MA_API ma_result ma_sound_get_cursor_in_pcm_frames(ma_sound* pSound, ma_uint64* pCursor);
MA_API ma_result ma_sound_get_length_in_pcm_frames(ma_sound* pSound, ma_uint64* pLength); MA_API ma_result ma_sound_get_length_in_pcm_frames(ma_sound* pSound, ma_uint64* pLength);
@@ -7752,7 +7791,7 @@ For more information, please refer to <http://unlicense.org/>
=============================================================================== ===============================================================================
ALTERNATIVE 2 - MIT No Attribution ALTERNATIVE 2 - MIT No Attribution
=============================================================================== ===============================================================================
Copyright 2023 David Reid Copyright 2025 David Reid
Permission is hereby granted, free of charge, to any person obtaining a copy of Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in this software and associated documentation files (the "Software"), to deal in
+42 -31
View File
@@ -1,6 +1,6 @@
/* /*
Audio playback and capture library. Choice of public domain or MIT-0. See license statements at the end of this file. Audio playback and capture library. Choice of public domain or MIT-0. See license statements at the end of this file.
miniaudio - v0.11.22 - TBD miniaudio - v0.11.22 - 2025-02-24
David Reid - mackron@gmail.com David Reid - mackron@gmail.com
@@ -28520,7 +28520,21 @@ static ma_result ma_device_start__alsa(ma_device* pDevice)
} }
if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) {
/* Don't need to do anything for playback because it'll be started automatically when enough data has been written. */ /*
When data is written to the device we wait for the device to get ready to receive data with poll(). In my testing
I have observed that poll() can sometimes block forever unless the device is started explicitly with snd_pcm_start()
or some data is written with snd_pcm_writei().
To resolve this I've decided to do an explicit start with snd_pcm_start(). The problem with this is that the device
is started without any data in the internal buffer which will result in an immediate underrun. If instead we were
to call into snd_pcm_writei() in an attempt to prevent the underrun, we would run the risk of a weird deadlock
issue as documented inside ma_device_write__alsa().
*/
resultALSA = ((ma_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((ma_snd_pcm_t*)pDevice->alsa.pPCMPlayback);
if (resultALSA < 0) {
ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start playback device.");
return ma_result_from_errno(-resultALSA);
}
} }
return MA_SUCCESS; return MA_SUCCESS;
@@ -28581,7 +28595,6 @@ static ma_result ma_device_stop__alsa(ma_device* pDevice)
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Failed to read from playback wakeupfd. read() = %d\n", resultRead); ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Failed to read from playback wakeupfd. read() = %d\n", resultRead);
} }
} }
} }
return MA_SUCCESS; return MA_SUCCESS;
@@ -28607,7 +28620,7 @@ static ma_result ma_device_wait__alsa(ma_device* pDevice, ma_snd_pcm_t* pPCM, st
/* /*
Before checking the ALSA poll descriptor flag we need to check if the wakeup descriptor Before checking the ALSA poll descriptor flag we need to check if the wakeup descriptor
has had it's POLLIN flag set. If so, we need to actually read the data and then exit has had it's POLLIN flag set. If so, we need to actually read the data and then exit the
function. The wakeup descriptor will be the first item in the descriptors buffer. function. The wakeup descriptor will be the first item in the descriptors buffer.
*/ */
if ((pPollDescriptors[0].revents & POLLIN) != 0) { if ((pPollDescriptors[0].revents & POLLIN) != 0) {
@@ -28636,7 +28649,7 @@ static ma_result ma_device_wait__alsa(ma_device* pDevice, ma_snd_pcm_t* pPCM, st
ma_snd_pcm_state_t state = ((ma_snd_pcm_state_proc)pDevice->pContext->alsa.snd_pcm_state)(pPCM); ma_snd_pcm_state_t state = ((ma_snd_pcm_state_proc)pDevice->pContext->alsa.snd_pcm_state)(pPCM);
if (state == MA_SND_PCM_STATE_XRUN) { if (state == MA_SND_PCM_STATE_XRUN) {
/* The PCM is in a xrun state. This will be recovered from at a higher level. We can disregard this. */ /* The PCM is in a xrun state. This will be recovered from at a higher level. We can disregard this. */
} else { } else {
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[ALSA] POLLERR detected. status = %d\n", ((ma_snd_pcm_state_proc)pDevice->pContext->alsa.snd_pcm_state)(pPCM)); ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[ALSA] POLLERR detected. status = %d\n", ((ma_snd_pcm_state_proc)pDevice->pContext->alsa.snd_pcm_state)(pPCM));
} }
} }
@@ -38163,20 +38176,12 @@ static ma_result ma_create_and_configure_AAudioStreamBuilder__aaudio(ma_context*
((MA_PFN_AAudioStreamBuilder_setSampleRate)pContext->aaudio.AAudioStreamBuilder_setSampleRate)(pBuilder, pDescriptor->sampleRate); ((MA_PFN_AAudioStreamBuilder_setSampleRate)pContext->aaudio.AAudioStreamBuilder_setSampleRate)(pBuilder, pDescriptor->sampleRate);
} }
if (deviceType == ma_device_type_capture) { if (pDescriptor->channels != 0) {
if (pDescriptor->channels != 0) { ((MA_PFN_AAudioStreamBuilder_setChannelCount)pContext->aaudio.AAudioStreamBuilder_setChannelCount)(pBuilder, pDescriptor->channels);
((MA_PFN_AAudioStreamBuilder_setChannelCount)pContext->aaudio.AAudioStreamBuilder_setChannelCount)(pBuilder, pDescriptor->channels); }
}
if (pDescriptor->format != ma_format_unknown) { if (pDescriptor->format != ma_format_unknown) {
((MA_PFN_AAudioStreamBuilder_setFormat)pContext->aaudio.AAudioStreamBuilder_setFormat)(pBuilder, (pDescriptor->format == ma_format_s16) ? MA_AAUDIO_FORMAT_PCM_I16 : MA_AAUDIO_FORMAT_PCM_FLOAT); ((MA_PFN_AAudioStreamBuilder_setFormat)pContext->aaudio.AAudioStreamBuilder_setFormat)(pBuilder, (pDescriptor->format == ma_format_s16) ? MA_AAUDIO_FORMAT_PCM_I16 : MA_AAUDIO_FORMAT_PCM_FLOAT);
}
} else {
if (pDescriptor->channels != 0) {
((MA_PFN_AAudioStreamBuilder_setChannelCount)pContext->aaudio.AAudioStreamBuilder_setChannelCount)(pBuilder, pDescriptor->channels);
}
if (pDescriptor->format != ma_format_unknown) {
((MA_PFN_AAudioStreamBuilder_setFormat)pContext->aaudio.AAudioStreamBuilder_setFormat)(pBuilder, (pDescriptor->format == ma_format_s16) ? MA_AAUDIO_FORMAT_PCM_I16 : MA_AAUDIO_FORMAT_PCM_FLOAT);
}
} }
@@ -53118,12 +53123,7 @@ static ma_channel_conversion_path ma_channel_map_get_conversion_path(const ma_ch
ma_uint32 iChannelIn; ma_uint32 iChannelIn;
ma_bool32 areAllChannelPositionsPresent = MA_TRUE; ma_bool32 areAllChannelPositionsPresent = MA_TRUE;
for (iChannelIn = 0; iChannelIn < channelsIn; ++iChannelIn) { for (iChannelIn = 0; iChannelIn < channelsIn; ++iChannelIn) {
ma_bool32 isInputChannelPositionInOutput = MA_FALSE; ma_bool32 isInputChannelPositionInOutput = ma_channel_map_contains_channel_position(channelsOut, pChannelMapOut, ma_channel_map_get_channel(pChannelMapIn, channelsIn, iChannelIn));
if (ma_channel_map_contains_channel_position(channelsOut, pChannelMapOut, ma_channel_map_get_channel(pChannelMapIn, channelsIn, iChannelIn))) {
isInputChannelPositionInOutput = MA_TRUE;
break;
}
if (!isInputChannelPositionInOutput) { if (!isInputChannelPositionInOutput) {
areAllChannelPositionsPresent = MA_FALSE; areAllChannelPositionsPresent = MA_FALSE;
break; break;
@@ -60513,7 +60513,7 @@ extern "C" {
#define MA_DR_WAV_XSTRINGIFY(x) MA_DR_WAV_STRINGIFY(x) #define MA_DR_WAV_XSTRINGIFY(x) MA_DR_WAV_STRINGIFY(x)
#define MA_DR_WAV_VERSION_MAJOR 0 #define MA_DR_WAV_VERSION_MAJOR 0
#define MA_DR_WAV_VERSION_MINOR 13 #define MA_DR_WAV_VERSION_MINOR 13
#define MA_DR_WAV_VERSION_REVISION 17 #define MA_DR_WAV_VERSION_REVISION 18
#define MA_DR_WAV_VERSION_STRING MA_DR_WAV_XSTRINGIFY(MA_DR_WAV_VERSION_MAJOR) "." MA_DR_WAV_XSTRINGIFY(MA_DR_WAV_VERSION_MINOR) "." MA_DR_WAV_XSTRINGIFY(MA_DR_WAV_VERSION_REVISION) #define MA_DR_WAV_VERSION_STRING MA_DR_WAV_XSTRINGIFY(MA_DR_WAV_VERSION_MAJOR) "." MA_DR_WAV_XSTRINGIFY(MA_DR_WAV_VERSION_MINOR) "." MA_DR_WAV_XSTRINGIFY(MA_DR_WAV_VERSION_REVISION)
#include <stddef.h> #include <stddef.h>
#define MA_DR_WAVE_FORMAT_PCM 0x1 #define MA_DR_WAVE_FORMAT_PCM 0x1
@@ -65417,6 +65417,14 @@ MA_API ma_result ma_decoder_read_pcm_frames(ma_decoder* pDecoder, void* pFramesO
if (requiredInputFrameCount > 0) { if (requiredInputFrameCount > 0) {
result = ma_data_source_read_pcm_frames(pDecoder->pBackend, pIntermediaryBuffer, framesToReadThisIterationIn, &framesReadThisIterationIn); result = ma_data_source_read_pcm_frames(pDecoder->pBackend, pIntermediaryBuffer, framesToReadThisIterationIn, &framesReadThisIterationIn);
/*
Note here that even if we've reached the end, we don't want to abort because there might be more output frames needing to be
generated from cached input data, which might happen if resampling is being performed.
*/
if (result != MA_SUCCESS && result != MA_AT_END) {
break;
}
} else { } else {
framesReadThisIterationIn = 0; framesReadThisIterationIn = 0;
pIntermediaryBuffer[0] = 0; /* <-- This is just to silence a static analysis warning. */ pIntermediaryBuffer[0] = 0; /* <-- This is just to silence a static analysis warning. */
@@ -68540,7 +68548,7 @@ static ma_result ma_resource_manager_data_buffer_node_decode_next_page(ma_resour
} }
result = ma_decoder_read_pcm_frames(pDecoder, pPage->pAudioData, framesToTryReading, &framesRead); result = ma_decoder_read_pcm_frames(pDecoder, pPage->pAudioData, framesToTryReading, &framesRead);
if (framesRead > 0) { if (result == MA_SUCCESS && framesRead > 0) {
pPage->sizeInFrames = framesRead; pPage->sizeInFrames = framesRead;
result = ma_paged_audio_buffer_data_append_page(&pDataBufferNode->data.backend.decodedPaged.data, pPage); result = ma_paged_audio_buffer_data_append_page(&pDataBufferNode->data.backend.decodedPaged.data, pPage);
@@ -70999,6 +71007,7 @@ static ma_result ma_job_process__resource_manager__load_data_buffer(ma_job* pJob
goto done; /* <-- This will ensure the execution pointer is incremented. */ goto done; /* <-- This will ensure the execution pointer is incremented. */
} else { } else {
result = MA_SUCCESS; /* <-- Make sure this is reset. */ result = MA_SUCCESS; /* <-- Make sure this is reset. */
(void)result; /* <-- This is to suppress a static analysis diagnostic about "result" not being used. But for safety when I do future maintenance I don't want to delete that assignment. */
} }
/* Try initializing the connector if we haven't already. */ /* Try initializing the connector if we haven't already. */
@@ -77731,7 +77740,7 @@ code below please report the bug to the respective repository for the relevant p
*************************************************************************************************************************************************************** ***************************************************************************************************************************************************************
**************************************************************************************************************************************************************/ **************************************************************************************************************************************************************/
#if !defined(MA_NO_WAV) && (!defined(MA_NO_DECODING) || !defined(MA_NO_ENCODING)) #if !defined(MA_NO_WAV) && (!defined(MA_NO_DECODING) || !defined(MA_NO_ENCODING))
#if !defined(MA_DR_WAV_IMPLEMENTATION) && !defined(MA_DR_WAV_IMPLEMENTATION) /* For backwards compatibility. Will be removed in version 0.11 for cleanliness. */ #if !defined(MA_DR_WAV_IMPLEMENTATION)
/* dr_wav_c begin */ /* dr_wav_c begin */
#ifndef ma_dr_wav_c #ifndef ma_dr_wav_c
#define ma_dr_wav_c #define ma_dr_wav_c
@@ -79321,7 +79330,9 @@ MA_PRIVATE ma_bool32 ma_dr_wav_init__internal(ma_dr_wav* pWav, ma_dr_wav_chunk_p
compressionFormat = MA_DR_WAVE_FORMAT_MULAW; compressionFormat = MA_DR_WAVE_FORMAT_MULAW;
} else if (ma_dr_wav_fourcc_equal(type, "ima4")) { } else if (ma_dr_wav_fourcc_equal(type, "ima4")) {
compressionFormat = MA_DR_WAVE_FORMAT_DVI_ADPCM; compressionFormat = MA_DR_WAVE_FORMAT_DVI_ADPCM;
sampleSizeInBits = 4; sampleSizeInBits = 4;
(void)compressionFormat;
(void)sampleSizeInBits;
return MA_FALSE; return MA_FALSE;
} else { } else {
return MA_FALSE; return MA_FALSE;
@@ -82562,7 +82573,7 @@ MA_API ma_bool32 ma_dr_wav_fourcc_equal(const ma_uint8* a, const char* b)
#endif /* MA_NO_WAV */ #endif /* MA_NO_WAV */
#if !defined(MA_NO_FLAC) && !defined(MA_NO_DECODING) #if !defined(MA_NO_FLAC) && !defined(MA_NO_DECODING)
#if !defined(MA_DR_FLAC_IMPLEMENTATION) && !defined(MA_DR_FLAC_IMPLEMENTATION) /* For backwards compatibility. Will be removed in version 0.11 for cleanliness. */ #if !defined(MA_DR_FLAC_IMPLEMENTATION)
/* dr_flac_c begin */ /* dr_flac_c begin */
#ifndef ma_dr_flac_c #ifndef ma_dr_flac_c
#define ma_dr_flac_c #define ma_dr_flac_c
@@ -90311,7 +90322,7 @@ MA_API ma_bool32 ma_dr_flac_next_cuesheet_track(ma_dr_flac_cuesheet_track_iterat
#endif /* MA_NO_FLAC */ #endif /* MA_NO_FLAC */
#if !defined(MA_NO_MP3) && !defined(MA_NO_DECODING) #if !defined(MA_NO_MP3) && !defined(MA_NO_DECODING)
#if !defined(MA_DR_MP3_IMPLEMENTATION) && !defined(MA_DR_MP3_IMPLEMENTATION) /* For backwards compatibility. Will be removed in version 0.11 for cleanliness. */ #if !defined(MA_DR_MP3_IMPLEMENTATION)
/* dr_mp3_c begin */ /* dr_mp3_c begin */
#ifndef ma_dr_mp3_c #ifndef ma_dr_mp3_c
#define ma_dr_mp3_c #define ma_dr_mp3_c
+1 -10
View File
@@ -42,14 +42,8 @@ int ma_run_tests(int argc, char** argv)
int result; int result;
ma_bool32 hasError = MA_FALSE; ma_bool32 hasError = MA_FALSE;
size_t iTest; size_t iTest;
fs* pFS;
if (fs_init(NULL, &pFS) != FS_SUCCESS) { fs_mkdir(NULL, TEST_OUTPUT_DIR);
printf("Failed to initialize the file system.\n");
return 1;
}
fs_mkdir(pFS, TEST_OUTPUT_DIR);
for (iTest = 0; iTest < g_Tests.count; iTest += 1) { for (iTest = 0; iTest < g_Tests.count; iTest += 1) {
printf("=== BEGIN %s ===\n", g_Tests.pTests[iTest].pName); printf("=== BEGIN %s ===\n", g_Tests.pTests[iTest].pName);
@@ -61,9 +55,6 @@ int ma_run_tests(int argc, char** argv)
} }
} }
fs_uninit(pFS);
pFS = NULL;
if (hasError) { if (hasError) {
return 1; /* Something failed. */ return 1; /* Something failed. */
} else { } else {