Mercurial > hg > svapp
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 |