audio-orchestra/airtaudio/api/Core.cpp

1353 lines
52 KiB
C++

/**
* @author Gary P. SCAVONE
*
* @copyright 2001-2013 Gary P. Scavone, all right reserved
*
* @license like MIT (see license file)
*/
// *************************************************** //
//
// OS/API-specific methods.
//
// *************************************************** //
#if defined(__MACOSX_CORE__) || defined(__IOS_CORE__)
#include <airtaudio/Interface.h>
#include <airtaudio/debug.h>
airtaudio::Api* airtaudio::api::Core::Create(void) {
return new airtaudio::api::Core();
}
// The OS X CoreAudio API is designed to use a separate callback
// procedure for each of its audio devices. A single RtAudio duplex
// stream using two different devices is supported here, though it
// cannot be guaranteed to always behave correctly because we cannot
// synchronize these two callbacks.
//
// A property listener is installed for over/underrun information.
// However, no functionality is currently provided to allow property
// listeners to trigger user handlers because it is unclear what could
// be done if a critical stream parameter (buffer size, sample rate,
// device disconnect) notification arrived. The listeners entail
// quite a bit of extra code and most likely, a user program wouldn't
// be prepared for the result anyway. However, we do provide a flag
// to the client callback function to inform of an over/underrun.
// A structure to hold various information related to the CoreAudio API
// implementation.
struct CoreHandle {
AudioDeviceID id[2]; // device ids
#if defined(MAC_OS_X_VERSION_10_5) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5)
AudioDeviceIOProcID procId[2];
#endif
uint32_t iStream[2]; // device stream index (or first if using multiple)
uint32_t nStreams[2]; // number of streams to use
bool xrun[2];
char *deviceBuffer;
std::condition_variable condition;
int32_t drainCounter; // Tracks callback counts when draining
bool internalDrain; // Indicates if stop is initiated from callback or not.
CoreHandle(void) :
deviceBuffer(0),
drainCounter(0),
internalDrain(false) {
nStreams[0] = 1;
nStreams[1] = 1;
id[0] = 0;
id[1] = 0;
xrun[0] = false;
xrun[1] = false;
}
};
airtaudio::api::Core::Core(void) {
#if defined(AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER)
// This is a largely undocumented but absolutely necessary
// requirement starting with OS-X 10.6. If not called, queries and
// updates to various audio device properties are not handled
// correctly.
CFRunLoopRef theRunLoop = NULL;
AudioObjectPropertyAddress property = {
kAudioHardwarePropertyRunLoop,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
OSStatus result = AudioObjectSetPropertyData(kAudioObjectSystemObject,
&property,
0,
NULL,
sizeof(CFRunLoopRef),
&theRunLoop);
if (result != noErr) {
ATA_ERROR("airtaudio::api::Core::RtApiCore: error setting run loop property!");
}
#endif
}
airtaudio::api::Core::~Core(void) {
// The subclass destructor gets called before the base class
// destructor, so close an existing stream before deallocating
// apiDeviceId memory.
if (m_stream.state != STREAM_CLOSED) {
closeStream();
}
}
uint32_t airtaudio::api::Core::getDeviceCount(void) {
// Find out how many audio devices there are, if any.
uint32_t dataSize;
AudioObjectPropertyAddress propertyAddress = {
kAudioHardwarePropertyDevices,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
OSStatus result = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize);
if (result != noErr) {
ATA_ERROR("airtaudio::api::Core::getDeviceCount: OS-X error getting device info!");
return 0;
}
return dataSize / sizeof(AudioDeviceID);
}
uint32_t airtaudio::api::Core::getDefaultInputDevice(void) {
uint32_t nDevices = getDeviceCount();
if (nDevices <= 1) {
return 0;
}
AudioDeviceID id;
uint32_t dataSize = sizeof(AudioDeviceID);
AudioObjectPropertyAddress property = {
kAudioHardwarePropertyDefaultInputDevice,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
OSStatus result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
&property,
0,
NULL,
&dataSize,
&id);
if (result != noErr) {
ATA_ERROR("airtaudio::api::Core::getDefaultInputDevice: OS-X system error getting device.");
return 0;
}
dataSize *= nDevices;
AudioDeviceID deviceList[ nDevices ];
property.mSelector = kAudioHardwarePropertyDevices;
result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
&property,
0,
NULL,
&dataSize,
(void*)&deviceList);
if (result != noErr) {
ATA_ERROR("airtaudio::api::Core::getDefaultInputDevice: OS-X system error getting device IDs.");
return 0;
}
for (uint32_t iii=0; iii<nDevices; iii++) {
if (id == deviceList[iii]) {
return iii;
}
}
ATA_ERROR("airtaudio::api::Core::getDefaultInputDevice: No default device found!");
return 0;
}
uint32_t airtaudio::api::Core::getDefaultOutputDevice(void) {
uint32_t nDevices = getDeviceCount();
if (nDevices <= 1) {
return 0;
}
AudioDeviceID id;
uint32_t dataSize = sizeof(AudioDeviceID);
AudioObjectPropertyAddress property = {
kAudioHardwarePropertyDefaultOutputDevice,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
OSStatus result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
&property,
0,
NULL,
&dataSize,
&id);
if (result != noErr) {
ATA_ERROR("airtaudio::api::Core::getDefaultOutputDevice: OS-X system error getting device.");
return 0;
}
dataSize = sizeof(AudioDeviceID) * nDevices;
AudioDeviceID deviceList[ nDevices ];
property.mSelector = kAudioHardwarePropertyDevices;
result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
&property,
0,
NULL,
&dataSize,
(void*)&deviceList);
if (result != noErr) {
ATA_ERROR("airtaudio::api::Core::getDefaultOutputDevice: OS-X system error getting device IDs.");
return 0;
}
for (uint32_t iii=0; iii<nDevices; iii++) {
if (id == deviceList[iii]) {
return iii;
}
}
ATA_ERROR("airtaudio::api::Core::getDefaultOutputDevice: No default device found!");
return 0;
}
airtaudio::DeviceInfo airtaudio::api::Core::getDeviceInfo(uint32_t _device) {
airtaudio::DeviceInfo info;
info.probed = false;
// Get device ID
uint32_t nDevices = getDeviceCount();
if (nDevices == 0) {
ATA_ERROR("airtaudio::api::Core::getDeviceInfo: no devices found!");
return info;
}
if (_device >= nDevices) {
ATA_ERROR("airtaudio::api::Core::getDeviceInfo: device ID is invalid!");
return info;
}
AudioDeviceID deviceList[ nDevices ];
uint32_t dataSize = sizeof(AudioDeviceID) * nDevices;
AudioObjectPropertyAddress property = {
kAudioHardwarePropertyDevices,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
OSStatus result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
&property,
0,
NULL,
&dataSize,
(void*)&deviceList);
if (result != noErr) {
ATA_ERROR("airtaudio::api::Core::getDeviceInfo: OS-X system error getting device IDs.");
return info;
}
AudioDeviceID id = deviceList[ _device ];
// Get the device name.
info.name.erase();
CFStringRef cfname;
dataSize = sizeof(CFStringRef);
property.mSelector = kAudioObjectPropertyManufacturer;
result = AudioObjectGetPropertyData(id, &property, 0, NULL, &dataSize, &cfname);
if (result != noErr) {
ATA_ERROR("airtaudio::api::Core::probeDeviceInfo: system error (" << getErrorCode(result) << ") getting device manufacturer.");
return info;
}
//const char *mname = CFStringGetCStringPtr(cfname, CFStringGetSystemEncoding());
int32_t length = CFStringGetLength(cfname);
char *mname = (char *)malloc(length * 3 + 1);
CFStringGetCString(cfname, mname, length * 3 + 1, CFStringGetSystemEncoding());
info.name.append((const char *)mname, strlen(mname));
info.name.append(": ");
CFRelease(cfname);
free(mname);
property.mSelector = kAudioObjectPropertyName;
result = AudioObjectGetPropertyData(id, &property, 0, NULL, &dataSize, &cfname);
if (result != noErr) {
ATA_ERROR("airtaudio::api::Core::probeDeviceInfo: system error (" << getErrorCode(result) << ") getting device name.");
return info;
}
//const char *name = CFStringGetCStringPtr(cfname, CFStringGetSystemEncoding());
length = CFStringGetLength(cfname);
char *name = (char *)malloc(length * 3 + 1);
CFStringGetCString(cfname, name, length * 3 + 1, CFStringGetSystemEncoding());
info.name.append((const char *)name, strlen(name));
CFRelease(cfname);
free(name);
// Get the output stream "configuration".
AudioBufferList *bufferList = nil;
property.mSelector = kAudioDevicePropertyStreamConfiguration;
property.mScope = kAudioDevicePropertyScopeOutput;
// property.mElement = kAudioObjectPropertyElementWildcard;
dataSize = 0;
result = AudioObjectGetPropertyDataSize(id, &property, 0, NULL, &dataSize);
if (result != noErr || dataSize == 0) {
ATA_ERROR("airtaudio::api::Core::getDeviceInfo: system error (" << getErrorCode(result) << ") getting output stream configuration info for device (" << _device << ").");
return info;
}
// Allocate the AudioBufferList.
bufferList = (AudioBufferList *) malloc(dataSize);
if (bufferList == NULL) {
ATA_ERROR("airtaudio::api::Core::getDeviceInfo: memory error allocating output AudioBufferList.");
return info;
}
result = AudioObjectGetPropertyData(id, &property, 0, NULL, &dataSize, bufferList);
if ( result != noErr
|| dataSize == 0) {
free(bufferList);
ATA_ERROR("airtaudio::api::Core::getDeviceInfo: system error (" << getErrorCode(result) << ") getting output stream configuration for device (" << _device << ").");
return info;
}
// Get output channel information.
uint32_t i, nStreams = bufferList->mNumberBuffers;
for (i=0; i<nStreams; i++) {
info.outputChannels += bufferList->mBuffers[i].mNumberChannels;
}
free(bufferList);
// Get the input stream "configuration".
property.mScope = kAudioDevicePropertyScopeInput;
result = AudioObjectGetPropertyDataSize(id, &property, 0, NULL, &dataSize);
if ( result != noErr
|| dataSize == 0) {
ATA_ERROR("airtaudio::api::Core::getDeviceInfo: system error (" << getErrorCode(result) << ") getting input stream configuration info for device (" << _device << ").");
return info;
}
// Allocate the AudioBufferList.
bufferList = (AudioBufferList *) malloc(dataSize);
if (bufferList == NULL) {
ATA_ERROR("airtaudio::api::Core::getDeviceInfo: memory error allocating input AudioBufferList.");
return info;
}
result = AudioObjectGetPropertyData(id, &property, 0, NULL, &dataSize, bufferList);
if (result != noErr || dataSize == 0) {
free(bufferList);
ATA_ERROR("airtaudio::api::Core::getDeviceInfo: system error (" << getErrorCode(result) << ") getting input stream configuration for device (" << _device << ").");
return info;
}
// Get input channel information.
nStreams = bufferList->mNumberBuffers;
for (i=0; i<nStreams; i++) {
info.inputChannels += bufferList->mBuffers[i].mNumberChannels;
}
free(bufferList);
// If device opens for both playback and capture, we determine the channels.
if ( info.outputChannels > 0
&& info.inputChannels > 0) {
info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels;
}
// Probe the device sample rates.
bool isInput = false;
if (info.outputChannels == 0) {
isInput = true;
}
// Determine the supported sample rates.
property.mSelector = kAudioDevicePropertyAvailableNominalSampleRates;
if (isInput == false) property.mScope = kAudioDevicePropertyScopeOutput;
result = AudioObjectGetPropertyDataSize(id, &property, 0, NULL, &dataSize);
if ( result != kAudioHardwareNoError
|| dataSize == 0) {
ATA_ERROR("airtaudio::api::Core::getDeviceInfo: system error (" << getErrorCode(result) << ") getting sample rate info.");
return info;
}
uint32_t nRanges = dataSize / sizeof(AudioValueRange);
AudioValueRange rangeList[ nRanges ];
result = AudioObjectGetPropertyData(id, &property, 0, NULL, &dataSize, &rangeList);
if (result != kAudioHardwareNoError) {
ATA_ERROR("airtaudio::api::Core::getDeviceInfo: system error (" << getErrorCode(result) << ") getting sample rates.");
return info;
}
double minimumRate = 100000000.0, maximumRate = 0.0;
for (uint32_t i=0; i<nRanges; i++) {
if (rangeList[i].mMinimum < minimumRate) {
minimumRate = rangeList[i].mMinimum;
}
if (rangeList[i].mMaximum > maximumRate) {
maximumRate = rangeList[i].mMaximum;
}
}
info.sampleRates.clear();
for (uint32_t k=0; k<MAX_SAMPLE_RATES; k++) {
if (SAMPLE_RATES[k] >= (uint32_t) minimumRate && SAMPLE_RATES[k] <= (uint32_t) maximumRate) {
info.sampleRates.push_back(SAMPLE_RATES[k]);
}
}
if (info.sampleRates.size() == 0) {
ATA_ERROR("airtaudio::api::Core::probeDeviceInfo: No supported sample rates found for device (" << _device << ").");
return info;
}
// CoreAudio always uses 32-bit floating point data for PCM streams.
// Thus, any other "physical" formats supported by the device are of
// no interest to the client.
info.nativeFormats = FLOAT32;
if (info.outputChannels > 0) {
if (getDefaultOutputDevice() == _device) {
info.isDefaultOutput = true;
}
}
if (info.inputChannels > 0) {
if (getDefaultInputDevice() == _device) {
info.isDefaultInput = true;
}
}
info.probed = true;
return info;
}
static OSStatus callbackHandler(AudioDeviceID _inDevice,
const AudioTimeStamp* _inNow,
const AudioBufferList* _inInputData,
const AudioTimeStamp* _inInputTime,
AudioBufferList* _outOutputData,
const AudioTimeStamp* _inOutputTime,
void* _infoPointer) {
airtaudio::CallbackInfo* info = (airtaudio::CallbackInfo*)_infoPointer;
airtaudio::api::Core* object = (airtaudio::api::Core*)info->object;
if (object->callbackEvent(_inDevice, _inInputData, _outOutputData) == false) {
return kAudioHardwareUnspecifiedError;
} else {
return kAudioHardwareNoError;
}
}
static OSStatus xrunListener(AudioObjectID _inDevice,
uint32_t _nAddresses,
const AudioObjectPropertyAddress _properties[],
void* _handlePointer) {
CoreHandle* handle = (CoreHandle*)_handlePointer;
for (uint32_t i=0; i<_nAddresses; i++) {
if (_properties[i].mSelector == kAudioDeviceProcessorOverload) {
if (_properties[i].mScope == kAudioDevicePropertyScopeInput) {
handle->xrun[1] = true;
} else {
handle->xrun[0] = true;
}
}
}
return kAudioHardwareNoError;
}
static OSStatus rateListener(AudioObjectID _inDevice,
uint32_t _nAddresses,
const AudioObjectPropertyAddress _properties[],
void* _ratePointer) {
double *rate = (double*)_ratePointer;
uint32_t dataSize = sizeof(double);
AudioObjectPropertyAddress property = {
kAudioDevicePropertyNominalSampleRate,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
AudioObjectGetPropertyData(_inDevice, &property, 0, NULL, &dataSize, rate);
return kAudioHardwareNoError;
}
bool airtaudio::api::Core::probeDeviceOpen(uint32_t _device,
airtaudio::api::StreamMode _mode,
uint32_t _channels,
uint32_t _firstChannel,
uint32_t _sampleRate,
airtaudio::format _format,
uint32_t *_bufferSize,
airtaudio::StreamOptions *_options) {
// Get device ID
uint32_t nDevices = getDeviceCount();
if (nDevices == 0) {
// This should not happen because a check is made before this function is called.
ATA_ERROR("airtaudio::api::Core::probeDeviceOpen: no devices found!");
return false;
}
if (_device >= nDevices) {
// This should not happen because a check is made before this function is called.
ATA_ERROR("airtaudio::api::Core::probeDeviceOpen: device ID is invalid!");
return false;
}
AudioDeviceID deviceList[ nDevices ];
uint32_t dataSize = sizeof(AudioDeviceID) * nDevices;
AudioObjectPropertyAddress property = {
kAudioHardwarePropertyDevices,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
OSStatus result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
&property,
0,
NULL,
&dataSize,
(void *) &deviceList);
if (result != noErr) {
ATA_ERROR("airtaudio::api::Core::probeDeviceOpen: OS-X system error getting device IDs.");
return false;
}
AudioDeviceID id = deviceList[ _device ];
// Setup for stream mode.
bool isInput = false;
if (_mode == INPUT) {
isInput = true;
property.mScope = kAudioDevicePropertyScopeInput;
} else {
property.mScope = kAudioDevicePropertyScopeOutput;
}
// Get the stream "configuration".
AudioBufferList *bufferList = nil;
dataSize = 0;
property.mSelector = kAudioDevicePropertyStreamConfiguration;
result = AudioObjectGetPropertyDataSize(id, &property, 0, NULL, &dataSize);
if ( result != noErr
|| dataSize == 0) {
ATA_ERROR("airtaudio::api::Core::probeDeviceOpen: system error (" << getErrorCode(result) << ") getting stream configuration info for device (" << _device << ").");
return false;
}
// Allocate the AudioBufferList.
bufferList = (AudioBufferList *) malloc(dataSize);
if (bufferList == NULL) {
ATA_ERROR("airtaudio::api::Core::probeDeviceOpen: memory error allocating AudioBufferList.");
return false;
}
result = AudioObjectGetPropertyData(id, &property, 0, NULL, &dataSize, bufferList);
if ( result != noErr
|| dataSize == 0) {
ATA_ERROR("airtaudio::api::Core::probeDeviceOpen: system error (" << getErrorCode(result) << ") getting stream configuration for device (" << _device << ").");
return false;
}
// Search for one or more streams that contain the desired number of
// channels. CoreAudio devices can have an arbitrary number of
// streams and each stream can have an arbitrary number of channels.
// For each stream, a single buffer of interleaved samples is
// provided. RtAudio prefers the use of one stream of interleaved
// data or multiple consecutive single-channel streams. However, we
// now support multiple consecutive multi-channel streams of
// interleaved data as well.
uint32_t iStream, offsetCounter = _firstChannel;
uint32_t nStreams = bufferList->mNumberBuffers;
bool monoMode = false;
bool foundStream = false;
// First check that the device supports the requested number of
// channels.
uint32_t deviceChannels = 0;
for (iStream=0; iStream<nStreams; iStream++) {
deviceChannels += bufferList->mBuffers[iStream].mNumberChannels;
}
if (deviceChannels < (_channels + _firstChannel)) {
free(bufferList);
ATA_ERROR("airtaudio::api::Core::probeDeviceOpen: the device (" << _device << ") does not support the requested channel count.");
return false;
}
// Look for a single stream meeting our needs.
uint32_t firstStream, streamCount = 1, streamChannels = 0, channelOffset = 0;
for (iStream=0; iStream<nStreams; iStream++) {
streamChannels = bufferList->mBuffers[iStream].mNumberChannels;
if (streamChannels >= _channels + offsetCounter) {
firstStream = iStream;
channelOffset = offsetCounter;
foundStream = true;
break;
}
if (streamChannels > offsetCounter) {
break;
}
offsetCounter -= streamChannels;
}
// If we didn't find a single stream above, then we should be able
// to meet the channel specification with multiple streams.
if (foundStream == false) {
monoMode = true;
offsetCounter = _firstChannel;
for (iStream=0; iStream<nStreams; iStream++) {
streamChannels = bufferList->mBuffers[iStream].mNumberChannels;
if (streamChannels > offsetCounter) {
break;
}
offsetCounter -= streamChannels;
}
firstStream = iStream;
channelOffset = offsetCounter;
int32_t channelCounter = _channels + offsetCounter - streamChannels;
if (streamChannels > 1) {
monoMode = false;
}
while (channelCounter > 0) {
streamChannels = bufferList->mBuffers[++iStream].mNumberChannels;
if (streamChannels > 1) {
monoMode = false;
}
channelCounter -= streamChannels;
streamCount++;
}
}
free(bufferList);
// Determine the buffer size.
AudioValueRange bufferRange;
dataSize = sizeof(AudioValueRange);
property.mSelector = kAudioDevicePropertyBufferFrameSizeRange;
result = AudioObjectGetPropertyData(id, &property, 0, NULL, &dataSize, &bufferRange);
if (result != noErr) {
ATA_ERROR("airtaudio::api::Core::probeDeviceOpen: system error (" << getErrorCode(result) << ") getting buffer size range for device (" << _device << ").");
return false;
}
if (bufferRange.mMinimum > *_bufferSize) {
*_bufferSize = (uint64_t) bufferRange.mMinimum;
} else if (bufferRange.mMaximum < *_bufferSize) {
*_bufferSize = (uint64_t) bufferRange.mMaximum;
}
if ( _options != NULL
&& _options->flags & MINIMIZE_LATENCY) {
*_bufferSize = (uint64_t) bufferRange.mMinimum;
}
// Set the buffer size. For multiple streams, I'm assuming we only
// need to make this setting for the master channel.
uint32_t theSize = (uint32_t) *_bufferSize;
dataSize = sizeof(uint32_t);
property.mSelector = kAudioDevicePropertyBufferFrameSize;
result = AudioObjectSetPropertyData(id, &property, 0, NULL, dataSize, &theSize);
if (result != noErr) {
ATA_ERROR("airtaudio::api::Core::probeDeviceOpen: system error (" << getErrorCode(result) << ") setting the buffer size for device (" << _device << ").");
return false;
}
// If attempting to setup a duplex stream, the bufferSize parameter
// MUST be the same in both directions!
*_bufferSize = theSize;
if ( m_stream.mode == OUTPUT
&& _mode == INPUT
&& *_bufferSize != m_stream.bufferSize) {
ATA_ERROR("airtaudio::api::Core::probeDeviceOpen: system error setting buffer size for duplex stream on device (" << _device << ").");
return false;
}
m_stream.bufferSize = *_bufferSize;
m_stream.nBuffers = 1;
// Try to set "hog" mode ... it's not clear to me this is working.
if ( _options != NULL
&& _options->flags & HOG_DEVICE) {
pid_t hog_pid;
dataSize = sizeof(hog_pid);
property.mSelector = kAudioDevicePropertyHogMode;
result = AudioObjectGetPropertyData(id, &property, 0, NULL, &dataSize, &hog_pid);
if (result != noErr) {
ATA_ERROR("airtaudio::api::Core::probeDeviceOpen: system error (" << getErrorCode(result) << ") getting 'hog' state!");
return false;
}
if (hog_pid != getpid()) {
hog_pid = getpid();
result = AudioObjectSetPropertyData(id, &property, 0, NULL, dataSize, &hog_pid);
if (result != noErr) {
ATA_ERROR("airtaudio::api::Core::probeDeviceOpen: system error (" << getErrorCode(result) << ") setting 'hog' state!");
return false;
}
}
}
// Check and if necessary, change the sample rate for the device.
double nominalRate;
dataSize = sizeof(double);
property.mSelector = kAudioDevicePropertyNominalSampleRate;
result = AudioObjectGetPropertyData(id, &property, 0, NULL, &dataSize, &nominalRate);
if (result != noErr) {
ATA_ERROR("airtaudio::api::Core::probeDeviceOpen: system error (" << getErrorCode(result) << ") getting current sample rate.");
return false;
}
// Only change the sample rate if off by more than 1 Hz.
if (fabs(nominalRate - (double)_sampleRate) > 1.0) {
// Set a property listener for the sample rate change
double reportedRate = 0.0;
AudioObjectPropertyAddress tmp = { kAudioDevicePropertyNominalSampleRate, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
result = AudioObjectAddPropertyListener(id, &tmp, rateListener, (void *) &reportedRate);
if (result != noErr) {
ATA_ERROR("airtaudio::api::Core::probeDeviceOpen: system error (" << getErrorCode(result) << ") setting sample rate property listener for device (" << _device << ").");
return false;
}
nominalRate = (double) _sampleRate;
result = AudioObjectSetPropertyData(id, &property, 0, NULL, dataSize, &nominalRate);
if (result != noErr) {
ATA_ERROR("airtaudio::api::Core::probeDeviceOpen: system error (" << getErrorCode(result) << ") setting sample rate for device (" << _device << ").");
return false;
}
// Now wait until the reported nominal rate is what we just set.
uint32_t microCounter = 0;
while (reportedRate != nominalRate) {
microCounter += 5000;
if (microCounter > 5000000) {
break;
}
usleep(5000);
}
// Remove the property listener.
AudioObjectRemovePropertyListener(id, &tmp, rateListener, (void *) &reportedRate);
if (microCounter > 5000000) {
ATA_ERROR("airtaudio::api::Core::probeDeviceOpen: timeout waiting for sample rate update for device (" << _device << ").");
return false;
}
}
// Now set the stream format for all streams. Also, check the
// physical format of the device and change that if necessary.
AudioStreamBasicDescription description;
dataSize = sizeof(AudioStreamBasicDescription);
property.mSelector = kAudioStreamPropertyVirtualFormat;
result = AudioObjectGetPropertyData(id, &property, 0, NULL, &dataSize, &description);
if (result != noErr) {
ATA_ERROR("airtaudio::api::Core::probeDeviceOpen: system error (" << getErrorCode(result) << ") getting stream format for device (" << _device << ").");
return false;
}
// Set the sample rate and data format id. However, only make the
// change if the sample rate is not within 1.0 of the desired
// rate and the format is not linear pcm.
bool updateFormat = false;
if (fabs(description.mSampleRate - (double)_sampleRate) > 1.0) {
description.mSampleRate = (double) _sampleRate;
updateFormat = true;
}
if (description.mFormatID != kAudioFormatLinearPCM) {
description.mFormatID = kAudioFormatLinearPCM;
updateFormat = true;
}
if (updateFormat) {
result = AudioObjectSetPropertyData(id, &property, 0, NULL, dataSize, &description);
if (result != noErr) {
ATA_ERROR("airtaudio::api::Core::probeDeviceOpen: system error (" << getErrorCode(result) << ") setting sample rate or data format for device (" << _device << ").");
return false;
}
}
// Now check the physical format.
property.mSelector = kAudioStreamPropertyPhysicalFormat;
result = AudioObjectGetPropertyData(id, &property, 0, NULL, &dataSize, &description);
if (result != noErr) {
ATA_ERROR("airtaudio::api::Core::probeDeviceOpen: system error (" << getErrorCode(result) << ") getting stream physical format for device (" << _device << ").");
return false;
}
//std::cout << "Current physical stream format:" << std::endl;
//std::cout << " mBitsPerChan = " << description.mBitsPerChannel << std::endl;
//std::cout << " aligned high = " << (description.mFormatFlags & kAudioFormatFlagIsAlignedHigh) << ", isPacked = " << (description.mFormatFlags & kAudioFormatFlagIsPacked) << std::endl;
//std::cout << " bytesPerFrame = " << description.mBytesPerFrame << std::endl;
//std::cout << " sample rate = " << description.mSampleRate << std::endl;
if ( description.mFormatID != kAudioFormatLinearPCM
|| description.mBitsPerChannel < 16) {
description.mFormatID = kAudioFormatLinearPCM;
//description.mSampleRate = (double) sampleRate;
AudioStreamBasicDescription testDescription = description;
uint32_t formatFlags;
// We'll try higher bit rates first and then work our way down.
std::vector< std::pair<uint32_t, uint32_t> > physicalFormats;
formatFlags = (description.mFormatFlags | kLinearPCMFormatFlagIsFloat) & ~kLinearPCMFormatFlagIsSignedInteger;
physicalFormats.push_back(std::pair<float, uint32_t>(32, formatFlags));
formatFlags = (description.mFormatFlags | kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked) & ~kLinearPCMFormatFlagIsFloat;
physicalFormats.push_back(std::pair<float, uint32_t>(32, formatFlags));
physicalFormats.push_back(std::pair<float, uint32_t>(24, formatFlags)); // 24-bit packed
formatFlags &= ~(kAudioFormatFlagIsPacked | kAudioFormatFlagIsAlignedHigh);
physicalFormats.push_back(std::pair<float, uint32_t>(24.2, formatFlags)); // 24-bit in 4 bytes, aligned low
formatFlags |= kAudioFormatFlagIsAlignedHigh;
physicalFormats.push_back(std::pair<float, uint32_t>(24.4, formatFlags)); // 24-bit in 4 bytes, aligned high
formatFlags = (description.mFormatFlags | kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked) & ~kLinearPCMFormatFlagIsFloat;
physicalFormats.push_back(std::pair<float, uint32_t>(16, formatFlags));
physicalFormats.push_back(std::pair<float, uint32_t>(8, formatFlags));
bool setPhysicalFormat = false;
for(uint32_t i=0; i<physicalFormats.size(); i++) {
testDescription = description;
testDescription.mBitsPerChannel = (uint32_t) physicalFormats[i].first;
testDescription.mFormatFlags = physicalFormats[i].second;
if ( (24 == (uint32_t)physicalFormats[i].first)
&& ~(physicalFormats[i].second & kAudioFormatFlagIsPacked)) {
testDescription.mBytesPerFrame = 4 * testDescription.mChannelsPerFrame;
} else {
testDescription.mBytesPerFrame = testDescription.mBitsPerChannel/8 * testDescription.mChannelsPerFrame;
}
testDescription.mBytesPerPacket = testDescription.mBytesPerFrame * testDescription.mFramesPerPacket;
result = AudioObjectSetPropertyData(id, &property, 0, NULL, dataSize, &testDescription);
if (result == noErr) {
setPhysicalFormat = true;
//std::cout << "Updated physical stream format:" << std::endl;
//std::cout << " mBitsPerChan = " << testDescription.mBitsPerChannel << std::endl;
//std::cout << " aligned high = " << (testDescription.mFormatFlags & kAudioFormatFlagIsAlignedHigh) << ", isPacked = " << (testDescription.mFormatFlags & kAudioFormatFlagIsPacked) << std::endl;
//std::cout << " bytesPerFrame = " << testDescription.mBytesPerFrame << std::endl;
//std::cout << " sample rate = " << testDescription.mSampleRate << std::endl;
break;
}
}
if (!setPhysicalFormat) {
ATA_ERROR("airtaudio::api::Core::probeDeviceOpen: system error (" << getErrorCode(result) << ") setting physical data format for device (" << _device << ").");
return false;
}
} // done setting virtual/physical formats.
// Get the stream / device latency.
uint32_t latency;
dataSize = sizeof(uint32_t);
property.mSelector = kAudioDevicePropertyLatency;
if (AudioObjectHasProperty(id, &property) == true) {
result = AudioObjectGetPropertyData(id, &property, 0, NULL, &dataSize, &latency);
if (result == kAudioHardwareNoError) {
m_stream.latency[ _mode ] = latency;
} else {
ATA_ERROR("airtaudio::api::Core::probeDeviceOpen: system error (" << getErrorCode(result) << ") getting device latency for device (" << _device << ").");
return false;
}
}
// Byte-swapping: According to AudioHardware.h, the stream data will
// always be presented in native-endian format, so we should never
// need to byte swap.
m_stream.doByteSwap[_mode] = false;
// From the CoreAudio documentation, PCM data must be supplied as
// 32-bit floats.
m_stream.userFormat = _format;
m_stream.deviceFormat[_mode] = FLOAT32;
if (streamCount == 1) {
m_stream.nDeviceChannels[_mode] = description.mChannelsPerFrame;
} else {
// multiple streams
m_stream.nDeviceChannels[_mode] = _channels;
}
m_stream.nUserChannels[_mode] = _channels;
m_stream.channelOffset[_mode] = channelOffset; // offset within a CoreAudio stream
if ( _options != NULL
&& _options->flags & NONINTERLEAVED) {
m_stream.userInterleaved = false;
} else {
m_stream.userInterleaved = true;
}
m_stream.deviceInterleaved[_mode] = true;
if (monoMode == true) {
m_stream.deviceInterleaved[_mode] = false;
}
// Set flags for buffer conversion.
m_stream.doConvertBuffer[_mode] = false;
if (m_stream.userFormat != m_stream.deviceFormat[_mode]) {
m_stream.doConvertBuffer[_mode] = true;
}
if (m_stream.nUserChannels[_mode] < m_stream.nDeviceChannels[_mode]) {
m_stream.doConvertBuffer[_mode] = true;
}
if (streamCount == 1) {
if ( m_stream.nUserChannels[_mode] > 1
&& m_stream.userInterleaved != m_stream.deviceInterleaved[_mode]) {
m_stream.doConvertBuffer[_mode] = true;
}
} else if (monoMode && m_stream.userInterleaved) {
m_stream.doConvertBuffer[_mode] = true;
}
// Allocate our CoreHandle structure for the stream.
CoreHandle *handle = 0;
if (m_stream.apiHandle == 0) {
handle = new CoreHandle;
if (handle == NULL) {
ATA_ERROR("airtaudio::api::Core::probeDeviceOpen: error allocating CoreHandle memory.");
return false;
}
m_stream.apiHandle = (void *) handle;
} else {
handle = (CoreHandle *) m_stream.apiHandle;
}
handle->iStream[_mode] = firstStream;
handle->nStreams[_mode] = streamCount;
handle->id[_mode] = id;
// Allocate necessary internal buffers.
uint64_t bufferBytes;
bufferBytes = m_stream.nUserChannels[_mode] * *_bufferSize * formatBytes(m_stream.userFormat);
// m_stream.userBuffer[_mode] = (char *) calloc(bufferBytes, 1);
m_stream.userBuffer[_mode] = (char *) malloc(bufferBytes * sizeof(char));
memset(m_stream.userBuffer[_mode], 0, bufferBytes * sizeof(char));
if (m_stream.userBuffer[_mode] == NULL) {
ATA_ERROR("airtaudio::api::Core::probeDeviceOpen: error allocating user buffer memory.");
goto error;
}
// If possible, we will make use of the CoreAudio stream buffers as
// "device buffers". However, we can't do this if using multiple
// streams.
if ( m_stream.doConvertBuffer[_mode]
&& handle->nStreams[_mode] > 1) {
bool makeBuffer = true;
bufferBytes = m_stream.nDeviceChannels[_mode] * formatBytes(m_stream.deviceFormat[_mode]);
if (_mode == INPUT) {
if ( m_stream.mode == OUTPUT
&& m_stream.deviceBuffer) {
uint64_t bytesOut = m_stream.nDeviceChannels[0] * formatBytes(m_stream.deviceFormat[0]);
if (bufferBytes <= bytesOut) {
makeBuffer = false;
}
}
}
if (makeBuffer) {
bufferBytes *= *_bufferSize;
if (m_stream.deviceBuffer) {
free(m_stream.deviceBuffer);
m_stream.deviceBuffer = NULL;
}
m_stream.deviceBuffer = (char *) calloc(bufferBytes, 1);
if (m_stream.deviceBuffer == NULL) {
ATA_ERROR("airtaudio::api::Core::probeDeviceOpen: error allocating device buffer memory.");
goto error;
}
}
}
m_stream.sampleRate = _sampleRate;
m_stream.device[_mode] = _device;
m_stream.state = STREAM_STOPPED;
m_stream.callbackInfo.object = (void *) this;
// Setup the buffer conversion information structure.
if (m_stream.doConvertBuffer[_mode]) {
if (streamCount > 1) {
setConvertInfo(_mode, 0);
} else {
setConvertInfo(_mode, channelOffset);
}
}
if ( _mode == INPUT
&& m_stream.mode == OUTPUT
&& m_stream.device[0] == _device) {
// Only one callback procedure per device.
m_stream.mode = DUPLEX;
} else {
#if defined(MAC_OS_X_VERSION_10_5) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5)
result = AudioDeviceCreateIOProcID(id, callbackHandler, (void *) &m_stream.callbackInfo, &handle->procId[_mode]);
#else
// deprecated in favor of AudioDeviceCreateIOProcID()
result = AudioDeviceAddIOProc(id, callbackHandler, (void *) &m_stream.callbackInfo);
#endif
if (result != noErr) {
ATA_ERROR("airtaudio::api::Core::probeDeviceOpen: system error setting callback for device (" << _device << ").");
goto error;
}
if ( m_stream.mode == OUTPUT
&& _mode == INPUT) {
m_stream.mode = DUPLEX;
} else {
m_stream.mode = _mode;
}
}
// Setup the device property listener for over/underload.
property.mSelector = kAudioDeviceProcessorOverload;
result = AudioObjectAddPropertyListener(id, &property, xrunListener, (void *) handle);
return true;
error:
if (handle) {
delete handle;
m_stream.apiHandle = 0;
}
for (int32_t i=0; i<2; i++) {
if (m_stream.userBuffer[i]) {
free(m_stream.userBuffer[i]);
m_stream.userBuffer[i] = 0;
}
}
if (m_stream.deviceBuffer) {
free(m_stream.deviceBuffer);
m_stream.deviceBuffer = 0;
}
m_stream.state = STREAM_CLOSED;
return false;
}
enum airtaudio::errorType airtaudio::api::Core::closeStream(void) {
if (m_stream.state == STREAM_CLOSED) {
ATA_ERROR("airtaudio::api::Core::closeStream(): no open stream to close!");
return airtaudio::errorWarning;
}
CoreHandle *handle = (CoreHandle *) m_stream.apiHandle;
if ( m_stream.mode == OUTPUT
|| m_stream.mode == DUPLEX) {
if (m_stream.state == STREAM_RUNNING) {
AudioDeviceStop(handle->id[0], callbackHandler);
}
#if defined(MAC_OS_X_VERSION_10_5) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5)
AudioDeviceDestroyIOProcID(handle->id[0], handle->procId[0]);
#else
// deprecated in favor of AudioDeviceDestroyIOProcID()
AudioDeviceRemoveIOProc(handle->id[0], callbackHandler);
#endif
}
if ( m_stream.mode == INPUT
|| ( m_stream.mode == DUPLEX
&& m_stream.device[0] != m_stream.device[1])) {
if (m_stream.state == STREAM_RUNNING) {
AudioDeviceStop(handle->id[1], callbackHandler);
}
#if defined(MAC_OS_X_VERSION_10_5) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5)
AudioDeviceDestroyIOProcID(handle->id[1], handle->procId[1]);
#else
// deprecated in favor of AudioDeviceDestroyIOProcID()
AudioDeviceRemoveIOProc(handle->id[1], callbackHandler);
#endif
}
for (int32_t i=0; i<2; i++) {
if (m_stream.userBuffer[i]) {
free(m_stream.userBuffer[i]);
m_stream.userBuffer[i] = NULL;
}
}
if (m_stream.deviceBuffer) {
free(m_stream.deviceBuffer);
m_stream.deviceBuffer = NULL;
}
delete handle;
m_stream.apiHandle = 0;
m_stream.mode = UNINITIALIZED;
m_stream.state = STREAM_CLOSED;
return airtaudio::errorNone;
}
enum airtaudio::errorType airtaudio::api::Core::startStream(void) {
if (verifyStream() != airtaudio::errorNone) {
return airtaudio::errorFail;
}
if (m_stream.state == STREAM_RUNNING) {
ATA_ERROR("airtaudio::api::Core::startStream(): the stream is already running!");
return airtaudio::errorWarning;
}
OSStatus result = noErr;
CoreHandle *handle = (CoreHandle *) m_stream.apiHandle;
if ( m_stream.mode == OUTPUT
|| m_stream.mode == DUPLEX) {
result = AudioDeviceStart(handle->id[0], callbackHandler);
if (result != noErr) {
ATA_ERROR("airtaudio::api::Core::startStream: system error (" << getErrorCode(result) << ") starting callback procedure on device (" << m_stream.device[0] << ").");
goto unlock;
}
}
if ( m_stream.mode == INPUT
|| ( m_stream.mode == DUPLEX
&& m_stream.device[0] != m_stream.device[1])) {
result = AudioDeviceStart(handle->id[1], callbackHandler);
if (result != noErr) {
ATA_ERROR("airtaudio::api::Core::startStream: system error starting input callback procedure on device (" << m_stream.device[1] << ").");
goto unlock;
}
}
handle->drainCounter = 0;
handle->internalDrain = false;
m_stream.state = STREAM_RUNNING;
unlock:
if (result == noErr) {
return airtaudio::errorNone;
}
return airtaudio::errorSystemError;
}
enum airtaudio::errorType airtaudio::api::Core::stopStream(void) {
if (verifyStream() != airtaudio::errorNone) {
return airtaudio::errorFail;
}
if (m_stream.state == STREAM_STOPPED) {
ATA_ERROR("airtaudio::api::Core::stopStream(): the stream is already stopped!");
return airtaudio::errorWarning;
}
OSStatus result = noErr;
CoreHandle *handle = (CoreHandle *) m_stream.apiHandle;
if ( m_stream.mode == OUTPUT
|| m_stream.mode == DUPLEX) {
if (handle->drainCounter == 0) {
std::unique_lock<std::mutex> lck(m_stream.mutex);
handle->drainCounter = 2;
handle->condition.wait(lck);
}
result = AudioDeviceStop(handle->id[0], callbackHandler);
if (result != noErr) {
ATA_ERROR("airtaudio::api::Core::stopStream: system error (" << getErrorCode(result) << ") stopping callback procedure on device (" << m_stream.device[0] << ").");
goto unlock;
}
}
if ( m_stream.mode == INPUT
|| ( m_stream.mode == DUPLEX
&& m_stream.device[0] != m_stream.device[1])) {
result = AudioDeviceStop(handle->id[1], callbackHandler);
if (result != noErr) {
ATA_ERROR("airtaudio::api::Core::stopStream: system error (" << getErrorCode(result) << ") stopping input callback procedure on device (" << m_stream.device[1] << ").");
goto unlock;
}
}
m_stream.state = STREAM_STOPPED;
unlock:
if (result == noErr) {
return airtaudio::errorNone;
}
return airtaudio::errorSystemError;
}
enum airtaudio::errorType airtaudio::api::Core::abortStream(void) {
if (verifyStream() != airtaudio::errorNone) {
return airtaudio::errorFail;
}
if (m_stream.state == STREAM_STOPPED) {
ATA_ERROR("airtaudio::api::Core::abortStream(): the stream is already stopped!");
return airtaudio::errorWarning;
}
CoreHandle* handle = (CoreHandle*)m_stream.apiHandle;
handle->drainCounter = 2;
return stopStream();
}
// This function will be called by a spawned thread when the user
// callback function signals that the stream should be stopped or
// aborted. It is better to handle it this way because the
// callbackEvent() function probably should return before the AudioDeviceStop()
// function is called.
static void coreStopStream(void *_ptr) {
airtaudio::CallbackInfo* info = (airtaudio::CallbackInfo*)_ptr;
airtaudio::api::Core* object = (airtaudio::api::Core*)info->object;
object->stopStream();
}
bool airtaudio::api::Core::callbackEvent(AudioDeviceID _deviceId,
const AudioBufferList *_inBufferList,
const AudioBufferList *_outBufferList) {
if ( m_stream.state == STREAM_STOPPED
|| m_stream.state == STREAM_STOPPING) {
return true;
}
if (m_stream.state == STREAM_CLOSED) {
ATA_ERROR("airtaudio::api::Core::callbackEvent(): the stream is closed ... this shouldn't happen!");
return false;
}
CallbackInfo *info = (CallbackInfo *) &m_stream.callbackInfo;
CoreHandle *handle = (CoreHandle *) m_stream.apiHandle;
// Check if we were draining the stream and signal is finished.
if (handle->drainCounter > 3) {
m_stream.state = STREAM_STOPPING;
if (handle->internalDrain == true) {
new std::thread(coreStopStream, info);
} else {
// external call to stopStream()
handle->condition.notify_one();
}
return true;
}
AudioDeviceID outputDevice = handle->id[0];
// Invoke user callback to get fresh output data UNLESS we are
// draining stream or duplex mode AND the input/output devices are
// different AND this function is called for the input device.
if (handle->drainCounter == 0 && (m_stream.mode != DUPLEX || _deviceId == outputDevice)) {
airtaudio::AirTAudioCallback callback = (airtaudio::AirTAudioCallback) info->callback;
double streamTime = getStreamTime();
airtaudio::streamStatus status = 0;
if ( m_stream.mode != INPUT
&& handle->xrun[0] == true) {
status |= OUTPUT_UNDERFLOW;
handle->xrun[0] = false;
}
if ( m_stream.mode != OUTPUT
&& handle->xrun[1] == true) {
status |= INPUT_OVERFLOW;
handle->xrun[1] = false;
}
int32_t cbReturnValue = callback(m_stream.userBuffer[0],
m_stream.userBuffer[1],
m_stream.bufferSize,
streamTime,
status,
info->userData);
if (cbReturnValue == 2) {
m_stream.state = STREAM_STOPPING;
handle->drainCounter = 2;
abortStream();
return true;
} else if (cbReturnValue == 1) {
handle->drainCounter = 1;
handle->internalDrain = true;
}
}
if ( m_stream.mode == OUTPUT
|| ( m_stream.mode == DUPLEX
&& _deviceId == outputDevice)) {
if (handle->drainCounter > 1) {
// write zeros to the output stream
if (handle->nStreams[0] == 1) {
memset(_outBufferList->mBuffers[handle->iStream[0]].mData,
0,
_outBufferList->mBuffers[handle->iStream[0]].mDataByteSize);
} else {
// fill multiple streams with zeros
for (uint32_t i=0; i<handle->nStreams[0]; i++) {
memset(_outBufferList->mBuffers[handle->iStream[0]+i].mData,
0,
_outBufferList->mBuffers[handle->iStream[0]+i].mDataByteSize);
}
}
} else if (handle->nStreams[0] == 1) {
if (m_stream.doConvertBuffer[0]) {
// convert directly to CoreAudio stream buffer
convertBuffer((char*)_outBufferList->mBuffers[handle->iStream[0]].mData,
m_stream.userBuffer[0],
m_stream.convertInfo[0]);
} else {
// copy from user buffer
memcpy(_outBufferList->mBuffers[handle->iStream[0]].mData,
m_stream.userBuffer[0],
_outBufferList->mBuffers[handle->iStream[0]].mDataByteSize);
}
} else {
// fill multiple streams
float *inBuffer = (float *) m_stream.userBuffer[0];
if (m_stream.doConvertBuffer[0]) {
convertBuffer(m_stream.deviceBuffer, m_stream.userBuffer[0], m_stream.convertInfo[0]);
inBuffer = (float *) m_stream.deviceBuffer;
}
if (m_stream.deviceInterleaved[0] == false) { // mono mode
uint32_t bufferBytes = _outBufferList->mBuffers[handle->iStream[0]].mDataByteSize;
for (uint32_t i=0; i<m_stream.nUserChannels[0]; i++) {
memcpy(_outBufferList->mBuffers[handle->iStream[0]+i].mData,
(void *)&inBuffer[i*m_stream.bufferSize],
bufferBytes);
}
} else {
// fill multiple multi-channel streams with interleaved data
uint32_t streamChannels, channelsLeft, inJump, outJump, inOffset;
float *out, *in;
bool inInterleaved = (m_stream.userInterleaved) ? true : false;
uint32_t inChannels = m_stream.nUserChannels[0];
if (m_stream.doConvertBuffer[0]) {
inInterleaved = true; // device buffer will always be interleaved for nStreams > 1 and not mono mode
inChannels = m_stream.nDeviceChannels[0];
}
if (inInterleaved) {
inOffset = 1;
} else {
inOffset = m_stream.bufferSize;
}
channelsLeft = inChannels;
for (uint32_t i=0; i<handle->nStreams[0]; i++) {
in = inBuffer;
out = (float *) _outBufferList->mBuffers[handle->iStream[0]+i].mData;
streamChannels = _outBufferList->mBuffers[handle->iStream[0]+i].mNumberChannels;
outJump = 0;
// Account for possible channel offset in first stream
if (i == 0 && m_stream.channelOffset[0] > 0) {
streamChannels -= m_stream.channelOffset[0];
outJump = m_stream.channelOffset[0];
out += outJump;
}
// Account for possible unfilled channels at end of the last stream
if (streamChannels > channelsLeft) {
outJump = streamChannels - channelsLeft;
streamChannels = channelsLeft;
}
// Determine input buffer offsets and skips
if (inInterleaved) {
inJump = inChannels;
in += inChannels - channelsLeft;
} else {
inJump = 1;
in += (inChannels - channelsLeft) * inOffset;
}
for (uint32_t i=0; i<m_stream.bufferSize; i++) {
for (uint32_t j=0; j<streamChannels; j++) {
*out++ = in[j*inOffset];
}
out += outJump;
in += inJump;
}
channelsLeft -= streamChannels;
}
}
}
if (handle->drainCounter) {
handle->drainCounter++;
goto unlock;
}
}
AudioDeviceID inputDevice;
inputDevice = handle->id[1];
if ( m_stream.mode == INPUT
|| ( m_stream.mode == DUPLEX
&& _deviceId == inputDevice)) {
if (handle->nStreams[1] == 1) {
if (m_stream.doConvertBuffer[1]) {
// convert directly from CoreAudio stream buffer
convertBuffer(m_stream.userBuffer[1],
(char *) _inBufferList->mBuffers[handle->iStream[1]].mData,
m_stream.convertInfo[1]);
} else { // copy to user buffer
memcpy(m_stream.userBuffer[1],
_inBufferList->mBuffers[handle->iStream[1]].mData,
_inBufferList->mBuffers[handle->iStream[1]].mDataByteSize);
}
} else { // read from multiple streams
float *outBuffer = (float *) m_stream.userBuffer[1];
if (m_stream.doConvertBuffer[1]) {
outBuffer = (float *) m_stream.deviceBuffer;
}
if (m_stream.deviceInterleaved[1] == false) {
// mono mode
uint32_t bufferBytes = _inBufferList->mBuffers[handle->iStream[1]].mDataByteSize;
for (uint32_t i=0; i<m_stream.nUserChannels[1]; i++) {
memcpy((void *)&outBuffer[i*m_stream.bufferSize],
_inBufferList->mBuffers[handle->iStream[1]+i].mData,
bufferBytes);
}
} else {
// read from multiple multi-channel streams
uint32_t streamChannels, channelsLeft, inJump, outJump, outOffset;
float *out, *in;
bool outInterleaved = (m_stream.userInterleaved) ? true : false;
uint32_t outChannels = m_stream.nUserChannels[1];
if (m_stream.doConvertBuffer[1]) {
outInterleaved = true; // device buffer will always be interleaved for nStreams > 1 and not mono mode
outChannels = m_stream.nDeviceChannels[1];
}
if (outInterleaved) {
outOffset = 1;
} else {
outOffset = m_stream.bufferSize;
}
channelsLeft = outChannels;
for (uint32_t i=0; i<handle->nStreams[1]; i++) {
out = outBuffer;
in = (float *) _inBufferList->mBuffers[handle->iStream[1]+i].mData;
streamChannels = _inBufferList->mBuffers[handle->iStream[1]+i].mNumberChannels;
inJump = 0;
// Account for possible channel offset in first stream
if (i == 0 && m_stream.channelOffset[1] > 0) {
streamChannels -= m_stream.channelOffset[1];
inJump = m_stream.channelOffset[1];
in += inJump;
}
// Account for possible unread channels at end of the last stream
if (streamChannels > channelsLeft) {
inJump = streamChannels - channelsLeft;
streamChannels = channelsLeft;
}
// Determine output buffer offsets and skips
if (outInterleaved) {
outJump = outChannels;
out += outChannels - channelsLeft;
} else {
outJump = 1;
out += (outChannels - channelsLeft) * outOffset;
}
for (uint32_t i=0; i<m_stream.bufferSize; i++) {
for (uint32_t j=0; j<streamChannels; j++) {
out[j*outOffset] = *in++;
}
out += outJump;
in += inJump;
}
channelsLeft -= streamChannels;
}
}
if (m_stream.doConvertBuffer[1]) { // convert from our internal "device" buffer
convertBuffer(m_stream.userBuffer[1],
m_stream.deviceBuffer,
m_stream.convertInfo[1]);
}
}
}
unlock:
//m_stream.mutex.unlock();
airtaudio::Api::tickStreamTime();
return true;
}
const char* airtaudio::api::Core::getErrorCode(OSStatus _code) {
switch(_code) {
case kAudioHardwareNotRunningError:
return "kAudioHardwareNotRunningError";
case kAudioHardwareUnspecifiedError:
return "kAudioHardwareUnspecifiedError";
case kAudioHardwareUnknownPropertyError:
return "kAudioHardwareUnknownPropertyError";
case kAudioHardwareBadPropertySizeError:
return "kAudioHardwareBadPropertySizeError";
case kAudioHardwareIllegalOperationError:
return "kAudioHardwareIllegalOperationError";
case kAudioHardwareBadObjectError:
return "kAudioHardwareBadObjectError";
case kAudioHardwareBadDeviceError:
return "kAudioHardwareBadDeviceError";
case kAudioHardwareBadStreamError:
return "kAudioHardwareBadStreamError";
case kAudioHardwareUnsupportedOperationError:
return "kAudioHardwareUnsupportedOperationError";
case kAudioDeviceUnsupportedFormatError:
return "kAudioDeviceUnsupportedFormatError";
case kAudioDevicePermissionsError:
return "kAudioDevicePermissionsError";
default:
return "CoreAudio unknown error";
}
}
#endif