/* RetroArch - A frontend for libretro. * Copyright (C) 2010-2014 - Hans-Kristian Arntzen * Copyright (C) 2011-2014 - Chris Moeller * * RetroArch is free software: you can redistribute it and/or modify it under the terms * of the GNU General Public License as published by the Free Software Found- * ation, either version 3 of the License, or (at your option) any later version. * * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with RetroArch. * If not, see . */ #include #if TARGET_OS_IPHONE #include #else #include #endif #include #include #include #include #include #include #include #include #include #include "../audio_driver.h" #include "../../verbosity.h" typedef struct coreaudio { slock_t *lock; scond_t *cond; #if !HAS_MACOSX_10_12 ComponentInstance dev; #else AudioComponentInstance dev; #endif fifo_buffer_t *buffer; size_t buffer_size; bool dev_alive; bool is_paused; bool nonblock; } coreaudio_t; static void coreaudio_free(void *data) { coreaudio_t *dev = (coreaudio_t*)data; if (!dev) return; if (dev->dev_alive) { AudioOutputUnitStop(dev->dev); #if !HAS_MACOSX_10_12 CloseComponent(dev->dev); #else AudioComponentInstanceDispose(dev->dev); #endif } if (dev->buffer) fifo_free(dev->buffer); slock_free(dev->lock); scond_free(dev->cond); free(dev); } static OSStatus audio_write_cb(void *userdata, AudioUnitRenderActionFlags *action_flags, const AudioTimeStamp *time_stamp, UInt32 bus_number, UInt32 number_frames, AudioBufferList *io_data) { unsigned write_avail; void *outbuf = NULL; coreaudio_t *dev = (coreaudio_t*)userdata; (void)time_stamp; (void)bus_number; (void)number_frames; if (!io_data || io_data->mNumberBuffers != 1) return noErr; write_avail = io_data->mBuffers[0].mDataByteSize; outbuf = io_data->mBuffers[0].mData; slock_lock(dev->lock); if (FIFO_READ_AVAIL(dev->buffer) < write_avail) { *action_flags = kAudioUnitRenderAction_OutputIsSilence; /* Seems to be needed. */ memset(outbuf, 0, write_avail); slock_unlock(dev->lock); /* Technically possible to deadlock without. */ scond_signal(dev->cond); return noErr; } fifo_read(dev->buffer, outbuf, write_avail); slock_unlock(dev->lock); scond_signal(dev->cond); return noErr; } #if !TARGET_OS_IPHONE static void choose_output_device(coreaudio_t *dev, const char* device) { int i; UInt32 deviceCount; AudioObjectPropertyAddress propaddr; AudioDeviceID *devices = NULL; UInt32 size = 0; propaddr.mSelector = kAudioHardwarePropertyDevices; #if HAS_MACOSX_10_12 propaddr.mScope = kAudioObjectPropertyScopeOutput; #else propaddr.mScope = kAudioObjectPropertyScopeGlobal; #endif propaddr.mElement = kAudioObjectPropertyElementMaster; if (AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propaddr, 0, 0, &size) != noErr) return; deviceCount = size / sizeof(AudioDeviceID); devices = (AudioDeviceID*)malloc(size); if (!devices || AudioObjectGetPropertyData(kAudioObjectSystemObject, &propaddr, 0, 0, &size, devices) != noErr) goto done; #if HAS_MACOSX_10_12 #else propaddr.mScope = kAudioDevicePropertyScopeOutput; #endif propaddr.mSelector = kAudioDevicePropertyDeviceName; for (i = 0; i < (int)deviceCount; i ++) { char device_name[1024]; device_name[0] = 0; size = 1024; if (AudioObjectGetPropertyData(devices[i], &propaddr, 0, 0, &size, device_name) == noErr && string_is_equal(device_name, device)) { AudioUnitSetProperty(dev->dev, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &devices[i], sizeof(AudioDeviceID)); goto done; } } done: free(devices); } #endif static void *coreaudio_init(const char *device, unsigned rate, unsigned latency, unsigned block_frames, unsigned *new_rate) { size_t fifo_size; UInt32 i_size; AudioStreamBasicDescription real_desc; #if !HAS_MACOSX_10_12 Component comp; #else AudioComponent comp; #endif #ifndef TARGET_OS_IPHONE AudioChannelLayout layout = {0}; #endif AURenderCallbackStruct cb = {0}; AudioStreamBasicDescription stream_desc = {0}; #if !HAS_MACOSX_10_12 ComponentDescription desc = {0}; #else AudioComponentDescription desc = {0}; #endif coreaudio_t *dev = (coreaudio_t*) calloc(1, sizeof(*dev)); if (!dev) return NULL; dev->lock = slock_new(); dev->cond = scond_new(); /* Create AudioComponent */ desc.componentType = kAudioUnitType_Output; #if TARGET_OS_IPHONE desc.componentSubType = kAudioUnitSubType_RemoteIO; #else desc.componentSubType = kAudioUnitSubType_HALOutput; #endif desc.componentManufacturer = kAudioUnitManufacturer_Apple; #if !HAS_MACOSX_10_12 if (!(comp = FindNextComponent(NULL, &desc))) goto error; #else if (!(comp = AudioComponentFindNext(NULL, &desc))) goto error; #endif #if !HAS_MACOSX_10_12 if ((OpenAComponent(comp, &dev->dev) != noErr)) goto error; #else if ((AudioComponentInstanceNew(comp, &dev->dev) != noErr)) goto error; #endif #if !TARGET_OS_IPHONE if (device) choose_output_device(dev, device); #endif dev->dev_alive = true; /* Set audio format */ stream_desc.mSampleRate = rate; stream_desc.mBitsPerChannel = sizeof(float) * CHAR_BIT; stream_desc.mChannelsPerFrame = 2; stream_desc.mBytesPerPacket = 2 * sizeof(float); stream_desc.mBytesPerFrame = 2 * sizeof(float); stream_desc.mFramesPerPacket = 1; stream_desc.mFormatID = kAudioFormatLinearPCM; stream_desc.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked | (is_little_endian() ? 0 : kAudioFormatFlagIsBigEndian); if (AudioUnitSetProperty(dev->dev, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &stream_desc, sizeof(stream_desc)) != noErr) goto error; /* Check returned audio format. */ i_size = sizeof(real_desc); if (AudioUnitGetProperty(dev->dev, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &real_desc, &i_size) != noErr) goto error; if (real_desc.mChannelsPerFrame != stream_desc.mChannelsPerFrame) goto error; if (real_desc.mBitsPerChannel != stream_desc.mBitsPerChannel) goto error; if (real_desc.mFormatFlags != stream_desc.mFormatFlags) goto error; if (real_desc.mFormatID != stream_desc.mFormatID) goto error; RARCH_LOG("[CoreAudio]: Using output sample rate of %.1f Hz\n", (float)real_desc.mSampleRate); *new_rate = real_desc.mSampleRate; /* Set channel layout (fails on iOS). */ #ifndef TARGET_OS_IPHONE layout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo; if (AudioUnitSetProperty(dev->dev, kAudioUnitProperty_AudioChannelLayout, kAudioUnitScope_Input, 0, &layout, sizeof(layout)) != noErr) goto error; #endif /* Set callbacks and finish up. */ cb.inputProc = audio_write_cb; cb.inputProcRefCon = dev; if (AudioUnitSetProperty(dev->dev, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &cb, sizeof(cb)) != noErr) goto error; if (AudioUnitInitialize(dev->dev) != noErr) goto error; fifo_size = (latency * (*new_rate)) / 1000; fifo_size *= 2 * sizeof(float); dev->buffer_size = fifo_size; if (!(dev->buffer = fifo_new(fifo_size))) goto error; RARCH_LOG("[CoreAudio]: Using buffer size of %u bytes: (latency = %u ms)\n", (unsigned)fifo_size, latency); if (AudioOutputUnitStart(dev->dev) != noErr) goto error; return dev; error: RARCH_ERR("[CoreAudio]: Failed to initialize driver ...\n"); coreaudio_free(dev); return NULL; } static ssize_t coreaudio_write(void *data, const void *buf_, size_t size) { coreaudio_t *dev = (coreaudio_t*)data; const uint8_t *buf = (const uint8_t*)buf_; size_t written = 0; while (!dev->is_paused && size > 0) { size_t write_avail; slock_lock(dev->lock); write_avail = FIFO_WRITE_AVAIL(dev->buffer); if (write_avail > size) write_avail = size; fifo_write(dev->buffer, buf, write_avail); buf += write_avail; written += write_avail; size -= write_avail; if (dev->nonblock) { slock_unlock(dev->lock); break; } #if TARGET_OS_IOS if (write_avail == 0 && !scond_wait_timeout( dev->cond, dev->lock, 300000)) { slock_unlock(dev->lock); break; } #else if (write_avail == 0) scond_wait(dev->cond, dev->lock); #endif slock_unlock(dev->lock); } return written; } static void coreaudio_set_nonblock_state(void *data, bool state) { coreaudio_t *dev = (coreaudio_t*)data; if (dev) dev->nonblock = state; } static bool coreaudio_alive(void *data) { coreaudio_t *dev = (coreaudio_t*)data; if (!dev) return false; return !dev->is_paused; } static bool coreaudio_stop(void *data) { coreaudio_t *dev = (coreaudio_t*)data; if (!dev) return false; dev->is_paused = (AudioOutputUnitStop(dev->dev) == noErr) ? true : false; return dev->is_paused ? true : false; } static bool coreaudio_start(void *data, bool is_shutdown) { coreaudio_t *dev = (coreaudio_t*)data; if (!dev) return false; dev->is_paused = (AudioOutputUnitStart(dev->dev) == noErr) ? false : true; return dev->is_paused ? false : true; } static bool coreaudio_use_float(void *data) { return true; } static size_t coreaudio_write_avail(void *data) { size_t avail; coreaudio_t *dev = (coreaudio_t*)data; slock_lock(dev->lock); avail = FIFO_WRITE_AVAIL(dev->buffer); slock_unlock(dev->lock); return avail; } static size_t coreaudio_buffer_size(void *data) { coreaudio_t *dev = (coreaudio_t*)data; return dev->buffer_size; } static void *coreaudio_device_list_new(void *data) { /* TODO/FIXME */ return NULL; } static void coreaudio_device_list_free(void *data, void *array_list_data) { /* TODO/FIXME */ } audio_driver_t audio_coreaudio = { coreaudio_init, coreaudio_write, coreaudio_stop, coreaudio_start, coreaudio_alive, coreaudio_set_nonblock_state, coreaudio_free, coreaudio_use_float, "coreaudio", coreaudio_device_list_new, coreaudio_device_list_free, coreaudio_write_avail, coreaudio_buffer_size, };