1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-06-17 08:30:06 +02:00

Make scrubbing work when timer interval constant is shortened to 1 ms

* scrubbing:
  Allow scrub timer interval to be 1 ms, without bogus out-of-memory warnings.
  Less scrub lag: avoid redundant disk traffic in Mixer...
  Less scrub lag: don't poll for available data, get woken up directly
This commit is contained in:
Paul Licameli 2016-05-15 16:18:43 -04:00
commit c3246e3f7b
4 changed files with 79 additions and 46 deletions

View File

@ -405,18 +405,28 @@ struct AudioIO::ScrubQueue
double LastTimeInQueue() const double LastTimeInQueue() const
{ {
// Needed by the main thread sometimes // Needed by the main thread sometimes
wxCriticalSectionLocker locker(mUpdating); wxMutexLocker locker(mUpdating);
const Entry &previous = mEntries[(mLeadingIdx + Size - 1) % Size]; const Entry &previous = mEntries[(mLeadingIdx + Size - 1) % Size];
return previous.mS1 / mRate; return previous.mS1 / mRate;
} }
void PoisonPill()
{
// Main thread is shutting down the scrubbing
wxMutexLocker locker(mUpdating);
mPoisoned = true;
mAvailable.Signal();
}
bool Producer(double end, double maxSpeed, bool bySpeed, bool maySkip) bool Producer(double end, double maxSpeed, bool bySpeed, bool maySkip)
{ {
wxASSERT(!mPoisoned);
// Main thread indicates a scrubbing interval // Main thread indicates a scrubbing interval
// MAY ADVANCE mLeadingIdx, BUT IT NEVER CATCHES UP TO mTrailingIdx. // MAY ADVANCE mLeadingIdx, BUT IT NEVER CATCHES UP TO mTrailingIdx.
wxCriticalSectionLocker locker(mUpdating); wxMutexLocker locker(mUpdating);
const unsigned next = (mLeadingIdx + 1) % Size; const unsigned next = (mLeadingIdx + 1) % Size;
if (next != mTrailingIdx) if (next != mTrailingIdx)
{ {
@ -429,8 +439,10 @@ struct AudioIO::ScrubQueue
const bool success = const bool success =
(InitEntry(mEntries[mLeadingIdx], startTime, end, maxSpeed, (InitEntry(mEntries[mLeadingIdx], startTime, end, maxSpeed,
bySpeed, &previous, maySkip)); bySpeed, &previous, maySkip));
if (success) if (success) {
mLeadingIdx = next; mLeadingIdx = next;
mAvailable.Signal();
}
return success; return success;
} }
else else
@ -450,7 +462,10 @@ struct AudioIO::ScrubQueue
// MAY ADVANCE mMiddleIdx, WHICH MAY EQUAL mLeadingIdx, BUT DOES NOT PASS IT. // MAY ADVANCE mMiddleIdx, WHICH MAY EQUAL mLeadingIdx, BUT DOES NOT PASS IT.
wxCriticalSectionLocker locker(mUpdating); wxMutexLocker locker(mUpdating);
while(!mPoisoned && mMiddleIdx == mLeadingIdx)
mAvailable.Wait();
if (mMiddleIdx != mLeadingIdx) if (mMiddleIdx != mLeadingIdx)
{ {
// There is work in the queue // There is work in the queue
@ -463,7 +478,8 @@ struct AudioIO::ScrubQueue
} }
else else
{ {
// next entry is not yet ready wxASSERT(mPoisoned);
// We got the shut-down signal
startSample = endSample = duration = -1L; startSample = endSample = duration = -1L;
} }
} }
@ -475,7 +491,7 @@ struct AudioIO::ScrubQueue
// MAY ADVANCE mTrailingIdx, BUT IT NEVER CATCHES UP TO mMiddleIdx. // MAY ADVANCE mTrailingIdx, BUT IT NEVER CATCHES UP TO mMiddleIdx.
wxCriticalSectionLocker locker(mUpdating); wxMutexLocker locker(mUpdating);
// Mark entries as partly or fully "consumed" for // Mark entries as partly or fully "consumed" for
// purposes of mTime update. It should not happen that // purposes of mTime update. It should not happen that
@ -678,7 +694,9 @@ private:
const double mRate; const double mRate;
const long mMinStutter; const long mMinStutter;
wxLongLong mLastScrubTimeMillis; wxLongLong mLastScrubTimeMillis;
mutable wxCriticalSection mUpdating; mutable wxMutex mUpdating;
mutable wxCondition mAvailable { mUpdating };
bool mPoisoned { false };
}; };
#endif #endif
@ -1710,14 +1728,6 @@ int AudioIO::StartStream(const WaveTrackArray &playbackTracks,
sampleCount playbackMixBufferSize = sampleCount playbackMixBufferSize =
(sampleCount)mPlaybackSamplesToCopy; (sampleCount)mPlaybackSamplesToCopy;
// In the extraordinarily rare case that we can't even afford 100 samples, just give up.
if(playbackBufferSize < 100 || playbackMixBufferSize < 100)
{
StartStreamCleanup();
wxMessageBox(_("Out of memory!"));
return 0;
}
mPlaybackBuffers = new RingBuffer* [mPlaybackTracks->size()]; mPlaybackBuffers = new RingBuffer* [mPlaybackTracks->size()];
mPlaybackMixers = new Mixer* [mPlaybackTracks->size()]; mPlaybackMixers = new Mixer* [mPlaybackTracks->size()];
@ -1786,7 +1796,19 @@ int AudioIO::StartStream(const WaveTrackArray &playbackTracks,
mCaptureRingBufferSecs *= 0.5; mCaptureRingBufferSecs *= 0.5;
mMinCaptureSecsToCopy *= 0.5; mMinCaptureSecsToCopy *= 0.5;
bDone = false; bDone = false;
}
// In the extraordinarily rare case that we can't even afford 100 samples, just give up.
sampleCount playbackBufferSize =
(sampleCount)lrint(mRate * mPlaybackRingBufferSecs);
sampleCount playbackMixBufferSize =
(sampleCount)mPlaybackSamplesToCopy;
if(playbackBufferSize < 100 || playbackMixBufferSize < 100)
{
StartStreamCleanup();
wxMessageBox(_("Out of memory!"));
return 0;
}
}
} while(!bDone); } while(!bDone);
if (mNumPlaybackChannels > 0) if (mNumPlaybackChannels > 0)
@ -2166,6 +2188,8 @@ void AudioIO::StopStream()
// //
mAudioThreadFillBuffersLoopRunning = false; mAudioThreadFillBuffersLoopRunning = false;
if (mScrubQueue)
mScrubQueue->PoisonPill();
// Audacity can deadlock if it tries to update meters while // Audacity can deadlock if it tries to update meters while
// we're stopping PortAudio (because the meter updating code // we're stopping PortAudio (because the meter updating code
@ -2821,7 +2845,16 @@ AudioThread::ExitCode AudioThread::Entry()
} }
gAudioIO->mAudioThreadFillBuffersLoopActive = false; gAudioIO->mAudioThreadFillBuffersLoopActive = false;
Sleep(10); if (gAudioIO->mPlayMode == AudioIO::PLAY_SCRUB) {
// Rely on the Wait() in ScrubQueue::Transformer()
// This allows the scrubbing update interval to be made very short without
// playback becoming intermittent.
}
else {
// Perhaps this too could use a condition variable, for available space in the
// ring buffer, instead of a polling loop? But no harm in doing it this way.
Sleep(10);
}
} }
return 0; return 0;

View File

@ -650,7 +650,7 @@ private:
bool mInputMixerWorks; bool mInputMixerWorks;
float mMixerOutputVol; float mMixerOutputVol;
enum { volatile enum {
PLAY_STRAIGHT, PLAY_STRAIGHT,
PLAY_LOOPED, PLAY_LOOPED,
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT #ifdef EXPERIMENTAL_SCRUBBING_SUPPORT

View File

@ -22,7 +22,6 @@
#include "Audacity.h" #include "Audacity.h"
#include "Mix.h" #include "Mix.h"
#include <math.h> #include <math.h>
@ -251,13 +250,13 @@ Mixer::Mixer(const WaveTrackConstArray &inputTracks,
const auto numInputTracks = inputTracks.size(); const auto numInputTracks = inputTracks.size();
mHighQuality = highQuality; mHighQuality = highQuality;
mNumInputTracks = numInputTracks; mNumInputTracks = numInputTracks;
mInputTrack = new const WaveTrack*[mNumInputTracks]; mInputTrack = new WaveTrackCache[mNumInputTracks];
// mSamplePos holds for each track the next sample position not // mSamplePos holds for each track the next sample position not
// yet processed. // yet processed.
mSamplePos = new sampleCount[mNumInputTracks]; mSamplePos = new sampleCount[mNumInputTracks];
for(i=0; i<mNumInputTracks; i++) { for(i=0; i<mNumInputTracks; i++) {
mInputTrack[i] = inputTracks[i]; mInputTrack[i].SetTrack(inputTracks[i]);
mSamplePos[i] = inputTracks[i]->TimeToLongSamples(startTime); mSamplePos[i] = inputTracks[i]->TimeToLongSamples(startTime);
} }
mTimeTrack = warpOptions.timeTrack; mTimeTrack = warpOptions.timeTrack;
@ -313,7 +312,7 @@ Mixer::Mixer(const WaveTrackConstArray &inputTracks,
mSampleQueue = new float *[mNumInputTracks]; mSampleQueue = new float *[mNumInputTracks];
mResample = new Resample*[mNumInputTracks]; mResample = new Resample*[mNumInputTracks];
for(i=0; i<mNumInputTracks; i++) { for(i=0; i<mNumInputTracks; i++) {
double factor = (mRate / mInputTrack[i]->GetRate()); double factor = (mRate / mInputTrack[i].GetTrack()->GetRate());
double minFactor, maxFactor; double minFactor, maxFactor;
if (mTimeTrack) { if (mTimeTrack) {
// variable rate resampling // variable rate resampling
@ -408,11 +407,12 @@ void MixBuffers(int numChannels, int *channelFlags, float *gains,
} }
} }
sampleCount Mixer::MixVariableRates(int *channelFlags, const WaveTrack *track, sampleCount Mixer::MixVariableRates(int *channelFlags, WaveTrackCache &cache,
sampleCount *pos, float *queue, sampleCount *pos, float *queue,
int *queueStart, int *queueLen, int *queueStart, int *queueLen,
Resample * pResample) Resample * pResample)
{ {
const WaveTrack *const track = cache.GetTrack();
const double trackRate = track->GetRate(); const double trackRate = track->GetRate();
const double initialWarp = mRate / mSpeed / trackRate; const double initialWarp = mRate / mSpeed / trackRate;
const double tstep = 1.0 / trackRate; const double tstep = 1.0 / trackRate;
@ -455,10 +455,8 @@ sampleCount Mixer::MixVariableRates(int *channelFlags, const WaveTrack *track,
// Nothing to do if past end of play interval // Nothing to do if past end of play interval
if (getLen > 0) { if (getLen > 0) {
if (backwards) { if (backwards) {
track->Get((samplePtr)&queue[*queueLen], auto results = cache.Get(floatSample, *pos - (getLen - 1), getLen);
floatSample, memcpy(&queue[*queueLen], results, sizeof(float) * getLen);
*pos - (getLen - 1),
getLen);
track->GetEnvelopeValues(mEnvValues, track->GetEnvelopeValues(mEnvValues,
getLen, getLen,
@ -468,10 +466,8 @@ sampleCount Mixer::MixVariableRates(int *channelFlags, const WaveTrack *track,
*pos -= getLen; *pos -= getLen;
} }
else { else {
track->Get((samplePtr)&queue[*queueLen], auto results = cache.Get(floatSample, *pos, getLen);
floatSample, memcpy(&queue[*queueLen], results, sizeof(float) * getLen);
*pos,
getLen);
track->GetEnvelopeValues(mEnvValues, track->GetEnvelopeValues(mEnvValues,
getLen, getLen,
@ -558,9 +554,10 @@ sampleCount Mixer::MixVariableRates(int *channelFlags, const WaveTrack *track,
return out; return out;
} }
sampleCount Mixer::MixSameRate(int *channelFlags, const WaveTrack *track, sampleCount Mixer::MixSameRate(int *channelFlags, WaveTrackCache &cache,
sampleCount *pos) sampleCount *pos)
{ {
const WaveTrack *const track = cache.GetTrack();
int slen = mMaxOut; int slen = mMaxOut;
int c; int c;
const double t = *pos / track->GetRate(); const double t = *pos / track->GetRate();
@ -588,7 +585,8 @@ sampleCount Mixer::MixSameRate(int *channelFlags, const WaveTrack *track,
slen = mMaxOut; slen = mMaxOut;
if (backwards) { if (backwards) {
track->Get((samplePtr)mFloatBuffer, floatSample, *pos - (slen - 1), slen); auto results = cache.Get(floatSample, *pos - (slen - 1), slen);
memcpy(mFloatBuffer, results, sizeof(float) * slen);
track->GetEnvelopeValues(mEnvValues, slen, t - (slen - 1) / mRate, 1.0 / mRate); track->GetEnvelopeValues(mEnvValues, slen, t - (slen - 1) / mRate, 1.0 / mRate);
for(int i=0; i<slen; i++) for(int i=0; i<slen; i++)
mFloatBuffer[i] *= mEnvValues[i]; // Track gain control will go here? mFloatBuffer[i] *= mEnvValues[i]; // Track gain control will go here?
@ -597,7 +595,8 @@ sampleCount Mixer::MixSameRate(int *channelFlags, const WaveTrack *track,
*pos -= slen; *pos -= slen;
} }
else { else {
track->Get((samplePtr)mFloatBuffer, floatSample, *pos, slen); auto results = cache.Get(floatSample, *pos, slen);
memcpy(mFloatBuffer, results, sizeof(float) * slen);
track->GetEnvelopeValues(mEnvValues, slen, t, 1.0 / mRate); track->GetEnvelopeValues(mEnvValues, slen, t, 1.0 / mRate);
for(int i=0; i<slen; i++) for(int i=0; i<slen; i++)
mFloatBuffer[i] *= mEnvValues[i]; // Track gain control will go here? mFloatBuffer[i] *= mEnvValues[i]; // Track gain control will go here?
@ -632,7 +631,7 @@ sampleCount Mixer::Process(sampleCount maxToProcess)
Clear(); Clear();
for(i=0; i<mNumInputTracks; i++) { for(i=0; i<mNumInputTracks; i++) {
const WaveTrack *track = mInputTrack[i]; const WaveTrack *const track = mInputTrack[i].GetTrack();
for(j=0; j<mNumChannels; j++) for(j=0; j<mNumChannels; j++)
channelFlags[j] = 0; channelFlags[j] = 0;
@ -661,12 +660,12 @@ sampleCount Mixer::Process(sampleCount maxToProcess)
} }
if (mbVariableRates || track->GetRate() != mRate) if (mbVariableRates || track->GetRate() != mRate)
maxOut = std::max(maxOut, maxOut = std::max(maxOut,
MixVariableRates(channelFlags, track, MixVariableRates(channelFlags, mInputTrack[i],
&mSamplePos[i], mSampleQueue[i], &mSamplePos[i], mSampleQueue[i],
&mQueueStart[i], &mQueueLen[i], mResample[i])); &mQueueStart[i], &mQueueLen[i], mResample[i]));
else else
maxOut = std::max(maxOut, maxOut = std::max(maxOut,
MixSameRate(channelFlags, track, &mSamplePos[i])); MixSameRate(channelFlags, mInputTrack[i], &mSamplePos[i]));
double t = (double)mSamplePos[i] / (double)track->GetRate(); double t = (double)mSamplePos[i] / (double)track->GetRate();
if (mT0 > mT1) if (mT0 > mT1)
@ -728,7 +727,7 @@ void Mixer::Restart()
mTime = mT0; mTime = mT0;
for(i=0; i<mNumInputTracks; i++) for(i=0; i<mNumInputTracks; i++)
mSamplePos[i] = mInputTrack[i]->TimeToLongSamples(mT0); mSamplePos[i] = mInputTrack[i].GetTrack()->TimeToLongSamples(mT0);
for(i=0; i<mNumInputTracks; i++) { for(i=0; i<mNumInputTracks; i++) {
mQueueStart[i] = 0; mQueueStart[i] = 0;
@ -748,7 +747,7 @@ void Mixer::Reposition(double t)
mTime = std::max(mT0, (std::min(mT1, mTime))); mTime = std::max(mT0, (std::min(mT1, mTime)));
for(i=0; i<mNumInputTracks; i++) { for(i=0; i<mNumInputTracks; i++) {
mSamplePos[i] = mInputTrack[i]->TimeToLongSamples(mTime); mSamplePos[i] = mInputTrack[i].GetTrack()->TimeToLongSamples(mTime);
mQueueStart[i] = 0; mQueueStart[i] = 0;
mQueueLen[i] = 0; mQueueLen[i] = 0;
} }

View File

@ -14,16 +14,16 @@
#include "MemoryX.h" #include "MemoryX.h"
#include <wx/string.h> #include <wx/string.h>
#include "SampleFormat.h" #include "SampleFormat.h"
#include "Resample.h"
class Resample;
class DirManager; class DirManager;
class TimeTrack; class TimeTrack;
class TrackFactory; class TrackFactory;
class TrackList; class TrackList;
class WaveTrack; class WaveTrack;
class WaveTrackConstArray; class WaveTrackConstArray;
class WaveTrackCache;
/** @brief Mixes together all input tracks, applying any envelopes, amplitude /** @brief Mixes together all input tracks, applying any envelopes, amplitude
* gain, panning, and real-time effects in the process. * gain, panning, and real-time effects in the process.
@ -141,10 +141,10 @@ class AUDACITY_DLL_API Mixer {
private: private:
void Clear(); void Clear();
sampleCount MixSameRate(int *channelFlags, const WaveTrack *src, sampleCount MixSameRate(int *channelFlags, WaveTrackCache &cache,
sampleCount *pos); sampleCount *pos);
sampleCount MixVariableRates(int *channelFlags, const WaveTrack *track, sampleCount MixVariableRates(int *channelFlags, WaveTrackCache &cache,
sampleCount *pos, float *queue, sampleCount *pos, float *queue,
int *queueStart, int *queueLen, int *queueStart, int *queueLen,
Resample * pResample); Resample * pResample);
@ -152,7 +152,8 @@ class AUDACITY_DLL_API Mixer {
private: private:
// Input // Input
int mNumInputTracks; int mNumInputTracks;
const WaveTrack **mInputTrack; WaveTrackCache *mInputTrack;
bool mbVariableRates; bool mbVariableRates;
const TimeTrack *mTimeTrack; const TimeTrack *mTimeTrack;
sampleCount *mSamplePos; sampleCount *mSamplePos;