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"
38 #include "alnumeric.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
51 #include <IOKit/audio/IOAudioTypes.h>
52 #define CAN_ENUMERATE 1
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)
103 mString
[sizeof(UInt32
)-1-i
] = ch
;
107 constexpr FourCCPrinter(int code
) noexcept
: FourCCPrinter
{static_cast<UInt32
>(code
)} { }
109 constexpr const char *c_str() const noexcept
{ return mString
; }
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
,
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
)
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
)};
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
);
175 /* If that failed, just get the C string. Hopefully there's nothing bad
179 if(GetDevPropertySize(devId
, kAudioDevicePropertyDeviceName
, false, 0, &propSize
))
182 devname
.resize(propSize
+1, '\0');
183 if(GetDevProperty(devId
, kAudioDevicePropertyDeviceName
, false, 0, propSize
, &devname
[0]))
190 /* Clear extraneous nul chars that may have been written with the name
191 * string, and return it.
193 while(!devname
.empty() && !devname
.back())
198 UInt32
GetDeviceChannelCount(AudioDeviceID devId
, bool isCapture
)
201 auto err
= GetDevPropertySize(devId
, kAudioUnitProperty_AudioChannelLayout
, isCapture
, 0,
205 ERR("kAudioDevicePropertyStreamConfiguration size query failed: '%s' (%u)\n",
206 FourCCPrinter
{err
}.c_str(), err
);
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
,
217 ERR("kAudioDevicePropertyStreamConfiguration query failed: '%s' (%u)\n",
218 FourCCPrinter
{err
}.c_str(), err
);
222 return channel_layout
->mNumberChannelDescriptions
;
226 void EnumerateDevices(std::vector
<DeviceEntry
> &list
, bool isCapture
)
229 if(auto err
= GetHwPropertySize(kAudioHardwarePropertyDevices
, &propSize
))
231 ERR("Failed to get device list size: %u\n", err
);
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
);
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
)
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
);
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',
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
};
287 auto check_name
= [&name
](const DeviceEntry
&entry
) -> bool
288 { return entry
.mName
== name
; };
290 name
= curitem
->mName
;
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();
303 struct DeviceHelper
{
306 AudioObjectPropertyAddress addr
{kAudioHardwarePropertyDefaultOutputDevice
,
307 kAudioObjectPropertyScopeGlobal
, kAudioObjectPropertyElementMaster
};
308 OSStatus status
= AudioObjectAddPropertyListener(kAudioObjectSystemObject
, &addr
, DeviceListenerProc
, nil
);
310 ERR("AudioObjectAddPropertyListener fail: %d", status
);
314 AudioObjectPropertyAddress addr
{kAudioHardwarePropertyDefaultOutputDevice
,
315 kAudioObjectPropertyScopeGlobal
, kAudioObjectPropertyElementMaster
};
316 OSStatus status
= AudioObjectRemovePropertyListener(kAudioObjectSystemObject
, &addr
, DeviceListenerProc
, nil
);
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
));
333 case kAudioHardwarePropertyDefaultInputDevice
:
334 alc::Event(alc::EventType::DefaultDeviceChanged
, alc::DeviceType::Capture
,
335 "Default capture device changed: "+std::to_string(inAddresses
[i
].mSelector
));
343 static std::optional
<DeviceHelper
> sDeviceHelper
;
347 static constexpr char ca_device
[] = "CoreAudio Default";
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
{};
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
);
390 void CoreAudioPlayback::open(std::string_view name
)
393 AudioDeviceID audioDevice
{kAudioDeviceUnknown
};
395 GetHwProperty(kAudioHardwarePropertyDefaultOutputDevice
, sizeof(audioDevice
),
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
;
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()};
419 /* open the default output unit */
420 AudioComponentDescription desc
{};
421 desc
.componentType
= kAudioUnitType_Output
;
423 desc
.componentSubType
= (audioDevice
== kAudioDeviceUnknown
) ?
424 kAudioUnitSubType_DefaultOutput
: kAudioUnitSubType_HALOutput
;
426 desc
.componentSubType
= kAudioUnitSubType_RemoteIO
;
428 desc
.componentManufacturer
= kAudioUnitManufacturer_Apple
;
429 desc
.componentFlags
= 0;
430 desc
.componentFlagsMask
= 0;
432 AudioComponent comp
{AudioComponentFindNext(NULL
, &desc
)};
434 throw al::backend_exception
{al::backend_error::NoDevice
, "Could not find audio component"};
436 AudioUnit audioUnit
{};
437 OSStatus err
{AudioComponentInstanceNew(comp
, &audioUnit
)};
439 throw al::backend_exception
{al::backend_error::NoDevice
,
440 "Could not create component instance: '%s' (%u)", FourCCPrinter
{err
}.c_str(), err
};
443 if(audioDevice
!= kAudioDeviceUnknown
)
444 AudioUnitSetProperty(audioUnit
, kAudioOutputUnitProperty_CurrentDevice
,
445 kAudioUnitScope_Global
, OutputElement
, &audioDevice
, sizeof(AudioDeviceID
));
448 err
= AudioUnitInitialize(audioUnit
);
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.
458 AudioUnitUninitialize(mAudioUnit
);
459 AudioComponentInstanceDispose(mAudioUnit
);
461 mAudioUnit
= audioUnit
;
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
)
481 err
= GetDevProperty(audioDevice
, kAudioDevicePropertyDataSource
, false,
482 kAudioObjectPropertyElementMaster
, sizeof(type
), &type
);
484 ERR("Failed to get audio device type: %u\n", err
);
487 TRACE("Got device type '%s'\n", FourCCPrinter
{type
}.c_str());
488 mDevice
->Flags
.set(DirectEar
, (type
== kIOAudioOutputPortSubTypeHeadphones
));
497 bool CoreAudioPlayback::reset()
499 OSStatus err
{AudioUnitUninitialize(mAudioUnit
)};
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(),
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
);
525 /* Use the sample rate from the output unit's current parameters, but reset
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
);
537 al::span
<const AudioChannelLabel
> map
;
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
)
562 mDevice
->FmtType
= DevFmtByte
;
565 streamFormat
.mFormatFlags
|= kLinearPCMFormatFlagIsSignedInteger
;
566 streamFormat
.mBitsPerChannel
= 8;
569 mDevice
->FmtType
= DevFmtShort
;
572 streamFormat
.mFormatFlags
|= kLinearPCMFormatFlagIsSignedInteger
;
573 streamFormat
.mBitsPerChannel
= 16;
576 mDevice
->FmtType
= DevFmtInt
;
579 streamFormat
.mFormatFlags
|= kLinearPCMFormatFlagIsSignedInteger
;
580 streamFormat
.mBitsPerChannel
= 32;
583 streamFormat
.mFormatFlags
|= kLinearPCMFormatFlagIsFloat
;
584 streamFormat
.mBitsPerChannel
= 32;
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
));
594 ERR("AudioUnitSetProperty(StreamFormat) failed: '%s' (%u)\n", FourCCPrinter
{err
}.c_str(),
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
);
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
);
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(),
629 if(chaniter
!= chanmaps
.cend())
630 mDevice
->FmtChans
= chaniter
->fmt
;
635 setDefaultWFXChannelOrder();
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
));
648 ERR("AudioUnitSetProperty(SetRenderCallback) failed: '%s' (%u)\n",
649 FourCCPrinter
{err
}.c_str(), err
);
653 /* init the default audio unit... */
654 err
= AudioUnitInitialize(mAudioUnit
);
657 ERR("AudioUnitInitialize failed: '%s' (%u)\n", FourCCPrinter
{err
}.c_str(), err
);
664 void CoreAudioPlayback::start()
666 const OSStatus err
{AudioOutputUnitStart(mAudioUnit
)};
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
)};
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};
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()
709 AudioComponentInstanceDispose(mAudioUnit
);
714 OSStatus
CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags
*ioActionFlags
,
715 const AudioTimeStamp
*inTimeStamp
, UInt32 inBusNumber
, UInt32 inNumberFrames
,
716 AudioBufferList
*) noexcept
719 std::byte buf
[std::max(sizeof(AudioBufferList
), offsetof(AudioBufferList
, mBuffers
[1]))];
720 AudioBufferList list
;
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
)};
732 ERR("AudioUnitRender capture error: '%s' (%u)\n", FourCCPrinter
{err
}.c_str(), err
);
736 std::ignore
= mRing
->write(mCaptureData
.data(), inNumberFrames
);
741 void CoreAudioCapture::open(std::string_view name
)
744 AudioDeviceID audioDevice
{kAudioDeviceUnknown
};
746 GetHwProperty(kAudioHardwarePropertyDefaultInputDevice
, sizeof(audioDevice
),
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
;
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()};
770 AudioComponentDescription desc
{};
771 desc
.componentType
= kAudioUnitType_Output
;
773 desc
.componentSubType
= (audioDevice
== kAudioDeviceUnknown
) ?
774 kAudioUnitSubType_DefaultOutput
: kAudioUnitSubType_HALOutput
;
776 desc
.componentSubType
= kAudioUnitSubType_RemoteIO
;
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
)};
785 throw al::backend_exception
{al::backend_error::NoDevice
, "Could not find audio component"};
787 // Open the component
788 OSStatus err
{AudioComponentInstanceNew(comp
, &mAudioUnit
)};
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
795 err
= AudioUnitSetProperty(mAudioUnit
, kAudioOutputUnitProperty_EnableIO
,
796 kAudioUnitScope_Output
, OutputElement
, &enableIO
, sizeof(enableIO
));
798 throw al::backend_exception
{al::backend_error::DeviceError
,
799 "Could not disable audio unit output property: '%s' (%u)", FourCCPrinter
{err
}.c_str(),
802 // Turn on AudioUnit input
804 err
= AudioUnitSetProperty(mAudioUnit
, kAudioOutputUnitProperty_EnableIO
,
805 kAudioUnitScope_Input
, InputElement
, &enableIO
, sizeof(enableIO
));
807 throw al::backend_exception
{al::backend_error::DeviceError
,
808 "Could not enable audio unit input property: '%s' (%u)", FourCCPrinter
{err
}.c_str(),
812 if(audioDevice
!= kAudioDeviceUnknown
)
813 AudioUnitSetProperty(mAudioUnit
, kAudioOutputUnitProperty_CurrentDevice
,
814 kAudioUnitScope_Global
, InputElement
, &audioDevice
, sizeof(AudioDeviceID
));
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
));
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
831 err
= AudioUnitSetProperty(mAudioUnit
, kAudioUnitProperty_ShouldAllocateBuffer
,
832 kAudioUnitScope_Output
, InputElement
, &flag
, sizeof(flag
));
834 throw al::backend_exception
{al::backend_error::DeviceError
,
835 "Could not disable buffer allocation property: '%s' (%u)", FourCCPrinter
{err
}.c_str(),
838 // Initialize the device
839 err
= AudioUnitInitialize(mAudioUnit
);
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
)
858 requestedFormat
.mBitsPerChannel
= 8;
859 requestedFormat
.mFormatFlags
= kAudioFormatFlagIsSignedInteger
| kAudioFormatFlagIsPacked
;
862 requestedFormat
.mBitsPerChannel
= 8;
863 requestedFormat
.mFormatFlags
= kAudioFormatFlagIsPacked
;
866 requestedFormat
.mBitsPerChannel
= 16;
867 requestedFormat
.mFormatFlags
= kAudioFormatFlagIsSignedInteger
868 | kAudioFormatFlagsNativeEndian
| kAudioFormatFlagIsPacked
;
871 requestedFormat
.mBitsPerChannel
= 16;
872 requestedFormat
.mFormatFlags
= kAudioFormatFlagsNativeEndian
| kAudioFormatFlagIsPacked
;
875 requestedFormat
.mBitsPerChannel
= 32;
876 requestedFormat
.mFormatFlags
= kAudioFormatFlagIsSignedInteger
877 | kAudioFormatFlagsNativeEndian
| kAudioFormatFlagIsPacked
;
880 requestedFormat
.mBitsPerChannel
= 32;
881 requestedFormat
.mFormatFlags
= kAudioFormatFlagsNativeEndian
| kAudioFormatFlagIsPacked
;
884 requestedFormat
.mBitsPerChannel
= 32;
885 requestedFormat
.mFormatFlags
= kLinearPCMFormatFlagIsFloat
| kAudioFormatFlagsNativeEndian
886 | kAudioFormatFlagIsPacked
;
890 switch(mDevice
->FmtChans
)
893 requestedFormat
.mChannelsPerFrame
= 1;
896 requestedFormat
.mChannelsPerFrame
= 2;
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
));
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
);
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";
985 void CoreAudioCapture::start()
987 OSStatus err
{AudioOutputUnitStart(mAudioUnit
)};
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
)};
997 ERR("AudioOutputUnitStop failed: '%s' (%u)\n", FourCCPrinter
{err
}.c_str(), err
);
1000 void CoreAudioCapture::captureSamples(std::byte
*buffer
, uint samples
)
1004 std::ignore
= mRing
->read(buffer
, samples
);
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()));
1032 BackendFactory
&CoreAudioBackendFactory::getFactory()
1034 static CoreAudioBackendFactory factory
{};
1038 bool CoreAudioBackendFactory::init()
1041 sDeviceHelper
.emplace();
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
;
1053 auto append_name
= [&outnames
](const DeviceEntry
&entry
) -> void
1054 { outnames
.emplace_back(entry
.mName
); };
1058 case BackendType::Playback
:
1059 EnumerateDevices(PlaybackList
, false);
1060 outnames
.reserve(PlaybackList
.size());
1061 std::for_each(PlaybackList
.cbegin(), PlaybackList
.cend(), append_name
);
1063 case BackendType::Capture
:
1064 EnumerateDevices(CaptureList
, true);
1065 outnames
.reserve(CaptureList
.size());
1066 std::for_each(CaptureList
.cbegin(), CaptureList
.cend(), append_name
);
1074 case BackendType::Playback
:
1075 case BackendType::Capture
:
1076 outnames
.emplace_back(ca_device
);
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
}};
1092 alc::EventSupport
CoreAudioBackendFactory::queryEventSupport(alc::EventType eventType
, BackendType
)
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
:
1104 return alc::EventSupport::NoSupport
;