1 | /****************************************************************************
|
---|
2 | **
|
---|
3 | ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
---|
4 | ** All rights reserved.
|
---|
5 | ** Contact: Nokia Corporation ([email protected])
|
---|
6 | **
|
---|
7 | ** This file is part of the QtMultimedia module of the Qt Toolkit.
|
---|
8 | **
|
---|
9 | ** $QT_BEGIN_LICENSE:LGPL$
|
---|
10 | ** Commercial Usage
|
---|
11 | ** Licensees holding valid Qt Commercial licenses may use this file in
|
---|
12 | ** accordance with the Qt Commercial License Agreement provided with the
|
---|
13 | ** Software or, alternatively, in accordance with the terms contained in
|
---|
14 | ** a written agreement between you and Nokia.
|
---|
15 | **
|
---|
16 | ** GNU Lesser General Public License Usage
|
---|
17 | ** Alternatively, this file may be used under the terms of the GNU Lesser
|
---|
18 | ** General Public License version 2.1 as published by the Free Software
|
---|
19 | ** Foundation and appearing in the file LICENSE.LGPL included in the
|
---|
20 | ** packaging of this file. Please review the following information to
|
---|
21 | ** ensure the GNU Lesser General Public License version 2.1 requirements
|
---|
22 | ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
---|
23 | **
|
---|
24 | ** In addition, as a special exception, Nokia gives you certain additional
|
---|
25 | ** rights. These rights are described in the Nokia Qt LGPL Exception
|
---|
26 | ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
---|
27 | **
|
---|
28 | ** GNU General Public License Usage
|
---|
29 | ** Alternatively, this file may be used under the terms of the GNU
|
---|
30 | ** General Public License version 3.0 as published by the Free Software
|
---|
31 | ** Foundation and appearing in the file LICENSE.GPL included in the
|
---|
32 | ** packaging of this file. Please review the following information to
|
---|
33 | ** ensure the GNU General Public License version 3.0 requirements will be
|
---|
34 | ** met: http://www.gnu.org/copyleft/gpl.html.
|
---|
35 | **
|
---|
36 | ** If you have questions regarding the use of this file, please contact
|
---|
37 | ** Nokia at [email protected].
|
---|
38 | ** $QT_END_LICENSE$
|
---|
39 | **
|
---|
40 | ****************************************************************************/
|
---|
41 |
|
---|
42 | //
|
---|
43 | // W A R N I N G
|
---|
44 | // -------------
|
---|
45 | //
|
---|
46 | // This file is not part of the Qt API. It exists for the convenience
|
---|
47 | // of other Qt classes. This header file may change from version to
|
---|
48 | // version without notice, or even be removed.
|
---|
49 | //
|
---|
50 | // We mean it.
|
---|
51 | //
|
---|
52 |
|
---|
53 | #include <QtCore/qendian.h>
|
---|
54 | #include <QtCore/qtimer.h>
|
---|
55 | #include <QtCore/qdebug.h>
|
---|
56 |
|
---|
57 | #include <QtMultimedia/qaudioinput.h>
|
---|
58 |
|
---|
59 | #include "qaudio_mac_p.h"
|
---|
60 | #include "qaudioinput_mac_p.h"
|
---|
61 | #include "qaudiodeviceinfo_mac_p.h"
|
---|
62 |
|
---|
63 | QT_BEGIN_NAMESPACE
|
---|
64 |
|
---|
65 |
|
---|
66 | namespace QtMultimediaInternal
|
---|
67 | {
|
---|
68 |
|
---|
69 | static const int default_buffer_size = 4 * 1024;
|
---|
70 |
|
---|
71 | class QAudioBufferList
|
---|
72 | {
|
---|
73 | public:
|
---|
74 | QAudioBufferList(AudioStreamBasicDescription const& streamFormat):
|
---|
75 | owner(false),
|
---|
76 | sf(streamFormat)
|
---|
77 | {
|
---|
78 | const bool isInterleaved = (sf.mFormatFlags & kAudioFormatFlagIsNonInterleaved) == 0;
|
---|
79 | const int numberOfBuffers = isInterleaved ? 1 : sf.mChannelsPerFrame;
|
---|
80 |
|
---|
81 | dataSize = 0;
|
---|
82 |
|
---|
83 | bfs = reinterpret_cast<AudioBufferList*>(qMalloc(sizeof(AudioBufferList) +
|
---|
84 | (sizeof(AudioBuffer) * numberOfBuffers)));
|
---|
85 |
|
---|
86 | bfs->mNumberBuffers = numberOfBuffers;
|
---|
87 | for (int i = 0; i < numberOfBuffers; ++i) {
|
---|
88 | bfs->mBuffers[i].mNumberChannels = isInterleaved ? numberOfBuffers : 1;
|
---|
89 | bfs->mBuffers[i].mDataByteSize = 0;
|
---|
90 | bfs->mBuffers[i].mData = 0;
|
---|
91 | }
|
---|
92 | }
|
---|
93 |
|
---|
94 | QAudioBufferList(AudioStreamBasicDescription const& streamFormat, char* buffer, int bufferSize):
|
---|
95 | owner(false),
|
---|
96 | sf(streamFormat),
|
---|
97 | bfs(0)
|
---|
98 | {
|
---|
99 | dataSize = bufferSize;
|
---|
100 |
|
---|
101 | bfs = reinterpret_cast<AudioBufferList*>(qMalloc(sizeof(AudioBufferList) + sizeof(AudioBuffer)));
|
---|
102 |
|
---|
103 | bfs->mNumberBuffers = 1;
|
---|
104 | bfs->mBuffers[0].mNumberChannels = 1;
|
---|
105 | bfs->mBuffers[0].mDataByteSize = dataSize;
|
---|
106 | bfs->mBuffers[0].mData = buffer;
|
---|
107 | }
|
---|
108 |
|
---|
109 | QAudioBufferList(AudioStreamBasicDescription const& streamFormat, int framesToBuffer):
|
---|
110 | owner(true),
|
---|
111 | sf(streamFormat),
|
---|
112 | bfs(0)
|
---|
113 | {
|
---|
114 | const bool isInterleaved = (sf.mFormatFlags & kAudioFormatFlagIsNonInterleaved) == 0;
|
---|
115 | const int numberOfBuffers = isInterleaved ? 1 : sf.mChannelsPerFrame;
|
---|
116 |
|
---|
117 | dataSize = framesToBuffer * sf.mBytesPerFrame;
|
---|
118 |
|
---|
119 | bfs = reinterpret_cast<AudioBufferList*>(qMalloc(sizeof(AudioBufferList) +
|
---|
120 | (sizeof(AudioBuffer) * numberOfBuffers)));
|
---|
121 | bfs->mNumberBuffers = numberOfBuffers;
|
---|
122 | for (int i = 0; i < numberOfBuffers; ++i) {
|
---|
123 | bfs->mBuffers[i].mNumberChannels = isInterleaved ? numberOfBuffers : 1;
|
---|
124 | bfs->mBuffers[i].mDataByteSize = dataSize;
|
---|
125 | bfs->mBuffers[i].mData = qMalloc(dataSize);
|
---|
126 | }
|
---|
127 | }
|
---|
128 |
|
---|
129 | ~QAudioBufferList()
|
---|
130 | {
|
---|
131 | if (owner) {
|
---|
132 | for (UInt32 i = 0; i < bfs->mNumberBuffers; ++i)
|
---|
133 | qFree(bfs->mBuffers[i].mData);
|
---|
134 | }
|
---|
135 |
|
---|
136 | qFree(bfs);
|
---|
137 | }
|
---|
138 |
|
---|
139 | AudioBufferList* audioBufferList() const
|
---|
140 | {
|
---|
141 | return bfs;
|
---|
142 | }
|
---|
143 |
|
---|
144 | char* data(int buffer = 0) const
|
---|
145 | {
|
---|
146 | return static_cast<char*>(bfs->mBuffers[buffer].mData);
|
---|
147 | }
|
---|
148 |
|
---|
149 | qint64 bufferSize(int buffer = 0) const
|
---|
150 | {
|
---|
151 | return bfs->mBuffers[buffer].mDataByteSize;
|
---|
152 | }
|
---|
153 |
|
---|
154 | int frameCount(int buffer = 0) const
|
---|
155 | {
|
---|
156 | return bfs->mBuffers[buffer].mDataByteSize / sf.mBytesPerFrame;
|
---|
157 | }
|
---|
158 |
|
---|
159 | int packetCount(int buffer = 0) const
|
---|
160 | {
|
---|
161 | return bfs->mBuffers[buffer].mDataByteSize / sf.mBytesPerPacket;
|
---|
162 | }
|
---|
163 |
|
---|
164 | int packetSize() const
|
---|
165 | {
|
---|
166 | return sf.mBytesPerPacket;
|
---|
167 | }
|
---|
168 |
|
---|
169 | void reset()
|
---|
170 | {
|
---|
171 | for (UInt32 i = 0; i < bfs->mNumberBuffers; ++i) {
|
---|
172 | bfs->mBuffers[i].mDataByteSize = dataSize;
|
---|
173 | bfs->mBuffers[i].mData = 0;
|
---|
174 | }
|
---|
175 | }
|
---|
176 |
|
---|
177 | private:
|
---|
178 | bool owner;
|
---|
179 | int dataSize;
|
---|
180 | AudioStreamBasicDescription sf;
|
---|
181 | AudioBufferList* bfs;
|
---|
182 | };
|
---|
183 |
|
---|
184 | class QAudioPacketFeeder
|
---|
185 | {
|
---|
186 | public:
|
---|
187 | QAudioPacketFeeder(QAudioBufferList* abl):
|
---|
188 | audioBufferList(abl)
|
---|
189 | {
|
---|
190 | totalPackets = audioBufferList->packetCount();
|
---|
191 | position = 0;
|
---|
192 | }
|
---|
193 |
|
---|
194 | bool feed(AudioBufferList& dst, UInt32& packetCount)
|
---|
195 | {
|
---|
196 | if (position == totalPackets) {
|
---|
197 | dst.mBuffers[0].mDataByteSize = 0;
|
---|
198 | packetCount = 0;
|
---|
199 | return false;
|
---|
200 | }
|
---|
201 |
|
---|
202 | if (totalPackets - position < packetCount)
|
---|
203 | packetCount = totalPackets - position;
|
---|
204 |
|
---|
205 | dst.mBuffers[0].mDataByteSize = packetCount * audioBufferList->packetSize();
|
---|
206 | dst.mBuffers[0].mData = audioBufferList->data() + (position * audioBufferList->packetSize());
|
---|
207 |
|
---|
208 | position += packetCount;
|
---|
209 |
|
---|
210 | return true;
|
---|
211 | }
|
---|
212 |
|
---|
213 | bool empty() const
|
---|
214 | {
|
---|
215 | return position == totalPackets;
|
---|
216 | }
|
---|
217 |
|
---|
218 | private:
|
---|
219 | UInt32 totalPackets;
|
---|
220 | UInt32 position;
|
---|
221 | QAudioBufferList* audioBufferList;
|
---|
222 | };
|
---|
223 |
|
---|
224 | class QAudioInputBuffer : public QObject
|
---|
225 | {
|
---|
226 | Q_OBJECT
|
---|
227 |
|
---|
228 | public:
|
---|
229 | QAudioInputBuffer(int bufferSize,
|
---|
230 | int maxPeriodSize,
|
---|
231 | AudioStreamBasicDescription const& inputFormat,
|
---|
232 | AudioStreamBasicDescription const& outputFormat,
|
---|
233 | QObject* parent):
|
---|
234 | QObject(parent),
|
---|
235 | m_deviceError(false),
|
---|
236 | m_audioConverter(0),
|
---|
237 | m_inputFormat(inputFormat),
|
---|
238 | m_outputFormat(outputFormat)
|
---|
239 | {
|
---|
240 | m_maxPeriodSize = maxPeriodSize;
|
---|
241 | m_periodTime = m_maxPeriodSize / m_outputFormat.mBytesPerFrame * 1000 / m_outputFormat.mSampleRate;
|
---|
242 | m_buffer = new QAudioRingBuffer(bufferSize + (bufferSize % maxPeriodSize == 0 ? 0 : maxPeriodSize - (bufferSize % maxPeriodSize)));
|
---|
243 | m_inputBufferList = new QAudioBufferList(m_inputFormat);
|
---|
244 |
|
---|
245 | m_flushTimer = new QTimer(this);
|
---|
246 | connect(m_flushTimer, SIGNAL(timeout()), SLOT(flushBuffer()));
|
---|
247 |
|
---|
248 | if (toQAudioFormat(inputFormat) != toQAudioFormat(outputFormat)) {
|
---|
249 | if (AudioConverterNew(&m_inputFormat, &m_outputFormat, &m_audioConverter) != noErr) {
|
---|
250 | qWarning() << "QAudioInput: Unable to create an Audio Converter";
|
---|
251 | m_audioConverter = 0;
|
---|
252 | }
|
---|
253 | }
|
---|
254 | }
|
---|
255 |
|
---|
256 | ~QAudioInputBuffer()
|
---|
257 | {
|
---|
258 | delete m_buffer;
|
---|
259 | }
|
---|
260 |
|
---|
261 | qint64 renderFromDevice(AudioUnit audioUnit,
|
---|
262 | AudioUnitRenderActionFlags* ioActionFlags,
|
---|
263 | const AudioTimeStamp* inTimeStamp,
|
---|
264 | UInt32 inBusNumber,
|
---|
265 | UInt32 inNumberFrames)
|
---|
266 | {
|
---|
267 | const bool pullMode = m_device == 0;
|
---|
268 |
|
---|
269 | OSStatus err;
|
---|
270 | qint64 framesRendered = 0;
|
---|
271 |
|
---|
272 | m_inputBufferList->reset();
|
---|
273 | err = AudioUnitRender(audioUnit,
|
---|
274 | ioActionFlags,
|
---|
275 | inTimeStamp,
|
---|
276 | inBusNumber,
|
---|
277 | inNumberFrames,
|
---|
278 | m_inputBufferList->audioBufferList());
|
---|
279 |
|
---|
280 | if (m_audioConverter != 0) {
|
---|
281 | QAudioPacketFeeder feeder(m_inputBufferList);
|
---|
282 |
|
---|
283 | int copied = 0;
|
---|
284 | const int available = m_buffer->free();
|
---|
285 |
|
---|
286 | while (err == noErr && !feeder.empty()) {
|
---|
287 | QAudioRingBuffer::Region region = m_buffer->acquireWriteRegion(available);
|
---|
288 |
|
---|
289 | if (region.second == 0)
|
---|
290 | break;
|
---|
291 |
|
---|
292 | AudioBufferList output;
|
---|
293 | output.mNumberBuffers = 1;
|
---|
294 | output.mBuffers[0].mNumberChannels = 1;
|
---|
295 | output.mBuffers[0].mDataByteSize = region.second;
|
---|
296 | output.mBuffers[0].mData = region.first;
|
---|
297 |
|
---|
298 | UInt32 packetSize = region.second / m_outputFormat.mBytesPerPacket;
|
---|
299 | err = AudioConverterFillComplexBuffer(m_audioConverter,
|
---|
300 | converterCallback,
|
---|
301 | &feeder,
|
---|
302 | &packetSize,
|
---|
303 | &output,
|
---|
304 | 0);
|
---|
305 | region.second = output.mBuffers[0].mDataByteSize;
|
---|
306 | copied += region.second;
|
---|
307 |
|
---|
308 | m_buffer->releaseWriteRegion(region);
|
---|
309 | }
|
---|
310 |
|
---|
311 | framesRendered += copied / m_outputFormat.mBytesPerFrame;
|
---|
312 | }
|
---|
313 | else {
|
---|
314 | const int available = m_inputBufferList->bufferSize();
|
---|
315 | bool wecan = true;
|
---|
316 | int copied = 0;
|
---|
317 |
|
---|
318 | while (wecan && copied < available) {
|
---|
319 | QAudioRingBuffer::Region region = m_buffer->acquireWriteRegion(available - copied);
|
---|
320 |
|
---|
321 | if (region.second > 0) {
|
---|
322 | memcpy(region.first, m_inputBufferList->data() + copied, region.second);
|
---|
323 | copied += region.second;
|
---|
324 | }
|
---|
325 | else
|
---|
326 | wecan = false;
|
---|
327 |
|
---|
328 | m_buffer->releaseWriteRegion(region);
|
---|
329 | }
|
---|
330 |
|
---|
331 | framesRendered = copied / m_outputFormat.mBytesPerFrame;
|
---|
332 | }
|
---|
333 |
|
---|
334 | if (pullMode && framesRendered > 0)
|
---|
335 | emit readyRead();
|
---|
336 |
|
---|
337 | return framesRendered;
|
---|
338 | }
|
---|
339 |
|
---|
340 | qint64 readBytes(char* data, qint64 len)
|
---|
341 | {
|
---|
342 | bool wecan = true;
|
---|
343 | qint64 bytesCopied = 0;
|
---|
344 |
|
---|
345 | len -= len % m_maxPeriodSize;
|
---|
346 | while (wecan && bytesCopied < len) {
|
---|
347 | QAudioRingBuffer::Region region = m_buffer->acquireReadRegion(len - bytesCopied);
|
---|
348 |
|
---|
349 | if (region.second > 0) {
|
---|
350 | memcpy(data + bytesCopied, region.first, region.second);
|
---|
351 | bytesCopied += region.second;
|
---|
352 | }
|
---|
353 | else
|
---|
354 | wecan = false;
|
---|
355 |
|
---|
356 | m_buffer->releaseReadRegion(region);
|
---|
357 | }
|
---|
358 |
|
---|
359 | return bytesCopied;
|
---|
360 | }
|
---|
361 |
|
---|
362 | void setFlushDevice(QIODevice* device)
|
---|
363 | {
|
---|
364 | if (m_device != device)
|
---|
365 | m_device = device;
|
---|
366 | }
|
---|
367 |
|
---|
368 | void startFlushTimer()
|
---|
369 | {
|
---|
370 | if (m_device != 0) {
|
---|
371 | m_flushTimer->start((m_buffer->size() - (m_maxPeriodSize * 2)) / m_maxPeriodSize * m_periodTime);
|
---|
372 | }
|
---|
373 | }
|
---|
374 |
|
---|
375 | void stopFlushTimer()
|
---|
376 | {
|
---|
377 | m_flushTimer->stop();
|
---|
378 | }
|
---|
379 |
|
---|
380 | void flush(bool all = false)
|
---|
381 | {
|
---|
382 | if (m_device == 0)
|
---|
383 | return;
|
---|
384 |
|
---|
385 | const int used = m_buffer->used();
|
---|
386 | const int readSize = all ? used : used - (used % m_maxPeriodSize);
|
---|
387 |
|
---|
388 | if (readSize > 0) {
|
---|
389 | bool wecan = true;
|
---|
390 | int flushed = 0;
|
---|
391 |
|
---|
392 | while (!m_deviceError && wecan && flushed < readSize) {
|
---|
393 | QAudioRingBuffer::Region region = m_buffer->acquireReadRegion(readSize - flushed);
|
---|
394 |
|
---|
395 | if (region.second > 0) {
|
---|
396 | int bytesWritten = m_device->write(region.first, region.second);
|
---|
397 | if (bytesWritten < 0) {
|
---|
398 | stopFlushTimer();
|
---|
399 | m_deviceError = true;
|
---|
400 | }
|
---|
401 | else {
|
---|
402 | region.second = bytesWritten;
|
---|
403 | flushed += bytesWritten;
|
---|
404 | wecan = bytesWritten != 0;
|
---|
405 | }
|
---|
406 | }
|
---|
407 | else
|
---|
408 | wecan = false;
|
---|
409 |
|
---|
410 | m_buffer->releaseReadRegion(region);
|
---|
411 | }
|
---|
412 | }
|
---|
413 | }
|
---|
414 |
|
---|
415 | void reset()
|
---|
416 | {
|
---|
417 | m_buffer->reset();
|
---|
418 | m_deviceError = false;
|
---|
419 | }
|
---|
420 |
|
---|
421 | int available() const
|
---|
422 | {
|
---|
423 | return m_buffer->free();
|
---|
424 | }
|
---|
425 |
|
---|
426 | int used() const
|
---|
427 | {
|
---|
428 | return m_buffer->used();
|
---|
429 | }
|
---|
430 |
|
---|
431 | signals:
|
---|
432 | void readyRead();
|
---|
433 |
|
---|
434 | private slots:
|
---|
435 | void flushBuffer()
|
---|
436 | {
|
---|
437 | flush();
|
---|
438 | }
|
---|
439 |
|
---|
440 | private:
|
---|
441 | bool m_deviceError;
|
---|
442 | int m_maxPeriodSize;
|
---|
443 | int m_periodTime;
|
---|
444 | QIODevice* m_device;
|
---|
445 | QTimer* m_flushTimer;
|
---|
446 | QAudioRingBuffer* m_buffer;
|
---|
447 | QAudioBufferList* m_inputBufferList;
|
---|
448 | AudioConverterRef m_audioConverter;
|
---|
449 | AudioStreamBasicDescription m_inputFormat;
|
---|
450 | AudioStreamBasicDescription m_outputFormat;
|
---|
451 |
|
---|
452 | const static OSStatus as_empty = 'qtem';
|
---|
453 |
|
---|
454 | // Converter callback
|
---|
455 | static OSStatus converterCallback(AudioConverterRef inAudioConverter,
|
---|
456 | UInt32* ioNumberDataPackets,
|
---|
457 | AudioBufferList* ioData,
|
---|
458 | AudioStreamPacketDescription** outDataPacketDescription,
|
---|
459 | void* inUserData)
|
---|
460 | {
|
---|
461 | Q_UNUSED(inAudioConverter);
|
---|
462 | Q_UNUSED(outDataPacketDescription);
|
---|
463 |
|
---|
464 | QAudioPacketFeeder* feeder = static_cast<QAudioPacketFeeder*>(inUserData);
|
---|
465 |
|
---|
466 | if (!feeder->feed(*ioData, *ioNumberDataPackets))
|
---|
467 | return as_empty;
|
---|
468 |
|
---|
469 | return noErr;
|
---|
470 | }
|
---|
471 | };
|
---|
472 |
|
---|
473 |
|
---|
474 | class MacInputDevice : public QIODevice
|
---|
475 | {
|
---|
476 | Q_OBJECT
|
---|
477 |
|
---|
478 | public:
|
---|
479 | MacInputDevice(QAudioInputBuffer* audioBuffer, QObject* parent):
|
---|
480 | QIODevice(parent),
|
---|
481 | m_audioBuffer(audioBuffer)
|
---|
482 | {
|
---|
483 | open(QIODevice::ReadOnly | QIODevice::Unbuffered);
|
---|
484 | connect(m_audioBuffer, SIGNAL(readyRead()), SIGNAL(readyRead()));
|
---|
485 | }
|
---|
486 |
|
---|
487 | qint64 readData(char* data, qint64 len)
|
---|
488 | {
|
---|
489 | return m_audioBuffer->readBytes(data, len);
|
---|
490 | }
|
---|
491 |
|
---|
492 | qint64 writeData(const char* data, qint64 len)
|
---|
493 | {
|
---|
494 | Q_UNUSED(data);
|
---|
495 | Q_UNUSED(len);
|
---|
496 |
|
---|
497 | return 0;
|
---|
498 | }
|
---|
499 |
|
---|
500 | bool isSequential() const
|
---|
501 | {
|
---|
502 | return true;
|
---|
503 | }
|
---|
504 |
|
---|
505 | private:
|
---|
506 | QAudioInputBuffer* m_audioBuffer;
|
---|
507 | };
|
---|
508 |
|
---|
509 | }
|
---|
510 |
|
---|
511 |
|
---|
512 | QAudioInputPrivate::QAudioInputPrivate(const QByteArray& device, QAudioFormat const& format):
|
---|
513 | audioFormat(format)
|
---|
514 | {
|
---|
515 | QDataStream ds(device);
|
---|
516 | quint32 did, mode;
|
---|
517 |
|
---|
518 | ds >> did >> mode;
|
---|
519 |
|
---|
520 | if (QAudio::Mode(mode) == QAudio::AudioOutput)
|
---|
521 | errorCode = QAudio::OpenError;
|
---|
522 | else {
|
---|
523 | audioDeviceInfo = new QAudioDeviceInfoInternal(device, QAudio::AudioInput);
|
---|
524 | isOpen = false;
|
---|
525 | audioDeviceId = AudioDeviceID(did);
|
---|
526 | audioUnit = 0;
|
---|
527 | startTime = 0;
|
---|
528 | totalFrames = 0;
|
---|
529 | audioBuffer = 0;
|
---|
530 | internalBufferSize = QtMultimediaInternal::default_buffer_size;
|
---|
531 | clockFrequency = AudioGetHostClockFrequency() / 1000;
|
---|
532 | errorCode = QAudio::NoError;
|
---|
533 | stateCode = QAudio::StoppedState;
|
---|
534 |
|
---|
535 | intervalTimer = new QTimer(this);
|
---|
536 | intervalTimer->setInterval(1000);
|
---|
537 | connect(intervalTimer, SIGNAL(timeout()), SIGNAL(notify()));
|
---|
538 | }
|
---|
539 | }
|
---|
540 |
|
---|
541 | QAudioInputPrivate::~QAudioInputPrivate()
|
---|
542 | {
|
---|
543 | close();
|
---|
544 | delete audioDeviceInfo;
|
---|
545 | }
|
---|
546 |
|
---|
547 | bool QAudioInputPrivate::open()
|
---|
548 | {
|
---|
549 | UInt32 size = 0;
|
---|
550 |
|
---|
551 | if (isOpen)
|
---|
552 | return true;
|
---|
553 |
|
---|
554 | ComponentDescription cd;
|
---|
555 | cd.componentType = kAudioUnitType_Output;
|
---|
556 | cd.componentSubType = kAudioUnitSubType_HALOutput;
|
---|
557 | cd.componentManufacturer = kAudioUnitManufacturer_Apple;
|
---|
558 | cd.componentFlags = 0;
|
---|
559 | cd.componentFlagsMask = 0;
|
---|
560 |
|
---|
561 | // Open
|
---|
562 | Component cp = FindNextComponent(NULL, &cd);
|
---|
563 | if (cp == 0) {
|
---|
564 | qWarning() << "QAudioInput: Failed to find HAL Output component";
|
---|
565 | return false;
|
---|
566 | }
|
---|
567 |
|
---|
568 | if (OpenAComponent(cp, &audioUnit) != noErr) {
|
---|
569 | qWarning() << "QAudioInput: Unable to Open Output Component";
|
---|
570 | return false;
|
---|
571 | }
|
---|
572 |
|
---|
573 | // Set mode
|
---|
574 | // switch to input mode
|
---|
575 | UInt32 enable = 1;
|
---|
576 | if (AudioUnitSetProperty(audioUnit,
|
---|
577 | kAudioOutputUnitProperty_EnableIO,
|
---|
578 | kAudioUnitScope_Input,
|
---|
579 | 1,
|
---|
580 | &enable,
|
---|
581 | sizeof(enable)) != noErr) {
|
---|
582 | qWarning() << "QAudioInput: Unable to switch to input mode (Enable Input)";
|
---|
583 | return false;
|
---|
584 | }
|
---|
585 |
|
---|
586 | enable = 0;
|
---|
587 | if (AudioUnitSetProperty(audioUnit,
|
---|
588 | kAudioOutputUnitProperty_EnableIO,
|
---|
589 | kAudioUnitScope_Output,
|
---|
590 | 0,
|
---|
591 | &enable,
|
---|
592 | sizeof(enable)) != noErr) {
|
---|
593 | qWarning() << "QAudioInput: Unable to switch to input mode (Disable output)";
|
---|
594 | return false;
|
---|
595 | }
|
---|
596 |
|
---|
597 | // register callback
|
---|
598 | AURenderCallbackStruct cb;
|
---|
599 | cb.inputProc = inputCallback;
|
---|
600 | cb.inputProcRefCon = this;
|
---|
601 |
|
---|
602 | if (AudioUnitSetProperty(audioUnit,
|
---|
603 | kAudioOutputUnitProperty_SetInputCallback,
|
---|
604 | kAudioUnitScope_Global,
|
---|
605 | 0,
|
---|
606 | &cb,
|
---|
607 | sizeof(cb)) != noErr) {
|
---|
608 | qWarning() << "QAudioInput: Failed to set AudioUnit callback";
|
---|
609 | return false;
|
---|
610 | }
|
---|
611 |
|
---|
612 | // Set Audio Device
|
---|
613 | if (AudioUnitSetProperty(audioUnit,
|
---|
614 | kAudioOutputUnitProperty_CurrentDevice,
|
---|
615 | kAudioUnitScope_Global,
|
---|
616 | 0,
|
---|
617 | &audioDeviceId,
|
---|
618 | sizeof(audioDeviceId)) != noErr) {
|
---|
619 | qWarning() << "QAudioInput: Unable to use configured device";
|
---|
620 | return false;
|
---|
621 | }
|
---|
622 |
|
---|
623 | // Set format
|
---|
624 | // Wanted
|
---|
625 | streamFormat = toAudioStreamBasicDescription(audioFormat);
|
---|
626 |
|
---|
627 | // Required on unit
|
---|
628 | if (audioFormat == audioDeviceInfo->preferredFormat()) {
|
---|
629 | deviceFormat = streamFormat;
|
---|
630 | AudioUnitSetProperty(audioUnit,
|
---|
631 | kAudioUnitProperty_StreamFormat,
|
---|
632 | kAudioUnitScope_Output,
|
---|
633 | 1,
|
---|
634 | &deviceFormat,
|
---|
635 | sizeof(deviceFormat));
|
---|
636 | }
|
---|
637 | else {
|
---|
638 | size = sizeof(deviceFormat);
|
---|
639 | if (AudioUnitGetProperty(audioUnit,
|
---|
640 | kAudioUnitProperty_StreamFormat,
|
---|
641 | kAudioUnitScope_Input,
|
---|
642 | 1,
|
---|
643 | &deviceFormat,
|
---|
644 | &size) != noErr) {
|
---|
645 | qWarning() << "QAudioInput: Unable to retrieve device format";
|
---|
646 | return false;
|
---|
647 | }
|
---|
648 |
|
---|
649 | if (AudioUnitSetProperty(audioUnit,
|
---|
650 | kAudioUnitProperty_StreamFormat,
|
---|
651 | kAudioUnitScope_Output,
|
---|
652 | 1,
|
---|
653 | &deviceFormat,
|
---|
654 | sizeof(deviceFormat)) != noErr) {
|
---|
655 | qWarning() << "QAudioInput: Unable to set device format";
|
---|
656 | return false;
|
---|
657 | }
|
---|
658 | }
|
---|
659 |
|
---|
660 | // Setup buffers
|
---|
661 | UInt32 numberOfFrames;
|
---|
662 | size = sizeof(UInt32);
|
---|
663 | if (AudioUnitGetProperty(audioUnit,
|
---|
664 | kAudioDevicePropertyBufferFrameSize,
|
---|
665 | kAudioUnitScope_Global,
|
---|
666 | 0,
|
---|
667 | &numberOfFrames,
|
---|
668 | &size) != noErr) {
|
---|
669 | qWarning() << "QAudioInput: Failed to get audio period size";
|
---|
670 | return false;
|
---|
671 | }
|
---|
672 |
|
---|
673 | // Allocate buffer
|
---|
674 | periodSizeBytes = numberOfFrames * streamFormat.mBytesPerFrame;
|
---|
675 |
|
---|
676 | if (internalBufferSize < periodSizeBytes * 2)
|
---|
677 | internalBufferSize = periodSizeBytes * 2;
|
---|
678 | else
|
---|
679 | internalBufferSize -= internalBufferSize % streamFormat.mBytesPerFrame;
|
---|
680 |
|
---|
681 | audioBuffer = new QtMultimediaInternal::QAudioInputBuffer(internalBufferSize,
|
---|
682 | periodSizeBytes,
|
---|
683 | deviceFormat,
|
---|
684 | streamFormat,
|
---|
685 | this);
|
---|
686 |
|
---|
687 | audioIO = new QtMultimediaInternal::MacInputDevice(audioBuffer, this);
|
---|
688 |
|
---|
689 | // Init
|
---|
690 | if (AudioUnitInitialize(audioUnit) != noErr) {
|
---|
691 | qWarning() << "QAudioInput: Failed to initialize AudioUnit";
|
---|
692 | return false;
|
---|
693 | }
|
---|
694 |
|
---|
695 | isOpen = true;
|
---|
696 |
|
---|
697 | return isOpen;
|
---|
698 | }
|
---|
699 |
|
---|
700 | void QAudioInputPrivate::close()
|
---|
701 | {
|
---|
702 | if (audioUnit != 0) {
|
---|
703 | AudioOutputUnitStop(audioUnit);
|
---|
704 | AudioUnitUninitialize(audioUnit);
|
---|
705 | CloseComponent(audioUnit);
|
---|
706 | }
|
---|
707 |
|
---|
708 | delete audioBuffer;
|
---|
709 | }
|
---|
710 |
|
---|
711 | QAudioFormat QAudioInputPrivate::format() const
|
---|
712 | {
|
---|
713 | return audioFormat;
|
---|
714 | }
|
---|
715 |
|
---|
716 | QIODevice* QAudioInputPrivate::start(QIODevice* device)
|
---|
717 | {
|
---|
718 | QIODevice* op = device;
|
---|
719 |
|
---|
720 | if (!audioDeviceInfo->isFormatSupported(audioFormat) || !open()) {
|
---|
721 | stateCode = QAudio::StoppedState;
|
---|
722 | errorCode = QAudio::OpenError;
|
---|
723 | return audioIO;
|
---|
724 | }
|
---|
725 |
|
---|
726 | reset();
|
---|
727 | audioBuffer->reset();
|
---|
728 | audioBuffer->setFlushDevice(op);
|
---|
729 |
|
---|
730 | if (op == 0)
|
---|
731 | op = audioIO;
|
---|
732 |
|
---|
733 | // Start
|
---|
734 | startTime = AudioGetCurrentHostTime();
|
---|
735 | totalFrames = 0;
|
---|
736 |
|
---|
737 | audioThreadStart();
|
---|
738 |
|
---|
739 | stateCode = QAudio::ActiveState;
|
---|
740 | errorCode = QAudio::NoError;
|
---|
741 | emit stateChanged(stateCode);
|
---|
742 |
|
---|
743 | return op;
|
---|
744 | }
|
---|
745 |
|
---|
746 | void QAudioInputPrivate::stop()
|
---|
747 | {
|
---|
748 | QMutexLocker lock(&mutex);
|
---|
749 | if (stateCode != QAudio::StoppedState) {
|
---|
750 | audioThreadStop();
|
---|
751 | audioBuffer->flush(true);
|
---|
752 |
|
---|
753 | errorCode = QAudio::NoError;
|
---|
754 | stateCode = QAudio::StoppedState;
|
---|
755 | QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, stateCode));
|
---|
756 | }
|
---|
757 | }
|
---|
758 |
|
---|
759 | void QAudioInputPrivate::reset()
|
---|
760 | {
|
---|
761 | QMutexLocker lock(&mutex);
|
---|
762 | if (stateCode != QAudio::StoppedState) {
|
---|
763 | audioThreadStop();
|
---|
764 |
|
---|
765 | errorCode = QAudio::NoError;
|
---|
766 | stateCode = QAudio::StoppedState;
|
---|
767 | QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, stateCode));
|
---|
768 | }
|
---|
769 | }
|
---|
770 |
|
---|
771 | void QAudioInputPrivate::suspend()
|
---|
772 | {
|
---|
773 | QMutexLocker lock(&mutex);
|
---|
774 | if (stateCode == QAudio::ActiveState || stateCode == QAudio::IdleState) {
|
---|
775 | audioThreadStop();
|
---|
776 |
|
---|
777 | errorCode = QAudio::NoError;
|
---|
778 | stateCode = QAudio::SuspendedState;
|
---|
779 | QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, stateCode));
|
---|
780 | }
|
---|
781 | }
|
---|
782 |
|
---|
783 | void QAudioInputPrivate::resume()
|
---|
784 | {
|
---|
785 | QMutexLocker lock(&mutex);
|
---|
786 | if (stateCode == QAudio::SuspendedState) {
|
---|
787 | audioThreadStart();
|
---|
788 |
|
---|
789 | errorCode = QAudio::NoError;
|
---|
790 | stateCode = QAudio::ActiveState;
|
---|
791 | QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, stateCode));
|
---|
792 | }
|
---|
793 | }
|
---|
794 |
|
---|
795 | int QAudioInputPrivate::bytesReady() const
|
---|
796 | {
|
---|
797 | return audioBuffer->used();
|
---|
798 | }
|
---|
799 |
|
---|
800 | int QAudioInputPrivate::periodSize() const
|
---|
801 | {
|
---|
802 | return periodSizeBytes;
|
---|
803 | }
|
---|
804 |
|
---|
805 | void QAudioInputPrivate::setBufferSize(int bs)
|
---|
806 | {
|
---|
807 | internalBufferSize = bs;
|
---|
808 | }
|
---|
809 |
|
---|
810 | int QAudioInputPrivate::bufferSize() const
|
---|
811 | {
|
---|
812 | return internalBufferSize;
|
---|
813 | }
|
---|
814 |
|
---|
815 | void QAudioInputPrivate::setNotifyInterval(int milliSeconds)
|
---|
816 | {
|
---|
817 | if (intervalTimer->interval() == milliSeconds)
|
---|
818 | return;
|
---|
819 |
|
---|
820 | if (milliSeconds <= 0)
|
---|
821 | milliSeconds = 0;
|
---|
822 |
|
---|
823 | intervalTimer->setInterval(milliSeconds);
|
---|
824 | }
|
---|
825 |
|
---|
826 | int QAudioInputPrivate::notifyInterval() const
|
---|
827 | {
|
---|
828 | return intervalTimer->interval();
|
---|
829 | }
|
---|
830 |
|
---|
831 | qint64 QAudioInputPrivate::processedUSecs() const
|
---|
832 | {
|
---|
833 | return totalFrames * 1000000 / audioFormat.frequency();
|
---|
834 | }
|
---|
835 |
|
---|
836 | qint64 QAudioInputPrivate::elapsedUSecs() const
|
---|
837 | {
|
---|
838 | if (stateCode == QAudio::StoppedState)
|
---|
839 | return 0;
|
---|
840 |
|
---|
841 | return (AudioGetCurrentHostTime() - startTime) / (clockFrequency / 1000);
|
---|
842 | }
|
---|
843 |
|
---|
844 | QAudio::Error QAudioInputPrivate::error() const
|
---|
845 | {
|
---|
846 | return errorCode;
|
---|
847 | }
|
---|
848 |
|
---|
849 | QAudio::State QAudioInputPrivate::state() const
|
---|
850 | {
|
---|
851 | return stateCode;
|
---|
852 | }
|
---|
853 |
|
---|
854 | void QAudioInputPrivate::audioThreadStop()
|
---|
855 | {
|
---|
856 | stopTimers();
|
---|
857 | if (audioThreadState.testAndSetAcquire(Running, Stopped))
|
---|
858 | threadFinished.wait(&mutex);
|
---|
859 | }
|
---|
860 |
|
---|
861 | void QAudioInputPrivate::audioThreadStart()
|
---|
862 | {
|
---|
863 | startTimers();
|
---|
864 | audioThreadState = Running;
|
---|
865 | AudioOutputUnitStart(audioUnit);
|
---|
866 | }
|
---|
867 |
|
---|
868 | void QAudioInputPrivate::audioDeviceStop()
|
---|
869 | {
|
---|
870 | AudioOutputUnitStop(audioUnit);
|
---|
871 | audioThreadState = Stopped;
|
---|
872 | threadFinished.wakeOne();
|
---|
873 | }
|
---|
874 |
|
---|
875 | void QAudioInputPrivate::audioDeviceFull()
|
---|
876 | {
|
---|
877 | QMutexLocker lock(&mutex);
|
---|
878 | if (stateCode == QAudio::ActiveState) {
|
---|
879 | audioDeviceStop();
|
---|
880 |
|
---|
881 | errorCode = QAudio::UnderrunError;
|
---|
882 | stateCode = QAudio::IdleState;
|
---|
883 | QMetaObject::invokeMethod(this, "deviceStopped", Qt::QueuedConnection);
|
---|
884 | }
|
---|
885 | }
|
---|
886 |
|
---|
887 | void QAudioInputPrivate::audioDeviceError()
|
---|
888 | {
|
---|
889 | QMutexLocker lock(&mutex);
|
---|
890 | if (stateCode == QAudio::ActiveState) {
|
---|
891 | audioDeviceStop();
|
---|
892 |
|
---|
893 | errorCode = QAudio::IOError;
|
---|
894 | stateCode = QAudio::StoppedState;
|
---|
895 | QMetaObject::invokeMethod(this, "deviceStopped", Qt::QueuedConnection);
|
---|
896 | }
|
---|
897 | }
|
---|
898 |
|
---|
899 | void QAudioInputPrivate::startTimers()
|
---|
900 | {
|
---|
901 | audioBuffer->startFlushTimer();
|
---|
902 | if (intervalTimer->interval() > 0)
|
---|
903 | intervalTimer->start();
|
---|
904 | }
|
---|
905 |
|
---|
906 | void QAudioInputPrivate::stopTimers()
|
---|
907 | {
|
---|
908 | audioBuffer->stopFlushTimer();
|
---|
909 | intervalTimer->stop();
|
---|
910 | }
|
---|
911 |
|
---|
912 | void QAudioInputPrivate::deviceStopped()
|
---|
913 | {
|
---|
914 | stopTimers();
|
---|
915 | emit stateChanged(stateCode);
|
---|
916 | }
|
---|
917 |
|
---|
918 | // Input callback
|
---|
919 | OSStatus QAudioInputPrivate::inputCallback(void* inRefCon,
|
---|
920 | AudioUnitRenderActionFlags* ioActionFlags,
|
---|
921 | const AudioTimeStamp* inTimeStamp,
|
---|
922 | UInt32 inBusNumber,
|
---|
923 | UInt32 inNumberFrames,
|
---|
924 | AudioBufferList* ioData)
|
---|
925 | {
|
---|
926 | Q_UNUSED(ioData);
|
---|
927 |
|
---|
928 | QAudioInputPrivate* d = static_cast<QAudioInputPrivate*>(inRefCon);
|
---|
929 |
|
---|
930 | const int threadState = d->audioThreadState.fetchAndAddAcquire(0);
|
---|
931 | if (threadState == Stopped)
|
---|
932 | d->audioDeviceStop();
|
---|
933 | else {
|
---|
934 | qint64 framesWritten;
|
---|
935 |
|
---|
936 | framesWritten = d->audioBuffer->renderFromDevice(d->audioUnit,
|
---|
937 | ioActionFlags,
|
---|
938 | inTimeStamp,
|
---|
939 | inBusNumber,
|
---|
940 | inNumberFrames);
|
---|
941 |
|
---|
942 | if (framesWritten > 0)
|
---|
943 | d->totalFrames += framesWritten;
|
---|
944 | else if (framesWritten == 0)
|
---|
945 | d->audioDeviceFull();
|
---|
946 | else if (framesWritten < 0)
|
---|
947 | d->audioDeviceError();
|
---|
948 | }
|
---|
949 |
|
---|
950 | return noErr;
|
---|
951 | }
|
---|
952 |
|
---|
953 |
|
---|
954 | QT_END_NAMESPACE
|
---|
955 |
|
---|
956 | #include "qaudioinput_mac_p.moc"
|
---|
957 |
|
---|