comparison audio/AudioCallbackRecordTarget.cpp @ 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 298d864113f0
comparison
equal deleted inserted replaced
574:b3c35447ef31 575:c2e27ad7f408
18 #include "base/TempDirectory.h" 18 #include "base/TempDirectory.h"
19 19
20 #include "data/model/WritableWaveFileModel.h" 20 #include "data/model/WritableWaveFileModel.h"
21 21
22 #include <QDir> 22 #include <QDir>
23 #include <QTimer>
23 24
24 AudioCallbackRecordTarget::AudioCallbackRecordTarget(ViewManagerBase *manager, 25 AudioCallbackRecordTarget::AudioCallbackRecordTarget(ViewManagerBase *manager,
25 QString clientName) : 26 QString clientName) :
26 m_viewManager(manager), 27 m_viewManager(manager),
27 m_clientName(clientName.toUtf8().data()), 28 m_clientName(clientName.toUtf8().data()),
28 m_recording(false), 29 m_recording(false),
29 m_recordSampleRate(44100), 30 m_recordSampleRate(44100),
30 m_recordChannelCount(2), 31 m_recordChannelCount(2),
31 m_frameCount(0), 32 m_frameCount(0),
32 m_model(0), 33 m_model(0),
34 m_buffers(0),
35 m_bufferCount(0),
33 m_inputLeft(0.f), 36 m_inputLeft(0.f),
34 m_inputRight(0.f) 37 m_inputRight(0.f)
35 { 38 {
36 m_viewManager->setAudioRecordTarget(this); 39 m_viewManager->setAudioRecordTarget(this);
37 40
38 connect(this, SIGNAL(recordStatusChanged(bool)), 41 connect(this, SIGNAL(recordStatusChanged(bool)),
39 m_viewManager, SLOT(recordStatusChanged(bool))); 42 m_viewManager, SLOT(recordStatusChanged(bool)));
43
44 recreateBuffers();
40 } 45 }
41 46
42 AudioCallbackRecordTarget::~AudioCallbackRecordTarget() 47 AudioCallbackRecordTarget::~AudioCallbackRecordTarget()
43 { 48 {
44 QMutexLocker locker(&m_mutex);
45 m_viewManager->setAudioRecordTarget(0); 49 m_viewManager->setAudioRecordTarget(0);
46 } 50
47 51 QMutexLocker locker(&m_bufPtrMutex);
52 for (int c = 0; c < m_bufferCount; ++c) {
53 delete m_buffers[c];
54 }
55 delete[] m_buffers;
56 }
57
58 void
59 AudioCallbackRecordTarget::recreateBuffers()
60 {
61 static int bufferSize = 441000;
62
63 int count = m_recordChannelCount;
64
65 if (count > m_bufferCount) {
66
67 RingBuffer<float> **newBuffers = new RingBuffer<float> *[count];
68 for (int c = 0; c < m_bufferCount; ++c) {
69 newBuffers[c] = m_buffers[c];
70 }
71 for (int c = m_bufferCount; c < count; ++c) {
72 newBuffers[c] = new RingBuffer<float>(bufferSize);
73 }
74
75 // This is the only place where m_buffers is rewritten and
76 // should be the only possible source of contention against
77 // putSamples for this mutex (as the model-updating code is
78 // supposed to run in the same thread as this)
79 QMutexLocker locker(&m_bufPtrMutex);
80 delete[] m_buffers;
81 m_buffers = newBuffers;
82 m_bufferCount = count;
83 }
84 }
85
48 int 86 int
49 AudioCallbackRecordTarget::getApplicationSampleRate() const 87 AudioCallbackRecordTarget::getApplicationSampleRate() const
50 { 88 {
51 return 0; // don't care 89 return 0; // don't care
52 } 90 }
75 113
76 void 114 void
77 AudioCallbackRecordTarget::setSystemRecordChannelCount(int c) 115 AudioCallbackRecordTarget::setSystemRecordChannelCount(int c)
78 { 116 {
79 m_recordChannelCount = c; 117 m_recordChannelCount = c;
118 recreateBuffers();
80 } 119 }
81 120
82 void 121 void
83 AudioCallbackRecordTarget::putSamples(const float *const *samples, int, int nframes) 122 AudioCallbackRecordTarget::putSamples(const float *const *samples, int, int nframes)
123 {
124 // This may be called from RT context, and in a different thread
125 // from everything else in this class. It takes a mutex that
126 // should almost never be contended (see recreateBuffers())
127 if (!m_recording) return;
128
129 QMutexLocker locker(&m_bufPtrMutex);
130 if (m_buffers && m_bufferCount >= m_recordChannelCount) {
131 for (int c = 0; c < m_recordChannelCount; ++c) {
132 m_buffers[c]->write(samples[c], nframes);
133 }
134 }
135 }
136
137 void
138 AudioCallbackRecordTarget::updateModel()
84 { 139 {
85 bool secChanged = false; 140 bool secChanged = false;
86 sv_frame_t frameToEmit = 0; 141 sv_frame_t frameToEmit = 0;
87 142
88 { 143 int nframes = 0;
89 QMutexLocker locker(&m_mutex); //!!! bad here 144 for (int c = 0; c < m_recordChannelCount; ++c) {
90 if (!m_recording) return; 145 if (c == 0 || m_buffers[c]->getReadSpace() < nframes) {
91 146 nframes = m_buffers[c]->getReadSpace();
92 m_model->addSamples(samples, nframes);
93
94 sv_frame_t priorFrameCount = m_frameCount;
95 m_frameCount += nframes;
96
97 RealTime priorRT = RealTime::frame2RealTime
98 (priorFrameCount, m_recordSampleRate);
99 RealTime postRT = RealTime::frame2RealTime
100 (m_frameCount, m_recordSampleRate);
101
102 secChanged = (postRT.sec > priorRT.sec);
103 if (secChanged) {
104 m_model->updateModel(); //!!! v bad here
105 frameToEmit = m_frameCount;
106 } 147 }
148 }
149
150 if (nframes == 0) {
151 return;
152 }
153
154 float **samples = new float *[m_recordChannelCount];
155 for (int c = 0; c < m_recordChannelCount; ++c) {
156 samples[c] = new float[nframes];
157 m_buffers[c]->read(samples[c], nframes);
158 }
159
160 m_model->addSamples(samples, nframes);
161
162 for (int c = 0; c < m_recordChannelCount; ++c) {
163 delete[] samples[c];
164 }
165 delete[] samples;
166
167 sv_frame_t priorFrameCount = m_frameCount;
168 m_frameCount += nframes;
169
170 RealTime priorRT =
171 RealTime::frame2RealTime(priorFrameCount, m_recordSampleRate);
172
173 RealTime postRT =
174 RealTime::frame2RealTime(m_frameCount, m_recordSampleRate);
175
176 secChanged = (postRT.sec > priorRT.sec);
177 if (secChanged) {
178 m_model->updateModel();
179 frameToEmit = m_frameCount;
107 } 180 }
108 181
109 if (secChanged) { 182 if (secChanged) {
110 emit recordDurationChanged(frameToEmit, m_recordSampleRate); 183 emit recordDurationChanged(frameToEmit, m_recordSampleRate);
111 } 184 }
185
186 if (m_recording) {
187 QTimer::singleShot(1000, this, SLOT(updateModel()));
188 }
112 } 189 }
113 190
114 void 191 void
115 AudioCallbackRecordTarget::setInputLevels(float left, float right) 192 AudioCallbackRecordTarget::setInputLevels(float left, float right)
116 { 193 {
129 } 206 }
130 207
131 void 208 void
132 AudioCallbackRecordTarget::modelAboutToBeDeleted() 209 AudioCallbackRecordTarget::modelAboutToBeDeleted()
133 { 210 {
134 QMutexLocker locker(&m_mutex);
135 if (sender() == m_model) { 211 if (sender() == m_model) {
136 m_model = 0; 212 m_model = 0;
137 m_recording = false; 213 m_recording = false;
138 } 214 }
139 } 215 }
168 } 244 }
169 245
170 WritableWaveFileModel * 246 WritableWaveFileModel *
171 AudioCallbackRecordTarget::startRecording() 247 AudioCallbackRecordTarget::startRecording()
172 { 248 {
173 { 249 if (m_recording) {
174 QMutexLocker locker(&m_mutex); 250 SVCERR << "WARNING: AudioCallbackRecordTarget::startRecording: We are already recording" << endl;
175 251 return 0;
176 if (m_recording) { 252 }
177 SVCERR << "WARNING: AudioCallbackRecordTarget::startRecording: We are already recording" << endl; 253
178 return 0; 254 m_model = 0;
179 } 255 m_frameCount = 0;
180 256
257 QString folder = getRecordFolder();
258 if (folder == "") return 0;
259 QDir recordedDir(folder);
260
261 QDateTime now = QDateTime::currentDateTime();
262
263 // Don't use QDateTime::toString(Qt::ISODate) as the ":" character
264 // isn't permitted in filenames on Windows
265 QString nowString = now.toString("yyyyMMdd-HHmmss-zzz");
266
267 QString filename = tr("recorded-%1.wav").arg(nowString);
268 QString label = tr("Recorded %1").arg(nowString);
269
270 m_audioFileName = recordedDir.filePath(filename);
271
272 m_model = new WritableWaveFileModel(m_recordSampleRate,
273 m_recordChannelCount,
274 m_audioFileName);
275
276 if (!m_model->isOK()) {
277 SVCERR << "ERROR: AudioCallbackRecordTarget::startRecording: Recording failed"
278 << endl;
279 //!!! and throw?
280 delete m_model;
181 m_model = 0; 281 m_model = 0;
182 m_frameCount = 0; 282 return 0;
183 283 }
184 QString folder = getRecordFolder(); 284
185 if (folder == "") return 0; 285 m_model->setObjectName(label);
186 QDir recordedDir(folder); 286 m_recording = true;
187
188 QDateTime now = QDateTime::currentDateTime();
189
190 // Don't use QDateTime::toString(Qt::ISODate) as the ":" character
191 // isn't permitted in filenames on Windows
192 QString nowString = now.toString("yyyyMMdd-HHmmss-zzz");
193
194 QString filename = tr("recorded-%1.wav").arg(nowString);
195 QString label = tr("Recorded %1").arg(nowString);
196
197 m_audioFileName = recordedDir.filePath(filename);
198
199 m_model = new WritableWaveFileModel(m_recordSampleRate,
200 m_recordChannelCount,
201 m_audioFileName);
202
203 if (!m_model->isOK()) {
204 SVCERR << "ERROR: AudioCallbackRecordTarget::startRecording: Recording failed"
205 << endl;
206 //!!! and throw?
207 delete m_model;
208 m_model = 0;
209 return 0;
210 }
211
212 m_model->setObjectName(label);
213 m_recording = true;
214 }
215 287
216 emit recordStatusChanged(true); 288 emit recordStatusChanged(true);
289
290 QTimer::singleShot(1000, this, SLOT(updateModel()));
291
217 return m_model; 292 return m_model;
218 } 293 }
219 294
220 void 295 void
221 AudioCallbackRecordTarget::stopRecording() 296 AudioCallbackRecordTarget::stopRecording()
222 { 297 {
223 { 298 if (!m_recording) {
224 QMutexLocker locker(&m_mutex); 299 SVCERR << "WARNING: AudioCallbackRecordTarget::startRecording: Not recording" << endl;
225 if (!m_recording) { 300 return;
226 SVCERR << "WARNING: AudioCallbackRecordTarget::startRecording: Not recording" << endl; 301 }
227 return; 302
228 } 303 m_recording = false;
229 304
230 m_model->writeComplete(); 305 m_bufPtrMutex.lock();
231 m_model = 0; 306 m_bufPtrMutex.unlock();
232 m_recording = false; 307
233 } 308 // buffers should now be up-to-date
234 309 updateModel();
310
311 m_model->writeComplete();
312 m_model = 0;
313
235 emit recordStatusChanged(false); 314 emit recordStatusChanged(false);
236 emit recordCompleted(); 315 emit recordCompleted();
237 } 316 }
238 317
239 318