123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687 |
- /*
- * 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;
- AudioDeviceID deviceID;
- AudioValueRange frameRange;
- UInt32 audioDevicePropertyBufferFrameSize;
- AudioDeviceIOProcID ioprocid;
- 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(&deviceID);
- if (status != kAudioHardwareNoError) {
- coreaudio_playback_logerr (status,
- "Could not get default output Device\n");
- return status;
- }
- if (deviceID == kAudioDeviceUnknown) {
- dolog ("Could not initialize playback - Unknown Audiodevice\n");
- return status;
- }
- /* get minimum and maximum buffer frame sizes */
- status = coreaudio_get_framesizerange(deviceID, &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) {
- audioDevicePropertyBufferFrameSize = (UInt32) frameRange.mMinimum;
- dolog ("warning: Upsizing Buffer Frames to %f\n", frameRange.mMinimum);
- } else if (frameRange.mMaximum < core->frameSizeSetting) {
- audioDevicePropertyBufferFrameSize = (UInt32) frameRange.mMaximum;
- dolog ("warning: Downsizing Buffer Frames to %f\n", frameRange.mMaximum);
- } else {
- audioDevicePropertyBufferFrameSize = core->frameSizeSetting;
- }
- /* set Buffer Frame Size */
- status = coreaudio_set_framesize(deviceID,
- &audioDevicePropertyBufferFrameSize);
- if (status == kAudioHardwareBadObjectError) {
- return 0;
- }
- if (status != kAudioHardwareNoError) {
- coreaudio_playback_logerr (status,
- "Could not set device buffer frame size %" PRIu32 "\n",
- (uint32_t)audioDevicePropertyBufferFrameSize);
- return status;
- }
- /* get Buffer Frame Size */
- status = coreaudio_get_framesize(deviceID,
- &audioDevicePropertyBufferFrameSize);
- if (status == kAudioHardwareBadObjectError) {
- return 0;
- }
- if (status != kAudioHardwareNoError) {
- coreaudio_playback_logerr (status,
- "Could not get device buffer frame size\n");
- return status;
- }
- /* set Samplerate */
- status = coreaudio_set_streamformat(deviceID, &streamBasicDescription);
- if (status == kAudioHardwareBadObjectError) {
- return 0;
- }
- if (status != kAudioHardwareNoError) {
- coreaudio_playback_logerr (status,
- "Could not set samplerate %lf\n",
- streamBasicDescription.mSampleRate);
- 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.
- */
- ioprocid = NULL;
- status = AudioDeviceCreateIOProcID(deviceID,
- audioDeviceIOProc,
- &core->hw,
- &ioprocid);
- if (status == kAudioHardwareBadDeviceError) {
- return 0;
- }
- if (status != kAudioHardwareNoError || ioprocid == NULL) {
- coreaudio_playback_logerr (status, "Could not set IOProc\n");
- return status;
- }
- core->outputDeviceID = deviceID;
- core->audioDevicePropertyBufferFrameSize = audioDevicePropertyBufferFrameSize;
- core->hw.samples = core->bufferCount * core->audioDevicePropertyBufferFrameSize;
- audio_generic_initialize_buffer_out(&core->hw);
- core->ioprocid = ioprocid;
- 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);
- }
- init_out_device(core);
- if (core->outputDeviceID) {
- 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);
|