Rename QtMultimediaKit to QtMultimedia.
There are a few legacy bits left in place so it passes CI, and then qt5.git etc can be updated. Change-Id: I6b082e50e6958c72fdabc2974992e16d90dafa3a Reviewed-on: http://codereview.qt-project.org/5368 Reviewed-by: Qt Sanity Bot <qt_sanity_bot@ovi.com> Reviewed-by: Jonas Rabbe <jonas.rabbe@nokia.com>
This commit is contained in:
committed by
Qt by Nokia
parent
55bc4f2b46
commit
03f22bcdaf
989
src/multimedia/audio/qaudioinput_mac_p.cpp
Normal file
989
src/multimedia/audio/qaudioinput_mac_p.cpp
Normal file
@@ -0,0 +1,989 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** GNU Lesser General Public License Usage
|
||||
** This file may be used under the terms of the GNU Lesser General Public
|
||||
** License version 2.1 as published by the Free Software Foundation and
|
||||
** appearing in the file LICENSE.LGPL included in the packaging of this
|
||||
** file. Please review the following information to ensure the GNU Lesser
|
||||
** General Public License version 2.1 requirements will be met:
|
||||
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Nokia gives you certain additional
|
||||
** rights. These rights are described in the Nokia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU General
|
||||
** Public License version 3.0 as published by the Free Software Foundation
|
||||
** and appearing in the file LICENSE.GPL included in the packaging of this
|
||||
** file. Please review the following information to ensure the GNU General
|
||||
** Public License version 3.0 requirements will be met:
|
||||
** http://www.gnu.org/copyleft/gpl.html.
|
||||
**
|
||||
** Other Usage
|
||||
** Alternatively, this file may be used in accordance with the terms and
|
||||
** conditions contained in a signed written agreement between you and Nokia.
|
||||
**
|
||||
**
|
||||
**
|
||||
**
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
//
|
||||
// W A R N I N G
|
||||
// -------------
|
||||
//
|
||||
// This file is not part of the Qt API. It exists for the convenience
|
||||
// of other Qt classes. This header file may change from version to
|
||||
// version without notice, or even be removed.
|
||||
//
|
||||
// INTERNAL USE ONLY: Do NOT use for any other purpose.
|
||||
//
|
||||
|
||||
#include <QtCore/qendian.h>
|
||||
#include <QtCore/qtimer.h>
|
||||
#include <QtCore/qdebug.h>
|
||||
|
||||
#include <qaudioinput.h>
|
||||
|
||||
#include "qaudio_mac_p.h"
|
||||
#include "qaudioinput_mac_p.h"
|
||||
#include "qaudiodeviceinfo_mac_p.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
|
||||
namespace QtMultimediaInternal
|
||||
{
|
||||
|
||||
static const int default_buffer_size = 4 * 1024;
|
||||
|
||||
class QAudioBufferList
|
||||
{
|
||||
public:
|
||||
QAudioBufferList(AudioStreamBasicDescription const& streamFormat):
|
||||
owner(false),
|
||||
sf(streamFormat)
|
||||
{
|
||||
const bool isInterleaved = (sf.mFormatFlags & kAudioFormatFlagIsNonInterleaved) == 0;
|
||||
const int numberOfBuffers = isInterleaved ? 1 : sf.mChannelsPerFrame;
|
||||
|
||||
dataSize = 0;
|
||||
|
||||
bfs = reinterpret_cast<AudioBufferList*>(qMalloc(sizeof(AudioBufferList) +
|
||||
(sizeof(AudioBuffer) * numberOfBuffers)));
|
||||
|
||||
bfs->mNumberBuffers = numberOfBuffers;
|
||||
for (int i = 0; i < numberOfBuffers; ++i) {
|
||||
bfs->mBuffers[i].mNumberChannels = isInterleaved ? numberOfBuffers : 1;
|
||||
bfs->mBuffers[i].mDataByteSize = 0;
|
||||
bfs->mBuffers[i].mData = 0;
|
||||
}
|
||||
}
|
||||
|
||||
QAudioBufferList(AudioStreamBasicDescription const& streamFormat, char* buffer, int bufferSize):
|
||||
owner(false),
|
||||
sf(streamFormat),
|
||||
bfs(0)
|
||||
{
|
||||
dataSize = bufferSize;
|
||||
|
||||
bfs = reinterpret_cast<AudioBufferList*>(qMalloc(sizeof(AudioBufferList) + sizeof(AudioBuffer)));
|
||||
|
||||
bfs->mNumberBuffers = 1;
|
||||
bfs->mBuffers[0].mNumberChannels = 1;
|
||||
bfs->mBuffers[0].mDataByteSize = dataSize;
|
||||
bfs->mBuffers[0].mData = buffer;
|
||||
}
|
||||
|
||||
QAudioBufferList(AudioStreamBasicDescription const& streamFormat, int framesToBuffer):
|
||||
owner(true),
|
||||
sf(streamFormat),
|
||||
bfs(0)
|
||||
{
|
||||
const bool isInterleaved = (sf.mFormatFlags & kAudioFormatFlagIsNonInterleaved) == 0;
|
||||
const int numberOfBuffers = isInterleaved ? 1 : sf.mChannelsPerFrame;
|
||||
|
||||
dataSize = framesToBuffer * sf.mBytesPerFrame;
|
||||
|
||||
bfs = reinterpret_cast<AudioBufferList*>(qMalloc(sizeof(AudioBufferList) +
|
||||
(sizeof(AudioBuffer) * numberOfBuffers)));
|
||||
bfs->mNumberBuffers = numberOfBuffers;
|
||||
for (int i = 0; i < numberOfBuffers; ++i) {
|
||||
bfs->mBuffers[i].mNumberChannels = isInterleaved ? numberOfBuffers : 1;
|
||||
bfs->mBuffers[i].mDataByteSize = dataSize;
|
||||
bfs->mBuffers[i].mData = qMalloc(dataSize);
|
||||
}
|
||||
}
|
||||
|
||||
~QAudioBufferList()
|
||||
{
|
||||
if (owner) {
|
||||
for (UInt32 i = 0; i < bfs->mNumberBuffers; ++i)
|
||||
qFree(bfs->mBuffers[i].mData);
|
||||
}
|
||||
|
||||
qFree(bfs);
|
||||
}
|
||||
|
||||
AudioBufferList* audioBufferList() const
|
||||
{
|
||||
return bfs;
|
||||
}
|
||||
|
||||
char* data(int buffer = 0) const
|
||||
{
|
||||
return static_cast<char*>(bfs->mBuffers[buffer].mData);
|
||||
}
|
||||
|
||||
qint64 bufferSize(int buffer = 0) const
|
||||
{
|
||||
return bfs->mBuffers[buffer].mDataByteSize;
|
||||
}
|
||||
|
||||
int frameCount(int buffer = 0) const
|
||||
{
|
||||
return bfs->mBuffers[buffer].mDataByteSize / sf.mBytesPerFrame;
|
||||
}
|
||||
|
||||
int packetCount(int buffer = 0) const
|
||||
{
|
||||
return bfs->mBuffers[buffer].mDataByteSize / sf.mBytesPerPacket;
|
||||
}
|
||||
|
||||
int packetSize() const
|
||||
{
|
||||
return sf.mBytesPerPacket;
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
for (UInt32 i = 0; i < bfs->mNumberBuffers; ++i) {
|
||||
bfs->mBuffers[i].mDataByteSize = dataSize;
|
||||
bfs->mBuffers[i].mData = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool owner;
|
||||
int dataSize;
|
||||
AudioStreamBasicDescription sf;
|
||||
AudioBufferList* bfs;
|
||||
};
|
||||
|
||||
class QAudioPacketFeeder
|
||||
{
|
||||
public:
|
||||
QAudioPacketFeeder(QAudioBufferList* abl):
|
||||
audioBufferList(abl)
|
||||
{
|
||||
totalPackets = audioBufferList->packetCount();
|
||||
position = 0;
|
||||
}
|
||||
|
||||
bool feed(AudioBufferList& dst, UInt32& packetCount)
|
||||
{
|
||||
if (position == totalPackets) {
|
||||
dst.mBuffers[0].mDataByteSize = 0;
|
||||
packetCount = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (totalPackets - position < packetCount)
|
||||
packetCount = totalPackets - position;
|
||||
|
||||
dst.mBuffers[0].mDataByteSize = packetCount * audioBufferList->packetSize();
|
||||
dst.mBuffers[0].mData = audioBufferList->data() + (position * audioBufferList->packetSize());
|
||||
|
||||
position += packetCount;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
return position == totalPackets;
|
||||
}
|
||||
|
||||
private:
|
||||
UInt32 totalPackets;
|
||||
UInt32 position;
|
||||
QAudioBufferList* audioBufferList;
|
||||
};
|
||||
|
||||
class QAudioInputBuffer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QAudioInputBuffer(int bufferSize,
|
||||
int maxPeriodSize,
|
||||
AudioStreamBasicDescription const& inputFormat,
|
||||
AudioStreamBasicDescription const& outputFormat,
|
||||
QObject* parent):
|
||||
QObject(parent),
|
||||
m_deviceError(false),
|
||||
m_audioConverter(0),
|
||||
m_inputFormat(inputFormat),
|
||||
m_outputFormat(outputFormat)
|
||||
{
|
||||
m_maxPeriodSize = maxPeriodSize;
|
||||
m_periodTime = m_maxPeriodSize / m_outputFormat.mBytesPerFrame * 1000 / m_outputFormat.mSampleRate;
|
||||
m_buffer = new QAudioRingBuffer(bufferSize + (bufferSize % maxPeriodSize == 0 ? 0 : maxPeriodSize - (bufferSize % maxPeriodSize)));
|
||||
m_inputBufferList = new QAudioBufferList(m_inputFormat);
|
||||
|
||||
m_flushTimer = new QTimer(this);
|
||||
connect(m_flushTimer, SIGNAL(timeout()), SLOT(flushBuffer()));
|
||||
|
||||
if (toQAudioFormat(inputFormat) != toQAudioFormat(outputFormat)) {
|
||||
if (AudioConverterNew(&m_inputFormat, &m_outputFormat, &m_audioConverter) != noErr) {
|
||||
qWarning() << "QAudioInput: Unable to create an Audio Converter";
|
||||
m_audioConverter = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~QAudioInputBuffer()
|
||||
{
|
||||
delete m_buffer;
|
||||
}
|
||||
|
||||
qint64 renderFromDevice(AudioUnit audioUnit,
|
||||
AudioUnitRenderActionFlags* ioActionFlags,
|
||||
const AudioTimeStamp* inTimeStamp,
|
||||
UInt32 inBusNumber,
|
||||
UInt32 inNumberFrames)
|
||||
{
|
||||
const bool pullMode = m_device == 0;
|
||||
|
||||
OSStatus err;
|
||||
qint64 framesRendered = 0;
|
||||
|
||||
m_inputBufferList->reset();
|
||||
err = AudioUnitRender(audioUnit,
|
||||
ioActionFlags,
|
||||
inTimeStamp,
|
||||
inBusNumber,
|
||||
inNumberFrames,
|
||||
m_inputBufferList->audioBufferList());
|
||||
|
||||
if (m_audioConverter != 0) {
|
||||
QAudioPacketFeeder feeder(m_inputBufferList);
|
||||
|
||||
int copied = 0;
|
||||
const int available = m_buffer->free();
|
||||
|
||||
while (err == noErr && !feeder.empty()) {
|
||||
QAudioRingBuffer::Region region = m_buffer->acquireWriteRegion(available);
|
||||
|
||||
if (region.second == 0)
|
||||
break;
|
||||
|
||||
AudioBufferList output;
|
||||
output.mNumberBuffers = 1;
|
||||
output.mBuffers[0].mNumberChannels = 1;
|
||||
output.mBuffers[0].mDataByteSize = region.second;
|
||||
output.mBuffers[0].mData = region.first;
|
||||
|
||||
UInt32 packetSize = region.second / m_outputFormat.mBytesPerPacket;
|
||||
err = AudioConverterFillComplexBuffer(m_audioConverter,
|
||||
converterCallback,
|
||||
&feeder,
|
||||
&packetSize,
|
||||
&output,
|
||||
0);
|
||||
region.second = output.mBuffers[0].mDataByteSize;
|
||||
copied += region.second;
|
||||
|
||||
m_buffer->releaseWriteRegion(region);
|
||||
}
|
||||
|
||||
framesRendered += copied / m_outputFormat.mBytesPerFrame;
|
||||
}
|
||||
else {
|
||||
const int available = m_inputBufferList->bufferSize();
|
||||
bool wecan = true;
|
||||
int copied = 0;
|
||||
|
||||
while (wecan && copied < available) {
|
||||
QAudioRingBuffer::Region region = m_buffer->acquireWriteRegion(available - copied);
|
||||
|
||||
if (region.second > 0) {
|
||||
memcpy(region.first, m_inputBufferList->data() + copied, region.second);
|
||||
copied += region.second;
|
||||
}
|
||||
else
|
||||
wecan = false;
|
||||
|
||||
m_buffer->releaseWriteRegion(region);
|
||||
}
|
||||
|
||||
framesRendered = copied / m_outputFormat.mBytesPerFrame;
|
||||
}
|
||||
|
||||
if (pullMode && framesRendered > 0)
|
||||
emit readyRead();
|
||||
|
||||
return framesRendered;
|
||||
}
|
||||
|
||||
qint64 readBytes(char* data, qint64 len)
|
||||
{
|
||||
bool wecan = true;
|
||||
qint64 bytesCopied = 0;
|
||||
|
||||
len -= len % m_maxPeriodSize;
|
||||
while (wecan && bytesCopied < len) {
|
||||
QAudioRingBuffer::Region region = m_buffer->acquireReadRegion(len - bytesCopied);
|
||||
|
||||
if (region.second > 0) {
|
||||
memcpy(data + bytesCopied, region.first, region.second);
|
||||
bytesCopied += region.second;
|
||||
}
|
||||
else
|
||||
wecan = false;
|
||||
|
||||
m_buffer->releaseReadRegion(region);
|
||||
}
|
||||
|
||||
return bytesCopied;
|
||||
}
|
||||
|
||||
void setFlushDevice(QIODevice* device)
|
||||
{
|
||||
if (m_device != device)
|
||||
m_device = device;
|
||||
}
|
||||
|
||||
void startFlushTimer()
|
||||
{
|
||||
if (m_device != 0) {
|
||||
m_flushTimer->start((m_buffer->size() - (m_maxPeriodSize * 2)) / m_maxPeriodSize * m_periodTime);
|
||||
}
|
||||
}
|
||||
|
||||
void stopFlushTimer()
|
||||
{
|
||||
m_flushTimer->stop();
|
||||
}
|
||||
|
||||
void flush(bool all = false)
|
||||
{
|
||||
if (m_device == 0)
|
||||
return;
|
||||
|
||||
const int used = m_buffer->used();
|
||||
const int readSize = all ? used : used - (used % m_maxPeriodSize);
|
||||
|
||||
if (readSize > 0) {
|
||||
bool wecan = true;
|
||||
int flushed = 0;
|
||||
|
||||
while (!m_deviceError && wecan && flushed < readSize) {
|
||||
QAudioRingBuffer::Region region = m_buffer->acquireReadRegion(readSize - flushed);
|
||||
|
||||
if (region.second > 0) {
|
||||
int bytesWritten = m_device->write(region.first, region.second);
|
||||
if (bytesWritten < 0) {
|
||||
stopFlushTimer();
|
||||
m_deviceError = true;
|
||||
}
|
||||
else {
|
||||
region.second = bytesWritten;
|
||||
flushed += bytesWritten;
|
||||
wecan = bytesWritten != 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
wecan = false;
|
||||
|
||||
m_buffer->releaseReadRegion(region);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
m_buffer->reset();
|
||||
m_deviceError = false;
|
||||
}
|
||||
|
||||
int available() const
|
||||
{
|
||||
return m_buffer->free();
|
||||
}
|
||||
|
||||
int used() const
|
||||
{
|
||||
return m_buffer->used();
|
||||
}
|
||||
|
||||
signals:
|
||||
void readyRead();
|
||||
|
||||
private slots:
|
||||
void flushBuffer()
|
||||
{
|
||||
flush();
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_deviceError;
|
||||
int m_maxPeriodSize;
|
||||
int m_periodTime;
|
||||
QIODevice* m_device;
|
||||
QTimer* m_flushTimer;
|
||||
QAudioRingBuffer* m_buffer;
|
||||
QAudioBufferList* m_inputBufferList;
|
||||
AudioConverterRef m_audioConverter;
|
||||
AudioStreamBasicDescription m_inputFormat;
|
||||
AudioStreamBasicDescription m_outputFormat;
|
||||
|
||||
const static OSStatus as_empty = 'qtem';
|
||||
|
||||
// Converter callback
|
||||
static OSStatus converterCallback(AudioConverterRef inAudioConverter,
|
||||
UInt32* ioNumberDataPackets,
|
||||
AudioBufferList* ioData,
|
||||
AudioStreamPacketDescription** outDataPacketDescription,
|
||||
void* inUserData)
|
||||
{
|
||||
Q_UNUSED(inAudioConverter);
|
||||
Q_UNUSED(outDataPacketDescription);
|
||||
|
||||
QAudioPacketFeeder* feeder = static_cast<QAudioPacketFeeder*>(inUserData);
|
||||
|
||||
if (!feeder->feed(*ioData, *ioNumberDataPackets))
|
||||
return as_empty;
|
||||
|
||||
return noErr;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class MacInputDevice : public QIODevice
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacInputDevice(QAudioInputBuffer* audioBuffer, QObject* parent):
|
||||
QIODevice(parent),
|
||||
m_audioBuffer(audioBuffer)
|
||||
{
|
||||
open(QIODevice::ReadOnly | QIODevice::Unbuffered);
|
||||
connect(m_audioBuffer, SIGNAL(readyRead()), SIGNAL(readyRead()));
|
||||
}
|
||||
|
||||
qint64 readData(char* data, qint64 len)
|
||||
{
|
||||
return m_audioBuffer->readBytes(data, len);
|
||||
}
|
||||
|
||||
qint64 writeData(const char* data, qint64 len)
|
||||
{
|
||||
Q_UNUSED(data);
|
||||
Q_UNUSED(len);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool isSequential() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
QAudioInputBuffer* m_audioBuffer;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
QAudioInputPrivate::QAudioInputPrivate(const QByteArray& device)
|
||||
{
|
||||
QDataStream ds(device);
|
||||
quint32 did, mode;
|
||||
|
||||
ds >> did >> mode;
|
||||
|
||||
if (QAudio::Mode(mode) == QAudio::AudioOutput)
|
||||
errorCode = QAudio::OpenError;
|
||||
else {
|
||||
audioDeviceInfo = new QAudioDeviceInfoInternal(device, QAudio::AudioInput);
|
||||
isOpen = false;
|
||||
audioDeviceId = AudioDeviceID(did);
|
||||
audioUnit = 0;
|
||||
startTime = 0;
|
||||
totalFrames = 0;
|
||||
audioBuffer = 0;
|
||||
internalBufferSize = QtMultimediaInternal::default_buffer_size;
|
||||
clockFrequency = AudioGetHostClockFrequency() / 1000;
|
||||
errorCode = QAudio::NoError;
|
||||
stateCode = QAudio::StoppedState;
|
||||
|
||||
intervalTimer = new QTimer(this);
|
||||
intervalTimer->setInterval(1000);
|
||||
connect(intervalTimer, SIGNAL(timeout()), SIGNAL(notify()));
|
||||
}
|
||||
}
|
||||
|
||||
QAudioInputPrivate::~QAudioInputPrivate()
|
||||
{
|
||||
close();
|
||||
delete audioDeviceInfo;
|
||||
}
|
||||
|
||||
bool QAudioInputPrivate::open()
|
||||
{
|
||||
UInt32 size = 0;
|
||||
|
||||
if (isOpen)
|
||||
return true;
|
||||
|
||||
ComponentDescription cd;
|
||||
cd.componentType = kAudioUnitType_Output;
|
||||
cd.componentSubType = kAudioUnitSubType_HALOutput;
|
||||
cd.componentManufacturer = kAudioUnitManufacturer_Apple;
|
||||
cd.componentFlags = 0;
|
||||
cd.componentFlagsMask = 0;
|
||||
|
||||
// Open
|
||||
Component cp = FindNextComponent(NULL, &cd);
|
||||
if (cp == 0) {
|
||||
qWarning() << "QAudioInput: Failed to find HAL Output component";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (OpenAComponent(cp, &audioUnit) != noErr) {
|
||||
qWarning() << "QAudioInput: Unable to Open Output Component";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set mode
|
||||
// switch to input mode
|
||||
UInt32 enable = 1;
|
||||
if (AudioUnitSetProperty(audioUnit,
|
||||
kAudioOutputUnitProperty_EnableIO,
|
||||
kAudioUnitScope_Input,
|
||||
1,
|
||||
&enable,
|
||||
sizeof(enable)) != noErr) {
|
||||
qWarning() << "QAudioInput: Unable to switch to input mode (Enable Input)";
|
||||
return false;
|
||||
}
|
||||
|
||||
enable = 0;
|
||||
if (AudioUnitSetProperty(audioUnit,
|
||||
kAudioOutputUnitProperty_EnableIO,
|
||||
kAudioUnitScope_Output,
|
||||
0,
|
||||
&enable,
|
||||
sizeof(enable)) != noErr) {
|
||||
qWarning() << "QAudioInput: Unable to switch to input mode (Disable output)";
|
||||
return false;
|
||||
}
|
||||
|
||||
// register callback
|
||||
AURenderCallbackStruct cb;
|
||||
cb.inputProc = inputCallback;
|
||||
cb.inputProcRefCon = this;
|
||||
|
||||
if (AudioUnitSetProperty(audioUnit,
|
||||
kAudioOutputUnitProperty_SetInputCallback,
|
||||
kAudioUnitScope_Global,
|
||||
0,
|
||||
&cb,
|
||||
sizeof(cb)) != noErr) {
|
||||
qWarning() << "QAudioInput: Failed to set AudioUnit callback";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set Audio Device
|
||||
if (AudioUnitSetProperty(audioUnit,
|
||||
kAudioOutputUnitProperty_CurrentDevice,
|
||||
kAudioUnitScope_Global,
|
||||
0,
|
||||
&audioDeviceId,
|
||||
sizeof(audioDeviceId)) != noErr) {
|
||||
qWarning() << "QAudioInput: Unable to use configured device";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set format
|
||||
// Wanted
|
||||
streamFormat = toAudioStreamBasicDescription(audioFormat);
|
||||
|
||||
// Required on unit
|
||||
if (audioFormat == audioDeviceInfo->preferredFormat()) {
|
||||
deviceFormat = streamFormat;
|
||||
AudioUnitSetProperty(audioUnit,
|
||||
kAudioUnitProperty_StreamFormat,
|
||||
kAudioUnitScope_Output,
|
||||
1,
|
||||
&deviceFormat,
|
||||
sizeof(deviceFormat));
|
||||
}
|
||||
else {
|
||||
size = sizeof(deviceFormat);
|
||||
if (AudioUnitGetProperty(audioUnit,
|
||||
kAudioUnitProperty_StreamFormat,
|
||||
kAudioUnitScope_Input,
|
||||
1,
|
||||
&deviceFormat,
|
||||
&size) != noErr) {
|
||||
qWarning() << "QAudioInput: Unable to retrieve device format";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (AudioUnitSetProperty(audioUnit,
|
||||
kAudioUnitProperty_StreamFormat,
|
||||
kAudioUnitScope_Output,
|
||||
1,
|
||||
&deviceFormat,
|
||||
sizeof(deviceFormat)) != noErr) {
|
||||
qWarning() << "QAudioInput: Unable to set device format";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Setup buffers
|
||||
UInt32 numberOfFrames;
|
||||
size = sizeof(UInt32);
|
||||
if (AudioUnitGetProperty(audioUnit,
|
||||
kAudioDevicePropertyBufferFrameSize,
|
||||
kAudioUnitScope_Global,
|
||||
0,
|
||||
&numberOfFrames,
|
||||
&size) != noErr) {
|
||||
qWarning() << "QAudioInput: Failed to get audio period size";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocate buffer
|
||||
periodSizeBytes = numberOfFrames * streamFormat.mBytesPerFrame;
|
||||
|
||||
if (internalBufferSize < periodSizeBytes * 2)
|
||||
internalBufferSize = periodSizeBytes * 2;
|
||||
else
|
||||
internalBufferSize -= internalBufferSize % streamFormat.mBytesPerFrame;
|
||||
|
||||
audioBuffer = new QtMultimediaInternal::QAudioInputBuffer(internalBufferSize,
|
||||
periodSizeBytes,
|
||||
deviceFormat,
|
||||
streamFormat,
|
||||
this);
|
||||
|
||||
audioIO = new QtMultimediaInternal::MacInputDevice(audioBuffer, this);
|
||||
|
||||
// Init
|
||||
if (AudioUnitInitialize(audioUnit) != noErr) {
|
||||
qWarning() << "QAudioInput: Failed to initialize AudioUnit";
|
||||
return false;
|
||||
}
|
||||
|
||||
isOpen = true;
|
||||
|
||||
return isOpen;
|
||||
}
|
||||
|
||||
void QAudioInputPrivate::close()
|
||||
{
|
||||
if (audioUnit != 0) {
|
||||
AudioOutputUnitStop(audioUnit);
|
||||
AudioUnitUninitialize(audioUnit);
|
||||
CloseComponent(audioUnit);
|
||||
}
|
||||
|
||||
delete audioBuffer;
|
||||
}
|
||||
|
||||
QAudioFormat QAudioInputPrivate::format() const
|
||||
{
|
||||
return audioFormat;
|
||||
}
|
||||
|
||||
void QAudioInputPrivate::setFormat(const QAudioFormat& fmt)
|
||||
{
|
||||
if (stateCode == QAudio::StoppedState)
|
||||
audioFormat = fmt;
|
||||
}
|
||||
|
||||
void QAudioInputPrivate::start(QIODevice* device)
|
||||
{
|
||||
QIODevice* op = device;
|
||||
|
||||
if (!audioDeviceInfo->isFormatSupported(audioFormat) || !open()) {
|
||||
stateCode = QAudio::StoppedState;
|
||||
errorCode = QAudio::OpenError;
|
||||
return;
|
||||
}
|
||||
|
||||
reset();
|
||||
audioBuffer->reset();
|
||||
audioBuffer->setFlushDevice(op);
|
||||
|
||||
if (op == 0)
|
||||
op = audioIO;
|
||||
|
||||
// Start
|
||||
startTime = AudioGetCurrentHostTime();
|
||||
totalFrames = 0;
|
||||
|
||||
audioThreadStart();
|
||||
|
||||
stateCode = QAudio::ActiveState;
|
||||
errorCode = QAudio::NoError;
|
||||
emit stateChanged(stateCode);
|
||||
}
|
||||
|
||||
QIODevice* QAudioInputPrivate::start()
|
||||
{
|
||||
QIODevice* op = 0;
|
||||
|
||||
if (!audioDeviceInfo->isFormatSupported(audioFormat) || !open()) {
|
||||
stateCode = QAudio::StoppedState;
|
||||
errorCode = QAudio::OpenError;
|
||||
return audioIO;
|
||||
}
|
||||
|
||||
reset();
|
||||
audioBuffer->reset();
|
||||
audioBuffer->setFlushDevice(op);
|
||||
|
||||
if (op == 0)
|
||||
op = audioIO;
|
||||
|
||||
// Start
|
||||
startTime = AudioGetCurrentHostTime();
|
||||
totalFrames = 0;
|
||||
|
||||
audioThreadStart();
|
||||
|
||||
stateCode = QAudio::ActiveState;
|
||||
errorCode = QAudio::NoError;
|
||||
emit stateChanged(stateCode);
|
||||
|
||||
return op;
|
||||
}
|
||||
|
||||
void QAudioInputPrivate::stop()
|
||||
{
|
||||
QMutexLocker lock(&mutex);
|
||||
if (stateCode != QAudio::StoppedState) {
|
||||
audioThreadStop();
|
||||
audioBuffer->flush(true);
|
||||
|
||||
errorCode = QAudio::NoError;
|
||||
stateCode = QAudio::StoppedState;
|
||||
QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, stateCode));
|
||||
}
|
||||
}
|
||||
|
||||
void QAudioInputPrivate::reset()
|
||||
{
|
||||
QMutexLocker lock(&mutex);
|
||||
if (stateCode != QAudio::StoppedState) {
|
||||
audioThreadStop();
|
||||
|
||||
errorCode = QAudio::NoError;
|
||||
stateCode = QAudio::StoppedState;
|
||||
QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, stateCode));
|
||||
}
|
||||
}
|
||||
|
||||
void QAudioInputPrivate::suspend()
|
||||
{
|
||||
QMutexLocker lock(&mutex);
|
||||
if (stateCode == QAudio::ActiveState || stateCode == QAudio::IdleState) {
|
||||
audioThreadStop();
|
||||
|
||||
errorCode = QAudio::NoError;
|
||||
stateCode = QAudio::SuspendedState;
|
||||
QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, stateCode));
|
||||
}
|
||||
}
|
||||
|
||||
void QAudioInputPrivate::resume()
|
||||
{
|
||||
QMutexLocker lock(&mutex);
|
||||
if (stateCode == QAudio::SuspendedState) {
|
||||
audioThreadStart();
|
||||
|
||||
errorCode = QAudio::NoError;
|
||||
stateCode = QAudio::ActiveState;
|
||||
QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, stateCode));
|
||||
}
|
||||
}
|
||||
|
||||
int QAudioInputPrivate::bytesReady() const
|
||||
{
|
||||
return audioBuffer->used();
|
||||
}
|
||||
|
||||
int QAudioInputPrivate::periodSize() const
|
||||
{
|
||||
return periodSizeBytes;
|
||||
}
|
||||
|
||||
void QAudioInputPrivate::setBufferSize(int bs)
|
||||
{
|
||||
internalBufferSize = bs;
|
||||
}
|
||||
|
||||
int QAudioInputPrivate::bufferSize() const
|
||||
{
|
||||
return internalBufferSize;
|
||||
}
|
||||
|
||||
void QAudioInputPrivate::setNotifyInterval(int milliSeconds)
|
||||
{
|
||||
if (intervalTimer->interval() == milliSeconds)
|
||||
return;
|
||||
|
||||
if (milliSeconds <= 0)
|
||||
milliSeconds = 0;
|
||||
|
||||
intervalTimer->setInterval(milliSeconds);
|
||||
}
|
||||
|
||||
int QAudioInputPrivate::notifyInterval() const
|
||||
{
|
||||
return intervalTimer->interval();
|
||||
}
|
||||
|
||||
qint64 QAudioInputPrivate::processedUSecs() const
|
||||
{
|
||||
return totalFrames * 1000000 / audioFormat.frequency();
|
||||
}
|
||||
|
||||
qint64 QAudioInputPrivate::elapsedUSecs() const
|
||||
{
|
||||
if (stateCode == QAudio::StoppedState)
|
||||
return 0;
|
||||
|
||||
return (AudioGetCurrentHostTime() - startTime) / (clockFrequency / 1000);
|
||||
}
|
||||
|
||||
QAudio::Error QAudioInputPrivate::error() const
|
||||
{
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
QAudio::State QAudioInputPrivate::state() const
|
||||
{
|
||||
return stateCode;
|
||||
}
|
||||
|
||||
void QAudioInputPrivate::audioThreadStop()
|
||||
{
|
||||
stopTimers();
|
||||
if (audioThreadState.testAndSetAcquire(Running, Stopped))
|
||||
threadFinished.wait(&mutex);
|
||||
}
|
||||
|
||||
void QAudioInputPrivate::audioThreadStart()
|
||||
{
|
||||
startTimers();
|
||||
audioThreadState = Running;
|
||||
AudioOutputUnitStart(audioUnit);
|
||||
}
|
||||
|
||||
void QAudioInputPrivate::audioDeviceStop()
|
||||
{
|
||||
AudioOutputUnitStop(audioUnit);
|
||||
audioThreadState = Stopped;
|
||||
threadFinished.wakeOne();
|
||||
}
|
||||
|
||||
void QAudioInputPrivate::audioDeviceFull()
|
||||
{
|
||||
QMutexLocker lock(&mutex);
|
||||
if (stateCode == QAudio::ActiveState) {
|
||||
audioDeviceStop();
|
||||
|
||||
errorCode = QAudio::UnderrunError;
|
||||
stateCode = QAudio::IdleState;
|
||||
QMetaObject::invokeMethod(this, "deviceStopped", Qt::QueuedConnection);
|
||||
}
|
||||
}
|
||||
|
||||
void QAudioInputPrivate::audioDeviceError()
|
||||
{
|
||||
QMutexLocker lock(&mutex);
|
||||
if (stateCode == QAudio::ActiveState) {
|
||||
audioDeviceStop();
|
||||
|
||||
errorCode = QAudio::IOError;
|
||||
stateCode = QAudio::StoppedState;
|
||||
QMetaObject::invokeMethod(this, "deviceStopped", Qt::QueuedConnection);
|
||||
}
|
||||
}
|
||||
|
||||
void QAudioInputPrivate::startTimers()
|
||||
{
|
||||
audioBuffer->startFlushTimer();
|
||||
if (intervalTimer->interval() > 0)
|
||||
intervalTimer->start();
|
||||
}
|
||||
|
||||
void QAudioInputPrivate::stopTimers()
|
||||
{
|
||||
audioBuffer->stopFlushTimer();
|
||||
intervalTimer->stop();
|
||||
}
|
||||
|
||||
void QAudioInputPrivate::deviceStopped()
|
||||
{
|
||||
stopTimers();
|
||||
emit stateChanged(stateCode);
|
||||
}
|
||||
|
||||
// Input callback
|
||||
OSStatus QAudioInputPrivate::inputCallback(void* inRefCon,
|
||||
AudioUnitRenderActionFlags* ioActionFlags,
|
||||
const AudioTimeStamp* inTimeStamp,
|
||||
UInt32 inBusNumber,
|
||||
UInt32 inNumberFrames,
|
||||
AudioBufferList* ioData)
|
||||
{
|
||||
Q_UNUSED(ioData);
|
||||
|
||||
QAudioInputPrivate* d = static_cast<QAudioInputPrivate*>(inRefCon);
|
||||
|
||||
const int threadState = d->audioThreadState.fetchAndAddAcquire(0);
|
||||
if (threadState == Stopped)
|
||||
d->audioDeviceStop();
|
||||
else {
|
||||
qint64 framesWritten;
|
||||
|
||||
framesWritten = d->audioBuffer->renderFromDevice(d->audioUnit,
|
||||
ioActionFlags,
|
||||
inTimeStamp,
|
||||
inBusNumber,
|
||||
inNumberFrames);
|
||||
|
||||
if (framesWritten > 0)
|
||||
d->totalFrames += framesWritten;
|
||||
else if (framesWritten == 0)
|
||||
d->audioDeviceFull();
|
||||
else if (framesWritten < 0)
|
||||
d->audioDeviceError();
|
||||
}
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#include "qaudioinput_mac_p.moc"
|
||||
Reference in New Issue
Block a user