1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-06-23 15:50:05 +02:00

Reduce scrub lag yet more, at expense of possible skips in play...

by discarding work from the queue sometimes on the consumer side.

Also more careful mixed long - double arithmetic.
This commit is contained in:
Paul Licameli 2016-05-16 17:12:32 -04:00
parent 57fb3cd12c
commit b6764d1bf7
2 changed files with 75 additions and 15 deletions

View File

@ -372,7 +372,7 @@ So a small, fixed queue size should be adequate.
struct AudioIO::ScrubQueue struct AudioIO::ScrubQueue
{ {
ScrubQueue(double t0, double t1, wxLongLong startClockMillis, ScrubQueue(double t0, double t1, wxLongLong startClockMillis,
double rate, double maxSpeed, double rate, double maxSpeed, long maxDebt,
const ScrubbingOptions &options) const ScrubbingOptions &options)
: mTrailingIdx(0) : mTrailingIdx(0)
, mMiddleIdx(1) , mMiddleIdx(1)
@ -380,6 +380,7 @@ struct AudioIO::ScrubQueue
, mRate(rate) , mRate(rate)
, mLastScrubTimeMillis(startClockMillis) , mLastScrubTimeMillis(startClockMillis)
, mUpdating() , mUpdating()
, mMaxDebt { maxDebt }
{ {
// Ignore options.adjustStart, pass false. // Ignore options.adjustStart, pass false.
@ -463,28 +464,78 @@ 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.
if (!cleanup) bool checkDebt = false;
if (!cleanup) {
cleanup.create(mUpdating); cleanup.create(mUpdating);
// Check for cancellation of work only when re-enetering the cricial section
checkDebt = true;
}
while(!mNudged && mMiddleIdx == mLeadingIdx) while(!mNudged && mMiddleIdx == mLeadingIdx)
mAvailable.Wait(); mAvailable.Wait();
mNudged = false; mNudged = false;
if (mMiddleIdx != mLeadingIdx) auto now = ::wxGetLocalTimeMillis();
{
// There is work in the queue if (checkDebt &&
mLastTransformerTimeMillis >= 0 && // Not the first time for this scrub
mMiddleIdx != mLeadingIdx) {
// There is work in the queue, but if Producer is outrunning us, discard some,
// which may make a skip yet keep playback better synchronized with user gestures.
const auto interval = (now - mLastTransformerTimeMillis).ToDouble() / 1000.0;
const Entry &previous = mEntries[(mMiddleIdx + Size - 1) % Size];
const auto deficit =
static_cast<long>(interval * mRate) - // Samples needed in the last time interval
mCredit; // Samples done in the last time interval
mCredit = 0;
mDebt += deficit;
auto toDiscard = mDebt - mMaxDebt;
while (toDiscard > 0 && mMiddleIdx != mLeadingIdx) {
// Cancel some debt (discard some new work)
auto &entry = mEntries[mMiddleIdx];
auto &dur = entry.mDuration;
if (toDiscard >= dur) {
// Discard entire queue entry
mDebt -= dur;
toDiscard -= dur;
dur = 0; // So Consumer() will handle abandoned entry correctly
mMiddleIdx = (mMiddleIdx + 1) % Size;
}
else {
// Adjust the start time
auto &start = entry.mS0;
const auto end = entry.mS1;
const auto ratio = static_cast<double>(toDiscard) / static_cast<double>(dur);
const auto adjustment = static_cast<long>(std::abs(end - start) * ratio);
if (start <= end)
start += adjustment;
else
start -= adjustment;
mDebt -= toDiscard;
dur -= toDiscard;
toDiscard = 0;
}
}
}
if (mMiddleIdx != mLeadingIdx) {
// There is still work in the queue, after cancelling debt
Entry &entry = mEntries[mMiddleIdx]; Entry &entry = mEntries[mMiddleIdx];
startSample = entry.mS0; startSample = entry.mS0;
endSample = entry.mS1; endSample = entry.mS1;
duration = entry.mDuration; duration = entry.mDuration;
const unsigned next = (mMiddleIdx + 1) % Size; mMiddleIdx = (mMiddleIdx + 1) % Size;
mMiddleIdx = next; mCredit += duration;
} }
else else {
{ // We got the shut-down signal, or we got nudged, or we discarded all the work.
// We got the shut-down signal, or we got nudged
startSample = endSample = duration = -1L; startSample = endSample = duration = -1L;
} }
if (checkDebt)
mLastTransformerTimeMillis = now;
} }
double Consumer(unsigned long frames) double Consumer(unsigned long frames)
@ -635,7 +686,10 @@ private:
double GetTime(double rate) const double GetTime(double rate) const
{ {
return (mS0 + ((mS1 - mS0) * mPlayed) / double(mDuration)) / rate; return
(mS0 +
(mS1 - mS0) * static_cast<double>(mPlayed) / static_cast<double>(mDuration))
/ rate;
} }
// These sample counts are initialized in the UI, producer, thread: // These sample counts are initialized in the UI, producer, thread:
@ -680,6 +734,12 @@ private:
unsigned mLeadingIdx; unsigned mLeadingIdx;
const double mRate; const double mRate;
wxLongLong mLastScrubTimeMillis; wxLongLong mLastScrubTimeMillis;
wxLongLong mLastTransformerTimeMillis { -1LL };
long mCredit { 0L };
long mDebt { 0L };
const long mMaxDebt;
mutable wxMutex mUpdating; mutable wxMutex mUpdating;
mutable wxCondition mAvailable { mUpdating }; mutable wxCondition mAvailable { mUpdating };
bool mNudged { false }; bool mNudged { false };
@ -1850,7 +1910,7 @@ int AudioIO::StartStream(const WaveTrackArray &playbackTracks,
const auto &scrubOptions = *options.pScrubbingOptions; const auto &scrubOptions = *options.pScrubbingOptions;
mScrubQueue = mScrubQueue =
new ScrubQueue(mT0, mT1, scrubOptions.startClockTimeMillis, new ScrubQueue(mT0, mT1, scrubOptions.startClockTimeMillis,
sampleRate, scrubOptions.maxSpeed, sampleRate, scrubOptions.maxSpeed, 2 * scrubOptions.minStutter,
*options.pScrubbingOptions); *options.pScrubbingOptions);
mScrubDuration = 0; mScrubDuration = 0;
mSilentScrub = false; mSilentScrub = false;
@ -3446,7 +3506,7 @@ void AudioIO::FillBuffers()
double startTime, endTime, speed; double startTime, endTime, speed;
startTime = startSample / mRate; startTime = startSample / mRate;
endTime = endSample / mRate; endTime = endSample / mRate;
speed = double(abs(endSample - startSample)) / mScrubDuration; speed = double(std::abs(endSample - startSample)) / mScrubDuration;
for (i = 0; i < mPlaybackTracks->size(); i++) for (i = 0; i < mPlaybackTracks->size(); i++)
mPlaybackMixers[i]->SetTimesAndSpeed(startTime, endTime, speed); mPlaybackMixers[i]->SetTimesAndSpeed(startTime, endTime, speed);
} }
@ -4167,7 +4227,7 @@ int audacityAudioCallback(const void *inputBuffer, void *outputBuffer,
(gAudioIO->mT0, gAudioIO->mTime); (gAudioIO->mT0, gAudioIO->mTime);
else else
gAudioIO->mWarpedTime = gAudioIO->mTime - gAudioIO->mT0; gAudioIO->mWarpedTime = gAudioIO->mTime - gAudioIO->mT0;
gAudioIO->mWarpedTime = abs(gAudioIO->mWarpedTime); gAudioIO->mWarpedTime = std::abs(gAudioIO->mWarpedTime);
// Reset mixer positions and flush buffers for all tracks // Reset mixer positions and flush buffers for all tracks
for (i = 0; i < (unsigned int)numPlaybackTracks; i++) for (i = 0; i < (unsigned int)numPlaybackTracks; i++)

View File

@ -303,7 +303,7 @@ bool Scrubber::MaybeStartScrubbing(wxCoord xx)
AudioIOStartStreamOptions options(mProject->GetDefaultPlayOptions()); AudioIOStartStreamOptions options(mProject->GetDefaultPlayOptions());
options.pScrubbingOptions = &mOptions; options.pScrubbingOptions = &mOptions;
options.timeTrack = NULL; options.timeTrack = NULL;
mOptions.delay = (ScrubPollInterval_ms / 1000.0); mOptions.delay = (ScrubPollInterval_ms * 0.9 / 1000.0);
#ifdef USE_TRANSCRIPTION_TOOLBAR #ifdef USE_TRANSCRIPTION_TOOLBAR
if (!mAlwaysSeeking) { if (!mAlwaysSeeking) {
// Take the starting speed limit from the transcription toolbar, // Take the starting speed limit from the transcription toolbar,