mackron / dr_libs

Audio decoding libraries for C/C++, each in a single source file.
Other
1.27k stars 206 forks source link

[newbie] dr_mp3: drmp3_read_pcm_frames(...) from encoded buffer size #142

Closed Flix01 closed 4 years ago

Flix01 commented 4 years ago

Hello. First of all: thank you for making this wonderful header-only audio decoding library!

Second: I'm currently trying to use dr_mp3 to decode mp3 internet radios.

Thanks in advance for your help.

mackron commented 4 years ago

The first thing to decide is whether or not drmp3_read_pcm_frames() is the right tool for the job. Internet radio is one of the few scenarios where I feel like the low level API might be a better match. This is drmp3dec_decode_frame() which takes a raw data buffer and returns the number of PCM frames decoded and the number of bytes consumed (via the info parameter). Just something for you to consider in case it might be more suitable for you. Look at the code for drmp3_decode_next_frame_ex__callbacks() for example usage. Note that it decodes one MP3 frame which is made up of many PCM frames.

To answer your questions more directly:

mackron commented 4 years ago

Minor clarification, DRMP3_MIN_DATA_CHUNK_SIZE is not public so you won't be able to use that directly unless you put the implementation of dr_mp3 before wherever it is you use it. It is coded to 16KB.

Flix01 commented 4 years ago

Thanks for your detailed explanation.

I'll try to use the low-level API soon. Regarding my two questions:

If you want to use drmp3_read_pcm_frames(), initialize using the callback version of drmp3_init(). In your read callback, always return the number of bytes dr_mp3 requests - if you return less than this, dr_mp3 will treat it as the end of the stream and stop decoding (of course, if you actually are at the end of the stream you should return whatever you have available and no more). Each time you call drmp3_read_pcm_frames(), set your desired frame count to the capacity of your output buffer. The return value of drmp3_read_pcm_frames() will be the number of frames actually decoded. Run it in a loop.

Yes, that's what I'm already doing, basically just something like:

plain c code of the main callbacks I currently use. ```c static mp3 = {0}; /* ={}; in c++ */ #define MY_ENCODED_BUFFER_SIZE (4096*32*4 ) unsigned char encodedBuffer[MY_ENCODED_BUFFER_SIZE]; size_t encodedBufferSize=0; // dr_mp3 callback size_t mp3_callback(void* pUserData, void* pBufferOut, size_t bytesToRead) { // 3 dbg lines static size_t cnt = 0; printf("%lu) DBG mp3_callback: bytesToRead=[%lu/%lu]\n",cnt,bytesToRead,encodedBufferSize);fflush(stdout); ++cnt; assert(bytesToRead<=encodedBufferSize); memcpy(pBufferOut,&encodedBuffer[0],bytesToRead); memmove(&encodedBuffer[0],&encodedBuffer[bytesToRead],encodedBufferSize-bytesToRead); // slow... encodedBufferSize-=bytesToRead; return bytesToRead; } // curl body callback size_t stream_callback(char *ptr, size_t size, size_t nmemb, void *userdata) { size_t amount = size*nmemb, assert(encodedBufferSize+amount<=MY_ENCODED_BUFFER_SIZE); memcpy(&encodedBuffer[encodedBufferSize],ptr,amount); encodedBufferSize+=amount; if (mp3.channels==0) if (encodedBufferSize>SOME_QUANTITY) { if (drmp3_init(&mp3,mp3_callback,NULL/*onSeek*/,NULL/* pUserData*/, NULL)) { fprintf(stderr,"drmp3_init(...) OK.\n"); assert(mp3.channels>0); // Here I call drmp3_read_pcm_frames_s16() to decode some pcm frames [how many?] } } } else if (encodedBufferSize>SOME_OTHER_QUANTITY) { // Here I call drmp3_read_pcm_frames_s16() to decode some pcm frames [how many?] } return amount; } ```

Except that my stream never ends (but I guess I can return 0 from the curl body callback after a call to drmp3_uninit(&mp3); to change radio station). I can't simply set my desired frame count to the capacity of my pcm output buffer (not shown above) when I call drmp3_read_pcm_frames(), because otherwise drmp3 tries to read more encoded data than available. I'm trying to use the bitrate info I can extract from the curl header callback (not shown above) together with the sampleRate and the channels fields drmp3 can give me to calculate the number of pcm frames... it works, but it's not robust enough.

Moreover I would like code to handle connection drop and resume: that produces an mp3 stream broken at one point; sometimes I see that drmp3 reads a big amount of encoded data in these cases and hits the assert in mp3_callback (I suspect that maybe curl sends some other non-mp3 data when connection is back, but I still have to investigate: in any case it's not a priority at this point).

Forget about DRMP3_DATA_CHUNK_SIZE - that's an implementation detail. If you're using the low-level API that I mentioned above (drmp3dec_decode_frame()), your minimum data size passed into the function should actually be DRMP3_MIN_DATA_CHUNK_SIZE, unless you've approached the end of the stream in which case you just pass in as much as you've got (so yes, your assertion is correct about preventing decoding of samples that don't exist).

Minor clarification, DRMP3_MIN_DATA_CHUNK_SIZE is not public so you won't be able to use that directly unless you put the implementation of dr_mp3 before wherever it is you use it. It is coded to 16KB.

Got it! I used to set DRMP3_DATA_CHUNK_SIZE to the minimum (16384), to have the smallest read granularity and minimize the possibility of read overflows. But I'll try the low level API now.

No problem with hard-coding DRMP3_MIN_DATA_CHUNK_SIZE at all.

In short: I'll try the low-level API, reporting possible issues here.

Thanks for your help!

mackron commented 4 years ago

OK, so basically you want to know how many PCM frames will be decoded from an encoded buffer of a particular size? Nothing in dr_mp3 will do that for you directly. This requires running through the encoded data and, at a minimum, decoding the headers of each MP3 frame and adding up the frame counts. Doing this will require you to repeatedly call drmp3dec_decode_frame().

I think it's a good idea to consider the low-level API. With this one you would just loop until you run out of input data. If you want to go down that route, you may also want to consider using minimp3 directly - dr_mp3 is a wrapper around minimp3.

Flix01 commented 4 years ago

It works like a charm! And the code is even easier than before! Thank you for your help. Basically I just use 2 structs and 2 functions and that's all:

    drmp3dec mp3;
    drmp3dec_frame_info mp3info;
    drmp3dec_init(...);
    drmp3dec_decode_frame(...);

(it's so easy that I can even switch from dr_mp3.h to minimp3.h with an in-code definition).

Basically what I do is to only call drmp3dec_decode_frame(...) in steps of 16384 encoded buffer chunks. And code seems more robust when connection is lost and resumed too.

Maybe I'll make a gist when I clean up the code (unluckily using OpenAL, not miniaudio).

Thank you again.

Flix01 commented 4 years ago

Made a gist here mini_mp3_radio_decoder.c.