/*
* Copyright (c) 2002, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
//#define USE_ERROR
//#define USE_TRACE
//#define USE_VERBOSE_TRACE
#include <AudioUnit/AudioUnit.h>
#include <AudioToolbox/AudioConverter.h>
#include <pthread.h>
#include <math.h>
/*
#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)
#include <CoreAudio/CoreAudioTypes.h>
#else
#include <CoreAudioTypes.h>
#endif
*/
#include "PLATFORM_API_MacOSX_Utils.h"
extern "C" {
#include "Utilities.h"
#include "DirectAudio.h"
}
#if USE_DAUDIO == TRUE
#ifdef USE_TRACE
static void PrintStreamDesc(const AudioStreamBasicDescription *inDesc) {
TRACE4("ID='%c%c%c%c'", (char)(inDesc->mFormatID >> 24), (char)(inDesc->mFormatID >> 16), (char)(inDesc->mFormatID >> 8), (char)(inDesc->mFormatID));
TRACE2(", %f Hz, flags=0x%lX", (float)inDesc->mSampleRate, (long unsigned)inDesc->mFormatFlags);
TRACE2(", %ld channels, %ld bits", (long)inDesc->mChannelsPerFrame, (long)inDesc->mBitsPerChannel);
TRACE1(", %ld bytes per frame\n", (long)inDesc->mBytesPerFrame);
}
#else
static inline void PrintStreamDesc(const AudioStreamBasicDescription *inDesc) { }
#endif
#define MAX(x, y) ((x) >= (y) ? (x) : (y))
#define MIN(x, y) ((x) <= (y) ? (x) : (y))
// =======================================
// MixerProvider functions implementation
static DeviceList deviceCache;
INT32 DAUDIO_GetDirectAudioDeviceCount() {
deviceCache.Refresh();
int count = deviceCache.GetCount();
if (count > 0) {
// add "default" device
count++;
TRACE1("DAUDIO_GetDirectAudioDeviceCount: returns %d devices\n", count);
} else {
TRACE0("DAUDIO_GetDirectAudioDeviceCount: no devices found\n");
}
return count;
}
INT32 DAUDIO_GetDirectAudioDeviceDescription(INT32 mixerIndex, DirectAudioDeviceDescription *desc) {
bool result = true;
desc->deviceID = 0;
if (mixerIndex == 0) {
// default device
strncpy(desc->name, "Default Audio Device", DAUDIO_STRING_LENGTH);
strncpy(desc->description, "Default Audio Device", DAUDIO_STRING_LENGTH);
desc->maxSimulLines = -1;
} else {
AudioDeviceID deviceID;
result = deviceCache.GetDeviceInfo(mixerIndex-1, &deviceID, DAUDIO_STRING_LENGTH,
desc->name, desc->vendor, desc->description, desc->version);
if (result) {
desc->deviceID = (INT32)deviceID;
desc->maxSimulLines = -1;
}
}
return result ? TRUE : FALSE;
}
void DAUDIO_GetFormats(INT32 mixerIndex, INT32 deviceID, int isSource, void* creator) {
TRACE3(">>DAUDIO_GetFormats mixerIndex=%d deviceID=0x%x isSource=%d\n", (int)mixerIndex, (int)deviceID, isSource);
AudioDeviceID audioDeviceID = deviceID == 0 ? GetDefaultDevice(isSource) : (AudioDeviceID)deviceID;
if (audioDeviceID == 0) {
return;
}
int totalChannels = GetChannelCount(audioDeviceID, isSource);
if (totalChannels == 0) {
TRACE0("<<DAUDIO_GetFormats, no streams!\n");
return;
}
if (isSource && totalChannels < 2) {
// report 2 channels even if only mono is supported
totalChannels = 2;
}
int channels[] = {1, 2, totalChannels};
int channelsCount = MIN(totalChannels, 3);
float hardwareSampleRate = GetSampleRate(audioDeviceID, isSource);
TRACE2(" DAUDIO_GetFormats: got %d channels, sampleRate == %f\n", totalChannels, hardwareSampleRate);
// any sample rates are supported
float sampleRate = -1;
static int sampleBits[] = {8, 16, 24};
static int sampleBitsCount = sizeof(sampleBits)/sizeof(sampleBits[0]);
// the last audio format is the default one (used by DataLine.open() if format is not specified)
// consider as default 16bit PCM stereo (mono is stereo is not supported) with the current sample rate
int defBits = 16;
int defChannels = MIN(2, channelsCount);
float defSampleRate = hardwareSampleRate;
// don't add default format is sample rate is not specified
bool addDefault = defSampleRate > 0;
// TODO: CoreAudio can handle signed/unsigned, little-endian/big-endian
// TODO: register the formats (to prevent DirectAudio software conversion) - need to fix DirectAudioDevice.createDataLineInfo
// to avoid software conversions if both signed/unsigned or big-/little-endian are supported
for (int channelIndex = 0; channelIndex < channelsCount; channelIndex++) {
for (int bitIndex = 0; bitIndex < sampleBitsCount; bitIndex++) {
int bits = sampleBits[bitIndex];
if (addDefault && bits == defBits && channels[channelIndex] != defChannels && sampleRate == defSampleRate) {
// the format is the default one, don't add it now
continue;
}
DAUDIO_AddAudioFormat(creator,
bits, // sample size in bits
-1, // frame size (auto)
channels[channelIndex], // channels
sampleRate, // sample rate
DAUDIO_PCM, // only accept PCM
bits == 8 ? FALSE : TRUE, // signed
bits == 8 ? FALSE // little-endian for 8bit
: UTIL_IsBigEndianPlatform());
}
}
// add default format
if (addDefault) {
DAUDIO_AddAudioFormat(creator,
defBits, // 16 bits
-1, // automatically calculate frame size
defChannels, // channels
defSampleRate, // sample rate
DAUDIO_PCM, // PCM
TRUE, // signed
UTIL_IsBigEndianPlatform()); // native endianess
}
TRACE0("<<DAUDIO_GetFormats\n");
}
// =======================================
// Source/Target DataLine functions implementation
// ====
/* 1writer-1reader ring buffer class with flush() support */
class RingBuffer {
public:
RingBuffer() : pBuffer(NULL), nBufferSize(0) {
pthread_mutex_init(&lockMutex, NULL);
}
~RingBuffer() {
Deallocate();
pthread_mutex_destroy(&lockMutex);
}
// extraBytes: number of additionally allocated bytes to prevent data
// overlapping when almost whole buffer is filled
// (required only if Write() can override the buffer)
bool Allocate(int requestedBufferSize, int extraBytes) {
int fullBufferSize = requestedBufferSize + extraBytes;
int powerOfTwo = 1;
while (powerOfTwo < fullBufferSize) {
powerOfTwo <<= 1;
}
pBuffer = (Byte*)malloc(powerOfTwo);
if (pBuffer == NULL) {
ERROR0("RingBuffer::Allocate: OUT OF MEMORY\n");
return false;
}
nBufferSize = requestedBufferSize;
nAllocatedBytes = powerOfTwo;
nPosMask = powerOfTwo - 1;
nWritePos = 0;
nReadPos = 0;
nFlushPos = -1;
TRACE2("RingBuffer::Allocate: OK, bufferSize=%d, allocated:%d\n", nBufferSize, nAllocatedBytes);
return true;
}
void Deallocate() {
if (pBuffer) {
free(pBuffer);
pBuffer = NULL;
nBufferSize = 0;
}
}
inline int GetBufferSize() {
return nBufferSize;
}
inline int GetAllocatedSize() {
return nAllocatedBytes;
}
// gets number of bytes available for reading
int GetValidByteCount() {
lock();
INT64 result = nWritePos - (nFlushPos >= 0 ? nFlushPos : nReadPos);
unlock();
return result > (INT64)nBufferSize ? nBufferSize : (int)result;
}
int Write(void *srcBuffer, int len, bool preventOverflow) {
lock();
TRACE2("RingBuffer::Write (%d bytes, preventOverflow=%d)\n", len, preventOverflow ? 1 : 0);
TRACE2(" writePos = %lld (%d)", (long long)nWritePos, Pos2Offset(nWritePos));
TRACE2(" readPos=%lld (%d)", (long long)nReadPos, Pos2Offset(nReadPos));
TRACE2(" flushPos=%lld (%d)\n", (long long)nFlushPos, Pos2Offset(nFlushPos));
INT64 writePos = nWritePos;
if (preventOverflow) {
INT64 avail_read = writePos - (nFlushPos >= 0 ? nFlushPos : nReadPos);
if (avail_read >= (INT64)nBufferSize) {
// no space
TRACE0(" preventOverlow: OVERFLOW => len = 0;\n");
len = 0;
} else {
int avail_write = nBufferSize - (int)avail_read;
if (len > avail_write) {
TRACE2(" preventOverlow: desrease len: %d => %d\n", len, avail_write);
len = avail_write;
}
}
}
unlock();
if (len > 0) {
write((Byte *)srcBuffer, Pos2Offset(writePos), len);
lock();
TRACE4("--RingBuffer::Write writePos: %lld (%d) => %lld, (%d)\n",
(long long)nWritePos, Pos2Offset(nWritePos), (long long)nWritePos + len, Pos2Offset(nWritePos + len));
nWritePos += len;
unlock();
}
return len;
}
int Read(void *dstBuffer, int len) {
lock();
TRACE1("RingBuffer::Read (%d bytes)\n", len);
TRACE2(" writePos = %lld (%d)", (long long)nWritePos, Pos2Offset(nWritePos));
TRACE2(" readPos=%lld (%d)", (long long)nReadPos, Pos2Offset(nReadPos));
TRACE2(" flushPos=%lld (%d)\n", (long long)nFlushPos, Pos2Offset(nFlushPos));
applyFlush();
INT64 avail_read = nWritePos - nReadPos;
// check for overflow
if (avail_read > (INT64)nBufferSize) {
nReadPos = nWritePos - nBufferSize;
avail_read = nBufferSize;
TRACE0(" OVERFLOW\n");
}
INT64 readPos = nReadPos;
unlock();
if (len > (int)avail_read) {
TRACE2(" RingBuffer::Read - don't have enough data, len: %d => %d\n", len, (int)avail_read);
len = (int)avail_read;
}
if (len > 0) {
read((Byte *)dstBuffer, Pos2Offset(readPos), len);
lock();
if (applyFlush()) {
// just got flush(), results became obsolete
TRACE0("--RingBuffer::Read, got Flush, return 0\n");
len = 0;
} else {
TRACE4("--RingBuffer::Read readPos: %lld (%d) => %lld (%d)\n",
(long long)nReadPos, Pos2Offset(nReadPos), (long long)nReadPos + len, Pos2Offset(nReadPos + len));
nReadPos += len;
}
unlock();
} else {
// underrun!
}
return len;
}
// returns number of the flushed bytes
int Flush() {
lock();
INT64 flushedBytes = nWritePos - (nFlushPos >= 0 ? nFlushPos : nReadPos);
nFlushPos = nWritePos;
unlock();
return flushedBytes > (INT64)nBufferSize ? nBufferSize : (int)flushedBytes;
}
private:
Byte *pBuffer;
int nBufferSize;
int nAllocatedBytes;
INT64 nPosMask;
pthread_mutex_t lockMutex;
volatile INT64 nWritePos;
volatile INT64 nReadPos;
// Flush() sets nFlushPos value to nWritePos;
// next Read() sets nReadPos to nFlushPos and resests nFlushPos to -1
volatile INT64 nFlushPos;
inline void lock() {
pthread_mutex_lock(&lockMutex);
}
inline void unlock() {
pthread_mutex_unlock(&lockMutex);
}
inline bool applyFlush() {
if (nFlushPos >= 0) {
nReadPos = nFlushPos;
nFlushPos = -1;
return true;
}
return false;
}
inline int Pos2Offset(INT64 pos) {
return (int)(pos & nPosMask);
}
void write(Byte *srcBuffer, int dstOffset, int len) {
int dstEndOffset = dstOffset + len;
int lenAfterWrap = dstEndOffset - nAllocatedBytes;
if (lenAfterWrap > 0) {
// dest.buffer does wrap
len = nAllocatedBytes - dstOffset;
memcpy(pBuffer+dstOffset, srcBuffer, len);
memcpy(pBuffer, srcBuffer+len, lenAfterWrap);
} else {
// dest.buffer does not wrap
memcpy(pBuffer+dstOffset, srcBuffer, len);
}
}
void read(Byte *dstBuffer, int srcOffset, int len) {
int srcEndOffset = srcOffset + len;
int lenAfterWrap = srcEndOffset - nAllocatedBytes;
if (lenAfterWrap > 0) {
// need to unwrap data
len = nAllocatedBytes - srcOffset;
memcpy(dstBuffer, pBuffer+srcOffset, len);
memcpy(dstBuffer+len, pBuffer, lenAfterWrap);
} else {
// source buffer is not wrapped
memcpy(dstBuffer, pBuffer+srcOffset, len);
}
}
};
class Resampler {
private:
enum {
kResamplerEndOfInputData = 1 // error to interrupt conversion (end of input data)
};
public:
Resampler() : converter(NULL), outBuffer(NULL) { }
~Resampler() {
if (converter != NULL) {
AudioConverterDispose(converter);
}
if (outBuffer != NULL) {
free(outBuffer);
}
}
// inFormat & outFormat must be interleaved!
bool Init(const AudioStreamBasicDescription *inFormat, const AudioStreamBasicDescription *outFormat,
int inputBufferSizeInBytes)
{
TRACE0(">>Resampler::Init\n");
TRACE0(" inFormat: ");
PrintStreamDesc(inFormat);
TRACE0(" outFormat: ");
PrintStreamDesc(outFormat);
TRACE1(" inputBufferSize: %d bytes\n", inputBufferSizeInBytes);
OSStatus err;
if ((outFormat->mFormatFlags & kAudioFormatFlagIsNonInterleaved) != 0 && outFormat->mChannelsPerFrame != 1) {
ERROR0("Resampler::Init ERROR: outFormat is non-interleaved\n");
return false;
}
if ((inFormat->mFormatFlags & kAudioFormatFlagIsNonInterleaved) != 0 && inFormat->mChannelsPerFrame != 1) {
ERROR0("Resampler::Init ERROR: inFormat is non-interleaved\n");
return false;
}
memcpy(&asbdIn, inFormat, sizeof(AudioStreamBasicDescription));
memcpy(&asbdOut, outFormat, sizeof(AudioStreamBasicDescription));
err = AudioConverterNew(inFormat, outFormat, &converter);
if (err || converter == NULL) {
OS_ERROR1(err, "Resampler::Init (AudioConverterNew), converter=%p", converter);
return false;
}
// allocate buffer for output data
int maximumInFrames = inputBufferSizeInBytes / inFormat->mBytesPerFrame;
// take into account trailingFrames
AudioConverterPrimeInfo primeInfo = {0, 0};
UInt32 sizePrime = sizeof(primeInfo);
err = AudioConverterGetProperty(converter, kAudioConverterPrimeInfo, &sizePrime, &primeInfo);
if (err) {
OS_ERROR0(err, "Resampler::Init (get kAudioConverterPrimeInfo)");
// ignore the error
} else {
// the default primeMethod is kConverterPrimeMethod_Normal, so we need only trailingFrames
maximumInFrames += primeInfo.trailingFrames;
}
float outBufferSizeInFrames = (outFormat->mSampleRate / inFormat->mSampleRate) * ((float)maximumInFrames);
// to avoid complex calculation just set outBufferSize as double of the calculated value
outBufferSize = (int)outBufferSizeInFrames * outFormat->mBytesPerFrame * 2;
// safety check - consider 256 frame as the minimum input buffer
int minOutSize = 256 * outFormat->mBytesPerFrame;
if (outBufferSize < minOutSize) {
outBufferSize = minOutSize;
}
outBuffer = malloc(outBufferSize);
if (outBuffer == NULL) {
ERROR1("Resampler::Init ERROR: malloc failed (%d bytes)\n", outBufferSize);
AudioConverterDispose(converter);
converter = NULL;
return false;
}
TRACE1(" allocated: %d bytes for output buffer\n", outBufferSize);
TRACE0("<<Resampler::Init: OK\n");
return true;
}
// returns size of the internal output buffer
int GetOutBufferSize() {
return outBufferSize;
}
// process next part of data (writes resampled data to the ringBuffer without overflow check)
int Process(void *srcBuffer, int len, RingBuffer *ringBuffer) {
int bytesWritten = 0;
TRACE2(">>Resampler::Process: %d bytes, converter = %p\n", len, converter);
if (converter == NULL) { // sanity check
bytesWritten = ringBuffer->Write(srcBuffer, len, false);
} else {
InputProcData data;
data.pThis = this;
data.data = (Byte *)srcBuffer;
data.dataSize = len;
OSStatus err;
do {
AudioBufferList abl; // by default it contains 1 AudioBuffer
abl.mNumberBuffers = 1;
abl.mBuffers[0].mNumberChannels = asbdOut.mChannelsPerFrame;
abl.mBuffers[0].mDataByteSize = outBufferSize;
abl.mBuffers[0].mData = outBuffer;
UInt32 packets = (UInt32)outBufferSize / asbdOut.mBytesPerPacket;
TRACE2(">>AudioConverterFillComplexBuffer: request %d packets, provide %d bytes buffer\n",
(int)packets, (int)abl.mBuffers[0].mDataByteSize);
err = AudioConverterFillComplexBuffer(converter, ConverterInputProc, &data, &packets, &abl, NULL);
TRACE2("<<AudioConverterFillComplexBuffer: got %d packets (%d bytes)\n",
(int)packets, (int)abl.mBuffers[0].mDataByteSize);
if (packets > 0) {
int bytesToWrite = (int)(packets * asbdOut.mBytesPerPacket);
bytesWritten += ringBuffer->Write(abl.mBuffers[0].mData, bytesToWrite, false);
}
// if outputBuffer is small to store all available frames,
// we get noErr here. In the case just continue the conversion
} while (err == noErr);
if (err != kResamplerEndOfInputData) {
// unexpected error
OS_ERROR0(err, "Resampler::Process (AudioConverterFillComplexBuffer)");
}
}
TRACE2("<<Resampler::Process: written %d bytes (converted from %d bytes)\n", bytesWritten, len);
return bytesWritten;
}
// resets internal bufferes
void Discontinue() {
TRACE0(">>Resampler::Discontinue\n");
if (converter != NULL) {
AudioConverterReset(converter);
}
TRACE0("<<Resampler::Discontinue\n");
}
private:
AudioConverterRef converter;
// buffer for output data
// note that there is no problem if the buffer is not big enough to store
// all converted data - it's only performance issue
void *outBuffer;
int outBufferSize;
AudioStreamBasicDescription asbdIn;
AudioStreamBasicDescription asbdOut;
struct InputProcData {
Resampler *pThis;
Byte *data; // data == NULL means we handle Discontinue(false)
int dataSize; // == 0 if all data was already provided to the converted of we handle Discontinue(false)
};
static OSStatus ConverterInputProc(AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets,
AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void *inUserData)
{
InputProcData *data = (InputProcData *)inUserData;
TRACE3(" >>ConverterInputProc: requested %d packets, data contains %d bytes (%d packets)\n",
(int)*ioNumberDataPackets, (int)data->dataSize, (int)(data->dataSize / data->pThis->asbdIn.mBytesPerPacket));
if (data->dataSize == 0) {
// already called & provided all input data
// interrupt conversion by returning error
*ioNumberDataPackets = 0;
TRACE0(" <<ConverterInputProc: returns kResamplerEndOfInputData\n");
return kResamplerEndOfInputData;
}
ioData->mNumberBuffers = 1;
ioData->mBuffers[0].mNumberChannels = data->pThis->asbdIn.mChannelsPerFrame;
ioData->mBuffers[0].mDataByteSize = data->dataSize;
ioData->mBuffers[0].mData = data->data;
*ioNumberDataPackets = data->dataSize / data->pThis->asbdIn.mBytesPerPacket;
// all data has been provided to the converter
data->dataSize = 0;
TRACE1(" <<ConverterInputProc: returns %d packets\n", (int)(*ioNumberDataPackets));
return noErr;
}
};
struct OSX_DirectAudioDevice {
AudioUnit audioUnit;
RingBuffer ringBuffer;
AudioStreamBasicDescription asbd;
// only for target lines
UInt32 inputBufferSizeInBytes;
Resampler *resampler;
// to detect discontinuity (to reset resampler)
SInt64 lastWrittenSampleTime;
OSX_DirectAudioDevice() : audioUnit(NULL), asbd(), resampler(NULL), lastWrittenSampleTime(0) {
}
~OSX_DirectAudioDevice() {
if (audioUnit) {
AudioComponentInstanceDispose(audioUnit);
}
if (resampler) {
delete resampler;
}
}
};
static AudioUnit CreateOutputUnit(AudioDeviceID deviceID, int isSource)
{
OSStatus err;
AudioUnit unit;
AudioComponentDescription desc;
desc.componentType = kAudioUnitType_Output;
desc.componentSubType = (deviceID == 0 && isSource) ? kAudioUnitSubType_DefaultOutput : kAudioUnitSubType_HALOutput;
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
AudioComponent comp = AudioComponentFindNext(NULL, &desc);
err = AudioComponentInstanceNew(comp, &unit);
if (err) {
OS_ERROR0(err, "CreateOutputUnit:OpenAComponent");
return NULL;
}
if (!isSource) {
int enableIO = 0;
err = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output,
0, &enableIO, sizeof(enableIO));
if (err) {
OS_ERROR0(err, "SetProperty (output EnableIO)");
}
enableIO = 1;
err = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input,
1, &enableIO, sizeof(enableIO));
if (err) {
OS_ERROR0(err, "SetProperty (input EnableIO)");
}
if (!deviceID) {
// get real AudioDeviceID for default input device (macosx current input device)
deviceID = GetDefaultDevice(isSource);
if (!deviceID) {
AudioComponentInstanceDispose(unit);
return NULL;
}
}
}
if (deviceID) {
err = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global,
0, &deviceID, sizeof(deviceID));
if (err) {
OS_ERROR0(err, "SetProperty (CurrentDevice)");
AudioComponentInstanceDispose(unit);
return NULL;
}
}
return unit;
}
static OSStatus OutputCallback(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData)
{
OSX_DirectAudioDevice *device = (OSX_DirectAudioDevice*)inRefCon;
int nchannels = ioData->mNumberBuffers; // should be always == 1 (interleaved channels)
AudioBuffer *audioBuffer = ioData->mBuffers;
TRACE3(">>OutputCallback: busNum=%d, requested %d frames (%d bytes)\n",
(int)inBusNumber, (int)inNumberFrames, (int)(inNumberFrames * device->asbd.mBytesPerFrame));
TRACE3(" abl: %d buffers, buffer[0].channels=%d, buffer.size=%d\n",
nchannels, (int)audioBuffer->mNumberChannels, (int)audioBuffer->mDataByteSize);
int bytesToRead = inNumberFrames * device->asbd.mBytesPerFrame;
if (bytesToRead > (int)audioBuffer->mDataByteSize) {
TRACE0("--OutputCallback: !!! audioBuffer IS TOO SMALL!!!\n");
bytesToRead = audioBuffer->mDataByteSize / device->asbd.mBytesPerFrame * device->asbd.mBytesPerFrame;
}
int bytesRead = device->ringBuffer.Read(audioBuffer->mData, bytesToRead);
if (bytesRead < bytesToRead) {
// no enough data (underrun)
TRACE2("--OutputCallback: !!! UNDERRUN (read %d bytes of %d)!!!\n", bytesRead, bytesToRead);
// silence the rest
memset((Byte*)audioBuffer->mData + bytesRead, 0, bytesToRead-bytesRead);
bytesRead = bytesToRead;
}
audioBuffer->mDataByteSize = (UInt32)bytesRead;
// SAFETY: set mDataByteSize for all other AudioBuffer in the AudioBufferList to zero
while (--nchannels > 0) {
audioBuffer++;
audioBuffer->mDataByteSize = 0;
}
TRACE1("<<OutputCallback (returns %d)\n", bytesRead);
return noErr;
}
static OSStatus InputCallback(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData)
{
OSX_DirectAudioDevice *device = (OSX_DirectAudioDevice*)inRefCon;
TRACE4(">>InputCallback: busNum=%d, timeStamp=%lld, %d frames (%d bytes)\n",
(int)inBusNumber, (long long)inTimeStamp->mSampleTime, (int)inNumberFrames, (int)(inNumberFrames * device->asbd.mBytesPerFrame));
AudioBufferList abl; // by default it contains 1 AudioBuffer
abl.mNumberBuffers = 1;
abl.mBuffers[0].mNumberChannels = device->asbd.mChannelsPerFrame;
abl.mBuffers[0].mDataByteSize = device->inputBufferSizeInBytes; // assume this is == (inNumberFrames * device->asbd.mBytesPerFrame)
abl.mBuffers[0].mData = NULL; // request for the audioUnit's buffer
OSStatus err = AudioUnitRender(device->audioUnit, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, &abl);
if (err) {
OS_ERROR0(err, "<<InputCallback: AudioUnitRender");
} else {
if (device->resampler != NULL) {
// test for discontinuity
// AUHAL starts timestamps at zero, so test if the current timestamp less then the last written
SInt64 sampleTime = inTimeStamp->mSampleTime;
if (sampleTime < device->lastWrittenSampleTime) {
// discontinuity, reset the resampler
TRACE2(" InputCallback (RESAMPLED), DISCONTINUITY (%f -> %f)\n",
(float)device->lastWrittenSampleTime, (float)sampleTime);
device->resampler->Discontinue();
} else {
TRACE2(" InputCallback (RESAMPLED), continuous: lastWrittenSampleTime = %f, sampleTime=%f\n",
(float)device->lastWrittenSampleTime, (float)sampleTime);
}
device->lastWrittenSampleTime = sampleTime + inNumberFrames;
int bytesWritten = device->resampler->Process(abl.mBuffers[0].mData, (int)abl.mBuffers[0].mDataByteSize, &device->ringBuffer);
TRACE2("<<InputCallback (RESAMPLED, saved %d bytes of %d)\n", bytesWritten, (int)abl.mBuffers[0].mDataByteSize);
} else {
int bytesWritten = device->ringBuffer.Write(abl.mBuffers[0].mData, (int)abl.mBuffers[0].mDataByteSize, false);
TRACE2("<<InputCallback (saved %d bytes of %d)\n", bytesWritten, (int)abl.mBuffers[0].mDataByteSize);
}
}
return noErr;
}
static void FillASBDForNonInterleavedPCM(AudioStreamBasicDescription& asbd,
float sampleRate, int channels, int sampleSizeInBits, bool isFloat, int isSigned, bool isBigEndian)
{
// FillOutASBDForLPCM cannot produce unsigned integer format
asbd.mSampleRate = sampleRate;
asbd.mFormatID = kAudioFormatLinearPCM;
asbd.mFormatFlags = (isFloat ? kAudioFormatFlagIsFloat : (isSigned ? kAudioFormatFlagIsSignedInteger : 0))
| (isBigEndian ? (kAudioFormatFlagIsBigEndian) : 0)
| kAudioFormatFlagIsPacked;
asbd.mBytesPerPacket = channels * ((sampleSizeInBits + 7) / 8);
asbd.mFramesPerPacket = 1;
asbd.mBytesPerFrame = asbd.mBytesPerPacket;
asbd.mChannelsPerFrame = channels;
asbd.mBitsPerChannel = sampleSizeInBits;
}
void* DAUDIO_Open(INT32 mixerIndex, INT32 deviceID, int isSource,
int encoding, float sampleRate, int sampleSizeInBits,
int frameSize, int channels,
int isSigned, int isBigEndian, int bufferSizeInBytes)
{
TRACE3(">>DAUDIO_Open: mixerIndex=%d deviceID=0x%x isSource=%d\n", (int)mixerIndex, (unsigned int)deviceID, isSource);
TRACE3(" sampleRate=%d sampleSizeInBits=%d channels=%d\n", (int)sampleRate, sampleSizeInBits, channels);
#ifdef USE_TRACE
{
AudioDeviceID audioDeviceID = deviceID;
if (audioDeviceID == 0) {
// default device
audioDeviceID = GetDefaultDevice(isSource);
}
char name[256];
OSStatus err = GetAudioObjectProperty(audioDeviceID, kAudioUnitScope_Global, kAudioDevicePropertyDeviceName, 256, &name, 0);
if (err != noErr) {
OS_ERROR1(err, " audioDeviceID=0x%x, name is N/A:", (int)audioDeviceID);
} else {
TRACE2(" audioDeviceID=0x%x, name=%s\n", (int)audioDeviceID, name);
}
}
#endif
if (encoding != DAUDIO_PCM) {
ERROR1("<<DAUDIO_Open: ERROR: unsupported encoding (%d)\n", encoding);
return NULL;
}
if (channels <= 0) {
ERROR1("<<DAUDIO_Open: ERROR: Invalid number of channels=%d!\n", channels);
return NULL;
}
OSX_DirectAudioDevice *device = new OSX_DirectAudioDevice();
AudioUnitScope scope = isSource ? kAudioUnitScope_Input : kAudioUnitScope_Output;
int element = isSource ? 0 : 1;
OSStatus err = noErr;
int extraBufferBytes = 0;
device->audioUnit = CreateOutputUnit(deviceID, isSource);
if (!device->audioUnit) {
delete device;
return NULL;
}
if (!isSource) {
AudioDeviceID actualDeviceID = deviceID != 0 ? deviceID : GetDefaultDevice(isSource);
float hardwareSampleRate = GetSampleRate(actualDeviceID, isSource);
TRACE2("--DAUDIO_Open: sampleRate = %f, hardwareSampleRate=%f\n", sampleRate, hardwareSampleRate);
if (fabs(sampleRate - hardwareSampleRate) > 1) {
device->resampler = new Resampler();
// request HAL for Float32 with native endianess
FillASBDForNonInterleavedPCM(device->asbd, hardwareSampleRate, channels, 32, true, false, kAudioFormatFlagsNativeEndian != 0);
} else {
sampleRate = hardwareSampleRate; // in case sample rates are not exactly equal
}
}
if (device->resampler == NULL) {
// no resampling, request HAL for the requested format
FillASBDForNonInterleavedPCM(device->asbd, sampleRate, channels, sampleSizeInBits, false, isSigned, isBigEndian);
}
err = AudioUnitSetProperty(device->audioUnit, kAudioUnitProperty_StreamFormat, scope, element, &device->asbd, sizeof(device->asbd));
if (err) {
OS_ERROR0(err, "<<DAUDIO_Open set StreamFormat");
delete device;
return NULL;
}
AURenderCallbackStruct output;
output.inputProc = isSource ? OutputCallback : InputCallback;
output.inputProcRefCon = device;
err = AudioUnitSetProperty(device->audioUnit,
isSource
? (AudioUnitPropertyID)kAudioUnitProperty_SetRenderCallback
: (AudioUnitPropertyID)kAudioOutputUnitProperty_SetInputCallback,
kAudioUnitScope_Global, 0, &output, sizeof(output));
if (err) {
OS_ERROR0(err, "<<DAUDIO_Open set RenderCallback");
delete device;
return NULL;
}
err = AudioUnitInitialize(device->audioUnit);
if (err) {
OS_ERROR0(err, "<<DAUDIO_Open UnitInitialize");
delete device;
return NULL;
}
if (!isSource) {
// for target lines we need extra bytes in the ringBuffer
// to prevent collisions when InputCallback overrides data on overflow
UInt32 size;
OSStatus err;
size = sizeof(device->inputBufferSizeInBytes);
err = AudioUnitGetProperty(device->audioUnit, kAudioDevicePropertyBufferFrameSize, kAudioUnitScope_Global,
0, &device->inputBufferSizeInBytes, &size);
if (err) {
OS_ERROR0(err, "<<DAUDIO_Open (TargetDataLine)GetBufferSize\n");
delete device;
return NULL;
}
device->inputBufferSizeInBytes *= device->asbd.mBytesPerFrame; // convert frames to bytes
extraBufferBytes = (int)device->inputBufferSizeInBytes;
}
if (device->resampler != NULL) {
// resampler output format is a user requested format (== ringBuffer format)
AudioStreamBasicDescription asbdOut; // ringBuffer format
FillASBDForNonInterleavedPCM(asbdOut, sampleRate, channels, sampleSizeInBits, false, isSigned, isBigEndian);
// set resampler input buffer size to the HAL buffer size
if (!device->resampler->Init(&device->asbd, &asbdOut, (int)device->inputBufferSizeInBytes)) {
ERROR0("<<DAUDIO_Open: resampler.Init() FAILED.\n");
delete device;
return NULL;
}
// extra bytes in the ringBuffer (extraBufferBytes) should be equal resampler output buffer size
extraBufferBytes = device->resampler->GetOutBufferSize();
}
if (!device->ringBuffer.Allocate(bufferSizeInBytes, extraBufferBytes)) {
ERROR0("<<DAUDIO_Open: Ring buffer allocation error\n");
delete device;
return NULL;
}
TRACE0("<<DAUDIO_Open: OK\n");
return device;
}
int DAUDIO_Start(void* id, int isSource) {
OSX_DirectAudioDevice *device = (OSX_DirectAudioDevice*)id;
TRACE0("DAUDIO_Start\n");
OSStatus err = AudioOutputUnitStart(device->audioUnit);
if (err != noErr) {
OS_ERROR0(err, "DAUDIO_Start");
}
return err == noErr ? TRUE : FALSE;
}
int DAUDIO_Stop(void* id, int isSource) {
OSX_DirectAudioDevice *device = (OSX_DirectAudioDevice*)id;
TRACE0("DAUDIO_Stop\n");
OSStatus err = AudioOutputUnitStop(device->audioUnit);
return err == noErr ? TRUE : FALSE;
}
void DAUDIO_Close(void* id, int isSource) {
OSX_DirectAudioDevice *device = (OSX_DirectAudioDevice*)id;
TRACE0("DAUDIO_Close\n");
delete device;
}
int DAUDIO_Write(void* id, char* data, int byteSize) {
OSX_DirectAudioDevice *device = (OSX_DirectAudioDevice*)id;
TRACE1(">>DAUDIO_Write: %d bytes to write\n", byteSize);
int result = device->ringBuffer.Write(data, byteSize, true);
TRACE1("<<DAUDIO_Write: %d bytes written\n", result);
return result;
}
int DAUDIO_Read(void* id, char* data, int byteSize) {
OSX_DirectAudioDevice *device = (OSX_DirectAudioDevice*)id;
TRACE1(">>DAUDIO_Read: %d bytes to read\n", byteSize);
int result = device->ringBuffer.Read(data, byteSize);
TRACE1("<<DAUDIO_Read: %d bytes has been read\n", result);
return result;
}
int DAUDIO_GetBufferSize(void* id, int isSource) {
OSX_DirectAudioDevice *device = (OSX_DirectAudioDevice*)id;
int bufferSizeInBytes = device->ringBuffer.GetBufferSize();
TRACE1("DAUDIO_GetBufferSize returns %d\n", bufferSizeInBytes);
return bufferSizeInBytes;
}
int DAUDIO_StillDraining(void* id, int isSource) {
OSX_DirectAudioDevice *device = (OSX_DirectAudioDevice*)id;
int draining = device->ringBuffer.GetValidByteCount() > 0 ? TRUE : FALSE;
TRACE1("DAUDIO_StillDraining returns %d\n", draining);
return draining;
}
int DAUDIO_Flush(void* id, int isSource) {
OSX_DirectAudioDevice *device = (OSX_DirectAudioDevice*)id;
TRACE0("DAUDIO_Flush\n");
device->ringBuffer.Flush();
return TRUE;
}
int DAUDIO_GetAvailable(void* id, int isSource) {
OSX_DirectAudioDevice *device = (OSX_DirectAudioDevice*)id;
int bytesInBuffer = device->ringBuffer.GetValidByteCount();
if (isSource) {
return device->ringBuffer.GetBufferSize() - bytesInBuffer;
} else {
return bytesInBuffer;
}
}
INT64 DAUDIO_GetBytePosition(void* id, int isSource, INT64 javaBytePos) {
OSX_DirectAudioDevice *device = (OSX_DirectAudioDevice*)id;
INT64 position;
if (isSource) {
position = javaBytePos - device->ringBuffer.GetValidByteCount();
} else {
position = javaBytePos + device->ringBuffer.GetValidByteCount();
}
TRACE2("DAUDIO_GetBytePosition returns %lld (javaBytePos = %lld)\n", (long long)position, (long long)javaBytePos);
return position;
}
void DAUDIO_SetBytePosition(void* id, int isSource, INT64 javaBytePos) {
// no need javaBytePos (it's available in DAUDIO_GetBytePosition)
}
int DAUDIO_RequiresServicing(void* id, int isSource) {
return FALSE;
}
void DAUDIO_Service(void* id, int isSource) {
// unreachable
}
#endif // USE_DAUDIO == TRUE