diff options
Diffstat (limited to 'src/osd/modules/sound/sdl_sound.cpp')
-rw-r--r-- | src/osd/modules/sound/sdl_sound.cpp | 521 |
1 files changed, 138 insertions, 383 deletions
diff --git a/src/osd/modules/sound/sdl_sound.cpp b/src/osd/modules/sound/sdl_sound.cpp index 7f4993bea35..35c11eae86d 100644 --- a/src/osd/modules/sound/sdl_sound.cpp +++ b/src/osd/modules/sound/sdl_sound.cpp @@ -24,450 +24,205 @@ #include <cmath> #include <fstream> #include <memory> +#include <map> namespace osd { namespace { -//============================================================ -// DEBUGGING -//============================================================ - -#define LOG_SOUND 0 - -#define SDLMAME_SOUND_LOG "sound.log" - - -//============================================================ -// CLASS -//============================================================ - class sound_sdl : public osd_module, public sound_module { public: - - // number of samples per SDL callback - static inline constexpr int SDL_XFER_SAMPLES = 512; - sound_sdl() : - osd_module(OSD_SOUND_PROVIDER, "sdl"), sound_module(), - sample_rate(0), - sdl_xfer_samples(SDL_XFER_SAMPLES), - stream_in_initialized(0), - attenuation(0), - buf_locked(0), - stream_buffer(nullptr), - stream_buffer_size(0), - buffer_underflows(0), - buffer_overflows(0) + osd_module(OSD_SOUND_PROVIDER, "sdl"), sound_module() { } + virtual ~sound_sdl() { } virtual int init(osd_interface &osd, const osd_options &options) override; virtual void exit() override; - // sound_module + virtual bool external_per_channel_volume() override { return false; } + virtual bool split_streams_per_source() override { return false; } - virtual void update_audio_stream(bool is_throttled, const int16_t *buffer, int samples_this_frame) override; - virtual void set_mastervolume(int attenuation) override; + virtual uint32_t get_generation() override; + virtual osd::audio_info get_information() override; + virtual uint32_t stream_sink_open(uint32_t node, std::string name, uint32_t rate) override; + virtual void stream_close(uint32_t id) override; + virtual void stream_sink_update(uint32_t id, const int16_t *buffer, int samples_this_frame) 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; + struct device_info { + std::string m_name; + int m_freq; + uint8_t m_channels; + device_info(const char *name, int freq, uint8_t channels) : m_name(name), m_freq(freq), m_channels(channels) {} }; - 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 sample_rate; - int sdl_xfer_samples; - int stream_in_initialized; - int attenuation; + struct stream_info { + uint32_t m_id; + SDL_AudioDeviceID m_sdl_id; + abuffer m_buffer; + stream_info(uint32_t id, uint8_t channels) : m_id(id), m_sdl_id(0), m_buffer(channels) {} + }; - int buf_locked; - std::unique_ptr<ring_buffer> stream_buffer; - uint32_t stream_buffer_size; + std::vector<device_info> m_devices; + uint32_t m_default_sink; + uint32_t m_stream_next_id; + std::map<uint32_t, std::unique_ptr<stream_info>> m_streams; - // diagnostics - int buffer_underflows; - int buffer_overflows; - std::unique_ptr<std::ofstream> sound_log; + static void sink_callback(void *userdata, Uint8 *stream, int len); }; - -//============================================================ -// 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 +// sound_sdl::init //============================================================ -int sound_sdl::ring_buffer::append(const void *data, size_t size) +int sound_sdl::init(osd_interface &osd, const osd_options &options) { - if (free_size() < size) - return -1; + m_stream_next_id = 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) + if(SDL_InitSubSystem(SDL_INIT_AUDIO)) { + osd_printf_error("Could not initialize SDL %s\n", SDL_GetError()); 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--; + 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"); + + // Capture is not implemented in SDL2, and the enumeration + // interface is different in SDL3 + int dev_count = SDL_GetNumAudioDevices(0); + for(int i=0; i != dev_count; i++) { + SDL_AudioSpec spec; + const char *name = SDL_GetAudioDeviceName(i, 0); + int err = SDL_GetAudioDeviceSpec(i, 0, &spec); + if(!err) + m_devices.emplace_back(name, spec.freq, spec.channels); } + char *def_name; + SDL_AudioSpec def_spec; + if(!SDL_GetDefaultAudioInfo(&def_name, &def_spec, 0)) { + uint32_t idx; + for(idx = 0; idx != m_devices.size() && m_devices[idx].m_name != def_name; idx++); + if(idx == m_devices.size()) + m_devices.emplace_back(def_name, def_spec.freq, def_spec.channels); + m_default_sink = idx+1; + SDL_free(def_name); + } else + m_default_sink = 0; + return 0; } -//============================================================ -// 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) +void sound_sdl::exit() { - // 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); + SDL_QuitSubSystem(SDL_INIT_AUDIO); } - - -//============================================================ -// set_mastervolume -//============================================================ - -void sound_sdl::set_mastervolume(int _attenuation) +uint32_t sound_sdl::get_generation() { - // 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); - } + // sdl2 is not dynamic w.r.t devices + return 1; } -//============================================================ -// sdl_callback -//============================================================ -void sound_sdl::sdl_callback(void *userdata, Uint8 *stream, int len) +osd::audio_info sound_sdl::get_information() { - 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); + enum { FL, FR, FC, LFE, BL, BR, BC, SL, SR }; + static const char *const posname[9] = { "FL", "FR", "FC", "LFE", "BL", "BR", "BC", "SL", "SR" }; + + static std::array<double, 3> pos3d[9] = { + { -0.2, 0.0, 1.0 }, + { 0.2, 0.0, 1.0 }, + { 0.0, 0.0, 1.0 }, + { 0.0, -0.5, 1.0 }, + { -0.2, 0.0, -0.5 }, + { 0.2, 0.0, -0.5 }, + { 0.0, 0.0, -0.5 }, + { -0.2, 0.0, 0.0 }, + { 0.2, 0.0, 0.0 }, + }; + + static const uint32_t positions[8][8] = { + { FC }, + { FL, FR }, + { FL, FR, LFE }, + { FL, FR, BL, BR }, + { FL, FR, LFE, BL, BR }, + { FL, FR, FC, LFE, BL, BR }, + { FL, FR, FC, LFE, BC, SL, SR }, + { FL, FR, FC, LFE, BL, BR, SL, SR } + }; - // Maybe read whatever is left in the stream_buffer anyway? - memset(stream, 0, len); - return; + osd::audio_info result; + result.m_nodes.resize(m_devices.size()); + result.m_default_sink = m_default_sink; + result.m_default_source = 0; + result.m_generation = 1; + for(uint32_t node = 0; node != m_devices.size(); node++) { + result.m_nodes[node].m_name = m_devices[node].m_name; + result.m_nodes[node].m_id = node + 1; + uint32_t freq = m_devices[node].m_freq; + result.m_nodes[node].m_rate = audio_rate_range{ freq, freq, freq }; + result.m_nodes[node].m_sinks = m_devices[node].m_channels; + for(uint32_t port = 0; port != m_devices[node].m_channels; port++) { + uint32_t pos = positions[m_devices[node].m_channels-1][port]; + result.m_nodes[node].m_port_names.push_back(posname[pos]); + result.m_nodes[node].m_port_positions.push_back(pos3d[pos]); + } } - - 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); + return result; } - -//============================================================ -// sound_sdl::init -//============================================================ - -int sound_sdl::init(osd_interface &osd, const osd_options &options) +uint32_t sound_sdl::stream_sink_open(uint32_t node, std::string name, uint32_t rate) { - 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 - sample_rate = options.sample_rate(); - 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(options.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"); + device_info &dev = m_devices[node-1]; + std::unique_ptr<stream_info> stream = std::make_unique<stream_info>(m_stream_next_id ++, dev.m_channels); + + SDL_AudioSpec dspec, ospec; + dspec.freq = rate; + dspec.format = AUDIO_S16SYS; + dspec.channels = dev.m_channels; + dspec.samples = 512; + dspec.callback = sink_callback; + dspec.userdata = stream.get(); + + stream->m_sdl_id = SDL_OpenAudioDevice(dev.m_name.c_str(), 0, &dspec, &ospec, 0); + if(!stream->m_sdl_id) 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_PauseAudioDevice(stream->m_sdl_id, 0); + uint32_t id = stream->m_id; + m_streams[stream->m_id] = std::move(stream); + return id; } - - -//============================================================ -// sdl_kill -//============================================================ - -void sound_sdl::exit() +void sound_sdl::stream_close(uint32_t id) { - // if nothing to do, don't do it - if (sample_rate == 0) + auto si = m_streams.find(id); + if(si == m_streams.end()) 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(); - } + SDL_CloseAudioDevice(si->second->m_sdl_id); + m_streams.erase(si); } - - -//============================================================ -// dsound_create_buffers -//============================================================ - -int sound_sdl::sdl_create_buffers() +void sound_sdl::stream_sink_update(uint32_t id, const int16_t *buffer, int samples_this_frame) { - 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; + auto si = m_streams.find(id); + if(si == m_streams.end()) + return; + stream_info *stream = si->second.get(); + SDL_LockAudioDevice(stream->m_sdl_id); + stream->m_buffer.push(buffer, samples_this_frame); + SDL_UnlockAudioDevice(stream->m_sdl_id); } -//============================================================ -// sdl_destroy_buffers -//============================================================ - -void sound_sdl::sdl_destroy_buffers() +void sound_sdl::sink_callback(void *userdata, uint8_t *data, int len) { - // release the buffer - stream_buffer.reset(); + stream_info *stream = reinterpret_cast<stream_info *>(userdata); + stream->m_buffer.get((int16_t *)data, len / 2 / stream->m_buffer.channels()); } } // anonymous namespace |