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:
parent
f4e2fb5eac
commit
eeb301e50d
164
src/AudioIO.cpp
164
src/AudioIO.cpp
@ -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)
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user