From 18ec0951bd1c3e8913ba554bd919113c6198c3eb Mon Sep 17 00:00:00 2001 From: Vas Crabb Date: Sun, 5 Apr 2015 01:46:12 +1100 Subject: [PATCH] Add a simple CoreAudio sound output module It doesn't provide facilities for AU effects, although that could be added pretty easily if someone wants to. Advantages over SDL sound output are simpler code and lower latency. --- makefile | 1 + scripts/src/osd/modules.lua | 8 +- src/osd/modules/font/font_osx.c | 2 +- src/osd/modules/lib/osdobj_common.c | 1 + src/osd/modules/sound/coreaudio_sound.c | 340 ++++++++++++++++++++++++ src/osd/modules/sound/direct_sound.c | 4 +- src/osd/modules/sound/sdl_sound.c | 29 +- 7 files changed, 359 insertions(+), 26 deletions(-) create mode 100644 src/osd/modules/sound/coreaudio_sound.c diff --git a/makefile b/makefile index 5f8be84310b..75314f21f87 100644 --- a/makefile +++ b/makefile @@ -464,6 +464,7 @@ SCRIPTS = scripts/genie.lua \ scripts/src/main.lua \ scripts/src/3rdparty.lua \ scripts/src/cpu.lua \ + scripts/src/osd/modules.lua \ $(wildcard scripts/src/osd/$(OSD)*.lua) \ scripts/src/sound.lua \ scripts/src/tools.lua \ diff --git a/scripts/src/osd/modules.lua b/scripts/src/osd/modules.lua index ecab93027ac..6c427d54bf2 100644 --- a/scripts/src/osd/modules.lua +++ b/scripts/src/osd/modules.lua @@ -26,6 +26,7 @@ function osdmodulesbuild() MAME_DIR .. "src/osd/modules/midi/none.c", MAME_DIR .. "src/osd/modules/sound/js_sound.c", MAME_DIR .. "src/osd/modules/sound/direct_sound.c", + MAME_DIR .. "src/osd/modules/sound/coreaudio_sound.c", MAME_DIR .. "src/osd/modules/sound/sdl_sound.c", MAME_DIR .. "src/osd/modules/sound/none.c", } @@ -181,7 +182,6 @@ function osdmodulestargetconf() } elseif _OPTIONS["targetos"]=="macosx" then links { - "CoreAudio.framework", "CoreMIDI.framework", } end @@ -218,6 +218,12 @@ function osdmodulestargetconf() "dsound", "dxguid", } + elseif _OPTIONS["targetos"]=="macosx" then + links { + "AudioUnit.framework", + "CoreAudio.framework", + "CoreServices.framework", + } end end diff --git a/src/osd/modules/font/font_osx.c b/src/osd/modules/font/font_osx.c index e7e58b35ad9..90b4544ac2e 100644 --- a/src/osd/modules/font/font_osx.c +++ b/src/osd/modules/font/font_osx.c @@ -196,7 +196,7 @@ public: } }; -#else /* SDLMAME_UNIX */ +#else /* SDLMAME_MACOSX */ MODULE_NOT_SUPPORTED(font_osx, OSD_FONT_PROVIDER, "osx") #endif diff --git a/src/osd/modules/lib/osdobj_common.c b/src/osd/modules/lib/osdobj_common.c index c5a64486ed8..38f60059059 100644 --- a/src/osd/modules/lib/osdobj_common.c +++ b/src/osd/modules/lib/osdobj_common.c @@ -165,6 +165,7 @@ void osd_common_t::register_options() REGISTER_MODULE(m_mod_man, SOUND_DSOUND); REGISTER_MODULE(m_mod_man, SOUND_JS); REGISTER_MODULE(m_mod_man, SOUND_SDL); + REGISTER_MODULE(m_mod_man, SOUND_COREAUDIO); REGISTER_MODULE(m_mod_man, SOUND_NONE); #ifdef SDLMAME_MACOSX diff --git a/src/osd/modules/sound/coreaudio_sound.c b/src/osd/modules/sound/coreaudio_sound.c new file mode 100644 index 00000000000..bf55958091f --- /dev/null +++ b/src/osd/modules/sound/coreaudio_sound.c @@ -0,0 +1,340 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +//============================================================ +// +// sound.c - CoreAudio implementation of MAME sound routines +// +// Copyright (c) 1996-2015, Nicola Salmoria and the MAME Team. +// Visit http://mamedev.org for licensing and usage restrictions. +// +//============================================================ + +#include "sound_module.h" +#include "modules/osdmodule.h" + +#ifdef SDLMAME_MACOSX + +#include +#include +#include + + +class sound_coreaudio : public osd_module, public sound_module +{ +public: + sound_coreaudio() : + osd_module(OSD_SOUND_PROVIDER, "coreaudio"), + sound_module(), + m_open(false), + m_started(false), + m_attenuation(0), + m_scale(128), + m_sample_bytes(0), + m_headroom(0), + m_buffer_size(0), + m_buffer(NULL), + m_playpos(0), + m_writepos(0), + m_in_underrun(false), + m_overflows(0), + m_underflows(0) + { + } + virtual ~sound_coreaudio() + { + } + + virtual int init(); + virtual void exit(); + + // sound_module + + virtual void update_audio_stream(bool is_throttled, INT16 const *buffer, int samples_this_frame); + virtual void set_mastervolume(int attenuation); + +private: + enum + { + LATENCY_MIN = 1, + LATENCY_MAX = 5 + }; + + UINT32 clamped_latency() const { return MAX(MIN(m_audio_latency, LATENCY_MAX), LATENCY_MIN); } + UINT32 buffer_avail() const { return ((m_writepos <= m_playpos) ? m_buffer_size : 0) + m_playpos - m_writepos; } + UINT32 buffer_used() const { return ((m_playpos < m_writepos) ? m_buffer_size : 0) + m_writepos - m_playpos; } + + void copy_scaled(void *dst, void const *src, UINT32 bytes) const + { + bytes /= sizeof(INT16); + INT16 const *s = (INT16 const *)src; + for (INT16 *d = (INT16 *)dst; bytes > 0; bytes--, s++, d++) + *d = (*s * m_scale) >> 7; + } + + OSStatus render( + AudioUnitRenderActionFlags *action_flags, + const AudioTimeStamp *timestamp, + UInt32 bus_number, + UInt32 number_frames, + AudioBufferList *data); + + static OSStatus render_callback( + void *refcon, + AudioUnitRenderActionFlags *action_flags, + const AudioTimeStamp *timestamp, + UInt32 bus_number, + UInt32 number_frames, + AudioBufferList *data); + + bool m_open; + bool m_started; + AudioUnit m_output; + int m_attenuation; + UINT32 m_scale; + UINT32 m_sample_bytes; + UINT32 m_headroom; + UINT32 m_buffer_size; + INT8 *m_buffer; + UINT32 m_playpos; + UINT32 m_writepos; + bool m_in_underrun; + unsigned m_overflows; + unsigned m_underflows; +}; + + +int sound_coreaudio::init() +{ + OSStatus err; + + // Don't bother with any of this if sound is disabled + if (sample_rate() == 0) + return 0; + + // Get the Default Output AudioUnit component and open an instance + ComponentDescription output_desc; + output_desc.componentType = kAudioUnitType_Output; + output_desc.componentSubType = kAudioUnitSubType_DefaultOutput; + output_desc.componentManufacturer = kAudioUnitManufacturer_Apple; + output_desc.componentFlags = 0; + output_desc.componentFlagsMask = 0; + Component output_comp = FindNextComponent(NULL, &output_desc); + if (!output_comp) + { + osd_printf_error("Could not find Default Output AudioUnit component\n"); + return -1; + } + err = OpenAComponent(output_comp, &m_output); + if (noErr != err) + { + osd_printf_error("Could not open Default Output AudioUnit component (%ld)\n", (long)err); + return -1; + } + + // Set render callback + AURenderCallbackStruct renderer; + renderer.inputProc = sound_coreaudio::render_callback; + renderer.inputProcRefCon = this; + err = AudioUnitSetProperty(m_output, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &renderer, sizeof(renderer)); + if (noErr != err) + { + CloseComponent(m_output); + osd_printf_error("Could not set audio output render callback (%ld)\n", (long)err); + return -1; + } + + // Set audio stream format for two-channel native-endian 16-bit packed linear PCM + AudioStreamBasicDescription format; + format.mSampleRate = sample_rate(); + format.mFormatID = kAudioFormatLinearPCM; + format.mFormatFlags = kAudioFormatFlagsNativeEndian + | kLinearPCMFormatFlagIsSignedInteger + | kLinearPCMFormatFlagIsPacked; + format.mFramesPerPacket = 1; + format.mChannelsPerFrame = 2; + format.mBitsPerChannel = 16; + format.mBytesPerFrame = format.mChannelsPerFrame * format.mBitsPerChannel / 8; + format.mBytesPerPacket = format.mFramesPerPacket * format.mBytesPerFrame; + err = AudioUnitSetProperty(m_output, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &format, sizeof(format)); + if (noErr != err) + { + CloseComponent(m_output); + osd_printf_error("Could not set audio output stream format (%ld)\n", (long)err); + return -1; + } + m_sample_bytes = format.mBytesPerFrame; + + // Allocate buffer + m_headroom = (clamped_latency() * sample_rate() / 40) * m_sample_bytes; + m_buffer_size = MAX((sample_rate() * m_sample_bytes * (clamped_latency() + 2) / 20480) * 1024, m_sample_bytes * 256); + m_buffer = global_alloc_array_clear(INT8, m_buffer_size); + if (!m_buffer) + { + CloseComponent(m_output); + osd_printf_error("Could not allocate stream buffer\n"); + return -1; + } + m_playpos = 0; + m_writepos = m_headroom; + m_in_underrun = false; + m_overflows = m_underflows = 0; + + // Initialise and start + err = AudioUnitInitialize(m_output); + if (noErr != err) + { + CloseComponent(m_output); + global_free_array(m_buffer); + m_buffer = NULL; + osd_printf_error("Could not initialize audio output (%ld)\n", (long)err); + return -1; + } + err = AudioOutputUnitStart(m_output); + if (noErr != err) + { + AudioUnitUninitialize(m_output); + CloseComponent(m_output); + global_free_array(m_buffer); + m_buffer = NULL; + osd_printf_error("Could not start audio output (%ld)\n", (long)err); + return -1; + } + m_open = true; + m_started = true; + osd_printf_verbose("Audio: End initialization\n"); + return 0; +} + + +void sound_coreaudio::exit() +{ + if (m_open) + { + osd_printf_verbose("Closing output AudioUnit component\n"); + AudioOutputUnitStop(m_output); + AudioUnitUninitialize(m_output); + CloseComponent(m_output); + m_open = false; + m_started = false; + } + if (m_buffer) + { + global_free_array(m_buffer); + m_buffer = NULL; + } + if (m_overflows || m_underflows) + osd_printf_verbose("Sound buffer: overflows=%u underflows=%u\n", m_overflows, m_underflows); +} + + +void sound_coreaudio::update_audio_stream(bool is_throttled, INT16 const *buffer, int samples_this_frame) +{ + if ((sample_rate() == 0) || !m_buffer) + return; + + UINT32 const bytes_this_frame = samples_this_frame * m_sample_bytes; + if (bytes_this_frame >= buffer_avail()) + { + m_overflows++; + return; + } + + UINT32 const chunk = MIN(m_buffer_size - m_writepos, bytes_this_frame); + memcpy(m_buffer + m_writepos, (INT8 *)buffer, chunk); + m_writepos += chunk; + if (m_writepos >= m_buffer_size) + m_writepos = 0; + + if (chunk < bytes_this_frame) + { + assert(0U == m_writepos); + assert(m_playpos > (bytes_this_frame - chunk)); + memcpy(m_buffer, (INT8 *)buffer + chunk, bytes_this_frame - chunk); + m_writepos += bytes_this_frame - chunk; + } +} + + +void sound_coreaudio::set_mastervolume(int attenuation) +{ + m_attenuation = MAX(MIN(attenuation, 0), -32); + m_scale = (UINT32)(pow(10.0, m_attenuation / 20.0) * 128); + if (m_open) + { + if (-32 == m_attenuation) + { + if (m_started) + { + if (noErr == AudioOutputUnitStop(m_output)) + m_started = false; + } + } + else + { + if (!m_started) + { + if (noErr == AudioOutputUnitStart(m_output)) + m_started = true; + } + } + } +} + + +OSStatus sound_coreaudio::render( + AudioUnitRenderActionFlags *action_flags, + const AudioTimeStamp *timestamp, + UInt32 bus_number, + UInt32 number_frames, + AudioBufferList *data) +{ + UINT32 const number_bytes = number_frames * m_sample_bytes; + UINT32 const used = buffer_used(); + if (m_in_underrun && (used < m_headroom)) + { + memset(data->mBuffers[0].mData, 0, number_bytes); + return noErr; + } + m_in_underrun = false; + if (number_bytes > used) + { + m_in_underrun = true; + m_underflows++; + memset(data->mBuffers[0].mData, 0, number_bytes); + return noErr; + } + + UINT32 const chunk = MIN(m_buffer_size - m_playpos, number_bytes); + copy_scaled((INT8 *)data->mBuffers[0].mData, m_buffer + m_playpos, chunk); + m_playpos += chunk; + if (m_playpos >= m_buffer_size) + m_playpos = 0; + + if (chunk < number_bytes) + { + assert(0U == m_playpos); + assert(m_writepos >= (number_bytes - chunk)); + copy_scaled((INT8 *)data->mBuffers[0].mData + chunk, m_buffer, number_bytes - chunk); + m_playpos += number_bytes - chunk; + } + + return noErr; +} + + +OSStatus sound_coreaudio::render_callback( + void *refcon, + AudioUnitRenderActionFlags *action_flags, + const AudioTimeStamp *timestamp, + UInt32 bus_number, + UInt32 number_frames, + AudioBufferList *data) +{ + return ((sound_coreaudio *)refcon)->render(action_flags, timestamp, bus_number, number_frames, data); +} + +#else /* SDLMAME_MACOSX */ + MODULE_NOT_SUPPORTED(sound_coreaudio, OSD_SOUND_PROVIDER, "coreaudio") +#endif + +MODULE_DEFINITION(SOUND_COREAUDIO, sound_coreaudio) diff --git a/src/osd/modules/sound/direct_sound.c b/src/osd/modules/sound/direct_sound.c index 57af1ed9761..a005813adb7 100644 --- a/src/osd/modules/sound/direct_sound.c +++ b/src/osd/modules/sound/direct_sound.c @@ -275,7 +275,7 @@ HRESULT sound_direct_sound::dsound_init() } // set the cooperative level - #ifdef SDLMAME_WIN32 +#ifdef SDLMAME_WIN32 SDL_SysWMinfo wminfo; SDL_VERSION(&wminfo.version); #if (SDLMAME_SDL2) @@ -287,7 +287,7 @@ HRESULT sound_direct_sound::dsound_init() #endif #else result = IDirectSound_SetCooperativeLevel(dsound, win_window_list->m_hwnd, DSSCL_PRIORITY); - #endif +#endif if (result != DS_OK) { osd_printf_error("Error setting DirectSound cooperative level: %08x\n", (UINT32)result); diff --git a/src/osd/modules/sound/sdl_sound.c b/src/osd/modules/sound/sdl_sound.c index ba1e842f4a9..0c9550421b7 100644 --- a/src/osd/modules/sound/sdl_sound.c +++ b/src/osd/modules/sound/sdl_sound.c @@ -332,20 +332,14 @@ void sound_sdl::update_audio_stream(bool is_throttled, const INT16 *buffer, int void sound_sdl::set_mastervolume(int _attenuation) { // clamp the attenuation to 0-32 range - if (_attenuation > 0) - _attenuation = 0; - if (_attenuation < -32) - _attenuation = -32; + attenuation = MAX(MIN(_attenuation, 0), -32); - attenuation = _attenuation; - - if ((attenuation == -32) && (stream_in_initialized)) + if (stream_in_initialized) { - SDL_PauseAudio(1); - } - else if (stream_in_initialized) - { - SDL_PauseAudio(0); + if (attenuation == -32) + SDL_PauseAudio(1); + else + SDL_PauseAudio(0); } } @@ -455,17 +449,8 @@ int sound_sdl::init() sdl_xfer_samples = obtained.samples; - audio_latency = m_audio_latency; - // pin audio latency - if (audio_latency > MAX_AUDIO_LATENCY) - { - audio_latency = MAX_AUDIO_LATENCY; - } - else if (audio_latency < 1) - { - audio_latency = 1; - } + audio_latency = MAX(MIN(m_audio_latency, MAX_AUDIO_LATENCY), 1); // compute the buffer sizes stream_buffer_size = (sample_rate() * 2 * sizeof(INT16) * (2 + audio_latency)) / 30;