From 8e151aba8d31b00a9f8bc3df25b54139f0dab869 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Mon, 20 Aug 2018 09:57:50 -0400 Subject: [PATCH] Split out AudioIO::AllocateBuffers --- src/AudioIO.cpp | 303 +++++++++++++++++++++++++----------------------- src/AudioIO.h | 10 ++ 2 files changed, 168 insertions(+), 145 deletions(-) diff --git a/src/AudioIO.cpp b/src/AudioIO.cpp index a041e8ce3..c314a7d3f 100644 --- a/src/AudioIO.cpp +++ b/src/AudioIO.cpp @@ -1995,8 +1995,6 @@ int AudioIO::StartStream(const TransportTracks &tracks, mCaptureBuffers.reset(); mResample.reset(); - double playbackTime = 4.0; - #ifdef EXPERIMENTAL_MIDI_OUT streamStartTime = 0; streamStartTime = SystemTime(mUsingAlsa); @@ -2005,32 +2003,6 @@ int AudioIO::StartStream(const TransportTracks &tracks, mPlaybackSchedule.Init( t0, t1, options, mCaptureTracks.empty() ? nullptr : &mRecordingSchedule ); const bool scrubbing = mPlaybackSchedule.Interactive(); - if (scrubbing) - playbackTime = - lrint(options.pScrubbingOptions->delay * sampleRate) / sampleRate; - - // - // The RingBuffer sizes, and the max amount of the buffer to - // fill at a time, both grow linearly with the number of - // tracks. This allows us to scale up to many tracks without - // killing performance. - // - - // real playback time to produce with each filling of the buffers - // by the Audio thread (except at the end of playback): - // usually, make fillings fewer and longer for less CPU usage. - // But for useful scrubbing, we can't run too far ahead without checking - // mouse input, so make fillings more and shorter. - // What Audio thread produces for playback is then consumed by the PortAudio - // thread, in many smaller pieces. - wxASSERT( playbackTime >= 0 ); - mPlaybackSamplesToCopy = playbackTime * mRate; - - // Capacity of the playback buffer. - mPlaybackRingBufferSecs = 10.0; - - mCaptureRingBufferSecs = 4.5 + 0.5 * std::min(size_t(16), mCaptureTracks.size()); - mMinCaptureSecsToCopy = 0.2 + 0.2 * std::min(size_t(16), mCaptureTracks.size()); unsigned int playbackChannels = 0; unsigned int captureChannels = 0; @@ -2092,123 +2064,8 @@ int AudioIO::StartStream(const TransportTracks &tracks, return 0; } - // - // The (audio) stream has been opened successfully (assuming we tried - // to open it). We now proceed to - // allocate the memory structures the stream will need. - // - - bool bDone; - do - { - bDone = true; // assume success - try - { - if( mNumPlaybackChannels > 0 ) { - // Allocate output buffers. For every output track we allocate - // a ring buffer of five seconds - auto playbackBufferSize = - (size_t)lrint(mRate * mPlaybackRingBufferSecs); - auto playbackMixBufferSize = - mPlaybackSamplesToCopy; - - mPlaybackBuffers.reinit(mPlaybackTracks.size()); - mPlaybackMixers.reinit(mPlaybackTracks.size()); - - const Mixer::WarpOptions &warpOptions = -#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT - scrubbing - ? Mixer::WarpOptions - (ScrubbingOptions::MinAllowedScrubSpeed(), - ScrubbingOptions::MaxAllowedScrubSpeed()) - : -#endif - Mixer::WarpOptions(mPlaybackSchedule.mTimeTrack); - - for (unsigned int i = 0; i < mPlaybackTracks.size(); i++) - { - // Bug 1763 - We must fade in from zero to avoid a click on starting. - mPlaybackTracks[i]->SetOldChannelGain(0, 0.0); - mPlaybackTracks[i]->SetOldChannelGain(1, 0.0); - mPlaybackBuffers[i] = std::make_unique(floatSample, playbackBufferSize); - - // use track time for the end time, not real time! - WaveTrackConstArray mixTracks; - mixTracks.push_back(mPlaybackTracks[i]); - - double endTime; - if (make_iterator_range(tracks.prerollTracks).contains(mPlaybackTracks[i])) - // Stop playing this track after pre-roll - endTime = t0; - else - // Pass t1 -- not mT1 as may have been adjusted for latency - // -- so that overdub recording stops playing back samples - // at the right time, though transport may continue to record - endTime = t1; - - mPlaybackMixers[i] = std::make_unique - (mixTracks, - // Don't throw for read errors, just play silence: - false, - warpOptions, - mPlaybackSchedule.mT0, - endTime, - 1, - playbackMixBufferSize, false, - mRate, floatSample, false); - mPlaybackMixers[i]->ApplyTrackGains(false); - } - } - - if( mNumCaptureChannels > 0 ) - { - // Allocate input buffers. For every input track we allocate - // a ring buffer of five seconds - auto captureBufferSize = (size_t)(mRate * mCaptureRingBufferSecs + 0.5); - - // In the extraordinarily rare case that we can't even afford 100 samples, just give up. - if(captureBufferSize < 100) - { - StartStreamCleanup(); - AudacityMessageBox(_("Out of memory!")); - return 0; - } - - mCaptureBuffers.reinit(mCaptureTracks.size()); - mResample.reinit(mCaptureTracks.size()); - mFactor = sampleRate / mRate; - - for( unsigned int i = 0; i < mCaptureTracks.size(); i++ ) - { - mCaptureBuffers[i] = std::make_unique - ( mCaptureTracks[i]->GetSampleFormat(), - captureBufferSize ); - mResample[i] = std::make_unique(true, mFactor, mFactor); // constant rate resampling - } - } - } - catch(std::bad_alloc&) - { - // Oops! Ran out of memory. This is pretty rare, so we'll just - // try deleting everything, halving our buffer size, and try again. - StartStreamCleanup(true); - mPlaybackRingBufferSecs *= 0.5; - mPlaybackSamplesToCopy /= 2; - mCaptureRingBufferSecs *= 0.5; - mMinCaptureSecsToCopy *= 0.5; - bDone = false; - - // In the extraordinarily rare case that we can't even afford 100 samples, just give up. - auto playbackBufferSize = (size_t)lrint(mRate * mPlaybackRingBufferSecs); - auto playbackMixBufferSize = mPlaybackSamplesToCopy; - if(playbackBufferSize < 100 || playbackMixBufferSize < 100) - { - StartStreamCleanup(); - AudacityMessageBox(_("Out of memory!")); - return 0; - } - } - } while(!bDone); + if ( ! AllocateBuffers( options, tracks, t0, t1, sampleRate, scrubbing ) ) + return 0; if (mNumPlaybackChannels > 0) { @@ -2365,6 +2222,162 @@ int AudioIO::StartStream(const TransportTracks &tracks, return mStreamToken; } +bool AudioIO::AllocateBuffers( + const AudioIOStartStreamOptions &options, + const TransportTracks &tracks, double t0, double t1, double sampleRate, + bool scrubbing ) +{ + // + // The (audio) stream has been opened successfully (assuming we tried + // to open it). We now proceed to + // allocate the memory structures the stream will need. + // + + // + // The RingBuffer sizes, and the max amount of the buffer to + // fill at a time, both grow linearly with the number of + // tracks. This allows us to scale up to many tracks without + // killing performance. + // + + // real playback time to produce with each filling of the buffers + // by the Audio thread (except at the end of playback): + // usually, make fillings fewer and longer for less CPU usage. + // But for useful scrubbing, we can't run too far ahead without checking + // mouse input, so make fillings more and shorter. + // What Audio thread produces for playback is then consumed by the PortAudio + // thread, in many smaller pieces. + + double playbackTime = 4.0; + if (scrubbing) + playbackTime = + lrint(options.pScrubbingOptions->delay * sampleRate) / sampleRate; + + wxASSERT( playbackTime >= 0 ); + mPlaybackSamplesToCopy = playbackTime * mRate; + + // Capacity of the playback buffer. + mPlaybackRingBufferSecs = 10.0; + + mCaptureRingBufferSecs = 4.5 + 0.5 * std::min(size_t(16), mCaptureTracks.size()); + mMinCaptureSecsToCopy = 0.2 + 0.2 * std::min(size_t(16), mCaptureTracks.size()); + + bool bDone; + do + { + bDone = true; // assume success + try + { + if( mNumPlaybackChannels > 0 ) { + // Allocate output buffers. For every output track we allocate + // a ring buffer of ten seconds + auto playbackBufferSize = + (size_t)lrint(mRate * mPlaybackRingBufferSecs); + auto playbackMixBufferSize = + mPlaybackSamplesToCopy; + + mPlaybackBuffers.reinit(mPlaybackTracks.size()); + mPlaybackMixers.reinit(mPlaybackTracks.size()); + + const Mixer::WarpOptions &warpOptions = +#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT + scrubbing + ? Mixer::WarpOptions + (ScrubbingOptions::MinAllowedScrubSpeed(), + ScrubbingOptions::MaxAllowedScrubSpeed()) + : +#endif + Mixer::WarpOptions(mPlaybackSchedule.mTimeTrack); + + for (unsigned int i = 0; i < mPlaybackTracks.size(); i++) + { + // Bug 1763 - We must fade in from zero to avoid a click on starting. + mPlaybackTracks[i]->SetOldChannelGain(0, 0.0); + mPlaybackTracks[i]->SetOldChannelGain(1, 0.0); + + mPlaybackBuffers[i] = std::make_unique(floatSample, playbackBufferSize); + + // use track time for the end time, not real time! + WaveTrackConstArray mixTracks; + mixTracks.push_back(mPlaybackTracks[i]); + + double endTime; + if (make_iterator_range(tracks.prerollTracks).contains(mPlaybackTracks[i])) + // Stop playing this track after pre-roll + endTime = t0; + else + // Pass t1 -- not mT1 as may have been adjusted for latency + // -- so that overdub recording stops playing back samples + // at the right time, though transport may continue to record + endTime = t1; + + mPlaybackMixers[i] = std::make_unique + (mixTracks, + // Don't throw for read errors, just play silence: + false, + warpOptions, + mPlaybackSchedule.mT0, + endTime, + 1, + playbackMixBufferSize, false, + mRate, floatSample, false); + mPlaybackMixers[i]->ApplyTrackGains(false); + } + } + + if( mNumCaptureChannels > 0 ) + { + // Allocate input buffers. For every input track we allocate + // a ring buffer of five seconds + auto captureBufferSize = (size_t)(mRate * mCaptureRingBufferSecs + 0.5); + + // In the extraordinarily rare case that we can't even afford 100 samples, just give up. + if(captureBufferSize < 100) + { + StartStreamCleanup(); + AudacityMessageBox(_("Out of memory!")); + return false; + } + + mCaptureBuffers.reinit(mCaptureTracks.size()); + mResample.reinit(mCaptureTracks.size()); + mFactor = sampleRate / mRate; + + for( unsigned int i = 0; i < mCaptureTracks.size(); i++ ) + { + mCaptureBuffers[i] = std::make_unique + ( mCaptureTracks[i]->GetSampleFormat(), + captureBufferSize ); + mResample[i] = std::make_unique(true, mFactor, mFactor); // constant rate resampling + } + } + } + catch(std::bad_alloc&) + { + // Oops! Ran out of memory. This is pretty rare, so we'll just + // try deleting everything, halving our buffer size, and try again. + StartStreamCleanup(true); + mPlaybackRingBufferSecs *= 0.5; + mPlaybackSamplesToCopy /= 2; + mCaptureRingBufferSecs *= 0.5; + mMinCaptureSecsToCopy *= 0.5; + bDone = false; + + // In the extraordinarily rare case that we can't even afford 100 samples, just give up. + auto playbackBufferSize = (size_t)lrint(mRate * mPlaybackRingBufferSecs); + auto playbackMixBufferSize = mPlaybackSamplesToCopy; + if(playbackBufferSize < 100 || playbackMixBufferSize < 100) + { + StartStreamCleanup(); + AudacityMessageBox(_("Out of memory!")); + return false; + } + } + } while(!bDone); + + return true; +} + void AudioIO::StartStreamCleanup(bool bOnlyBuffers) { if (mNumPlaybackChannels > 0) diff --git a/src/AudioIO.h b/src/AudioIO.h index 5241e4227..d90c7546a 100644 --- a/src/AudioIO.h +++ b/src/AudioIO.h @@ -581,6 +581,16 @@ private: /** \brief How many sample rates to try */ static const int NumRatesToTry; + /** \brief Allocate RingBuffer structures, and others, needed for playback + * and recording. + * + * Returns true iff successful. + */ + bool AllocateBuffers( + const AudioIOStartStreamOptions &options, + const TransportTracks &tracks, double t0, double t1, double sampleRate, + bool scrubbing ); + /** \brief Clean up after StartStream if it fails. * * If bOnlyBuffers is specified, it only cleans up the buffers. */