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.
This commit is contained in:
Vas Crabb 2015-04-05 01:46:12 +11:00
parent 3725b2fdc6
commit 18ec0951bd
7 changed files with 359 additions and 26 deletions

View File

@ -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 \

View File

@ -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

View File

@ -196,7 +196,7 @@ public:
}
};
#else /* SDLMAME_UNIX */
#else /* SDLMAME_MACOSX */
MODULE_NOT_SUPPORTED(font_osx, OSD_FONT_PROVIDER, "osx")
#endif

View File

@ -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

View File

@ -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 <AudioUnit/AudioUnit.h>
#include <CoreAudio/CoreAudio.h>
#include <CoreServices/CoreServices.h>
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)

View File

@ -332,19 +332,13 @@ 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)
{
if (attenuation == -32)
SDL_PauseAudio(1);
}
else if (stream_in_initialized)
{
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;