Encoding / Decoding Audio Formats in Memory in Real Time


I’m working on a system that requires realtime encoding / decoding of compressed audio formats. This is to perform some analysis on the artefacts introduced by the encoding process.

My approach so far is to create an AudioFormatWriter and AudioFormatReader and to get the process up and running I’m using a WavAudioFormat reader and writer (to avoid any potential complications with compressed formats).

Something like this:

WavAudioFormat wavFormat;
mOutputStream = new MemoryOutputStream(mOutputBlock, false);
mWriter = wavFormat.createWriterFor(mOutputStream, 44100, 1, 32, {}, 0);

mInputStream = new MemoryInputStream(mOutputBlock, false);
mReader = wavFormat.createReaderFor(mInputStream, true);

And then in the render method I’m doing this:

float** channels = buffers; // internal audio buffer

mWriter->writeFromFloatArrays(channels, 1, numFrames);

bool success = mReader->read(channels, 1, 0, numFrames);

But all I’m getting is silence from the AudioFormatReader (mReader).

Any ideas?

The wave file has a size of zero samples until the writer is flushed or destroyed.
This is, because the number of samples is written in the header at the end of writing, when the size is known.

But if you want it to run continuously, you need a format that allows streaming. Not sure, if there is any in JUCE…

It’s probably not going to work through the Juce abstractions, you may need to try using the encoder/decoder libraries directly.

Thanks. The writer code is working fine. I can copy the data in the MemoryBlock assigned to the MemoryOutputStream into a buffer via this code:

mWriter->writeFromFloatArrays(channels, 1, numFrames);

float* inputBuffer = buffer.getChannelBuffer(0);
mOutputBlock.copyTo((void*)inputBuffer, 0, framesInBytes);

No joy from the AudioFormatReader though. The MemoryInputStream shares the same MemoryBlock as the MemoryOutputStream.

Obviously for compressed formats the code above isn’t much use if I can’t pass it to a suitable decoder.

I feel like I’m doing something fundamentally wrong…

Like I tried to explain before, the mOutputStream has the length of zero samples. You can verify that by stepping into the mReader.

Also, there is no need to call setPosition on the InputStream, once you handed it over to the AudioFormatReader. On the contrary, I guess it could blow the whole thing up.

You might be able to circumvent the problem by calling


before creating the reader. Then it will see at least the already written few samples.
But the JUCE AudioFormatReaders will read the header only once, so even if you append samples later, the reader will see the input as exceeded and stop reading, producing silence.

That is what I tried to explain, when I said you would need a format, that allows streaming, i.e. not knowing what the size will be.

It has nothing to do with the encoding/compression of your data, it is usually a trait of the container.

I see. Thanks daniel.

If you’re doing this in memory, why are you using an audio format at all? Just stick your samples in a FIFO, and then read from the FIFO.

I guess it’s for A/B testing the encoder (or doing a cancelation test)…

This is for a system that compares the original uncompressed audio against various compressed formats like MP3, AAC, Ogg etc in real time.

I was looking for a way to encode to a compressed format and decode back to floating point in real-time so the WAV example was just to illustrate the write / reader approach I had in mind for a memory based real time process.

Sounds like I’ll need to use the encoder libraries directly.

Right, yes, for that you probably would need to go down to the underlying libraries. Although you might be able to bodge something by periodically calling flush() on the writer, and then creating a new reader to read from it. (IIRC the tracktion engine will call flush() every second or so, which means that even if something goes wrong, the file up to that point will be readable)

1 Like

Thanks. I may attempt to bodge something. After all life is short. Perhaps I could modify the JUCE decoder routines to do what I want too.