From: Philippe Mathieu-Daudé Date: Tue, 15 Feb 2022 10:05:44 +0000 (+0100) Subject: audio: Rename coreaudio extension to use Objective-C compiler X-Git-Url: http://git.maquefel.me/?a=commitdiff_plain;h=8b46d7e2dc8ec4b3515e43bc28ee77d9afab8b23;p=qemu.git audio: Rename coreaudio extension to use Objective-C compiler The coreaudio library includes Objective-C declarations (using the caret '^' symbol to declare block references [*]). When building with a C compiler we get: [175/839] Compiling C object libcommon.fa.p/audio_coreaudio.c.o In file included from /Library/Developer/CommandLineTools/SDKs/MacOSX12.sdk/System/Library/Frameworks/CoreAudio.framework/Headers/CoreAudio.h:18, from ../../audio/coreaudio.c:26: /Library/Developer/CommandLineTools/SDKs/MacOSX12.sdk/System/Library/Frameworks/CoreAudio.framework/Headers/AudioHardware.h:162:2: error: expected identifier or '(' before '^' token 162 | (^AudioObjectPropertyListenerBlock)( UInt32 inNumberAddresses, | ^ FAILED: libcommon.fa.p/audio_coreaudio.c.o Rename the file to use the Objective-C default extension (.m) so meson calls the correct compiler. [*] https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html Reviewed-by: Christian Schoenebeck Signed-off-by: Philippe Mathieu-Daudé --- diff --git a/audio/coreaudio.c b/audio/coreaudio.c deleted file mode 100644 index 3186b68474..0000000000 --- a/audio/coreaudio.c +++ /dev/null @@ -1,687 +0,0 @@ -/* - * QEMU OS X CoreAudio audio driver - * - * Copyright (c) 2005 Mike Kronenberg - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#include "qemu/osdep.h" -#include -#include /* pthread_X */ - -#include "qemu/main-loop.h" -#include "qemu/module.h" -#include "audio.h" - -#define AUDIO_CAP "coreaudio" -#include "audio_int.h" - -typedef struct coreaudioVoiceOut { - HWVoiceOut hw; - pthread_mutex_t buf_mutex; - AudioDeviceID outputDeviceID; - int frameSizeSetting; - uint32_t bufferCount; - UInt32 audioDevicePropertyBufferFrameSize; - AudioDeviceIOProcID ioprocid; - bool enabled; -} coreaudioVoiceOut; - -#if !defined(MAC_OS_VERSION_12_0) \ - || (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_VERSION_12_0) -#define kAudioObjectPropertyElementMain kAudioObjectPropertyElementMaster -#endif - -static const AudioObjectPropertyAddress voice_addr = { - kAudioHardwarePropertyDefaultOutputDevice, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMain -}; - -static OSStatus coreaudio_get_voice(AudioDeviceID *id) -{ - UInt32 size = sizeof(*id); - - return AudioObjectGetPropertyData(kAudioObjectSystemObject, - &voice_addr, - 0, - NULL, - &size, - id); -} - -static OSStatus coreaudio_get_framesizerange(AudioDeviceID id, - AudioValueRange *framerange) -{ - UInt32 size = sizeof(*framerange); - AudioObjectPropertyAddress addr = { - kAudioDevicePropertyBufferFrameSizeRange, - kAudioDevicePropertyScopeOutput, - kAudioObjectPropertyElementMain - }; - - return AudioObjectGetPropertyData(id, - &addr, - 0, - NULL, - &size, - framerange); -} - -static OSStatus coreaudio_get_framesize(AudioDeviceID id, UInt32 *framesize) -{ - UInt32 size = sizeof(*framesize); - AudioObjectPropertyAddress addr = { - kAudioDevicePropertyBufferFrameSize, - kAudioDevicePropertyScopeOutput, - kAudioObjectPropertyElementMain - }; - - return AudioObjectGetPropertyData(id, - &addr, - 0, - NULL, - &size, - framesize); -} - -static OSStatus coreaudio_set_framesize(AudioDeviceID id, UInt32 *framesize) -{ - UInt32 size = sizeof(*framesize); - AudioObjectPropertyAddress addr = { - kAudioDevicePropertyBufferFrameSize, - kAudioDevicePropertyScopeOutput, - kAudioObjectPropertyElementMain - }; - - return AudioObjectSetPropertyData(id, - &addr, - 0, - NULL, - size, - framesize); -} - -static OSStatus coreaudio_set_streamformat(AudioDeviceID id, - AudioStreamBasicDescription *d) -{ - UInt32 size = sizeof(*d); - AudioObjectPropertyAddress addr = { - kAudioDevicePropertyStreamFormat, - kAudioDevicePropertyScopeOutput, - kAudioObjectPropertyElementMain - }; - - return AudioObjectSetPropertyData(id, - &addr, - 0, - NULL, - size, - d); -} - -static OSStatus coreaudio_get_isrunning(AudioDeviceID id, UInt32 *result) -{ - UInt32 size = sizeof(*result); - AudioObjectPropertyAddress addr = { - kAudioDevicePropertyDeviceIsRunning, - kAudioDevicePropertyScopeOutput, - kAudioObjectPropertyElementMain - }; - - return AudioObjectGetPropertyData(id, - &addr, - 0, - NULL, - &size, - result); -} - -static void coreaudio_logstatus (OSStatus status) -{ - const char *str = "BUG"; - - switch (status) { - case kAudioHardwareNoError: - str = "kAudioHardwareNoError"; - break; - - case kAudioHardwareNotRunningError: - str = "kAudioHardwareNotRunningError"; - break; - - case kAudioHardwareUnspecifiedError: - str = "kAudioHardwareUnspecifiedError"; - break; - - case kAudioHardwareUnknownPropertyError: - str = "kAudioHardwareUnknownPropertyError"; - break; - - case kAudioHardwareBadPropertySizeError: - str = "kAudioHardwareBadPropertySizeError"; - break; - - case kAudioHardwareIllegalOperationError: - str = "kAudioHardwareIllegalOperationError"; - break; - - case kAudioHardwareBadDeviceError: - str = "kAudioHardwareBadDeviceError"; - break; - - case kAudioHardwareBadStreamError: - str = "kAudioHardwareBadStreamError"; - break; - - case kAudioHardwareUnsupportedOperationError: - str = "kAudioHardwareUnsupportedOperationError"; - break; - - case kAudioDeviceUnsupportedFormatError: - str = "kAudioDeviceUnsupportedFormatError"; - break; - - case kAudioDevicePermissionsError: - str = "kAudioDevicePermissionsError"; - break; - - default: - AUD_log (AUDIO_CAP, "Reason: status code %" PRId32 "\n", (int32_t)status); - return; - } - - AUD_log (AUDIO_CAP, "Reason: %s\n", str); -} - -static void GCC_FMT_ATTR (2, 3) coreaudio_logerr ( - OSStatus status, - const char *fmt, - ... - ) -{ - va_list ap; - - va_start (ap, fmt); - AUD_log (AUDIO_CAP, fmt, ap); - va_end (ap); - - coreaudio_logstatus (status); -} - -static void GCC_FMT_ATTR (3, 4) coreaudio_logerr2 ( - OSStatus status, - const char *typ, - const char *fmt, - ... - ) -{ - va_list ap; - - AUD_log (AUDIO_CAP, "Could not initialize %s\n", typ); - - va_start (ap, fmt); - AUD_vlog (AUDIO_CAP, fmt, ap); - va_end (ap); - - coreaudio_logstatus (status); -} - -#define coreaudio_playback_logerr(status, ...) \ - coreaudio_logerr2(status, "playback", __VA_ARGS__) - -static int coreaudio_buf_lock (coreaudioVoiceOut *core, const char *fn_name) -{ - int err; - - err = pthread_mutex_lock (&core->buf_mutex); - if (err) { - dolog ("Could not lock voice for %s\nReason: %s\n", - fn_name, strerror (err)); - return -1; - } - return 0; -} - -static int coreaudio_buf_unlock (coreaudioVoiceOut *core, const char *fn_name) -{ - int err; - - err = pthread_mutex_unlock (&core->buf_mutex); - if (err) { - dolog ("Could not unlock voice for %s\nReason: %s\n", - fn_name, strerror (err)); - return -1; - } - return 0; -} - -#define COREAUDIO_WRAPPER_FUNC(name, ret_type, args_decl, args) \ - static ret_type glue(coreaudio_, name)args_decl \ - { \ - coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; \ - ret_type ret; \ - \ - if (coreaudio_buf_lock(core, "coreaudio_" #name)) { \ - return 0; \ - } \ - \ - ret = glue(audio_generic_, name)args; \ - \ - coreaudio_buf_unlock(core, "coreaudio_" #name); \ - return ret; \ - } -COREAUDIO_WRAPPER_FUNC(buffer_get_free, size_t, (HWVoiceOut *hw), (hw)) -COREAUDIO_WRAPPER_FUNC(get_buffer_out, void *, (HWVoiceOut *hw, size_t *size), - (hw, size)) -COREAUDIO_WRAPPER_FUNC(put_buffer_out, size_t, - (HWVoiceOut *hw, void *buf, size_t size), - (hw, buf, size)) -COREAUDIO_WRAPPER_FUNC(write, size_t, (HWVoiceOut *hw, void *buf, size_t size), - (hw, buf, size)) -#undef COREAUDIO_WRAPPER_FUNC - -/* - * callback to feed audiooutput buffer. called without iothread lock. - * allowed to lock "buf_mutex", but disallowed to have any other locks. - */ -static OSStatus audioDeviceIOProc( - AudioDeviceID inDevice, - const AudioTimeStamp *inNow, - const AudioBufferList *inInputData, - const AudioTimeStamp *inInputTime, - AudioBufferList *outOutputData, - const AudioTimeStamp *inOutputTime, - void *hwptr) -{ - UInt32 frameCount, pending_frames; - void *out = outOutputData->mBuffers[0].mData; - HWVoiceOut *hw = hwptr; - coreaudioVoiceOut *core = (coreaudioVoiceOut *) hwptr; - size_t len; - - if (coreaudio_buf_lock (core, "audioDeviceIOProc")) { - inInputTime = 0; - return 0; - } - - if (inDevice != core->outputDeviceID) { - coreaudio_buf_unlock (core, "audioDeviceIOProc(old device)"); - return 0; - } - - frameCount = core->audioDevicePropertyBufferFrameSize; - pending_frames = hw->pending_emul / hw->info.bytes_per_frame; - - /* if there are not enough samples, set signal and return */ - if (pending_frames < frameCount) { - inInputTime = 0; - coreaudio_buf_unlock (core, "audioDeviceIOProc(empty)"); - return 0; - } - - len = frameCount * hw->info.bytes_per_frame; - while (len) { - size_t write_len, start; - - start = audio_ring_posb(hw->pos_emul, hw->pending_emul, hw->size_emul); - assert(start < hw->size_emul); - - write_len = MIN(MIN(hw->pending_emul, len), - hw->size_emul - start); - - memcpy(out, hw->buf_emul + start, write_len); - hw->pending_emul -= write_len; - len -= write_len; - out += write_len; - } - - coreaudio_buf_unlock (core, "audioDeviceIOProc"); - return 0; -} - -static OSStatus init_out_device(coreaudioVoiceOut *core) -{ - OSStatus status; - AudioValueRange frameRange; - - AudioStreamBasicDescription streamBasicDescription = { - .mBitsPerChannel = core->hw.info.bits, - .mBytesPerFrame = core->hw.info.bytes_per_frame, - .mBytesPerPacket = core->hw.info.bytes_per_frame, - .mChannelsPerFrame = core->hw.info.nchannels, - .mFormatFlags = kLinearPCMFormatFlagIsFloat, - .mFormatID = kAudioFormatLinearPCM, - .mFramesPerPacket = 1, - .mSampleRate = core->hw.info.freq - }; - - status = coreaudio_get_voice(&core->outputDeviceID); - if (status != kAudioHardwareNoError) { - coreaudio_playback_logerr (status, - "Could not get default output Device\n"); - return status; - } - if (core->outputDeviceID == kAudioDeviceUnknown) { - dolog ("Could not initialize playback - Unknown Audiodevice\n"); - return status; - } - - /* get minimum and maximum buffer frame sizes */ - status = coreaudio_get_framesizerange(core->outputDeviceID, - &frameRange); - if (status == kAudioHardwareBadObjectError) { - return 0; - } - if (status != kAudioHardwareNoError) { - coreaudio_playback_logerr (status, - "Could not get device buffer frame range\n"); - return status; - } - - if (frameRange.mMinimum > core->frameSizeSetting) { - core->audioDevicePropertyBufferFrameSize = (UInt32) frameRange.mMinimum; - dolog ("warning: Upsizing Buffer Frames to %f\n", frameRange.mMinimum); - } else if (frameRange.mMaximum < core->frameSizeSetting) { - core->audioDevicePropertyBufferFrameSize = (UInt32) frameRange.mMaximum; - dolog ("warning: Downsizing Buffer Frames to %f\n", frameRange.mMaximum); - } else { - core->audioDevicePropertyBufferFrameSize = core->frameSizeSetting; - } - - /* set Buffer Frame Size */ - status = coreaudio_set_framesize(core->outputDeviceID, - &core->audioDevicePropertyBufferFrameSize); - if (status == kAudioHardwareBadObjectError) { - return 0; - } - if (status != kAudioHardwareNoError) { - coreaudio_playback_logerr (status, - "Could not set device buffer frame size %" PRIu32 "\n", - (uint32_t)core->audioDevicePropertyBufferFrameSize); - return status; - } - - /* get Buffer Frame Size */ - status = coreaudio_get_framesize(core->outputDeviceID, - &core->audioDevicePropertyBufferFrameSize); - if (status == kAudioHardwareBadObjectError) { - return 0; - } - if (status != kAudioHardwareNoError) { - coreaudio_playback_logerr (status, - "Could not get device buffer frame size\n"); - return status; - } - core->hw.samples = core->bufferCount * core->audioDevicePropertyBufferFrameSize; - - /* set Samplerate */ - status = coreaudio_set_streamformat(core->outputDeviceID, - &streamBasicDescription); - if (status == kAudioHardwareBadObjectError) { - return 0; - } - if (status != kAudioHardwareNoError) { - coreaudio_playback_logerr (status, - "Could not set samplerate %lf\n", - streamBasicDescription.mSampleRate); - core->outputDeviceID = kAudioDeviceUnknown; - return status; - } - - /* - * set Callback. - * - * On macOS 11.3.1, Core Audio calls AudioDeviceIOProc after calling an - * internal function named HALB_Mutex::Lock(), which locks a mutex in - * HALB_IOThread::Entry(void*). HALB_Mutex::Lock() is also called in - * AudioObjectGetPropertyData, which is called by coreaudio driver. - * Therefore, the specified callback must be designed to avoid a deadlock - * with the callers of AudioObjectGetPropertyData. - */ - core->ioprocid = NULL; - status = AudioDeviceCreateIOProcID(core->outputDeviceID, - audioDeviceIOProc, - &core->hw, - &core->ioprocid); - if (status == kAudioHardwareBadDeviceError) { - return 0; - } - if (status != kAudioHardwareNoError || core->ioprocid == NULL) { - coreaudio_playback_logerr (status, "Could not set IOProc\n"); - core->outputDeviceID = kAudioDeviceUnknown; - return status; - } - - return 0; -} - -static void fini_out_device(coreaudioVoiceOut *core) -{ - OSStatus status; - UInt32 isrunning; - - /* stop playback */ - status = coreaudio_get_isrunning(core->outputDeviceID, &isrunning); - if (status != kAudioHardwareBadObjectError) { - if (status != kAudioHardwareNoError) { - coreaudio_logerr(status, - "Could not determine whether Device is playing\n"); - } - - if (isrunning) { - status = AudioDeviceStop(core->outputDeviceID, core->ioprocid); - if (status != kAudioHardwareBadDeviceError && status != kAudioHardwareNoError) { - coreaudio_logerr(status, "Could not stop playback\n"); - } - } - } - - /* remove callback */ - status = AudioDeviceDestroyIOProcID(core->outputDeviceID, - core->ioprocid); - if (status != kAudioHardwareBadDeviceError && status != kAudioHardwareNoError) { - coreaudio_logerr(status, "Could not remove IOProc\n"); - } - core->outputDeviceID = kAudioDeviceUnknown; -} - -static void update_device_playback_state(coreaudioVoiceOut *core) -{ - OSStatus status; - UInt32 isrunning; - - status = coreaudio_get_isrunning(core->outputDeviceID, &isrunning); - if (status != kAudioHardwareNoError) { - if (status != kAudioHardwareBadObjectError) { - coreaudio_logerr(status, - "Could not determine whether Device is playing\n"); - } - - return; - } - - if (core->enabled) { - /* start playback */ - if (!isrunning) { - status = AudioDeviceStart(core->outputDeviceID, core->ioprocid); - if (status != kAudioHardwareBadDeviceError && status != kAudioHardwareNoError) { - coreaudio_logerr (status, "Could not resume playback\n"); - } - } - } else { - /* stop playback */ - if (isrunning) { - status = AudioDeviceStop(core->outputDeviceID, - core->ioprocid); - if (status != kAudioHardwareBadDeviceError && status != kAudioHardwareNoError) { - coreaudio_logerr(status, "Could not pause playback\n"); - } - } - } -} - -/* called without iothread lock. */ -static OSStatus handle_voice_change( - AudioObjectID in_object_id, - UInt32 in_number_addresses, - const AudioObjectPropertyAddress *in_addresses, - void *in_client_data) -{ - coreaudioVoiceOut *core = in_client_data; - - qemu_mutex_lock_iothread(); - - if (core->outputDeviceID) { - fini_out_device(core); - } - - if (!init_out_device(core)) { - update_device_playback_state(core); - } - - qemu_mutex_unlock_iothread(); - return 0; -} - -static int coreaudio_init_out(HWVoiceOut *hw, struct audsettings *as, - void *drv_opaque) -{ - OSStatus status; - coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; - int err; - Audiodev *dev = drv_opaque; - AudiodevCoreaudioPerDirectionOptions *cpdo = dev->u.coreaudio.out; - struct audsettings obt_as; - - /* create mutex */ - err = pthread_mutex_init(&core->buf_mutex, NULL); - if (err) { - dolog("Could not create mutex\nReason: %s\n", strerror (err)); - return -1; - } - - obt_as = *as; - as = &obt_as; - as->fmt = AUDIO_FORMAT_F32; - audio_pcm_init_info (&hw->info, as); - - core->frameSizeSetting = audio_buffer_frames( - qapi_AudiodevCoreaudioPerDirectionOptions_base(cpdo), as, 11610); - - core->bufferCount = cpdo->has_buffer_count ? cpdo->buffer_count : 4; - - status = AudioObjectAddPropertyListener(kAudioObjectSystemObject, - &voice_addr, handle_voice_change, - core); - if (status != kAudioHardwareNoError) { - coreaudio_playback_logerr (status, - "Could not listen to voice property change\n"); - return -1; - } - - if (init_out_device(core)) { - status = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, - &voice_addr, - handle_voice_change, - core); - if (status != kAudioHardwareNoError) { - coreaudio_playback_logerr(status, - "Could not remove voice property change listener\n"); - } - - return -1; - } - - return 0; -} - -static void coreaudio_fini_out (HWVoiceOut *hw) -{ - OSStatus status; - int err; - coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; - - status = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, - &voice_addr, - handle_voice_change, - core); - if (status != kAudioHardwareNoError) { - coreaudio_logerr(status, "Could not remove voice property change listener\n"); - } - - fini_out_device(core); - - /* destroy mutex */ - err = pthread_mutex_destroy(&core->buf_mutex); - if (err) { - dolog("Could not destroy mutex\nReason: %s\n", strerror (err)); - } -} - -static void coreaudio_enable_out(HWVoiceOut *hw, bool enable) -{ - coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; - - core->enabled = enable; - update_device_playback_state(core); -} - -static void *coreaudio_audio_init(Audiodev *dev) -{ - return dev; -} - -static void coreaudio_audio_fini (void *opaque) -{ -} - -static struct audio_pcm_ops coreaudio_pcm_ops = { - .init_out = coreaudio_init_out, - .fini_out = coreaudio_fini_out, - /* wrapper for audio_generic_write */ - .write = coreaudio_write, - /* wrapper for audio_generic_buffer_get_free */ - .buffer_get_free = coreaudio_buffer_get_free, - /* wrapper for audio_generic_get_buffer_out */ - .get_buffer_out = coreaudio_get_buffer_out, - /* wrapper for audio_generic_put_buffer_out */ - .put_buffer_out = coreaudio_put_buffer_out, - .enable_out = coreaudio_enable_out -}; - -static struct audio_driver coreaudio_audio_driver = { - .name = "coreaudio", - .descr = "CoreAudio http://developer.apple.com/audio/coreaudio.html", - .init = coreaudio_audio_init, - .fini = coreaudio_audio_fini, - .pcm_ops = &coreaudio_pcm_ops, - .can_be_default = 1, - .max_voices_out = 1, - .max_voices_in = 0, - .voice_size_out = sizeof (coreaudioVoiceOut), - .voice_size_in = 0 -}; - -static void register_audio_coreaudio(void) -{ - audio_driver_register(&coreaudio_audio_driver); -} -type_init(register_audio_coreaudio); diff --git a/audio/coreaudio.m b/audio/coreaudio.m new file mode 100644 index 0000000000..3186b68474 --- /dev/null +++ b/audio/coreaudio.m @@ -0,0 +1,687 @@ +/* + * QEMU OS X CoreAudio audio driver + * + * Copyright (c) 2005 Mike Kronenberg + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "qemu/osdep.h" +#include +#include /* pthread_X */ + +#include "qemu/main-loop.h" +#include "qemu/module.h" +#include "audio.h" + +#define AUDIO_CAP "coreaudio" +#include "audio_int.h" + +typedef struct coreaudioVoiceOut { + HWVoiceOut hw; + pthread_mutex_t buf_mutex; + AudioDeviceID outputDeviceID; + int frameSizeSetting; + uint32_t bufferCount; + UInt32 audioDevicePropertyBufferFrameSize; + AudioDeviceIOProcID ioprocid; + bool enabled; +} coreaudioVoiceOut; + +#if !defined(MAC_OS_VERSION_12_0) \ + || (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_VERSION_12_0) +#define kAudioObjectPropertyElementMain kAudioObjectPropertyElementMaster +#endif + +static const AudioObjectPropertyAddress voice_addr = { + kAudioHardwarePropertyDefaultOutputDevice, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMain +}; + +static OSStatus coreaudio_get_voice(AudioDeviceID *id) +{ + UInt32 size = sizeof(*id); + + return AudioObjectGetPropertyData(kAudioObjectSystemObject, + &voice_addr, + 0, + NULL, + &size, + id); +} + +static OSStatus coreaudio_get_framesizerange(AudioDeviceID id, + AudioValueRange *framerange) +{ + UInt32 size = sizeof(*framerange); + AudioObjectPropertyAddress addr = { + kAudioDevicePropertyBufferFrameSizeRange, + kAudioDevicePropertyScopeOutput, + kAudioObjectPropertyElementMain + }; + + return AudioObjectGetPropertyData(id, + &addr, + 0, + NULL, + &size, + framerange); +} + +static OSStatus coreaudio_get_framesize(AudioDeviceID id, UInt32 *framesize) +{ + UInt32 size = sizeof(*framesize); + AudioObjectPropertyAddress addr = { + kAudioDevicePropertyBufferFrameSize, + kAudioDevicePropertyScopeOutput, + kAudioObjectPropertyElementMain + }; + + return AudioObjectGetPropertyData(id, + &addr, + 0, + NULL, + &size, + framesize); +} + +static OSStatus coreaudio_set_framesize(AudioDeviceID id, UInt32 *framesize) +{ + UInt32 size = sizeof(*framesize); + AudioObjectPropertyAddress addr = { + kAudioDevicePropertyBufferFrameSize, + kAudioDevicePropertyScopeOutput, + kAudioObjectPropertyElementMain + }; + + return AudioObjectSetPropertyData(id, + &addr, + 0, + NULL, + size, + framesize); +} + +static OSStatus coreaudio_set_streamformat(AudioDeviceID id, + AudioStreamBasicDescription *d) +{ + UInt32 size = sizeof(*d); + AudioObjectPropertyAddress addr = { + kAudioDevicePropertyStreamFormat, + kAudioDevicePropertyScopeOutput, + kAudioObjectPropertyElementMain + }; + + return AudioObjectSetPropertyData(id, + &addr, + 0, + NULL, + size, + d); +} + +static OSStatus coreaudio_get_isrunning(AudioDeviceID id, UInt32 *result) +{ + UInt32 size = sizeof(*result); + AudioObjectPropertyAddress addr = { + kAudioDevicePropertyDeviceIsRunning, + kAudioDevicePropertyScopeOutput, + kAudioObjectPropertyElementMain + }; + + return AudioObjectGetPropertyData(id, + &addr, + 0, + NULL, + &size, + result); +} + +static void coreaudio_logstatus (OSStatus status) +{ + const char *str = "BUG"; + + switch (status) { + case kAudioHardwareNoError: + str = "kAudioHardwareNoError"; + break; + + case kAudioHardwareNotRunningError: + str = "kAudioHardwareNotRunningError"; + break; + + case kAudioHardwareUnspecifiedError: + str = "kAudioHardwareUnspecifiedError"; + break; + + case kAudioHardwareUnknownPropertyError: + str = "kAudioHardwareUnknownPropertyError"; + break; + + case kAudioHardwareBadPropertySizeError: + str = "kAudioHardwareBadPropertySizeError"; + break; + + case kAudioHardwareIllegalOperationError: + str = "kAudioHardwareIllegalOperationError"; + break; + + case kAudioHardwareBadDeviceError: + str = "kAudioHardwareBadDeviceError"; + break; + + case kAudioHardwareBadStreamError: + str = "kAudioHardwareBadStreamError"; + break; + + case kAudioHardwareUnsupportedOperationError: + str = "kAudioHardwareUnsupportedOperationError"; + break; + + case kAudioDeviceUnsupportedFormatError: + str = "kAudioDeviceUnsupportedFormatError"; + break; + + case kAudioDevicePermissionsError: + str = "kAudioDevicePermissionsError"; + break; + + default: + AUD_log (AUDIO_CAP, "Reason: status code %" PRId32 "\n", (int32_t)status); + return; + } + + AUD_log (AUDIO_CAP, "Reason: %s\n", str); +} + +static void GCC_FMT_ATTR (2, 3) coreaudio_logerr ( + OSStatus status, + const char *fmt, + ... + ) +{ + va_list ap; + + va_start (ap, fmt); + AUD_log (AUDIO_CAP, fmt, ap); + va_end (ap); + + coreaudio_logstatus (status); +} + +static void GCC_FMT_ATTR (3, 4) coreaudio_logerr2 ( + OSStatus status, + const char *typ, + const char *fmt, + ... + ) +{ + va_list ap; + + AUD_log (AUDIO_CAP, "Could not initialize %s\n", typ); + + va_start (ap, fmt); + AUD_vlog (AUDIO_CAP, fmt, ap); + va_end (ap); + + coreaudio_logstatus (status); +} + +#define coreaudio_playback_logerr(status, ...) \ + coreaudio_logerr2(status, "playback", __VA_ARGS__) + +static int coreaudio_buf_lock (coreaudioVoiceOut *core, const char *fn_name) +{ + int err; + + err = pthread_mutex_lock (&core->buf_mutex); + if (err) { + dolog ("Could not lock voice for %s\nReason: %s\n", + fn_name, strerror (err)); + return -1; + } + return 0; +} + +static int coreaudio_buf_unlock (coreaudioVoiceOut *core, const char *fn_name) +{ + int err; + + err = pthread_mutex_unlock (&core->buf_mutex); + if (err) { + dolog ("Could not unlock voice for %s\nReason: %s\n", + fn_name, strerror (err)); + return -1; + } + return 0; +} + +#define COREAUDIO_WRAPPER_FUNC(name, ret_type, args_decl, args) \ + static ret_type glue(coreaudio_, name)args_decl \ + { \ + coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; \ + ret_type ret; \ + \ + if (coreaudio_buf_lock(core, "coreaudio_" #name)) { \ + return 0; \ + } \ + \ + ret = glue(audio_generic_, name)args; \ + \ + coreaudio_buf_unlock(core, "coreaudio_" #name); \ + return ret; \ + } +COREAUDIO_WRAPPER_FUNC(buffer_get_free, size_t, (HWVoiceOut *hw), (hw)) +COREAUDIO_WRAPPER_FUNC(get_buffer_out, void *, (HWVoiceOut *hw, size_t *size), + (hw, size)) +COREAUDIO_WRAPPER_FUNC(put_buffer_out, size_t, + (HWVoiceOut *hw, void *buf, size_t size), + (hw, buf, size)) +COREAUDIO_WRAPPER_FUNC(write, size_t, (HWVoiceOut *hw, void *buf, size_t size), + (hw, buf, size)) +#undef COREAUDIO_WRAPPER_FUNC + +/* + * callback to feed audiooutput buffer. called without iothread lock. + * allowed to lock "buf_mutex", but disallowed to have any other locks. + */ +static OSStatus audioDeviceIOProc( + AudioDeviceID inDevice, + const AudioTimeStamp *inNow, + const AudioBufferList *inInputData, + const AudioTimeStamp *inInputTime, + AudioBufferList *outOutputData, + const AudioTimeStamp *inOutputTime, + void *hwptr) +{ + UInt32 frameCount, pending_frames; + void *out = outOutputData->mBuffers[0].mData; + HWVoiceOut *hw = hwptr; + coreaudioVoiceOut *core = (coreaudioVoiceOut *) hwptr; + size_t len; + + if (coreaudio_buf_lock (core, "audioDeviceIOProc")) { + inInputTime = 0; + return 0; + } + + if (inDevice != core->outputDeviceID) { + coreaudio_buf_unlock (core, "audioDeviceIOProc(old device)"); + return 0; + } + + frameCount = core->audioDevicePropertyBufferFrameSize; + pending_frames = hw->pending_emul / hw->info.bytes_per_frame; + + /* if there are not enough samples, set signal and return */ + if (pending_frames < frameCount) { + inInputTime = 0; + coreaudio_buf_unlock (core, "audioDeviceIOProc(empty)"); + return 0; + } + + len = frameCount * hw->info.bytes_per_frame; + while (len) { + size_t write_len, start; + + start = audio_ring_posb(hw->pos_emul, hw->pending_emul, hw->size_emul); + assert(start < hw->size_emul); + + write_len = MIN(MIN(hw->pending_emul, len), + hw->size_emul - start); + + memcpy(out, hw->buf_emul + start, write_len); + hw->pending_emul -= write_len; + len -= write_len; + out += write_len; + } + + coreaudio_buf_unlock (core, "audioDeviceIOProc"); + return 0; +} + +static OSStatus init_out_device(coreaudioVoiceOut *core) +{ + OSStatus status; + AudioValueRange frameRange; + + AudioStreamBasicDescription streamBasicDescription = { + .mBitsPerChannel = core->hw.info.bits, + .mBytesPerFrame = core->hw.info.bytes_per_frame, + .mBytesPerPacket = core->hw.info.bytes_per_frame, + .mChannelsPerFrame = core->hw.info.nchannels, + .mFormatFlags = kLinearPCMFormatFlagIsFloat, + .mFormatID = kAudioFormatLinearPCM, + .mFramesPerPacket = 1, + .mSampleRate = core->hw.info.freq + }; + + status = coreaudio_get_voice(&core->outputDeviceID); + if (status != kAudioHardwareNoError) { + coreaudio_playback_logerr (status, + "Could not get default output Device\n"); + return status; + } + if (core->outputDeviceID == kAudioDeviceUnknown) { + dolog ("Could not initialize playback - Unknown Audiodevice\n"); + return status; + } + + /* get minimum and maximum buffer frame sizes */ + status = coreaudio_get_framesizerange(core->outputDeviceID, + &frameRange); + if (status == kAudioHardwareBadObjectError) { + return 0; + } + if (status != kAudioHardwareNoError) { + coreaudio_playback_logerr (status, + "Could not get device buffer frame range\n"); + return status; + } + + if (frameRange.mMinimum > core->frameSizeSetting) { + core->audioDevicePropertyBufferFrameSize = (UInt32) frameRange.mMinimum; + dolog ("warning: Upsizing Buffer Frames to %f\n", frameRange.mMinimum); + } else if (frameRange.mMaximum < core->frameSizeSetting) { + core->audioDevicePropertyBufferFrameSize = (UInt32) frameRange.mMaximum; + dolog ("warning: Downsizing Buffer Frames to %f\n", frameRange.mMaximum); + } else { + core->audioDevicePropertyBufferFrameSize = core->frameSizeSetting; + } + + /* set Buffer Frame Size */ + status = coreaudio_set_framesize(core->outputDeviceID, + &core->audioDevicePropertyBufferFrameSize); + if (status == kAudioHardwareBadObjectError) { + return 0; + } + if (status != kAudioHardwareNoError) { + coreaudio_playback_logerr (status, + "Could not set device buffer frame size %" PRIu32 "\n", + (uint32_t)core->audioDevicePropertyBufferFrameSize); + return status; + } + + /* get Buffer Frame Size */ + status = coreaudio_get_framesize(core->outputDeviceID, + &core->audioDevicePropertyBufferFrameSize); + if (status == kAudioHardwareBadObjectError) { + return 0; + } + if (status != kAudioHardwareNoError) { + coreaudio_playback_logerr (status, + "Could not get device buffer frame size\n"); + return status; + } + core->hw.samples = core->bufferCount * core->audioDevicePropertyBufferFrameSize; + + /* set Samplerate */ + status = coreaudio_set_streamformat(core->outputDeviceID, + &streamBasicDescription); + if (status == kAudioHardwareBadObjectError) { + return 0; + } + if (status != kAudioHardwareNoError) { + coreaudio_playback_logerr (status, + "Could not set samplerate %lf\n", + streamBasicDescription.mSampleRate); + core->outputDeviceID = kAudioDeviceUnknown; + return status; + } + + /* + * set Callback. + * + * On macOS 11.3.1, Core Audio calls AudioDeviceIOProc after calling an + * internal function named HALB_Mutex::Lock(), which locks a mutex in + * HALB_IOThread::Entry(void*). HALB_Mutex::Lock() is also called in + * AudioObjectGetPropertyData, which is called by coreaudio driver. + * Therefore, the specified callback must be designed to avoid a deadlock + * with the callers of AudioObjectGetPropertyData. + */ + core->ioprocid = NULL; + status = AudioDeviceCreateIOProcID(core->outputDeviceID, + audioDeviceIOProc, + &core->hw, + &core->ioprocid); + if (status == kAudioHardwareBadDeviceError) { + return 0; + } + if (status != kAudioHardwareNoError || core->ioprocid == NULL) { + coreaudio_playback_logerr (status, "Could not set IOProc\n"); + core->outputDeviceID = kAudioDeviceUnknown; + return status; + } + + return 0; +} + +static void fini_out_device(coreaudioVoiceOut *core) +{ + OSStatus status; + UInt32 isrunning; + + /* stop playback */ + status = coreaudio_get_isrunning(core->outputDeviceID, &isrunning); + if (status != kAudioHardwareBadObjectError) { + if (status != kAudioHardwareNoError) { + coreaudio_logerr(status, + "Could not determine whether Device is playing\n"); + } + + if (isrunning) { + status = AudioDeviceStop(core->outputDeviceID, core->ioprocid); + if (status != kAudioHardwareBadDeviceError && status != kAudioHardwareNoError) { + coreaudio_logerr(status, "Could not stop playback\n"); + } + } + } + + /* remove callback */ + status = AudioDeviceDestroyIOProcID(core->outputDeviceID, + core->ioprocid); + if (status != kAudioHardwareBadDeviceError && status != kAudioHardwareNoError) { + coreaudio_logerr(status, "Could not remove IOProc\n"); + } + core->outputDeviceID = kAudioDeviceUnknown; +} + +static void update_device_playback_state(coreaudioVoiceOut *core) +{ + OSStatus status; + UInt32 isrunning; + + status = coreaudio_get_isrunning(core->outputDeviceID, &isrunning); + if (status != kAudioHardwareNoError) { + if (status != kAudioHardwareBadObjectError) { + coreaudio_logerr(status, + "Could not determine whether Device is playing\n"); + } + + return; + } + + if (core->enabled) { + /* start playback */ + if (!isrunning) { + status = AudioDeviceStart(core->outputDeviceID, core->ioprocid); + if (status != kAudioHardwareBadDeviceError && status != kAudioHardwareNoError) { + coreaudio_logerr (status, "Could not resume playback\n"); + } + } + } else { + /* stop playback */ + if (isrunning) { + status = AudioDeviceStop(core->outputDeviceID, + core->ioprocid); + if (status != kAudioHardwareBadDeviceError && status != kAudioHardwareNoError) { + coreaudio_logerr(status, "Could not pause playback\n"); + } + } + } +} + +/* called without iothread lock. */ +static OSStatus handle_voice_change( + AudioObjectID in_object_id, + UInt32 in_number_addresses, + const AudioObjectPropertyAddress *in_addresses, + void *in_client_data) +{ + coreaudioVoiceOut *core = in_client_data; + + qemu_mutex_lock_iothread(); + + if (core->outputDeviceID) { + fini_out_device(core); + } + + if (!init_out_device(core)) { + update_device_playback_state(core); + } + + qemu_mutex_unlock_iothread(); + return 0; +} + +static int coreaudio_init_out(HWVoiceOut *hw, struct audsettings *as, + void *drv_opaque) +{ + OSStatus status; + coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; + int err; + Audiodev *dev = drv_opaque; + AudiodevCoreaudioPerDirectionOptions *cpdo = dev->u.coreaudio.out; + struct audsettings obt_as; + + /* create mutex */ + err = pthread_mutex_init(&core->buf_mutex, NULL); + if (err) { + dolog("Could not create mutex\nReason: %s\n", strerror (err)); + return -1; + } + + obt_as = *as; + as = &obt_as; + as->fmt = AUDIO_FORMAT_F32; + audio_pcm_init_info (&hw->info, as); + + core->frameSizeSetting = audio_buffer_frames( + qapi_AudiodevCoreaudioPerDirectionOptions_base(cpdo), as, 11610); + + core->bufferCount = cpdo->has_buffer_count ? cpdo->buffer_count : 4; + + status = AudioObjectAddPropertyListener(kAudioObjectSystemObject, + &voice_addr, handle_voice_change, + core); + if (status != kAudioHardwareNoError) { + coreaudio_playback_logerr (status, + "Could not listen to voice property change\n"); + return -1; + } + + if (init_out_device(core)) { + status = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, + &voice_addr, + handle_voice_change, + core); + if (status != kAudioHardwareNoError) { + coreaudio_playback_logerr(status, + "Could not remove voice property change listener\n"); + } + + return -1; + } + + return 0; +} + +static void coreaudio_fini_out (HWVoiceOut *hw) +{ + OSStatus status; + int err; + coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; + + status = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, + &voice_addr, + handle_voice_change, + core); + if (status != kAudioHardwareNoError) { + coreaudio_logerr(status, "Could not remove voice property change listener\n"); + } + + fini_out_device(core); + + /* destroy mutex */ + err = pthread_mutex_destroy(&core->buf_mutex); + if (err) { + dolog("Could not destroy mutex\nReason: %s\n", strerror (err)); + } +} + +static void coreaudio_enable_out(HWVoiceOut *hw, bool enable) +{ + coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; + + core->enabled = enable; + update_device_playback_state(core); +} + +static void *coreaudio_audio_init(Audiodev *dev) +{ + return dev; +} + +static void coreaudio_audio_fini (void *opaque) +{ +} + +static struct audio_pcm_ops coreaudio_pcm_ops = { + .init_out = coreaudio_init_out, + .fini_out = coreaudio_fini_out, + /* wrapper for audio_generic_write */ + .write = coreaudio_write, + /* wrapper for audio_generic_buffer_get_free */ + .buffer_get_free = coreaudio_buffer_get_free, + /* wrapper for audio_generic_get_buffer_out */ + .get_buffer_out = coreaudio_get_buffer_out, + /* wrapper for audio_generic_put_buffer_out */ + .put_buffer_out = coreaudio_put_buffer_out, + .enable_out = coreaudio_enable_out +}; + +static struct audio_driver coreaudio_audio_driver = { + .name = "coreaudio", + .descr = "CoreAudio http://developer.apple.com/audio/coreaudio.html", + .init = coreaudio_audio_init, + .fini = coreaudio_audio_fini, + .pcm_ops = &coreaudio_pcm_ops, + .can_be_default = 1, + .max_voices_out = 1, + .max_voices_in = 0, + .voice_size_out = sizeof (coreaudioVoiceOut), + .voice_size_in = 0 +}; + +static void register_audio_coreaudio(void) +{ + audio_driver_register(&coreaudio_audio_driver); +} +type_init(register_audio_coreaudio); diff --git a/audio/meson.build b/audio/meson.build index d9b295514f..94dab16891 100644 --- a/audio/meson.build +++ b/audio/meson.build @@ -7,7 +7,7 @@ softmmu_ss.add(files( 'wavcapture.c', )) -softmmu_ss.add(when: coreaudio, if_true: files('coreaudio.c')) +softmmu_ss.add(when: coreaudio, if_true: files('coreaudio.m')) softmmu_ss.add(when: dsound, if_true: files('dsoundaudio.c', 'audio_win_int.c')) audio_modules = {}