audio-orchestra/audio/orchestra/api/Core.cpp

1303 lines
50 KiB
C++

/** @file
* @author Edouard DUPIN
* @copyright 2011, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file)
* @fork from RTAudio
*/
// *************************************************** //
//
// OS/API-specific methods.
//
// *************************************************** //
#if defined(__MACOSX_CORE__) || defined(ORCHESTRA_BUILD_MACOSX_CORE)
#include <audio/orchestra/Interface.h>
#include <audio/orchestra/debug.h>
#include <etk/thread.h>
#include <etk/thread/tools.h>
audio::orchestra::Api* audio::orchestra::api::Core::create() {
return new audio::orchestra::api::Core();
}
#undef __class__
#define __class__ "api::Core"
namespace audio {
namespace orchestra {
namespace api {
class CorePrivate {
public:
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;
std11::condition_variable condition;
int32_t drainCounter; // Tracks callback counts when draining
bool internalDrain; // Indicates if stop is initiated from callback or not.
CorePrivate() :
deviceBuffer(0),
drainCounter(0),
internalDrain(false) {
nStreams[0] = 1;
nStreams[1] = 1;
id[0] = 0;
id[1] = 0;
xrun[0] = false;
xrun[1] = false;
}
};
}
}
}
audio::orchestra::api::Core::Core() :
m_private(new audio::orchestra::api::CorePrivate()) {
#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 = nullptr;
AudioObjectPropertyAddress property = {
kAudioHardwarePropertyRunLoop,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
OSStatus result = AudioObjectSetPropertyData(kAudioObjectSystemObject,
&property,
0,
nullptr,
sizeof(CFRunLoopRef),
&theRunLoop);
if (result != noErr) {
ATA_ERROR("error setting run loop property!");
}
#endif
}
audio::orchestra::api::Core::~Core() {
// The subclass destructor gets called before the base class
// destructor, so close an existing stream before deallocating
// apiDeviceId memory.
if (m_state != audio::orchestra::state_closed) {
closeStream();
}
}
uint32_t audio::orchestra::api::Core::getDeviceCount() {
// Find out how many audio devices there are, if any.
uint32_t dataSize;
AudioObjectPropertyAddress propertyAddress = {
kAudioHardwarePropertyDevices,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
OSStatus result = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, nullptr, &dataSize);
if (result != noErr) {
ATA_ERROR("OS-X error getting device info!");
return 0;
}
return dataSize / sizeof(AudioDeviceID);
}
uint32_t audio::orchestra::api::Core::getDefaultInputDevice() {
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,
nullptr,
&dataSize,
&id);
if (result != noErr) {
ATA_ERROR("OS-X system error getting device.");
return 0;
}
dataSize *= nDevices;
AudioDeviceID deviceList[ nDevices ];
property.mSelector = kAudioHardwarePropertyDevices;
result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
&property,
0,
nullptr,
&dataSize,
(void*)&deviceList);
if (result != noErr) {
ATA_ERROR("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("No default device found!");
return 0;
}
uint32_t audio::orchestra::api::Core::getDefaultOutputDevice() {
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,
nullptr,
&dataSize,
&id);
if (result != noErr) {
ATA_ERROR("OS-X system error getting device.");
return 0;
}
dataSize = sizeof(AudioDeviceID) * nDevices;
AudioDeviceID deviceList[ nDevices ];
property.mSelector = kAudioHardwarePropertyDevices;
result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
&property,
0,
nullptr,
&dataSize,
(void*)&deviceList);
if (result != noErr) {
ATA_ERROR("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("No default device found!");
return 0;
}
audio::orchestra::DeviceInfo audio::orchestra::api::Core::getDeviceInfo(uint32_t _device) {
audio::orchestra::DeviceInfo info;
info.probed = false;
// Get device ID
uint32_t nDevices = getDeviceCount();
if (nDevices == 0) {
ATA_ERROR("no devices found!");
return info;
}
if (_device >= nDevices) {
ATA_ERROR("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,
nullptr,
&dataSize,
(void*)&deviceList);
if (result != noErr) {
ATA_ERROR("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, nullptr, &dataSize, &cfname);
if (result != noErr) {
ATA_ERROR("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, nullptr, &dataSize, &cfname);
if (result != noErr) {
ATA_ERROR("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, nullptr, &dataSize);
if (result != noErr || dataSize == 0) {
ATA_ERROR("system error (" << getErrorCode(result) << ") getting output stream configuration info for device (" << _device << ").");
return info;
}
// Allocate the AudioBufferList.
bufferList = (AudioBufferList *) malloc(dataSize);
if (bufferList == nullptr) {
ATA_ERROR("memory error allocating output AudioBufferList.");
return info;
}
result = AudioObjectGetPropertyData(id, &property, 0, nullptr, &dataSize, bufferList);
if ( result != noErr
|| dataSize == 0) {
free(bufferList);
ATA_ERROR("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, nullptr, &dataSize);
if ( result != noErr
|| dataSize == 0) {
ATA_ERROR("system error (" << getErrorCode(result) << ") getting input stream configuration info for device (" << _device << ").");
return info;
}
// Allocate the AudioBufferList.
bufferList = (AudioBufferList *) malloc(dataSize);
if (bufferList == nullptr) {
ATA_ERROR("memory error allocating input AudioBufferList.");
return info;
}
result = AudioObjectGetPropertyData(id, &property, 0, nullptr, &dataSize, bufferList);
if (result != noErr || dataSize == 0) {
free(bufferList);
ATA_ERROR("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, nullptr, &dataSize);
if ( result != kAudioHardwareNoError
|| dataSize == 0) {
ATA_ERROR("system error (" << getErrorCode(result) << ") getting sample rate info.");
return info;
}
uint32_t nRanges = dataSize / sizeof(AudioValueRange);
AudioValueRange rangeList[ nRanges ];
result = AudioObjectGetPropertyData(id, &property, 0, nullptr, &dataSize, &rangeList);
if (result != kAudioHardwareNoError) {
ATA_ERROR("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 (auto &it : audio::orchestra::genericSampleRate()) {
if ( it >= minimumRate
&& it <= maximumRate) {
info.sampleRates.push_back(it);
}
}
if (info.sampleRates.size() == 0) {
ATA_ERROR("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.push_back(audio::format_float);
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;
}
OSStatus audio::orchestra::api::Core::callbackEvent(AudioDeviceID _inDevice,
const AudioTimeStamp* _inNow,
const AudioBufferList* _inInputData,
const AudioTimeStamp* _inInputTime,
AudioBufferList* _outOutputData,
const AudioTimeStamp* _inOutputTime,
void* _userData) {
audio::orchestra::api::Core* myClass = reinterpret_cast<audio::orchestra::api::Core*>(_userData);
audio::Time inputTime;
audio::Time outputTime;
if (_inInputTime != nullptr) {
inputTime = audio::Time(_inInputTime->mHostTime/1000000000LL, _inInputTime->mHostTime%1000000000LL);
}
if (_inOutputTime != nullptr) {
outputTime = audio::Time(_inOutputTime->mHostTime/1000000000LL, _inOutputTime->mHostTime%1000000000LL);
}
if (myClass->callbackEvent(_inDevice, _inInputData, inputTime, _outOutputData, outputTime) == false) {
return kAudioHardwareUnspecifiedError;
} else {
return kAudioHardwareNoError;
}
}
OSStatus audio::orchestra::api::Core::xrunListener(AudioObjectID _inDevice,
uint32_t _nAddresses,
const AudioObjectPropertyAddress _properties[],
void* _userData) {
audio::orchestra::api::Core* myClass = reinterpret_cast<audio::orchestra::api::Core*>(_userData);
for (uint32_t i=0; i<_nAddresses; i++) {
if (_properties[i].mSelector == kAudioDeviceProcessorOverload) {
if (_properties[i].mScope == kAudioDevicePropertyScopeInput) {
myClass->m_private->xrun[1] = true;
} else {
myClass->m_private->xrun[0] = true;
}
}
}
return kAudioHardwareNoError;
}
static OSStatus rateListener(AudioObjectID _inDevice,
UInt32 _nAddresses,
const AudioObjectPropertyAddress _properties[],
void* _ratePointer) {
double *rate = (double*)_ratePointer;
uint32_t dataSize = sizeof(double);
AudioObjectPropertyAddress property = {
kAudioDevicePropertyNominalSampleRate,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
AudioObjectGetPropertyData(_inDevice, &property, 0, nullptr, &dataSize, rate);
return kAudioHardwareNoError;
}
bool audio::orchestra::api::Core::probeDeviceOpen(uint32_t _device,
audio::orchestra::mode _mode,
uint32_t _channels,
uint32_t _firstChannel,
uint32_t _sampleRate,
audio::format _format,
uint32_t *_bufferSize,
const audio::orchestra::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("no devices found!");
return false;
}
if (_device >= nDevices) {
// This should not happen because a check is made before this function is called.
ATA_ERROR("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,
nullptr,
&dataSize,
(void *) &deviceList);
if (result != noErr) {
ATA_ERROR("OS-X system error getting device IDs.");
return false;
}
AudioDeviceID id = deviceList[ _device ];
// Setup for stream mode.
bool isInput = false;
if (_mode == audio::orchestra::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, nullptr, &dataSize);
if ( result != noErr
|| dataSize == 0) {
ATA_ERROR("system error (" << getErrorCode(result) << ") getting stream configuration info for device (" << _device << ").");
return false;
}
// Allocate the AudioBufferList.
bufferList = (AudioBufferList *) malloc(dataSize);
if (bufferList == nullptr) {
ATA_ERROR("memory error allocating AudioBufferList.");
return false;
}
result = AudioObjectGetPropertyData(id, &property, 0, nullptr, &dataSize, bufferList);
if ( result != noErr
|| dataSize == 0) {
ATA_ERROR("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("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, nullptr, &dataSize, &bufferRange);
if (result != noErr) {
ATA_ERROR("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.flags.m_minimizeLatency == true) {
*_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, nullptr, dataSize, &theSize);
if (result != noErr) {
ATA_ERROR("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_mode == audio::orchestra::mode_output
&& _mode == audio::orchestra::mode_input
&& *_bufferSize != m_bufferSize) {
ATA_ERROR("system error setting buffer size for duplex stream on device (" << _device << ").");
return false;
}
m_bufferSize = *_bufferSize;
m_nBuffers = 1;
// Check and if necessary, change the sample rate for the device.
double nominalRate;
dataSize = sizeof(double);
property.mSelector = kAudioDevicePropertyNominalSampleRate;
result = AudioObjectGetPropertyData(id, &property, 0, nullptr, &dataSize, &nominalRate);
if (result != noErr) {
ATA_ERROR("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("system error (" << getErrorCode(result) << ") setting sample rate property listener for device (" << _device << ").");
return false;
}
nominalRate = (double) _sampleRate;
result = AudioObjectSetPropertyData(id, &property, 0, nullptr, dataSize, &nominalRate);
if (result != noErr) {
ATA_ERROR("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("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, nullptr, &dataSize, &description);
if (result != noErr) {
ATA_ERROR("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, nullptr, dataSize, &description);
if (result != noErr) {
ATA_ERROR("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, nullptr, &dataSize, &description);
if (result != noErr) {
ATA_ERROR("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, nullptr, 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("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, nullptr, &dataSize, &latency);
if (result == kAudioHardwareNoError) {
m_latency[ _mode ] = latency;
} else {
ATA_ERROR("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_doByteSwap[modeToIdTable(_mode)] = false;
// From the CoreAudio documentation, PCM data must be supplied as
// 32-bit floats.
m_userFormat = _format;
m_deviceFormat[modeToIdTable(_mode)] = audio::format_float;
if (streamCount == 1) {
m_nDeviceChannels[modeToIdTable(_mode)] = description.mChannelsPerFrame;
} else {
// multiple streams
m_nDeviceChannels[modeToIdTable(_mode)] = _channels;
}
m_nUserChannels[modeToIdTable(_mode)] = _channels;
m_channelOffset[modeToIdTable(_mode)] = channelOffset; // offset within a CoreAudio stream
m_deviceInterleaved[modeToIdTable(_mode)] = true;
if (monoMode == true) {
m_deviceInterleaved[modeToIdTable(_mode)] = false;
}
// Set flags for buffer conversion.
m_doConvertBuffer[modeToIdTable(_mode)] = false;
if (m_userFormat != m_deviceFormat[modeToIdTable(_mode)]) {
m_doConvertBuffer[modeToIdTable(_mode)] = true;
}
if (m_nUserChannels[modeToIdTable(_mode)] < m_nDeviceChannels[modeToIdTable(_mode)]) {
m_doConvertBuffer[modeToIdTable(_mode)] = true;
}
if (streamCount == 1) {
if ( m_nUserChannels[modeToIdTable(_mode)] > 1
&& m_deviceInterleaved[modeToIdTable(_mode)] == false) {
m_doConvertBuffer[modeToIdTable(_mode)] = true;
}
} else if (monoMode) {
m_doConvertBuffer[modeToIdTable(_mode)] = true;
}
m_private->iStream[modeToIdTable(_mode)] = firstStream;
m_private->nStreams[modeToIdTable(_mode)] = streamCount;
m_private->id[modeToIdTable(_mode)] = id;
// Allocate necessary internal buffers.
uint64_t bufferBytes;
bufferBytes = m_nUserChannels[modeToIdTable(_mode)] * *_bufferSize * audio::getFormatBytes(m_userFormat);
// m_userBuffer[modeToIdTable(_mode)] = (char *) calloc(bufferBytes, 1);
m_userBuffer[modeToIdTable(_mode)].resize(bufferBytes, 0);
if (m_userBuffer[modeToIdTable(_mode)].size() == 0) {
ATA_ERROR("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_doConvertBuffer[modeToIdTable(_mode)]
&& m_private->nStreams[modeToIdTable(_mode)] > 1) {
bool makeBuffer = true;
bufferBytes = m_nDeviceChannels[modeToIdTable(_mode)] * audio::getFormatBytes(m_deviceFormat[modeToIdTable(_mode)]);
if (_mode == audio::orchestra::mode_input) {
if ( m_mode == audio::orchestra::mode_output
&& m_deviceBuffer) {
uint64_t bytesOut = m_nDeviceChannels[0] * audio::getFormatBytes(m_deviceFormat[0]);
if (bufferBytes <= bytesOut) {
makeBuffer = false;
}
}
}
if (makeBuffer) {
bufferBytes *= *_bufferSize;
if (m_deviceBuffer) {
free(m_deviceBuffer);
m_deviceBuffer = nullptr;
}
m_deviceBuffer = (char *) calloc(bufferBytes, 1);
if (m_deviceBuffer == nullptr) {
ATA_ERROR("error allocating device buffer memory.");
goto error;
}
}
}
m_sampleRate = _sampleRate;
m_device[modeToIdTable(_mode)] = _device;
m_state = audio::orchestra::state_stopped;
ATA_VERBOSE("Set state as stopped");
// Setup the buffer conversion information structure.
if (m_doConvertBuffer[modeToIdTable(_mode)]) {
if (streamCount > 1) {
setConvertInfo(_mode, 0);
} else {
setConvertInfo(_mode, channelOffset);
}
}
if ( _mode == audio::orchestra::mode_input
&& m_mode == audio::orchestra::mode_output
&& m_device[0] == _device) {
// Only one callback procedure per device.
m_mode = audio::orchestra::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, &audio::orchestra::api::Core::callbackEvent, this, &m_private->procId[modeToIdTable(_mode)]);
#else
// deprecated in favor of AudioDeviceCreateIOProcID()
result = AudioDeviceAddIOProc(id, &audio::orchestra::api::Core::callbackEvent, this);
#endif
if (result != noErr) {
ATA_ERROR("system error setting callback for device (" << _device << ").");
goto error;
}
if ( m_mode == audio::orchestra::mode_output
&& _mode == audio::orchestra::mode_input) {
m_mode = audio::orchestra::mode_duplex;
} else {
m_mode = _mode;
}
}
// Setup the device property listener for over/underload.
property.mSelector = kAudioDeviceProcessorOverload;
result = AudioObjectAddPropertyListener(id, &property, &audio::orchestra::api::Core::xrunListener, this);
return true;
error:
m_userBuffer[0].clear();
m_userBuffer[1].clear();
if (m_deviceBuffer) {
free(m_deviceBuffer);
m_deviceBuffer = 0;
}
m_state = audio::orchestra::state_closed;
ATA_VERBOSE("Set state as closed");
return false;
}
enum audio::orchestra::error audio::orchestra::api::Core::closeStream() {
if (m_state == audio::orchestra::state_closed) {
ATA_ERROR("no open stream to close!");
return audio::orchestra::error_warning;
}
if ( m_mode == audio::orchestra::mode_output
|| m_mode == audio::orchestra::mode_duplex) {
if (m_state == audio::orchestra::state_running) {
AudioDeviceStop(m_private->id[0], &audio::orchestra::api::Core::callbackEvent);
}
#if defined(MAC_OS_X_VERSION_10_5) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5)
AudioDeviceDestroyIOProcID(m_private->id[0], m_private->procId[0]);
#else
// deprecated in favor of AudioDeviceDestroyIOProcID()
AudioDeviceRemoveIOProc(m_private->id[0], &audio::orchestra::api::Core::callbackEvent);
#endif
}
if ( m_mode == audio::orchestra::mode_input
|| ( m_mode == audio::orchestra::mode_duplex
&& m_device[0] != m_device[1])) {
if (m_state == audio::orchestra::state_running) {
AudioDeviceStop(m_private->id[1], &audio::orchestra::api::Core::callbackEvent);
}
#if defined(MAC_OS_X_VERSION_10_5) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5)
AudioDeviceDestroyIOProcID(m_private->id[1], m_private->procId[1]);
#else
// deprecated in favor of AudioDeviceDestroyIOProcID()
AudioDeviceRemoveIOProc(m_private->id[1], &audio::orchestra::api::Core::callbackEvent);
#endif
}
m_userBuffer[0].clear();
m_userBuffer[1].clear();
if (m_deviceBuffer) {
free(m_deviceBuffer);
m_deviceBuffer = nullptr;
}
m_mode = audio::orchestra::mode_unknow;
m_state = audio::orchestra::state_closed;
ATA_VERBOSE("Set state as closed");
return audio::orchestra::error_none;
}
enum audio::orchestra::error audio::orchestra::api::Core::startStream() {
// TODO : Check return ...
audio::orchestra::Api::startStream();
if (verifyStream() != audio::orchestra::error_none) {
return audio::orchestra::error_fail;
}
if (m_state == audio::orchestra::state_running) {
ATA_ERROR("the stream is already running!");
return audio::orchestra::error_warning;
}
OSStatus result = noErr;
if ( m_mode == audio::orchestra::mode_output
|| m_mode == audio::orchestra::mode_duplex) {
result = AudioDeviceStart(m_private->id[0], &audio::orchestra::api::Core::callbackEvent);
if (result != noErr) {
ATA_ERROR("system error (" << getErrorCode(result) << ") starting callback procedure on device (" << m_device[0] << ").");
goto unlock;
}
}
if ( m_mode == audio::orchestra::mode_input
|| ( m_mode == audio::orchestra::mode_duplex
&& m_device[0] != m_device[1])) {
result = AudioDeviceStart(m_private->id[1], &audio::orchestra::api::Core::callbackEvent);
if (result != noErr) {
ATA_ERROR("system error starting input callback procedure on device (" << m_device[1] << ").");
goto unlock;
}
}
m_private->drainCounter = 0;
m_private->internalDrain = false;
m_state = audio::orchestra::state_running;
ATA_VERBOSE("Set state as running");
unlock:
if (result == noErr) {
return audio::orchestra::error_none;
}
return audio::orchestra::error_systemError;
}
enum audio::orchestra::error audio::orchestra::api::Core::stopStream() {
if (verifyStream() != audio::orchestra::error_none) {
return audio::orchestra::error_fail;
}
if (m_state == audio::orchestra::state_stopped) {
ATA_ERROR("the stream is already stopped!");
return audio::orchestra::error_warning;
}
OSStatus result = noErr;
if ( m_mode == audio::orchestra::mode_output
|| m_mode == audio::orchestra::mode_duplex) {
if (m_private->drainCounter == 0) {
std11::unique_lock<std11::mutex> lck(m_mutex);
m_private->drainCounter = 2;
m_private->condition.wait(lck);
}
result = AudioDeviceStop(m_private->id[0], &audio::orchestra::api::Core::callbackEvent);
if (result != noErr) {
ATA_ERROR("system error (" << getErrorCode(result) << ") stopping callback procedure on device (" << m_device[0] << ").");
goto unlock;
}
}
if ( m_mode == audio::orchestra::mode_input
|| ( m_mode == audio::orchestra::mode_duplex
&& m_device[0] != m_device[1])) {
result = AudioDeviceStop(m_private->id[1], &audio::orchestra::api::Core::callbackEvent);
if (result != noErr) {
ATA_ERROR("system error (" << getErrorCode(result) << ") stopping input callback procedure on device (" << m_device[1] << ").");
goto unlock;
}
}
m_state = audio::orchestra::state_stopped;
ATA_VERBOSE("Set state as stopped");
unlock:
if (result == noErr) {
return audio::orchestra::error_none;
}
return audio::orchestra::error_systemError;
}
enum audio::orchestra::error audio::orchestra::api::Core::abortStream() {
if (verifyStream() != audio::orchestra::error_none) {
return audio::orchestra::error_fail;
}
if (m_state == audio::orchestra::state_stopped) {
ATA_ERROR("the stream is already stopped!");
return audio::orchestra::error_warning;
}
m_private->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.
void audio::orchestra::api::Core::coreStopStream(void *_userData) {
etk::thread::setName("CoreAudio_stopStream");
audio::orchestra::api::Core* myClass = reinterpret_cast<audio::orchestra::api::Core*>(_userData);
myClass->stopStream();
}
bool audio::orchestra::api::Core::callbackEvent(AudioDeviceID _deviceId,
const AudioBufferList *_inBufferList,
const audio::Time& _inTime,
const AudioBufferList *_outBufferList,
const audio::Time& _outTime) {
if ( m_state == audio::orchestra::state_stopped
|| m_state == audio::orchestra::state_stopping) {
return true;
}
if (m_state == audio::orchestra::state_closed) {
ATA_ERROR("the stream is closed ... this shouldn't happen!");
return false;
}
// Check if we were draining the stream and signal is finished.
if (m_private->drainCounter > 3) {
m_state = audio::orchestra::state_stopping;
ATA_VERBOSE("Set state as stopping");
if (m_private->internalDrain == true) {
new std11::thread(&audio::orchestra::api::Core::coreStopStream, this);
} else {
// external call to stopStream()
m_private->condition.notify_one();
}
return true;
}
AudioDeviceID outputDevice = m_private->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 (m_private->drainCounter == 0 && (m_mode != audio::orchestra::mode_duplex || _deviceId == outputDevice)) {
std::vector<enum audio::orchestra::status> status;
if ( m_mode != audio::orchestra::mode_input
&& m_private->xrun[0] == true) {
status.push_back(audio::orchestra::status_underflow);
m_private->xrun[0] = false;
}
if ( m_mode != audio::orchestra::mode_output
&& m_private->xrun[1] == true) {
status.push_back(audio::orchestra::status_overflow);
m_private->xrun[1] = false;
}
int32_t cbReturnValue = m_callback(&m_userBuffer[1][0],
_inTime,
&m_userBuffer[0][0],
_outTime,
m_bufferSize,
status);
if (cbReturnValue == 2) {
m_state = audio::orchestra::state_stopping;
ATA_VERBOSE("Set state as stopping");
m_private->drainCounter = 2;
abortStream();
return true;
} else if (cbReturnValue == 1) {
m_private->drainCounter = 1;
m_private->internalDrain = true;
}
}
if ( m_mode == audio::orchestra::mode_output
|| ( m_mode == audio::orchestra::mode_duplex
&& _deviceId == outputDevice)) {
if (m_private->drainCounter > 1) {
// write zeros to the output stream
if (m_private->nStreams[0] == 1) {
memset(_outBufferList->mBuffers[m_private->iStream[0]].mData,
0,
_outBufferList->mBuffers[m_private->iStream[0]].mDataByteSize);
} else {
// fill multiple streams with zeros
for (uint32_t i=0; i<m_private->nStreams[0]; i++) {
memset(_outBufferList->mBuffers[m_private->iStream[0]+i].mData,
0,
_outBufferList->mBuffers[m_private->iStream[0]+i].mDataByteSize);
}
}
} else if (m_private->nStreams[0] == 1) {
if (m_doConvertBuffer[0]) {
// convert directly to CoreAudio stream buffer
convertBuffer((char*)_outBufferList->mBuffers[m_private->iStream[0]].mData,
&m_userBuffer[0][0],
m_convertInfo[0]);
} else {
// copy from user buffer
memcpy(_outBufferList->mBuffers[m_private->iStream[0]].mData,
&m_userBuffer[0][0],
_outBufferList->mBuffers[m_private->iStream[0]].mDataByteSize);
}
} else {
// fill multiple streams
float *inBuffer = (float *) &m_userBuffer[0][0];
if (m_doConvertBuffer[0]) {
convertBuffer(m_deviceBuffer, &m_userBuffer[0][0], m_convertInfo[0]);
inBuffer = (float *) m_deviceBuffer;
}
if (m_deviceInterleaved[0] == false) { // mono mode
uint32_t bufferBytes = _outBufferList->mBuffers[m_private->iStream[0]].mDataByteSize;
for (uint32_t i=0; i<m_nUserChannels[0]; i++) {
memcpy(_outBufferList->mBuffers[m_private->iStream[0]+i].mData,
(void *)&inBuffer[i*m_bufferSize],
bufferBytes);
}
} else {
// fill multiple multi-channel streams with interleaved data
uint32_t streamChannels, channelsLeft, inJump, outJump, inOffset;
float *out, *in;
bool inInterleaved = true;
uint32_t inChannels = m_nUserChannels[0];
if (m_doConvertBuffer[0]) {
inInterleaved = true; // device buffer will always be interleaved for nStreams > 1 and not mono mode
inChannels = m_nDeviceChannels[0];
}
if (inInterleaved) {
inOffset = 1;
} else {
inOffset = m_bufferSize;
}
channelsLeft = inChannels;
for (uint32_t i=0; i<m_private->nStreams[0]; i++) {
in = inBuffer;
out = (float *) _outBufferList->mBuffers[m_private->iStream[0]+i].mData;
streamChannels = _outBufferList->mBuffers[m_private->iStream[0]+i].mNumberChannels;
outJump = 0;
// Account for possible channel offset in first stream
if (i == 0 && m_channelOffset[0] > 0) {
streamChannels -= m_channelOffset[0];
outJump = m_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_bufferSize; i++) {
for (uint32_t j=0; j<streamChannels; j++) {
*out++ = in[j*inOffset];
}
out += outJump;
in += inJump;
}
channelsLeft -= streamChannels;
}
}
}
if (m_private->drainCounter) {
m_private->drainCounter++;
goto unlock;
}
}
AudioDeviceID inputDevice;
inputDevice = m_private->id[1];
if ( m_mode == audio::orchestra::mode_input
|| ( m_mode == audio::orchestra::mode_duplex
&& _deviceId == inputDevice)) {
if (m_private->nStreams[1] == 1) {
if (m_doConvertBuffer[1]) {
// convert directly from CoreAudio stream buffer
convertBuffer(&m_userBuffer[1][0],
(char *) _inBufferList->mBuffers[m_private->iStream[1]].mData,
m_convertInfo[1]);
} else { // copy to user buffer
memcpy(&m_userBuffer[1][0],
_inBufferList->mBuffers[m_private->iStream[1]].mData,
_inBufferList->mBuffers[m_private->iStream[1]].mDataByteSize);
}
} else { // read from multiple streams
float *outBuffer = (float *) &m_userBuffer[1][0];
if (m_doConvertBuffer[1]) {
outBuffer = (float *) m_deviceBuffer;
}
if (m_deviceInterleaved[1] == false) {
// mono mode
uint32_t bufferBytes = _inBufferList->mBuffers[m_private->iStream[1]].mDataByteSize;
for (uint32_t i=0; i<m_nUserChannels[1]; i++) {
memcpy((void *)&outBuffer[i*m_bufferSize],
_inBufferList->mBuffers[m_private->iStream[1]+i].mData,
bufferBytes);
}
} else {
// read from multiple multi-channel streams
uint32_t streamChannels, channelsLeft, inJump, outJump, outOffset;
float *out, *in;
bool outInterleaved = true;
uint32_t outChannels = m_nUserChannels[1];
if (m_doConvertBuffer[1]) {
outInterleaved = true; // device buffer will always be interleaved for nStreams > 1 and not mono mode
outChannels = m_nDeviceChannels[1];
}
if (outInterleaved) {
outOffset = 1;
} else {
outOffset = m_bufferSize;
}
channelsLeft = outChannels;
for (uint32_t i=0; i<m_private->nStreams[1]; i++) {
out = outBuffer;
in = (float *) _inBufferList->mBuffers[m_private->iStream[1]+i].mData;
streamChannels = _inBufferList->mBuffers[m_private->iStream[1]+i].mNumberChannels;
inJump = 0;
// Account for possible channel offset in first stream
if (i == 0 && m_channelOffset[1] > 0) {
streamChannels -= m_channelOffset[1];
inJump = m_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_bufferSize; i++) {
for (uint32_t j=0; j<streamChannels; j++) {
out[j*outOffset] = *in++;
}
out += outJump;
in += inJump;
}
channelsLeft -= streamChannels;
}
}
if (m_doConvertBuffer[1]) { // convert from our internal "device" buffer
convertBuffer(&m_userBuffer[1][0],
m_deviceBuffer,
m_convertInfo[1]);
}
}
}
unlock:
//m_mutex.unlock();
audio::orchestra::Api::tickStreamTime();
return true;
}
const char* audio::orchestra::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