Use kAudioObjectPropertyElementMaster on macOS for compatibility
[openal-soft.git] / alc / backends / coreaudio.cpp
blob2e103d4562db082f4abb594ba329c30b29ab18e2
1 /**
2 * OpenAL cross platform audio library
3 * Copyright (C) 1999-2007 by authors.
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 * Or go to http://www.gnu.org/copyleft/lgpl.html
21 #include "config.h"
23 #include "coreaudio.h"
25 #include <cinttypes>
26 #include <cmath>
27 #include <functional>
28 #include <memory>
29 #include <optional>
30 #include <stdint.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string>
34 #include <string.h>
35 #include <unistd.h>
36 #include <vector>
38 #include "alnumeric.h"
39 #include "alstring.h"
40 #include "core/converter.h"
41 #include "core/device.h"
42 #include "core/logging.h"
43 #include "ringbuffer.h"
45 #include <AudioUnit/AudioUnit.h>
46 #include <AudioToolbox/AudioToolbox.h>
48 #if TARGET_OS_IOS || TARGET_OS_TV
49 #define CAN_ENUMERATE 0
50 #else
51 #include <IOKit/audio/IOAudioTypes.h>
52 #define CAN_ENUMERATE 1
53 #endif
55 namespace {
57 constexpr auto OutputElement = 0;
58 constexpr auto InputElement = 1;
60 // These following arrays should always be defined in ascending AudioChannelLabel value order
61 constexpr std::array<AudioChannelLabel, 1> MonoChanMap { kAudioChannelLabel_Mono };
62 constexpr std::array<AudioChannelLabel, 2> StereoChanMap { kAudioChannelLabel_Left, kAudioChannelLabel_Right};
63 constexpr std::array<AudioChannelLabel, 4> QuadChanMap {
64 kAudioChannelLabel_Left, kAudioChannelLabel_Right,
65 kAudioChannelLabel_LeftSurround, kAudioChannelLabel_RightSurround
67 constexpr std::array<AudioChannelLabel, 6> X51ChanMap {
68 kAudioChannelLabel_Left, kAudioChannelLabel_Right,
69 kAudioChannelLabel_Center, kAudioChannelLabel_LFEScreen,
70 kAudioChannelLabel_LeftSurround, kAudioChannelLabel_RightSurround
72 constexpr std::array<AudioChannelLabel, 6> X51RearChanMap {
73 kAudioChannelLabel_Left, kAudioChannelLabel_Right,
74 kAudioChannelLabel_Center, kAudioChannelLabel_LFEScreen,
75 kAudioChannelLabel_RearSurroundRight, kAudioChannelLabel_RearSurroundLeft
77 constexpr std::array<AudioChannelLabel, 7> X61ChanMap {
78 kAudioChannelLabel_Left, kAudioChannelLabel_Right,
79 kAudioChannelLabel_Center, kAudioChannelLabel_LFEScreen,
80 kAudioChannelLabel_CenterSurround,
81 kAudioChannelLabel_RearSurroundRight, kAudioChannelLabel_RearSurroundLeft
83 constexpr std::array<AudioChannelLabel, 8> X71ChanMap {
84 kAudioChannelLabel_Left, kAudioChannelLabel_Right,
85 kAudioChannelLabel_Center, kAudioChannelLabel_LFEScreen,
86 kAudioChannelLabel_LeftSurround, kAudioChannelLabel_RightSurround,
87 kAudioChannelLabel_LeftCenter, kAudioChannelLabel_RightCenter
90 struct FourCCPrinter {
91 char mString[sizeof(UInt32) + 1]{};
93 constexpr FourCCPrinter(UInt32 code) noexcept
95 for(size_t i{0};i < sizeof(UInt32);++i)
97 const auto ch = static_cast<char>(code & 0xff);
98 /* If this breaks early it'll leave the first byte null, to get
99 * read as a 0-length string.
101 if(ch <= 0x1f || ch >= 0x7f)
102 break;
103 mString[sizeof(UInt32)-1-i] = ch;
104 code >>= 8;
107 constexpr FourCCPrinter(int code) noexcept : FourCCPrinter{static_cast<UInt32>(code)} { }
109 constexpr const char *c_str() const noexcept { return mString; }
112 #if CAN_ENUMERATE
113 struct DeviceEntry {
114 AudioDeviceID mId;
115 std::string mName;
118 std::vector<DeviceEntry> PlaybackList;
119 std::vector<DeviceEntry> CaptureList;
122 OSStatus GetHwProperty(AudioHardwarePropertyID propId, UInt32 dataSize, void *propData)
124 const AudioObjectPropertyAddress addr{propId, kAudioObjectPropertyScopeGlobal,
125 kAudioObjectPropertyElementMaster};
126 return AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr, 0, nullptr, &dataSize,
127 propData);
130 OSStatus GetHwPropertySize(AudioHardwarePropertyID propId, UInt32 *outSize)
132 const AudioObjectPropertyAddress addr{propId, kAudioObjectPropertyScopeGlobal,
133 kAudioObjectPropertyElementMaster};
134 return AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &addr, 0, nullptr, outSize);
137 OSStatus GetDevProperty(AudioDeviceID devId, AudioDevicePropertyID propId, bool isCapture,
138 UInt32 elem, UInt32 dataSize, void *propData)
140 static const AudioObjectPropertyScope scopes[2]{kAudioDevicePropertyScopeOutput,
141 kAudioDevicePropertyScopeInput};
142 const AudioObjectPropertyAddress addr{propId, scopes[isCapture], elem};
143 return AudioObjectGetPropertyData(devId, &addr, 0, nullptr, &dataSize, propData);
146 OSStatus GetDevPropertySize(AudioDeviceID devId, AudioDevicePropertyID inPropertyID,
147 bool isCapture, UInt32 elem, UInt32 *outSize)
149 static const AudioObjectPropertyScope scopes[2]{kAudioDevicePropertyScopeOutput,
150 kAudioDevicePropertyScopeInput};
151 const AudioObjectPropertyAddress addr{inPropertyID, scopes[isCapture], elem};
152 return AudioObjectGetPropertyDataSize(devId, &addr, 0, nullptr, outSize);
156 std::string GetDeviceName(AudioDeviceID devId)
158 std::string devname;
159 CFStringRef nameRef;
161 /* Try to get the device name as a CFString, for Unicode name support. */
162 OSStatus err{GetDevProperty(devId, kAudioDevicePropertyDeviceNameCFString, false, 0,
163 sizeof(nameRef), &nameRef)};
164 if(err == noErr)
166 const CFIndex propSize{CFStringGetMaximumSizeForEncoding(CFStringGetLength(nameRef),
167 kCFStringEncodingUTF8)};
168 devname.resize(static_cast<size_t>(propSize)+1, '\0');
170 CFStringGetCString(nameRef, &devname[0], propSize+1, kCFStringEncodingUTF8);
171 CFRelease(nameRef);
173 else
175 /* If that failed, just get the C string. Hopefully there's nothing bad
176 * with this.
178 UInt32 propSize{};
179 if(GetDevPropertySize(devId, kAudioDevicePropertyDeviceName, false, 0, &propSize))
180 return devname;
182 devname.resize(propSize+1, '\0');
183 if(GetDevProperty(devId, kAudioDevicePropertyDeviceName, false, 0, propSize, &devname[0]))
185 devname.clear();
186 return devname;
190 /* Clear extraneous nul chars that may have been written with the name
191 * string, and return it.
193 while(!devname.empty() && !devname.back())
194 devname.pop_back();
195 return devname;
198 UInt32 GetDeviceChannelCount(AudioDeviceID devId, bool isCapture)
200 UInt32 propSize{};
201 auto err = GetDevPropertySize(devId, kAudioUnitProperty_AudioChannelLayout, isCapture, 0,
202 &propSize);
203 if(err)
205 ERR("kAudioDevicePropertyStreamConfiguration size query failed: '%s' (%u)\n",
206 FourCCPrinter{err}.c_str(), err);
207 return 0;
210 auto channel_data = std::make_unique<char[]>(propSize);
211 auto *channel_layout = reinterpret_cast<AudioChannelLayout*>(channel_data.get());
213 err = GetDevProperty(devId, kAudioUnitProperty_AudioChannelLayout, isCapture, 0, propSize,
214 channel_layout);
215 if(err)
217 ERR("kAudioDevicePropertyStreamConfiguration query failed: '%s' (%u)\n",
218 FourCCPrinter{err}.c_str(), err);
219 return 0;
222 return channel_layout->mNumberChannelDescriptions;
226 void EnumerateDevices(std::vector<DeviceEntry> &list, bool isCapture)
228 UInt32 propSize{};
229 if(auto err = GetHwPropertySize(kAudioHardwarePropertyDevices, &propSize))
231 ERR("Failed to get device list size: %u\n", err);
232 return;
235 auto devIds = std::vector<AudioDeviceID>(propSize/sizeof(AudioDeviceID), kAudioDeviceUnknown);
236 if(auto err = GetHwProperty(kAudioHardwarePropertyDevices, propSize, devIds.data()))
238 ERR("Failed to get device list: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
239 return;
242 std::vector<DeviceEntry> newdevs;
243 newdevs.reserve(devIds.size());
245 AudioDeviceID defaultId{kAudioDeviceUnknown};
246 GetHwProperty(isCapture ? kAudioHardwarePropertyDefaultInputDevice :
247 kAudioHardwarePropertyDefaultOutputDevice, sizeof(defaultId), &defaultId);
249 if(defaultId != kAudioDeviceUnknown)
251 newdevs.emplace_back(DeviceEntry{defaultId, GetDeviceName(defaultId)});
252 const auto &entry = newdevs.back();
253 TRACE("Got device: %s = ID %u\n", entry.mName.c_str(), entry.mId);
255 for(const AudioDeviceID devId : devIds)
257 if(devId == kAudioDeviceUnknown)
258 continue;
260 auto match_devid = [devId](const DeviceEntry &entry) noexcept -> bool
261 { return entry.mId == devId; };
262 auto match = std::find_if(newdevs.cbegin(), newdevs.cend(), match_devid);
263 if(match != newdevs.cend()) continue;
265 auto numChannels = GetDeviceChannelCount(devId, isCapture);
266 if(numChannels > 0)
268 newdevs.emplace_back(DeviceEntry{devId, GetDeviceName(devId)});
269 const auto &entry = newdevs.back();
270 TRACE("Got device: %s = ID %u\n", entry.mName.c_str(), entry.mId);
274 if(newdevs.size() > 1)
276 /* Rename entries that have matching names, by appending '#2', '#3',
277 * etc, as needed.
279 for(auto curitem = newdevs.begin()+1;curitem != newdevs.end();++curitem)
281 auto check_match = [curitem](const DeviceEntry &entry) -> bool
282 { return entry.mName == curitem->mName; };
283 if(std::find_if(newdevs.begin(), curitem, check_match) != curitem)
285 std::string name{curitem->mName};
286 size_t count{1};
287 auto check_name = [&name](const DeviceEntry &entry) -> bool
288 { return entry.mName == name; };
289 do {
290 name = curitem->mName;
291 name += " #";
292 name += std::to_string(++count);
293 } while(std::find_if(newdevs.begin(), curitem, check_name) != curitem);
294 curitem->mName = std::move(name);
299 newdevs.shrink_to_fit();
300 newdevs.swap(list);
303 struct DeviceHelper {
304 DeviceHelper()
306 AudioObjectPropertyAddress addr{kAudioHardwarePropertyDefaultOutputDevice,
307 kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster};
308 OSStatus status = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &addr, DeviceListenerProc, nil);
309 if (status != noErr)
310 ERR("AudioObjectAddPropertyListener fail: %d", status);
312 ~DeviceHelper()
314 AudioObjectPropertyAddress addr{kAudioHardwarePropertyDefaultOutputDevice,
315 kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster};
316 OSStatus status = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &addr, DeviceListenerProc, nil);
317 if (status != noErr)
318 ERR("AudioObjectRemovePropertyListener fail: %d", status);
321 static OSStatus DeviceListenerProc(AudioObjectID /*inObjectID*/, UInt32 inNumberAddresses,
322 const AudioObjectPropertyAddress *inAddresses, void* /*inClientData*/)
324 for(UInt32 i = 0; i < inNumberAddresses; ++i)
326 switch(inAddresses[i].mSelector)
328 case kAudioHardwarePropertyDefaultOutputDevice:
329 case kAudioHardwarePropertyDefaultSystemOutputDevice:
330 alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback,
331 "Default playback device changed: "+std::to_string(inAddresses[i].mSelector));
332 break;
333 case kAudioHardwarePropertyDefaultInputDevice:
334 alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture,
335 "Default capture device changed: "+std::to_string(inAddresses[i].mSelector));
336 break;
339 return noErr;
343 static std::optional<DeviceHelper> sDeviceHelper;
345 #else
347 static constexpr char ca_device[] = "CoreAudio Default";
348 #endif
351 struct CoreAudioPlayback final : public BackendBase {
352 CoreAudioPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
353 ~CoreAudioPlayback() override;
355 OSStatus MixerProc(AudioUnitRenderActionFlags *ioActionFlags,
356 const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
357 AudioBufferList *ioData) noexcept;
359 void open(std::string_view name) override;
360 bool reset() override;
361 void start() override;
362 void stop() override;
364 AudioUnit mAudioUnit{};
366 uint mFrameSize{0u};
367 AudioStreamBasicDescription mFormat{}; // This is the OpenAL format as a CoreAudio ASBD
370 CoreAudioPlayback::~CoreAudioPlayback()
372 AudioUnitUninitialize(mAudioUnit);
373 AudioComponentInstanceDispose(mAudioUnit);
377 OSStatus CoreAudioPlayback::MixerProc(AudioUnitRenderActionFlags*, const AudioTimeStamp*, UInt32,
378 UInt32, AudioBufferList *ioData) noexcept
380 for(size_t i{0};i < ioData->mNumberBuffers;++i)
382 auto &buffer = ioData->mBuffers[i];
383 mDevice->renderSamples(buffer.mData, buffer.mDataByteSize/mFrameSize,
384 buffer.mNumberChannels);
386 return noErr;
390 void CoreAudioPlayback::open(std::string_view name)
392 #if CAN_ENUMERATE
393 AudioDeviceID audioDevice{kAudioDeviceUnknown};
394 if(name.empty())
395 GetHwProperty(kAudioHardwarePropertyDefaultOutputDevice, sizeof(audioDevice),
396 &audioDevice);
397 else
399 if(PlaybackList.empty())
400 EnumerateDevices(PlaybackList, false);
402 auto find_name = [name](const DeviceEntry &entry) -> bool
403 { return entry.mName == name; };
404 auto devmatch = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), find_name);
405 if(devmatch == PlaybackList.cend())
406 throw al::backend_exception{al::backend_error::NoDevice,
407 "Device name \"%.*s\" not found", al::sizei(name), name.data()};
409 audioDevice = devmatch->mId;
411 #else
412 if(name.empty())
413 name = ca_device;
414 else if(name != ca_device)
415 throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
416 al::sizei(name), name.data()};
417 #endif
419 /* open the default output unit */
420 AudioComponentDescription desc{};
421 desc.componentType = kAudioUnitType_Output;
422 #if CAN_ENUMERATE
423 desc.componentSubType = (audioDevice == kAudioDeviceUnknown) ?
424 kAudioUnitSubType_DefaultOutput : kAudioUnitSubType_HALOutput;
425 #else
426 desc.componentSubType = kAudioUnitSubType_RemoteIO;
427 #endif
428 desc.componentManufacturer = kAudioUnitManufacturer_Apple;
429 desc.componentFlags = 0;
430 desc.componentFlagsMask = 0;
432 AudioComponent comp{AudioComponentFindNext(NULL, &desc)};
433 if(comp == nullptr)
434 throw al::backend_exception{al::backend_error::NoDevice, "Could not find audio component"};
436 AudioUnit audioUnit{};
437 OSStatus err{AudioComponentInstanceNew(comp, &audioUnit)};
438 if(err != noErr)
439 throw al::backend_exception{al::backend_error::NoDevice,
440 "Could not create component instance: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
442 #if CAN_ENUMERATE
443 if(audioDevice != kAudioDeviceUnknown)
444 AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_CurrentDevice,
445 kAudioUnitScope_Global, OutputElement, &audioDevice, sizeof(AudioDeviceID));
446 #endif
448 err = AudioUnitInitialize(audioUnit);
449 if(err != noErr)
450 throw al::backend_exception{al::backend_error::DeviceError,
451 "Could not initialize audio unit: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
453 /* WARNING: I don't know if "valid" audio unit values are guaranteed to be
454 * non-0. If not, this logic is broken.
456 if(mAudioUnit)
458 AudioUnitUninitialize(mAudioUnit);
459 AudioComponentInstanceDispose(mAudioUnit);
461 mAudioUnit = audioUnit;
463 #if CAN_ENUMERATE
464 if(!name.empty())
465 mDeviceName = name;
466 else
468 UInt32 propSize{sizeof(audioDevice)};
469 audioDevice = kAudioDeviceUnknown;
470 AudioUnitGetProperty(audioUnit, kAudioOutputUnitProperty_CurrentDevice,
471 kAudioUnitScope_Global, OutputElement, &audioDevice, &propSize);
473 std::string devname{GetDeviceName(audioDevice)};
474 if(!devname.empty()) mDeviceName = std::move(devname);
475 else mDeviceName = "Unknown Device Name";
478 if(audioDevice != kAudioDeviceUnknown)
480 UInt32 type{};
481 err = GetDevProperty(audioDevice, kAudioDevicePropertyDataSource, false,
482 kAudioObjectPropertyElementMaster, sizeof(type), &type);
483 if(err != noErr)
484 ERR("Failed to get audio device type: %u\n", err);
485 else
487 TRACE("Got device type '%s'\n", FourCCPrinter{type}.c_str());
488 mDevice->Flags.set(DirectEar, (type == kIOAudioOutputPortSubTypeHeadphones));
492 #else
493 mDeviceName = name;
494 #endif
497 bool CoreAudioPlayback::reset()
499 OSStatus err{AudioUnitUninitialize(mAudioUnit)};
500 if(err != noErr)
501 ERR("AudioUnitUninitialize failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
503 /* retrieve default output unit's properties (output side) */
504 AudioStreamBasicDescription streamFormat{};
505 UInt32 size{sizeof(streamFormat)};
506 err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output,
507 OutputElement, &streamFormat, &size);
508 if(err != noErr || size != sizeof(streamFormat))
510 ERR("AudioUnitGetProperty(StreamFormat) failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(),
511 err);
512 return false;
515 #if 0
516 TRACE("Output streamFormat of default output unit -\n");
517 TRACE(" streamFormat.mFramesPerPacket = %d\n", streamFormat.mFramesPerPacket);
518 TRACE(" streamFormat.mChannelsPerFrame = %d\n", streamFormat.mChannelsPerFrame);
519 TRACE(" streamFormat.mBitsPerChannel = %d\n", streamFormat.mBitsPerChannel);
520 TRACE(" streamFormat.mBytesPerPacket = %d\n", streamFormat.mBytesPerPacket);
521 TRACE(" streamFormat.mBytesPerFrame = %d\n", streamFormat.mBytesPerFrame);
522 TRACE(" streamFormat.mSampleRate = %5.0f\n", streamFormat.mSampleRate);
523 #endif
525 /* Use the sample rate from the output unit's current parameters, but reset
526 * everything else.
528 if(mDevice->Frequency != streamFormat.mSampleRate)
530 mDevice->BufferSize = static_cast<uint>(mDevice->BufferSize*streamFormat.mSampleRate/
531 mDevice->Frequency + 0.5);
532 mDevice->Frequency = static_cast<uint>(streamFormat.mSampleRate);
535 struct ChannelMap {
536 DevFmtChannels fmt;
537 al::span<const AudioChannelLabel> map;
538 bool is_51rear;
541 static constexpr std::array<ChannelMap,7> chanmaps{{
542 { DevFmtX71, X71ChanMap, false },
543 { DevFmtX61, X61ChanMap, false },
544 { DevFmtX51, X51ChanMap, false },
545 { DevFmtX51, X51RearChanMap, true },
546 { DevFmtQuad, QuadChanMap, false },
547 { DevFmtStereo, StereoChanMap, false },
548 { DevFmtMono, MonoChanMap, false }
551 /* FIXME: How to tell what channels are what in the output device, and how
552 * to specify what we're giving? e.g. 6.0 vs 5.1
554 streamFormat.mChannelsPerFrame = mDevice->channelsFromFmt();
556 streamFormat.mFramesPerPacket = 1;
557 streamFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kLinearPCMFormatFlagIsPacked;
558 streamFormat.mFormatID = kAudioFormatLinearPCM;
559 switch(mDevice->FmtType)
561 case DevFmtUByte:
562 mDevice->FmtType = DevFmtByte;
563 /* fall-through */
564 case DevFmtByte:
565 streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
566 streamFormat.mBitsPerChannel = 8;
567 break;
568 case DevFmtUShort:
569 mDevice->FmtType = DevFmtShort;
570 /* fall-through */
571 case DevFmtShort:
572 streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
573 streamFormat.mBitsPerChannel = 16;
574 break;
575 case DevFmtUInt:
576 mDevice->FmtType = DevFmtInt;
577 /* fall-through */
578 case DevFmtInt:
579 streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
580 streamFormat.mBitsPerChannel = 32;
581 break;
582 case DevFmtFloat:
583 streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsFloat;
584 streamFormat.mBitsPerChannel = 32;
585 break;
587 streamFormat.mBytesPerFrame = streamFormat.mChannelsPerFrame*streamFormat.mBitsPerChannel/8;
588 streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame*streamFormat.mFramesPerPacket;
590 err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
591 OutputElement, &streamFormat, sizeof(streamFormat));
592 if(err != noErr)
594 ERR("AudioUnitSetProperty(StreamFormat) failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(),
595 err);
596 return false;
599 if(!mDevice->Flags.test(ChannelsRequest))
601 auto propSize = UInt32{};
602 auto writable = Boolean{};
604 err = AudioUnitGetPropertyInfo(mAudioUnit, kAudioUnitProperty_AudioChannelLayout,
605 kAudioUnitScope_Output, OutputElement, &propSize, &writable);
606 if(err == noErr)
608 auto layout_data = std::make_unique<char[]>(propSize);
609 auto *layout = reinterpret_cast<AudioChannelLayout*>(layout_data.get());
611 err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_AudioChannelLayout,
612 kAudioUnitScope_Output, OutputElement, layout, &propSize);
613 if(err == noErr)
615 auto descs = al::span{std::data(layout->mChannelDescriptions),
616 layout->mNumberChannelDescriptions};
617 auto labels = std::vector<AudioChannelLayoutTag>(descs.size());
619 std::transform(descs.begin(), descs.end(), labels.begin(),
620 std::mem_fn(&AudioChannelDescription::mChannelLabel));
621 sort(labels.begin(), labels.end());
623 auto chaniter = std::find_if(chanmaps.cbegin(), chanmaps.cend(),
624 [&labels](const ChannelMap &chanmap) -> bool
626 return std::includes(labels.begin(), labels.end(), chanmap.map.begin(),
627 chanmap.map.end());
629 if(chaniter != chanmaps.cend())
630 mDevice->FmtChans = chaniter->fmt;
635 setDefaultWFXChannelOrder();
637 /* setup callback */
638 mFrameSize = mDevice->frameSizeFromFmt();
639 AURenderCallbackStruct input{};
640 input.inputProc = [](void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) noexcept
641 { return static_cast<CoreAudioPlayback*>(inRefCon)->MixerProc(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData); };
642 input.inputProcRefCon = this;
644 err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_SetRenderCallback,
645 kAudioUnitScope_Input, OutputElement, &input, sizeof(AURenderCallbackStruct));
646 if(err != noErr)
648 ERR("AudioUnitSetProperty(SetRenderCallback) failed: '%s' (%u)\n",
649 FourCCPrinter{err}.c_str(), err);
650 return false;
653 /* init the default audio unit... */
654 err = AudioUnitInitialize(mAudioUnit);
655 if(err != noErr)
657 ERR("AudioUnitInitialize failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
658 return false;
661 return true;
664 void CoreAudioPlayback::start()
666 const OSStatus err{AudioOutputUnitStart(mAudioUnit)};
667 if(err != noErr)
668 throw al::backend_exception{al::backend_error::DeviceError,
669 "AudioOutputUnitStart failed: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
672 void CoreAudioPlayback::stop()
674 OSStatus err{AudioOutputUnitStop(mAudioUnit)};
675 if(err != noErr)
676 ERR("AudioOutputUnitStop failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
680 struct CoreAudioCapture final : public BackendBase {
681 CoreAudioCapture(DeviceBase *device) noexcept : BackendBase{device} { }
682 ~CoreAudioCapture() override;
684 OSStatus RecordProc(AudioUnitRenderActionFlags *ioActionFlags,
685 const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber,
686 UInt32 inNumberFrames, AudioBufferList *ioData) noexcept;
688 void open(std::string_view name) override;
689 void start() override;
690 void stop() override;
691 void captureSamples(std::byte *buffer, uint samples) override;
692 uint availableSamples() override;
694 AudioUnit mAudioUnit{0};
696 uint mFrameSize{0u};
697 AudioStreamBasicDescription mFormat{}; // This is the OpenAL format as a CoreAudio ASBD
699 SampleConverterPtr mConverter;
701 std::vector<char> mCaptureData;
703 RingBufferPtr mRing{nullptr};
706 CoreAudioCapture::~CoreAudioCapture()
708 if(mAudioUnit)
709 AudioComponentInstanceDispose(mAudioUnit);
710 mAudioUnit = 0;
714 OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags *ioActionFlags,
715 const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
716 AudioBufferList*) noexcept
718 union {
719 std::byte buf[std::max(sizeof(AudioBufferList), offsetof(AudioBufferList, mBuffers[1]))];
720 AudioBufferList list;
721 } audiobuf{};
723 audiobuf.list.mNumberBuffers = 1;
724 audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame;
725 audiobuf.list.mBuffers[0].mData = mCaptureData.data();
726 audiobuf.list.mBuffers[0].mDataByteSize = static_cast<UInt32>(mCaptureData.size());
728 OSStatus err{AudioUnitRender(mAudioUnit, ioActionFlags, inTimeStamp, inBusNumber,
729 inNumberFrames, &audiobuf.list)};
730 if(err != noErr)
732 ERR("AudioUnitRender capture error: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
733 return err;
736 std::ignore = mRing->write(mCaptureData.data(), inNumberFrames);
737 return noErr;
741 void CoreAudioCapture::open(std::string_view name)
743 #if CAN_ENUMERATE
744 AudioDeviceID audioDevice{kAudioDeviceUnknown};
745 if(name.empty())
746 GetHwProperty(kAudioHardwarePropertyDefaultInputDevice, sizeof(audioDevice),
747 &audioDevice);
748 else
750 if(CaptureList.empty())
751 EnumerateDevices(CaptureList, true);
753 auto find_name = [name](const DeviceEntry &entry) -> bool
754 { return entry.mName == name; };
755 auto devmatch = std::find_if(CaptureList.cbegin(), CaptureList.cend(), find_name);
756 if(devmatch == CaptureList.cend())
757 throw al::backend_exception{al::backend_error::NoDevice,
758 "Device name \"%.*s\" not found", al::sizei(name), name.data()};
760 audioDevice = devmatch->mId;
762 #else
763 if(name.empty())
764 name = ca_device;
765 else if(name != ca_device)
766 throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
767 al::sizei(name), name.data()};
768 #endif
770 AudioComponentDescription desc{};
771 desc.componentType = kAudioUnitType_Output;
772 #if CAN_ENUMERATE
773 desc.componentSubType = (audioDevice == kAudioDeviceUnknown) ?
774 kAudioUnitSubType_DefaultOutput : kAudioUnitSubType_HALOutput;
775 #else
776 desc.componentSubType = kAudioUnitSubType_RemoteIO;
777 #endif
778 desc.componentManufacturer = kAudioUnitManufacturer_Apple;
779 desc.componentFlags = 0;
780 desc.componentFlagsMask = 0;
782 // Search for component with given description
783 AudioComponent comp{AudioComponentFindNext(NULL, &desc)};
784 if(comp == NULL)
785 throw al::backend_exception{al::backend_error::NoDevice, "Could not find audio component"};
787 // Open the component
788 OSStatus err{AudioComponentInstanceNew(comp, &mAudioUnit)};
789 if(err != noErr)
790 throw al::backend_exception{al::backend_error::NoDevice,
791 "Could not create component instance: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
793 // Turn off AudioUnit output
794 UInt32 enableIO{0};
795 err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO,
796 kAudioUnitScope_Output, OutputElement, &enableIO, sizeof(enableIO));
797 if(err != noErr)
798 throw al::backend_exception{al::backend_error::DeviceError,
799 "Could not disable audio unit output property: '%s' (%u)", FourCCPrinter{err}.c_str(),
800 err};
802 // Turn on AudioUnit input
803 enableIO = 1;
804 err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO,
805 kAudioUnitScope_Input, InputElement, &enableIO, sizeof(enableIO));
806 if(err != noErr)
807 throw al::backend_exception{al::backend_error::DeviceError,
808 "Could not enable audio unit input property: '%s' (%u)", FourCCPrinter{err}.c_str(),
809 err};
811 #if CAN_ENUMERATE
812 if(audioDevice != kAudioDeviceUnknown)
813 AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice,
814 kAudioUnitScope_Global, InputElement, &audioDevice, sizeof(AudioDeviceID));
815 #endif
817 // set capture callback
818 AURenderCallbackStruct input{};
819 input.inputProc = [](void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) noexcept
820 { return static_cast<CoreAudioCapture*>(inRefCon)->RecordProc(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData); };
821 input.inputProcRefCon = this;
823 err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_SetInputCallback,
824 kAudioUnitScope_Global, InputElement, &input, sizeof(AURenderCallbackStruct));
825 if(err != noErr)
826 throw al::backend_exception{al::backend_error::DeviceError,
827 "Could not set capture callback: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
829 // Disable buffer allocation for capture
830 UInt32 flag{0};
831 err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_ShouldAllocateBuffer,
832 kAudioUnitScope_Output, InputElement, &flag, sizeof(flag));
833 if(err != noErr)
834 throw al::backend_exception{al::backend_error::DeviceError,
835 "Could not disable buffer allocation property: '%s' (%u)", FourCCPrinter{err}.c_str(),
836 err};
838 // Initialize the device
839 err = AudioUnitInitialize(mAudioUnit);
840 if(err != noErr)
841 throw al::backend_exception{al::backend_error::DeviceError,
842 "Could not initialize audio unit: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
844 // Get the hardware format
845 AudioStreamBasicDescription hardwareFormat{};
846 UInt32 propertySize{sizeof(hardwareFormat)};
847 err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
848 InputElement, &hardwareFormat, &propertySize);
849 if(err != noErr || propertySize != sizeof(hardwareFormat))
850 throw al::backend_exception{al::backend_error::DeviceError,
851 "Could not get input format: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
853 // Set up the requested format description
854 AudioStreamBasicDescription requestedFormat{};
855 switch(mDevice->FmtType)
857 case DevFmtByte:
858 requestedFormat.mBitsPerChannel = 8;
859 requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
860 break;
861 case DevFmtUByte:
862 requestedFormat.mBitsPerChannel = 8;
863 requestedFormat.mFormatFlags = kAudioFormatFlagIsPacked;
864 break;
865 case DevFmtShort:
866 requestedFormat.mBitsPerChannel = 16;
867 requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger
868 | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
869 break;
870 case DevFmtUShort:
871 requestedFormat.mBitsPerChannel = 16;
872 requestedFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
873 break;
874 case DevFmtInt:
875 requestedFormat.mBitsPerChannel = 32;
876 requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger
877 | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
878 break;
879 case DevFmtUInt:
880 requestedFormat.mBitsPerChannel = 32;
881 requestedFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
882 break;
883 case DevFmtFloat:
884 requestedFormat.mBitsPerChannel = 32;
885 requestedFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat | kAudioFormatFlagsNativeEndian
886 | kAudioFormatFlagIsPacked;
887 break;
890 switch(mDevice->FmtChans)
892 case DevFmtMono:
893 requestedFormat.mChannelsPerFrame = 1;
894 break;
895 case DevFmtStereo:
896 requestedFormat.mChannelsPerFrame = 2;
897 break;
899 case DevFmtQuad:
900 case DevFmtX51:
901 case DevFmtX61:
902 case DevFmtX71:
903 case DevFmtX714:
904 case DevFmtX7144:
905 case DevFmtX3D71:
906 case DevFmtAmbi3D:
907 throw al::backend_exception{al::backend_error::DeviceError, "%s not supported",
908 DevFmtChannelsString(mDevice->FmtChans)};
911 requestedFormat.mBytesPerFrame = requestedFormat.mChannelsPerFrame * requestedFormat.mBitsPerChannel / 8;
912 requestedFormat.mBytesPerPacket = requestedFormat.mBytesPerFrame;
913 requestedFormat.mSampleRate = mDevice->Frequency;
914 requestedFormat.mFormatID = kAudioFormatLinearPCM;
915 requestedFormat.mReserved = 0;
916 requestedFormat.mFramesPerPacket = 1;
918 // save requested format description for later use
919 mFormat = requestedFormat;
920 mFrameSize = mDevice->frameSizeFromFmt();
922 // Use intermediate format for sample rate conversion (outputFormat)
923 // Set sample rate to the same as hardware for resampling later
924 AudioStreamBasicDescription outputFormat{requestedFormat};
925 outputFormat.mSampleRate = hardwareFormat.mSampleRate;
927 // The output format should be the requested format, but using the hardware sample rate
928 // This is because the AudioUnit will automatically scale other properties, except for sample rate
929 err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output,
930 InputElement, &outputFormat, sizeof(outputFormat));
931 if(err != noErr)
932 throw al::backend_exception{al::backend_error::DeviceError,
933 "Could not set input format: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
935 /* Calculate the minimum AudioUnit output format frame count for the pre-
936 * conversion ring buffer. Ensure at least 100ms for the total buffer.
938 double srateScale{outputFormat.mSampleRate / mDevice->Frequency};
939 auto FrameCount64 = std::max(static_cast<uint64_t>(std::ceil(mDevice->BufferSize*srateScale)),
940 static_cast<UInt32>(outputFormat.mSampleRate)/10_u64);
941 FrameCount64 += MaxResamplerPadding;
942 if(FrameCount64 > std::numeric_limits<int32_t>::max())
943 throw al::backend_exception{al::backend_error::DeviceError,
944 "Calculated frame count is too large: %" PRIu64, FrameCount64};
946 UInt32 outputFrameCount{};
947 propertySize = sizeof(outputFrameCount);
948 err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_MaximumFramesPerSlice,
949 kAudioUnitScope_Global, OutputElement, &outputFrameCount, &propertySize);
950 if(err != noErr || propertySize != sizeof(outputFrameCount))
951 throw al::backend_exception{al::backend_error::DeviceError,
952 "Could not get input frame count: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
954 mCaptureData.resize(outputFrameCount * mFrameSize);
956 outputFrameCount = static_cast<UInt32>(std::max(uint64_t{outputFrameCount}, FrameCount64));
957 mRing = RingBuffer::Create(outputFrameCount, mFrameSize, false);
959 /* Set up sample converter if needed */
960 if(outputFormat.mSampleRate != mDevice->Frequency)
961 mConverter = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType,
962 mFormat.mChannelsPerFrame, static_cast<uint>(hardwareFormat.mSampleRate),
963 mDevice->Frequency, Resampler::FastBSinc24);
965 #if CAN_ENUMERATE
966 if(!name.empty())
967 mDeviceName = name;
968 else
970 UInt32 propSize{sizeof(audioDevice)};
971 audioDevice = kAudioDeviceUnknown;
972 AudioUnitGetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice,
973 kAudioUnitScope_Global, InputElement, &audioDevice, &propSize);
975 std::string devname{GetDeviceName(audioDevice)};
976 if(!devname.empty()) mDeviceName = std::move(devname);
977 else mDeviceName = "Unknown Device Name";
979 #else
980 mDeviceName = name;
981 #endif
985 void CoreAudioCapture::start()
987 OSStatus err{AudioOutputUnitStart(mAudioUnit)};
988 if(err != noErr)
989 throw al::backend_exception{al::backend_error::DeviceError,
990 "AudioOutputUnitStart failed: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
993 void CoreAudioCapture::stop()
995 OSStatus err{AudioOutputUnitStop(mAudioUnit)};
996 if(err != noErr)
997 ERR("AudioOutputUnitStop failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
1000 void CoreAudioCapture::captureSamples(std::byte *buffer, uint samples)
1002 if(!mConverter)
1004 std::ignore = mRing->read(buffer, samples);
1005 return;
1008 auto rec_vec = mRing->getReadVector();
1009 const void *src0{rec_vec[0].buf};
1010 auto src0len = static_cast<uint>(rec_vec[0].len);
1011 uint got{mConverter->convert(&src0, &src0len, buffer, samples)};
1012 size_t total_read{rec_vec[0].len - src0len};
1013 if(got < samples && !src0len && rec_vec[1].len > 0)
1015 const void *src1{rec_vec[1].buf};
1016 auto src1len = static_cast<uint>(rec_vec[1].len);
1017 got += mConverter->convert(&src1, &src1len, buffer + got*mFrameSize, samples-got);
1018 total_read += rec_vec[1].len - src1len;
1021 mRing->readAdvance(total_read);
1024 uint CoreAudioCapture::availableSamples()
1026 if(!mConverter) return static_cast<uint>(mRing->readSpace());
1027 return mConverter->availableOut(static_cast<uint>(mRing->readSpace()));
1030 } // namespace
1032 BackendFactory &CoreAudioBackendFactory::getFactory()
1034 static CoreAudioBackendFactory factory{};
1035 return factory;
1038 bool CoreAudioBackendFactory::init()
1040 #if CAN_ENUMERATE
1041 sDeviceHelper.emplace();
1042 #endif
1043 return true;
1046 bool CoreAudioBackendFactory::querySupport(BackendType type)
1047 { return type == BackendType::Playback || type == BackendType::Capture; }
1049 auto CoreAudioBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
1051 std::vector<std::string> outnames;
1052 #if CAN_ENUMERATE
1053 auto append_name = [&outnames](const DeviceEntry &entry) -> void
1054 { outnames.emplace_back(entry.mName); };
1056 switch(type)
1058 case BackendType::Playback:
1059 EnumerateDevices(PlaybackList, false);
1060 outnames.reserve(PlaybackList.size());
1061 std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name);
1062 break;
1063 case BackendType::Capture:
1064 EnumerateDevices(CaptureList, true);
1065 outnames.reserve(CaptureList.size());
1066 std::for_each(CaptureList.cbegin(), CaptureList.cend(), append_name);
1067 break;
1070 #else
1072 switch(type)
1074 case BackendType::Playback:
1075 case BackendType::Capture:
1076 outnames.emplace_back(ca_device);
1077 break;
1079 #endif
1080 return outnames;
1083 BackendPtr CoreAudioBackendFactory::createBackend(DeviceBase *device, BackendType type)
1085 if(type == BackendType::Playback)
1086 return BackendPtr{new CoreAudioPlayback{device}};
1087 if(type == BackendType::Capture)
1088 return BackendPtr{new CoreAudioCapture{device}};
1089 return nullptr;
1092 alc::EventSupport CoreAudioBackendFactory::queryEventSupport(alc::EventType eventType, BackendType)
1094 switch(eventType)
1096 case alc::EventType::DefaultDeviceChanged:
1097 return alc::EventSupport::FullSupport;
1099 case alc::EventType::DeviceAdded:
1100 case alc::EventType::DeviceRemoved:
1101 case alc::EventType::Count:
1102 break;
1104 return alc::EventSupport::NoSupport;