| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097 |
- /**
- * OpenAL cross platform audio library
- * Copyright (C) 1999-2007 by authors.
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public
- * License along with this library; if not, write to the
- * Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- * Or go to http://www.gnu.org/copyleft/lgpl.html
- */
- #include "config.h"
- #include "coreaudio.h"
- #include <cinttypes>
- #include <cmath>
- #include <functional>
- #include <memory>
- #include <optional>
- #include <stdint.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string>
- #include <string.h>
- #include <unistd.h>
- #include <vector>
- #include "alnumeric.h"
- #include "alstring.h"
- #include "core/converter.h"
- #include "core/device.h"
- #include "core/logging.h"
- #include "fmt/core.h"
- #include "ringbuffer.h"
- #include <AudioUnit/AudioUnit.h>
- #include <AudioToolbox/AudioToolbox.h>
- #if TARGET_OS_IOS || TARGET_OS_TV
- #define CAN_ENUMERATE 0
- #else
- #include <IOKit/audio/IOAudioTypes.h>
- #define CAN_ENUMERATE 1
- #endif
- namespace {
- constexpr auto OutputElement = 0;
- constexpr auto InputElement = 1;
- // These following arrays should always be defined in ascending AudioChannelLabel value order
- constexpr std::array<AudioChannelLabel, 1> MonoChanMap { kAudioChannelLabel_Mono };
- constexpr std::array<AudioChannelLabel, 2> StereoChanMap { kAudioChannelLabel_Left, kAudioChannelLabel_Right};
- constexpr std::array<AudioChannelLabel, 4> QuadChanMap {
- kAudioChannelLabel_Left, kAudioChannelLabel_Right,
- kAudioChannelLabel_LeftSurround, kAudioChannelLabel_RightSurround
- };
- constexpr std::array<AudioChannelLabel, 6> X51ChanMap {
- kAudioChannelLabel_Left, kAudioChannelLabel_Right,
- kAudioChannelLabel_Center, kAudioChannelLabel_LFEScreen,
- kAudioChannelLabel_LeftSurround, kAudioChannelLabel_RightSurround
- };
- constexpr std::array<AudioChannelLabel, 6> X51RearChanMap {
- kAudioChannelLabel_Left, kAudioChannelLabel_Right,
- kAudioChannelLabel_Center, kAudioChannelLabel_LFEScreen,
- kAudioChannelLabel_RearSurroundRight, kAudioChannelLabel_RearSurroundLeft
- };
- constexpr std::array<AudioChannelLabel, 7> X61ChanMap {
- kAudioChannelLabel_Left, kAudioChannelLabel_Right,
- kAudioChannelLabel_Center, kAudioChannelLabel_LFEScreen,
- kAudioChannelLabel_CenterSurround,
- kAudioChannelLabel_RearSurroundRight, kAudioChannelLabel_RearSurroundLeft
- };
- constexpr std::array<AudioChannelLabel, 8> X71ChanMap {
- kAudioChannelLabel_Left, kAudioChannelLabel_Right,
- kAudioChannelLabel_Center, kAudioChannelLabel_LFEScreen,
- kAudioChannelLabel_LeftSurround, kAudioChannelLabel_RightSurround,
- kAudioChannelLabel_LeftCenter, kAudioChannelLabel_RightCenter
- };
- struct FourCCPrinter {
- char mString[sizeof(UInt32) + 1]{};
- explicit constexpr FourCCPrinter(UInt32 code) noexcept
- {
- for(size_t i{0};i < sizeof(UInt32);++i)
- {
- const auto ch = static_cast<char>(code & 0xff);
- /* If this breaks early it'll leave the first byte null, to get
- * read as a 0-length string.
- */
- if(ch <= 0x1f || ch >= 0x7f)
- break;
- mString[sizeof(UInt32)-1-i] = ch;
- code >>= 8;
- }
- }
- explicit constexpr FourCCPrinter(OSStatus code) noexcept : FourCCPrinter{static_cast<UInt32>(code)} { }
- constexpr const char *c_str() const noexcept { return mString; }
- };
- #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.empty() && !devname.back())
- devname.pop_back();
- return devname;
- }
- auto GetDeviceChannelCount(AudioDeviceID devId, bool isCapture) -> UInt32
- {
- auto propSize = UInt32{};
- auto err = GetDevPropertySize(devId, kAudioDevicePropertyStreamConfiguration, isCapture, 0,
- &propSize);
- if(err)
- {
- ERR("kAudioDevicePropertyStreamConfiguration size query failed: '{}' ({})",
- FourCCPrinter{err}.c_str(), 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: '{}' ({})",
- FourCCPrinter{err}.c_str(), err);
- return 0;
- }
- auto numChannels = UInt32{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: {}", 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: '{}' ({})", FourCCPrinter{err}.c_str(), 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: {} = ID {}", entry.mName, 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: {} = ID {}", entry.mName, 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)
- {
- auto name = std::string{curitem->mName};
- auto count = 1_uz;
- auto check_name = [&name](const DeviceEntry &entry) -> bool
- { return entry.mName == name; };
- do {
- name = fmt::format("{} #{}", curitem->mName, ++count);
- } while(std::find_if(newdevs.begin(), curitem, check_name) != curitem);
- curitem->mName = std::move(name);
- }
- }
- }
- newdevs.shrink_to_fit();
- newdevs.swap(list);
- }
- struct DeviceHelper {
- DeviceHelper()
- {
- AudioObjectPropertyAddress addr{kAudioHardwarePropertyDefaultOutputDevice,
- kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster};
- OSStatus status = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &addr, DeviceListenerProc, nil);
- if (status != noErr)
- ERR("AudioObjectAddPropertyListener fail: {}", status);
- }
- ~DeviceHelper()
- {
- AudioObjectPropertyAddress addr{kAudioHardwarePropertyDefaultOutputDevice,
- kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster};
- OSStatus status = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &addr, DeviceListenerProc, nil);
- if (status != noErr)
- ERR("AudioObjectRemovePropertyListener fail: {}", status);
- }
- static OSStatus DeviceListenerProc(AudioObjectID /*inObjectID*/, UInt32 inNumberAddresses,
- const AudioObjectPropertyAddress *inAddresses, void* /*inClientData*/)
- {
- for(UInt32 i = 0; i < inNumberAddresses; ++i)
- {
- switch(inAddresses[i].mSelector)
- {
- case kAudioHardwarePropertyDefaultOutputDevice:
- case kAudioHardwarePropertyDefaultSystemOutputDevice:
- alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback,
- "Default playback device changed: "+std::to_string(inAddresses[i].mSelector));
- break;
- case kAudioHardwarePropertyDefaultInputDevice:
- alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture,
- "Default capture device changed: "+std::to_string(inAddresses[i].mSelector));
- break;
- }
- }
- return noErr;
- }
- };
- static std::optional<DeviceHelper> sDeviceHelper;
- #else
- static constexpr char ca_device[] = "CoreAudio Default";
- #endif
- struct CoreAudioPlayback final : public BackendBase {
- explicit CoreAudioPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
- ~CoreAudioPlayback() override;
- OSStatus MixerProc(AudioUnitRenderActionFlags *ioActionFlags,
- const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
- AudioBufferList *ioData) noexcept;
- void open(std::string_view name) override;
- bool reset() override;
- void start() override;
- void stop() override;
- AudioUnit mAudioUnit{};
- uint mFrameSize{0u};
- AudioStreamBasicDescription mFormat{}; // This is the OpenAL format as a CoreAudio ASBD
- };
- CoreAudioPlayback::~CoreAudioPlayback()
- {
- AudioUnitUninitialize(mAudioUnit);
- AudioComponentInstanceDispose(mAudioUnit);
- }
- OSStatus CoreAudioPlayback::MixerProc(AudioUnitRenderActionFlags*, const AudioTimeStamp*, UInt32,
- UInt32, AudioBufferList *ioData) noexcept
- {
- 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(std::string_view name)
- {
- #if CAN_ENUMERATE
- AudioDeviceID audioDevice{kAudioDeviceUnknown};
- if(name.empty())
- 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 \"{}\" not found", name};
- audioDevice = devmatch->mId;
- }
- #else
- if(name.empty())
- name = ca_device;
- else if(name != ca_device)
- throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found",
- name};
- #endif
- /* open the default output unit */
- AudioComponentDescription desc{};
- desc.componentType = kAudioUnitType_Output;
- #if CAN_ENUMERATE
- desc.componentSubType = (audioDevice == kAudioDeviceUnknown) ?
- kAudioUnitSubType_DefaultOutput : kAudioUnitSubType_HALOutput;
- #else
- desc.componentSubType = kAudioUnitSubType_RemoteIO;
- #endif
- desc.componentManufacturer = kAudioUnitManufacturer_Apple;
- desc.componentFlags = 0;
- desc.componentFlagsMask = 0;
- AudioComponent comp{AudioComponentFindNext(NULL, &desc)};
- if(comp == nullptr)
- throw al::backend_exception{al::backend_error::NoDevice, "Could not find audio component"};
- AudioUnit audioUnit{};
- OSStatus err{AudioComponentInstanceNew(comp, &audioUnit)};
- if(err != noErr)
- throw al::backend_exception{al::backend_error::NoDevice,
- "Could not create component instance: '{}' ({})", FourCCPrinter{err}.c_str(), err};
- #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{al::backend_error::DeviceError,
- "Could not initialize audio unit: '{}' ({})", FourCCPrinter{err}.c_str(), 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.empty())
- mDeviceName = 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()) mDeviceName = std::move(devname);
- else mDeviceName = "Unknown Device Name";
- }
- if(audioDevice != kAudioDeviceUnknown)
- {
- UInt32 type{};
- err = GetDevProperty(audioDevice, kAudioDevicePropertyDataSource, false,
- kAudioObjectPropertyElementMaster, sizeof(type), &type);
- if(err != noErr)
- WARN("Failed to get audio device type: '{}' ({})", FourCCPrinter{err}.c_str(), err);
- else
- {
- TRACE("Got device type '{}'", FourCCPrinter{type}.c_str());
- mDevice->Flags.set(DirectEar, (type == kIOAudioOutputPortSubTypeHeadphones));
- }
- }
- #else
- mDeviceName = name;
- #endif
- }
- bool CoreAudioPlayback::reset()
- {
- OSStatus err{AudioUnitUninitialize(mAudioUnit)};
- if(err != noErr)
- ERR("AudioUnitUninitialize failed: '{}' ({})", FourCCPrinter{err}.c_str(), err);
- /* retrieve default output unit's properties (output side) */
- AudioStreamBasicDescription streamFormat{};
- UInt32 size{sizeof(streamFormat)};
- err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output,
- OutputElement, &streamFormat, &size);
- if(err != noErr || size != sizeof(streamFormat))
- {
- ERR("AudioUnitGetProperty(StreamFormat) failed: '{}' ({})", FourCCPrinter{err}.c_str(),
- err);
- return false;
- }
- /* Use the sample rate from the output unit's current parameters, but reset
- * everything else.
- */
- if(mDevice->mSampleRate != streamFormat.mSampleRate)
- {
- mDevice->mBufferSize = static_cast<uint>(mDevice->mBufferSize*streamFormat.mSampleRate/
- mDevice->mSampleRate + 0.5);
- mDevice->mSampleRate = static_cast<uint>(streamFormat.mSampleRate);
- }
- struct ChannelMap {
- DevFmtChannels fmt;
- al::span<const AudioChannelLabel> map;
- bool is_51rear;
- };
- static constexpr std::array<ChannelMap,7> chanmaps{{
- { DevFmtX71, X71ChanMap, false },
- { DevFmtX61, X61ChanMap, false },
- { DevFmtX51, X51ChanMap, false },
- { DevFmtX51, X51RearChanMap, true },
- { DevFmtQuad, QuadChanMap, false },
- { DevFmtStereo, StereoChanMap, false },
- { DevFmtMono, MonoChanMap, false }
- }};
- if(!mDevice->Flags.test(ChannelsRequest))
- {
- auto propSize = UInt32{};
- auto writable = Boolean{};
- err = AudioUnitGetPropertyInfo(mAudioUnit, kAudioUnitProperty_AudioChannelLayout,
- kAudioUnitScope_Output, OutputElement, &propSize, &writable);
- if(err == noErr)
- {
- auto layout_data = std::make_unique<char[]>(propSize);
- auto *layout = reinterpret_cast<AudioChannelLayout*>(layout_data.get());
- err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_AudioChannelLayout,
- kAudioUnitScope_Output, OutputElement, layout, &propSize);
- if(err == noErr)
- {
- auto descs = al::span{std::data(layout->mChannelDescriptions),
- layout->mNumberChannelDescriptions};
- auto labels = std::vector<AudioChannelLayoutTag>(descs.size());
- std::transform(descs.begin(), descs.end(), labels.begin(),
- std::mem_fn(&AudioChannelDescription::mChannelLabel));
- sort(labels.begin(), labels.end());
- auto check_labels = [&labels](const ChannelMap &chanmap) -> bool
- {
- return std::includes(labels.begin(), labels.end(), chanmap.map.begin(),
- chanmap.map.end());
- };
- auto chaniter = std::find_if(chanmaps.cbegin(), chanmaps.cend(), check_labels);
- if(chaniter != chanmaps.cend())
- mDevice->FmtChans = chaniter->fmt;
- }
- }
- }
- /* TODO: Also set kAudioUnitProperty_AudioChannelLayout according to the AL
- * device's channel configuration.
- */
- streamFormat.mChannelsPerFrame = mDevice->channelsFromFmt();
- streamFormat.mFramesPerPacket = 1;
- streamFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kLinearPCMFormatFlagIsPacked;
- streamFormat.mFormatID = kAudioFormatLinearPCM;
- switch(mDevice->FmtType)
- {
- case DevFmtUByte:
- mDevice->FmtType = DevFmtByte;
- /* fall-through */
- case DevFmtByte:
- streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
- streamFormat.mBitsPerChannel = 8;
- break;
- case DevFmtUShort:
- mDevice->FmtType = DevFmtShort;
- /* fall-through */
- case DevFmtShort:
- streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
- streamFormat.mBitsPerChannel = 16;
- break;
- case DevFmtUInt:
- mDevice->FmtType = DevFmtInt;
- /* fall-through */
- case DevFmtInt:
- streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
- streamFormat.mBitsPerChannel = 32;
- break;
- case DevFmtFloat:
- streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsFloat;
- streamFormat.mBitsPerChannel = 32;
- break;
- }
- streamFormat.mBytesPerFrame = streamFormat.mChannelsPerFrame*streamFormat.mBitsPerChannel/8;
- streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame*streamFormat.mFramesPerPacket;
- err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
- OutputElement, &streamFormat, sizeof(streamFormat));
- if(err != noErr)
- {
- ERR("AudioUnitSetProperty(StreamFormat) failed: '{}' ({})", FourCCPrinter{err}.c_str(),
- err);
- return false;
- }
- setDefaultWFXChannelOrder();
- /* setup callback */
- mFrameSize = mDevice->frameSizeFromFmt();
- AURenderCallbackStruct input{};
- input.inputProc = [](void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) noexcept
- { return static_cast<CoreAudioPlayback*>(inRefCon)->MixerProc(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData); };
- input.inputProcRefCon = this;
- err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_SetRenderCallback,
- kAudioUnitScope_Input, OutputElement, &input, sizeof(AURenderCallbackStruct));
- if(err != noErr)
- {
- ERR("AudioUnitSetProperty(SetRenderCallback) failed: '{}' ({})",
- FourCCPrinter{err}.c_str(), err);
- return false;
- }
- /* init the default audio unit... */
- err = AudioUnitInitialize(mAudioUnit);
- if(err != noErr)
- {
- ERR("AudioUnitInitialize failed: '{}' ({})", FourCCPrinter{err}.c_str(), err);
- return false;
- }
- return true;
- }
- void CoreAudioPlayback::start()
- {
- const OSStatus err{AudioOutputUnitStart(mAudioUnit)};
- if(err != noErr)
- throw al::backend_exception{al::backend_error::DeviceError,
- "AudioOutputUnitStart failed: '{}' ({})", FourCCPrinter{err}.c_str(), err};
- }
- void CoreAudioPlayback::stop()
- {
- OSStatus err{AudioOutputUnitStop(mAudioUnit)};
- if(err != noErr)
- ERR("AudioOutputUnitStop failed: '{}' ({})", FourCCPrinter{err}.c_str(), err);
- }
- struct CoreAudioCapture final : public BackendBase {
- explicit CoreAudioCapture(DeviceBase *device) noexcept : BackendBase{device} { }
- ~CoreAudioCapture() override;
- OSStatus RecordProc(AudioUnitRenderActionFlags *ioActionFlags,
- const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber,
- UInt32 inNumberFrames, AudioBufferList *ioData) noexcept;
- void open(std::string_view name) override;
- void start() override;
- void stop() override;
- void captureSamples(std::byte *buffer, uint samples) override;
- uint availableSamples() override;
- AudioUnit mAudioUnit{0};
- uint mFrameSize{0u};
- AudioStreamBasicDescription mFormat{}; // This is the OpenAL format as a CoreAudio ASBD
- SampleConverterPtr mConverter;
- std::vector<char> mCaptureData;
- RingBufferPtr mRing{nullptr};
- };
- CoreAudioCapture::~CoreAudioCapture()
- {
- if(mAudioUnit)
- AudioComponentInstanceDispose(mAudioUnit);
- mAudioUnit = 0;
- }
- OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags *ioActionFlags,
- const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
- AudioBufferList*) noexcept
- {
- union {
- std::byte buf[std::max(sizeof(AudioBufferList), offsetof(AudioBufferList, mBuffers[1]))];
- AudioBufferList list;
- } audiobuf{};
- 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());
- OSStatus err{AudioUnitRender(mAudioUnit, ioActionFlags, inTimeStamp, inBusNumber,
- inNumberFrames, &audiobuf.list)};
- if(err != noErr)
- {
- ERR("AudioUnitRender capture error: '{}' ({})", FourCCPrinter{err}.c_str(), err);
- return err;
- }
- std::ignore = mRing->write(mCaptureData.data(), inNumberFrames);
- return noErr;
- }
- void CoreAudioCapture::open(std::string_view name)
- {
- #if CAN_ENUMERATE
- AudioDeviceID audioDevice{kAudioDeviceUnknown};
- if(name.empty())
- 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 \"{}\" not found", name};
- audioDevice = devmatch->mId;
- }
- #else
- if(name.empty())
- name = ca_device;
- else if(name != ca_device)
- throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found",
- name};
- #endif
- AudioComponentDescription desc{};
- desc.componentType = kAudioUnitType_Output;
- #if CAN_ENUMERATE
- desc.componentSubType = (audioDevice == kAudioDeviceUnknown) ?
- kAudioUnitSubType_DefaultOutput : kAudioUnitSubType_HALOutput;
- #else
- desc.componentSubType = kAudioUnitSubType_RemoteIO;
- #endif
- desc.componentManufacturer = kAudioUnitManufacturer_Apple;
- desc.componentFlags = 0;
- desc.componentFlagsMask = 0;
- // Search for component with given description
- AudioComponent comp{AudioComponentFindNext(NULL, &desc)};
- if(comp == NULL)
- throw al::backend_exception{al::backend_error::NoDevice, "Could not find audio component"};
- // Open the component
- OSStatus err{AudioComponentInstanceNew(comp, &mAudioUnit)};
- if(err != noErr)
- throw al::backend_exception{al::backend_error::NoDevice,
- "Could not create component instance: '{}' ({})", FourCCPrinter{err}.c_str(), err};
- // Turn off AudioUnit output
- UInt32 enableIO{0};
- err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO,
- kAudioUnitScope_Output, OutputElement, &enableIO, sizeof(enableIO));
- if(err != noErr)
- throw al::backend_exception{al::backend_error::DeviceError,
- "Could not disable audio unit output property: '{}' ({})", FourCCPrinter{err}.c_str(),
- err};
- // Turn on AudioUnit input
- enableIO = 1;
- err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO,
- kAudioUnitScope_Input, InputElement, &enableIO, sizeof(enableIO));
- if(err != noErr)
- throw al::backend_exception{al::backend_error::DeviceError,
- "Could not enable audio unit input property: '{}' ({})", FourCCPrinter{err}.c_str(),
- 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 = [](void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) noexcept
- { return static_cast<CoreAudioCapture*>(inRefCon)->RecordProc(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData); };
- input.inputProcRefCon = this;
- err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_SetInputCallback,
- kAudioUnitScope_Global, InputElement, &input, sizeof(AURenderCallbackStruct));
- if(err != noErr)
- throw al::backend_exception{al::backend_error::DeviceError,
- "Could not set capture callback: '{}' ({})", FourCCPrinter{err}.c_str(), 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{al::backend_error::DeviceError,
- "Could not disable buffer allocation property: '{}' ({})", FourCCPrinter{err}.c_str(),
- err};
- // Initialize the device
- err = AudioUnitInitialize(mAudioUnit);
- if(err != noErr)
- throw al::backend_exception{al::backend_error::DeviceError,
- "Could not initialize audio unit: '{}' ({})", FourCCPrinter{err}.c_str(), err};
- // Get the hardware format
- AudioStreamBasicDescription hardwareFormat{};
- UInt32 propertySize{sizeof(hardwareFormat)};
- err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
- InputElement, &hardwareFormat, &propertySize);
- if(err != noErr || propertySize != sizeof(hardwareFormat))
- throw al::backend_exception{al::backend_error::DeviceError,
- "Could not get input format: '{}' ({})", FourCCPrinter{err}.c_str(), 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;
- break;
- case DevFmtUShort:
- requestedFormat.mBitsPerChannel = 16;
- requestedFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
- break;
- case DevFmtInt:
- requestedFormat.mBitsPerChannel = 32;
- requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger
- | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
- break;
- case DevFmtUInt:
- requestedFormat.mBitsPerChannel = 32;
- requestedFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
- break;
- case DevFmtFloat:
- requestedFormat.mBitsPerChannel = 32;
- requestedFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat | kAudioFormatFlagsNativeEndian
- | kAudioFormatFlagIsPacked;
- break;
- }
- switch(mDevice->FmtChans)
- {
- case DevFmtMono:
- requestedFormat.mChannelsPerFrame = 1;
- break;
- case DevFmtStereo:
- requestedFormat.mChannelsPerFrame = 2;
- break;
- case DevFmtQuad:
- case DevFmtX51:
- case DevFmtX61:
- case DevFmtX71:
- case DevFmtX714:
- case DevFmtX7144:
- case DevFmtX3D71:
- case DevFmtAmbi3D:
- throw al::backend_exception{al::backend_error::DeviceError, "{} not supported",
- DevFmtChannelsString(mDevice->FmtChans)};
- }
- requestedFormat.mBytesPerFrame = requestedFormat.mChannelsPerFrame * requestedFormat.mBitsPerChannel / 8;
- requestedFormat.mBytesPerPacket = requestedFormat.mBytesPerFrame;
- requestedFormat.mSampleRate = mDevice->mSampleRate;
- requestedFormat.mFormatID = kAudioFormatLinearPCM;
- requestedFormat.mReserved = 0;
- requestedFormat.mFramesPerPacket = 1;
- // save requested format description for later use
- mFormat = requestedFormat;
- mFrameSize = mDevice->frameSizeFromFmt();
- // Use intermediate format for sample rate conversion (outputFormat)
- // Set sample rate to the same as hardware for resampling later
- 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,
- InputElement, &outputFormat, sizeof(outputFormat));
- if(err != noErr)
- throw al::backend_exception{al::backend_error::DeviceError,
- "Could not set input format: '{}' ({})", FourCCPrinter{err}.c_str(), 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->mSampleRate};
- auto FrameCount64 = std::max(static_cast<uint64_t>(std::ceil(mDevice->mBufferSize*srateScale)),
- static_cast<UInt32>(outputFormat.mSampleRate)/10_u64);
- FrameCount64 += MaxResamplerPadding;
- if(FrameCount64 > std::numeric_limits<int32_t>::max())
- throw al::backend_exception{al::backend_error::DeviceError,
- "Calculated frame count is too large: {}", FrameCount64};
- 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: '{}' ({})", FourCCPrinter{err}.c_str(), err};
- mCaptureData.resize(outputFrameCount * mFrameSize);
- outputFrameCount = static_cast<UInt32>(std::max(uint64_t{outputFrameCount}, FrameCount64));
- mRing = RingBuffer::Create(outputFrameCount, mFrameSize, false);
- /* Set up sample converter if needed */
- if(outputFormat.mSampleRate != mDevice->mSampleRate)
- mConverter = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType,
- mFormat.mChannelsPerFrame, static_cast<uint>(hardwareFormat.mSampleRate),
- mDevice->mSampleRate, Resampler::FastBSinc24);
- #if CAN_ENUMERATE
- if(!name.empty())
- mDeviceName = 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()) mDeviceName = std::move(devname);
- else mDeviceName = "Unknown Device Name";
- }
- #else
- mDeviceName = name;
- #endif
- }
- void CoreAudioCapture::start()
- {
- OSStatus err{AudioOutputUnitStart(mAudioUnit)};
- if(err != noErr)
- throw al::backend_exception{al::backend_error::DeviceError,
- "AudioOutputUnitStart failed: '{}' ({})", FourCCPrinter{err}.c_str(), err};
- }
- void CoreAudioCapture::stop()
- {
- OSStatus err{AudioOutputUnitStop(mAudioUnit)};
- if(err != noErr)
- ERR("AudioOutputUnitStop failed: '{}' ({})", FourCCPrinter{err}.c_str(), err);
- }
- void CoreAudioCapture::captureSamples(std::byte *buffer, uint samples)
- {
- if(!mConverter)
- {
- std::ignore = mRing->read(buffer, samples);
- return;
- }
- auto rec_vec = mRing->getReadVector();
- const void *src0{rec_vec[0].buf};
- auto src0len = static_cast<uint>(rec_vec[0].len);
- uint got{mConverter->convert(&src0, &src0len, buffer, samples)};
- size_t total_read{rec_vec[0].len - src0len};
- if(got < samples && !src0len && rec_vec[1].len > 0)
- {
- const void *src1{rec_vec[1].buf};
- auto src1len = static_cast<uint>(rec_vec[1].len);
- got += mConverter->convert(&src1, &src1len, buffer + got*mFrameSize, samples-got);
- total_read += rec_vec[1].len - src1len;
- }
- mRing->readAdvance(total_read);
- }
- uint CoreAudioCapture::availableSamples()
- {
- if(!mConverter) return static_cast<uint>(mRing->readSpace());
- return mConverter->availableOut(static_cast<uint>(mRing->readSpace()));
- }
- } // namespace
- BackendFactory &CoreAudioBackendFactory::getFactory()
- {
- static CoreAudioBackendFactory factory{};
- return factory;
- }
- bool CoreAudioBackendFactory::init()
- {
- #if CAN_ENUMERATE
- sDeviceHelper.emplace();
- #endif
- return true;
- }
- bool CoreAudioBackendFactory::querySupport(BackendType type)
- { return type == BackendType::Playback || type == BackendType::Capture; }
- auto CoreAudioBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
- {
- std::vector<std::string> outnames;
- #if CAN_ENUMERATE
- auto append_name = [&outnames](const DeviceEntry &entry) -> void
- { outnames.emplace_back(entry.mName); };
- switch(type)
- {
- case BackendType::Playback:
- EnumerateDevices(PlaybackList, false);
- outnames.reserve(PlaybackList.size());
- std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name);
- break;
- case BackendType::Capture:
- EnumerateDevices(CaptureList, true);
- outnames.reserve(CaptureList.size());
- std::for_each(CaptureList.cbegin(), CaptureList.cend(), append_name);
- break;
- }
- #else
- switch(type)
- {
- case BackendType::Playback:
- case BackendType::Capture:
- outnames.emplace_back(ca_device);
- break;
- }
- #endif
- return outnames;
- }
- BackendPtr CoreAudioBackendFactory::createBackend(DeviceBase *device, BackendType type)
- {
- if(type == BackendType::Playback)
- return BackendPtr{new CoreAudioPlayback{device}};
- if(type == BackendType::Capture)
- return BackendPtr{new CoreAudioCapture{device}};
- return nullptr;
- }
- alc::EventSupport CoreAudioBackendFactory::queryEventSupport(alc::EventType eventType, BackendType)
- {
- switch(eventType)
- {
- case alc::EventType::DefaultDeviceChanged:
- return alc::EventSupport::FullSupport;
- case alc::EventType::DeviceAdded:
- case alc::EventType::DeviceRemoved:
- case alc::EventType::Count:
- break;
- }
- return alc::EventSupport::NoSupport;
- }
|