/* 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 "../driver.h" #include "../general.h" #include "../fifo_buffer.h" #include #include "../boolean.h" #include #ifdef OSX #include #else #include #endif #include #include #include typedef struct coreaudio { pthread_mutex_t lock; pthread_cond_t cond; #ifdef OSX_PPC ComponentInstance dev; #else AudioComponentInstance dev; #endif bool dev_alive; fifo_buffer_t *buffer; bool nonblock; size_t buffer_size; } coreaudio_t; static bool g_interrupted; static void coreaudio_free(void *data) { coreaudio_t *dev = (coreaudio_t*)data; if (!dev) return; if (dev->dev_alive) { AudioOutputUnitStop(dev->dev); #ifdef OSX_PPC CloseComponent(dev->dev); #else AudioComponentInstanceDispose(dev->dev); #endif } if (dev->buffer) fifo_free(dev->buffer); pthread_mutex_destroy(&dev->lock); pthread_cond_destroy(&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) { coreaudio_t *dev = (coreaudio_t*)userdata; (void)time_stamp; (void)bus_number; (void)number_frames; if (!io_data) return noErr; if (io_data->mNumberBuffers != 1) return noErr; unsigned write_avail = io_data->mBuffers[0].mDataByteSize; void *outbuf = io_data->mBuffers[0].mData; pthread_mutex_lock(&dev->lock); if (fifo_read_avail(dev->buffer) < write_avail) { *action_flags = kAudioUnitRenderAction_OutputIsSilence; memset(outbuf, 0, write_avail); // Seems to be needed. pthread_mutex_unlock(&dev->lock); pthread_cond_signal(&dev->cond); // Technically possible to deadlock without. return noErr; } fifo_read(dev->buffer, outbuf, write_avail); pthread_mutex_unlock(&dev->lock); pthread_cond_signal(&dev->cond); return noErr; } #ifdef OSX static void choose_output_device(coreaudio_t *dev, const char* device) { AudioObjectPropertyAddress propaddr = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; UInt32 size = 0; if (AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propaddr, 0, 0, &size) != noErr) return; UInt32 deviceCount = size / sizeof(AudioDeviceID); AudioDeviceID *devices = malloc(size); if (!devices || AudioObjectGetPropertyData(kAudioObjectSystemObject, &propaddr, 0, 0, &size, devices) != noErr) goto done; propaddr.mScope = kAudioDevicePropertyScopeOutput; propaddr.mSelector = kAudioDevicePropertyDeviceName; size = 1024; for (unsigned i = 0; i < deviceCount; i ++) { char device_name[1024]; device_name[0] = 0; if (AudioObjectGetPropertyData(devices[i], &propaddr, 0, 0, &size, device_name) == noErr && strcmp(device_name, device) == 0) { AudioUnitSetProperty(dev->dev, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &devices[i], sizeof(AudioDeviceID)); goto done; } } done: free(devices); } #endif #ifdef IOS static void coreaudio_interrupt_listener(void *data, UInt32 interrupt_state) { (void)data; g_interrupted = (interrupt_state == kAudioSessionBeginInterruption); } #endif static void *coreaudio_init(const char *device, unsigned rate, unsigned latency) { (void)device; coreaudio_t *dev = (coreaudio_t*)calloc(1, sizeof(*dev)); if (!dev) return NULL; pthread_mutex_init(&dev->lock, NULL); pthread_cond_init(&dev->cond, NULL); #ifdef IOS static bool session_initialized = false; if (!session_initialized) { session_initialized = true; AudioSessionInitialize(0, 0, coreaudio_interrupt_listener, 0); AudioSessionSetActive(true); } #endif // Create AudioComponent #ifdef OSX_PPC ComponentDescription desc = {0}; #else AudioComponentDescription desc = {0}; #endif desc.componentType = kAudioUnitType_Output; #ifdef IOS desc.componentSubType = kAudioUnitSubType_RemoteIO; #else desc.componentSubType = kAudioUnitSubType_HALOutput; #endif desc.componentManufacturer = kAudioUnitManufacturer_Apple; #ifdef OSX_PPC Component comp = FindNextComponent(NULL, &desc); #else AudioComponent comp = AudioComponentFindNext(NULL, &desc); #endif if (comp == NULL) goto error; #ifdef OSX_PPC if (OpenAComponent(comp, &dev->dev) != noErr) #else if (AudioComponentInstanceNew(comp, &dev->dev) != noErr) #endif goto error; #ifdef OSX if (device) choose_output_device(dev, device); #endif dev->dev_alive = true; // Set audio format AudioStreamBasicDescription stream_desc = {0}; AudioStreamBasicDescription real_desc; 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 UInt32 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); g_settings.audio.out_rate = real_desc.mSampleRate; // Set channel layout (fails on iOS) #ifndef IOS AudioChannelLayout layout = {0}; 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 AURenderCallbackStruct cb = {0}; 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; size_t fifo_size; fifo_size = (latency * g_settings.audio.out_rate) / 1000; fifo_size *= 2 * sizeof(float); dev->buffer_size = fifo_size; dev->buffer = fifo_new(fifo_size); if (!dev->buffer) 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; #ifdef IOS struct timeval time; gettimeofday(&time, 0); struct timespec timeout; memset(&timeout, 0, sizeof(timeout)); timeout.tv_sec = time.tv_sec + 3; timeout.tv_nsec = time.tv_usec * 1000; #endif while (!g_interrupted && size > 0) { pthread_mutex_lock(&dev->lock); size_t 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) { pthread_mutex_unlock(&dev->lock); break; } #ifdef IOS if (write_avail == 0 && pthread_cond_timedwait(&dev->cond, &dev->lock, &timeout) == ETIMEDOUT) g_interrupted = true; #else if (write_avail == 0) pthread_cond_wait(&dev->cond, &dev->lock); #endif pthread_mutex_unlock(&dev->lock); } return written; } static bool coreaudio_stop(void *data) { coreaudio_t *dev = (coreaudio_t*)data; return AudioOutputUnitStop(dev->dev) == noErr; } static void coreaudio_set_nonblock_state(void *data, bool state) { coreaudio_t *dev = (coreaudio_t*)data; dev->nonblock = state; } static bool coreaudio_start(void *data) { coreaudio_t *dev = (coreaudio_t*)data; return AudioOutputUnitStart(dev->dev) == noErr; } static bool coreaudio_use_float(void *data) { (void)data; return true; } static size_t coreaudio_write_avail(void *data) { coreaudio_t *dev = (coreaudio_t*)data; pthread_mutex_lock(&dev->lock); size_t avail = fifo_write_avail(dev->buffer); pthread_mutex_unlock(&dev->lock); return avail; } static size_t coreaudio_buffer_size(void *data) { coreaudio_t *dev = (coreaudio_t*)data; return dev->buffer_size; } const audio_driver_t audio_coreaudio = { coreaudio_init, coreaudio_write, coreaudio_stop, coreaudio_start, coreaudio_set_nonblock_state, coreaudio_free, coreaudio_use_float, "coreaudio", coreaudio_write_avail, coreaudio_buffer_size, };