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:
parent
4ef8da8f16
commit
9777d3e880
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"));
|
||||
|
||||
|
|
|
@ -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:
|
||||
//
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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];
|
||||
|
|
Loading…
Reference in New Issue