# HG changeset patch # User Chris Cannam # Date 1481294449 0 # Node ID 2a1e9e017484ec0bf336bc22effc24c47b0e7ade # Parent 8c11ca1ebc39327a9d58ce7f40916fc564bf891b Fixes to sample rate and latency handling diff -r 8c11ca1ebc39 -r 2a1e9e017484 audio/AudioCallbackPlaySource.cpp --- a/audio/AudioCallbackPlaySource.cpp Fri Dec 09 13:46:34 2016 +0000 +++ b/audio/AudioCallbackPlaySource.cpp Fri Dec 09 14:40:49 2016 +0000 @@ -54,7 +54,7 @@ m_sourceChannelCount(0), m_blockSize(1024), m_sourceSampleRate(0), - m_targetSampleRate(0), + m_deviceSampleRate(0), m_playLatency(0), m_target(0), m_lastRetrievalTimestamp(0.0), @@ -231,7 +231,9 @@ } if (srChanged) { + SVCERR << "AudioCallbackPlaySource: Source rate changed" << endl; + if (m_resamplerWrapper) { SVCERR << "AudioCallbackPlaySource: Source sample rate changed to " << m_sourceSampleRate << ", updating resampler wrapper" << endl; @@ -239,6 +241,15 @@ (int(round(m_sourceSampleRate))); m_resamplerWrapper->reset(); } + + delete m_timeStretcher; + delete m_monoStretcher; + m_timeStretcher = 0; + m_monoStretcher = 0; + + if (m_stretchRatio != 1.f) { + setTimeStretch(m_stretchRatio); + } } rebuildRangeLists(); @@ -632,12 +643,12 @@ // This method attempts to estimate which audio sample frame is // "currently coming through the speakers". - sv_samplerate_t targetRate = getTargetSampleRate(); + sv_samplerate_t deviceRate = getDeviceSampleRate(); sv_frame_t latency = m_playLatency; // at target rate RealTime latency_t = RealTime::zeroTime; - if (targetRate != 0) { - latency_t = RealTime::frame2RealTime(latency, targetRate); + if (deviceRate != 0) { + latency_t = RealTime::frame2RealTime(latency, deviceRate); } return getCurrentFrame(latency_t); @@ -652,16 +663,18 @@ sv_frame_t AudioCallbackPlaySource::getCurrentFrame(RealTime latency_t) { - // We resample when filling the ring buffer, and time-stretch when - // draining it. The buffer contains data at the "target rate" and - // the latency provided by the target is also at the target rate. - // Because of the multiple rates involved, we do the actual - // calculation using RealTime instead. + // The ring buffers contain data at the source sample rate and all + // processing (including time stretching) happens at this + // rate. Resampling only happens after the audio data leaves this + // class. + + // (But because historically more than one sample rate could have + // been involved here, we do latency calculations using RealTime + // values instead of samples.) - sv_samplerate_t sourceRate = getSourceSampleRate(); - sv_samplerate_t targetRate = getTargetSampleRate(); + sv_samplerate_t rate = getSourceSampleRate(); - if (sourceRate == 0 || targetRate == 0) return 0; + if (rate == 0) return 0; int inbuffer = 0; // at target rate @@ -681,7 +694,7 @@ bool looping = m_viewManager->getPlayLoopMode(); - RealTime inbuffer_t = RealTime::frame2RealTime(inbuffer, targetRate); + RealTime inbuffer_t = RealTime::frame2RealTime(inbuffer, rate); sv_frame_t stretchlat = 0; double timeRatio = 1.0; @@ -691,7 +704,7 @@ timeRatio = m_timeStretcher->getTimeRatio(); } - RealTime stretchlat_t = RealTime::frame2RealTime(stretchlat, targetRate); + RealTime stretchlat_t = RealTime::frame2RealTime(stretchlat, rate); // When the target has just requested a block from us, the last // sample it obtained was our buffer fill frame count minus the @@ -709,8 +722,7 @@ m_trustworthyTimestamps && lastRetrievalTimestamp != 0.0) { - lastretrieved_t = RealTime::frame2RealTime - (lastRetrievedBlockSize, targetRate); + lastretrieved_t = RealTime::frame2RealTime(lastRetrievedBlockSize, rate); // calculate number of frames at target rate that have elapsed // since the end of the last call to getSourceSamples @@ -727,11 +739,10 @@ } else { - lastretrieved_t = RealTime::frame2RealTime - (getTargetBlockSize(), targetRate); + lastretrieved_t = RealTime::frame2RealTime(getTargetBlockSize(), rate); } - RealTime bufferedto_t = RealTime::frame2RealTime(readBufferFill, sourceRate); + RealTime bufferedto_t = RealTime::frame2RealTime(readBufferFill, rate); if (timeRatio != 1.0) { lastretrieved_t = lastretrieved_t / timeRatio; @@ -757,7 +768,7 @@ - latency_t - stretchlat_t - lastretrieved_t - inbuffer_t + sincerequest_t; if (playing_t < RealTime::zeroTime) playing_t = RealTime::zeroTime; - sv_frame_t frame = RealTime::realTime2Frame(playing_t, sourceRate); + sv_frame_t frame = RealTime::realTime2Frame(playing_t, rate); return m_viewManager->alignPlaybackFrameToReference(frame); } @@ -794,8 +805,7 @@ // duration of playback! if (!m_playStartFramePassed) { - RealTime playstart_t = RealTime::frame2RealTime(m_playStartFrame, - sourceRate); + RealTime playstart_t = RealTime::frame2RealTime(m_playStartFrame, rate); if (playing_t < playstart_t) { // cerr << "playing_t " << playing_t << " < playstart_t " // << playstart_t << endl; @@ -853,7 +863,7 @@ if (playing_t < RealTime::zeroTime) playing_t = RealTime::zeroTime; - sv_frame_t frame = RealTime::realTime2Frame(playing_t, sourceRate); + sv_frame_t frame = RealTime::realTime2Frame(playing_t, rate); if (m_lastCurrentFrame > 0 && !looping) { if (frame < m_lastCurrentFrame) { @@ -938,15 +948,7 @@ void AudioCallbackPlaySource::setSystemPlaybackSampleRate(int sr) { - bool first = (m_targetSampleRate == 0); - - m_targetSampleRate = sr; - - if (first && (m_stretchRatio != 1.f)) { - // couldn't create a stretcher before because we had no sample - // rate: make one now - setTimeStretch(m_stretchRatio); - } + m_deviceSampleRate = sr; } void @@ -983,10 +985,9 @@ } sv_samplerate_t -AudioCallbackPlaySource::getTargetSampleRate() const +AudioCallbackPlaySource::getDeviceSampleRate() const { - if (m_targetSampleRate) return m_targetSampleRate; - else return getSourceSampleRate(); + return m_deviceSampleRate; } int @@ -1013,19 +1014,20 @@ { m_stretchRatio = factor; - if (!getTargetSampleRate()) return; // have to make our stretcher later + int rate = int(getSourceSampleRate()); + if (!rate) return; // have to make our stretcher later if (m_timeStretcher || (factor == 1.0)) { // stretch ratio will be set in next process call if appropriate } else { m_stretcherInputCount = getTargetChannelCount(); RubberBandStretcher *stretcher = new RubberBandStretcher - (int(getTargetSampleRate()), + (rate, m_stretcherInputCount, RubberBandStretcher::OptionProcessRealTime, factor); RubberBandStretcher *monoStretcher = new RubberBandStretcher - (int(getTargetSampleRate()), + (rate, 1, RubberBandStretcher::OptionProcessRealTime, factor); diff -r 8c11ca1ebc39 -r 2a1e9e017484 audio/AudioCallbackPlaySource.h --- a/audio/AudioCallbackPlaySource.h Fri Dec 09 13:46:34 2016 +0000 +++ b/audio/AudioCallbackPlaySource.h Fri Dec 09 14:40:49 2016 +0000 @@ -145,7 +145,7 @@ /** * Set the playback latency of the target audio device, in frames - * at the target sample rate. This is the difference between the + * at the device sample rate. This is the difference between the * frame currently "leaving the speakers" and the last frame (or * highest last frame across all channels) requested via * getSamples(). The default is zero. @@ -170,7 +170,7 @@ * Return the sample rate set by the target audio device (or the * source sample rate if the target hasn't set one). */ - virtual sv_samplerate_t getTargetSampleRate() const; + sv_samplerate_t getDeviceSampleRate() const; /** * Indicate how many channels the target audio device was opened @@ -335,7 +335,7 @@ int m_sourceChannelCount; sv_frame_t m_blockSize; sv_samplerate_t m_sourceSampleRate; - sv_samplerate_t m_targetSampleRate; + sv_samplerate_t m_deviceSampleRate; sv_frame_t m_playLatency; breakfastquay::SystemPlaybackTarget *m_target; double m_lastRetrievalTimestamp;