/* * 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/module.h" #include "audio.h" #define AUDIO_CAP "coreaudio" #include "audio_int.h" #ifndef MAC_OS_X_VERSION_10_6 #define MAC_OS_X_VERSION_10_6 1060 #endif typedef struct coreaudioVoiceOut { HWVoiceOut hw; pthread_mutex_t mutex; AudioDeviceID outputDeviceID; UInt32 audioDevicePropertyBufferFrameSize; AudioStreamBasicDescription outputStreamBasicDescription; AudioDeviceIOProcID ioprocid; } coreaudioVoiceOut; #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 /* The APIs used here only become available from 10.6 */ static OSStatus coreaudio_get_voice(AudioDeviceID *id) { UInt32 size = sizeof(*id); AudioObjectPropertyAddress addr = { kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; return AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr, 0, NULL, &size, id); } static OSStatus coreaudio_get_framesizerange(AudioDeviceID id, AudioValueRange *framerange) { UInt32 size = sizeof(*framerange); AudioObjectPropertyAddress addr = { kAudioDevicePropertyBufferFrameSizeRange, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster }; 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, kAudioObjectPropertyElementMaster }; 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, kAudioObjectPropertyElementMaster }; return AudioObjectSetPropertyData(id, &addr, 0, NULL, size, framesize); } static OSStatus coreaudio_get_streamformat(AudioDeviceID id, AudioStreamBasicDescription *d) { UInt32 size = sizeof(*d); AudioObjectPropertyAddress addr = { kAudioDevicePropertyStreamFormat, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster }; return AudioObjectGetPropertyData(id, &addr, 0, NULL, &size, d); } static OSStatus coreaudio_set_streamformat(AudioDeviceID id, AudioStreamBasicDescription *d) { UInt32 size = sizeof(*d); AudioObjectPropertyAddress addr = { kAudioDevicePropertyStreamFormat, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster }; 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, kAudioObjectPropertyElementMaster }; return AudioObjectGetPropertyData(id, &addr, 0, NULL, &size, result); } #else /* Legacy versions of functions using deprecated APIs */ static OSStatus coreaudio_get_voice(AudioDeviceID *id) { UInt32 size = sizeof(*id); return AudioHardwareGetProperty( kAudioHardwarePropertyDefaultOutputDevice, &size, id); } static OSStatus coreaudio_get_framesizerange(AudioDeviceID id, AudioValueRange *framerange) { UInt32 size = sizeof(*framerange); return AudioDeviceGetProperty( id, 0, 0, kAudioDevicePropertyBufferFrameSizeRange, &size, framerange); } static OSStatus coreaudio_get_framesize(AudioDeviceID id, UInt32 *framesize) { UInt32 size = sizeof(*framesize); return AudioDeviceGetProperty( id, 0, false, kAudioDevicePropertyBufferFrameSize, &size, framesize); } static OSStatus coreaudio_set_framesize(AudioDeviceID id, UInt32 *framesize) { UInt32 size = sizeof(*framesize); return AudioDeviceSetProperty( id, NULL, 0, false, kAudioDevicePropertyBufferFrameSize, size, framesize); } static OSStatus coreaudio_get_streamformat(AudioDeviceID id, AudioStreamBasicDescription *d) { UInt32 size = sizeof(*d); return AudioDeviceGetProperty( id, 0, false, kAudioDevicePropertyStreamFormat, &size, d); } static OSStatus coreaudio_set_streamformat(AudioDeviceID id, AudioStreamBasicDescription *d) { UInt32 size = sizeof(*d); return AudioDeviceSetProperty( id, 0, 0, 0, kAudioDevicePropertyStreamFormat, size, d); } static OSStatus coreaudio_get_isrunning(AudioDeviceID id, UInt32 *result) { UInt32 size = sizeof(*result); return AudioDeviceGetProperty( id, 0, 0, kAudioDevicePropertyDeviceIsRunning, &size, result); } #endif 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); } static inline UInt32 isPlaying (AudioDeviceID outputDeviceID) { OSStatus status; UInt32 result = 0; status = coreaudio_get_isrunning(outputDeviceID, &result); if (status != kAudioHardwareNoError) { coreaudio_logerr(status, "Could not determine whether Device is playing\n"); } return result; } static int coreaudio_lock (coreaudioVoiceOut *core, const char *fn_name) { int err; err = pthread_mutex_lock (&core->mutex); if (err) { dolog ("Could not lock voice for %s\nReason: %s\n", fn_name, strerror (err)); return -1; } return 0; } static int coreaudio_unlock (coreaudioVoiceOut *core, const char *fn_name) { int err; err = pthread_mutex_unlock (&core->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_lock(core, "coreaudio_" #name)) { \ return 0; \ } \ \ ret = glue(audio_generic_, name)args; \ \ coreaudio_unlock(core, "coreaudio_" #name); \ return ret; \ } 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 */ 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_lock (core, "audioDeviceIOProc")) { inInputTime = 0; 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_unlock (core, "audioDeviceIOProc(empty)"); return 0; } len = frameCount * hw->info.bytes_per_frame; while (len) { size_t write_len; ssize_t start = ((ssize_t) hw->pos_emul) - hw->pending_emul; if (start < 0) { start += hw->size_emul; } assert(start >= 0 && 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_unlock (core, "audioDeviceIOProc"); return 0; } static int coreaudio_init_out(HWVoiceOut *hw, struct audsettings *as, void *drv_opaque) { OSStatus status; coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; int err; const char *typ = "playback"; AudioValueRange frameRange; Audiodev *dev = drv_opaque; AudiodevCoreaudioPerDirectionOptions *cpdo = dev->u.coreaudio.out; int frames; struct audsettings fake_as; /* create mutex */ err = pthread_mutex_init(&core->mutex, NULL); if (err) { dolog("Could not create mutex\nReason: %s\n", strerror (err)); return -1; } /* * The canonical audio format for CoreAudio on macOS is float. Currently * there is no generic code for AUDIO_FORMAT_F32 in qemu. Here we select * AUDIO_FORMAT_S32 instead because only the sample size has to match. */ fake_as = *as; as = &fake_as; as->fmt = AUDIO_FORMAT_S32; audio_pcm_init_info (&hw->info, as); status = coreaudio_get_voice(&core->outputDeviceID); if (status != kAudioHardwareNoError) { coreaudio_logerr2 (status, typ, "Could not get default output Device\n"); return -1; } if (core->outputDeviceID == kAudioDeviceUnknown) { dolog ("Could not initialize %s - Unknown Audiodevice\n", typ); return -1; } /* get minimum and maximum buffer frame sizes */ status = coreaudio_get_framesizerange(core->outputDeviceID, &frameRange); if (status != kAudioHardwareNoError) { coreaudio_logerr2 (status, typ, "Could not get device buffer frame range\n"); return -1; } frames = audio_buffer_frames( qapi_AudiodevCoreaudioPerDirectionOptions_base(cpdo), as, 11610); if (frameRange.mMinimum > frames) { core->audioDevicePropertyBufferFrameSize = (UInt32) frameRange.mMinimum; dolog ("warning: Upsizing Buffer Frames to %f\n", frameRange.mMinimum); } else if (frameRange.mMaximum < frames) { core->audioDevicePropertyBufferFrameSize = (UInt32) frameRange.mMaximum; dolog ("warning: Downsizing Buffer Frames to %f\n", frameRange.mMaximum); } else { core->audioDevicePropertyBufferFrameSize = frames; } /* set Buffer Frame Size */ status = coreaudio_set_framesize(core->outputDeviceID, &core->audioDevicePropertyBufferFrameSize); if (status != kAudioHardwareNoError) { coreaudio_logerr2 (status, typ, "Could not set device buffer frame size %" PRIu32 "\n", (uint32_t)core->audioDevicePropertyBufferFrameSize); return -1; } /* get Buffer Frame Size */ status = coreaudio_get_framesize(core->outputDeviceID, &core->audioDevicePropertyBufferFrameSize); if (status != kAudioHardwareNoError) { coreaudio_logerr2 (status, typ, "Could not get device buffer frame size\n"); return -1; } hw->samples = (cpdo->has_buffer_count ? cpdo->buffer_count : 4) * core->audioDevicePropertyBufferFrameSize; /* get StreamFormat */ status = coreaudio_get_streamformat(core->outputDeviceID, &core->outputStreamBasicDescription); if (status != kAudioHardwareNoError) { coreaudio_logerr2 (status, typ, "Could not get Device Stream properties\n"); core->outputDeviceID = kAudioDeviceUnknown; return -1; } /* set Samplerate */ core->outputStreamBasicDescription.mSampleRate = (Float64) as->freq; status = coreaudio_set_streamformat(core->outputDeviceID, &core->outputStreamBasicDescription); if (status != kAudioHardwareNoError) { coreaudio_logerr2 (status, typ, "Could not set samplerate %d\n", as->freq); core->outputDeviceID = kAudioDeviceUnknown; return -1; } /* set Callback */ core->ioprocid = NULL; status = AudioDeviceCreateIOProcID(core->outputDeviceID, audioDeviceIOProc, hw, &core->ioprocid); if (status != kAudioHardwareNoError || core->ioprocid == NULL) { coreaudio_logerr2 (status, typ, "Could not set IOProc\n"); core->outputDeviceID = kAudioDeviceUnknown; return -1; } /* start Playback */ if (!isPlaying(core->outputDeviceID)) { status = AudioDeviceStart(core->outputDeviceID, core->ioprocid); if (status != kAudioHardwareNoError) { coreaudio_logerr2 (status, typ, "Could not start playback\n"); AudioDeviceDestroyIOProcID(core->outputDeviceID, core->ioprocid); core->outputDeviceID = kAudioDeviceUnknown; return -1; } } return 0; } static void coreaudio_fini_out (HWVoiceOut *hw) { OSStatus status; int err; coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; if (!audio_is_cleaning_up()) { /* stop playback */ if (isPlaying(core->outputDeviceID)) { status = AudioDeviceStop(core->outputDeviceID, core->ioprocid); if (status != kAudioHardwareNoError) { coreaudio_logerr (status, "Could not stop playback\n"); } } /* remove callback */ status = AudioDeviceDestroyIOProcID(core->outputDeviceID, core->ioprocid); if (status != kAudioHardwareNoError) { coreaudio_logerr (status, "Could not remove IOProc\n"); } } core->outputDeviceID = kAudioDeviceUnknown; /* destroy mutex */ err = pthread_mutex_destroy(&core->mutex); if (err) { dolog("Could not destroy mutex\nReason: %s\n", strerror (err)); } } static void coreaudio_enable_out(HWVoiceOut *hw, bool enable) { OSStatus status; coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; if (enable) { /* start playback */ if (!isPlaying(core->outputDeviceID)) { status = AudioDeviceStart(core->outputDeviceID, core->ioprocid); if (status != kAudioHardwareNoError) { coreaudio_logerr (status, "Could not resume playback\n"); } } } else { /* stop playback */ if (!audio_is_cleaning_up()) { if (isPlaying(core->outputDeviceID)) { status = AudioDeviceStop(core->outputDeviceID, core->ioprocid); if (status != kAudioHardwareNoError) { coreaudio_logerr (status, "Could not pause playback\n"); } } } } } 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_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);