diff --git a/Makefile b/Makefile index 0cadf95c73..26cc1530f7 100644 --- a/Makefile +++ b/Makefile @@ -83,6 +83,11 @@ ifeq ($(HAVE_PULSE), 1) DEFINES += $(PULSE_CFLAGS) endif +ifeq ($(HAVE_COREAUDIO), 1) + OBJ += audio/coreaudio.o + LIBS += -framework CoreServices -framework CoreAudio -framework AudioUnit +endif + ifeq ($(HAVE_SDL), 1) OBJ += gfx/sdl.o gfx/gl.o input/sdl.o audio/sdl.o fifo_buffer.o DEFINES += $(SDL_CFLAGS) $(BSD_LOCAL_INC) diff --git a/audio/coreaudio.c b/audio/coreaudio.c new file mode 100644 index 0000000000..27b514cb96 --- /dev/null +++ b/audio/coreaudio.c @@ -0,0 +1,278 @@ +/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes. + * Copyright (C) 2010-2011 - Hans-Kristian Arntzen + * + * Some code herein may be based on code found in BSNES. + * + * SSNES 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. + * + * SSNES 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 SSNES. + * If not, see . + */ + + +#include "driver.h" +#include "general.h" +#include "fifo_buffer.h" +#include +#include +#include + +#include +#include +#include +#include + +typedef struct coreaudio +{ + pthread_mutex_t lock; + pthread_cond_t cond; + + ComponentInstance dev; + bool dev_alive; + + fifo_buffer_t *buffer; + bool nonblock; +} coreaudio_t; + +static void coreaudio_free(void *data) +{ + coreaudio_t *dev = data; + if (!dev) + return; + + if (dev->dev_alive) + { + AudioOutputUnitStop(dev->dev); + CloseComponent(dev->dev); + } + + if (dev->buffer) + fifo_free(dev->buffer); + + pthread_mutex_destroy(&dev->lock); + pthread_cond_destroy(&dev->cond); + + free(dev); +} + +static OSStatus audio_cb(void *userdata, AudioUnitRenderActionFlags *action_flags, + const AudioTimeStamp *time_stamp, UInt32 bus_number, + UInt32 number_frames, AudioBufferList *io_data) +{ + coreaudio_t *dev = 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; + + pthread_mutex_lock(&dev->lock); + if (fifo_read_avail(dev->buffer) < write_avail) + { + *action_flags = kAudioUnitRenderAction_OutputIsSilence; + pthread_mutex_unlock(&dev->lock); + pthread_cond_signal(&dev->cond); // Technically possible to deadlock without. + return noErr; + } + + void *outbuf = io_data->mBuffers[0].mData; + fifo_read(dev->buffer, output, write_avail); + pthread_mutex_unlock(&dev->lock); + pthread_cond_signal(&dev->cond); + return noErr; +} + +static void* coreaudio_init(const char* device, unsigned rate, unsigned latency) +{ + (void)device; + + coreaudio_t *dev = calloc(1, sizeof(*dev)); + if (!dev) + return NULL; + + CFRunLoopRef run_loop = NULL; + AudioObjectPropertyAddress addr = { + kAudioHardwarePropertyRunLoop, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyyElementMaster + }; + + pthread_mutex_init(&dev->lock, NULL); + pthread_cond_init(&dev->cond, NULL); + AudioObjectSetPropertyData(kAudioObjectSystemObject, &addr, 0, NULL, + sizeof(CFRunLoopRef), &run_loop); + + ComponentDescription desc = { + .componentType = kAudioUnitType_Output, + .componentSubType = kAudioUnitSubType_HALOutput, + .componentManufacturer = kAudioUnitManufacturer_Apple, + }; + + Component comp = FindNextComponent(NULL, &desc); + if (comp == NULL) + goto error; + + OSStatus res = OpenAComponent(comp, &dev->dev); + if (res != noErr) + goto error; + + dev->dev_alive = true; + + AudioStreamBasicDescription stream_desc = { + .mSampleRate = rate, + .mBitsPerChannel = sizeof(float) * CHAR_BIT, + .mChannelsPerFrame = 2, + .mBytesPerPacket = 2 * sizeof(float), + .mFramesPerPacket = 1, + .mFormatID = kAudioFormatLinearPCM, + .mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked | (is_little_endian() ? 0 : kAudioFormatFlagisBigEndian), + }; + + res = AudioUnitSetProperty(dev->dev, kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, 0, &stream_desc, sizeof(stream_desc)); + if (res != noErr) + goto error; + + AudioStreamBasicDescription real_desc; + UInt32 i_size = sizeof(real_desc); + res = AudioUnitGetProperty(dev->dev, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &real_desc, &i_size); + if (res != noErr) + goto error; + + SSNES_LOG("[CoreAudio]: Using output sample rate of %.1f\n", (float)real_desc.mSampleRate); + g_settings.audio.out_rate = real_desc.mSampleRate; + + 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; + + AudioChannelLayout layout = { + .mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelBitmap, + .mChannelBitmap = (1 << 2) - 1, + }; + + res = AudioUnitSetProperty(dev->dev, kAudioUnitProperty_AudioChannelLayout, + kAudioUnitScope_Input, 0, &layout, sizeof(layout)); + if (res != noErr) + goto error; + + AURenderCallbackStruct cb = { + .inputProc = audio_cb, + .inputProcRefCon = dev, + }; + + res = AudioUnitSetProperty(dev->dev, kAudioUnitProperty_SetRenderCallback, + kAudioUnitScope_Input, 0, &cb, sizeof(cb)); + if (res != noErr) + goto error; + + res = AudioUnitInitialize(dev->dev); + if (res != noErr) + goto error; + + size_t fifo_size = (latency * g_settings.audio.out_rate) / 1000; + fifo_size *= 2 * sizeof(float); + + dev->buffer = fifo_new(fifo_size); + if (!dev->buffer) + goto error; + + SSNES_LOG("[CoreAudio]: Using buffer size of %u bytes\n", (unsigned)fifo_size); + + res = AudioOutputUnitStart(dev->dev); + if (res != noErr) + goto error; + + return dev; + +error: + SSNES_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 = data; + + const uint8_t *buf = buf_; + size_t written = 0; + + while (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; + } + + if (write_avail == 0) + pthread_cond_wait(&dev->cond, &dev->lock); + pthread_mutex_unlock(&dev->lock); + } + + return written; +} + +static bool coreaudio_stop(void *data) +{ + coreaudio_t *dev = data; + return AudioOutputUnitStop(dev->dev) == noErr; +} + +static void coreaudio_set_nonblock_state(void *data, bool state) +{ + coreaudio_t *dev = data; + dev->nonblock = state; +} + +static bool coreaudio_start(void *data) +{ + coreaudio_t *dev = data; + return AudioOutputUnitStart(dev->dev) == noErr; +} + +static bool coreaudio_use_float(void *data) +{ + (void)data; + return true; +} + +const audio_driver_t audio_rsound = { + .init = coreaudio_init, + .write = coreaudio_write, + .stop = coreaudio_stop, + .start = coreaudio_start, + .set_nonblock_state = coreaudio_set_nonblock_state, + .free = coreaudio_free, + .use_float = coreaudio_use_float, + .ident = "coreaudio" +}; + diff --git a/config.def.h b/config.def.h index 9f3bd6e56e..22a872c014 100644 --- a/config.def.h +++ b/config.def.h @@ -58,6 +58,7 @@ #define AUDIO_PULSE 10 #define AUDIO_EXT 15 #define AUDIO_DSOUND 16 +#define AUDIO_COREAUDIO 17 //////////////////////// #define INPUT_SDL 7 #define INPUT_X 12 @@ -83,6 +84,8 @@ #define AUDIO_DEFAULT_DRIVER AUDIO_OSS #elif defined(HAVE_JACK) #define AUDIO_DEFAULT_DRIVER AUDIO_JACK +#elif defined(HAVE_COREAUDIO) +#define AUDIO_DEFAULT_DRIVER AUDIO_COREAUDIO #elif defined(HAVE_AL) #define AUDIO_DEFAULT_DRIVER AUDIO_AL #elif defined(HAVE_DSOUND) diff --git a/driver.c b/driver.c index 0d9b5ccef1..353339d477 100644 --- a/driver.c +++ b/driver.c @@ -38,6 +38,9 @@ static const audio_driver_t *audio_drivers[] = { #ifdef HAVE_RSOUND &audio_rsound, #endif +#ifdef HAVE_COREAUDIO + &audio_coreaudio, +#endif #ifdef HAVE_AL &audio_openal, #endif diff --git a/driver.h b/driver.h index 30df26393c..743a58347a 100644 --- a/driver.h +++ b/driver.h @@ -159,6 +159,7 @@ extern const audio_driver_t audio_xa; extern const audio_driver_t audio_pulse; extern const audio_driver_t audio_ext; extern const audio_driver_t audio_dsound; +extern const audio_driver_t audio_coreaudio; extern const video_driver_t video_gl; extern const video_driver_t video_xvideo; extern const video_driver_t video_sdl; diff --git a/qb/config.libs.sh b/qb/config.libs.sh index c12689fc29..f3ba583371 100644 --- a/qb/config.libs.sh +++ b/qb/config.libs.sh @@ -46,6 +46,8 @@ check_pkgconf ROAR libroar check_pkgconf JACK jack 0.120.1 check_pkgconf PULSE libpulse +check_lib COREAUDIO "-framework AudioUnit" AudioUnitInitialize + check_pkgconf SDL sdl 1.2.10 check_critical SDL "Cannot find SDL 1.2 library." check_pkgconf SDL_NEW sdl 1.3 @@ -81,7 +83,7 @@ check_pkgconf PYTHON python3 add_define_make OS $OS # Creates config.mk and config.h. -VARS="ALSA OSS OSS_BSD OSS_LIB AL RSOUND ROAR JACK PULSE SDL DYLIB CG XML SDL_IMAGE DYNAMIC FFMPEG AVCODEC AVFORMAT AVUTIL SWSCALE SRC CONFIGFILE FREETYPE XVIDEO NETPLAY FBO STRL PYTHON" +VARS="ALSA OSS OSS_BSD OSS_LIB AL RSOUND ROAR JACK COREAUDIO PULSE SDL DYLIB CG XML SDL_IMAGE DYNAMIC FFMPEG AVCODEC AVFORMAT AVUTIL SWSCALE SRC CONFIGFILE FREETYPE XVIDEO NETPLAY FBO STRL PYTHON" create_config_make config.mk $VARS create_config_header config.h $VARS diff --git a/qb/config.params.sh b/qb/config.params.sh index 6f526649d4..40b8f16a4c 100644 --- a/qb/config.params.sh +++ b/qb/config.params.sh @@ -23,6 +23,7 @@ add_command_line_enable RSOUND "Enable RSound support" auto add_command_line_enable ROAR "Enable RoarAudio support" auto add_command_line_enable AL "Enable OpenAL support" auto add_command_line_enable JACK "Enable JACK support" auto +add_command_line_enable COREAUDIO "Enable CoreAudio support" auto add_command_line_enable PULSE "Enable PulseAudio support" auto add_command_line_enable FREETYPE "Enable FreeType support" auto add_command_line_enable XVIDEO "Enable XVideo support" auto diff --git a/settings.c b/settings.c index e0e9b1a32f..d56e3f031a 100644 --- a/settings.c +++ b/settings.c @@ -71,6 +71,9 @@ static void set_defaults(void) case AUDIO_ROAR: def_audio = "roar"; break; + case AUDIO_COREAUDIO: + def_audio = "coreaudio"; + break; case AUDIO_AL: def_audio = "openal"; break;