blob: f512f4731ab343da1b9e65ec6fd07c6c1196d7bd [file] [log] [blame]
/*
* QEMU OS X CoreAudio audio driver
*
* Copyright (c) 2008 The Android Open Source Project
* 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"
#if 0
# define D(...) fprintf(stderr, __VA_ARGS__)
#else
# define D(...) ((void)0)
#endif
struct {
int out_buffer_frames;
int out_nbuffers;
int in_buffer_frames;
int in_nbuffers;
int isAtexit;
} conf = {
.out_buffer_frames = 512,
.out_nbuffers = 4,
.in_buffer_frames = 512,
.in_nbuffers = 4,
.isAtexit = 0
};
/***************************************************************************************/
/***************************************************************************************/
/*** ***/
/*** U T I L I T Y R O U T I N E S ***/
/*** ***/
/***************************************************************************************/
/***************************************************************************************/
static void coreaudio_logstatus (OSStatus status)
{
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 %d\n", 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);
}
/***************************************************************************************/
/***************************************************************************************/
/*** ***/
/*** S H A R E D I N / O U T V O I C E ***/
/*** ***/
/***************************************************************************************/
/***************************************************************************************/
typedef struct coreAudioVoice {
pthread_mutex_t mutex;
AudioDeviceID deviceID;
Boolean isInput;
UInt32 bufferFrameSize;
AudioStreamBasicDescription streamBasicDescription;
AudioDeviceIOProc ioproc;
int live;
int decr;
int pos;
} coreaudioVoice;
static inline UInt32
coreaudio_voice_isPlaying (coreaudioVoice *core)
{
OSStatus status;
UInt32 result = 0;
UInt32 propertySize = sizeof(core->deviceID);
status = AudioDeviceGetProperty(
core->deviceID, 0, core->isInput,
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_voice_lock (coreaudioVoice *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_voice_unlock (coreaudioVoice *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_voice_ctl (coreaudioVoice* core, int cmd)
{
OSStatus status;
switch (cmd) {
case VOICE_ENABLE:
/* start playback */
D("%s: %s started\n", __FUNCTION__, core->isInput ? "input" : "output");
if (!coreaudio_voice_isPlaying(core)) {
status = AudioDeviceStart(core->deviceID, core->ioproc);
if (status != kAudioHardwareNoError) {
coreaudio_logerr (status, "Could not resume playback\n");
}
}
break;
case VOICE_DISABLE:
/* stop playback */
D("%s: %s stopped\n", __FUNCTION__, core->isInput ? "input" : "output");
if (!conf.isAtexit) {
if (coreaudio_voice_isPlaying(core)) {
status = AudioDeviceStop(core->deviceID, core->ioproc);
if (status != kAudioHardwareNoError) {
coreaudio_logerr (status, "Could not pause playback\n");
}
}
}
break;
}
return 0;
}
static void
coreaudio_voice_fini (coreaudioVoice* core)
{
OSStatus status;
int err;
if (!conf.isAtexit) {
/* stop playback */
coreaudio_voice_ctl(core, VOICE_DISABLE);
/* remove callback */
status = AudioDeviceRemoveIOProc(core->deviceID, core->ioproc);
if (status != kAudioHardwareNoError) {
coreaudio_logerr (status, "Could not remove IOProc\n");
}
}
core->deviceID = kAudioDeviceUnknown;
/* destroy mutex */
err = pthread_mutex_destroy(&core->mutex);
if (err) {
dolog("Could not destroy mutex\nReason: %s\n", strerror (err));
}
}
static int
coreaudio_voice_init (coreaudioVoice* core,
struct audsettings* as,
int frameSize,
AudioDeviceIOProc ioproc,
void* hw,
int input)
{
OSStatus status;
UInt32 propertySize;
int err;
int bits = 8;
AudioValueRange frameRange;
const char* typ = input ? "input" : "playback";
core->isInput = input ? true : false;
/* create mutex */
err = pthread_mutex_init(&core->mutex, NULL);
if (err) {
dolog("Could not create mutex\nReason: %s\n", strerror (err));
return -1;
}
if (as->fmt == AUD_FMT_S16 || as->fmt == AUD_FMT_U16) {
bits = 16;
}
// TODO: audio_pcm_init_info (&hw->info, as);
/* open default output device */
/* note: we use DefaultSystemOutputDevice because DefaultOutputDevice seems to
* always link to the internal speakers, and not the ones selected through system properties
* go figure...
*/
propertySize = sizeof(core->deviceID);
status = AudioHardwareGetProperty(
input ? kAudioHardwarePropertyDefaultInputDevice :
kAudioHardwarePropertyDefaultSystemOutputDevice,
&propertySize,
&core->deviceID);
if (status != kAudioHardwareNoError) {
coreaudio_logerr2 (status, typ,
"Could not get default %s device\n", typ);
return -1;
}
if (core->deviceID == 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->deviceID,
0,
core->isInput,
kAudioDevicePropertyBufferFrameSizeRange,
&propertySize,
&frameRange);
if (status != kAudioHardwareNoError) {
coreaudio_logerr2 (status, typ,
"Could not get device buffer frame range\n");
return -1;
}
if (frameRange.mMinimum > frameSize) {
core->bufferFrameSize = (UInt32) frameRange.mMinimum;
dolog ("warning: Upsizing Output Buffer Frames to %f\n", frameRange.mMinimum);
}
else if (frameRange.mMaximum < frameSize) {
core->bufferFrameSize = (UInt32) frameRange.mMaximum;
dolog ("warning: Downsizing Output Buffer Frames to %f\n", frameRange.mMaximum);
}
else {
core->bufferFrameSize = frameSize;
}
/* set Buffer Frame Size */
propertySize = sizeof(core->bufferFrameSize);
status = AudioDeviceSetProperty(
core->deviceID,
NULL,
0,
core->isInput,
kAudioDevicePropertyBufferFrameSize,
propertySize,
&core->bufferFrameSize);
if (status != kAudioHardwareNoError) {
coreaudio_logerr2 (status, typ,
"Could not set device buffer frame size %d\n",
(unsigned)core->bufferFrameSize);
return -1;
}
/* get Buffer Frame Size */
propertySize = sizeof(core->bufferFrameSize);
status = AudioDeviceGetProperty(
core->deviceID,
0,
core->isInput,
kAudioDevicePropertyBufferFrameSize,
&propertySize,
&core->bufferFrameSize);
if (status != kAudioHardwareNoError) {
coreaudio_logerr2 (status, typ,
"Could not get device buffer frame size\n");
return -1;
}
// TODO: hw->samples = *pNBuffers * core->bufferFrameSize;
/* get StreamFormat */
propertySize = sizeof(core->streamBasicDescription);
status = AudioDeviceGetProperty(
core->deviceID,
0,
core->isInput,
kAudioDevicePropertyStreamFormat,
&propertySize,
&core->streamBasicDescription);
if (status != kAudioHardwareNoError) {
coreaudio_logerr2 (status, typ,
"Could not get Device Stream properties\n");
core->deviceID = kAudioDeviceUnknown;
return -1;
}
/* set Samplerate */
core->streamBasicDescription.mSampleRate = (Float64) as->freq;
propertySize = sizeof(core->streamBasicDescription);
status = AudioDeviceSetProperty(
core->deviceID,
0,
0,
core->isInput,
kAudioDevicePropertyStreamFormat,
propertySize,
&core->streamBasicDescription);
if (status != kAudioHardwareNoError) {
coreaudio_logerr2 (status, typ, "Could not set samplerate %d\n",
as->freq);
core->deviceID = kAudioDeviceUnknown;
return -1;
}
/* set Callback */
core->ioproc = ioproc;
status = AudioDeviceAddIOProc(core->deviceID, ioproc, hw);
if (status != kAudioHardwareNoError) {
coreaudio_logerr2 (status, typ, "Could not set IOProc\n");
core->deviceID = kAudioDeviceUnknown;
return -1;
}
/* start Playback */
if (!input && !coreaudio_voice_isPlaying(core)) {
status = AudioDeviceStart(core->deviceID, core->ioproc);
if (status != kAudioHardwareNoError) {
coreaudio_logerr2 (status, typ, "Could not start playback\n");
AudioDeviceRemoveIOProc(core->deviceID, core->ioproc);
core->deviceID = kAudioDeviceUnknown;
return -1;
}
}
return 0;
}
/***************************************************************************************/
/***************************************************************************************/
/*** ***/
/*** O U T P U T V O I C E ***/
/*** ***/
/***************************************************************************************/
/***************************************************************************************/
typedef struct coreaudioVoiceOut {
HWVoiceOut hw;
coreaudioVoice core[1];
} coreaudioVoiceOut;
#define CORE_OUT(hw) ((coreaudioVoiceOut*)(hw))->core
static int coreaudio_run_out (HWVoiceOut *hw, int live)
{
int decr;
coreaudioVoice *core = CORE_OUT(hw);
if (coreaudio_voice_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->pos;
coreaudio_voice_unlock (core, "coreaudio_run_out");
return decr;
}
/* callback to feed audiooutput buffer */
static OSStatus audioOutDeviceIOProc(
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;
coreaudioVoice *core = CORE_OUT(hw);
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_voice_lock (core, "audioDeviceIOProc")) {
inInputTime = 0;
return 0;
}
frameCount = core->bufferFrameSize;
live = core->live;
/* if there are not enough samples, set signal and return */
if (live < (int)frameCount) {
inInputTime = 0;
coreaudio_voice_unlock (core, "audioDeviceIOProc(empty)");
return 0;
}
rpos = core->pos;
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->pos = rpos;
coreaudio_voice_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)
{
coreaudioVoice* core = CORE_OUT(hw);
int err;
audio_pcm_init_info (&hw->info, as);
err = coreaudio_voice_init (core, as, conf.out_buffer_frames, audioOutDeviceIOProc, hw, 0);
if (err < 0)
return err;
hw->samples = core->bufferFrameSize * conf.out_nbuffers;
return 0;
}
static void coreaudio_fini_out (HWVoiceOut *hw)
{
coreaudioVoice *core = CORE_OUT(hw);
coreaudio_voice_fini (core);
}
static int
coreaudio_ctl_out (HWVoiceOut *hw, int cmd, ...)
{
coreaudioVoice *core = CORE_OUT(hw);
return coreaudio_voice_ctl (core, cmd);
}
/***************************************************************************************/
/***************************************************************************************/
/*** ***/
/*** I N P U T V O I C E ***/
/*** ***/
/***************************************************************************************/
/***************************************************************************************/
typedef struct coreaudioVoiceIn {
HWVoiceIn hw;
coreaudioVoice core[1];
} coreaudioVoiceIn;
#define CORE_IN(hw) ((coreaudioVoiceIn *) (hw))->core
static int coreaudio_run_in (HWVoiceIn *hw)
{
int decr;
coreaudioVoice *core = CORE_IN(hw);
if (coreaudio_voice_lock (core, "coreaudio_run_in")) {
return 0;
}
D("%s: core.decr=%d core.pos=%d\n", __FUNCTION__, core->decr, core->pos);
decr = core->decr;
core->decr -= decr;
hw->wpos = core->pos;
coreaudio_voice_unlock (core, "coreaudio_run_in");
return decr;
}
/* callback to feed audiooutput buffer */
static OSStatus audioInDeviceIOProc(
AudioDeviceID inDevice,
const AudioTimeStamp* inNow,
const AudioBufferList* inInputData,
const AudioTimeStamp* inInputTime,
AudioBufferList* outOutputData,
const AudioTimeStamp* inOutputTime,
void* hwptr)
{
UInt32 frame, frameCount;
float *in = inInputData->mBuffers[0].mData;
HWVoiceIn *hw = hwptr;
coreaudioVoice *core = CORE_IN(hw);
int wpos, avail;
struct st_sample *dst;
#ifndef FLOAT_MIXENG
#ifdef RECIPROCAL
const float scale = 1.f / UINT_MAX;
#else
const float scale = UINT_MAX;
#endif
#endif
if (coreaudio_voice_lock (core, "audioDeviceIOProc")) {
inInputTime = 0;
return 0;
}
frameCount = core->bufferFrameSize;
avail = hw->samples - hw->total_samples_captured - core->decr;
D("%s: enter avail=%d core.decr=%d core.pos=%d hw.samples=%d hw.total_samples_captured=%d frameCount=%d\n",
__FUNCTION__, avail, core->decr, core->pos, hw->samples, hw->total_samples_captured, (int)frameCount);
/* if there are not enough samples, set signal and return */
if (avail < (int)frameCount) {
inInputTime = 0;
coreaudio_voice_unlock (core, "audioDeviceIOProc(empty)");
return 0;
}
wpos = core->pos;
dst = hw->conv_buf + wpos;
/* fill buffer */
for (frame = 0; frame < frameCount; frame++) {
#ifdef FLOAT_MIXENG
dst[frame].l = *in++; /* left channel */
dst[frame].r = *in++; /* right channel */
#else
#ifdef RECIPROCAL
dst[frame].l = *in++ * scale; /* left channel */
dst[frame].r = *in++ * scale; /* right channel */
#else
dst[frame].l = *in++ / scale; /* left channel */
dst[frame].r = *in++ / scale; /* right channel */
#endif
#endif
}
wpos = (wpos + frameCount) % hw->samples;
core->decr += frameCount;
core->pos = wpos;
D("exit: core.decr=%d core.pos=%d\n", core->decr, core->pos);
coreaudio_voice_unlock (core, "audioDeviceIOProc");
return 0;
}
static int
coreaudio_read (SWVoiceIn *sw, void *buf, int len)
{
int result = audio_pcm_sw_read (sw, buf, len);
D("%s: audio_pcm_sw_read(%d) returned %d\n", __FUNCTION__, len, result);
return result;
}
static int
coreaudio_init_in (HWVoiceIn *hw, struct audsettings *as)
{
coreaudioVoice* core = CORE_IN(hw);
int err;
audio_pcm_init_info (&hw->info, as);
err = coreaudio_voice_init (core, as, conf.in_buffer_frames, audioInDeviceIOProc, hw, 1);
if (err < 0) {
return err;
}
hw->samples = core->bufferFrameSize * conf.in_nbuffers;
return 0;
}
static void
coreaudio_fini_in (HWVoiceIn *hw)
{
coreaudioVoice* core = CORE_IN(hw);
coreaudio_voice_fini(core);
}
static int
coreaudio_ctl_in (HWVoiceIn *hw, int cmd, ...)
{
coreaudioVoice* core = CORE_IN(hw);
return coreaudio_voice_ctl(core, cmd);
}
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 = "OUT_BUFFER_SIZE",
.tag = AUD_OPT_INT,
.valp = &conf.out_buffer_frames,
.descr = "Size of the output buffer in frames"
},
{
.name = "OUT_BUFFER_COUNT",
.tag = AUD_OPT_INT,
.valp = &conf.out_nbuffers,
.descr = "Number of output buffers"
},
{
.name = "IN_BUFFER_SIZE",
.tag = AUD_OPT_INT,
.valp = &conf.in_buffer_frames,
.descr = "Size of the input buffer in frames"
},
{
.name = "IN_BUFFER_COUNT",
.tag = AUD_OPT_INT,
.valp = &conf.in_nbuffers,
.descr = "Number of input 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,
.init_in = coreaudio_init_in,
.fini_in = coreaudio_fini_in,
.run_in = coreaudio_run_in,
.read = coreaudio_read,
.ctl_in = coreaudio_ctl_in
};
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 = 1,
.voice_size_out = sizeof (coreaudioVoiceOut),
.voice_size_in = sizeof (coreaudioVoiceIn),
};