Alert user to drop-outs during recording...

1) When the program detects this, insert zeroes into the recording to keep the
other good parts synchronized.

2) When recording stops, a message box alerts the user, and a label track is
added showing the lost parts, labelled with consecutive numbers.

3) A menu item visible in alpha builds only is added to Tools, to simulate
recording errors at random times and test the reporting feature.
This commit is contained in:
Paul Licameli 2018-01-15 14:48:39 -05:00
parent 4ef8da8f16
commit 9777d3e880
8 changed files with 121 additions and 10 deletions

View File

@ -1865,6 +1865,8 @@ int AudioIO::StartStream(const WaveTrackConstArray &playbackTracks,
double t0, double t1,
const AudioIOStartStreamOptions &options)
{
mLostSamples = 0;
mLostCaptureIntervals.clear();
auto cleanup = finally ( [this] { ClearRecordingException(); } );
if( IsBusy() )
@ -2711,6 +2713,10 @@ void AudioIO::StopStream()
double recordingOffset =
mLastRecordingOffset + latencyCorrection / 1000.0;
for (auto &interval : mLostCaptureIntervals)
interval.first += recordingOffset,
interval.second += recordingOffset;
for (unsigned int i = 0; i < mCaptureTracks.size(); i++) {
// The calls to Flush, and (less likely) Clear and InsertSilence,
// may cause exceptions because of exhaustion of disk space.
@ -4633,7 +4639,7 @@ int audacityAudioCallback(const void *inputBuffer, void *outputBuffer,
#else
const PaStreamCallbackTimeInfo * WXUNUSED(timeInfo),
#endif
const PaStreamCallbackFlags WXUNUSED(statusFlags), void * WXUNUSED(userData) )
const PaStreamCallbackFlags statusFlags, void * WXUNUSED(userData) )
{
auto numPlaybackChannels = gAudioIO->mNumPlaybackChannels;
auto numPlaybackTracks = gAudioIO->mPlaybackTracks.size();
@ -5145,10 +5151,34 @@ int audacityAudioCallback(const void *inputBuffer, void *outputBuffer,
if( inputBuffer && (numCaptureChannels > 0) )
{
// The error likely from a too-busy CPU falling behind real-time data
// is paInputOverflow, but let's check the other input error too
bool inputError =
(statusFlags & (paInputOverflow | paInputUnderflow))
&& !(statusFlags & paPrimingOutput);
size_t len = framesPerBuffer;
for(unsigned t = 0; t < numCaptureChannels; t++) {
for(unsigned t = 0; t < numCaptureChannels; t++)
len = std::min( len,
gAudioIO->mCaptureBuffers[t]->AvailForPut());
gAudioIO->mCaptureBuffers[t]->AvailForPut());
if (gAudioIO->mSimulateRecordingErrors && 100LL * rand() < RAND_MAX)
// Make spurious errors for purposes of testing the error
// reporting
len = 0;
// A different symptom is that len < framesPerBuffer because
// the other thread, executing FillBuffers, isn't consuming fast
// enough from mCaptureBuffers; maybe it's CPU-bound, or maybe the
// storage device it writes is too slow
if (inputError || len < framesPerBuffer) {
// Assume that any good partial buffer should be written leftmost
// and zeroes will be padded after; label the zeroes.
auto start = gAudioIO->mTime;
auto end = start + framesPerBuffer / gAudioIO->mRate;
auto middle = start + len / gAudioIO->mRate;
auto interval = std::make_pair( middle, end );
gAudioIO->mLostCaptureIntervals.push_back( interval );
}
if (len < framesPerBuffer)
@ -5202,6 +5232,19 @@ int audacityAudioCallback(const void *inputBuffer, void *outputBuffer,
wxUnusedVar(put);
}
}
if (len < framesPerBuffer) {
for(unsigned t = 0; t < numCaptureChannels; t++) {
// Get here probably because of failure to keep up with real
// time, causing loss of input samples.
// Pad with zeroes
const auto put = gAudioIO->mCaptureBuffers[t]->Clear(
gAudioIO->mCaptureFormat, framesPerBuffer - len);
// wxASSERT(put == len);
// but we can't assert in this thread
wxUnusedVar(put);
}
}
}
// Update the current time position if not scrubbing
@ -5325,4 +5368,3 @@ int audacityAudioCallback(const void *inputBuffer, void *outputBuffer,
return callbackReturn;
}

View File

@ -18,6 +18,7 @@
#include "Experimental.h"
#include "MemoryX.h"
#include <utility>
#include <vector>
#include <wx/atomic.h>
@ -677,7 +678,7 @@ private:
unsigned int mNumCaptureChannels;
unsigned int mNumPlaybackChannels;
sampleFormat mCaptureFormat;
int mLostSamples;
unsigned long long mLostSamples{ 0 };
volatile bool mAudioThreadShouldCallFillBuffersOnce;
volatile bool mAudioThreadFillBuffersLoopRunning;
volatile bool mAudioThreadFillBuffersLoopActive;
@ -795,6 +796,15 @@ private:
{ wxAtomicInc( mRecordingException ); }
void ClearRecordingException()
{ if (mRecordingException) wxAtomicDec( mRecordingException ); }
std::vector< std::pair<double, double> > mLostCaptureIntervals;
public:
const std::vector< std::pair<double, double> > &LostCaptureIntervals()
{ return mLostCaptureIntervals; }
// Used only for testing purposes in alpha builds
bool mSimulateRecordingErrors{ false };
};
#endif

View File

@ -1231,6 +1231,14 @@ void AudacityProject::CreateMenusAndCommands()
c->AddSeparator();
c->BeginSubMenu( _("&Tools") );
#ifdef IS_ALPHA
c->AddCheck(wxT("SimulateRecordingErrors"),
_("Simulate Recording Errors"),
FN(OnSimulateRecordingErrors),
gAudioIO->mSimulateRecordingErrors);
#endif
c->AddItem(wxT("Screenshot"), _("&Screenshot Tools..."), FN(OnScreenshot));
// PRL: team consensus for 2.2.0 was, we let end users have this diagnostic,
@ -8407,6 +8415,13 @@ void AudacityProject::OnCrashReport(const CommandContext &)
}
#endif
void AudacityProject::OnSimulateRecordingErrors(const CommandContext &)
{
bool &setting = gAudioIO->mSimulateRecordingErrors;
mCommandManager.Check(wxT("SimulateRecordingErrors"), !setting);
setting = !setting;
}
void AudacityProject::OnScreenshot(const CommandContext &)
{
::OpenScreenshotTools();

View File

@ -509,6 +509,7 @@ void OnBenchmark(const CommandContext &);
#if defined(EXPERIMENTAL_CRASH_REPORT)
void OnCrashReport(const CommandContext &);
#endif
void OnSimulateRecordingErrors(const CommandContext &);
void OnScreenshot(const CommandContext &);
void OnAudioDeviceInfo(const CommandContext &);
#ifdef EXPERIMENTAL_MIDI_OUT

View File

@ -5371,6 +5371,27 @@ void AudacityProject::OnAudioIOStopRecording()
// Only push state if we were capturing and not monitoring
if (GetAudioIOToken() > 0)
{
auto &intervals = gAudioIO->LostCaptureIntervals();
if (intervals.size()) {
// Make a track with labels for recording errors
auto uTrack = GetTrackFactory()->NewLabelTrack();
auto pTrack = uTrack.get();
GetTracks()->Add( std::move(uTrack) );
pTrack->SetName(_("Errors"));
long counter = 1;
for (auto &interval : intervals)
pTrack->AddLabel(
SelectedRegion{ interval.first, interval.second },
wxString::Format(wxT("%ld"), counter++),
-2 );
AudacityMessageBox(_(
"Recorded audio was lost at the labelled locations.\n\
This may have happened because you are saving directly to a \
slow external storage device, or because other applications are \
competing with Audacity for processor time."
));
}
// Add to history
PushState(_("Recorded Audio"), _("Record"));

View File

@ -75,6 +75,27 @@ size_t RingBuffer::Put(samplePtr buffer, sampleFormat format,
return copied;
}
size_t RingBuffer::Clear(sampleFormat format, size_t samplesToClear)
{
samplesToClear = std::min( samplesToClear, AvailForPut() );
size_t cleared = 0;
auto pos = mEnd;
while(samplesToClear) {
auto block = std::min( samplesToClear, mBufferSize - pos );
ClearSamples(mBuffer.ptr(), format, pos, block);
pos = (pos + block) % mBufferSize;
samplesToClear -= block;
cleared += block;
}
mEnd = pos;
return cleared;
}
//
// For the reader only:
//

View File

@ -24,6 +24,7 @@ class RingBuffer {
size_t AvailForPut();
size_t Put(samplePtr buffer, sampleFormat format, size_t samples);
size_t Clear(sampleFormat format, size_t samples);
//
// For the reader only:

View File

@ -76,19 +76,19 @@ const wxChar *GetSampleFormatStr(sampleFormat format)
}
// TODO: Risky? Assumes 0.0f is represented by 0x00000000;
void ClearSamples(samplePtr src, sampleFormat format,
void ClearSamples(samplePtr dst, sampleFormat format,
size_t start, size_t len)
{
auto size = SAMPLE_SIZE(format);
memset(src + start*size, 0, len*size);
memset(dst + start*size, 0, len*size);
}
void ReverseSamples(samplePtr src, sampleFormat format,
void ReverseSamples(samplePtr dst, sampleFormat format,
int start, int len)
{
auto size = SAMPLE_SIZE(format);
samplePtr first = src + start * size;
samplePtr last = src + (start + len - 1) * size;
samplePtr first = dst + start * size;
samplePtr last = dst + (start + len - 1) * size;
enum : size_t { fixedSize = SAMPLE_SIZE(floatSample) };
wxASSERT(size <= fixedSize);
char temp[fixedSize];