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