aboutsummaryrefslogtreecommitdiffstats
path: root/alc/backends/coreaudio.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'alc/backends/coreaudio.cpp')
-rw-r--r--alc/backends/coreaudio.cpp753
1 files changed, 513 insertions, 240 deletions
diff --git a/alc/backends/coreaudio.cpp b/alc/backends/coreaudio.cpp
index 7c18287b..8b0e75fd 100644
--- a/alc/backends/coreaudio.cpp
+++ b/alc/backends/coreaudio.cpp
@@ -20,31 +20,241 @@
#include "config.h"
-#include "backends/coreaudio.h"
+#include "coreaudio.h"
+#include <inttypes.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <unistd.h>
+
+#include <cmath>
+#include <memory>
+#include <string>
-#include "alcmain.h"
-#include "alexcpt.h"
-#include "alu.h"
+#include "alnumeric.h"
+#include "core/converter.h"
+#include "core/device.h"
+#include "core/logging.h"
#include "ringbuffer.h"
-#include "converter.h"
-#include "backends/base.h"
-#include <unistd.h>
#include <AudioUnit/AudioUnit.h>
#include <AudioToolbox/AudioToolbox.h>
namespace {
-static const ALCchar ca_device[] = "CoreAudio Default";
+#if TARGET_OS_IOS || TARGET_OS_TV
+#define CAN_ENUMERATE 0
+#else
+#define CAN_ENUMERATE 1
+#endif
+
+constexpr auto OutputElement = 0;
+constexpr auto InputElement = 1;
+
+#if CAN_ENUMERATE
+struct DeviceEntry {
+ AudioDeviceID mId;
+ std::string mName;
+};
+
+std::vector<DeviceEntry> PlaybackList;
+std::vector<DeviceEntry> CaptureList;
+
+
+OSStatus GetHwProperty(AudioHardwarePropertyID propId, UInt32 dataSize, void *propData)
+{
+ const AudioObjectPropertyAddress addr{propId, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+ return AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr, 0, nullptr, &dataSize,
+ propData);
+}
+
+OSStatus GetHwPropertySize(AudioHardwarePropertyID propId, UInt32 *outSize)
+{
+ const AudioObjectPropertyAddress addr{propId, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+ return AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &addr, 0, nullptr, outSize);
+}
+
+OSStatus GetDevProperty(AudioDeviceID devId, AudioDevicePropertyID propId, bool isCapture,
+ UInt32 elem, UInt32 dataSize, void *propData)
+{
+ static const AudioObjectPropertyScope scopes[2]{kAudioDevicePropertyScopeOutput,
+ kAudioDevicePropertyScopeInput};
+ const AudioObjectPropertyAddress addr{propId, scopes[isCapture], elem};
+ return AudioObjectGetPropertyData(devId, &addr, 0, nullptr, &dataSize, propData);
+}
+
+OSStatus GetDevPropertySize(AudioDeviceID devId, AudioDevicePropertyID inPropertyID,
+ bool isCapture, UInt32 elem, UInt32 *outSize)
+{
+ static const AudioObjectPropertyScope scopes[2]{kAudioDevicePropertyScopeOutput,
+ kAudioDevicePropertyScopeInput};
+ const AudioObjectPropertyAddress addr{inPropertyID, scopes[isCapture], elem};
+ return AudioObjectGetPropertyDataSize(devId, &addr, 0, nullptr, outSize);
+}
+
+
+std::string GetDeviceName(AudioDeviceID devId)
+{
+ std::string devname;
+ CFStringRef nameRef;
+
+ /* Try to get the device name as a CFString, for Unicode name support. */
+ OSStatus err{GetDevProperty(devId, kAudioDevicePropertyDeviceNameCFString, false, 0,
+ sizeof(nameRef), &nameRef)};
+ if(err == noErr)
+ {
+ const CFIndex propSize{CFStringGetMaximumSizeForEncoding(CFStringGetLength(nameRef),
+ kCFStringEncodingUTF8)};
+ devname.resize(static_cast<size_t>(propSize)+1, '\0');
+
+ CFStringGetCString(nameRef, &devname[0], propSize+1, kCFStringEncodingUTF8);
+ CFRelease(nameRef);
+ }
+ else
+ {
+ /* If that failed, just get the C string. Hopefully there's nothing bad
+ * with this.
+ */
+ UInt32 propSize{};
+ if(GetDevPropertySize(devId, kAudioDevicePropertyDeviceName, false, 0, &propSize))
+ return devname;
+
+ devname.resize(propSize+1, '\0');
+ if(GetDevProperty(devId, kAudioDevicePropertyDeviceName, false, 0, propSize, &devname[0]))
+ {
+ devname.clear();
+ return devname;
+ }
+ }
+
+ /* Clear extraneous nul chars that may have been written with the name
+ * string, and return it.
+ */
+ while(!devname.back())
+ devname.pop_back();
+ return devname;
+}
+
+UInt32 GetDeviceChannelCount(AudioDeviceID devId, bool isCapture)
+{
+ UInt32 propSize{};
+ auto err = GetDevPropertySize(devId, kAudioDevicePropertyStreamConfiguration, isCapture, 0,
+ &propSize);
+ if(err)
+ {
+ ERR("kAudioDevicePropertyStreamConfiguration size query failed: %u\n", err);
+ return 0;
+ }
+
+ auto buflist_data = std::make_unique<char[]>(propSize);
+ auto *buflist = reinterpret_cast<AudioBufferList*>(buflist_data.get());
+
+ err = GetDevProperty(devId, kAudioDevicePropertyStreamConfiguration, isCapture, 0, propSize,
+ buflist);
+ if(err)
+ {
+ ERR("kAudioDevicePropertyStreamConfiguration query failed: %u\n", err);
+ return 0;
+ }
+
+ UInt32 numChannels{0};
+ for(size_t i{0};i < buflist->mNumberBuffers;++i)
+ numChannels += buflist->mBuffers[i].mNumberChannels;
+
+ return numChannels;
+}
+
+
+void EnumerateDevices(std::vector<DeviceEntry> &list, bool isCapture)
+{
+ UInt32 propSize{};
+ if(auto err = GetHwPropertySize(kAudioHardwarePropertyDevices, &propSize))
+ {
+ ERR("Failed to get device list size: %u\n", err);
+ return;
+ }
+
+ auto devIds = std::vector<AudioDeviceID>(propSize/sizeof(AudioDeviceID), kAudioDeviceUnknown);
+ if(auto err = GetHwProperty(kAudioHardwarePropertyDevices, propSize, devIds.data()))
+ {
+ ERR("Failed to get device list: %u\n", err);
+ return;
+ }
+
+ std::vector<DeviceEntry> newdevs;
+ newdevs.reserve(devIds.size());
+
+ AudioDeviceID defaultId{kAudioDeviceUnknown};
+ GetHwProperty(isCapture ? kAudioHardwarePropertyDefaultInputDevice :
+ kAudioHardwarePropertyDefaultOutputDevice, sizeof(defaultId), &defaultId);
+
+ if(defaultId != kAudioDeviceUnknown)
+ {
+ newdevs.emplace_back(DeviceEntry{defaultId, GetDeviceName(defaultId)});
+ const auto &entry = newdevs.back();
+ TRACE("Got device: %s = ID %u\n", entry.mName.c_str(), entry.mId);
+ }
+ for(const AudioDeviceID devId : devIds)
+ {
+ if(devId == kAudioDeviceUnknown)
+ continue;
+
+ auto match_devid = [devId](const DeviceEntry &entry) noexcept -> bool
+ { return entry.mId == devId; };
+ auto match = std::find_if(newdevs.cbegin(), newdevs.cend(), match_devid);
+ if(match != newdevs.cend()) continue;
+
+ auto numChannels = GetDeviceChannelCount(devId, isCapture);
+ if(numChannels > 0)
+ {
+ newdevs.emplace_back(DeviceEntry{devId, GetDeviceName(devId)});
+ const auto &entry = newdevs.back();
+ TRACE("Got device: %s = ID %u\n", entry.mName.c_str(), entry.mId);
+ }
+ }
+
+ if(newdevs.size() > 1)
+ {
+ /* Rename entries that have matching names, by appending '#2', '#3',
+ * etc, as needed.
+ */
+ for(auto curitem = newdevs.begin()+1;curitem != newdevs.end();++curitem)
+ {
+ auto check_match = [curitem](const DeviceEntry &entry) -> bool
+ { return entry.mName == curitem->mName; };
+ if(std::find_if(newdevs.begin(), curitem, check_match) != curitem)
+ {
+ std::string name{curitem->mName};
+ size_t count{1};
+ auto check_name = [&name](const DeviceEntry &entry) -> bool
+ { return entry.mName == name; };
+ do {
+ name = curitem->mName;
+ name += " #";
+ name += std::to_string(++count);
+ } while(std::find_if(newdevs.begin(), curitem, check_name) != curitem);
+ curitem->mName = std::move(name);
+ }
+ }
+ }
+
+ newdevs.shrink_to_fit();
+ newdevs.swap(list);
+}
+
+#else
+
+static constexpr char ca_device[] = "CoreAudio Default";
+#endif
struct CoreAudioPlayback final : public BackendBase {
- CoreAudioPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
+ CoreAudioPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~CoreAudioPlayback() override;
OSStatus MixerProc(AudioUnitRenderActionFlags *ioActionFlags,
@@ -58,14 +268,14 @@ struct CoreAudioPlayback final : public BackendBase {
inBusNumber, inNumberFrames, ioData);
}
- void open(const ALCchar *name) override;
+ void open(const char *name) override;
bool reset() override;
- bool start() override;
+ void start() override;
void stop() override;
AudioUnit mAudioUnit{};
- ALuint mFrameSize{0u};
+ uint mFrameSize{0u};
AudioStreamBasicDescription mFormat{}; // This is the OpenAL format as a CoreAudio ASBD
DEF_NEWDEL(CoreAudioPlayback)
@@ -81,26 +291,53 @@ CoreAudioPlayback::~CoreAudioPlayback()
OSStatus CoreAudioPlayback::MixerProc(AudioUnitRenderActionFlags*, const AudioTimeStamp*, UInt32,
UInt32, AudioBufferList *ioData) noexcept
{
- std::lock_guard<CoreAudioPlayback> _{*this};
- aluMixData(mDevice, ioData->mBuffers[0].mData, ioData->mBuffers[0].mDataByteSize/mFrameSize);
+ for(size_t i{0};i < ioData->mNumberBuffers;++i)
+ {
+ auto &buffer = ioData->mBuffers[i];
+ mDevice->renderSamples(buffer.mData, buffer.mDataByteSize/mFrameSize,
+ buffer.mNumberChannels);
+ }
return noErr;
}
-void CoreAudioPlayback::open(const ALCchar *name)
+void CoreAudioPlayback::open(const char *name)
{
+#if CAN_ENUMERATE
+ AudioDeviceID audioDevice{kAudioDeviceUnknown};
+ if(!name)
+ GetHwProperty(kAudioHardwarePropertyDefaultOutputDevice, sizeof(audioDevice),
+ &audioDevice);
+ else
+ {
+ if(PlaybackList.empty())
+ EnumerateDevices(PlaybackList, false);
+
+ auto find_name = [name](const DeviceEntry &entry) -> bool
+ { return entry.mName == name; };
+ auto devmatch = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), find_name);
+ if(devmatch == PlaybackList.cend())
+ throw al::backend_exception{al::backend_error::NoDevice,
+ "Device name \"%s\" not found", name};
+
+ audioDevice = devmatch->mId;
+ }
+#else
if(!name)
name = ca_device;
else if(strcmp(name, ca_device) != 0)
- throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
+ throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
+ name};
+#endif
/* open the default output unit */
AudioComponentDescription desc{};
desc.componentType = kAudioUnitType_Output;
-#if TARGET_OS_IOS
- desc.componentSubType = kAudioUnitSubType_RemoteIO;
+#if CAN_ENUMERATE
+ desc.componentSubType = (audioDevice == kAudioDeviceUnknown) ?
+ kAudioUnitSubType_DefaultOutput : kAudioUnitSubType_HALOutput;
#else
- desc.componentSubType = kAudioUnitSubType_DefaultOutput;
+ desc.componentSubType = kAudioUnitSubType_RemoteIO;
#endif
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0;
@@ -108,19 +345,52 @@ void CoreAudioPlayback::open(const ALCchar *name)
AudioComponent comp{AudioComponentFindNext(NULL, &desc)};
if(comp == nullptr)
- throw al::backend_exception{ALC_INVALID_VALUE, "Could not find audio component"};
+ throw al::backend_exception{al::backend_error::NoDevice, "Could not find audio component"};
- OSStatus err{AudioComponentInstanceNew(comp, &mAudioUnit)};
+ AudioUnit audioUnit{};
+ OSStatus err{AudioComponentInstanceNew(comp, &audioUnit)};
if(err != noErr)
- throw al::backend_exception{ALC_INVALID_VALUE, "Could not create component instance: %u",
- err};
+ throw al::backend_exception{al::backend_error::NoDevice,
+ "Could not create component instance: %u", err};
- /* init and start the default audio unit... */
- err = AudioUnitInitialize(mAudioUnit);
+#if CAN_ENUMERATE
+ if(audioDevice != kAudioDeviceUnknown)
+ AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_CurrentDevice,
+ kAudioUnitScope_Global, OutputElement, &audioDevice, sizeof(AudioDeviceID));
+#endif
+
+ err = AudioUnitInitialize(audioUnit);
if(err != noErr)
- throw al::backend_exception{ALC_INVALID_VALUE, "Could not initialize audio unit: %u", err};
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Could not initialize audio unit: %u", err};
+ /* WARNING: I don't know if "valid" audio unit values are guaranteed to be
+ * non-0. If not, this logic is broken.
+ */
+ if(mAudioUnit)
+ {
+ AudioUnitUninitialize(mAudioUnit);
+ AudioComponentInstanceDispose(mAudioUnit);
+ }
+ mAudioUnit = audioUnit;
+
+#if CAN_ENUMERATE
+ if(name)
+ mDevice->DeviceName = name;
+ else
+ {
+ UInt32 propSize{sizeof(audioDevice)};
+ audioDevice = kAudioDeviceUnknown;
+ AudioUnitGetProperty(audioUnit, kAudioOutputUnitProperty_CurrentDevice,
+ kAudioUnitScope_Global, OutputElement, &audioDevice, &propSize);
+
+ std::string devname{GetDeviceName(audioDevice)};
+ if(!devname.empty()) mDevice->DeviceName = std::move(devname);
+ else mDevice->DeviceName = "Unknown Device Name";
+ }
+#else
mDevice->DeviceName = name;
+#endif
}
bool CoreAudioPlayback::reset()
@@ -131,10 +401,10 @@ bool CoreAudioPlayback::reset()
/* retrieve default output unit's properties (output side) */
AudioStreamBasicDescription streamFormat{};
- auto size = static_cast<UInt32>(sizeof(AudioStreamBasicDescription));
+ UInt32 size{sizeof(streamFormat)};
err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output,
- 0, &streamFormat, &size);
- if(err != noErr || size != sizeof(AudioStreamBasicDescription))
+ OutputElement, &streamFormat, &size);
+ if(err != noErr || size != sizeof(streamFormat))
{
ERR("AudioUnitGetProperty failed\n");
return false;
@@ -150,99 +420,65 @@ bool CoreAudioPlayback::reset()
TRACE(" streamFormat.mSampleRate = %5.0f\n", streamFormat.mSampleRate);
#endif
- /* set default output unit's input side to match output side */
- err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
- 0, &streamFormat, size);
- if(err != noErr)
- {
- ERR("AudioUnitSetProperty failed\n");
- return false;
- }
-
+ /* Use the sample rate from the output unit's current parameters, but reset
+ * everything else.
+ */
if(mDevice->Frequency != streamFormat.mSampleRate)
{
- mDevice->BufferSize = static_cast<ALuint>(uint64_t{mDevice->BufferSize} *
- streamFormat.mSampleRate / mDevice->Frequency);
- mDevice->Frequency = static_cast<ALuint>(streamFormat.mSampleRate);
+ mDevice->BufferSize = static_cast<uint>(mDevice->BufferSize*streamFormat.mSampleRate/
+ mDevice->Frequency + 0.5);
+ mDevice->Frequency = static_cast<uint>(streamFormat.mSampleRate);
}
/* FIXME: How to tell what channels are what in the output device, and how
- * to specify what we're giving? eg, 6.0 vs 5.1 */
- switch(streamFormat.mChannelsPerFrame)
- {
- case 1:
- mDevice->FmtChans = DevFmtMono;
- break;
- case 2:
- mDevice->FmtChans = DevFmtStereo;
- break;
- case 4:
- mDevice->FmtChans = DevFmtQuad;
- break;
- case 6:
- mDevice->FmtChans = DevFmtX51;
- break;
- case 7:
- mDevice->FmtChans = DevFmtX61;
- break;
- case 8:
- mDevice->FmtChans = DevFmtX71;
- break;
- default:
- ERR("Unhandled channel count (%d), using Stereo\n", streamFormat.mChannelsPerFrame);
- mDevice->FmtChans = DevFmtStereo;
- streamFormat.mChannelsPerFrame = 2;
- break;
- }
- SetDefaultWFXChannelOrder(mDevice);
+ * to specify what we're giving? e.g. 6.0 vs 5.1
+ */
+ streamFormat.mChannelsPerFrame = mDevice->channelsFromFmt();
- /* use channel count and sample rate from the default output unit's current
- * parameters, but reset everything else */
streamFormat.mFramesPerPacket = 1;
- streamFormat.mFormatFlags = 0;
+ streamFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kLinearPCMFormatFlagIsPacked;
+ streamFormat.mFormatID = kAudioFormatLinearPCM;
switch(mDevice->FmtType)
{
case DevFmtUByte:
mDevice->FmtType = DevFmtByte;
/* fall-through */
case DevFmtByte:
- streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
+ streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
streamFormat.mBitsPerChannel = 8;
break;
case DevFmtUShort:
mDevice->FmtType = DevFmtShort;
/* fall-through */
case DevFmtShort:
- streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
+ streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
streamFormat.mBitsPerChannel = 16;
break;
case DevFmtUInt:
mDevice->FmtType = DevFmtInt;
/* fall-through */
case DevFmtInt:
- streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
+ streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
streamFormat.mBitsPerChannel = 32;
break;
case DevFmtFloat:
- streamFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat;
+ streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsFloat;
streamFormat.mBitsPerChannel = 32;
break;
}
- streamFormat.mBytesPerFrame = streamFormat.mChannelsPerFrame *
- streamFormat.mBitsPerChannel / 8;
- streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame;
- streamFormat.mFormatID = kAudioFormatLinearPCM;
- streamFormat.mFormatFlags |= kAudioFormatFlagsNativeEndian |
- kLinearPCMFormatFlagIsPacked;
+ streamFormat.mBytesPerFrame = streamFormat.mChannelsPerFrame*streamFormat.mBitsPerChannel/8;
+ streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame*streamFormat.mFramesPerPacket;
err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
- 0, &streamFormat, sizeof(AudioStreamBasicDescription));
+ OutputElement, &streamFormat, sizeof(streamFormat));
if(err != noErr)
{
ERR("AudioUnitSetProperty failed\n");
return false;
}
+ setDefaultWFXChannelOrder();
+
/* setup callback */
mFrameSize = mDevice->frameSizeFromFmt();
AURenderCallbackStruct input{};
@@ -250,7 +486,7 @@ bool CoreAudioPlayback::reset()
input.inputProcRefCon = this;
err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_SetRenderCallback,
- kAudioUnitScope_Input, 0, &input, sizeof(AURenderCallbackStruct));
+ kAudioUnitScope_Input, OutputElement, &input, sizeof(AURenderCallbackStruct));
if(err != noErr)
{
ERR("AudioUnitSetProperty failed\n");
@@ -268,15 +504,12 @@ bool CoreAudioPlayback::reset()
return true;
}
-bool CoreAudioPlayback::start()
+void CoreAudioPlayback::start()
{
- OSStatus err{AudioOutputUnitStart(mAudioUnit)};
+ const OSStatus err{AudioOutputUnitStart(mAudioUnit)};
if(err != noErr)
- {
- ERR("AudioOutputUnitStart failed\n");
- return false;
- }
- return true;
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "AudioOutputUnitStart failed: %d", err};
}
void CoreAudioPlayback::stop()
@@ -288,7 +521,7 @@ void CoreAudioPlayback::stop()
struct CoreAudioCapture final : public BackendBase {
- CoreAudioCapture(ALCdevice *device) noexcept : BackendBase{device} { }
+ CoreAudioCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~CoreAudioCapture() override;
OSStatus RecordProc(AudioUnitRenderActionFlags *ioActionFlags,
@@ -302,19 +535,21 @@ struct CoreAudioCapture final : public BackendBase {
inBusNumber, inNumberFrames, ioData);
}
- void open(const ALCchar *name) override;
- bool start() override;
+ void open(const char *name) override;
+ void start() override;
void stop() override;
- ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
- ALCuint availableSamples() override;
+ void captureSamples(al::byte *buffer, uint samples) override;
+ uint availableSamples() override;
AudioUnit mAudioUnit{0};
- ALuint mFrameSize{0u};
+ uint mFrameSize{0u};
AudioStreamBasicDescription mFormat{}; // This is the OpenAL format as a CoreAudio ASBD
SampleConverterPtr mConverter;
+ al::vector<char> mCaptureData;
+
RingBufferPtr mRing{nullptr};
DEF_NEWDEL(CoreAudioCapture)
@@ -328,181 +563,176 @@ CoreAudioCapture::~CoreAudioCapture()
}
-OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags*,
- const AudioTimeStamp *inTimeStamp, UInt32, UInt32 inNumberFrames,
+OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags *ioActionFlags,
+ const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
AudioBufferList*) noexcept
{
- AudioUnitRenderActionFlags flags = 0;
union {
- al::byte _[sizeof(AudioBufferList) + sizeof(AudioBuffer)*2];
+ al::byte _[maxz(sizeof(AudioBufferList), offsetof(AudioBufferList, mBuffers[1]))];
AudioBufferList list;
} audiobuf{};
- auto rec_vec = mRing->getWriteVector();
- inNumberFrames = static_cast<UInt32>(minz(inNumberFrames,
- rec_vec.first.len+rec_vec.second.len));
+ audiobuf.list.mNumberBuffers = 1;
+ audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame;
+ audiobuf.list.mBuffers[0].mData = mCaptureData.data();
+ audiobuf.list.mBuffers[0].mDataByteSize = static_cast<UInt32>(mCaptureData.size());
- // Fill the ringbuffer's two segments with data from the input device
- if(rec_vec.first.len >= inNumberFrames)
- {
- audiobuf.list.mNumberBuffers = 1;
- audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame;
- audiobuf.list.mBuffers[0].mData = rec_vec.first.buf;
- audiobuf.list.mBuffers[0].mDataByteSize = inNumberFrames * mFormat.mBytesPerFrame;
- }
- else
- {
- const auto remaining = static_cast<ALuint>(inNumberFrames - rec_vec.first.len);
- audiobuf.list.mNumberBuffers = 2;
- audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame;
- audiobuf.list.mBuffers[0].mData = rec_vec.first.buf;
- audiobuf.list.mBuffers[0].mDataByteSize = static_cast<UInt32>(rec_vec.first.len) *
- mFormat.mBytesPerFrame;
- audiobuf.list.mBuffers[1].mNumberChannels = mFormat.mChannelsPerFrame;
- audiobuf.list.mBuffers[1].mData = rec_vec.second.buf;
- audiobuf.list.mBuffers[1].mDataByteSize = remaining * mFormat.mBytesPerFrame;
- }
- OSStatus err{AudioUnitRender(mAudioUnit, &flags, inTimeStamp, audiobuf.list.mNumberBuffers,
+ OSStatus err{AudioUnitRender(mAudioUnit, ioActionFlags, inTimeStamp, inBusNumber,
inNumberFrames, &audiobuf.list)};
if(err != noErr)
{
- ERR("AudioUnitRender error: %d\n", err);
+ ERR("AudioUnitRender capture error: %d\n", err);
return err;
}
- mRing->writeAdvance(inNumberFrames);
+ mRing->write(mCaptureData.data(), inNumberFrames);
return noErr;
}
-void CoreAudioCapture::open(const ALCchar *name)
+void CoreAudioCapture::open(const char *name)
{
- AudioStreamBasicDescription requestedFormat; // The application requested format
- AudioStreamBasicDescription hardwareFormat; // The hardware format
- AudioStreamBasicDescription outputFormat; // The AudioUnit output format
- AURenderCallbackStruct input;
- AudioComponentDescription desc;
- UInt32 outputFrameCount;
- UInt32 propertySize;
-#if !TARGET_OS_IOS
- AudioObjectPropertyAddress propertyAddress;
-#endif
- UInt32 enableIO;
- AudioComponent comp;
- OSStatus err;
+#if CAN_ENUMERATE
+ AudioDeviceID audioDevice{kAudioDeviceUnknown};
+ if(!name)
+ GetHwProperty(kAudioHardwarePropertyDefaultInputDevice, sizeof(audioDevice),
+ &audioDevice);
+ else
+ {
+ if(CaptureList.empty())
+ EnumerateDevices(CaptureList, true);
+
+ auto find_name = [name](const DeviceEntry &entry) -> bool
+ { return entry.mName == name; };
+ auto devmatch = std::find_if(CaptureList.cbegin(), CaptureList.cend(), find_name);
+ if(devmatch == CaptureList.cend())
+ throw al::backend_exception{al::backend_error::NoDevice,
+ "Device name \"%s\" not found", name};
+ audioDevice = devmatch->mId;
+ }
+#else
if(!name)
name = ca_device;
else if(strcmp(name, ca_device) != 0)
- throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
+ throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
+ name};
+#endif
+ AudioComponentDescription desc{};
desc.componentType = kAudioUnitType_Output;
-#if TARGET_OS_IOS
- desc.componentSubType = kAudioUnitSubType_RemoteIO;
+#if CAN_ENUMERATE
+ desc.componentSubType = (audioDevice == kAudioDeviceUnknown) ?
+ kAudioUnitSubType_DefaultOutput : kAudioUnitSubType_HALOutput;
#else
- desc.componentSubType = kAudioUnitSubType_HALOutput;
+ desc.componentSubType = kAudioUnitSubType_RemoteIO;
#endif
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
// Search for component with given description
- comp = AudioComponentFindNext(NULL, &desc);
+ AudioComponent comp{AudioComponentFindNext(NULL, &desc)};
if(comp == NULL)
- throw al::backend_exception{ALC_INVALID_VALUE, "Could not find audio component"};
+ throw al::backend_exception{al::backend_error::NoDevice, "Could not find audio component"};
// Open the component
- err = AudioComponentInstanceNew(comp, &mAudioUnit);
+ OSStatus err{AudioComponentInstanceNew(comp, &mAudioUnit)};
if(err != noErr)
- throw al::backend_exception{ALC_INVALID_VALUE, "Could not create component instance: %u",
- err};
+ throw al::backend_exception{al::backend_error::NoDevice,
+ "Could not create component instance: %u", err};
// Turn off AudioUnit output
- enableIO = 0;
+ UInt32 enableIO{0};
err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO,
- kAudioUnitScope_Output, 0, &enableIO, sizeof(ALuint));
+ kAudioUnitScope_Output, OutputElement, &enableIO, sizeof(enableIO));
if(err != noErr)
- throw al::backend_exception{ALC_INVALID_VALUE,
+ throw al::backend_exception{al::backend_error::DeviceError,
"Could not disable audio unit output property: %u", err};
// Turn on AudioUnit input
enableIO = 1;
err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO,
- kAudioUnitScope_Input, 1, &enableIO, sizeof(ALuint));
+ kAudioUnitScope_Input, InputElement, &enableIO, sizeof(enableIO));
if(err != noErr)
- throw al::backend_exception{ALC_INVALID_VALUE,
+ throw al::backend_exception{al::backend_error::DeviceError,
"Could not enable audio unit input property: %u", err};
-#if !TARGET_OS_IOS
- {
- // Get the default input device
- AudioDeviceID inputDevice = kAudioDeviceUnknown;
-
- propertySize = sizeof(AudioDeviceID);
- propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;
- propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
- propertyAddress.mElement = kAudioObjectPropertyElementMaster;
-
- err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, nullptr,
- &propertySize, &inputDevice);
- if(err != noErr)
- throw al::backend_exception{ALC_INVALID_VALUE, "Could not get input device: %u", err};
- if(inputDevice == kAudioDeviceUnknown)
- throw al::backend_exception{ALC_INVALID_VALUE, "Unknown input device"};
-
- // Track the input device
- err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice,
- kAudioUnitScope_Global, 0, &inputDevice, sizeof(AudioDeviceID));
- if(err != noErr)
- throw al::backend_exception{ALC_INVALID_VALUE, "Could not set input device: %u", err};
- }
+#if CAN_ENUMERATE
+ if(audioDevice != kAudioDeviceUnknown)
+ AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice,
+ kAudioUnitScope_Global, InputElement, &audioDevice, sizeof(AudioDeviceID));
#endif
// set capture callback
+ AURenderCallbackStruct input{};
input.inputProc = CoreAudioCapture::RecordProcC;
input.inputProcRefCon = this;
err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_SetInputCallback,
- kAudioUnitScope_Global, 0, &input, sizeof(AURenderCallbackStruct));
+ kAudioUnitScope_Global, InputElement, &input, sizeof(AURenderCallbackStruct));
+ if(err != noErr)
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Could not set capture callback: %u", err};
+
+ // Disable buffer allocation for capture
+ UInt32 flag{0};
+ err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_ShouldAllocateBuffer,
+ kAudioUnitScope_Output, InputElement, &flag, sizeof(flag));
if(err != noErr)
- throw al::backend_exception{ALC_INVALID_VALUE, "Could not set capture callback: %u", err};
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Could not disable buffer allocation property: %u", err};
// Initialize the device
err = AudioUnitInitialize(mAudioUnit);
if(err != noErr)
- throw al::backend_exception{ALC_INVALID_VALUE, "Could not initialize audio unit: %u", err};
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Could not initialize audio unit: %u", err};
// Get the hardware format
- propertySize = sizeof(AudioStreamBasicDescription);
+ AudioStreamBasicDescription hardwareFormat{};
+ UInt32 propertySize{sizeof(hardwareFormat)};
err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
- 1, &hardwareFormat, &propertySize);
- if(err != noErr || propertySize != sizeof(AudioStreamBasicDescription))
- throw al::backend_exception{ALC_INVALID_VALUE, "Could not get input format: %u", err};
+ InputElement, &hardwareFormat, &propertySize);
+ if(err != noErr || propertySize != sizeof(hardwareFormat))
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Could not get input format: %u", err};
// Set up the requested format description
+ AudioStreamBasicDescription requestedFormat{};
switch(mDevice->FmtType)
{
+ case DevFmtByte:
+ requestedFormat.mBitsPerChannel = 8;
+ requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
+ break;
case DevFmtUByte:
requestedFormat.mBitsPerChannel = 8;
requestedFormat.mFormatFlags = kAudioFormatFlagIsPacked;
break;
case DevFmtShort:
requestedFormat.mBitsPerChannel = 16;
- requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
+ requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger
+ | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
+ break;
+ case DevFmtUShort:
+ requestedFormat.mBitsPerChannel = 16;
+ requestedFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
break;
case DevFmtInt:
requestedFormat.mBitsPerChannel = 32;
- requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
+ requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger
+ | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
+ break;
+ case DevFmtUInt:
+ requestedFormat.mBitsPerChannel = 32;
+ requestedFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
break;
case DevFmtFloat:
requestedFormat.mBitsPerChannel = 32;
- requestedFormat.mFormatFlags = kAudioFormatFlagIsPacked;
+ requestedFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat | kAudioFormatFlagsNativeEndian
+ | kAudioFormatFlagIsPacked;
break;
- case DevFmtByte:
- case DevFmtUShort:
- case DevFmtUInt:
- throw al::backend_exception{ALC_INVALID_VALUE, "%s samples not suppoted",
- DevFmtTypeString(mDevice->FmtType)};
}
switch(mDevice->FmtChans)
@@ -516,11 +746,12 @@ void CoreAudioCapture::open(const ALCchar *name)
case DevFmtQuad:
case DevFmtX51:
- case DevFmtX51Rear:
case DevFmtX61:
case DevFmtX71:
+ case DevFmtX714:
+ case DevFmtX3D71:
case DevFmtAmbi3D:
- throw al::backend_exception{ALC_INVALID_VALUE, "%s not supported",
+ throw al::backend_exception{al::backend_error::DeviceError, "%s not supported",
DevFmtChannelsString(mDevice->FmtChans)};
}
@@ -537,53 +768,73 @@ void CoreAudioCapture::open(const ALCchar *name)
// Use intermediate format for sample rate conversion (outputFormat)
// Set sample rate to the same as hardware for resampling later
- outputFormat = requestedFormat;
+ AudioStreamBasicDescription outputFormat{requestedFormat};
outputFormat.mSampleRate = hardwareFormat.mSampleRate;
// The output format should be the requested format, but using the hardware sample rate
// This is because the AudioUnit will automatically scale other properties, except for sample rate
err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output,
- 1, &outputFormat, sizeof(outputFormat));
+ InputElement, &outputFormat, sizeof(outputFormat));
if(err != noErr)
- throw al::backend_exception{ALC_INVALID_VALUE, "Could not set input format: %u", err};
-
- // Set the AudioUnit output format frame count
- uint64_t FrameCount64{mDevice->UpdateSize};
- FrameCount64 = static_cast<uint64_t>(FrameCount64*outputFormat.mSampleRate + mDevice->Frequency-1) /
- mDevice->Frequency;
- FrameCount64 += MAX_RESAMPLER_PADDING;
- if(FrameCount64 > std::numeric_limits<uint32_t>::max()/2)
- throw al::backend_exception{ALC_INVALID_VALUE,
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Could not set input format: %u", err};
+
+ /* Calculate the minimum AudioUnit output format frame count for the pre-
+ * conversion ring buffer. Ensure at least 100ms for the total buffer.
+ */
+ double srateScale{outputFormat.mSampleRate / mDevice->Frequency};
+ auto FrameCount64 = maxu64(static_cast<uint64_t>(std::ceil(mDevice->BufferSize*srateScale)),
+ static_cast<UInt32>(outputFormat.mSampleRate)/10);
+ FrameCount64 += MaxResamplerPadding;
+ if(FrameCount64 > std::numeric_limits<int32_t>::max())
+ throw al::backend_exception{al::backend_error::DeviceError,
"Calculated frame count is too large: %" PRIu64, FrameCount64};
- outputFrameCount = static_cast<uint32_t>(FrameCount64);
- err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_MaximumFramesPerSlice,
- kAudioUnitScope_Output, 0, &outputFrameCount, sizeof(outputFrameCount));
- if(err != noErr)
- throw al::backend_exception{ALC_INVALID_VALUE, "Failed to set capture frame count: %u",
- err};
+ UInt32 outputFrameCount{};
+ propertySize = sizeof(outputFrameCount);
+ err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_MaximumFramesPerSlice,
+ kAudioUnitScope_Global, OutputElement, &outputFrameCount, &propertySize);
+ if(err != noErr || propertySize != sizeof(outputFrameCount))
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Could not get input frame count: %u", err};
+
+ mCaptureData.resize(outputFrameCount * mFrameSize);
+
+ outputFrameCount = static_cast<UInt32>(maxu64(outputFrameCount, FrameCount64));
+ mRing = RingBuffer::Create(outputFrameCount, mFrameSize, false);
- // Set up sample converter if needed
+ /* Set up sample converter if needed */
if(outputFormat.mSampleRate != mDevice->Frequency)
- mConverter = CreateSampleConverter(mDevice->FmtType, mDevice->FmtType,
- mFormat.mChannelsPerFrame, static_cast<ALuint>(hardwareFormat.mSampleRate),
+ mConverter = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType,
+ mFormat.mChannelsPerFrame, static_cast<uint>(hardwareFormat.mSampleRate),
mDevice->Frequency, Resampler::FastBSinc24);
- mRing = CreateRingBuffer(outputFrameCount, mFrameSize, false);
-
+#if CAN_ENUMERATE
+ if(name)
+ mDevice->DeviceName = name;
+ else
+ {
+ UInt32 propSize{sizeof(audioDevice)};
+ audioDevice = kAudioDeviceUnknown;
+ AudioUnitGetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice,
+ kAudioUnitScope_Global, InputElement, &audioDevice, &propSize);
+
+ std::string devname{GetDeviceName(audioDevice)};
+ if(!devname.empty()) mDevice->DeviceName = std::move(devname);
+ else mDevice->DeviceName = "Unknown Device Name";
+ }
+#else
mDevice->DeviceName = name;
+#endif
}
-bool CoreAudioCapture::start()
+void CoreAudioCapture::start()
{
OSStatus err{AudioOutputUnitStart(mAudioUnit)};
if(err != noErr)
- {
- ERR("AudioOutputUnitStart failed\n");
- return false;
- }
- return true;
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "AudioOutputUnitStart failed: %d", err};
}
void CoreAudioCapture::stop()
@@ -593,35 +844,34 @@ void CoreAudioCapture::stop()
ERR("AudioOutputUnitStop failed\n");
}
-ALCenum CoreAudioCapture::captureSamples(al::byte *buffer, ALCuint samples)
+void CoreAudioCapture::captureSamples(al::byte *buffer, uint samples)
{
if(!mConverter)
{
mRing->read(buffer, samples);
- return ALC_NO_ERROR;
+ return;
}
auto rec_vec = mRing->getReadVector();
const void *src0{rec_vec.first.buf};
- auto src0len = static_cast<ALuint>(rec_vec.first.len);
- ALuint got{mConverter->convert(&src0, &src0len, buffer, samples)};
+ auto src0len = static_cast<uint>(rec_vec.first.len);
+ uint got{mConverter->convert(&src0, &src0len, buffer, samples)};
size_t total_read{rec_vec.first.len - src0len};
if(got < samples && !src0len && rec_vec.second.len > 0)
{
const void *src1{rec_vec.second.buf};
- auto src1len = static_cast<ALuint>(rec_vec.second.len);
- got += mConverter->convert(&src1, &src1len, buffer+got, samples-got);
+ auto src1len = static_cast<uint>(rec_vec.second.len);
+ got += mConverter->convert(&src1, &src1len, buffer + got*mFrameSize, samples-got);
total_read += rec_vec.second.len - src1len;
}
mRing->readAdvance(total_read);
- return ALC_NO_ERROR;
}
-ALCuint CoreAudioCapture::availableSamples()
+uint CoreAudioCapture::availableSamples()
{
- if(!mConverter) return static_cast<ALCuint>(mRing->readSpace());
- return mConverter->availableOut(static_cast<ALCuint>(mRing->readSpace()));
+ if(!mConverter) return static_cast<uint>(mRing->readSpace());
+ return mConverter->availableOut(static_cast<uint>(mRing->readSpace()));
}
} // namespace
@@ -637,19 +887,42 @@ bool CoreAudioBackendFactory::init() { return true; }
bool CoreAudioBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback || type == BackendType::Capture; }
-void CoreAudioBackendFactory::probe(DevProbe type, std::string *outnames)
+std::string CoreAudioBackendFactory::probe(BackendType type)
{
+ std::string outnames;
+#if CAN_ENUMERATE
+ auto append_name = [&outnames](const DeviceEntry &entry) -> void
+ {
+ /* Includes null char. */
+ outnames.append(entry.mName.c_str(), entry.mName.length()+1);
+ };
switch(type)
{
- case DevProbe::Playback:
- case DevProbe::Capture:
- /* Includes null char. */
- outnames->append(ca_device, sizeof(ca_device));
- break;
+ case BackendType::Playback:
+ EnumerateDevices(PlaybackList, false);
+ std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name);
+ break;
+ case BackendType::Capture:
+ EnumerateDevices(CaptureList, true);
+ std::for_each(CaptureList.cbegin(), CaptureList.cend(), append_name);
+ break;
}
+
+#else
+
+ switch(type)
+ {
+ case BackendType::Playback:
+ case BackendType::Capture:
+ /* Includes null char. */
+ outnames.append(ca_device, sizeof(ca_device));
+ break;
+ }
+#endif
+ return outnames;
}
-BackendPtr CoreAudioBackendFactory::createBackend(ALCdevice *device, BackendType type)
+BackendPtr CoreAudioBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new CoreAudioPlayback{device}};