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
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
,
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
)
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
)};
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
);
118 /* If that failed, just get the C string. Hopefully there's nothing bad
122 if(GetDevPropertySize(devId
, kAudioDevicePropertyDeviceName
, false, 0, &propSize
))
125 devname
.resize(propSize
+1, '\0');
126 if(GetDevProperty(devId
, kAudioDevicePropertyDeviceName
, false, 0, propSize
, &devname
[0]))
133 /* Clear extraneous nul chars that may have been written with the name
134 * string, and return it.
136 while(!devname
.back())
141 UInt32
GetDeviceChannelCount(AudioDeviceID devId
, bool isCapture
)
144 auto err
= GetDevPropertySize(devId
, kAudioDevicePropertyStreamConfiguration
, isCapture
, 0,
148 ERR("kAudioDevicePropertyStreamConfiguration size query failed: %u\n", err
);
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
,
159 ERR("kAudioDevicePropertyStreamConfiguration query failed: %u\n", err
);
163 UInt32 numChannels
{0};
164 for(size_t i
{0};i
< buflist
->mNumberBuffers
;++i
)
165 numChannels
+= buflist
->mBuffers
[i
].mNumberChannels
;
171 void EnumerateDevices(std::vector
<DeviceEntry
> &list
, bool isCapture
)
174 if(auto err
= GetHwPropertySize(kAudioHardwarePropertyDevices
, &propSize
))
176 ERR("Failed to get device list size: %u\n", err
);
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
);
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
)
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
);
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',
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
};
232 auto check_name
= [&name
](const DeviceEntry
&entry
) -> bool
233 { return entry
.mName
== name
; };
235 name
= curitem
->mName
;
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();
250 static constexpr char ca_device
[] = "CoreAudio Default";
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
{};
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
);
302 void CoreAudioPlayback::open(const char *name
)
305 AudioDeviceID audioDevice
{kAudioDeviceUnknown
};
307 GetHwProperty(kAudioHardwarePropertyDefaultOutputDevice
, sizeof(audioDevice
),
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
;
326 else if(strcmp(name
, ca_device
) != 0)
327 throw al::backend_exception
{al::backend_error::NoDevice
, "Device name \"%s\" not found",
331 /* open the default output unit */
332 AudioComponentDescription desc
{};
333 desc
.componentType
= kAudioUnitType_Output
;
335 desc
.componentSubType
= (audioDevice
== kAudioDeviceUnknown
) ?
336 kAudioUnitSubType_DefaultOutput
: kAudioUnitSubType_HALOutput
;
338 desc
.componentSubType
= kAudioUnitSubType_RemoteIO
;
340 desc
.componentManufacturer
= kAudioUnitManufacturer_Apple
;
341 desc
.componentFlags
= 0;
342 desc
.componentFlagsMask
= 0;
344 AudioComponent comp
{AudioComponentFindNext(NULL
, &desc
)};
346 throw al::backend_exception
{al::backend_error::NoDevice
, "Could not find audio component"};
348 AudioUnit audioUnit
{};
349 OSStatus err
{AudioComponentInstanceNew(comp
, &audioUnit
)};
351 throw al::backend_exception
{al::backend_error::NoDevice
,
352 "Could not create component instance: %u", err
};
355 if(audioDevice
!= kAudioDeviceUnknown
)
356 AudioUnitSetProperty(audioUnit
, kAudioOutputUnitProperty_CurrentDevice
,
357 kAudioUnitScope_Global
, 0, &audioDevice
, sizeof(AudioDeviceID
));
360 err
= AudioUnitInitialize(audioUnit
);
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.
370 AudioUnitUninitialize(mAudioUnit
);
371 AudioComponentInstanceDispose(mAudioUnit
);
373 mAudioUnit
= audioUnit
;
377 mDevice
->DeviceName
= name
;
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";
390 mDevice
->DeviceName
= name
;
394 bool CoreAudioPlayback::reset()
396 OSStatus err
{AudioUnitUninitialize(mAudioUnit
)};
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");
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
);
421 /* Use the sample rate from the output unit's current parameters, but reset
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
)
442 mDevice
->FmtType
= DevFmtByte
;
445 streamFormat
.mFormatFlags
|= kLinearPCMFormatFlagIsSignedInteger
;
446 streamFormat
.mBitsPerChannel
= 8;
449 mDevice
->FmtType
= DevFmtShort
;
452 streamFormat
.mFormatFlags
|= kLinearPCMFormatFlagIsSignedInteger
;
453 streamFormat
.mBitsPerChannel
= 16;
456 mDevice
->FmtType
= DevFmtInt
;
459 streamFormat
.mFormatFlags
|= kLinearPCMFormatFlagIsSignedInteger
;
460 streamFormat
.mBitsPerChannel
= 32;
463 streamFormat
.mFormatFlags
|= kLinearPCMFormatFlagIsFloat
;
464 streamFormat
.mBitsPerChannel
= 32;
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
));
474 ERR("AudioUnitSetProperty failed\n");
478 setDefaultWFXChannelOrder();
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
));
490 ERR("AudioUnitSetProperty failed\n");
494 /* init the default audio unit... */
495 err
= AudioUnitInitialize(mAudioUnit
);
498 ERR("AudioUnitInitialize failed\n");
505 void CoreAudioPlayback::start()
507 const OSStatus err
{AudioOutputUnitStart(mAudioUnit
)};
509 throw al::backend_exception
{al::backend_error::DeviceError
,
510 "AudioOutputUnitStart failed: %d", err
};
513 void CoreAudioPlayback::stop()
515 OSStatus err
{AudioOutputUnitStop(mAudioUnit
)};
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};
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()
557 AudioComponentInstanceDispose(mAudioUnit
);
562 OSStatus
CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags
*,
563 const AudioTimeStamp
*inTimeStamp
, UInt32
, UInt32 inNumberFrames
,
564 AudioBufferList
*) noexcept
566 AudioUnitRenderActionFlags flags
= 0;
568 al::byte _
[sizeof(AudioBufferList
) + sizeof(AudioBuffer
)*2];
569 AudioBufferList list
;
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
;
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
)};
600 ERR("AudioUnitRender error: %d\n", err
);
604 mRing
->writeAdvance(inNumberFrames
);
609 void CoreAudioCapture::open(const char *name
)
612 AudioDeviceID audioDevice
{kAudioDeviceUnknown
};
614 GetHwProperty(kAudioHardwarePropertyDefaultInputDevice
, sizeof(audioDevice
),
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
;
633 else if(strcmp(name
, ca_device
) != 0)
634 throw al::backend_exception
{al::backend_error::NoDevice
, "Device name \"%s\" not found",
638 AudioComponentDescription desc
{};
639 desc
.componentType
= kAudioUnitType_Output
;
641 desc
.componentSubType
= (audioDevice
== kAudioDeviceUnknown
) ?
642 kAudioUnitSubType_DefaultOutput
: kAudioUnitSubType_HALOutput
;
644 desc
.componentSubType
= kAudioUnitSubType_RemoteIO
;
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
)};
653 throw al::backend_exception
{al::backend_error::NoDevice
, "Could not find audio component"};
655 // Open the component
656 OSStatus err
{AudioComponentInstanceNew(comp
, &mAudioUnit
)};
658 throw al::backend_exception
{al::backend_error::NoDevice
,
659 "Could not create component instance: %u", err
};
662 if(audioDevice
!= kAudioDeviceUnknown
)
663 AudioUnitSetProperty(mAudioUnit
, kAudioOutputUnitProperty_CurrentDevice
,
664 kAudioUnitScope_Global
, 0, &audioDevice
, sizeof(AudioDeviceID
));
667 // Turn off AudioUnit output
669 err
= AudioUnitSetProperty(mAudioUnit
, kAudioOutputUnitProperty_EnableIO
,
670 kAudioUnitScope_Output
, 0, &enableIO
, sizeof(enableIO
));
672 throw al::backend_exception
{al::backend_error::DeviceError
,
673 "Could not disable audio unit output property: %u", err
};
675 // Turn on AudioUnit input
677 err
= AudioUnitSetProperty(mAudioUnit
, kAudioOutputUnitProperty_EnableIO
,
678 kAudioUnitScope_Input
, 1, &enableIO
, sizeof(enableIO
));
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
));
691 throw al::backend_exception
{al::backend_error::DeviceError
,
692 "Could not set capture callback: %u", err
};
694 // Disable buffer allocation for capture
696 err
= AudioUnitSetProperty(mAudioUnit
, kAudioUnitProperty_ShouldAllocateBuffer
,
697 kAudioUnitScope_Output
, 1, &flag
, sizeof(flag
));
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
);
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
)
722 requestedFormat
.mBitsPerChannel
= 8;
723 requestedFormat
.mFormatFlags
= kAudioFormatFlagIsSignedInteger
| kAudioFormatFlagIsPacked
;
726 requestedFormat
.mBitsPerChannel
= 8;
727 requestedFormat
.mFormatFlags
= kAudioFormatFlagIsPacked
;
730 requestedFormat
.mBitsPerChannel
= 16;
731 requestedFormat
.mFormatFlags
= kAudioFormatFlagIsSignedInteger
732 | kAudioFormatFlagsNativeEndian
| kAudioFormatFlagIsPacked
;
735 requestedFormat
.mBitsPerChannel
= 16;
736 requestedFormat
.mFormatFlags
= kAudioFormatFlagsNativeEndian
| kAudioFormatFlagIsPacked
;
739 requestedFormat
.mBitsPerChannel
= 32;
740 requestedFormat
.mFormatFlags
= kAudioFormatFlagIsSignedInteger
741 | kAudioFormatFlagsNativeEndian
| kAudioFormatFlagIsPacked
;
744 requestedFormat
.mBitsPerChannel
= 32;
745 requestedFormat
.mFormatFlags
= kAudioFormatFlagsNativeEndian
| kAudioFormatFlagIsPacked
;
748 requestedFormat
.mBitsPerChannel
= 32;
749 requestedFormat
.mFormatFlags
= kLinearPCMFormatFlagIsFloat
| kAudioFormatFlagsNativeEndian
750 | kAudioFormatFlagIsPacked
;
754 switch(mDevice
->FmtChans
)
757 requestedFormat
.mChannelsPerFrame
= 1;
760 requestedFormat
.mChannelsPerFrame
= 2;
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
));
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
);
827 mDevice
->DeviceName
= name
;
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";
840 mDevice
->DeviceName
= name
;
845 void CoreAudioCapture::start()
847 OSStatus err
{AudioOutputUnitStart(mAudioUnit
)};
849 throw al::backend_exception
{al::backend_error::DeviceError
,
850 "AudioOutputUnitStart failed: %d", err
};
853 void CoreAudioCapture::stop()
855 OSStatus err
{AudioOutputUnitStop(mAudioUnit
)};
857 ERR("AudioOutputUnitStop failed\n");
860 void CoreAudioCapture::captureSamples(al::byte
*buffer
, uint samples
)
864 mRing
->read(buffer
, samples
);
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()));
892 BackendFactory
&CoreAudioBackendFactory::getFactory()
894 static CoreAudioBackendFactory 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
;
907 auto append_name
= [&outnames
](const DeviceEntry
&entry
) -> void
909 /* Includes null char. */
910 outnames
.append(entry
.mName
.c_str(), entry
.mName
.length()+1);
914 case BackendType::Playback
:
915 EnumerateDevices(PlaybackList
, false);
916 std::for_each(PlaybackList
.cbegin(), PlaybackList
.cend(), append_name
);
918 case BackendType::Capture
:
919 EnumerateDevices(CaptureList
, true);
920 std::for_each(CaptureList
.cbegin(), CaptureList
.cend(), append_name
);
928 case BackendType::Playback
:
929 case BackendType::Capture
:
930 /* Includes null char. */
931 outnames
.append(ca_device
, sizeof(ca_device
));
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
}};