Handle 3D7.1 as a separate channel configuration
[openal-soft.git] / alc / backends / coreaudio.cpp
blobb81bd58a1424b44b23903fabc5b9d98c4659e704
1 /**
2 * OpenAL cross platform audio library
3 * Copyright (C) 1999-2007 by authors.
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 * Or go to http://www.gnu.org/copyleft/lgpl.html
21 #include "config.h"
23 #include "coreaudio.h"
25 #include <inttypes.h>
26 #include <stdint.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
32 #include <cmath>
33 #include <memory>
34 #include <string>
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>
46 namespace {
48 #if TARGET_OS_IOS || TARGET_OS_TV
49 #define CAN_ENUMERATE 0
50 #else
51 #define CAN_ENUMERATE 1
52 #endif
55 #if CAN_ENUMERATE
56 struct DeviceEntry {
57 AudioDeviceID mId;
58 std::string mName;
61 std::vector<DeviceEntry> PlaybackList;
62 std::vector<DeviceEntry> CaptureList;
65 OSStatus GetHwProperty(AudioHardwarePropertyID propId, UInt32 dataSize, void *propData)
67 const AudioObjectPropertyAddress addr{propId, kAudioObjectPropertyScopeGlobal,
68 kAudioObjectPropertyElementMaster};
69 return AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr, 0, nullptr, &dataSize,
70 propData);
73 OSStatus GetHwPropertySize(AudioHardwarePropertyID propId, UInt32 *outSize)
75 const AudioObjectPropertyAddress addr{propId, kAudioObjectPropertyScopeGlobal,
76 kAudioObjectPropertyElementMaster};
77 return AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &addr, 0, nullptr, outSize);
80 OSStatus GetDevProperty(AudioDeviceID devId, AudioDevicePropertyID propId, bool isCapture,
81 UInt32 elem, UInt32 dataSize, void *propData)
83 static const AudioObjectPropertyScope scopes[2]{kAudioDevicePropertyScopeOutput,
84 kAudioDevicePropertyScopeInput};
85 const AudioObjectPropertyAddress addr{propId, scopes[isCapture], elem};
86 return AudioObjectGetPropertyData(devId, &addr, 0, nullptr, &dataSize, propData);
89 OSStatus GetDevPropertySize(AudioDeviceID devId, AudioDevicePropertyID inPropertyID,
90 bool isCapture, UInt32 elem, UInt32 *outSize)
92 static const AudioObjectPropertyScope scopes[2]{kAudioDevicePropertyScopeOutput,
93 kAudioDevicePropertyScopeInput};
94 const AudioObjectPropertyAddress addr{inPropertyID, scopes[isCapture], elem};
95 return AudioObjectGetPropertyDataSize(devId, &addr, 0, nullptr, outSize);
99 std::string GetDeviceName(AudioDeviceID devId)
101 std::string devname;
102 CFStringRef nameRef;
104 /* Try to get the device name as a CFString, for Unicode name support. */
105 OSStatus err{GetDevProperty(devId, kAudioDevicePropertyDeviceNameCFString, false, 0,
106 sizeof(nameRef), &nameRef)};
107 if(err == noErr)
109 const CFIndex propSize{CFStringGetMaximumSizeForEncoding(CFStringGetLength(nameRef),
110 kCFStringEncodingUTF8)};
111 devname.resize(static_cast<size_t>(propSize)+1, '\0');
113 CFStringGetCString(nameRef, &devname[0], propSize+1, kCFStringEncodingUTF8);
114 CFRelease(nameRef);
116 else
118 /* If that failed, just get the C string. Hopefully there's nothing bad
119 * with this.
121 UInt32 propSize{};
122 if(GetDevPropertySize(devId, kAudioDevicePropertyDeviceName, false, 0, &propSize))
123 return devname;
125 devname.resize(propSize+1, '\0');
126 if(GetDevProperty(devId, kAudioDevicePropertyDeviceName, false, 0, propSize, &devname[0]))
128 devname.clear();
129 return devname;
133 /* Clear extraneous nul chars that may have been written with the name
134 * string, and return it.
136 while(!devname.back())
137 devname.pop_back();
138 return devname;
141 UInt32 GetDeviceChannelCount(AudioDeviceID devId, bool isCapture)
143 UInt32 propSize{};
144 auto err = GetDevPropertySize(devId, kAudioDevicePropertyStreamConfiguration, isCapture, 0,
145 &propSize);
146 if(err)
148 ERR("kAudioDevicePropertyStreamConfiguration size query failed: %u\n", err);
149 return 0;
152 auto buflist_data = std::make_unique<char[]>(propSize);
153 auto *buflist = reinterpret_cast<AudioBufferList*>(buflist_data.get());
155 err = GetDevProperty(devId, kAudioDevicePropertyStreamConfiguration, isCapture, 0, propSize,
156 buflist);
157 if(err)
159 ERR("kAudioDevicePropertyStreamConfiguration query failed: %u\n", err);
160 return 0;
163 UInt32 numChannels{0};
164 for(size_t i{0};i < buflist->mNumberBuffers;++i)
165 numChannels += buflist->mBuffers[i].mNumberChannels;
167 return numChannels;
171 void EnumerateDevices(std::vector<DeviceEntry> &list, bool isCapture)
173 UInt32 propSize{};
174 if(auto err = GetHwPropertySize(kAudioHardwarePropertyDevices, &propSize))
176 ERR("Failed to get device list size: %u\n", err);
177 return;
180 auto devIds = std::vector<AudioDeviceID>(propSize/sizeof(AudioDeviceID), kAudioDeviceUnknown);
181 if(auto err = GetHwProperty(kAudioHardwarePropertyDevices, propSize, devIds.data()))
183 ERR("Failed to get device list: %u\n", err);
184 return;
187 std::vector<DeviceEntry> newdevs;
188 newdevs.reserve(devIds.size());
190 AudioDeviceID defaultId{kAudioDeviceUnknown};
191 GetHwProperty(isCapture ? kAudioHardwarePropertyDefaultInputDevice :
192 kAudioHardwarePropertyDefaultOutputDevice, sizeof(defaultId), &defaultId);
194 if(defaultId != kAudioDeviceUnknown)
196 newdevs.emplace_back(DeviceEntry{defaultId, GetDeviceName(defaultId)});
197 const auto &entry = newdevs.back();
198 TRACE("Got device: %s = ID %u\n", entry.mName.c_str(), entry.mId);
200 for(const AudioDeviceID devId : devIds)
202 if(devId == kAudioDeviceUnknown)
203 continue;
205 auto match_devid = [devId](const DeviceEntry &entry) noexcept -> bool
206 { return entry.mId == devId; };
207 auto match = std::find_if(newdevs.cbegin(), newdevs.cend(), match_devid);
208 if(match != newdevs.cend()) continue;
210 auto numChannels = GetDeviceChannelCount(devId, isCapture);
211 if(numChannels > 0)
213 newdevs.emplace_back(DeviceEntry{devId, GetDeviceName(devId)});
214 const auto &entry = newdevs.back();
215 TRACE("Got device: %s = ID %u\n", entry.mName.c_str(), entry.mId);
219 if(newdevs.size() > 1)
221 /* Rename entries that have matching names, by appending '#2', '#3',
222 * etc, as needed.
224 for(auto curitem = newdevs.begin()+1;curitem != newdevs.end();++curitem)
226 auto check_match = [curitem](const DeviceEntry &entry) -> bool
227 { return entry.mName == curitem->mName; };
228 if(std::find_if(newdevs.begin(), curitem, check_match) != curitem)
230 std::string name{curitem->mName};
231 size_t count{1};
232 auto check_name = [&name](const DeviceEntry &entry) -> bool
233 { return entry.mName == name; };
234 do {
235 name = curitem->mName;
236 name += " #";
237 name += std::to_string(++count);
238 } while(std::find_if(newdevs.begin(), curitem, check_name) != curitem);
239 curitem->mName = std::move(name);
244 newdevs.shrink_to_fit();
245 newdevs.swap(list);
248 #else
250 static constexpr char ca_device[] = "CoreAudio Default";
251 #endif
254 struct CoreAudioPlayback final : public BackendBase {
255 CoreAudioPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
256 ~CoreAudioPlayback() override;
258 OSStatus MixerProc(AudioUnitRenderActionFlags *ioActionFlags,
259 const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
260 AudioBufferList *ioData) noexcept;
261 static OSStatus MixerProcC(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags,
262 const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
263 AudioBufferList *ioData) noexcept
265 return static_cast<CoreAudioPlayback*>(inRefCon)->MixerProc(ioActionFlags, inTimeStamp,
266 inBusNumber, inNumberFrames, ioData);
269 void open(const char *name) override;
270 bool reset() override;
271 void start() override;
272 void stop() override;
274 AudioUnit mAudioUnit{};
276 uint mFrameSize{0u};
277 AudioStreamBasicDescription mFormat{}; // This is the OpenAL format as a CoreAudio ASBD
279 DEF_NEWDEL(CoreAudioPlayback)
282 CoreAudioPlayback::~CoreAudioPlayback()
284 AudioUnitUninitialize(mAudioUnit);
285 AudioComponentInstanceDispose(mAudioUnit);
289 OSStatus CoreAudioPlayback::MixerProc(AudioUnitRenderActionFlags*, const AudioTimeStamp*, UInt32,
290 UInt32, AudioBufferList *ioData) noexcept
292 for(size_t i{0};i < ioData->mNumberBuffers;++i)
294 auto &buffer = ioData->mBuffers[i];
295 mDevice->renderSamples(buffer.mData, buffer.mDataByteSize/mFrameSize,
296 buffer.mNumberChannels);
298 return noErr;
302 void CoreAudioPlayback::open(const char *name)
304 #if CAN_ENUMERATE
305 AudioDeviceID audioDevice{kAudioDeviceUnknown};
306 if(!name)
307 GetHwProperty(kAudioHardwarePropertyDefaultOutputDevice, sizeof(audioDevice),
308 &audioDevice);
309 else
311 if(PlaybackList.empty())
312 EnumerateDevices(PlaybackList, false);
314 auto find_name = [name](const DeviceEntry &entry) -> bool
315 { return entry.mName == name; };
316 auto devmatch = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), find_name);
317 if(devmatch == PlaybackList.cend())
318 throw al::backend_exception{al::backend_error::NoDevice,
319 "Device name \"%s\" not found", name};
321 audioDevice = devmatch->mId;
323 #else
324 if(!name)
325 name = ca_device;
326 else if(strcmp(name, ca_device) != 0)
327 throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
328 name};
329 #endif
331 /* open the default output unit */
332 AudioComponentDescription desc{};
333 desc.componentType = kAudioUnitType_Output;
334 #if CAN_ENUMERATE
335 desc.componentSubType = (audioDevice == kAudioDeviceUnknown) ?
336 kAudioUnitSubType_DefaultOutput : kAudioUnitSubType_HALOutput;
337 #else
338 desc.componentSubType = kAudioUnitSubType_RemoteIO;
339 #endif
340 desc.componentManufacturer = kAudioUnitManufacturer_Apple;
341 desc.componentFlags = 0;
342 desc.componentFlagsMask = 0;
344 AudioComponent comp{AudioComponentFindNext(NULL, &desc)};
345 if(comp == nullptr)
346 throw al::backend_exception{al::backend_error::NoDevice, "Could not find audio component"};
348 AudioUnit audioUnit{};
349 OSStatus err{AudioComponentInstanceNew(comp, &audioUnit)};
350 if(err != noErr)
351 throw al::backend_exception{al::backend_error::NoDevice,
352 "Could not create component instance: %u", err};
354 #if CAN_ENUMERATE
355 if(audioDevice != kAudioDeviceUnknown)
356 AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_CurrentDevice,
357 kAudioUnitScope_Global, 0, &audioDevice, sizeof(AudioDeviceID));
358 #endif
360 err = AudioUnitInitialize(audioUnit);
361 if(err != noErr)
362 throw al::backend_exception{al::backend_error::DeviceError,
363 "Could not initialize audio unit: %u", err};
365 /* WARNING: I don't know if "valid" audio unit values are guaranteed to be
366 * non-0. If not, this logic is broken.
368 if(mAudioUnit)
370 AudioUnitUninitialize(mAudioUnit);
371 AudioComponentInstanceDispose(mAudioUnit);
373 mAudioUnit = audioUnit;
375 #if CAN_ENUMERATE
376 if(name)
377 mDevice->DeviceName = name;
378 else
380 UInt32 propSize{sizeof(audioDevice)};
381 audioDevice = kAudioDeviceUnknown;
382 AudioUnitGetProperty(audioUnit, kAudioOutputUnitProperty_CurrentDevice,
383 kAudioUnitScope_Global, 0, &audioDevice, &propSize);
385 std::string devname{GetDeviceName(audioDevice)};
386 if(!devname.empty()) mDevice->DeviceName = std::move(devname);
387 else mDevice->DeviceName = "Unknown Device Name";
389 #else
390 mDevice->DeviceName = name;
391 #endif
394 bool CoreAudioPlayback::reset()
396 OSStatus err{AudioUnitUninitialize(mAudioUnit)};
397 if(err != noErr)
398 ERR("-- AudioUnitUninitialize failed.\n");
400 /* retrieve default output unit's properties (output side) */
401 AudioStreamBasicDescription streamFormat{};
402 UInt32 size{sizeof(streamFormat)};
403 err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output,
404 0, &streamFormat, &size);
405 if(err != noErr || size != sizeof(streamFormat))
407 ERR("AudioUnitGetProperty failed\n");
408 return false;
411 #if 0
412 TRACE("Output streamFormat of default output unit -\n");
413 TRACE(" streamFormat.mFramesPerPacket = %d\n", streamFormat.mFramesPerPacket);
414 TRACE(" streamFormat.mChannelsPerFrame = %d\n", streamFormat.mChannelsPerFrame);
415 TRACE(" streamFormat.mBitsPerChannel = %d\n", streamFormat.mBitsPerChannel);
416 TRACE(" streamFormat.mBytesPerPacket = %d\n", streamFormat.mBytesPerPacket);
417 TRACE(" streamFormat.mBytesPerFrame = %d\n", streamFormat.mBytesPerFrame);
418 TRACE(" streamFormat.mSampleRate = %5.0f\n", streamFormat.mSampleRate);
419 #endif
421 /* Use the sample rate from the output unit's current parameters, but reset
422 * everything else.
424 if(mDevice->Frequency != streamFormat.mSampleRate)
426 mDevice->BufferSize = static_cast<uint>(uint64_t{mDevice->BufferSize} *
427 streamFormat.mSampleRate / mDevice->Frequency);
428 mDevice->Frequency = static_cast<uint>(streamFormat.mSampleRate);
431 /* FIXME: How to tell what channels are what in the output device, and how
432 * to specify what we're giving? e.g. 6.0 vs 5.1
434 streamFormat.mChannelsPerFrame = mDevice->channelsFromFmt();
436 streamFormat.mFramesPerPacket = 1;
437 streamFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kLinearPCMFormatFlagIsPacked;
438 streamFormat.mFormatID = kAudioFormatLinearPCM;
439 switch(mDevice->FmtType)
441 case DevFmtUByte:
442 mDevice->FmtType = DevFmtByte;
443 /* fall-through */
444 case DevFmtByte:
445 streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
446 streamFormat.mBitsPerChannel = 8;
447 break;
448 case DevFmtUShort:
449 mDevice->FmtType = DevFmtShort;
450 /* fall-through */
451 case DevFmtShort:
452 streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
453 streamFormat.mBitsPerChannel = 16;
454 break;
455 case DevFmtUInt:
456 mDevice->FmtType = DevFmtInt;
457 /* fall-through */
458 case DevFmtInt:
459 streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
460 streamFormat.mBitsPerChannel = 32;
461 break;
462 case DevFmtFloat:
463 streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsFloat;
464 streamFormat.mBitsPerChannel = 32;
465 break;
467 streamFormat.mBytesPerFrame = streamFormat.mChannelsPerFrame*streamFormat.mBitsPerChannel/8;
468 streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame*streamFormat.mFramesPerPacket;
470 err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
471 0, &streamFormat, sizeof(streamFormat));
472 if(err != noErr)
474 ERR("AudioUnitSetProperty failed\n");
475 return false;
478 setDefaultWFXChannelOrder();
480 /* setup callback */
481 mFrameSize = mDevice->frameSizeFromFmt();
482 AURenderCallbackStruct input{};
483 input.inputProc = CoreAudioPlayback::MixerProcC;
484 input.inputProcRefCon = this;
486 err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_SetRenderCallback,
487 kAudioUnitScope_Input, 0, &input, sizeof(AURenderCallbackStruct));
488 if(err != noErr)
490 ERR("AudioUnitSetProperty failed\n");
491 return false;
494 /* init the default audio unit... */
495 err = AudioUnitInitialize(mAudioUnit);
496 if(err != noErr)
498 ERR("AudioUnitInitialize failed\n");
499 return false;
502 return true;
505 void CoreAudioPlayback::start()
507 const OSStatus err{AudioOutputUnitStart(mAudioUnit)};
508 if(err != noErr)
509 throw al::backend_exception{al::backend_error::DeviceError,
510 "AudioOutputUnitStart failed: %d", err};
513 void CoreAudioPlayback::stop()
515 OSStatus err{AudioOutputUnitStop(mAudioUnit)};
516 if(err != noErr)
517 ERR("AudioOutputUnitStop failed\n");
521 struct CoreAudioCapture final : public BackendBase {
522 CoreAudioCapture(DeviceBase *device) noexcept : BackendBase{device} { }
523 ~CoreAudioCapture() override;
525 OSStatus RecordProc(AudioUnitRenderActionFlags *ioActionFlags,
526 const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber,
527 UInt32 inNumberFrames, AudioBufferList *ioData) noexcept;
528 static OSStatus RecordProcC(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags,
529 const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
530 AudioBufferList *ioData) noexcept
532 return static_cast<CoreAudioCapture*>(inRefCon)->RecordProc(ioActionFlags, inTimeStamp,
533 inBusNumber, inNumberFrames, ioData);
536 void open(const char *name) override;
537 void start() override;
538 void stop() override;
539 void captureSamples(al::byte *buffer, uint samples) override;
540 uint availableSamples() override;
542 AudioUnit mAudioUnit{0};
544 uint mFrameSize{0u};
545 AudioStreamBasicDescription mFormat{}; // This is the OpenAL format as a CoreAudio ASBD
547 SampleConverterPtr mConverter;
549 RingBufferPtr mRing{nullptr};
551 DEF_NEWDEL(CoreAudioCapture)
554 CoreAudioCapture::~CoreAudioCapture()
556 if(mAudioUnit)
557 AudioComponentInstanceDispose(mAudioUnit);
558 mAudioUnit = 0;
562 OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags*,
563 const AudioTimeStamp *inTimeStamp, UInt32, UInt32 inNumberFrames,
564 AudioBufferList*) noexcept
566 AudioUnitRenderActionFlags flags = 0;
567 union {
568 al::byte _[sizeof(AudioBufferList) + sizeof(AudioBuffer)*2];
569 AudioBufferList list;
570 } audiobuf{};
572 auto rec_vec = mRing->getWriteVector();
573 inNumberFrames = static_cast<UInt32>(minz(inNumberFrames,
574 rec_vec.first.len+rec_vec.second.len));
576 // Fill the ringbuffer's two segments with data from the input device
577 if(rec_vec.first.len >= inNumberFrames)
579 audiobuf.list.mNumberBuffers = 1;
580 audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame;
581 audiobuf.list.mBuffers[0].mData = rec_vec.first.buf;
582 audiobuf.list.mBuffers[0].mDataByteSize = inNumberFrames * mFormat.mBytesPerFrame;
584 else
586 const auto remaining = static_cast<uint>(inNumberFrames - rec_vec.first.len);
587 audiobuf.list.mNumberBuffers = 2;
588 audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame;
589 audiobuf.list.mBuffers[0].mData = rec_vec.first.buf;
590 audiobuf.list.mBuffers[0].mDataByteSize = static_cast<UInt32>(rec_vec.first.len) *
591 mFormat.mBytesPerFrame;
592 audiobuf.list.mBuffers[1].mNumberChannels = mFormat.mChannelsPerFrame;
593 audiobuf.list.mBuffers[1].mData = rec_vec.second.buf;
594 audiobuf.list.mBuffers[1].mDataByteSize = remaining * mFormat.mBytesPerFrame;
596 OSStatus err{AudioUnitRender(mAudioUnit, &flags, inTimeStamp, audiobuf.list.mNumberBuffers,
597 inNumberFrames, &audiobuf.list)};
598 if(err != noErr)
600 ERR("AudioUnitRender error: %d\n", err);
601 return err;
604 mRing->writeAdvance(inNumberFrames);
605 return noErr;
609 void CoreAudioCapture::open(const char *name)
611 #if CAN_ENUMERATE
612 AudioDeviceID audioDevice{kAudioDeviceUnknown};
613 if(!name)
614 GetHwProperty(kAudioHardwarePropertyDefaultInputDevice, sizeof(audioDevice),
615 &audioDevice);
616 else
618 if(CaptureList.empty())
619 EnumerateDevices(CaptureList, true);
621 auto find_name = [name](const DeviceEntry &entry) -> bool
622 { return entry.mName == name; };
623 auto devmatch = std::find_if(CaptureList.cbegin(), CaptureList.cend(), find_name);
624 if(devmatch == CaptureList.cend())
625 throw al::backend_exception{al::backend_error::NoDevice,
626 "Device name \"%s\" not found", name};
628 audioDevice = devmatch->mId;
630 #else
631 if(!name)
632 name = ca_device;
633 else if(strcmp(name, ca_device) != 0)
634 throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
635 name};
636 #endif
638 AudioComponentDescription desc{};
639 desc.componentType = kAudioUnitType_Output;
640 #if CAN_ENUMERATE
641 desc.componentSubType = (audioDevice == kAudioDeviceUnknown) ?
642 kAudioUnitSubType_DefaultOutput : kAudioUnitSubType_HALOutput;
643 #else
644 desc.componentSubType = kAudioUnitSubType_RemoteIO;
645 #endif
646 desc.componentManufacturer = kAudioUnitManufacturer_Apple;
647 desc.componentFlags = 0;
648 desc.componentFlagsMask = 0;
650 // Search for component with given description
651 AudioComponent comp{AudioComponentFindNext(NULL, &desc)};
652 if(comp == NULL)
653 throw al::backend_exception{al::backend_error::NoDevice, "Could not find audio component"};
655 // Open the component
656 OSStatus err{AudioComponentInstanceNew(comp, &mAudioUnit)};
657 if(err != noErr)
658 throw al::backend_exception{al::backend_error::NoDevice,
659 "Could not create component instance: %u", err};
661 #if CAN_ENUMERATE
662 if(audioDevice != kAudioDeviceUnknown)
663 AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice,
664 kAudioUnitScope_Global, 0, &audioDevice, sizeof(AudioDeviceID));
665 #endif
667 // Turn off AudioUnit output
668 UInt32 enableIO{0};
669 err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO,
670 kAudioUnitScope_Output, 0, &enableIO, sizeof(enableIO));
671 if(err != noErr)
672 throw al::backend_exception{al::backend_error::DeviceError,
673 "Could not disable audio unit output property: %u", err};
675 // Turn on AudioUnit input
676 enableIO = 1;
677 err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO,
678 kAudioUnitScope_Input, 1, &enableIO, sizeof(enableIO));
679 if(err != noErr)
680 throw al::backend_exception{al::backend_error::DeviceError,
681 "Could not enable audio unit input property: %u", err};
683 // set capture callback
684 AURenderCallbackStruct input{};
685 input.inputProc = CoreAudioCapture::RecordProcC;
686 input.inputProcRefCon = this;
688 err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_SetInputCallback,
689 kAudioUnitScope_Global, 0, &input, sizeof(AURenderCallbackStruct));
690 if(err != noErr)
691 throw al::backend_exception{al::backend_error::DeviceError,
692 "Could not set capture callback: %u", err};
694 // Disable buffer allocation for capture
695 UInt32 flag{0};
696 err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_ShouldAllocateBuffer,
697 kAudioUnitScope_Output, 1, &flag, sizeof(flag));
698 if(err != noErr)
699 throw al::backend_exception{al::backend_error::DeviceError,
700 "Could not disable buffer allocation property: %u", err};
702 // Initialize the device
703 err = AudioUnitInitialize(mAudioUnit);
704 if(err != noErr)
705 throw al::backend_exception{al::backend_error::DeviceError,
706 "Could not initialize audio unit: %u", err};
708 // Get the hardware format
709 AudioStreamBasicDescription hardwareFormat{};
710 UInt32 propertySize{sizeof(hardwareFormat)};
711 err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
712 1, &hardwareFormat, &propertySize);
713 if(err != noErr || propertySize != sizeof(hardwareFormat))
714 throw al::backend_exception{al::backend_error::DeviceError,
715 "Could not get input format: %u", err};
717 // Set up the requested format description
718 AudioStreamBasicDescription requestedFormat{};
719 switch(mDevice->FmtType)
721 case DevFmtByte:
722 requestedFormat.mBitsPerChannel = 8;
723 requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
724 break;
725 case DevFmtUByte:
726 requestedFormat.mBitsPerChannel = 8;
727 requestedFormat.mFormatFlags = kAudioFormatFlagIsPacked;
728 break;
729 case DevFmtShort:
730 requestedFormat.mBitsPerChannel = 16;
731 requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger
732 | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
733 break;
734 case DevFmtUShort:
735 requestedFormat.mBitsPerChannel = 16;
736 requestedFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
737 break;
738 case DevFmtInt:
739 requestedFormat.mBitsPerChannel = 32;
740 requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger
741 | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
742 break;
743 case DevFmtUInt:
744 requestedFormat.mBitsPerChannel = 32;
745 requestedFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
746 break;
747 case DevFmtFloat:
748 requestedFormat.mBitsPerChannel = 32;
749 requestedFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat | kAudioFormatFlagsNativeEndian
750 | kAudioFormatFlagIsPacked;
751 break;
754 switch(mDevice->FmtChans)
756 case DevFmtMono:
757 requestedFormat.mChannelsPerFrame = 1;
758 break;
759 case DevFmtStereo:
760 requestedFormat.mChannelsPerFrame = 2;
761 break;
763 case DevFmtQuad:
764 case DevFmtX51:
765 case DevFmtX61:
766 case DevFmtX71:
767 case DevFmtX3D71:
768 case DevFmtAmbi3D:
769 throw al::backend_exception{al::backend_error::DeviceError, "%s not supported",
770 DevFmtChannelsString(mDevice->FmtChans)};
773 requestedFormat.mBytesPerFrame = requestedFormat.mChannelsPerFrame * requestedFormat.mBitsPerChannel / 8;
774 requestedFormat.mBytesPerPacket = requestedFormat.mBytesPerFrame;
775 requestedFormat.mSampleRate = mDevice->Frequency;
776 requestedFormat.mFormatID = kAudioFormatLinearPCM;
777 requestedFormat.mReserved = 0;
778 requestedFormat.mFramesPerPacket = 1;
780 // save requested format description for later use
781 mFormat = requestedFormat;
782 mFrameSize = mDevice->frameSizeFromFmt();
784 // Use intermediate format for sample rate conversion (outputFormat)
785 // Set sample rate to the same as hardware for resampling later
786 AudioStreamBasicDescription outputFormat{requestedFormat};
787 outputFormat.mSampleRate = hardwareFormat.mSampleRate;
789 // The output format should be the requested format, but using the hardware sample rate
790 // This is because the AudioUnit will automatically scale other properties, except for sample rate
791 err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output,
792 1, &outputFormat, sizeof(outputFormat));
793 if(err != noErr)
794 throw al::backend_exception{al::backend_error::DeviceError,
795 "Could not set input format: %u", err};
797 /* Calculate the minimum AudioUnit output format frame count for the pre-
798 * conversion ring buffer. Ensure at least 100ms for the total buffer.
800 double srateScale{double{outputFormat.mSampleRate} / mDevice->Frequency};
801 auto FrameCount64 = maxu64(static_cast<uint64_t>(std::ceil(mDevice->BufferSize*srateScale)),
802 static_cast<UInt32>(outputFormat.mSampleRate)/10);
803 FrameCount64 += MaxResamplerPadding;
804 if(FrameCount64 > std::numeric_limits<int32_t>::max())
805 throw al::backend_exception{al::backend_error::DeviceError,
806 "Calculated frame count is too large: %" PRIu64, FrameCount64};
808 UInt32 outputFrameCount{};
809 propertySize = sizeof(outputFrameCount);
810 err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_MaximumFramesPerSlice,
811 kAudioUnitScope_Global, 0, &outputFrameCount, &propertySize);
812 if(err != noErr || propertySize != sizeof(outputFrameCount))
813 throw al::backend_exception{al::backend_error::DeviceError,
814 "Could not get input frame count: %u", err};
816 outputFrameCount = static_cast<UInt32>(maxu64(outputFrameCount, FrameCount64));
817 mRing = RingBuffer::Create(outputFrameCount, mFrameSize, false);
819 /* Set up sample converter if needed */
820 if(outputFormat.mSampleRate != mDevice->Frequency)
821 mConverter = CreateSampleConverter(mDevice->FmtType, mDevice->FmtType,
822 mFormat.mChannelsPerFrame, static_cast<uint>(hardwareFormat.mSampleRate),
823 mDevice->Frequency, Resampler::FastBSinc24);
825 #if CAN_ENUMERATE
826 if(name)
827 mDevice->DeviceName = name;
828 else
830 UInt32 propSize{sizeof(audioDevice)};
831 audioDevice = kAudioDeviceUnknown;
832 AudioUnitGetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice,
833 kAudioUnitScope_Global, 0, &audioDevice, &propSize);
835 std::string devname{GetDeviceName(audioDevice)};
836 if(!devname.empty()) mDevice->DeviceName = std::move(devname);
837 else mDevice->DeviceName = "Unknown Device Name";
839 #else
840 mDevice->DeviceName = name;
841 #endif
845 void CoreAudioCapture::start()
847 OSStatus err{AudioOutputUnitStart(mAudioUnit)};
848 if(err != noErr)
849 throw al::backend_exception{al::backend_error::DeviceError,
850 "AudioOutputUnitStart failed: %d", err};
853 void CoreAudioCapture::stop()
855 OSStatus err{AudioOutputUnitStop(mAudioUnit)};
856 if(err != noErr)
857 ERR("AudioOutputUnitStop failed\n");
860 void CoreAudioCapture::captureSamples(al::byte *buffer, uint samples)
862 if(!mConverter)
864 mRing->read(buffer, samples);
865 return;
868 auto rec_vec = mRing->getReadVector();
869 const void *src0{rec_vec.first.buf};
870 auto src0len = static_cast<uint>(rec_vec.first.len);
871 uint got{mConverter->convert(&src0, &src0len, buffer, samples)};
872 size_t total_read{rec_vec.first.len - src0len};
873 if(got < samples && !src0len && rec_vec.second.len > 0)
875 const void *src1{rec_vec.second.buf};
876 auto src1len = static_cast<uint>(rec_vec.second.len);
877 got += mConverter->convert(&src1, &src1len, buffer + got*mFrameSize, samples-got);
878 total_read += rec_vec.second.len - src1len;
881 mRing->readAdvance(total_read);
884 uint CoreAudioCapture::availableSamples()
886 if(!mConverter) return static_cast<uint>(mRing->readSpace());
887 return mConverter->availableOut(static_cast<uint>(mRing->readSpace()));
890 } // namespace
892 BackendFactory &CoreAudioBackendFactory::getFactory()
894 static CoreAudioBackendFactory factory{};
895 return factory;
898 bool CoreAudioBackendFactory::init() { return true; }
900 bool CoreAudioBackendFactory::querySupport(BackendType type)
901 { return type == BackendType::Playback || type == BackendType::Capture; }
903 std::string CoreAudioBackendFactory::probe(BackendType type)
905 std::string outnames;
906 #if CAN_ENUMERATE
907 auto append_name = [&outnames](const DeviceEntry &entry) -> void
909 /* Includes null char. */
910 outnames.append(entry.mName.c_str(), entry.mName.length()+1);
912 switch(type)
914 case BackendType::Playback:
915 EnumerateDevices(PlaybackList, false);
916 std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name);
917 break;
918 case BackendType::Capture:
919 EnumerateDevices(CaptureList, true);
920 std::for_each(CaptureList.cbegin(), CaptureList.cend(), append_name);
921 break;
924 #else
926 switch(type)
928 case BackendType::Playback:
929 case BackendType::Capture:
930 /* Includes null char. */
931 outnames.append(ca_device, sizeof(ca_device));
932 break;
934 #endif
935 return outnames;
938 BackendPtr CoreAudioBackendFactory::createBackend(DeviceBase *device, BackendType type)
940 if(type == BackendType::Playback)
941 return BackendPtr{new CoreAudioPlayback{device}};
942 if(type == BackendType::Capture)
943 return BackendPtr{new CoreAudioCapture{device}};
944 return nullptr;