mame/src/osd/modules/sound/sdl_sound.cpp

466 lines
12 KiB
C++

// license:BSD-3-Clause
// copyright-holders:Olivier Galibert, R. Belmont
//============================================================
//
// sound.c - SDL implementation of MAME sound routines
//
// SDLMAME by Olivier Galibert and R. Belmont
//
//============================================================
#include "sound_module.h"
#include "modules/osdmodule.h"
#if (defined(OSD_SDL) || defined(USE_SDL_SOUND))
// standard sdl header
#include <SDL2/SDL.h>
// MAME headers
#include "emu.h"
#include "emuopts.h"
#include "../../sdl/osdsdl.h"
#include <algorithm>
#include <fstream>
#include <memory>
//============================================================
// DEBUGGING
//============================================================
#define LOG_SOUND 0
//============================================================
// CLASS
//============================================================
class sound_sdl : public osd_module, public sound_module
{
public:
// number of samples per SDL callback
static const int SDL_XFER_SAMPLES = 512;
sound_sdl() :
osd_module(OSD_SOUND_PROVIDER, "sdl"), sound_module(),
stream_in_initialized(0),
attenuation(0), buf_locked(0), stream_buffer(nullptr), stream_buffer_size(0), buffer_underflows(0), buffer_overflows(0)
{
sdl_xfer_samples = SDL_XFER_SAMPLES;
}
virtual ~sound_sdl() { }
virtual int init(const osd_options &options) override;
virtual void exit() override;
// sound_module
virtual void update_audio_stream(bool is_throttled, const int16_t *buffer, int samples_this_frame) override;
virtual void set_mastervolume(int attenuation) override;
private:
class ring_buffer
{
public:
ring_buffer(size_t size);
size_t data_size() const { return (tail - head + buffer_size) % buffer_size; }
size_t free_size() const { return (head - tail - 1 + buffer_size) % buffer_size; }
int append(const void *data, size_t size);
int pop(void *data, size_t size);
private:
std::unique_ptr<int8_t []> const buffer;
size_t const buffer_size;
int head = 0, tail = 0;
};
static void sdl_callback(void *userdata, Uint8 *stream, int len);
void lock_buffer();
void unlock_buffer();
void attenuate(int16_t *data, int bytes);
void copy_sample_data(bool is_throttled, const int16_t *data, int bytes_to_copy);
int sdl_create_buffers();
void sdl_destroy_buffers();
int sdl_xfer_samples;
int stream_in_initialized;
int attenuation;
int buf_locked;
std::unique_ptr<ring_buffer> stream_buffer;
uint32_t stream_buffer_size;
// diagnostics
int buffer_underflows;
int buffer_overflows;
std::unique_ptr<std::ofstream> sound_log;
};
//============================================================
// PARAMETERS
//============================================================
// maximum audio latency
#define MAX_AUDIO_LATENCY 5
//============================================================
// ring_buffer - constructor
//============================================================
sound_sdl::ring_buffer::ring_buffer(size_t size)
: buffer(std::make_unique<int8_t []>(size + 1)), buffer_size(size + 1)
{
// A size+1 bytes buffer is allocated because it can never be full.
// Otherwise the case head == tail couldn't be distinguished between a
// full buffer and an empty buffer.
std::fill_n(buffer.get(), size + 1, 0);
}
//============================================================
// ring_buffer::append
//============================================================
int sound_sdl::ring_buffer::append(const void *data, size_t size)
{
if (free_size() < size)
return -1;
int8_t const *const data8 = reinterpret_cast<int8_t const *>(data);
size_t sz = buffer_size - tail;
if (size <= sz)
sz = size;
else
std::copy_n(&data8[sz], size - sz, &buffer[0]);
std::copy_n(data8, sz, &buffer[tail]);
tail = (tail + size) % buffer_size;
return 0;
}
//============================================================
// ring_buffer::pop
//============================================================
int sound_sdl::ring_buffer::pop(void *data, size_t size)
{
if (data_size() < size)
return -1;
int8_t *const data8 = reinterpret_cast<int8_t *>(data);
size_t sz = buffer_size - head;
if (size <= sz)
sz = size;
else
{
std::copy_n(&buffer[0], size - sz, &data8[sz]);
std::fill_n(&buffer[0], size - sz, 0);
}
std::copy_n(&buffer[head], sz, data8);
std::fill_n(&buffer[head], sz, 0);
head = (head + size) % buffer_size;
return 0;
}
//============================================================
// sound_sdl - destructor
//============================================================
//============================================================
// lock_buffer
//============================================================
void sound_sdl::lock_buffer()
{
if (!buf_locked)
SDL_LockAudio();
buf_locked++;
if (LOG_SOUND)
*sound_log << "locking\n";
}
//============================================================
// unlock_buffer
//============================================================
void sound_sdl::unlock_buffer()
{
buf_locked--;
if (!buf_locked)
SDL_UnlockAudio();
if (LOG_SOUND)
*sound_log << "unlocking\n";
}
//============================================================
// Apply attenuation
//============================================================
void sound_sdl::attenuate(int16_t *data, int bytes_to_copy)
{
int level = (int) (pow(10.0, (double) attenuation / 20.0) * 128.0);
int count = bytes_to_copy / sizeof(*data);
while (count > 0)
{
*data = (*data * level) >> 7; /* / 128 */
data++;
count--;
}
}
//============================================================
// copy_sample_data
//============================================================
void sound_sdl::copy_sample_data(bool is_throttled, const int16_t *data, int bytes_to_copy)
{
lock_buffer();
int const err = stream_buffer->append(data, bytes_to_copy);
unlock_buffer();
if (LOG_SOUND && err)
*sound_log << "Late detection of overflow. This shouldn't happen.\n";
}
//============================================================
// update_audio_stream
//============================================================
void sound_sdl::update_audio_stream(bool is_throttled, const int16_t *buffer, int samples_this_frame)
{
// if nothing to do, don't do it
if (sample_rate() == 0 || !stream_buffer)
return;
if (!stream_in_initialized)
{
// Fill in some zeros to prevent an initial buffer underflow
int8_t zero = 0;
size_t zsize = stream_buffer->free_size() / 2;
while (zsize--)
stream_buffer->append(&zero, 1);
// start playing
SDL_PauseAudio(0);
stream_in_initialized = 1;
}
size_t bytes_this_frame = samples_this_frame * sizeof(*buffer) * 2;
size_t free_size = stream_buffer->free_size();
size_t data_size = stream_buffer->data_size();
if (stream_buffer->free_size() < bytes_this_frame) {
if (LOG_SOUND)
util::stream_format(*sound_log, "Overflow: DS=%u FS=%u BTF=%u\n", data_size, free_size, bytes_this_frame);
buffer_overflows++;
return;
}
copy_sample_data(is_throttled, buffer, bytes_this_frame);
size_t nfree_size = stream_buffer->free_size();
size_t ndata_size = stream_buffer->data_size();
if (LOG_SOUND)
util::stream_format(*sound_log, "Appended data: DS=%u(%u) FS=%u(%u) BTF=%u\n", data_size, ndata_size, free_size, nfree_size, bytes_this_frame);
}
//============================================================
// set_mastervolume
//============================================================
void sound_sdl::set_mastervolume(int _attenuation)
{
// clamp the attenuation to 0-32 range
attenuation = std::clamp(_attenuation, -32, 0);
if (stream_in_initialized)
{
if (attenuation == -32)
SDL_PauseAudio(1);
else
SDL_PauseAudio(0);
}
}
//============================================================
// sdl_callback
//============================================================
void sound_sdl::sdl_callback(void *userdata, Uint8 *stream, int len)
{
sound_sdl *thiz = reinterpret_cast<sound_sdl *>(userdata);
size_t const free_size = thiz->stream_buffer->free_size();
size_t const data_size = thiz->stream_buffer->data_size();
if (data_size < len)
{
thiz->buffer_underflows++;
if (LOG_SOUND)
util::stream_format(*thiz->sound_log, "Underflow at sdl_callback: DS=%u FS=%u Len=%d\n", data_size, free_size, len);
// Maybe read whatever is left in the stream_buffer anyway?
memset(stream, 0, len);
return;
}
int err = thiz->stream_buffer->pop((void *)stream, len);
if (LOG_SOUND && err)
*thiz->sound_log << "Late detection of underflow. This shouldn't happen.\n";
thiz->attenuate((int16_t *)stream, len);
if (LOG_SOUND)
util::stream_format(*thiz->sound_log, "callback: xfer DS=%u FS=%u Len=%d\n", data_size, free_size, len);
}
//============================================================
// sound_sdl::init
//============================================================
int sound_sdl::init(const osd_options &options)
{
int n_channels = 2;
int audio_latency;
SDL_AudioSpec aspec, obtained;
if (LOG_SOUND)
sound_log = std::make_unique<std::ofstream>(SDLMAME_SOUND_LOG);
// skip if sound disabled
if (sample_rate() != 0)
{
if (SDL_InitSubSystem(SDL_INIT_AUDIO))
{
osd_printf_error("Could not initialize SDL %s\n", SDL_GetError());
return -1;
}
osd_printf_verbose("Audio: Start initialization\n");
char const *const audio_driver = SDL_GetCurrentAudioDriver();
osd_printf_verbose("Audio: Driver is %s\n", audio_driver ? audio_driver : "not initialized");
sdl_xfer_samples = SDL_XFER_SAMPLES;
stream_in_initialized = 0;
// set up the audio specs
aspec.freq = sample_rate();
aspec.format = AUDIO_S16SYS; // keep endian independent
aspec.channels = n_channels;
aspec.samples = sdl_xfer_samples;
aspec.callback = sdl_callback;
aspec.userdata = this;
if (SDL_OpenAudio(&aspec, &obtained) < 0)
goto cant_start_audio;
osd_printf_verbose("Audio: frequency: %d, channels: %d, samples: %d\n",
obtained.freq, obtained.channels, obtained.samples);
sdl_xfer_samples = obtained.samples;
// pin audio latency
audio_latency = std::clamp(m_audio_latency, 1, MAX_AUDIO_LATENCY);
// compute the buffer sizes
stream_buffer_size = (sample_rate() * 2 * sizeof(int16_t) * (2 + audio_latency)) / 30;
stream_buffer_size = (stream_buffer_size / 1024) * 1024;
if (stream_buffer_size < 1024)
stream_buffer_size = 1024;
// create the buffers
if (sdl_create_buffers())
goto cant_create_buffers;
// set the startup volume
set_mastervolume(attenuation);
osd_printf_verbose("Audio: End initialization\n");
return 0;
// error handling
cant_create_buffers:
cant_start_audio:
osd_printf_verbose("Audio: Initialization failed. SDL error: %s\n", SDL_GetError());
return -1;
}
return 0;
}
//============================================================
// sdl_kill
//============================================================
void sound_sdl::exit()
{
// if nothing to do, don't do it
if (sample_rate() == 0)
return;
osd_printf_verbose("sdl_kill: closing audio\n");
SDL_CloseAudio();
SDL_QuitSubSystem(SDL_INIT_AUDIO);
// kill the buffers
sdl_destroy_buffers();
// print out over/underflow stats
if (buffer_overflows || buffer_underflows)
osd_printf_verbose("Sound buffer: overflows=%d underflows=%d\n", buffer_overflows, buffer_underflows);
if (LOG_SOUND)
{
util::stream_format(*sound_log, "Sound buffer: overflows=%d underflows=%d\n", buffer_overflows, buffer_underflows);
sound_log.reset();
}
}
//============================================================
// dsound_create_buffers
//============================================================
int sound_sdl::sdl_create_buffers()
{
osd_printf_verbose("sdl_create_buffers: creating stream buffer of %u bytes\n", stream_buffer_size);
stream_buffer = std::make_unique<ring_buffer>(stream_buffer_size);
buf_locked = 0;
return 0;
}
//============================================================
// sdl_destroy_buffers
//============================================================
void sound_sdl::sdl_destroy_buffers()
{
// release the buffer
stream_buffer.reset();
}
#else /* SDLMAME_UNIX */
MODULE_NOT_SUPPORTED(sound_sdl, OSD_SOUND_PROVIDER, "sdl")
#endif
MODULE_DEFINITION(SOUND_SDL, sound_sdl)