Mercurial > hg > svapp
changeset 575:c2e27ad7f408 3.0-integration
Pull out record buffer into a separate RT-ish thread
author | Chris Cannam |
---|---|
date | Wed, 04 Jan 2017 16:53:06 +0000 |
parents | b3c35447ef31 |
children | 702272b78bbe |
files | audio/AudioCallbackRecordTarget.cpp audio/AudioCallbackRecordTarget.h |
diffstat | 2 files changed, 156 insertions(+), 70 deletions(-) [+] |
line wrap: on
line diff
--- a/audio/AudioCallbackRecordTarget.cpp Wed Jan 04 16:03:12 2017 +0000 +++ b/audio/AudioCallbackRecordTarget.cpp Wed Jan 04 16:53:06 2017 +0000 @@ -20,6 +20,7 @@ #include "data/model/WritableWaveFileModel.h" #include <QDir> +#include <QTimer> AudioCallbackRecordTarget::AudioCallbackRecordTarget(ViewManagerBase *manager, QString clientName) : @@ -30,6 +31,8 @@ m_recordChannelCount(2), m_frameCount(0), m_model(0), + m_buffers(0), + m_bufferCount(0), m_inputLeft(0.f), m_inputRight(0.f) { @@ -37,14 +40,49 @@ connect(this, SIGNAL(recordStatusChanged(bool)), m_viewManager, SLOT(recordStatusChanged(bool))); + + recreateBuffers(); } AudioCallbackRecordTarget::~AudioCallbackRecordTarget() { - QMutexLocker locker(&m_mutex); m_viewManager->setAudioRecordTarget(0); + + QMutexLocker locker(&m_bufPtrMutex); + for (int c = 0; c < m_bufferCount; ++c) { + delete m_buffers[c]; + } + delete[] m_buffers; } +void +AudioCallbackRecordTarget::recreateBuffers() +{ + static int bufferSize = 441000; + + int count = m_recordChannelCount; + + if (count > m_bufferCount) { + + RingBuffer<float> **newBuffers = new RingBuffer<float> *[count]; + for (int c = 0; c < m_bufferCount; ++c) { + newBuffers[c] = m_buffers[c]; + } + for (int c = m_bufferCount; c < count; ++c) { + newBuffers[c] = new RingBuffer<float>(bufferSize); + } + + // This is the only place where m_buffers is rewritten and + // should be the only possible source of contention against + // putSamples for this mutex (as the model-updating code is + // supposed to run in the same thread as this) + QMutexLocker locker(&m_bufPtrMutex); + delete[] m_buffers; + m_buffers = newBuffers; + m_bufferCount = count; + } +} + int AudioCallbackRecordTarget::getApplicationSampleRate() const { @@ -77,38 +115,77 @@ AudioCallbackRecordTarget::setSystemRecordChannelCount(int c) { m_recordChannelCount = c; + recreateBuffers(); } void AudioCallbackRecordTarget::putSamples(const float *const *samples, int, int nframes) { + // This may be called from RT context, and in a different thread + // from everything else in this class. It takes a mutex that + // should almost never be contended (see recreateBuffers()) + if (!m_recording) return; + + QMutexLocker locker(&m_bufPtrMutex); + if (m_buffers && m_bufferCount >= m_recordChannelCount) { + for (int c = 0; c < m_recordChannelCount; ++c) { + m_buffers[c]->write(samples[c], nframes); + } + } +} + +void +AudioCallbackRecordTarget::updateModel() +{ bool secChanged = false; sv_frame_t frameToEmit = 0; - { - QMutexLocker locker(&m_mutex); //!!! bad here - if (!m_recording) return; + int nframes = 0; + for (int c = 0; c < m_recordChannelCount; ++c) { + if (c == 0 || m_buffers[c]->getReadSpace() < nframes) { + nframes = m_buffers[c]->getReadSpace(); + } + } - m_model->addSamples(samples, nframes); + if (nframes == 0) { + return; + } - sv_frame_t priorFrameCount = m_frameCount; - m_frameCount += nframes; + float **samples = new float *[m_recordChannelCount]; + for (int c = 0; c < m_recordChannelCount; ++c) { + samples[c] = new float[nframes]; + m_buffers[c]->read(samples[c], nframes); + } - RealTime priorRT = RealTime::frame2RealTime - (priorFrameCount, m_recordSampleRate); - RealTime postRT = RealTime::frame2RealTime - (m_frameCount, m_recordSampleRate); + m_model->addSamples(samples, nframes); - secChanged = (postRT.sec > priorRT.sec); - if (secChanged) { - m_model->updateModel(); //!!! v bad here - frameToEmit = m_frameCount; - } + for (int c = 0; c < m_recordChannelCount; ++c) { + delete[] samples[c]; + } + delete[] samples; + + sv_frame_t priorFrameCount = m_frameCount; + m_frameCount += nframes; + + RealTime priorRT = + RealTime::frame2RealTime(priorFrameCount, m_recordSampleRate); + + RealTime postRT = + RealTime::frame2RealTime(m_frameCount, m_recordSampleRate); + + secChanged = (postRT.sec > priorRT.sec); + if (secChanged) { + m_model->updateModel(); + frameToEmit = m_frameCount; } if (secChanged) { emit recordDurationChanged(frameToEmit, m_recordSampleRate); } + + if (m_recording) { + QTimer::singleShot(1000, this, SLOT(updateModel())); + } } void @@ -131,7 +208,6 @@ void AudioCallbackRecordTarget::modelAboutToBeDeleted() { - QMutexLocker locker(&m_mutex); if (sender() == m_model) { m_model = 0; m_recording = false; @@ -170,68 +246,71 @@ WritableWaveFileModel * AudioCallbackRecordTarget::startRecording() { - { - QMutexLocker locker(&m_mutex); - - if (m_recording) { - SVCERR << "WARNING: AudioCallbackRecordTarget::startRecording: We are already recording" << endl; - return 0; - } - - m_model = 0; - m_frameCount = 0; - - QString folder = getRecordFolder(); - if (folder == "") return 0; - QDir recordedDir(folder); - - QDateTime now = QDateTime::currentDateTime(); - - // Don't use QDateTime::toString(Qt::ISODate) as the ":" character - // isn't permitted in filenames on Windows - QString nowString = now.toString("yyyyMMdd-HHmmss-zzz"); - - QString filename = tr("recorded-%1.wav").arg(nowString); - QString label = tr("Recorded %1").arg(nowString); - - m_audioFileName = recordedDir.filePath(filename); - - m_model = new WritableWaveFileModel(m_recordSampleRate, - m_recordChannelCount, - m_audioFileName); - - if (!m_model->isOK()) { - SVCERR << "ERROR: AudioCallbackRecordTarget::startRecording: Recording failed" - << endl; - //!!! and throw? - delete m_model; - m_model = 0; - return 0; - } - - m_model->setObjectName(label); - m_recording = true; + if (m_recording) { + SVCERR << "WARNING: AudioCallbackRecordTarget::startRecording: We are already recording" << endl; + return 0; } + m_model = 0; + m_frameCount = 0; + + QString folder = getRecordFolder(); + if (folder == "") return 0; + QDir recordedDir(folder); + + QDateTime now = QDateTime::currentDateTime(); + + // Don't use QDateTime::toString(Qt::ISODate) as the ":" character + // isn't permitted in filenames on Windows + QString nowString = now.toString("yyyyMMdd-HHmmss-zzz"); + + QString filename = tr("recorded-%1.wav").arg(nowString); + QString label = tr("Recorded %1").arg(nowString); + + m_audioFileName = recordedDir.filePath(filename); + + m_model = new WritableWaveFileModel(m_recordSampleRate, + m_recordChannelCount, + m_audioFileName); + + if (!m_model->isOK()) { + SVCERR << "ERROR: AudioCallbackRecordTarget::startRecording: Recording failed" + << endl; + //!!! and throw? + delete m_model; + m_model = 0; + return 0; + } + + m_model->setObjectName(label); + m_recording = true; + emit recordStatusChanged(true); + + QTimer::singleShot(1000, this, SLOT(updateModel())); + return m_model; } void AudioCallbackRecordTarget::stopRecording() { - { - QMutexLocker locker(&m_mutex); - if (!m_recording) { - SVCERR << "WARNING: AudioCallbackRecordTarget::startRecording: Not recording" << endl; - return; - } - - m_model->writeComplete(); - m_model = 0; - m_recording = false; + if (!m_recording) { + SVCERR << "WARNING: AudioCallbackRecordTarget::startRecording: Not recording" << endl; + return; } + m_recording = false; + + m_bufPtrMutex.lock(); + m_bufPtrMutex.unlock(); + + // buffers should now be up-to-date + updateModel(); + + m_model->writeComplete(); + m_model = 0; + emit recordStatusChanged(false); emit recordCompleted(); }
--- a/audio/AudioCallbackRecordTarget.h Wed Jan 04 16:03:12 2017 +0000 +++ b/audio/AudioCallbackRecordTarget.h Wed Jan 04 16:53:06 2017 +0000 @@ -20,11 +20,13 @@ #include <bqaudioio/ApplicationRecordTarget.h> #include <string> +#include <atomic> #include <QObject> #include <QMutex> #include "base/BaseTypes.h" +#include "base/RingBuffer.h" class ViewManagerBase; class WritableWaveFileModel; @@ -73,19 +75,24 @@ protected slots: void modelAboutToBeDeleted(); + void updateModel(); private: ViewManagerBase *m_viewManager; std::string m_clientName; - bool m_recording; + std::atomic_bool m_recording; sv_samplerate_t m_recordSampleRate; int m_recordChannelCount; sv_frame_t m_frameCount; QString m_audioFileName; WritableWaveFileModel *m_model; + RingBuffer<float> **m_buffers; + QMutex m_bufPtrMutex; + int m_bufferCount; float m_inputLeft; float m_inputRight; - QMutex m_mutex; + + void recreateBuffers(); }; #endif