550 lines
15 KiB
C
550 lines
15 KiB
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 <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
|
|
};
|