Przeglądaj źródła

Reviewed audio issues

- Updated dr_mp3 and implemented it
- Reviewed sampleCount vs frameCount
- Reviewed XM playing (some weird things...)
Ray 6 lat temu
rodzic
commit
f7667aad8d
3 zmienionych plików z 314 dodań i 104 usunięć
  1. 2 0
      examples/audio/audio_music_stream.c
  2. 23 21
      src/audio.c
  3. 289 83
      src/external/dr_mp3.h

+ 2 - 0
examples/audio/audio_music_stream.c

@@ -59,6 +59,8 @@ int main()
         
         // Get timePlayed scaled to bar dimensions (400 pixels)
         timePlayed = GetMusicTimePlayed(music)/GetMusicTimeLength(music)*400;
+        
+        if (timePlayed > 400) StopMusicStream(music);
         //----------------------------------------------------------------------------------
 
         // Draw

+ 23 - 21
src/audio.c

@@ -214,7 +214,7 @@ typedef enum { AUDIO_BUFFER_USAGE_STATIC = 0, AUDIO_BUFFER_USAGE_STREAM } AudioB
 
 // Audio buffer structure
 // NOTE: Slightly different logic is used when feeding data to the playback device depending on whether or not data is streamed
-typedef struct AudioBuffer AudioBuffer;
+typedef struct AudioBuffer AudioBuffer; 
 struct AudioBuffer {
     mal_dsp dsp;                    // Required for format conversion
     float volume;
@@ -1130,13 +1130,12 @@ Music LoadMusicStream(const char *fileName)
             TraceLog(LOG_INFO, "[%s] MP3 sample rate: %i", fileName, music->ctxMp3.sampleRate);
             TraceLog(LOG_INFO, "[%s] MP3 bits per sample: %i", fileName, 32);
             TraceLog(LOG_INFO, "[%s] MP3 channels: %i", fileName, music->ctxMp3.channels);
-            TraceLog(LOG_INFO, "[%s] MP3 frames remaining: %i", fileName, (unsigned int)music->ctxMp3.framesRemaining);
             
             music->stream = InitAudioStream(music->ctxMp3.sampleRate, 32, music->ctxMp3.channels);
             
             // TODO: There is not an easy way to compute the total number of samples available
             // in an MP3, frames size could be variable... we tried with a 60 seconds music... but crashes...
-            music->totalSamples = 60*music->ctxMp3.sampleRate*music->ctxMp3.channels;
+            music->totalSamples = drmp3_get_pcm_frame_count(&music->ctxMp3)*music->ctxMp3.channels;
             music->samplesLeft = music->totalSamples;
             music->ctxType = MUSIC_AUDIO_MP3;
             music->loopCount = -1;                       // Infinite loop by default
@@ -1161,8 +1160,8 @@ Music LoadMusicStream(const char *fileName)
             music->ctxType = MUSIC_MODULE_XM;
             music->loopCount = -1;                       // Infinite loop by default
 
-            TraceLog(LOG_DEBUG, "[%s] XM number of samples: %i", fileName, music->totalSamples);
-            TraceLog(LOG_DEBUG, "[%s] XM track length: %11.6f sec", fileName, (float)music->totalSamples/48000.0f);
+            TraceLog(LOG_INFO, "[%s] XM number of samples: %i", fileName, music->totalSamples);
+            TraceLog(LOG_INFO, "[%s] XM track length: %11.6f sec", fileName, (float)music->totalSamples/48000.0f);
         }
         else musicLoaded = false;
     }
@@ -1283,7 +1282,7 @@ void StopMusicStream(Music music)
         case MUSIC_AUDIO_FLAC: /* TODO: Restart FLAC context */ break;
 #endif
 #if defined(SUPPORT_FILEFORMAT_MP3)
-        case MUSIC_AUDIO_MP3: /* TODO: Restart MP3 context */ break;
+        case MUSIC_AUDIO_MP3: drmp3_seek_to_pcm_frame(&music->ctxMp3, 0); break;
 #endif
 #if defined(SUPPORT_FILEFORMAT_XM)
         case MUSIC_MODULE_XM: /* TODO: Restart XM context */ break;
@@ -1306,13 +1305,13 @@ void UpdateMusicStream(Music music)
     unsigned int subBufferSizeInFrames = ((AudioBuffer *)music->stream.audioBuffer)->bufferSizeInFrames/2;
 
     // NOTE: Using dynamic allocation because it could require more than 16KB
-    void *pcm = calloc(subBufferSizeInFrames*music->stream.sampleSize/8*music->stream.channels, 1);
+    void *pcm = calloc(subBufferSizeInFrames*music->stream.channels*music->stream.sampleSize/8, 1);
 
     int samplesCount = 0;    // Total size of data steamed in L+R samples for xm floats, individual L or R for ogg shorts
 
     while (IsAudioBufferProcessed(music->stream))
     {
-        if (music->samplesLeft >= subBufferSizeInFrames) samplesCount = subBufferSizeInFrames;
+        if ((music->samplesLeft/music->stream.channels) >= subBufferSizeInFrames) samplesCount = subBufferSizeInFrames*music->stream.channels;
         else samplesCount = music->samplesLeft;
 
         // TODO: Really don't like ctxType thingy...
@@ -1321,27 +1320,31 @@ void UpdateMusicStream(Music music)
             case MUSIC_AUDIO_OGG:
             {
                 // NOTE: Returns the number of samples to process (be careful! we ask for number of shorts!)
-                stb_vorbis_get_samples_short_interleaved(music->ctxOgg, music->stream.channels, (short *)pcm, samplesCount*music->stream.channels);
+                stb_vorbis_get_samples_short_interleaved(music->ctxOgg, music->stream.channels, (short *)pcm, samplesCount);
 
             } break;
         #if defined(SUPPORT_FILEFORMAT_FLAC)
             case MUSIC_AUDIO_FLAC:
             {
                 // NOTE: Returns the number of samples to process
-                unsigned int numSamplesFlac = (unsigned int)drflac_read_s16(music->ctxFlac, samplesCount*music->stream.channels, (short *)pcm);
+                unsigned int numSamplesFlac = (unsigned int)drflac_read_s16(music->ctxFlac, samplesCount, (short *)pcm);
 
             } break;
         #endif
         #if defined(SUPPORT_FILEFORMAT_MP3)
             case MUSIC_AUDIO_MP3: 
             {
-                // NOTE: Returns the number of samples to process
-                unsigned int numSamplesMp3 = (unsigned int)drmp3_read_f32(&music->ctxMp3, samplesCount*music->stream.channels, (float *)pcm);
+                // NOTE: samplesCount, actually refers to framesCount and returns the number of frames processed
+                unsigned int numFramesMp3 = (unsigned int)drmp3_read_pcm_frames_f32(&music->ctxMp3, samplesCount/music->stream.channels, (float *)pcm);
 
             } break;
         #endif
         #if defined(SUPPORT_FILEFORMAT_XM)
-            case MUSIC_MODULE_XM: jar_xm_generate_samples_16bit(music->ctxXm, pcm, samplesCount); break;
+            case MUSIC_MODULE_XM: 
+            {
+                // NOTE: Internally this function considers 2 channels generation, so samplesCount/2 --> WEIRD
+                jar_xm_generate_samples_16bit(music->ctxXm, (short *)pcm, samplesCount/2); 
+            } break;
         #endif
         #if defined(SUPPORT_FILEFORMAT_MOD)
             case MUSIC_MODULE_MOD: jar_mod_fillbuffer(&music->ctxMod, pcm, samplesCount, 0); break;
@@ -1414,7 +1417,7 @@ void SetMusicLoopCount(Music music, int count)
 // Get music time length (in seconds)
 float GetMusicTimeLength(Music music)
 {
-    float totalSeconds = (float)music->totalSamples/music->stream.sampleRate;
+    float totalSeconds = (float)music->totalSamples/(music->stream.sampleRate*music->stream.channels);
 
     return totalSeconds;
 }
@@ -1425,12 +1428,11 @@ float GetMusicTimePlayed(Music music)
     float secondsPlayed = 0.0f;
 
     unsigned int samplesPlayed = music->totalSamples - music->samplesLeft;
-    secondsPlayed = (float)samplesPlayed/music->stream.sampleRate;
+    secondsPlayed = (float)samplesPlayed/(music->stream.sampleRate*music->stream.channels);
 
     return secondsPlayed;
 }
 
-
 // Init audio stream (to stream audio pcm data)
 AudioStream InitAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels)
 {
@@ -1509,11 +1511,11 @@ void UpdateAudioStream(AudioStream stream, const void *data, int samplesCount)
         unsigned char *subBuffer = audioBuffer->buffer + ((subBufferSizeInFrames*stream.channels*(stream.sampleSize/8))*subBufferToUpdate);
 
         // Does this API expect a whole buffer to be updated in one go? Assuming so, but if not will need to change this logic.
-        if (subBufferSizeInFrames >= (mal_uint32)samplesCount)
+        if (subBufferSizeInFrames >= (mal_uint32)samplesCount/stream.channels)
         {
             mal_uint32 framesToWrite = subBufferSizeInFrames;
             
-            if (framesToWrite > (mal_uint32)samplesCount) framesToWrite = (mal_uint32)samplesCount;
+            if (framesToWrite > ((mal_uint32)samplesCount/stream.channels)) framesToWrite = (mal_uint32)samplesCount/stream.channels;
 
             mal_uint32 bytesToWrite = framesToWrite*stream.channels*(stream.sampleSize/8);
             memcpy(subBuffer, data, bytesToWrite);
@@ -1867,13 +1869,13 @@ static Wave LoadMP3(const char *fileName)
     Wave wave = { 0 };
 
     // Decode an entire MP3 file in one go
-    uint64_t totalSampleCount = 0;
+    uint64_t totalFrameCount = 0;
     drmp3_config config = { 0 };
-    wave.data = drmp3_open_and_decode_file_f32(fileName, &config, &totalSampleCount);
+    wave.data = drmp3_open_file_and_read_f32(fileName, &config, &totalFrameCount);
     
     wave.channels = config.outputChannels;
     wave.sampleRate = config.outputSampleRate;
-    wave.sampleCount = (int)totalSampleCount;
+    wave.sampleCount = (int)totalFrameCount*wave.channels;
     wave.sampleSize = 32;
 
     // NOTE: Only support up to 2 channels (mono, stereo)

+ 289 - 83
src/external/dr_mp3.h

@@ -1,5 +1,5 @@
 // MP3 audio decoder. Public domain. See "unlicense" statement at the end of this file.
-// dr_mp3 - v0.3.2 - 2018-09-11
+// dr_mp3 - v0.4.0 - 2018-xx-xx
 //
 // David Reid - [email protected]
 //
@@ -52,11 +52,6 @@
 //
 // #define DR_MP3_NO_SIMD
 //   Disable SIMD optimizations.
-//
-//
-// LIMITATIONS
-// ===========
-// - Seeking is extremely inefficient.
 
 #ifndef dr_mp3_h
 #define dr_mp3_h
@@ -92,7 +87,8 @@ typedef drmp3_uint32     drmp3_bool32;
 #define DRMP3_TRUE       1
 #define DRMP3_FALSE      0
 
-#define DRMP3_MAX_SAMPLES_PER_FRAME (1152*2)
+#define DRMP3_MAX_PCM_FRAMES_PER_MP3_FRAME  1152
+#define DRMP3_MAX_SAMPLES_PER_FRAME         (DRMP3_MAX_PCM_FRAMES_PER_MP3_FRAME*2)
 
 
 // Low Level Push API
@@ -214,11 +210,12 @@ typedef struct
     drmp3_read_proc onRead;
     drmp3_seek_proc onSeek;
     void* pUserData;
-    drmp3_uint32 frameChannels;     // The number of channels in the currently loaded MP3 frame. Internal use only.
-    drmp3_uint32 frameSampleRate;   // The sample rate of the currently loaded MP3 frame. Internal use only.
-    drmp3_uint32 framesConsumed;
-    drmp3_uint32 framesRemaining;
-    drmp3_uint8 frames[sizeof(float)*DRMP3_MAX_SAMPLES_PER_FRAME];  // <-- Multipled by sizeof(float) to ensure there's enough room for DR_MP3_FLOAT_OUTPUT.
+    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_src src;
     size_t dataSize;
     size_t dataCapacity;
@@ -268,12 +265,21 @@ void drmp3_uninit(drmp3* pMP3);
 // Reads PCM frames as interleaved 32-bit IEEE floating point PCM.
 //
 // Note that framesToRead specifies the number of PCM frames to read, _not_ the number of MP3 frames.
-drmp3_uint64 drmp3_read_f32(drmp3* pMP3, drmp3_uint64 framesToRead, float* pBufferOut);
+drmp3_uint64 drmp3_read_pcm_frames_f32(drmp3* pMP3, drmp3_uint64 framesToRead, float* pBufferOut);
 
 // Seeks to a specific frame.
 //
 // Note that this is _not_ an MP3 frame, but rather a PCM frame.
-drmp3_bool32 drmp3_seek_to_frame(drmp3* pMP3, drmp3_uint64 frameIndex);
+drmp3_bool32 drmp3_seek_to_pcm_frame(drmp3* pMP3, drmp3_uint64 frameIndex);
+
+// Calculates the total number of PCM frames in the MP3 stream. Cannot be used for infinite streams such as internet
+// radio. Runs in linear time. Returns 0 on error.
+drmp3_uint64 drmp3_get_pcm_frame_count(drmp3* pMP3);
+
+// Calculates the total number of MP3 frames in the MP3 stream. Cannot be used for infinite streams such as internet
+// radio. Runs in linear time. Returns 0 on error.
+drmp3_uint64 drmp3_get_mp3_frame_count(drmp3* pMP3);
+
 
 
 // Opens an decodes an entire MP3 stream as a single operation.
@@ -281,10 +287,10 @@ drmp3_bool32 drmp3_seek_to_frame(drmp3* pMP3, drmp3_uint64 frameIndex);
 // pConfig is both an input and output. On input it contains what you want. On output it contains what you got.
 //
 // Free the returned pointer with drmp3_free().
-float* drmp3_open_and_decode_f32(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount);
-float* drmp3_open_and_decode_memory_f32(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount);
+float* drmp3_open_and_read_f32(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount);
+float* drmp3_open_memory_and_read_f32(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount);
 #ifndef DR_MP3_NO_STDIO
-float* drmp3_open_and_decode_file_f32(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount);
+float* drmp3_open_file_and_read_f32(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount);
 #endif
 
 // Frees any memory that was allocated by a public drmp3 API.
@@ -2376,35 +2382,46 @@ drmp3_uint64 drmp3_src_read_frames_linear(drmp3_src* pSRC, drmp3_uint64 frameCou
 }
 
 
+static size_t drmp3__on_read(drmp3* pMP3, void* pBufferOut, size_t bytesToRead)
+{
+    return pMP3->onRead(pMP3->pUserData, pBufferOut, bytesToRead);
+}
 
-static drmp3_bool32 drmp3_decode_next_frame(drmp3* pMP3)
+static drmp3_bool32 drmp3__on_seek(drmp3* pMP3, int offset, drmp3_seek_origin origin)
+{
+    drmp3_assert(offset >= 0);
+    return pMP3->onSeek(pMP3->pUserData, offset, origin);
+}
+
+
+static drmp3_uint32 drmp3_decode_next_frame_ex(drmp3* pMP3, drmp3d_sample_t* pPCMFrames)
 {
     drmp3_assert(pMP3 != NULL);
     drmp3_assert(pMP3->onRead != NULL);
 
     if (pMP3->atEnd) {
-        return DRMP3_FALSE;
+        return 0;
     }
 
-    do
-    {
+    drmp3_uint32 pcmFramesRead = 0;
+    do {
         // minimp3 recommends doing data submission in 16K chunks. If we don't have at least 16K bytes available, get more.
         if (pMP3->dataSize < DRMP3_DATA_CHUNK_SIZE) {
             if (pMP3->dataCapacity < DRMP3_DATA_CHUNK_SIZE) {
                 pMP3->dataCapacity = DRMP3_DATA_CHUNK_SIZE;
                 drmp3_uint8* pNewData = (drmp3_uint8*)drmp3_realloc(pMP3->pData, pMP3->dataCapacity);
                 if (pNewData == NULL) {
-                    return DRMP3_FALSE; // Out of memory.
+                    return 0; // Out of memory.
                 }
 
                 pMP3->pData = pNewData;
             }
 
-            size_t bytesRead = pMP3->onRead(pMP3->pUserData, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize));
+            size_t bytesRead = drmp3__on_read(pMP3, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize));
             if (bytesRead == 0) {
                 if (pMP3->dataSize == 0) {
                     pMP3->atEnd = DRMP3_TRUE;
-                    return DRMP3_FALSE; // No data.
+                    return 0; // No data.
                 }
             }
 
@@ -2413,23 +2430,23 @@ static drmp3_bool32 drmp3_decode_next_frame(drmp3* pMP3)
 
         if (pMP3->dataSize > INT_MAX) {
             pMP3->atEnd = DRMP3_TRUE;
-            return DRMP3_FALSE; // File too big.
+            return 0; // File too big.
         }
 
         drmp3dec_frame_info info;
-        drmp3_uint32 samplesRead = drmp3dec_decode_frame(&pMP3->decoder, pMP3->pData, (int)pMP3->dataSize, (drmp3d_sample_t*)pMP3->frames, &info);    // <-- Safe size_t -> int conversion thanks to the check above.
-        if (samplesRead != 0) {
+        pcmFramesRead = drmp3dec_decode_frame(&pMP3->decoder, pMP3->pData, (int)pMP3->dataSize, pPCMFrames, &info);    // <-- Safe size_t -> int conversion thanks to the check above.
+        if (pcmFramesRead != 0) {
             size_t leftoverDataSize = (pMP3->dataSize - (size_t)info.frame_bytes);
             for (size_t i = 0; i < leftoverDataSize; ++i) {
                 pMP3->pData[i] = pMP3->pData[i + (size_t)info.frame_bytes];
             }
                 
             pMP3->dataSize = leftoverDataSize;
-            pMP3->framesConsumed = 0;
-            pMP3->framesRemaining = samplesRead;
-            pMP3->frameChannels = info.channels;
-            pMP3->frameSampleRate = info.hz;
-            drmp3_src_set_input_sample_rate(&pMP3->src, pMP3->frameSampleRate);
+            pMP3->pcmFramesConsumedInMP3Frame = 0;
+            pMP3->pcmFramesRemainingInMP3Frame = pcmFramesRead;
+            pMP3->mp3FrameChannels = info.channels;
+            pMP3->mp3FrameSampleRate = info.hz;
+            drmp3_src_set_input_sample_rate(&pMP3->src, pMP3->mp3FrameSampleRate);
             break;
         } else {
             // Need more data. minimp3 recommends doing data submission in 16K chunks.
@@ -2438,24 +2455,47 @@ static drmp3_bool32 drmp3_decode_next_frame(drmp3* pMP3)
                 pMP3->dataCapacity += DRMP3_DATA_CHUNK_SIZE;
                 drmp3_uint8* pNewData = (drmp3_uint8*)drmp3_realloc(pMP3->pData, pMP3->dataCapacity);
                 if (pNewData == NULL) {
-                    return DRMP3_FALSE; // Out of memory.
+                    return 0; // Out of memory.
                 }
 
                 pMP3->pData = pNewData;
             }
 
             // Fill in a chunk.
-            size_t bytesRead = pMP3->onRead(pMP3->pUserData, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize));
+            size_t bytesRead = drmp3__on_read(pMP3, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize));
             if (bytesRead == 0) {
                 pMP3->atEnd = DRMP3_TRUE;
-                return DRMP3_FALSE; // Error reading more data.
+                return 0; // Error reading more data.
             }
 
             pMP3->dataSize += bytesRead;
         }
     } while (DRMP3_TRUE);
 
-    return DRMP3_TRUE;
+    return pcmFramesRead;
+}
+
+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);
+}
+
+static drmp3_uint32 drmp3_seek_next_frame(drmp3* pMP3)
+{
+    drmp3_assert(pMP3 != NULL);
+
+    drmp3_uint32 pcmFrameCount = drmp3_decode_next_frame_ex(pMP3, NULL);
+    if (pcmFrameCount == 0) {
+        return 0;
+    }
+
+    // We have essentially just skipped past the frame, so just set the remaining samples to 0.
+    pMP3->currentPCMFrame             += pcmFrameCount;
+    pMP3->pcmFramesConsumedInMP3Frame  = pcmFrameCount;
+    pMP3->pcmFramesRemainingInMP3Frame = 0;
+
+    return pcmFrameCount;
 }
 
 static drmp3_uint64 drmp3_read_src(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, void* pUserData)
@@ -2465,64 +2505,64 @@ static drmp3_uint64 drmp3_read_src(drmp3_src* pSRC, drmp3_uint64 frameCount, voi
     drmp3_assert(pMP3->onRead != NULL);
 
     float* pFramesOutF = (float*)pFramesOut;
-    drmp3_uint32 totalFramesRead = 0;
+    drmp3_uint64 totalFramesRead = 0;
 
     while (frameCount > 0) {
         // Read from the in-memory buffer first.
-        while (pMP3->framesRemaining > 0 && frameCount > 0) {
-            drmp3d_sample_t* frames = (drmp3d_sample_t*)pMP3->frames;
+        while (pMP3->pcmFramesRemainingInMP3Frame > 0 && frameCount > 0) {
+            drmp3d_sample_t* frames = (drmp3d_sample_t*)pMP3->pcmFrames;
 #ifndef DR_MP3_FLOAT_OUTPUT
-            if (pMP3->frameChannels == 1) {
+            if (pMP3->mp3FrameChannels == 1) {
                 if (pMP3->channels == 1) {
                     // Mono -> Mono.
-                    pFramesOutF[0] = frames[pMP3->framesConsumed] / 32768.0f;
+                    pFramesOutF[0] = frames[pMP3->pcmFramesConsumedInMP3Frame] / 32768.0f;
                 } else {
                     // Mono -> Stereo.
-                    pFramesOutF[0] = frames[pMP3->framesConsumed] / 32768.0f;
-                    pFramesOutF[1] = frames[pMP3->framesConsumed] / 32768.0f;
+                    pFramesOutF[0] = frames[pMP3->pcmFramesConsumedInMP3Frame] / 32768.0f;
+                    pFramesOutF[1] = frames[pMP3->pcmFramesConsumedInMP3Frame] / 32768.0f;
                 }
             } else {
                 if (pMP3->channels == 1) {
                     // Stereo -> Mono
                     float sample = 0;
-                    sample += frames[(pMP3->framesConsumed*pMP3->frameChannels)+0] / 32768.0f;
-                    sample += frames[(pMP3->framesConsumed*pMP3->frameChannels)+1] / 32768.0f;
+                    sample += frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+0] / 32768.0f;
+                    sample += frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+1] / 32768.0f;
                     pFramesOutF[0] = sample * 0.5f;
                 } else {
                     // Stereo -> Stereo
-                    pFramesOutF[0] = frames[(pMP3->framesConsumed*pMP3->frameChannels)+0] / 32768.0f;
-                    pFramesOutF[1] = frames[(pMP3->framesConsumed*pMP3->frameChannels)+1] / 32768.0f;
+                    pFramesOutF[0] = frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+0] / 32768.0f;
+                    pFramesOutF[1] = frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+1] / 32768.0f;
                 }
             }
 #else
-            if (pMP3->frameChannels == 1) {
+            if (pMP3->mp3FrameChannels == 1) {
                 if (pMP3->channels == 1) {
                     // Mono -> Mono.
-                    pFramesOutF[0] = frames[pMP3->framesConsumed];
+                    pFramesOutF[0] = frames[pMP3->pcmFramesConsumedInMP3Frame];
                 } else {
                     // Mono -> Stereo.
-                    pFramesOutF[0] = frames[pMP3->framesConsumed];
-                    pFramesOutF[1] = frames[pMP3->framesConsumed];
+                    pFramesOutF[0] = frames[pMP3->pcmFramesConsumedInMP3Frame];
+                    pFramesOutF[1] = frames[pMP3->pcmFramesConsumedInMP3Frame];
                 }
             } else {
                 if (pMP3->channels == 1) {
                     // Stereo -> Mono
                     float sample = 0;
-                    sample += frames[(pMP3->framesConsumed*pMP3->frameChannels)+0];
-                    sample += frames[(pMP3->framesConsumed*pMP3->frameChannels)+1];
+                    sample += frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+0];
+                    sample += frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+1];
                     pFramesOutF[0] = sample * 0.5f;
                 } else {
                     // Stereo -> Stereo
-                    pFramesOutF[0] = frames[(pMP3->framesConsumed*pMP3->frameChannels)+0];
-                    pFramesOutF[1] = frames[(pMP3->framesConsumed*pMP3->frameChannels)+1];
+                    pFramesOutF[0] = frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+0];
+                    pFramesOutF[1] = frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+1];
                 }
             }
 #endif
 
-            pMP3->framesConsumed += 1;
-            pMP3->framesRemaining -= 1;
-            frameCount -= 1;
+            pMP3->pcmFramesConsumedInMP3Frame += 1;
+            pMP3->pcmFramesRemainingInMP3Frame -= 1;
             totalFramesRead += 1;
+            frameCount -= 1;
             pFramesOutF += pSRC->config.channels;
         }
 
@@ -2530,11 +2570,11 @@ static drmp3_uint64 drmp3_read_src(drmp3_src* pSRC, drmp3_uint64 frameCount, voi
             break;
         }
 
-        drmp3_assert(pMP3->framesRemaining == 0);
+        drmp3_assert(pMP3->pcmFramesRemainingInMP3Frame == 0);
 
         // At this point we have exhausted our in-memory buffer so we need to re-fill. Note that the sample rate may have changed
         // at this point which means we'll also need to update our sample rate conversion pipeline.
-        if (!drmp3_decode_next_frame(pMP3)) {
+        if (drmp3_decode_next_frame(pMP3) == 0) {
             break;
         }
     }
@@ -2710,7 +2750,9 @@ drmp3_bool32 drmp3_init_file(drmp3* pMP3, const char* filePath, const drmp3_conf
 
 void drmp3_uninit(drmp3* pMP3)
 {
-    if (pMP3 == NULL) return;
+    if (pMP3 == NULL) {
+        return;
+    }
     
 #ifndef DR_MP3_NO_STDIO
     if (pMP3->onRead == drmp3__on_read_stdio) {
@@ -2721,9 +2763,11 @@ void drmp3_uninit(drmp3* pMP3)
     drmp3_free(pMP3->pData);
 }
 
-drmp3_uint64 drmp3_read_f32(drmp3* pMP3, drmp3_uint64 framesToRead, float* pBufferOut)
+drmp3_uint64 drmp3_read_pcm_frames_f32(drmp3* pMP3, drmp3_uint64 framesToRead, float* pBufferOut)
 {
-    if (pMP3 == NULL || pMP3->onRead == NULL) return 0;
+    if (pMP3 == NULL || pMP3->onRead == NULL) {
+        return 0;
+    }
 
     drmp3_uint64 totalFramesRead = 0;
 
@@ -2735,7 +2779,7 @@ drmp3_uint64 drmp3_read_f32(drmp3* pMP3, drmp3_uint64 framesToRead, float* pBuff
                 framesToReadRightNow = framesToRead;
             }
 
-            drmp3_uint64 framesJustRead = drmp3_read_f32(pMP3, framesToReadRightNow, temp);
+            drmp3_uint64 framesJustRead = drmp3_read_pcm_frames_f32(pMP3, framesToReadRightNow, temp);
             if (framesJustRead == 0) {
                 break;
             }
@@ -2745,40 +2789,191 @@ drmp3_uint64 drmp3_read_f32(drmp3* pMP3, drmp3_uint64 framesToRead, float* pBuff
         }
     } else {
         totalFramesRead = drmp3_src_read_frames_ex(&pMP3->src, framesToRead, pBufferOut, DRMP3_TRUE);
+        pMP3->currentPCMFrame += totalFramesRead;
     }
 
     return totalFramesRead;
 }
 
-drmp3_bool32 drmp3_seek_to_frame(drmp3* pMP3, drmp3_uint64 frameIndex)
+drmp3_bool32 drmp3_seek_to_start_of_stream(drmp3* pMP3)
 {
-    if (pMP3 == NULL || pMP3->onSeek == NULL) return DRMP3_FALSE;
+    drmp3_assert(pMP3 != NULL);
+    drmp3_assert(pMP3->onSeek != NULL);
 
     // Seek to the start of the stream to begin with.
-    if (!pMP3->onSeek(pMP3->pUserData, 0, drmp3_seek_origin_start)) {
+    if (!drmp3__on_seek(pMP3, 0, drmp3_seek_origin_start)) {
         return DRMP3_FALSE;
     }
 
     // Clear any cached data.
-    pMP3->framesConsumed = 0;
-    pMP3->framesRemaining = 0;
+    pMP3->pcmFramesConsumedInMP3Frame = 0;
+    pMP3->pcmFramesRemainingInMP3Frame = 0;
+    pMP3->currentPCMFrame = 0;
     pMP3->dataSize = 0;
     pMP3->atEnd = DRMP3_FALSE;
 
-    // TODO: Optimize.
-    //
-    // This is inefficient. We simply read frames from the start of the stream.
-    drmp3_uint64 framesRead = drmp3_read_f32(pMP3, frameIndex, NULL);
-    if (framesRead != frameIndex) {
+    return DRMP3_TRUE;
+}
+
+drmp3_bool32 drmp3_seek_to_pcm_frame__brute_force(drmp3* pMP3, drmp3_uint64 frameIndex)
+{
+    drmp3_assert(pMP3 != NULL);
+
+    if (frameIndex == pMP3->currentPCMFrame) {
+        return DRMP3_TRUE;
+    }
+
+    // If we're moving foward we just read from where we're at. Otherwise we need to move back to the start of
+    // the stream and read from the beginning.
+    drmp3_uint64 framesToReadAndDiscard;
+    if (frameIndex >= pMP3->currentPCMFrame) {
+        // Moving foward.
+        framesToReadAndDiscard = frameIndex - pMP3->currentPCMFrame;
+    } else {
+        // Moving backward. Move to the start of the stream and then move forward.
+        framesToReadAndDiscard = frameIndex;
+        if (!drmp3_seek_to_start_of_stream(pMP3)) {
+            return DRMP3_FALSE;
+        }
+    }
+
+    // MP3 is a bit annoying when it comes to seeking because of the bit reservoir. It basically means that an MP3 frame can possibly
+    // depend on some of the data of prior frames. This means it's not as simple as seeking to the first byte of the MP3 frame that
+    // contains the sample because that MP3 frame will need the data from the previous MP3 frame (which we just seeked past!). To
+    // resolve this we seek past a number of MP3 frames up to a point, and then read-and-discard the remainder.
+    drmp3_uint64 maxFramesToReadAndDiscard = DRMP3_MAX_PCM_FRAMES_PER_MP3_FRAME * 3;
+
+    // First get rid of anything that's still sitting in the buffer.
+    if (framesToReadAndDiscard > maxFramesToReadAndDiscard && framesToReadAndDiscard > pMP3->pcmFramesRemainingInMP3Frame) {
+        framesToReadAndDiscard            -= pMP3->pcmFramesRemainingInMP3Frame;
+        pMP3->currentPCMFrame             += pMP3->pcmFramesRemainingInMP3Frame;
+        pMP3->pcmFramesConsumedInMP3Frame += pMP3->pcmFramesRemainingInMP3Frame;
+        pMP3->pcmFramesRemainingInMP3Frame = 0;
+    }
+
+    // Now get rid of leading whole frames.
+    while (framesToReadAndDiscard > maxFramesToReadAndDiscard) {
+        drmp3_uint32 pcmFramesSeeked = drmp3_seek_next_frame(pMP3);
+        if (pcmFramesSeeked == 0) {
+            break;
+        }
+
+        framesToReadAndDiscard -= pcmFramesSeeked;
+    }
+
+    // The last step is to read-and-discard any remaining PCM frames to make it sample-exact.
+    drmp3_uint64 framesRead = drmp3_read_pcm_frames_f32(pMP3, framesToReadAndDiscard, NULL);
+    if (framesRead != framesToReadAndDiscard) {
         return DRMP3_FALSE;
     }
 
     return DRMP3_TRUE;
 }
 
+drmp3_bool32 drmp3_seek_to_pcm_frame(drmp3* pMP3, drmp3_uint64 frameIndex)
+{
+    if (pMP3 == NULL || pMP3->onSeek == NULL) {
+        return DRMP3_FALSE;
+    }
+
+    // We currently only support brute force seeking.
+    return drmp3_seek_to_pcm_frame__brute_force(pMP3, frameIndex);
+}
+
+drmp3_uint64 drmp3_get_pcm_frame_count(drmp3* pMP3)
+{
+    if (pMP3 == NULL) {
+        return 0;
+    }
+
+    // The way this works is we move back to the start of the stream, iterate over each MP3 frame and calculate the frame count based
+    // on our output sample rate, the seek back to the PCM frame we were sitting on before calling this function.
+
+    // The stream must support seeking for this to work.
+    if (pMP3->onSeek == NULL) {
+        return 0;
+    }
+
+    // We'll need to seek back to where we were, so grab the PCM frame we're currently sitting on so we can restore later.
+    drmp3_uint64 currentPCMFrame = pMP3->currentPCMFrame;
+    
+    if (!drmp3_seek_to_start_of_stream(pMP3)) {
+        return 0;
+    }
+
+    drmp3_uint64 totalPCMFrameCount = 0;
+    float totalPCMFrameCountFractionalPart = 0; // <-- With resampling there will be a fractional part to each MP3 frame that we need to accumulate.
+    for (;;) {
+        drmp3_uint32 pcmFramesInCurrentMP3FrameIn = drmp3_decode_next_frame_ex(pMP3, NULL);   // <-- Passing in NULL here will prevent decoding of the MP3 frame which should save time.
+        if (pcmFramesInCurrentMP3FrameIn == 0) {
+            break;
+        }
+
+        float srcRatio = (float)pMP3->mp3FrameSampleRate / (float)pMP3->sampleRate;
+        drmp3_assert(srcRatio > 0);
+
+        float        pcmFramesInCurrentMP3FrameOutF = totalPCMFrameCountFractionalPart + (pcmFramesInCurrentMP3FrameIn / srcRatio);
+        drmp3_uint32 pcmFramesInCurrentMP3FrameOut  = (drmp3_uint32)pcmFramesInCurrentMP3FrameOutF;
+        totalPCMFrameCountFractionalPart = pcmFramesInCurrentMP3FrameOutF - pcmFramesInCurrentMP3FrameOut;
+        totalPCMFrameCount += pcmFramesInCurrentMP3FrameOut;
+    }
 
+    // Finally, we need to seek back to where we were.
+    if (!drmp3_seek_to_start_of_stream(pMP3)) {
+        return 0;
+    }
+
+    if (!drmp3_seek_to_pcm_frame(pMP3, currentPCMFrame)) {
+        return 0;
+    }
+
+    return totalPCMFrameCount;
+}
 
-float* drmp3__full_decode_and_close_f32(drmp3* pMP3, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount)
+drmp3_uint64 drmp3_get_mp3_frame_count(drmp3* pMP3)
+{
+    if (pMP3 == NULL) {
+        return 0;
+    }
+
+    // This works the same way as drmp3_get_pcm_frame_count() - move to the start, count MP3 frames, move back to the previous position.
+
+    // The stream must support seeking for this to work.
+    if (pMP3->onSeek == NULL) {
+        return 0;
+    }
+
+    // We'll need to seek back to where we were, so grab the PCM frame we're currently sitting on so we can restore later.
+    drmp3_uint64 currentPCMFrame = pMP3->currentPCMFrame;
+    
+    if (!drmp3_seek_to_start_of_stream(pMP3)) {
+        return 0;
+    }
+
+    drmp3_uint64 totalMP3FrameCount = 0;
+    for (;;) {
+        drmp3_uint32 pcmFramesInCurrentMP3FrameIn = drmp3_decode_next_frame_ex(pMP3, NULL);
+        if (pcmFramesInCurrentMP3FrameIn == 0) {
+            break;
+        }
+
+        totalMP3FrameCount += 1;
+    }
+
+    // Finally, we need to seek back to where we were.
+    if (!drmp3_seek_to_start_of_stream(pMP3)) {
+        return 0;
+    }
+
+    if (!drmp3_seek_to_pcm_frame(pMP3, currentPCMFrame)) {
+        return 0;
+    }
+
+    return totalMP3FrameCount;
+}
+
+
+float* drmp3__full_read_and_close_f32(drmp3* pMP3, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount)
 {
     drmp3_assert(pMP3 != NULL);
 
@@ -2789,7 +2984,7 @@ float* drmp3__full_decode_and_close_f32(drmp3* pMP3, drmp3_config* pConfig, drmp
     float temp[4096];
     for (;;) {
         drmp3_uint64 framesToReadRightNow = drmp3_countof(temp) / pMP3->channels;
-        drmp3_uint64 framesJustRead = drmp3_read_f32(pMP3, framesToReadRightNow, temp);
+        drmp3_uint64 framesJustRead = drmp3_read_pcm_frames_f32(pMP3, framesToReadRightNow, temp);
         if (framesJustRead == 0) {
             break;
         }
@@ -2835,35 +3030,35 @@ float* drmp3__full_decode_and_close_f32(drmp3* pMP3, drmp3_config* pConfig, drmp
     return pFrames;
 }
 
-float* drmp3_open_and_decode_f32(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount)
+float* drmp3_open_and_read_f32(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount)
 {
     drmp3 mp3;
     if (!drmp3_init(&mp3, onRead, onSeek, pUserData, pConfig)) {
         return NULL;
     }
 
-    return drmp3__full_decode_and_close_f32(&mp3, pConfig, pTotalFrameCount);
+    return drmp3__full_read_and_close_f32(&mp3, pConfig, pTotalFrameCount);
 }
 
-float* drmp3_open_and_decode_memory_f32(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount)
+float* drmp3_open_memory_and_read_f32(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount)
 {
     drmp3 mp3;
     if (!drmp3_init_memory(&mp3, pData, dataSize, pConfig)) {
         return NULL;
     }
 
-    return drmp3__full_decode_and_close_f32(&mp3, pConfig, pTotalFrameCount);
+    return drmp3__full_read_and_close_f32(&mp3, pConfig, pTotalFrameCount);
 }
 
 #ifndef DR_MP3_NO_STDIO
-float* drmp3_open_and_decode_file_f32(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount)
+float* drmp3_open_file_and_read_f32(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount)
 {
     drmp3 mp3;
     if (!drmp3_init_file(&mp3, filePath, pConfig)) {
         return NULL;
     }
 
-    return drmp3__full_decode_and_close_f32(&mp3, pConfig, pTotalFrameCount);
+    return drmp3__full_read_and_close_f32(&mp3, pConfig, pTotalFrameCount);
 }
 #endif
 
@@ -2890,7 +3085,18 @@ void drmp3_free(void* p)
 
 
 // REVISION HISTORY
-// ===============
+// ================
+//
+// v0.4.0 - 2018-xx-xx
+//   - API CHANGE: Rename some APIs:
+//     - drmp3_read_f32 -> to drmp3_read_pcm_frames_f32
+//     - drmp3_seek_to_frame -> drmp3_seek_to_pcm_frame
+//     - drmp3_open_and_decode_f32 -> drmp3_open_and_read_f32
+//     - drmp3_open_and_decode_memory_f32 -> drmp3_open_memory_and_read_f32
+//     - drmp3_open_and_decode_file_f32 -> drmp3_open_file_and_read_f32
+//   - Add drmp3_get_pcm_frame_count().
+//   - Add drmp3_get_mp3_frame_count().
+//   - Improve seeking performance.
 //
 // v0.3.2 - 2018-09-11
 //   - Fix a couple of memory leaks.