|  | /* | 
|  | * 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 <CoreAudio/CoreAudio.h> | 
|  | #include <string.h>             /* strerror */ | 
|  | #include <pthread.h>            /* pthread_X */ | 
|  |  | 
|  | #include "qemu-common.h" | 
|  | #include "audio.h" | 
|  |  | 
|  | #define AUDIO_CAP "coreaudio" | 
|  | #include "audio_int.h" | 
|  |  | 
|  | struct { | 
|  | int buffer_frames; | 
|  | int nbuffers; | 
|  | int isAtexit; | 
|  | } conf = { | 
|  | .buffer_frames = 512, | 
|  | .nbuffers = 4, | 
|  | .isAtexit = 0 | 
|  | }; | 
|  |  | 
|  | typedef struct coreaudioVoiceOut { | 
|  | HWVoiceOut hw; | 
|  | pthread_mutex_t mutex; | 
|  | int isAtexit; | 
|  | AudioDeviceID outputDeviceID; | 
|  | UInt32 audioDevicePropertyBufferFrameSize; | 
|  | AudioStreamBasicDescription outputStreamBasicDescription; | 
|  | int live; | 
|  | int decr; | 
|  | int rpos; | 
|  | } coreaudioVoiceOut; | 
|  |  | 
|  | 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; | 
|  | UInt32 propertySize = sizeof(outputDeviceID); | 
|  | status = AudioDeviceGetProperty( | 
|  | outputDeviceID, 0, 0, | 
|  | kAudioDevicePropertyDeviceIsRunning, &propertySize, &result); | 
|  | if (status != kAudioHardwareNoError) { | 
|  | coreaudio_logerr(status, | 
|  | "Could not determine whether Device is playing\n"); | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | static void coreaudio_atexit (void) | 
|  | { | 
|  | conf.isAtexit = 1; | 
|  | } | 
|  |  | 
|  | 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; | 
|  | } | 
|  |  | 
|  | static int coreaudio_run_out (HWVoiceOut *hw, int live) | 
|  | { | 
|  | int decr; | 
|  | coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; | 
|  |  | 
|  | if (coreaudio_lock (core, "coreaudio_run_out")) { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | if (core->decr > live) { | 
|  | ldebug ("core->decr %d live %d core->live %d\n", | 
|  | core->decr, | 
|  | live, | 
|  | core->live); | 
|  | } | 
|  |  | 
|  | decr = audio_MIN (core->decr, live); | 
|  | core->decr -= decr; | 
|  |  | 
|  | core->live = live - decr; | 
|  | hw->rpos = core->rpos; | 
|  |  | 
|  | coreaudio_unlock (core, "coreaudio_run_out"); | 
|  | return decr; | 
|  | } | 
|  |  | 
|  | /* 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 frame, frameCount; | 
|  | float *out = outOutputData->mBuffers[0].mData; | 
|  | HWVoiceOut *hw = hwptr; | 
|  | coreaudioVoiceOut *core = (coreaudioVoiceOut *) hwptr; | 
|  | int rpos, live; | 
|  | struct st_sample *src; | 
|  | #ifndef FLOAT_MIXENG | 
|  | #ifdef RECIPROCAL | 
|  | const float scale = 1.f / UINT_MAX; | 
|  | #else | 
|  | const float scale = UINT_MAX; | 
|  | #endif | 
|  | #endif | 
|  |  | 
|  | if (coreaudio_lock (core, "audioDeviceIOProc")) { | 
|  | inInputTime = 0; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | frameCount = core->audioDevicePropertyBufferFrameSize; | 
|  | live = core->live; | 
|  |  | 
|  | /* if there are not enough samples, set signal and return */ | 
|  | if (live < frameCount) { | 
|  | inInputTime = 0; | 
|  | coreaudio_unlock (core, "audioDeviceIOProc(empty)"); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | rpos = core->rpos; | 
|  | src = hw->mix_buf + rpos; | 
|  |  | 
|  | /* fill buffer */ | 
|  | for (frame = 0; frame < frameCount; frame++) { | 
|  | #ifdef FLOAT_MIXENG | 
|  | *out++ = src[frame].l; /* left channel */ | 
|  | *out++ = src[frame].r; /* right channel */ | 
|  | #else | 
|  | #ifdef RECIPROCAL | 
|  | *out++ = src[frame].l * scale; /* left channel */ | 
|  | *out++ = src[frame].r * scale; /* right channel */ | 
|  | #else | 
|  | *out++ = src[frame].l / scale; /* left channel */ | 
|  | *out++ = src[frame].r / scale; /* right channel */ | 
|  | #endif | 
|  | #endif | 
|  | } | 
|  |  | 
|  | rpos = (rpos + frameCount) % hw->samples; | 
|  | core->decr += frameCount; | 
|  | core->rpos = rpos; | 
|  |  | 
|  | coreaudio_unlock (core, "audioDeviceIOProc"); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int coreaudio_write (SWVoiceOut *sw, void *buf, int len) | 
|  | { | 
|  | return audio_pcm_sw_write (sw, buf, len); | 
|  | } | 
|  |  | 
|  | static int coreaudio_init_out (HWVoiceOut *hw, struct audsettings *as) | 
|  | { | 
|  | OSStatus status; | 
|  | coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; | 
|  | UInt32 propertySize; | 
|  | int err; | 
|  | const char *typ = "playback"; | 
|  | AudioValueRange frameRange; | 
|  |  | 
|  | /* create mutex */ | 
|  | err = pthread_mutex_init(&core->mutex, NULL); | 
|  | if (err) { | 
|  | dolog("Could not create mutex\nReason: %s\n", strerror (err)); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | audio_pcm_init_info (&hw->info, as); | 
|  |  | 
|  | /* open default output device */ | 
|  | propertySize = sizeof(core->outputDeviceID); | 
|  | status = AudioHardwareGetProperty( | 
|  | kAudioHardwarePropertyDefaultOutputDevice, | 
|  | &propertySize, | 
|  | &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 */ | 
|  | propertySize = sizeof(frameRange); | 
|  | status = AudioDeviceGetProperty( | 
|  | core->outputDeviceID, | 
|  | 0, | 
|  | 0, | 
|  | kAudioDevicePropertyBufferFrameSizeRange, | 
|  | &propertySize, | 
|  | &frameRange); | 
|  | if (status != kAudioHardwareNoError) { | 
|  | coreaudio_logerr2 (status, typ, | 
|  | "Could not get device buffer frame range\n"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (frameRange.mMinimum > conf.buffer_frames) { | 
|  | core->audioDevicePropertyBufferFrameSize = (UInt32) frameRange.mMinimum; | 
|  | dolog ("warning: Upsizing Buffer Frames to %f\n", frameRange.mMinimum); | 
|  | } | 
|  | else if (frameRange.mMaximum < conf.buffer_frames) { | 
|  | core->audioDevicePropertyBufferFrameSize = (UInt32) frameRange.mMaximum; | 
|  | dolog ("warning: Downsizing Buffer Frames to %f\n", frameRange.mMaximum); | 
|  | } | 
|  | else { | 
|  | core->audioDevicePropertyBufferFrameSize = conf.buffer_frames; | 
|  | } | 
|  |  | 
|  | /* set Buffer Frame Size */ | 
|  | propertySize = sizeof(core->audioDevicePropertyBufferFrameSize); | 
|  | status = AudioDeviceSetProperty( | 
|  | core->outputDeviceID, | 
|  | NULL, | 
|  | 0, | 
|  | false, | 
|  | kAudioDevicePropertyBufferFrameSize, | 
|  | propertySize, | 
|  | &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 */ | 
|  | propertySize = sizeof(core->audioDevicePropertyBufferFrameSize); | 
|  | status = AudioDeviceGetProperty( | 
|  | core->outputDeviceID, | 
|  | 0, | 
|  | false, | 
|  | kAudioDevicePropertyBufferFrameSize, | 
|  | &propertySize, | 
|  | &core->audioDevicePropertyBufferFrameSize); | 
|  | if (status != kAudioHardwareNoError) { | 
|  | coreaudio_logerr2 (status, typ, | 
|  | "Could not get device buffer frame size\n"); | 
|  | return -1; | 
|  | } | 
|  | hw->samples = conf.nbuffers * core->audioDevicePropertyBufferFrameSize; | 
|  |  | 
|  | /* get StreamFormat */ | 
|  | propertySize = sizeof(core->outputStreamBasicDescription); | 
|  | status = AudioDeviceGetProperty( | 
|  | core->outputDeviceID, | 
|  | 0, | 
|  | false, | 
|  | kAudioDevicePropertyStreamFormat, | 
|  | &propertySize, | 
|  | &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; | 
|  | propertySize = sizeof(core->outputStreamBasicDescription); | 
|  | status = AudioDeviceSetProperty( | 
|  | core->outputDeviceID, | 
|  | 0, | 
|  | 0, | 
|  | 0, | 
|  | kAudioDevicePropertyStreamFormat, | 
|  | propertySize, | 
|  | &core->outputStreamBasicDescription); | 
|  | if (status != kAudioHardwareNoError) { | 
|  | coreaudio_logerr2 (status, typ, "Could not set samplerate %d\n", | 
|  | as->freq); | 
|  | core->outputDeviceID = kAudioDeviceUnknown; | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | /* set Callback */ | 
|  | status = AudioDeviceAddIOProc(core->outputDeviceID, audioDeviceIOProc, hw); | 
|  | if (status != kAudioHardwareNoError) { | 
|  | coreaudio_logerr2 (status, typ, "Could not set IOProc\n"); | 
|  | core->outputDeviceID = kAudioDeviceUnknown; | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | /* start Playback */ | 
|  | if (!isPlaying(core->outputDeviceID)) { | 
|  | status = AudioDeviceStart(core->outputDeviceID, audioDeviceIOProc); | 
|  | if (status != kAudioHardwareNoError) { | 
|  | coreaudio_logerr2 (status, typ, "Could not start playback\n"); | 
|  | AudioDeviceRemoveIOProc(core->outputDeviceID, audioDeviceIOProc); | 
|  | core->outputDeviceID = kAudioDeviceUnknown; | 
|  | return -1; | 
|  | } | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void coreaudio_fini_out (HWVoiceOut *hw) | 
|  | { | 
|  | OSStatus status; | 
|  | int err; | 
|  | coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; | 
|  |  | 
|  | if (!conf.isAtexit) { | 
|  | /* stop playback */ | 
|  | if (isPlaying(core->outputDeviceID)) { | 
|  | status = AudioDeviceStop(core->outputDeviceID, audioDeviceIOProc); | 
|  | if (status != kAudioHardwareNoError) { | 
|  | coreaudio_logerr (status, "Could not stop playback\n"); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* remove callback */ | 
|  | status = AudioDeviceRemoveIOProc(core->outputDeviceID, | 
|  | audioDeviceIOProc); | 
|  | 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 int coreaudio_ctl_out (HWVoiceOut *hw, int cmd, ...) | 
|  | { | 
|  | OSStatus status; | 
|  | coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; | 
|  |  | 
|  | switch (cmd) { | 
|  | case VOICE_ENABLE: | 
|  | /* start playback */ | 
|  | if (!isPlaying(core->outputDeviceID)) { | 
|  | status = AudioDeviceStart(core->outputDeviceID, audioDeviceIOProc); | 
|  | if (status != kAudioHardwareNoError) { | 
|  | coreaudio_logerr (status, "Could not resume playback\n"); | 
|  | } | 
|  | } | 
|  | break; | 
|  |  | 
|  | case VOICE_DISABLE: | 
|  | /* stop playback */ | 
|  | if (!conf.isAtexit) { | 
|  | if (isPlaying(core->outputDeviceID)) { | 
|  | status = AudioDeviceStop(core->outputDeviceID, audioDeviceIOProc); | 
|  | if (status != kAudioHardwareNoError) { | 
|  | coreaudio_logerr (status, "Could not pause playback\n"); | 
|  | } | 
|  | } | 
|  | } | 
|  | break; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void *coreaudio_audio_init (void) | 
|  | { | 
|  | atexit(coreaudio_atexit); | 
|  | return &coreaudio_audio_init; | 
|  | } | 
|  |  | 
|  | static void coreaudio_audio_fini (void *opaque) | 
|  | { | 
|  | (void) opaque; | 
|  | } | 
|  |  | 
|  | static struct audio_option coreaudio_options[] = { | 
|  | { | 
|  | .name  = "BUFFER_SIZE", | 
|  | .tag   = AUD_OPT_INT, | 
|  | .valp  = &conf.buffer_frames, | 
|  | .descr = "Size of the buffer in frames" | 
|  | }, | 
|  | { | 
|  | .name  = "BUFFER_COUNT", | 
|  | .tag   = AUD_OPT_INT, | 
|  | .valp  = &conf.nbuffers, | 
|  | .descr = "Number of buffers" | 
|  | }, | 
|  | { /* End of list */ } | 
|  | }; | 
|  |  | 
|  | static struct audio_pcm_ops coreaudio_pcm_ops = { | 
|  | .init_out = coreaudio_init_out, | 
|  | .fini_out = coreaudio_fini_out, | 
|  | .run_out  = coreaudio_run_out, | 
|  | .write    = coreaudio_write, | 
|  | .ctl_out  = coreaudio_ctl_out | 
|  | }; | 
|  |  | 
|  | struct audio_driver coreaudio_audio_driver = { | 
|  | .name           = "coreaudio", | 
|  | .descr          = "CoreAudio http://developer.apple.com/audio/coreaudio.html", | 
|  | .options        = coreaudio_options, | 
|  | .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 | 
|  | }; |