Mercurial > hg > svapp
changeset 559:7b115a6505b8 3.0-integration
Handle increases in the overall channel count by closing and reopening the audio device.
author | Chris Cannam |
---|---|
date | Tue, 13 Dec 2016 12:03:48 +0000 |
parents | 206d65e2b69a |
children | e348e0c52115 |
files | audio/AudioCallbackPlaySource.cpp audio/AudioCallbackPlaySource.h audio/AudioRecordTarget.cpp audio/AudioRecordTarget.h framework/MainWindowBase.cpp |
diffstat | 5 files changed, 148 insertions(+), 48 deletions(-) [+] |
line wrap: on
line diff
--- a/audio/AudioCallbackPlaySource.cpp Mon Dec 12 17:15:24 2016 +0000 +++ b/audio/AudioCallbackPlaySource.cpp Tue Dec 13 12:03:48 2016 +0000 @@ -30,9 +30,13 @@ #include "bqaudioio/SystemPlaybackTarget.h" #include "bqaudioio/ResamplerWrapper.h" +#include "bqvec/VectorOps.h" + #include <rubberband/RubberBandStretcher.h> using namespace RubberBand; +using breakfastquay::v_zero_channels; + #include <iostream> #include <cassert> @@ -55,6 +59,7 @@ m_blockSize(1024), m_sourceSampleRate(0), m_deviceSampleRate(0), + m_deviceChannelCount(0), m_playLatency(0), m_target(0), m_lastRetrievalTimestamp(0.0), @@ -158,7 +163,7 @@ m_lastModelEndFrame = model->getEndFrame(); } - bool buffersChanged = false, srChanged = false; + bool buffersIncreased = false, srChanged = false; int modelChannels = 1; ReadOnlyWaveFileModel *rowfm = qobject_cast<ReadOnlyWaveFileModel *>(model); @@ -225,7 +230,7 @@ if (!m_writeBuffers || (int)m_writeBuffers->size() < getTargetChannelCount()) { clearRingBuffers(true, getTargetChannelCount()); - buffersChanged = true; + buffersIncreased = true; } else { if (willPlay) clearRingBuffers(true); } @@ -256,17 +261,20 @@ m_mutex.unlock(); - //!!! - m_audioGenerator->setTargetChannelCount(getTargetChannelCount()); + if (buffersIncreased) { + SVDEBUG << "AudioCallbackPlaySource::addModel: Number of buffers increased, signalling channelCountIncreased" << endl; + emit channelCountIncreased(); + } + if (!m_fillThread) { m_fillThread = new FillThread(*this); m_fillThread->start(); } #ifdef DEBUG_AUDIO_PLAY_SOURCE - cout << "AudioCallbackPlaySource::addModel: now have " << m_models.size() << " model(s)" << endl; + SVDEBUG << "AudioCallbackPlaySource::addModel: now have " << m_models.size() << " model(s)" << endl; #endif connect(model, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)), @@ -275,7 +283,7 @@ #ifdef DEBUG_AUDIO_PLAY_SOURCE cout << "AudioCallbackPlaySource::addModel: awakening thread" << endl; #endif - + m_condition.wakeAll(); } @@ -580,6 +588,11 @@ void AudioCallbackPlaySource::setSystemPlaybackTarget(breakfastquay::SystemPlaybackTarget *target) { + if (target == 0) { + // reset target-related facts and figures + m_deviceSampleRate = 0; + m_deviceChannelCount = 0; + } m_target = target; } @@ -948,8 +961,9 @@ } void -AudioCallbackPlaySource::setSystemPlaybackChannelCount(int) +AudioCallbackPlaySource::setSystemPlaybackChannelCount(int count) { + m_deviceChannelCount = count; } void @@ -999,6 +1013,12 @@ return m_sourceChannelCount; } +int +AudioCallbackPlaySource::getDeviceChannelCount() const +{ + return m_deviceChannelCount; +} + sv_samplerate_t AudioCallbackPlaySource::getSourceSampleRate() const { @@ -1041,19 +1061,70 @@ } int -AudioCallbackPlaySource::getSourceSamples(int count, float **buffer) +AudioCallbackPlaySource::getSourceSamples(float *const *buffer, + int requestedChannels, + int count) { + // In principle, the target will handle channel mapping in cases + // where our channel count differs from the device's. But that + // only holds if our channel count doesn't change -- i.e. if + // getApplicationChannelCount() always returns the same value as + // it did when the target was created, and if this function always + // returns that number of channels. + // + // Unfortunately that can't hold for us -- we always have at least + // 2 channels but if the user opens a new main model with more + // channels than that (and more than the last main model) then our + // target channel count necessarily gets increased. + // + // We have: + // + // getSourceChannelCount() -> number of channels available to + // provide from real model data + // + // getTargetChannelCount() -> number we will actually provide; + // same as getSourceChannelCount() except that it is always at + // least 2 + // + // getDeviceChannelCount() -> number the device will emit, usually + // equal to the value of getTargetChannelCount() at the time the + // device was initialised, unless the device could not provide + // that number + // + // requestedChannels -> number the device is expecting from us, + // always equal to the value of getTargetChannelCount() at the + // time the device was initialised + // + // If the requested channel count is at least the target channel + // count, then we go ahead and provide the target channels as + // expected. We just zero any spare channels. + // + // If the requested channel count is smaller than the target + // channel count, then we don't know what to do and we provide + // nothing. This shouldn't happen as long as management is on the + // ball -- we emit channelCountIncreased() when the target channel + // count increases, and whatever code "owns" the driver should + // have reopened the audio device when it got that signal. But + // there's a race condition there, which we accommodate with this + // check. + + int channels = getTargetChannelCount(); + if (!m_playing) { #ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING SVDEBUG << "AudioCallbackPlaySource::getSourceSamples: Not playing" << endl; #endif - for (int ch = 0; ch < getTargetChannelCount(); ++ch) { - for (int i = 0; i < count; ++i) { - buffer[ch][i] = 0.0; - } - } + v_zero_channels(buffer, requestedChannels, count); return 0; } + if (requestedChannels < channels) { + SVDEBUG << "AudioCallbackPlaySource::getSourceSamples: Not enough device channels (" << requestedChannels << ", need " << channels << "); hoping device is about to be reopened" << endl; + v_zero_channels(buffer, requestedChannels, count); + return 0; + } + if (requestedChannels > channels) { + v_zero_channels(buffer + channels, requestedChannels - channels, count); + } #ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING SVDEBUG << "AudioCallbackPlaySource::getSourceSamples: Playing" << endl; @@ -1062,7 +1133,7 @@ // Ensure that all buffers have at least the amount of data we // need -- else reduce the size of our requests correspondingly - for (int ch = 0; ch < getTargetChannelCount(); ++ch) { + for (int ch = 0; ch < channels; ++ch) { RingBuffer<float> *rb = getReadRingBuffer(ch); @@ -1125,9 +1196,9 @@ int got = 0; - cerr << "getTargetChannelCount() == " << getTargetChannelCount() << endl; + cerr << "channels == " << channels << endl; - for (int ch = 0; ch < getTargetChannelCount(); ++ch) { + for (int ch = 0; ch < channels; ++ch) { RingBuffer<float> *rb = getReadRingBuffer(ch); @@ -1145,7 +1216,7 @@ #endif } - for (int ch = 0; ch < getTargetChannelCount(); ++ch) { + for (int ch = 0; ch < channels; ++ch) { for (int i = got; i < count; ++i) { buffer[ch][i] = 0.0; } @@ -1163,7 +1234,6 @@ return got; } - int channels = getTargetChannelCount(); sv_frame_t available; sv_frame_t fedToStretcher = 0; int warned = 0; @@ -1238,11 +1308,7 @@ ts->retrieve(buffer, size_t(count)); - for (int c = stretchChannels; c < getTargetChannelCount(); ++c) { - for (int i = 0; i < count; ++i) { - buffer[c][i] = buffer[0][i]; - } - } + v_zero_channels(buffer + stretchChannels, channels - stretchChannels, count); applyAuditioningEffect(count, buffer); @@ -1256,7 +1322,7 @@ } void -AudioCallbackPlaySource::applyAuditioningEffect(sv_frame_t count, float **buffers) +AudioCallbackPlaySource::applyAuditioningEffect(sv_frame_t count, float *const *buffers) { if (m_auditioningPluginBypassed) return; RealTimePluginInstance *plugin = m_auditioningPlugin;
--- a/audio/AudioCallbackPlaySource.h Mon Dec 12 17:15:24 2016 +0000 +++ b/audio/AudioCallbackPlaySource.h Tue Dec 13 12:03:48 2016 +0000 @@ -90,23 +90,23 @@ * from the given frame. If playback is already under way, reseek * to the given frame and continue. */ - virtual void play(sv_frame_t startFrame); + virtual void play(sv_frame_t startFrame) override; /** * Stop playback and ensure that no more data is returned. */ - virtual void stop(); + virtual void stop() override; /** * Return whether playback is currently supposed to be happening. */ - virtual bool isPlaying() const { return m_playing; } + virtual bool isPlaying() const override { return m_playing; } /** * Return the frame number that is currently expected to be coming * out of the speakers. (i.e. compensating for playback latency.) */ - virtual sv_frame_t getCurrentPlayingFrame(); + virtual sv_frame_t getCurrentPlayingFrame() override; /** * Return the last frame that would come out of the speakers if we @@ -133,7 +133,7 @@ * Set the block size of the target audio device. This should be * called by the target class. */ - virtual void setSystemPlaybackBlockSize(int blockSize); + virtual void setSystemPlaybackBlockSize(int blockSize) override; /** * Get the block size of the target audio device. This may be an @@ -150,7 +150,7 @@ * highest last frame across all channels) requested via * getSamples(). The default is zero. */ - void setSystemPlaybackLatency(int); + virtual void setSystemPlaybackLatency(int) override; /** * Get the playback latency of the target audio device. @@ -164,7 +164,7 @@ * source sample rate, this class will resample automatically to * fit. */ - void setSystemPlaybackSampleRate(int); + virtual void setSystemPlaybackSampleRate(int) override; /** * Return the sample rate set by the target audio device (or the @@ -175,9 +175,11 @@ /** * Indicate how many channels the target audio device was opened * with. Note that the target device does channel mixing in the - * case where our requested channel count does not match its. + * case where our requested channel count does not match its, so + * long as we provide the number of channels we specified when the + * target was started in getApplicationChannelCount(). */ - void setSystemPlaybackChannelCount(int); + virtual void setSystemPlaybackChannelCount(int) override; /** * Set the current output levels for metering (for call from the @@ -212,6 +214,13 @@ virtual int getTargetChannelCount() const override; /** + * Get the number of channels of audio the device is + * expecting. Equal to whatever getTargetChannelCount() was + * returning at the time the device was initialised. + */ + int getDeviceChannelCount() const; + + /** * ApplicationPlaybackSource equivalent of the above. * * override from breakfastquay::ApplicationPlaybackSource @@ -249,7 +258,7 @@ * audio data, in all channels. This may safely be called from a * realtime thread. */ - virtual int getSourceSamples(int count, float **buffer); + virtual int getSourceSamples(float *const *buffer, int nchannels, int count) override; /** * Set the time stretcher factor (i.e. playback speed). @@ -293,6 +302,8 @@ sv_samplerate_t available, bool willResample); + void channelCountIncreased(); + void audioOverloadPluginDisabled(); void audioTimeStretchMultiChannelDisabled(); @@ -334,6 +345,7 @@ sv_frame_t m_blockSize; sv_samplerate_t m_sourceSampleRate; sv_samplerate_t m_deviceSampleRate; + int m_deviceChannelCount; sv_frame_t m_playLatency; breakfastquay::SystemPlaybackTarget *m_target; double m_lastRetrievalTimestamp; @@ -393,7 +405,7 @@ sv_frame_t mixModels(sv_frame_t &frame, sv_frame_t count, float **buffers); // Called from getSourceSamples. - void applyAuditioningEffect(sv_frame_t count, float **buffers); + void applyAuditioningEffect(sv_frame_t count, float *const *buffers); // Ranges of current selections, if play selection is active std::vector<RealTime> m_rangeStarts;
--- a/audio/AudioRecordTarget.cpp Mon Dec 12 17:15:24 2016 +0000 +++ b/audio/AudioRecordTarget.cpp Tue Dec 13 12:03:48 2016 +0000 @@ -38,6 +38,18 @@ QMutexLocker locker(&m_mutex); } +int +AudioRecordTarget::getApplicationSampleRate() const +{ + return 0; // don't care +} + +int +AudioRecordTarget::getApplicationChannelCount() const +{ + return m_recordChannelCount; +} + void AudioRecordTarget::setSystemRecordBlockSize(int) { @@ -61,7 +73,7 @@ } void -AudioRecordTarget::putSamples(int nframes, float **samples) +AudioRecordTarget::putSamples(const float *const *samples, int nchannels, int nframes) { bool secChanged = false; sv_frame_t frameToEmit = 0;
--- a/audio/AudioRecordTarget.h Mon Dec 12 17:15:24 2016 +0000 +++ b/audio/AudioRecordTarget.h Tue Dec 13 12:03:48 2016 +0000 @@ -36,21 +36,21 @@ AudioRecordTarget(ViewManagerBase *, QString clientName); virtual ~AudioRecordTarget(); - virtual std::string getClientName() const { return m_clientName; } + virtual std::string getClientName() const override { return m_clientName; } - virtual int getApplicationSampleRate() const { return 0; } // don't care - virtual int getApplicationChannelCount() const { return 2; } + virtual int getApplicationSampleRate() const override; + virtual int getApplicationChannelCount() const override; - virtual void setSystemRecordBlockSize(int); - virtual void setSystemRecordSampleRate(int); - virtual void setSystemRecordLatency(int); - virtual void setSystemRecordChannelCount(int); + virtual void setSystemRecordBlockSize(int) override; + virtual void setSystemRecordSampleRate(int) override; + virtual void setSystemRecordLatency(int) override; + virtual void setSystemRecordChannelCount(int) override; - virtual void putSamples(int nframes, float **samples); + virtual void putSamples(const float *const *samples, int nchannels, int nframes) override; - virtual void setInputLevels(float peakLeft, float peakRight); + virtual void setInputLevels(float peakLeft, float peakRight) override; - virtual void audioProcessingOverload() { } + virtual void audioProcessingOverload() override { } QString getRecordContainerFolder(); QString getRecordFolder();
--- a/framework/MainWindowBase.cpp Mon Dec 12 17:15:24 2016 +0000 +++ b/framework/MainWindowBase.cpp Tue Dec 13 12:03:48 2016 +0000 @@ -242,6 +242,8 @@ connect(m_playSource, SIGNAL(sampleRateMismatch(sv_samplerate_t, sv_samplerate_t, bool)), this, SLOT(sampleRateMismatch(sv_samplerate_t, sv_samplerate_t, bool))); + connect(m_playSource, SIGNAL(channelCountIncreased()), + this, SLOT(recreateAudioIO())); connect(m_playSource, SIGNAL(audioOverloadPluginDisabled()), this, SLOT(audioOverloadPluginDisabled())); connect(m_playSource, SIGNAL(audioTimeStretchMultiChannelDisabled()), @@ -299,7 +301,6 @@ deleteAudioIO(); // Then delete the Application objects. - delete m_resamplerWrapper; delete m_playSource; delete m_recordTarget; @@ -2373,15 +2374,24 @@ MainWindowBase::deleteAudioIO() { // First prevent this trying to call target. - if (m_playSource) m_playSource->setSystemPlaybackTarget(0); + if (m_playSource) { + m_playSource->setSystemPlaybackTarget(0); + m_playSource->setResamplerWrapper(0); + } // Then delete the breakfastquay::System object. // Only one of these two exists! delete m_audioIO; delete m_playTarget; + // And the breakfastquay resampler wrapper. We need to + // delete/recreate this if the channel count changes, which is one + // of the use cases for recreateAudioIO() calling this + delete m_resamplerWrapper; + m_audioIO = 0; m_playTarget = 0; + m_resamplerWrapper = 0; } void