macOS versions older than 12.0 are no longer supported. docs/about/build-platforms.rst says: > Support for the previous major version will be dropped 2 years after > the new major version is released or when the vendor itself drops > support, whichever comes first. macOS 12.0 was released 2021: https://www.apple.com/newsroom/2021/10/macos-monterey-is-now-available/ Signed-off-by: Akihiko Odaki <akihiko.odaki@daynix.com> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-ID: <20240629-macos-v1-2-6e70a6b700a0@daynix.com> Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org>
		
			
				
	
	
		
			682 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Objective-C
		
	
	
	
	
	
			
		
		
	
	
			682 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Objective-C
		
	
	
	
	
	
/*
 | 
						|
 * 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 <CoreAudio/CoreAudio.h>
 | 
						|
#include <pthread.h>            /* 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;
 | 
						|
 | 
						|
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 G_GNUC_PRINTF (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 G_GNUC_PRINTF (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 BQL.
 | 
						|
 * 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 BQL. */
 | 
						|
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;
 | 
						|
 | 
						|
    bql_lock();
 | 
						|
 | 
						|
    if (core->outputDeviceID) {
 | 
						|
        fini_out_device(core);
 | 
						|
    }
 | 
						|
 | 
						|
    if (!init_out_device(core)) {
 | 
						|
        update_device_playback_state(core);
 | 
						|
    }
 | 
						|
 | 
						|
    bql_unlock();
 | 
						|
    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, Error **errp)
 | 
						|
{
 | 
						|
    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,
 | 
						|
    .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);
 |