5#ifdef COREAUDIO_BACKEND
14 const AudioObjectPropertyAddress& address)
16 return AudioObjectHasProperty(
object, &address);
27CoreAudioBackend::CoreAudioBackend()
29 MF_INFO(C, X,
"CoreAudio backend initialised");
32CoreAudioBackend::~CoreAudioBackend()
37std::unique_ptr<AudioDevice> CoreAudioBackend::create_device_manager()
39 return std::make_unique<CoreAudioDevice>();
42std::unique_ptr<AudioStream> CoreAudioBackend::create_stream(
43 unsigned int output_device_id,
44 unsigned int input_device_id,
45 GlobalStreamInfo& stream_info,
48 CoreAudioDevice devices;
50 return std::make_unique<CoreAudioStream>(
51 devices.resolve_output_device(output_device_id),
52 devices.resolve_input_device(input_device_id),
57std::string CoreAudioBackend::get_version_string()
const
62void CoreAudioBackend::cleanup()
70CoreAudioDevice::CoreAudioDevice()
75std::vector<DeviceInfo> CoreAudioDevice::get_output_devices()
const
77 std::vector<DeviceInfo> result;
78 result.reserve(m_outputs.size());
80 for (
const auto& device : m_outputs)
81 result.push_back(device.info);
86std::vector<DeviceInfo> CoreAudioDevice::get_input_devices()
const
88 std::vector<DeviceInfo> result;
89 result.reserve(m_inputs.size());
91 for (
const auto& device : m_inputs)
92 result.push_back(device.info);
97unsigned int CoreAudioDevice::get_default_output_device()
const
99 return m_default_output;
102unsigned int CoreAudioDevice::get_default_input_device()
const
104 return m_default_input;
107AudioDeviceID CoreAudioDevice::resolve_output_device(
108 unsigned int index)
const
110 if (index >= m_outputs.size())
111 return m_default_output_id;
113 return m_outputs[index].id;
116AudioDeviceID CoreAudioDevice::resolve_input_device(
117 unsigned int index)
const
119 if (index >= m_inputs.size())
120 return m_default_input_id;
122 return m_inputs[index].id;
125std::string CoreAudioDevice::get_device_name(AudioDeviceID device_id)
127 AudioObjectPropertyAddress address {
128 .mSelector = kAudioObjectPropertyName,
129 .mScope = kAudioObjectPropertyScopeGlobal,
130 .mElement = kAudioObjectPropertyElementMain
133 CFStringRef name_ref =
nullptr;
134 UInt32 size =
sizeof(CFStringRef);
136 auto status = AudioObjectGetPropertyData(
142 static_cast<void*
>(&name_ref));
144 if (status != noErr || !name_ref)
145 return "Unknown Device";
153 kCFStringEncodingUTF8);
160uint32_t CoreAudioDevice::get_channel_count(
161 AudioDeviceID device_id,
162 AudioObjectPropertyScope scope)
164 AudioObjectPropertyAddress address {
165 .mSelector = kAudioDevicePropertyStreamConfiguration,
167 .mElement = kAudioObjectPropertyElementMain
172 auto status = AudioObjectGetPropertyDataSize(
179 if (status != noErr || size == 0)
182 auto buffer = std::make_unique<uint8_t[]>(size);
184 auto* config =
reinterpret_cast<AudioBufferList*
>(buffer.get());
186 status = AudioObjectGetPropertyData(
197 uint32_t channels = 0;
199 for (UInt32 i = 0; i < config->mNumberBuffers; ++i)
200 channels += config->mBuffers[i].mNumberChannels;
205double CoreAudioDevice::get_nominal_sample_rate(
206 AudioDeviceID device_id)
208 AudioObjectPropertyAddress address {
209 .mSelector = kAudioDevicePropertyNominalSampleRate,
210 .mScope = kAudioObjectPropertyScopeGlobal,
211 .mElement = kAudioObjectPropertyElementMain
214 Float64 rate = 48000.0;
215 UInt32 size =
sizeof(rate);
217 auto status = AudioObjectGetPropertyData(
228 return static_cast<double>(rate);
231void CoreAudioDevice::enumerate_devices()
233 AudioObjectPropertyAddress devices_address {
234 .mSelector = kAudioHardwarePropertyDevices,
235 .mScope = kAudioObjectPropertyScopeGlobal,
236 .mElement = kAudioObjectPropertyElementMain
241 auto status = AudioObjectGetPropertyDataSize(
242 kAudioObjectSystemObject,
248 if (status != noErr) {
249 error<std::runtime_error>(
252 std::source_location::current(),
253 "Failed to query CoreAudio device list");
256 std::vector<AudioDeviceID> device_ids(
257 size /
sizeof(AudioDeviceID));
259 status = AudioObjectGetPropertyData(
260 kAudioObjectSystemObject,
267 if (status != noErr) {
268 error<std::runtime_error>(
271 std::source_location::current(),
272 "Failed to retrieve CoreAudio device list");
276 AudioObjectPropertyAddress address {
277 .mSelector = kAudioHardwarePropertyDefaultOutputDevice,
278 .mScope = kAudioObjectPropertyScopeGlobal,
279 .mElement = kAudioObjectPropertyElementMain
282 UInt32 property_size =
sizeof(AudioDeviceID);
284 AudioObjectGetPropertyData(
285 kAudioObjectSystemObject,
290 &m_default_output_id);
294 AudioObjectPropertyAddress address {
295 .mSelector = kAudioHardwarePropertyDefaultInputDevice,
296 .mScope = kAudioObjectPropertyScopeGlobal,
297 .mElement = kAudioObjectPropertyElementMain
300 UInt32 property_size =
sizeof(AudioDeviceID);
302 AudioObjectGetPropertyData(
303 kAudioObjectSystemObject,
308 &m_default_input_id);
311 for (
auto device_id : device_ids) {
313 const auto name = get_device_name(device_id);
315 const auto output_channels = get_channel_count(
317 kAudioDevicePropertyScopeOutput);
319 const auto input_channels = get_channel_count(
321 kAudioDevicePropertyScopeInput);
323 const auto sample_rate = get_nominal_sample_rate(device_id);
325 if (output_channels > 0) {
329 info.output_channels = output_channels;
330 info.input_channels = 0;
331 info.preferred_sample_rate =
static_cast<uint32_t
>(sample_rate);
333 info.is_default_output = (device_id == m_default_output_id);
335 if (info.is_default_output)
336 m_default_output =
static_cast<unsigned int>(m_outputs.size());
338 m_outputs.push_back({ device_id,
342 if (input_channels > 0) {
346 info.output_channels = 0;
347 info.input_channels = input_channels;
348 info.preferred_sample_rate =
static_cast<uint32_t
>(sample_rate);
350 info.is_default_input = (device_id == m_default_input_id);
352 if (info.is_default_input)
353 m_default_input =
static_cast<unsigned int>(m_inputs.size());
355 m_inputs.push_back({ device_id,
363 "CoreAudio enumeration: {} output device(s), {} input device(s)",
372CoreAudioStream::CoreAudioStream(
373 AudioDeviceID output_device_id,
374 AudioDeviceID input_device_id,
375 GlobalStreamInfo& stream_info,
377 : m_output_device_id(output_device_id)
378 , m_input_device_id(input_device_id)
379 , m_stream_info(stream_info)
380 , m_user_data(user_data)
384CoreAudioStream::~CoreAudioStream()
393void CoreAudioStream::open()
395 if (m_is_open.load())
398 if (!configure_output_unit()) {
399 error<std::runtime_error>(C, X, std::source_location::current(),
400 "Failed to configure CoreAudio output unit");
403 if (!configure_output_device()) {
404 error<std::runtime_error>(C, X, std::source_location::current(),
405 "Failed to configure CoreAudio output device");
408 if (!configure_output_format()) {
409 error<std::runtime_error>(C, X, std::source_location::current(),
410 "Failed to configure CoreAudio output format");
413 auto max_frames =
static_cast<UInt32
>(m_stream_info.buffer_size);
415 AudioUnitSetProperty(
417 kAudioUnitProperty_MaximumFramesPerSlice,
418 kAudioUnitScope_Global,
423 auto status = AudioUnitInitialize(m_output_unit);
425 if (status != noErr) {
426 error<std::runtime_error>(C, X, std::source_location::current(),
427 "AudioUnitInitialize failed for output ({})",
static_cast<int>(status));
430 m_input_enabled.store(
431 m_stream_info.input.enabled && m_stream_info.input.channels > 0,
432 std::memory_order_release);
434 if (m_input_enabled.load(std::memory_order_acquire)) {
436 if (!configure_input_unit()) {
437 error<std::runtime_error>(C, X, std::source_location::current(),
438 "Failed to configure CoreAudio input unit");
441 if (!configure_input_device()) {
442 error<std::runtime_error>(C, X, std::source_location::current(),
443 "Failed to configure CoreAudio input device");
446 if (!configure_input_format()) {
447 error<std::runtime_error>(C, X, std::source_location::current(),
448 "Failed to configure CoreAudio input format");
451 AudioUnitSetProperty(
453 kAudioUnitProperty_MaximumFramesPerSlice,
454 kAudioUnitScope_Global,
459 status = AudioUnitInitialize(m_input_unit);
461 if (status != noErr) {
462 error<std::runtime_error>(C, X, std::source_location::current(),
463 "AudioUnitInitialize failed for input ({})",
static_cast<int>(status));
467 m_is_open.store(
true);
470void CoreAudioStream::start()
472 if (!m_is_open.load()) {
473 error<std::runtime_error>(C, X, std::source_location::current(),
474 "Cannot start stream before open()");
477 if (m_is_running.load())
480 if (m_input_enabled.load(std::memory_order_acquire)) {
481 auto status = AudioOutputUnitStart(m_input_unit);
482 if (status != noErr) {
483 error<std::runtime_error>(C, X, std::source_location::current(),
484 "AudioOutputUnitStart failed for input ({})",
static_cast<int>(status));
488 auto status = AudioOutputUnitStart(m_output_unit);
490 if (status != noErr) {
491 error<std::runtime_error>(C, X, std::source_location::current(),
492 "AudioOutputUnitStart failed for output ({})",
static_cast<int>(status));
495 m_is_running.store(
true);
498void CoreAudioStream::stop()
500 if (!m_is_running.load())
503 AudioOutputUnitStop(m_output_unit);
505 if (m_input_enabled.load(std::memory_order_acquire))
506 AudioOutputUnitStop(m_input_unit);
508 m_is_running.store(
false);
511void CoreAudioStream::pause()
514 m_is_paused.store(
true, std::memory_order_release);
517void CoreAudioStream::resume()
519 if (!m_is_paused.load(std::memory_order_acquire))
523 m_is_paused.store(
false, std::memory_order_release);
526void CoreAudioStream::close()
528 if (!m_is_open.load())
531 if (m_is_running.load())
534 if (m_input_buffer_list) {
535 std::free(m_input_buffer_list->mBuffers[0].mData);
536 std::free(m_input_buffer_list);
537 m_input_buffer_list =
nullptr;
541 AudioUnitUninitialize(m_input_unit);
542 AudioComponentInstanceDispose(m_input_unit);
543 m_input_unit =
nullptr;
546 AudioUnitUninitialize(m_output_unit);
547 AudioComponentInstanceDispose(m_output_unit);
548 m_output_unit =
nullptr;
550 m_is_open.store(
false);
553bool CoreAudioStream::is_running()
const
555 return m_is_running.load(std::memory_order_acquire);
558bool CoreAudioStream::is_open()
const
560 return m_is_open.load(std::memory_order_acquire);
563void CoreAudioStream::set_process_callback(
564 std::function<
int(
void*,
void*,
unsigned int)> callback)
566 m_process_callback = std::move(callback);
569bool CoreAudioStream::configure_output_unit()
571 AudioComponentDescription desc {};
572 desc.componentType = kAudioUnitType_Output;
573 desc.componentSubType = kAudioUnitSubType_HALOutput;
574 desc.componentManufacturer = kAudioUnitManufacturer_Apple;
576 AudioComponent component = AudioComponentFindNext(
nullptr, &desc);
579 MF_ERROR(C, X,
"Failed to find HALOutput AudioUnit");
583 auto status = AudioComponentInstanceNew(component, &m_output_unit);
585 if (status != noErr || !m_output_unit) {
586 MF_ERROR(C, X,
"AudioComponentInstanceNew failed for output ({})",
587 static_cast<int>(status));
593 status = AudioUnitSetProperty(
595 kAudioOutputUnitProperty_EnableIO,
596 kAudioUnitScope_Output,
601 if (status != noErr) {
602 MF_ERROR(C, X,
"Failed to enable output bus ({})",
static_cast<int>(status));
608 status = AudioUnitSetProperty(
610 kAudioOutputUnitProperty_EnableIO,
611 kAudioUnitScope_Input,
616 if (status != noErr) {
617 MF_ERROR(C, X,
"Failed to disable input bus on output unit ({})",
618 static_cast<int>(status));
622 AURenderCallbackStruct callback {};
623 callback.inputProc = &CoreAudioStream::output_callback;
624 callback.inputProcRefCon =
this;
626 status = AudioUnitSetProperty(
628 kAudioUnitProperty_SetRenderCallback,
629 kAudioUnitScope_Input,
634 if (status != noErr) {
635 MF_ERROR(C, X,
"Failed to register render callback ({})",
static_cast<int>(status));
642bool CoreAudioStream::configure_input_unit()
644 AudioComponentDescription desc {};
645 desc.componentType = kAudioUnitType_Output;
646 desc.componentSubType = kAudioUnitSubType_HALOutput;
647 desc.componentManufacturer = kAudioUnitManufacturer_Apple;
649 AudioComponent component = AudioComponentFindNext(
nullptr, &desc);
652 MF_ERROR(C, X,
"Failed to find HALOutput AudioUnit for input");
656 auto status = AudioComponentInstanceNew(component, &m_input_unit);
658 if (status != noErr || !m_input_unit) {
659 MF_ERROR(C, X,
"AudioComponentInstanceNew failed for input ({})",
660 static_cast<int>(status));
666 status = AudioUnitSetProperty(
668 kAudioOutputUnitProperty_EnableIO,
669 kAudioUnitScope_Output,
674 if (status != noErr) {
675 MF_ERROR(C, X,
"Failed to disable output bus on input unit ({})",
676 static_cast<int>(status));
682 status = AudioUnitSetProperty(
684 kAudioOutputUnitProperty_EnableIO,
685 kAudioUnitScope_Input,
690 if (status != noErr) {
691 MF_ERROR(C, X,
"Failed to enable input bus ({})",
static_cast<int>(status));
695 AURenderCallbackStruct callback {};
696 callback.inputProc = &CoreAudioStream::input_callback;
697 callback.inputProcRefCon =
this;
699 status = AudioUnitSetProperty(
701 kAudioOutputUnitProperty_SetInputCallback,
702 kAudioUnitScope_Global,
707 if (status != noErr) {
708 MF_ERROR(C, X,
"Failed to register input callback ({})",
static_cast<int>(status));
715bool CoreAudioStream::configure_output_device()
717 auto status = AudioUnitSetProperty(
719 kAudioOutputUnitProperty_CurrentDevice,
720 kAudioUnitScope_Global,
723 sizeof(m_output_device_id));
725 if (status != noErr) {
726 MF_ERROR(C, X,
"Failed to set output device {} ({})",
727 static_cast<unsigned>(m_output_device_id),
static_cast<int>(status));
731 AudioObjectPropertyAddress buffer_addr {
732 .mSelector = kAudioDevicePropertyBufferFrameSize,
733 .mScope = kAudioObjectPropertyScopeGlobal,
734 .mElement = kAudioObjectPropertyElementMain
737 auto requested =
static_cast<UInt32
>(m_stream_info.buffer_size);
739 AudioObjectSetPropertyData(
748 UInt32 actual_size =
sizeof(actual);
750 if (AudioObjectGetPropertyData(m_output_device_id, &buffer_addr,
751 0,
nullptr, &actual_size, &actual)
753 MF_INFO(C, X,
"Output buffer size: requested={} actual={}", requested, actual);
759bool CoreAudioStream::configure_input_device()
761 auto status = AudioUnitSetProperty(
763 kAudioOutputUnitProperty_CurrentDevice,
764 kAudioUnitScope_Global,
767 sizeof(m_input_device_id));
769 if (status != noErr) {
770 MF_ERROR(C, X,
"Failed to set input device {} ({})",
771 static_cast<unsigned>(m_input_device_id),
static_cast<int>(status));
775 AudioObjectPropertyAddress buffer_addr {
776 .mSelector = kAudioDevicePropertyBufferFrameSize,
777 .mScope = kAudioObjectPropertyScopeGlobal,
778 .mElement = kAudioObjectPropertyElementMain
781 auto requested =
static_cast<UInt32
>(m_stream_info.buffer_size);
783 AudioObjectSetPropertyData(
792 UInt32 actual_size =
sizeof(actual);
794 if (AudioObjectGetPropertyData(m_input_device_id, &buffer_addr,
795 0,
nullptr, &actual_size, &actual)
797 MF_INFO(C, X,
"Input buffer size: requested={} actual={}", requested, actual);
803bool CoreAudioStream::configure_output_format()
805 AudioStreamBasicDescription
format {};
806 format.mSampleRate = m_stream_info.sample_rate;
807 format.mFormatID = kAudioFormatLinearPCM;
808 format.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked;
809 format.mBitsPerChannel = 64;
810 format.mChannelsPerFrame = m_stream_info.output.channels;
811 format.mFramesPerPacket = 1;
812 format.mBytesPerFrame =
sizeof(double) *
format.mChannelsPerFrame;
815 auto status = AudioUnitSetProperty(
817 kAudioUnitProperty_StreamFormat,
818 kAudioUnitScope_Input,
823 if (status != noErr) {
824 MF_ERROR(C, X,
"Failed to set output stream format ({})",
static_cast<int>(status));
828 MF_LOG(C, X,
"Output format: {} channels {} Hz {} bits",
834bool CoreAudioStream::configure_input_format()
836 AudioStreamBasicDescription
format {};
837 format.mSampleRate = m_stream_info.sample_rate;
838 format.mFormatID = kAudioFormatLinearPCM;
839 format.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked;
840 format.mBitsPerChannel = 64;
841 format.mChannelsPerFrame = m_stream_info.input.channels;
842 format.mFramesPerPacket = 1;
843 format.mBytesPerFrame =
sizeof(double) *
format.mChannelsPerFrame;
846 auto status = AudioUnitSetProperty(
848 kAudioUnitProperty_StreamFormat,
849 kAudioUnitScope_Output,
854 if (status != noErr) {
855 MF_ERROR(C, X,
"Failed to set input stream format ({})",
static_cast<int>(status));
859 const auto bytes =
static_cast<UInt32
>(
860 sizeof(double) * m_stream_info.input.channels * m_stream_info.buffer_size);
862 m_input_buffer_list =
static_cast<AudioBufferList*
>(
863 std::calloc(1,
sizeof(AudioBufferList) +
sizeof(AudioBuffer)));
865 m_input_buffer_list->mNumberBuffers = 1;
866 m_input_buffer_list->mBuffers[0].mNumberChannels = m_stream_info.input.channels;
867 m_input_buffer_list->mBuffers[0].mDataByteSize = bytes;
868 m_input_buffer_list->mBuffers[0].mData = std::calloc(1, bytes);
870 MF_LOG(C, X,
"Input format: {} channels {} Hz {} bits",
876OSStatus CoreAudioStream::output_callback(
878 AudioUnitRenderActionFlags*,
879 const AudioTimeStamp*,
882 AudioBufferList* io_data)
884 auto*
stream =
static_cast<CoreAudioStream*
>(ref_con);
889 if (!
stream->m_process_callback) {
890 for (UInt32
b = 0;
b < io_data->mNumberBuffers; ++
b)
891 std::memset(io_data->mBuffers[
b].mData, 0, io_data->mBuffers[
b].mDataByteSize);
895 auto* output =
static_cast<double*
>(io_data->mBuffers[0].mData);
897 void*
input =
stream->m_input_enabled.load(std::memory_order_acquire)
898 ?
stream->m_input_buffer_list->mBuffers[0].mData
901 stream->m_process_callback(output,
input, num_frames);
906OSStatus CoreAudioStream::input_callback(
908 AudioUnitRenderActionFlags* action_flags,
909 const AudioTimeStamp* time_stamp,
914 auto*
stream =
static_cast<CoreAudioStream*
>(ref_con);
919 stream->m_input_buffer_list->mBuffers[0].mDataByteSize =
static_cast<UInt32
>(
920 sizeof(double) *
stream->m_stream_info.input.channels * num_frames);
928 stream->m_input_buffer_list);
#define MF_INFO(comp, ctx,...)
#define MF_LOG(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
Core::GlobalStreamInfo stream
Core::GlobalInputConfig input
@ AudioBackend
Audio processing backend (Pipewire, wasapi, coreaudio)
std::string format(format_string< std::remove_cvref_t< Args >... > fmt_str, Args &&... args)
@ Core
Core engine, backend, subsystems.
void stop()
Stop all Portal::Graphics operations.