From b8dacd95c4aeff9b5e3cab3e32255a8c2dd255b3 Mon Sep 17 00:00:00 2001
From: Ozkan Sezer <[EMAIL REDACTED]>
Date: Mon, 10 Mar 2025 01:23:56 +0300
Subject: [PATCH] update to latest drmp3
---
src/codecs/dr_libs/dr_mp3.h | 820 ++++++++++++++++++++++++++++--------
1 file changed, 648 insertions(+), 172 deletions(-)
diff --git a/src/codecs/dr_libs/dr_mp3.h b/src/codecs/dr_libs/dr_mp3.h
index aee3cef9..1ea12060 100644
--- a/src/codecs/dr_libs/dr_mp3.h
+++ b/src/codecs/dr_libs/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.40 - 2024-12-17
+dr_mp3 - v0.7.0 - TBD
David Reid - mackron@gmail.com
@@ -9,29 +9,6 @@ GitHub: https://github.com/mackron/dr_libs
Based on minimp3 (https://github.com/lieff/minimp3) which is where the real work was done. See the bottom of this file for differences between minimp3 and dr_mp3.
*/
-/*
-RELEASE NOTES - VERSION 0.6
-===========================
-Version 0.6 includes breaking changes with the configuration of decoders. The ability to customize the number of output channels and the sample rate has been
-removed. You must now use the channel count and sample rate reported by the MP3 stream itself, and all channel and sample rate conversion must be done
-yourself.
-
-
-Changes to Initialization
--------------------------
-Previously, `drmp3_init()`, etc. took a pointer to a `drmp3_config` object that allowed you to customize the output channels and sample rate. This has been
-removed. If you need the old behaviour you will need to convert the data yourself or just not upgrade. The following APIs have changed.
-
- `drmp3_init()`
- `drmp3_init_memory()`
- `drmp3_init_file()`
-
-
-Miscellaneous Changes
----------------------
-Support for loading a file from a `wchar_t` string has been added via the `drmp3_init_file_w()` API.
-*/
-
/*
Introduction
=============
@@ -94,8 +71,8 @@ extern "C" {
#define DRMP3_XSTRINGIFY(x) DRMP3_STRINGIFY(x)
#define DRMP3_VERSION_MAJOR 0
-#define DRMP3_VERSION_MINOR 6
-#define DRMP3_VERSION_REVISION 40
+#define DRMP3_VERSION_MINOR 7
+#define DRMP3_VERSION_REVISION 0
#define DRMP3_VERSION_STRING DRMP3_XSTRINGIFY(DRMP3_VERSION_MAJOR) "." DRMP3_XSTRINGIFY(DRMP3_VERSION_MINOR) "." DRMP3_XSTRINGIFY(DRMP3_VERSION_REVISION)
#include <stddef.h> /* For size_t. */
@@ -133,6 +110,9 @@ typedef drmp3_uint8 drmp3_bool8;
typedef drmp3_uint32 drmp3_bool32;
#define DRMP3_TRUE 1
#define DRMP3_FALSE 0
+
+/* Weird shifting syntax is for VC6 compatibility. */
+#define DRMP3_UINT64_MAX (((drmp3_uint64)0xFFFFFFFF << 32) | (drmp3_uint64)0xFFFFFFFF)
/* End Sized Types */
/* Decorations */
@@ -154,7 +134,7 @@ typedef drmp3_uint32 drmp3_bool32;
#endif
#endif
- #if defined(DR_MP3_IMPLEMENTATION) || defined(DRMP3_IMPLEMENTATION)
+ #if defined(DR_MP3_IMPLEMENTATION)
#define DRMP3_API DRMP3_DLL_EXPORT
#else
#define DRMP3_API DRMP3_DLL_IMPORT
@@ -279,7 +259,7 @@ Low Level Push API
*/
typedef struct
{
- int frame_bytes, channels, hz, layer, bitrate_kbps;
+ int frame_bytes, channels, sample_rate, layer, bitrate_kbps;
} drmp3dec_frame_info;
typedef struct
@@ -307,7 +287,8 @@ Main API (Pull API)
typedef enum
{
drmp3_seek_origin_start,
- drmp3_seek_origin_current
+ drmp3_seek_origin_current,
+ drmp3_seek_origin_end
} drmp3_seek_origin;
typedef struct
@@ -318,10 +299,27 @@ typedef struct
drmp3_uint16 pcmFramesToDiscard; /* The number of leading samples to read and discard. These are discarded after mp3FramesToDiscard. */
} drmp3_seek_point;
+typedef enum
+{
+ DRMP3_METADATA_TYPE_ID3V1,
+ DRMP3_METADATA_TYPE_ID3V2,
+ DRMP3_METADATA_TYPE_APE,
+ DRMP3_METADATA_TYPE_XING,
+ DRMP3_METADATA_TYPE_VBRI
+} drmp3_metadata_type;
+
+typedef struct
+{
+ drmp3_metadata_type type;
+ const void* pRawData; /* A pointer to the raw data. */
+ size_t rawDataSize;
+} drmp3_metadata;
+
+
/*
Callback for when data is read. Return value is the number of bytes actually read.
-pUserData [in] The user data that was passed to drmp3_init(), drmp3_open() and family.
+pUserData [in] The user data that was passed to drmp3_init(), and family.
pBufferOut [out] The output buffer.
bytesToRead [in] The number of bytes to read.
@@ -335,17 +333,33 @@ typedef size_t (* drmp3_read_proc)(void* pUserData, void* pBufferOut, size_t byt
/*
Callback for when data needs to be seeked.
-pUserData [in] The user data that was passed to drmp3_init(), drmp3_open() and family.
-offset [in] The number of bytes to move, relative to the origin. Will never be negative.
-origin [in] The origin of the seek - the current position or the start of the stream.
+pUserData [in] The user data that was passed to drmp3_init(), and family.
+offset [in] The number of bytes to move, relative to the origin. Can be negative.
+origin [in] The origin of the seek.
Returns whether or not the seek was successful.
-
-Whether or not it is relative to the beginning or current position is determined by the "origin" parameter which
-will be either drmp3_seek_origin_start or drmp3_seek_origin_current.
*/
typedef drmp3_bool32 (* drmp3_seek_proc)(void* pUserData, int offset, drmp3_seek_origin origin);
+/*
+Callback for retrieving the current cursor position.
+
+pUserData [in] The user data that was passed to drmp3_init(), and family.
+pCursor [out] The cursor position in bytes from the start of the stream.
+
+Returns whether or not the cursor position was successfully retrieved.
+*/
+typedef drmp3_bool32 (* drmp3_tell_proc)(void* pUserData, drmp3_int64* pCursor);
+
+
+/*
+Callback for when metadata is read.
+
+Only the raw data is provided. The client is responsible for parsing the contents of the data themsevles.
+*/
+typedef void (* drmp3_meta_proc)(void* pUserData, const drmp3_metadata* pMetadata);
+
+
typedef struct
{
drmp3_uint32 channels;
@@ -359,22 +373,31 @@ typedef struct
drmp3_uint32 sampleRate;
drmp3_read_proc onRead;
drmp3_seek_proc onSeek;
+ drmp3_meta_proc onMeta;
void* pUserData;
+ void* pUserDataMeta;
drmp3_allocation_callbacks allocationCallbacks;
drmp3_uint32 mp3FrameChannels; /* The number of channels in the currently loaded MP3 frame. Internal use only. */
drmp3_uint32 mp3FrameSampleRate; /* The sample rate of the currently loaded MP3 frame. Internal use only. */
drmp3_uint32 pcmFramesConsumedInMP3Frame;
drmp3_uint32 pcmFramesRemainingInMP3Frame;
drmp3_uint8 pcmFrames[sizeof(float)*DRMP3_MAX_SAMPLES_PER_FRAME]; /* <-- Multipled by sizeof(float) to ensure there's enough room for DR_MP3_FLOAT_OUTPUT. */
- drmp3_uint64 currentPCMFrame; /* The current PCM frame, globally, based on the output sample rate. Mainly used for seeking. */
+ drmp3_uint64 currentPCMFrame; /* The current PCM frame, globally. */
drmp3_uint64 streamCursor; /* The current byte the decoder is sitting on in the raw stream. */
+ drmp3_uint64 streamLength; /* The length of the stream in bytes. dr_mp3 will not read beyond this. If a ID3v1 or APE tag is present, this will be set to the first byte of the tag. */
+ drmp3_uint64 streamStartOffset; /* The offset of the start of the MP3 data. This is used for skipping ID3v2 and VBR tags. */
drmp3_seek_point* pSeekPoints; /* NULL by default. Set with drmp3_bind_seek_table(). Memory is owned by the client. dr_mp3 will never attempt to free this pointer. */
drmp3_uint32 seekPointCount; /* The number of items in pSeekPoints. When set to 0 assumes to no seek table. Defaults to zero. */
+ drmp3_uint32 delayInPCMFrames;
+ drmp3_uint32 paddingInPCMFrames;
+ drmp3_uint64 totalPCMFrameCount; /* Set to DRMP3_UINT64_MAX if the length is unknown. Includes delay and padding. */
+ drmp3_bool32 isVBR;
+ drmp3_bool32 isCBR;
size_t dataSize;
size_t dataCapacity;
size_t dataConsumed;
drmp3_uint8* pData;
- drmp3_bool32 atEnd : 1;
+ drmp3_bool32 atEnd;
struct
{
const drmp3_uint8* pData;
@@ -388,6 +411,7 @@ Initializes an MP3 decoder.
onRead [in] The function to call when data needs to be read from the client.
onSeek [in] The function to call when the read position of the client data needs to move.
+onTell [in] The function to call when the read position of the client data needs to be retrieved.
pUserData [in, optional] A pointer to application defined data that will be passed to onRead and onSeek.
Returns true if successful; false otherwise.
@@ -396,7 +420,7 @@ Close the loader with drmp3_uninit().
See also: drmp3_init_file(), drmp3_init_memory(), drmp3_uninit()
*/
-DRMP3_API drmp3_bool32 drmp3_init(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, const drmp3_allocation_callbacks* pAllocationCallbacks);
+DRMP3_API drmp3_bool32 drmp3_init(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, drmp3_tell_proc onTell, drmp3_meta_proc onMeta, void* pUserData, const drmp3_allocation_callbacks* pAllocationCallbacks);
/*
Initializes an MP3 decoder from a block of memory.
@@ -406,6 +430,7 @@ the lifetime of the drmp3 object.
The buffer should contain the contents of the entire MP3 file.
*/
+DRMP3_API drmp3_bool32 drmp3_init_memory_with_metadata(drmp3* pMP3, const void* pData, size_t dataSize, drmp3_meta_proc onMeta, void* pUserDataMeta, const drmp3_allocation_callbacks* pAllocationCallbacks);
DRMP3_API drmp3_bool32 drmp3_init_memory(drmp3* pMP3, const void* pData, size_t dataSize, const drmp3_allocation_callbacks* pAllocationCallbacks);
#ifndef DR_MP3_NO_STDIO
@@ -416,6 +441,9 @@ This holds the internal FILE object until drmp3_uninit() is called. Keep this in
objects because the operating system may restrict the number of file handles an application can have open at
any given time.
*/
+DRMP3_API drmp3_bool32 drmp3_init_file_with_metadata(drmp3* pMP3, const char* pFilePath, drmp3_meta_proc onMeta, void* pUserDataMeta, const drmp3_allocation_callbacks* pAllocationCallbacks);
+DRMP3_API drmp3_bool32 drmp3_init_file_with_metadata_w(drmp3* pMP3, const wchar_t* pFilePath, drmp3_meta_proc onMeta, void* pUserDataMeta, const drmp3_allocation_callbacks* pAllocationCallbacks);
+
DRMP3_API drmp3_bool32 drmp3_init_file(drmp3* pMP3, const char* pFilePath, const drmp3_allocation_callbacks* pAllocationCallbacks);
DRMP3_API drmp3_bool32 drmp3_init_file_w(drmp3* pMP3, const wchar_t* pFilePath, const drmp3_allocation_callbacks* pAllocationCallbacks);
#endif
@@ -495,8 +523,8 @@ On output pConfig will receive the channel count and sample rate of the stream.
Free the returned pointer with drmp3_free().
*/
-DRMP3_API float* drmp3_open_and_read_pcm_frames_f32(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks);
-DRMP3_API drmp3_int16* drmp3_open_and_read_pcm_frames_s16(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks);
+DRMP3_API float* drmp3_open_and_read_pcm_frames_f32(drmp3_read_proc onRead, drmp3_seek_proc onSeek, drmp3_tell_proc onTell, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks);
+DRMP3_API drmp3_int16* drmp3_open_and_read_pcm_frames_s16(drmp3_read_proc onRead, drmp3_seek_proc onSeek, drmp3_tell_proc onTell, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks);
DRMP3_API float* drmp3_open_memory_and_read_pcm_frames_f32(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks);
DRMP3_API drmp3_int16* drmp3_open_memory_and_read_pcm_frames_s16(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks);
@@ -529,7 +557,7 @@ DRMP3_API void drmp3_free(void* p, const drmp3_allocation_callbacks* pAllocation
************************************************************************************************************************************************************
************************************************************************************************************************************************************/
-#if defined(DR_MP3_IMPLEMENTATION) || defined(DRMP3_IMPLEMENTATION)
+#if defined(DR_MP3_IMPLEMENTATION)
#ifndef dr_mp3_c
#define dr_mp3_c
@@ -2296,7 +2324,7 @@ DRMP3_API int drmp3dec_decode_frame(drmp3dec *dec, const drmp3_uint8 *mp3, int m
DRMP3_COPY_MEMORY(dec->header, hdr, DRMP3_HDR_SIZE);
info->frame_bytes = i + frame_size;
info->channels = DRMP3_HDR_IS_MONO(hdr) ? 1 : 2;
- info->hz = drmp3_hdr_sample_rate_hz(hdr);
+ info->sample_rate = drmp3_hdr_sample_rate_hz(hdr);
info->layer = 4 - DRMP3_HDR_GET_LAYER(hdr);
info->bitrate_kbps = drmp3_hdr_bitrate_kbps(hdr);
@@ -2589,14 +2617,48 @@ static drmp3_allocation_callbacks drmp3_copy_allocation_callbacks_or_defaults(co
static size_t drmp3__on_read(drmp3* pMP3, void* pBufferOut, size_t bytesToRead)
{
- size_t bytesRead = pMP3->onRead(pMP3->pUserData, pBufferOut, bytesToRead);
+ size_t bytesRead;
+
+ DRMP3_ASSERT(pMP3 != NULL);
+ DRMP3_ASSERT(pMP3->onRead != NULL);
+
+ /*
+ Don't try reading 0 bytes from the callback. This can happen when the stream is clamped against
+ ID3v1 or APE tags at the end of the stream.
+ */
+ if (bytesToRead == 0) {
+ return 0;
+ }
+
+ bytesRead = pMP3->onRead(pMP3->pUserData, pBufferOut, bytesToRead);
pMP3->streamCursor += bytesRead;
+
return bytesRead;
}
+static size_t drmp3__on_read_clamped(drmp3* pMP3, void* pBufferOut, size_t bytesToRead)
+{
+ DRMP3_ASSERT(pMP3 != NULL);
+ DRMP3_ASSERT(pMP3->onRead != NULL);
+
+ if (pMP3->streamLength == DRMP3_UINT64_MAX) {
+ return drmp3__on_read(pMP3, pBufferOut, bytesToRead);
+ } else {
+ drmp3_uint64 bytesRemaining;
+
+ bytesRemaining = (pMP3->streamLength - pMP3->streamCursor);
+ if (bytesToRead > bytesRemaining) {
+ bytesToRead = (size_t)bytesRemaining;
+ }
+
+ return drmp3__on_read(pMP3, pBufferOut, bytesToRead);
+ }
+}
+
static drmp3_bool32 drmp3__on_seek(drmp3* pMP3, int offset, drmp3_seek_origin origin)
{
DRMP3_ASSERT(offset >= 0);
+ DRMP3_ASSERT(origin == drmp3_seek_origin_start || origin == drmp3_seek_origin_current);
if (!pMP3->onSeek(pMP3->pUserData, offset, origin)) {
return DRMP3_FALSE;
@@ -2604,7 +2666,7 @@ static drmp3_bool32 drmp3__on_seek(drmp3* pMP3, int offset, drmp3_seek_origin or
if (origin == drmp3_seek_origin_start) {
pMP3->streamCursor = (drmp3_uint64)offset;
- } else {
+ } else{
pMP3->streamCursor += offset;
}
@@ -2617,7 +2679,6 @@ static drmp3_bool32 drmp3__on_seek_64(drmp3* pMP3, drmp3_uint64 offset, drmp3_se
return drmp3__on_seek(pMP3, (int)offset, origin);
}
-
/* Getting here "offset" is too large for a 32-bit integer. We just keep seeking forward until we hit the offset. */
if (!drmp3__on_seek(pMP3, 0x7FFFFFFF, drmp3_seek_origin_start)) {
return DRMP3_FALSE;
@@ -2641,8 +2702,22 @@ static drmp3_bool32 drmp3__on_seek_64(drmp3* pMP3, drmp3_uint64 offset, drmp3_se
return DRMP3_TRUE;
}
+static void drmp3__on_meta(drmp3* pMP3, drmp3_metadata_type type, const void* pRawData, size_t rawDataSize)
+{
+ if (pMP3->onMeta) {
+ drmp3_metadata metadata;
+
+ DRMP3_ZERO_OBJECT(&metadata);
+ metadata.type = type;
+ metadata.pRawData = pRawData;
+ metadata.rawDataSize = rawDataSize;
-static drmp3_uint32 drmp3_decode_next_frame_ex__callbacks(drmp3* pMP3, drmp3d_sample_t* pPCMFrames)
+ pMP3->onMeta(pMP3->pUserDataMeta, &metadata);
+ }
+}
+
+
+static drmp3_uint32 drmp3_decode_next_frame_ex__callbacks(drmp3* pMP3, drmp3d_sample_t* pPCMFrames, drmp3dec_frame_info* pMP3FrameInfo, const drmp3_uint8** ppMP3FrameData)
{
drmp3_uint32 pcmFramesRead = 0;
@@ -2682,7 +2757,7 @@ static drmp3_uint32 drmp3_decode_next_frame_ex__callbacks(drmp3* pMP3, drmp3d_sa
pMP3->dataCapacity = newDataCap;
}
- bytesRead = drmp3__on_read(pMP3, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize));
+ bytesRead = drmp3__on_read_clamped(pMP3, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize));
if (bytesRead == 0) {
if (pMP3->dataSize == 0) {
pMP3->atEnd = DRMP3_TRUE;
@@ -2709,10 +2784,8 @@ static drmp3_uint32 drmp3_decode_next_frame_ex__callbacks(drmp3* pMP3, drmp3d_sa
pcmFramesRead = drmp3dec_decode_frame(&pMP3->decoder, pMP3->pData + pMP3->dataConsumed, (int)pMP3->dataSize, pPCMFrames, &info); /* <-- Safe size_t -> int conversion thanks to the check above. */
/* Consume the data. */
- if (info.frame_bytes > 0) {
- pMP3->dataConsumed += (size_t)info.frame_bytes;
- pMP3->dataSize -= (size_t)info.frame_bytes;
- }
+ pMP3->dataConsumed += (size_t)info.frame_bytes;
+ pMP3->dataSize -= (size_t)info.frame_bytes;
/* pcmFramesRead will be equal to 0 if decoding failed. If it is zero and info.frame_bytes > 0 then we have successfully decoded the frame. */
if (pcmFramesRead > 0) {
@@ -2720,7 +2793,16 @@ static drmp3_uint32 drmp3_decode_next_frame_ex__callbacks(drmp3* pMP3, drmp3d_sa
pMP3->pcmFramesConsumedInMP3Frame = 0;
pMP3->pcmFramesRemainingInMP3Frame = pcmFramesRead;
pMP3->mp3FrameChannels = info.channels;
- pMP3->mp3FrameSampleRate = info.hz;
+ pMP3->mp3FrameSampleRate = info.sample_rate;
+
+ if (pMP3FrameInfo != NULL) {
+ *pMP3FrameInfo = info;
+ }
+
+ if (ppMP3FrameData != NULL) {
+ *ppMP3FrameData = pMP3->pData + pMP3->dataConsumed - (size_t)info.frame_bytes;
+ }
+
break;
} else if (info.frame_bytes == 0) {
/* Need more data. minimp3 recommends doing data submission in 16K chunks. */
@@ -2747,7 +2829,7 @@ static drmp3_uint32 drmp3_decode_next_frame_ex__callbacks(drmp3* pMP3, drmp3d_sa
}
/* Fill in a chunk. */
- bytesRead = drmp3__on_read(pMP3, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize));
+ bytesRead = drmp3__on_read_clamped(pMP3, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize));
if (bytesRead == 0) {
pMP3->atEnd = DRMP3_TRUE;
return 0; /* Error reading more data. */
@@ -2760,7 +2842,7 @@ static drmp3_uint32 drmp3_decode_next_frame_ex__callbacks(drmp3* pMP3, drmp3d_sa
return pcmFramesRead;
}
-static drmp3_uint32 drmp3_decode_next_frame_ex__memory(drmp3* pMP3, drmp3d_sample_t* pPCMFrames)
+static drmp3_uint32 drmp3_decode_next_frame_ex__memory(drmp3* pMP3, drmp3d_sample_t* pPCMFrames, drmp3dec_frame_info* pMP3FrameInfo, const drmp3_uint8** ppMP3FrameData)
{
drmp3_uint32 pcmFramesRead = 0;
drmp3dec_frame_info info;
@@ -2779,7 +2861,16 @@ static drmp3_uint32 drmp3_decode_next_frame_ex__memory(drmp3* pMP3, drmp3d_sampl
pMP3->pcmFramesConsumedInMP3Frame = 0;
pMP3->pcmFramesRemainingInMP3Frame = pcmFramesRead;
pMP3->mp3FrameChannels = info.channels;
- pMP3->mp3FrameSampleRate = info.hz;
+ pMP3->mp3FrameSampleRate = info.sample_rate;
+
+ if (pMP3FrameInfo != NULL) {
+ *pMP3FrameInfo = info;
+ }
+
+ if (ppMP3FrameData != NULL) {
+ *ppMP3FrameData = pMP3->memory.pData + pMP3->memory.currentReadPos;
+ }
+
break;
} else if (info.frame_bytes > 0) {
/* No frames were read, but it looks like we skipped past one. Read the next MP3 frame. */
@@ -2796,19 +2887,19 @@ static drmp3_uint32 drmp3_decode_next_frame_ex__memory(drmp3* pMP3, drmp3d_sampl
return pcmFramesRead;
}
-static drmp3_uint32 drmp3_decode_next_frame_ex(drmp3* pMP3, drmp3d_sample_t* pPCMFrames)
+static drmp3_uint32 drmp3_decode_next_frame_ex(drmp3* pMP3, drmp3d_sample_t* pPCMFrames, drmp3dec_frame_info* pMP3FrameInfo, const drmp3_uint8** ppMP3FrameData)
{
if (pMP3->memory.pData != NULL && pMP3->memory.dataSize > 0) {
- return drmp3_decode_next_frame_ex__memory(pMP3, pPCMFrames);
+ return drmp3_decode_next_frame_ex__memory(pMP3, pPCMFrames, pMP3FrameInfo, ppMP3FrameData);
} else {
- return drmp3_decode_next_frame_ex__callbacks(pMP3, pPCMFrames);
+ return drmp3_decode_next_frame_ex__callbacks(pMP3, pPCMFrames, pMP3FrameInfo, ppMP3FrameData);
}
}
static drmp3_uint32 drmp3_decode_next_frame(drmp3* pMP3)
{
DRMP3_ASSERT(pMP3 != NULL);
- return drmp3_decode_next_frame_ex(pMP3, (drmp3d_sample_t*)pMP3->pcmFrames);
+ return drmp3_decode_next_frame_ex(pMP3, (drmp3d_sample_t*)pMP3->pcmFrames, NULL, NULL);
}
#if 0
@@ -2818,7 +2909,7 @@ static drmp3_uint32 drmp3_seek_next_frame(drmp3* pMP3)
DRMP3_ASSERT(pMP3 != NULL);
- pcmFrameCount = drmp3_decode_next_frame_ex(pMP3, NULL);
+ pcmFrameCount = drmp3_decode_next_frame_ex(pMP3, NULL, NULL, NULL);
if (pcmFrameCount == 0) {
return 0;
}
@@ -2832,8 +2923,13 @@ static drmp3_uint32 drmp3_seek_next_frame(drmp3* pMP3)
}
#endif
-static drmp3_bool32 drmp3_init_internal(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, const drmp3_allocation_callbacks* pAllocationCallbacks)
+static drmp3_bool32 drmp3_init_internal(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, drmp3_tell_proc onTell, drmp3_meta_proc onMeta, void* pUserData, void* pUserDataMeta, const drmp3_allocation_callbacks* pAllocationCallbacks)
{
+ drmp3dec_frame_info firstFrameInfo;
+ const drmp3_uint8* pFirstFrameData;
+ drmp3_uint32 firstFramePCMFrameCount;
+ drmp3_uint32 detectedMP3FrameCount = 0xFFFFFFFF;
+
DRMP3_ASSERT(pMP3 != NULL);
DRMP3_ASSERT(onRead != NULL);
@@ -2842,17 +2938,317 @@ static drmp3_bool32 drmp3_init_internal(drmp3* pMP3, drmp3_read_proc onRead, drm
pMP3->onRead = onRead;
pMP3->onSeek = onSeek;
+ pMP3->onMeta = onMeta;
pMP3->pUserData = pUserData;
+ pMP3->pUserDataMeta = pUserDataMeta;
pMP3->allocationCallbacks = drmp3_copy_allocation_callbacks_or_defaults(pAllocationCallbacks);
if (pMP3->allocationCallbacks.onFree == NULL || (pMP3->allocationCallbacks.onMalloc == NULL && pMP3->allocationCallbacks.onRealloc == NULL)) {
return DRMP3_FALSE; /* Invalid allocation callbacks. */
}
- /* Decode the first frame to confirm that it is indeed a valid MP3 stream. */
- if (drmp3_decode_next_frame(pMP3) == 0) {
+ pMP3->streamCursor = 0;
+ pMP3->streamLength = DRMP3_UINT64_MAX;
+ pMP3->streamStartOffset = 0;
+ pMP3->delayInPCMFrames = 0;
+ pMP3->paddingInPCMFrames = 0;
+ pMP3->totalPCMFrameCount = DRMP3_UINT64_MAX;
+
+ /* We'll first check for any ID3v1 or APE tags. */
+ #if 1
+ if (onSeek != NULL && onTell != NULL) {
+ if (onSeek(pUserData, 0, drmp3_seek_origin_end)) {
+ drmp3_int64 streamLen;
+ int streamEndOffset = 0;
+
+ /* First get the length of the stream. We need this so we can ensure the stream is big enough to store the tags. */
+ if (onTell(pUserData, &streamLen)) {
+ /* ID3v1 */
+ if (streamLen > 128) {
+ char id3[3];
+ if (onSeek(pUserData, streamEndOffset - 128, drmp3_seek_origin_end)) {
+ if (onRead(pUserData, id3, 3) == 3 && id3[0] == 'T' && id3[1] == 'A' && id3[2] == 'G') {
+ /* We have an ID3v1 tag. */
+ streamEndOffset -= 128;
+ streamLen -= 128;
+
+ /* Fire a metadata callback for the TAG data. */
+ if (onMeta != NULL) {
+ drmp3_uint8 tag[128];
+ tag[0] = 'T'; tag[1] = 'A'; tag[2] = 'G';
+
+ if (onRead(pUserData, tag + 3, 125) == 125) {
+ drmp3__on_meta(pMP3, DRMP3_METADATA_TYPE_ID3V1, tag, 128);
+ }
+ }
+ } else {
+ /* No ID3v1 tag. */
+ }
+ } else {
+ /* Failed to seek to the ID3v1 tag. */
+ }
+ } else {
+ /* Stream too short. No ID3v1 tag. */
+ }
+
+ /* APE */
+ if (streamLen > 32) {
+ char ape[32]; /* The footer. */
+ if (onSeek(pUserData, streamEndOffset - 32, drmp3_seek_origin_end)) {
+ if (onRead(pUserData, ape, 32) == 32 && ape[0] == 'A' && ape[1] == 'P' && ape[2] == 'E' && ape[3] == 'T' && ape[4] == 'A' && ape[5] == 'G' && ape[6] == 'E' && ape[7] == 'X') {
+ /* We have an APE tag. */
+ drmp3_uint32 tagSize =
+ ((drmp3_uint32)ape[24] << 0) |
+ ((drmp3_uint32)ape[25] << 8) |
+ ((drmp3_uint32)ape[26] << 16) |
+ ((drmp3_uint32)ape[27] << 24);
+
+ streamEndOffset -= 32 + tagSize;
+ streamLen -= 32 + tagSize;
+
+ /* Fire a metadata callback for the APE data. Must include both the main content and footer. */
+ if (onMeta != NULL) {
+ /* We first need to seek to the start of the APE tag. */
+ if (onSeek(pUserData, streamEndOffset, drmp3_seek_origin_end)) {
+ size_t apeTagSize = (size_t)tagSize + 32;
+ drmp3_uint8* pTagData = (drmp3_uint8*)drmp3_malloc(apeTagSize, pAllocationCallbacks);
+ if (pTagData != NULL) {
+ if (onRead(pUserData, pTagData, apeTagSize) == apeTagSize) {
+ drmp3__on_meta(pMP3, DRMP3_METADATA_TYPE_APE, pTagData, apeTagSize);
+ }
+
+ drmp3_free(pTagData, pAllocationCallbacks);
+ }
+ }
+ }
+ }
+ }
+ } else {
+ /* Stream too short. No APE tag. */
+ }
+
+ /* Seek back to the start. */
+ if (!onSeek(pUserData, 0, drmp3_seek_origin_start)) {
+ return DRMP3_FALSE; /* Failed to seek back to the start. */
+ }
+
+ pMP3->streamLength = (drmp3_uint64)streamLen;
+
+ if (pMP3->memory.pData != NULL) {
+ pMP3->memory.dataSize = (size_t)pMP3->streamLength;
+ }
+ } else {
+ /* Failed to get the length of the stream. ID3v1 and APE tags cannot be skipped. */
+ if (!onSeek(pUserData, 0, drmp3_seek_origin_start)) {
+ return DRMP3_FALSE; /* Failed to seek back to the start. */
+ }
+ }
+ } else {
+ /* Failed to seek to the end. Cannot skip ID3v1 or APE tags. */
+ }
+ } else {
+ /* No onSeek or onTell callback. Cannot skip ID3v1 or APE tags. */
+ }
+ #endif
+
+
+ /* ID3v2 tags */
+ #if 1
+ {
+ char header[10];
+ if (onRead(pUserData, header, 10) == 10) {
+ if (header[0] == 'I' && header[1] == 'D' && header[2] == '3') {
+ drmp3_uint32 tagSize =
+ (((drmp3_uint32)header[6] & 0x7F) << 21) |
+ (((drmp3_uint32)header[7] & 0x7F) << 14) |
+ (((drmp3_uint32)header[8] & 0x7F) << 7) |
+ (((drmp3_uint32)header[9] & 0x7F) << 0);
+
+ /* Account for the footer. */
+ if (header[5] & 0x10) {
+ tagSize += 10;
+ }
+
+ /* Read the tag content and fire a metadata callback. */
+ if (onMeta != NULL) {
+ size_t tagSizeWithHeader = 10 + tagSize;
+ drmp3_uint8* pTagData = (drmp3_uint8*)drmp3_malloc(tagSizeWithHeader, pAllocationCallbacks);
+ if (pTagData != NULL) {
+ DRMP3_COPY_MEMORY(pTagData, header, 10);
+
+ if (onRead(pUserData, pTagData + 10, tagSize) == tagSize) {
+ drmp3__on_meta(pMP3, DRMP3_METADATA_TYPE_ID3V2, pTagData, tagSizeWithHeader);
+ }
+
+ drmp3_free(pTagData, pAllocationCallbacks);
+ }
+ } else {
+ /* Don't have a metadata callback, so just skip the tag. */
+ if (onSeek != NULL) {
+ if (!onSeek(pUserData, tagSize, drmp3_seek_origin_current)) {
+ return DRMP3_FALSE; /* Failed to seek past the ID3v2 tag. */
+ }
+ } else {
+ /* Don't have a seek callback. Read and discard. */
+ char discard[1024];
+
+ while (tagSize > 0) {
+ size_t bytesToRead = tagSize;
+ if (bytesToRead > sizeof(discard)) {
+ bytesToRead = sizeof(discard);
+ }
+
+ if (onRead(pUserData, discard, bytesToRead) != bytesToRead) {
+ return DRMP3_FALSE; /* Failed to read data. */
+ }
+
+ tagSize -= (drmp3_uint32)bytesToRead;
+ }
+ }
+ }
+
+ pMP3->streamStartOffset += 10 + tagSize; /* +10 for the header. */
+ pMP3->streamCursor = pMP3->streamStartOffset;
+ } else {
+ /* Not an ID3v2 tag. Seek back to the start. */
+ if (onSeek != NULL) {
+ if (!onSeek(pUserData, 0, drmp3_seek_origin_start)) {
+ return DRMP3_FALSE; /* Failed to seek back to the start. */
+ }
+ } else {
+ /* Don't have a seek callback to move backwards. We'll just fall through and let the decoding process re-sync. The ideal solution here would be to read into the cache. */
+
+ /*
+
(Patch may be truncated, please check the link at the top of this post.)