Clean up the debug example a little
[openal-soft.git] / alc / backends / coreaudio.cpp
blobc222fff5f8486fa80c5a7cdd0f7f96303eeb69d9
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 <memory>
28 #include <stdint.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string>
32 #include <string.h>
33 #include <unistd.h>
34 #include <vector>
35 #include <optional>
37 #include "alnumeric.h"
38 #include "alstring.h"
39 #include "core/converter.h"
40 #include "core/device.h"
41 #include "core/logging.h"
42 #include "ringbuffer.h"
44 #include <AudioUnit/AudioUnit.h>
45 #include <AudioToolbox/AudioToolbox.h>
47 #if TARGET_OS_IOS || TARGET_OS_TV
48 #define CAN_ENUMERATE 0
49 #else
50 #include <IOKit/audio/IOAudioTypes.h>
51 #define CAN_ENUMERATE 1
52 #endif
54 namespace {
56 constexpr auto OutputElement = 0;
57 constexpr auto InputElement = 1;
59 struct FourCCPrinter {
60 char mString[sizeof(UInt32) + 1]{};
62 constexpr FourCCPrinter(UInt32 code) noexcept
64 for(size_t i{0};i < sizeof(UInt32);++i)
66 const auto ch = static_cast<char>(code & 0xff);
67 /* If this breaks early it'll leave the first byte null, to get
68 * read as a 0-length string.
70 if(ch <= 0x1f || ch >= 0x7f)
71 break;
72 mString[sizeof(UInt32)-1-i] = ch;
73 code >>= 8;
76 constexpr FourCCPrinter(int code) noexcept : FourCCPrinter{static_cast<UInt32>(code)} { }
78 constexpr const char *c_str() const noexcept { return mString; }
81 #if CAN_ENUMERATE
82 struct DeviceEntry {
83 AudioDeviceID mId;
84 std::string mName;
87 std::vector<DeviceEntry> PlaybackList;
88 std::vector<DeviceEntry> CaptureList;
91 OSStatus GetHwProperty(AudioHardwarePropertyID propId, UInt32 dataSize, void *propData)
93 const AudioObjectPropertyAddress addr{propId, kAudioObjectPropertyScopeGlobal,
94 kAudioObjectPropertyElementMaster};
95 return AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr, 0, nullptr, &dataSize,
96 propData);
99 OSStatus GetHwPropertySize(AudioHardwarePropertyID propId, UInt32 *outSize)
101 const AudioObjectPropertyAddress addr{propId, kAudioObjectPropertyScopeGlobal,
102 kAudioObjectPropertyElementMaster};
103 return AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &addr, 0, nullptr, outSize);
106 OSStatus GetDevProperty(AudioDeviceID devId, AudioDevicePropertyID propId, bool isCapture,
107 UInt32 elem, UInt32 dataSize, void *propData)
109 static const AudioObjectPropertyScope scopes[2]{kAudioDevicePropertyScopeOutput,
110 kAudioDevicePropertyScopeInput};
111 const AudioObjectPropertyAddress addr{propId, scopes[isCapture], elem};
112 return AudioObjectGetPropertyData(devId, &addr, 0, nullptr, &dataSize, propData);
115 OSStatus GetDevPropertySize(AudioDeviceID devId, AudioDevicePropertyID inPropertyID,
116 bool isCapture, UInt32 elem, UInt32 *outSize)
118 static const AudioObjectPropertyScope scopes[2]{kAudioDevicePropertyScopeOutput,
119 kAudioDevicePropertyScopeInput};
120 const AudioObjectPropertyAddress addr{inPropertyID, scopes[isCapture], elem};
121 return AudioObjectGetPropertyDataSize(devId, &addr, 0, nullptr, outSize);
125 std::string GetDeviceName(AudioDeviceID devId)
127 std::string devname;
128 CFStringRef nameRef;
130 /* Try to get the device name as a CFString, for Unicode name support. */
131 OSStatus err{GetDevProperty(devId, kAudioDevicePropertyDeviceNameCFString, false, 0,
132 sizeof(nameRef), &nameRef)};
133 if(err == noErr)
135 const CFIndex propSize{CFStringGetMaximumSizeForEncoding(CFStringGetLength(nameRef),
136 kCFStringEncodingUTF8)};
137 devname.resize(static_cast<size_t>(propSize)+1, '\0');
139 CFStringGetCString(nameRef, &devname[0], propSize+1, kCFStringEncodingUTF8);
140 CFRelease(nameRef);
142 else
144 /* If that failed, just get the C string. Hopefully there's nothing bad
145 * with this.
147 UInt32 propSize{};
148 if(GetDevPropertySize(devId, kAudioDevicePropertyDeviceName, false, 0, &propSize))
149 return devname;
151 devname.resize(propSize+1, '\0');
152 if(GetDevProperty(devId, kAudioDevicePropertyDeviceName, false, 0, propSize, &devname[0]))
154 devname.clear();
155 return devname;
159 /* Clear extraneous nul chars that may have been written with the name
160 * string, and return it.
162 while(!devname.back())
163 devname.pop_back();
164 return devname;
167 UInt32 GetDeviceChannelCount(AudioDeviceID devId, bool isCapture)
169 UInt32 propSize{};
170 auto err = GetDevPropertySize(devId, kAudioDevicePropertyStreamConfiguration, isCapture, 0,
171 &propSize);
172 if(err)
174 ERR("kAudioDevicePropertyStreamConfiguration size query failed: '%s' (%u)\n",
175 FourCCPrinter{err}.c_str(), err);
176 return 0;
179 auto buflist_data = std::make_unique<char[]>(propSize);
180 auto *buflist = reinterpret_cast<AudioBufferList*>(buflist_data.get());
182 err = GetDevProperty(devId, kAudioDevicePropertyStreamConfiguration, isCapture, 0, propSize,
183 buflist);
184 if(err)
186 ERR("kAudioDevicePropertyStreamConfiguration query failed: '%s' (%u)\n",
187 FourCCPrinter{err}.c_str(), err);
188 return 0;
191 UInt32 numChannels{0};
192 for(size_t i{0};i < buflist->mNumberBuffers;++i)
193 numChannels += buflist->mBuffers[i].mNumberChannels;
195 return numChannels;
199 void EnumerateDevices(std::vector<DeviceEntry> &list, bool isCapture)
201 UInt32 propSize{};
202 if(auto err = GetHwPropertySize(kAudioHardwarePropertyDevices, &propSize))
204 ERR("Failed to get device list size: %u\n", err);
205 return;
208 auto devIds = std::vector<AudioDeviceID>(propSize/sizeof(AudioDeviceID), kAudioDeviceUnknown);
209 if(auto err = GetHwProperty(kAudioHardwarePropertyDevices, propSize, devIds.data()))
211 ERR("Failed to get device list: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
212 return;
215 std::vector<DeviceEntry> newdevs;
216 newdevs.reserve(devIds.size());
218 AudioDeviceID defaultId{kAudioDeviceUnknown};
219 GetHwProperty(isCapture ? kAudioHardwarePropertyDefaultInputDevice :
220 kAudioHardwarePropertyDefaultOutputDevice, sizeof(defaultId), &defaultId);
222 if(defaultId != kAudioDeviceUnknown)
224 newdevs.emplace_back(DeviceEntry{defaultId, GetDeviceName(defaultId)});
225 const auto &entry = newdevs.back();
226 TRACE("Got device: %s = ID %u\n", entry.mName.c_str(), entry.mId);
228 for(const AudioDeviceID devId : devIds)
230 if(devId == kAudioDeviceUnknown)
231 continue;
233 auto match_devid = [devId](const DeviceEntry &entry) noexcept -> bool
234 { return entry.mId == devId; };
235 auto match = std::find_if(newdevs.cbegin(), newdevs.cend(), match_devid);
236 if(match != newdevs.cend()) continue;
238 auto numChannels = GetDeviceChannelCount(devId, isCapture);
239 if(numChannels > 0)
241 newdevs.emplace_back(DeviceEntry{devId, GetDeviceName(devId)});
242 const auto &entry = newdevs.back();
243 TRACE("Got device: %s = ID %u\n", entry.mName.c_str(), entry.mId);
247 if(newdevs.size() > 1)
249 /* Rename entries that have matching names, by appending '#2', '#3',
250 * etc, as needed.
252 for(auto curitem = newdevs.begin()+1;curitem != newdevs.end();++curitem)
254 auto check_match = [curitem](const DeviceEntry &entry) -> bool
255 { return entry.mName == curitem->mName; };
256 if(std::find_if(newdevs.begin(), curitem, check_match) != curitem)
258 std::string name{curitem->mName};
259 size_t count{1};
260 auto check_name = [&name](const DeviceEntry &entry) -> bool
261 { return entry.mName == name; };
262 do {
263 name = curitem->mName;
264 name += " #";
265 name += std::to_string(++count);
266 } while(std::find_if(newdevs.begin(), curitem, check_name) != curitem);
267 curitem->mName = std::move(name);
272 newdevs.shrink_to_fit();
273 newdevs.swap(list);
276 struct DeviceHelper {
277 DeviceHelper()
279 AudioObjectPropertyAddress addr{kAudioHardwarePropertyDefaultOutputDevice,
280 kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain};
281 OSStatus status = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &addr, DeviceListenerProc, nil);
282 if (status != noErr)
283 ERR("AudioObjectAddPropertyListener fail: %d", status);
285 ~DeviceHelper()
287 AudioObjectPropertyAddress addr{kAudioHardwarePropertyDefaultOutputDevice,
288 kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain};
289 OSStatus status = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &addr, DeviceListenerProc, nil);
290 if (status != noErr)
291 ERR("AudioObjectRemovePropertyListener fail: %d", status);
294 static OSStatus DeviceListenerProc(AudioObjectID /*inObjectID*/, UInt32 inNumberAddresses,
295 const AudioObjectPropertyAddress *inAddresses, void* /*inClientData*/)
297 for(UInt32 i = 0; i < inNumberAddresses; ++i)
299 switch(inAddresses[i].mSelector)
301 case kAudioHardwarePropertyDefaultOutputDevice:
302 case kAudioHardwarePropertyDefaultSystemOutputDevice:
303 alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback,
304 "Default playback device changed: "+std::to_string(inAddresses[i].mSelector));
305 break;
306 case kAudioHardwarePropertyDefaultInputDevice:
307 alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture,
308 "Default capture device changed: "+std::to_string(inAddresses[i].mSelector));
309 break;
312 return noErr;
316 static std::optional<DeviceHelper> sDeviceHelper;
318 #else
320 static constexpr char ca_device[] = "CoreAudio Default";
321 #endif
324 struct CoreAudioPlayback final : public BackendBase {
325 CoreAudioPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
326 ~CoreAudioPlayback() override;
328 OSStatus MixerProc(AudioUnitRenderActionFlags *ioActionFlags,
329 const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
330 AudioBufferList *ioData) noexcept;
332 void open(std::string_view name) override;
333 bool reset() override;
334 void start() override;
335 void stop() override;
337 AudioUnit mAudioUnit{};
339 uint mFrameSize{0u};
340 AudioStreamBasicDescription mFormat{}; // This is the OpenAL format as a CoreAudio ASBD
343 CoreAudioPlayback::~CoreAudioPlayback()
345 AudioUnitUninitialize(mAudioUnit);
346 AudioComponentInstanceDispose(mAudioUnit);
350 OSStatus CoreAudioPlayback::MixerProc(AudioUnitRenderActionFlags*, const AudioTimeStamp*, UInt32,
351 UInt32, AudioBufferList *ioData) noexcept
353 for(size_t i{0};i < ioData->mNumberBuffers;++i)
355 auto &buffer = ioData->mBuffers[i];
356 mDevice->renderSamples(buffer.mData, buffer.mDataByteSize/mFrameSize,
357 buffer.mNumberChannels);
359 return noErr;
363 void CoreAudioPlayback::open(std::string_view name)
365 #if CAN_ENUMERATE
366 AudioDeviceID audioDevice{kAudioDeviceUnknown};
367 if(name.empty())
368 GetHwProperty(kAudioHardwarePropertyDefaultOutputDevice, sizeof(audioDevice),
369 &audioDevice);
370 else
372 if(PlaybackList.empty())
373 EnumerateDevices(PlaybackList, false);
375 auto find_name = [name](const DeviceEntry &entry) -> bool
376 { return entry.mName == name; };
377 auto devmatch = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), find_name);
378 if(devmatch == PlaybackList.cend())
379 throw al::backend_exception{al::backend_error::NoDevice,
380 "Device name \"%.*s\" not found", al::sizei(name), name.data()};
382 audioDevice = devmatch->mId;
384 #else
385 if(name.empty())
386 name = ca_device;
387 else if(name != ca_device)
388 throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
389 al::sizei(name), name.data()};
390 #endif
392 /* open the default output unit */
393 AudioComponentDescription desc{};
394 desc.componentType = kAudioUnitType_Output;
395 #if CAN_ENUMERATE
396 desc.componentSubType = (audioDevice == kAudioDeviceUnknown) ?
397 kAudioUnitSubType_DefaultOutput : kAudioUnitSubType_HALOutput;
398 #else
399 desc.componentSubType = kAudioUnitSubType_RemoteIO;
400 #endif
401 desc.componentManufacturer = kAudioUnitManufacturer_Apple;
402 desc.componentFlags = 0;
403 desc.componentFlagsMask = 0;
405 AudioComponent comp{AudioComponentFindNext(NULL, &desc)};
406 if(comp == nullptr)
407 throw al::backend_exception{al::backend_error::NoDevice, "Could not find audio component"};
409 AudioUnit audioUnit{};
410 OSStatus err{AudioComponentInstanceNew(comp, &audioUnit)};
411 if(err != noErr)
412 throw al::backend_exception{al::backend_error::NoDevice,
413 "Could not create component instance: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
415 #if CAN_ENUMERATE
416 if(audioDevice != kAudioDeviceUnknown)
417 AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_CurrentDevice,
418 kAudioUnitScope_Global, OutputElement, &audioDevice, sizeof(AudioDeviceID));
419 #endif
421 err = AudioUnitInitialize(audioUnit);
422 if(err != noErr)
423 throw al::backend_exception{al::backend_error::DeviceError,
424 "Could not initialize audio unit: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
426 /* WARNING: I don't know if "valid" audio unit values are guaranteed to be
427 * non-0. If not, this logic is broken.
429 if(mAudioUnit)
431 AudioUnitUninitialize(mAudioUnit);
432 AudioComponentInstanceDispose(mAudioUnit);
434 mAudioUnit = audioUnit;
436 #if CAN_ENUMERATE
437 if(!name.empty())
438 mDeviceName = name;
439 else
441 UInt32 propSize{sizeof(audioDevice)};
442 audioDevice = kAudioDeviceUnknown;
443 AudioUnitGetProperty(audioUnit, kAudioOutputUnitProperty_CurrentDevice,
444 kAudioUnitScope_Global, OutputElement, &audioDevice, &propSize);
446 std::string devname{GetDeviceName(audioDevice)};
447 if(!devname.empty()) mDeviceName = std::move(devname);
448 else mDeviceName = "Unknown Device Name";
451 if(audioDevice != kAudioDeviceUnknown)
453 UInt32 type{};
454 err = GetDevProperty(audioDevice, kAudioDevicePropertyDataSource, false,
455 kAudioObjectPropertyElementMaster, sizeof(type), &type);
456 if(err != noErr)
457 ERR("Failed to get audio device type: %u\n", err);
458 else
460 TRACE("Got device type '%s'\n", FourCCPrinter{type}.c_str());
461 mDevice->Flags.set(DirectEar, (type == kIOAudioOutputPortSubTypeHeadphones));
465 #else
466 mDeviceName = name;
467 #endif
470 bool CoreAudioPlayback::reset()
472 OSStatus err{AudioUnitUninitialize(mAudioUnit)};
473 if(err != noErr)
474 ERR("AudioUnitUninitialize failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
476 /* retrieve default output unit's properties (output side) */
477 AudioStreamBasicDescription streamFormat{};
478 UInt32 size{sizeof(streamFormat)};
479 err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output,
480 OutputElement, &streamFormat, &size);
481 if(err != noErr || size != sizeof(streamFormat))
483 ERR("AudioUnitGetProperty(StreamFormat) failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(),
484 err);
485 return false;
488 #if 0
489 TRACE("Output streamFormat of default output unit -\n");
490 TRACE(" streamFormat.mFramesPerPacket = %d\n", streamFormat.mFramesPerPacket);
491 TRACE(" streamFormat.mChannelsPerFrame = %d\n", streamFormat.mChannelsPerFrame);
492 TRACE(" streamFormat.mBitsPerChannel = %d\n", streamFormat.mBitsPerChannel);
493 TRACE(" streamFormat.mBytesPerPacket = %d\n", streamFormat.mBytesPerPacket);
494 TRACE(" streamFormat.mBytesPerFrame = %d\n", streamFormat.mBytesPerFrame);
495 TRACE(" streamFormat.mSampleRate = %5.0f\n", streamFormat.mSampleRate);
496 #endif
498 /* Use the sample rate from the output unit's current parameters, but reset
499 * everything else.
501 if(mDevice->Frequency != streamFormat.mSampleRate)
503 mDevice->BufferSize = static_cast<uint>(mDevice->BufferSize*streamFormat.mSampleRate/
504 mDevice->Frequency + 0.5);
505 mDevice->Frequency = static_cast<uint>(streamFormat.mSampleRate);
508 /* FIXME: How to tell what channels are what in the output device, and how
509 * to specify what we're giving? e.g. 6.0 vs 5.1
511 streamFormat.mChannelsPerFrame = mDevice->channelsFromFmt();
513 streamFormat.mFramesPerPacket = 1;
514 streamFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kLinearPCMFormatFlagIsPacked;
515 streamFormat.mFormatID = kAudioFormatLinearPCM;
516 switch(mDevice->FmtType)
518 case DevFmtUByte:
519 mDevice->FmtType = DevFmtByte;
520 /* fall-through */
521 case DevFmtByte:
522 streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
523 streamFormat.mBitsPerChannel = 8;
524 break;
525 case DevFmtUShort:
526 mDevice->FmtType = DevFmtShort;
527 /* fall-through */
528 case DevFmtShort:
529 streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
530 streamFormat.mBitsPerChannel = 16;
531 break;
532 case DevFmtUInt:
533 mDevice->FmtType = DevFmtInt;
534 /* fall-through */
535 case DevFmtInt:
536 streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
537 streamFormat.mBitsPerChannel = 32;
538 break;
539 case DevFmtFloat:
540 streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsFloat;
541 streamFormat.mBitsPerChannel = 32;
542 break;
544 streamFormat.mBytesPerFrame = streamFormat.mChannelsPerFrame*streamFormat.mBitsPerChannel/8;
545 streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame*streamFormat.mFramesPerPacket;
547 err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
548 OutputElement, &streamFormat, sizeof(streamFormat));
549 if(err != noErr)
551 ERR("AudioUnitSetProperty(StreamFormat) failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(),
552 err);
553 return false;
556 setDefaultWFXChannelOrder();
558 /* setup callback */
559 mFrameSize = mDevice->frameSizeFromFmt();
560 AURenderCallbackStruct input{};
561 input.inputProc = [](void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) noexcept
562 { return static_cast<CoreAudioPlayback*>(inRefCon)->MixerProc(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData); };
563 input.inputProcRefCon = this;
565 err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_SetRenderCallback,
566 kAudioUnitScope_Input, OutputElement, &input, sizeof(AURenderCallbackStruct));
567 if(err != noErr)
569 ERR("AudioUnitSetProperty(SetRenderCallback) failed: '%s' (%u)\n",
570 FourCCPrinter{err}.c_str(), err);
571 return false;
574 /* init the default audio unit... */
575 err = AudioUnitInitialize(mAudioUnit);
576 if(err != noErr)
578 ERR("AudioUnitInitialize failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
579 return false;
582 return true;
585 void CoreAudioPlayback::start()
587 const OSStatus err{AudioOutputUnitStart(mAudioUnit)};
588 if(err != noErr)
589 throw al::backend_exception{al::backend_error::DeviceError,
590 "AudioOutputUnitStart failed: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
593 void CoreAudioPlayback::stop()
595 OSStatus err{AudioOutputUnitStop(mAudioUnit)};
596 if(err != noErr)
597 ERR("AudioOutputUnitStop failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
601 struct CoreAudioCapture final : public BackendBase {
602 CoreAudioCapture(DeviceBase *device) noexcept : BackendBase{device} { }
603 ~CoreAudioCapture() override;
605 OSStatus RecordProc(AudioUnitRenderActionFlags *ioActionFlags,
606 const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber,
607 UInt32 inNumberFrames, AudioBufferList *ioData) noexcept;
609 void open(std::string_view name) override;
610 void start() override;
611 void stop() override;
612 void captureSamples(std::byte *buffer, uint samples) override;
613 uint availableSamples() override;
615 AudioUnit mAudioUnit{0};
617 uint mFrameSize{0u};
618 AudioStreamBasicDescription mFormat{}; // This is the OpenAL format as a CoreAudio ASBD
620 SampleConverterPtr mConverter;
622 std::vector<char> mCaptureData;
624 RingBufferPtr mRing{nullptr};
627 CoreAudioCapture::~CoreAudioCapture()
629 if(mAudioUnit)
630 AudioComponentInstanceDispose(mAudioUnit);
631 mAudioUnit = 0;
635 OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags *ioActionFlags,
636 const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
637 AudioBufferList*) noexcept
639 union {
640 std::byte buf[std::max(sizeof(AudioBufferList), offsetof(AudioBufferList, mBuffers[1]))];
641 AudioBufferList list;
642 } audiobuf{};
644 audiobuf.list.mNumberBuffers = 1;
645 audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame;
646 audiobuf.list.mBuffers[0].mData = mCaptureData.data();
647 audiobuf.list.mBuffers[0].mDataByteSize = static_cast<UInt32>(mCaptureData.size());
649 OSStatus err{AudioUnitRender(mAudioUnit, ioActionFlags, inTimeStamp, inBusNumber,
650 inNumberFrames, &audiobuf.list)};
651 if(err != noErr)
653 ERR("AudioUnitRender capture error: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
654 return err;
657 std::ignore = mRing->write(mCaptureData.data(), inNumberFrames);
658 return noErr;
662 void CoreAudioCapture::open(std::string_view name)
664 #if CAN_ENUMERATE
665 AudioDeviceID audioDevice{kAudioDeviceUnknown};
666 if(name.empty())
667 GetHwProperty(kAudioHardwarePropertyDefaultInputDevice, sizeof(audioDevice),
668 &audioDevice);
669 else
671 if(CaptureList.empty())
672 EnumerateDevices(CaptureList, true);
674 auto find_name = [name](const DeviceEntry &entry) -> bool
675 { return entry.mName == name; };
676 auto devmatch = std::find_if(CaptureList.cbegin(), CaptureList.cend(), find_name);
677 if(devmatch == CaptureList.cend())
678 throw al::backend_exception{al::backend_error::NoDevice,
679 "Device name \"%.*s\" not found", al::sizei(name), name.data()};
681 audioDevice = devmatch->mId;
683 #else
684 if(name.empty())
685 name = ca_device;
686 else if(name != ca_device)
687 throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
688 al::sizei(name), name.data()};
689 #endif
691 AudioComponentDescription desc{};
692 desc.componentType = kAudioUnitType_Output;
693 #if CAN_ENUMERATE
694 desc.componentSubType = (audioDevice == kAudioDeviceUnknown) ?
695 kAudioUnitSubType_DefaultOutput : kAudioUnitSubType_HALOutput;
696 #else
697 desc.componentSubType = kAudioUnitSubType_RemoteIO;
698 #endif
699 desc.componentManufacturer = kAudioUnitManufacturer_Apple;
700 desc.componentFlags = 0;
701 desc.componentFlagsMask = 0;
703 // Search for component with given description
704 AudioComponent comp{AudioComponentFindNext(NULL, &desc)};
705 if(comp == NULL)
706 throw al::backend_exception{al::backend_error::NoDevice, "Could not find audio component"};
708 // Open the component
709 OSStatus err{AudioComponentInstanceNew(comp, &mAudioUnit)};
710 if(err != noErr)
711 throw al::backend_exception{al::backend_error::NoDevice,
712 "Could not create component instance: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
714 // Turn off AudioUnit output
715 UInt32 enableIO{0};
716 err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO,
717 kAudioUnitScope_Output, OutputElement, &enableIO, sizeof(enableIO));
718 if(err != noErr)
719 throw al::backend_exception{al::backend_error::DeviceError,
720 "Could not disable audio unit output property: '%s' (%u)", FourCCPrinter{err}.c_str(),
721 err};
723 // Turn on AudioUnit input
724 enableIO = 1;
725 err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO,
726 kAudioUnitScope_Input, InputElement, &enableIO, sizeof(enableIO));
727 if(err != noErr)
728 throw al::backend_exception{al::backend_error::DeviceError,
729 "Could not enable audio unit input property: '%s' (%u)", FourCCPrinter{err}.c_str(),
730 err};
732 #if CAN_ENUMERATE
733 if(audioDevice != kAudioDeviceUnknown)
734 AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice,
735 kAudioUnitScope_Global, InputElement, &audioDevice, sizeof(AudioDeviceID));
736 #endif
738 // set capture callback
739 AURenderCallbackStruct input{};
740 input.inputProc = [](void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) noexcept
741 { return static_cast<CoreAudioCapture*>(inRefCon)->RecordProc(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData); };
742 input.inputProcRefCon = this;
744 err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_SetInputCallback,
745 kAudioUnitScope_Global, InputElement, &input, sizeof(AURenderCallbackStruct));
746 if(err != noErr)
747 throw al::backend_exception{al::backend_error::DeviceError,
748 "Could not set capture callback: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
750 // Disable buffer allocation for capture
751 UInt32 flag{0};
752 err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_ShouldAllocateBuffer,
753 kAudioUnitScope_Output, InputElement, &flag, sizeof(flag));
754 if(err != noErr)
755 throw al::backend_exception{al::backend_error::DeviceError,
756 "Could not disable buffer allocation property: '%s' (%u)", FourCCPrinter{err}.c_str(),
757 err};
759 // Initialize the device
760 err = AudioUnitInitialize(mAudioUnit);
761 if(err != noErr)
762 throw al::backend_exception{al::backend_error::DeviceError,
763 "Could not initialize audio unit: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
765 // Get the hardware format
766 AudioStreamBasicDescription hardwareFormat{};
767 UInt32 propertySize{sizeof(hardwareFormat)};
768 err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
769 InputElement, &hardwareFormat, &propertySize);
770 if(err != noErr || propertySize != sizeof(hardwareFormat))
771 throw al::backend_exception{al::backend_error::DeviceError,
772 "Could not get input format: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
774 // Set up the requested format description
775 AudioStreamBasicDescription requestedFormat{};
776 switch(mDevice->FmtType)
778 case DevFmtByte:
779 requestedFormat.mBitsPerChannel = 8;
780 requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
781 break;
782 case DevFmtUByte:
783 requestedFormat.mBitsPerChannel = 8;
784 requestedFormat.mFormatFlags = kAudioFormatFlagIsPacked;
785 break;
786 case DevFmtShort:
787 requestedFormat.mBitsPerChannel = 16;
788 requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger
789 | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
790 break;
791 case DevFmtUShort:
792 requestedFormat.mBitsPerChannel = 16;
793 requestedFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
794 break;
795 case DevFmtInt:
796 requestedFormat.mBitsPerChannel = 32;
797 requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger
798 | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
799 break;
800 case DevFmtUInt:
801 requestedFormat.mBitsPerChannel = 32;
802 requestedFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
803 break;
804 case DevFmtFloat:
805 requestedFormat.mBitsPerChannel = 32;
806 requestedFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat | kAudioFormatFlagsNativeEndian
807 | kAudioFormatFlagIsPacked;
808 break;
811 switch(mDevice->FmtChans)
813 case DevFmtMono:
814 requestedFormat.mChannelsPerFrame = 1;
815 break;
816 case DevFmtStereo:
817 requestedFormat.mChannelsPerFrame = 2;
818 break;
820 case DevFmtQuad:
821 case DevFmtX51:
822 case DevFmtX61:
823 case DevFmtX71:
824 case DevFmtX714:
825 case DevFmtX7144:
826 case DevFmtX3D71:
827 case DevFmtAmbi3D:
828 throw al::backend_exception{al::backend_error::DeviceError, "%s not supported",
829 DevFmtChannelsString(mDevice->FmtChans)};
832 requestedFormat.mBytesPerFrame = requestedFormat.mChannelsPerFrame * requestedFormat.mBitsPerChannel / 8;
833 requestedFormat.mBytesPerPacket = requestedFormat.mBytesPerFrame;
834 requestedFormat.mSampleRate = mDevice->Frequency;
835 requestedFormat.mFormatID = kAudioFormatLinearPCM;
836 requestedFormat.mReserved = 0;
837 requestedFormat.mFramesPerPacket = 1;
839 // save requested format description for later use
840 mFormat = requestedFormat;
841 mFrameSize = mDevice->frameSizeFromFmt();
843 // Use intermediate format for sample rate conversion (outputFormat)
844 // Set sample rate to the same as hardware for resampling later
845 AudioStreamBasicDescription outputFormat{requestedFormat};
846 outputFormat.mSampleRate = hardwareFormat.mSampleRate;
848 // The output format should be the requested format, but using the hardware sample rate
849 // This is because the AudioUnit will automatically scale other properties, except for sample rate
850 err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output,
851 InputElement, &outputFormat, sizeof(outputFormat));
852 if(err != noErr)
853 throw al::backend_exception{al::backend_error::DeviceError,
854 "Could not set input format: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
856 /* Calculate the minimum AudioUnit output format frame count for the pre-
857 * conversion ring buffer. Ensure at least 100ms for the total buffer.
859 double srateScale{outputFormat.mSampleRate / mDevice->Frequency};
860 auto FrameCount64 = std::max(static_cast<uint64_t>(std::ceil(mDevice->BufferSize*srateScale)),
861 static_cast<UInt32>(outputFormat.mSampleRate)/10_u64);
862 FrameCount64 += MaxResamplerPadding;
863 if(FrameCount64 > std::numeric_limits<int32_t>::max())
864 throw al::backend_exception{al::backend_error::DeviceError,
865 "Calculated frame count is too large: %" PRIu64, FrameCount64};
867 UInt32 outputFrameCount{};
868 propertySize = sizeof(outputFrameCount);
869 err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_MaximumFramesPerSlice,
870 kAudioUnitScope_Global, OutputElement, &outputFrameCount, &propertySize);
871 if(err != noErr || propertySize != sizeof(outputFrameCount))
872 throw al::backend_exception{al::backend_error::DeviceError,
873 "Could not get input frame count: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
875 mCaptureData.resize(outputFrameCount * mFrameSize);
877 outputFrameCount = static_cast<UInt32>(std::max(uint64_t{outputFrameCount}, FrameCount64));
878 mRing = RingBuffer::Create(outputFrameCount, mFrameSize, false);
880 /* Set up sample converter if needed */
881 if(outputFormat.mSampleRate != mDevice->Frequency)
882 mConverter = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType,
883 mFormat.mChannelsPerFrame, static_cast<uint>(hardwareFormat.mSampleRate),
884 mDevice->Frequency, Resampler::FastBSinc24);
886 #if CAN_ENUMERATE
887 if(!name.empty())
888 mDeviceName = name;
889 else
891 UInt32 propSize{sizeof(audioDevice)};
892 audioDevice = kAudioDeviceUnknown;
893 AudioUnitGetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice,
894 kAudioUnitScope_Global, InputElement, &audioDevice, &propSize);
896 std::string devname{GetDeviceName(audioDevice)};
897 if(!devname.empty()) mDeviceName = std::move(devname);
898 else mDeviceName = "Unknown Device Name";
900 #else
901 mDeviceName = name;
902 #endif
906 void CoreAudioCapture::start()
908 OSStatus err{AudioOutputUnitStart(mAudioUnit)};
909 if(err != noErr)
910 throw al::backend_exception{al::backend_error::DeviceError,
911 "AudioOutputUnitStart failed: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
914 void CoreAudioCapture::stop()
916 OSStatus err{AudioOutputUnitStop(mAudioUnit)};
917 if(err != noErr)
918 ERR("AudioOutputUnitStop failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
921 void CoreAudioCapture::captureSamples(std::byte *buffer, uint samples)
923 if(!mConverter)
925 std::ignore = mRing->read(buffer, samples);
926 return;
929 auto rec_vec = mRing->getReadVector();
930 const void *src0{rec_vec.first.buf};
931 auto src0len = static_cast<uint>(rec_vec.first.len);
932 uint got{mConverter->convert(&src0, &src0len, buffer, samples)};
933 size_t total_read{rec_vec.first.len - src0len};
934 if(got < samples && !src0len && rec_vec.second.len > 0)
936 const void *src1{rec_vec.second.buf};
937 auto src1len = static_cast<uint>(rec_vec.second.len);
938 got += mConverter->convert(&src1, &src1len, buffer + got*mFrameSize, samples-got);
939 total_read += rec_vec.second.len - src1len;
942 mRing->readAdvance(total_read);
945 uint CoreAudioCapture::availableSamples()
947 if(!mConverter) return static_cast<uint>(mRing->readSpace());
948 return mConverter->availableOut(static_cast<uint>(mRing->readSpace()));
951 } // namespace
953 BackendFactory &CoreAudioBackendFactory::getFactory()
955 static CoreAudioBackendFactory factory{};
956 return factory;
959 bool CoreAudioBackendFactory::init()
961 #if CAN_ENUMERATE
962 sDeviceHelper.emplace();
963 #endif
964 return true;
967 bool CoreAudioBackendFactory::querySupport(BackendType type)
968 { return type == BackendType::Playback || type == BackendType::Capture; }
970 auto CoreAudioBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
972 std::vector<std::string> outnames;
973 #if CAN_ENUMERATE
974 auto append_name = [&outnames](const DeviceEntry &entry) -> void
975 { outnames.emplace_back(entry.mName); };
977 switch(type)
979 case BackendType::Playback:
980 EnumerateDevices(PlaybackList, false);
981 outnames.reserve(PlaybackList.size());
982 std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name);
983 break;
984 case BackendType::Capture:
985 EnumerateDevices(CaptureList, true);
986 outnames.reserve(CaptureList.size());
987 std::for_each(CaptureList.cbegin(), CaptureList.cend(), append_name);
988 break;
991 #else
993 switch(type)
995 case BackendType::Playback:
996 case BackendType::Capture:
997 outnames.emplace_back(ca_device);
998 break;
1000 #endif
1001 return outnames;
1004 BackendPtr CoreAudioBackendFactory::createBackend(DeviceBase *device, BackendType type)
1006 if(type == BackendType::Playback)
1007 return BackendPtr{new CoreAudioPlayback{device}};
1008 if(type == BackendType::Capture)
1009 return BackendPtr{new CoreAudioCapture{device}};
1010 return nullptr;
1013 alc::EventSupport CoreAudioBackendFactory::queryEventSupport(alc::EventType eventType, BackendType)
1015 switch(eventType)
1017 case alc::EventType::DefaultDeviceChanged:
1018 return alc::EventSupport::FullSupport;
1020 case alc::EventType::DeviceAdded:
1021 case alc::EventType::DeviceRemoved:
1022 case alc::EventType::Count:
1023 break;
1025 return alc::EventSupport::NoSupport;