Render audio in a thread creates deadlock

I’m trying to render audio in a separate thread, in order to avoid blocking the message thread. So I can show a progress bar, reporting the export status.

But strangely the function call hangs when calling renderer->processBlock (buffer,midiBuffer);. it looks to me like a deadlock. you can see the call stacks when the hanging happens. And the rendering function is down below.

How can I correctly rendering audio in separate thread? Or what’s the right way to render the audio without completely blocking the gui thread. Thanks in advance!

void run() override
        {
                outputFile.deleteFile(); // Make sure there aren't any old versions of the file left over
                OutputStream* outStream (outputFile.createOutputStream().release());
                outStream->setPosition (0);

                double sampleRate = 44100.0;
                unsigned int numChannels = 2;
                int bitDepth = 16;
                StringPairArray metadata (true);
                metadata.set("title", String (outputFile.getFileName()));
                metadata.set("samplerate", String (sampleRate));
                metadata.set("bitdepth", String (bitDepth));
                metadata.set("numChannels", String (numChannels));

                std::unique_ptr<AiffAudioFormat> aiffAudioFormat  = std::make_unique<AiffAudioFormat> ();
                std::unique_ptr<AudioFormatWriter> writer {aiffAudioFormat->createWriterFor (outStream, sampleRate,n umChannels, bitDepth, metadata, 0)};

                std::shared_ptr<AudioProcessorGraph> renderer = project->getGraph();
                const ScopedLock sl (renderer->getCallbackLock());
                
                const int numSamples = 1024;
                renderer->prepareToPlay (sampleRate, numSamples);
                renderer->setNonRealtime (true);

                int numOutputChannels = (int) numChannels;

                AudioBuffer<float> buffer {numOutputChannels, numSamples};
                MidiBuffer midiBuffer;

                int64 totalSamples = project->getTotalSamples();

                int64 playheadPosition = 0;
                project->transport->setExporting (true);
                
                int thingsToDo = (int) (totalSamples / numSamples);
                for (int i = 0; i < thingsToDo; ++i)
                {
                    renderer->processBlock (buffer,midiBuffer);
                    writer->writeFromAudioSampleBuffer (buffer, 0, numSamples);
                    project->transport->process (project->transport->getTransportState(), buffer, midiBuffer);

                    auto transport = project->transport;
                    if (auto transportPtr = transport)
                        renderer->setPlayHead (transportPtr.get());
                    
                    playheadPosition += numSamples;
                }
            project->transport->setExporting (false);
        }

Is this the same lock? That would explain the deadlock, as it would be held whilst then trying to processBlock in the loop further down.

The AudioProcessorGraph can only render on one thread. That’s why there’s the lock you are seeing. I guess the graph is still running on the audio thread while you want to export? You could either stop calling the graph on the audio thread during the export (which will involve waiting for it to stop) or just construct a completely separate graph object from your data and use that for the export. + what asimilon said… you can’t lock there. It’s too early and the graph locks itself when it is building.

1 Like

@asimilon I did try exporting without that lock but the same dead lock happens

@pflugshaupt could you give an example of how to correctly stop calling the graph on the audio thread and wait for it to stop? I tried calling this function: suspendProcessing(true). But I am not sure if this is the right thing to do and how to wait for it to stop.

I’m also using suspendProcessing() in my plugins and it works as expected. It is thread safe. The code would look like this:

// prepare data
suspendProcessing(true);
// set new data 
suspendProcessing(false);

suspendProcessing(true); blocks until the processing block is finished if required. After that, it suspends processing until you set suspendProcessing(false);. Chances are big that it does not suspend if you keep the suspend window small. For example if you only swap a pointer.

Edit: this works for plugins

1 Like