mackron / dr_libs

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

Support for streaming use-cases (i.e. write files without making a copy of the raw samples) #250

Closed kylepl closed 1 year ago

kylepl commented 1 year ago

Hi,

My use case is where I receive a stream of PCM data (this happens to be 16-bit integer), without any file - just the raw samples.

I then, occasionally (say once a second), want to send a file version of this data elsewhere, which requires it be in a file format. Assuming that I'm sending it as a .wav file, and that I can convert the PCM data once (on receiving the stream) to the internal format (though probably 16-bit integer already is fine), is there a tractable way with the existing APIs to be able to compute the bytes without the extra copy?

That would require some management by the clients, but my assumption is getting some sort of start_of_file_bytes and end_of_file_bytes given the format and the number of frame. Then, when I am transmitting this elsewhere, I can implement a callback functions that first copies the bytes from the header, then my PCM data, then the footer.

Essentially I would to avoid these repeated copies, at the cost of having to manage the in-memory representation of a file that is not just a contiguous buffer.

Thoughts? Thanks!

Kyle

mackron commented 1 year ago

I think this should work quite easily if I'm understanding your scenario, but I've not put it into practice myself personally.

Use drwav_init_write_sequential_pcm_frames(). This will take a PCM frame count (total number of samples divided by the channel count). That value will be written to the header (the non-sequential version will accumulate the value as you read and then seek back to the header and write it out when you uninitialize the encoder). It will also take a callback that will be fired when the encoder wants to write out data. This callback will be where you would transfer the data over the network.

Example (untested):

size_t wav_write_callback(void* pUserData, const void* pData, size_t bytesToWrite)
{
    // Transfer pData over the network. bytesToWrite the size of the data. Return the number of bytes actually
    // sent. If the return value is less than bytesToWrite, dr_wav will treat it as an error and abort. Make sure
    // you send the entire data.
}

...

// Initialize (this will output the header data).
if (!drwav_init_write_sequential_pcm_frames(pWav, pFormat, frameCount, wav_write_callback, pWriteCallbackData, NULL)) {
    // Error initializing encoder.
}

// Write audio data.
drwav_write_pcm_frames(pWav, frameCount, pAudioData); // Returns the number of frames actually written.

// Uninitialize. In sequential mode this won't send anything. In non-sequential mode, it will seek back to the
// start and write out the total number of frames that were written with drwav_write_pcm_frames(). This is
// why you need to use sequential mode when writing out over the network.
drwav_uninit(pWav);
kylepl commented 1 year ago

Ah, great, thanks for the explanation.

So previously I was using drwav_init_memory_write_sequential, which was have the library manage the memory, and this will instead just give me callbacks on writing, instead.

I'm gonna close this for now, and re-open if I fail to get it to work. Thanks!