From d2a663a9aa78cd55808a1b4856fa28a73a157767 Mon Sep 17 00:00:00 2001 From: David Reid Date: Fri, 30 Oct 2020 20:05:08 +1000 Subject: [PATCH 01/27] Update revision history. --- miniaudio.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/miniaudio.h b/miniaudio.h index 07fb4606..65b328e1 100644 --- a/miniaudio.h +++ b/miniaudio.h @@ -62784,11 +62784,12 @@ The following miscellaneous changes have also been made. REVISION HISTORY ================ v0.10.21 - TBD - - Add ma_is_backend_enabled() and ma_get_enabled_backends() for retrieving available backends at run-time. + - Add ma_is_backend_enabled() and ma_get_enabled_backends() for retrieving enabled backends at run-time. - WASAPI: Fix a copy and paste bug relating to loopback mode. - Core Audio: Fix a bug when using multiple contexts. - Core Audio: Fix a compilation warning. - Core Audio: Improvements to sample rate selection. + - Core Audio: Improvements to format/channels/rate selection when requesting defaults. - Core Audio: Add notes regarding the Apple notarization process. - Fix some bugs due to null pointer dereferences. From 447e22e09ff5b549561ea628aaa6ccc084d07e31 Mon Sep 17 00:00:00 2001 From: David Reid Date: Fri, 30 Oct 2020 20:14:18 +1000 Subject: [PATCH 02/27] Update dr_libs. --- extras/dr_flac.h | 24 +++-- extras/dr_mp3.h | 9 +- extras/dr_wav.h | 232 ++++++++++++++++++++++++++++++++++------------- miniaudio.h | 190 ++++++++++++++++++++++++++------------ 4 files changed, 323 insertions(+), 132 deletions(-) diff --git a/extras/dr_flac.h b/extras/dr_flac.h index 9359a7ed..b1fd0452 100644 --- a/extras/dr_flac.h +++ b/extras/dr_flac.h @@ -1,6 +1,6 @@ /* FLAC audio decoder. Choice of public domain or MIT-0. See license statements at the end of this file. -dr_flac - v0.12.19 - 2020-08-30 +dr_flac - v0.12.20 - 2020-09-08 David Reid - mackron@gmail.com @@ -232,7 +232,7 @@ extern "C" { #define DRFLAC_VERSION_MAJOR 0 #define DRFLAC_VERSION_MINOR 12 -#define DRFLAC_VERSION_REVISION 19 +#define DRFLAC_VERSION_REVISION 20 #define DRFLAC_VERSION_STRING DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MAJOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MINOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_REVISION) #include /* For size_t. */ @@ -1828,14 +1828,15 @@ static DRFLAC_INLINE drflac_uint64 drflac__swap_endian_uint64(drflac_uint64 n) #error "This compiler does not support the byte swap intrinsic." #endif #else - return ((n & (drflac_uint64)0xFF00000000000000) >> 56) | - ((n & (drflac_uint64)0x00FF000000000000) >> 40) | - ((n & (drflac_uint64)0x0000FF0000000000) >> 24) | - ((n & (drflac_uint64)0x000000FF00000000) >> 8) | - ((n & (drflac_uint64)0x00000000FF000000) << 8) | - ((n & (drflac_uint64)0x0000000000FF0000) << 24) | - ((n & (drflac_uint64)0x000000000000FF00) << 40) | - ((n & (drflac_uint64)0x00000000000000FF) << 56); + /* Weird "<< 32" bitshift is required for C89 because it doesn't support 64-bit constants. Should be optimized out by a good compiler. */ + return ((n & ((drflac_uint64)0xFF000000 << 32)) >> 56) | + ((n & ((drflac_uint64)0x00FF0000 << 32)) >> 40) | + ((n & ((drflac_uint64)0x0000FF00 << 32)) >> 24) | + ((n & ((drflac_uint64)0x000000FF << 32)) >> 8) | + ((n & ((drflac_uint64)0xFF000000 )) << 8) | + ((n & ((drflac_uint64)0x00FF0000 )) << 24) | + ((n & ((drflac_uint64)0x0000FF00 )) << 40) | + ((n & ((drflac_uint64)0x000000FF )) << 56); #endif } @@ -11772,6 +11773,9 @@ DRFLAC_API drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterat /* REVISION HISTORY ================ +v0.12.20 - 2020-09-08 + - Fix a compilation error on older compilers. + v0.12.19 - 2020-08-30 - Fix a bug due to an undefined 32-bit shift. diff --git a/extras/dr_mp3.h b/extras/dr_mp3.h index a8e52e58..d575ca29 100644 --- a/extras/dr_mp3.h +++ b/extras/dr_mp3.h @@ -1,6 +1,6 @@ /* MP3 audio decoder. Choice of public domain or MIT-0. See license statements at the end of this file. -dr_mp3 - v0.6.16 - 2020-08-02 +dr_mp3 - v0.6.17 - 2020-09-28 David Reid - mackron@gmail.com @@ -95,7 +95,7 @@ extern "C" { #define DRMP3_VERSION_MAJOR 0 #define DRMP3_VERSION_MINOR 6 -#define DRMP3_VERSION_REVISION 16 +#define DRMP3_VERSION_REVISION 17 #define DRMP3_VERSION_STRING DRMP3_XSTRINGIFY(DRMP3_VERSION_MAJOR) "." DRMP3_XSTRINGIFY(DRMP3_VERSION_MINOR) "." DRMP3_XSTRINGIFY(DRMP3_VERSION_REVISION) #include /* For size_t. */ @@ -713,6 +713,8 @@ static __inline__ __attribute__((always_inline)) drmp3_int32 drmp3_clip_int16_ar __asm__ ("ssat %0, #16, %1" : "=r"(x) : "r"(a)); return x; } +#else +#define DRMP3_HAVE_ARMV6 0 #endif @@ -4430,6 +4432,9 @@ counts rather than sample counts. /* REVISION HISTORY ================ +v0.6.17 - 2020-09-28 + - Bring up to date with minimp3. + v0.6.16 - 2020-08-02 - Simplify sized types. diff --git a/extras/dr_wav.h b/extras/dr_wav.h index a5289925..f5fec70e 100644 --- a/extras/dr_wav.h +++ b/extras/dr_wav.h @@ -1,6 +1,6 @@ /* WAV audio loader and writer. Choice of public domain or MIT-0. See license statements at the end of this file. -dr_wav - v0.12.10 - 2020-08-24 +dr_wav - v0.12.12 - 2020-09-28 David Reid - mackron@gmail.com @@ -18,7 +18,7 @@ Changes to Chunk Callback dr_wav supports the ability to fire a callback when a chunk is encounted (except for WAVE and FMT chunks). The callback has been updated to include both the container (RIFF or Wave64) and the FMT chunk which contains information about the format of the data in the wave file. -Previously, there was no direct way to determine the container, and therefore no way discriminate against the different IDs in the chunk header (RIFF and +Previously, there was no direct way to determine the container, and therefore no way to discriminate against the different IDs in the chunk header (RIFF and Wave64 containers encode chunk ID's differently). The `container` parameter can be used to know which ID to use. Sometimes it can be useful to know the data format at the time the chunk callback is fired. A pointer to a `drwav_fmt` object is now passed into the chunk @@ -144,7 +144,7 @@ extern "C" { #define DRWAV_VERSION_MAJOR 0 #define DRWAV_VERSION_MINOR 12 -#define DRWAV_VERSION_REVISION 10 +#define DRWAV_VERSION_REVISION 12 #define DRWAV_VERSION_STRING DRWAV_XSTRINGIFY(DRWAV_VERSION_MAJOR) "." DRWAV_XSTRINGIFY(DRWAV_VERSION_MINOR) "." DRWAV_XSTRINGIFY(DRWAV_VERSION_REVISION) #include /* For size_t. */ @@ -298,7 +298,8 @@ typedef enum typedef enum { drwav_container_riff, - drwav_container_w64 + drwav_container_w64, + drwav_container_rf64 } drwav_container; typedef struct @@ -419,8 +420,8 @@ Returns the number of bytes read + seeked. To read data from the chunk, call onRead(), passing in pReadSeekUserData as the first parameter. Do the same for seeking with onSeek(). The return value must be the total number of bytes you have read _plus_ seeked. -Use the `container` argument to discriminate the fields in `pChunkHeader->id`. If the container is `drwav_container_riff` you should use `id.fourcc`, -otherwise you should use `id.guid`. +Use the `container` argument to discriminate the fields in `pChunkHeader->id`. If the container is `drwav_container_riff` or `drwav_container_rf64` you should +use `id.fourcc`, otherwise you should use `id.guid`. The `pFMT` parameter can be used to determine the data format of the wave file. Use `drwav_fmt_get_format()` to get the sample format, which will be one of the `DR_WAVE_FORMAT_*` identifiers. @@ -881,8 +882,8 @@ Helper for initializing a writer which outputs data to a memory buffer. dr_wav will manage the memory allocations, however it is up to the caller to free the data with drwav_free(). -The buffer will remain allocated even after drwav_uninit() is called. Indeed, the buffer should not be -considered valid until after drwav_uninit() has been called anyway. +The buffer will remain allocated even after drwav_uninit() is called. The buffer should not be considered valid +until after drwav_uninit() has been called. */ DRWAV_API drwav_bool32 drwav_init_memory_write(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, const drwav_allocation_callbacks* pAllocationCallbacks); DRWAV_API drwav_bool32 drwav_init_memory_write_sequential(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, const drwav_allocation_callbacks* pAllocationCallbacks); @@ -1236,14 +1237,15 @@ static DRWAV_INLINE drwav_uint64 drwav__bswap64(drwav_uint64 n) #error "This compiler does not support the byte swap intrinsic." #endif #else - return ((n & (drwav_uint64)0xFF00000000000000) >> 56) | - ((n & (drwav_uint64)0x00FF000000000000) >> 40) | - ((n & (drwav_uint64)0x0000FF0000000000) >> 24) | - ((n & (drwav_uint64)0x000000FF00000000) >> 8) | - ((n & (drwav_uint64)0x00000000FF000000) << 8) | - ((n & (drwav_uint64)0x0000000000FF0000) << 24) | - ((n & (drwav_uint64)0x000000000000FF00) << 40) | - ((n & (drwav_uint64)0x00000000000000FF) << 56); + /* Weird "<< 32" bitshift is required for C89 because it doesn't support 64-bit constants. Should be optimized out by a good compiler. */ + return ((n & ((drwav_uint64)0xFF000000 << 32)) >> 56) | + ((n & ((drwav_uint64)0x00FF0000 << 32)) >> 40) | + ((n & ((drwav_uint64)0x0000FF00 << 32)) >> 24) | + ((n & ((drwav_uint64)0x000000FF << 32)) >> 8) | + ((n & ((drwav_uint64)0xFF000000 )) << 8) | + ((n & ((drwav_uint64)0x00FF0000 )) << 24) | + ((n & ((drwav_uint64)0x0000FF00 )) << 40) | + ((n & ((drwav_uint64)0x000000FF )) << 56); #endif } @@ -1537,7 +1539,7 @@ static drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_for static drwav_result drwav__read_chunk_header(drwav_read_proc onRead, void* pUserData, drwav_container container, drwav_uint64* pRunningBytesReadOut, drwav_chunk_header* pHeaderOut) { - if (container == drwav_container_riff) { + if (container == drwav_container_riff || container == drwav_container_rf64) { drwav_uint8 sizeInBytes[4]; if (onRead(pUserData, pHeaderOut->id.fourcc, 4) != 4) { @@ -1629,7 +1631,7 @@ static drwav_bool32 drwav__read_fmt(drwav_read_proc onRead, drwav_seek_proc onSe /* Skip non-fmt chunks. */ - while ((container == drwav_container_riff && !drwav__fourcc_equal(header.id.fourcc, "fmt ")) || (container == drwav_container_w64 && !drwav__guid_equal(header.id.guid, drwavGUID_W64_FMT))) { + while (((container == drwav_container_riff || container == drwav_container_rf64) && !drwav__fourcc_equal(header.id.fourcc, "fmt ")) || (container == drwav_container_w64 && !drwav__guid_equal(header.id.guid, drwavGUID_W64_FMT))) { if (!drwav__seek_forward(onSeek, header.sizeInBytes + header.paddingSize, pUserData)) { return DRWAV_FALSE; } @@ -1643,7 +1645,7 @@ static drwav_bool32 drwav__read_fmt(drwav_read_proc onRead, drwav_seek_proc onSe /* Validation. */ - if (container == drwav_container_riff) { + if (container == drwav_container_riff || container == drwav_container_rf64) { if (!drwav__fourcc_equal(header.id.fourcc, "fmt ")) { return DRWAV_FALSE; } @@ -1817,9 +1819,9 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, drwav_uint8 riff[4]; drwav_fmt fmt; unsigned short translatedFormatTag; - drwav_uint64 sampleCountFromFactChunk; drwav_bool32 foundDataChunk; - drwav_uint64 dataChunkSize; + drwav_uint64 dataChunkSize = 0; /* <-- Important! Don't explicitly set this to 0 anywhere else. Calculation of the size of the data chunk is performed in different paths depending on the container. */ + drwav_uint64 sampleCountFromFactChunk = 0; /* Same as dataChunkSize - make sure this is the only place this is initialized to 0. */ drwav_uint64 chunkSize; cursor = 0; @@ -1852,12 +1854,14 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, return DRWAV_FALSE; } } + } else if (drwav__fourcc_equal(riff, "RF64")) { + pWav->container = drwav_container_rf64; } else { return DRWAV_FALSE; /* Unknown or unsupported container. */ } - if (pWav->container == drwav_container_riff) { + if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64) { drwav_uint8 chunkSizeBytes[4]; drwav_uint8 wave[4]; @@ -1866,8 +1870,14 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, return DRWAV_FALSE; } - if (drwav__bytes_to_u32(chunkSizeBytes) < 36) { - return DRWAV_FALSE; /* Chunk size should always be at least 36 bytes. */ + if (pWav->container == drwav_container_riff) { + if (drwav__bytes_to_u32(chunkSizeBytes) < 36) { + return DRWAV_FALSE; /* Chunk size should always be at least 36 bytes. */ + } + } else { + if (drwav__bytes_to_u32(chunkSizeBytes) != 0xFFFFFFFF) { + return DRWAV_FALSE; /* Chunk size should always be set to -1/0xFFFFFFFF for RF64. The actual size is retrieved later. */ + } } if (drwav__on_read(pWav->onRead, pWav->pUserData, wave, sizeof(wave), &cursor) != sizeof(wave)) { @@ -1900,6 +1910,54 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, } + /* For RF64, the "ds64" chunk must come next, before the "fmt " chunk. */ + if (pWav->container == drwav_container_rf64) { + drwav_uint8 sizeBytes[8]; + drwav_uint64 bytesRemainingInChunk; + drwav_chunk_header header; + drwav_result result = drwav__read_chunk_header(pWav->onRead, pWav->pUserData, pWav->container, &cursor, &header); + if (result != DRWAV_SUCCESS) { + return DRWAV_FALSE; + } + + if (!drwav__fourcc_equal(header.id.fourcc, "ds64")) { + return DRWAV_FALSE; /* Expecting "ds64". */ + } + + bytesRemainingInChunk = header.sizeInBytes + header.paddingSize; + + /* We don't care about the size of the RIFF chunk - skip it. */ + if (!drwav__seek_forward(pWav->onSeek, 8, pWav->pUserData)) { + return DRWAV_FALSE; + } + bytesRemainingInChunk -= 8; + cursor += 8; + + + /* Next 8 bytes is the size of the "data" chunk. */ + if (drwav__on_read(pWav->onRead, pWav->pUserData, sizeBytes, sizeof(sizeBytes), &cursor) != sizeof(sizeBytes)) { + return DRWAV_FALSE; + } + bytesRemainingInChunk -= 8; + dataChunkSize = drwav__bytes_to_u64(sizeBytes); + + + /* Next 8 bytes is the same count which we would usually derived from the FACT chunk if it was available. */ + if (drwav__on_read(pWav->onRead, pWav->pUserData, sizeBytes, sizeof(sizeBytes), &cursor) != sizeof(sizeBytes)) { + return DRWAV_FALSE; + } + bytesRemainingInChunk -= 8; + sampleCountFromFactChunk = drwav__bytes_to_u64(sizeBytes); + + + /* Skip over everything else. */ + if (!drwav__seek_forward(pWav->onSeek, bytesRemainingInChunk, pWav->pUserData)) { + return DRWAV_FALSE; + } + cursor += bytesRemainingInChunk; + } + + /* The next bytes should be the "fmt " chunk. */ if (!drwav__read_fmt(pWav->onRead, pWav->onSeek, pWav->pUserData, pWav->container, &cursor, &fmt)) { return DRWAV_FALSE; /* Failed to read the "fmt " chunk. */ @@ -1921,9 +1979,6 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, } - - sampleCountFromFactChunk = 0; - /* We need to enumerate over each chunk for two reasons: 1) The "data" chunk may not be the next one @@ -1932,7 +1987,6 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, In order to correctly report each chunk back to the client we will need to keep looping until the end of the file. */ foundDataChunk = DRWAV_FALSE; - dataChunkSize = 0; /* The next chunk we care about is the "data" chunk. This is not necessarily the next chunk so we'll need to loop. */ for (;;) @@ -1968,10 +2022,12 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, } chunkSize = header.sizeInBytes; - if (pWav->container == drwav_container_riff) { + if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64) { if (drwav__fourcc_equal(header.id.fourcc, "data")) { foundDataChunk = DRWAV_TRUE; - dataChunkSize = chunkSize; + if (pWav->container != drwav_container_rf64) { /* The data chunk size for RF64 will always be set to 0xFFFFFFFF here. It was set to it's true value earlier. */ + dataChunkSize = chunkSize; + } } } else { if (drwav__guid_equal(header.id.guid, drwavGUID_W64_DATA)) { @@ -2011,7 +2067,7 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, sampleCountFromFactChunk = 0; } } - } else { + } else if (pWav->container == drwav_container_w64) { if (drwav__guid_equal(header.id.guid, drwavGUID_W64_FACT)) { if (drwav__on_read(pWav->onRead, pWav->pUserData, &sampleCountFromFactChunk, 8, &cursor) != 8) { return DRWAV_FALSE; @@ -2022,10 +2078,12 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, pWav->dataChunkDataPos = cursor; } } + } else if (pWav->container == drwav_container_rf64) { + /* We retrieved the sample count from the ds64 chunk earlier so no need to do that here. */ } /* "smpl" chunk. */ - if (pWav->container == drwav_container_riff) { + if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64) { if (drwav__fourcc_equal(header.id.fourcc, "smpl")) { drwav_uint8 smplHeaderData[36]; /* 36 = size of the smpl header section, not including the loop data. */ if (chunkSize >= sizeof(smplHeaderData)) { @@ -2193,13 +2251,12 @@ DRWAV_API drwav_bool32 drwav_init_ex(drwav* pWav, drwav_read_proc onRead, drwav_ static drwav_uint32 drwav__riff_chunk_size_riff(drwav_uint64 dataChunkSize) { - drwav_uint32 dataSubchunkPaddingSize = drwav__chunk_padding_size_riff(dataChunkSize); - - if (dataChunkSize <= (0xFFFFFFFFUL - 36 - dataSubchunkPaddingSize)) { - return 36 + (drwav_uint32)(dataChunkSize + dataSubchunkPaddingSize); - } else { - return 0xFFFFFFFF; + drwav_uint64 chunkSize = 4 + 24 + dataChunkSize + drwav__chunk_padding_size_riff(dataChunkSize); /* 4 = "WAVE". 24 = "fmt " chunk. */ + if (chunkSize > 0xFFFFFFFFUL) { + chunkSize = 0xFFFFFFFFUL; } + + return (drwav_uint32)chunkSize; /* Safe cast due to the clamp above. */ } static drwav_uint32 drwav__data_chunk_size_riff(drwav_uint64 dataChunkSize) @@ -2223,6 +2280,21 @@ static drwav_uint64 drwav__data_chunk_size_w64(drwav_uint64 dataChunkSize) return 24 + dataChunkSize; /* +24 because W64 includes the size of the GUID and size fields. */ } +static drwav_uint64 drwav__riff_chunk_size_rf64(drwav_uint64 dataChunkSize) +{ + drwav_uint64 chunkSize = 4 + 36 + 24 + dataChunkSize + drwav__chunk_padding_size_riff(dataChunkSize); /* 4 = "WAVE". 36 = "ds64" chunk. 24 = "fmt " chunk. */ + if (chunkSize > 0xFFFFFFFFUL) { + chunkSize = 0xFFFFFFFFUL; + } + + return chunkSize; +} + +static drwav_uint64 drwav__data_chunk_size_rf64(drwav_uint64 dataChunkSize) +{ + return dataChunkSize; +} + static size_t drwav__write(drwav* pWav, const void* pData, size_t dataSize) { @@ -2342,23 +2414,42 @@ static drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_for /* "RIFF" chunk. */ if (pFormat->container == drwav_container_riff) { - drwav_uint32 chunkSizeRIFF = 36 + (drwav_uint32)initialDataChunkSize; /* +36 = "RIFF"+[RIFF Chunk Size]+"WAVE" + [sizeof "fmt " chunk] */ + drwav_uint32 chunkSizeRIFF = 28 + (drwav_uint32)initialDataChunkSize; /* +28 = "WAVE" + [sizeof "fmt " chunk] */ runningPos += drwav__write(pWav, "RIFF", 4); runningPos += drwav__write_u32ne_to_le(pWav, chunkSizeRIFF); runningPos += drwav__write(pWav, "WAVE", 4); - } else { - drwav_uint64 chunkSizeRIFF = 80 + 24 + initialDataChunkSize; /* +24 because W64 includes the size of the GUID and size fields. */ + } else if (pFormat->container == drwav_container_w64) { + drwav_uint64 chunkSizeRIFF = 80 + 24 + initialDataChunkSize; /* +24 because W64 includes the size of the GUID and size fields. */ runningPos += drwav__write(pWav, drwavGUID_W64_RIFF, 16); runningPos += drwav__write_u64ne_to_le(pWav, chunkSizeRIFF); runningPos += drwav__write(pWav, drwavGUID_W64_WAVE, 16); + } else if (pFormat->container == drwav_container_rf64) { + runningPos += drwav__write(pWav, "RF64", 4); + runningPos += drwav__write_u32ne_to_le(pWav, 0xFFFFFFFF); /* Always 0xFFFFFFFF for RF64. Set to a proper value in the "ds64" chunk. */ + runningPos += drwav__write(pWav, "WAVE", 4); } + + /* "ds64" chunk (RF64 only). */ + if (pFormat->container == drwav_container_rf64) { + drwav_uint32 initialds64ChunkSize = 28; /* 28 = [Size of RIFF (8 bytes)] + [Size of DATA (8 bytes)] + [Sample Count (8 bytes)] + [Table Length (4 bytes)]. Table length always set to 0. */ + drwav_uint64 initialRiffChunkSize = 8 + initialds64ChunkSize + initialDataChunkSize; /* +8 for the ds64 header. */ + + runningPos += drwav__write(pWav, "ds64", 4); + runningPos += drwav__write_u32ne_to_le(pWav, initialds64ChunkSize); /* Size of ds64. */ + runningPos += drwav__write_u64ne_to_le(pWav, initialRiffChunkSize); /* Size of RIFF. Set to true value at the end. */ + runningPos += drwav__write_u64ne_to_le(pWav, initialDataChunkSize); /* Size of DATA. Set to true value at the end. */ + runningPos += drwav__write_u64ne_to_le(pWav, totalSampleCount); /* Sample count. */ + runningPos += drwav__write_u32ne_to_le(pWav, 0); /* Table length. Always set to zero in our case since we're not doing any other chunks than "DATA". */ + } + + /* "fmt " chunk. */ - if (pFormat->container == drwav_container_riff) { + if (pFormat->container == drwav_container_riff || pFormat->container == drwav_container_rf64) { chunkSizeFMT = 16; runningPos += drwav__write(pWav, "fmt ", 4); runningPos += drwav__write_u32ne_to_le(pWav, (drwav_uint32)chunkSizeFMT); - } else { + } else if (pFormat->container == drwav_container_w64) { chunkSizeFMT = 40; runningPos += drwav__write(pWav, drwavGUID_W64_FMT, 16); runningPos += drwav__write_u64ne_to_le(pWav, chunkSizeFMT); @@ -2378,25 +2469,15 @@ static drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_for drwav_uint32 chunkSizeDATA = (drwav_uint32)initialDataChunkSize; runningPos += drwav__write(pWav, "data", 4); runningPos += drwav__write_u32ne_to_le(pWav, chunkSizeDATA); - } else { - drwav_uint64 chunkSizeDATA = 24 + initialDataChunkSize; /* +24 because W64 includes the size of the GUID and size fields. */ + } else if (pFormat->container == drwav_container_w64) { + drwav_uint64 chunkSizeDATA = 24 + initialDataChunkSize; /* +24 because W64 includes the size of the GUID and size fields. */ runningPos += drwav__write(pWav, drwavGUID_W64_DATA, 16); runningPos += drwav__write_u64ne_to_le(pWav, chunkSizeDATA); + } else if (pFormat->container == drwav_container_rf64) { + runningPos += drwav__write(pWav, "data", 4); + runningPos += drwav__write_u32ne_to_le(pWav, 0xFFFFFFFF); /* Always set to 0xFFFFFFFF for RF64. The true size of the data chunk is specified in the ds64 chunk. */ } - - /* Simple validation. */ - if (pFormat->container == drwav_container_riff) { - if (runningPos != 20 + chunkSizeFMT + 8) { - return DRWAV_FALSE; - } - } else { - if (runningPos != 40 + chunkSizeFMT + 24) { - return DRWAV_FALSE; - } - } - - /* Set some properties for the client's convenience. */ pWav->container = pFormat->container; pWav->channels = (drwav_uint16)pFormat->channels; @@ -2440,14 +2521,17 @@ DRWAV_API drwav_uint64 drwav_target_write_size_bytes(const drwav_data_format* pF /* Casting totalSampleCount to drwav_int64 for VC6 compatibility. No issues in practice because nobody is going to exhaust the whole 63 bits. */ drwav_uint64 targetDataSizeBytes = (drwav_uint64)((drwav_int64)totalSampleCount * pFormat->channels * pFormat->bitsPerSample/8.0); drwav_uint64 riffChunkSizeBytes; - drwav_uint64 fileSizeBytes; + drwav_uint64 fileSizeBytes = 0; if (pFormat->container == drwav_container_riff) { riffChunkSizeBytes = drwav__riff_chunk_size_riff(targetDataSizeBytes); - fileSizeBytes = (8 + riffChunkSizeBytes); /* +8 because WAV doesn't include the size of the ChunkID and ChunkSize fields. */ - } else { + fileSizeBytes = (8 + riffChunkSizeBytes); /* +8 because WAV doesn't include the size of the ChunkID and ChunkSize fields. */ + } else if (pFormat->container == drwav_container_w64) { riffChunkSizeBytes = drwav__riff_chunk_size_w64(targetDataSizeBytes); fileSizeBytes = riffChunkSizeBytes; + } else if (pFormat->container == drwav_container_rf64) { + riffChunkSizeBytes = drwav__riff_chunk_size_rf64(targetDataSizeBytes); + fileSizeBytes = (8 + riffChunkSizeBytes); /* +8 because WAV doesn't include the size of the ChunkID and ChunkSize fields. */ } return fileSizeBytes; @@ -3353,7 +3437,7 @@ DRWAV_API drwav_result drwav_uninit(drwav* pWav) drwav_uint32 paddingSize = 0; /* Padding. Do not adjust pWav->dataChunkDataSize - this should not include the padding. */ - if (pWav->container == drwav_container_riff) { + if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64) { paddingSize = drwav__chunk_padding_size_riff(pWav->dataChunkDataSize); } else { paddingSize = drwav__chunk_padding_size_w64(pWav->dataChunkDataSize); @@ -3381,7 +3465,7 @@ DRWAV_API drwav_result drwav_uninit(drwav* pWav) drwav_uint32 dataChunkSize = drwav__data_chunk_size_riff(pWav->dataChunkDataSize); drwav__write_u32ne_to_le(pWav, dataChunkSize); } - } else { + } else if (pWav->container == drwav_container_w64) { /* The "RIFF" chunk size. */ if (pWav->onSeek(pWav->pUserData, 16, drwav_seek_origin_start)) { drwav_uint64 riffChunkSize = drwav__riff_chunk_size_w64(pWav->dataChunkDataSize); @@ -3393,6 +3477,21 @@ DRWAV_API drwav_result drwav_uninit(drwav* pWav) drwav_uint64 dataChunkSize = drwav__data_chunk_size_w64(pWav->dataChunkDataSize); drwav__write_u64ne_to_le(pWav, dataChunkSize); } + } else if (pWav->container == drwav_container_rf64) { + /* We only need to update the ds64 chunk. The "RIFF" and "data" chunks always have their sizes set to 0xFFFFFFFF for RF64. */ + int ds64BodyPos = 12 + 8; + + /* The "RIFF" chunk size. */ + if (pWav->onSeek(pWav->pUserData, ds64BodyPos + 0, drwav_seek_origin_start)) { + drwav_uint64 riffChunkSize = drwav__riff_chunk_size_rf64(pWav->dataChunkDataSize); + drwav__write_u64ne_to_le(pWav, riffChunkSize); + } + + /* The "data" chunk size. */ + if (pWav->onSeek(pWav->pUserData, ds64BodyPos + 8, drwav_seek_origin_start)) { + drwav_uint64 dataChunkSize = drwav__data_chunk_size_rf64(pWav->dataChunkDataSize); + drwav__write_u64ne_to_le(pWav, dataChunkSize); + } } } @@ -5934,6 +6033,13 @@ two different ways to initialize a drwav object. /* REVISION HISTORY ================ +v0.12.12 - 2020-09-28 + - Add support for RF64. + - Fix a bug in writing mode where the size of the RIFF chunk incorrectly includes the header section. + +v0.12.11 - 2020-09-08 + - Fix a compilation error on older compilers. + v0.12.10 - 2020-08-24 - Fix a bug when seeking with ADPCM formats. diff --git a/miniaudio.h b/miniaudio.h index 65b328e1..4e2f05a9 100644 --- a/miniaudio.h +++ b/miniaudio.h @@ -42831,7 +42831,7 @@ extern "C" { #define DRWAV_XSTRINGIFY(x) DRWAV_STRINGIFY(x) #define DRWAV_VERSION_MAJOR 0 #define DRWAV_VERSION_MINOR 12 -#define DRWAV_VERSION_REVISION 10 +#define DRWAV_VERSION_REVISION 12 #define DRWAV_VERSION_STRING DRWAV_XSTRINGIFY(DRWAV_VERSION_MAJOR) "." DRWAV_XSTRINGIFY(DRWAV_VERSION_MINOR) "." DRWAV_XSTRINGIFY(DRWAV_VERSION_REVISION) #include typedef signed char drwav_int8; @@ -42970,7 +42970,8 @@ typedef enum typedef enum { drwav_container_riff, - drwav_container_w64 + drwav_container_w64, + drwav_container_rf64 } drwav_container; typedef struct { @@ -43203,7 +43204,7 @@ extern "C" { #define DRFLAC_XSTRINGIFY(x) DRFLAC_STRINGIFY(x) #define DRFLAC_VERSION_MAJOR 0 #define DRFLAC_VERSION_MINOR 12 -#define DRFLAC_VERSION_REVISION 19 +#define DRFLAC_VERSION_REVISION 20 #define DRFLAC_VERSION_STRING DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MAJOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MINOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_REVISION) #include typedef signed char drflac_int8; @@ -43564,7 +43565,7 @@ extern "C" { #define DRMP3_XSTRINGIFY(x) DRMP3_STRINGIFY(x) #define DRMP3_VERSION_MAJOR 0 #define DRMP3_VERSION_MINOR 6 -#define DRMP3_VERSION_REVISION 16 +#define DRMP3_VERSION_REVISION 17 #define DRMP3_VERSION_STRING DRMP3_XSTRINGIFY(DRMP3_VERSION_MAJOR) "." DRMP3_XSTRINGIFY(DRMP3_VERSION_MINOR) "." DRMP3_XSTRINGIFY(DRMP3_VERSION_REVISION) #include typedef signed char drmp3_int8; @@ -47605,14 +47606,14 @@ static DRWAV_INLINE drwav_uint64 drwav__bswap64(drwav_uint64 n) #error "This compiler does not support the byte swap intrinsic." #endif #else - return ((n & (drwav_uint64)0xFF00000000000000) >> 56) | - ((n & (drwav_uint64)0x00FF000000000000) >> 40) | - ((n & (drwav_uint64)0x0000FF0000000000) >> 24) | - ((n & (drwav_uint64)0x000000FF00000000) >> 8) | - ((n & (drwav_uint64)0x00000000FF000000) << 8) | - ((n & (drwav_uint64)0x0000000000FF0000) << 24) | - ((n & (drwav_uint64)0x000000000000FF00) << 40) | - ((n & (drwav_uint64)0x00000000000000FF) << 56); + return ((n & ((drwav_uint64)0xFF000000 << 32)) >> 56) | + ((n & ((drwav_uint64)0x00FF0000 << 32)) >> 40) | + ((n & ((drwav_uint64)0x0000FF00 << 32)) >> 24) | + ((n & ((drwav_uint64)0x000000FF << 32)) >> 8) | + ((n & ((drwav_uint64)0xFF000000 )) << 8) | + ((n & ((drwav_uint64)0x00FF0000 )) << 24) | + ((n & ((drwav_uint64)0x0000FF00 )) << 40) | + ((n & ((drwav_uint64)0x000000FF )) << 56); #endif } static DRWAV_INLINE drwav_int16 drwav__bswap_s16(drwav_int16 n) @@ -47848,7 +47849,7 @@ static drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uint64 sam static drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount); static drwav_result drwav__read_chunk_header(drwav_read_proc onRead, void* pUserData, drwav_container container, drwav_uint64* pRunningBytesReadOut, drwav_chunk_header* pHeaderOut) { - if (container == drwav_container_riff) { + if (container == drwav_container_riff || container == drwav_container_rf64) { drwav_uint8 sizeInBytes[4]; if (onRead(pUserData, pHeaderOut->id.fourcc, 4) != 4) { return DRWAV_AT_END; @@ -47917,7 +47918,7 @@ static drwav_bool32 drwav__read_fmt(drwav_read_proc onRead, drwav_seek_proc onSe if (drwav__read_chunk_header(onRead, pUserData, container, pRunningBytesReadOut, &header) != DRWAV_SUCCESS) { return DRWAV_FALSE; } - while ((container == drwav_container_riff && !drwav__fourcc_equal(header.id.fourcc, "fmt ")) || (container == drwav_container_w64 && !drwav__guid_equal(header.id.guid, drwavGUID_W64_FMT))) { + while (((container == drwav_container_riff || container == drwav_container_rf64) && !drwav__fourcc_equal(header.id.fourcc, "fmt ")) || (container == drwav_container_w64 && !drwav__guid_equal(header.id.guid, drwavGUID_W64_FMT))) { if (!drwav__seek_forward(onSeek, header.sizeInBytes + header.paddingSize, pUserData)) { return DRWAV_FALSE; } @@ -47926,7 +47927,7 @@ static drwav_bool32 drwav__read_fmt(drwav_read_proc onRead, drwav_seek_proc onSe return DRWAV_FALSE; } } - if (container == drwav_container_riff) { + if (container == drwav_container_riff || container == drwav_container_rf64) { if (!drwav__fourcc_equal(header.id.fourcc, "fmt ")) { return DRWAV_FALSE; } @@ -48059,9 +48060,9 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, drwav_uint8 riff[4]; drwav_fmt fmt; unsigned short translatedFormatTag; - drwav_uint64 sampleCountFromFactChunk; drwav_bool32 foundDataChunk; - drwav_uint64 dataChunkSize; + drwav_uint64 dataChunkSize = 0; + drwav_uint64 sampleCountFromFactChunk = 0; drwav_uint64 chunkSize; cursor = 0; sequential = (flags & DRWAV_SEQUENTIAL) != 0; @@ -48082,17 +48083,25 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, return DRWAV_FALSE; } } + } else if (drwav__fourcc_equal(riff, "RF64")) { + pWav->container = drwav_container_rf64; } else { return DRWAV_FALSE; } - if (pWav->container == drwav_container_riff) { + if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64) { drwav_uint8 chunkSizeBytes[4]; drwav_uint8 wave[4]; if (drwav__on_read(pWav->onRead, pWav->pUserData, chunkSizeBytes, sizeof(chunkSizeBytes), &cursor) != sizeof(chunkSizeBytes)) { return DRWAV_FALSE; } - if (drwav__bytes_to_u32(chunkSizeBytes) < 36) { - return DRWAV_FALSE; + if (pWav->container == drwav_container_riff) { + if (drwav__bytes_to_u32(chunkSizeBytes) < 36) { + return DRWAV_FALSE; + } + } else { + if (drwav__bytes_to_u32(chunkSizeBytes) != 0xFFFFFFFF) { + return DRWAV_FALSE; + } } if (drwav__on_read(pWav->onRead, pWav->pUserData, wave, sizeof(wave), &cursor) != sizeof(wave)) { return DRWAV_FALSE; @@ -48116,6 +48125,38 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, return DRWAV_FALSE; } } + if (pWav->container == drwav_container_rf64) { + drwav_uint8 sizeBytes[8]; + drwav_uint64 bytesRemainingInChunk; + drwav_chunk_header header; + drwav_result result = drwav__read_chunk_header(pWav->onRead, pWav->pUserData, pWav->container, &cursor, &header); + if (result != DRWAV_SUCCESS) { + return DRWAV_FALSE; + } + if (!drwav__fourcc_equal(header.id.fourcc, "ds64")) { + return DRWAV_FALSE; + } + bytesRemainingInChunk = header.sizeInBytes + header.paddingSize; + if (!drwav__seek_forward(pWav->onSeek, 8, pWav->pUserData)) { + return DRWAV_FALSE; + } + bytesRemainingInChunk -= 8; + cursor += 8; + if (drwav__on_read(pWav->onRead, pWav->pUserData, sizeBytes, sizeof(sizeBytes), &cursor) != sizeof(sizeBytes)) { + return DRWAV_FALSE; + } + bytesRemainingInChunk -= 8; + dataChunkSize = drwav__bytes_to_u64(sizeBytes); + if (drwav__on_read(pWav->onRead, pWav->pUserData, sizeBytes, sizeof(sizeBytes), &cursor) != sizeof(sizeBytes)) { + return DRWAV_FALSE; + } + bytesRemainingInChunk -= 8; + sampleCountFromFactChunk = drwav__bytes_to_u64(sizeBytes); + if (!drwav__seek_forward(pWav->onSeek, bytesRemainingInChunk, pWav->pUserData)) { + return DRWAV_FALSE; + } + cursor += bytesRemainingInChunk; + } if (!drwav__read_fmt(pWav->onRead, pWav->onSeek, pWav->pUserData, pWav->container, &cursor, &fmt)) { return DRWAV_FALSE; } @@ -48129,9 +48170,7 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, if (translatedFormatTag == DR_WAVE_FORMAT_EXTENSIBLE) { translatedFormatTag = drwav__bytes_to_u16(fmt.subFormat + 0); } - sampleCountFromFactChunk = 0; foundDataChunk = DRWAV_FALSE; - dataChunkSize = 0; for (;;) { drwav_chunk_header header; @@ -48155,10 +48194,12 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, pWav->dataChunkDataPos = cursor; } chunkSize = header.sizeInBytes; - if (pWav->container == drwav_container_riff) { + if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64) { if (drwav__fourcc_equal(header.id.fourcc, "data")) { foundDataChunk = DRWAV_TRUE; - dataChunkSize = chunkSize; + if (pWav->container != drwav_container_rf64) { + dataChunkSize = chunkSize; + } } } else { if (drwav__guid_equal(header.id.guid, drwavGUID_W64_DATA)) { @@ -48185,7 +48226,7 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, sampleCountFromFactChunk = 0; } } - } else { + } else if (pWav->container == drwav_container_w64) { if (drwav__guid_equal(header.id.guid, drwavGUID_W64_FACT)) { if (drwav__on_read(pWav->onRead, pWav->pUserData, &sampleCountFromFactChunk, 8, &cursor) != 8) { return DRWAV_FALSE; @@ -48195,8 +48236,9 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, pWav->dataChunkDataPos = cursor; } } + } else if (pWav->container == drwav_container_rf64) { } - if (pWav->container == drwav_container_riff) { + if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64) { if (drwav__fourcc_equal(header.id.fourcc, "smpl")) { drwav_uint8 smplHeaderData[36]; if (chunkSize >= sizeof(smplHeaderData)) { @@ -48315,12 +48357,11 @@ DRWAV_API drwav_bool32 drwav_init_ex(drwav* pWav, drwav_read_proc onRead, drwav_ } static drwav_uint32 drwav__riff_chunk_size_riff(drwav_uint64 dataChunkSize) { - drwav_uint32 dataSubchunkPaddingSize = drwav__chunk_padding_size_riff(dataChunkSize); - if (dataChunkSize <= (0xFFFFFFFFUL - 36 - dataSubchunkPaddingSize)) { - return 36 + (drwav_uint32)(dataChunkSize + dataSubchunkPaddingSize); - } else { - return 0xFFFFFFFF; + drwav_uint64 chunkSize = 4 + 24 + dataChunkSize + drwav__chunk_padding_size_riff(dataChunkSize); + if (chunkSize > 0xFFFFFFFFUL) { + chunkSize = 0xFFFFFFFFUL; } + return (drwav_uint32)chunkSize; } static drwav_uint32 drwav__data_chunk_size_riff(drwav_uint64 dataChunkSize) { @@ -48339,6 +48380,18 @@ static drwav_uint64 drwav__data_chunk_size_w64(drwav_uint64 dataChunkSize) { return 24 + dataChunkSize; } +static drwav_uint64 drwav__riff_chunk_size_rf64(drwav_uint64 dataChunkSize) +{ + drwav_uint64 chunkSize = 4 + 36 + 24 + dataChunkSize + drwav__chunk_padding_size_riff(dataChunkSize); + if (chunkSize > 0xFFFFFFFFUL) { + chunkSize = 0xFFFFFFFFUL; + } + return chunkSize; +} +static drwav_uint64 drwav__data_chunk_size_rf64(drwav_uint64 dataChunkSize) +{ + return dataChunkSize; +} static size_t drwav__write(drwav* pWav, const void* pData, size_t dataSize) { DRWAV_ASSERT(pWav != NULL); @@ -48419,21 +48472,35 @@ static drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_for } pWav->dataChunkDataSizeTargetWrite = initialDataChunkSize; if (pFormat->container == drwav_container_riff) { - drwav_uint32 chunkSizeRIFF = 36 + (drwav_uint32)initialDataChunkSize; + drwav_uint32 chunkSizeRIFF = 28 + (drwav_uint32)initialDataChunkSize; runningPos += drwav__write(pWav, "RIFF", 4); runningPos += drwav__write_u32ne_to_le(pWav, chunkSizeRIFF); runningPos += drwav__write(pWav, "WAVE", 4); - } else { + } else if (pFormat->container == drwav_container_w64) { drwav_uint64 chunkSizeRIFF = 80 + 24 + initialDataChunkSize; runningPos += drwav__write(pWav, drwavGUID_W64_RIFF, 16); runningPos += drwav__write_u64ne_to_le(pWav, chunkSizeRIFF); runningPos += drwav__write(pWav, drwavGUID_W64_WAVE, 16); + } else if (pFormat->container == drwav_container_rf64) { + runningPos += drwav__write(pWav, "RF64", 4); + runningPos += drwav__write_u32ne_to_le(pWav, 0xFFFFFFFF); + runningPos += drwav__write(pWav, "WAVE", 4); } - if (pFormat->container == drwav_container_riff) { + if (pFormat->container == drwav_container_rf64) { + drwav_uint32 initialds64ChunkSize = 28; + drwav_uint64 initialRiffChunkSize = 8 + initialds64ChunkSize + initialDataChunkSize; + runningPos += drwav__write(pWav, "ds64", 4); + runningPos += drwav__write_u32ne_to_le(pWav, initialds64ChunkSize); + runningPos += drwav__write_u64ne_to_le(pWav, initialRiffChunkSize); + runningPos += drwav__write_u64ne_to_le(pWav, initialDataChunkSize); + runningPos += drwav__write_u64ne_to_le(pWav, totalSampleCount); + runningPos += drwav__write_u32ne_to_le(pWav, 0); + } + if (pFormat->container == drwav_container_riff || pFormat->container == drwav_container_rf64) { chunkSizeFMT = 16; runningPos += drwav__write(pWav, "fmt ", 4); runningPos += drwav__write_u32ne_to_le(pWav, (drwav_uint32)chunkSizeFMT); - } else { + } else if (pFormat->container == drwav_container_w64) { chunkSizeFMT = 40; runningPos += drwav__write(pWav, drwavGUID_W64_FMT, 16); runningPos += drwav__write_u64ne_to_le(pWav, chunkSizeFMT); @@ -48449,19 +48516,13 @@ static drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_for drwav_uint32 chunkSizeDATA = (drwav_uint32)initialDataChunkSize; runningPos += drwav__write(pWav, "data", 4); runningPos += drwav__write_u32ne_to_le(pWav, chunkSizeDATA); - } else { + } else if (pFormat->container == drwav_container_w64) { drwav_uint64 chunkSizeDATA = 24 + initialDataChunkSize; runningPos += drwav__write(pWav, drwavGUID_W64_DATA, 16); runningPos += drwav__write_u64ne_to_le(pWav, chunkSizeDATA); - } - if (pFormat->container == drwav_container_riff) { - if (runningPos != 20 + chunkSizeFMT + 8) { - return DRWAV_FALSE; - } - } else { - if (runningPos != 40 + chunkSizeFMT + 24) { - return DRWAV_FALSE; - } + } else if (pFormat->container == drwav_container_rf64) { + runningPos += drwav__write(pWav, "data", 4); + runningPos += drwav__write_u32ne_to_le(pWav, 0xFFFFFFFF); } pWav->container = pFormat->container; pWav->channels = (drwav_uint16)pFormat->channels; @@ -48495,13 +48556,16 @@ DRWAV_API drwav_uint64 drwav_target_write_size_bytes(const drwav_data_format* pF { drwav_uint64 targetDataSizeBytes = (drwav_uint64)((drwav_int64)totalSampleCount * pFormat->channels * pFormat->bitsPerSample/8.0); drwav_uint64 riffChunkSizeBytes; - drwav_uint64 fileSizeBytes; + drwav_uint64 fileSizeBytes = 0; if (pFormat->container == drwav_container_riff) { riffChunkSizeBytes = drwav__riff_chunk_size_riff(targetDataSizeBytes); fileSizeBytes = (8 + riffChunkSizeBytes); - } else { + } else if (pFormat->container == drwav_container_w64) { riffChunkSizeBytes = drwav__riff_chunk_size_w64(targetDataSizeBytes); fileSizeBytes = riffChunkSizeBytes; + } else if (pFormat->container == drwav_container_rf64) { + riffChunkSizeBytes = drwav__riff_chunk_size_rf64(targetDataSizeBytes); + fileSizeBytes = (8 + riffChunkSizeBytes); } return fileSizeBytes; } @@ -49273,7 +49337,7 @@ DRWAV_API drwav_result drwav_uninit(drwav* pWav) } if (pWav->onWrite != NULL) { drwav_uint32 paddingSize = 0; - if (pWav->container == drwav_container_riff) { + if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64) { paddingSize = drwav__chunk_padding_size_riff(pWav->dataChunkDataSize); } else { paddingSize = drwav__chunk_padding_size_w64(pWav->dataChunkDataSize); @@ -49292,7 +49356,7 @@ DRWAV_API drwav_result drwav_uninit(drwav* pWav) drwav_uint32 dataChunkSize = drwav__data_chunk_size_riff(pWav->dataChunkDataSize); drwav__write_u32ne_to_le(pWav, dataChunkSize); } - } else { + } else if (pWav->container == drwav_container_w64) { if (pWav->onSeek(pWav->pUserData, 16, drwav_seek_origin_start)) { drwav_uint64 riffChunkSize = drwav__riff_chunk_size_w64(pWav->dataChunkDataSize); drwav__write_u64ne_to_le(pWav, riffChunkSize); @@ -49301,6 +49365,16 @@ DRWAV_API drwav_result drwav_uninit(drwav* pWav) drwav_uint64 dataChunkSize = drwav__data_chunk_size_w64(pWav->dataChunkDataSize); drwav__write_u64ne_to_le(pWav, dataChunkSize); } + } else if (pWav->container == drwav_container_rf64) { + int ds64BodyPos = 12 + 8; + if (pWav->onSeek(pWav->pUserData, ds64BodyPos + 0, drwav_seek_origin_start)) { + drwav_uint64 riffChunkSize = drwav__riff_chunk_size_rf64(pWav->dataChunkDataSize); + drwav__write_u64ne_to_le(pWav, riffChunkSize); + } + if (pWav->onSeek(pWav->pUserData, ds64BodyPos + 8, drwav_seek_origin_start)) { + drwav_uint64 dataChunkSize = drwav__data_chunk_size_rf64(pWav->dataChunkDataSize); + drwav__write_u64ne_to_le(pWav, dataChunkSize); + } } } if (pWav->isSequentialWrite) { @@ -51513,14 +51587,14 @@ static DRFLAC_INLINE drflac_uint64 drflac__swap_endian_uint64(drflac_uint64 n) #error "This compiler does not support the byte swap intrinsic." #endif #else - return ((n & (drflac_uint64)0xFF00000000000000) >> 56) | - ((n & (drflac_uint64)0x00FF000000000000) >> 40) | - ((n & (drflac_uint64)0x0000FF0000000000) >> 24) | - ((n & (drflac_uint64)0x000000FF00000000) >> 8) | - ((n & (drflac_uint64)0x00000000FF000000) << 8) | - ((n & (drflac_uint64)0x0000000000FF0000) << 24) | - ((n & (drflac_uint64)0x000000000000FF00) << 40) | - ((n & (drflac_uint64)0x00000000000000FF) << 56); + return ((n & ((drflac_uint64)0xFF000000 << 32)) >> 56) | + ((n & ((drflac_uint64)0x00FF0000 << 32)) >> 40) | + ((n & ((drflac_uint64)0x0000FF00 << 32)) >> 24) | + ((n & ((drflac_uint64)0x000000FF << 32)) >> 8) | + ((n & ((drflac_uint64)0xFF000000 )) << 8) | + ((n & ((drflac_uint64)0x00FF0000 )) << 24) | + ((n & ((drflac_uint64)0x0000FF00 )) << 40) | + ((n & ((drflac_uint64)0x000000FF )) << 56); #endif } static DRFLAC_INLINE drflac_uint16 drflac__be2host_16(drflac_uint16 n) @@ -59404,6 +59478,8 @@ static __inline__ __attribute__((always_inline)) drmp3_int32 drmp3_clip_int16_ar __asm__ ("ssat %0, #16, %1" : "=r"(x) : "r"(a)); return x; } +#else +#define DRMP3_HAVE_ARMV6 0 #endif typedef struct { From ae88112e4f4d15e554967a6dfa1a2ac7699bcdf0 Mon Sep 17 00:00:00 2001 From: David Reid Date: Fri, 30 Oct 2020 20:23:21 +1000 Subject: [PATCH 03/27] Version 0.10.21 --- extras/miniaudio_split/miniaudio.c | 527 ++++++++++++++++++++--------- extras/miniaudio_split/miniaudio.h | 86 ++++- miniaudio.h | 4 +- 3 files changed, 450 insertions(+), 167 deletions(-) diff --git a/extras/miniaudio_split/miniaudio.c b/extras/miniaudio_split/miniaudio.c index 6b8749a7..343bcee5 100644 --- a/extras/miniaudio_split/miniaudio.c +++ b/extras/miniaudio_split/miniaudio.c @@ -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.10.20 - 2020-10-06 +miniaudio - v0.10.21 - 2020-10-30 David Reid - mackron@gmail.com @@ -3662,6 +3662,137 @@ MA_API const char* ma_get_backend_name(ma_backend backend) } } +MA_API ma_bool32 ma_is_backend_enabled(ma_backend backend) +{ + /* + This looks a little bit gross, but we want all backends to be included in the switch to avoid warnings on some compilers + about some enums not being handled by the switch statement. + */ + switch (backend) + { + case ma_backend_wasapi: + #if defined(MA_HAS_WASAPI) + return MA_TRUE; + #else + return MA_FALSE; + #endif + case ma_backend_dsound: + #if defined(MA_HAS_DSOUND) + return MA_TRUE; + #else + return MA_FALSE; + #endif + case ma_backend_winmm: + #if defined(MA_HAS_WINMM) + return MA_TRUE; + #else + return MA_FALSE; + #endif + case ma_backend_coreaudio: + #if defined(MA_HAS_COREAUDIO) + return MA_TRUE; + #else + return MA_FALSE; + #endif + case ma_backend_sndio: + #if defined(MA_HAS_SNDIO) + return MA_TRUE; + #else + return MA_FALSE; + #endif + case ma_backend_audio4: + #if defined(MA_HAS_AUDIO4) + return MA_TRUE; + #else + return MA_FALSE; + #endif + case ma_backend_oss: + #if defined(MA_HAS_OSS) + return MA_TRUE; + #else + return MA_FALSE; + #endif + case ma_backend_pulseaudio: + #if defined(MA_HAS_PULSEAUDIO) + return MA_TRUE; + #else + return MA_FALSE; + #endif + case ma_backend_alsa: + #if defined(MA_HAS_ALSA) + return MA_TRUE; + #else + return MA_FALSE; + #endif + case ma_backend_jack: + #if defined(MA_HAS_JACK) + return MA_TRUE; + #else + return MA_FALSE; + #endif + case ma_backend_aaudio: + #if defined(MA_HAS_AAUDIO) + return MA_TRUE; + #else + return MA_FALSE; + #endif + case ma_backend_opensl: + #if defined(MA_HAS_OPENSL) + return MA_TRUE; + #else + return MA_FALSE; + #endif + case ma_backend_webaudio: + #if defined(MA_HAS_WEBAUDIO) + return MA_TRUE; + #else + return MA_FALSE; + #endif + case ma_backend_null: + #if defined(MA_HAS_NULL) + return MA_TRUE; + #else + return MA_FALSE; + #endif + + default: return MA_FALSE; + } +} + +MA_API ma_result ma_get_enabled_backends(ma_backend* pBackends, size_t backendCap, size_t* pBackendCount) +{ + size_t backendCount; + size_t iBackend; + ma_result result = MA_SUCCESS; + + if (pBackendCount == NULL) { + return MA_INVALID_ARGS; + } + + backendCount = 0; + + for (iBackend = 0; iBackend <= ma_backend_null; iBackend += 1) { + ma_backend backend = (ma_backend)iBackend; + + if (ma_is_backend_enabled(backend)) { + /* The backend is enabled. Try adding it to the list. If there's no room, MA_NO_SPACE needs to be returned. */ + if (backendCount == backendCap) { + result = MA_NO_SPACE; + break; + } else { + pBackends[backendCount] = backend; + backendCount += 1; + } + } + } + + if (pBackendCount != NULL) { + *pBackendCount = backendCount; + } + + return result; +} + MA_API ma_bool32 ma_is_loopback_supported(ma_backend backend) { switch (backend) @@ -5107,6 +5238,10 @@ static ma_result ma_device_main_loop__null(ma_device* pDevice) { ma_result result = MA_SUCCESS; ma_bool32 exitLoop = MA_FALSE; + ma_uint8 capturedDeviceData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; + ma_uint8 playbackDeviceData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; + ma_uint32 capturedDeviceDataCapInFrames = sizeof(capturedDeviceData) / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); + ma_uint32 playbackDeviceDataCapInFrames = sizeof(playbackDeviceData) / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); MA_ASSERT(pDevice != NULL); @@ -5128,10 +5263,6 @@ static ma_result ma_device_main_loop__null(ma_device* pDevice) ma_uint32 capturedDevicePeriodSizeInFrames = ma_min(pDevice->capture.internalPeriodSizeInFrames, pDevice->playback.internalPeriodSizeInFrames); while (totalCapturedDeviceFramesProcessed < capturedDevicePeriodSizeInFrames) { - ma_uint8 capturedDeviceData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint8 playbackDeviceData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 capturedDeviceDataCapInFrames = sizeof(capturedDeviceData) / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); - ma_uint32 playbackDeviceDataCapInFrames = sizeof(playbackDeviceData) / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); ma_uint32 capturedDeviceFramesRemaining; ma_uint32 capturedDeviceFramesProcessed; ma_uint32 capturedDeviceFramesToProcess; @@ -5212,26 +5343,23 @@ static ma_result ma_device_main_loop__null(ma_device* pDevice) case ma_device_type_capture: { - /* We read in chunks of the period size, but use a stack allocated buffer for the intermediary. */ - ma_uint8 intermediaryBuffer[8192]; - ma_uint32 intermediaryBufferSizeInFrames = sizeof(intermediaryBuffer) / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); ma_uint32 periodSizeInFrames = pDevice->capture.internalPeriodSizeInFrames; ma_uint32 framesReadThisPeriod = 0; while (framesReadThisPeriod < periodSizeInFrames) { ma_uint32 framesRemainingInPeriod = periodSizeInFrames - framesReadThisPeriod; ma_uint32 framesProcessed; ma_uint32 framesToReadThisIteration = framesRemainingInPeriod; - if (framesToReadThisIteration > intermediaryBufferSizeInFrames) { - framesToReadThisIteration = intermediaryBufferSizeInFrames; + if (framesToReadThisIteration > capturedDeviceDataCapInFrames) { + framesToReadThisIteration = capturedDeviceDataCapInFrames; } - result = ma_device_read__null(pDevice, intermediaryBuffer, framesToReadThisIteration, &framesProcessed); + result = ma_device_read__null(pDevice, capturedDeviceData, framesToReadThisIteration, &framesProcessed); if (result != MA_SUCCESS) { exitLoop = MA_TRUE; break; } - ma_device__send_frames_to_client(pDevice, framesProcessed, intermediaryBuffer); + ma_device__send_frames_to_client(pDevice, framesProcessed, capturedDeviceData); framesReadThisPeriod += framesProcessed; } @@ -5240,21 +5368,19 @@ static ma_result ma_device_main_loop__null(ma_device* pDevice) case ma_device_type_playback: { /* We write in chunks of the period size, but use a stack allocated buffer for the intermediary. */ - ma_uint8 intermediaryBuffer[8192]; - ma_uint32 intermediaryBufferSizeInFrames = sizeof(intermediaryBuffer) / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); ma_uint32 periodSizeInFrames = pDevice->playback.internalPeriodSizeInFrames; ma_uint32 framesWrittenThisPeriod = 0; while (framesWrittenThisPeriod < periodSizeInFrames) { ma_uint32 framesRemainingInPeriod = periodSizeInFrames - framesWrittenThisPeriod; ma_uint32 framesProcessed; ma_uint32 framesToWriteThisIteration = framesRemainingInPeriod; - if (framesToWriteThisIteration > intermediaryBufferSizeInFrames) { - framesToWriteThisIteration = intermediaryBufferSizeInFrames; + if (framesToWriteThisIteration > playbackDeviceDataCapInFrames) { + framesToWriteThisIteration = playbackDeviceDataCapInFrames; } - ma_device__read_frames_from_client(pDevice, framesToWriteThisIteration, intermediaryBuffer); + ma_device__read_frames_from_client(pDevice, framesToWriteThisIteration, playbackDeviceData); - result = ma_device_write__null(pDevice, intermediaryBuffer, framesToWriteThisIteration, &framesProcessed); + result = ma_device_write__null(pDevice, playbackDeviceData, framesToWriteThisIteration, &framesProcessed); if (result != MA_SUCCESS) { exitLoop = MA_TRUE; break; @@ -7866,7 +7992,7 @@ static ma_result ma_device_stop__wasapi(ma_device* pDevice) In loopback mode it's possible for WaitForSingleObject() to get stuck in a deadlock when nothing is being played. When nothing is being played, the event is never signalled internally by WASAPI which means we will deadlock when stopping the device. */ - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex || pDevice->type == ma_device_type_duplex) { + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex || pDevice->type == ma_device_type_loopback) { SetEvent((HANDLE)pDevice->wasapi.hEventCapture); } @@ -11264,6 +11390,10 @@ static ma_result ma_device_main_loop__winmm(ma_device* pDevice) { ma_result result = MA_SUCCESS; ma_bool32 exitLoop = MA_FALSE; + ma_uint8 capturedDeviceData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; + ma_uint8 playbackDeviceData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; + ma_uint32 capturedDeviceDataCapInFrames = sizeof(capturedDeviceData) / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); + ma_uint32 playbackDeviceDataCapInFrames = sizeof(playbackDeviceData) / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); MA_ASSERT(pDevice != NULL); @@ -11307,10 +11437,6 @@ static ma_result ma_device_main_loop__winmm(ma_device* pDevice) ma_uint32 capturedDevicePeriodSizeInFrames = ma_min(pDevice->capture.internalPeriodSizeInFrames, pDevice->playback.internalPeriodSizeInFrames); while (totalCapturedDeviceFramesProcessed < capturedDevicePeriodSizeInFrames) { - ma_uint8 capturedDeviceData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint8 playbackDeviceData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 capturedDeviceDataCapInFrames = sizeof(capturedDeviceData) / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); - ma_uint32 playbackDeviceDataCapInFrames = sizeof(playbackDeviceData) / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); ma_uint32 capturedDeviceFramesRemaining; ma_uint32 capturedDeviceFramesProcessed; ma_uint32 capturedDeviceFramesToProcess; @@ -11391,25 +11517,23 @@ static ma_result ma_device_main_loop__winmm(ma_device* pDevice) case ma_device_type_capture: { /* We read in chunks of the period size, but use a stack allocated buffer for the intermediary. */ - ma_uint8 intermediaryBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 intermediaryBufferSizeInFrames = sizeof(intermediaryBuffer) / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); ma_uint32 periodSizeInFrames = pDevice->capture.internalPeriodSizeInFrames; ma_uint32 framesReadThisPeriod = 0; while (framesReadThisPeriod < periodSizeInFrames) { ma_uint32 framesRemainingInPeriod = periodSizeInFrames - framesReadThisPeriod; ma_uint32 framesProcessed; ma_uint32 framesToReadThisIteration = framesRemainingInPeriod; - if (framesToReadThisIteration > intermediaryBufferSizeInFrames) { - framesToReadThisIteration = intermediaryBufferSizeInFrames; + if (framesToReadThisIteration > capturedDeviceDataCapInFrames) { + framesToReadThisIteration = capturedDeviceDataCapInFrames; } - result = ma_device_read__winmm(pDevice, intermediaryBuffer, framesToReadThisIteration, &framesProcessed); + result = ma_device_read__winmm(pDevice, capturedDeviceData, framesToReadThisIteration, &framesProcessed); if (result != MA_SUCCESS) { exitLoop = MA_TRUE; break; } - ma_device__send_frames_to_client(pDevice, framesProcessed, intermediaryBuffer); + ma_device__send_frames_to_client(pDevice, framesProcessed, capturedDeviceData); framesReadThisPeriod += framesProcessed; } @@ -11418,21 +11542,19 @@ static ma_result ma_device_main_loop__winmm(ma_device* pDevice) case ma_device_type_playback: { /* We write in chunks of the period size, but use a stack allocated buffer for the intermediary. */ - ma_uint8 intermediaryBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 intermediaryBufferSizeInFrames = sizeof(intermediaryBuffer) / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); ma_uint32 periodSizeInFrames = pDevice->playback.internalPeriodSizeInFrames; ma_uint32 framesWrittenThisPeriod = 0; while (framesWrittenThisPeriod < periodSizeInFrames) { ma_uint32 framesRemainingInPeriod = periodSizeInFrames - framesWrittenThisPeriod; ma_uint32 framesProcessed; ma_uint32 framesToWriteThisIteration = framesRemainingInPeriod; - if (framesToWriteThisIteration > intermediaryBufferSizeInFrames) { - framesToWriteThisIteration = intermediaryBufferSizeInFrames; + if (framesToWriteThisIteration > playbackDeviceDataCapInFrames) { + framesToWriteThisIteration = playbackDeviceDataCapInFrames; } - ma_device__read_frames_from_client(pDevice, framesToWriteThisIteration, intermediaryBuffer); + ma_device__read_frames_from_client(pDevice, framesToWriteThisIteration, playbackDeviceData); - result = ma_device_write__winmm(pDevice, intermediaryBuffer, framesToWriteThisIteration, &framesProcessed); + result = ma_device_write__winmm(pDevice, playbackDeviceData, framesToWriteThisIteration, &framesProcessed); if (result != MA_SUCCESS) { exitLoop = MA_TRUE; break; @@ -17136,7 +17258,14 @@ static ma_result ma_get_channel_map_from_AudioChannelLayout(AudioChannelLayout* Need to use the tag to determine the channel map. For now I'm just assuming a default channel map, but later on this should be updated to determine the mapping based on the tag. */ - UInt32 channelCount = ma_min(AudioChannelLayoutTag_GetNumberOfChannels(pChannelLayout->mChannelLayoutTag), channelMapCap); + UInt32 channelCount; + + /* Our channel map retrieval APIs below take 32-bit integers, so we'll want to clamp the channel map capacity. */ + if (channelMapCap > 0xFFFFFFFF) { + channelMapCap = 0xFFFFFFFF; + } + + channelCount = ma_min(AudioChannelLayoutTag_GetNumberOfChannels(pChannelLayout->mChannelLayoutTag), (UInt32)channelMapCap); switch (pChannelLayout->mChannelLayoutTag) { @@ -17742,7 +17871,7 @@ static ma_result ma_find_AudioObjectID(ma_context* pContext, ma_device_type devi } -static ma_result ma_find_best_format__coreaudio(ma_context* pContext, AudioObjectID deviceObjectID, ma_device_type deviceType, ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_bool32 usingDefaultFormat, ma_bool32 usingDefaultChannels, ma_bool32 usingDefaultSampleRate, AudioStreamBasicDescription* pFormat) +static ma_result ma_find_best_format__coreaudio(ma_context* pContext, AudioObjectID deviceObjectID, ma_device_type deviceType, ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_bool32 usingDefaultFormat, ma_bool32 usingDefaultChannels, ma_bool32 usingDefaultSampleRate, const AudioStreamBasicDescription* pOrigFormat, AudioStreamBasicDescription* pFormat) { UInt32 deviceFormatDescriptionCount; AudioStreamRangedDescription* pDeviceFormatDescriptions; @@ -17761,40 +17890,20 @@ static ma_result ma_find_best_format__coreaudio(ma_context* pContext, AudioObjec desiredSampleRate = sampleRate; if (usingDefaultSampleRate) { - /* - When using the device's default sample rate, we get the highest priority standard rate supported by the device. Otherwise - we just use the pre-set rate. - */ - ma_uint32 iStandardRate; - for (iStandardRate = 0; iStandardRate < ma_countof(g_maStandardSampleRatePriorities); ++iStandardRate) { - ma_uint32 standardRate = g_maStandardSampleRatePriorities[iStandardRate]; - ma_bool32 foundRate = MA_FALSE; - UInt32 iDeviceRate; - - for (iDeviceRate = 0; iDeviceRate < deviceFormatDescriptionCount; ++iDeviceRate) { - ma_uint32 deviceRate = (ma_uint32)pDeviceFormatDescriptions[iDeviceRate].mFormat.mSampleRate; - - if (deviceRate == standardRate) { - desiredSampleRate = standardRate; - foundRate = MA_TRUE; - break; - } - } - - if (foundRate) { - break; - } - } + desiredSampleRate = pOrigFormat->mSampleRate; } desiredChannelCount = channels; if (usingDefaultChannels) { - ma_get_AudioObject_channel_count(pContext, deviceObjectID, deviceType, &desiredChannelCount); /* <-- Not critical if this fails. */ + desiredChannelCount = pOrigFormat->mChannelsPerFrame; } desiredFormat = format; if (usingDefaultFormat) { - desiredFormat = g_maFormatPriorities[0]; + result = ma_format_from_AudioStreamBasicDescription(pOrigFormat, &desiredFormat); + if (result != MA_SUCCESS || desiredFormat == ma_format_unknown) { + desiredFormat = g_maFormatPriorities[0]; + } } /* @@ -18652,17 +18761,22 @@ static ma_result ma_context__init_device_tracking__coreaudio(ma_context* pContex ma_spinlock_lock(&g_DeviceTrackingInitLock_CoreAudio); { - AudioObjectPropertyAddress propAddress; - propAddress.mScope = kAudioObjectPropertyScopeGlobal; - propAddress.mElement = kAudioObjectPropertyElementMaster; - - ma_mutex_init(&g_DeviceTrackingMutex_CoreAudio); - - propAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice; - ((ma_AudioObjectAddPropertyListener_proc)pContext->coreaudio.AudioObjectAddPropertyListener)(kAudioObjectSystemObject, &propAddress, &ma_default_device_changed__coreaudio, NULL); - - propAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice; - ((ma_AudioObjectAddPropertyListener_proc)pContext->coreaudio.AudioObjectAddPropertyListener)(kAudioObjectSystemObject, &propAddress, &ma_default_device_changed__coreaudio, NULL); + /* Don't do anything if we've already initializd device tracking. */ + if (g_DeviceTrackingInitCounter_CoreAudio == 0) { + AudioObjectPropertyAddress propAddress; + propAddress.mScope = kAudioObjectPropertyScopeGlobal; + propAddress.mElement = kAudioObjectPropertyElementMaster; + + ma_mutex_init(&g_DeviceTrackingMutex_CoreAudio); + + propAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice; + ((ma_AudioObjectAddPropertyListener_proc)pContext->coreaudio.AudioObjectAddPropertyListener)(kAudioObjectSystemObject, &propAddress, &ma_default_device_changed__coreaudio, NULL); + + propAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice; + ((ma_AudioObjectAddPropertyListener_proc)pContext->coreaudio.AudioObjectAddPropertyListener)(kAudioObjectSystemObject, &propAddress, &ma_default_device_changed__coreaudio, NULL); + + g_DeviceTrackingInitCounter_CoreAudio += 1; + } } ma_spinlock_unlock(&g_DeviceTrackingInitLock_CoreAudio); @@ -18675,21 +18789,26 @@ static ma_result ma_context__uninit_device_tracking__coreaudio(ma_context* pCont ma_spinlock_lock(&g_DeviceTrackingInitLock_CoreAudio); { - AudioObjectPropertyAddress propAddress; - propAddress.mScope = kAudioObjectPropertyScopeGlobal; - propAddress.mElement = kAudioObjectPropertyElementMaster; + g_DeviceTrackingInitCounter_CoreAudio -= 1; - propAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice; - ((ma_AudioObjectRemovePropertyListener_proc)pContext->coreaudio.AudioObjectRemovePropertyListener)(kAudioObjectSystemObject, &propAddress, &ma_default_device_changed__coreaudio, NULL); - - propAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice; - ((ma_AudioObjectRemovePropertyListener_proc)pContext->coreaudio.AudioObjectRemovePropertyListener)(kAudioObjectSystemObject, &propAddress, &ma_default_device_changed__coreaudio, NULL); - - /* At this point there should be no tracked devices. If so there's an error somewhere. */ - MA_ASSERT(g_ppTrackedDevices_CoreAudio == NULL); - MA_ASSERT(g_TrackedDeviceCount_CoreAudio == 0); - - ma_mutex_uninit(&g_DeviceTrackingMutex_CoreAudio); + if (g_DeviceTrackingInitCounter_CoreAudio == 0) { + AudioObjectPropertyAddress propAddress; + propAddress.mScope = kAudioObjectPropertyScopeGlobal; + propAddress.mElement = kAudioObjectPropertyElementMaster; + + propAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice; + ((ma_AudioObjectRemovePropertyListener_proc)pContext->coreaudio.AudioObjectRemovePropertyListener)(kAudioObjectSystemObject, &propAddress, &ma_default_device_changed__coreaudio, NULL); + + propAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice; + ((ma_AudioObjectRemovePropertyListener_proc)pContext->coreaudio.AudioObjectRemovePropertyListener)(kAudioObjectSystemObject, &propAddress, &ma_default_device_changed__coreaudio, NULL); + + /* At this point there should be no tracked devices. If not there's an error somewhere. */ + if (g_ppTrackedDevices_CoreAudio != NULL) { + ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_WARNING, "You have uninitialized all contexts while an associated device is still active.", MA_INVALID_OPERATION); + } + + ma_mutex_uninit(&g_DeviceTrackingMutex_CoreAudio); + } } ma_spinlock_unlock(&g_DeviceTrackingInitLock_CoreAudio); @@ -19056,27 +19175,31 @@ static ma_result ma_device_init_internal__coreaudio(ma_context* pContext, ma_dev #if defined(MA_APPLE_DESKTOP) AudioStreamBasicDescription origFormat; UInt32 origFormatSize; + + origFormatSize = sizeof(origFormat); + status = ((ma_AudioUnitGetProperty_proc)pContext->coreaudio.AudioUnitGetProperty)(pData->audioUnit, kAudioUnitProperty_StreamFormat, formatScope, formatElement, &origFormat, &origFormatSize); + if (status != noErr) { + ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); + return result; + } - result = ma_find_best_format__coreaudio(pContext, deviceObjectID, deviceType, pData->formatIn, pData->channelsIn, pData->sampleRateIn, pData->usingDefaultFormat, pData->usingDefaultChannels, pData->usingDefaultSampleRate, &bestFormat); + result = ma_find_best_format__coreaudio(pContext, deviceObjectID, deviceType, pData->formatIn, pData->channelsIn, pData->sampleRateIn, pData->usingDefaultFormat, pData->usingDefaultChannels, pData->usingDefaultSampleRate, &origFormat, &bestFormat); if (result != MA_SUCCESS) { ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); return result; } + /* + Update 2020-10-10: + + I cannot remember where I read this in the documentation and I cannot find it again. For now I'm going to remove this + and see what the feedback from the community is like. If this results in issues we can add it back in again. The idea + is that the closest sample rate natively supported by the backend to the requested sample rate should be used if possible. + */ + #if 0 /* From what I can see, Apple's documentation implies that we should keep the sample rate consistent. */ - origFormatSize = sizeof(origFormat); - if (deviceType == ma_device_type_playback) { - status = ((ma_AudioUnitGetProperty_proc)pContext->coreaudio.AudioUnitGetProperty)(pData->audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, MA_COREAUDIO_OUTPUT_BUS, &origFormat, &origFormatSize); - } else { - status = ((ma_AudioUnitGetProperty_proc)pContext->coreaudio.AudioUnitGetProperty)(pData->audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, MA_COREAUDIO_INPUT_BUS, &origFormat, &origFormatSize); - } - - if (status != noErr) { - ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); - return result; - } - bestFormat.mSampleRate = origFormat.mSampleRate; + #endif status = ((ma_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)(pData->audioUnit, kAudioUnitProperty_StreamFormat, formatScope, formatElement, &bestFormat, sizeof(bestFormat)); if (status != noErr) { @@ -31827,7 +31950,7 @@ static ma_result ma_resampler_process_pcm_frames__seek__linear(ma_resampler* pRe static ma_result ma_resampler_process_pcm_frames__seek__speex(ma_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, ma_uint64* pFrameCountOut) { /* The generic seek method is implemented in on top of ma_resampler_process_pcm_frames__read() by just processing into a dummy buffer. */ - float devnull[8192]; + float devnull[4096]; ma_uint64 totalOutputFramesToProcess; ma_uint64 totalOutputFramesProcessed; ma_uint64 totalInputFramesProcessed; @@ -36162,7 +36285,7 @@ MA_API ma_result ma_vfs_write(ma_vfs* pVFS, ma_vfs_file file, const void* pSrc, { ma_vfs_callbacks* pCallbacks = (ma_vfs_callbacks*)pVFS; - if (pBytesWritten == NULL) { + if (pBytesWritten != NULL) { *pBytesWritten = 0; } @@ -36399,9 +36522,13 @@ static ma_result ma_default_vfs_read__win32(ma_vfs* pVFS, ma_vfs_file file, void } readResult = ReadFile((HANDLE)file, ma_offset_ptr(pDst, totalBytesRead), bytesToRead, &bytesRead, NULL); + if (readResult == 1 && bytesRead == 0) { + break; /* EOF */ + } + totalBytesRead += bytesRead; - if (bytesRead < bytesToRead || (readResult == 1 && bytesRead == 0)) { + if (bytesRead < bytesToRead) { break; /* EOF */ } @@ -36448,7 +36575,7 @@ static ma_result ma_default_vfs_write__win32(ma_vfs* pVFS, ma_vfs_file file, con } } - if (pBytesWritten == NULL) { + if (pBytesWritten != NULL) { *pBytesWritten = totalBytesWritten; } @@ -36910,7 +37037,7 @@ extern "C" { #define DRWAV_XSTRINGIFY(x) DRWAV_STRINGIFY(x) #define DRWAV_VERSION_MAJOR 0 #define DRWAV_VERSION_MINOR 12 -#define DRWAV_VERSION_REVISION 10 +#define DRWAV_VERSION_REVISION 12 #define DRWAV_VERSION_STRING DRWAV_XSTRINGIFY(DRWAV_VERSION_MAJOR) "." DRWAV_XSTRINGIFY(DRWAV_VERSION_MINOR) "." DRWAV_XSTRINGIFY(DRWAV_VERSION_REVISION) #include typedef signed char drwav_int8; @@ -37049,7 +37176,8 @@ typedef enum typedef enum { drwav_container_riff, - drwav_container_w64 + drwav_container_w64, + drwav_container_rf64 } drwav_container; typedef struct { @@ -37282,7 +37410,7 @@ extern "C" { #define DRFLAC_XSTRINGIFY(x) DRFLAC_STRINGIFY(x) #define DRFLAC_VERSION_MAJOR 0 #define DRFLAC_VERSION_MINOR 12 -#define DRFLAC_VERSION_REVISION 19 +#define DRFLAC_VERSION_REVISION 20 #define DRFLAC_VERSION_STRING DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MAJOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MINOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_REVISION) #include typedef signed char drflac_int8; @@ -37643,7 +37771,7 @@ extern "C" { #define DRMP3_XSTRINGIFY(x) DRMP3_STRINGIFY(x) #define DRMP3_VERSION_MAJOR 0 #define DRMP3_VERSION_MINOR 6 -#define DRMP3_VERSION_REVISION 16 +#define DRMP3_VERSION_REVISION 17 #define DRMP3_VERSION_STRING DRMP3_XSTRINGIFY(DRMP3_VERSION_MAJOR) "." DRMP3_XSTRINGIFY(DRMP3_VERSION_MINOR) "." DRMP3_XSTRINGIFY(DRMP3_VERSION_REVISION) #include typedef signed char drmp3_int8; @@ -41684,14 +41812,14 @@ static DRWAV_INLINE drwav_uint64 drwav__bswap64(drwav_uint64 n) #error "This compiler does not support the byte swap intrinsic." #endif #else - return ((n & (drwav_uint64)0xFF00000000000000) >> 56) | - ((n & (drwav_uint64)0x00FF000000000000) >> 40) | - ((n & (drwav_uint64)0x0000FF0000000000) >> 24) | - ((n & (drwav_uint64)0x000000FF00000000) >> 8) | - ((n & (drwav_uint64)0x00000000FF000000) << 8) | - ((n & (drwav_uint64)0x0000000000FF0000) << 24) | - ((n & (drwav_uint64)0x000000000000FF00) << 40) | - ((n & (drwav_uint64)0x00000000000000FF) << 56); + return ((n & ((drwav_uint64)0xFF000000 << 32)) >> 56) | + ((n & ((drwav_uint64)0x00FF0000 << 32)) >> 40) | + ((n & ((drwav_uint64)0x0000FF00 << 32)) >> 24) | + ((n & ((drwav_uint64)0x000000FF << 32)) >> 8) | + ((n & ((drwav_uint64)0xFF000000 )) << 8) | + ((n & ((drwav_uint64)0x00FF0000 )) << 24) | + ((n & ((drwav_uint64)0x0000FF00 )) << 40) | + ((n & ((drwav_uint64)0x000000FF )) << 56); #endif } static DRWAV_INLINE drwav_int16 drwav__bswap_s16(drwav_int16 n) @@ -41927,7 +42055,7 @@ static drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uint64 sam static drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount); static drwav_result drwav__read_chunk_header(drwav_read_proc onRead, void* pUserData, drwav_container container, drwav_uint64* pRunningBytesReadOut, drwav_chunk_header* pHeaderOut) { - if (container == drwav_container_riff) { + if (container == drwav_container_riff || container == drwav_container_rf64) { drwav_uint8 sizeInBytes[4]; if (onRead(pUserData, pHeaderOut->id.fourcc, 4) != 4) { return DRWAV_AT_END; @@ -41996,7 +42124,7 @@ static drwav_bool32 drwav__read_fmt(drwav_read_proc onRead, drwav_seek_proc onSe if (drwav__read_chunk_header(onRead, pUserData, container, pRunningBytesReadOut, &header) != DRWAV_SUCCESS) { return DRWAV_FALSE; } - while ((container == drwav_container_riff && !drwav__fourcc_equal(header.id.fourcc, "fmt ")) || (container == drwav_container_w64 && !drwav__guid_equal(header.id.guid, drwavGUID_W64_FMT))) { + while (((container == drwav_container_riff || container == drwav_container_rf64) && !drwav__fourcc_equal(header.id.fourcc, "fmt ")) || (container == drwav_container_w64 && !drwav__guid_equal(header.id.guid, drwavGUID_W64_FMT))) { if (!drwav__seek_forward(onSeek, header.sizeInBytes + header.paddingSize, pUserData)) { return DRWAV_FALSE; } @@ -42005,7 +42133,7 @@ static drwav_bool32 drwav__read_fmt(drwav_read_proc onRead, drwav_seek_proc onSe return DRWAV_FALSE; } } - if (container == drwav_container_riff) { + if (container == drwav_container_riff || container == drwav_container_rf64) { if (!drwav__fourcc_equal(header.id.fourcc, "fmt ")) { return DRWAV_FALSE; } @@ -42138,9 +42266,9 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, drwav_uint8 riff[4]; drwav_fmt fmt; unsigned short translatedFormatTag; - drwav_uint64 sampleCountFromFactChunk; drwav_bool32 foundDataChunk; - drwav_uint64 dataChunkSize; + drwav_uint64 dataChunkSize = 0; + drwav_uint64 sampleCountFromFactChunk = 0; drwav_uint64 chunkSize; cursor = 0; sequential = (flags & DRWAV_SEQUENTIAL) != 0; @@ -42161,17 +42289,25 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, return DRWAV_FALSE; } } + } else if (drwav__fourcc_equal(riff, "RF64")) { + pWav->container = drwav_container_rf64; } else { return DRWAV_FALSE; } - if (pWav->container == drwav_container_riff) { + if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64) { drwav_uint8 chunkSizeBytes[4]; drwav_uint8 wave[4]; if (drwav__on_read(pWav->onRead, pWav->pUserData, chunkSizeBytes, sizeof(chunkSizeBytes), &cursor) != sizeof(chunkSizeBytes)) { return DRWAV_FALSE; } - if (drwav__bytes_to_u32(chunkSizeBytes) < 36) { - return DRWAV_FALSE; + if (pWav->container == drwav_container_riff) { + if (drwav__bytes_to_u32(chunkSizeBytes) < 36) { + return DRWAV_FALSE; + } + } else { + if (drwav__bytes_to_u32(chunkSizeBytes) != 0xFFFFFFFF) { + return DRWAV_FALSE; + } } if (drwav__on_read(pWav->onRead, pWav->pUserData, wave, sizeof(wave), &cursor) != sizeof(wave)) { return DRWAV_FALSE; @@ -42195,6 +42331,38 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, return DRWAV_FALSE; } } + if (pWav->container == drwav_container_rf64) { + drwav_uint8 sizeBytes[8]; + drwav_uint64 bytesRemainingInChunk; + drwav_chunk_header header; + drwav_result result = drwav__read_chunk_header(pWav->onRead, pWav->pUserData, pWav->container, &cursor, &header); + if (result != DRWAV_SUCCESS) { + return DRWAV_FALSE; + } + if (!drwav__fourcc_equal(header.id.fourcc, "ds64")) { + return DRWAV_FALSE; + } + bytesRemainingInChunk = header.sizeInBytes + header.paddingSize; + if (!drwav__seek_forward(pWav->onSeek, 8, pWav->pUserData)) { + return DRWAV_FALSE; + } + bytesRemainingInChunk -= 8; + cursor += 8; + if (drwav__on_read(pWav->onRead, pWav->pUserData, sizeBytes, sizeof(sizeBytes), &cursor) != sizeof(sizeBytes)) { + return DRWAV_FALSE; + } + bytesRemainingInChunk -= 8; + dataChunkSize = drwav__bytes_to_u64(sizeBytes); + if (drwav__on_read(pWav->onRead, pWav->pUserData, sizeBytes, sizeof(sizeBytes), &cursor) != sizeof(sizeBytes)) { + return DRWAV_FALSE; + } + bytesRemainingInChunk -= 8; + sampleCountFromFactChunk = drwav__bytes_to_u64(sizeBytes); + if (!drwav__seek_forward(pWav->onSeek, bytesRemainingInChunk, pWav->pUserData)) { + return DRWAV_FALSE; + } + cursor += bytesRemainingInChunk; + } if (!drwav__read_fmt(pWav->onRead, pWav->onSeek, pWav->pUserData, pWav->container, &cursor, &fmt)) { return DRWAV_FALSE; } @@ -42208,9 +42376,7 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, if (translatedFormatTag == DR_WAVE_FORMAT_EXTENSIBLE) { translatedFormatTag = drwav__bytes_to_u16(fmt.subFormat + 0); } - sampleCountFromFactChunk = 0; foundDataChunk = DRWAV_FALSE; - dataChunkSize = 0; for (;;) { drwav_chunk_header header; @@ -42234,10 +42400,12 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, pWav->dataChunkDataPos = cursor; } chunkSize = header.sizeInBytes; - if (pWav->container == drwav_container_riff) { + if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64) { if (drwav__fourcc_equal(header.id.fourcc, "data")) { foundDataChunk = DRWAV_TRUE; - dataChunkSize = chunkSize; + if (pWav->container != drwav_container_rf64) { + dataChunkSize = chunkSize; + } } } else { if (drwav__guid_equal(header.id.guid, drwavGUID_W64_DATA)) { @@ -42264,7 +42432,7 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, sampleCountFromFactChunk = 0; } } - } else { + } else if (pWav->container == drwav_container_w64) { if (drwav__guid_equal(header.id.guid, drwavGUID_W64_FACT)) { if (drwav__on_read(pWav->onRead, pWav->pUserData, &sampleCountFromFactChunk, 8, &cursor) != 8) { return DRWAV_FALSE; @@ -42274,8 +42442,9 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, pWav->dataChunkDataPos = cursor; } } + } else if (pWav->container == drwav_container_rf64) { } - if (pWav->container == drwav_container_riff) { + if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64) { if (drwav__fourcc_equal(header.id.fourcc, "smpl")) { drwav_uint8 smplHeaderData[36]; if (chunkSize >= sizeof(smplHeaderData)) { @@ -42394,12 +42563,11 @@ DRWAV_API drwav_bool32 drwav_init_ex(drwav* pWav, drwav_read_proc onRead, drwav_ } static drwav_uint32 drwav__riff_chunk_size_riff(drwav_uint64 dataChunkSize) { - drwav_uint32 dataSubchunkPaddingSize = drwav__chunk_padding_size_riff(dataChunkSize); - if (dataChunkSize <= (0xFFFFFFFFUL - 36 - dataSubchunkPaddingSize)) { - return 36 + (drwav_uint32)(dataChunkSize + dataSubchunkPaddingSize); - } else { - return 0xFFFFFFFF; + drwav_uint64 chunkSize = 4 + 24 + dataChunkSize + drwav__chunk_padding_size_riff(dataChunkSize); + if (chunkSize > 0xFFFFFFFFUL) { + chunkSize = 0xFFFFFFFFUL; } + return (drwav_uint32)chunkSize; } static drwav_uint32 drwav__data_chunk_size_riff(drwav_uint64 dataChunkSize) { @@ -42418,6 +42586,18 @@ static drwav_uint64 drwav__data_chunk_size_w64(drwav_uint64 dataChunkSize) { return 24 + dataChunkSize; } +static drwav_uint64 drwav__riff_chunk_size_rf64(drwav_uint64 dataChunkSize) +{ + drwav_uint64 chunkSize = 4 + 36 + 24 + dataChunkSize + drwav__chunk_padding_size_riff(dataChunkSize); + if (chunkSize > 0xFFFFFFFFUL) { + chunkSize = 0xFFFFFFFFUL; + } + return chunkSize; +} +static drwav_uint64 drwav__data_chunk_size_rf64(drwav_uint64 dataChunkSize) +{ + return dataChunkSize; +} static size_t drwav__write(drwav* pWav, const void* pData, size_t dataSize) { DRWAV_ASSERT(pWav != NULL); @@ -42498,21 +42678,35 @@ static drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_for } pWav->dataChunkDataSizeTargetWrite = initialDataChunkSize; if (pFormat->container == drwav_container_riff) { - drwav_uint32 chunkSizeRIFF = 36 + (drwav_uint32)initialDataChunkSize; + drwav_uint32 chunkSizeRIFF = 28 + (drwav_uint32)initialDataChunkSize; runningPos += drwav__write(pWav, "RIFF", 4); runningPos += drwav__write_u32ne_to_le(pWav, chunkSizeRIFF); runningPos += drwav__write(pWav, "WAVE", 4); - } else { + } else if (pFormat->container == drwav_container_w64) { drwav_uint64 chunkSizeRIFF = 80 + 24 + initialDataChunkSize; runningPos += drwav__write(pWav, drwavGUID_W64_RIFF, 16); runningPos += drwav__write_u64ne_to_le(pWav, chunkSizeRIFF); runningPos += drwav__write(pWav, drwavGUID_W64_WAVE, 16); + } else if (pFormat->container == drwav_container_rf64) { + runningPos += drwav__write(pWav, "RF64", 4); + runningPos += drwav__write_u32ne_to_le(pWav, 0xFFFFFFFF); + runningPos += drwav__write(pWav, "WAVE", 4); } - if (pFormat->container == drwav_container_riff) { + if (pFormat->container == drwav_container_rf64) { + drwav_uint32 initialds64ChunkSize = 28; + drwav_uint64 initialRiffChunkSize = 8 + initialds64ChunkSize + initialDataChunkSize; + runningPos += drwav__write(pWav, "ds64", 4); + runningPos += drwav__write_u32ne_to_le(pWav, initialds64ChunkSize); + runningPos += drwav__write_u64ne_to_le(pWav, initialRiffChunkSize); + runningPos += drwav__write_u64ne_to_le(pWav, initialDataChunkSize); + runningPos += drwav__write_u64ne_to_le(pWav, totalSampleCount); + runningPos += drwav__write_u32ne_to_le(pWav, 0); + } + if (pFormat->container == drwav_container_riff || pFormat->container == drwav_container_rf64) { chunkSizeFMT = 16; runningPos += drwav__write(pWav, "fmt ", 4); runningPos += drwav__write_u32ne_to_le(pWav, (drwav_uint32)chunkSizeFMT); - } else { + } else if (pFormat->container == drwav_container_w64) { chunkSizeFMT = 40; runningPos += drwav__write(pWav, drwavGUID_W64_FMT, 16); runningPos += drwav__write_u64ne_to_le(pWav, chunkSizeFMT); @@ -42528,19 +42722,13 @@ static drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_for drwav_uint32 chunkSizeDATA = (drwav_uint32)initialDataChunkSize; runningPos += drwav__write(pWav, "data", 4); runningPos += drwav__write_u32ne_to_le(pWav, chunkSizeDATA); - } else { + } else if (pFormat->container == drwav_container_w64) { drwav_uint64 chunkSizeDATA = 24 + initialDataChunkSize; runningPos += drwav__write(pWav, drwavGUID_W64_DATA, 16); runningPos += drwav__write_u64ne_to_le(pWav, chunkSizeDATA); - } - if (pFormat->container == drwav_container_riff) { - if (runningPos != 20 + chunkSizeFMT + 8) { - return DRWAV_FALSE; - } - } else { - if (runningPos != 40 + chunkSizeFMT + 24) { - return DRWAV_FALSE; - } + } else if (pFormat->container == drwav_container_rf64) { + runningPos += drwav__write(pWav, "data", 4); + runningPos += drwav__write_u32ne_to_le(pWav, 0xFFFFFFFF); } pWav->container = pFormat->container; pWav->channels = (drwav_uint16)pFormat->channels; @@ -42574,13 +42762,16 @@ DRWAV_API drwav_uint64 drwav_target_write_size_bytes(const drwav_data_format* pF { drwav_uint64 targetDataSizeBytes = (drwav_uint64)((drwav_int64)totalSampleCount * pFormat->channels * pFormat->bitsPerSample/8.0); drwav_uint64 riffChunkSizeBytes; - drwav_uint64 fileSizeBytes; + drwav_uint64 fileSizeBytes = 0; if (pFormat->container == drwav_container_riff) { riffChunkSizeBytes = drwav__riff_chunk_size_riff(targetDataSizeBytes); fileSizeBytes = (8 + riffChunkSizeBytes); - } else { + } else if (pFormat->container == drwav_container_w64) { riffChunkSizeBytes = drwav__riff_chunk_size_w64(targetDataSizeBytes); fileSizeBytes = riffChunkSizeBytes; + } else if (pFormat->container == drwav_container_rf64) { + riffChunkSizeBytes = drwav__riff_chunk_size_rf64(targetDataSizeBytes); + fileSizeBytes = (8 + riffChunkSizeBytes); } return fileSizeBytes; } @@ -43352,7 +43543,7 @@ DRWAV_API drwav_result drwav_uninit(drwav* pWav) } if (pWav->onWrite != NULL) { drwav_uint32 paddingSize = 0; - if (pWav->container == drwav_container_riff) { + if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64) { paddingSize = drwav__chunk_padding_size_riff(pWav->dataChunkDataSize); } else { paddingSize = drwav__chunk_padding_size_w64(pWav->dataChunkDataSize); @@ -43371,7 +43562,7 @@ DRWAV_API drwav_result drwav_uninit(drwav* pWav) drwav_uint32 dataChunkSize = drwav__data_chunk_size_riff(pWav->dataChunkDataSize); drwav__write_u32ne_to_le(pWav, dataChunkSize); } - } else { + } else if (pWav->container == drwav_container_w64) { if (pWav->onSeek(pWav->pUserData, 16, drwav_seek_origin_start)) { drwav_uint64 riffChunkSize = drwav__riff_chunk_size_w64(pWav->dataChunkDataSize); drwav__write_u64ne_to_le(pWav, riffChunkSize); @@ -43380,6 +43571,16 @@ DRWAV_API drwav_result drwav_uninit(drwav* pWav) drwav_uint64 dataChunkSize = drwav__data_chunk_size_w64(pWav->dataChunkDataSize); drwav__write_u64ne_to_le(pWav, dataChunkSize); } + } else if (pWav->container == drwav_container_rf64) { + int ds64BodyPos = 12 + 8; + if (pWav->onSeek(pWav->pUserData, ds64BodyPos + 0, drwav_seek_origin_start)) { + drwav_uint64 riffChunkSize = drwav__riff_chunk_size_rf64(pWav->dataChunkDataSize); + drwav__write_u64ne_to_le(pWav, riffChunkSize); + } + if (pWav->onSeek(pWav->pUserData, ds64BodyPos + 8, drwav_seek_origin_start)) { + drwav_uint64 dataChunkSize = drwav__data_chunk_size_rf64(pWav->dataChunkDataSize); + drwav__write_u64ne_to_le(pWav, dataChunkSize); + } } } if (pWav->isSequentialWrite) { @@ -45592,14 +45793,14 @@ static DRFLAC_INLINE drflac_uint64 drflac__swap_endian_uint64(drflac_uint64 n) #error "This compiler does not support the byte swap intrinsic." #endif #else - return ((n & (drflac_uint64)0xFF00000000000000) >> 56) | - ((n & (drflac_uint64)0x00FF000000000000) >> 40) | - ((n & (drflac_uint64)0x0000FF0000000000) >> 24) | - ((n & (drflac_uint64)0x000000FF00000000) >> 8) | - ((n & (drflac_uint64)0x00000000FF000000) << 8) | - ((n & (drflac_uint64)0x0000000000FF0000) << 24) | - ((n & (drflac_uint64)0x000000000000FF00) << 40) | - ((n & (drflac_uint64)0x00000000000000FF) << 56); + return ((n & ((drflac_uint64)0xFF000000 << 32)) >> 56) | + ((n & ((drflac_uint64)0x00FF0000 << 32)) >> 40) | + ((n & ((drflac_uint64)0x0000FF00 << 32)) >> 24) | + ((n & ((drflac_uint64)0x000000FF << 32)) >> 8) | + ((n & ((drflac_uint64)0xFF000000 )) << 8) | + ((n & ((drflac_uint64)0x00FF0000 )) << 24) | + ((n & ((drflac_uint64)0x0000FF00 )) << 40) | + ((n & ((drflac_uint64)0x000000FF )) << 56); #endif } static DRFLAC_INLINE drflac_uint16 drflac__be2host_16(drflac_uint16 n) @@ -53483,6 +53684,8 @@ static __inline__ __attribute__((always_inline)) drmp3_int32 drmp3_clip_int16_ar __asm__ ("ssat %0, #16, %1" : "=r"(x) : "r"(a)); return x; } +#else +#define DRMP3_HAVE_ARMV6 0 #endif typedef struct { diff --git a/extras/miniaudio_split/miniaudio.h b/extras/miniaudio_split/miniaudio.h index abd865a5..f24d0ed3 100644 --- a/extras/miniaudio_split/miniaudio.h +++ b/extras/miniaudio_split/miniaudio.h @@ -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.10.20 - 2020-10-06 +miniaudio - v0.10.21 - 2020-10-30 David Reid - mackron@gmail.com @@ -20,7 +20,7 @@ extern "C" { #define MA_VERSION_MAJOR 0 #define MA_VERSION_MINOR 10 -#define MA_VERSION_REVISION 20 +#define MA_VERSION_REVISION 21 #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__) @@ -1525,6 +1525,8 @@ typedef enum ma_backend_null /* <-- Must always be the last item. Lowest priority, and used as the terminator for backend enumeration. */ } ma_backend; +#define MA_BACKEND_COUNT (ma_backend_null+1) + /* The callback for processing audio data from the device. @@ -2474,7 +2476,7 @@ struct ma_device ma_uint32 currentPeriodFramesRemainingPlayback; ma_uint32 currentPeriodFramesRemainingCapture; ma_uint64 lastProcessedFramePlayback; - ma_uint32 lastProcessedFrameCapture; + ma_uint64 lastProcessedFrameCapture; ma_bool32 isStarted; } null_device; #endif @@ -3674,6 +3676,84 @@ Retrieves a friendly name for a backend. */ MA_API const char* ma_get_backend_name(ma_backend backend); +/* +Determines whether or not the given backend is available by the compilation environment. +*/ +MA_API ma_bool32 ma_is_backend_enabled(ma_backend backend); + +/* +Retrieves compile-time enabled backends. + + +Parameters +---------- +pBackends(out, optional) + A pointer to the buffer that will receive the enabled backends. Set to NULL to retrieve the backend count. Setting + the capacity of the buffer to `MA_BUFFER_COUNT` will guarantee it's large enough for all backends. + +backendCap(in) + The capacity of the `pBackends` buffer. + +pBackendCount(out) + A pointer to the variable that will receive the enabled backend count. + + +Return Value +------------ +MA_SUCCESS if successful. +MA_INVALID_ARGS if `pBackendCount` is NULL. +MA_NO_SPACE if the capacity of `pBackends` is not large enough. + +If `MA_NO_SPACE` is returned, the `pBackends` buffer will be filled with `*pBackendCount` values. + + +Thread Safety +------------- +Safe. + + +Callback Safety +--------------- +Safe. + + +Remarks +------- +If you want to retrieve the number of backends so you can determine the capacity of `pBackends` buffer, you can call +this function with `pBackends` set to NULL. + +This will also enumerate the null backend. If you don't want to include this you need to check for `ma_backend_null` +when you enumerate over the returned backends and handle it appropriately. Alternatively, you can disable it at +compile time with `MA_NO_NULL`. + +The returned backends are determined based on compile time settings, not the platform it's currently running on. For +example, PulseAudio will be returned if it was enabled at compile time, even when the user doesn't actually have +PulseAudio installed. + + +Example 1 +--------- +The example below retrieves the enabled backend count using a fixed sized buffer allocated on the stack. The buffer is +given a capacity of `MA_BACKEND_COUNT` which will guarantee it'll be large enough to store all available backends. +Since `MA_BACKEND_COUNT` is always a relatively small value, this should be suitable for most scenarios. + +``` +ma_backend enabledBackends[MA_BACKEND_COUNT]; +size_t enabledBackendCount; + +result = ma_get_enabled_backends(enabledBackends, MA_BACKEND_COUNT, &enabledBackendCount); +if (result != MA_SUCCESS) { + // Failed to retrieve enabled backends. Should never happen in this example since all inputs are valid. +} +``` + + +See Also +-------- +ma_is_backend_enabled() +*/ +MA_API ma_result ma_get_enabled_backends(ma_backend* pBackends, size_t backendCap, size_t* pBackendCount); + /* Determines whether or not loopback mode is support by a backend. */ diff --git a/miniaudio.h b/miniaudio.h index 4e2f05a9..f1c76d59 100644 --- a/miniaudio.h +++ b/miniaudio.h @@ -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.10.21 - TBD +miniaudio - v0.10.21 - 2020-10-30 David Reid - mackron@gmail.com @@ -62859,7 +62859,7 @@ The following miscellaneous changes have also been made. /* REVISION HISTORY ================ -v0.10.21 - TBD +v0.10.21 - 2020-10-30 - Add ma_is_backend_enabled() and ma_get_enabled_backends() for retrieving enabled backends at run-time. - WASAPI: Fix a copy and paste bug relating to loopback mode. - Core Audio: Fix a bug when using multiple contexts. From 3018ba3ee5db212cef2dd6785f342fbbbb7ef30f Mon Sep 17 00:00:00 2001 From: David Reid Date: Sat, 31 Oct 2020 16:43:25 +1000 Subject: [PATCH 04/27] Some refactoring to the PulseAudio backend. The PulseAudio mainloop and context objects have been moved out of the device and into the context. --- miniaudio.h | 427 +++++++------------------ tests/test_deviceio/ma_test_deviceio.c | 5 + 2 files changed, 124 insertions(+), 308 deletions(-) diff --git a/miniaudio.h b/miniaudio.h index f1c76d59..fb79e552 100644 --- a/miniaudio.h +++ b/miniaudio.h @@ -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.10.21 - 2020-10-30 +miniaudio - v0.10.22 - TBD David Reid - mackron@gmail.com @@ -1443,7 +1443,7 @@ extern "C" { #define MA_VERSION_MAJOR 0 #define MA_VERSION_MINOR 10 -#define MA_VERSION_REVISION 21 +#define MA_VERSION_REVISION 22 #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__) @@ -3432,9 +3432,9 @@ struct ma_context ma_proc pa_stream_writable_size; ma_proc pa_stream_readable_size; - char* pApplicationName; - char* pServerName; - ma_bool32 tryAutoSpawn; + /*pa_mainloop**/ ma_ptr pMainLoop; + /*pa_context**/ ma_ptr pPulseContext; + /*pa_context_state*/ ma_uint32 pulseContextState; } pulse; #endif #ifdef MA_SUPPORT_JACK @@ -3781,12 +3781,8 @@ struct ma_device #ifdef MA_SUPPORT_PULSEAUDIO struct { - /*pa_mainloop**/ ma_ptr pMainLoop; - /*pa_mainloop_api**/ ma_ptr pAPI; - /*pa_context**/ ma_ptr pPulseContext; /*pa_stream**/ ma_ptr pStreamPlayback; /*pa_stream**/ ma_ptr pStreamCapture; - /*pa_context_state*/ ma_uint32 pulseContextState; void* pMappedBufferPlayback; const void* pMappedBufferCapture; ma_uint32 mappedBufferFramesRemainingPlayback; @@ -20299,6 +20295,10 @@ typedef struct static ma_result ma_result_from_pulse(int result) { + if (result < 0) { + return MA_ERROR; + } + switch (result) { case MA_PA_OK: return MA_SUCCESS; case MA_PA_ERR_ACCESS: return MA_ACCESS_DENIED; @@ -20467,14 +20467,13 @@ static ma_pa_channel_position_t ma_channel_position_to_pulse(ma_channel position } #endif -static ma_result ma_wait_for_operation__pulse(ma_context* pContext, ma_pa_mainloop* pMainLoop, ma_pa_operation* pOP) +static ma_result ma_wait_for_operation__pulse(ma_context* pContext, ma_pa_operation* pOP) { MA_ASSERT(pContext != NULL); - MA_ASSERT(pMainLoop != NULL); MA_ASSERT(pOP != NULL); while (((ma_pa_operation_get_state_proc)pContext->pulse.pa_operation_get_state)(pOP) == MA_PA_OPERATION_RUNNING) { - int error = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)(pMainLoop, 1, NULL); + int error = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)(pContext->pulse.pMainLoop, 1, NULL); if (error < 0) { return ma_result_from_pulse(error); } @@ -20483,15 +20482,6 @@ static ma_result ma_wait_for_operation__pulse(ma_context* pContext, ma_pa_mainlo return MA_SUCCESS; } -static ma_result ma_device__wait_for_operation__pulse(ma_device* pDevice, ma_pa_operation* pOP) -{ - MA_ASSERT(pDevice != NULL); - MA_ASSERT(pOP != NULL); - - return ma_wait_for_operation__pulse(pDevice->pContext, (ma_pa_mainloop*)pDevice->pulse.pMainLoop, pOP); -} - - static ma_bool32 ma_context_is_device_id_equal__pulse(ma_context* pContext, const ma_device_id* pID0, const ma_device_id* pID1) { MA_ASSERT(pContext != NULL); @@ -20572,10 +20562,6 @@ static ma_result ma_context_enumerate_devices__pulse(ma_context* pContext, ma_en ma_result result = MA_SUCCESS; ma_context_enumerate_devices_callback_data__pulse callbackData; ma_pa_operation* pOP = NULL; - ma_pa_mainloop* pMainLoop; - ma_pa_mainloop_api* pAPI; - ma_pa_context* pPulseContext; - int error; MA_ASSERT(pContext != NULL); MA_ASSERT(callback != NULL); @@ -20585,65 +20571,15 @@ static ma_result ma_context_enumerate_devices__pulse(ma_context* pContext, ma_en callbackData.pUserData = pUserData; callbackData.isTerminated = MA_FALSE; - pMainLoop = ((ma_pa_mainloop_new_proc)pContext->pulse.pa_mainloop_new)(); - if (pMainLoop == NULL) { - return MA_FAILED_TO_INIT_BACKEND; - } - - pAPI = ((ma_pa_mainloop_get_api_proc)pContext->pulse.pa_mainloop_get_api)(pMainLoop); - if (pAPI == NULL) { - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); - return MA_FAILED_TO_INIT_BACKEND; - } - - pPulseContext = ((ma_pa_context_new_proc)pContext->pulse.pa_context_new)(pAPI, pContext->pulse.pApplicationName); - if (pPulseContext == NULL) { - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); - return MA_FAILED_TO_INIT_BACKEND; - } - - error = ((ma_pa_context_connect_proc)pContext->pulse.pa_context_connect)(pPulseContext, pContext->pulse.pServerName, (pContext->pulse.tryAutoSpawn) ? 0 : MA_PA_CONTEXT_NOAUTOSPAWN, NULL); - if (error != MA_PA_OK) { - ((ma_pa_context_unref_proc)pContext->pulse.pa_context_unref)(pPulseContext); - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); - return ma_result_from_pulse(error); - } - - for (;;) { - ma_pa_context_state_t state = ((ma_pa_context_get_state_proc)pContext->pulse.pa_context_get_state)(pPulseContext); - if (state == MA_PA_CONTEXT_READY) { - break; /* Success. */ - } - if (state == MA_PA_CONTEXT_CONNECTING || state == MA_PA_CONTEXT_AUTHORIZING || state == MA_PA_CONTEXT_SETTING_NAME) { - error = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)(pMainLoop, 1, NULL); - if (error < 0) { - result = ma_result_from_pulse(error); - goto done; - } - -#ifdef MA_DEBUG_OUTPUT - printf("[PulseAudio] pa_context_get_state() returned %d. Waiting.\n", state); -#endif - continue; /* Keep trying. */ - } - if (state == MA_PA_CONTEXT_UNCONNECTED || state == MA_PA_CONTEXT_FAILED || state == MA_PA_CONTEXT_TERMINATED) { -#ifdef MA_DEBUG_OUTPUT - printf("[PulseAudio] pa_context_get_state() returned %d. Failed.\n", state); -#endif - goto done; /* Failed. */ - } - } - - /* Playback. */ if (!callbackData.isTerminated) { - pOP = ((ma_pa_context_get_sink_info_list_proc)pContext->pulse.pa_context_get_sink_info_list)(pPulseContext, ma_context_enumerate_devices_sink_callback__pulse, &callbackData); + pOP = ((ma_pa_context_get_sink_info_list_proc)pContext->pulse.pa_context_get_sink_info_list)(pContext->pulse.pPulseContext, ma_context_enumerate_devices_sink_callback__pulse, &callbackData); if (pOP == NULL) { result = MA_ERROR; goto done; } - result = ma_wait_for_operation__pulse(pContext, pMainLoop, pOP); + result = ma_wait_for_operation__pulse(pContext, pOP); ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); if (result != MA_SUCCESS) { goto done; @@ -20653,13 +20589,13 @@ static ma_result ma_context_enumerate_devices__pulse(ma_context* pContext, ma_en /* Capture. */ if (!callbackData.isTerminated) { - pOP = ((ma_pa_context_get_source_info_list_proc)pContext->pulse.pa_context_get_source_info_list)(pPulseContext, ma_context_enumerate_devices_source_callback__pulse, &callbackData); + pOP = ((ma_pa_context_get_source_info_list_proc)pContext->pulse.pa_context_get_source_info_list)(pContext->pulse.pPulseContext, ma_context_enumerate_devices_source_callback__pulse, &callbackData); if (pOP == NULL) { result = MA_ERROR; goto done; } - result = ma_wait_for_operation__pulse(pContext, pMainLoop, pOP); + result = ma_wait_for_operation__pulse(pContext, pOP); ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); if (result != MA_SUCCESS) { goto done; @@ -20667,9 +20603,6 @@ static ma_result ma_context_enumerate_devices__pulse(ma_context* pContext, ma_en } done: - ((ma_pa_context_disconnect_proc)pContext->pulse.pa_context_disconnect)(pPulseContext); - ((ma_pa_context_unref_proc)pContext->pulse.pa_context_unref)(pPulseContext); - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); return result; } @@ -20743,10 +20676,6 @@ static ma_result ma_context_get_device_info__pulse(ma_context* pContext, ma_devi ma_result result = MA_SUCCESS; ma_context_get_device_info_callback_data__pulse callbackData; ma_pa_operation* pOP = NULL; - ma_pa_mainloop* pMainLoop; - ma_pa_mainloop_api* pAPI; - ma_pa_context* pPulseContext; - int error; MA_ASSERT(pContext != NULL); @@ -20758,63 +20687,14 @@ static ma_result ma_context_get_device_info__pulse(ma_context* pContext, ma_devi callbackData.pDeviceInfo = pDeviceInfo; callbackData.foundDevice = MA_FALSE; - pMainLoop = ((ma_pa_mainloop_new_proc)pContext->pulse.pa_mainloop_new)(); - if (pMainLoop == NULL) { - return MA_FAILED_TO_INIT_BACKEND; - } - - pAPI = ((ma_pa_mainloop_get_api_proc)pContext->pulse.pa_mainloop_get_api)(pMainLoop); - if (pAPI == NULL) { - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); - return MA_FAILED_TO_INIT_BACKEND; - } - - pPulseContext = ((ma_pa_context_new_proc)pContext->pulse.pa_context_new)(pAPI, pContext->pulse.pApplicationName); - if (pPulseContext == NULL) { - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); - return MA_FAILED_TO_INIT_BACKEND; - } - - error = ((ma_pa_context_connect_proc)pContext->pulse.pa_context_connect)(pPulseContext, pContext->pulse.pServerName, 0, NULL); - if (error != MA_PA_OK) { - ((ma_pa_context_unref_proc)pContext->pulse.pa_context_unref)(pPulseContext); - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); - return ma_result_from_pulse(error); - } - - for (;;) { - ma_pa_context_state_t state = ((ma_pa_context_get_state_proc)pContext->pulse.pa_context_get_state)(pPulseContext); - if (state == MA_PA_CONTEXT_READY) { - break; /* Success. */ - } - if (state == MA_PA_CONTEXT_CONNECTING || state == MA_PA_CONTEXT_AUTHORIZING || state == MA_PA_CONTEXT_SETTING_NAME) { - error = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)(pMainLoop, 1, NULL); - if (error < 0) { - result = ma_result_from_pulse(error); - goto done; - } - -#ifdef MA_DEBUG_OUTPUT - printf("[PulseAudio] pa_context_get_state() returned %d. Waiting.\n", state); -#endif - continue; /* Keep trying. */ - } - if (state == MA_PA_CONTEXT_UNCONNECTED || state == MA_PA_CONTEXT_FAILED || state == MA_PA_CONTEXT_TERMINATED) { -#ifdef MA_DEBUG_OUTPUT - printf("[PulseAudio] pa_context_get_state() returned %d. Failed.\n", state); -#endif - goto done; /* Failed. */ - } - } - if (deviceType == ma_device_type_playback) { - pOP = ((ma_pa_context_get_sink_info_by_name_proc)pContext->pulse.pa_context_get_sink_info_by_name)(pPulseContext, pDeviceID->pulse, ma_context_get_device_info_sink_callback__pulse, &callbackData); + pOP = ((ma_pa_context_get_sink_info_by_name_proc)pContext->pulse.pa_context_get_sink_info_by_name)(pContext->pulse.pPulseContext, pDeviceID->pulse, ma_context_get_device_info_sink_callback__pulse, &callbackData); } else { - pOP = ((ma_pa_context_get_source_info_by_name_proc)pContext->pulse.pa_context_get_source_info_by_name)(pPulseContext, pDeviceID->pulse, ma_context_get_device_info_source_callback__pulse, &callbackData); + pOP = ((ma_pa_context_get_source_info_by_name_proc)pContext->pulse.pa_context_get_source_info_by_name)(pContext->pulse.pPulseContext, pDeviceID->pulse, ma_context_get_device_info_source_callback__pulse, &callbackData); } if (pOP != NULL) { - ma_wait_for_operation__pulse(pContext, pMainLoop, pOP); + ma_wait_for_operation__pulse(pContext, pOP); ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); } else { result = MA_ERROR; @@ -20826,28 +20706,12 @@ static ma_result ma_context_get_device_info__pulse(ma_context* pContext, ma_devi goto done; } - done: - ((ma_pa_context_disconnect_proc)pContext->pulse.pa_context_disconnect)(pPulseContext); - ((ma_pa_context_unref_proc)pContext->pulse.pa_context_unref)(pPulseContext); - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); return result; } -static void ma_pulse_device_state_callback(ma_pa_context* pPulseContext, void* pUserData) -{ - ma_device* pDevice; - ma_context* pContext; - pDevice = (ma_device*)pUserData; - MA_ASSERT(pDevice != NULL); - - pContext = pDevice->pContext; - MA_ASSERT(pContext != NULL); - - pDevice->pulse.pulseContextState = ((ma_pa_context_get_state_proc)pContext->pulse.pa_context_get_state)(pPulseContext); -} static void ma_device_sink_info_callback(ma_pa_context* pPulseContext, const ma_pa_sink_info* pInfo, int endOfList, void* pUserData) { @@ -20926,14 +20790,11 @@ static void ma_device_uninit__pulse(ma_device* pDevice) ((ma_pa_stream_disconnect_proc)pContext->pulse.pa_stream_disconnect)((ma_pa_stream*)pDevice->pulse.pStreamCapture); ((ma_pa_stream_unref_proc)pContext->pulse.pa_stream_unref)((ma_pa_stream*)pDevice->pulse.pStreamCapture); } + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { ((ma_pa_stream_disconnect_proc)pContext->pulse.pa_stream_disconnect)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); ((ma_pa_stream_unref_proc)pContext->pulse.pa_stream_unref)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); } - - ((ma_pa_context_disconnect_proc)pContext->pulse.pa_context_disconnect)((ma_pa_context*)pDevice->pulse.pPulseContext); - ((ma_pa_context_unref_proc)pContext->pulse.pa_context_unref)((ma_pa_context*)pDevice->pulse.pPulseContext); - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)pDevice->pulse.pMainLoop); } static ma_pa_buffer_attr ma_device__pa_buffer_attr_new(ma_uint32 periodSizeInFrames, ma_uint32 periods, const ma_pa_sample_spec* ss) @@ -20948,7 +20809,7 @@ static ma_pa_buffer_attr ma_device__pa_buffer_attr_new(ma_uint32 periodSizeInFra return attr; } -static ma_pa_stream* ma_device__pa_stream_new__pulse(ma_device* pDevice, const char* pStreamName, const ma_pa_sample_spec* ss, const ma_pa_channel_map* cmap) +static ma_pa_stream* ma_context__pa_stream_new__pulse(ma_context* pContext, const char* pStreamName, const ma_pa_sample_spec* ss, const ma_pa_channel_map* cmap) { static int g_StreamCounter = 0; char actualStreamName[256]; @@ -20961,7 +20822,7 @@ static ma_pa_stream* ma_device__pa_stream_new__pulse(ma_device* pDevice, const c } g_StreamCounter += 1; - return ((ma_pa_stream_new_proc)pDevice->pContext->pulse.pa_stream_new)((ma_pa_context*)pDevice->pulse.pPulseContext, actualStreamName, ss, cmap); + return ((ma_pa_stream_new_proc)pContext->pulse.pa_stream_new)((ma_pa_context*)pContext->pulse.pPulseContext, actualStreamName, ss, cmap); } static ma_result ma_device_init__pulse(ma_context* pContext, const ma_device_config* pConfig, ma_device* pDevice) @@ -21008,64 +20869,17 @@ static ma_result ma_device_init__pulse(ma_context* pContext, const ma_device_con periodSizeInMilliseconds = ma_calculate_buffer_size_in_milliseconds_from_frames(pConfig->periodSizeInFrames, pConfig->sampleRate); } - pDevice->pulse.pMainLoop = ((ma_pa_mainloop_new_proc)pContext->pulse.pa_mainloop_new)(); - if (pDevice->pulse.pMainLoop == NULL) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create main loop for device.", MA_FAILED_TO_INIT_BACKEND); - goto on_error0; - } - - pDevice->pulse.pAPI = ((ma_pa_mainloop_get_api_proc)pContext->pulse.pa_mainloop_get_api)((ma_pa_mainloop*)pDevice->pulse.pMainLoop); - if (pDevice->pulse.pAPI == NULL) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to retrieve PulseAudio main loop.", MA_FAILED_TO_INIT_BACKEND); - goto on_error1; - } - - pDevice->pulse.pPulseContext = ((ma_pa_context_new_proc)pContext->pulse.pa_context_new)((ma_pa_mainloop_api*)pDevice->pulse.pAPI, pContext->pulse.pApplicationName); - if (pDevice->pulse.pPulseContext == NULL) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio context for device.", MA_FAILED_TO_INIT_BACKEND); - goto on_error1; - } - - error = ((ma_pa_context_connect_proc)pContext->pulse.pa_context_connect)((ma_pa_context*)pDevice->pulse.pPulseContext, pContext->pulse.pServerName, (pContext->pulse.tryAutoSpawn) ? 0 : MA_PA_CONTEXT_NOAUTOSPAWN, NULL); - if (error != MA_PA_OK) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio context.", ma_result_from_pulse(error)); - goto on_error2; - } - - - pDevice->pulse.pulseContextState = MA_PA_CONTEXT_UNCONNECTED; - ((ma_pa_context_set_state_callback_proc)pContext->pulse.pa_context_set_state_callback)((ma_pa_context*)pDevice->pulse.pPulseContext, ma_pulse_device_state_callback, pDevice); - - /* Wait for PulseAudio to get itself ready before returning. */ - for (;;) { - if (pDevice->pulse.pulseContextState == MA_PA_CONTEXT_READY) { - break; - } - - /* An error may have occurred. */ - if (pDevice->pulse.pulseContextState == MA_PA_CONTEXT_FAILED || pDevice->pulse.pulseContextState == MA_PA_CONTEXT_TERMINATED) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while connecting the PulseAudio context.", MA_ERROR); - goto on_error3; - } - - error = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pDevice->pulse.pMainLoop, 1, NULL); - if (error < 0) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] The PulseAudio main loop returned an error while connecting the PulseAudio context.", ma_result_from_pulse(error)); - goto on_error3; - } - } - if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { - pOP = ((ma_pa_context_get_source_info_by_name_proc)pContext->pulse.pa_context_get_source_info_by_name)((ma_pa_context*)pDevice->pulse.pPulseContext, devCapture, ma_device_source_info_callback, &sourceInfo); + pOP = ((ma_pa_context_get_source_info_by_name_proc)pContext->pulse.pa_context_get_source_info_by_name)((ma_pa_context*)pContext->pulse.pPulseContext, devCapture, ma_device_source_info_callback, &sourceInfo); if (pOP != NULL) { - ma_device__wait_for_operation__pulse(pDevice, pOP); + ma_wait_for_operation__pulse(pContext, pOP); ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); } else { result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to retrieve source info for capture device.", ma_result_from_pulse(error)); - goto on_error3; + goto on_error0; } - ss = sourceInfo.sample_spec; + ss = sourceInfo.sample_spec; cmap = sourceInfo.channel_map; pDevice->capture.internalPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(periodSizeInMilliseconds, ss.rate); @@ -21076,10 +20890,10 @@ static ma_result ma_device_init__pulse(ma_context* pContext, const ma_device_con printf("[PulseAudio] Capture attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; internalPeriodSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDevice->capture.internalPeriodSizeInFrames); #endif - pDevice->pulse.pStreamCapture = ma_device__pa_stream_new__pulse(pDevice, pConfig->pulse.pStreamNameCapture, &ss, &cmap); + pDevice->pulse.pStreamCapture = ma_context__pa_stream_new__pulse(pContext, pConfig->pulse.pStreamNameCapture, &ss, &cmap); if (pDevice->pulse.pStreamCapture == NULL) { result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio capture stream.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); - goto on_error3; + goto on_error0; } streamFlags = MA_PA_STREAM_START_CORKED | MA_PA_STREAM_FIX_FORMAT | MA_PA_STREAM_FIX_RATE | MA_PA_STREAM_FIX_CHANNELS; @@ -21090,14 +20904,14 @@ static ma_result ma_device_init__pulse(ma_context* pContext, const ma_device_con error = ((ma_pa_stream_connect_record_proc)pContext->pulse.pa_stream_connect_record)((ma_pa_stream*)pDevice->pulse.pStreamCapture, devCapture, &attr, streamFlags); if (error != MA_PA_OK) { result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio capture stream.", ma_result_from_pulse(error)); - goto on_error4; + goto on_error1; } while (((ma_pa_stream_get_state_proc)pContext->pulse.pa_stream_get_state)((ma_pa_stream*)pDevice->pulse.pStreamCapture) != MA_PA_STREAM_READY) { - error = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pDevice->pulse.pMainLoop, 1, NULL); + error = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pContext->pulse.pMainLoop, 1, NULL); if (error < 0) { result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] The PulseAudio main loop returned an error while connecting the PulseAudio capture stream.", ma_result_from_pulse(error)); - goto on_error5; + goto on_error2; } } @@ -21110,7 +20924,7 @@ static ma_result ma_device_init__pulse(ma_context* pContext, const ma_device_con pOP = ((ma_pa_stream_set_buffer_attr_proc)pContext->pulse.pa_stream_set_buffer_attr)((ma_pa_stream*)pDevice->pulse.pStreamCapture, &attr, NULL, NULL); if (pOP != NULL) { - ma_device__wait_for_operation__pulse(pDevice, pOP); + ma_wait_for_operation__pulse(pContext, pOP); ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); } } @@ -21145,25 +20959,25 @@ static ma_result ma_device_init__pulse(ma_context* pContext, const ma_device_con /* Name. */ devCapture = ((ma_pa_stream_get_device_name_proc)pContext->pulse.pa_stream_get_device_name)((ma_pa_stream*)pDevice->pulse.pStreamCapture); if (devCapture != NULL) { - ma_pa_operation* pOP = ((ma_pa_context_get_source_info_by_name_proc)pContext->pulse.pa_context_get_source_info_by_name)((ma_pa_context*)pDevice->pulse.pPulseContext, devCapture, ma_device_source_name_callback, pDevice); + ma_pa_operation* pOP = ((ma_pa_context_get_source_info_by_name_proc)pContext->pulse.pa_context_get_source_info_by_name)((ma_pa_context*)pContext->pulse.pPulseContext, devCapture, ma_device_source_name_callback, pDevice); if (pOP != NULL) { - ma_device__wait_for_operation__pulse(pDevice, pOP); + ma_wait_for_operation__pulse(pContext, pOP); ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); } } } if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { - pOP = ((ma_pa_context_get_sink_info_by_name_proc)pContext->pulse.pa_context_get_sink_info_by_name)((ma_pa_context*)pDevice->pulse.pPulseContext, devPlayback, ma_device_sink_info_callback, &sinkInfo); + pOP = ((ma_pa_context_get_sink_info_by_name_proc)pContext->pulse.pa_context_get_sink_info_by_name)((ma_pa_context*)pContext->pulse.pPulseContext, devPlayback, ma_device_sink_info_callback, &sinkInfo); if (pOP != NULL) { - ma_device__wait_for_operation__pulse(pDevice, pOP); + ma_wait_for_operation__pulse(pDevice->pContext, pOP); ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); } else { result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to retrieve sink info for playback device.", ma_result_from_pulse(error)); - goto on_error3; + goto on_error2; } - ss = sinkInfo.sample_spec; + ss = sinkInfo.sample_spec; cmap = sinkInfo.channel_map; pDevice->playback.internalPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(periodSizeInMilliseconds, ss.rate); @@ -21174,10 +20988,10 @@ static ma_result ma_device_init__pulse(ma_context* pContext, const ma_device_con printf("[PulseAudio] Playback attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; internalPeriodSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDevice->playback.internalPeriodSizeInFrames); #endif - pDevice->pulse.pStreamPlayback = ma_device__pa_stream_new__pulse(pDevice, pConfig->pulse.pStreamNamePlayback, &ss, &cmap); + pDevice->pulse.pStreamPlayback = ma_context__pa_stream_new__pulse(pContext, pConfig->pulse.pStreamNamePlayback, &ss, &cmap); if (pDevice->pulse.pStreamPlayback == NULL) { result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio playback stream.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); - goto on_error3; + goto on_error2; } streamFlags = MA_PA_STREAM_START_CORKED | MA_PA_STREAM_FIX_FORMAT | MA_PA_STREAM_FIX_RATE | MA_PA_STREAM_FIX_CHANNELS; @@ -21188,14 +21002,14 @@ static ma_result ma_device_init__pulse(ma_context* pContext, const ma_device_con error = ((ma_pa_stream_connect_playback_proc)pContext->pulse.pa_stream_connect_playback)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, devPlayback, &attr, streamFlags, NULL, NULL); if (error != MA_PA_OK) { result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio playback stream.", ma_result_from_pulse(error)); - goto on_error6; + goto on_error3; } while (((ma_pa_stream_get_state_proc)pContext->pulse.pa_stream_get_state)((ma_pa_stream*)pDevice->pulse.pStreamPlayback) != MA_PA_STREAM_READY) { - error = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pDevice->pulse.pMainLoop, 1, NULL); + error = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pContext->pulse.pMainLoop, 1, NULL); if (error < 0) { result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] The PulseAudio main loop returned an error while connecting the PulseAudio playback stream.", ma_result_from_pulse(error)); - goto on_error7; + goto on_error4; } } @@ -21208,7 +21022,7 @@ static ma_result ma_device_init__pulse(ma_context* pContext, const ma_device_con pOP = ((ma_pa_stream_set_buffer_attr_proc)pContext->pulse.pa_stream_set_buffer_attr)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, &attr, NULL, NULL); if (pOP != NULL) { - ma_device__wait_for_operation__pulse(pDevice, pOP); + ma_wait_for_operation__pulse(pDevice->pContext, pOP); ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); } } @@ -21243,9 +21057,9 @@ static ma_result ma_device_init__pulse(ma_context* pContext, const ma_device_con /* Name. */ devPlayback = ((ma_pa_stream_get_device_name_proc)pContext->pulse.pa_stream_get_device_name)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); if (devPlayback != NULL) { - ma_pa_operation* pOP = ((ma_pa_context_get_sink_info_by_name_proc)pContext->pulse.pa_context_get_sink_info_by_name)((ma_pa_context*)pDevice->pulse.pPulseContext, devPlayback, ma_device_sink_name_callback, pDevice); + ma_pa_operation* pOP = ((ma_pa_context_get_sink_info_by_name_proc)pContext->pulse.pa_context_get_sink_info_by_name)((ma_pa_context*)pContext->pulse.pPulseContext, devPlayback, ma_device_sink_name_callback, pDevice); if (pOP != NULL) { - ma_device__wait_for_operation__pulse(pDevice, pOP); + ma_wait_for_operation__pulse(pContext, pOP); ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); } } @@ -21254,25 +21068,22 @@ static ma_result ma_device_init__pulse(ma_context* pContext, const ma_device_con return MA_SUCCESS; -on_error7: +on_error4: if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { ((ma_pa_stream_disconnect_proc)pContext->pulse.pa_stream_disconnect)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); } -on_error6: +on_error3: if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { ((ma_pa_stream_unref_proc)pContext->pulse.pa_stream_unref)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); } -on_error5: +on_error2: if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { ((ma_pa_stream_disconnect_proc)pContext->pulse.pa_stream_disconnect)((ma_pa_stream*)pDevice->pulse.pStreamCapture); } -on_error4: +on_error1: if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { ((ma_pa_stream_unref_proc)pContext->pulse.pa_stream_unref)((ma_pa_stream*)pDevice->pulse.pStreamCapture); } -on_error3: ((ma_pa_context_disconnect_proc)pContext->pulse.pa_context_disconnect)((ma_pa_context*)pDevice->pulse.pPulseContext); -on_error2: ((ma_pa_context_unref_proc)pContext->pulse.pa_context_unref)((ma_pa_context*)pDevice->pulse.pPulseContext); -on_error1: ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)pDevice->pulse.pMainLoop); on_error0: return result; } @@ -21311,7 +21122,7 @@ static ma_result ma_device__cork_stream__pulse(ma_device* pDevice, ma_device_typ return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to cork PulseAudio stream.", (cork == 0) ? MA_FAILED_TO_START_BACKEND_DEVICE : MA_FAILED_TO_STOP_BACKEND_DEVICE); } - result = ma_device__wait_for_operation__pulse(pDevice, pOP); + result = ma_wait_for_operation__pulse(pDevice->pContext, pOP); ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); if (result != MA_SUCCESS) { @@ -21348,7 +21159,7 @@ static ma_result ma_device_stop__pulse(ma_device* pDevice) /* The stream needs to be drained if it's a playback device. */ pOP = ((ma_pa_stream_drain_proc)pDevice->pContext->pulse.pa_stream_drain)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, ma_pulse_operation_complete_callback, &wasSuccessful); if (pOP != NULL) { - ma_device__wait_for_operation__pulse(pDevice, pOP); + ma_wait_for_operation__pulse(pDevice->pContext, pOP); ((ma_pa_operation_unref_proc)pDevice->pContext->pulse.pa_operation_unref)(pOP); } @@ -21440,7 +21251,7 @@ static ma_result ma_device_write__pulse(ma_device* pDevice, const void* pPCMFram break; } else { /* No data available. Need to wait for more. */ - int error = ((ma_pa_mainloop_iterate_proc)pDevice->pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pDevice->pulse.pMainLoop, 1, NULL); + int error = ((ma_pa_mainloop_iterate_proc)pDevice->pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pDevice->pContext->pulse.pMainLoop, 1, NULL); if (error < 0) { return ma_result_from_pulse(error); } @@ -21582,7 +21393,7 @@ static ma_result ma_device_read__pulse(ma_device* pDevice, void* pPCMFrames, ma_ an actual microphone connected. I'm experimenting here by not blocking in pa_mainloop_iterate() and instead sleep for a bit when there are no dispatches. */ - error = ((ma_pa_mainloop_iterate_proc)pDevice->pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pDevice->pulse.pMainLoop, 0, NULL); + error = ((ma_pa_mainloop_iterate_proc)pDevice->pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pDevice->pContext->pulse.pMainLoop, 0, NULL); if (error < 0) { return ma_result_from_pulse(error); } @@ -21788,16 +21599,39 @@ static ma_result ma_device_main_loop__pulse(ma_device* pDevice) } +static ma_result ma_context_wait_for_connection__pulse(ma_context* pContext) +{ + for (;;) { + ma_pa_context_state_t state; + int pulseResult; + + state = ((ma_pa_context_get_state_proc)pContext->pulse.pa_context_get_state)((ma_pa_context*)pContext->pulse.pPulseContext); + if (state == MA_PA_CONTEXT_READY) { + return MA_SUCCESS; + } + + if (state == MA_PA_CONTEXT_FAILED || state == MA_PA_CONTEXT_TERMINATED) { + return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while connecting the PulseAudio context.", MA_ERROR); + } + + pulseResult = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pContext->pulse.pMainLoop, 1, NULL); + if (pulseResult < 0) { + return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[PulseAudio] The PulseAudio main loop returned an error while connecting the PulseAudio context.", ma_result_from_pulse(pulseResult)); + } + } + + /* Should never get here. */ + return MA_SUCCESS; +} + static ma_result ma_context_uninit__pulse(ma_context* pContext) { MA_ASSERT(pContext != NULL); MA_ASSERT(pContext->backend == ma_backend_pulseaudio); - ma_free(pContext->pulse.pServerName, &pContext->allocationCallbacks); - pContext->pulse.pServerName = NULL; - - ma_free(pContext->pulse.pApplicationName, &pContext->allocationCallbacks); - pContext->pulse.pApplicationName = NULL; + ((ma_pa_context_disconnect_proc)pContext->pulse.pa_context_disconnect)((ma_pa_context*)pContext->pulse.pPulseContext); + ((ma_pa_context_unref_proc)pContext->pulse.pa_context_unref)((ma_pa_context*)pContext->pulse.pPulseContext); + ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)pContext->pulse.pMainLoop); #ifndef MA_NO_RUNTIME_LINKING ma_dlclose(pContext, pContext->pulse.pulseSO); @@ -21808,6 +21642,7 @@ static ma_result ma_context_uninit__pulse(ma_context* pContext) static ma_result ma_context_init__pulse(const ma_context_config* pConfig, ma_context* pContext) { + ma_result result; #ifndef MA_NO_RUNTIME_LINKING const char* libpulseNames[] = { "libpulse.so", @@ -21973,71 +21808,44 @@ static ma_result ma_context_init__pulse(const ma_context_config* pConfig, ma_con pContext->onDeviceStop = NULL; pContext->onDeviceMainLoop = ma_device_main_loop__pulse; - if (pConfig->pulse.pApplicationName) { - pContext->pulse.pApplicationName = ma_copy_string(pConfig->pulse.pApplicationName, &pContext->allocationCallbacks); + /* The PulseAudio context maps well to miniaudio's notion of a context. The pa_context object will be initialized as part of the ma_context. */ + pContext->pulse.pMainLoop = ((ma_pa_mainloop_new_proc)pContext->pulse.pa_mainloop_new)(); + if (pContext->pulse.pMainLoop == NULL) { + result = ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create main loop for device.", MA_FAILED_TO_INIT_BACKEND); + #ifndef MA_NO_RUNTIME_LINKING + ma_dlclose(pContext, pContext->pulse.pulseSO); + #endif + return result; } - if (pConfig->pulse.pServerName) { - pContext->pulse.pServerName = ma_copy_string(pConfig->pulse.pServerName, &pContext->allocationCallbacks); + + pContext->pulse.pPulseContext = ((ma_pa_context_new_proc)pContext->pulse.pa_context_new)(((ma_pa_mainloop_get_api_proc)pContext->pulse.pa_mainloop_get_api)((ma_pa_mainloop*)pContext->pulse.pMainLoop), pConfig->pulse.pApplicationName); + if (pContext->pulse.pPulseContext == NULL) { + result = ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio context for device.", MA_FAILED_TO_INIT_BACKEND); + ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pContext->pulse.pMainLoop); + #ifndef MA_NO_RUNTIME_LINKING + ma_dlclose(pContext, pContext->pulse.pulseSO); + #endif + return result; } - pContext->pulse.tryAutoSpawn = pConfig->pulse.tryAutoSpawn; - - /* - Although we have found the libpulse library, it doesn't necessarily mean PulseAudio is useable. We need to initialize - and connect a dummy PulseAudio context to test PulseAudio's usability. - */ - { - ma_pa_mainloop* pMainLoop; - ma_pa_mainloop_api* pAPI; - ma_pa_context* pPulseContext; - int error; - pMainLoop = ((ma_pa_mainloop_new_proc)pContext->pulse.pa_mainloop_new)(); - if (pMainLoop == NULL) { - ma_free(pContext->pulse.pServerName, &pContext->allocationCallbacks); - ma_free(pContext->pulse.pApplicationName, &pContext->allocationCallbacks); - #ifndef MA_NO_RUNTIME_LINKING - ma_dlclose(pContext, pContext->pulse.pulseSO); - #endif - return MA_NO_BACKEND; - } + /* Now we need to connect to the context. Everything is asynchronous so we need to wait for it to connect before returning. */ + result = ma_result_from_pulse(((ma_pa_context_connect_proc)pContext->pulse.pa_context_connect)((ma_pa_context*)pContext->pulse.pPulseContext, pConfig->pulse.pServerName, (pConfig->pulse.tryAutoSpawn) ? 0 : MA_PA_CONTEXT_NOAUTOSPAWN, NULL)); + if (result != MA_SUCCESS) { + ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio context.", result); + ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pContext->pulse.pMainLoop); + #ifndef MA_NO_RUNTIME_LINKING + ma_dlclose(pContext, pContext->pulse.pulseSO); + #endif + return result; + } - pAPI = ((ma_pa_mainloop_get_api_proc)pContext->pulse.pa_mainloop_get_api)(pMainLoop); - if (pAPI == NULL) { - ma_free(pContext->pulse.pServerName, &pContext->allocationCallbacks); - ma_free(pContext->pulse.pApplicationName, &pContext->allocationCallbacks); - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); - #ifndef MA_NO_RUNTIME_LINKING - ma_dlclose(pContext, pContext->pulse.pulseSO); - #endif - return MA_NO_BACKEND; - } - - pPulseContext = ((ma_pa_context_new_proc)pContext->pulse.pa_context_new)(pAPI, pContext->pulse.pApplicationName); - if (pPulseContext == NULL) { - ma_free(pContext->pulse.pServerName, &pContext->allocationCallbacks); - ma_free(pContext->pulse.pApplicationName, &pContext->allocationCallbacks); - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); - #ifndef MA_NO_RUNTIME_LINKING - ma_dlclose(pContext, pContext->pulse.pulseSO); - #endif - return MA_NO_BACKEND; - } - - error = ((ma_pa_context_connect_proc)pContext->pulse.pa_context_connect)(pPulseContext, pContext->pulse.pServerName, 0, NULL); - if (error != MA_PA_OK) { - ma_free(pContext->pulse.pServerName, &pContext->allocationCallbacks); - ma_free(pContext->pulse.pApplicationName, &pContext->allocationCallbacks); - ((ma_pa_context_unref_proc)pContext->pulse.pa_context_unref)(pPulseContext); - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); - #ifndef MA_NO_RUNTIME_LINKING - ma_dlclose(pContext, pContext->pulse.pulseSO); - #endif - return MA_NO_BACKEND; - } - - ((ma_pa_context_disconnect_proc)pContext->pulse.pa_context_disconnect)(pPulseContext); - ((ma_pa_context_unref_proc)pContext->pulse.pa_context_unref)(pPulseContext); - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); + result = ma_context_wait_for_connection__pulse(pContext); + if (result != MA_SUCCESS) { + ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pContext->pulse.pMainLoop); + #ifndef MA_NO_RUNTIME_LINKING + ma_dlclose(pContext, pContext->pulse.pulseSO); + #endif + return result; } return MA_SUCCESS; @@ -62859,6 +62667,9 @@ The following miscellaneous changes have also been made. /* REVISION HISTORY ================ +v0.10.22 - TBD + - Improvements to the PulseAudio backend. + v0.10.21 - 2020-10-30 - Add ma_is_backend_enabled() and ma_get_enabled_backends() for retrieving enabled backends at run-time. - WASAPI: Fix a copy and paste bug relating to loopback mode. diff --git a/tests/test_deviceio/ma_test_deviceio.c b/tests/test_deviceio/ma_test_deviceio.c index b14cd1e8..0ce7a631 100644 --- a/tests/test_deviceio/ma_test_deviceio.c +++ b/tests/test_deviceio/ma_test_deviceio.c @@ -579,5 +579,10 @@ done: ma_encoder_uninit(&g_State.encoder); } + + printf("Press Enter to quit..."); + getchar(); + getchar(); + return 0; } From e808a67777d455d924bb8a358b2f9a65716cf429 Mon Sep 17 00:00:00 2001 From: David Reid Date: Sun, 1 Nov 2020 14:30:06 +1000 Subject: [PATCH 05/27] Refactor to the PulseAudio backend. * The main loop is now handled properly by pa_threaded_mainloop. * Rather than waiting for operations to complete inline, the main loop is now iterated on a separate thread. * Data is now written and read to and from the relevant stream via callbacks rather than a hacky loop. * Code overall has been simplified. * Includes a rant about bad API design in PulseAudio. This should hopefully address these public issues: * https://github.com/mackron/miniaudio/issues/106 * https://github.com/mackron/miniaudio/issues/187 --- miniaudio.h | 1165 ++++++++++++++++++++++++++------------------------- 1 file changed, 597 insertions(+), 568 deletions(-) diff --git a/miniaudio.h b/miniaudio.h index fb79e552..028c1189 100644 --- a/miniaudio.h +++ b/miniaudio.h @@ -3389,9 +3389,23 @@ struct ma_context ma_handle pulseSO; ma_proc pa_mainloop_new; ma_proc pa_mainloop_free; + ma_proc pa_mainloop_quit; ma_proc pa_mainloop_get_api; ma_proc pa_mainloop_iterate; ma_proc pa_mainloop_wakeup; + ma_proc pa_threaded_mainloop_new; + ma_proc pa_threaded_mainloop_free; + ma_proc pa_threaded_mainloop_start; + ma_proc pa_threaded_mainloop_stop; + ma_proc pa_threaded_mainloop_lock; + ma_proc pa_threaded_mainloop_unlock; + ma_proc pa_threaded_mainloop_wait; + ma_proc pa_threaded_mainloop_signal; + ma_proc pa_threaded_mainloop_accept; + ma_proc pa_threaded_mainloop_get_retval; + ma_proc pa_threaded_mainloop_get_api; + ma_proc pa_threaded_mainloop_in_thread; + ma_proc pa_threaded_mainloop_set_name; ma_proc pa_context_new; ma_proc pa_context_unref; ma_proc pa_context_connect; @@ -3432,9 +3446,8 @@ struct ma_context ma_proc pa_stream_writable_size; ma_proc pa_stream_readable_size; - /*pa_mainloop**/ ma_ptr pMainLoop; + /*pa_threaded_mainloop**/ ma_ptr pMainLoop; /*pa_context**/ ma_ptr pPulseContext; - /*pa_context_state*/ ma_uint32 pulseContextState; } pulse; #endif #ifdef MA_SUPPORT_JACK @@ -3783,13 +3796,7 @@ struct ma_device { /*pa_stream**/ ma_ptr pStreamPlayback; /*pa_stream**/ ma_ptr pStreamCapture; - void* pMappedBufferPlayback; - const void* pMappedBufferCapture; - ma_uint32 mappedBufferFramesRemainingPlayback; - ma_uint32 mappedBufferFramesRemainingCapture; - ma_uint32 mappedBufferFramesCapacityPlayback; - ma_uint32 mappedBufferFramesCapacityCapture; - ma_bool32 breakFromMainLoop : 1; + ma_pcm_rb duplexRB; } pulse; #endif #ifdef MA_SUPPORT_JACK @@ -19738,11 +19745,120 @@ PulseAudio Backend ******************************************************************************/ #ifdef MA_HAS_PULSEAUDIO /* -It is assumed pulseaudio.h is available when compile-time linking is being used. We use this for type safety when using -compile time linking (we don't have this luxury when using runtime linking without headers). +The PulseAudio API, along with Apple's Core Audio, is the worst of the maintream audio APIs. This is a brief description of what's going on +in the PulseAudio backend. I apologize if this gets a bit ranty for your liking - you might want to skip this discussion. -When using compile time linking, each of our ma_* equivalents should use the sames types as defined by the header. The -reason for this is that it allow us to take advantage of proper type safety. +PulseAudio has something they call the "Simple API", which unfortunately isn't suitable for miniaudio. I've not seen anywhere where it +allows you to enumerate over devices, nor does it seem to support the ability to stop and start streams. Looking at the documentation, it +appears as though the stream is constantly running and you prevent sound from being emitted or captured by simply not calling the read or +write functions. This is not a professional solution as it would be much better to *actually* stop the underlying stream. Perhaps the +simple API has some smarts to do this automatically, but I'm not sure. Another limitation with the simple API is that it seems inefficient +when you want to have multiple streams to a single context. For these reasons, miniaudio is not using the simple API. + +Since we're not using the simple API, we're left with the asynchronous API as our only other option. And boy, is this where it starts to +get fun, and I don't mean that in a good way... + +The problems start with the very name of the API - "asynchronous". Yes, this is an asynchronous oriented API which means your commands +don't immediately take effect. You instead need to issue your commands, and then wait for them to complete. The waiting mechanism is +enabled through the use of a "main loop". In the asychronous API you cannot get away from the main loop, and the main loop is where almost +all of PulseAudio's problems stem from. + +When you first initialize PulseAudio you need an object referred to as "main loop". You can implement this yourself by defining your own +vtable, but it's much easier to just use one of the built-in main loop implementations. There's two generic implementations called +pa_mainloop and pa_threaded_mainloop, and another implementation specific to GLib called pa_glib_mainloop. We're using pa_threaded_mainloop +because it simplifies management of the worker thread. The idea of the main loop object is pretty self explanatory - you're supposed to use +it to implement a worker thread which runs in a loop. The main loop is where operations are actually executed. + +To initialize the main loop, you just use `pa_threaded_mainloop_new()`. This is the first function you'll call. You can then get a pointer +to the vtable with `pa_threaded_mainloop_get_api()` (the main loop vtable is called `pa_mainloop_api`). Again, you can bypass the threaded +main loop object entirely and just implement `pa_mainloop_api` directly, but there's no need for it unless you're doing something extremely +specialized such as if you want to integrate it into your application's existing main loop infrastructure. + +Once you have your main loop vtable (the `pa_mainloop_api` object) you can create the PulseAudio context. This is very similar to +miniaudio's context and they map to each other quite well. You have one context to many streams, which is basically the same as miniaudio's +one `ma_context` to many `ma_device`s. Here's where it starts to get annoying, however. When you first create the PulseAudio context, which +is done with `pa_context_new()`, it's not actually connected to anything. When you connect, you call `pa_context_connect()`. However, if +you remember, PulseAudio is an asynchronous API. That means you cannot just assume the context is connected after `pa_context_context()` +has returned. You instead need to wait for it to connect. To do this, you need to either wait for a callback to get fired, which you can +set with `pa_context_set_state_callback()`, or you can continuously poll the context's state. Either way, you need to run this in a loop. +All objects from here out are created from the context, and, I believe, you can't be creating these objects until the context is connected. +This waiting loop is therefore unavoidable. In order for the waiting to ever complete, however, the main loop needs to be running. Before +attempting to connect the context, the main loop needs to be started with `pa_threaded_mainloop_start()`. + +The reason for this asynchronous design is to support cases where you're connecting to a remote server, say through a local network or an +internet connection. However, the *VAST* majority of cases don't involve this at all - they just connect to a local "server" running on the +host machine. The fact that this would be the default rather than making `pa_context_connect()` synchronous tends to boggle the mind. + +Once the context has been created and connected you can start creating a stream. A PulseAudio stream is analogous to miniaudio's device. +The initialization of a stream is fairly standard - you configure some attributes (analogous to miniaudio's device config) and then call +`pa_stream_new()` to actually create it. Here is where we start to get into "operations". When configuring the stream, you can get +information about the source (such as sample format, sample rate, etc.), however it's not synchronous. Instead, a `pa_operation` object +is returned from `pa_context_get_source_info_by_name()` (capture) or `pa_context_get_sink_info_by_name()` (playback). Then, you need to +run a loop (again!) to wait for the operation to complete which you can determine via a callback or polling, just like we did with the +context. Then, as an added bonus, you need to decrement the reference counter of the `pa_operation` object to ensure memory is cleaned up. +All of that just to retrieve basic information about a device! + +Once the basic information about the device has been retrieved, miniaudio can now create the stream with `ma_stream_new()`. Like the +context, this needs to be connected. But we need to be careful here, because we're now about to introduce one of the most horrific design +choices in PulseAudio. + +PulseAudio allows you to specify a callback that is fired when data can be written to or read from a stream. The language is important here +because PulseAudio takes it literally, specifically the "can be". You would think these callbacks would be appropriate as the place for +writing and reading data to and from the stream, and that would be right, except when it's not. When you initialize the stream, you can +set a flag that tells PulseAudio to not start the stream automatically. This is required because miniaudio does not auto-start devices +straight after initialization - you need to call `ma_device_start()` manually. The problem is that even when this flag is specified, +PulseAudio will immediately fire it's write or read callback. This is *technically* correct (based on the wording in the documentation) +because indeed, data *can* be written at this point. The problem is that it's not *practical*. It makes sense that the write/read callback +would be where a program will want to write or read data to or from the stream, but when it's called before the application has even +requested that the stream be started, it's just not practical because the program probably isn't ready for any kind of data delivery at +that point (it may still need to load files or whatnot). Instead, this callback should only be fired when the application requests the +stream be started which is how it works with literally *every* other callback-based audio API. Since miniaudio forbids firing of the data +callback until the device has been started (as it should be with *all* callback based APIs), logic needs to be added to ensure miniaudio +doesn't just blindly fire the application-defined data callback from within the PulseAudio callback before the stream has actually been +started. The device state is used for this - if the state is anything other than `MA_STATE_STARTING` or `MA_STATE_STARTED`, the main data +callback is not fired. + +This, unfortunately, is not the end of the problems with the PulseAudio write callback. Any normal callback based audio API will +continuously fire the callback at regular intervals based on the size of the internal buffer. This will only ever be fired when the device +is running, and will be fired regardless of whether or not the user actually wrote anything to the device/stream. This not the case in +PulseAudio. In PulseAudio, the data callback will *only* be called if you wrote something to it previously. That means, if you don't call +`pa_stream_write()`, the callback will not get fired. On the surface you wouldn't think this would matter because you should be always +writing data, and if you don't have anything to write, just write silence. That's fine until you want to drain the stream. You see, if +you're continuously writing data to the stream, the stream will never get drained! That means in order to drain the stream, you need to +*not* write data to it! But remember, when you don't write data to the stream, the callback won't get fired again! Why is draining +important? Because that's how we've defined stopping to work in miniaudio. In miniaudio, stopping the device requires it to be drained +before returning from ma_device_stop(). So we've stopped the device, which requires us to drain, but draining requires us to *not* write +data to the stream (or else it won't ever complete draining), but not writing to the stream means the callback won't get fired again! + +This becomes a problem when stopping and then restarting the device. When the device is stopped, it's drained, which requires us to *not* +write anything to the stream. But then, since we didn't write anything to it, the write callback will *never* get called again if we just +resume the stream naively. This means that starting the stream requires us to write data to the stream from outside the callback. This +disconnect is something PulseAudio has got seriously wrong - there should only ever be a single source of data delivery, that being the +callback. (I have tried using `pa_stream_flush()` to trigger the write callback to fire, but this just doesn't work for some reason.) + +Once you've created the stream, you need to connect it which involves the whole waiting procedure. This is the same process as the context, +only this time you'll poll for the state with `pa_stream_get_status()`. The starting and stopping of a streaming is referred to as +"corking" in PulseAudio. The analogy is corking a barrel. To start the stream, you uncork it, to stop it you cork it. Personally I think +it's silly - why would you not just call it "starting" and "stopping" like any other normal audio API? Anyway, the act of corking is, you +guessed it, asynchronous. This means you'll need our waiting loop as usual. Again, why this asynchronous design is the default is +absolutely beyond me. Would it really be that hard to just make it run synchronously? + +Teardown is pretty simple (what?!). It's just a matter of calling the relevant `_unref()` function on each object in reverse order that +they were initialized in. + +That's about it from the PulseAudio side. A bit ranty, I know, but they really need to fix that main loop and callback system. They're +embarrassingly unpractical. The main loop thing is an easy fix - have synchronous versions of all APIs. If an application wants these to +run asynchronously, they can execute them in a separate thread themselves. The desire to run these asynchronously is such a niche +requirement - it makes no sense to make it the default. The stream write callback needs to be change, or an alternative provided, that is +constantly fired, regardless of whether or not `pa_stream_write()` has been called, and it needs to take a pointer to a buffer as a +parameter which the program just writes to directly rather than having to call `pa_stream_writable_size()` and `pa_stream_write()`. These +changes alone will change PulseAudio from one of the worst audio APIs to one of the best. +*/ + + +/* +It is assumed pulseaudio.h is available when linking at compile time. When linking at compile time, we use the declarations in the header +to check for type safety. We cannot do this when linking at run time because the header might not be available. */ #ifdef MA_NO_RUNTIME_LINKING @@ -20141,12 +20257,13 @@ typedef int ma_pa_sample_format_t; #define MA_PA_SAMPLE_S24_32LE 11 #define MA_PA_SAMPLE_S24_32BE 12 -typedef struct ma_pa_mainloop ma_pa_mainloop; -typedef struct ma_pa_mainloop_api ma_pa_mainloop_api; -typedef struct ma_pa_context ma_pa_context; -typedef struct ma_pa_operation ma_pa_operation; -typedef struct ma_pa_stream ma_pa_stream; -typedef struct ma_pa_spawn_api ma_pa_spawn_api; +typedef struct ma_pa_mainloop ma_pa_mainloop; +typedef struct ma_pa_threaded_mainloop ma_pa_threaded_mainloop; +typedef struct ma_pa_mainloop_api ma_pa_mainloop_api; +typedef struct ma_pa_context ma_pa_context; +typedef struct ma_pa_operation ma_pa_operation; +typedef struct ma_pa_stream ma_pa_stream; +typedef struct ma_pa_spawn_api ma_pa_spawn_api; typedef struct { @@ -20243,9 +20360,23 @@ typedef void (* ma_pa_free_cb_t) (void* p); typedef ma_pa_mainloop* (* ma_pa_mainloop_new_proc) (void); typedef void (* ma_pa_mainloop_free_proc) (ma_pa_mainloop* m); +typedef void (* ma_pa_mainloop_quit_proc) (ma_pa_mainloop* m, int retval); typedef ma_pa_mainloop_api* (* ma_pa_mainloop_get_api_proc) (ma_pa_mainloop* m); typedef int (* ma_pa_mainloop_iterate_proc) (ma_pa_mainloop* m, int block, int* retval); typedef void (* ma_pa_mainloop_wakeup_proc) (ma_pa_mainloop* m); +typedef ma_pa_threaded_mainloop* (* ma_pa_threaded_mainloop_new_proc) (void); +typedef void (* ma_pa_threaded_mainloop_free_proc) (ma_pa_threaded_mainloop* m); +typedef int (* ma_pa_threaded_mainloop_start_proc) (ma_pa_threaded_mainloop* m); +typedef void (* ma_pa_threaded_mainloop_stop_proc) (ma_pa_threaded_mainloop* m); +typedef void (* ma_pa_threaded_mainloop_lock_proc) (ma_pa_threaded_mainloop* m); +typedef void (* ma_pa_threaded_mainloop_unlock_proc) (ma_pa_threaded_mainloop* m); +typedef void (* ma_pa_threaded_mainloop_wait_proc) (ma_pa_threaded_mainloop* m); +typedef void (* ma_pa_threaded_mainloop_signal_proc) (ma_pa_threaded_mainloop* m, int wait_for_accept); +typedef void (* ma_pa_threaded_mainloop_accept_proc) (ma_pa_threaded_mainloop* m); +typedef int (* ma_pa_threaded_mainloop_get_retval_proc) (ma_pa_threaded_mainloop* m); +typedef ma_pa_mainloop_api* (* ma_pa_threaded_mainloop_get_api_proc) (ma_pa_threaded_mainloop* m); +typedef int (* ma_pa_threaded_mainloop_in_thread_proc) (ma_pa_threaded_mainloop* m); +typedef void (* ma_pa_threaded_mainloop_set_name_proc) (ma_pa_threaded_mainloop* m); typedef ma_pa_context* (* ma_pa_context_new_proc) (ma_pa_mainloop_api* mainloop, const char* name); typedef void (* ma_pa_context_unref_proc) (ma_pa_context* c); typedef int (* ma_pa_context_connect_proc) (ma_pa_context* c, const char* server, ma_pa_context_flags_t flags, const ma_pa_spawn_api* api); @@ -20467,21 +20598,116 @@ static ma_pa_channel_position_t ma_channel_position_to_pulse(ma_channel position } #endif +static void ma_mainloop_lock__pulse(ma_context* pContext, const char* what) +{ + (void)what; + + MA_ASSERT(pContext != NULL); + + /*printf("locking mainloop by %s\n", what);*/ + ((ma_pa_threaded_mainloop_lock_proc)pContext->pulse.pa_threaded_mainloop_lock)((ma_pa_threaded_mainloop*)pContext->pulse.pMainLoop); +} + +static void ma_mainloop_unlock__pulse(ma_context* pContext, const char* what) +{ + (void)what; + + MA_ASSERT(pContext != NULL); + + /*printf("unlocking mainloop by %s\n", what);*/ + ((ma_pa_threaded_mainloop_unlock_proc)pContext->pulse.pa_threaded_mainloop_unlock)((ma_pa_threaded_mainloop*)pContext->pulse.pMainLoop); +} + static ma_result ma_wait_for_operation__pulse(ma_context* pContext, ma_pa_operation* pOP) { + ma_pa_operation_state_t state; + MA_ASSERT(pContext != NULL); MA_ASSERT(pOP != NULL); - while (((ma_pa_operation_get_state_proc)pContext->pulse.pa_operation_get_state)(pOP) == MA_PA_OPERATION_RUNNING) { - int error = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)(pContext->pulse.pMainLoop, 1, NULL); - if (error < 0) { - return ma_result_from_pulse(error); + for (;;) { + ma_mainloop_lock__pulse(pContext, "ma_wait_for_operation__pulse"); + { + state = ((ma_pa_operation_get_state_proc)pContext->pulse.pa_operation_get_state)(pOP); } + ma_mainloop_unlock__pulse(pContext, "ma_wait_for_operation__pulse"); + + if (state != MA_PA_OPERATION_RUNNING) { + break; /* Done. */ + } + + ma_yield(); } return MA_SUCCESS; } +static ma_result ma_wait_for_operation_and_unref__pulse(ma_context* pContext, ma_pa_operation* pOP) +{ + ma_result result; + + if (pOP == NULL) { + return MA_INVALID_ARGS; + } + + result = ma_wait_for_operation__pulse(pContext, pOP); + ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); + + return result; +} + +static ma_result ma_context_wait_for_pa_context_to_connect__pulse(ma_context* pContext) +{ + ma_pa_context_state_t state; + + for (;;) { + ma_mainloop_lock__pulse(pContext, "ma_context_wait_for_pa_context_to_connect__pulse"); + { + state = ((ma_pa_context_get_state_proc)pContext->pulse.pa_context_get_state)((ma_pa_context*)pContext->pulse.pPulseContext); + } + ma_mainloop_unlock__pulse(pContext, "ma_context_wait_for_pa_context_to_connect__pulse"); + + if (state == MA_PA_CONTEXT_READY) { + break; /* Done. */ + } + + if (state == MA_PA_CONTEXT_FAILED || state == MA_PA_CONTEXT_TERMINATED) { + return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while connecting the PulseAudio context.", MA_ERROR); + } + + ma_yield(); + } + + /* Should never get here. */ + return MA_SUCCESS; +} + +static ma_result ma_context_wait_for_pa_stream_to_connect__pulse(ma_context* pContext, ma_pa_stream* pStream) +{ + ma_pa_stream_state_t state; + + for (;;) { + ma_mainloop_lock__pulse(pContext, "ma_context_wait_for_pa_stream_to_connect__pulse"); + { + state = ((ma_pa_stream_get_state_proc)pContext->pulse.pa_stream_get_state)(pStream); + } + ma_mainloop_unlock__pulse(pContext, "ma_context_wait_for_pa_stream_to_connect__pulse"); + + if (state == MA_PA_STREAM_READY) { + break; /* Done. */ + } + + if (state == MA_PA_STREAM_FAILED || state == MA_PA_STREAM_TERMINATED) { + return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while connecting the PulseAudio stream.", MA_ERROR); + } + + ma_yield(); + } + + return MA_SUCCESS; +} + + static ma_bool32 ma_context_is_device_id_equal__pulse(ma_context* pContext, const ma_device_id* pID0, const ma_device_id* pID1) { MA_ASSERT(pContext != NULL); @@ -20636,8 +20862,8 @@ static void ma_context_get_device_info_sink_callback__pulse(ma_pa_context* pPuls pData->pDeviceInfo->maxChannels = pInfo->sample_spec.channels; pData->pDeviceInfo->minSampleRate = pInfo->sample_spec.rate; pData->pDeviceInfo->maxSampleRate = pInfo->sample_spec.rate; - pData->pDeviceInfo->formatCount = 1; - pData->pDeviceInfo->formats[0] = ma_format_from_pulse(pInfo->sample_spec.format); + pData->pDeviceInfo->formatCount = 1; + pData->pDeviceInfo->formats[0] = ma_format_from_pulse(pInfo->sample_spec.format); (void)pPulseContext; /* Unused. */ } @@ -20661,12 +20887,12 @@ static void ma_context_get_device_info_source_callback__pulse(ma_pa_context* pPu ma_strncpy_s(pData->pDeviceInfo->name, sizeof(pData->pDeviceInfo->name), pInfo->description, (size_t)-1); } - pData->pDeviceInfo->minChannels = pInfo->sample_spec.channels; - pData->pDeviceInfo->maxChannels = pInfo->sample_spec.channels; + pData->pDeviceInfo->minChannels = pInfo->sample_spec.channels; + pData->pDeviceInfo->maxChannels = pInfo->sample_spec.channels; pData->pDeviceInfo->minSampleRate = pInfo->sample_spec.rate; pData->pDeviceInfo->maxSampleRate = pInfo->sample_spec.rate; - pData->pDeviceInfo->formatCount = 1; - pData->pDeviceInfo->formats[0] = ma_format_from_pulse(pInfo->sample_spec.format); + pData->pDeviceInfo->formatCount = 1; + pData->pDeviceInfo->formats[0] = ma_format_from_pulse(pInfo->sample_spec.format); (void)pPulseContext; /* Unused. */ } @@ -20694,8 +20920,7 @@ static ma_result ma_context_get_device_info__pulse(ma_context* pContext, ma_devi } if (pOP != NULL) { - ma_wait_for_operation__pulse(pContext, pOP); - ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); + ma_wait_for_operation_and_unref__pulse(pContext, pOP); } else { result = MA_ERROR; goto done; @@ -20711,8 +20936,6 @@ done: } - - static void ma_device_sink_info_callback(ma_pa_context* pPulseContext, const ma_pa_sink_info* pInfo, int endOfList, void* pUserData) { ma_pa_sink_info* pInfoOut; @@ -20795,6 +21018,10 @@ static void ma_device_uninit__pulse(ma_device* pDevice) ((ma_pa_stream_disconnect_proc)pContext->pulse.pa_stream_disconnect)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); ((ma_pa_stream_unref_proc)pContext->pulse.pa_stream_unref)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); } + + if (pDevice->type == ma_device_type_duplex) { + ma_pcm_rb_uninit(&pDevice->pulse.duplexRB); + } } static ma_pa_buffer_attr ma_device__pa_buffer_attr_new(ma_uint32 periodSizeInFrames, ma_uint32 periods, const ma_pa_sample_spec* ss) @@ -20825,6 +21052,159 @@ static ma_pa_stream* ma_context__pa_stream_new__pulse(ma_context* pContext, cons return ((ma_pa_stream_new_proc)pContext->pulse.pa_stream_new)((ma_pa_context*)pContext->pulse.pPulseContext, actualStreamName, ss, cmap); } + +static void ma_device_on_read__pulse(ma_pa_stream* pStream, size_t byteCount, void* pUserData) +{ + ma_device* pDevice = (ma_device*)pUserData; + ma_uint32 bpf; + ma_uint64 frameCount; + ma_uint64 framesProcessed; + + MA_ASSERT(pDevice != NULL); + + bpf = ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); + MA_ASSERT(bpf > 0); + + frameCount = byteCount / bpf; + framesProcessed = 0; + + while (ma_device__get_state(pDevice) == MA_STATE_STARTED && framesProcessed < frameCount) { + const void* pMappedPCMFrames; + size_t bytesMapped; + ma_uint64 framesMapped; + + int pulseResult = ((ma_pa_stream_peek_proc)pDevice->pContext->pulse.pa_stream_peek)(pStream, &pMappedPCMFrames, &bytesMapped); + if (pulseResult < 0) { + break; /* Failed to map. Abort. */ + } + + framesMapped = bytesMapped / bpf; + if (framesMapped > 0) { + if (pMappedPCMFrames != NULL) { + if (pDevice->type == ma_device_type_duplex) { + ma_device__handle_duplex_callback_capture(pDevice, framesMapped, pMappedPCMFrames, &pDevice->pulse.duplexRB); + } else { + ma_device__send_frames_to_client(pDevice, framesMapped, pMappedPCMFrames); + } + } else { + /* It's a hole. */ + #if defined(MA_DEBUG_OUTPUT) + printf("[PulseAudio] ma_device_on_read__pulse: Hole.\n"); + #endif + } + + pulseResult = ((ma_pa_stream_drop_proc)pDevice->pContext->pulse.pa_stream_drop)(pStream); + if (pulseResult < 0) { + break; /* Failed to drop the buffer. */ + } + + framesProcessed += framesMapped; + + } else { + /* Nothing was mapped. Just abort. */ + break; + } + } +} + +static ma_result ma_device_write_to_stream__pulse(ma_device* pDevice, ma_pa_stream* pStream, ma_uint64* pFramesProcessed) +{ + ma_result result = MA_SUCCESS; + ma_uint64 framesProcessed = 0; + size_t bytesMapped; + ma_uint32 bpf; + ma_uint32 deviceState; + + MA_ASSERT(pDevice != NULL); + MA_ASSERT(pStream != NULL); + + bpf = ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); + MA_ASSERT(bpf > 0); + + deviceState = ma_device__get_state(pDevice); + + bytesMapped = ((ma_pa_stream_writable_size_proc)pDevice->pContext->pulse.pa_stream_writable_size)(pStream); + if (bytesMapped != (size_t)-1) { + if (bytesMapped > 0) { + ma_uint64 framesMapped; + void* pMappedPCMFrames; + int pulseResult = ((ma_pa_stream_begin_write_proc)pDevice->pContext->pulse.pa_stream_begin_write)(pStream, &pMappedPCMFrames, &bytesMapped); + if (pulseResult < 0) { + result = ma_result_from_pulse(pulseResult); + goto done; + } + + framesMapped = bytesMapped / bpf; + + if (deviceState == MA_STATE_STARTED) { + if (pDevice->type == ma_device_type_duplex) { + ma_device__handle_duplex_callback_playback(pDevice, framesMapped, pMappedPCMFrames, &pDevice->pulse.duplexRB); + } else { + ma_device__read_frames_from_client(pDevice, framesMapped, pMappedPCMFrames); + } + } else { + /* Device is not started. Don't write anything to it. */ + } + + pulseResult = ((ma_pa_stream_write_proc)pDevice->pContext->pulse.pa_stream_write)(pStream, pMappedPCMFrames, bytesMapped, NULL, 0, MA_PA_SEEK_RELATIVE); + if (pulseResult < 0) { + result = ma_result_from_pulse(pulseResult); + goto done; /* Failed to write data to stream. */ + } + + framesProcessed += framesMapped; + } else { + result = MA_ERROR; /* No data available. Abort. */ + goto done; + } + } else { + result = MA_ERROR; /* Failed to retrieve the writable size. Abort. */ + goto done; + } + +done: + if (pFramesProcessed != NULL) { + *pFramesProcessed = framesProcessed; + } + + return result; +} + +static void ma_device_on_write__pulse(ma_pa_stream* pStream, size_t byteCount, void* pUserData) +{ + ma_device* pDevice = (ma_device*)pUserData; + ma_uint32 bpf; + ma_uint64 frameCount; + ma_uint64 framesProcessed; + ma_result result; + + MA_ASSERT(pDevice != NULL); + + bpf = ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); + MA_ASSERT(bpf > 0); + + frameCount = byteCount / bpf; + framesProcessed = 0; + + while (framesProcessed < frameCount) { + ma_uint64 framesProcessedThisIteration; + ma_uint32 deviceState; + + /* Don't keep trying to process frames if the device isn't started. */ + deviceState = ma_device__get_state(pDevice); + if (deviceState != MA_STATE_STARTING && deviceState != MA_STATE_STARTED) { + break; + } + + result = ma_device_write_to_stream__pulse(pDevice, pStream, &framesProcessedThisIteration); + if (result != MA_SUCCESS) { + break; + } + + framesProcessed += framesProcessedThisIteration; + } +} + static ma_result ma_device_init__pulse(ma_context* pContext, const ma_device_config* pConfig, ma_device* pDevice) { ma_result result = MA_SUCCESS; @@ -20872,8 +21252,7 @@ static ma_result ma_device_init__pulse(ma_context* pContext, const ma_device_con if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { pOP = ((ma_pa_context_get_source_info_by_name_proc)pContext->pulse.pa_context_get_source_info_by_name)((ma_pa_context*)pContext->pulse.pPulseContext, devCapture, ma_device_source_info_callback, &sourceInfo); if (pOP != NULL) { - ma_wait_for_operation__pulse(pContext, pOP); - ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); + ma_wait_for_operation_and_unref__pulse(pContext, pOP); } else { result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to retrieve source info for capture device.", ma_result_from_pulse(error)); goto on_error0; @@ -20896,24 +21275,6 @@ static ma_result ma_device_init__pulse(ma_context* pContext, const ma_device_con goto on_error0; } - streamFlags = MA_PA_STREAM_START_CORKED | MA_PA_STREAM_FIX_FORMAT | MA_PA_STREAM_FIX_RATE | MA_PA_STREAM_FIX_CHANNELS; - if (devCapture != NULL) { - streamFlags |= MA_PA_STREAM_DONT_MOVE; - } - - error = ((ma_pa_stream_connect_record_proc)pContext->pulse.pa_stream_connect_record)((ma_pa_stream*)pDevice->pulse.pStreamCapture, devCapture, &attr, streamFlags); - if (error != MA_PA_OK) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio capture stream.", ma_result_from_pulse(error)); - goto on_error1; - } - - while (((ma_pa_stream_get_state_proc)pContext->pulse.pa_stream_get_state)((ma_pa_stream*)pDevice->pulse.pStreamCapture) != MA_PA_STREAM_READY) { - error = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pContext->pulse.pMainLoop, 1, NULL); - if (error < 0) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] The PulseAudio main loop returned an error while connecting the PulseAudio capture stream.", ma_result_from_pulse(error)); - goto on_error2; - } - } /* Internal format. */ pActualSS = ((ma_pa_stream_get_sample_spec_proc)pContext->pulse.pa_stream_get_sample_spec)((ma_pa_stream*)pDevice->pulse.pStreamCapture); @@ -20921,12 +21282,7 @@ static ma_result ma_device_init__pulse(ma_context* pContext, const ma_device_con /* If anything has changed between the requested and the actual sample spec, we need to update the buffer. */ if (ss.format != pActualSS->format || ss.channels != pActualSS->channels || ss.rate != pActualSS->rate) { attr = ma_device__pa_buffer_attr_new(pDevice->capture.internalPeriodSizeInFrames, pConfig->periods, pActualSS); - - pOP = ((ma_pa_stream_set_buffer_attr_proc)pContext->pulse.pa_stream_set_buffer_attr)((ma_pa_stream*)pDevice->pulse.pStreamCapture, &attr, NULL, NULL); - if (pOP != NULL) { - ma_wait_for_operation__pulse(pContext, pOP); - ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); - } + ma_wait_for_operation_and_unref__pulse(pContext, ((ma_pa_stream_set_buffer_attr_proc)pContext->pulse.pa_stream_set_buffer_attr)((ma_pa_stream*)pDevice->pulse.pStreamCapture, &attr, NULL, NULL)); } ss = *pActualSS; @@ -20959,19 +21315,36 @@ static ma_result ma_device_init__pulse(ma_context* pContext, const ma_device_con /* Name. */ devCapture = ((ma_pa_stream_get_device_name_proc)pContext->pulse.pa_stream_get_device_name)((ma_pa_stream*)pDevice->pulse.pStreamCapture); if (devCapture != NULL) { - ma_pa_operation* pOP = ((ma_pa_context_get_source_info_by_name_proc)pContext->pulse.pa_context_get_source_info_by_name)((ma_pa_context*)pContext->pulse.pPulseContext, devCapture, ma_device_source_name_callback, pDevice); - if (pOP != NULL) { - ma_wait_for_operation__pulse(pContext, pOP); - ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); - } + ma_wait_for_operation_and_unref__pulse(pContext, ((ma_pa_context_get_source_info_by_name_proc)pContext->pulse.pa_context_get_source_info_by_name)((ma_pa_context*)pContext->pulse.pPulseContext, devCapture, ma_device_source_name_callback, pDevice)); + } + + + /* Good practice to set the callback after retrieving the internal format since the callback itself references those settings. */ + ((ma_pa_stream_set_read_callback_proc)pContext->pulse.pa_stream_set_read_callback)((ma_pa_stream*)pDevice->pulse.pStreamCapture, ma_device_on_read__pulse, pDevice); + + + /* Connect after we've got all of our internal state set up. */ + streamFlags = MA_PA_STREAM_START_CORKED | MA_PA_STREAM_FIX_FORMAT | MA_PA_STREAM_FIX_RATE | MA_PA_STREAM_FIX_CHANNELS; + if (devCapture != NULL) { + streamFlags |= MA_PA_STREAM_DONT_MOVE; + } + + error = ((ma_pa_stream_connect_record_proc)pContext->pulse.pa_stream_connect_record)((ma_pa_stream*)pDevice->pulse.pStreamCapture, devCapture, &attr, streamFlags); + if (error != MA_PA_OK) { + result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio capture stream.", ma_result_from_pulse(error)); + goto on_error1; + } + + result = ma_context_wait_for_pa_stream_to_connect__pulse(pDevice->pContext, (ma_pa_stream*)pDevice->pulse.pStreamCapture); + if (result != MA_SUCCESS) { + goto on_error2; } } if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { pOP = ((ma_pa_context_get_sink_info_by_name_proc)pContext->pulse.pa_context_get_sink_info_by_name)((ma_pa_context*)pContext->pulse.pPulseContext, devPlayback, ma_device_sink_info_callback, &sinkInfo); if (pOP != NULL) { - ma_wait_for_operation__pulse(pDevice->pContext, pOP); - ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); + ma_wait_for_operation_and_unref__pulse(pContext, pOP); } else { result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to retrieve sink info for playback device.", ma_result_from_pulse(error)); goto on_error2; @@ -20994,24 +21367,6 @@ static ma_result ma_device_init__pulse(ma_context* pContext, const ma_device_con goto on_error2; } - streamFlags = MA_PA_STREAM_START_CORKED | MA_PA_STREAM_FIX_FORMAT | MA_PA_STREAM_FIX_RATE | MA_PA_STREAM_FIX_CHANNELS; - if (devPlayback != NULL) { - streamFlags |= MA_PA_STREAM_DONT_MOVE; - } - - error = ((ma_pa_stream_connect_playback_proc)pContext->pulse.pa_stream_connect_playback)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, devPlayback, &attr, streamFlags, NULL, NULL); - if (error != MA_PA_OK) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio playback stream.", ma_result_from_pulse(error)); - goto on_error3; - } - - while (((ma_pa_stream_get_state_proc)pContext->pulse.pa_stream_get_state)((ma_pa_stream*)pDevice->pulse.pStreamPlayback) != MA_PA_STREAM_READY) { - error = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pContext->pulse.pMainLoop, 1, NULL); - if (error < 0) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] The PulseAudio main loop returned an error while connecting the PulseAudio playback stream.", ma_result_from_pulse(error)); - goto on_error4; - } - } /* Internal format. */ pActualSS = ((ma_pa_stream_get_sample_spec_proc)pContext->pulse.pa_stream_get_sample_spec)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); @@ -21019,12 +21374,7 @@ static ma_result ma_device_init__pulse(ma_context* pContext, const ma_device_con /* If anything has changed between the requested and the actual sample spec, we need to update the buffer. */ if (ss.format != pActualSS->format || ss.channels != pActualSS->channels || ss.rate != pActualSS->rate) { attr = ma_device__pa_buffer_attr_new(pDevice->playback.internalPeriodSizeInFrames, pConfig->periods, pActualSS); - - pOP = ((ma_pa_stream_set_buffer_attr_proc)pContext->pulse.pa_stream_set_buffer_attr)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, &attr, NULL, NULL); - if (pOP != NULL) { - ma_wait_for_operation__pulse(pDevice->pContext, pOP); - ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); - } + ma_wait_for_operation_and_unref__pulse(pContext, ((ma_pa_stream_set_buffer_attr_proc)pContext->pulse.pa_stream_set_buffer_attr)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, &attr, NULL, NULL)); } ss = *pActualSS; @@ -21057,11 +21407,55 @@ static ma_result ma_device_init__pulse(ma_context* pContext, const ma_device_con /* Name. */ devPlayback = ((ma_pa_stream_get_device_name_proc)pContext->pulse.pa_stream_get_device_name)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); if (devPlayback != NULL) { - ma_pa_operation* pOP = ((ma_pa_context_get_sink_info_by_name_proc)pContext->pulse.pa_context_get_sink_info_by_name)((ma_pa_context*)pContext->pulse.pPulseContext, devPlayback, ma_device_sink_name_callback, pDevice); - if (pOP != NULL) { - ma_wait_for_operation__pulse(pContext, pOP); - ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); + ma_wait_for_operation_and_unref__pulse(pContext, ((ma_pa_context_get_sink_info_by_name_proc)pContext->pulse.pa_context_get_sink_info_by_name)((ma_pa_context*)pContext->pulse.pPulseContext, devPlayback, ma_device_sink_name_callback, pDevice)); + } + + + /* + Good practice to set the callback after retrieving the internal format since the callback itself references those settings. Note that + this callback will be fired as soon as the stream is connected, even though it's started as corked. The callback needs to handle a + device state of MA_STATE_UNINITIALIZED. + */ + ((ma_pa_stream_set_write_callback_proc)pContext->pulse.pa_stream_set_write_callback)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, ma_device_on_write__pulse, pDevice); + + + /* Connect after we've got all of our internal state set up. */ + streamFlags = MA_PA_STREAM_START_CORKED | MA_PA_STREAM_FIX_FORMAT | MA_PA_STREAM_FIX_RATE | MA_PA_STREAM_FIX_CHANNELS; + if (devPlayback != NULL) { + streamFlags |= MA_PA_STREAM_DONT_MOVE; + } + + error = ((ma_pa_stream_connect_playback_proc)pContext->pulse.pa_stream_connect_playback)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, devPlayback, &attr, streamFlags, NULL, NULL); + if (error != MA_PA_OK) { + result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio playback stream.", ma_result_from_pulse(error)); + goto on_error3; + } + + result = ma_context_wait_for_pa_stream_to_connect__pulse(pDevice->pContext, (ma_pa_stream*)pDevice->pulse.pStreamPlayback); + if (result != MA_SUCCESS) { + goto on_error3; + } + } + + + /* We need a ring buffer for handling duplex mode. */ + if (pConfig->deviceType == ma_device_type_duplex) { + ma_uint32 rbSizeInFrames = (ma_uint32)ma_calculate_frame_count_after_resampling(pDevice->sampleRate, pDevice->capture.internalSampleRate, pDevice->capture.internalPeriodSizeInFrames * pDevice->capture.internalPeriods); + result = ma_pcm_rb_init(pDevice->capture.format, pDevice->capture.channels, rbSizeInFrames, NULL, &pDevice->pContext->allocationCallbacks, &pDevice->pulse.duplexRB); + if (result != MA_SUCCESS) { + result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to initialize ring buffer.", result); + goto on_error4; + } + + /* We need a period to act as a buffer for cases where the playback and capture device's end up desyncing. */ + { + ma_uint32 marginSizeInFrames = rbSizeInFrames / pDevice->capture.internalPeriods; + void* pMarginData; + ma_pcm_rb_acquire_write(&pDevice->pulse.duplexRB, &marginSizeInFrames, &pMarginData); + { + MA_ZERO_MEMORY(pMarginData, marginSizeInFrames * ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels)); } + ma_pcm_rb_commit_write(&pDevice->pulse.duplexRB, marginSizeInFrames, pMarginData); } } @@ -21122,9 +21516,7 @@ static ma_result ma_device__cork_stream__pulse(ma_device* pDevice, ma_device_typ return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to cork PulseAudio stream.", (cork == 0) ? MA_FAILED_TO_START_BACKEND_DEVICE : MA_FAILED_TO_STOP_BACKEND_DEVICE); } - result = ma_wait_for_operation__pulse(pDevice->pContext, pOP); - ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); - + result = ma_wait_for_operation_and_unref__pulse(pDevice->pContext, pOP); if (result != MA_SUCCESS) { return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while waiting for the PulseAudio stream to cork.", result); } @@ -21140,11 +21532,44 @@ static ma_result ma_device__cork_stream__pulse(ma_device* pDevice, ma_device_typ return MA_SUCCESS; } +static ma_result ma_device_start__pulse(ma_device* pDevice) +{ + ma_result result; + + MA_ASSERT(pDevice != NULL); + + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + result = ma_device__cork_stream__pulse(pDevice, ma_device_type_capture, 0); + if (result != MA_SUCCESS) { + return result; + } + } + + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + /* We need to fill some data before uncorking. Not doing this will result in the write callback never getting fired. */ + ma_mainloop_lock__pulse(pDevice->pContext, "ma_device_start__pulse"); + { + result = ma_device_write_to_stream__pulse(pDevice, pDevice->pulse.pStreamPlayback, NULL); + } + ma_mainloop_unlock__pulse(pDevice->pContext, "ma_device_start__pulse"); + + if (result != MA_SUCCESS) { + return result; /* Failed to write data. Not sure what to do here... Just aborting. */ + } + + result = ma_device__cork_stream__pulse(pDevice, ma_device_type_playback, 0); + if (result != MA_SUCCESS) { + return result; + } + } + + return MA_SUCCESS; +} + static ma_result ma_device_stop__pulse(ma_device* pDevice) { ma_result result; ma_bool32 wasSuccessful; - ma_pa_operation* pOP; MA_ASSERT(pDevice != NULL); @@ -21157,11 +21582,7 @@ static ma_result ma_device_stop__pulse(ma_device* pDevice) if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { /* The stream needs to be drained if it's a playback device. */ - pOP = ((ma_pa_stream_drain_proc)pDevice->pContext->pulse.pa_stream_drain)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, ma_pulse_operation_complete_callback, &wasSuccessful); - if (pOP != NULL) { - ma_wait_for_operation__pulse(pDevice->pContext, pOP); - ((ma_pa_operation_unref_proc)pDevice->pContext->pulse.pa_operation_unref)(pOP); - } + ma_wait_for_operation_and_unref__pulse(pDevice->pContext, ((ma_pa_stream_drain_proc)pDevice->pContext->pulse.pa_stream_drain)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, ma_pulse_operation_complete_callback, &wasSuccessful)); result = ma_device__cork_stream__pulse(pDevice, ma_device_type_playback, 1); if (result != MA_SUCCESS) { @@ -21169,461 +21590,9 @@ static ma_result ma_device_stop__pulse(ma_device* pDevice) } } - return MA_SUCCESS; -} - -static ma_result ma_device_write__pulse(ma_device* pDevice, const void* pPCMFrames, ma_uint32 frameCount, ma_uint32* pFramesWritten) -{ - ma_uint32 totalFramesWritten; - - MA_ASSERT(pDevice != NULL); - MA_ASSERT(pPCMFrames != NULL); - MA_ASSERT(frameCount > 0); - - if (pFramesWritten != NULL) { - *pFramesWritten = 0; - } - - totalFramesWritten = 0; - while (totalFramesWritten < frameCount) { - if (ma_device__get_state(pDevice) != MA_STATE_STARTED) { - return MA_DEVICE_NOT_STARTED; - } - - /* Place the data into the mapped buffer if we have one. */ - if (pDevice->pulse.pMappedBufferPlayback != NULL && pDevice->pulse.mappedBufferFramesRemainingPlayback > 0) { - ma_uint32 bpf = ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); - ma_uint32 mappedBufferFramesConsumed = pDevice->pulse.mappedBufferFramesCapacityPlayback - pDevice->pulse.mappedBufferFramesRemainingPlayback; - - void* pDst = (ma_uint8*)pDevice->pulse.pMappedBufferPlayback + (mappedBufferFramesConsumed * bpf); - const void* pSrc = (const ma_uint8*)pPCMFrames + (totalFramesWritten * bpf); - ma_uint32 framesToCopy = ma_min(pDevice->pulse.mappedBufferFramesRemainingPlayback, (frameCount - totalFramesWritten)); - MA_COPY_MEMORY(pDst, pSrc, framesToCopy * bpf); - - pDevice->pulse.mappedBufferFramesRemainingPlayback -= framesToCopy; - totalFramesWritten += framesToCopy; - } - - /* - Getting here means we've run out of data in the currently mapped chunk. We need to write this to the device and then try - mapping another chunk. If this fails we need to wait for space to become available. - */ - if (pDevice->pulse.mappedBufferFramesCapacityPlayback > 0 && pDevice->pulse.mappedBufferFramesRemainingPlayback == 0) { - size_t nbytes = pDevice->pulse.mappedBufferFramesCapacityPlayback * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); - - int error = ((ma_pa_stream_write_proc)pDevice->pContext->pulse.pa_stream_write)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, pDevice->pulse.pMappedBufferPlayback, nbytes, NULL, 0, MA_PA_SEEK_RELATIVE); - if (error < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to write data to the PulseAudio stream.", ma_result_from_pulse(error)); - } - - pDevice->pulse.pMappedBufferPlayback = NULL; - pDevice->pulse.mappedBufferFramesRemainingPlayback = 0; - pDevice->pulse.mappedBufferFramesCapacityPlayback = 0; - } - - MA_ASSERT(totalFramesWritten <= frameCount); - if (totalFramesWritten == frameCount) { - break; - } - - /* Getting here means we need to map a new buffer. If we don't have enough space we need to wait for more. */ - for (;;) { - size_t writableSizeInBytes; - - /* If the device has been corked, don't try to continue. */ - if (((ma_pa_stream_is_corked_proc)pDevice->pContext->pulse.pa_stream_is_corked)((ma_pa_stream*)pDevice->pulse.pStreamPlayback)) { - break; - } - - writableSizeInBytes = ((ma_pa_stream_writable_size_proc)pDevice->pContext->pulse.pa_stream_writable_size)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); - if (writableSizeInBytes != (size_t)-1) { - if (writableSizeInBytes > 0) { - /* Data is avaialable. */ - size_t bytesToMap = writableSizeInBytes; - int error = ((ma_pa_stream_begin_write_proc)pDevice->pContext->pulse.pa_stream_begin_write)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, &pDevice->pulse.pMappedBufferPlayback, &bytesToMap); - if (error < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to map write buffer.", ma_result_from_pulse(error)); - } - - pDevice->pulse.mappedBufferFramesCapacityPlayback = bytesToMap / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); - pDevice->pulse.mappedBufferFramesRemainingPlayback = pDevice->pulse.mappedBufferFramesCapacityPlayback; - - break; - } else { - /* No data available. Need to wait for more. */ - int error = ((ma_pa_mainloop_iterate_proc)pDevice->pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pDevice->pContext->pulse.pMainLoop, 1, NULL); - if (error < 0) { - return ma_result_from_pulse(error); - } - - continue; - } - } else { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to query the stream's writable size.", MA_ERROR); - } - } - } - - if (pFramesWritten != NULL) { - *pFramesWritten = totalFramesWritten; - } - - return MA_SUCCESS; -} - -static ma_result ma_device_read__pulse(ma_device* pDevice, void* pPCMFrames, ma_uint32 frameCount, ma_uint32* pFramesRead) -{ - ma_uint32 totalFramesRead; - - MA_ASSERT(pDevice != NULL); - MA_ASSERT(pPCMFrames != NULL); - MA_ASSERT(frameCount > 0); - - if (pFramesRead != NULL) { - *pFramesRead = 0; - } - - totalFramesRead = 0; - while (totalFramesRead < frameCount) { - if (ma_device__get_state(pDevice) != MA_STATE_STARTED) { - return MA_DEVICE_NOT_STARTED; - } - - /* - If a buffer is mapped we need to read from that first. Once it's consumed we need to drop it. Note that pDevice->pulse.pMappedBufferCapture can be null in which - case it could be a hole. In this case we just write zeros into the output buffer. - */ - if (pDevice->pulse.mappedBufferFramesRemainingCapture > 0) { - ma_uint32 bpf = ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); - ma_uint32 mappedBufferFramesConsumed = pDevice->pulse.mappedBufferFramesCapacityCapture - pDevice->pulse.mappedBufferFramesRemainingCapture; - - ma_uint32 framesToCopy = ma_min(pDevice->pulse.mappedBufferFramesRemainingCapture, (frameCount - totalFramesRead)); - void* pDst = (ma_uint8*)pPCMFrames + (totalFramesRead * bpf); - - /* - This little bit of logic here is specifically for PulseAudio and it's hole management. The buffer pointer will be set to NULL - when the current fragment is a hole. For a hole we just output silence. - */ - if (pDevice->pulse.pMappedBufferCapture != NULL) { - const void* pSrc = (const ma_uint8*)pDevice->pulse.pMappedBufferCapture + (mappedBufferFramesConsumed * bpf); - MA_COPY_MEMORY(pDst, pSrc, framesToCopy * bpf); - } else { - MA_ZERO_MEMORY(pDst, framesToCopy * bpf); - #if defined(MA_DEBUG_OUTPUT) - printf("[PulseAudio] ma_device_read__pulse: Filling hole with silence.\n"); - #endif - } - - pDevice->pulse.mappedBufferFramesRemainingCapture -= framesToCopy; - totalFramesRead += framesToCopy; - } - - /* - Getting here means we've run out of data in the currently mapped chunk. We need to drop this from the device and then try - mapping another chunk. If this fails we need to wait for data to become available. - */ - if (pDevice->pulse.mappedBufferFramesCapacityCapture > 0 && pDevice->pulse.mappedBufferFramesRemainingCapture == 0) { - int error; - - #if defined(MA_DEBUG_OUTPUT) - printf("[PulseAudio] ma_device_read__pulse: Call pa_stream_drop()\n"); - #endif - - error = ((ma_pa_stream_drop_proc)pDevice->pContext->pulse.pa_stream_drop)((ma_pa_stream*)pDevice->pulse.pStreamCapture); - if (error != 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to drop fragment.", ma_result_from_pulse(error)); - } - - pDevice->pulse.pMappedBufferCapture = NULL; - pDevice->pulse.mappedBufferFramesRemainingCapture = 0; - pDevice->pulse.mappedBufferFramesCapacityCapture = 0; - } - - MA_ASSERT(totalFramesRead <= frameCount); - if (totalFramesRead == frameCount) { - break; - } - - /* Getting here means we need to map a new buffer. If we don't have enough data we wait for more. */ - for (;;) { - int error; - size_t bytesMapped; - - if (ma_device__get_state(pDevice) != MA_STATE_STARTED) { - break; - } - - /* If the device has been corked, don't try to continue. */ - if (((ma_pa_stream_is_corked_proc)pDevice->pContext->pulse.pa_stream_is_corked)((ma_pa_stream*)pDevice->pulse.pStreamCapture)) { - #if defined(MA_DEBUG_OUTPUT) - printf("[PulseAudio] ma_device_read__pulse: Corked.\n"); - #endif - break; - } - - MA_ASSERT(pDevice->pulse.pMappedBufferCapture == NULL); /* <-- We're about to map a buffer which means we shouldn't have an existing mapping. */ - - error = ((ma_pa_stream_peek_proc)pDevice->pContext->pulse.pa_stream_peek)((ma_pa_stream*)pDevice->pulse.pStreamCapture, &pDevice->pulse.pMappedBufferCapture, &bytesMapped); - if (error < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to peek capture buffer.", ma_result_from_pulse(error)); - } - - if (bytesMapped > 0) { - pDevice->pulse.mappedBufferFramesCapacityCapture = bytesMapped / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); - pDevice->pulse.mappedBufferFramesRemainingCapture = pDevice->pulse.mappedBufferFramesCapacityCapture; - - #if defined(MA_DEBUG_OUTPUT) - printf("[PulseAudio] ma_device_read__pulse: Mapped. mappedBufferFramesCapacityCapture=%d, mappedBufferFramesRemainingCapture=%d\n", pDevice->pulse.mappedBufferFramesCapacityCapture, pDevice->pulse.mappedBufferFramesRemainingCapture); - #endif - - if (pDevice->pulse.pMappedBufferCapture == NULL) { - /* It's a hole. */ - #if defined(MA_DEBUG_OUTPUT) - printf("[PulseAudio] ma_device_read__pulse: Call pa_stream_peek(). Hole.\n"); - #endif - } - - break; - } else { - if (pDevice->pulse.pMappedBufferCapture == NULL) { - /* Nothing available yet. Need to wait for more. */ - - /* - I have had reports of a deadlock in this part of the code. I have reproduced this when using the "Built-in Audio Analogue Stereo" device without - an actual microphone connected. I'm experimenting here by not blocking in pa_mainloop_iterate() and instead sleep for a bit when there are no - dispatches. - */ - error = ((ma_pa_mainloop_iterate_proc)pDevice->pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pDevice->pContext->pulse.pMainLoop, 0, NULL); - if (error < 0) { - return ma_result_from_pulse(error); - } - - /* Sleep for a bit if nothing was dispatched. */ - if (error == 0) { - ma_sleep(1); - } - - #if defined(MA_DEBUG_OUTPUT) - printf("[PulseAudio] ma_device_read__pulse: No data available. Waiting. mappedBufferFramesCapacityCapture=%d, mappedBufferFramesRemainingCapture=%d\n", pDevice->pulse.mappedBufferFramesCapacityCapture, pDevice->pulse.mappedBufferFramesRemainingCapture); - #endif - } else { - /* Getting here means we mapped 0 bytes, but have a non-NULL buffer. I don't think this should ever happen. */ - MA_ASSERT(MA_FALSE); - } - } - } - } - - if (pFramesRead != NULL) { - *pFramesRead = totalFramesRead; - } - - return MA_SUCCESS; -} - -static ma_result ma_device_main_loop__pulse(ma_device* pDevice) -{ - ma_result result = MA_SUCCESS; - ma_bool32 exitLoop = MA_FALSE; - - MA_ASSERT(pDevice != NULL); - - /* The stream needs to be uncorked first. We do this at the top for both capture and playback for PulseAudio. */ - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - result = ma_device__cork_stream__pulse(pDevice, ma_device_type_capture, 0); - if (result != MA_SUCCESS) { - return result; - } - } - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - result = ma_device__cork_stream__pulse(pDevice, ma_device_type_playback, 0); - if (result != MA_SUCCESS) { - return result; - } - } - - - while (ma_device__get_state(pDevice) == MA_STATE_STARTED && !exitLoop) { - switch (pDevice->type) - { - case ma_device_type_duplex: - { - /* The process is: device_read -> convert -> callback -> convert -> device_write */ - ma_uint32 totalCapturedDeviceFramesProcessed = 0; - ma_uint32 capturedDevicePeriodSizeInFrames = ma_min(pDevice->capture.internalPeriodSizeInFrames, pDevice->playback.internalPeriodSizeInFrames); - - while (totalCapturedDeviceFramesProcessed < capturedDevicePeriodSizeInFrames) { - ma_uint8 capturedDeviceData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint8 playbackDeviceData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 capturedDeviceDataCapInFrames = sizeof(capturedDeviceData) / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); - ma_uint32 playbackDeviceDataCapInFrames = sizeof(playbackDeviceData) / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); - ma_uint32 capturedDeviceFramesRemaining; - ma_uint32 capturedDeviceFramesProcessed; - ma_uint32 capturedDeviceFramesToProcess; - ma_uint32 capturedDeviceFramesToTryProcessing = capturedDevicePeriodSizeInFrames - totalCapturedDeviceFramesProcessed; - if (capturedDeviceFramesToTryProcessing > capturedDeviceDataCapInFrames) { - capturedDeviceFramesToTryProcessing = capturedDeviceDataCapInFrames; - } - - result = ma_device_read__pulse(pDevice, capturedDeviceData, capturedDeviceFramesToTryProcessing, &capturedDeviceFramesToProcess); - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - capturedDeviceFramesRemaining = capturedDeviceFramesToProcess; - capturedDeviceFramesProcessed = 0; - - for (;;) { - ma_uint8 capturedClientData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint8 playbackClientData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 capturedClientDataCapInFrames = sizeof(capturedClientData) / ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels); - ma_uint32 playbackClientDataCapInFrames = sizeof(playbackClientData) / ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); - ma_uint64 capturedClientFramesToProcessThisIteration = ma_min(capturedClientDataCapInFrames, playbackClientDataCapInFrames); - ma_uint64 capturedDeviceFramesToProcessThisIteration = capturedDeviceFramesRemaining; - ma_uint8* pRunningCapturedDeviceFrames = ma_offset_ptr(capturedDeviceData, capturedDeviceFramesProcessed * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels)); - - /* Convert capture data from device format to client format. */ - result = ma_data_converter_process_pcm_frames(&pDevice->capture.converter, pRunningCapturedDeviceFrames, &capturedDeviceFramesToProcessThisIteration, capturedClientData, &capturedClientFramesToProcessThisIteration); - if (result != MA_SUCCESS) { - break; - } - - /* - If we weren't able to generate any output frames it must mean we've exhaused all of our input. The only time this would not be the case is if capturedClientData was too small - which should never be the case when it's of the size MA_DATA_CONVERTER_STACK_BUFFER_SIZE. - */ - if (capturedClientFramesToProcessThisIteration == 0) { - break; - } - - ma_device__on_data(pDevice, playbackClientData, capturedClientData, (ma_uint32)capturedClientFramesToProcessThisIteration); /* Safe cast .*/ - - capturedDeviceFramesProcessed += (ma_uint32)capturedDeviceFramesToProcessThisIteration; /* Safe cast. */ - capturedDeviceFramesRemaining -= (ma_uint32)capturedDeviceFramesToProcessThisIteration; /* Safe cast. */ - - /* At this point the playbackClientData buffer should be holding data that needs to be written to the device. */ - for (;;) { - ma_uint64 convertedClientFrameCount = capturedClientFramesToProcessThisIteration; - ma_uint64 convertedDeviceFrameCount = playbackDeviceDataCapInFrames; - result = ma_data_converter_process_pcm_frames(&pDevice->playback.converter, playbackClientData, &convertedClientFrameCount, playbackDeviceData, &convertedDeviceFrameCount); - if (result != MA_SUCCESS) { - break; - } - - result = ma_device_write__pulse(pDevice, playbackDeviceData, (ma_uint32)convertedDeviceFrameCount, NULL); /* Safe cast. */ - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - capturedClientFramesToProcessThisIteration -= (ma_uint32)convertedClientFrameCount; /* Safe cast. */ - if (capturedClientFramesToProcessThisIteration == 0) { - break; - } - } - - /* In case an error happened from ma_device_write__pulse()... */ - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - } - - totalCapturedDeviceFramesProcessed += capturedDeviceFramesProcessed; - } - } break; - - case ma_device_type_capture: - { - ma_uint8 intermediaryBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 intermediaryBufferSizeInFrames = sizeof(intermediaryBuffer) / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); - ma_uint32 periodSizeInFrames = pDevice->capture.internalPeriodSizeInFrames; - ma_uint32 framesReadThisPeriod = 0; - while (framesReadThisPeriod < periodSizeInFrames) { - ma_uint32 framesRemainingInPeriod = periodSizeInFrames - framesReadThisPeriod; - ma_uint32 framesProcessed; - ma_uint32 framesToReadThisIteration = framesRemainingInPeriod; - if (framesToReadThisIteration > intermediaryBufferSizeInFrames) { - framesToReadThisIteration = intermediaryBufferSizeInFrames; - } - - result = ma_device_read__pulse(pDevice, intermediaryBuffer, framesToReadThisIteration, &framesProcessed); - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - ma_device__send_frames_to_client(pDevice, framesProcessed, intermediaryBuffer); - - framesReadThisPeriod += framesProcessed; - } - } break; - - case ma_device_type_playback: - { - ma_uint8 intermediaryBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 intermediaryBufferSizeInFrames = sizeof(intermediaryBuffer) / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); - ma_uint32 periodSizeInFrames = pDevice->playback.internalPeriodSizeInFrames; - ma_uint32 framesWrittenThisPeriod = 0; - while (framesWrittenThisPeriod < periodSizeInFrames) { - ma_uint32 framesRemainingInPeriod = periodSizeInFrames - framesWrittenThisPeriod; - ma_uint32 framesProcessed; - ma_uint32 framesToWriteThisIteration = framesRemainingInPeriod; - if (framesToWriteThisIteration > intermediaryBufferSizeInFrames) { - framesToWriteThisIteration = intermediaryBufferSizeInFrames; - } - - ma_device__read_frames_from_client(pDevice, framesToWriteThisIteration, intermediaryBuffer); - - result = ma_device_write__pulse(pDevice, intermediaryBuffer, framesToWriteThisIteration, &framesProcessed); - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - framesWrittenThisPeriod += framesProcessed; - } - } break; - - /* To silence a warning. Will never hit this. */ - case ma_device_type_loopback: - default: break; - } - } - - /* Here is where the device needs to be stopped. */ - ma_device_stop__pulse(pDevice); - return result; } - -static ma_result ma_context_wait_for_connection__pulse(ma_context* pContext) -{ - for (;;) { - ma_pa_context_state_t state; - int pulseResult; - - state = ((ma_pa_context_get_state_proc)pContext->pulse.pa_context_get_state)((ma_pa_context*)pContext->pulse.pPulseContext); - if (state == MA_PA_CONTEXT_READY) { - return MA_SUCCESS; - } - - if (state == MA_PA_CONTEXT_FAILED || state == MA_PA_CONTEXT_TERMINATED) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while connecting the PulseAudio context.", MA_ERROR); - } - - pulseResult = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pContext->pulse.pMainLoop, 1, NULL); - if (pulseResult < 0) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[PulseAudio] The PulseAudio main loop returned an error while connecting the PulseAudio context.", ma_result_from_pulse(pulseResult)); - } - } - - /* Should never get here. */ - return MA_SUCCESS; -} - static ma_result ma_context_uninit__pulse(ma_context* pContext) { MA_ASSERT(pContext != NULL); @@ -21631,7 +21600,10 @@ static ma_result ma_context_uninit__pulse(ma_context* pContext) ((ma_pa_context_disconnect_proc)pContext->pulse.pa_context_disconnect)((ma_pa_context*)pContext->pulse.pPulseContext); ((ma_pa_context_unref_proc)pContext->pulse.pa_context_unref)((ma_pa_context*)pContext->pulse.pPulseContext); - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)pContext->pulse.pMainLoop); + + /* The mainloop needs to be stopped before freeing. */ + ((ma_pa_threaded_mainloop_stop_proc)pContext->pulse.pa_threaded_mainloop_stop)((ma_pa_threaded_mainloop*)pContext->pulse.pMainLoop); + ((ma_pa_threaded_mainloop_free_proc)pContext->pulse.pa_threaded_mainloop_free)((ma_pa_threaded_mainloop*)pContext->pulse.pMainLoop); #ifndef MA_NO_RUNTIME_LINKING ma_dlclose(pContext, pContext->pulse.pulseSO); @@ -21663,9 +21635,23 @@ static ma_result ma_context_init__pulse(const ma_context_config* pConfig, ma_con pContext->pulse.pa_mainloop_new = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_mainloop_new"); pContext->pulse.pa_mainloop_free = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_mainloop_free"); + pContext->pulse.pa_mainloop_quit = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_mainloop_quit"); pContext->pulse.pa_mainloop_get_api = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_mainloop_get_api"); pContext->pulse.pa_mainloop_iterate = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_mainloop_iterate"); pContext->pulse.pa_mainloop_wakeup = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_mainloop_wakeup"); + pContext->pulse.pa_threaded_mainloop_new = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_threaded_mainloop_new"); + pContext->pulse.pa_threaded_mainloop_free = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_threaded_mainloop_free"); + pContext->pulse.pa_threaded_mainloop_start = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_threaded_mainloop_start"); + pContext->pulse.pa_threaded_mainloop_stop = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_threaded_mainloop_stop"); + pContext->pulse.pa_threaded_mainloop_lock = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_threaded_mainloop_lock"); + pContext->pulse.pa_threaded_mainloop_unlock = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_threaded_mainloop_unlock"); + pContext->pulse.pa_threaded_mainloop_wait = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_threaded_mainloop_wait"); + pContext->pulse.pa_threaded_mainloop_signal = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_threaded_mainloop_signal"); + pContext->pulse.pa_threaded_mainloop_accept = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_threaded_mainloop_accept"); + pContext->pulse.pa_threaded_mainloop_get_retval = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_threaded_mainloop_get_retval"); + pContext->pulse.pa_threaded_mainloop_get_api = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_threaded_mainloop_get_api"); + pContext->pulse.pa_threaded_mainloop_in_thread = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_threaded_mainloop_in_thread"); + pContext->pulse.pa_threaded_mainloop_set_name = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_threaded_mainloop_set_name"); pContext->pulse.pa_context_new = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_context_new"); pContext->pulse.pa_context_unref = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_context_unref"); pContext->pulse.pa_context_connect = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_context_connect"); @@ -21709,9 +21695,23 @@ static ma_result ma_context_init__pulse(const ma_context_config* pConfig, ma_con /* This strange assignment system is just for type safety. */ ma_pa_mainloop_new_proc _pa_mainloop_new = pa_mainloop_new; ma_pa_mainloop_free_proc _pa_mainloop_free = pa_mainloop_free; + ma_pa_mainloop_quit_proc _pa_mainloop_quit = pa_mainloop_quit; ma_pa_mainloop_get_api_proc _pa_mainloop_get_api = pa_mainloop_get_api; ma_pa_mainloop_iterate_proc _pa_mainloop_iterate = pa_mainloop_iterate; ma_pa_mainloop_wakeup_proc _pa_mainloop_wakeup = pa_mainloop_wakeup; + ma_pa_threaded_mainloop_new_proc _pa_threaded_mainloop_new = pa_threaded_mainloop_new; + ma_pa_threaded_mainloop_free_proc _pa_threaded_mainloop_free = pa_threaded_mainloop_free; + ma_pa_threaded_mainloop_start_proc _pa_threaded_mainloop_start = pa_threaded_mainloop_start; + ma_pa_threaded_mainloop_stop_proc _pa_threaded_mainloop_stop = pa_threaded_mainloop_stop; + ma_pa_threaded_mainloop_lock_proc _pa_threaded_mainloop_lock = pa_threaded_mainloop_lock; + ma_pa_threaded_mainloop_unlock_proc _pa_threaded_mainloop_unlock = pa_threaded_mainloop_unlock; + ma_pa_threaded_mainloop_wait_proc _pa_threaded_mainloop_wait = pa_threaded_mainloop_wait; + ma_pa_threaded_mainloop_signal_proc _pa_threaded_mainloop_signal = pa_threaded_mainloop_signal; + ma_pa_threaded_mainloop_accept_proc _pa_threaded_mainloop_accept = pa_threaded_mainloop_accept; + ma_pa_threaded_mainloop_get_retval_proc _pa_threaded_mainloop_get_retval = pa_threaded_mainloop_get_retval; + ma_pa_threaded_mainloop_get_api_proc _pa_threaded_mainloop_get_api = pa_threaded_mainloop_get_api; + ma_pa_threaded_mainloop_in_thread_proc _pa_threaded_mainloop_in_thread = pa_threaded_mainloop_in_thread; + ma_pa_threaded_mainloop_set_name_proc _pa_threaded_mainloop_set_name = pa_threaded_mainloop_set_name; ma_pa_context_new_proc _pa_context_new = pa_context_new; ma_pa_context_unref_proc _pa_context_unref = pa_context_unref; ma_pa_context_connect_proc _pa_context_connect = pa_context_connect; @@ -21754,9 +21754,23 @@ static ma_result ma_context_init__pulse(const ma_context_config* pConfig, ma_con pContext->pulse.pa_mainloop_new = (ma_proc)_pa_mainloop_new; pContext->pulse.pa_mainloop_free = (ma_proc)_pa_mainloop_free; + pContext->pulse.pa_mainloop_quit = (ma_proc)_pa_mainloop_quit; pContext->pulse.pa_mainloop_get_api = (ma_proc)_pa_mainloop_get_api; pContext->pulse.pa_mainloop_iterate = (ma_proc)_pa_mainloop_iterate; pContext->pulse.pa_mainloop_wakeup = (ma_proc)_pa_mainloop_wakeup; + pContext->pulse.pa_threaded_mainloop_new = (ma_proc)_pa_threaded_mainloop_new; + pContext->pulse.pa_threaded_mainloop_free = (ma_proc)_pa_threaded_mainloop_free; + pContext->pulse.pa_threaded_mainloop_start = (ma_proc)_pa_threaded_mainloop_start; + pContext->pulse.pa_threaded_mainloop_stop = (ma_proc)_pa_threaded_mainloop_stop; + pContext->pulse.pa_threaded_mainloop_lock = (ma_proc)_pa_threaded_mainloop_lock; + pContext->pulse.pa_threaded_mainloop_unlock = (ma_proc)_pa_threaded_mainloop_unlock; + pContext->pulse.pa_threaded_mainloop_wait = (ma_proc)_pa_threaded_mainloop_wait; + pContext->pulse.pa_threaded_mainloop_signal = (ma_proc)_pa_threaded_mainloop_signal; + pContext->pulse.pa_threaded_mainloop_accept = (ma_proc)_pa_threaded_mainloop_accept; + pContext->pulse.pa_threaded_mainloop_get_retval = (ma_proc)_pa_threaded_mainloop_get_retval; + pContext->pulse.pa_threaded_mainloop_get_api = (ma_proc)_pa_threaded_mainloop_get_api; + pContext->pulse.pa_threaded_mainloop_in_thread = (ma_proc)_pa_threaded_mainloop_in_thread; + pContext->pulse.pa_threaded_mainloop_set_name = (ma_proc)_pa_threaded_mainloop_set_name; pContext->pulse.pa_context_new = (ma_proc)_pa_context_new; pContext->pulse.pa_context_unref = (ma_proc)_pa_context_unref; pContext->pulse.pa_context_connect = (ma_proc)_pa_context_connect; @@ -21798,30 +21812,32 @@ static ma_result ma_context_init__pulse(const ma_context_config* pConfig, ma_con pContext->pulse.pa_stream_readable_size = (ma_proc)_pa_stream_readable_size; #endif + pContext->isBackendAsynchronous = MA_TRUE; /* We are using PulseAudio in asynchronous mode. */ + pContext->onUninit = ma_context_uninit__pulse; pContext->onDeviceIDEqual = ma_context_is_device_id_equal__pulse; pContext->onEnumDevices = ma_context_enumerate_devices__pulse; pContext->onGetDeviceInfo = ma_context_get_device_info__pulse; pContext->onDeviceInit = ma_device_init__pulse; pContext->onDeviceUninit = ma_device_uninit__pulse; - pContext->onDeviceStart = NULL; - pContext->onDeviceStop = NULL; - pContext->onDeviceMainLoop = ma_device_main_loop__pulse; + pContext->onDeviceStart = ma_device_start__pulse; + pContext->onDeviceStop = ma_device_stop__pulse; + pContext->onDeviceMainLoop = NULL; /* Set to null since this backend is asynchronous. */ /* The PulseAudio context maps well to miniaudio's notion of a context. The pa_context object will be initialized as part of the ma_context. */ - pContext->pulse.pMainLoop = ((ma_pa_mainloop_new_proc)pContext->pulse.pa_mainloop_new)(); + pContext->pulse.pMainLoop = ((ma_pa_threaded_mainloop_new_proc)pContext->pulse.pa_threaded_mainloop_new)(); if (pContext->pulse.pMainLoop == NULL) { - result = ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create main loop for device.", MA_FAILED_TO_INIT_BACKEND); + result = ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create mainloop.", MA_FAILED_TO_INIT_BACKEND); #ifndef MA_NO_RUNTIME_LINKING ma_dlclose(pContext, pContext->pulse.pulseSO); #endif return result; } - pContext->pulse.pPulseContext = ((ma_pa_context_new_proc)pContext->pulse.pa_context_new)(((ma_pa_mainloop_get_api_proc)pContext->pulse.pa_mainloop_get_api)((ma_pa_mainloop*)pContext->pulse.pMainLoop), pConfig->pulse.pApplicationName); + pContext->pulse.pPulseContext = ((ma_pa_context_new_proc)pContext->pulse.pa_context_new)(((ma_pa_threaded_mainloop_get_api_proc)pContext->pulse.pa_threaded_mainloop_get_api)((ma_pa_threaded_mainloop*)pContext->pulse.pMainLoop), pConfig->pulse.pApplicationName); if (pContext->pulse.pPulseContext == NULL) { - result = ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio context for device.", MA_FAILED_TO_INIT_BACKEND); - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pContext->pulse.pMainLoop); + result = ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio context.", MA_FAILED_TO_INIT_BACKEND); + ((ma_pa_threaded_mainloop_free_proc)pContext->pulse.pa_threaded_mainloop_free)(pContext->pulse.pMainLoop); #ifndef MA_NO_RUNTIME_LINKING ma_dlclose(pContext, pContext->pulse.pulseSO); #endif @@ -21832,16 +21848,29 @@ static ma_result ma_context_init__pulse(const ma_context_config* pConfig, ma_con result = ma_result_from_pulse(((ma_pa_context_connect_proc)pContext->pulse.pa_context_connect)((ma_pa_context*)pContext->pulse.pPulseContext, pConfig->pulse.pServerName, (pConfig->pulse.tryAutoSpawn) ? 0 : MA_PA_CONTEXT_NOAUTOSPAWN, NULL)); if (result != MA_SUCCESS) { ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio context.", result); - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pContext->pulse.pMainLoop); + ((ma_pa_threaded_mainloop_free_proc)pContext->pulse.pa_threaded_mainloop_free)(pContext->pulse.pMainLoop); #ifndef MA_NO_RUNTIME_LINKING ma_dlclose(pContext, pContext->pulse.pulseSO); #endif return result; } - result = ma_context_wait_for_connection__pulse(pContext); + /* We now need to start the mainloop. Once the loop has started we can then wait for the PulseAudio context to connect. */ + result = ma_result_from_pulse(((ma_pa_threaded_mainloop_start_proc)pContext->pulse.pa_threaded_mainloop_start)((ma_pa_threaded_mainloop*)pContext->pulse.pMainLoop)); if (result != MA_SUCCESS) { - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pContext->pulse.pMainLoop); + ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to start mainloop.", result); + ((ma_pa_context_unref_proc)pContext->pulse.pa_context_unref)((ma_pa_context*)pContext->pulse.pPulseContext); + ((ma_pa_threaded_mainloop_free_proc)pContext->pulse.pa_threaded_mainloop_free)(pContext->pulse.pMainLoop); + #ifndef MA_NO_RUNTIME_LINKING + ma_dlclose(pContext, pContext->pulse.pulseSO); + #endif + return result; + } + + result = ma_context_wait_for_pa_context_to_connect__pulse(pContext); + if (result != MA_SUCCESS) { + ((ma_pa_threaded_mainloop_stop_proc)pContext->pulse.pa_threaded_mainloop_stop)(pContext->pulse.pMainLoop); + ((ma_pa_threaded_mainloop_free_proc)pContext->pulse.pa_threaded_mainloop_free)(pContext->pulse.pMainLoop); #ifndef MA_NO_RUNTIME_LINKING ma_dlclose(pContext, pContext->pulse.pulseSO); #endif @@ -62668,7 +62697,7 @@ The following miscellaneous changes have also been made. REVISION HISTORY ================ v0.10.22 - TBD - - Improvements to the PulseAudio backend. + - Refactor to the PulseAudio backend. v0.10.21 - 2020-10-30 - Add ma_is_backend_enabled() and ma_get_enabled_backends() for retrieving enabled backends at run-time. From c8444550594ded96673a89df3b6f91b72c16d0fd Mon Sep 17 00:00:00 2001 From: David Reid Date: Sun, 1 Nov 2020 15:19:35 +1000 Subject: [PATCH 06/27] Fix bugs with ma_decoder_init_file*(). Public issue https://github.com/mackron/miniaudio/issues/213 --- miniaudio.h | 128 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 90 insertions(+), 38 deletions(-) diff --git a/miniaudio.h b/miniaudio.h index 028c1189..5eb915c2 100644 --- a/miniaudio.h +++ b/miniaudio.h @@ -42652,6 +42652,80 @@ MA_API ma_result ma_default_vfs_init(ma_default_vfs* pVFS, const ma_allocation_c } +MA_API ma_result ma_vfs_or_default_open(ma_default_vfs* pVFS, const char* pFilePath, ma_uint32 openMode, ma_vfs_file* pFile) +{ + if (pVFS != NULL) { + return ma_vfs_open(pVFS, pFilePath, openMode, pFile); + } else { + return ma_default_vfs_open(pVFS, pFilePath, openMode, pFile); + } +} + +MA_API ma_result ma_vfs_or_default_open_w(ma_vfs* pVFS, const wchar_t* pFilePath, ma_uint32 openMode, ma_vfs_file* pFile) +{ + if (pVFS != NULL) { + return ma_vfs_open_w(pVFS, pFilePath, openMode, pFile); + } else { + return ma_default_vfs_open_w(pVFS, pFilePath, openMode, pFile); + } +} + +MA_API ma_result ma_vfs_or_default_close(ma_vfs* pVFS, ma_vfs_file file) +{ + if (pVFS != NULL) { + return ma_vfs_close(pVFS, file); + } else { + return ma_default_vfs_close(pVFS, file); + } +} + +MA_API ma_result ma_vfs_or_default_read(ma_vfs* pVFS, ma_vfs_file file, void* pDst, size_t sizeInBytes, size_t* pBytesRead) +{ + if (pVFS != NULL) { + return ma_vfs_read(pVFS, file, pDst, sizeInBytes, pBytesRead); + } else { + return ma_default_vfs_read(pVFS, file, pDst, sizeInBytes, pBytesRead); + } +} + +MA_API ma_result ma_vfs_or_default_write(ma_vfs* pVFS, ma_vfs_file file, const void* pSrc, size_t sizeInBytes, size_t* pBytesWritten) +{ + if (pVFS != NULL) { + return ma_vfs_write(pVFS, file, pSrc, sizeInBytes, pBytesWritten); + } else { + return ma_default_vfs_write(pVFS, file, pSrc, sizeInBytes, pBytesWritten); + } +} + +MA_API ma_result ma_vfs_or_default_seek(ma_vfs* pVFS, ma_vfs_file file, ma_int64 offset, ma_seek_origin origin) +{ + if (pVFS != NULL) { + return ma_vfs_seek(pVFS, file, offset, origin); + } else { + return ma_default_vfs_seek(pVFS, file, offset, origin); + } +} + +MA_API ma_result ma_vfs_or_default_tell(ma_vfs* pVFS, ma_vfs_file file, ma_int64* pCursor) +{ + if (pVFS != NULL) { + return ma_vfs_tell(pVFS, file, pCursor); + } else { + return ma_default_vfs_tell(pVFS, file, pCursor); + } +} + +MA_API ma_result ma_vfs_or_default_info(ma_vfs* pVFS, ma_vfs_file file, ma_file_info* pInfo) +{ + if (pVFS != NULL) { + return ma_vfs_info(pVFS, file, pInfo); + } else { + return ma_default_vfs_info(pVFS, file, pInfo); + } +} + + + /************************************************************************************************************************************************************** Decoding and Encoding Headers. These are auto-generated from a tool. @@ -45314,11 +45388,7 @@ static size_t ma_decoder__on_read_vfs(ma_decoder* pDecoder, void* pBufferOut, si MA_ASSERT(pDecoder != NULL); MA_ASSERT(pBufferOut != NULL); - if (pDecoder->backend.vfs.pVFS == NULL) { - ma_default_vfs_read(NULL, pDecoder->backend.vfs.file, pBufferOut, bytesToRead, &bytesRead); - } else { - ma_vfs_read(pDecoder->backend.vfs.pVFS, pDecoder->backend.vfs.file, pBufferOut, bytesToRead, &bytesRead); - } + ma_vfs_or_default_read(pDecoder->backend.vfs.pVFS, pDecoder->backend.vfs.file, pBufferOut, bytesToRead, &bytesRead); return bytesRead; } @@ -45329,12 +45399,7 @@ static ma_bool32 ma_decoder__on_seek_vfs(ma_decoder* pDecoder, int offset, ma_se MA_ASSERT(pDecoder != NULL); - if (pDecoder->backend.vfs.pVFS == NULL) { - result = ma_default_vfs_seek(NULL, pDecoder->backend.vfs.file, offset, origin); - } else { - result = ma_vfs_seek(pDecoder->backend.vfs.pVFS, pDecoder->backend.vfs.file, offset, origin); - } - + result = ma_vfs_or_default_seek(pDecoder->backend.vfs.pVFS, pDecoder->backend.vfs.file, offset, origin); if (result != MA_SUCCESS) { return MA_FALSE; } @@ -45356,12 +45421,7 @@ static ma_result ma_decoder__preinit_vfs(ma_vfs* pVFS, const char* pFilePath, co return MA_INVALID_ARGS; } - if (pVFS == NULL) { - result = ma_default_vfs_open(NULL, pFilePath, MA_OPEN_MODE_READ, &file); - } else { - result = ma_vfs_open(pVFS, pFilePath, MA_OPEN_MODE_READ, &file); - } - + result = ma_vfs_or_default_open(pVFS, pFilePath, MA_OPEN_MODE_READ, &file); if (result != MA_SUCCESS) { return result; } @@ -45418,7 +45478,7 @@ MA_API ma_result ma_decoder_init_vfs(ma_vfs* pVFS, const char* pFilePath, const } if (result != MA_SUCCESS) { - ma_vfs_close(pVFS, pDecoder->backend.vfs.file); + ma_vfs_or_default_close(pVFS, pDecoder->backend.vfs.file); return result; } @@ -45443,7 +45503,7 @@ MA_API ma_result ma_decoder_init_vfs_wav(ma_vfs* pVFS, const char* pFilePath, co } if (result != MA_SUCCESS) { - ma_vfs_close(pVFS, pDecoder->backend.vfs.file); + ma_vfs_or_default_close(pVFS, pDecoder->backend.vfs.file); } return result; @@ -45474,7 +45534,7 @@ MA_API ma_result ma_decoder_init_vfs_flac(ma_vfs* pVFS, const char* pFilePath, c } if (result != MA_SUCCESS) { - ma_vfs_close(pVFS, pDecoder->backend.vfs.file); + ma_vfs_or_default_close(pVFS, pDecoder->backend.vfs.file); } return result; @@ -45505,7 +45565,7 @@ MA_API ma_result ma_decoder_init_vfs_mp3(ma_vfs* pVFS, const char* pFilePath, co } if (result != MA_SUCCESS) { - ma_vfs_close(pVFS, pDecoder->backend.vfs.file); + ma_vfs_or_default_close(pVFS, pDecoder->backend.vfs.file); } return result; @@ -45536,7 +45596,7 @@ MA_API ma_result ma_decoder_init_vfs_vorbis(ma_vfs* pVFS, const char* pFilePath, } if (result != MA_SUCCESS) { - ma_vfs_close(pVFS, pDecoder->backend.vfs.file); + ma_vfs_or_default_close(pVFS, pDecoder->backend.vfs.file); } return result; @@ -45565,12 +45625,7 @@ static ma_result ma_decoder__preinit_vfs_w(ma_vfs* pVFS, const wchar_t* pFilePat return MA_INVALID_ARGS; } - if (pVFS == NULL) { - result = ma_default_vfs_open_w(NULL, pFilePath, MA_OPEN_MODE_READ, &file); - } else { - result = ma_vfs_open_w(pVFS, pFilePath, MA_OPEN_MODE_READ, &file); - } - + result = ma_vfs_or_default_open_w(pVFS, pFilePath, MA_OPEN_MODE_READ, &file); if (result != MA_SUCCESS) { return result; } @@ -45627,7 +45682,7 @@ MA_API ma_result ma_decoder_init_vfs_w(ma_vfs* pVFS, const wchar_t* pFilePath, c } if (result != MA_SUCCESS) { - ma_vfs_close(pVFS, pDecoder->backend.vfs.file); + ma_vfs_or_default_close(pVFS, pDecoder->backend.vfs.file); return result; } @@ -45652,7 +45707,7 @@ MA_API ma_result ma_decoder_init_vfs_wav_w(ma_vfs* pVFS, const wchar_t* pFilePat } if (result != MA_SUCCESS) { - ma_vfs_close(pVFS, pDecoder->backend.vfs.file); + ma_vfs_or_default_close(pVFS, pDecoder->backend.vfs.file); } return result; @@ -45683,7 +45738,7 @@ MA_API ma_result ma_decoder_init_vfs_flac_w(ma_vfs* pVFS, const wchar_t* pFilePa } if (result != MA_SUCCESS) { - ma_vfs_close(pVFS, pDecoder->backend.vfs.file); + ma_vfs_or_default_close(pVFS, pDecoder->backend.vfs.file); } return result; @@ -45714,7 +45769,7 @@ MA_API ma_result ma_decoder_init_vfs_mp3_w(ma_vfs* pVFS, const wchar_t* pFilePat } if (result != MA_SUCCESS) { - ma_vfs_close(pVFS, pDecoder->backend.vfs.file); + ma_vfs_or_default_close(pVFS, pDecoder->backend.vfs.file); } return result; @@ -45745,7 +45800,7 @@ MA_API ma_result ma_decoder_init_vfs_vorbis_w(ma_vfs* pVFS, const wchar_t* pFile } if (result != MA_SUCCESS) { - ma_vfs_close(pVFS, pDecoder->backend.vfs.file); + ma_vfs_or_default_close(pVFS, pDecoder->backend.vfs.file); } return result; @@ -45823,11 +45878,7 @@ MA_API ma_result ma_decoder_uninit(ma_decoder* pDecoder) } if (pDecoder->onRead == ma_decoder__on_read_vfs) { - if (pDecoder->backend.vfs.pVFS == NULL) { - ma_default_vfs_close(NULL, pDecoder->backend.vfs.file); - } else { - ma_vfs_close(pDecoder->backend.vfs.pVFS, pDecoder->backend.vfs.file); - } + ma_vfs_or_default_close(pDecoder->backend.vfs.pVFS, pDecoder->backend.vfs.file); } ma_data_converter_uninit(&pDecoder->converter); @@ -62698,6 +62749,7 @@ REVISION HISTORY ================ v0.10.22 - TBD - Refactor to the PulseAudio backend. + - Fix bugs in ma_decoder_init_file*() where the file handle is not closed after a decoding error. v0.10.21 - 2020-10-30 - Add ma_is_backend_enabled() and ma_get_enabled_backends() for retrieving enabled backends at run-time. From c4099c86c63bd2cf71d46c809e485f1d5b5e32cd Mon Sep 17 00:00:00 2001 From: David Reid Date: Sun, 1 Nov 2020 17:09:36 +1000 Subject: [PATCH 07/27] PulseAudio: Add support for detecting default devices. Public issue https://github.com/mackron/miniaudio/issues/126 --- miniaudio.h | 543 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 315 insertions(+), 228 deletions(-) diff --git a/miniaudio.h b/miniaudio.h index 5eb915c2..e7d98811 100644 --- a/miniaudio.h +++ b/miniaudio.h @@ -20719,223 +20719,6 @@ static ma_bool32 ma_context_is_device_id_equal__pulse(ma_context* pContext, cons } -typedef struct -{ - ma_context* pContext; - ma_enum_devices_callback_proc callback; - void* pUserData; - ma_bool32 isTerminated; -} ma_context_enumerate_devices_callback_data__pulse; - -static void ma_context_enumerate_devices_sink_callback__pulse(ma_pa_context* pPulseContext, const ma_pa_sink_info* pSinkInfo, int endOfList, void* pUserData) -{ - ma_context_enumerate_devices_callback_data__pulse* pData = (ma_context_enumerate_devices_callback_data__pulse*)pUserData; - ma_device_info deviceInfo; - - MA_ASSERT(pData != NULL); - - if (endOfList || pData->isTerminated) { - return; - } - - MA_ZERO_OBJECT(&deviceInfo); - - /* The name from PulseAudio is the ID for miniaudio. */ - if (pSinkInfo->name != NULL) { - ma_strncpy_s(deviceInfo.id.pulse, sizeof(deviceInfo.id.pulse), pSinkInfo->name, (size_t)-1); - } - - /* The description from PulseAudio is the name for miniaudio. */ - if (pSinkInfo->description != NULL) { - ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), pSinkInfo->description, (size_t)-1); - } - - pData->isTerminated = !pData->callback(pData->pContext, ma_device_type_playback, &deviceInfo, pData->pUserData); - - (void)pPulseContext; /* Unused. */ -} - -static void ma_context_enumerate_devices_source_callback__pulse(ma_pa_context* pPulseContext, const ma_pa_source_info* pSinkInfo, int endOfList, void* pUserData) -{ - ma_context_enumerate_devices_callback_data__pulse* pData = (ma_context_enumerate_devices_callback_data__pulse*)pUserData; - ma_device_info deviceInfo; - - MA_ASSERT(pData != NULL); - - if (endOfList || pData->isTerminated) { - return; - } - - MA_ZERO_OBJECT(&deviceInfo); - - /* The name from PulseAudio is the ID for miniaudio. */ - if (pSinkInfo->name != NULL) { - ma_strncpy_s(deviceInfo.id.pulse, sizeof(deviceInfo.id.pulse), pSinkInfo->name, (size_t)-1); - } - - /* The description from PulseAudio is the name for miniaudio. */ - if (pSinkInfo->description != NULL) { - ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), pSinkInfo->description, (size_t)-1); - } - - pData->isTerminated = !pData->callback(pData->pContext, ma_device_type_capture, &deviceInfo, pData->pUserData); - - (void)pPulseContext; /* Unused. */ -} - -static ma_result ma_context_enumerate_devices__pulse(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) -{ - ma_result result = MA_SUCCESS; - ma_context_enumerate_devices_callback_data__pulse callbackData; - ma_pa_operation* pOP = NULL; - - MA_ASSERT(pContext != NULL); - MA_ASSERT(callback != NULL); - - callbackData.pContext = pContext; - callbackData.callback = callback; - callbackData.pUserData = pUserData; - callbackData.isTerminated = MA_FALSE; - - /* Playback. */ - if (!callbackData.isTerminated) { - pOP = ((ma_pa_context_get_sink_info_list_proc)pContext->pulse.pa_context_get_sink_info_list)(pContext->pulse.pPulseContext, ma_context_enumerate_devices_sink_callback__pulse, &callbackData); - if (pOP == NULL) { - result = MA_ERROR; - goto done; - } - - result = ma_wait_for_operation__pulse(pContext, pOP); - ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); - if (result != MA_SUCCESS) { - goto done; - } - } - - - /* Capture. */ - if (!callbackData.isTerminated) { - pOP = ((ma_pa_context_get_source_info_list_proc)pContext->pulse.pa_context_get_source_info_list)(pContext->pulse.pPulseContext, ma_context_enumerate_devices_source_callback__pulse, &callbackData); - if (pOP == NULL) { - result = MA_ERROR; - goto done; - } - - result = ma_wait_for_operation__pulse(pContext, pOP); - ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); - if (result != MA_SUCCESS) { - goto done; - } - } - -done: - return result; -} - - -typedef struct -{ - ma_device_info* pDeviceInfo; - ma_bool32 foundDevice; -} ma_context_get_device_info_callback_data__pulse; - -static void ma_context_get_device_info_sink_callback__pulse(ma_pa_context* pPulseContext, const ma_pa_sink_info* pInfo, int endOfList, void* pUserData) -{ - ma_context_get_device_info_callback_data__pulse* pData = (ma_context_get_device_info_callback_data__pulse*)pUserData; - - if (endOfList > 0) { - return; - } - - MA_ASSERT(pData != NULL); - pData->foundDevice = MA_TRUE; - - if (pInfo->name != NULL) { - ma_strncpy_s(pData->pDeviceInfo->id.pulse, sizeof(pData->pDeviceInfo->id.pulse), pInfo->name, (size_t)-1); - } - - if (pInfo->description != NULL) { - ma_strncpy_s(pData->pDeviceInfo->name, sizeof(pData->pDeviceInfo->name), pInfo->description, (size_t)-1); - } - - pData->pDeviceInfo->minChannels = pInfo->sample_spec.channels; - pData->pDeviceInfo->maxChannels = pInfo->sample_spec.channels; - pData->pDeviceInfo->minSampleRate = pInfo->sample_spec.rate; - pData->pDeviceInfo->maxSampleRate = pInfo->sample_spec.rate; - pData->pDeviceInfo->formatCount = 1; - pData->pDeviceInfo->formats[0] = ma_format_from_pulse(pInfo->sample_spec.format); - - (void)pPulseContext; /* Unused. */ -} - -static void ma_context_get_device_info_source_callback__pulse(ma_pa_context* pPulseContext, const ma_pa_source_info* pInfo, int endOfList, void* pUserData) -{ - ma_context_get_device_info_callback_data__pulse* pData = (ma_context_get_device_info_callback_data__pulse*)pUserData; - - if (endOfList > 0) { - return; - } - - MA_ASSERT(pData != NULL); - pData->foundDevice = MA_TRUE; - - if (pInfo->name != NULL) { - ma_strncpy_s(pData->pDeviceInfo->id.pulse, sizeof(pData->pDeviceInfo->id.pulse), pInfo->name, (size_t)-1); - } - - if (pInfo->description != NULL) { - ma_strncpy_s(pData->pDeviceInfo->name, sizeof(pData->pDeviceInfo->name), pInfo->description, (size_t)-1); - } - - pData->pDeviceInfo->minChannels = pInfo->sample_spec.channels; - pData->pDeviceInfo->maxChannels = pInfo->sample_spec.channels; - pData->pDeviceInfo->minSampleRate = pInfo->sample_spec.rate; - pData->pDeviceInfo->maxSampleRate = pInfo->sample_spec.rate; - pData->pDeviceInfo->formatCount = 1; - pData->pDeviceInfo->formats[0] = ma_format_from_pulse(pInfo->sample_spec.format); - - (void)pPulseContext; /* Unused. */ -} - -static ma_result ma_context_get_device_info__pulse(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_share_mode shareMode, ma_device_info* pDeviceInfo) -{ - ma_result result = MA_SUCCESS; - ma_context_get_device_info_callback_data__pulse callbackData; - ma_pa_operation* pOP = NULL; - - MA_ASSERT(pContext != NULL); - - /* No exclusive mode with the PulseAudio backend. */ - if (shareMode == ma_share_mode_exclusive) { - return MA_SHARE_MODE_NOT_SUPPORTED; - } - - callbackData.pDeviceInfo = pDeviceInfo; - callbackData.foundDevice = MA_FALSE; - - if (deviceType == ma_device_type_playback) { - pOP = ((ma_pa_context_get_sink_info_by_name_proc)pContext->pulse.pa_context_get_sink_info_by_name)(pContext->pulse.pPulseContext, pDeviceID->pulse, ma_context_get_device_info_sink_callback__pulse, &callbackData); - } else { - pOP = ((ma_pa_context_get_source_info_by_name_proc)pContext->pulse.pa_context_get_source_info_by_name)(pContext->pulse.pPulseContext, pDeviceID->pulse, ma_context_get_device_info_source_callback__pulse, &callbackData); - } - - if (pOP != NULL) { - ma_wait_for_operation_and_unref__pulse(pContext, pOP); - } else { - result = MA_ERROR; - goto done; - } - - if (!callbackData.foundDevice) { - result = MA_NO_DEVICE; - goto done; - } - -done: - return result; -} - - static void ma_device_sink_info_callback(ma_pa_context* pPulseContext, const ma_pa_sink_info* pInfo, int endOfList, void* pUserData) { ma_pa_sink_info* pInfoOut; @@ -21000,6 +20783,315 @@ static void ma_device_source_name_callback(ma_pa_context* pPulseContext, const m (void)pPulseContext; /* Unused. */ } + +static ma_result ma_context_get_sink_info__pulse(ma_context* pContext, const char* pDeviceName, ma_pa_sink_info* pSinkInfo) +{ + ma_pa_operation* pOP; + + pOP = ((ma_pa_context_get_sink_info_by_name_proc)pContext->pulse.pa_context_get_sink_info_by_name)((ma_pa_context*)pContext->pulse.pPulseContext, pDeviceName, ma_device_sink_info_callback, pSinkInfo); + if (pOP == NULL) { + return MA_ERROR; + } + + ma_wait_for_operation_and_unref__pulse(pContext, pOP); + return MA_SUCCESS; +} + +static ma_result ma_context_get_source_info__pulse(ma_context* pContext, const char* pDeviceName, ma_pa_source_info* pSourceInfo) +{ + ma_pa_operation* pOP; + + pOP = ((ma_pa_context_get_source_info_by_name_proc)pContext->pulse.pa_context_get_source_info_by_name)((ma_pa_context*)pContext->pulse.pPulseContext, pDeviceName, ma_device_source_info_callback, pSourceInfo); + if (pOP == NULL) { + return MA_ERROR; + } + + ma_wait_for_operation_and_unref__pulse(pContext, pOP); + return MA_SUCCESS; +} + +static ma_result ma_context_get_default_device_index__pulse(ma_context* pContext, ma_device_type deviceType, ma_uint32* pIndex) +{ + ma_result result; + + MA_ASSERT(pContext != NULL); + MA_ASSERT(pIndex != NULL); + + if (pIndex != NULL) { + *pIndex = (ma_uint32)-1; + } + + if (deviceType == ma_device_type_playback) { + ma_pa_sink_info sinkInfo; + result = ma_context_get_sink_info__pulse(pContext, NULL, &sinkInfo); + if (result != MA_SUCCESS) { + return result; + } + + if (pIndex != NULL) { + *pIndex = sinkInfo.index; + } + } + + if (deviceType == ma_device_type_capture) { + ma_pa_source_info sourceInfo; + result = ma_context_get_source_info__pulse(pContext, NULL, &sourceInfo); + if (result != MA_SUCCESS) { + return result; + } + + if (pIndex != NULL) { + *pIndex = sourceInfo.index; + } + } + + return MA_SUCCESS; +} + + +typedef struct +{ + ma_context* pContext; + ma_enum_devices_callback_proc callback; + void* pUserData; + ma_bool32 isTerminated; + ma_uint32 defaultDeviceIndexPlayback; + ma_uint32 defaultDeviceIndexCapture; +} ma_context_enumerate_devices_callback_data__pulse; + +static void ma_context_enumerate_devices_sink_callback__pulse(ma_pa_context* pPulseContext, const ma_pa_sink_info* pSinkInfo, int endOfList, void* pUserData) +{ + ma_context_enumerate_devices_callback_data__pulse* pData = (ma_context_enumerate_devices_callback_data__pulse*)pUserData; + ma_device_info deviceInfo; + + MA_ASSERT(pData != NULL); + + if (endOfList || pData->isTerminated) { + return; + } + + MA_ZERO_OBJECT(&deviceInfo); + + /* The name from PulseAudio is the ID for miniaudio. */ + if (pSinkInfo->name != NULL) { + ma_strncpy_s(deviceInfo.id.pulse, sizeof(deviceInfo.id.pulse), pSinkInfo->name, (size_t)-1); + } + + /* The description from PulseAudio is the name for miniaudio. */ + if (pSinkInfo->description != NULL) { + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), pSinkInfo->description, (size_t)-1); + } + + if (pSinkInfo->index == pData->defaultDeviceIndexPlayback) { + deviceInfo._private.isDefault = MA_TRUE; + } + + pData->isTerminated = !pData->callback(pData->pContext, ma_device_type_playback, &deviceInfo, pData->pUserData); + + (void)pPulseContext; /* Unused. */ +} + +static void ma_context_enumerate_devices_source_callback__pulse(ma_pa_context* pPulseContext, const ma_pa_source_info* pSourceInfo, int endOfList, void* pUserData) +{ + ma_context_enumerate_devices_callback_data__pulse* pData = (ma_context_enumerate_devices_callback_data__pulse*)pUserData; + ma_device_info deviceInfo; + + MA_ASSERT(pData != NULL); + + if (endOfList || pData->isTerminated) { + return; + } + + MA_ZERO_OBJECT(&deviceInfo); + + /* The name from PulseAudio is the ID for miniaudio. */ + if (pSourceInfo->name != NULL) { + ma_strncpy_s(deviceInfo.id.pulse, sizeof(deviceInfo.id.pulse), pSourceInfo->name, (size_t)-1); + } + + /* The description from PulseAudio is the name for miniaudio. */ + if (pSourceInfo->description != NULL) { + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), pSourceInfo->description, (size_t)-1); + } + + if (pSourceInfo->index == pData->defaultDeviceIndexCapture) { + deviceInfo._private.isDefault = MA_TRUE; + } + + pData->isTerminated = !pData->callback(pData->pContext, ma_device_type_capture, &deviceInfo, pData->pUserData); + + (void)pPulseContext; /* Unused. */ +} + +static ma_result ma_context_enumerate_devices__pulse(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) +{ + ma_result result = MA_SUCCESS; + ma_context_enumerate_devices_callback_data__pulse callbackData; + ma_pa_operation* pOP = NULL; + + MA_ASSERT(pContext != NULL); + MA_ASSERT(callback != NULL); + + callbackData.pContext = pContext; + callbackData.callback = callback; + callbackData.pUserData = pUserData; + callbackData.isTerminated = MA_FALSE; + callbackData.defaultDeviceIndexPlayback = (ma_uint32)-1; + callbackData.defaultDeviceIndexCapture = (ma_uint32)-1; + + /* We need to get the index of the default devices. */ + ma_context_get_default_device_index__pulse(pContext, ma_device_type_playback, &callbackData.defaultDeviceIndexPlayback); + ma_context_get_default_device_index__pulse(pContext, ma_device_type_capture, &callbackData.defaultDeviceIndexCapture); + + /* Playback. */ + if (!callbackData.isTerminated) { + pOP = ((ma_pa_context_get_sink_info_list_proc)pContext->pulse.pa_context_get_sink_info_list)(pContext->pulse.pPulseContext, ma_context_enumerate_devices_sink_callback__pulse, &callbackData); + if (pOP == NULL) { + result = MA_ERROR; + goto done; + } + + result = ma_wait_for_operation__pulse(pContext, pOP); + ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); + if (result != MA_SUCCESS) { + goto done; + } + } + + + /* Capture. */ + if (!callbackData.isTerminated) { + pOP = ((ma_pa_context_get_source_info_list_proc)pContext->pulse.pa_context_get_source_info_list)(pContext->pulse.pPulseContext, ma_context_enumerate_devices_source_callback__pulse, &callbackData); + if (pOP == NULL) { + result = MA_ERROR; + goto done; + } + + result = ma_wait_for_operation__pulse(pContext, pOP); + ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); + if (result != MA_SUCCESS) { + goto done; + } + } + +done: + return result; +} + + +typedef struct +{ + ma_device_info* pDeviceInfo; + ma_uint32 defaultDeviceIndex; + ma_bool32 foundDevice; +} ma_context_get_device_info_callback_data__pulse; + +static void ma_context_get_device_info_sink_callback__pulse(ma_pa_context* pPulseContext, const ma_pa_sink_info* pInfo, int endOfList, void* pUserData) +{ + ma_context_get_device_info_callback_data__pulse* pData = (ma_context_get_device_info_callback_data__pulse*)pUserData; + + if (endOfList > 0) { + return; + } + + MA_ASSERT(pData != NULL); + pData->foundDevice = MA_TRUE; + + if (pInfo->name != NULL) { + ma_strncpy_s(pData->pDeviceInfo->id.pulse, sizeof(pData->pDeviceInfo->id.pulse), pInfo->name, (size_t)-1); + } + + if (pInfo->description != NULL) { + ma_strncpy_s(pData->pDeviceInfo->name, sizeof(pData->pDeviceInfo->name), pInfo->description, (size_t)-1); + } + + pData->pDeviceInfo->minChannels = pInfo->sample_spec.channels; + pData->pDeviceInfo->maxChannels = pInfo->sample_spec.channels; + pData->pDeviceInfo->minSampleRate = pInfo->sample_spec.rate; + pData->pDeviceInfo->maxSampleRate = pInfo->sample_spec.rate; + pData->pDeviceInfo->formatCount = 1; + pData->pDeviceInfo->formats[0] = ma_format_from_pulse(pInfo->sample_spec.format); + + if (pData->defaultDeviceIndex == pInfo->index) { + pData->pDeviceInfo->_private.isDefault = MA_TRUE; + } + + (void)pPulseContext; /* Unused. */ +} + +static void ma_context_get_device_info_source_callback__pulse(ma_pa_context* pPulseContext, const ma_pa_source_info* pInfo, int endOfList, void* pUserData) +{ + ma_context_get_device_info_callback_data__pulse* pData = (ma_context_get_device_info_callback_data__pulse*)pUserData; + + if (endOfList > 0) { + return; + } + + MA_ASSERT(pData != NULL); + pData->foundDevice = MA_TRUE; + + if (pInfo->name != NULL) { + ma_strncpy_s(pData->pDeviceInfo->id.pulse, sizeof(pData->pDeviceInfo->id.pulse), pInfo->name, (size_t)-1); + } + + if (pInfo->description != NULL) { + ma_strncpy_s(pData->pDeviceInfo->name, sizeof(pData->pDeviceInfo->name), pInfo->description, (size_t)-1); + } + + pData->pDeviceInfo->minChannels = pInfo->sample_spec.channels; + pData->pDeviceInfo->maxChannels = pInfo->sample_spec.channels; + pData->pDeviceInfo->minSampleRate = pInfo->sample_spec.rate; + pData->pDeviceInfo->maxSampleRate = pInfo->sample_spec.rate; + pData->pDeviceInfo->formatCount = 1; + pData->pDeviceInfo->formats[0] = ma_format_from_pulse(pInfo->sample_spec.format); + + if (pData->defaultDeviceIndex == pInfo->index) { + pData->pDeviceInfo->_private.isDefault = MA_TRUE; + } + + (void)pPulseContext; /* Unused. */ +} + +static ma_result ma_context_get_device_info__pulse(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_share_mode shareMode, ma_device_info* pDeviceInfo) +{ + ma_result result = MA_SUCCESS; + ma_context_get_device_info_callback_data__pulse callbackData; + ma_pa_operation* pOP = NULL; + + MA_ASSERT(pContext != NULL); + + /* No exclusive mode with the PulseAudio backend. */ + if (shareMode == ma_share_mode_exclusive) { + return MA_SHARE_MODE_NOT_SUPPORTED; + } + + callbackData.pDeviceInfo = pDeviceInfo; + callbackData.foundDevice = MA_FALSE; + + result = ma_context_get_default_device_index__pulse(pContext, deviceType, &callbackData.defaultDeviceIndex); + + if (deviceType == ma_device_type_playback) { + pOP = ((ma_pa_context_get_sink_info_by_name_proc)pContext->pulse.pa_context_get_sink_info_by_name)(pContext->pulse.pPulseContext, pDeviceID->pulse, ma_context_get_device_info_sink_callback__pulse, &callbackData); + } else { + pOP = ((ma_pa_context_get_source_info_by_name_proc)pContext->pulse.pa_context_get_source_info_by_name)(pContext->pulse.pPulseContext, pDeviceID->pulse, ma_context_get_device_info_source_callback__pulse, &callbackData); + } + + if (pOP != NULL) { + ma_wait_for_operation_and_unref__pulse(pContext, pOP); + } else { + result = MA_ERROR; + goto done; + } + + if (!callbackData.foundDevice) { + result = MA_NO_DEVICE; + goto done; + } + +done: + return result; +} + static void ma_device_uninit__pulse(ma_device* pDevice) { ma_context* pContext; @@ -21214,7 +21306,6 @@ static ma_result ma_device_init__pulse(ma_context* pContext, const ma_device_con ma_uint32 periodSizeInMilliseconds; ma_pa_sink_info sinkInfo; ma_pa_source_info sourceInfo; - ma_pa_operation* pOP = NULL; ma_pa_sample_spec ss; ma_pa_channel_map cmap; ma_pa_buffer_attr attr; @@ -21250,11 +21341,9 @@ static ma_result ma_device_init__pulse(ma_context* pContext, const ma_device_con } if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { - pOP = ((ma_pa_context_get_source_info_by_name_proc)pContext->pulse.pa_context_get_source_info_by_name)((ma_pa_context*)pContext->pulse.pPulseContext, devCapture, ma_device_source_info_callback, &sourceInfo); - if (pOP != NULL) { - ma_wait_for_operation_and_unref__pulse(pContext, pOP); - } else { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to retrieve source info for capture device.", ma_result_from_pulse(error)); + result = ma_context_get_source_info__pulse(pContext, devCapture, &sourceInfo); + if (result != MA_SUCCESS) { + ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to retrieve source info for capture device.", result); goto on_error0; } @@ -21342,11 +21431,9 @@ static ma_result ma_device_init__pulse(ma_context* pContext, const ma_device_con } if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { - pOP = ((ma_pa_context_get_sink_info_by_name_proc)pContext->pulse.pa_context_get_sink_info_by_name)((ma_pa_context*)pContext->pulse.pPulseContext, devPlayback, ma_device_sink_info_callback, &sinkInfo); - if (pOP != NULL) { - ma_wait_for_operation_and_unref__pulse(pContext, pOP); - } else { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to retrieve sink info for playback device.", ma_result_from_pulse(error)); + result = ma_context_get_sink_info__pulse(pContext, devPlayback, &sinkInfo); + if (result != MA_SUCCESS) { + ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to retrieve sink info for playback device.", result); goto on_error2; } From edfc9bbcaf6bc60a7e8e528da886a01b5da86524 Mon Sep 17 00:00:00 2001 From: David Reid Date: Sun, 1 Nov 2020 17:29:48 +1000 Subject: [PATCH 08/27] ALSA: Fix a bug in ma_context_get_device_info(). --- miniaudio.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/miniaudio.h b/miniaudio.h index e7d98811..6dab0fd1 100644 --- a/miniaudio.h +++ b/miniaudio.h @@ -18386,12 +18386,14 @@ static ma_result ma_context_get_device_info__alsa(ma_context* pContext, ma_devic /* We need to initialize a HW parameters object in order to know what formats are supported. */ pHWParams = (ma_snd_pcm_hw_params_t*)ma__calloc_from_callbacks(((ma_snd_pcm_hw_params_sizeof_proc)pContext->alsa.snd_pcm_hw_params_sizeof)(), &pContext->allocationCallbacks); if (pHWParams == NULL) { + ((ma_snd_pcm_close_proc)pContext->alsa.snd_pcm_close)(pPCM); return MA_OUT_OF_MEMORY; } resultALSA = ((ma_snd_pcm_hw_params_any_proc)pContext->alsa.snd_pcm_hw_params_any)(pPCM, pHWParams); if (resultALSA < 0) { ma__free_from_callbacks(pHWParams, &pContext->allocationCallbacks); + ((ma_snd_pcm_close_proc)pContext->alsa.snd_pcm_close)(pPCM); return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to initialize hardware parameters. snd_pcm_hw_params_any() failed.", ma_result_from_errno(-resultALSA)); } @@ -18404,6 +18406,7 @@ static ma_result ma_context_get_device_info__alsa(ma_context* pContext, ma_devic pFormatMask = (ma_snd_pcm_format_mask_t*)ma__calloc_from_callbacks(((ma_snd_pcm_format_mask_sizeof_proc)pContext->alsa.snd_pcm_format_mask_sizeof)(), &pContext->allocationCallbacks); if (pFormatMask == NULL) { ma__free_from_callbacks(pHWParams, &pContext->allocationCallbacks); + ((ma_snd_pcm_close_proc)pContext->alsa.snd_pcm_close)(pPCM); return MA_OUT_OF_MEMORY; } @@ -62837,6 +62840,7 @@ REVISION HISTORY v0.10.22 - TBD - Refactor to the PulseAudio backend. - Fix bugs in ma_decoder_init_file*() where the file handle is not closed after a decoding error. + - ALSA: Fix a bug in ma_context_get_device_info() where the PCM handle is left open in the event of an error. v0.10.21 - 2020-10-30 - Add ma_is_backend_enabled() and ma_get_enabled_backends() for retrieving enabled backends at run-time. From 7c937c491ed04c78846d5fb53103ad91a2b8ae98 Mon Sep 17 00:00:00 2001 From: David Reid Date: Sun, 1 Nov 2020 17:55:43 +1000 Subject: [PATCH 09/27] ALSA: Add basic support for detecting default devices. Public issue https://github.com/mackron/miniaudio/issues/126 --- miniaudio.h | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/miniaudio.h b/miniaudio.h index 6dab0fd1..064a4532 100644 --- a/miniaudio.h +++ b/miniaudio.h @@ -18242,6 +18242,15 @@ static ma_result ma_context_enumerate_devices__alsa(ma_context* pContext, ma_enu MA_ZERO_OBJECT(&deviceInfo); ma_strncpy_s(deviceInfo.id.alsa, sizeof(deviceInfo.id.alsa), hwid, (size_t)-1); + /* + There's no good way to determine whether or not a device is the default on Linux. We're just going to do something simple and + just use the name of "default" as the indicator. + */ + if (ma_strcmp(deviceInfo.id.alsa, "default") == 0) { + deviceInfo._private.isDefault = MA_TRUE; + } + + /* DESC is the friendly name. We treat this slightly differently depending on whether or not we are using verbose device enumeration. In verbose mode we want to take the entire description so that the end-user can distinguish @@ -18363,9 +18372,9 @@ static ma_result ma_context_get_device_info__alsa(ma_context* pContext, ma_devic MA_ASSERT(pContext != NULL); /* We just enumerate to find basic information about the device. */ - data.deviceType = deviceType; - data.pDeviceID = pDeviceID; - data.shareMode = shareMode; + data.deviceType = deviceType; + data.pDeviceID = pDeviceID; + data.shareMode = shareMode; data.pDeviceInfo = pDeviceInfo; data.foundDevice = MA_FALSE; result = ma_context_enumerate_devices__alsa(pContext, ma_context_get_device_info_enum_callback__alsa, &data); @@ -18377,6 +18386,10 @@ static ma_result ma_context_get_device_info__alsa(ma_context* pContext, ma_devic return MA_NO_DEVICE; } + if (ma_strcmp(pDeviceInfo->id.alsa, "default") == 0) { + pDeviceInfo->_private.isDefault = MA_TRUE; + } + /* For detailed info we need to open the device. */ result = ma_context_open_pcm__alsa(pContext, shareMode, deviceType, pDeviceID, 0, &pPCM); if (result != MA_SUCCESS) { From 9b4e524b3f76a8c7b8fdd0474c241b1edcf7a6e8 Mon Sep 17 00:00:00 2001 From: David Reid Date: Sun, 1 Nov 2020 18:29:14 +1000 Subject: [PATCH 10/27] JACK: Add support for default device detection. Public issue https://github.com/mackron/miniaudio/issues/126 --- miniaudio.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/miniaudio.h b/miniaudio.h index 064a4532..3d03ecce 100644 --- a/miniaudio.h +++ b/miniaudio.h @@ -22093,6 +22093,7 @@ static ma_result ma_context_enumerate_devices__jack(ma_context* pContext, ma_enu ma_device_info deviceInfo; MA_ZERO_OBJECT(&deviceInfo); ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + deviceInfo._private.isDefault = MA_TRUE; /* JACK only uses default devices. */ cbResult = callback(pContext, ma_device_type_playback, &deviceInfo, pUserData); } @@ -22101,6 +22102,7 @@ static ma_result ma_context_enumerate_devices__jack(ma_context* pContext, ma_enu ma_device_info deviceInfo; MA_ZERO_OBJECT(&deviceInfo); ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + deviceInfo._private.isDefault = MA_TRUE; /* JACK only uses default devices. */ cbResult = callback(pContext, ma_device_type_capture, &deviceInfo, pUserData); } @@ -22131,6 +22133,9 @@ static ma_result ma_context_get_device_info__jack(ma_context* pContext, ma_devic ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); } + /* Jack only uses default devices. */ + pDeviceInfo->_private.isDefault = MA_TRUE; + /* Jack only supports f32 and has a specific channel count and sample rate. */ pDeviceInfo->formatCount = 1; pDeviceInfo->formats[0] = ma_format_f32; From a1052923b0f25541e2bc2e134eade8e94079c138 Mon Sep 17 00:00:00 2001 From: David Reid Date: Sun, 1 Nov 2020 18:39:32 +1000 Subject: [PATCH 11/27] WinMM, DirectSound: Add support for default device detection. Public issue https://github.com/mackron/miniaudio/issues/126 --- miniaudio.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/miniaudio.h b/miniaudio.h index 3d03ecce..786ce703 100644 --- a/miniaudio.h +++ b/miniaudio.h @@ -15079,6 +15079,7 @@ static BOOL CALLBACK ma_context_enumerate_devices_callback__dsound(LPGUID lpGuid MA_COPY_MEMORY(deviceInfo.id.dsound, lpGuid, 16); } else { MA_ZERO_MEMORY(deviceInfo.id.dsound, 16); + deviceInfo._private.isDefault = MA_TRUE; } /* Name / Description */ @@ -15140,6 +15141,7 @@ static BOOL CALLBACK ma_context_get_device_info_callback__dsound(LPGUID lpGuid, if ((pData->pDeviceID == NULL || ma_is_guid_null(pData->pDeviceID->dsound)) && (lpGuid == NULL || ma_is_guid_null(lpGuid))) { /* Default device. */ ma_strncpy_s(pData->pDeviceInfo->name, sizeof(pData->pDeviceInfo->name), lpcstrDescription, (size_t)-1); + pData->pDeviceInfo->_private.isDefault = MA_TRUE; pData->found = MA_TRUE; return FALSE; /* Stop enumeration. */ } else { @@ -16628,6 +16630,11 @@ static ma_result ma_context_enumerate_devices__winmm(ma_context* pContext, ma_en MA_ZERO_OBJECT(&deviceInfo); deviceInfo.id.winmm = iPlaybackDevice; + /* The first enumerated device is the default device. */ + if (iPlaybackDevice == 0) { + deviceInfo._private.isDefault = MA_TRUE; + } + if (ma_context_get_device_info_from_WAVEOUTCAPS2(pContext, &caps, &deviceInfo) == MA_SUCCESS) { ma_bool32 cbResult = callback(pContext, ma_device_type_playback, &deviceInfo, pUserData); if (cbResult == MA_FALSE) { @@ -16652,6 +16659,11 @@ static ma_result ma_context_enumerate_devices__winmm(ma_context* pContext, ma_en MA_ZERO_OBJECT(&deviceInfo); deviceInfo.id.winmm = iCaptureDevice; + /* The first enumerated device is the default device. */ + if (iCaptureDevice == 0) { + deviceInfo._private.isDefault = MA_TRUE; + } + if (ma_context_get_device_info_from_WAVEINCAPS2(pContext, &caps, &deviceInfo) == MA_SUCCESS) { ma_bool32 cbResult = callback(pContext, ma_device_type_capture, &deviceInfo, pUserData); if (cbResult == MA_FALSE) { @@ -16681,6 +16693,11 @@ static ma_result ma_context_get_device_info__winmm(ma_context* pContext, ma_devi pDeviceInfo->id.winmm = winMMDeviceID; + /* The first ID is the default device. */ + if (winMMDeviceID == 0) { + pDeviceInfo->_private.isDefault = MA_TRUE; + } + if (deviceType == ma_device_type_playback) { MMRESULT result; MA_WAVEOUTCAPS2A caps; From b61ea2461e4c5fd46d2e601e1035cb656a82ea20 Mon Sep 17 00:00:00 2001 From: David Reid Date: Sun, 1 Nov 2020 18:50:47 +1000 Subject: [PATCH 12/27] WebAudio: Add support for default device detection. Public issue https://github.com/mackron/miniaudio/issues/126 --- miniaudio.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/miniaudio.h b/miniaudio.h index 786ce703..2dd6617c 100644 --- a/miniaudio.h +++ b/miniaudio.h @@ -30346,6 +30346,7 @@ static ma_result ma_context_enumerate_devices__webaudio(ma_context* pContext, ma ma_device_info deviceInfo; MA_ZERO_OBJECT(&deviceInfo); ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + deviceInfo._private.isDefault = MA_TRUE; /* Only supporting default devices. */ cbResult = callback(pContext, ma_device_type_playback, &deviceInfo, pUserData); } @@ -30355,6 +30356,7 @@ static ma_result ma_context_enumerate_devices__webaudio(ma_context* pContext, ma ma_device_info deviceInfo; MA_ZERO_OBJECT(&deviceInfo); ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + deviceInfo._private.isDefault = MA_TRUE; /* Only supporting default devices. */ cbResult = callback(pContext, ma_device_type_capture, &deviceInfo, pUserData); } } @@ -30386,6 +30388,9 @@ static ma_result ma_context_get_device_info__webaudio(ma_context* pContext, ma_d ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); } + /* Only supporting default devices. */ + pDeviceInfo->_private.isDefault = MA_TRUE; + /* Web Audio can support any number of channels and sample rates. It only supports f32 formats, however. */ pDeviceInfo->minChannels = 1; pDeviceInfo->maxChannels = MA_MAX_CHANNELS; From 704dccf65f749c09fb8d8b4b372f2b0587f43fe6 Mon Sep 17 00:00:00 2001 From: David Reid Date: Sun, 1 Nov 2020 20:18:43 +1000 Subject: [PATCH 13/27] Fix compilation errors on BSD. --- miniaudio.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/miniaudio.h b/miniaudio.h index 2dd6617c..ac1523c1 100644 --- a/miniaudio.h +++ b/miniaudio.h @@ -27725,6 +27725,8 @@ OSS Backend #define SNDCTL_DSP_HALT SNDCTL_DSP_RESET #endif +#define MA_OSS_DEFAULT_DEVICE_NAME "/dev/dsp" + static int ma_open_temp_device__oss() { /* The OSS sample code uses "/dev/mixer" as the device for getting system properties so I'm going to do the same. */ @@ -27752,7 +27754,7 @@ static ma_result ma_context_open_device__oss(ma_context* pContext, ma_device_typ return MA_INVALID_ARGS; } - deviceName = "/dev/dsp"; + deviceName = MA_OSS_DEFAULT_DEVICE_NAME; if (pDeviceID != NULL) { deviceName = pDeviceID->oss; } @@ -42602,7 +42604,7 @@ static ma_result ma_default_vfs_tell__stdio(ma_vfs* pVFS, ma_vfs_file file, ma_i return MA_SUCCESS; } -#if !defined(_MSC_VER) && !((defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 1) || defined(_XOPEN_SOURCE) || defined(_POSIX_SOURCE)) +#if !defined(_MSC_VER) && !((defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 1) || defined(_XOPEN_SOURCE) || defined(_POSIX_SOURCE)) && !defined(MA_BSD) int fileno(FILE *stream); #endif From ab2fd23beb5ae83544c0b381efef9d18280001f2 Mon Sep 17 00:00:00 2001 From: David Reid Date: Sun, 1 Nov 2020 20:40:59 +1000 Subject: [PATCH 14/27] Improve compiler support for older versions of GCC. --- miniaudio.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/miniaudio.h b/miniaudio.h index ac1523c1..b6ef7d2f 100644 --- a/miniaudio.h +++ b/miniaudio.h @@ -1451,7 +1451,7 @@ extern "C" { #pragma warning(disable:4201) /* nonstandard extension used: nameless struct/union */ #pragma warning(disable:4214) /* nonstandard extension used: bit field types other than int */ #pragma warning(disable:4324) /* structure was padded due to alignment specifier */ -#else +#elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpedantic" /* For ISO C99 doesn't support unnamed structs/unions [-Wpedantic] */ #if defined(__clang__) @@ -1504,7 +1504,7 @@ typedef unsigned int ma_uint32; typedef signed __int64 ma_int64; typedef unsigned __int64 ma_uint64; #else - #if defined(__GNUC__) + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wlong-long" #if defined(__clang__) @@ -1513,7 +1513,7 @@ typedef unsigned int ma_uint32; #endif typedef signed long long ma_int64; typedef unsigned long long ma_uint64; - #if defined(__GNUC__) + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic pop #endif #endif @@ -3910,7 +3910,7 @@ struct ma_device }; #if defined(_MSC_VER) && !defined(__clang__) #pragma warning(pop) -#else +#elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic pop /* For ISO C99 doesn't support unnamed structs/unions [-Wpedantic] */ #endif @@ -6392,7 +6392,7 @@ static MA_INLINE void ma_yield() #endif -#if defined(__GNUC__) +#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-variable" #endif @@ -6429,7 +6429,7 @@ static ma_format g_maFormatPriorities[] = { ma_format_u8 /* Low quality */ }; -#if defined(__GNUC__) +#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic pop #endif From a5612b4f9c98d47d0dd583e3be33d9e1dc4ac6d1 Mon Sep 17 00:00:00 2001 From: David Reid Date: Sun, 1 Nov 2020 20:50:59 +1000 Subject: [PATCH 15/27] Update c89atomic. --- miniaudio.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/miniaudio.h b/miniaudio.h index b6ef7d2f..4c1ce10e 100644 --- a/miniaudio.h +++ b/miniaudio.h @@ -7730,7 +7730,7 @@ typedef unsigned int c89atomic_uint32; typedef signed __int64 c89atomic_int64; typedef unsigned __int64 c89atomic_uint64; #else - #if defined(__GNUC__) + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wlong-long" #if defined(__clang__) @@ -7739,7 +7739,7 @@ typedef unsigned int c89atomic_uint32; #endif typedef signed long long c89atomic_int64; typedef unsigned long long c89atomic_uint64; - #if defined(__GNUC__) + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic pop #endif #endif From 12c7babea10456ef347e05072df2186a2073ad69 Mon Sep 17 00:00:00 2001 From: David Reid Date: Sun, 1 Nov 2020 20:53:16 +1000 Subject: [PATCH 16/27] Fix compilation warnings. --- miniaudio.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/miniaudio.h b/miniaudio.h index 4c1ce10e..27f3700e 100644 --- a/miniaudio.h +++ b/miniaudio.h @@ -37915,13 +37915,13 @@ MA_API ma_uint64 ma_resampler_get_required_input_frame_count(ma_resampler* pResa case ma_resample_algorithm_speex: { #if defined(MA_HAS_SPEEX_RESAMPLER) - ma_uint64 count; + spx_uint64_t count; int speexErr = ma_speex_resampler_get_required_input_frame_count((SpeexResamplerState*)pResampler->state.speex.pSpeexResamplerState, outputFrameCount, &count); if (speexErr != RESAMPLER_ERR_SUCCESS) { return 0; } - return count; + return (ma_uint64)count; #else break; #endif @@ -37955,13 +37955,13 @@ MA_API ma_uint64 ma_resampler_get_expected_output_frame_count(ma_resampler* pRes case ma_resample_algorithm_speex: { #if defined(MA_HAS_SPEEX_RESAMPLER) - ma_uint64 count; + spx_uint64_t count; int speexErr = ma_speex_resampler_get_expected_output_frame_count((SpeexResamplerState*)pResampler->state.speex.pSpeexResamplerState, inputFrameCount, &count); if (speexErr != RESAMPLER_ERR_SUCCESS) { return 0; } - return count; + return (ma_uint64)count; #else break; #endif @@ -62882,6 +62882,7 @@ REVISION HISTORY v0.10.22 - TBD - Refactor to the PulseAudio backend. - Fix bugs in ma_decoder_init_file*() where the file handle is not closed after a decoding error. + - Fix some compilation warnings on GCC and Clang relating to the Speex resampler. - ALSA: Fix a bug in ma_context_get_device_info() where the PCM handle is left open in the event of an error. v0.10.21 - 2020-10-30 From eda3dcc866c80f31c28ae86d23d4c67b8623e13e Mon Sep 17 00:00:00 2001 From: David Reid Date: Sun, 1 Nov 2020 21:00:22 +1000 Subject: [PATCH 17/27] Fix some warnings in the Speex resampler. --- extras/speex_resampler/ma_speex_resampler.h | 25 ++++++++++----------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/extras/speex_resampler/ma_speex_resampler.h b/extras/speex_resampler/ma_speex_resampler.h index eaeb22d5..c5680176 100644 --- a/extras/speex_resampler/ma_speex_resampler.h +++ b/extras/speex_resampler/ma_speex_resampler.h @@ -6,21 +6,20 @@ #define RANDOM_PREFIX ma_speex #include "thirdparty/speex_resampler.h" -#ifdef _MSC_VER - #if defined(__clang__) +#if defined(_MSC_VER) + typedef unsigned __int64 spx_uint64_t; +#else + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wlanguage-extension-token" - #pragma GCC diagnostic ignored "-Wlong-long" - #pragma GCC diagnostic ignored "-Wc++11-long-long" + #pragma GCC diagnostic ignored "-Wlong-long" + #if defined(__clang__) + #pragma GCC diagnostic ignored "-Wc++11-long-long" + #endif #endif - typedef unsigned __int64 spx_uint64_t; - #if defined(__clang__) + typedef unsigned long long spx_uint64_t; + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic pop #endif -#else - #define MA_HAS_STDINT - #include - typedef uint64_t spx_uint64_t; #endif int ma_speex_resampler_get_required_input_frame_count(SpeexResamplerState* st, spx_uint64_t out_len, spx_uint64_t* in_len); @@ -50,14 +49,14 @@ int ma_speex_resampler_get_expected_output_frame_count(SpeexResamplerState* st, #pragma warning(disable:4244) /* conversion from 'x' to 'y', possible loss of data */ #pragma warning(disable:4018) /* signed/unsigned mismatch */ #pragma warning(disable:4706) /* assignment within conditional expression */ -#else +#elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wsign-compare" /* comparison between signed and unsigned integer expressions */ #endif #include "thirdparty/resample.c" #if defined(_MSC_VER) && !defined(__clang__) #pragma warning(pop) -#else +#elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic pop #endif From c10e21c119480b1bd85d85a29746c15170145b98 Mon Sep 17 00:00:00 2001 From: David Reid Date: Sun, 1 Nov 2020 21:04:16 +1000 Subject: [PATCH 18/27] Fix a const warning. --- miniaudio.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/miniaudio.h b/miniaudio.h index 27f3700e..fa0ad457 100644 --- a/miniaudio.h +++ b/miniaudio.h @@ -26268,7 +26268,7 @@ static ma_result ma_device_init_handle__sndio(ma_context* pContext, const ma_dev int openFlags = 0; struct ma_sio_cap caps; struct ma_sio_par par; - ma_device_id* pDeviceID; + const ma_device_id* pDeviceID; ma_format format; ma_uint32 channels; ma_uint32 sampleRate; From f6800b423a96d4658287a9a8c7d9696e6bef3e54 Mon Sep 17 00:00:00 2001 From: David Reid Date: Sun, 1 Nov 2020 21:11:23 +1000 Subject: [PATCH 19/27] Minor change to the deviceio test. --- tests/test_deviceio/ma_test_deviceio.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_deviceio/ma_test_deviceio.c b/tests/test_deviceio/ma_test_deviceio.c index 0ce7a631..6ccfcb84 100644 --- a/tests/test_deviceio/ma_test_deviceio.c +++ b/tests/test_deviceio/ma_test_deviceio.c @@ -240,10 +240,14 @@ ma_result print_device_info(ma_context* pContext, ma_device_type deviceType, con MA_ASSERT(pDeviceInfo != NULL); +#if 1 result = ma_context_get_device_info(pContext, deviceType, &pDeviceInfo->id, ma_share_mode_shared, &detailedDeviceInfo); if (result != MA_SUCCESS) { return result; } +#else + detailedDeviceInfo = *pDeviceInfo; +#endif printf("%s\n", pDeviceInfo->name); printf(" Default: %s\n", (detailedDeviceInfo._private.isDefault) ? "Yes" : "No"); From 89e5ae2144d449b433b0178a02ab1111b85e2d3f Mon Sep 17 00:00:00 2001 From: David Reid Date: Sun, 1 Nov 2020 21:43:34 +1000 Subject: [PATCH 20/27] Update dr_libs. --- extras/dr_flac.h | 36 ++++++++++++++++++++++++++++-------- extras/dr_mp3.h | 11 +++++++---- extras/dr_wav.h | 11 +++++++---- miniaudio.h | 28 ++++++++++++++++------------ 4 files changed, 58 insertions(+), 28 deletions(-) diff --git a/extras/dr_flac.h b/extras/dr_flac.h index b1fd0452..f4e3520a 100644 --- a/extras/dr_flac.h +++ b/extras/dr_flac.h @@ -1,6 +1,6 @@ /* FLAC audio decoder. Choice of public domain or MIT-0. See license statements at the end of this file. -dr_flac - v0.12.20 - 2020-09-08 +dr_flac - v0.12.21 - 2020-11-01 David Reid - mackron@gmail.com @@ -232,7 +232,7 @@ extern "C" { #define DRFLAC_VERSION_MAJOR 0 #define DRFLAC_VERSION_MINOR 12 -#define DRFLAC_VERSION_REVISION 20 +#define DRFLAC_VERSION_REVISION 21 #define DRFLAC_VERSION_STRING DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MAJOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MINOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_REVISION) #include /* For size_t. */ @@ -248,7 +248,7 @@ typedef unsigned int drflac_uint32; typedef signed __int64 drflac_int64; typedef unsigned __int64 drflac_uint64; #else - #if defined(__GNUC__) + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wlong-long" #if defined(__clang__) @@ -257,7 +257,7 @@ typedef unsigned int drflac_uint32; #endif typedef signed long long drflac_int64; typedef unsigned long long drflac_uint64; - #if defined(__GNUC__) + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic pop #endif #endif @@ -1319,7 +1319,7 @@ DRFLAC_API drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterat #define dr_flac_c /* Disable some annoying warnings. */ -#if defined(__GNUC__) +#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic push #if __GNUC__ >= 7 #pragma GCC diagnostic ignored "-Wimplicit-fallthrough" @@ -1367,7 +1367,15 @@ DRFLAC_API drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterat #define DRFLAC_ARM #endif -/* Intrinsics Support */ +/* +Intrinsics Support + +There's a bug in GCC 4.2.x which results in an incorrect compilation error when using _mm_slli_epi32() where it complains with + + "error: shift must be an immediate" + +Unfortuantely dr_flac depends on this for a few things so we're just going to disable SSE on GCC 4.2 and below. +*/ #if !defined(DR_FLAC_NO_SIMD) #if defined(DRFLAC_X64) || defined(DRFLAC_X86) #if defined(_MSC_VER) && !defined(__clang__) @@ -1378,7 +1386,7 @@ DRFLAC_API drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterat #if _MSC_VER >= 1600 && !defined(DRFLAC_NO_SSE41) /* 2010 */ #define DRFLAC_SUPPORT_SSE41 #endif - #else + #elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC__ >= 3))) /* Assume GNUC-style. */ #if defined(__SSE2__) && !defined(DRFLAC_NO_SSE2) #define DRFLAC_SUPPORT_SSE2 @@ -5741,6 +5749,9 @@ static drflac_bool32 drflac__seek_to_approximate_flac_frame_to_byte(drflac* pFla *pLastSuccessfulSeekOffset = pFlac->firstFLACFramePosInBytes; for (;;) { + /* After rangeLo == rangeHi == targetByte fails, we need to break out. */ + drflac_uint64 lastTargetByte = targetByte; + /* When seeking to a byte, failure probably means we've attempted to seek beyond the end of the stream. To counter this we just halve it each attempt. */ if (!drflac__seek_to_byte(&pFlac->bs, targetByte)) { /* If we couldn't even seek to the first byte in the stream we have a problem. Just abandon the whole thing. */ @@ -5781,6 +5792,11 @@ static drflac_bool32 drflac__seek_to_approximate_flac_frame_to_byte(drflac* pFla } #endif } + + /* We already tried this byte and there are no more to try, break out. */ + if(targetByte == lastTargetByte) { + return DRFLAC_FALSE; + } } /* The current PCM frame needs to be updated based on the frame we just seeked to. */ @@ -11763,7 +11779,7 @@ DRFLAC_API drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterat return DRFLAC_TRUE; } -#if defined(__GNUC__) +#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic pop #endif #endif /* dr_flac_c */ @@ -11773,6 +11789,10 @@ DRFLAC_API drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterat /* REVISION HISTORY ================ +v0.12.21 - 2020-11-01 + - Fix a possible deadlock when seeking. + - Improve compiler support for older versions of GCC. + v0.12.20 - 2020-09-08 - Fix a compilation error on older compilers. diff --git a/extras/dr_mp3.h b/extras/dr_mp3.h index d575ca29..7c40bcd2 100644 --- a/extras/dr_mp3.h +++ b/extras/dr_mp3.h @@ -1,6 +1,6 @@ /* MP3 audio decoder. Choice of public domain or MIT-0. See license statements at the end of this file. -dr_mp3 - v0.6.17 - 2020-09-28 +dr_mp3 - v0.6.18 - 2020-11-01 David Reid - mackron@gmail.com @@ -95,7 +95,7 @@ extern "C" { #define DRMP3_VERSION_MAJOR 0 #define DRMP3_VERSION_MINOR 6 -#define DRMP3_VERSION_REVISION 17 +#define DRMP3_VERSION_REVISION 18 #define DRMP3_VERSION_STRING DRMP3_XSTRINGIFY(DRMP3_VERSION_MAJOR) "." DRMP3_XSTRINGIFY(DRMP3_VERSION_MINOR) "." DRMP3_XSTRINGIFY(DRMP3_VERSION_REVISION) #include /* For size_t. */ @@ -111,7 +111,7 @@ typedef unsigned int drmp3_uint32; typedef signed __int64 drmp3_int64; typedef unsigned __int64 drmp3_uint64; #else - #if defined(__GNUC__) + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wlong-long" #if defined(__clang__) @@ -120,7 +120,7 @@ typedef unsigned int drmp3_uint32; #endif typedef signed long long drmp3_int64; typedef unsigned long long drmp3_uint64; - #if defined(__GNUC__) + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic pop #endif #endif @@ -4432,6 +4432,9 @@ counts rather than sample counts. /* REVISION HISTORY ================ +v0.6.18 - 2020-11-01 + - Improve compiler support for older versions of GCC. + v0.6.17 - 2020-09-28 - Bring up to date with minimp3. diff --git a/extras/dr_wav.h b/extras/dr_wav.h index f5fec70e..55b28358 100644 --- a/extras/dr_wav.h +++ b/extras/dr_wav.h @@ -1,6 +1,6 @@ /* WAV audio loader and writer. Choice of public domain or MIT-0. See license statements at the end of this file. -dr_wav - v0.12.12 - 2020-09-28 +dr_wav - v0.12.13 - 2020-11-01 David Reid - mackron@gmail.com @@ -144,7 +144,7 @@ extern "C" { #define DRWAV_VERSION_MAJOR 0 #define DRWAV_VERSION_MINOR 12 -#define DRWAV_VERSION_REVISION 12 +#define DRWAV_VERSION_REVISION 13 #define DRWAV_VERSION_STRING DRWAV_XSTRINGIFY(DRWAV_VERSION_MAJOR) "." DRWAV_XSTRINGIFY(DRWAV_VERSION_MINOR) "." DRWAV_XSTRINGIFY(DRWAV_VERSION_REVISION) #include /* For size_t. */ @@ -160,7 +160,7 @@ typedef unsigned int drwav_uint32; typedef signed __int64 drwav_int64; typedef unsigned __int64 drwav_uint64; #else - #if defined(__GNUC__) + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wlong-long" #if defined(__clang__) @@ -169,7 +169,7 @@ typedef unsigned int drwav_uint32; #endif typedef signed long long drwav_int64; typedef unsigned long long drwav_uint64; - #if defined(__GNUC__) + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic pop #endif #endif @@ -6033,6 +6033,9 @@ two different ways to initialize a drwav object. /* REVISION HISTORY ================ +v0.12.13 - 2020-11-01 + - Improve compiler support for older versions of GCC. + v0.12.12 - 2020-09-28 - Add support for RF64. - Fix a bug in writing mode where the size of the RIFF chunk incorrectly includes the header section. diff --git a/miniaudio.h b/miniaudio.h index fa0ad457..f1c33049 100644 --- a/miniaudio.h +++ b/miniaudio.h @@ -42874,7 +42874,7 @@ extern "C" { #define DRWAV_XSTRINGIFY(x) DRWAV_STRINGIFY(x) #define DRWAV_VERSION_MAJOR 0 #define DRWAV_VERSION_MINOR 12 -#define DRWAV_VERSION_REVISION 12 +#define DRWAV_VERSION_REVISION 13 #define DRWAV_VERSION_STRING DRWAV_XSTRINGIFY(DRWAV_VERSION_MAJOR) "." DRWAV_XSTRINGIFY(DRWAV_VERSION_MINOR) "." DRWAV_XSTRINGIFY(DRWAV_VERSION_REVISION) #include typedef signed char drwav_int8; @@ -42887,7 +42887,7 @@ typedef unsigned int drwav_uint32; typedef signed __int64 drwav_int64; typedef unsigned __int64 drwav_uint64; #else - #if defined(__GNUC__) + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wlong-long" #if defined(__clang__) @@ -42896,7 +42896,7 @@ typedef unsigned int drwav_uint32; #endif typedef signed long long drwav_int64; typedef unsigned long long drwav_uint64; - #if defined(__GNUC__) + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic pop #endif #endif @@ -43247,7 +43247,7 @@ extern "C" { #define DRFLAC_XSTRINGIFY(x) DRFLAC_STRINGIFY(x) #define DRFLAC_VERSION_MAJOR 0 #define DRFLAC_VERSION_MINOR 12 -#define DRFLAC_VERSION_REVISION 20 +#define DRFLAC_VERSION_REVISION 21 #define DRFLAC_VERSION_STRING DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MAJOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MINOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_REVISION) #include typedef signed char drflac_int8; @@ -43260,7 +43260,7 @@ typedef unsigned int drflac_uint32; typedef signed __int64 drflac_int64; typedef unsigned __int64 drflac_uint64; #else - #if defined(__GNUC__) + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wlong-long" #if defined(__clang__) @@ -43269,7 +43269,7 @@ typedef unsigned int drflac_uint32; #endif typedef signed long long drflac_int64; typedef unsigned long long drflac_uint64; - #if defined(__GNUC__) + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic pop #endif #endif @@ -43608,7 +43608,7 @@ extern "C" { #define DRMP3_XSTRINGIFY(x) DRMP3_STRINGIFY(x) #define DRMP3_VERSION_MAJOR 0 #define DRMP3_VERSION_MINOR 6 -#define DRMP3_VERSION_REVISION 17 +#define DRMP3_VERSION_REVISION 18 #define DRMP3_VERSION_STRING DRMP3_XSTRINGIFY(DRMP3_VERSION_MAJOR) "." DRMP3_XSTRINGIFY(DRMP3_VERSION_MINOR) "." DRMP3_XSTRINGIFY(DRMP3_VERSION_REVISION) #include typedef signed char drmp3_int8; @@ -43621,7 +43621,7 @@ typedef unsigned int drmp3_uint32; typedef signed __int64 drmp3_int64; typedef unsigned __int64 drmp3_uint64; #else - #if defined(__GNUC__) + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wlong-long" #if defined(__clang__) @@ -43630,7 +43630,7 @@ typedef unsigned int drmp3_uint32; #endif typedef signed long long drmp3_int64; typedef unsigned long long drmp3_uint64; - #if defined(__GNUC__) + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic pop #endif #endif @@ -51180,7 +51180,7 @@ DRWAV_API drwav_bool32 drwav_fourcc_equal(const drwav_uint8* a, const char* b) /* dr_flac_c begin */ #ifndef dr_flac_c #define dr_flac_c -#if defined(__GNUC__) +#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic push #if __GNUC__ >= 7 #pragma GCC diagnostic ignored "-Wimplicit-fallthrough" @@ -51224,7 +51224,7 @@ DRWAV_API drwav_bool32 drwav_fourcc_equal(const drwav_uint8* a, const char* b) #if _MSC_VER >= 1600 && !defined(DRFLAC_NO_SSE41) #define DRFLAC_SUPPORT_SSE41 #endif - #else + #elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC__ >= 3))) #if defined(__SSE2__) && !defined(DRFLAC_NO_SSE2) #define DRFLAC_SUPPORT_SSE2 #endif @@ -54555,6 +54555,7 @@ static drflac_bool32 drflac__seek_to_approximate_flac_frame_to_byte(drflac* pFla DRFLAC_ASSERT(targetByte <= rangeHi); *pLastSuccessfulSeekOffset = pFlac->firstFLACFramePosInBytes; for (;;) { + drflac_uint64 lastTargetByte = targetByte; if (!drflac__seek_to_byte(&pFlac->bs, targetByte)) { if (targetByte == 0) { drflac__seek_to_first_frame(pFlac); @@ -54580,6 +54581,9 @@ static drflac_bool32 drflac__seek_to_approximate_flac_frame_to_byte(drflac* pFla } #endif } + if(targetByte == lastTargetByte) { + return DRFLAC_FALSE; + } } drflac__get_pcm_frame_range_of_current_flac_frame(pFlac, &pFlac->currentPCMFrame, NULL); DRFLAC_ASSERT(targetByte <= rangeHi); @@ -59318,7 +59322,7 @@ DRFLAC_API drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterat } return DRFLAC_TRUE; } -#if defined(__GNUC__) +#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic pop #endif #endif From 27a7fea804d60ffbdbd19c6ab5812ff7d15742b7 Mon Sep 17 00:00:00 2001 From: David Reid Date: Sun, 1 Nov 2020 21:49:01 +1000 Subject: [PATCH 21/27] Update dr_flac. --- extras/dr_flac.h | 9 ++++++--- miniaudio.h | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/extras/dr_flac.h b/extras/dr_flac.h index f4e3520a..906af001 100644 --- a/extras/dr_flac.h +++ b/extras/dr_flac.h @@ -1,6 +1,6 @@ /* FLAC audio decoder. Choice of public domain or MIT-0. See license statements at the end of this file. -dr_flac - v0.12.21 - 2020-11-01 +dr_flac - v0.12.22 - 2020-11-01 David Reid - mackron@gmail.com @@ -232,7 +232,7 @@ extern "C" { #define DRFLAC_VERSION_MAJOR 0 #define DRFLAC_VERSION_MINOR 12 -#define DRFLAC_VERSION_REVISION 21 +#define DRFLAC_VERSION_REVISION 22 #define DRFLAC_VERSION_STRING DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MAJOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MINOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_REVISION) #include /* For size_t. */ @@ -1386,7 +1386,7 @@ Unfortuantely dr_flac depends on this for a few things so we're just going to di #if _MSC_VER >= 1600 && !defined(DRFLAC_NO_SSE41) /* 2010 */ #define DRFLAC_SUPPORT_SSE41 #endif - #elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC__ >= 3))) + #elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3))) /* Assume GNUC-style. */ #if defined(__SSE2__) && !defined(DRFLAC_NO_SSE2) #define DRFLAC_SUPPORT_SSE2 @@ -11789,6 +11789,9 @@ DRFLAC_API drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterat /* REVISION HISTORY ================ +v0.12.22 - 2020-11-01 + - Fix an error with the previous release. + v0.12.21 - 2020-11-01 - Fix a possible deadlock when seeking. - Improve compiler support for older versions of GCC. diff --git a/miniaudio.h b/miniaudio.h index f1c33049..4da1d9cd 100644 --- a/miniaudio.h +++ b/miniaudio.h @@ -43247,7 +43247,7 @@ extern "C" { #define DRFLAC_XSTRINGIFY(x) DRFLAC_STRINGIFY(x) #define DRFLAC_VERSION_MAJOR 0 #define DRFLAC_VERSION_MINOR 12 -#define DRFLAC_VERSION_REVISION 21 +#define DRFLAC_VERSION_REVISION 22 #define DRFLAC_VERSION_STRING DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MAJOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MINOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_REVISION) #include typedef signed char drflac_int8; @@ -51224,7 +51224,7 @@ DRWAV_API drwav_bool32 drwav_fourcc_equal(const drwav_uint8* a, const char* b) #if _MSC_VER >= 1600 && !defined(DRFLAC_NO_SSE41) #define DRFLAC_SUPPORT_SSE41 #endif - #elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC__ >= 3))) + #elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3))) #if defined(__SSE2__) && !defined(DRFLAC_NO_SSE2) #define DRFLAC_SUPPORT_SSE2 #endif From 2dc604ecde0f02280690c72f943bfb8bf52dd820 Mon Sep 17 00:00:00 2001 From: David Reid Date: Tue, 3 Nov 2020 18:51:31 +1000 Subject: [PATCH 22/27] PulseAudio: Always use the PA_STREAM_ADJUST_LATENCY flag on streams. This should fix the following puplic issues: * https://github.com/mackron/miniaudio/issues/106 * https://github.com/mackron/miniaudio/issues/187 --- miniaudio.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/miniaudio.h b/miniaudio.h index 4da1d9cd..e9059566 100644 --- a/miniaudio.h +++ b/miniaudio.h @@ -21446,7 +21446,7 @@ static ma_result ma_device_init__pulse(ma_context* pContext, const ma_device_con /* Connect after we've got all of our internal state set up. */ - streamFlags = MA_PA_STREAM_START_CORKED | MA_PA_STREAM_FIX_FORMAT | MA_PA_STREAM_FIX_RATE | MA_PA_STREAM_FIX_CHANNELS; + streamFlags = MA_PA_STREAM_START_CORKED | MA_PA_STREAM_ADJUST_LATENCY | MA_PA_STREAM_FIX_FORMAT | MA_PA_STREAM_FIX_RATE | MA_PA_STREAM_FIX_CHANNELS; if (devCapture != NULL) { streamFlags |= MA_PA_STREAM_DONT_MOVE; } @@ -21540,7 +21540,7 @@ static ma_result ma_device_init__pulse(ma_context* pContext, const ma_device_con /* Connect after we've got all of our internal state set up. */ - streamFlags = MA_PA_STREAM_START_CORKED | MA_PA_STREAM_FIX_FORMAT | MA_PA_STREAM_FIX_RATE | MA_PA_STREAM_FIX_CHANNELS; + streamFlags = MA_PA_STREAM_START_CORKED | MA_PA_STREAM_ADJUST_LATENCY | MA_PA_STREAM_FIX_FORMAT | MA_PA_STREAM_FIX_RATE | MA_PA_STREAM_FIX_CHANNELS; if (devPlayback != NULL) { streamFlags |= MA_PA_STREAM_DONT_MOVE; } From d8536197c486fd2c2347520883d5a20bfa42a5fa Mon Sep 17 00:00:00 2001 From: David Reid Date: Tue, 3 Nov 2020 19:01:56 +1000 Subject: [PATCH 23/27] PulseAudio: Minor code restructure. --- miniaudio.h | 95 ++++++++++++++++++++++++++--------------------------- 1 file changed, 47 insertions(+), 48 deletions(-) diff --git a/miniaudio.h b/miniaudio.h index e9059566..27494a8b 100644 --- a/miniaudio.h +++ b/miniaudio.h @@ -21398,6 +21398,28 @@ static ma_result ma_device_init__pulse(ma_context* pContext, const ma_device_con } + /* The callback needs to be set before connecting the stream. */ + ((ma_pa_stream_set_read_callback_proc)pContext->pulse.pa_stream_set_read_callback)((ma_pa_stream*)pDevice->pulse.pStreamCapture, ma_device_on_read__pulse, pDevice); + + + /* Connect after we've got all of our internal state set up. */ + streamFlags = MA_PA_STREAM_START_CORKED | MA_PA_STREAM_ADJUST_LATENCY | MA_PA_STREAM_FIX_FORMAT | MA_PA_STREAM_FIX_RATE | MA_PA_STREAM_FIX_CHANNELS; + if (devCapture != NULL) { + streamFlags |= MA_PA_STREAM_DONT_MOVE; + } + + error = ((ma_pa_stream_connect_record_proc)pContext->pulse.pa_stream_connect_record)((ma_pa_stream*)pDevice->pulse.pStreamCapture, devCapture, &attr, streamFlags); + if (error != MA_PA_OK) { + result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio capture stream.", ma_result_from_pulse(error)); + goto on_error1; + } + + result = ma_context_wait_for_pa_stream_to_connect__pulse(pDevice->pContext, (ma_pa_stream*)pDevice->pulse.pStreamCapture); + if (result != MA_SUCCESS) { + goto on_error2; + } + + /* Internal format. */ pActualSS = ((ma_pa_stream_get_sample_spec_proc)pContext->pulse.pa_stream_get_sample_spec)((ma_pa_stream*)pDevice->pulse.pStreamCapture); if (pActualSS != NULL) { @@ -21439,28 +21461,6 @@ static ma_result ma_device_init__pulse(ma_context* pContext, const ma_device_con if (devCapture != NULL) { ma_wait_for_operation_and_unref__pulse(pContext, ((ma_pa_context_get_source_info_by_name_proc)pContext->pulse.pa_context_get_source_info_by_name)((ma_pa_context*)pContext->pulse.pPulseContext, devCapture, ma_device_source_name_callback, pDevice)); } - - - /* Good practice to set the callback after retrieving the internal format since the callback itself references those settings. */ - ((ma_pa_stream_set_read_callback_proc)pContext->pulse.pa_stream_set_read_callback)((ma_pa_stream*)pDevice->pulse.pStreamCapture, ma_device_on_read__pulse, pDevice); - - - /* Connect after we've got all of our internal state set up. */ - streamFlags = MA_PA_STREAM_START_CORKED | MA_PA_STREAM_ADJUST_LATENCY | MA_PA_STREAM_FIX_FORMAT | MA_PA_STREAM_FIX_RATE | MA_PA_STREAM_FIX_CHANNELS; - if (devCapture != NULL) { - streamFlags |= MA_PA_STREAM_DONT_MOVE; - } - - error = ((ma_pa_stream_connect_record_proc)pContext->pulse.pa_stream_connect_record)((ma_pa_stream*)pDevice->pulse.pStreamCapture, devCapture, &attr, streamFlags); - if (error != MA_PA_OK) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio capture stream.", ma_result_from_pulse(error)); - goto on_error1; - } - - result = ma_context_wait_for_pa_stream_to_connect__pulse(pDevice->pContext, (ma_pa_stream*)pDevice->pulse.pStreamCapture); - if (result != MA_SUCCESS) { - goto on_error2; - } } if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { @@ -21488,6 +21488,31 @@ static ma_result ma_device_init__pulse(ma_context* pContext, const ma_device_con } + /* + Note that this callback will be fired as soon as the stream is connected, even though it's started as corked. The callback needs to handle a + device state of MA_STATE_UNINITIALIZED. + */ + ((ma_pa_stream_set_write_callback_proc)pContext->pulse.pa_stream_set_write_callback)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, ma_device_on_write__pulse, pDevice); + + + /* Connect after we've got all of our internal state set up. */ + streamFlags = MA_PA_STREAM_START_CORKED | MA_PA_STREAM_ADJUST_LATENCY | MA_PA_STREAM_FIX_FORMAT | MA_PA_STREAM_FIX_RATE | MA_PA_STREAM_FIX_CHANNELS; + if (devPlayback != NULL) { + streamFlags |= MA_PA_STREAM_DONT_MOVE; + } + + error = ((ma_pa_stream_connect_playback_proc)pContext->pulse.pa_stream_connect_playback)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, devPlayback, &attr, streamFlags, NULL, NULL); + if (error != MA_PA_OK) { + result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio playback stream.", ma_result_from_pulse(error)); + goto on_error3; + } + + result = ma_context_wait_for_pa_stream_to_connect__pulse(pDevice->pContext, (ma_pa_stream*)pDevice->pulse.pStreamPlayback); + if (result != MA_SUCCESS) { + goto on_error3; + } + + /* Internal format. */ pActualSS = ((ma_pa_stream_get_sample_spec_proc)pContext->pulse.pa_stream_get_sample_spec)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); if (pActualSS != NULL) { @@ -21529,32 +21554,6 @@ static ma_result ma_device_init__pulse(ma_context* pContext, const ma_device_con if (devPlayback != NULL) { ma_wait_for_operation_and_unref__pulse(pContext, ((ma_pa_context_get_sink_info_by_name_proc)pContext->pulse.pa_context_get_sink_info_by_name)((ma_pa_context*)pContext->pulse.pPulseContext, devPlayback, ma_device_sink_name_callback, pDevice)); } - - - /* - Good practice to set the callback after retrieving the internal format since the callback itself references those settings. Note that - this callback will be fired as soon as the stream is connected, even though it's started as corked. The callback needs to handle a - device state of MA_STATE_UNINITIALIZED. - */ - ((ma_pa_stream_set_write_callback_proc)pContext->pulse.pa_stream_set_write_callback)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, ma_device_on_write__pulse, pDevice); - - - /* Connect after we've got all of our internal state set up. */ - streamFlags = MA_PA_STREAM_START_CORKED | MA_PA_STREAM_ADJUST_LATENCY | MA_PA_STREAM_FIX_FORMAT | MA_PA_STREAM_FIX_RATE | MA_PA_STREAM_FIX_CHANNELS; - if (devPlayback != NULL) { - streamFlags |= MA_PA_STREAM_DONT_MOVE; - } - - error = ((ma_pa_stream_connect_playback_proc)pContext->pulse.pa_stream_connect_playback)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, devPlayback, &attr, streamFlags, NULL, NULL); - if (error != MA_PA_OK) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio playback stream.", ma_result_from_pulse(error)); - goto on_error3; - } - - result = ma_context_wait_for_pa_stream_to_connect__pulse(pDevice->pContext, (ma_pa_stream*)pDevice->pulse.pStreamPlayback); - if (result != MA_SUCCESS) { - goto on_error3; - } } From 99c96c958e7c6e7c92f53601a8f18982a1a0ac92 Mon Sep 17 00:00:00 2001 From: David Reid Date: Tue, 3 Nov 2020 19:05:30 +1000 Subject: [PATCH 24/27] PulseAudio: Remove some unnecessary code. --- miniaudio.h | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/miniaudio.h b/miniaudio.h index 27494a8b..385dfc98 100644 --- a/miniaudio.h +++ b/miniaudio.h @@ -21423,12 +21423,6 @@ static ma_result ma_device_init__pulse(ma_context* pContext, const ma_device_con /* Internal format. */ pActualSS = ((ma_pa_stream_get_sample_spec_proc)pContext->pulse.pa_stream_get_sample_spec)((ma_pa_stream*)pDevice->pulse.pStreamCapture); if (pActualSS != NULL) { - /* If anything has changed between the requested and the actual sample spec, we need to update the buffer. */ - if (ss.format != pActualSS->format || ss.channels != pActualSS->channels || ss.rate != pActualSS->rate) { - attr = ma_device__pa_buffer_attr_new(pDevice->capture.internalPeriodSizeInFrames, pConfig->periods, pActualSS); - ma_wait_for_operation_and_unref__pulse(pContext, ((ma_pa_stream_set_buffer_attr_proc)pContext->pulse.pa_stream_set_buffer_attr)((ma_pa_stream*)pDevice->pulse.pStreamCapture, &attr, NULL, NULL)); - } - ss = *pActualSS; } @@ -21516,12 +21510,6 @@ static ma_result ma_device_init__pulse(ma_context* pContext, const ma_device_con /* Internal format. */ pActualSS = ((ma_pa_stream_get_sample_spec_proc)pContext->pulse.pa_stream_get_sample_spec)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); if (pActualSS != NULL) { - /* If anything has changed between the requested and the actual sample spec, we need to update the buffer. */ - if (ss.format != pActualSS->format || ss.channels != pActualSS->channels || ss.rate != pActualSS->rate) { - attr = ma_device__pa_buffer_attr_new(pDevice->playback.internalPeriodSizeInFrames, pConfig->periods, pActualSS); - ma_wait_for_operation_and_unref__pulse(pContext, ((ma_pa_stream_set_buffer_attr_proc)pContext->pulse.pa_stream_set_buffer_attr)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, &attr, NULL, NULL)); - } - ss = *pActualSS; } From 9d180b540ae7a4233036f18319a69098409b44d4 Mon Sep 17 00:00:00 2001 From: David Reid Date: Tue, 3 Nov 2020 19:07:48 +1000 Subject: [PATCH 25/27] Fix a build error when ALSA and JACK are disabled on Linux builds. --- miniaudio.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/miniaudio.h b/miniaudio.h index 385dfc98..2b1dbec1 100644 --- a/miniaudio.h +++ b/miniaudio.h @@ -10382,10 +10382,11 @@ static void ma_device__send_frames_to_client(ma_device* pDevice, ma_uint32 frame /* We only want to expose ma_device__handle_duplex_callback_capture() and ma_device__handle_duplex_callback_playback() if we have an asynchronous backend enabled. */ -#if defined(MA_HAS_JACK) || \ - defined(MA_HAS_COREAUDIO) || \ - defined(MA_HAS_AAUDIO) || \ - defined(MA_HAS_OPENSL) || \ +#if defined(MA_HAS_PULSEAUDIO) || \ + defined(MA_HAS_JACK) || \ + defined(MA_HAS_COREAUDIO) || \ + defined(MA_HAS_AAUDIO) || \ + defined(MA_HAS_OPENSL) || \ defined(MA_HAS_WEBAUDIO) static ma_result ma_device__handle_duplex_callback_capture(ma_device* pDevice, ma_uint32 frameCountInDeviceFormat, const void* pFramesInDeviceFormat, ma_pcm_rb* pRB) { From 0d0f7e4f9284ac4dbd1546a2e8fba62e24d88722 Mon Sep 17 00:00:00 2001 From: David Reid Date: Tue, 3 Nov 2020 19:12:04 +1000 Subject: [PATCH 26/27] Update revision history. --- miniaudio.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/miniaudio.h b/miniaudio.h index 2b1dbec1..83c83b6d 100644 --- a/miniaudio.h +++ b/miniaudio.h @@ -62872,9 +62872,10 @@ The following miscellaneous changes have also been made. REVISION HISTORY ================ v0.10.22 - TBD - - Refactor to the PulseAudio backend. + - Refactor to the PulseAudio backend. This simplifies the implementation and fixes a capture bug. - Fix bugs in ma_decoder_init_file*() where the file handle is not closed after a decoding error. - Fix some compilation warnings on GCC and Clang relating to the Speex resampler. + - Fix a compilation error for the Linux build when the ALSA and JACK backends are both disabled. - ALSA: Fix a bug in ma_context_get_device_info() where the PCM handle is left open in the event of an error. v0.10.21 - 2020-10-30 From 704750269b4ece043798afd34e225934e3138f6a Mon Sep 17 00:00:00 2001 From: David Reid Date: Tue, 3 Nov 2020 20:26:00 +1000 Subject: [PATCH 27/27] Fix some compilation errors with MA_NO_RUNTIME_LINKING. --- miniaudio.h | 198 +++++++++++++++++++++++++++------------------------- 1 file changed, 102 insertions(+), 96 deletions(-) diff --git a/miniaudio.h b/miniaudio.h index 83c83b6d..e24c52e6 100644 --- a/miniaudio.h +++ b/miniaudio.h @@ -277,87 +277,91 @@ The Emscripten build emits Web Audio JavaScript directly and should compile clea ------------------ `#define` these options before including miniaudio.h. - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | Option | Description | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_WASAPI | Disables the WASAPI backend. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_DSOUND | Disables the DirectSound backend. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_WINMM | Disables the WinMM backend. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_ALSA | Disables the ALSA backend. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_PULSEAUDIO | Disables the PulseAudio backend. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_JACK | Disables the JACK backend. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_COREAUDIO | Disables the Core Audio backend. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_SNDIO | Disables the sndio backend. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_AUDIO4 | Disables the audio(4) backend. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_OSS | Disables the OSS backend. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_AAUDIO | Disables the AAudio backend. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_OPENSL | Disables the OpenSL|ES backend. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_WEBAUDIO | Disables the Web Audio backend. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_NULL | Disables the null backend. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_DECODING | Disables decoding APIs. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_ENCODING | Disables encoding APIs. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_WAV | Disables the built-in WAV decoder and encoder. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_FLAC | Disables the built-in FLAC decoder. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_MP3 | Disables the built-in MP3 decoder. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_DEVICE_IO | Disables playback and recording. This will disable ma_context and ma_device APIs. This is useful if you only want to use | - | | miniaudio's data conversion and/or decoding APIs. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_THREADING | Disables the ma_thread, ma_mutex, ma_semaphore and ma_event APIs. This option is useful if you only need to use miniaudio for | - | | data conversion, decoding and/or encoding. Some families of APIs require threading which means the following options must also | - | | be set: | - | | | - | | ``` | - | | MA_NO_DEVICE_IO | - | | ``` | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_GENERATION | Disables generation APIs such a ma_waveform and ma_noise. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_SSE2 | Disables SSE2 optimizations. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_AVX2 | Disables AVX2 optimizations. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_AVX512 | Disables AVX-512 optimizations. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_NEON | Disables NEON optimizations. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_LOG_LEVEL [level] | Sets the logging level. Set level to one of the following: | - | | | - | | ``` | - | | MA_LOG_LEVEL_VERBOSE | - | | MA_LOG_LEVEL_INFO | - | | MA_LOG_LEVEL_WARNING | - | | MA_LOG_LEVEL_ERROR | - | | ``` | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_DEBUG_OUTPUT | Enable printf() debug output. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_COINIT_VALUE | Windows only. The value to pass to internal calls to `CoInitializeEx()`. Defaults to `COINIT_MULTITHREADED`. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_API | Controls how public APIs should be decorated. Defaults to `extern`. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_DLL | If set, configures MA_API to either import or export APIs depending on whether or not the implementation is being defined. If | - | | defining the implementation, MA_API will be configured to export. Otherwise it will be configured to import. This has no effect | - | | if MA_API is defined externally. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ + +-----------------------+---------------------------------------------------------------------------------------------------------------------------------+ + | Option | Description | + +-----------------------+---------------------------------------------------------------------------------------------------------------------------------+ + | MA_NO_WASAPI | Disables the WASAPI backend. | + +-----------------------+---------------------------------------------------------------------------------------------------------------------------------+ + | MA_NO_DSOUND | Disables the DirectSound backend. | + +-----------------------+---------------------------------------------------------------------------------------------------------------------------------+ + | MA_NO_WINMM | Disables the WinMM backend. | + +-----------------------+---------------------------------------------------------------------------------------------------------------------------------+ + | MA_NO_ALSA | Disables the ALSA backend. | + +-----------------------+---------------------------------------------------------------------------------------------------------------------------------+ + | MA_NO_PULSEAUDIO | Disables the PulseAudio backend. | + +-----------------------+---------------------------------------------------------------------------------------------------------------------------------+ + | MA_NO_JACK | Disables the JACK backend. | + +-----------------------+---------------------------------------------------------------------------------------------------------------------------------+ + | MA_NO_COREAUDIO | Disables the Core Audio backend. | + +-----------------------+---------------------------------------------------------------------------------------------------------------------------------+ + | MA_NO_SNDIO | Disables the sndio backend. | + +-----------------------+---------------------------------------------------------------------------------------------------------------------------------+ + | MA_NO_AUDIO4 | Disables the audio(4) backend. | + +-----------------------+---------------------------------------------------------------------------------------------------------------------------------+ + | MA_NO_OSS | Disables the OSS backend. | + +-----------------------+---------------------------------------------------------------------------------------------------------------------------------+ + | MA_NO_AAUDIO | Disables the AAudio backend. | + +-----------------------+---------------------------------------------------------------------------------------------------------------------------------+ + | MA_NO_OPENSL | Disables the OpenSL|ES backend. | + +-----------------------+---------------------------------------------------------------------------------------------------------------------------------+ + | MA_NO_WEBAUDIO | Disables the Web Audio backend. | + +-----------------------+---------------------------------------------------------------------------------------------------------------------------------+ + | MA_NO_NULL | Disables the null backend. | + +-----------------------+---------------------------------------------------------------------------------------------------------------------------------+ + | MA_NO_DECODING | Disables decoding APIs. | + +-----------------------+---------------------------------------------------------------------------------------------------------------------------------+ + | MA_NO_ENCODING | Disables encoding APIs. | + +-----------------------+---------------------------------------------------------------------------------------------------------------------------------+ + | MA_NO_WAV | Disables the built-in WAV decoder and encoder. | + +-----------------------+---------------------------------------------------------------------------------------------------------------------------------+ + | MA_NO_FLAC | Disables the built-in FLAC decoder. | + +-----------------------+---------------------------------------------------------------------------------------------------------------------------------+ + | MA_NO_MP3 | Disables the built-in MP3 decoder. | + +-----------------------+---------------------------------------------------------------------------------------------------------------------------------+ + | MA_NO_DEVICE_IO | Disables playback and recording. This will disable ma_context and ma_device APIs. This is useful if you only want to use | + | | miniaudio's data conversion and/or decoding APIs. | + +-----------------------+---------------------------------------------------------------------------------------------------------------------------------+ + | MA_NO_THREADING | Disables the ma_thread, ma_mutex, ma_semaphore and ma_event APIs. This option is useful if you only need to use miniaudio for | + | | data conversion, decoding and/or encoding. Some families of APIs require threading which means the following options must also | + | | be set: | + | | | + | | ``` | + | | MA_NO_DEVICE_IO | + | | ``` | + +-----------------------+---------------------------------------------------------------------------------------------------------------------------------+ + | MA_NO_GENERATION | Disables generation APIs such a ma_waveform and ma_noise. | + +-----------------------+---------------------------------------------------------------------------------------------------------------------------------+ + | MA_NO_SSE2 | Disables SSE2 optimizations. | + +-----------------------+---------------------------------------------------------------------------------------------------------------------------------+ + | MA_NO_AVX2 | Disables AVX2 optimizations. | + +-----------------------+---------------------------------------------------------------------------------------------------------------------------------+ + | MA_NO_AVX512 | Disables AVX-512 optimizations. | + +-----------------------+---------------------------------------------------------------------------------------------------------------------------------+ + | MA_NO_NEON | Disables NEON optimizations. | + +-----------------------+---------------------------------------------------------------------------------------------------------------------------------+ + | MA_NO_RUNTIME_LINKING | Disables runtime linking. This is useful for passing Apple's notarization process. When enabling this, you may need to avoid | + | | using `-std=c89` or `-std=c99` on Linux builds or else you may end up with compilation errors due to conflicts with `timespec` | + | | and `timeval` data types. | + +-----------------------+---------------------------------------------------------------------------------------------------------------------------------+ + | MA_LOG_LEVEL [level] | Sets the logging level. Set level to one of the following: | + | | | + | | ``` | + | | MA_LOG_LEVEL_VERBOSE | + | | MA_LOG_LEVEL_INFO | + | | MA_LOG_LEVEL_WARNING | + | | MA_LOG_LEVEL_ERROR | + | | ``` | + +-----------------------+---------------------------------------------------------------------------------------------------------------------------------+ + | MA_DEBUG_OUTPUT | Enable printf() debug output. | + +-----------------------+---------------------------------------------------------------------------------------------------------------------------------+ + | MA_COINIT_VALUE | Windows only. The value to pass to internal calls to `CoInitializeEx()`. Defaults to `COINIT_MULTITHREADED`. | + +-----------------------+---------------------------------------------------------------------------------------------------------------------------------+ + | MA_API | Controls how public APIs should be decorated. Defaults to `extern`. | + +-----------------------+---------------------------------------------------------------------------------------------------------------------------------+ + | MA_DLL | If set, configures MA_API to either import or export APIs depending on whether or not the implementation is being defined. If | + | | defining the implementation, MA_API will be configured to export. Otherwise it will be configured to import. This has no effect | + | | if MA_API is defined externally. | + +-----------------------+---------------------------------------------------------------------------------------------------------------------------------+ 3. Definitions @@ -20091,18 +20095,19 @@ typedef pa_sample_format_t ma_pa_sample_format_t; #define MA_PA_SAMPLE_S24_32LE PA_SAMPLE_S24_32LE #define MA_PA_SAMPLE_S24_32BE PA_SAMPLE_S24_32BE -typedef pa_mainloop ma_pa_mainloop; -typedef pa_mainloop_api ma_pa_mainloop_api; -typedef pa_context ma_pa_context; -typedef pa_operation ma_pa_operation; -typedef pa_stream ma_pa_stream; -typedef pa_spawn_api ma_pa_spawn_api; -typedef pa_buffer_attr ma_pa_buffer_attr; -typedef pa_channel_map ma_pa_channel_map; -typedef pa_cvolume ma_pa_cvolume; -typedef pa_sample_spec ma_pa_sample_spec; -typedef pa_sink_info ma_pa_sink_info; -typedef pa_source_info ma_pa_source_info; +typedef pa_mainloop ma_pa_mainloop; +typedef pa_threaded_mainloop ma_pa_threaded_mainloop; +typedef pa_mainloop_api ma_pa_mainloop_api; +typedef pa_context ma_pa_context; +typedef pa_operation ma_pa_operation; +typedef pa_stream ma_pa_stream; +typedef pa_spawn_api ma_pa_spawn_api; +typedef pa_buffer_attr ma_pa_buffer_attr; +typedef pa_channel_map ma_pa_channel_map; +typedef pa_cvolume ma_pa_cvolume; +typedef pa_sample_spec ma_pa_sample_spec; +typedef pa_sink_info ma_pa_sink_info; +typedef pa_source_info ma_pa_source_info; typedef pa_context_notify_cb_t ma_pa_context_notify_cb_t; typedef pa_sink_info_cb_t ma_pa_sink_info_cb_t; @@ -20410,7 +20415,7 @@ typedef void (* ma_pa_threaded_mainloop_accept_proc) ( typedef int (* ma_pa_threaded_mainloop_get_retval_proc) (ma_pa_threaded_mainloop* m); typedef ma_pa_mainloop_api* (* ma_pa_threaded_mainloop_get_api_proc) (ma_pa_threaded_mainloop* m); typedef int (* ma_pa_threaded_mainloop_in_thread_proc) (ma_pa_threaded_mainloop* m); -typedef void (* ma_pa_threaded_mainloop_set_name_proc) (ma_pa_threaded_mainloop* m); +typedef void (* ma_pa_threaded_mainloop_set_name_proc) (ma_pa_threaded_mainloop* m, const char* name); typedef ma_pa_context* (* ma_pa_context_new_proc) (ma_pa_mainloop_api* mainloop, const char* name); typedef void (* ma_pa_context_unref_proc) (ma_pa_context* c); typedef int (* ma_pa_context_connect_proc) (ma_pa_context* c, const char* server, ma_pa_context_flags_t flags, const ma_pa_spawn_api* api); @@ -62873,10 +62878,11 @@ REVISION HISTORY ================ v0.10.22 - TBD - Refactor to the PulseAudio backend. This simplifies the implementation and fixes a capture bug. - - Fix bugs in ma_decoder_init_file*() where the file handle is not closed after a decoding error. + - Fix bugs in `ma_decoder_init_file*()` where the file handle is not closed after a decoding error. - Fix some compilation warnings on GCC and Clang relating to the Speex resampler. - Fix a compilation error for the Linux build when the ALSA and JACK backends are both disabled. - - ALSA: Fix a bug in ma_context_get_device_info() where the PCM handle is left open in the event of an error. + - ALSA: Fix a bug in `ma_context_get_device_info()` where the PCM handle is left open in the event of an error. + - Add documentation for `MA_NO_RUNTIME_LINKING`. v0.10.21 - 2020-10-30 - Add ma_is_backend_enabled() and ma_get_enabled_backends() for retrieving enabled backends at run-time.