1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-05-02 00:29:41 +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
{
// Needed by the main thread sometimes
wxCriticalSectionLocker locker(mUpdating);
wxMutexLocker locker(mUpdating);
const Entry &previous = mEntries[(mLeadingIdx + Size - 1) % Size];
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)
{
wxASSERT(!mPoisoned);
// Main thread indicates a scrubbing interval
// MAY ADVANCE mLeadingIdx, BUT IT NEVER CATCHES UP TO mTrailingIdx.
wxCriticalSectionLocker locker(mUpdating);
wxMutexLocker locker(mUpdating);
const unsigned next = (mLeadingIdx + 1) % Size;
if (next != mTrailingIdx)
{
@ -429,8 +439,10 @@ struct AudioIO::ScrubQueue
const bool success =
(InitEntry(mEntries[mLeadingIdx], startTime, end, maxSpeed,
bySpeed, &previous, maySkip));
if (success)
if (success) {
mLeadingIdx = next;
mAvailable.Signal();
}
return success;
}
else
@ -450,7 +462,10 @@ struct AudioIO::ScrubQueue
// 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)
{
// There is work in the queue
@ -463,7 +478,8 @@ struct AudioIO::ScrubQueue
}
else
{
// next entry is not yet ready
wxASSERT(mPoisoned);
// We got the shut-down signal
startSample = endSample = duration = -1L;
}
}
@ -475,7 +491,7 @@ struct AudioIO::ScrubQueue
// MAY ADVANCE mTrailingIdx, BUT IT NEVER CATCHES UP TO mMiddleIdx.
wxCriticalSectionLocker locker(mUpdating);
wxMutexLocker locker(mUpdating);
// Mark entries as partly or fully "consumed" for
// purposes of mTime update. It should not happen that
@ -678,7 +694,9 @@ private:
const double mRate;
const long mMinStutter;
wxLongLong mLastScrubTimeMillis;
mutable wxCriticalSection mUpdating;
mutable wxMutex mUpdating;
mutable wxCondition mAvailable { mUpdating };
bool mPoisoned { false };
};
#endif
@ -1710,14 +1728,6 @@ int AudioIO::StartStream(const WaveTrackArray &playbackTracks,
sampleCount playbackMixBufferSize =
(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()];
mPlaybackMixers = new Mixer* [mPlaybackTracks->size()];
@ -1786,7 +1796,19 @@ int AudioIO::StartStream(const WaveTrackArray &playbackTracks,
mCaptureRingBufferSecs *= 0.5;
mMinCaptureSecsToCopy *= 0.5;
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);
if (mNumPlaybackChannels > 0)
@ -2166,6 +2188,8 @@ void AudioIO::StopStream()
//
mAudioThreadFillBuffersLoopRunning = false;
if (mScrubQueue)
mScrubQueue->PoisonPill();
// Audacity can deadlock if it tries to update meters while
// we're stopping PortAudio (because the meter updating code
@ -2821,7 +2845,16 @@ AudioThread::ExitCode AudioThread::Entry()
}
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;

View File

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

View File

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

View File

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