Audio support for Mac OS X added.

git-svn-id: http://voip.null.ro/svn/yate@3154 acf43c95-373e-0410-b603-e72c3f656dc1
This commit is contained in:
oana 2010-03-30 12:12:32 +00:00
parent aa528b1523
commit 1f51c74a0a
4 changed files with 998 additions and 0 deletions

View File

@ -27,6 +27,8 @@
#ifdef _WINDOWS
#define DEFAULT_DEVICE "dsound/*"
#elif defined(__APPLE__)
#define DEFAULT_DEVICE "coreaudio/*"
#elif defined(__linux__)
#define DEFAULT_DEVICE "alsa/default"
#else

View File

@ -595,6 +595,19 @@ HAVE_ALSA="yes"
AC_MSG_RESULT([$HAVE_ALSA])
AC_SUBST(HAVE_ALSA)
# check for Mac OS X audio headers
HAVE_COREAUDIO=no
if [[ "x$uname_os" = "xDarwin" ]]; then
AC_MSG_CHECKING([for Mac OS X audio headers])
AC_TRY_COMPILE([
#include <AudioUnit/AudioUnit.h>
#include <AudioToolbox/AudioToolbox.h>
],[],
[HAVE_COREAUDIO="yes"],[]
)
AC_MSG_RESULT([$HAVE_COREAUDIO])
fi
AC_SUBST(HAVE_COREAUDIO)
HAVE_GSM=no
GSM_INC=""

View File

@ -77,6 +77,10 @@ ifneq (@HAVE_ALSA@,no)
PROGS := $(PROGS) client/alsachan.yate
endif
ifneq (@HAVE_COREAUDIO@,no)
PROGS := $(PROGS) client/coreaudio.yate
endif
ifneq (@HAVE_QT4@,no)
PROGS := $(PROGS) qt4/updater.yate
endif
@ -254,6 +258,8 @@ enumroute.yate: LOCALLIBS = @RESOLV_LIB@
client/alsachan.yate: LOCALLIBS = -lasound
client/coreaudio.yate: LOCALLIBS = -framework CoreServices -framework CoreAudio -framework AudioUnit -framework AudioToolBox
yiaxchan.yate: ../libs/yiax/libyateiax.a
yiaxchan.yate: LOCALFLAGS = -I@top_srcdir@/libs/yiax
yiaxchan.yate: LOCALLIBS = -L../libs/yiax -lyateiax

977
modules/client/coreaudio.cpp Executable file
View File

@ -0,0 +1,977 @@
/*
* coreaudio.cpp
* This file is part of the YATE Project http://YATE.null.ro
*
* CoreAudio sound channel driver for Mac OS X.
*
* Yet Another Telephony Engine - a fully featured software PBX and IVR
* Copyright (C) 2004-2010 Null Team
*
* This program 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <yatephone.h>
#include <AudioUnit/AudioUnit.h>
#include <AudioToolbox/AudioToolbox.h>
#define FRAME_SIZE 320
using namespace TelEngine;
namespace { //anonymous
class CoreAudioSource : public ThreadedSource
{
public:
CoreAudioSource();
~CoreAudioSource();
// inherited methods
bool init(const String& type);
virtual void run();
virtual void cleanup();
virtual bool control(NamedList& params);
// append to the internal buffer data read from input source
void sendData(AudioBufferList *buf);
// provide data to the AudioConverter taken from the internal buffer
DataBlock getData(UInt32 pkts);
// helper function for allocating buffers
AudioBufferList* allocateAudioBufferList(UInt32 numChannels, UInt32 size);
// helper function for freeing buffers
void destroyAudioBufferList(AudioBufferList* list);
// helper function for obtaining an AudioConverter
OSStatus buildConverter(AudioStreamBasicDescription inFormat, AudioConverterRef* ac);
// obtain the output format of the AudioUnit
inline AudioStreamBasicDescription outFormat()
{ return m_outDevFormat; }
private:
// callback for obtaining data from input source
static OSStatus inputCallback(void* inRefCon, AudioUnitRenderActionFlags* ioActionFlags, const AudioTimeStamp* inTimeStamp,
UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList* ioData);
// default input AudioUnit
AudioUnit fAudioUnit;
// input buffer
AudioBufferList* m_inAudioBuffer;
// sample rate converter
AudioConverterRef m_audioConvert;
// input device id
AudioDeviceID fInputDevID;
// output format of the AudioUnit
AudioStreamBasicDescription m_outDevFormat;
// the desired format for yate, passed to the sample rate converter
AudioStreamBasicDescription m_convertToFormat;
// total amount of bytes sent
unsigned int m_total;
// check if the AudioDevice supports setting the volume
bool m_volSettable;
// internal buffer
DataBlock m_data;
};
class CoreAudioConsumer : public DataConsumer, public Mutex
{
public:
CoreAudioConsumer();
~CoreAudioConsumer();
bool init(const String& type);
// inherited methods
virtual unsigned long Consume(const DataBlock &data, unsigned long tStamp, unsigned long flags);
virtual bool control(NamedList& params);
virtual void getData(AudioBufferList* buf);
private:
// callback through which the AudioUnit requires data to play
static OSStatus outputCallback(void* inRefCon, AudioUnitRenderActionFlags* ioActionFlags, const AudioTimeStamp* inTimeStamp,
UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList* ioData);
// the AudioUnit
AudioUnit fAudioUnit;
// total amount of data written to the output
unsigned int m_total;
// check if the AudioDevice supports setting the volume
bool m_volSettable;
// AudioDevice ID
AudioDeviceID fOutputDevID;
// internal buffer
DataBlock m_data;
};
class CoreAudioChan : public CallEndpoint
{
public:
CoreAudioChan(const String& dev);
~CoreAudioChan();
bool init();
virtual void disconnected(bool final, const char *reason);
void answer();
inline void setTarget(const char* target = 0)
{ m_target = target; }
inline const String& getTarget() const
{ return m_target; }
private:
String m_dev;
String m_target;
};
class CoreAudioHandler : public MessageHandler
{
public:
CoreAudioHandler(const char *name) : MessageHandler(name) { }
virtual bool received(Message &msg);
};
class StatusHandler : public MessageHandler
{
public:
StatusHandler() : MessageHandler("engine.status") { }
virtual bool received(Message &msg);
};
class DropHandler : public MessageHandler
{
public:
DropHandler() : MessageHandler("call.drop") { }
virtual bool received(Message &msg);
};
class MasqHandler : public MessageHandler
{
public:
MasqHandler(int prio) : MessageHandler("chan.masquerade",prio) { }
virtual bool received(Message &msg);
};
class AttachHandler : public MessageHandler
{
public:
AttachHandler() : MessageHandler("chan.attach") { }
virtual bool received(Message &msg);
};
class CoreAudioPlugin : public Plugin
{
public:
CoreAudioPlugin();
virtual void initialize();
virtual bool isBusy() const;
private:
CoreAudioHandler* m_handler;
};
static CoreAudioChan* s_audioChan = 0;
INIT_PLUGIN(CoreAudioPlugin);
// test if a device permits setting the volume
static bool checkVolumeSettable(AudioDeviceID devId, UInt32 inChannel,Boolean isInput)
{
Boolean isWritable = false;
OSStatus err = AudioDeviceGetPropertyInfo(devId,inChannel,isInput,kAudioDevicePropertyVolumeScalar,NULL,&isWritable);
if (err != noErr) {
DDebug(DebugInfo, "CoreAudio - Failed to get volume property err=%4.4s, %ld",(char*)&err,err);
return false;
}
return isWritable;
}
// callback for the sample rate converter
OSStatus convertCallback(AudioConverterRef inAudioConverter, UInt32* ioNumberDataPackets, AudioBufferList* ioData,
AudioStreamPacketDescription** outDataPacketDescription, void* inUserData)
{
CoreAudioSource* src = static_cast<CoreAudioSource*> (inUserData);
if (!src)
return 1;
// try to get data with the required length
DataBlock data = src->getData(*ioNumberDataPackets);
if (data.length() > 0)
XDebug(DebugInfo,"CoreAudio::convertCallBack() packetsReq=%d pktsAvailable=%d", (int)*ioNumberDataPackets,data.length()/2);
// signal that we have no data to convert and return
if (data.length() == 0) {
*ioNumberDataPackets = 0;
return 1;
}
// determine how much we can read into the converter's input buffer
UInt32 maxPackets = data.length() / src->outFormat().mBytesPerFrame;
if (*ioNumberDataPackets > maxPackets)
*ioNumberDataPackets = maxPackets;
else
maxPackets = *ioNumberDataPackets;
// fill the converters input buffer
ioData->mBuffers[0].mData = data.data();
ioData->mBuffers[0].mDataByteSize = maxPackets * src->outFormat().mBytesPerFrame;
ioData->mBuffers[0].mNumberChannels = 1;
data.cut(-(maxPackets * src->outFormat().mBytesPerFrame));
return noErr;
}
CoreAudioSource::CoreAudioSource()
: m_inAudioBuffer(0), m_audioConvert(NULL), fInputDevID(0), m_total(0)
{
Debug(DebugAll,"CoreAudioSource::CoreAudioSource() [%p]",this);
}
CoreAudioSource::~CoreAudioSource()
{
Debug(DebugAll,"CoreAudioSource::~CoreAudioSource() [%p] total=%u",this,m_total);
OSStatus err = AudioOutputUnitStop(fAudioUnit);
if(err != noErr)
Debug(DebugInfo,"CoreAudioSource::~CoreAudioSource() [%p] - Failed to stop AU",this);
err = AudioUnitUninitialize(fAudioUnit);
if(err != noErr)
Debug(DebugInfo,"CoreAudioSource::~CoreAudioSource() [%p] - Failed to uninitialize AU",this);
destroyAudioBufferList(m_inAudioBuffer);
}
bool CoreAudioSource::init(const String& type)
{
OSStatus err = noErr;
UInt32 param;
// open the AudioOutputUnit, provide description
Component component;
ComponentDescription description;
description.componentType = kAudioUnitType_Output;
description.componentSubType = kAudioUnitSubType_HALOutput;
description.componentManufacturer = kAudioUnitManufacturer_Apple;
description.componentFlags = 0;
description.componentFlagsMask = 0;
if((component = FindNextComponent(NULL,&description))) {
err = OpenAComponent(component,&fAudioUnit);
if(err != noErr) {
fAudioUnit = NULL;
Debug(DebugInfo,"CoreAUdioSource::init() [%p] - failed to open component error==%4.4s, %ld",this,(char*)&err,err);
return false;
}
}
// configure AudioOutputUnit for input, enable input on the AUHAL
param = 1;
err = AudioUnitSetProperty(fAudioUnit,kAudioOutputUnitProperty_EnableIO,kAudioUnitScope_Input,1,&param,sizeof(UInt32));
if (err == noErr) {
// disable output on the AUHAL
param = 0;
err = AudioUnitSetProperty(fAudioUnit,kAudioOutputUnitProperty_EnableIO,kAudioUnitScope_Input,0,&param,sizeof(UInt32));
}
else {
Debug(DebugInfo,"CoreAUdioSource::init() [%p] - failed to configure AudioUnit for input error==%4.4s, %ld",this,(char*)&err,err);
return false;
}
// select the default input device
param = sizeof(AudioDeviceID);
err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice,&param,&fInputDevID);
if(err != noErr) {
Debug(DebugInfo,"CoreAudioSource::init() [%p] - failed to get input device error==%4.4s, %ld",this,(char*)&err,err);
return false;
}
// set the current device to the AudioUnit
err = AudioUnitSetProperty(fAudioUnit,kAudioOutputUnitProperty_CurrentDevice,kAudioUnitScope_Global,0,&fInputDevID,sizeof(AudioDeviceID));
if (err != noErr) {
Debug(DebugInfo,"CoreAudioSource::init() [%p] - failed to set AU input device=%4.4s, %ld",this,(char*)&err,err);
return false;
}
// setup render callback
AURenderCallbackStruct callback;
callback.inputProc = CoreAudioSource::inputCallback;
callback.inputProcRefCon = this;
err = AudioUnitSetProperty(fAudioUnit,kAudioOutputUnitProperty_SetInputCallback,kAudioUnitScope_Global,0,&callback,sizeof(AURenderCallbackStruct));
if (err != noErr) {
Debug(DebugInfo,"CoreAudioSource::init() [%p] - could not set callback error==%4.4s, %ld",this,(char*)&err,err);
return false;
}
// get hardware device format
param = sizeof(AudioStreamBasicDescription);
AudioStreamBasicDescription devFormat;
err = AudioUnitGetProperty(fAudioUnit,kAudioUnitProperty_StreamFormat,kAudioUnitScope_Input,1,&devFormat,&param);
if(err != noErr) {
Debug(DebugInfo,"CoreAudioSource::init() [%p] - failed to get input device AudioStreamBasicDescription error==%4.4s, %ld",this,(char*)&err,err);
return false;
}
DDebug(DebugInfo,"CoreAudioSource::init() [%p] - hardware device input format is : channels/frame=%u, sampleRate=%f, bits/channel=%u, "
"bytes/frame=%u, frames/packet=%u, bytes/packet=%u, formatFlags=0x%x",
this,(unsigned int)devFormat.mChannelsPerFrame,devFormat.mSampleRate,(unsigned int)devFormat.mBitsPerChannel,
(unsigned int)devFormat.mBytesPerFrame,(unsigned int)devFormat.mFramesPerPacket,(unsigned int)devFormat.mBytesPerPacket,
(unsigned int)devFormat.mFormatFlags);
m_outDevFormat.mChannelsPerFrame = 1;
m_outDevFormat.mSampleRate = devFormat.mSampleRate;
m_outDevFormat.mFormatID = kAudioFormatLinearPCM;
m_outDevFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
m_outDevFormat.mFormatFlags &= ~kAudioFormatFlagIsBigEndian;
#if __BIG_ENDIAN__
m_outDevFormat.mFormatFlags |= kAudioFormatFlagIsBigEndian;
#endif
m_outDevFormat.mBitsPerChannel = sizeof(int16_t) * 8;
m_outDevFormat.mBytesPerFrame = m_outDevFormat.mBitsPerChannel / 8;
m_outDevFormat.mFramesPerPacket = 1;
m_outDevFormat.mBytesPerPacket = m_outDevFormat.mBytesPerFrame;
// det the AudioUnit output data format
err = AudioUnitSetProperty(fAudioUnit,kAudioUnitProperty_StreamFormat,kAudioUnitScope_Output,1,&m_outDevFormat,sizeof(AudioStreamBasicDescription));
if(err != noErr) {
Debug(DebugInfo, "CoreAudioSource::init() [%p] - failed to set output data format error==%4.4s, %ld",this,(char*)&err,err);
return false;
}
DDebug(DebugInfo,"CoreAudioSource::init() [%p] - AudioUnit output format is : channels/frame=%u, sampleRate=%f, bits/channel=%u, "
"bytes/frame=%u, frames/packet=%u, bytes/packet=%u, formatFlags=0x%x",
this,(unsigned int)m_outDevFormat.mChannelsPerFrame,m_outDevFormat.mSampleRate,(unsigned int)m_outDevFormat.mBitsPerChannel,
(unsigned int)m_outDevFormat.mBytesPerFrame,(unsigned int)m_outDevFormat.mFramesPerPacket,(unsigned int)m_outDevFormat.mBytesPerPacket,
(unsigned int)m_outDevFormat.mFormatFlags);
// obtain a sample rate converter
err = buildConverter(m_outDevFormat,&m_audioConvert);
if (err != noErr) {
Debug(DebugInfo,"CoreAudioSource::init() [%p] - failed to get sample rate converter error==%4.4s, %ld",this,(char*)&err,err);
return false;
}
// get the number of frames in the IO buffer
UInt32 audioSamples;
param = sizeof(UInt32);
err = AudioUnitGetProperty(fAudioUnit,kAudioDevicePropertyBufferFrameSize,kAudioUnitScope_Global,0,&audioSamples,&param);
if(err != noErr) {
Debug(DebugInfo,"CoreAudioSource::init() [%p] - failed to get audio sample size error==%4.4s, %ld",this,(char*)&err,err);
return false;
}
// Initialize the AU
err = AudioUnitInitialize(fAudioUnit);
if(err != noErr) {
Debug(DebugInfo,"CoreAudioSource::init() [%p] - Failed to initialize AU error==%4.4s, %ld",this,(char*)&err,err);
return false;
}
// allocate AudioBufferList
m_inAudioBuffer = allocateAudioBufferList(m_outDevFormat.mChannelsPerFrame,audioSamples * m_outDevFormat.mBytesPerFrame);
if(m_inAudioBuffer == NULL) {
Debug(DebugInfo,"CoreAudioSource::init() [%p] - Failed to allocate audio buffers",this);
return false;
}
// Start pulling for audio data
err = AudioOutputUnitStart(fAudioUnit);
if(err != noErr) {
Debug(DebugInfo,"CoreAudioSource::init() [%p] - Failed to start the AudioUnit error==%4.4s, %ld",this,(char*)&err,err);
return false;
}
else
Debug(DebugInfo,"CoreAudioSource::init() [%p] - AudioUnit started",this);
// check if the device lets us set the volume
m_volSettable = checkVolumeSettable(fInputDevID,0,true);
Debug(DebugAll,"CoreAudioSource::init() [%p] - volume %s settable",this,(m_volSettable ? "is" : "isn't"));
return start("CoreAudioSource");
}
OSStatus CoreAudioSource::buildConverter(AudioStreamBasicDescription inputFormat, AudioConverterRef* ac)
{
m_convertToFormat.mChannelsPerFrame = 1;
m_convertToFormat.mSampleRate = 8000.0;
m_convertToFormat.mFormatID = kAudioFormatLinearPCM;
m_convertToFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
m_convertToFormat.mFormatFlags &= ~kAudioFormatFlagIsBigEndian;
#if __BIG_ENDIAN__
m_convertToFormat.mFormatFlags |= kAudioFormatFlagIsBigEndian;
#endif
m_convertToFormat.mBitsPerChannel = sizeof(int16_t) * 8;
m_convertToFormat.mBytesPerFrame = m_convertToFormat.mBitsPerChannel / 8;
m_convertToFormat.mFramesPerPacket = 1;
m_convertToFormat.mBytesPerPacket = m_convertToFormat.mBytesPerFrame;
DDebug(DebugInfo,"CoreAudioSource::buildConverter() [%p] - AudioConverter output format is : channels/frame=%u, sampleRate=%f, bits/channel=%u, "
"bytes/frame=%u, frames/packet=%u, bytes/packet=%u, formatFlags=0x%x",
this,(unsigned int)m_convertToFormat.mChannelsPerFrame,m_convertToFormat.mSampleRate,(unsigned int)m_convertToFormat.mBitsPerChannel,
(unsigned int)m_convertToFormat.mBytesPerFrame,(unsigned int)m_convertToFormat.mFramesPerPacket,(unsigned int)m_convertToFormat.mBytesPerPacket,
(unsigned int)m_convertToFormat.mFormatFlags);
OSStatus err = noErr;
err = AudioConverterNew(&inputFormat,&m_convertToFormat,ac);
if (err != noErr) {
Debug(DebugInfo,"CoreAudioSource::buildConverter() [%p] failed to get converter error==%4.4s, %ld",this,(char*)&err, err);
return err;
}
// set channel map
SInt32 channelMap[] = { 0 };
err = AudioConverterSetProperty(*ac, kAudioConverterChannelMap, sizeof(SInt32), channelMap);
// set converter complexity
UInt32 size = sizeof(kAudioConverterSampleRateConverterComplexity_Mastering);
UInt32 prop = kAudioConverterSampleRateConverterComplexity_Mastering;
err = AudioConverterSetProperty(*ac,kAudioConverterSampleRateConverterComplexity,size,&prop);
if (err != noErr)
Debug(DebugInfo,"CoreAudioSource::buildConverter() [%p] failed to set converter complexity error==%4.4s, %ld",this,(char*)&err,err);
return noErr;
}
AudioBufferList* CoreAudioSource::allocateAudioBufferList(UInt32 numChannels, UInt32 size)
{
AudioBufferList* list;
DDebug(DebugAll,"CoreAudioSource::allocateAudioBufferList(channels= %d,size=%d) [%p]",(int)numChannels,(int)size,this);
list = (AudioBufferList*)calloc(1, sizeof(AudioBufferList) + numChannels * sizeof(AudioBuffer));
if(list == NULL)
return NULL;
list->mNumberBuffers = numChannels;
for(UInt32 i = 0; i < numChannels; ++i) {
list->mBuffers[i].mNumberChannels = 1;
list->mBuffers[i].mDataByteSize = size;
list->mBuffers[i].mData = malloc(size);
if(list->mBuffers[i].mData == NULL) {
destroyAudioBufferList(list);
return NULL;
}
}
return list;
}
void CoreAudioSource::destroyAudioBufferList(AudioBufferList* list)
{
DDebug(DebugAll,"CoreAudioSource::destroyAudioBufferList(list=%p) [%p]",list,this);
if(list) {
for(UInt32 i = 0; i < list->mNumberBuffers; i++) {
if(list->mBuffers[i].mData)
free(list->mBuffers[i].mData);
}
free(list);
}
}
void CoreAudioSource::sendData(AudioBufferList* buf)
{
// append to internal buffer data we receive from input
if (!buf)
return;
lock();
for (unsigned int i = 0; i < m_outDevFormat.mChannelsPerFrame; i++)
m_data.append(buf->mBuffers[i].mData,buf->mBuffers[i].mDataByteSize);
XDebug(DebugAll,"CoreAudioSource::sendData(buffer=%p,buffer_length=%d), internal buffer length=%d [%p]",buf,(int)buf->mBuffers[0].mDataByteSize,m_data.length(),this);
unlock();
}
DataBlock CoreAudioSource::getData(UInt32 pkts)
{
// return to the converter a data block with the required size or the maximum available
DataBlock data;
lock();
if (pkts * m_outDevFormat.mBytesPerFrame > m_data.length())
pkts = m_data.length() / m_outDevFormat.mBytesPerFrame;
data.assign(m_data.data(),pkts * m_outDevFormat.mBytesPerFrame);
m_data.cut(-pkts * m_outDevFormat.mBytesPerFrame );
unlock();
return data;
}
OSStatus CoreAudioSource::inputCallback(void* inRefCon, AudioUnitRenderActionFlags* ioActionFlags, const AudioTimeStamp* inTimeStamp,
UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList* ioData)
{
CoreAudioSource* source = (CoreAudioSource*) inRefCon;
OSStatus err = noErr;
// Render into audio buffer
err = AudioUnitRender(source->fAudioUnit,ioActionFlags,inTimeStamp,inBusNumber,inNumberFrames,source->m_inAudioBuffer);
if(err)
Debug(DebugInfo,"CoreAudioSource::inputCallback() [%p] AudioUnitRender() failed with error=%4.4s, %ld",source,(char*)&err,err);
source->sendData(source->m_inAudioBuffer);
return err;
}
void CoreAudioSource::run()
{
DataBlock frame;
AudioBufferList fillBufList;
fillBufList.mNumberBuffers = 1;
fillBufList.mBuffers[0].mNumberChannels = 1;
fillBufList.mBuffers[0].mData = new char[FRAME_SIZE];
do {
if (!looping())
break;
if (frame.length() < FRAME_SIZE) {
// try to get more data from input and convert it to the desired sample rate
UInt32 outBuffSize = FRAME_SIZE / m_convertToFormat.mBytesPerPacket;
//AudioStreamPacketDescription* pktDesc = NULL;
fillBufList.mBuffers[0].mDataByteSize = FRAME_SIZE;
OSStatus err = AudioConverterFillComplexBuffer(m_audioConvert,convertCallback,this,&outBuffSize,&fillBufList,NULL/*pktDesc*/);
if (err != noErr && err != 1)
Debug(DebugInfo,"CoreAudioSource::run() - AudioConvertFillComplexBuffer() failed with error=%4.4s, %ld", (char*)&err, err);
if (outBuffSize == 0) {
Thread::idle();
continue;
}
frame.append(fillBufList.mBuffers[0].mData,outBuffSize * m_convertToFormat.mBytesPerPacket);
}
if (frame.length() >= FRAME_SIZE) {
// we have enough data to send forward
DataBlock data(frame.data(),FRAME_SIZE,false);
Forward(data);
data.clear(false);
frame.cut(-FRAME_SIZE);
m_total += FRAME_SIZE;
}
}
while (true);
delete [] (char*)fillBufList.mBuffers[0].mData;
Debug(DebugAll,"CoreAudioSource [%p] end of data",this);
}
void CoreAudioSource::cleanup()
{
Debug(DebugAll,"CoreAudioSource [%p] cleanup, total=%u",this,m_total);
AudioConverterDispose(m_audioConvert);
ThreadedSource::cleanup();
}
bool CoreAudioSource::control(NamedList& params)
{
DDebug(DebugInfo,"CoreAudioSource::control() [%p]",this);
if (!m_volSettable)
return false;
int vol = params.getIntValue("in_volume",-1);
if (vol == -1) {
Debug(DebugAll,"CoreAudioSource::control() [%p] - invalid value to set for volume",this);
return false;
}
Float32 volValue = vol / 100.0;
OSStatus err = AudioDeviceSetProperty(fInputDevID,NULL,0,true,kAudioDevicePropertyVolumeScalar,sizeof(Float32),&volValue);
if (err != noErr)
Debug(DebugInfo,"CoreAudioSource::control() [%p] - set volume failed with error=%4.4s, %ld",this,(char*)&err,err);
if(params.getParam("out_volume"))
return false;
return err == noErr;
}
CoreAudioConsumer::CoreAudioConsumer()
: Mutex(false,"CoreAudioConsumer"),
m_total(0), m_volSettable(false), fOutputDevID(0)
{
Debug(DebugAll,"AudioSystemConsumer::AudioSystemConsumer() [%p]",this);
}
CoreAudioConsumer::~CoreAudioConsumer()
{
Debug(DebugAll,"CoreAudioConsumer::~CoreAudioConsumer() [%p] total=%u",this,m_total);
OSStatus err = AudioOutputUnitStop(fAudioUnit);
if(err != noErr)
Debug(DebugInfo,"CoreAudioConsumer::~CoreAudioConsumer() [%p] - Failed to stop output AudioUnit error=%4.4s, %ld",this,(char*)&err,err);
err = AudioUnitUninitialize(fAudioUnit);
if(err != noErr)
Debug(DebugInfo,"CoreAudioConsumer::~CoreAudioConsumer() [%p] - Failed to uninitialize the AudioUnit error=%4.4s, %ld",this,(char*)&err,err);
}
bool CoreAudioConsumer::init(const String& type)
{
OSStatus err = noErr;
// open the AudioOutputUnit, provide description
Component component;
ComponentDescription description;
description.componentType = kAudioUnitType_Output;
description.componentSubType = kAudioUnitSubType_DefaultOutput;
description.componentManufacturer = kAudioUnitManufacturer_Apple;
description.componentFlags = 0;
description.componentFlagsMask = 0;
if((component = FindNextComponent(NULL,&description))) {
err = OpenAComponent(component,&fAudioUnit);
if(err != noErr) {
Debug(DebugInfo,"CoreAudioConsumer::init() [%p] - failed to open component error==%4.4s, %ld",this,(char*)&err,err);
fAudioUnit = NULL;
return false;
}
}
// set up the callback to generate output to the output unit
AURenderCallbackStruct callback;
callback.inputProc = CoreAudioConsumer::outputCallback;
callback.inputProcRefCon = this;
err = AudioUnitSetProperty (fAudioUnit,kAudioUnitProperty_SetRenderCallback,kAudioUnitScope_Input,0,&callback,sizeof(callback));
if (err != noErr)
Debug(DebugInfo,"CoreAudioConsumer::init() [%p]- callback could not be set error=%4.4s, %ld",this,(char*)&err,err);
// provide the input format of the date we're supplying
AudioStreamBasicDescription inputFormat;
inputFormat.mSampleRate = 8000.0;
inputFormat.mFormatID = kAudioFormatLinearPCM;
inputFormat.mBitsPerChannel = sizeof(int16_t) * 8; // = 16
inputFormat.mBytesPerFrame = inputFormat.mBitsPerChannel / 8;
inputFormat.mFramesPerPacket = 1;
inputFormat.mBytesPerPacket = inputFormat.mBytesPerFrame;
inputFormat.mChannelsPerFrame = 1;
inputFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
inputFormat.mFormatFlags &= ~kLinearPCMFormatFlagIsNonInterleaved;
err = AudioUnitSetProperty(fAudioUnit,kAudioUnitProperty_StreamFormat,kAudioUnitScope_Input,0,&inputFormat,sizeof(AudioStreamBasicDescription));
if (err != noErr) {
Debug(DebugInfo,"CoreAudioConsumer::init() [%p] - set input format failed error==%4.4s, %ld",this,(char*)&err,err);
return false;
}
// initialize the AudioUnit
err = AudioUnitInitialize(fAudioUnit);
if (err != noErr) {
Debug(DebugInfo,"CoreAudioConsumer::init() [%p] - AudioUnitInitialize failed error=%4.4s, %ld",this,(char*)&err,err);
return false;
}
// start the AudioUnit
err = AudioOutputUnitStart(fAudioUnit);
if (err != noErr) {
Debug(DebugInfo,"CoreAudioConsumer::init() [%p] - AudioUnitStart failed error=%4.4s, %ld",this,(char*)&err,err);
return false;
}
// get the id of the default output device
UInt32 param = sizeof(AudioDeviceID);
err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice,&param,&fOutputDevID);
if(err != noErr)
Debug(DebugMild,"CoreAudioConsumer::init() [%p] - Failed to get the device id of the output device error==%4.4s, %ld",this,(char*)&err,err);
m_volSettable = checkVolumeSettable(fOutputDevID,1,false);
Debug(DebugInfo,"CoreAudioConsumer::init() volume %s settable",(m_volSettable ? "is" : "isn't"));
return true;
}
void CoreAudioConsumer::getData(AudioBufferList* buf)
{
if (!buf)
return;
// put the data into the output buffer
UInt32 len = buf->mBuffers[0].mDataByteSize; // there should be only one buffer;
lock();
if (m_data.length() == 0) {
::memset(buf->mBuffers[0].mData,0,len);
unlock();
return;
}
if (len > m_data.length())
len = m_data.length();
if (len > 0) {
::memcpy(buf->mBuffers[0].mData,m_data.data(),len);
m_data.cut(-len);
}
unlock();
}
OSStatus CoreAudioConsumer::outputCallback(void* inRefCon, AudioUnitRenderActionFlags* ioActionFlags, const AudioTimeStamp* inTimeStamp,
UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList* ioData)
{
CoreAudioConsumer* dst = static_cast<CoreAudioConsumer*>(inRefCon);
if (!dst)
return 1;
XDebug(DebugAll,"CoreAudioConsumer::outputCallback() [%p] inNumberFrames=%d buffersCount=%d buffersize=%d",dst,(unsigned int)inNumberFrames,
(unsigned int)ioData->mNumberBuffers,(unsigned int)ioData->mBuffers[0].mDataByteSize);
dst->getData(ioData);
return noErr;
}
unsigned long CoreAudioConsumer::Consume(const DataBlock &data, unsigned long tStamp, unsigned long flags)
{
// append to the internal buffer received data
if (data.null())
return 0;
lock();
m_total += data.length();
m_data.append(data);
unlock();
return invalidStamp();
}
bool CoreAudioConsumer::control(NamedList& params)
{
DDebug(DebugAll,"CoreAudioConsumer::control() [%p]",this);
if (!m_volSettable)
return false;
int vol = params.getIntValue("out_volume",-1);
if (vol == -1) {
Debug(DebugAll,"CoreAudioConsumer::control() [%p] invalid value to set for volume",this);
return false;
}
Float32 volValue = vol / 100.0;
// set the volume for the output on every channel
OSStatus err1 = AudioDeviceSetProperty(fOutputDevID,NULL,1,false,kAudioDevicePropertyVolumeScalar,sizeof(Float32),&volValue);
OSStatus err2 = AudioDeviceSetProperty(fOutputDevID,NULL,2,false,kAudioDevicePropertyVolumeScalar,sizeof(Float32),&volValue);
return err1 == noErr && err2 == noErr;
}
CoreAudioChan::CoreAudioChan(const String& dev)
: CallEndpoint("coreaudio"),
m_dev(dev)
{
Debug(DebugAll,"CoreAudioChan::CoreAudioChan ('%s') [%p]",dev.c_str(),this);
s_audioChan = this;
}
CoreAudioChan::~CoreAudioChan()
{
Debug(DebugAll,"CoreAudioChan::~CoreAudioChan() [%p]",this);
setTarget();
setSource();
setConsumer();
s_audioChan = 0;
}
bool CoreAudioChan::init()
{
CoreAudioSource* source = new CoreAudioSource();
if (!source->init(m_dev)) {
source->deref();
return false;
}
setSource(source);
source->deref();
CoreAudioConsumer* cons = new CoreAudioConsumer();
if (!cons->init(m_dev)) {
cons->deref();
setSource();
return false;
}
setConsumer(cons);
cons->deref();
return true;
}
void CoreAudioChan::disconnected(bool final, const char *reason)
{
Debug(DebugInfo,"CoreAudioChan::disconnected() '%s' [%p]",reason,this);
setTarget();
}
void CoreAudioChan::answer()
{
Message* m = new Message("call.answered");
m->addParam("module","coreaudio");
String tmp("coreaudio/");
tmp += m_dev;
m->addParam("id",tmp);
if (m_target)
m->addParam("targetid",m_target);
Engine::enqueue(m);
}
bool CoreAudioHandler::received(Message &msg)
{
Debug(DebugInfo,"CoreAudio received call.execute");
String dest(msg.getValue("callto"));
if (dest.null())
return false;
Regexp r("^coreaudio/\\(.*\\)$");
if (!dest.matches(r))
return false;
if (s_audioChan) {
msg.setParam("error","busy");
return false;
}
CoreAudioChan *chan = new CoreAudioChan(dest.matchString(1).c_str());
if (!chan->init()) {
chan->destruct();
return false;
}
CallEndpoint* ch = static_cast<CallEndpoint*>(msg.userData());
Debug(DebugInfo,"We are routing to device '%s'",dest.matchString(1).c_str());
if (ch && chan->connect(ch,msg.getValue("reason"))) {
chan->setTarget(msg.getValue("id"));
msg.setParam("peerid",dest);
msg.setParam("targetid",dest);
chan->answer();
chan->deref();
}
else {
const char *direct = msg.getValue("direct");
if (direct) {
Message m("call.execute");
m.addParam("module","audiocore");
m.addParam("cdrtrack",String::boolText(false));
m.addParam("id",dest);
m.addParam("caller",dest);
m.addParam("callto",direct);
m.userData(chan);
if (Engine::dispatch(m)) {
chan->setTarget(m.getValue("targetid"));
msg.addParam("targetid",chan->getTarget());
chan->deref();
return true;
}
Debug(DebugInfo,"CoreAudio outgoing call not accepted!");
chan->destruct();
return false;
}
const char *targ = msg.getValue("target");
if (!targ) {
Debug(DebugWarn,"CoreAudio outgoing call with no target!");
chan->destruct();
return false;
}
Message m("call.route");
m.addParam("module","audiocore");
m.addParam("cdrtrack",String::boolText(false));
m.addParam("id",dest);
m.addParam("caller",dest);
m.addParam("called",targ);
if (Engine::dispatch(m)) {
m = "call.execute";
m.addParam("callto",m.retValue());
m.retValue() = 0;
m.userData(chan);
if (Engine::dispatch(m)) {
chan->setTarget(m.getValue("targetid"));
msg.addParam("targetid",chan->getTarget());
chan->deref();
return true;
}
Debug(DebugInfo,"CoreAudio outgoing call not accepted!");
}
else
Debug(DebugWarn,"CoreAudio outgoing call but no route!");
chan->destruct();
return false;
}
return true;
}
bool StatusHandler::received(Message &msg)
{
const String* sel = msg.getParam("module");
if (sel && (*sel != "coreaudio"))
return false;
msg.retValue() << "name=coreaudio,type=misc;chan=" << (s_audioChan != 0 ) << "\r\n";
return false;
}
bool MasqHandler::received(Message &msg)
{
String id(msg.getValue("id"));
if (msg.getParam("message") && id.startsWith("coreaudio/")) {
msg = msg.getValue("message");
msg.clearParam("message");
if (s_audioChan) {
msg.addParam("targetid",s_audioChan->getTarget());
msg.userData(s_audioChan);
}
}
return false;
}
bool DropHandler::received(Message &msg)
{
String id(msg.getValue("id"));
if (id.null() || id.startsWith("coreaudio/")) {
if (s_audioChan) {
Debug("CoreAudio",DebugInfo,"ping call");
s_audioChan->disconnect();
}
return !id.null();
}
return false;
}
bool AttachHandler::received(Message& msg)
{
int more = 2;
String src(msg.getValue("source"));
if (src.null())
more--;
else {
if (!src.startSkip("coreaudio/",false))
src = "";
}
String cons(msg.getValue("consumer"));
if (cons.null())
more--;
else {
if (!cons.startSkip("coreaudio/",false))
cons = "";
}
if (src.null() && cons.null())
return false;
if (src && cons && (src != cons)) {
Debug(DebugWarn,"CoreAudio asked to attach source '%s' and consumer '%s'",src.c_str(),cons.c_str());
return false;
}
RefPointer<DataEndpoint> dd = static_cast<DataEndpoint*>(msg.userObject("DataEndpoint"));
if (!dd) {
CallEndpoint *ch = static_cast<CallEndpoint*>(msg.userObject("CallEndpoint"));
if (ch) {
DataEndpoint::commonMutex().lock();
dd = ch->setEndpoint();
DataEndpoint::commonMutex().unlock();
}
}
if (!dd) {
Debug(DebugWarn,"CoreAudio attach request with no control or data channel!");
return false;
}
if (src) {
CoreAudioSource* s = new CoreAudioSource();
if (s->init(src))
dd->setSource(s);
s->deref();
}
if (cons) {
CoreAudioConsumer* c = new CoreAudioConsumer();
if (c->init(cons))
dd->setConsumer(c);
c->deref();
}
// Stop dispatching if we handled all requested
return !more;
}
CoreAudioPlugin::CoreAudioPlugin()
: Plugin("coreaudio"),
m_handler(0)
{
Output("Loaded module CoreAudio");
}
void CoreAudioPlugin::initialize()
{
Output("Initializing module CoreAudio");
if (!m_handler) {
m_handler = new CoreAudioHandler("call.execute");
Engine::install(m_handler);
Engine::install(new MasqHandler(10));
Engine::install(new DropHandler());
Engine::install(new StatusHandler());
Engine::install(new AttachHandler());
}
}
bool CoreAudioPlugin::isBusy() const
{
return (s_audioChan != 0);
}
}; // anonymous namespace