AudioFormatWriter leak

Hello,
This part of code would work “before” (some time ago, I don’t know exactly):



const File strippedLastRecordedSound = UPIAssetManager::getUserRecordedSounds().getChildFile (lastRecordedSound.getFileNameWithoutExtension()+"tmpStripped.wav");
if (auto fos = strippedLastRecordedSound.createOutputStream())
			{
				if (bufferToStrip.getNumSamples() > 0)
				{
					WavAudioFormat wavFormat;
                    std::unique_ptr <AudioFormatWriter> afw (wavFormat.createWriterFor(fos.get(), sampleRate, bufferToStrip.getNumChannels(), 16, StringPairArray(), 0));
					afw->writeFromAudioSampleBuffer (bufferToStrip, 0, bufferToStrip.getNumSamples());
					afw.reset();
				}
			}


Now, replacing afw.reset() by afw.release() goes without crashing, but may provoke a leak?

(In both cases, afw is written on disk. )

1 Like

Replacing reset with release will leak, so this is not advised.

The docs for createWriterFor say this:

@param streamToWriteTo

the stream that the data will go to - this will be
deleted by the AudioFormatWriter object when it's no longer
needed. If no AudioFormatWriter can be created by this method,
the stream will NOT be deleted, so that the caller can re-use it
to try to open a different format, etc

This means if afw is not nullptr after the call to createWriterFor, then you should call fos.release() immediately in order to avoid the stream being deleted twice, first by the destructor of the AudioFormatWriter and then by the destructor of the unique_ptr<FileOutputStream>. There is no reason to call afw.reset() in the location shown in your snippet, as afw will be destroyed at the following } anyway.

Having this code, as I understand you describe, I still get these leaked objects when I close the app.

void drag_and_drop::mouseDrag(const MouseEvent &) {
        ...
	auto fout = FileOutputStream( outf );

	if ( fout.openedOk() ) {
		WavAudioFormat format;
		const auto [ buf, sample_rate ] = m_drag_cb();
		unique_ptr<AudioFormatWriter> writer(
		  format.createWriterFor( &fout, sample_rate, AudioChannelSet::stereo(), 16, StringPairArray{}, 0 ) );
		writer->writeFromAudioSampleBuffer( buf, 0, buf.getNumSamples() );
		writer->flush();

		writer.release();
		performExternalDragDropOfFiles( { outf.getFullPathName() }, false, nullptr, []( void ) {} );
	}
}

*** Leaked objects detected: 1 instance(s) of class WavAudioFormatWriter
JUCE Assertion failure in juce_LeakedObjectDetector.h:92
*** Leaked objects detected: 1 instance(s) of class AudioFormatWriter
JUCE Assertion failure in juce_LeakedObjectDetector.h:92
*** Leaked objects detected: 11 instance(s) of class MemoryBlock
JUCE Assertion failure in juce_LeakedObjectDetector.h:92
*** Leaked objects detected: 1 instance(s) of class BigInteger
JUCE Assertion failure in juce_LeakedObjectDetector.h:92

If you look at the AudioRecordingDemo.h, it shows how to write a file using the WavAudioFormat.

In short:

File file; // destination file

if (auto fileStream = file.createOutputStream()) // heap-based stream
{
    if (auto writer = WavAudioFormat{}.createWriterFor (fileStream.get(), sampleRate, 1, 16, {}, 0))
    {
        fileStream.release(); // ownership has been passed to the writer

        writer->writeFromAudioSampleBuffer (...);
    }
}

As explained above, the input stream will be automatically deleted by the AudioFormatWriter if creating the writer is successful. This means you must use a heap-allocated output stream so that the stream can be deleted successfully later on. In your code, you’ve used a stack-allocated stream, and this will cause problems when the writer attempts to free the stream.

If your AudioFormatWriter is managed by a unique_ptr, there’s no need to reset or release it manually. Calling writer.release() prevents the unique_ptr from freeing the AudioFormatWriter at the end of the scope, causing the leak. I suggest removing this line.

1 Like

Thank you so much, both for the immediate response and because this works marvels. No more memory leak.

Thank you! :pray: