mirror of
https://github.com/mackron/miniaudio.git
synced 2026-04-24 01:04:02 +02:00
Merge branch 'master' into dev-0.12
This commit is contained in:
@@ -1,104 +0,0 @@
|
||||
/*
|
||||
IMPORTANT NOTE: Cosmopolitan is not officially supported by miniaudio. This file was added just as
|
||||
a way to play around and experiment with Cosmopolitan as a proof of concept and to test the viability
|
||||
of supporting such a compiler. If you get compilation or runtime errors you're on your own.
|
||||
|
||||
---------------------------------------------------------------------------------------------------
|
||||
|
||||
This is a version of windows.h for compiling with Cosmopolitan. It's not complete. It's intended to
|
||||
define some missing items from cosmopolitan.h. Hopefully as the project develops we can eventually
|
||||
eliminate all of the content in this file.
|
||||
*/
|
||||
#ifndef _WINDOWS_
|
||||
#define _WINDOWS_
|
||||
|
||||
#define WINAPI
|
||||
#define STDMETHODCALLTYPE
|
||||
#define CALLBACK
|
||||
|
||||
typedef uint64_t HWND;
|
||||
typedef uint64_t HANDLE;
|
||||
typedef uint64_t HKEY;
|
||||
typedef uint64_t HWAVEIN;
|
||||
typedef uint64_t HWAVEOUT;
|
||||
typedef uint32_t HRESULT;
|
||||
typedef uint8_t BYTE;
|
||||
typedef uint16_t WORD;
|
||||
typedef uint32_t DWORD;
|
||||
typedef uint64_t DWORDLONG;
|
||||
typedef int32_t BOOL;
|
||||
typedef int32_t LONG; /* `long` is always 32-bit on Windows. */
|
||||
typedef int64_t LONGLONG;
|
||||
typedef uint32_t ULONG; /* `long` is always 32-bit on Windows. */
|
||||
typedef uint64_t ULONGLONG;
|
||||
typedef char16_t WCHAR;
|
||||
typedef unsigned int UINT;
|
||||
typedef char CHAR;
|
||||
typedef uint64_t ULONG_PTR; /* Everything is 64-bit with Cosmopolitan. */
|
||||
typedef ULONG_PTR DWORD_PTR;
|
||||
|
||||
#define TRUE 1
|
||||
#define FALSE 0
|
||||
|
||||
#define WAIT_OBJECT_0 0
|
||||
#define INFINITE 0xFFFFFFFF
|
||||
|
||||
#define CP_UTF8 65001
|
||||
|
||||
#define FAILED(hr) ((hr) < 0)
|
||||
#define SUCCEEDED(hr) ((hr) >= 0)
|
||||
|
||||
#define NOERROR 0
|
||||
#define S_OK 0
|
||||
#define S_FALSE 1
|
||||
#define E_POINTER ((HRESULT)0x80004003)
|
||||
#define E_UNEXPECTED ((HRESULT)0x8000FFFF)
|
||||
#define E_NOTIMPL ((HRESULT)0x80004001)
|
||||
#define E_OUTOFMEMORY ((HRESULT)0x8007000E)
|
||||
#define E_INVALIDARG ((HRESULT)0x80070057)
|
||||
#define E_NOINTERFACE ((HRESULT)0x80004002)
|
||||
#define E_HANDLE ((HRESULT)0x80070006)
|
||||
#define E_ABORT ((HRESULT)0x80004004)
|
||||
#define E_FAIL ((HRESULT)0x80004005)
|
||||
#define E_ACCESSDENIED ((HRESULT)0x80070005)
|
||||
|
||||
#define ERROR_SUCCESS 0
|
||||
#define ERROR_FILE_NOT_FOUND 2
|
||||
#define ERROR_PATH_NOT_FOUND 3
|
||||
#define ERROR_TOO_MANY_OPEN_FILES 4
|
||||
#define ERROR_ACCESS_DENIED 5
|
||||
#define ERROR_NOT_ENOUGH_MEMORY 8
|
||||
#define ERROR_HANDLE_EOF 38
|
||||
#define ERROR_INVALID_PARAMETER 87
|
||||
#define ERROR_DISK_FULL 112
|
||||
#define ERROR_SEM_TIMEOUT 121
|
||||
#define ERROR_NEGATIVE_SEEK 131
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned long Data1;
|
||||
unsigned short Data2;
|
||||
unsigned short Data3;
|
||||
unsigned char Data4[8];
|
||||
} GUID, IID;
|
||||
|
||||
typedef int64_t LARGE_INTEGER;
|
||||
|
||||
|
||||
|
||||
#define HKEY_LOCAL_MACHINE ((HKEY)(ULONG_PTR)(0x80000002))
|
||||
#define KEY_READ 0x00020019
|
||||
|
||||
|
||||
static HANDLE CreateEventA(struct NtSecurityAttributes* lpEventAttributes, bool32 bManualReset, bool32 bInitialState, const char* lpName)
|
||||
{
|
||||
assert(lpName == NULL); /* If this is ever triggered we'll need to do a ANSI-to-Unicode conversion. */
|
||||
return (HANDLE)CreateEvent(lpEventAttributes, bManualReset, bInitialState, (const char16_t*)lpName);
|
||||
}
|
||||
|
||||
static BOOL IsEqualGUID(const GUID* a, const GUID* b)
|
||||
{
|
||||
return memcmp(a, b, sizeof(GUID)) == 0;
|
||||
}
|
||||
|
||||
#endif /* _WINDOWS_ */
|
||||
@@ -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.
|
||||
miniaudio - v0.11.18 - 2023-08-07
|
||||
miniaudio - v0.11.19 - 2023-11-04
|
||||
|
||||
David Reid - mackron@gmail.com
|
||||
|
||||
@@ -820,7 +820,7 @@ static MA_INLINE void ma_zero_memory_default(void* p, size_t sz)
|
||||
#define ma_abs(x) (((x) > 0) ? (x) : -(x))
|
||||
#define ma_clamp(x, lo, hi) (ma_max(lo, ma_min(x, hi)))
|
||||
#define ma_offset_ptr(p, offset) (((ma_uint8*)(p)) + (offset))
|
||||
#define ma_align(x, a) ((x + (a-1)) & ~(a-1))
|
||||
#define ma_align(x, a) (((x) + ((a)-1)) & ~((a)-1))
|
||||
#define ma_align_64(x) ma_align(x, 8)
|
||||
|
||||
#define ma_buffer_frame_capacity(buffer, channels, format) (sizeof(buffer) / ma_get_bytes_per_sample(format) / (channels))
|
||||
@@ -2221,7 +2221,7 @@ MA_API ma_result ma_log_postv(ma_log* pLog, ma_uint32 level, const char* pFormat
|
||||
/* First try formatting into our fixed sized stack allocated buffer. If this is too small we'll fallback to a heap allocation. */
|
||||
length = vsnprintf(pFormattedMessageStack, sizeof(pFormattedMessageStack), pFormat, args);
|
||||
if (length < 0) {
|
||||
return MA_INVALID_OPERATION; /* An error occured when trying to convert the buffer. */
|
||||
return MA_INVALID_OPERATION; /* An error occurred when trying to convert the buffer. */
|
||||
}
|
||||
|
||||
if ((size_t)length < sizeof(pFormattedMessageStack)) {
|
||||
@@ -4762,7 +4762,15 @@ static void ma_thread_wait__posix(ma_thread* pThread)
|
||||
|
||||
static ma_result ma_mutex_init__posix(ma_mutex* pMutex)
|
||||
{
|
||||
int result = pthread_mutex_init((pthread_mutex_t*)pMutex, NULL);
|
||||
int result;
|
||||
|
||||
if (pMutex == NULL) {
|
||||
return MA_INVALID_ARGS;
|
||||
}
|
||||
|
||||
MA_ZERO_OBJECT(pMutex);
|
||||
|
||||
result = pthread_mutex_init((pthread_mutex_t*)pMutex, NULL);
|
||||
if (result != 0) {
|
||||
return ma_result_from_errno(result);
|
||||
}
|
||||
@@ -7034,7 +7042,7 @@ Timing
|
||||
*******************************************************************************/
|
||||
#if defined(MA_WIN32) && !defined(MA_POSIX)
|
||||
static LARGE_INTEGER g_ma_TimerFrequency; /* <-- Initialized to zero since it's static. */
|
||||
void ma_timer_init(ma_timer* pTimer)
|
||||
static void ma_timer_init(ma_timer* pTimer)
|
||||
{
|
||||
LARGE_INTEGER counter;
|
||||
|
||||
@@ -7046,7 +7054,7 @@ Timing
|
||||
pTimer->counter = counter.QuadPart;
|
||||
}
|
||||
|
||||
double ma_timer_get_time_in_seconds(ma_timer* pTimer)
|
||||
static double ma_timer_get_time_in_seconds(ma_timer* pTimer)
|
||||
{
|
||||
LARGE_INTEGER counter;
|
||||
if (!QueryPerformanceCounter(&counter)) {
|
||||
@@ -7219,30 +7227,36 @@ static void ma_device__on_notification(ma_device_notification notification)
|
||||
}
|
||||
}
|
||||
|
||||
void ma_device__on_notification_started(ma_device* pDevice)
|
||||
static void ma_device__on_notification_started(ma_device* pDevice)
|
||||
{
|
||||
ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_started));
|
||||
}
|
||||
|
||||
void ma_device__on_notification_stopped(ma_device* pDevice)
|
||||
static void ma_device__on_notification_stopped(ma_device* pDevice)
|
||||
{
|
||||
ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_stopped));
|
||||
}
|
||||
|
||||
void ma_device__on_notification_rerouted(ma_device* pDevice)
|
||||
/* Not all platforms support reroute notifications. */
|
||||
#if !defined(MA_EMSCRIPTEN)
|
||||
static void ma_device__on_notification_rerouted(ma_device* pDevice)
|
||||
{
|
||||
ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_rerouted));
|
||||
}
|
||||
#endif
|
||||
|
||||
void ma_device__on_notification_interruption_began(ma_device* pDevice)
|
||||
/* Interruptions are only used on some platforms. */
|
||||
#if defined(MA_APPLE_MOBILE)
|
||||
static void ma_device__on_notification_interruption_began(ma_device* pDevice)
|
||||
{
|
||||
ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_interruption_began));
|
||||
}
|
||||
|
||||
void ma_device__on_notification_interruption_ended(ma_device* pDevice)
|
||||
static void ma_device__on_notification_interruption_ended(ma_device* pDevice)
|
||||
{
|
||||
ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_interruption_ended));
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
static void ma_device__on_data_inner(ma_device* pDevice, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount)
|
||||
@@ -7697,10 +7711,10 @@ static MA_INLINE void ma_device__set_state(ma_device* pDevice, ma_device_state n
|
||||
|
||||
|
||||
#if defined(MA_WIN32)
|
||||
GUID MA_GUID_KSDATAFORMAT_SUBTYPE_PCM = {0x00000001, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};
|
||||
GUID MA_GUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = {0x00000003, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};
|
||||
/*GUID MA_GUID_KSDATAFORMAT_SUBTYPE_ALAW = {0x00000006, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};*/
|
||||
/*GUID MA_GUID_KSDATAFORMAT_SUBTYPE_MULAW = {0x00000007, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};*/
|
||||
static GUID MA_GUID_KSDATAFORMAT_SUBTYPE_PCM = {0x00000001, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};
|
||||
static GUID MA_GUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = {0x00000003, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};
|
||||
/*static GUID MA_GUID_KSDATAFORMAT_SUBTYPE_ALAW = {0x00000006, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};*/
|
||||
/*static GUID MA_GUID_KSDATAFORMAT_SUBTYPE_MULAW = {0x00000007, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};*/
|
||||
#endif
|
||||
|
||||
|
||||
@@ -11852,7 +11866,7 @@ static ma_result ma_device_read__wasapi(ma_device* pDevice, void* pFrames, ma_ui
|
||||
|
||||
/* At this point we should be able to loop back to the start of the loop and try retrieving a data buffer again. */
|
||||
} else {
|
||||
/* An error occured and we need to abort. */
|
||||
/* An error occurred and we need to abort. */
|
||||
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from capture device in preparation for reading from the device. HRESULT = %d. Stopping device.\n", (int)hr);
|
||||
result = ma_result_from_HRESULT(hr);
|
||||
break;
|
||||
@@ -23416,7 +23430,7 @@ static ma_result ma_context_init__coreaudio(ma_context* pContext, const ma_conte
|
||||
#endif
|
||||
|
||||
#if !defined(MA_NO_RUNTIME_LINKING) && !defined(MA_APPLE_MOBILE)
|
||||
pContext->coreaudio.hCoreFoundation = ma_dlopen(ma_context_get_log(pContext), "CoreFoundation.framework/CoreFoundation");
|
||||
pContext->coreaudio.hCoreFoundation = ma_dlopen(ma_context_get_log(pContext), "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation");
|
||||
if (pContext->coreaudio.hCoreFoundation == NULL) {
|
||||
return MA_API_NOT_FOUND;
|
||||
}
|
||||
@@ -23425,7 +23439,7 @@ static ma_result ma_context_init__coreaudio(ma_context* pContext, const ma_conte
|
||||
pContext->coreaudio.CFRelease = ma_dlsym(ma_context_get_log(pContext), pContext->coreaudio.hCoreFoundation, "CFRelease");
|
||||
|
||||
|
||||
pContext->coreaudio.hCoreAudio = ma_dlopen(ma_context_get_log(pContext), "CoreAudio.framework/CoreAudio");
|
||||
pContext->coreaudio.hCoreAudio = ma_dlopen(ma_context_get_log(pContext), "/System/Library/Frameworks/CoreAudio.framework/CoreAudio");
|
||||
if (pContext->coreaudio.hCoreAudio == NULL) {
|
||||
ma_dlclose(ma_context_get_log(pContext), pContext->coreaudio.hCoreFoundation);
|
||||
return MA_API_NOT_FOUND;
|
||||
@@ -23443,7 +23457,7 @@ static ma_result ma_context_init__coreaudio(ma_context* pContext, const ma_conte
|
||||
The way it'll work is that it'll first try AudioUnit, and if the required symbols are not present there we'll fall back to
|
||||
AudioToolbox.
|
||||
*/
|
||||
pContext->coreaudio.hAudioUnit = ma_dlopen(ma_context_get_log(pContext), "AudioUnit.framework/AudioUnit");
|
||||
pContext->coreaudio.hAudioUnit = ma_dlopen(ma_context_get_log(pContext), "/System/Library/Frameworks/AudioUnit.framework/AudioUnit");
|
||||
if (pContext->coreaudio.hAudioUnit == NULL) {
|
||||
ma_dlclose(ma_context_get_log(pContext), pContext->coreaudio.hCoreAudio);
|
||||
ma_dlclose(ma_context_get_log(pContext), pContext->coreaudio.hCoreFoundation);
|
||||
@@ -23453,7 +23467,7 @@ static ma_result ma_context_init__coreaudio(ma_context* pContext, const ma_conte
|
||||
if (ma_dlsym(ma_context_get_log(pContext), pContext->coreaudio.hAudioUnit, "AudioComponentFindNext") == NULL) {
|
||||
/* Couldn't find the required symbols in AudioUnit, so fall back to AudioToolbox. */
|
||||
ma_dlclose(ma_context_get_log(pContext), pContext->coreaudio.hAudioUnit);
|
||||
pContext->coreaudio.hAudioUnit = ma_dlopen(ma_context_get_log(pContext), "AudioToolbox.framework/AudioToolbox");
|
||||
pContext->coreaudio.hAudioUnit = ma_dlopen(ma_context_get_log(pContext), "/System/Library/Frameworks/AudioToolbox.framework/AudioToolbox");
|
||||
if (pContext->coreaudio.hAudioUnit == NULL) {
|
||||
ma_dlclose(ma_context_get_log(pContext), pContext->coreaudio.hCoreAudio);
|
||||
ma_dlclose(ma_context_get_log(pContext), pContext->coreaudio.hCoreFoundation);
|
||||
@@ -28538,7 +28552,7 @@ static void ma_audio_worklet_processor_created__webaudio(EMSCRIPTEN_WEBAUDIO_T a
|
||||
|
||||
/* With the audio worklet initialized we can now attach it to the graph. */
|
||||
if (pParameters->pConfig->deviceType == ma_device_type_capture || pParameters->pConfig->deviceType == ma_device_type_duplex) {
|
||||
ma_result attachmentResult = EM_ASM_INT({
|
||||
ma_result attachmentResult = (ma_result)EM_ASM_INT({
|
||||
var getUserMediaResult = 0;
|
||||
var audioWorklet = emscriptenGetAudioObject($0);
|
||||
var audioContext = emscriptenGetAudioObject($1);
|
||||
@@ -28569,7 +28583,7 @@ static void ma_audio_worklet_processor_created__webaudio(EMSCRIPTEN_WEBAUDIO_T a
|
||||
|
||||
/* If it's playback only we can now attach the worklet node to the graph. This has already been done for the duplex case. */
|
||||
if (pParameters->pConfig->deviceType == ma_device_type_playback) {
|
||||
ma_result attachmentResult = EM_ASM_INT({
|
||||
ma_result attachmentResult = (ma_result)EM_ASM_INT({
|
||||
var audioWorklet = emscriptenGetAudioObject($0);
|
||||
var audioContext = emscriptenGetAudioObject($1);
|
||||
audioWorklet.connect(audioContext.destination);
|
||||
@@ -28784,7 +28798,7 @@ static ma_result ma_device_init__webaudio(ma_device* pDevice, const ma_device_co
|
||||
|
||||
/* First thing we need is an AudioContext. */
|
||||
var audioContextOptions = {};
|
||||
if (deviceType == window.miniaudio.device_type.playback) {
|
||||
if (deviceType == window.miniaudio.device_type.playback && sampleRate != 0) {
|
||||
audioContextOptions.sampleRate = sampleRate;
|
||||
}
|
||||
|
||||
@@ -29027,7 +29041,7 @@ static ma_result ma_context_init__webaudio(ma_context* pContext, const ma_contex
|
||||
};
|
||||
|
||||
miniaudio.unlock_event_types = (function(){
|
||||
return ['touchstart', 'touchend', 'click'];
|
||||
return ['touchend', 'click'];
|
||||
})();
|
||||
|
||||
miniaudio.unlock = function() {
|
||||
@@ -29472,6 +29486,11 @@ static ma_thread_result MA_THREADCALL ma_worker_thread(void* pData)
|
||||
ma_device__on_notification_stopped(pDevice);
|
||||
}
|
||||
|
||||
/* If we stopped because the device has been uninitialized, abort now. */
|
||||
if (ma_device_get_state(pDevice) == ma_device_state_uninitialized) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* A function somewhere is waiting for the device to have stopped for real so we need to signal an event to allow it to continue. */
|
||||
ma_device__set_state(pDevice, ma_device_state_stopped);
|
||||
ma_event_signal(&pDevice->stopEvent);
|
||||
@@ -30682,10 +30701,23 @@ MA_API void ma_device_uninit(ma_device* pDevice)
|
||||
return;
|
||||
}
|
||||
|
||||
/* Make sure the device is stopped first. The backends will probably handle this naturally, but I like to do it explicitly for my own sanity. */
|
||||
if (ma_device_is_started(pDevice)) {
|
||||
ma_device_stop(pDevice);
|
||||
/*
|
||||
It's possible for the miniaudio side of the device and the backend to not be in sync due to
|
||||
system-level situations such as the computer being put into sleep mode and the backend not
|
||||
notifying miniaudio of the fact the device has stopped. It's possible for this to result in a
|
||||
deadlock due to miniaudio thinking the device is in a running state, when in fact it's not
|
||||
running at all. For this reason I am no longer explicitly stopping the device. I don't think
|
||||
this should affect anyone in practice since uninitializing the backend will naturally stop the
|
||||
device anyway.
|
||||
*/
|
||||
#if 0
|
||||
{
|
||||
/* Make sure the device is stopped first. The backends will probably handle this naturally, but I like to do it explicitly for my own sanity. */
|
||||
if (ma_device_is_started(pDevice)) {
|
||||
ma_device_stop(pDevice);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Putting the device into an uninitialized state will make the worker thread return. */
|
||||
ma_device__set_state(pDevice, ma_device_state_uninitialized);
|
||||
@@ -41417,7 +41449,7 @@ static ma_result ma_channel_map_apply_mono_in_f32(float* MA_RESTRICT pFramesOut,
|
||||
for (iFrame = 0; iFrame < unrolledFrameCount; iFrame += 1) {
|
||||
__m128 in0 = _mm_set1_ps(pFramesIn[iFrame*2 + 0]);
|
||||
__m128 in1 = _mm_set1_ps(pFramesIn[iFrame*2 + 1]);
|
||||
_mm_storeu_ps(&pFramesOut[iFrame*4 + 0], _mm_shuffle_ps(in1, in0, _MM_SHUFFLE(0, 0, 0, 0)));
|
||||
_mm_storeu_ps(&pFramesOut[iFrame*4 + 0], _mm_shuffle_ps(in0, in1, _MM_SHUFFLE(0, 0, 0, 0)));
|
||||
}
|
||||
|
||||
/* Tail. */
|
||||
@@ -41443,7 +41475,7 @@ static ma_result ma_channel_map_apply_mono_in_f32(float* MA_RESTRICT pFramesOut,
|
||||
__m128 in1 = _mm_set1_ps(pFramesIn[iFrame*2 + 1]);
|
||||
|
||||
_mm_storeu_ps(&pFramesOut[iFrame*12 + 0], in0);
|
||||
_mm_storeu_ps(&pFramesOut[iFrame*12 + 4], _mm_shuffle_ps(in1, in0, _MM_SHUFFLE(0, 0, 0, 0)));
|
||||
_mm_storeu_ps(&pFramesOut[iFrame*12 + 4], _mm_shuffle_ps(in0, in1, _MM_SHUFFLE(0, 0, 0, 0)));
|
||||
_mm_storeu_ps(&pFramesOut[iFrame*12 + 8], in1);
|
||||
}
|
||||
|
||||
@@ -48283,7 +48315,7 @@ extern "C" {
|
||||
#define MA_DR_WAV_XSTRINGIFY(x) MA_DR_WAV_STRINGIFY(x)
|
||||
#define MA_DR_WAV_VERSION_MAJOR 0
|
||||
#define MA_DR_WAV_VERSION_MINOR 13
|
||||
#define MA_DR_WAV_VERSION_REVISION 12
|
||||
#define MA_DR_WAV_VERSION_REVISION 13
|
||||
#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>
|
||||
#define MA_DR_WAVE_FORMAT_PCM 0x1
|
||||
@@ -48703,7 +48735,7 @@ extern "C" {
|
||||
#define MA_DR_FLAC_XSTRINGIFY(x) MA_DR_FLAC_STRINGIFY(x)
|
||||
#define MA_DR_FLAC_VERSION_MAJOR 0
|
||||
#define MA_DR_FLAC_VERSION_MINOR 12
|
||||
#define MA_DR_FLAC_VERSION_REVISION 41
|
||||
#define MA_DR_FLAC_VERSION_REVISION 42
|
||||
#define MA_DR_FLAC_VERSION_STRING MA_DR_FLAC_XSTRINGIFY(MA_DR_FLAC_VERSION_MAJOR) "." MA_DR_FLAC_XSTRINGIFY(MA_DR_FLAC_VERSION_MINOR) "." MA_DR_FLAC_XSTRINGIFY(MA_DR_FLAC_VERSION_REVISION)
|
||||
#include <stddef.h>
|
||||
#if defined(_MSC_VER) && _MSC_VER >= 1700
|
||||
@@ -48990,7 +49022,7 @@ extern "C" {
|
||||
#define MA_DR_MP3_XSTRINGIFY(x) MA_DR_MP3_STRINGIFY(x)
|
||||
#define MA_DR_MP3_VERSION_MAJOR 0
|
||||
#define MA_DR_MP3_VERSION_MINOR 6
|
||||
#define MA_DR_MP3_VERSION_REVISION 37
|
||||
#define MA_DR_MP3_VERSION_REVISION 38
|
||||
#define MA_DR_MP3_VERSION_STRING MA_DR_MP3_XSTRINGIFY(MA_DR_MP3_VERSION_MAJOR) "." MA_DR_MP3_XSTRINGIFY(MA_DR_MP3_VERSION_MINOR) "." MA_DR_MP3_XSTRINGIFY(MA_DR_MP3_VERSION_REVISION)
|
||||
#include <stddef.h>
|
||||
#define MA_DR_MP3_MAX_PCM_FRAMES_PER_MP3_FRAME 1152
|
||||
@@ -53408,7 +53440,7 @@ MA_API ma_result ma_decoder_init_file(const char* pFilePath, const ma_decoder_co
|
||||
/* Probably no implementation for loading from a file path. Use miniaudio's file IO instead. */
|
||||
result = ma_decoder_init_vfs(NULL, pFilePath, pConfig, pDecoder);
|
||||
if (result != MA_SUCCESS) {
|
||||
return MA_SUCCESS;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53558,7 +53590,7 @@ MA_API ma_result ma_decoder_init_file_w(const wchar_t* pFilePath, const ma_decod
|
||||
/* Probably no implementation for loading from a file path. Use miniaudio's file IO instead. */
|
||||
result = ma_decoder_init_vfs_w(NULL, pFilePath, pConfig, pDecoder);
|
||||
if (result != MA_SUCCESS) {
|
||||
return MA_SUCCESS;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57326,7 +57358,7 @@ static ma_result ma_resource_manager_data_buffer_init_ex_internal(ma_resource_ma
|
||||
async = (flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC) != 0;
|
||||
|
||||
/*
|
||||
Fences need to be acquired before doing anything. These must be aquired and released outside of
|
||||
Fences need to be acquired before doing anything. These must be acquired and released outside of
|
||||
the node to ensure there's no holes where ma_fence_wait() could prematurely return before the
|
||||
data buffer has completed initialization.
|
||||
|
||||
@@ -60598,7 +60630,7 @@ MA_API ma_result ma_node_init_preallocated(ma_node_graph* pNodeGraph, const ma_n
|
||||
}
|
||||
|
||||
if (heapLayout.outputBusOffset != MA_SIZE_MAX) {
|
||||
pNodeBase->pOutputBuses = (ma_node_output_bus*)ma_offset_ptr(pHeap, heapLayout.inputBusOffset);
|
||||
pNodeBase->pOutputBuses = (ma_node_output_bus*)ma_offset_ptr(pHeap, heapLayout.outputBusOffset);
|
||||
} else {
|
||||
pNodeBase->pOutputBuses = pNodeBase->_outputBuses;
|
||||
}
|
||||
@@ -61089,11 +61121,11 @@ static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusInde
|
||||
|
||||
/*
|
||||
At this point we know that we are inside our start/stop times. However, we may need to adjust
|
||||
our frame count and output pointer to accomodate since we could be straddling the time period
|
||||
our frame count and output pointer to accommodate since we could be straddling the time period
|
||||
that this function is getting called for.
|
||||
|
||||
It's possible (and likely) that the start time does not line up with the output buffer. We
|
||||
therefore need to offset it by a number of frames to accomodate. The same thing applies for
|
||||
therefore need to offset it by a number of frames to accommodate. The same thing applies for
|
||||
the stop time.
|
||||
*/
|
||||
timeOffsetBeg = (globalTimeBeg < startTime) ? (ma_uint32)(globalTimeEnd - startTime) : 0;
|
||||
@@ -62679,7 +62711,7 @@ static void ma_engine_node_process_pcm_frames__general(ma_engine_node* pEngineNo
|
||||
if (fadeStartOffsetInFrames == (ma_int64)(~(ma_uint64)0)) {
|
||||
fadeStartOffsetInFrames = 0;
|
||||
} else {
|
||||
fadeStartOffsetInFrames -= ma_engine_get_time(pEngineNode->pEngine);
|
||||
fadeStartOffsetInFrames -= ma_engine_get_time_in_pcm_frames(pEngineNode->pEngine);
|
||||
}
|
||||
|
||||
ma_fader_set_fade_ex(&pEngineNode->fader, fadeVolumeBeg, fadeVolumeEnd, fadeLengthInFrames, fadeStartOffsetInFrames);
|
||||
@@ -64116,6 +64148,10 @@ MA_API void ma_engine_listener_get_cone(const ma_engine* pEngine, ma_uint32 list
|
||||
*pOuterGain = 0;
|
||||
}
|
||||
|
||||
if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
ma_spatializer_listener_get_cone(&pEngine->listeners[listenerIndex], pInnerAngleInRadians, pOuterAngleInRadians, pOuterGain);
|
||||
}
|
||||
|
||||
@@ -64685,7 +64721,7 @@ MA_API ma_result ma_sound_stop_with_fade_in_pcm_frames(ma_sound* pSound, ma_uint
|
||||
}
|
||||
|
||||
/* Stopping with a fade out requires us to schedule the stop into the future by the fade length. */
|
||||
ma_sound_set_stop_time_with_fade_in_pcm_frames(pSound, ma_engine_get_time(ma_sound_get_engine(pSound)) + fadeLengthInFrames, fadeLengthInFrames);
|
||||
ma_sound_set_stop_time_with_fade_in_pcm_frames(pSound, ma_engine_get_time_in_pcm_frames(ma_sound_get_engine(pSound)) + fadeLengthInFrames, fadeLengthInFrames);
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
@@ -65058,6 +65094,10 @@ MA_API void ma_sound_get_cone(const ma_sound* pSound, float* pInnerAngleInRadian
|
||||
*pOuterGain = 0;
|
||||
}
|
||||
|
||||
if (pSound == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
ma_spatializer_get_cone(&pSound->engineNode.spatializer, pInnerAngleInRadians, pOuterAngleInRadians, pOuterGain);
|
||||
}
|
||||
|
||||
@@ -65339,6 +65379,8 @@ MA_API ma_result ma_sound_get_data_format(ma_sound* pSound, ma_format* pFormat,
|
||||
|
||||
MA_API ma_result ma_sound_get_cursor_in_pcm_frames(ma_sound* pSound, ma_uint64* pCursor)
|
||||
{
|
||||
ma_uint64 seekTarget;
|
||||
|
||||
if (pSound == NULL) {
|
||||
return MA_INVALID_ARGS;
|
||||
}
|
||||
@@ -65348,7 +65390,13 @@ MA_API ma_result ma_sound_get_cursor_in_pcm_frames(ma_sound* pSound, ma_uint64*
|
||||
return MA_INVALID_OPERATION;
|
||||
}
|
||||
|
||||
return ma_data_source_get_cursor_in_pcm_frames(pSound->pDataSource, pCursor);
|
||||
seekTarget = ma_atomic_load_64(&pSound->seekTarget);
|
||||
if (seekTarget != MA_SEEK_TARGET_NONE) {
|
||||
*pCursor = seekTarget;
|
||||
return MA_SUCCESS;
|
||||
} else {
|
||||
return ma_data_source_get_cursor_in_pcm_frames(pSound->pDataSource, pCursor);
|
||||
}
|
||||
}
|
||||
|
||||
MA_API ma_result ma_sound_get_length_in_pcm_frames(ma_sound* pSound, ma_uint64* pLength)
|
||||
@@ -65367,16 +65415,28 @@ MA_API ma_result ma_sound_get_length_in_pcm_frames(ma_sound* pSound, ma_uint64*
|
||||
|
||||
MA_API ma_result ma_sound_get_cursor_in_seconds(ma_sound* pSound, float* pCursor)
|
||||
{
|
||||
if (pSound == NULL) {
|
||||
return MA_INVALID_ARGS;
|
||||
ma_result result;
|
||||
ma_uint64 cursorInPCMFrames;
|
||||
ma_uint32 sampleRate;
|
||||
|
||||
if (pCursor != NULL) {
|
||||
*pCursor = 0;
|
||||
}
|
||||
|
||||
/* The notion of a cursor is only valid for sounds that are backed by a data source. */
|
||||
if (pSound->pDataSource == NULL) {
|
||||
return MA_INVALID_OPERATION;
|
||||
result = ma_sound_get_cursor_in_pcm_frames(pSound, &cursorInPCMFrames);
|
||||
if (result != MA_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return ma_data_source_get_cursor_in_seconds(pSound->pDataSource, pCursor);
|
||||
result = ma_sound_get_data_format(pSound, NULL, NULL, &sampleRate, NULL, 0);
|
||||
if (result != MA_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
|
||||
/* VC6 does not support division of unsigned 64-bit integers with floating point numbers. Need to use a signed number. This shouldn't effect anything in practice. */
|
||||
*pCursor = (ma_int64)cursorInPCMFrames / (float)sampleRate;
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
MA_API ma_result ma_sound_get_length_in_seconds(ma_sound* pSound, float* pLength)
|
||||
@@ -65775,8 +65835,8 @@ code below please report the bug to the respective repository for the relevant p
|
||||
#define ma_dr_wav_clamp(x, lo, hi) (ma_dr_wav_max((lo), ma_dr_wav_min((hi), (x))))
|
||||
#define ma_dr_wav_offset_ptr(p, offset) (((ma_uint8*)(p)) + (offset))
|
||||
#define MA_DR_WAV_MAX_SIMD_VECTOR_SIZE 32
|
||||
#define MA_DR_WAV_INT64_MIN ((ma_int64)0x80000000 << 32)
|
||||
#define MA_DR_WAV_INT64_MAX ((((ma_int64)0x7FFFFFFF) << 32) | 0xFFFFFFFF)
|
||||
#define MA_DR_WAV_INT64_MIN ((ma_int64) ((ma_uint64)0x80000000 << 32))
|
||||
#define MA_DR_WAV_INT64_MAX ((ma_int64)(((ma_uint64)0x7FFFFFFF << 32) | 0xFFFFFFFF))
|
||||
#if defined(_MSC_VER) && _MSC_VER >= 1400
|
||||
#define MA_DR_WAV_HAS_BYTESWAP16_INTRINSIC
|
||||
#define MA_DR_WAV_HAS_BYTESWAP32_INTRINSIC
|
||||
@@ -70904,7 +70964,7 @@ static MA_INLINE ma_uint32 ma_dr_flac__swap_endian_uint32(ma_uint32 n)
|
||||
#if defined(_MSC_VER) && !defined(__clang__)
|
||||
return _byteswap_ulong(n);
|
||||
#elif defined(__GNUC__) || defined(__clang__)
|
||||
#if defined(MA_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 6) && !defined(MA_64BIT)
|
||||
#if defined(MA_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 6) && !defined(__ARM_ARCH_6M__) && !defined(MA_64BIT)
|
||||
ma_uint32 r;
|
||||
__asm__ __volatile__ (
|
||||
#if defined(MA_64BIT)
|
||||
@@ -71645,7 +71705,7 @@ static MA_INLINE ma_uint32 ma_dr_flac__clz_lzcnt(ma_dr_flac_cache_t x)
|
||||
);
|
||||
return r;
|
||||
}
|
||||
#elif defined(MA_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 5) && !defined(MA_64BIT)
|
||||
#elif defined(MA_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 5) && !defined(__ARM_ARCH_6M__) && !defined(MA_64BIT)
|
||||
{
|
||||
unsigned int r;
|
||||
__asm__ __volatile__ (
|
||||
@@ -74386,7 +74446,7 @@ static ma_bool32 ma_dr_flac__read_and_decode_metadata(ma_dr_flac_read_proc onRea
|
||||
for (;;) {
|
||||
ma_dr_flac_metadata metadata;
|
||||
ma_uint8 isLastBlock = 0;
|
||||
ma_uint8 blockType;
|
||||
ma_uint8 blockType = 0;
|
||||
ma_uint32 blockSize;
|
||||
if (ma_dr_flac__read_and_decode_block_header(onRead, pUserData, &isLastBlock, &blockType, &blockSize) == MA_FALSE) {
|
||||
return MA_FALSE;
|
||||
@@ -78466,7 +78526,7 @@ static int ma_dr_mp3_have_simd(void)
|
||||
#else
|
||||
#define MA_DR_MP3_HAVE_SIMD 0
|
||||
#endif
|
||||
#if defined(__ARM_ARCH) && (__ARM_ARCH >= 6) && !defined(__aarch64__) && !defined(_M_ARM64)
|
||||
#if defined(__ARM_ARCH) && (__ARM_ARCH >= 6) && !defined(__aarch64__) && !defined(_M_ARM64) && !defined(__ARM_ARCH_6M__)
|
||||
#define MA_DR_MP3_HAVE_ARMV6 1
|
||||
static __inline__ __attribute__((always_inline)) ma_int32 ma_dr_mp3_clip_int16_arm(ma_int32 a)
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
miniaudio - v0.11.18 - 2023-08-07
|
||||
miniaudio - v0.11.19 - 2023-11-04
|
||||
|
||||
David Reid - mackron@gmail.com
|
||||
|
||||
@@ -20,7 +20,7 @@ extern "C" {
|
||||
|
||||
#define MA_VERSION_MAJOR 0
|
||||
#define MA_VERSION_MINOR 11
|
||||
#define MA_VERSION_REVISION 18
|
||||
#define MA_VERSION_REVISION 19
|
||||
#define MA_VERSION_STRING MA_XSTRINGIFY(MA_VERSION_MAJOR) "." MA_XSTRINGIFY(MA_VERSION_MINOR) "." MA_XSTRINGIFY(MA_VERSION_REVISION)
|
||||
|
||||
#if defined(_MSC_VER) && !defined(__clang__)
|
||||
@@ -571,7 +571,7 @@ typedef enum
|
||||
ma_standard_sample_rate_192000 = 192000,
|
||||
|
||||
ma_standard_sample_rate_16000 = 16000, /* Extreme lows */
|
||||
ma_standard_sample_rate_11025 = 11250,
|
||||
ma_standard_sample_rate_11025 = 11025,
|
||||
ma_standard_sample_rate_8000 = 8000,
|
||||
|
||||
ma_standard_sample_rate_352800 = 352800, /* Extreme highs */
|
||||
@@ -1694,7 +1694,7 @@ MA_API void ma_resampler_uninit(ma_resampler* pResampler, const ma_allocation_ca
|
||||
/*
|
||||
Converts the given input data.
|
||||
|
||||
Both the input and output frames must be in the format specified in the config when the resampler was initilized.
|
||||
Both the input and output frames must be in the format specified in the config when the resampler was initialized.
|
||||
|
||||
On input, [pFrameCountOut] contains the number of output frames to process. On output it contains the number of output frames that
|
||||
were actually processed, which may be less than the requested amount which will happen if there's not enough input data. You can use
|
||||
@@ -3339,7 +3339,7 @@ struct ma_device_config
|
||||
ma_uint32 periods;
|
||||
ma_performance_profile performanceProfile;
|
||||
ma_bool8 noPreSilencedOutputBuffer; /* When set to true, the contents of the output buffer passed into the data callback will be left undefined rather than initialized to silence. */
|
||||
ma_bool8 noClip; /* When set to true, the contents of the output buffer passed into the data callback will be clipped after returning. Only applies when the playback sample format is f32. */
|
||||
ma_bool8 noClip; /* When set to true, the contents of the output buffer passed into the data callback will not be clipped after returning. Only applies when the playback sample format is f32. */
|
||||
ma_bool8 noDisableDenormals; /* Do not disable denormals when firing the data callback. */
|
||||
ma_bool8 noFixedSizedCallback; /* Disables strict fixed-sized data callbacks. Setting this to true will result in the period size being treated only as a hint to the backend. This is an optimization for those who don't need fixed sized callbacks. */
|
||||
ma_device_data_proc dataCallback;
|
||||
@@ -4923,8 +4923,8 @@ then be set directly on the structure. Below are the members of the `ma_device_c
|
||||
callback will write to every sample in the output buffer, or if you are doing your own clearing.
|
||||
|
||||
noClip
|
||||
When set to true, the contents of the output buffer passed into the data callback will be clipped after returning. When set to false (default), the
|
||||
contents of the output buffer are left alone after returning and it will be left up to the backend itself to decide whether or not the clip. This only
|
||||
When set to true, the contents of the output buffer are left alone after returning and it will be left up to the backend itself to decide whether or
|
||||
not to clip. When set to false (default), the contents of the output buffer passed into the data callback will be clipped after returning. This only
|
||||
applies when the playback sample format is f32.
|
||||
|
||||
noDisableDenormals
|
||||
@@ -5437,8 +5437,6 @@ speakers or received from the microphone which can in turn result in de-syncs.
|
||||
|
||||
Do not call this in any callback.
|
||||
|
||||
This will be called implicitly by `ma_device_uninit()`.
|
||||
|
||||
|
||||
See Also
|
||||
--------
|
||||
@@ -6475,7 +6473,7 @@ MA_API ma_noise_config ma_noise_config_init(ma_format format, ma_uint32 channels
|
||||
|
||||
typedef struct
|
||||
{
|
||||
ma_data_source_vtable ds;
|
||||
ma_data_source_base ds;
|
||||
ma_noise_config config;
|
||||
ma_lcg lcg;
|
||||
union
|
||||
@@ -6873,7 +6871,7 @@ typedef struct
|
||||
/*
|
||||
Extended processing callback. This callback is used for effects that process input and output
|
||||
at different rates (i.e. they perform resampling). This is similar to the simple version, only
|
||||
they take two seperate frame counts: one for input, and one for output.
|
||||
they take two separate frame counts: one for input, and one for output.
|
||||
|
||||
On input, `pFrameCountOut` is equal to the capacity of the output buffer for each bus, whereas
|
||||
`pFrameCountIn` will be equal to the number of PCM frames in each of the buffers in `ppFramesIn`.
|
||||
|
||||
@@ -9,7 +9,7 @@ extern "C" {
|
||||
typedef struct
|
||||
{
|
||||
ma_node_config nodeConfig;
|
||||
ma_uint32 channels; /* The number of channels of the source, which will be the same as the output. Must be 1 or 2. The excite bus must always have one channel. */
|
||||
ma_uint32 channels;
|
||||
} ma_channel_combiner_node_config;
|
||||
|
||||
MA_API ma_channel_combiner_node_config ma_channel_combiner_node_config_init(ma_uint32 channels);
|
||||
|
||||
@@ -9,7 +9,7 @@ extern "C" {
|
||||
typedef struct
|
||||
{
|
||||
ma_node_config nodeConfig;
|
||||
ma_uint32 channels; /* The number of channels of the source, which will be the same as the output. Must be 1 or 2. The excite bus must always have one channel. */
|
||||
ma_uint32 channels;
|
||||
} ma_channel_separator_node_config;
|
||||
|
||||
MA_API ma_channel_separator_node_config ma_channel_separator_node_config_init(ma_uint32 channels);
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
This is just a little experiment to explore some ideas for the kind of API that I would build if I
|
||||
was building my own operation system. The name "osaudio" means Operating System Audio. Or maybe you
|
||||
can think of it as Open Source Audio. It's whatever you want it to be.
|
||||
|
||||
The idea behind this project came about after considering the absurd complexity of audio APIs on
|
||||
various platforms after years of working on miniaudio. This project aims to disprove the idea that
|
||||
complete and flexible audio solutions and simple APIs are mutually exclusive and that it's possible
|
||||
to have both. I challenge anybody to prove me wrong.
|
||||
|
||||
In addition to the above, I also wanted to explore some ideas for a different API design to
|
||||
miniaudio. miniaudio uses a callback model for data transfer, whereas osaudio uses a blocking
|
||||
read/write model.
|
||||
|
||||
This project is essentially just a header file with a reference implementation that uses miniaudio
|
||||
under the hood. You can compile this very easily - just compile osaudio_miniaudio.c, and use
|
||||
osaudio.h just like any other header. There are no dependencies for the header, and the miniaudio
|
||||
implementation obviously requires miniaudio. Adjust the include path in osaudio_miniaudio.c if need
|
||||
be.
|
||||
|
||||
See osaudio.h for full documentation. Below is an example to get you started:
|
||||
|
||||
```c
|
||||
#include "osaudio.h"
|
||||
|
||||
...
|
||||
|
||||
osaudio_t audio;
|
||||
osaudio_config_t config;
|
||||
|
||||
osaudio_config_init(&config, OSAUDIO_OUTPUT);
|
||||
config.format = OSAUDIO_FORMAT_F32;
|
||||
config.channels = 2;
|
||||
config.rate = 48000;
|
||||
|
||||
osaudio_open(&audio, &config);
|
||||
|
||||
osaudio_write(audio, myAudioData, frameCount); // <-- This will block until all of the data has been sent to the device.
|
||||
|
||||
osaudio_close(audio);
|
||||
```
|
||||
|
||||
Compare the code above with the likes of other APIs like Core Audio and PipeWire. I challenge
|
||||
anybody to argue their APIs are cleaner and easier to use than this when it comes to simple audio
|
||||
playback.
|
||||
|
||||
If you have any feedback on this I'd be interested to hear it. In particular, I'd really like to
|
||||
hear from people who believe the likes of Core Audio (Apple), PipeWire, PulseAudio or any other
|
||||
audio API actually have good APIs (they don't!) and what makes their's better and/or worse than
|
||||
this project.
|
||||
@@ -0,0 +1,604 @@
|
||||
/*
|
||||
This is a simple API for low-level audio playback and capture. A reference implementation using
|
||||
miniaudio is provided in osaudio.c which can be found alongside this file. Consider all code
|
||||
public domain.
|
||||
|
||||
The idea behind this project came about after considering the absurd complexity of audio APIs on
|
||||
various platforms after years of working on miniaudio. This project aims to disprove the idea that
|
||||
complete and flexible audio solutions and simple APIs are mutually exclusive and that it's possible
|
||||
to have both. The idea of reliability through simplicity is the first and foremost goal of this
|
||||
project. The difference between this project and miniaudio is that this project is designed around
|
||||
the idea of what I would build if I was building an audio API for an operating system, such as at
|
||||
the level of WASAPI or ALSA. A cross-platform and cross-backend library like miniaudio is
|
||||
necessarily different in design, but there are indeed things that I would have done differently if
|
||||
given my time again, some of those ideas of which I'm expressing in this project.
|
||||
|
||||
---
|
||||
|
||||
The concept of low-level audio is simple - you have a device, such as a speaker system or a
|
||||
micrphone system, and then you write or read audio data to/from it. So in the case of playback, you
|
||||
need only write your raw audio data to the device which then emits it from the speakers when it's
|
||||
ready. Likewise, for capture you simply read audio data from the device which is filled with data
|
||||
by the microphone.
|
||||
|
||||
A complete low-level audio solution requires the following:
|
||||
|
||||
1) The ability to enumerate devices that are connected to the system.
|
||||
2) The ability to open and close a connection to a device.
|
||||
3) The ability to start and stop the device.
|
||||
4) The ability to write and read audio data to/from the device.
|
||||
5) The ability to query the device for it's data configuration.
|
||||
6) The ability to notify the application when certain events occur, such as the device being
|
||||
stopped, or rerouted.
|
||||
|
||||
The API presented here aims to meet all of the above requirements. It uses a single-threaded
|
||||
blocking read/write model for data delivery instead of a callback model. This makes it a bit more
|
||||
flexible since it gives the application full control over the audio thread. It might also make it
|
||||
more feasible to use this API on single-threaded systems.
|
||||
|
||||
Device enumeration is achieved with a single function: osaudio_enumerate(). This function returns
|
||||
an array of osaudio_info_t structures which contain information about each device. The array is
|
||||
allocated must be freed with free(). Contained within the osaudio_info_t struct is, most
|
||||
importantly, the device ID, which is used to open a connection to the device, and the name of the
|
||||
device which can be used to display to the user. For advanced users, it also includes information
|
||||
about the device's native data configuration.
|
||||
|
||||
Opening and closing a connection to a device is achieved with osaudio_open() and osaudio_close().
|
||||
An important concept is that of the ability to configure the device. This is achieved with the
|
||||
osaudio_config_t structure which is passed to osaudio_open(). In addition to the ID of the device,
|
||||
this structure includes information about the desired format, channel count and sample rate. You
|
||||
can also configure the latency of the device, or the buffer size, which is specified in frames. A
|
||||
flags member is used for specifying additional options, such as whether or not to disable automatic
|
||||
rerouting. Finally a callback can be specified for notifications. When osaudio_open() returns, the
|
||||
config structure will be filled with the device's actual configuration. You can inspect the channel
|
||||
map from this structure to know how to arrange the channels in your audio data.
|
||||
|
||||
This API uses a blocking write/read model for pushing and pulling data to/from the device. This
|
||||
is done with the osaudio_write() and osaudio_read() functions. These functions will block until
|
||||
the requested number of frames have been processed or the device is drained or flushed with
|
||||
osaudio_drain() or osaudio_flush() respectively. It is from these functions that the device is
|
||||
started. As soon as you start writing data with osaudio_write() or reading data with
|
||||
osaudio_read(), the device will start. When the device is drained of flushed with osaudio_drain()
|
||||
or osaudio_flush(), the device will be stopped. osaudio_drain() will block until the device has
|
||||
been drained, whereas osaudio_flush() will stop playback immediately and return. You can also pause
|
||||
and resume the device with osaudio_pause() and osaudio_resume(). Since reading and writing is
|
||||
blocking, it can be useful to know how many frames can be written/read without blocking. This is
|
||||
achieved with osaudio_get_avail().
|
||||
|
||||
Querying the device's configuration is achieved with osaudio_get_info(). This function will return
|
||||
a pointer to a osaudio_info_t structure which contains information about the device, most
|
||||
importantly it's name and data configuration. The name is important for displaying on a UI, and
|
||||
the data configuration is important for knowing how to format your audio data. The osaudio_info_t
|
||||
structure will contain an array of osaudio_config_t structures. This will contain one entry, which
|
||||
will contain the exact information that was returned in the config structure that was passed to
|
||||
osaudio_open().
|
||||
|
||||
A common requirement is to open a device that represents the operating system's default device.
|
||||
This is done easily by simply passing in NULL for the device ID. Below is an example for opening a
|
||||
default device:
|
||||
|
||||
int result;
|
||||
osaudio_t audio;
|
||||
osaudio_config_t config;
|
||||
|
||||
osaudio_config_init(&config, OSAUDIO_OUTPUT);
|
||||
config.format = OSAUDIO_FORMAT_F32;
|
||||
config.channels = 2;
|
||||
config.rate = 48000;
|
||||
|
||||
result = osaudio_open(&audio, &config);
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
printf("Failed to open device.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
osaudio_close(audio);
|
||||
|
||||
In the above example, the default device is opened for playback (OSAUDIO_OUTPUT). The format is
|
||||
set to 32-bit floating point (OSAUDIO_FORMAT_F32), the channel count is set to stereo (2), and the
|
||||
sample rate is set to 48kHz. The device is then closed when we're done with it.
|
||||
|
||||
If instead we wanted to open a specific device, we can do that by passing in the device ID. Below
|
||||
is an example for how to do this:
|
||||
|
||||
int result;
|
||||
osaudio_t audio;
|
||||
osaudio_config_t config;
|
||||
unsigned int infoCount;
|
||||
osaudio_info_t* info;
|
||||
|
||||
result = osaudio_enumerate(&infoCount, &info);
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
printf("Failed to enumerate devices.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// ... Iterate over the `info` array and find the device you want to open. Use the `direction` member to discriminate between input and output ...
|
||||
|
||||
osaudio_config_init(&config, OSAUDIO_OUTPUT);
|
||||
config.id = &info[indexOfYourChosenDevice].id;
|
||||
config.format = OSAUDIO_FORMAT_F32;
|
||||
config.channels = 2;
|
||||
config.rate = 48000;
|
||||
|
||||
osaudio_open(&audio, &config);
|
||||
|
||||
...
|
||||
|
||||
osaudio_close(audio);
|
||||
free(info); // The pointer returned by osaudio_enumerate() must be freed with free().
|
||||
|
||||
The id structure is just a 256 byte array that uniquely identifies the device. Implementations may
|
||||
have different representations for device IDs, and A 256 byte array should accomodates all
|
||||
device ID representations. Implementations are required to zero-fill unused bytes. The osaudio_id_t
|
||||
structure can be copied which makes it suitable for serialization and deserialization in situations
|
||||
where you may want to save the device ID to permanent storage so it can be stored in a config file.
|
||||
|
||||
Implementations need to do their own data conversion between the device's native data configuration
|
||||
and the requested configuration. In this case, when the format, channels and rate are specified in
|
||||
the config, they should be unchanged when osaudio_open() returns. If this is not possible,
|
||||
osaudio_open() will return OSAUDIO_FORMAT_NOT_SUPPORTED. However, there are cases where it's useful
|
||||
for a program to use the device's native configuration instead of some fixed configuration. This is
|
||||
achieved by setting the format, channels and rate to 0. Below is an example:
|
||||
|
||||
int result;
|
||||
osaudio_t audio;
|
||||
osaudio_config_t config;
|
||||
|
||||
osaudio_config_init(&config, OSAUDIO_OUTPUT);
|
||||
|
||||
result = osaudio_open(&audio, &config);
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
printf("Failed to open device.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// ... `config` will have been updated by osaudio_open() to contain the *actual* format/channels/rate ...
|
||||
|
||||
osaudio_close(audio);
|
||||
|
||||
In addition to the code above, you can explicitly call `osaudio_get_info()` to retrieve the format
|
||||
configuration. If you need to know the native configuration before opening the device, you can use
|
||||
enumeration. The format, channels and rate will be contined in the first item in the configs array.
|
||||
|
||||
The examples above all use playback, but the same applies for capture. The only difference is that
|
||||
the direction is set to OSAUDIO_INPUT instead of OSAUDIO_OUTPUT.
|
||||
|
||||
To output audio from the speakers you need to call osaudio_write(). Likewise, to capture audio from
|
||||
a microphone you need to call osaudio_read(). These functions will block until the requested number
|
||||
of frames have been written or read. The device will start automatically. Below is an example for
|
||||
writing some data to a device:
|
||||
|
||||
int result = osaudio_write(audio, myAudioData, myAudioDataFrameCount);
|
||||
if (result == OSAUDIO_SUCCESS) {
|
||||
printf("Successfully wrote %d frames of audio data.\n", myAudioDataFrameCount);
|
||||
} else {
|
||||
printf("Failed to write audio data.\n");
|
||||
}
|
||||
|
||||
osaudio_write() and osaudio_read() will return OSAUDIO_SUCCESS if the requested number of frames
|
||||
were written or read. You cannot call osaudio_close() while a write or read operation is in
|
||||
progress.
|
||||
|
||||
If you want to write or read audio data without blocking, you can use osaudio_get_avail() to
|
||||
determine how many frames are available for writing or reading. Below is an example:
|
||||
|
||||
unsigned int framesAvailable = osaudio_get_avail(audio);
|
||||
if (result > 0) {
|
||||
printf("There are %d frames available for writing.\n", framesAvailable);
|
||||
} else {
|
||||
printf("There are no frames available for writing.\n");
|
||||
}
|
||||
|
||||
If you want to abort a blocking write or read, you can use osaudio_flush(). This will result in any
|
||||
pending write or read operation being aborted.
|
||||
|
||||
There are several ways of pausing a device. The first is to just drain or flush the device and
|
||||
simply don't do any more read/write operations. A drain and flush will put the device into a
|
||||
stopped state until the next call to either read or write, depending on the device's direction.
|
||||
If, however, this does not suit your requirements, you can use osaudio_pause() and
|
||||
osaudio_resume(). Take note, however, that these functions will result in osaudio_drain() never
|
||||
returning because it'll result in the device being in a stopped state which in turn results in the
|
||||
buffer never being read and therefore never drained.
|
||||
|
||||
Everything is thread safe with a few minor exceptions which has no practical issues for the client:
|
||||
|
||||
* You cannot call any function while osaudio_open() is still in progress.
|
||||
* You cannot call osaudio_close() while any other function is still in progress.
|
||||
* You can only call osaudio_write() and osaudio_read() from one thread at a time.
|
||||
|
||||
None of these issues should be a problem for the client in practice. You won't have a valid
|
||||
osaudio_t object until osaudio_open() has returned. For osaudio_close(), it makes no sense to
|
||||
destroy the object while it's still in use, and doing so would mean the client is using very poor
|
||||
form. For osaudio_write() and osaudio_read(), you wouldn't ever want to call this simultaneously
|
||||
across multiple threads anyway because otherwise you'd end up with garbage audio.
|
||||
|
||||
The rules above only apply when working with a single osaudio_t object. You can have multiple
|
||||
osaudio_t objects open at the same time, and you can call any function on different osaudio_t
|
||||
objects simultaneously from different threads.
|
||||
|
||||
---
|
||||
|
||||
# Feedback
|
||||
|
||||
I'm looking for feedback on the following:
|
||||
|
||||
* Are the supported formats enough? If not, what other formats are needed, and what is the
|
||||
justification for including it? Just because it's the native format on one particular
|
||||
piece of hardware is not enough. Big-endian and little-endian will never be supported. All
|
||||
formats are native-endian.
|
||||
* Are the available channel positions enough? What other positions are needed?
|
||||
* Just some general criticism would be appreciated.
|
||||
|
||||
*/
|
||||
#ifndef osaudio_h
|
||||
#define osaudio_h
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
Support far pointers on relevant platforms (DOS, in particular). The version of this file
|
||||
distributed with an operating system wouldn't need this because they would just have an
|
||||
OS-specific version of this file, but as a reference it's useful to use far pointers here.
|
||||
*/
|
||||
#if defined(__MSDOS__) || defined(_MSDOS) || defined(__DOS__)
|
||||
#define OSAUDIO_FAR far
|
||||
#else
|
||||
#define OSAUDIO_FAR
|
||||
#endif
|
||||
|
||||
typedef struct _osaudio_t* osaudio_t;
|
||||
typedef struct osaudio_config_t osaudio_config_t;
|
||||
typedef struct osaudio_id_t osaudio_id_t;
|
||||
typedef struct osaudio_info_t osaudio_info_t;
|
||||
typedef struct osaudio_notification_t osaudio_notification_t;
|
||||
|
||||
/* Results codes. */
|
||||
typedef int osaudio_result_t;
|
||||
#define OSAUDIO_SUCCESS 0
|
||||
#define OSAUDIO_ERROR -1
|
||||
#define OSAUDIO_INVALID_ARGS -2
|
||||
#define OSAUDIO_INVALID_OPERATION -3
|
||||
#define OSAUDIO_OUT_OF_MEMORY -4
|
||||
#define OSAUDIO_FORMAT_NOT_SUPPORTED -101 /* The requested format is not supported. */
|
||||
#define OSAUDIO_XRUN -102 /* An underrun or overrun occurred. Can be returned by osaudio_read() or osaudio_write(). */
|
||||
#define OSAUDIO_DEVICE_STOPPED -103 /* The device is stopped. Can be returned by osaudio_drain(). It is invalid to call osaudio_drain() on a device that is not running because otherwise it'll get stuck. */
|
||||
|
||||
/* Directions. Cannot be combined. Use separate osaudio_t objects for birectional setups. */
|
||||
typedef int osaudio_direction_t;
|
||||
#define OSAUDIO_INPUT 1
|
||||
#define OSAUDIO_OUTPUT 2
|
||||
|
||||
/* All formats are native endian and interleaved. */
|
||||
typedef int osaudio_format_t;
|
||||
#define OSAUDIO_FORMAT_UNKNOWN 0
|
||||
#define OSAUDIO_FORMAT_F32 1
|
||||
#define OSAUDIO_FORMAT_U8 2
|
||||
#define OSAUDIO_FORMAT_S16 3
|
||||
#define OSAUDIO_FORMAT_S24 4 /* Tightly packed. */
|
||||
#define OSAUDIO_FORMAT_S32 5
|
||||
|
||||
/* Channel positions. */
|
||||
typedef unsigned char osaudio_channel_t;
|
||||
#define OSAUDIO_CHANNEL_NONE 0
|
||||
#define OSAUDIO_CHANNEL_MONO 1
|
||||
#define OSAUDIO_CHANNEL_FL 2
|
||||
#define OSAUDIO_CHANNEL_FR 3
|
||||
#define OSAUDIO_CHANNEL_FC 4
|
||||
#define OSAUDIO_CHANNEL_LFE 5
|
||||
#define OSAUDIO_CHANNEL_BL 6
|
||||
#define OSAUDIO_CHANNEL_BR 7
|
||||
#define OSAUDIO_CHANNEL_FLC 8
|
||||
#define OSAUDIO_CHANNEL_FRC 9
|
||||
#define OSAUDIO_CHANNEL_BC 10
|
||||
#define OSAUDIO_CHANNEL_SL 11
|
||||
#define OSAUDIO_CHANNEL_SR 12
|
||||
#define OSAUDIO_CHANNEL_TC 13
|
||||
#define OSAUDIO_CHANNEL_TFL 14
|
||||
#define OSAUDIO_CHANNEL_TFC 15
|
||||
#define OSAUDIO_CHANNEL_TFR 16
|
||||
#define OSAUDIO_CHANNEL_TBL 17
|
||||
#define OSAUDIO_CHANNEL_TBC 18
|
||||
#define OSAUDIO_CHANNEL_TBR 19
|
||||
#define OSAUDIO_CHANNEL_AUX0 20
|
||||
#define OSAUDIO_CHANNEL_AUX1 21
|
||||
#define OSAUDIO_CHANNEL_AUX2 22
|
||||
#define OSAUDIO_CHANNEL_AUX3 23
|
||||
#define OSAUDIO_CHANNEL_AUX4 24
|
||||
#define OSAUDIO_CHANNEL_AUX5 25
|
||||
#define OSAUDIO_CHANNEL_AUX6 26
|
||||
#define OSAUDIO_CHANNEL_AUX7 27
|
||||
#define OSAUDIO_CHANNEL_AUX8 28
|
||||
#define OSAUDIO_CHANNEL_AUX9 29
|
||||
#define OSAUDIO_CHANNEL_AUX10 30
|
||||
#define OSAUDIO_CHANNEL_AUX11 31
|
||||
#define OSAUDIO_CHANNEL_AUX12 32
|
||||
#define OSAUDIO_CHANNEL_AUX13 33
|
||||
#define OSAUDIO_CHANNEL_AUX14 34
|
||||
#define OSAUDIO_CHANNEL_AUX15 35
|
||||
#define OSAUDIO_CHANNEL_AUX16 36
|
||||
#define OSAUDIO_CHANNEL_AUX17 37
|
||||
#define OSAUDIO_CHANNEL_AUX18 38
|
||||
#define OSAUDIO_CHANNEL_AUX19 39
|
||||
#define OSAUDIO_CHANNEL_AUX20 40
|
||||
#define OSAUDIO_CHANNEL_AUX21 41
|
||||
#define OSAUDIO_CHANNEL_AUX22 42
|
||||
#define OSAUDIO_CHANNEL_AUX23 43
|
||||
#define OSAUDIO_CHANNEL_AUX24 44
|
||||
#define OSAUDIO_CHANNEL_AUX25 45
|
||||
#define OSAUDIO_CHANNEL_AUX26 46
|
||||
#define OSAUDIO_CHANNEL_AUX27 47
|
||||
#define OSAUDIO_CHANNEL_AUX28 48
|
||||
#define OSAUDIO_CHANNEL_AUX29 49
|
||||
#define OSAUDIO_CHANNEL_AUX30 50
|
||||
#define OSAUDIO_CHANNEL_AUX31 51
|
||||
|
||||
/* The maximum number of channels supported. */
|
||||
#define OSAUDIO_MAX_CHANNELS 64
|
||||
|
||||
/* Notification types. */
|
||||
typedef int osaudio_notification_type_t;
|
||||
#define OSAUDIO_NOTIFICATION_STARTED 0 /* The device was started in response to a call to osaudio_write() or osaudio_read(). */
|
||||
#define OSAUDIO_NOTIFICATION_STOPPED 1 /* The device was stopped in response to a call to osaudio_drain() or osaudio_flush(). */
|
||||
#define OSAUDIO_NOTIFICATION_REROUTED 2 /* The device was rerouted. Not all implementations need to support rerouting. */
|
||||
#define OSAUDIO_NOTIFICATION_INTERRUPTION_BEGIN 3 /* The device was interrupted due to something like a phone call. */
|
||||
#define OSAUDIO_NOTIFICATION_INTERRUPTION_END 4 /* The interruption has been ended. */
|
||||
|
||||
/* Flags. */
|
||||
#define OSAUDIO_FLAG_NO_REROUTING 1 /* When set, will tell the implementation to disable automatic rerouting if possible. This is a hint and may be ignored by the implementation. */
|
||||
#define OSAUDIO_FLAG_REPORT_XRUN 2 /* When set, will tell the implementation to report underruns and overruns via osaudio_write() and osaudio_read() by aborting and returning OSAUDIO_XRUN. */
|
||||
|
||||
struct osaudio_notification_t
|
||||
{
|
||||
osaudio_notification_type_t type; /* OSAUDIO_NOTIFICATION_* */
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
int _unused;
|
||||
} started;
|
||||
struct
|
||||
{
|
||||
int _unused;
|
||||
} stopped;
|
||||
struct
|
||||
{
|
||||
int _unused;
|
||||
} rerouted;
|
||||
struct
|
||||
{
|
||||
int _unused;
|
||||
} interruption;
|
||||
} data;
|
||||
};
|
||||
|
||||
struct osaudio_id_t
|
||||
{
|
||||
char data[256];
|
||||
};
|
||||
|
||||
struct osaudio_config_t
|
||||
{
|
||||
osaudio_id_t* device_id; /* Set to NULL to use default device. When non-null, automatic routing will be disabled. */
|
||||
osaudio_direction_t direction; /* OSAUDIO_INPUT or OSAUDIO_OUTPUT. Cannot be combined. Use separate osaudio_t objects for bidirectional setups. */
|
||||
osaudio_format_t format; /* OSAUDIO_FORMAT_* */
|
||||
unsigned int channels; /* Number of channels. */
|
||||
unsigned int rate; /* Sample rate in seconds. */
|
||||
osaudio_channel_t channel_map[OSAUDIO_MAX_CHANNELS]; /* Leave all items set to 0 for defaults. */
|
||||
unsigned int buffer_size; /* In frames. Set to 0 to use the system default. */
|
||||
unsigned int flags; /* A combination of OSAUDIO_FLAG_* */
|
||||
void (* notification)(void* user_data, const osaudio_notification_t* notification); /* Called when some kind of event occurs, such as a device being closed. Never called from the audio thread. */
|
||||
void* user_data; /* Passed to notification(). */
|
||||
};
|
||||
|
||||
struct osaudio_info_t
|
||||
{
|
||||
osaudio_id_t id;
|
||||
char name[256];
|
||||
osaudio_direction_t direction; /* OSAUDIO_INPUT or OSAUDIO_OUTPUT. */
|
||||
unsigned int config_count;
|
||||
osaudio_config_t* configs;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
Enumerates the available devices.
|
||||
|
||||
On output, `count` will contain the number of items in the `info` array. The array must be freed
|
||||
with free() when it's no longer needed.
|
||||
|
||||
Use the `direction` member to discriminate between input and output devices. Below is an example:
|
||||
|
||||
unsigned int count;
|
||||
osaudio_info_t* info;
|
||||
osaudio_enumerate(&count, &info);
|
||||
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (info[i].direction == OSAUDIO_OUTPUT) {
|
||||
printf("Output device: %s\n", info[i].name);
|
||||
} else {
|
||||
printf("Input device: %s\n", info[i].name);
|
||||
}
|
||||
}
|
||||
|
||||
You can use the `id` member to open a specific device with osaudio_open(). You do not need to do
|
||||
device enumeration if you only want to open the default device.
|
||||
*/
|
||||
osaudio_result_t osaudio_enumerate(unsigned int* count, osaudio_info_t** info);
|
||||
|
||||
/*
|
||||
Initializes a default config.
|
||||
|
||||
The config object will be cleared to zero, with the direction set to `direction`. This will result
|
||||
in a configuration that uses the device's native format, channels and rate.
|
||||
|
||||
osaudio_config_t is a transparent struct. Just set the relevant fields to the desired values after
|
||||
calling this function. Example:
|
||||
|
||||
osaudio_config_t config;
|
||||
osaudio_config_init(&config, OSAUDIO_OUTPUT);
|
||||
config.format = OSAUDIO_FORMAT_F32;
|
||||
config.channels = 2;
|
||||
config.rate = 48000;
|
||||
*/
|
||||
void osaudio_config_init(osaudio_config_t* config, osaudio_direction_t direction);
|
||||
|
||||
/*
|
||||
Opens a connection to a device.
|
||||
|
||||
On input, config must be filled with the desired configuration. On output, it will be filled with
|
||||
the actual configuration.
|
||||
|
||||
Initialize the config with osaudio_config_init() and then fill in the desired configuration. Below
|
||||
is an example:
|
||||
|
||||
osaudio_config_t config;
|
||||
osaudio_config_init(&config, OSAUDIO_OUTPUT);
|
||||
config.format = OSAUDIO_FORMAT_F32;
|
||||
config.channels = 2;
|
||||
config.rate = 48000;
|
||||
|
||||
When the format, channels or rate are left at their default values, or set to 0 (or
|
||||
OSAUDIO_FORMAT_UNKNOWN for format), the native format, channels or rate will use the device's
|
||||
native configuration:
|
||||
|
||||
osaudio_config_t config;
|
||||
osaudio_config_init(&config, OSAUDIO_OUTPUT);
|
||||
config.format = OSAUDIO_FORMAT_UNKNOWN;
|
||||
config.channels = 0;
|
||||
config.rate = 0;
|
||||
|
||||
The code above is equivalent to this:
|
||||
|
||||
osaudio_config_t config;
|
||||
osaudio_config_init(&config, OSAUDIO_OUTPUT);
|
||||
|
||||
On output the config will be filled with the actual configuration. The implementation will perform
|
||||
any necessary data conversion between the requested data configuration and the device's native
|
||||
configuration. If it cannot, the function will return a OSAUDIO_FORMAT_NOT_SUPPORTED error. In this
|
||||
case the caller can decide to reinitialize the device to use it's native configuration and do it's
|
||||
own data conversion, or abort if it cannot do so. Use the channel map to determine the ordering of
|
||||
your channels. Automatic channel map conversion is not performed - that must be done manually by
|
||||
the caller when transfering data to/from the device.
|
||||
|
||||
Close the device with osaudio_close().
|
||||
|
||||
Returns 0 on success, any other error code on failure.
|
||||
*/
|
||||
osaudio_result_t osaudio_open(osaudio_t* audio, osaudio_config_t* config);
|
||||
|
||||
/*
|
||||
Closes a connection to a device.
|
||||
|
||||
As soon as this function is called, the device should be considered invalid and unsuable. Do not
|
||||
attempt to use the audio object once this function has been called.
|
||||
|
||||
It's invalid to call this while any other function is still running. You can use osaudio_flush() to
|
||||
quickly abort any pending writes or reads. You can also use osaudio_drain() to wait for all pending
|
||||
writes or reads to complete.
|
||||
|
||||
Returns 0 on success, < 0 on failure.
|
||||
*/
|
||||
osaudio_result_t osaudio_close(osaudio_t audio);
|
||||
|
||||
/*
|
||||
Writes audio data to the device.
|
||||
|
||||
This will block until all data has been written or the device is closed.
|
||||
|
||||
You can only write from a single thread at any given time. If you want to write from multiple
|
||||
threads, you need to use your own synchronization mechanism.
|
||||
|
||||
This will automatically start the device if frame_count is > 0 and it's not in a paused state.
|
||||
|
||||
Use osaudio_get_avail() to determine how much data can be written without blocking.
|
||||
|
||||
Returns 0 on success, < 0 on failure.
|
||||
*/
|
||||
osaudio_result_t osaudio_write(osaudio_t audio, const void OSAUDIO_FAR* data, unsigned int frame_count);
|
||||
|
||||
/*
|
||||
Reads audio data from the device.
|
||||
|
||||
This will block until the requested number of frames has been read or the device is closed.
|
||||
|
||||
You can only read from a single thread at any given time. If you want to read from multiple
|
||||
threads, you need to use your own synchronization mechanism.
|
||||
|
||||
This will automatically start the device if frame_count is > 0 and it's not in a paused state.
|
||||
|
||||
Use osaudio_get_avail() to determine how much data can be read without blocking.
|
||||
|
||||
Returns 0 on success, < 0 on failure.
|
||||
*/
|
||||
osaudio_result_t osaudio_read(osaudio_t audio, void OSAUDIO_FAR* data, unsigned int frame_count);
|
||||
|
||||
/*
|
||||
Drains the device.
|
||||
|
||||
This will block until all pending reads or writes have completed.
|
||||
|
||||
If after calling this function another call to osaudio_write() or osaudio_read() is made, the
|
||||
device will be resumed like normal.
|
||||
|
||||
It is invalid to call this while the device is paused.
|
||||
|
||||
Returns 0 on success, < 0 on failure.
|
||||
*/
|
||||
osaudio_result_t osaudio_drain(osaudio_t audio);
|
||||
|
||||
/*
|
||||
Flushes the device.
|
||||
|
||||
This will immediately flush any pending reads or writes. It will not block. Any in-progress reads
|
||||
or writes will return immediately.
|
||||
|
||||
If after calling this function another thread starts reading or writing, the device will be resumed
|
||||
like normal.
|
||||
|
||||
Returns 0 on success, < 0 on failure.
|
||||
*/
|
||||
osaudio_result_t osaudio_flush(osaudio_t audio);
|
||||
|
||||
/*
|
||||
Pauses or resumes the device.
|
||||
|
||||
Pausing a device will trigger a OSAUDIO_NOTIFICATION_STOPPED notification. Resuming a device will
|
||||
trigger a OSAUDIO_NOTIFICATION_STARTED notification.
|
||||
|
||||
Returns 0 on success, < 0 on failure.
|
||||
*/
|
||||
osaudio_result_t osaudio_pause(osaudio_t audio);
|
||||
|
||||
/*
|
||||
Resumes the device.
|
||||
|
||||
Returns 0 on success, < 0 on failure.
|
||||
*/
|
||||
osaudio_result_t osaudio_resume(osaudio_t audio);
|
||||
|
||||
/*
|
||||
Returns the number of frames that can be read or written without blocking.
|
||||
*/
|
||||
unsigned int osaudio_get_avail(osaudio_t audio);
|
||||
|
||||
/*
|
||||
Gets information about the device.
|
||||
|
||||
There will be one item in the configs array which will contain the device's current configuration,
|
||||
the contents of which will match that of the config that was returned by osaudio_open().
|
||||
|
||||
Returns NULL on failure. Do not free the returned pointer. It's up to the implementation to manage
|
||||
the meory of this object.
|
||||
*/
|
||||
const osaudio_info_t* osaudio_get_info(osaudio_t audio);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* osaudio_h */
|
||||
@@ -0,0 +1,948 @@
|
||||
/*
|
||||
Consider this a reference implementation of osaudio. It uses miniaudio under the hood. You can add
|
||||
this file directly to your source tree, but you may need to update the miniaudio path.
|
||||
|
||||
This will use a mutex in osaudio_read() and osaudio_write(). It's a low-contention lock that's only
|
||||
used for the purpose of osaudio_drain(), but it's still a lock nonetheless. I'm not worrying about
|
||||
this too much right now because this is just an example implementation, but I might improve on this
|
||||
at a later date.
|
||||
*/
|
||||
#ifndef osaudio_miniaudio_c
|
||||
#define osaudio_miniaudio_c
|
||||
|
||||
#include "osaudio.h"
|
||||
|
||||
/*
|
||||
If you would rather define your own implementation of miniaudio, define OSAUDIO_NO_MINIAUDIO_IMPLEMENTATION. If you do this,
|
||||
you need to make sure you include the implmeentation before osaudio.c. This would only really be useful if you are wanting
|
||||
to do a unity build which uses other parts of miniaudio that this file is currently excluding.
|
||||
*/
|
||||
#ifndef OSAUDIO_NO_MINIAUDIO_IMPLEMENTATION
|
||||
#define MA_API static
|
||||
#define MA_NO_DECODING
|
||||
#define MA_NO_ENCODING
|
||||
#define MA_NO_RESOURCE_MANAGER
|
||||
#define MA_NO_NODE_GRAPH
|
||||
#define MA_NO_ENGINE
|
||||
#define MA_NO_GENERATION
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "../../miniaudio.h"
|
||||
#endif
|
||||
|
||||
struct _osaudio_t
|
||||
{
|
||||
ma_device device;
|
||||
osaudio_info_t info;
|
||||
osaudio_config_t config; /* info.configs will point to this. */
|
||||
ma_pcm_rb buffer;
|
||||
ma_semaphore bufferSemaphore; /* The semaphore for controlling access to the buffer. The audio thread will release the semaphore. The read and write functions will wait on it. */
|
||||
ma_atomic_bool32 isActive; /* Starts off as false. Set to true when config.buffer_size data has been written in the case of playback, or as soon as osaudio_read() is called in the case of capture. */
|
||||
ma_atomic_bool32 isPaused;
|
||||
ma_atomic_bool32 isFlushed; /* When set, activation of the device will flush any data that's currently in the buffer. Defaults to false, and will be set to true in osaudio_drain() and osaudio_flush(). */
|
||||
ma_atomic_bool32 xrunDetected; /* Used for detecting when an xrun has occurred and returning from osaudio_read/write() when OSAUDIO_FLAG_REPORT_XRUN is enabled. */
|
||||
ma_spinlock activateLock; /* Used for starting and stopping the device. Needed because two variables control this - isActive and isPaused. */
|
||||
ma_mutex drainLock; /* Used for osaudio_drain(). For mutal exclusion between drain() and read()/write(). Technically results in a lock in read()/write(), but not overthinking that since this is just a reference for now. */
|
||||
};
|
||||
|
||||
|
||||
static ma_bool32 osaudio_g_is_backend_known = MA_FALSE;
|
||||
static ma_backend osaudio_g_backend = ma_backend_wasapi;
|
||||
static ma_context osaudio_g_context;
|
||||
static ma_mutex osaudio_g_context_lock; /* Only used for device enumeration. Created and destroyed with our context. */
|
||||
static ma_uint32 osaudio_g_refcount = 0;
|
||||
static ma_spinlock osaudio_g_lock = 0;
|
||||
|
||||
|
||||
static osaudio_result_t osaudio_result_from_miniaudio(ma_result result)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case MA_SUCCESS: return OSAUDIO_SUCCESS;
|
||||
case MA_INVALID_ARGS: return OSAUDIO_INVALID_ARGS;
|
||||
case MA_INVALID_OPERATION: return OSAUDIO_INVALID_OPERATION;
|
||||
case MA_OUT_OF_MEMORY: return OSAUDIO_OUT_OF_MEMORY;
|
||||
default: return OSAUDIO_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
static ma_format osaudio_format_to_miniaudio(osaudio_format_t format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case OSAUDIO_FORMAT_F32: return ma_format_f32;
|
||||
case OSAUDIO_FORMAT_U8: return ma_format_u8;
|
||||
case OSAUDIO_FORMAT_S16: return ma_format_s16;
|
||||
case OSAUDIO_FORMAT_S24: return ma_format_s24;
|
||||
case OSAUDIO_FORMAT_S32: return ma_format_s32;
|
||||
default: return ma_format_unknown;
|
||||
}
|
||||
}
|
||||
|
||||
static osaudio_format_t osaudio_format_from_miniaudio(ma_format format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case ma_format_f32: return OSAUDIO_FORMAT_F32;
|
||||
case ma_format_u8: return OSAUDIO_FORMAT_U8;
|
||||
case ma_format_s16: return OSAUDIO_FORMAT_S16;
|
||||
case ma_format_s24: return OSAUDIO_FORMAT_S24;
|
||||
case ma_format_s32: return OSAUDIO_FORMAT_S32;
|
||||
default: return OSAUDIO_FORMAT_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static osaudio_channel_t osaudio_channel_from_miniaudio(ma_channel channel)
|
||||
{
|
||||
/* Channel positions between here and miniaudio will remain in sync. */
|
||||
return (osaudio_channel_t)channel;
|
||||
}
|
||||
|
||||
static ma_channel osaudio_channel_to_miniaudio(osaudio_channel_t channel)
|
||||
{
|
||||
/* Channel positions between here and miniaudio will remain in sync. */
|
||||
return (ma_channel)channel;
|
||||
}
|
||||
|
||||
|
||||
static void osaudio_dummy_data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
|
||||
{
|
||||
(void)pDevice;
|
||||
(void)pOutput;
|
||||
(void)pInput;
|
||||
(void)frameCount;
|
||||
}
|
||||
|
||||
static osaudio_result_t osaudio_determine_miniaudio_backend(ma_backend* pBackend, ma_device* pDummyDevice)
|
||||
{
|
||||
ma_device dummyDevice;
|
||||
ma_device_config dummyDeviceConfig;
|
||||
ma_result result;
|
||||
|
||||
/*
|
||||
To do this we initialize a dummy device. We allow the caller to make use of this device as an optimization. This is
|
||||
only used by osaudio_enumerate_devices() because that can make use of the context from the dummy device rather than
|
||||
having to create it's own. pDummyDevice can be null.
|
||||
*/
|
||||
if (pDummyDevice == NULL) {
|
||||
pDummyDevice = &dummyDevice;
|
||||
}
|
||||
|
||||
dummyDeviceConfig = ma_device_config_init(ma_device_type_playback);
|
||||
dummyDeviceConfig.dataCallback = osaudio_dummy_data_callback;
|
||||
|
||||
result = ma_device_init(NULL, &dummyDeviceConfig, pDummyDevice);
|
||||
if (result != MA_SUCCESS || pDummyDevice->pContext->backend == ma_backend_null) {
|
||||
/* Failed to open a default playback device. Try capture. */
|
||||
if (result == MA_SUCCESS) {
|
||||
/* This means we successfully initialize a device, but it's backend is null. It could be that there's no playback devices attached. Try capture. */
|
||||
ma_device_uninit(pDummyDevice);
|
||||
}
|
||||
|
||||
dummyDeviceConfig = ma_device_config_init(ma_device_type_capture);
|
||||
result = ma_device_init(NULL, &dummyDeviceConfig, pDummyDevice);
|
||||
}
|
||||
|
||||
if (result != MA_SUCCESS) {
|
||||
return osaudio_result_from_miniaudio(result);
|
||||
}
|
||||
|
||||
*pBackend = pDummyDevice->pContext->backend;
|
||||
|
||||
/* We're done. */
|
||||
if (pDummyDevice == &dummyDevice) {
|
||||
ma_device_uninit(&dummyDevice);
|
||||
}
|
||||
|
||||
return OSAUDIO_SUCCESS;
|
||||
}
|
||||
|
||||
static osaudio_result_t osaudio_ref_context_nolock()
|
||||
{
|
||||
/* Initialize the global context if necessary. */
|
||||
if (osaudio_g_refcount == 0) {
|
||||
osaudio_result_t result;
|
||||
|
||||
/* If we haven't got a known context, we'll need to determine it here. */
|
||||
if (osaudio_g_is_backend_known == MA_FALSE) {
|
||||
result = osaudio_determine_miniaudio_backend(&osaudio_g_backend, NULL);
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
result = osaudio_result_from_miniaudio(ma_context_init(&osaudio_g_backend, 1, NULL, &osaudio_g_context));
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Need a mutex for device enumeration. */
|
||||
ma_mutex_init(&osaudio_g_context_lock);
|
||||
}
|
||||
|
||||
osaudio_g_refcount += 1;
|
||||
|
||||
return OSAUDIO_SUCCESS;
|
||||
}
|
||||
|
||||
static osaudio_result_t osaudio_unref_context_nolock()
|
||||
{
|
||||
if (osaudio_g_refcount == 0) {
|
||||
return OSAUDIO_INVALID_OPERATION;
|
||||
}
|
||||
|
||||
osaudio_g_refcount -= 1;
|
||||
|
||||
/* Uninitialize the context if we don't have any more references. */
|
||||
if (osaudio_g_refcount == 0) {
|
||||
ma_context_uninit(&osaudio_g_context);
|
||||
ma_mutex_uninit(&osaudio_g_context_lock);
|
||||
}
|
||||
|
||||
return OSAUDIO_SUCCESS;
|
||||
}
|
||||
|
||||
static ma_context* osaudio_ref_context()
|
||||
{
|
||||
osaudio_result_t result;
|
||||
|
||||
ma_spinlock_lock(&osaudio_g_lock);
|
||||
{
|
||||
result = osaudio_ref_context_nolock();
|
||||
}
|
||||
ma_spinlock_unlock(&osaudio_g_lock);
|
||||
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return &osaudio_g_context;
|
||||
}
|
||||
|
||||
static osaudio_result_t osaudio_unref_context()
|
||||
{
|
||||
osaudio_result_t result;
|
||||
|
||||
ma_spinlock_lock(&osaudio_g_lock);
|
||||
{
|
||||
result = osaudio_unref_context_nolock();
|
||||
}
|
||||
ma_spinlock_unlock(&osaudio_g_lock);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static void osaudio_info_from_miniaudio(osaudio_info_t* info, const ma_device_info* infoMA)
|
||||
{
|
||||
unsigned int iNativeConfig;
|
||||
|
||||
/* It just so happens, by absolutely total coincidence, that the size of the ID and name are the same between here and miniaudio. What are the odds?! */
|
||||
memcpy(info->id.data, &infoMA->id, sizeof(info->id.data));
|
||||
memcpy(info->name, infoMA->name, sizeof(info->name));
|
||||
|
||||
info->config_count = (unsigned int)infoMA->nativeDataFormatCount;
|
||||
for (iNativeConfig = 0; iNativeConfig < info->config_count; iNativeConfig += 1) {
|
||||
unsigned int iChannel;
|
||||
|
||||
info->configs[iNativeConfig].device_id = &info->id;
|
||||
info->configs[iNativeConfig].direction = info->direction;
|
||||
info->configs[iNativeConfig].format = osaudio_format_from_miniaudio(infoMA->nativeDataFormats[iNativeConfig].format);
|
||||
info->configs[iNativeConfig].channels = (unsigned int)infoMA->nativeDataFormats[iNativeConfig].channels;
|
||||
info->configs[iNativeConfig].rate = (unsigned int)infoMA->nativeDataFormats[iNativeConfig].sampleRate;
|
||||
|
||||
/* Apparently miniaudio does not report channel positions. I don't know why I'm not doing that. */
|
||||
for (iChannel = 0; iChannel < info->configs[iNativeConfig].channels; iChannel += 1) {
|
||||
info->configs[iNativeConfig].channel_map[iChannel] = OSAUDIO_CHANNEL_NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static osaudio_result_t osaudio_enumerate_nolock(unsigned int* count, osaudio_info_t** info, ma_context* pContext)
|
||||
{
|
||||
osaudio_result_t result;
|
||||
ma_device_info* pPlaybackInfos;
|
||||
ma_uint32 playbackCount;
|
||||
ma_device_info* pCaptureInfos;
|
||||
ma_uint32 captureCount;
|
||||
ma_uint32 iInfo;
|
||||
size_t allocSize;
|
||||
osaudio_info_t* pRunningInfo;
|
||||
osaudio_config_t* pRunningConfig;
|
||||
|
||||
/* We now need to retrieve the device information from miniaudio. */
|
||||
result = osaudio_result_from_miniaudio(ma_context_get_devices(pContext, &pPlaybackInfos, &playbackCount, &pCaptureInfos, &captureCount));
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
osaudio_unref_context();
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
Because the caller needs to free the returned pointer it's important that we keep it all in one allocation. Because there can be
|
||||
a variable number of native configs we'll have to compute the size of the allocation first, and then do a second pass to fill
|
||||
out the data.
|
||||
*/
|
||||
allocSize = ((size_t)playbackCount + (size_t)captureCount) * sizeof(osaudio_info_t);
|
||||
|
||||
/* Now we need to iterate over each playback and capture device and add up the number of native configs. */
|
||||
for (iInfo = 0; iInfo < playbackCount; iInfo += 1) {
|
||||
ma_context_get_device_info(pContext, ma_device_type_playback, &pPlaybackInfos[iInfo].id, &pPlaybackInfos[iInfo]);
|
||||
allocSize += pPlaybackInfos[iInfo].nativeDataFormatCount * sizeof(osaudio_config_t);
|
||||
}
|
||||
for (iInfo = 0; iInfo < captureCount; iInfo += 1) {
|
||||
ma_context_get_device_info(pContext, ma_device_type_capture, &pCaptureInfos[iInfo].id, &pCaptureInfos[iInfo]);
|
||||
allocSize += pCaptureInfos[iInfo].nativeDataFormatCount * sizeof(osaudio_config_t);
|
||||
}
|
||||
|
||||
/* Now that we know the size of the allocation we can allocate it. */
|
||||
*info = (osaudio_info_t*)calloc(1, allocSize);
|
||||
if (*info == NULL) {
|
||||
osaudio_unref_context();
|
||||
return OSAUDIO_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
pRunningInfo = *info;
|
||||
pRunningConfig = (osaudio_config_t*)(((unsigned char*)*info) + (((size_t)playbackCount + (size_t)captureCount) * sizeof(osaudio_info_t)));
|
||||
|
||||
for (iInfo = 0; iInfo < playbackCount; iInfo += 1) {
|
||||
pRunningInfo->direction = OSAUDIO_OUTPUT;
|
||||
pRunningInfo->configs = pRunningConfig;
|
||||
osaudio_info_from_miniaudio(pRunningInfo, &pPlaybackInfos[iInfo]);
|
||||
|
||||
pRunningConfig += pRunningInfo->config_count;
|
||||
pRunningInfo += 1;
|
||||
}
|
||||
|
||||
for (iInfo = 0; iInfo < captureCount; iInfo += 1) {
|
||||
pRunningInfo->direction = OSAUDIO_INPUT;
|
||||
pRunningInfo->configs = pRunningConfig;
|
||||
osaudio_info_from_miniaudio(pRunningInfo, &pPlaybackInfos[iInfo]);
|
||||
|
||||
pRunningConfig += pRunningInfo->config_count;
|
||||
pRunningInfo += 1;
|
||||
}
|
||||
|
||||
*count = (unsigned int)(playbackCount + captureCount);
|
||||
|
||||
return OSAUDIO_SUCCESS;
|
||||
}
|
||||
|
||||
osaudio_result_t osaudio_enumerate(unsigned int* count, osaudio_info_t** info)
|
||||
{
|
||||
osaudio_result_t result;
|
||||
ma_context* pContext = NULL;
|
||||
|
||||
if (count != NULL) {
|
||||
*count = 0;
|
||||
}
|
||||
if (info != NULL) {
|
||||
*info = NULL;
|
||||
}
|
||||
|
||||
if (count == NULL || info == NULL) {
|
||||
return OSAUDIO_INVALID_ARGS;
|
||||
}
|
||||
|
||||
pContext = osaudio_ref_context();
|
||||
if (pContext == NULL) {
|
||||
return OSAUDIO_ERROR;
|
||||
}
|
||||
|
||||
ma_mutex_lock(&osaudio_g_context_lock);
|
||||
{
|
||||
result = osaudio_enumerate_nolock(count, info, pContext);
|
||||
}
|
||||
ma_mutex_unlock(&osaudio_g_context_lock);
|
||||
|
||||
/* We're done. We can now return. */
|
||||
osaudio_unref_context();
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
void osaudio_config_init(osaudio_config_t* config, osaudio_direction_t direction)
|
||||
{
|
||||
if (config == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
memset(config, 0, sizeof(*config));
|
||||
config->direction = direction;
|
||||
}
|
||||
|
||||
|
||||
static void osaudio_data_callback_playback(osaudio_t audio, void* pOutput, ma_uint32 frameCount)
|
||||
{
|
||||
/*
|
||||
If there's content in the buffer, read from it and release the semaphore. There needs to be a whole frameCount chunk
|
||||
in the buffer so we can keep everything in nice clean chunks. When we read from the buffer, we release a semaphore
|
||||
which will allow the main thread to write more data to the buffer.
|
||||
*/
|
||||
ma_uint32 framesToRead;
|
||||
ma_uint32 framesProcessed;
|
||||
void* pBuffer;
|
||||
|
||||
framesToRead = ma_pcm_rb_available_read(&audio->buffer);
|
||||
if (framesToRead > frameCount) {
|
||||
framesToRead = frameCount;
|
||||
}
|
||||
|
||||
framesProcessed = framesToRead;
|
||||
|
||||
/* For robustness we should run this in a loop in case the buffer wraps around. */
|
||||
while (frameCount > 0) {
|
||||
framesToRead = frameCount;
|
||||
|
||||
ma_pcm_rb_acquire_read(&audio->buffer, &framesToRead, &pBuffer);
|
||||
if (framesToRead == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
memcpy(pOutput, pBuffer, framesToRead * ma_get_bytes_per_frame(audio->device.playback.format, audio->device.playback.channels));
|
||||
ma_pcm_rb_commit_read(&audio->buffer, framesToRead);
|
||||
|
||||
frameCount -= framesToRead;
|
||||
pOutput = ((unsigned char*)pOutput) + (framesToRead * ma_get_bytes_per_frame(audio->device.playback.format, audio->device.playback.channels));
|
||||
}
|
||||
|
||||
/* Make sure we release the semaphore if we ended up reading anything. */
|
||||
if (framesProcessed > 0) {
|
||||
ma_semaphore_release(&audio->bufferSemaphore);
|
||||
}
|
||||
|
||||
if (frameCount > 0) {
|
||||
/* Underrun. Pad with silence. */
|
||||
ma_silence_pcm_frames(pOutput, frameCount, audio->device.playback.format, audio->device.playback.channels);
|
||||
ma_atomic_bool32_set(&audio->xrunDetected, MA_TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
static void osaudio_data_callback_capture(osaudio_t audio, const void* pInput, ma_uint32 frameCount)
|
||||
{
|
||||
/* If there's space in the buffer, write to it and release the semaphore. The semaphore is only released on full-chunk boundaries. */
|
||||
ma_uint32 framesToWrite;
|
||||
ma_uint32 framesProcessed;
|
||||
void* pBuffer;
|
||||
|
||||
framesToWrite = ma_pcm_rb_available_write(&audio->buffer);
|
||||
if (framesToWrite > frameCount) {
|
||||
framesToWrite = frameCount;
|
||||
}
|
||||
|
||||
framesProcessed = framesToWrite;
|
||||
|
||||
while (frameCount > 0) {
|
||||
framesToWrite = frameCount;
|
||||
|
||||
ma_pcm_rb_acquire_write(&audio->buffer, &framesToWrite, &pBuffer);
|
||||
if (framesToWrite == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
memcpy(pBuffer, pInput, framesToWrite * ma_get_bytes_per_frame(audio->device.capture.format, audio->device.capture.channels));
|
||||
ma_pcm_rb_commit_write(&audio->buffer, framesToWrite);
|
||||
|
||||
frameCount -= framesToWrite;
|
||||
pInput = ((unsigned char*)pInput) + (framesToWrite * ma_get_bytes_per_frame(audio->device.capture.format, audio->device.capture.channels));
|
||||
}
|
||||
|
||||
/* Make sure we release the semaphore if we ended up reading anything. */
|
||||
if (framesProcessed > 0) {
|
||||
ma_semaphore_release(&audio->bufferSemaphore);
|
||||
}
|
||||
|
||||
if (frameCount > 0) {
|
||||
/* Overrun. Not enough room to move our input data into the buffer. */
|
||||
ma_atomic_bool32_set(&audio->xrunDetected, MA_TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
static void osaudio_nofication_callback(const ma_device_notification* pNotification)
|
||||
{
|
||||
osaudio_t audio = (osaudio_t)pNotification->pDevice->pUserData;
|
||||
|
||||
if (audio->config.notification != NULL) {
|
||||
osaudio_notification_t notification;
|
||||
|
||||
switch (pNotification->type)
|
||||
{
|
||||
case ma_device_notification_type_started:
|
||||
{
|
||||
notification.type = OSAUDIO_NOTIFICATION_STARTED;
|
||||
} break;
|
||||
case ma_device_notification_type_stopped:
|
||||
{
|
||||
notification.type = OSAUDIO_NOTIFICATION_STOPPED;
|
||||
} break;
|
||||
case ma_device_notification_type_rerouted:
|
||||
{
|
||||
notification.type = OSAUDIO_NOTIFICATION_REROUTED;
|
||||
} break;
|
||||
case ma_device_notification_type_interruption_began:
|
||||
{
|
||||
notification.type = OSAUDIO_NOTIFICATION_INTERRUPTION_BEGIN;
|
||||
} break;
|
||||
case ma_device_notification_type_interruption_ended:
|
||||
{
|
||||
notification.type = OSAUDIO_NOTIFICATION_INTERRUPTION_END;
|
||||
} break;
|
||||
}
|
||||
|
||||
audio->config.notification(audio->config.user_data, ¬ification);
|
||||
}
|
||||
}
|
||||
|
||||
static void osaudio_data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
|
||||
{
|
||||
osaudio_t audio = (osaudio_t)pDevice->pUserData;
|
||||
|
||||
if (audio->info.direction == OSAUDIO_OUTPUT) {
|
||||
osaudio_data_callback_playback(audio, pOutput, frameCount);
|
||||
} else {
|
||||
osaudio_data_callback_capture(audio, pInput, frameCount);
|
||||
}
|
||||
}
|
||||
|
||||
osaudio_result_t osaudio_open(osaudio_t* audio, osaudio_config_t* config)
|
||||
{
|
||||
osaudio_result_t result;
|
||||
ma_context* pContext = NULL;
|
||||
ma_device_config deviceConfig;
|
||||
ma_device_info deviceInfo;
|
||||
int periodCount = 2;
|
||||
unsigned int iChannel;
|
||||
|
||||
if (audio != NULL) {
|
||||
*audio = NULL; /* Safety. */
|
||||
}
|
||||
|
||||
if (audio == NULL || config == NULL) {
|
||||
return OSAUDIO_INVALID_ARGS;
|
||||
}
|
||||
|
||||
pContext = osaudio_ref_context(); /* Will be unreferenced in osaudio_close(). */
|
||||
if (pContext == NULL) {
|
||||
return OSAUDIO_ERROR;
|
||||
}
|
||||
|
||||
*audio = (osaudio_t)calloc(1, sizeof(**audio));
|
||||
if (*audio == NULL) {
|
||||
osaudio_unref_context();
|
||||
return OSAUDIO_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
if (config->direction == OSAUDIO_OUTPUT) {
|
||||
deviceConfig = ma_device_config_init(ma_device_type_playback);
|
||||
deviceConfig.playback.format = osaudio_format_to_miniaudio(config->format);
|
||||
deviceConfig.playback.channels = (ma_uint32)config->channels;
|
||||
|
||||
if (config->channel_map[0] != OSAUDIO_CHANNEL_NONE) {
|
||||
for (iChannel = 0; iChannel < config->channels; iChannel += 1) {
|
||||
deviceConfig.playback.pChannelMap[iChannel] = osaudio_channel_to_miniaudio(config->channel_map[iChannel]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
deviceConfig = ma_device_config_init(ma_device_type_capture);
|
||||
deviceConfig.capture.format = osaudio_format_to_miniaudio(config->format);
|
||||
deviceConfig.capture.channels = (ma_uint32)config->channels;
|
||||
|
||||
if (config->channel_map[0] != OSAUDIO_CHANNEL_NONE) {
|
||||
for (iChannel = 0; iChannel < config->channels; iChannel += 1) {
|
||||
deviceConfig.capture.pChannelMap[iChannel] = osaudio_channel_to_miniaudio(config->channel_map[iChannel]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deviceConfig.sampleRate = (ma_uint32)config->rate;
|
||||
|
||||
/* If the buffer size is 0, we'll default to 10ms. */
|
||||
deviceConfig.periodSizeInFrames = (ma_uint32)config->buffer_size;
|
||||
if (deviceConfig.periodSizeInFrames == 0) {
|
||||
deviceConfig.periodSizeInMilliseconds = 10;
|
||||
}
|
||||
|
||||
deviceConfig.dataCallback = osaudio_data_callback;
|
||||
deviceConfig.pUserData = *audio;
|
||||
|
||||
if ((config->flags & OSAUDIO_FLAG_NO_REROUTING) != 0) {
|
||||
deviceConfig.wasapi.noAutoStreamRouting = MA_TRUE;
|
||||
}
|
||||
|
||||
if (config->notification != NULL) {
|
||||
deviceConfig.notificationCallback = osaudio_nofication_callback;
|
||||
}
|
||||
|
||||
result = osaudio_result_from_miniaudio(ma_device_init(pContext, &deviceConfig, &((*audio)->device)));
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
free(*audio);
|
||||
osaudio_unref_context();
|
||||
return result;
|
||||
}
|
||||
|
||||
/* The input config needs to be updated with actual values. */
|
||||
if (config->direction == OSAUDIO_OUTPUT) {
|
||||
config->format = osaudio_format_from_miniaudio((*audio)->device.playback.format);
|
||||
config->channels = (unsigned int)(*audio)->device.playback.channels;
|
||||
|
||||
for (iChannel = 0; iChannel < config->channels; iChannel += 1) {
|
||||
config->channel_map[iChannel] = osaudio_channel_from_miniaudio((*audio)->device.playback.channelMap[iChannel]);
|
||||
}
|
||||
} else {
|
||||
config->format = osaudio_format_from_miniaudio((*audio)->device.capture.format);
|
||||
config->channels = (unsigned int)(*audio)->device.capture.channels;
|
||||
|
||||
for (iChannel = 0; iChannel < config->channels; iChannel += 1) {
|
||||
config->channel_map[iChannel] = osaudio_channel_from_miniaudio((*audio)->device.capture.channelMap[iChannel]);
|
||||
}
|
||||
}
|
||||
|
||||
config->rate = (unsigned int)(*audio)->device.sampleRate;
|
||||
|
||||
if (deviceConfig.periodSizeInFrames == 0) {
|
||||
if (config->direction == OSAUDIO_OUTPUT) {
|
||||
config->buffer_size = (int)(*audio)->device.playback.internalPeriodSizeInFrames;
|
||||
} else {
|
||||
config->buffer_size = (int)(*audio)->device.capture.internalPeriodSizeInFrames;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* The device object needs to have a it's local info built. We can get the ID and name from miniaudio. */
|
||||
result = osaudio_result_from_miniaudio(ma_device_get_info(&(*audio)->device, (*audio)->device.type, &deviceInfo));
|
||||
if (result == MA_SUCCESS) {
|
||||
memcpy((*audio)->info.id.data, &deviceInfo.id, sizeof((*audio)->info.id.data));
|
||||
memcpy((*audio)->info.name, deviceInfo.name, sizeof((*audio)->info.name));
|
||||
}
|
||||
|
||||
(*audio)->info.direction = config->direction;
|
||||
(*audio)->info.config_count = 1;
|
||||
(*audio)->info.configs = &(*audio)->config;
|
||||
(*audio)->config = *config;
|
||||
(*audio)->config.device_id = &(*audio)->info.id;
|
||||
|
||||
|
||||
/* We need a ring buffer. */
|
||||
result = osaudio_result_from_miniaudio(ma_pcm_rb_init(osaudio_format_to_miniaudio(config->format), (ma_uint32)config->channels, (ma_uint32)config->buffer_size * periodCount, NULL, NULL, &(*audio)->buffer));
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
ma_device_uninit(&(*audio)->device);
|
||||
free(*audio);
|
||||
osaudio_unref_context();
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Now we need a semaphore to control access to the ring buffer to to block read/write when necessary. */
|
||||
result = osaudio_result_from_miniaudio(ma_semaphore_init((config->direction == OSAUDIO_OUTPUT) ? periodCount : 0, &(*audio)->bufferSemaphore));
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
ma_pcm_rb_uninit(&(*audio)->buffer);
|
||||
ma_device_uninit(&(*audio)->device);
|
||||
free(*audio);
|
||||
osaudio_unref_context();
|
||||
return result;
|
||||
}
|
||||
|
||||
return OSAUDIO_SUCCESS;
|
||||
}
|
||||
|
||||
osaudio_result_t osaudio_close(osaudio_t audio)
|
||||
{
|
||||
if (audio == NULL) {
|
||||
return OSAUDIO_INVALID_ARGS;
|
||||
}
|
||||
|
||||
ma_device_uninit(&audio->device);
|
||||
osaudio_unref_context();
|
||||
|
||||
return OSAUDIO_SUCCESS;
|
||||
}
|
||||
|
||||
static void osaudio_activate(osaudio_t audio)
|
||||
{
|
||||
ma_spinlock_lock(&audio->activateLock);
|
||||
{
|
||||
if (ma_atomic_bool32_get(&audio->isActive) == MA_FALSE) {
|
||||
ma_atomic_bool32_set(&audio->isActive, MA_TRUE);
|
||||
|
||||
/* If we need to flush, do so now before starting the device. */
|
||||
if (ma_atomic_bool32_get(&audio->isFlushed) == MA_TRUE) {
|
||||
ma_pcm_rb_reset(&audio->buffer);
|
||||
ma_atomic_bool32_set(&audio->isFlushed, MA_FALSE);
|
||||
}
|
||||
|
||||
/* If we're not paused, start the device. */
|
||||
if (ma_atomic_bool32_get(&audio->isPaused) == MA_FALSE) {
|
||||
ma_device_start(&audio->device);
|
||||
}
|
||||
}
|
||||
}
|
||||
ma_spinlock_unlock(&audio->activateLock);
|
||||
}
|
||||
|
||||
osaudio_result_t osaudio_write(osaudio_t audio, const void* data, unsigned int frame_count)
|
||||
{
|
||||
if (audio == NULL) {
|
||||
return OSAUDIO_INVALID_ARGS;
|
||||
}
|
||||
|
||||
ma_mutex_lock(&audio->drainLock);
|
||||
{
|
||||
/* Don't return until everything has been written. */
|
||||
while (frame_count > 0) {
|
||||
ma_uint32 framesToWrite = frame_count;
|
||||
ma_uint32 framesAvailableInBuffer;
|
||||
|
||||
/* There should be enough data available in the buffer now, but check anyway. */
|
||||
framesAvailableInBuffer = ma_pcm_rb_available_write(&audio->buffer);
|
||||
if (framesAvailableInBuffer > 0) {
|
||||
void* pBuffer;
|
||||
|
||||
if (framesToWrite > framesAvailableInBuffer) {
|
||||
framesToWrite = framesAvailableInBuffer;
|
||||
}
|
||||
|
||||
ma_pcm_rb_acquire_write(&audio->buffer, &framesToWrite, &pBuffer);
|
||||
{
|
||||
ma_copy_pcm_frames(pBuffer, data, framesToWrite, audio->device.playback.format, audio->device.playback.channels);
|
||||
}
|
||||
ma_pcm_rb_commit_write(&audio->buffer, framesToWrite);
|
||||
|
||||
frame_count -= (unsigned int)framesToWrite;
|
||||
data = (const void*)((const unsigned char*)data + (framesToWrite * ma_get_bytes_per_frame(audio->device.playback.format, audio->device.playback.channels)));
|
||||
|
||||
if (framesToWrite > 0) {
|
||||
osaudio_activate(audio);
|
||||
}
|
||||
} else {
|
||||
/* If we get here it means there's not enough data available in the buffer. We need to wait for more. */
|
||||
ma_semaphore_wait(&audio->bufferSemaphore);
|
||||
|
||||
/* If we're not active it probably means we've flushed. This write needs to be aborted. */
|
||||
if (ma_atomic_bool32_get(&audio->isActive) == MA_FALSE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ma_mutex_unlock(&audio->drainLock);
|
||||
|
||||
if ((audio->config.flags & OSAUDIO_FLAG_REPORT_XRUN) != 0) {
|
||||
if (ma_atomic_bool32_get(&audio->xrunDetected)) {
|
||||
ma_atomic_bool32_set(&audio->xrunDetected, MA_FALSE);
|
||||
return OSAUDIO_XRUN;
|
||||
}
|
||||
}
|
||||
|
||||
return OSAUDIO_SUCCESS;
|
||||
}
|
||||
|
||||
osaudio_result_t osaudio_read(osaudio_t audio, void* data, unsigned int frame_count)
|
||||
{
|
||||
if (audio == NULL) {
|
||||
return OSAUDIO_INVALID_ARGS;
|
||||
}
|
||||
|
||||
ma_mutex_lock(&audio->drainLock);
|
||||
{
|
||||
while (frame_count > 0) {
|
||||
ma_uint32 framesToRead = frame_count;
|
||||
ma_uint32 framesAvailableInBuffer;
|
||||
|
||||
/* There should be enough data available in the buffer now, but check anyway. */
|
||||
framesAvailableInBuffer = ma_pcm_rb_available_read(&audio->buffer);
|
||||
if (framesAvailableInBuffer > 0) {
|
||||
void* pBuffer;
|
||||
|
||||
if (framesToRead > framesAvailableInBuffer) {
|
||||
framesToRead = framesAvailableInBuffer;
|
||||
}
|
||||
|
||||
ma_pcm_rb_acquire_read(&audio->buffer, &framesToRead, &pBuffer);
|
||||
{
|
||||
ma_copy_pcm_frames(data, pBuffer, framesToRead, audio->device.capture.format, audio->device.capture.channels);
|
||||
}
|
||||
ma_pcm_rb_commit_read(&audio->buffer, framesToRead);
|
||||
|
||||
frame_count -= (unsigned int)framesToRead;
|
||||
data = (void*)((unsigned char*)data + (framesToRead * ma_get_bytes_per_frame(audio->device.capture.format, audio->device.capture.channels)));
|
||||
} else {
|
||||
/* Activate the device from the get go or else we'll never end up capturing anything. */
|
||||
osaudio_activate(audio);
|
||||
|
||||
/* If we get here it means there's not enough data available in the buffer. We need to wait for more. */
|
||||
ma_semaphore_wait(&audio->bufferSemaphore);
|
||||
|
||||
/* If we're not active it probably means we've flushed. This read needs to be aborted. */
|
||||
if (ma_atomic_bool32_get(&audio->isActive) == MA_FALSE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ma_mutex_unlock(&audio->drainLock);
|
||||
|
||||
if ((audio->config.flags & OSAUDIO_FLAG_REPORT_XRUN) != 0) {
|
||||
if (ma_atomic_bool32_get(&audio->xrunDetected)) {
|
||||
ma_atomic_bool32_set(&audio->xrunDetected, MA_FALSE);
|
||||
return OSAUDIO_XRUN;
|
||||
}
|
||||
}
|
||||
|
||||
return OSAUDIO_SUCCESS;
|
||||
}
|
||||
|
||||
osaudio_result_t osaudio_drain(osaudio_t audio)
|
||||
{
|
||||
if (audio == NULL) {
|
||||
return OSAUDIO_INVALID_ARGS;
|
||||
}
|
||||
|
||||
/* This cannot be called while the device is in a paused state. */
|
||||
if (ma_atomic_bool32_get(&audio->isPaused)) {
|
||||
return OSAUDIO_DEVICE_STOPPED;
|
||||
}
|
||||
|
||||
/* For capture we want to stop the device immediately or else we won't ever drain the buffer because miniaudio will be constantly filling it. */
|
||||
if (audio->info.direction == OSAUDIO_INPUT) {
|
||||
ma_device_stop(&audio->device);
|
||||
}
|
||||
|
||||
/*
|
||||
Mark the device as inactive *before* releasing the semaphore. When read/write completes waiting
|
||||
on the semaphore, they'll check this flag and abort.
|
||||
*/
|
||||
ma_atomic_bool32_set(&audio->isActive, MA_FALSE);
|
||||
|
||||
/*
|
||||
Again in capture mode, we need to release the semaphore before waiting for the drain lock because
|
||||
there's a chance read() will be waiting on the semaphore and will need to be woken up in order for
|
||||
it to be given to chance to return.
|
||||
*/
|
||||
if (audio->info.direction == OSAUDIO_INPUT) {
|
||||
ma_semaphore_release(&audio->bufferSemaphore);
|
||||
}
|
||||
|
||||
/* Now we need to wait for any pending reads or writes to complete. */
|
||||
ma_mutex_lock(&audio->drainLock);
|
||||
{
|
||||
/* No processing should be happening on the buffer at this point. Wait for miniaudio to consume the buffer. */
|
||||
while (ma_pcm_rb_available_read(&audio->buffer) > 0) {
|
||||
ma_sleep(1);
|
||||
}
|
||||
|
||||
/*
|
||||
At this point the buffer should be empty, and we shouldn't be in any read or write calls. If
|
||||
it's a playback device, we'll want to stop the device. There's no need to release the semaphore.
|
||||
*/
|
||||
if (audio->info.direction == OSAUDIO_OUTPUT) {
|
||||
ma_device_stop(&audio->device);
|
||||
}
|
||||
}
|
||||
ma_mutex_unlock(&audio->drainLock);
|
||||
|
||||
return OSAUDIO_SUCCESS;
|
||||
}
|
||||
|
||||
osaudio_result_t osaudio_flush(osaudio_t audio)
|
||||
{
|
||||
if (audio == NULL) {
|
||||
return OSAUDIO_INVALID_ARGS;
|
||||
}
|
||||
|
||||
/*
|
||||
First stop the device. This ensures the miniaudio background thread doesn't try modifying the
|
||||
buffer from under us while we're trying to flush it.
|
||||
*/
|
||||
ma_device_stop(&audio->device);
|
||||
|
||||
/*
|
||||
Mark the device as inactive *before* releasing the semaphore. When read/write completes waiting
|
||||
on the semaphore, they'll check this flag and abort.
|
||||
*/
|
||||
ma_atomic_bool32_set(&audio->isActive, MA_FALSE);
|
||||
|
||||
/*
|
||||
Release the semaphore after marking the device as inactive. This needs to be released in order
|
||||
to wakeup osaudio_read() and osaudio_write().
|
||||
*/
|
||||
ma_semaphore_release(&audio->bufferSemaphore);
|
||||
|
||||
/*
|
||||
The buffer should only be modified by osaudio_read() or osaudio_write(), or the miniaudio
|
||||
background thread. Therefore, we don't actually clear the buffer here. Instead we'll clear it
|
||||
in osaudio_activate(), depending on whether or not the below flag is set.
|
||||
*/
|
||||
ma_atomic_bool32_set(&audio->isFlushed, MA_TRUE);
|
||||
|
||||
return OSAUDIO_SUCCESS;
|
||||
}
|
||||
|
||||
osaudio_result_t osaudio_pause(osaudio_t audio)
|
||||
{
|
||||
osaudio_result_t result = OSAUDIO_SUCCESS;
|
||||
|
||||
if (audio == NULL) {
|
||||
return OSAUDIO_INVALID_ARGS;
|
||||
}
|
||||
|
||||
ma_spinlock_lock(&audio->activateLock);
|
||||
{
|
||||
if (ma_atomic_bool32_get(&audio->isPaused) == MA_FALSE) {
|
||||
ma_atomic_bool32_set(&audio->isPaused, MA_TRUE);
|
||||
|
||||
/* No need to stop the device if it's not active. */
|
||||
if (ma_atomic_bool32_get(&audio->isActive)) {
|
||||
result = osaudio_result_from_miniaudio(ma_device_stop(&audio->device));
|
||||
}
|
||||
}
|
||||
}
|
||||
ma_spinlock_unlock(&audio->activateLock);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
osaudio_result_t osaudio_resume(osaudio_t audio)
|
||||
{
|
||||
osaudio_result_t result = OSAUDIO_SUCCESS;
|
||||
|
||||
if (audio == NULL) {
|
||||
return OSAUDIO_INVALID_ARGS;
|
||||
}
|
||||
|
||||
ma_spinlock_lock(&audio->activateLock);
|
||||
{
|
||||
if (ma_atomic_bool32_get(&audio->isPaused)) {
|
||||
ma_atomic_bool32_set(&audio->isPaused, MA_FALSE);
|
||||
|
||||
/* Don't start the device unless it's active. */
|
||||
if (ma_atomic_bool32_get(&audio->isActive)) {
|
||||
result = osaudio_result_from_miniaudio(ma_device_start(&audio->device));
|
||||
}
|
||||
}
|
||||
}
|
||||
ma_spinlock_unlock(&audio->activateLock);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
unsigned int osaudio_get_avail(osaudio_t audio)
|
||||
{
|
||||
if (audio == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (audio->info.direction == OSAUDIO_OUTPUT) {
|
||||
return ma_pcm_rb_available_write(&audio->buffer);
|
||||
} else {
|
||||
return ma_pcm_rb_available_read(&audio->buffer);
|
||||
}
|
||||
}
|
||||
|
||||
const osaudio_info_t* osaudio_get_info(osaudio_t audio)
|
||||
{
|
||||
if (audio == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return &audio->info;
|
||||
}
|
||||
|
||||
#endif /* osaudio_miniaudio_c */
|
||||
@@ -0,0 +1,196 @@
|
||||
#include "../osaudio.h"
|
||||
|
||||
/* This example uses miniaudio for decoding audio files. */
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "../../../miniaudio.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define MODE_PLAYBACK 0
|
||||
#define MODE_CAPTURE 1
|
||||
#define MODE_DUPLEX 2
|
||||
|
||||
void enumerate_devices()
|
||||
{
|
||||
int result;
|
||||
unsigned int iDevice;
|
||||
unsigned int count;
|
||||
osaudio_info_t* pDeviceInfos;
|
||||
|
||||
result = osaudio_enumerate(&count, &pDeviceInfos);
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
printf("Failed to enumerate audio devices.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (iDevice = 0; iDevice < count; iDevice += 1) {
|
||||
printf("(%s) %s\n", (pDeviceInfos[iDevice].direction == OSAUDIO_OUTPUT) ? "Playback" : "Capture", pDeviceInfos[iDevice].name);
|
||||
}
|
||||
|
||||
free(pDeviceInfos);
|
||||
}
|
||||
|
||||
osaudio_t open_device(int direction)
|
||||
{
|
||||
int result;
|
||||
osaudio_t audio;
|
||||
osaudio_config_t config;
|
||||
|
||||
osaudio_config_init(&config, direction);
|
||||
config.format = OSAUDIO_FORMAT_F32;
|
||||
config.channels = 2;
|
||||
config.rate = 48000;
|
||||
config.flags = OSAUDIO_FLAG_REPORT_XRUN;
|
||||
|
||||
result = osaudio_open(&audio, &config);
|
||||
if (result != OSAUDIO_SUCCESS) {
|
||||
printf("Failed to open audio device.\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return audio;
|
||||
}
|
||||
|
||||
void do_playback(int argc, char** argv)
|
||||
{
|
||||
int result;
|
||||
osaudio_t audio;
|
||||
const osaudio_config_t* config;
|
||||
const char* pFilePath = NULL;
|
||||
ma_result resultMA;
|
||||
ma_decoder_config decoderConfig;
|
||||
ma_decoder decoder;
|
||||
|
||||
audio = open_device(OSAUDIO_OUTPUT);
|
||||
if (audio == NULL) {
|
||||
printf("Failed to open audio device.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
config = &osaudio_get_info(audio)->configs[0];
|
||||
|
||||
/* We want to always use f32. */
|
||||
if (config->format == OSAUDIO_FORMAT_F32) {
|
||||
if (argc > 1) {
|
||||
pFilePath = argv[1];
|
||||
|
||||
decoderConfig = ma_decoder_config_init(ma_format_f32, (ma_uint32)config->channels, (ma_uint32)config->rate);
|
||||
|
||||
resultMA = ma_decoder_init_file(pFilePath, &decoderConfig, &decoder);
|
||||
if (resultMA == MA_SUCCESS) {
|
||||
/* Now just keep looping over each sample until we get to the end. */
|
||||
for (;;) {
|
||||
float frames[1024];
|
||||
ma_uint64 frameCount;
|
||||
|
||||
resultMA = ma_decoder_read_pcm_frames(&decoder, frames, ma_countof(frames) / config->channels, &frameCount);
|
||||
if (resultMA != MA_SUCCESS) {
|
||||
break;
|
||||
}
|
||||
|
||||
result = osaudio_write(audio, frames, (unsigned int)frameCount); /* Safe cast. */
|
||||
if (result != OSAUDIO_SUCCESS && result != OSAUDIO_XRUN) {
|
||||
printf("Error writing to audio device.");
|
||||
break;
|
||||
}
|
||||
|
||||
if (result == OSAUDIO_XRUN) {
|
||||
printf("WARNING: An xrun occurred while writing to the playback device.\n");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
printf("Failed to open file: %s\n", pFilePath);
|
||||
}
|
||||
} else {
|
||||
printf("No input file.\n");
|
||||
}
|
||||
} else {
|
||||
printf("Unsupported device format.\n");
|
||||
}
|
||||
|
||||
/* Getting here means we're done and we can tear down. */
|
||||
osaudio_close(audio);
|
||||
}
|
||||
|
||||
void do_duplex()
|
||||
{
|
||||
int result;
|
||||
osaudio_t capture;
|
||||
osaudio_t playback;
|
||||
|
||||
capture = open_device(OSAUDIO_INPUT);
|
||||
if (capture == NULL) {
|
||||
printf("Failed to open capture device.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
playback = open_device(OSAUDIO_OUTPUT);
|
||||
if (playback == NULL) {
|
||||
osaudio_close(capture);
|
||||
printf("Failed to open playback device.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
float frames[1024];
|
||||
unsigned int frameCount;
|
||||
|
||||
frameCount = ma_countof(frames) / osaudio_get_info(capture)->configs[0].channels;
|
||||
|
||||
/* Capture. */
|
||||
result = osaudio_read(capture, frames, frameCount);
|
||||
if (result != OSAUDIO_SUCCESS && result != OSAUDIO_XRUN) {
|
||||
printf("Error reading from capture device.\n");
|
||||
break;
|
||||
}
|
||||
|
||||
if (result == OSAUDIO_XRUN) {
|
||||
printf("WARNING: An xrun occurred while reading from the capture device.\n");
|
||||
}
|
||||
|
||||
|
||||
/* Playback. */
|
||||
result = osaudio_write(playback, frames, frameCount);
|
||||
if (result != OSAUDIO_SUCCESS && result != OSAUDIO_XRUN) {
|
||||
printf("Error writing to playback device.\n");
|
||||
break;
|
||||
}
|
||||
|
||||
if (result == OSAUDIO_XRUN) {
|
||||
printf("WARNING: An xrun occurred while writing to the playback device.\n");
|
||||
}
|
||||
}
|
||||
|
||||
osaudio_close(capture);
|
||||
osaudio_close(playback);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
int mode = MODE_PLAYBACK;
|
||||
int iarg;
|
||||
|
||||
enumerate_devices();
|
||||
|
||||
for (iarg = 0; iarg < argc; iarg += 1) {
|
||||
if (strcmp(argv[iarg], "capture") == 0) {
|
||||
mode = MODE_CAPTURE;
|
||||
} else if (strcmp(argv[iarg], "duplex") == 0) {
|
||||
mode = MODE_DUPLEX;
|
||||
}
|
||||
}
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case MODE_PLAYBACK: do_playback(argc, argv); break;
|
||||
case MODE_CAPTURE: break;
|
||||
case MODE_DUPLEX: do_duplex(); break;
|
||||
}
|
||||
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user