You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
400 lines
11 KiB
400 lines
11 KiB
/*
|
|
* Copyright (C) 2013 Open Education Foundation
|
|
*
|
|
* Copyright (C) 2010-2013 Groupement d'Intérêt Public pour
|
|
* l'Education Numérique en Afrique (GIP ENA)
|
|
*
|
|
* This file is part of OpenBoard.
|
|
*
|
|
* OpenBoard is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, version 3 of the License,
|
|
* with a specific linking exception for the OpenSSL project's
|
|
* "OpenSSL" library (or with modified versions of it that use the
|
|
* same license as the "OpenSSL" library).
|
|
*
|
|
* OpenBoard is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with OpenBoard. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "UBAudioQueueRecorder.h"
|
|
|
|
#include "core/memcheck.h"
|
|
|
|
AudioStreamBasicDescription UBAudioQueueRecorder::sAudioFormat;
|
|
|
|
UBAudioQueueRecorder::UBAudioQueueRecorder(QObject* pParent)
|
|
: QObject(pParent)
|
|
, mQueue(0)
|
|
, mIsRecording(false)
|
|
, mBufferLengthInMs(500)
|
|
{
|
|
|
|
int sampleSize = sizeof(float); // TODO: check if this is system/ microphone-dependant
|
|
|
|
sAudioFormat.mSampleRate = 44100.0;
|
|
sAudioFormat.mFormatID = kAudioFormatLinearPCM;
|
|
sAudioFormat.mChannelsPerFrame = 1;
|
|
|
|
sAudioFormat.mBytesPerFrame = sampleSize;
|
|
sAudioFormat.mBitsPerChannel = 8 * sampleSize;
|
|
sAudioFormat.mBytesPerPacket = sampleSize;
|
|
sAudioFormat.mFramesPerPacket = 1;
|
|
sAudioFormat.mFormatFlags = kAudioFormatFlagIsFloat |
|
|
kAudioFormatFlagsNativeEndian |
|
|
kAudioFormatFlagIsPacked;
|
|
|
|
|
|
}
|
|
|
|
|
|
UBAudioQueueRecorder::~UBAudioQueueRecorder()
|
|
{
|
|
// NOOP
|
|
}
|
|
|
|
|
|
QStringList UBAudioQueueRecorder::waveInDevices()
|
|
{
|
|
QStringList deviceNames;
|
|
|
|
QList<AudioDeviceID> devices = inputDeviceIDs();
|
|
|
|
for (int i = 0; i < devices.length(); i++)
|
|
{
|
|
QString name = deviceNameFromDeviceID(devices[i]);
|
|
if (name.length() > 0)
|
|
deviceNames << name;
|
|
}
|
|
|
|
return deviceNames;
|
|
}
|
|
|
|
|
|
QList<AudioDeviceID> UBAudioQueueRecorder::inputDeviceIDs()
|
|
{
|
|
QList<AudioDeviceID> inputDeviceIDs;
|
|
UInt32 deviceIDsArraySize(0);
|
|
|
|
AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, &deviceIDsArraySize, 0);
|
|
|
|
AudioDeviceID deviceIDs[deviceIDsArraySize / sizeof(AudioDeviceID)];
|
|
|
|
AudioHardwareGetProperty(kAudioHardwarePropertyDevices, &deviceIDsArraySize, deviceIDs);
|
|
|
|
int deviceIDsCount = deviceIDsArraySize / sizeof(AudioDeviceID);
|
|
|
|
for (int i = 0; i < deviceIDsCount; i ++)
|
|
{
|
|
AudioStreamBasicDescription sf;
|
|
UInt32 size = sizeof(AudioStreamBasicDescription);
|
|
|
|
if (noErr == AudioDeviceGetProperty(deviceIDs[i], 0, true, kAudioDevicePropertyStreamFormat, &size, &sf))
|
|
{
|
|
inputDeviceIDs << deviceIDs[i];
|
|
}
|
|
}
|
|
|
|
/*
|
|
foreach(AudioDeviceID id, inputDeviceIDs)
|
|
{
|
|
qDebug() << "Device" << id << deviceNameFromDeviceID(id) << deviceUIDFromDeviceID(id);
|
|
}
|
|
*/
|
|
|
|
return inputDeviceIDs;
|
|
}
|
|
|
|
|
|
AudioDeviceID UBAudioQueueRecorder::defaultInputDeviceID()
|
|
{
|
|
AudioDeviceID deviceID;
|
|
UInt32 size = sizeof(deviceID);
|
|
|
|
if (noErr == AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice, &size, &deviceID))
|
|
{
|
|
return deviceID;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
AudioDeviceID UBAudioQueueRecorder::deviceIDFromDeviceName(const QString& name)
|
|
{
|
|
QList<AudioDeviceID> devices = inputDeviceIDs();
|
|
|
|
for (int i = 0; i < devices.length(); i++)
|
|
{
|
|
QString deviceName = deviceNameFromDeviceID(devices[i]);
|
|
if (deviceName == name)
|
|
return devices[i];
|
|
}
|
|
|
|
if (devices.length() > 0)
|
|
return devices[0];
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
|
|
QString UBAudioQueueRecorder::deviceUIDFromDeviceID(AudioDeviceID id)
|
|
{
|
|
CFStringRef name;
|
|
UInt32 size = sizeof(CFStringRef);
|
|
QString uid;
|
|
|
|
if (noErr == AudioDeviceGetProperty(id, 0, true, kAudioDevicePropertyDeviceUID, &size, &name))
|
|
{
|
|
char *cname = new char[1024];
|
|
|
|
CFStringGetCString (name, cname, 1024, kCFStringEncodingISOLatin1);
|
|
int length = CFStringGetLength (name);
|
|
|
|
uid = QString::fromLatin1(cname, length);
|
|
|
|
delete cname;
|
|
|
|
}
|
|
|
|
CFRelease(name);
|
|
|
|
return uid;
|
|
}
|
|
|
|
|
|
QString UBAudioQueueRecorder::deviceNameFromDeviceID(AudioDeviceID id)
|
|
{
|
|
CFStringRef name;
|
|
UInt32 size = sizeof(CFStringRef);
|
|
QString deviceName;
|
|
|
|
if (noErr == AudioDeviceGetProperty(id, 0, true, kAudioObjectPropertyName, &size, &name))
|
|
{
|
|
char cname[1024];
|
|
memset(cname,0,1024);
|
|
CFStringGetCString (name, cname, 1024, kCFStringEncodingUTF8);
|
|
deviceName = QString::fromUtf8(cname);
|
|
}
|
|
|
|
CFRelease(name);
|
|
|
|
return deviceName;
|
|
|
|
}
|
|
|
|
|
|
bool UBAudioQueueRecorder::init(const QString& waveInDeviceName)
|
|
{
|
|
if(mIsRecording)
|
|
{
|
|
setLastErrorMessage("Already recording ...");
|
|
return false;
|
|
}
|
|
|
|
OSStatus err = AudioQueueNewInput (&sAudioFormat, UBAudioQueueRecorder::audioQueueInputCallback,
|
|
this, 0, 0, 0, &mQueue);
|
|
|
|
if (err)
|
|
{
|
|
setLastErrorMessage(QString("Cannot acquire audio input %1").arg(err));
|
|
mQueue = 0;
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
//qDebug() << "init with waveInDeviceName ..." << waveInDeviceName;
|
|
|
|
if (waveInDeviceName.length() > 0 && waveInDeviceName != "Default")
|
|
{
|
|
AudioDeviceID deviceID = deviceIDFromDeviceName(waveInDeviceName);
|
|
|
|
if (deviceID)
|
|
{
|
|
QString deviceUID = deviceUIDFromDeviceID(deviceID);
|
|
if (deviceUID.length() > 0)
|
|
{
|
|
CFStringRef sr = CFStringCreateWithCString(0, deviceUID.toUtf8().constData(), kCFStringEncodingUTF8);
|
|
|
|
err = AudioQueueSetProperty(mQueue, kAudioQueueProperty_CurrentDevice, &sr, sizeof(CFStringRef));
|
|
if (err)
|
|
{
|
|
setLastErrorMessage(QString("Cannot set audio input %1 (%2)").arg(waveInDeviceName).arg(err));
|
|
}
|
|
else
|
|
{
|
|
qDebug() << "recording with input" << waveInDeviceName;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
setLastErrorMessage(QString("Cannot find audio input device UID with ID %1 (%2)").arg(deviceID).arg(err));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
setLastErrorMessage(QString("Cannot find audio input with name %1 (%2)").arg(waveInDeviceName).arg(err));
|
|
}
|
|
}
|
|
|
|
UInt32 monitor = true;
|
|
|
|
err = AudioQueueSetProperty(mQueue, kAudioQueueProperty_EnableLevelMetering , &monitor, sizeof(UInt32));
|
|
if (err)
|
|
{
|
|
qWarning() << QString("Cannot set recording level monitoring %1").arg(err);
|
|
}
|
|
|
|
int nbBuffers = 6;
|
|
mSampleBufferSize = sAudioFormat.mSampleRate * sAudioFormat.mChannelsPerFrame
|
|
* sAudioFormat.mBitsPerChannel / 8 * mBufferLengthInMs / 1000;
|
|
// BufferSize [bytes] = Length [s] * 44100 frames per second [Fr./s] * channels per frame [Ch./Fr.] * bytes per channel [bytes/Ch.]
|
|
|
|
for (int i = 0; i < nbBuffers; i++)
|
|
{
|
|
AudioQueueBufferRef outBuffer;
|
|
err = AudioQueueAllocateBuffer(mQueue, mSampleBufferSize, &outBuffer);
|
|
|
|
if (err)
|
|
{
|
|
setLastErrorMessage(QString("Cannot allocate audio buffer %1").arg(err));
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
mBuffers << outBuffer;
|
|
}
|
|
|
|
foreach(AudioQueueBufferRef buffer, mBuffers)
|
|
{
|
|
err = AudioQueueEnqueueBuffer(mQueue, buffer, 0, 0);
|
|
if (err)
|
|
{
|
|
setLastErrorMessage(QString("Cannot enqueue audio buffer %1").arg(err));
|
|
close();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
mMsTimeStamp = 0;
|
|
|
|
err = AudioQueueStart(mQueue, 0);
|
|
|
|
if (err)
|
|
{
|
|
setLastErrorMessage(QString("Cannot starting audio queue %1").arg(err));
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
mIsRecording = true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool UBAudioQueueRecorder::close()
|
|
{
|
|
if(mIsRecording)
|
|
{
|
|
mIsRecording = false;
|
|
|
|
OSStatus err = AudioQueueStop(mQueue, true);
|
|
|
|
if(err)
|
|
{
|
|
setLastErrorMessage(QString("Cannot stop audio queue %1").arg(err));
|
|
}
|
|
}
|
|
|
|
foreach(AudioQueueBufferRef buffer, mBuffers)
|
|
{
|
|
OSStatus err = AudioQueueFreeBuffer(mQueue, buffer);
|
|
if (err)
|
|
{
|
|
setLastErrorMessage(QString("Cannot free audio buffer %1").arg(err));
|
|
}
|
|
}
|
|
|
|
OSStatus err = AudioQueueDispose(mQueue, true);
|
|
mQueue = 0;
|
|
|
|
if(err)
|
|
{
|
|
setLastErrorMessage(QString("Cannot dispose audio queue %1").arg(err));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void UBAudioQueueRecorder::audioQueueInputCallback (void *inUserData,
|
|
AudioQueueRef inAQ,
|
|
AudioQueueBufferRef inBuffer,
|
|
const AudioTimeStamp *inStartTime,
|
|
UInt32 inNumberPacketDescriptions,
|
|
const AudioStreamPacketDescription *inPacketDescs)
|
|
{
|
|
Q_UNUSED(inAQ);
|
|
Q_UNUSED(inStartTime)
|
|
UBAudioQueueRecorder* recorder = (UBAudioQueueRecorder*)inUserData;
|
|
|
|
if(recorder && recorder->isRecording() && inBuffer)
|
|
{
|
|
recorder->emitNewWaveBuffer(inBuffer, inNumberPacketDescriptions, inPacketDescs);
|
|
|
|
OSStatus err = AudioQueueEnqueueBuffer(recorder->mQueue, inBuffer, 0, 0);
|
|
|
|
if(err)
|
|
{
|
|
recorder->setLastErrorMessage(QString("Cannot reenqueue buffer %1").arg(err));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void UBAudioQueueRecorder::emitNewWaveBuffer(AudioQueueBufferRef pBuffer,
|
|
int inNumberPacketDescriptions,
|
|
const AudioStreamPacketDescription *inPacketDescs)
|
|
{
|
|
|
|
emit newWaveBuffer(pBuffer->mAudioData, pBuffer->mAudioDataByteSize);
|
|
|
|
qreal level = 0;
|
|
UInt32 size;
|
|
|
|
if (noErr == AudioQueueGetPropertySize (mQueue, kAudioQueueProperty_CurrentLevelMeter, &size))
|
|
{
|
|
AudioQueueLevelMeterState levels[size / sizeof(AudioQueueLevelMeterState)];
|
|
size = sizeof(levels);
|
|
|
|
if (noErr == AudioQueueGetProperty(mQueue, kAudioQueueProperty_CurrentLevelMeter, &levels, &size))
|
|
{
|
|
if (size == 1)
|
|
level = levels[0].mAveragePower;
|
|
else if (size >= 2)
|
|
level = (levels[0].mAveragePower + levels[1].mAveragePower) / 2;
|
|
}
|
|
else
|
|
{
|
|
qDebug() << "cannot retreive audio level";
|
|
}
|
|
}
|
|
|
|
emit audioLevelChanged(level * 255);
|
|
}
|
|
|
|
|
|
|