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

Changes to the starting and stopping of scrub/seek/at-speed...

... Necessary but not sufficient for fixing the "bounce" at start of scrub, and
for simplifying thread synchronization
This commit is contained in:
Paul Licameli 2018-08-27 16:45:30 -04:00
commit 976e9aeec7
4 changed files with 80 additions and 49 deletions

View File

@ -549,15 +549,6 @@ struct AudioIO::ScrubState
} }
} }
// This is for avoiding deadlocks while starting a scrub:
// Audio stream needs to be unblocked
void Nudge()
{
wxMutexLocker locker(mUpdating);
mNudged = true;
mAvailable.Signal();
}
bool Update(double end, const ScrubbingOptions &options) bool Update(double end, const ScrubbingOptions &options)
{ {
// Main thread indicates a scrubbing interval // Main thread indicates a scrubbing interval
@ -617,14 +608,14 @@ struct AudioIO::ScrubState
if (!cleanup) { if (!cleanup) {
cleanup.create(mUpdating); cleanup.create(mUpdating);
} }
while(!mNudged && mMiddleIdx == mLeadingIdx) while(! mStopped.load( std::memory_order_relaxed )&&
mMiddleIdx == mLeadingIdx)
mAvailable.Wait(); mAvailable.Wait();
mNudged = false;
auto now = ::wxGetLocalTimeMillis(); auto now = ::wxGetLocalTimeMillis();
if (mMiddleIdx != mLeadingIdx) { if ( ! mStopped.load( std::memory_order_relaxed ) &&
mMiddleIdx != mLeadingIdx ) {
Data &entry = mEntries[mMiddleIdx]; Data &entry = mEntries[mMiddleIdx];
if (entry.mDuration > 0) { if (entry.mDuration > 0) {
// First use of the entry // First use of the entry
@ -646,11 +637,18 @@ struct AudioIO::ScrubState
} }
} }
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 discarded all the work.
startSample = endSample = duration = -1L; startSample = endSample = duration = -1L;
} }
} }
void Stop()
{
mStopped.store( true, std::memory_order_relaxed );
wxMutexLocker locker(mUpdating);
mAvailable.Signal();
}
double LastTrackTime() const double LastTrackTime() const
{ {
// Needed by the main thread sometimes // Needed by the main thread sometimes
@ -832,12 +830,12 @@ private:
unsigned mTrailingIdx; unsigned mTrailingIdx;
unsigned mMiddleIdx; unsigned mMiddleIdx;
unsigned mLeadingIdx; unsigned mLeadingIdx;
std::atomic<bool> mStopped { false };
const double mRate; const double mRate;
wxLongLong mLastScrubTimeMillis; wxLongLong mLastScrubTimeMillis;
mutable wxMutex mUpdating; mutable wxMutex mUpdating;
mutable wxCondition mAvailable { mUpdating }; mutable wxCondition mAvailable { mUpdating };
bool mNudged { false };
}; };
#endif #endif
@ -2006,9 +2004,17 @@ int AudioIO::StartStream(const TransportTracks &tracks,
mAudioThreadShouldCallFillBuffersOnce = true; mAudioThreadShouldCallFillBuffersOnce = true;
while( mAudioThreadShouldCallFillBuffersOnce ) { while( mAudioThreadShouldCallFillBuffersOnce ) {
if (mScrubState) #ifndef USE_SCRUB_THREAD
mScrubState->Nudge(); // Yuck, we either have to poll "by hand" when scrub polling doesn't
wxMilliSleep( 50 ); // work with a thread, or else yield to timer messages, but that would
// execute too much else
if (mScrubState) {
mOwningProject->GetScrubber().ContinueScrubbingPoll();
wxMilliSleep( Scrubber::ScrubPollInterval_ms );
}
else
#endif
wxMilliSleep( 50 );
} }
if(mNumPlaybackChannels > 0 || mNumCaptureChannels > 0) { if(mNumPlaybackChannels > 0 || mNumCaptureChannels > 0) {
@ -2513,8 +2519,6 @@ void AudioIO::StopStream()
// //
mAudioThreadFillBuffersLoopRunning = false; mAudioThreadFillBuffersLoopRunning = false;
if (mScrubState)
mScrubState->Nudge();
// 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
@ -2622,8 +2626,6 @@ void AudioIO::StopStream()
{ {
// LLL: Experienced recursive yield here...once. // LLL: Experienced recursive yield here...once.
wxGetApp().Yield(true); // Pass true for onlyIfNeeded to avoid recursive call error. wxGetApp().Yield(true); // Pass true for onlyIfNeeded to avoid recursive call error.
if (mScrubState)
mScrubState->Nudge();
wxMilliSleep( 50 ); wxMilliSleep( 50 );
} }
@ -2768,6 +2770,12 @@ bool AudioIO::UpdateScrub
return false; return false;
} }
void AudioIO::StopScrub()
{
if (mScrubState)
mScrubState->Stop();
}
double AudioIO::GetLastScrubTime() const double AudioIO::GetLastScrubTime() const
{ {
if (mScrubState) if (mScrubState)

View File

@ -261,6 +261,8 @@ class AUDACITY_DLL_API AudioIO final {
*/ */
bool UpdateScrub(double endTimeOrSpeed, const ScrubbingOptions &options); bool UpdateScrub(double endTimeOrSpeed, const ScrubbingOptions &options);
void StopScrub();
/** \brief return the ending time of the last scrub interval. /** \brief return the ending time of the last scrub interval.
*/ */
double GetLastScrubTime() const; double GetLastScrubTime() const;

View File

@ -51,9 +51,7 @@ enum {
ScrubSpeedStepsPerOctave = 4, ScrubSpeedStepsPerOctave = 4,
#endif #endif
ScrubPollInterval_ms = 50, kOneSecondCountdown = 1000 / Scrubber::ScrubPollInterval_ms,
kOneSecondCountdown = 1000 / ScrubPollInterval_ms,
}; };
static const double MinStutter = 0.2; static const double MinStutter = 0.2;
@ -336,8 +334,7 @@ bool Scrubber::MaybeStartScrubbing(wxCoord xx)
double time1 = std::min(maxTime, double time1 = std::min(maxTime,
viewInfo.PositionToTime(position, leftOffset) viewInfo.PositionToTime(position, leftOffset)
); );
if (time1 != time0) if (time1 != time0) {
{
if (busy) { if (busy) {
auto position = mScrubStartPosition; auto position = mScrubStartPosition;
ctb->StopPlaying(); ctb->StopPlaying();
@ -403,6 +400,15 @@ bool Scrubber::MaybeStartScrubbing(wxCoord xx)
); );
#endif #endif
mScrubSpeedDisplayCountdown = 0; mScrubSpeedDisplayCountdown = 0;
// Must start the thread and poller first or else PlayPlayRegion
// will insert some silence
StartPolling();
auto cleanup = finally([this]{
if (mScrubToken < 0)
StopPolling();
});
mScrubToken = mScrubToken =
ctb->PlayPlayRegion(SelectedRegion(time0, time1), options, ctb->PlayPlayRegion(SelectedRegion(time0, time1), options,
PlayMode::normalPlay, appearance, backwards); PlayMode::normalPlay, appearance, backwards);
@ -421,17 +427,7 @@ bool Scrubber::MaybeStartScrubbing(wxCoord xx)
mOptions.startClockTimeMillis = ::wxGetLocalTimeMillis(); mOptions.startClockTimeMillis = ::wxGetLocalTimeMillis();
if (IsScrubbing()) { if (IsScrubbing()) {
mPaused = false;
mLastScrubPosition = xx; mLastScrubPosition = xx;
#ifdef USE_SCRUB_THREAD
// Detached thread is self-deleting, after it receives the Delete() message
mpThread = safenew ScrubPollerThread{ *this };
mpThread->Create(4096);
mpThread->Run();
#endif
mPoller->Start(ScrubPollInterval_ms);
} }
// Return true whether we started scrub, or are still waiting to decide. // Return true whether we started scrub, or are still waiting to decide.
@ -491,6 +487,14 @@ bool Scrubber::StartSpeedPlay(double speed, double time0, double time1)
); );
#endif #endif
// Must start the thread and poller first or else PlayPlayRegion
// will insert some silence
StartPolling();
auto cleanup = finally([this]{
if (mScrubToken < 0)
StopPolling();
});
mScrubSpeedDisplayCountdown = 0; mScrubSpeedDisplayCountdown = 0;
// Aim to stop within 20 samples of correct position. // Aim to stop within 20 samples of correct position.
double stopTolerance = 20.0 / options.rate; double stopTolerance = 20.0 / options.rate;
@ -500,18 +504,9 @@ bool Scrubber::StartSpeedPlay(double speed, double time0, double time1)
PlayMode::normalPlay, appearance, backwards); PlayMode::normalPlay, appearance, backwards);
if (mScrubToken >= 0) { if (mScrubToken >= 0) {
mPaused = false;
mLastScrubPosition = 0; mLastScrubPosition = 0;
#ifdef USE_SCRUB_THREAD
// Detached thread is self-deleting, after it receives the Delete() message
mpThread = safenew ScrubPollerThread{ *this };
mpThread->Create(4096);
mpThread->Run();
#endif
mPoller->Start(ScrubPollInterval_ms);
} }
return true; return true;
} }
@ -630,8 +625,24 @@ void Scrubber::ContinueScrubbingUI()
} }
} }
void Scrubber::StopScrubbing() void Scrubber::StartPolling()
{ {
mPaused = false;
#ifdef USE_SCRUB_THREAD
// Detached thread is self-deleting, after it receives the Delete() message
mpThread = safenew ScrubPollerThread{ *this };
mpThread->Create(4096);
mpThread->Run();
#endif
mPoller->Start(ScrubPollInterval_ms);
}
void Scrubber::StopPolling()
{
mPaused = true;
#ifdef USE_SCRUB_THREAD #ifdef USE_SCRUB_THREAD
if (mpThread) { if (mpThread) {
mpThread->Delete(); mpThread->Delete();
@ -640,6 +651,12 @@ void Scrubber::StopScrubbing()
#endif #endif
mPoller->Stop(); mPoller->Stop();
}
void Scrubber::StopScrubbing()
{
gAudioIO->StopScrub();
StopPolling();
if (HasMark() && !mCancelled) { if (HasMark() && !mCancelled) {
const wxMouseState state(::wxGetMouseState()); const wxMouseState state(::wxGetMouseState());

View File

@ -72,6 +72,8 @@ struct ScrubbingOptions {
class Scrubber : public wxEvtHandler class Scrubber : public wxEvtHandler
{ {
public: public:
static constexpr unsigned ScrubPollInterval_ms = 50;
Scrubber(AudacityProject *project); Scrubber(AudacityProject *project);
~Scrubber(); ~Scrubber();
@ -154,6 +156,8 @@ public:
void CheckMenuItems(); void CheckMenuItems();
private: private:
void StartPolling();
void StopPolling();
void DoScrub(bool seek); void DoScrub(bool seek);
void OnActivateOrDeactivateApp(wxActivateEvent & event); void OnActivateOrDeactivateApp(wxActivateEvent & event);