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:
parent
aa528b1523
commit
1f51c74a0a
|
@ -27,6 +27,8 @@
|
||||||
|
|
||||||
#ifdef _WINDOWS
|
#ifdef _WINDOWS
|
||||||
#define DEFAULT_DEVICE "dsound/*"
|
#define DEFAULT_DEVICE "dsound/*"
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
#define DEFAULT_DEVICE "coreaudio/*"
|
||||||
#elif defined(__linux__)
|
#elif defined(__linux__)
|
||||||
#define DEFAULT_DEVICE "alsa/default"
|
#define DEFAULT_DEVICE "alsa/default"
|
||||||
#else
|
#else
|
||||||
|
|
13
configure.in
13
configure.in
|
@ -595,6 +595,19 @@ HAVE_ALSA="yes"
|
||||||
AC_MSG_RESULT([$HAVE_ALSA])
|
AC_MSG_RESULT([$HAVE_ALSA])
|
||||||
AC_SUBST(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
|
HAVE_GSM=no
|
||||||
GSM_INC=""
|
GSM_INC=""
|
||||||
|
|
|
@ -77,6 +77,10 @@ ifneq (@HAVE_ALSA@,no)
|
||||||
PROGS := $(PROGS) client/alsachan.yate
|
PROGS := $(PROGS) client/alsachan.yate
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
ifneq (@HAVE_COREAUDIO@,no)
|
||||||
|
PROGS := $(PROGS) client/coreaudio.yate
|
||||||
|
endif
|
||||||
|
|
||||||
ifneq (@HAVE_QT4@,no)
|
ifneq (@HAVE_QT4@,no)
|
||||||
PROGS := $(PROGS) qt4/updater.yate
|
PROGS := $(PROGS) qt4/updater.yate
|
||||||
endif
|
endif
|
||||||
|
@ -254,6 +258,8 @@ enumroute.yate: LOCALLIBS = @RESOLV_LIB@
|
||||||
|
|
||||||
client/alsachan.yate: LOCALLIBS = -lasound
|
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: ../libs/yiax/libyateiax.a
|
||||||
yiaxchan.yate: LOCALFLAGS = -I@top_srcdir@/libs/yiax
|
yiaxchan.yate: LOCALFLAGS = -I@top_srcdir@/libs/yiax
|
||||||
yiaxchan.yate: LOCALLIBS = -L../libs/yiax -lyateiax
|
yiaxchan.yate: LOCALLIBS = -L../libs/yiax -lyateiax
|
||||||
|
|
|
@ -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,¶m,sizeof(UInt32));
|
||||||
|
if (err == noErr) {
|
||||||
|
// disable output on the AUHAL
|
||||||
|
param = 0;
|
||||||
|
err = AudioUnitSetProperty(fAudioUnit,kAudioOutputUnitProperty_EnableIO,kAudioUnitScope_Input,0,¶m,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,¶m,&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,¶m);
|
||||||
|
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,¶m);
|
||||||
|
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,¶m,&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
|
Loading…
Reference in New Issue