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"
36 #include "alnumeric.h"
37 #include "core/converter.h"
38 #include "core/device.h"
39 #include "core/logging.h"
40 #include "ringbuffer.h"
42 #include <AudioUnit/AudioUnit.h>
43 #include <AudioToolbox/AudioToolbox.h>
48 #if TARGET_OS_IOS || TARGET_OS_TV
49 #define CAN_ENUMERATE 0
51 #define CAN_ENUMERATE 1
54 constexpr auto OutputElement = 0;
55 constexpr auto InputElement = 1;
63 std::vector<DeviceEntry> PlaybackList;
64 std::vector<DeviceEntry> CaptureList;
67 OSStatus GetHwProperty(AudioHardwarePropertyID propId, UInt32 dataSize, void *propData)
69 const AudioObjectPropertyAddress addr{propId, kAudioObjectPropertyScopeGlobal,
70 kAudioObjectPropertyElementMaster};
71 return AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr, 0, nullptr, &dataSize,
75 OSStatus GetHwPropertySize(AudioHardwarePropertyID propId, UInt32 *outSize)
77 const AudioObjectPropertyAddress addr{propId, kAudioObjectPropertyScopeGlobal,
78 kAudioObjectPropertyElementMaster};
79 return AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &addr, 0, nullptr, outSize);
82 OSStatus GetDevProperty(AudioDeviceID devId, AudioDevicePropertyID propId, bool isCapture,
83 UInt32 elem, UInt32 dataSize, void *propData)
85 static const AudioObjectPropertyScope scopes[2]{kAudioDevicePropertyScopeOutput,
86 kAudioDevicePropertyScopeInput};
87 const AudioObjectPropertyAddress addr{propId, scopes[isCapture], elem};
88 return AudioObjectGetPropertyData(devId, &addr, 0, nullptr, &dataSize, propData);
91 OSStatus GetDevPropertySize(AudioDeviceID devId, AudioDevicePropertyID inPropertyID,
92 bool isCapture, UInt32 elem, UInt32 *outSize)
94 static const AudioObjectPropertyScope scopes[2]{kAudioDevicePropertyScopeOutput,
95 kAudioDevicePropertyScopeInput};
96 const AudioObjectPropertyAddress addr{inPropertyID, scopes[isCapture], elem};
97 return AudioObjectGetPropertyDataSize(devId, &addr, 0, nullptr, outSize);
101 std::string GetDeviceName(AudioDeviceID devId)
106 /* Try to get the device name as a CFString, for Unicode name support. */
107 OSStatus err{GetDevProperty(devId, kAudioDevicePropertyDeviceNameCFString, false, 0,
108 sizeof(nameRef), &nameRef)};
111 const CFIndex propSize{CFStringGetMaximumSizeForEncoding(CFStringGetLength(nameRef),
112 kCFStringEncodingUTF8)};
113 devname.resize(static_cast<size_t>(propSize)+1, '\0');
115 CFStringGetCString(nameRef, &devname[0], propSize+1, kCFStringEncodingUTF8);
120 /* If that failed, just get the C string. Hopefully there's nothing bad
124 if(GetDevPropertySize(devId, kAudioDevicePropertyDeviceName, false, 0, &propSize))
127 devname.resize(propSize+1, '\0');
128 if(GetDevProperty(devId, kAudioDevicePropertyDeviceName, false, 0, propSize, &devname[0]))
135 /* Clear extraneous nul chars that may have been written with the name
136 * string, and return it.
138 while(!devname.back())
143 UInt32 GetDeviceChannelCount(AudioDeviceID devId, bool isCapture)
146 auto err = GetDevPropertySize(devId, kAudioDevicePropertyStreamConfiguration, isCapture, 0,
150 ERR("kAudioDevicePropertyStreamConfiguration size query failed: %u\n", err);
154 auto buflist_data = std::make_unique<char[]>(propSize);
155 auto *buflist = reinterpret_cast<AudioBufferList*>(buflist_data.get());
157 err = GetDevProperty(devId, kAudioDevicePropertyStreamConfiguration, isCapture, 0, propSize,
161 ERR("kAudioDevicePropertyStreamConfiguration query failed: %u\n", err);
165 UInt32 numChannels{0};
166 for(size_t i{0};i < buflist->mNumberBuffers;++i)
167 numChannels += buflist->mBuffers[i].mNumberChannels;
173 void EnumerateDevices(std::vector<DeviceEntry> &list, bool isCapture)
176 if(auto err = GetHwPropertySize(kAudioHardwarePropertyDevices, &propSize))
178 ERR("Failed to get device list size: %u\n", err);
182 auto devIds = std::vector<AudioDeviceID>(propSize/sizeof(AudioDeviceID), kAudioDeviceUnknown);
183 if(auto err = GetHwProperty(kAudioHardwarePropertyDevices, propSize, devIds.data()))
185 ERR("Failed to get device list: %u\n", err);
189 std::vector<DeviceEntry> newdevs;
190 newdevs.reserve(devIds.size());
192 AudioDeviceID defaultId{kAudioDeviceUnknown};
193 GetHwProperty(isCapture ? kAudioHardwarePropertyDefaultInputDevice :
194 kAudioHardwarePropertyDefaultOutputDevice, sizeof(defaultId), &defaultId);
196 if(defaultId != kAudioDeviceUnknown)
198 newdevs.emplace_back(DeviceEntry{defaultId, GetDeviceName(defaultId)});
199 const auto &entry = newdevs.back();
200 TRACE("Got device: %s = ID %u\n", entry.mName.c_str(), entry.mId);
202 for(const AudioDeviceID devId : devIds)
204 if(devId == kAudioDeviceUnknown)
207 auto match_devid = [devId](const DeviceEntry &entry) noexcept -> bool
208 { return entry.mId == devId; };
209 auto match = std::find_if(newdevs.cbegin(), newdevs.cend(), match_devid);
210 if(match != newdevs.cend()) continue;
212 auto numChannels = GetDeviceChannelCount(devId, isCapture);
215 newdevs.emplace_back(DeviceEntry{devId, GetDeviceName(devId)});
216 const auto &entry = newdevs.back();
217 TRACE("Got device: %s = ID %u\n", entry.mName.c_str(), entry.mId);
221 if(newdevs.size() > 1)
223 /* Rename entries that have matching names, by appending '#2', '#3',
226 for(auto curitem = newdevs.begin()+1;curitem != newdevs.end();++curitem)
228 auto check_match = [curitem](const DeviceEntry &entry) -> bool
229 { return entry.mName == curitem->mName; };
230 if(std::find_if(newdevs.begin(), curitem, check_match) != curitem)
232 std::string name{curitem->mName};
234 auto check_name = [&name](const DeviceEntry &entry) -> bool
235 { return entry.mName == name; };
237 name = curitem->mName;
239 name += std::to_string(++count);
240 } while(std::find_if(newdevs.begin(), curitem, check_name) != curitem);
241 curitem->mName = std::move(name);
246 newdevs.shrink_to_fit();
252 static constexpr char ca_device[] = "CoreAudio Default";
256 struct CoreAudioPlayback final : public BackendBase {
257 CoreAudioPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
258 ~CoreAudioPlayback() override;
260 OSStatus MixerProc(AudioUnitRenderActionFlags *ioActionFlags,
261 const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
262 AudioBufferList *ioData) noexcept;
263 static OSStatus MixerProcC(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags,
264 const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
265 AudioBufferList *ioData) noexcept
267 return static_cast<CoreAudioPlayback*>(inRefCon)->MixerProc(ioActionFlags, inTimeStamp,
268 inBusNumber, inNumberFrames, ioData);
271 void open(const char *name) override;
272 bool reset() override;
273 void start() override;
274 void stop() override;
276 AudioUnit mAudioUnit{};
279 AudioStreamBasicDescription mFormat{}; // This is the OpenAL format as a CoreAudio ASBD
281 DEF_NEWDEL(CoreAudioPlayback)
284 CoreAudioPlayback::~CoreAudioPlayback()
286 AudioUnitUninitialize(mAudioUnit);
287 AudioComponentInstanceDispose(mAudioUnit);
291 OSStatus CoreAudioPlayback::MixerProc(AudioUnitRenderActionFlags*, const AudioTimeStamp*, UInt32,
292 UInt32, AudioBufferList *ioData) noexcept
294 for(size_t i{0};i < ioData->mNumberBuffers;++i)
296 auto &buffer = ioData->mBuffers[i];
297 mDevice->renderSamples(buffer.mData, buffer.mDataByteSize/mFrameSize,
298 buffer.mNumberChannels);
304 void CoreAudioPlayback::open(const char *name)
307 AudioDeviceID audioDevice{kAudioDeviceUnknown};
309 GetHwProperty(kAudioHardwarePropertyDefaultOutputDevice, sizeof(audioDevice),
313 if(PlaybackList.empty())
314 EnumerateDevices(PlaybackList, false);
316 auto find_name = [name](const DeviceEntry &entry) -> bool
317 { return entry.mName == name; };
318 auto devmatch = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), find_name);
319 if(devmatch == PlaybackList.cend())
320 throw al::backend_exception{al::backend_error::NoDevice,
321 "Device name \"%s\" not found", name};
323 audioDevice = devmatch->mId;
328 else if(strcmp(name, ca_device) != 0)
329 throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
333 /* open the default output unit */
334 AudioComponentDescription desc{};
335 desc.componentType = kAudioUnitType_Output;
337 desc.componentSubType = (audioDevice == kAudioDeviceUnknown) ?
338 kAudioUnitSubType_DefaultOutput : kAudioUnitSubType_HALOutput;
340 desc.componentSubType = kAudioUnitSubType_RemoteIO;
342 desc.componentManufacturer = kAudioUnitManufacturer_Apple;
343 desc.componentFlags = 0;
344 desc.componentFlagsMask = 0;
346 AudioComponent comp{AudioComponentFindNext(NULL, &desc)};
348 throw al::backend_exception{al::backend_error::NoDevice, "Could not find audio component"};
350 AudioUnit audioUnit{};
351 OSStatus err{AudioComponentInstanceNew(comp, &audioUnit)};
353 throw al::backend_exception{al::backend_error::NoDevice,
354 "Could not create component instance: %u", err};
357 if(audioDevice != kAudioDeviceUnknown)
358 AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_CurrentDevice,
359 kAudioUnitScope_Global, OutputElement, &audioDevice, sizeof(AudioDeviceID));
362 err = AudioUnitInitialize(audioUnit);
364 throw al::backend_exception{al::backend_error::DeviceError,
365 "Could not initialize audio unit: %u", err};
367 /* WARNING: I don't know if "valid" audio unit values are guaranteed to be
368 * non-0. If not, this logic is broken.
372 AudioUnitUninitialize(mAudioUnit);
373 AudioComponentInstanceDispose(mAudioUnit);
375 mAudioUnit = audioUnit;
379 mDevice->DeviceName = name;
382 UInt32 propSize{sizeof(audioDevice)};
383 audioDevice = kAudioDeviceUnknown;
384 AudioUnitGetProperty(audioUnit, kAudioOutputUnitProperty_CurrentDevice,
385 kAudioUnitScope_Global, OutputElement, &audioDevice, &propSize);
387 std::string devname{GetDeviceName(audioDevice)};
388 if(!devname.empty()) mDevice->DeviceName = std::move(devname);
389 else mDevice->DeviceName = "Unknown Device Name";
392 mDevice->DeviceName = name;
396 bool CoreAudioPlayback::reset()
398 OSStatus err{AudioUnitUninitialize(mAudioUnit)};
400 ERR("-- AudioUnitUninitialize failed.\n");
402 /* retrieve default output unit's properties (output side) */
403 AudioStreamBasicDescription streamFormat{};
404 UInt32 size{sizeof(streamFormat)};
405 err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output,
406 OutputElement, &streamFormat, &size);
407 if(err != noErr || size != sizeof(streamFormat))
409 ERR("AudioUnitGetProperty failed\n");
414 TRACE("Output streamFormat of default output unit -\n");
415 TRACE(" streamFormat.mFramesPerPacket = %d\n", streamFormat.mFramesPerPacket);
416 TRACE(" streamFormat.mChannelsPerFrame = %d\n", streamFormat.mChannelsPerFrame);
417 TRACE(" streamFormat.mBitsPerChannel = %d\n", streamFormat.mBitsPerChannel);
418 TRACE(" streamFormat.mBytesPerPacket = %d\n", streamFormat.mBytesPerPacket);
419 TRACE(" streamFormat.mBytesPerFrame = %d\n", streamFormat.mBytesPerFrame);
420 TRACE(" streamFormat.mSampleRate = %5.0f\n", streamFormat.mSampleRate);
423 /* Use the sample rate from the output unit's current parameters, but reset
426 if(mDevice->Frequency != streamFormat.mSampleRate)
428 mDevice->BufferSize = static_cast<uint>(mDevice->BufferSize*streamFormat.mSampleRate/
429 mDevice->Frequency + 0.5);
430 mDevice->Frequency = static_cast<uint>(streamFormat.mSampleRate);
433 /* FIXME: How to tell what channels are what in the output device, and how
434 * to specify what we're giving? e.g. 6.0 vs 5.1
436 streamFormat.mChannelsPerFrame = mDevice->channelsFromFmt();
438 streamFormat.mFramesPerPacket = 1;
439 streamFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kLinearPCMFormatFlagIsPacked;
440 streamFormat.mFormatID = kAudioFormatLinearPCM;
441 switch(mDevice->FmtType)
444 mDevice->FmtType = DevFmtByte;
447 streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
448 streamFormat.mBitsPerChannel = 8;
451 mDevice->FmtType = DevFmtShort;
454 streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
455 streamFormat.mBitsPerChannel = 16;
458 mDevice->FmtType = DevFmtInt;
461 streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
462 streamFormat.mBitsPerChannel = 32;
465 streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsFloat;
466 streamFormat.mBitsPerChannel = 32;
469 streamFormat.mBytesPerFrame = streamFormat.mChannelsPerFrame*streamFormat.mBitsPerChannel/8;
470 streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame*streamFormat.mFramesPerPacket;
472 err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
473 OutputElement, &streamFormat, sizeof(streamFormat));
476 ERR("AudioUnitSetProperty failed\n");
480 setDefaultWFXChannelOrder();
483 mFrameSize = mDevice->frameSizeFromFmt();
484 AURenderCallbackStruct input{};
485 input.inputProc = CoreAudioPlayback::MixerProcC;
486 input.inputProcRefCon = this;
488 err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_SetRenderCallback,
489 kAudioUnitScope_Input, OutputElement, &input, sizeof(AURenderCallbackStruct));
492 ERR("AudioUnitSetProperty failed\n");
496 /* init the default audio unit... */
497 err = AudioUnitInitialize(mAudioUnit);
500 ERR("AudioUnitInitialize failed\n");
507 void CoreAudioPlayback::start()
509 const OSStatus err{AudioOutputUnitStart(mAudioUnit)};
511 throw al::backend_exception{al::backend_error::DeviceError,
512 "AudioOutputUnitStart failed: %d", err};
515 void CoreAudioPlayback::stop()
517 OSStatus err{AudioOutputUnitStop(mAudioUnit)};
519 ERR("AudioOutputUnitStop failed\n");
523 struct CoreAudioCapture final : public BackendBase {
524 CoreAudioCapture(DeviceBase *device) noexcept : BackendBase{device} { }
525 ~CoreAudioCapture() override;
527 OSStatus RecordProc(AudioUnitRenderActionFlags *ioActionFlags,
528 const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber,
529 UInt32 inNumberFrames, AudioBufferList *ioData) noexcept;
530 static OSStatus RecordProcC(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags,
531 const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
532 AudioBufferList *ioData) noexcept
534 return static_cast<CoreAudioCapture*>(inRefCon)->RecordProc(ioActionFlags, inTimeStamp,
535 inBusNumber, inNumberFrames, ioData);
538 void open(const char *name) override;
539 void start() override;
540 void stop() override;
541 void captureSamples(al::byte *buffer, uint samples) override;
542 uint availableSamples() override;
544 AudioUnit mAudioUnit{0};
547 AudioStreamBasicDescription mFormat{}; // This is the OpenAL format as a CoreAudio ASBD
549 SampleConverterPtr mConverter;
551 al::vector<char> mCaptureData;
553 RingBufferPtr mRing{nullptr};
555 DEF_NEWDEL(CoreAudioCapture)
558 CoreAudioCapture::~CoreAudioCapture()
561 AudioComponentInstanceDispose(mAudioUnit);
566 OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags *ioActionFlags,
567 const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
568 AudioBufferList*) noexcept
571 al::byte _[maxz(sizeof(AudioBufferList), offsetof(AudioBufferList, mBuffers[1]))];
572 AudioBufferList list;
575 audiobuf.list.mNumberBuffers = 1;
576 audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame;
577 audiobuf.list.mBuffers[0].mData = mCaptureData.data();
578 audiobuf.list.mBuffers[0].mDataByteSize = static_cast<UInt32>(mCaptureData.size());
580 OSStatus err{AudioUnitRender(mAudioUnit, ioActionFlags, inTimeStamp, inBusNumber,
581 inNumberFrames, &audiobuf.list)};
584 ERR("AudioUnitRender capture error: %d\n", err);
588 mRing->write(mCaptureData.data(), inNumberFrames);
593 void CoreAudioCapture::open(const char *name)
596 AudioDeviceID audioDevice{kAudioDeviceUnknown};
598 GetHwProperty(kAudioHardwarePropertyDefaultInputDevice, sizeof(audioDevice),
602 if(CaptureList.empty())
603 EnumerateDevices(CaptureList, true);
605 auto find_name = [name](const DeviceEntry &entry) -> bool
606 { return entry.mName == name; };
607 auto devmatch = std::find_if(CaptureList.cbegin(), CaptureList.cend(), find_name);
608 if(devmatch == CaptureList.cend())
609 throw al::backend_exception{al::backend_error::NoDevice,
610 "Device name \"%s\" not found", name};
612 audioDevice = devmatch->mId;
617 else if(strcmp(name, ca_device) != 0)
618 throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
622 AudioComponentDescription desc{};
623 desc.componentType = kAudioUnitType_Output;
625 desc.componentSubType = (audioDevice == kAudioDeviceUnknown) ?
626 kAudioUnitSubType_DefaultOutput : kAudioUnitSubType_HALOutput;
628 desc.componentSubType = kAudioUnitSubType_RemoteIO;
630 desc.componentManufacturer = kAudioUnitManufacturer_Apple;
631 desc.componentFlags = 0;
632 desc.componentFlagsMask = 0;
634 // Search for component with given description
635 AudioComponent comp{AudioComponentFindNext(NULL, &desc)};
637 throw al::backend_exception{al::backend_error::NoDevice, "Could not find audio component"};
639 // Open the component
640 OSStatus err{AudioComponentInstanceNew(comp, &mAudioUnit)};
642 throw al::backend_exception{al::backend_error::NoDevice,
643 "Could not create component instance: %u", err};
645 // Turn off AudioUnit output
647 err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO,
648 kAudioUnitScope_Output, OutputElement, &enableIO, sizeof(enableIO));
650 throw al::backend_exception{al::backend_error::DeviceError,
651 "Could not disable audio unit output property: %u", err};
653 // Turn on AudioUnit input
655 err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO,
656 kAudioUnitScope_Input, InputElement, &enableIO, sizeof(enableIO));
658 throw al::backend_exception{al::backend_error::DeviceError,
659 "Could not enable audio unit input property: %u", err};
662 if(audioDevice != kAudioDeviceUnknown)
663 AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice,
664 kAudioUnitScope_Global, InputElement, &audioDevice, sizeof(AudioDeviceID));
667 // set capture callback
668 AURenderCallbackStruct input{};
669 input.inputProc = CoreAudioCapture::RecordProcC;
670 input.inputProcRefCon = this;
672 err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_SetInputCallback,
673 kAudioUnitScope_Global, InputElement, &input, sizeof(AURenderCallbackStruct));
675 throw al::backend_exception{al::backend_error::DeviceError,
676 "Could not set capture callback: %u", err};
678 // Disable buffer allocation for capture
680 err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_ShouldAllocateBuffer,
681 kAudioUnitScope_Output, InputElement, &flag, sizeof(flag));
683 throw al::backend_exception{al::backend_error::DeviceError,
684 "Could not disable buffer allocation property: %u", err};
686 // Initialize the device
687 err = AudioUnitInitialize(mAudioUnit);
689 throw al::backend_exception{al::backend_error::DeviceError,
690 "Could not initialize audio unit: %u", err};
692 // Get the hardware format
693 AudioStreamBasicDescription hardwareFormat{};
694 UInt32 propertySize{sizeof(hardwareFormat)};
695 err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
696 InputElement, &hardwareFormat, &propertySize);
697 if(err != noErr || propertySize != sizeof(hardwareFormat))
698 throw al::backend_exception{al::backend_error::DeviceError,
699 "Could not get input format: %u", err};
701 // Set up the requested format description
702 AudioStreamBasicDescription requestedFormat{};
703 switch(mDevice->FmtType)
706 requestedFormat.mBitsPerChannel = 8;
707 requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
710 requestedFormat.mBitsPerChannel = 8;
711 requestedFormat.mFormatFlags = kAudioFormatFlagIsPacked;
714 requestedFormat.mBitsPerChannel = 16;
715 requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger
716 | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
719 requestedFormat.mBitsPerChannel = 16;
720 requestedFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
723 requestedFormat.mBitsPerChannel = 32;
724 requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger
725 | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
728 requestedFormat.mBitsPerChannel = 32;
729 requestedFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
732 requestedFormat.mBitsPerChannel = 32;
733 requestedFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat | kAudioFormatFlagsNativeEndian
734 | kAudioFormatFlagIsPacked;
738 switch(mDevice->FmtChans)
741 requestedFormat.mChannelsPerFrame = 1;
744 requestedFormat.mChannelsPerFrame = 2;
754 throw al::backend_exception{al::backend_error::DeviceError, "%s not supported",
755 DevFmtChannelsString(mDevice->FmtChans)};
758 requestedFormat.mBytesPerFrame = requestedFormat.mChannelsPerFrame * requestedFormat.mBitsPerChannel / 8;
759 requestedFormat.mBytesPerPacket = requestedFormat.mBytesPerFrame;
760 requestedFormat.mSampleRate = mDevice->Frequency;
761 requestedFormat.mFormatID = kAudioFormatLinearPCM;
762 requestedFormat.mReserved = 0;
763 requestedFormat.mFramesPerPacket = 1;
765 // save requested format description for later use
766 mFormat = requestedFormat;
767 mFrameSize = mDevice->frameSizeFromFmt();
769 // Use intermediate format for sample rate conversion (outputFormat)
770 // Set sample rate to the same as hardware for resampling later
771 AudioStreamBasicDescription outputFormat{requestedFormat};
772 outputFormat.mSampleRate = hardwareFormat.mSampleRate;
774 // The output format should be the requested format, but using the hardware sample rate
775 // This is because the AudioUnit will automatically scale other properties, except for sample rate
776 err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output,
777 InputElement, &outputFormat, sizeof(outputFormat));
779 throw al::backend_exception{al::backend_error::DeviceError,
780 "Could not set input format: %u", err};
782 /* Calculate the minimum AudioUnit output format frame count for the pre-
783 * conversion ring buffer. Ensure at least 100ms for the total buffer.
785 double srateScale{outputFormat.mSampleRate / mDevice->Frequency};
786 auto FrameCount64 = maxu64(static_cast<uint64_t>(std::ceil(mDevice->BufferSize*srateScale)),
787 static_cast<UInt32>(outputFormat.mSampleRate)/10);
788 FrameCount64 += MaxResamplerPadding;
789 if(FrameCount64 > std::numeric_limits<int32_t>::max())
790 throw al::backend_exception{al::backend_error::DeviceError,
791 "Calculated frame count is too large: %" PRIu64, FrameCount64};
793 UInt32 outputFrameCount{};
794 propertySize = sizeof(outputFrameCount);
795 err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_MaximumFramesPerSlice,
796 kAudioUnitScope_Global, OutputElement, &outputFrameCount, &propertySize);
797 if(err != noErr || propertySize != sizeof(outputFrameCount))
798 throw al::backend_exception{al::backend_error::DeviceError,
799 "Could not get input frame count: %u", err};
801 mCaptureData.resize(outputFrameCount * mFrameSize);
803 outputFrameCount = static_cast<UInt32>(maxu64(outputFrameCount, FrameCount64));
804 mRing = RingBuffer::Create(outputFrameCount, mFrameSize, false);
806 /* Set up sample converter if needed */
807 if(outputFormat.mSampleRate != mDevice->Frequency)
808 mConverter = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType,
809 mFormat.mChannelsPerFrame, static_cast<uint>(hardwareFormat.mSampleRate),
810 mDevice->Frequency, Resampler::FastBSinc24);
814 mDevice->DeviceName = name;
817 UInt32 propSize{sizeof(audioDevice)};
818 audioDevice = kAudioDeviceUnknown;
819 AudioUnitGetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice,
820 kAudioUnitScope_Global, InputElement, &audioDevice, &propSize);
822 std::string devname{GetDeviceName(audioDevice)};
823 if(!devname.empty()) mDevice->DeviceName = std::move(devname);
824 else mDevice->DeviceName = "Unknown Device Name";
827 mDevice->DeviceName = name;
832 void CoreAudioCapture::start()
834 OSStatus err{AudioOutputUnitStart(mAudioUnit)};
836 throw al::backend_exception{al::backend_error::DeviceError,
837 "AudioOutputUnitStart failed: %d", err};
840 void CoreAudioCapture::stop()
842 OSStatus err{AudioOutputUnitStop(mAudioUnit)};
844 ERR("AudioOutputUnitStop failed\n");
847 void CoreAudioCapture::captureSamples(al::byte *buffer, uint samples)
851 mRing->read(buffer, samples);
855 auto rec_vec = mRing->getReadVector();
856 const void *src0{rec_vec.first.buf};
857 auto src0len = static_cast<uint>(rec_vec.first.len);
858 uint got{mConverter->convert(&src0, &src0len, buffer, samples)};
859 size_t total_read{rec_vec.first.len - src0len};
860 if(got < samples && !src0len && rec_vec.second.len > 0)
862 const void *src1{rec_vec.second.buf};
863 auto src1len = static_cast<uint>(rec_vec.second.len);
864 got += mConverter->convert(&src1, &src1len, buffer + got*mFrameSize, samples-got);
865 total_read += rec_vec.second.len - src1len;
868 mRing->readAdvance(total_read);
871 uint CoreAudioCapture::availableSamples()
873 if(!mConverter) return static_cast<uint>(mRing->readSpace());
874 return mConverter->availableOut(static_cast<uint>(mRing->readSpace()));
879 BackendFactory &CoreAudioBackendFactory::getFactory()
881 static CoreAudioBackendFactory factory{};
885 bool CoreAudioBackendFactory::init() { return true; }
887 bool CoreAudioBackendFactory::querySupport(BackendType type)
888 { return type == BackendType::Playback || type == BackendType::Capture; }
890 std::string CoreAudioBackendFactory::probe(BackendType type)
892 std::string outnames;
894 auto append_name = [&outnames](const DeviceEntry &entry) -> void
896 /* Includes null char. */
897 outnames.append(entry.mName.c_str(), entry.mName.length()+1);
901 case BackendType::Playback:
902 EnumerateDevices(PlaybackList, false);
903 std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name);
905 case BackendType::Capture:
906 EnumerateDevices(CaptureList, true);
907 std::for_each(CaptureList.cbegin(), CaptureList.cend(), append_name);
915 case BackendType::Playback:
916 case BackendType::Capture:
917 /* Includes null char. */
918 outnames.append(ca_device, sizeof(ca_device));
925 BackendPtr CoreAudioBackendFactory::createBackend(DeviceBase *device, BackendType type)
927 if(type == BackendType::Playback)
928 return BackendPtr{new CoreAudioPlayback{device}};
929 if(type == BackendType::Capture)
930 return BackendPtr{new CoreAudioCapture{device}};