Add qemu 2.4.0
[kvmfornfv.git] / qemu / audio / coreaudio.c
diff --git a/qemu/audio/coreaudio.c b/qemu/audio/coreaudio.c
new file mode 100644 (file)
index 0000000..6dfd63e
--- /dev/null
@@ -0,0 +1,555 @@
+/*
+ * 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"
+
+static int isAtexit;
+
+typedef struct {
+    int buffer_frames;
+    int nbuffers;
+} CoreaudioConf;
+
+typedef struct coreaudioVoiceOut {
+    HWVoiceOut hw;
+    pthread_mutex_t mutex;
+    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)
+{
+    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,
+                              void *drv_opaque)
+{
+    OSStatus status;
+    coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw;
+    UInt32 propertySize;
+    int err;
+    const char *typ = "playback";
+    AudioValueRange frameRange;
+    CoreaudioConf *conf = drv_opaque;
+
+    /* 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 (!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 (!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 CoreaudioConf glob_conf = {
+    .buffer_frames = 512,
+    .nbuffers = 4,
+};
+
+static void *coreaudio_audio_init (void)
+{
+    CoreaudioConf *conf = g_malloc(sizeof(CoreaudioConf));
+    *conf = glob_conf;
+
+    atexit(coreaudio_atexit);
+    return conf;
+}
+
+static void coreaudio_audio_fini (void *opaque)
+{
+    g_free(opaque);
+}
+
+static struct audio_option coreaudio_options[] = {
+    {
+        .name  = "BUFFER_SIZE",
+        .tag   = AUD_OPT_INT,
+        .valp  = &glob_conf.buffer_frames,
+        .descr = "Size of the buffer in frames"
+    },
+    {
+        .name  = "BUFFER_COUNT",
+        .tag   = AUD_OPT_INT,
+        .valp  = &glob_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
+};