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

Gracefully handle disk exhaustion exceptions during recording

This commit is contained in:
Paul Licameli 2016-11-22 14:10:53 -05:00
parent f4e2fb5eac
commit eeb301e50d
2 changed files with 112 additions and 61 deletions

View File

@ -1650,6 +1650,8 @@ int AudioIO::StartStream(const ConstWaveTrackArray &playbackTracks,
double t0, double t1,
const AudioIOStartStreamOptions &options)
{
auto cleanup = finally ( [this] { ClearRecordingException(); } );
if( IsBusy() )
return 0;
@ -2250,6 +2252,8 @@ void AudioIO::SetMeters()
void AudioIO::StopStream()
{
auto cleanup = finally ( [this] { ClearRecordingException(); } );
if( mPortStreamV19 == NULL
#ifdef EXPERIMENTAL_MIDI_OUT
&& mMidiStream == NULL
@ -3420,6 +3424,25 @@ void AudioIO::FillBuffers()
{
unsigned int i;
auto delayedHandler = [this] ( AudacityException * pException ) {
// In the main thread, stop recording
// This is one place where the application handles disk
// exhaustion exceptions from wave track operations, without rolling
// back to the last pushed undo state. Instead, partial recording
// results are pushed as a NEW undo state. For this reason, as
// commented elsewhere, we want an exception safety guarantee for
// the output wave tracks, after the failed append operation, that
// the tracks remain as they were after the previous successful
// (block-level) appends.
// Note that the Flush in StopStream() may throw another exception,
// but StopStream() contains that exception, and the logic in
// AudacityException::DelayedHandlerAction prevents redundant message
// boxes.
StopStream();
DefaultDelayedHandlerAction{}( pException );
};
if (mPlaybackTracks.size() > 0)
{
// Though extremely unlikely, it is possible that some buffers
@ -3595,78 +3618,97 @@ void AudioIO::FillBuffers()
}
} // end of playback buffering
if (mCaptureTracks.size() > 0) // start record buffering
{
auto commonlyAvail = GetCommonlyAvailCapture();
if (!mRecordingException &&
mCaptureTracks.size() > 0)
GuardedCall<void>( [&] {
// start record buffering
auto commonlyAvail = GetCommonlyAvailCapture();
//
// Determine how much this will add to captured tracks
//
double deltat = commonlyAvail / mRate;
//
// Determine how much this will add to captured tracks
//
double deltat = commonlyAvail / mRate;
if (mAudioThreadShouldCallFillBuffersOnce ||
deltat >= mMinCaptureSecsToCopy)
{
// Append captured samples to the end of the WaveTracks.
// The WaveTracks have their own buffering for efficiency.
AutoSaveFile blockFileLog;
auto numChannels = mCaptureTracks.size();
for( i = 0; (int)i < numChannels; i++ )
if (mAudioThreadShouldCallFillBuffersOnce ||
deltat >= mMinCaptureSecsToCopy)
{
auto avail = commonlyAvail;
sampleFormat trackFormat = mCaptureTracks[i]->GetSampleFormat();
// Append captured samples to the end of the WaveTracks.
// The WaveTracks have their own buffering for efficiency.
AutoSaveFile blockFileLog;
auto numChannels = mCaptureTracks.size();
AutoSaveFile appendLog;
if( mFactor == 1.0 )
for( i = 0; (int)i < numChannels; i++ )
{
SampleBuffer temp(avail, trackFormat);
const auto got =
auto avail = commonlyAvail;
sampleFormat trackFormat = mCaptureTracks[i]->GetSampleFormat();
AutoSaveFile appendLog;
if( mFactor == 1.0 )
{
SampleBuffer temp(avail, trackFormat);
const auto got =
mCaptureBuffers[i]->Get(temp.ptr(), trackFormat, avail);
// wxASSERT(got == avail);
// but we can't assert in this thread
wxUnusedVar(got);
mCaptureTracks[i]-> Append(temp.ptr(), trackFormat, avail, 1,
&appendLog);
}
else
{
size_t size = lrint(avail * mFactor);
SampleBuffer temp1(avail, floatSample);
SampleBuffer temp2(size, floatSample);
const auto got =
// wxASSERT(got == avail);
// but we can't assert in this thread
wxUnusedVar(got);
// see comment in second handler about guarantee
mCaptureTracks[i]-> Append(temp.ptr(), trackFormat, avail, 1,
&appendLog);
}
else
{
size_t size = lrint(avail * mFactor);
SampleBuffer temp1(avail, floatSample);
SampleBuffer temp2(size, floatSample);
const auto got =
mCaptureBuffers[i]->Get(temp1.ptr(), floatSample, avail);
// wxASSERT(got == avail);
// but we can't assert in this thread
wxUnusedVar(got);
/* we are re-sampling on the fly. The last resampling call
* must flush any samples left in the rate conversion buffer
* so that they get recorded
*/
const auto results =
// wxASSERT(got == avail);
// but we can't assert in this thread
wxUnusedVar(got);
/* we are re-sampling on the fly. The last resampling call
* must flush any samples left in the rate conversion buffer
* so that they get recorded
*/
const auto results =
mResample[i]->Process(mFactor, (float *)temp1.ptr(), avail,
!IsStreamActive(), (float *)temp2.ptr(), size);
size = results.second;
mCaptureTracks[i]-> Append(temp2.ptr(), floatSample, size, 1,
&appendLog);
!IsStreamActive(), (float *)temp2.ptr(), size);
size = results.second;
// see comment in second handler about guarantee
mCaptureTracks[i]-> Append(temp2.ptr(), floatSample, size, 1,
&appendLog);
}
if (!appendLog.IsEmpty())
{
blockFileLog.StartTag(wxT("recordingrecovery"));
blockFileLog.WriteAttr(wxT("id"), mCaptureTracks[i]->GetAutoSaveIdent());
blockFileLog.WriteAttr(wxT("channel"), (int)i);
blockFileLog.WriteAttr(wxT("numchannels"), numChannels);
blockFileLog.WriteSubTree(appendLog);
blockFileLog.EndTag(wxT("recordingrecovery"));
}
}
if (!appendLog.IsEmpty())
{
blockFileLog.StartTag(wxT("recordingrecovery"));
blockFileLog.WriteAttr(wxT("id"), mCaptureTracks[i]->GetAutoSaveIdent());
blockFileLog.WriteAttr(wxT("channel"), (int)i);
blockFileLog.WriteAttr(wxT("numchannels"), numChannels);
blockFileLog.WriteSubTree(appendLog);
blockFileLog.EndTag(wxT("recordingrecovery"));
}
if (mListener && !blockFileLog.IsEmpty())
mListener->OnAudioIONewBlockFiles(blockFileLog);
}
if (mListener && !blockFileLog.IsEmpty())
mListener->OnAudioIONewBlockFiles(blockFileLog);
}
} // end of record buffering
// end of record buffering
},
// handler
[this] ( AudacityException *pException ) {
if ( pException ) {
// So that we don't attempt to fill the recording buffer again
// before the main thread stops recording
SetRecordingException();
return ;
}
else
// Don't want to intercept other exceptions (?)
throw;
},
delayedHandler
);
}
void AudioIO::SetListener(AudioIOListener* listener)

View File

@ -19,6 +19,7 @@
#include "MemoryX.h"
#include <vector>
#include <wx/atomic.h>
#ifdef USE_MIDI
@ -692,6 +693,14 @@ private:
bool mSilentScrub;
sampleCount mScrubDuration;
#endif
// A flag tested and set in one thread, cleared in another. Perhaps
// this guarantee of atomicity is more cautious than necessary.
wxAtomicInt mRecordingException {};
void SetRecordingException()
{ wxAtomicInc( mRecordingException ); }
void ClearRecordingException()
{ if (mRecordingException) wxAtomicDec( mRecordingException ); }
};
#endif