📄 maccoreaudio.cxx
字号:
/*
* maccoreaudio.cxx
*
* Copyright (c) 2004 Network for Educational Technology ETH
*
* Written by Hannes Friederich, Andreas Fenkart.
* Based on work of Shawn Pai-Hsiang Hsiao
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*
*/
#pragma implementation "maccoreaudio.h"
#include <ptlib/unix/ptlib/maccoreaudio.h>
#include <iostream> // used for Volume Listener
PCREATE_SOUND_PLUGIN(CoreAudio, PSoundChannelCoreAudio);
namespace PWLibStupidOSXHacks
{
int loadCoreAudioStuff;
}
/************** util *******************/
#ifdef PTRACING
// should also produce output when ptracing is disabled
#define checkStatus( err ) \
if(err) {\
OSStatus error = static_cast<OSStatus>(err);\
PTRACE(1, "CoreAudio Error " << __func__ << " " \
<< error << "(" << (char*)&err << ")" ); \
}
ostream& operator<<(ostream &os, AudioStreamBasicDescription &inDesc)
{
os << "- - - - - - - - - - - - - - - - - - - -\n";
os << " Sample Rate: " << inDesc.mSampleRate << endl;
os << " Format ID: " << (char*)&inDesc.mFormatID << endl;
os << " Format Flags: " << hex << inDesc.mFormatFlags << dec << endl;
os << " Bytes per Packet: " << inDesc.mBytesPerPacket << endl;
os << " Frames per Packet: " << inDesc.mFramesPerPacket << endl;
os << " Bytes per Frame: " << inDesc.mBytesPerFrame << endl;
os << " Channels per Frame: " << inDesc.mChannelsPerFrame << endl;
os << " Bits per Channel: " << inDesc.mBitsPerChannel << endl;
os << "- - - - - - - - - - - - - - - - - - - -\n";
return os;
}
ostream& operator<<(ostream &os, PSoundChannel::Directions &dir)
{
if(dir == PSoundChannel::Player)
os << " Player ";
else if(dir == PSoundChannel::Recorder)
os << " Recorder ";
else
os << " Unknown direction ";
return os;
}
ostream& operator<<(ostream &os, AudioValueRange range)
{
os << range.mMinimum << " " << range.mMaximum ;
return os;
}
ostream& operator<<(ostream &os, PSoundChannelCoreAudio::State &state)
{
switch(state){
case PSoundChannelCoreAudio::init_:
os << "init";
break;
case PSoundChannelCoreAudio::open_:
os << "open";
break;
case PSoundChannelCoreAudio::setformat_:
os << "setformat";
break;
case PSoundChannelCoreAudio::setbuffer_:
os << "setbuffer";
break;
case PSoundChannelCoreAudio::running_:
os << "running";
break;
case PSoundChannelCoreAudio::destroy_:
os << "destroy";
break;
default:
os << " Unknown state ";
}
return os;
}
#else
#define checkStatus( err ) \
if(err) {\
OSStatus error = static_cast<OSStatus>(err);\
cout << "CoreAudio Error " << __func__ << " " \
<< error << "(" << (char*)&err << ")" << endl; \
}
#endif
/***** PSound implementation *****/
PSound::PSound(unsigned channels,
unsigned sampleRate,
unsigned bitsPerSample,
PINDEX bufferSize,
const BYTE *data)
{
PAssert(0, PUnimplementedFunction);
}
PSound::PSound(const PFilePath & filename)
{
PAssert(0, PUnimplementedFunction);
}
PSound & PSound::operator=(const PBYTEArray & data)
{
PAssert(0, PUnimplementedFunction);
return *this;
}
BOOL PSound::Load(const PFilePath & filename)
{
PAssert(0, PUnimplementedFunction);
return false;
}
BOOL PSound::Save(const PFilePath & filename)
{
PAssert(0, PUnimplementedFunction);
return false;
}
BOOL PSound::Play()
{
PAssert(0, PUnimplementedFunction);
return false;
}
void PSound::SetFormat(unsigned numChannels,
unsigned sampleRate,
unsigned bitsPerSample)
{
this->numChannels = numChannels;
this->sampleRate = sampleRate;
this->sampleSize = bitsPerSample;
formatInfo.SetSize(0);
}
BOOL PSound::PlayFile(const PFilePath & file,
BOOL wait)
{
PAssert(0, PUnimplementedFunction);
return false;
}
void PSound::Beep()
{
PAssert(0, PUnimplementedFunction);
}
#include "maccoreaudio/circular_buffer.inl"
/***** PSoundChannel implementation *****/
PSoundChannelCoreAudio::PSoundChannelCoreAudio()
: state(init_), mCircularBuffer(NULL), converter_buffer(NULL),
mInputCircularBuffer(NULL), mInputBufferList(NULL), mOutputBufferList(NULL)
{
CommonConstruct();
}
PSoundChannelCoreAudio::PSoundChannelCoreAudio(const PString & device,
Directions dir,
unsigned numChannels,
unsigned sampleRate,
unsigned bitsPerSample)
: state(init_), mCircularBuffer(NULL), converter_buffer(NULL),
mInputCircularBuffer(NULL), mInputBufferList(NULL), mOutputBufferList(NULL)
{
CommonConstruct();
Open(device, dir, numChannels, sampleRate, bitsPerSample);
}
PSoundChannelCoreAudio::~PSoundChannelCoreAudio()
{
OSStatus err = noErr;
State curr(state);
state = destroy_;
usleep(1000*20); // about the intervall between two callbacks
// ensures that current callback has terminated
/* OutputUnit also for input device,
* Stop everything before deallocating buffers */
switch(curr) {
case running_:
err = AudioOutputUnitStop(mAudioUnit);
checkStatus(err);
PTRACE(1, direction << " AudioUnit stopped" );
usleep(1000*20); // ensure that all callbacks terminated
/* fall through */
case mute_:
case setbuffer_:
/* check for all buffers unconditionally */
err = AudioUnitUninitialize(mAudioUnit);
checkStatus(err);
/* fall through */
case setformat_:
err = AudioConverterDispose(converter);
checkStatus(err);
/* fall through */
case open_:
err = CloseComponent(mAudioUnit);
checkStatus(err);
err = AudioDeviceRemovePropertyListener(mDeviceID, 1,
kAudioPropertyWildcardSection, kAudioDevicePropertyVolumeScalar,
VolumeChangePropertyListener);
checkStatus(err);
/* fall through */
case init_:
case destroy_:
/* nop */;
}
/* now free all buffers */
if(this->converter_buffer != NULL){
free(this->converter_buffer);
this->converter_buffer = NULL;
}
if(this->mCircularBuffer != NULL){
delete this->mCircularBuffer;
this->mCircularBuffer = NULL;
}
if(this->mInputCircularBuffer !=NULL) {
delete this->mInputCircularBuffer;
this->mInputCircularBuffer = NULL;
}
if(this->mInputBufferList != NULL){
free(this->mInputBufferList);
this->mInputBufferList = NULL;
}
if(this->mOutputBufferList != NULL){
free(this->mOutputBufferList);
this->mOutputBufferList = NULL;
}
// tell PChannel::IsOpen() that the channel is closed.
os_handle = -1;
}
unsigned PSoundChannelCoreAudio::GetChannels() const
{
if(state >= setformat_)
return pwlibASBD.mChannelsPerFrame;
else
return 0;
}
unsigned PSoundChannelCoreAudio::GetSampleRate() const
{
if(state >= setformat_)
return (unsigned)(pwlibASBD.mSampleRate);
else
return 0;
}
unsigned PSoundChannelCoreAudio::GetSampleSize() const
{
if(state >= setformat_)
return (pwlibASBD.mBitsPerChannel);
else
return 0;
}
/*
* Functions for retrieving AudioDevice list
*/
#include "maccoreaudio/maccoreaudio_devices.inl"
PString PSoundChannelCoreAudio::GetDefaultDevice(Directions dir)
{
OSStatus err = noErr;
UInt32 theSize;
AudioDeviceID theID;
theSize = sizeof(AudioDeviceID);
if (dir == Player) {
err = AudioHardwareGetProperty(
kAudioHardwarePropertyDefaultOutputDevice,
&theSize, &theID);
}
else {
err = AudioHardwareGetProperty(
kAudioHardwarePropertyDefaultInputDevice,
&theSize, &theID);
}
if (err == 0) {
return CADeviceName(theID);
} else {
return CA_DUMMY_DEVICE_NAME;
}
}
PStringList PSoundChannelCoreAudio::GetDeviceNames(Directions dir)
{
PStringList devices;
int numDevices;
AudioDeviceID *deviceList;
bool isInput = (dir == Recorder);
numDevices = CADeviceList(&deviceList);
for (int i = 0; i < numDevices; i++) {
PString s = CADeviceName(deviceList[i]);
if (CADeviceSupportDirection(deviceList[i], isInput) > 0) {
devices.AppendString(s);
}
}
devices.AppendString(CA_DUMMY_DEVICE_NAME);
if(deviceList != NULL) {
free(deviceList);
deviceList = NULL;
}
return devices;
}
/*
* Functions responsible for converting 8kHz to 44k1Hz. It works like this.
* The AudioHardware is abstracted by an AudioUnit(AUHAL). Each time the device
* has more data available or needs new data a callback function is called.
* These are PlayRenderProc and RecordProc.
*
* The user data is stored in a format a set by ::SetFormat. Usually 8kHz, mono,
* 16bit unsigned. The AudioUnit usually provides 44,1kHz, stereo, 32bit float.
* So conversion is needed. The conversion is done by the AUConverter from
* the AudioToolbox framework.
*
* Currently inside the callback functions from the AUHAL, we pass the request
* to the Converter, that in turn uses another callback function to grab some
* of data in the input format. All this is done on-the-fly, which means inside
* the thread managing the AudioHardware. The callback functions of the
* converter are ComplexBufferFillPlayback, ComplexbufferFillRecord.
*
* The problem we have that 44,1kHz is not a multiple of 8kHz, so we can never
* be sure about how many data the converter is going to ask exactly,
* sometimes it might be 102, 106.. This is especially true in case of the
* first request, where it might ask some additional data depending on
* PrimeMethod that garantuees smoth resampling at the border.
*
* To summarize, when the AudioUnit device is ready to handle more data. It
* calls its callback function, within these functions the data is processed or
* prepared by pulling through AUConverter. The converter in turn calls its
* callback function to request more input data. Depending on whether we talk
* about Read or Write, this includes more or less complex buffering.
*/
/*
* Callback function called by the converter to request more data for
* playback.
*
* outDataPacketDesc is unused, because all our packets have the same
* format and do not need individual description
*/
OSStatus PSoundChannelCoreAudio::ComplexBufferFillPlayback(
AudioConverterRef inAudioConverter,
UInt32 *ioNumberDataPackets,
AudioBufferList *ioData,
AudioStreamPacketDescription **outDataPacketDesc,
void *inUserData)
{
OSStatus err = noErr;
PSoundChannelCoreAudio *This =
static_cast<PSoundChannelCoreAudio*>(inUserData);
AudioStreamBasicDescription pwlibASBD = This->pwlibASBD;
CircularBuffer* circBuf = This->mCircularBuffer;
// output might stop in case there is a complete buffer underrun!!!
UInt32 minPackets = MIN(*ioNumberDataPackets,
circBuf->size() / pwlibASBD.mBytesPerPacket );
UInt32 outBytes = minPackets* pwlibASBD.mBytesPerPacket;
//PTRACE(2, __func__ << " requested " << *ioNumberDataPackets <<
// " packets, " << " fetching " << minPackets << " packets");
if(outBytes > This->converter_buffer_size){
PTRACE(1, This->direction << " Converter buffer too small");
// doesn't matter converter will ask right again for remaining data
// converter buffer multiple of packet size
outBytes = This->converter_buffer_size;
}
// dequeue data from circular buffer, without locking(false)
outBytes = circBuf->Drain(This->converter_buffer, outBytes, false);
UInt32 reqBytes = *ioNumberDataPackets * pwlibASBD.mBytesPerPacket;
if(outBytes < reqBytes && outBytes < This->converter_buffer_size) {
reqBytes = MIN(reqBytes, This->converter_buffer_size);
PTRACE(1, "Buffer underrun, filling up with silence "
<< (reqBytes - outBytes) << " bytes ");
bzero(This->converter_buffer + outBytes, reqBytes - outBytes );
outBytes = reqBytes;
}
// fill structure that gets returned to converter
ioData->mBuffers[0].mData = (char*)This->converter_buffer;
ioData->mBuffers[0].mDataByteSize = outBytes;
*ioNumberDataPackets = outBytes / pwlibASBD.mBytesPerPacket;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -