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
23 #include "coreaudio.h"
37 #include "alnumeric.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
50 #include <IOKit/audio/IOAudioTypes.h>
51 #define CAN_ENUMERATE 1
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)
72 mString
[sizeof(UInt32
)-1-i
] = ch
;
76 constexpr FourCCPrinter(int code
) noexcept
: FourCCPrinter
{static_cast<UInt32
>(code
)} { }
78 constexpr const char *c_str() const noexcept
{ return mString
; }
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
,
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
)
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
)};
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
);
144 /* If that failed, just get the C string. Hopefully there's nothing bad
148 if(GetDevPropertySize(devId
, kAudioDevicePropertyDeviceName
, false, 0, &propSize
))
151 devname
.resize(propSize
+1, '\0');
152 if(GetDevProperty(devId
, kAudioDevicePropertyDeviceName
, false, 0, propSize
, &devname
[0]))
159 /* Clear extraneous nul chars that may have been written with the name
160 * string, and return it.
162 while(!devname
.back())
167 UInt32
GetDeviceChannelCount(AudioDeviceID devId
, bool isCapture
)
170 auto err
= GetDevPropertySize(devId
, kAudioDevicePropertyStreamConfiguration
, isCapture
, 0,
174 ERR("kAudioDevicePropertyStreamConfiguration size query failed: '%s' (%u)\n",
175 FourCCPrinter
{err
}.c_str(), err
);
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
,
186 ERR("kAudioDevicePropertyStreamConfiguration query failed: '%s' (%u)\n",
187 FourCCPrinter
{err
}.c_str(), err
);
191 UInt32 numChannels
{0};
192 for(size_t i
{0};i
< buflist
->mNumberBuffers
;++i
)
193 numChannels
+= buflist
->mBuffers
[i
].mNumberChannels
;
199 void EnumerateDevices(std::vector
<DeviceEntry
> &list
, bool isCapture
)
202 if(auto err
= GetHwPropertySize(kAudioHardwarePropertyDevices
, &propSize
))
204 ERR("Failed to get device list size: %u\n", err
);
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
);
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
)
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
);
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',
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
};
260 auto check_name
= [&name
](const DeviceEntry
&entry
) -> bool
261 { return entry
.mName
== name
; };
263 name
= curitem
->mName
;
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();
276 struct DeviceHelper
{
279 AudioObjectPropertyAddress addr
{kAudioHardwarePropertyDefaultOutputDevice
,
280 kAudioObjectPropertyScopeGlobal
, kAudioObjectPropertyElementMain
};
281 OSStatus status
= AudioObjectAddPropertyListener(kAudioObjectSystemObject
, &addr
, DeviceListenerProc
, nil
);
283 ERR("AudioObjectAddPropertyListener fail: %d", status
);
287 AudioObjectPropertyAddress addr
{kAudioHardwarePropertyDefaultOutputDevice
,
288 kAudioObjectPropertyScopeGlobal
, kAudioObjectPropertyElementMain
};
289 OSStatus status
= AudioObjectRemovePropertyListener(kAudioObjectSystemObject
, &addr
, DeviceListenerProc
, nil
);
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
));
306 case kAudioHardwarePropertyDefaultInputDevice
:
307 alc::Event(alc::EventType::DefaultDeviceChanged
, alc::DeviceType::Capture
,
308 "Default capture device changed: "+std::to_string(inAddresses
[i
].mSelector
));
316 static std::optional
<DeviceHelper
> sDeviceHelper
;
320 static constexpr char ca_device
[] = "CoreAudio Default";
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
{};
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
);
363 void CoreAudioPlayback::open(std::string_view name
)
366 AudioDeviceID audioDevice
{kAudioDeviceUnknown
};
368 GetHwProperty(kAudioHardwarePropertyDefaultOutputDevice
, sizeof(audioDevice
),
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
;
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()};
392 /* open the default output unit */
393 AudioComponentDescription desc
{};
394 desc
.componentType
= kAudioUnitType_Output
;
396 desc
.componentSubType
= (audioDevice
== kAudioDeviceUnknown
) ?
397 kAudioUnitSubType_DefaultOutput
: kAudioUnitSubType_HALOutput
;
399 desc
.componentSubType
= kAudioUnitSubType_RemoteIO
;
401 desc
.componentManufacturer
= kAudioUnitManufacturer_Apple
;
402 desc
.componentFlags
= 0;
403 desc
.componentFlagsMask
= 0;
405 AudioComponent comp
{AudioComponentFindNext(NULL
, &desc
)};
407 throw al::backend_exception
{al::backend_error::NoDevice
, "Could not find audio component"};
409 AudioUnit audioUnit
{};
410 OSStatus err
{AudioComponentInstanceNew(comp
, &audioUnit
)};
412 throw al::backend_exception
{al::backend_error::NoDevice
,
413 "Could not create component instance: '%s' (%u)", FourCCPrinter
{err
}.c_str(), err
};
416 if(audioDevice
!= kAudioDeviceUnknown
)
417 AudioUnitSetProperty(audioUnit
, kAudioOutputUnitProperty_CurrentDevice
,
418 kAudioUnitScope_Global
, OutputElement
, &audioDevice
, sizeof(AudioDeviceID
));
421 err
= AudioUnitInitialize(audioUnit
);
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.
431 AudioUnitUninitialize(mAudioUnit
);
432 AudioComponentInstanceDispose(mAudioUnit
);
434 mAudioUnit
= audioUnit
;
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
)
454 err
= GetDevProperty(audioDevice
, kAudioDevicePropertyDataSource
, false,
455 kAudioObjectPropertyElementMaster
, sizeof(type
), &type
);
457 ERR("Failed to get audio device type: %u\n", err
);
460 TRACE("Got device type '%s'\n", FourCCPrinter
{type
}.c_str());
461 mDevice
->Flags
.set(DirectEar
, (type
== kIOAudioOutputPortSubTypeHeadphones
));
470 bool CoreAudioPlayback::reset()
472 OSStatus err
{AudioUnitUninitialize(mAudioUnit
)};
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(),
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
);
498 /* Use the sample rate from the output unit's current parameters, but reset
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
)
519 mDevice
->FmtType
= DevFmtByte
;
522 streamFormat
.mFormatFlags
|= kLinearPCMFormatFlagIsSignedInteger
;
523 streamFormat
.mBitsPerChannel
= 8;
526 mDevice
->FmtType
= DevFmtShort
;
529 streamFormat
.mFormatFlags
|= kLinearPCMFormatFlagIsSignedInteger
;
530 streamFormat
.mBitsPerChannel
= 16;
533 mDevice
->FmtType
= DevFmtInt
;
536 streamFormat
.mFormatFlags
|= kLinearPCMFormatFlagIsSignedInteger
;
537 streamFormat
.mBitsPerChannel
= 32;
540 streamFormat
.mFormatFlags
|= kLinearPCMFormatFlagIsFloat
;
541 streamFormat
.mBitsPerChannel
= 32;
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
));
551 ERR("AudioUnitSetProperty(StreamFormat) failed: '%s' (%u)\n", FourCCPrinter
{err
}.c_str(),
556 setDefaultWFXChannelOrder();
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
));
569 ERR("AudioUnitSetProperty(SetRenderCallback) failed: '%s' (%u)\n",
570 FourCCPrinter
{err
}.c_str(), err
);
574 /* init the default audio unit... */
575 err
= AudioUnitInitialize(mAudioUnit
);
578 ERR("AudioUnitInitialize failed: '%s' (%u)\n", FourCCPrinter
{err
}.c_str(), err
);
585 void CoreAudioPlayback::start()
587 const OSStatus err
{AudioOutputUnitStart(mAudioUnit
)};
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
)};
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};
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()
630 AudioComponentInstanceDispose(mAudioUnit
);
635 OSStatus
CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags
*ioActionFlags
,
636 const AudioTimeStamp
*inTimeStamp
, UInt32 inBusNumber
, UInt32 inNumberFrames
,
637 AudioBufferList
*) noexcept
640 std::byte buf
[std::max(sizeof(AudioBufferList
), offsetof(AudioBufferList
, mBuffers
[1]))];
641 AudioBufferList list
;
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
)};
653 ERR("AudioUnitRender capture error: '%s' (%u)\n", FourCCPrinter
{err
}.c_str(), err
);
657 std::ignore
= mRing
->write(mCaptureData
.data(), inNumberFrames
);
662 void CoreAudioCapture::open(std::string_view name
)
665 AudioDeviceID audioDevice
{kAudioDeviceUnknown
};
667 GetHwProperty(kAudioHardwarePropertyDefaultInputDevice
, sizeof(audioDevice
),
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
;
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()};
691 AudioComponentDescription desc
{};
692 desc
.componentType
= kAudioUnitType_Output
;
694 desc
.componentSubType
= (audioDevice
== kAudioDeviceUnknown
) ?
695 kAudioUnitSubType_DefaultOutput
: kAudioUnitSubType_HALOutput
;
697 desc
.componentSubType
= kAudioUnitSubType_RemoteIO
;
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
)};
706 throw al::backend_exception
{al::backend_error::NoDevice
, "Could not find audio component"};
708 // Open the component
709 OSStatus err
{AudioComponentInstanceNew(comp
, &mAudioUnit
)};
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
716 err
= AudioUnitSetProperty(mAudioUnit
, kAudioOutputUnitProperty_EnableIO
,
717 kAudioUnitScope_Output
, OutputElement
, &enableIO
, sizeof(enableIO
));
719 throw al::backend_exception
{al::backend_error::DeviceError
,
720 "Could not disable audio unit output property: '%s' (%u)", FourCCPrinter
{err
}.c_str(),
723 // Turn on AudioUnit input
725 err
= AudioUnitSetProperty(mAudioUnit
, kAudioOutputUnitProperty_EnableIO
,
726 kAudioUnitScope_Input
, InputElement
, &enableIO
, sizeof(enableIO
));
728 throw al::backend_exception
{al::backend_error::DeviceError
,
729 "Could not enable audio unit input property: '%s' (%u)", FourCCPrinter
{err
}.c_str(),
733 if(audioDevice
!= kAudioDeviceUnknown
)
734 AudioUnitSetProperty(mAudioUnit
, kAudioOutputUnitProperty_CurrentDevice
,
735 kAudioUnitScope_Global
, InputElement
, &audioDevice
, sizeof(AudioDeviceID
));
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
));
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
752 err
= AudioUnitSetProperty(mAudioUnit
, kAudioUnitProperty_ShouldAllocateBuffer
,
753 kAudioUnitScope_Output
, InputElement
, &flag
, sizeof(flag
));
755 throw al::backend_exception
{al::backend_error::DeviceError
,
756 "Could not disable buffer allocation property: '%s' (%u)", FourCCPrinter
{err
}.c_str(),
759 // Initialize the device
760 err
= AudioUnitInitialize(mAudioUnit
);
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
)
779 requestedFormat
.mBitsPerChannel
= 8;
780 requestedFormat
.mFormatFlags
= kAudioFormatFlagIsSignedInteger
| kAudioFormatFlagIsPacked
;
783 requestedFormat
.mBitsPerChannel
= 8;
784 requestedFormat
.mFormatFlags
= kAudioFormatFlagIsPacked
;
787 requestedFormat
.mBitsPerChannel
= 16;
788 requestedFormat
.mFormatFlags
= kAudioFormatFlagIsSignedInteger
789 | kAudioFormatFlagsNativeEndian
| kAudioFormatFlagIsPacked
;
792 requestedFormat
.mBitsPerChannel
= 16;
793 requestedFormat
.mFormatFlags
= kAudioFormatFlagsNativeEndian
| kAudioFormatFlagIsPacked
;
796 requestedFormat
.mBitsPerChannel
= 32;
797 requestedFormat
.mFormatFlags
= kAudioFormatFlagIsSignedInteger
798 | kAudioFormatFlagsNativeEndian
| kAudioFormatFlagIsPacked
;
801 requestedFormat
.mBitsPerChannel
= 32;
802 requestedFormat
.mFormatFlags
= kAudioFormatFlagsNativeEndian
| kAudioFormatFlagIsPacked
;
805 requestedFormat
.mBitsPerChannel
= 32;
806 requestedFormat
.mFormatFlags
= kLinearPCMFormatFlagIsFloat
| kAudioFormatFlagsNativeEndian
807 | kAudioFormatFlagIsPacked
;
811 switch(mDevice
->FmtChans
)
814 requestedFormat
.mChannelsPerFrame
= 1;
817 requestedFormat
.mChannelsPerFrame
= 2;
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
));
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
);
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";
906 void CoreAudioCapture::start()
908 OSStatus err
{AudioOutputUnitStart(mAudioUnit
)};
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
)};
918 ERR("AudioOutputUnitStop failed: '%s' (%u)\n", FourCCPrinter
{err
}.c_str(), err
);
921 void CoreAudioCapture::captureSamples(std::byte
*buffer
, uint samples
)
925 std::ignore
= mRing
->read(buffer
, samples
);
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()));
953 BackendFactory
&CoreAudioBackendFactory::getFactory()
955 static CoreAudioBackendFactory factory
{};
959 bool CoreAudioBackendFactory::init()
962 sDeviceHelper
.emplace();
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
;
974 auto append_name
= [&outnames
](const DeviceEntry
&entry
) -> void
975 { outnames
.emplace_back(entry
.mName
); };
979 case BackendType::Playback
:
980 EnumerateDevices(PlaybackList
, false);
981 outnames
.reserve(PlaybackList
.size());
982 std::for_each(PlaybackList
.cbegin(), PlaybackList
.cend(), append_name
);
984 case BackendType::Capture
:
985 EnumerateDevices(CaptureList
, true);
986 outnames
.reserve(CaptureList
.size());
987 std::for_each(CaptureList
.cbegin(), CaptureList
.cend(), append_name
);
995 case BackendType::Playback
:
996 case BackendType::Capture
:
997 outnames
.emplace_back(ca_device
);
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
}};
1013 alc::EventSupport
CoreAudioBackendFactory::queryEventSupport(alc::EventType eventType
, BackendType
)
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
:
1025 return alc::EventSupport::NoSupport
;