1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-05-06 14:52:34 +02:00

Fix memory leak in RealtimeEffectManager

Signed-off-by: Emily Mabrey <emabrey@tenacityaudio.org>
This commit is contained in:
Emily Mabrey 2021-08-01 01:39:50 -04:00
parent 15c4f546f3
commit d69160975d
No known key found for this signature in database
GPG Key ID: 6F4EF47256A1B7DC
4 changed files with 213 additions and 224 deletions

View File

@ -635,11 +635,10 @@ struct AudioIoCallback::ScrubState : NonInterferingBase
mStopped.store( true, std::memory_order_relaxed ); mStopped.store( true, std::memory_order_relaxed );
} }
#if 0 #ifdef DRAG_SCRUB
// Needed only for the DRAG_SCRUB experiment // Needed only for the DRAG_SCRUB experiment
// Should make mS1 atomic then? // Should make mS1 atomic then?
double LastTrackTime() const double LastTrackTime() const {
{
// Needed by the main thread sometimes // Needed by the main thread sometimes
return mData.mS1.as_double() / mRate; return mData.mS1.as_double() / mRate;
} }
@ -909,14 +908,11 @@ class MidiThread final : public AudioThread {
// //
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
void AudioIO::Init() void AudioIO::Init() {
{ ugAudioIO.reset(new AudioIO());
ugAudioIO.reset(safenew AudioIO());
Get()->mThread->Run(); Get()->mThread->Run();
#ifdef EXPERIMENTAL_MIDI_OUT #if defined(EXPERIMENTAL_MIDI_OUT) && defined(USE_MIDI_THREAD)
#ifdef USE_MIDI_THREAD
Get()->mMidiThread->Run(); Get()->mMidiThread->Run();
#endif
#endif #endif
// Make sure device prefs are initialized // Make sure device prefs are initialized
@ -990,6 +986,7 @@ AudioIO::AudioIO()
#ifdef EXPERIMENTAL_AUTOMATED_INPUT_LEVEL_ADJUSTMENT #ifdef EXPERIMENTAL_AUTOMATED_INPUT_LEVEL_ADJUSTMENT
mAILAActive = false; mAILAActive = false;
#endif #endif
mStreamToken = 0; mStreamToken = 0;
mLastPaError = paNoError; mLastPaError = paNoError;
@ -1010,15 +1007,15 @@ AudioIO::AudioIO()
if (err != paNoError) { if (err != paNoError) {
auto errStr = XO("Could not find any audio devices.\n"); auto errStr = XO("Could not find any audio devices.\n");
errStr += XO("You will not be able to play or record audio.\n\n"); errStr += XO("You will not be able to play or record audio.\n\n");
wxString paErrStr = LAT1CTOWX(Pa_GetErrorText(err)); const wxString paErrStr = LAT1CTOWX(Pa_GetErrorText(err));
if (!paErrStr.empty()) if (!paErrStr.empty())
errStr += XO("Error: %s").Format( paErrStr ); errStr += XO("Error: %s").Format(paErrStr);
// XXX: we are in libaudacity, popping up dialogs not allowed! A // XXX: we are in libaudacity, popping up dialogs not allowed! A
// long-term solution will probably involve exceptions // long-term solution will probably involve exceptions
AudacityMessageBox( AudacityMessageBox(
errStr, errStr,
XO("Error Initializing Audio"), XO("Error Initializing Audio"),
wxICON_ERROR|wxOK); wxICON_ERROR | wxOK);
// Since PortAudio is not initialized, all calls to PortAudio // Since PortAudio is not initialized, all calls to PortAudio
// functions will fail. This will give reasonable behavior, since // functions will fail. This will give reasonable behavior, since
@ -1033,15 +1030,15 @@ AudioIO::AudioIO()
auto errStr = auto errStr =
XO("There was an error initializing the midi i/o layer.\n"); XO("There was an error initializing the midi i/o layer.\n");
errStr += XO("You will not be able to play midi.\n\n"); errStr += XO("You will not be able to play midi.\n\n");
wxString pmErrStr = LAT1CTOWX(Pm_GetErrorText(pmErr)); const wxString pmErrStr = LAT1CTOWX(Pm_GetErrorText(pmErr));
if (!pmErrStr.empty()) if (!pmErrStr.empty())
errStr += XO("Error: %s").Format( pmErrStr ); errStr += XO("Error: %s").Format(pmErrStr);
// XXX: we are in libaudacity, popping up dialogs not allowed! A // XXX: we are in libaudacity, popping up dialogs not allowed! A
// long-term solution will probably involve exceptions // long-term solution will probably involve exceptions
AudacityMessageBox( AudacityMessageBox(
errStr, errStr,
XO("Error Initializing Midi"), XO("Error Initializing Midi"),
wxICON_ERROR|wxOK); wxICON_ERROR | wxOK);
// Same logic for PortMidi as described above for PortAudio // Same logic for PortMidi as described above for PortAudio
} }
@ -2492,7 +2489,7 @@ void AudioIO::StopScrub()
mScrubState->Stop(); mScrubState->Stop();
} }
#if 0 #ifdef DRAG_SCRUB
// Only for DRAG_SCRUB // Only for DRAG_SCRUB
double AudioIO::GetLastScrubTime() const double AudioIO::GetLastScrubTime() const
{ {
@ -3843,19 +3840,18 @@ bool AudioIoCallback::FillOutputBuffers(
return true; return true;
} }
//Real time process section if (numPlaybackTracks == 0) {
{ CallbackCheckCompletion(mCallbackReturn, 0);
std::unique_ptr<AudioIOBufferHelper> bufHelper = std::make_unique<AudioIOBufferHelper>(numPlaybackChannels, framesPerBuffer); return false;
auto& em = RealtimeEffectManager::Get(); }
em.RealtimeProcessStart();
bool selected = false; bool selected = false;
int group = 0; int group = 0;
int chanCnt = 0; int chanCnt = 0;
auto& em = RealtimeEffectManager::Get();
// Choose a common size to take from all ring buffers // Choose a common size to take from all ring buffers
const auto toGet = std::min<size_t>(framesPerBuffer, GetCommonlyReadyPlayback()); const auto toGet = std::min<size_t>(framesPerBuffer, GetCommonlyReadyPlayback());
// The drop and dropQuickly booleans are so named for historical reasons. // The drop and dropQuickly booleans are so named for historical reasons.
// JKC: The original code attempted to be faster by doing nothing on silenced audio. // JKC: The original code attempted to be faster by doing nothing on silenced audio.
// This, IMHO, is 'premature optimisation'. Instead clearer and cleaner code would // This, IMHO, is 'premature optimisation'. Instead clearer and cleaner code would
@ -3871,55 +3867,50 @@ bool AudioIoCallback::FillOutputBuffers(
bool drop = false; // Track should become silent. bool drop = false; // Track should become silent.
bool dropQuickly = false; // Track has already been faded to silence. bool dropQuickly = false; // Track has already been faded to silence.
for (unsigned t = 0; t < numPlaybackTracks; t++) {
WaveTrack* vt = mPlaybackTracks[t].get();
bufHelper.get()->chans[chanCnt] = vt;
// TODO: more-than-two-channels decltype(framesPerBuffer) len = 0L;
auto nextTrack =
t + 1 < numPlaybackTracks
? mPlaybackTracks[t + 1].get()
: nullptr;
// First and last channel in this group (for example left and right std::unique_ptr<AudioIOBufferHelper> bufHelper = std::make_unique<AudioIOBufferHelper>(numPlaybackChannels, framesPerBuffer);
// channels of stereo). //Real time process section
bool firstChannel = vt->IsLeader(); {
bool lastChannel = !nextTrack || nextTrack->IsLeader(); em.RealtimeProcessStart();
for (unsigned int t = 0; t < numPlaybackTracks; t++) {
WaveTrack* channel_one = mPlaybackTracks[t].get();
bufHelper.get()->chans[chanCnt] = channel_one;
const WaveTrack* channel_two = t + 1 < numPlaybackTracks ? mPlaybackTracks[t + 1].get() : nullptr;
// First and last channel in this group (for example left and right channels of stereo).
const bool firstChannel = channel_one->IsLeader();
const bool lastChannel = !channel_two || channel_two->IsLeader();
if (firstChannel) { if (firstChannel) {
selected = vt->GetSelected(); selected = channel_one->GetSelected();
// IF mono THEN clear 'the other' channel. // IF mono THEN clear 'the other' channel.
if (lastChannel && (numPlaybackChannels > 1)) { if (lastChannel && (numPlaybackChannels > 1)) {
// TODO: more-than-two-channels // TODO: more-than-two-channels
memset(bufHelper.get()->tempBufs[1], 0, framesPerBuffer * sizeof(float)); memset(bufHelper.get()->tempBufs[1], 0, framesPerBuffer * sizeof(float));
} }
drop = TrackShouldBeSilent(*vt); dropQuickly = drop = TrackShouldBeSilent(*channel_one);
dropQuickly = drop;
} }
if (mbMicroFades) if (mbMicroFades)
dropQuickly = dropQuickly && TrackHasBeenFadedOut(*vt); dropQuickly = dropQuickly && TrackHasBeenFadedOut(*channel_one);
decltype(framesPerBuffer) len = 0;
if (dropQuickly) { if (dropQuickly) {
len = mPlaybackBuffers[t]->Discard(toGet); len = mPlaybackBuffers[t]->Discard(toGet);
// keep going here. // keep going here.
// we may still need to issue a paComplete. // we may still need to issue a paComplete.
} else { } else {
len = mPlaybackBuffers[t]->Get((samplePtr)bufHelper.get()->tempBufs[chanCnt], const auto ptrToSample = (samplePtr)bufHelper.get()->tempBufs[chanCnt];
floatSample,
toGet); len = mPlaybackBuffers[t]->Get(ptrToSample, floatSample, toGet);
// wxASSERT( len == toGet );
if (len < framesPerBuffer) if (len < framesPerBuffer) {
// This used to happen normally at the end of non-looping //Make sure we fill the output buffer even if we have insufficient length
// plays, but it can also be an anomalous case where the memset(&bufHelper.get()->tempBufs[chanCnt][len], 0, (framesPerBuffer - len) * sizeof(float));
// supply from FillBuffers fails to keep up with the }
// real-time demand in this thread (see bug 1932). We
// must supply something to the sound card, so pad it with
// zeroes and not random garbage.
memset((void*)&bufHelper.get()->tempBufs[chanCnt][len], 0,
(framesPerBuffer - len) * sizeof(float));
chanCnt++; chanCnt++;
} }
@ -3932,19 +3923,19 @@ bool AudioIoCallback::FillOutputBuffers(
// available, so maxLen ought to increase from 0 only once // available, so maxLen ought to increase from 0 only once
mMaxFramesOutput = std::max(mMaxFramesOutput, len); mMaxFramesOutput = std::max(mMaxFramesOutput, len);
if (!lastChannel) if (lastChannel) {
continue;
// Last channel of a track seen now // Last channel of a track seen now
len = mMaxFramesOutput; len = mMaxFramesOutput;
if (!dropQuickly && selected) if (!dropQuickly && selected)
len = em.RealtimeProcess(group, chanCnt, bufHelper.get()->tempBufs, len); len = em.RealtimeProcess(group, chanCnt, bufHelper.get()->tempBufs, len);
group++; group++;
CallbackCheckCompletion(mCallbackReturn, len); CallbackCheckCompletion(mCallbackReturn, len);
if (dropQuickly) // no samples to process, they've been discarded
continue; if (!dropQuickly) {
// no samples to process, they've been discarded
// Our channels aren't silent. We need to pass their data on. // Our channels aren't silent. We need to pass their data on.
// //
@ -3956,30 +3947,30 @@ bool AudioIoCallback::FillOutputBuffers(
// //
// Each channel in the tracks can output to more than one channel on the device. // Each channel in the tracks can output to more than one channel on the device.
// For example mono channels output to both left and right output channels. // For example mono channels output to both left and right output channels.
if (len > 0) for (int c = 0; c < chanCnt; c++) { const bool len_in_bounds = len > 0;
vt = bufHelper.get()->chans[c];
if (vt->GetChannelIgnoringPan() == Track::LeftChannel || vt->GetChannelIgnoringPan() == Track::MonoChannel) for (int c = 0; c < chanCnt && len_in_bounds; c++) {
AddToOutputChannel(0, outputMeterFloats, outputFloats, bufHelper.get()->tempBufs[c], drop, len, vt); const auto channel = bufHelper.get()->chans[c];
if (vt->GetChannelIgnoringPan() == Track::RightChannel || vt->GetChannelIgnoringPan() == Track::MonoChannel) const bool playLeftChannel = channel->GetChannelIgnoringPan() == Track::LeftChannel || channel->GetChannelIgnoringPan() == Track::MonoChannel;
AddToOutputChannel(1, outputMeterFloats, outputFloats, bufHelper.get()->tempBufs[c], drop, len, vt); const bool playRightChannel = channel->GetChannelIgnoringPan() == Track::RightChannel || channel->GetChannelIgnoringPan() == Track::MonoChannel;
if (playLeftChannel)
AddToOutputChannel(0, outputMeterFloats, outputFloats, bufHelper.get()->tempBufs[c], drop, len, channel);
if (playRightChannel)
AddToOutputChannel(1, outputMeterFloats, outputFloats, bufHelper.get()->tempBufs[c], drop, len, channel);
} }
chanCnt = 0; chanCnt = 0;
} }
}
}
// Poke: If there are no playback tracks, then the earlier check
// about the time indicator being past the end won't happen;
// do it here instead (but not if looping or scrubbing)
if (numPlaybackTracks == 0)
CallbackCheckCompletion(mCallbackReturn, 0);
// wxASSERT( maxLen == toGet ); // wxASSERT( maxLen == toGet );
em.RealtimeProcessEnd(); em.RealtimeProcessEnd();
delete bufHelper.release(); bufHelper.reset();
} }
mLastPlaybackTimeMillis = ::wxGetUTCTimeMillis(); mLastPlaybackTimeMillis = ::wxGetUTCTimeMillis();

View File

@ -11,9 +11,6 @@ Paul Licameli split from AudioIO.h
#ifndef __AUDACITY_AUDIO_IO_BASE__ #ifndef __AUDACITY_AUDIO_IO_BASE__
#define __AUDACITY_AUDIO_IO_BASE__ #define __AUDACITY_AUDIO_IO_BASE__
#include <cfloat> #include <cfloat>
#include <functional> #include <functional>
#include <vector> #include <vector>
@ -38,7 +35,7 @@ class BoundedEnvelope;
// Windows build needs complete type for parameter of wxWeakRef // Windows build needs complete type for parameter of wxWeakRef
// class MeterPanelBase; // class MeterPanelBase;
#include "widgets/MeterPanelBase.h" #include "widgets/MeterPanelBase.h"
using PRCrossfadeData = std::vector< std::vector < float > >; using PRCrossfadeData = std::vector< std::vector<float>>;
#define BAD_STREAM_TIME (-DBL_MAX) #define BAD_STREAM_TIME (-DBL_MAX)

View File

@ -33,14 +33,8 @@ class AudioIOBufferHelper
~AudioIOBufferHelper() { ~AudioIOBufferHelper() {
delete[] tempBufs[0]; delete[] tempBufs[0];
delete[] tempBufs; delete[] tempBufs;
tempBufs = nullptr;
delete[] chans; delete[] chans;
chans = nullptr;
} }
}; };

View File

@ -307,50 +307,46 @@ void RealtimeEffectManager::RealtimeProcessStart()
// //
size_t RealtimeEffectManager::RealtimeProcess(int group, unsigned chans, float **buffers, size_t numSamples) size_t RealtimeEffectManager::RealtimeProcess(int group, unsigned chans, float **buffers, size_t numSamples)
{ {
// Allocate the in/out buffer arrays
auto ibuf = new float* [chans];
auto obuf = new float* [chans];
float* temp = safenew float[numSamples];
const size_t memcpy_size = numSamples * sizeof(float);
// Allocate new output buffers and copy buffer input into newly allocated input buffers
for (unsigned int i = 0; i < chans; i++) {
ibuf[i] = new float[numSamples];
memcpy(ibuf[i], buffers[i], memcpy_size);
obuf[i] = new float[numSamples];
}
// Protect ourselves from the main thread // Protect ourselves from the main thread
mRealtimeLock.Enter(); mRealtimeLock.Enter();
// Can be suspended because of the audio stream being paused or because effects // Can be suspended because of the audio stream being paused or because effects
// have been suspended, so allow the samples to pass as-is. // have been suspended, so in that case do nothing.
if (mRealtimeSuspended || mStates.empty()) if (!mRealtimeSuspended && !mStates.empty()){
{
mRealtimeLock.Leave();
return numSamples;
}
// Remember when we started so we can calculate the amount of latency we // Remember when we started so we can calculate the amount of latency we
// are introducing // are introducing
wxMilliClock_t start = wxGetUTCTimeMillis(); wxMilliClock_t start = wxGetUTCTimeMillis();
// Allocate the in/out buffer arrays
auto ibuf = new float* [chans];
auto obuf = new float* [chans];
// And populate the input with the buffers we've been given while allocating
// NEW output buffers
for (unsigned int i = 0; i < chans; i++)
{
ibuf[i] = buffers[i];
obuf[i] = new float[numSamples];
}
// Now call each effect in the chain while swapping buffer pointers to feed the // Now call each effect in the chain while swapping buffer pointers to feed the
// output of one effect as the input to the next effect // output of one effect as the input to the next effect
size_t called = 0; size_t called = 0;
for (auto &state : mStates) for (auto& state : mStates) {
{ if (state->IsRealtimeActive()) {
if (state->IsRealtimeActive())
{
state->RealtimeProcess(group, chans, ibuf, obuf, numSamples); state->RealtimeProcess(group, chans, ibuf, obuf, numSamples);
called++; called++;
} }
for (unsigned int j = 0; j < chans; j++) for (size_t j = 0; j < chans; j++) {
{ memcpy(temp, ibuf[j], memcpy_size);
float *temp; memcpy(ibuf[j], obuf[j], memcpy_size);
temp = ibuf[j]; memcpy(obuf[j], temp, memcpy_size);
ibuf[j] = obuf[j];
obuf[j] = temp;
} }
} }
@ -358,22 +354,32 @@ size_t RealtimeEffectManager::RealtimeProcess(int group, unsigned chans, float *
// in the temporary buffers. If that's the case, we need to copy it over to // in the temporary buffers. If that's the case, we need to copy it over to
// the caller's buffers. This happens when the number of effects processed // the caller's buffers. This happens when the number of effects processed
// is odd. // is odd.
if (called & 1) if (called & 1) {
{ for (size_t i = 0; i < chans; i++) {
for (unsigned int i = 0; i < chans; i++) memcpy(buffers[i], ibuf[i], memcpy_size);
{
memcpy(buffers[i], ibuf[i], numSamples * sizeof(float));
} }
} }
delete ibuf;
delete[] obuf;
// Remember the latency // Remember the latency
mRealtimeLatency = (int) (wxGetUTCTimeMillis() - start).GetValue(); mRealtimeLatency = (int)(wxGetUTCTimeMillis() - start).GetValue();
}
mRealtimeLock.Leave(); mRealtimeLock.Leave();
delete[] temp;
for (size_t i = 0; i < chans; i++) {
delete[] obuf[i];
}
delete[] obuf;
for (size_t i = 0; i < chans; i++) {
delete[] ibuf[i];
}
delete[] ibuf;
// //
// This is wrong...needs to handle tails // This is wrong...needs to handle tails
// //
@ -519,9 +525,9 @@ size_t RealtimeEffectState::RealtimeProcess(int group,
const auto numAudioIn = mEffect.GetAudioInCount(); const auto numAudioIn = mEffect.GetAudioInCount();
const auto numAudioOut = mEffect.GetAudioOutCount(); const auto numAudioOut = mEffect.GetAudioOutCount();
auto clientIn = new float* [numAudioIn]; auto clientIn = safenew float* [numAudioIn];
auto clientOut = new float* [numAudioOut]; auto clientOut = safenew float* [numAudioOut];
auto dummybuf = new float [numSamples]; auto dummybuf = safenew float [numSamples];
decltype(numSamples) len = 0; decltype(numSamples) len = 0;
auto ichans = chans; auto ichans = chans;
@ -617,6 +623,7 @@ size_t RealtimeEffectState::RealtimeProcess(int group,
// Bump to next processor // Bump to next processor
processor++; processor++;
} }
delete[] clientIn; delete[] clientIn;
delete[] clientOut; delete[] clientOut;
delete[] dummybuf; delete[] dummybuf;