source: trunk/src/multimedia/audio/qaudiooutput_mac_p.cpp

Last change on this file was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

  • Property svn:eol-style set to native
File size: 18.8 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation (qt-info@nokia.com)
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 qt-info@nokia.com.
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 <CoreServices/CoreServices.h>
54#include <CoreAudio/CoreAudio.h>
55#include <AudioUnit/AudioUnit.h>
56#include <AudioToolbox/AudioToolbox.h>
57
58#include <QtCore/qendian.h>
59#include <QtCore/qbuffer.h>
60#include <QtCore/qtimer.h>
61#include <QtCore/qdebug.h>
62
63#include <QtMultimedia/qaudiooutput.h>
64
65#include "qaudio_mac_p.h"
66#include "qaudiooutput_mac_p.h"
67#include "qaudiodeviceinfo_mac_p.h"
68
69
70QT_BEGIN_NAMESPACE
71
72
73namespace QtMultimediaInternal
74{
75
76static const int default_buffer_size = 8 * 1024;
77
78
79class QAudioOutputBuffer : public QObject
80{
81 Q_OBJECT
82
83public:
84 QAudioOutputBuffer(int bufferSize, int maxPeriodSize, QAudioFormat const& audioFormat):
85 m_deviceError(false),
86 m_maxPeriodSize(maxPeriodSize),
87 m_device(0)
88 {
89 m_buffer = new QAudioRingBuffer(bufferSize + (bufferSize % maxPeriodSize == 0 ? 0 : maxPeriodSize - (bufferSize % maxPeriodSize)));
90 m_bytesPerFrame = (audioFormat.sampleSize() / 8) * audioFormat.channels();
91 m_periodTime = m_maxPeriodSize / m_bytesPerFrame * 1000 / audioFormat.frequency();
92
93 m_fillTimer = new QTimer(this);
94 connect(m_fillTimer, SIGNAL(timeout()), SLOT(fillBuffer()));
95 }
96
97 ~QAudioOutputBuffer()
98 {
99 delete m_buffer;
100 }
101
102 qint64 readFrames(char* data, qint64 maxFrames)
103 {
104 bool wecan = true;
105 qint64 framesRead = 0;
106
107 while (wecan && framesRead < maxFrames) {
108 QAudioRingBuffer::Region region = m_buffer->acquireReadRegion((maxFrames - framesRead) * m_bytesPerFrame);
109
110 if (region.second > 0) {
111 region.second -= region.second % m_bytesPerFrame;
112 memcpy(data + (framesRead * m_bytesPerFrame), region.first, region.second);
113 framesRead += region.second / m_bytesPerFrame;
114 }
115 else
116 wecan = false;
117
118 m_buffer->releaseReadRegion(region);
119 }
120
121 if (framesRead == 0 && m_deviceError)
122 framesRead = -1;
123
124 return framesRead;
125 }
126
127 qint64 writeBytes(const char* data, qint64 maxSize)
128 {
129 bool wecan = true;
130 qint64 bytesWritten = 0;
131
132 maxSize -= maxSize % m_bytesPerFrame;
133 while (wecan && bytesWritten < maxSize) {
134 QAudioRingBuffer::Region region = m_buffer->acquireWriteRegion(maxSize - bytesWritten);
135
136 if (region.second > 0) {
137 memcpy(region.first, data + bytesWritten, region.second);
138 bytesWritten += region.second;
139 }
140 else
141 wecan = false;
142
143 m_buffer->releaseWriteRegion(region);
144 }
145
146 if (bytesWritten > 0)
147 emit readyRead();
148
149 return bytesWritten;
150 }
151
152 int available() const
153 {
154 return m_buffer->free();
155 }
156
157 void reset()
158 {
159 m_buffer->reset();
160 m_deviceError = false;
161 }
162
163 void setPrefetchDevice(QIODevice* device)
164 {
165 if (m_device != device) {
166 m_device = device;
167 if (m_device != 0)
168 fillBuffer();
169 }
170 }
171
172 void startFillTimer()
173 {
174 if (m_device != 0)
175 m_fillTimer->start(m_buffer->size() / 2 / m_maxPeriodSize * m_periodTime);
176 }
177
178 void stopFillTimer()
179 {
180 m_fillTimer->stop();
181 }
182
183signals:
184 void readyRead();
185
186private slots:
187 void fillBuffer()
188 {
189 const int free = m_buffer->free();
190 const int writeSize = free - (free % m_maxPeriodSize);
191
192 if (writeSize > 0) {
193 bool wecan = true;
194 int filled = 0;
195
196 while (!m_deviceError && wecan && filled < writeSize) {
197 QAudioRingBuffer::Region region = m_buffer->acquireWriteRegion(writeSize - filled);
198
199 if (region.second > 0) {
200 region.second = m_device->read(region.first, region.second);
201 if (region.second > 0)
202 filled += region.second;
203 else if (region.second == 0)
204 wecan = false;
205 else if (region.second < 0) {
206 m_fillTimer->stop();
207 region.second = 0;
208 m_deviceError = true;
209 }
210 }
211 else
212 wecan = false;
213
214 m_buffer->releaseWriteRegion(region);
215 }
216
217 if (filled > 0)
218 emit readyRead();
219 }
220 }
221
222private:
223 bool m_deviceError;
224 int m_maxPeriodSize;
225 int m_bytesPerFrame;
226 int m_periodTime;
227 QIODevice* m_device;
228 QTimer* m_fillTimer;
229 QAudioRingBuffer* m_buffer;
230};
231
232
233}
234
235class MacOutputDevice : public QIODevice
236{
237 Q_OBJECT
238
239public:
240 MacOutputDevice(QtMultimediaInternal::QAudioOutputBuffer* audioBuffer, QObject* parent):
241 QIODevice(parent),
242 m_audioBuffer(audioBuffer)
243 {
244 open(QIODevice::WriteOnly | QIODevice::Unbuffered);
245 }
246
247 qint64 readData(char* data, qint64 len)
248 {
249 Q_UNUSED(data);
250 Q_UNUSED(len);
251
252 return 0;
253 }
254
255 qint64 writeData(const char* data, qint64 len)
256 {
257 return m_audioBuffer->writeBytes(data, len);
258 }
259
260 bool isSequential() const
261 {
262 return true;
263 }
264
265private:
266 QtMultimediaInternal::QAudioOutputBuffer* m_audioBuffer;
267};
268
269
270QAudioOutputPrivate::QAudioOutputPrivate(const QByteArray& device, const QAudioFormat& format):
271 audioFormat(format)
272{
273 QDataStream ds(device);
274 quint32 did, mode;
275
276 ds >> did >> mode;
277
278 if (QAudio::Mode(mode) == QAudio::AudioInput)
279 errorCode = QAudio::OpenError;
280 else {
281 audioDeviceInfo = new QAudioDeviceInfoInternal(device, QAudio::AudioOutput);
282 isOpen = false;
283 audioDeviceId = AudioDeviceID(did);
284 audioUnit = 0;
285 audioIO = 0;
286 startTime = 0;
287 totalFrames = 0;
288 audioBuffer = 0;
289 internalBufferSize = QtMultimediaInternal::default_buffer_size;
290 clockFrequency = AudioGetHostClockFrequency() / 1000;
291 errorCode = QAudio::NoError;
292 stateCode = QAudio::StoppedState;
293 audioThreadState = Stopped;
294
295 intervalTimer = new QTimer(this);
296 intervalTimer->setInterval(1000);
297 connect(intervalTimer, SIGNAL(timeout()), SIGNAL(notify()));
298 }
299}
300
301QAudioOutputPrivate::~QAudioOutputPrivate()
302{
303 delete audioDeviceInfo;
304 close();
305}
306
307bool QAudioOutputPrivate::open()
308{
309 if (errorCode != QAudio::NoError)
310 return false;
311
312 if (isOpen)
313 return true;
314
315 ComponentDescription cd;
316 cd.componentType = kAudioUnitType_Output;
317 cd.componentSubType = kAudioUnitSubType_HALOutput;
318 cd.componentManufacturer = kAudioUnitManufacturer_Apple;
319 cd.componentFlags = 0;
320 cd.componentFlagsMask = 0;
321
322 // Open
323 Component cp = FindNextComponent(NULL, &cd);
324 if (cp == 0) {
325 qWarning() << "QAudioOutput: Failed to find HAL Output component";
326 return false;
327 }
328
329 if (OpenAComponent(cp, &audioUnit) != noErr) {
330 qWarning() << "QAudioOutput: Unable to Open Output Component";
331 return false;
332 }
333
334 // register callback
335 AURenderCallbackStruct cb;
336 cb.inputProc = renderCallback;
337 cb.inputProcRefCon = this;
338
339 if (AudioUnitSetProperty(audioUnit,
340 kAudioUnitProperty_SetRenderCallback,
341 kAudioUnitScope_Global,
342 0,
343 &cb,
344 sizeof(cb)) != noErr) {
345 qWarning() << "QAudioOutput: Failed to set AudioUnit callback";
346 return false;
347 }
348
349 // Set Audio Device
350 if (AudioUnitSetProperty(audioUnit,
351 kAudioOutputUnitProperty_CurrentDevice,
352 kAudioUnitScope_Global,
353 0,
354 &audioDeviceId,
355 sizeof(audioDeviceId)) != noErr) {
356 qWarning() << "QAudioOutput: Unable to use configured device";
357 return false;
358 }
359
360 // Set stream format
361 streamFormat = toAudioStreamBasicDescription(audioFormat);
362
363 UInt32 size = sizeof(streamFormat);
364 if (AudioUnitSetProperty(audioUnit,
365 kAudioUnitProperty_StreamFormat,
366 kAudioUnitScope_Input,
367 0,
368 &streamFormat,
369 sizeof(streamFormat)) != noErr) {
370 qWarning() << "QAudioOutput: Unable to Set Stream information";
371 return false;
372 }
373
374 // Allocate buffer
375 UInt32 numberOfFrames = 0;
376 size = sizeof(UInt32);
377 if (AudioUnitGetProperty(audioUnit,
378 kAudioDevicePropertyBufferFrameSize,
379 kAudioUnitScope_Global,
380 0,
381 &numberOfFrames,
382 &size) != noErr) {
383 qWarning() << "QAudioInput: Failed to get audio period size";
384 return false;
385 }
386
387 periodSizeBytes = numberOfFrames * streamFormat.mBytesPerFrame;
388 if (internalBufferSize < periodSizeBytes * 2)
389 internalBufferSize = periodSizeBytes * 2;
390 else
391 internalBufferSize -= internalBufferSize % streamFormat.mBytesPerFrame;
392
393 audioBuffer = new QtMultimediaInternal::QAudioOutputBuffer(internalBufferSize, periodSizeBytes, audioFormat);
394 connect(audioBuffer, SIGNAL(readyRead()), SLOT(inputReady())); // Pull
395
396 audioIO = new MacOutputDevice(audioBuffer, this);
397
398 // Init
399 if (AudioUnitInitialize(audioUnit)) {
400 qWarning() << "QAudioOutput: Failed to initialize AudioUnit";
401 return false;
402 }
403
404 isOpen = true;
405
406 return true;
407}
408
409void QAudioOutputPrivate::close()
410{
411 if (audioUnit != 0) {
412 AudioOutputUnitStop(audioUnit);
413 AudioUnitUninitialize(audioUnit);
414 CloseComponent(audioUnit);
415 }
416
417 delete audioBuffer;
418}
419
420QAudioFormat QAudioOutputPrivate::format() const
421{
422 return audioFormat;
423}
424
425QIODevice* QAudioOutputPrivate::start(QIODevice* device)
426{
427 QIODevice* op = device;
428
429 if (!audioDeviceInfo->isFormatSupported(audioFormat) || !open()) {
430 stateCode = QAudio::StoppedState;
431 errorCode = QAudio::OpenError;
432 return audioIO;
433 }
434
435 reset();
436 audioBuffer->reset();
437 audioBuffer->setPrefetchDevice(op);
438
439 if (op == 0) {
440 op = audioIO;
441 stateCode = QAudio::IdleState;
442 }
443 else
444 stateCode = QAudio::ActiveState;
445
446 // Start
447 errorCode = QAudio::NoError;
448 totalFrames = 0;
449 startTime = AudioGetCurrentHostTime();
450
451 if (stateCode == QAudio::ActiveState)
452 audioThreadStart();
453
454 emit stateChanged(stateCode);
455
456 return op;
457}
458
459void QAudioOutputPrivate::stop()
460{
461 QMutexLocker lock(&mutex);
462 if (stateCode != QAudio::StoppedState) {
463 audioThreadDrain();
464
465 stateCode = QAudio::StoppedState;
466 errorCode = QAudio::NoError;
467 QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, stateCode));
468 }
469}
470
471void QAudioOutputPrivate::reset()
472{
473 QMutexLocker lock(&mutex);
474 if (stateCode != QAudio::StoppedState) {
475 audioThreadStop();
476
477 stateCode = QAudio::StoppedState;
478 errorCode = QAudio::NoError;
479 QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, stateCode));
480 }
481}
482
483void QAudioOutputPrivate::suspend()
484{
485 QMutexLocker lock(&mutex);
486 if (stateCode == QAudio::ActiveState || stateCode == QAudio::IdleState) {
487 audioThreadStop();
488
489 stateCode = QAudio::SuspendedState;
490 errorCode = QAudio::NoError;
491 QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, stateCode));
492 }
493}
494
495void QAudioOutputPrivate::resume()
496{
497 QMutexLocker lock(&mutex);
498 if (stateCode == QAudio::SuspendedState) {
499 audioThreadStart();
500
501 stateCode = QAudio::ActiveState;
502 errorCode = QAudio::NoError;
503 QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, stateCode));
504 }
505}
506
507int QAudioOutputPrivate::bytesFree() const
508{
509 return audioBuffer->available();
510}
511
512int QAudioOutputPrivate::periodSize() const
513{
514 return periodSizeBytes;
515}
516
517void QAudioOutputPrivate::setBufferSize(int bs)
518{
519 if (stateCode == QAudio::StoppedState)
520 internalBufferSize = bs;
521}
522
523int QAudioOutputPrivate::bufferSize() const
524{
525 return internalBufferSize;
526}
527
528void QAudioOutputPrivate::setNotifyInterval(int milliSeconds)
529{
530 if (intervalTimer->interval() == milliSeconds)
531 return;
532
533 if (milliSeconds <= 0)
534 milliSeconds = 0;
535
536 intervalTimer->setInterval(milliSeconds);
537}
538
539int QAudioOutputPrivate::notifyInterval() const
540{
541 return intervalTimer->interval();
542}
543
544qint64 QAudioOutputPrivate::processedUSecs() const
545{
546 return totalFrames * 1000000 / audioFormat.frequency();
547}
548
549qint64 QAudioOutputPrivate::elapsedUSecs() const
550{
551 if (stateCode == QAudio::StoppedState)
552 return 0;
553
554 return (AudioGetCurrentHostTime() - startTime) / (clockFrequency / 1000);
555}
556
557QAudio::Error QAudioOutputPrivate::error() const
558{
559 return errorCode;
560}
561
562QAudio::State QAudioOutputPrivate::state() const
563{
564 return stateCode;
565}
566
567void QAudioOutputPrivate::audioThreadStart()
568{
569 startTimers();
570 audioThreadState = Running;
571 AudioOutputUnitStart(audioUnit);
572}
573
574void QAudioOutputPrivate::audioThreadStop()
575{
576 stopTimers();
577 if (audioThreadState.testAndSetAcquire(Running, Stopped))
578 threadFinished.wait(&mutex);
579}
580
581void QAudioOutputPrivate::audioThreadDrain()
582{
583 stopTimers();
584 if (audioThreadState.testAndSetAcquire(Running, Draining))
585 threadFinished.wait(&mutex);
586}
587
588void QAudioOutputPrivate::audioDeviceStop()
589{
590 AudioOutputUnitStop(audioUnit);
591 audioThreadState = Stopped;
592 threadFinished.wakeOne();
593}
594
595void QAudioOutputPrivate::audioDeviceIdle()
596{
597 QMutexLocker lock(&mutex);
598 if (stateCode == QAudio::ActiveState) {
599 audioDeviceStop();
600
601 errorCode = QAudio::UnderrunError;
602 stateCode = QAudio::IdleState;
603 QMetaObject::invokeMethod(this, "deviceStopped", Qt::QueuedConnection);
604 }
605}
606
607void QAudioOutputPrivate::audioDeviceError()
608{
609 QMutexLocker lock(&mutex);
610 if (stateCode == QAudio::ActiveState) {
611 audioDeviceStop();
612
613 errorCode = QAudio::IOError;
614 stateCode = QAudio::StoppedState;
615 QMetaObject::invokeMethod(this, "deviceStopped", Qt::QueuedConnection);
616 }
617}
618
619void QAudioOutputPrivate::startTimers()
620{
621 audioBuffer->startFillTimer();
622 if (intervalTimer->interval() > 0)
623 intervalTimer->start();
624}
625
626void QAudioOutputPrivate::stopTimers()
627{
628 audioBuffer->stopFillTimer();
629 intervalTimer->stop();
630}
631
632
633void QAudioOutputPrivate::deviceStopped()
634{
635 intervalTimer->stop();
636 emit stateChanged(stateCode);
637}
638
639void QAudioOutputPrivate::inputReady()
640{
641 QMutexLocker lock(&mutex);
642 if (stateCode == QAudio::IdleState) {
643 audioThreadStart();
644
645 stateCode = QAudio::ActiveState;
646 errorCode = QAudio::NoError;
647
648 QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, stateCode));
649 }
650}
651
652
653OSStatus QAudioOutputPrivate::renderCallback(void* inRefCon,
654 AudioUnitRenderActionFlags* ioActionFlags,
655 const AudioTimeStamp* inTimeStamp,
656 UInt32 inBusNumber,
657 UInt32 inNumberFrames,
658 AudioBufferList* ioData)
659{
660 Q_UNUSED(ioActionFlags)
661 Q_UNUSED(inTimeStamp)
662 Q_UNUSED(inBusNumber)
663 Q_UNUSED(inNumberFrames)
664
665 QAudioOutputPrivate* d = static_cast<QAudioOutputPrivate*>(inRefCon);
666
667 const int threadState = d->audioThreadState.fetchAndAddAcquire(0);
668 if (threadState == Stopped) {
669 ioData->mBuffers[0].mDataByteSize = 0;
670 d->audioDeviceStop();
671 }
672 else {
673 const UInt32 bytesPerFrame = d->streamFormat.mBytesPerFrame;
674 qint64 framesRead;
675
676 framesRead = d->audioBuffer->readFrames((char*)ioData->mBuffers[0].mData,
677 ioData->mBuffers[0].mDataByteSize / bytesPerFrame);
678
679 if (framesRead > 0) {
680 ioData->mBuffers[0].mDataByteSize = framesRead * bytesPerFrame;
681 d->totalFrames += framesRead;
682 }
683 else {
684 ioData->mBuffers[0].mDataByteSize = 0;
685 if (framesRead == 0) {
686 if (threadState == Draining)
687 d->audioDeviceStop();
688 else
689 d->audioDeviceIdle();
690 }
691 else
692 d->audioDeviceError();
693 }
694 }
695
696 return noErr;
697}
698
699
700QT_END_NAMESPACE
701
702#include "qaudiooutput_mac_p.moc"
703
Note: See TracBrowser for help on using the repository browser.