diff options
Diffstat (limited to 'src/osd/modules/sound')
-rw-r--r-- | src/osd/modules/sound/coreaudio_sound.cpp | 244 | ||||
-rw-r--r-- | src/osd/modules/sound/direct_sound.cpp | 459 | ||||
-rw-r--r-- | src/osd/modules/sound/js_sound.cpp | 32 | ||||
-rw-r--r-- | src/osd/modules/sound/js_sound.js | 38 | ||||
-rw-r--r-- | src/osd/modules/sound/none.cpp | 23 | ||||
-rw-r--r-- | src/osd/modules/sound/pa_sound.cpp | 89 | ||||
-rw-r--r-- | src/osd/modules/sound/pipewire_sound.cpp | 823 | ||||
-rw-r--r-- | src/osd/modules/sound/pulse_sound.cpp | 533 | ||||
-rw-r--r-- | src/osd/modules/sound/sdl_sound.cpp | 531 | ||||
-rw-r--r-- | src/osd/modules/sound/sound_module.cpp | 61 | ||||
-rw-r--r-- | src/osd/modules/sound/sound_module.h | 73 | ||||
-rw-r--r-- | src/osd/modules/sound/xaudio2_sound.cpp | 180 |
12 files changed, 2125 insertions, 961 deletions
diff --git a/src/osd/modules/sound/coreaudio_sound.cpp b/src/osd/modules/sound/coreaudio_sound.cpp index d43d47a114f..7e9814af117 100644 --- a/src/osd/modules/sound/coreaudio_sound.cpp +++ b/src/osd/modules/sound/coreaudio_sound.cpp @@ -8,10 +8,11 @@ #include "sound_module.h" #include "modules/osdmodule.h" -#include "modules/lib/osdobj_common.h" #ifdef SDLMAME_MACOSX +#include "modules/lib/osdobj_common.h" + #include <AvailabilityMacros.h> #include <AudioToolbox/AudioToolbox.h> #include <AudioUnit/AudioUnit.h> @@ -19,19 +20,14 @@ #include <CoreFoundation/CoreFoundation.h> #include <CoreServices/CoreServices.h> -#include <string.h> - - -#ifdef MAC_OS_X_VERSION_MAX_ALLOWED - -#if MAC_OS_X_VERSION_MAX_ALLOWED < 1060 +#include <memory> +#include <new> +#include <cstring> -typedef ComponentDescription AudioComponentDescription; -#endif // MAC_OS_X_VERSION_MAX_ALLOWED < 1060 - -#endif // MAC_OS_X_VERSION_MAX_ALLOWED +namespace osd { +namespace { class sound_coreaudio : public osd_module, public sound_module { @@ -41,14 +37,15 @@ public: sound_module(), m_graph(nullptr), m_node_count(0), + m_sample_rate(0), + m_audio_latency(0), m_sample_bytes(0), m_headroom(0), m_buffer_size(0), - m_buffer(nullptr), + m_buffer(), m_playpos(0), m_writepos(0), m_in_underrun(false), - m_scale(128), m_overflows(0), m_underflows(0) { @@ -57,13 +54,12 @@ public: { } - virtual int init(osd_options const &options) override; + virtual int init(osd_interface &osd, osd_options const &options) override; virtual void exit() override; // sound_module - virtual void update_audio_stream(bool is_throttled, int16_t const *buffer, int samples_this_frame) override; - virtual void set_mastervolume(int attenuation) override; + virtual void stream_sink_update(uint32_t, int16_t const *buffer, int samples_this_frame) override; private: struct node_detail @@ -81,18 +77,10 @@ private: EFFECT_COUNT_MAX = 10 }; - uint32_t clamped_latency() const { return unsigned(std::max(std::min(m_audio_latency, int(LATENCY_MAX)), int(LATENCY_MIN))); } + uint32_t clamped_latency() const { return unsigned(std::clamp<int>(m_audio_latency, LATENCY_MIN, LATENCY_MAX)); } uint32_t buffer_avail() const { return ((m_writepos >= m_playpos) ? m_buffer_size : 0) + m_playpos - m_writepos; } uint32_t 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_t bytes) const - { - bytes /= sizeof(int16_t); - int16_t const *s = (int16_t const *)src; - for (int16_t *d = (int16_t *)dst; bytes > 0; bytes--, s++, d++) - *d = (*s * m_scale) >> 7; - } - bool create_graph(osd_options const &options); bool add_output(char const *name); bool add_device_output(char const *name); @@ -125,8 +113,8 @@ private: } bool get_output_device_id(char const *name, AudioDeviceID &id) const; - char *get_device_uid(AudioDeviceID id) const; - char *get_device_name(AudioDeviceID id) const; + std::unique_ptr<char []> get_device_uid(AudioDeviceID id) const; + std::unique_ptr<char []> get_device_name(AudioDeviceID id) const; UInt32 get_output_stream_count( AudioDeviceID id, char const *uid, @@ -141,17 +129,14 @@ private: CFPropertyListRef &class_info) const; CFPropertyListRef load_property_list(char const *name) const; - char *convert_cfstring_to_utf8(CFStringRef str) const + std::unique_ptr<char []> convert_cfstring_to_utf8(CFStringRef str) const { CFIndex const len = CFStringGetMaximumSizeForEncoding( CFStringGetLength(str), kCFStringEncodingUTF8); - char *const result = global_alloc_array_clear<char>(len + 1); - if (!CFStringGetCString(str, result, len + 1, kCFStringEncodingUTF8)) - { - global_free_array(result); - return nullptr; - } + std::unique_ptr<char []> result = std::make_unique<char []>(len + 1); + if (!CFStringGetCString(str, result.get(), len + 1, kCFStringEncodingUTF8)) + result.reset(); return result; } @@ -174,25 +159,28 @@ private: unsigned m_node_count; node_detail m_node_details[EFFECT_COUNT_MAX + 2]; - uint32_t m_sample_bytes; - uint32_t m_headroom; - uint32_t m_buffer_size; - int8_t *m_buffer; - uint32_t m_playpos; - uint32_t m_writepos; + int m_sample_rate; + int m_audio_latency; + uint32_t m_sample_bytes; + uint32_t m_headroom; + uint32_t m_buffer_size; + std::unique_ptr<int8_t []> m_buffer; + uint32_t m_playpos; + uint32_t m_writepos; bool m_in_underrun; - int32_t m_scale; unsigned m_overflows; unsigned m_underflows; }; -int sound_coreaudio::init(const osd_options &options) +int sound_coreaudio::init(osd_interface &osd, const osd_options &options) { OSStatus err; // Don't bother with any of this if sound is disabled - if (sample_rate() == 0) + m_sample_rate = options.sample_rate(); + m_audio_latency = options.audio_latency(); + if (m_sample_rate == 0) return 0; // Create the output graph @@ -202,7 +190,7 @@ int sound_coreaudio::init(const osd_options &options) // Set audio stream format for two-channel native-endian 16-bit packed linear PCM AudioStreamBasicDescription format; - format.mSampleRate = sample_rate(); + format.mSampleRate = m_sample_rate; format.mFormatID = kAudioFormatLinearPCM; format.mFormatFlags = kAudioFormatFlagsNativeEndian | kLinearPCMFormatFlagIsSignedInteger @@ -227,10 +215,13 @@ int sound_coreaudio::init(const osd_options &options) m_sample_bytes = format.mBytesPerFrame; // Allocate buffer - m_headroom = m_sample_bytes * (clamped_latency() * sample_rate() / 40); - m_buffer_size = m_sample_bytes * std::max<uint32_t>(sample_rate() * (clamped_latency() + 3) / 40, 256U); - m_buffer = global_alloc_array_clear<int8_t>(m_buffer_size); - if (!m_buffer) + m_headroom = m_sample_bytes * (clamped_latency() * m_sample_rate / 40); + m_buffer_size = m_sample_bytes * std::max<uint32_t>(m_sample_rate * (clamped_latency() + 3) / 40, 256U); + try + { + m_buffer = std::make_unique<int8_t []>(m_buffer_size); + } + catch (std::bad_alloc const &) { osd_printf_error("Could not allocate stream buffer\n"); goto close_graph_and_return_error; @@ -238,7 +229,6 @@ int sound_coreaudio::init(const osd_options &options) m_playpos = 0; m_writepos = m_headroom; m_in_underrun = false; - m_scale = 128; m_overflows = m_underflows = 0; // Initialise and start @@ -246,23 +236,21 @@ int sound_coreaudio::init(const osd_options &options) if (noErr != err) { osd_printf_error("Could not initialize AudioUnit graph (%ld)\n", (long)err); - goto free_buffer_and_return_error; + goto close_graph_and_return_error; } err = AUGraphStart(m_graph); if (noErr != err) { osd_printf_error("Could not start AudioUnit graph (%ld)\n", (long)err); AUGraphUninitialize(m_graph); - goto free_buffer_and_return_error; + goto close_graph_and_return_error; } osd_printf_verbose("Audio: End initialization\n"); return 0; -free_buffer_and_return_error: - global_free_array(m_buffer); - m_buffer_size = 0; - m_buffer = nullptr; close_graph_and_return_error: + m_buffer_size = 0; + m_buffer.reset(); AUGraphClose(m_graph); DisposeAUGraph(m_graph); m_graph = nullptr; @@ -283,20 +271,16 @@ void sound_coreaudio::exit() m_graph = nullptr; m_node_count = 0; } - if (m_buffer) - { - global_free_array(m_buffer); - m_buffer = nullptr; - } + m_buffer.reset(); if (m_overflows || m_underflows) osd_printf_verbose("Sound buffer: overflows=%u underflows=%u\n", m_overflows, m_underflows); osd_printf_verbose("Audio: End deinitialization\n"); } -void sound_coreaudio::update_audio_stream(bool is_throttled, int16_t const *buffer, int samples_this_frame) +void sound_coreaudio::stream_sink_update(uint32_t, int16_t const *buffer, int samples_this_frame) { - if ((sample_rate() == 0) || !m_buffer) + if ((m_sample_rate == 0) || !m_buffer) return; uint32_t const bytes_this_frame = samples_this_frame * m_sample_bytes; @@ -307,7 +291,7 @@ void sound_coreaudio::update_audio_stream(bool is_throttled, int16_t const *buff } uint32_t const chunk = std::min(m_buffer_size - m_writepos, bytes_this_frame); - memcpy(m_buffer + m_writepos, (int8_t *)buffer, chunk); + memcpy(&m_buffer[m_writepos], (int8_t *)buffer, chunk); m_writepos += chunk; if (m_writepos >= m_buffer_size) m_writepos = 0; @@ -316,19 +300,12 @@ void sound_coreaudio::update_audio_stream(bool is_throttled, int16_t const *buff { assert(0U == m_writepos); assert(m_playpos > (bytes_this_frame - chunk)); - memcpy(m_buffer, (int8_t *)buffer + chunk, bytes_this_frame - chunk); + memcpy(&m_buffer[0], (int8_t *)buffer + chunk, bytes_this_frame - chunk); m_writepos += bytes_this_frame - chunk; } } -void sound_coreaudio::set_mastervolume(int attenuation) -{ - int const clamped_attenuation = std::max(std::min(attenuation, 0), -32); - m_scale = (-32 == clamped_attenuation) ? 0 : (int32_t)(pow(10.0, clamped_attenuation / 20.0) * 128); -} - - bool sound_coreaudio::create_graph(osd_options const &options) { OSStatus err; @@ -648,7 +625,7 @@ bool sound_coreaudio::get_output_device_id( return false; } property_size /= sizeof(AudioDeviceID); - AudioDeviceID *const devices = global_alloc_array_clear<AudioDeviceID>(property_size); + std::unique_ptr<AudioDeviceID []> const devices = std::make_unique<AudioDeviceID []>(property_size); property_size *= sizeof(AudioDeviceID); err = AudioObjectGetPropertyData( kAudioObjectSystemObject, @@ -656,20 +633,19 @@ bool sound_coreaudio::get_output_device_id( 0, nullptr, &property_size, - devices); + devices.get()); UInt32 const device_count = property_size / sizeof(AudioDeviceID); if (noErr != err) { osd_printf_error("Error getting audio device list (%ld)\n", (long)err); - global_free_array(devices); return false; } for (UInt32 i = 0; device_count > i; i++) { - char *const device_uid = get_device_uid(devices[i]); - char *const device_name = get_device_name(devices[i]); - if ((nullptr == device_uid) && (nullptr == device_name)) + std::unique_ptr<char []> const device_uid = get_device_uid(devices[i]); + std::unique_ptr<char []> const device_name = get_device_name(devices[i]); + if (!device_uid && !device_name) { osd_printf_warning( "Could not get UID or name for device %lu - skipping\n", @@ -679,52 +655,46 @@ bool sound_coreaudio::get_output_device_id( UInt32 const streams = get_output_stream_count( devices[i], - device_uid, - device_name); + device_uid.get(), + device_name.get()); if (1U > streams) { osd_printf_verbose( "No output streams found for device %s (%s) - skipping\n", - (nullptr != device_name) ? device_name : "<anonymous>", - (nullptr != device_uid) ? device_uid : "<unknown>"); - if (nullptr != device_uid) global_free_array(device_uid); - if (nullptr != device_name) global_free_array(device_name); + device_name ? device_name.get() : "<anonymous>", + device_uid ? device_uid.get() : "<unknown>"); continue; } - for (std::size_t j = strlen(device_uid); (0 < j) && (' ' == device_uid[j - 1]); j--) + for (std::size_t j = strlen(device_uid.get()); (0 < j) && (' ' == device_uid[j - 1]); j--) device_uid[j - 1] = '\0'; - for (std::size_t j = strlen(device_name); (0 < j) && (' ' == device_name[j - 1]); j--) + for (std::size_t j = strlen(device_name.get()); (0 < j) && (' ' == device_name[j - 1]); j--) device_name[j - 1] = '\0'; - bool const matched_uid = (nullptr != device_uid) && !strcmp(name, device_uid); - bool const matched_name = (nullptr != device_name) && !strcmp(name, device_name); + bool const matched_uid = device_uid && !strcmp(name, device_uid.get()); + bool const matched_name = device_name && !strcmp(name, device_name.get()); if (matched_uid || matched_name) { osd_printf_verbose( "Matched device %s (%s) with %lu output stream(s)\n", - (nullptr != device_name) ? device_name : "<anonymous>", - (nullptr != device_uid) ? device_uid : "<unknown>", + device_name ? device_name.get() : "<anonymous>", + device_uid ? device_uid.get() : "<unknown>", (unsigned long)streams); } - global_free_array(device_uid); - global_free_array(device_name); if (matched_uid || matched_name) { id = devices[i]; - global_free_array(devices); return true; } } osd_printf_verbose("No audio output devices match %s\n", name); - global_free_array(devices); return false; } -char *sound_coreaudio::get_device_uid(AudioDeviceID id) const +std::unique_ptr<char []> sound_coreaudio::get_device_uid(AudioDeviceID id) const { AudioObjectPropertyAddress const uid_addr = { kAudioDevicePropertyDeviceUID, @@ -747,9 +717,9 @@ char *sound_coreaudio::get_device_uid(AudioDeviceID id) const (long)err); return nullptr; } - char *const result = convert_cfstring_to_utf8(device_uid); + std::unique_ptr<char []> result = convert_cfstring_to_utf8(device_uid); CFRelease(device_uid); - if (nullptr == result) + if (!result) { osd_printf_warning( "Error converting UID for audio device %lu to UTF-8\n", @@ -759,7 +729,7 @@ char *sound_coreaudio::get_device_uid(AudioDeviceID id) const } -char *sound_coreaudio::get_device_name(AudioDeviceID id) const +std::unique_ptr<char []> sound_coreaudio::get_device_name(AudioDeviceID id) const { AudioObjectPropertyAddress const name_addr = { kAudioDevicePropertyDeviceNameCFString, @@ -782,9 +752,9 @@ char *sound_coreaudio::get_device_name(AudioDeviceID id) const (long)err); return nullptr; } - char *const result = convert_cfstring_to_utf8(device_name); + std::unique_ptr<char []> result = convert_cfstring_to_utf8(device_name); CFRelease(device_name); - if (nullptr == result) + if (!result) { osd_printf_warning( "Error converting name for audio device %lu to UTF-8\n", @@ -913,51 +883,48 @@ CFPropertyListRef sound_coreaudio::load_property_list(char const *name) const (UInt8 const *)name, strlen(name), false); - if (nullptr == url) - { + if (!url) return nullptr; - } - CFDataRef data = nullptr; - SInt32 err; - Boolean const status = CFURLCreateDataAndPropertiesFromResource( - nullptr, - url, - &data, - nullptr, - nullptr, - &err); + CFReadStreamRef const stream = CFReadStreamCreateWithFile(nullptr, url); CFRelease(url); - if (!status) + if (!stream) { - osd_printf_error( - "Error reading data from %s (%ld)\n", - name, - (long)err); - if (nullptr != data) CFRelease(data); + osd_printf_error("Error opening file %s\n", name); + return nullptr; + } + if (!CFReadStreamOpen(stream)) + { + CFRelease(stream); + osd_printf_error("Error opening file %s\n", name); return nullptr; } - CFStringRef msg = nullptr; - CFPropertyListRef const result = CFPropertyListCreateFromXMLData( + CFErrorRef msg = nullptr; + CFPropertyListRef const result = CFPropertyListCreateWithStream( nullptr, - data, + stream, + 0, kCFPropertyListImmutable, + nullptr, &msg); - CFRelease(data); - if ((nullptr == result) || (nullptr != msg)) - { - char *buf = (nullptr != msg) ? convert_cfstring_to_utf8(msg) : nullptr; - if (nullptr != msg) + CFReadStreamClose(stream); + CFRelease(stream); + if (!result || msg) + { + CFStringRef const desc = msg ? CFErrorCopyDescription(msg) : nullptr; + std::unique_ptr<char []> const buf = desc ? convert_cfstring_to_utf8(desc) : nullptr; + if (desc) + CFRelease(desc); + if (msg) CFRelease(msg); - if (nullptr != buf) + if (buf) { osd_printf_error( "Error creating property list from %s: %s\n", name, - buf); - global_free_array(buf); + buf.get()); } else { @@ -965,7 +932,8 @@ CFPropertyListRef sound_coreaudio::load_property_list(char const *name) const "Error creating property list from %s\n", name); } - if (nullptr != result) CFRelease(result); + if (result) + CFRelease(result); return nullptr; } @@ -997,7 +965,7 @@ OSStatus sound_coreaudio::render( } uint32_t const chunk = std::min(m_buffer_size - m_playpos, number_bytes); - copy_scaled((int8_t *)data->mBuffers[0].mData, m_buffer + m_playpos, chunk); + memcpy((int8_t *)data->mBuffers[0].mData, &m_buffer[m_playpos], chunk); m_playpos += chunk; if (m_playpos >= m_buffer_size) m_playpos = 0; @@ -1006,7 +974,7 @@ OSStatus sound_coreaudio::render( { assert(0U == m_playpos); assert(m_writepos >= (number_bytes - chunk)); - copy_scaled((int8_t *)data->mBuffers[0].mData + chunk, m_buffer, number_bytes - chunk); + memcpy((int8_t *)data->mBuffers[0].mData + chunk, &m_buffer[0], number_bytes - chunk); m_playpos += number_bytes - chunk; } @@ -1025,8 +993,14 @@ OSStatus sound_coreaudio::render_callback( 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 +} // anonymous namespace + +} // namespace osd + +#else // SDLMAME_MACOSX + +namespace osd { namespace { MODULE_NOT_SUPPORTED(sound_coreaudio, OSD_SOUND_PROVIDER, "coreaudio") } } + +#endif // SDLMAME_MACOSX -MODULE_DEFINITION(SOUND_COREAUDIO, sound_coreaudio) +MODULE_DEFINITION(SOUND_COREAUDIO, osd::sound_coreaudio) diff --git a/src/osd/modules/sound/direct_sound.cpp b/src/osd/modules/sound/direct_sound.cpp index 4d6c8c26c50..7417def934c 100644 --- a/src/osd/modules/sound/direct_sound.cpp +++ b/src/osd/modules/sound/direct_sound.cpp @@ -11,236 +11,235 @@ #if defined(OSD_WINDOWS) || defined(SDLMAME_WIN32) -// standard windows headers -#include <windows.h> -#include <mmsystem.h> - -// undef WINNT for dsound.h to prevent duplicate definition -#undef WINNT -#include <dsound.h> -#undef interface - // MAME headers -#include "emu.h" -#include "osdepend.h" #include "emuopts.h" +// osd headers +#include "modules/lib/osdobj_common.h" +#include "osdepend.h" +#include "osdcore.h" + #ifdef SDLMAME_WIN32 -#include "../../sdl/osdsdl.h" +#include "sdl/window.h" #include <SDL2/SDL_syswm.h> -#include "../../sdl/window.h" #else #include "winmain.h" #include "window.h" #endif + #include <utility> +// standard windows headers + +#include <windows.h> + +#include <mmreg.h> +#include <mmsystem.h> + +#include <dsound.h> + +#include <wrl/client.h> + + //============================================================ // DEBUGGING //============================================================ #define LOG_SOUND 0 -#define LOG(x) do { if (LOG_SOUND) osd_printf_verbose x; } while(0) +#define LOG(...) do { if (LOG_SOUND) osd_printf_verbose(__VA_ARGS__); } while(0) -class sound_direct_sound : public osd_module, public sound_module +namespace osd { + +namespace { + +class buffer_base { public: + explicit operator bool() const { return bool(m_buffer); } - sound_direct_sound() : - osd_module(OSD_SOUND_PROVIDER, "dsound"), - sound_module(), - m_dsound(nullptr), - m_bytes_per_sample(0), - m_primary_buffer(), - m_stream_buffer(), - m_stream_buffer_in(0), - m_buffer_underflows(0), - m_buffer_overflows(0) - { - } - virtual ~sound_direct_sound() { } + unsigned long release() { return m_buffer.Reset(); } - virtual int init(osd_options const &options) override; - virtual void exit() override; +protected: + Microsoft::WRL::ComPtr<IDirectSoundBuffer> m_buffer; +}; - // sound_module - virtual void update_audio_stream(bool is_throttled, int16_t const *buffer, int samples_this_frame) override; - virtual void set_mastervolume(int attenuation) override; -private: - class buffer +class primary_buffer : public buffer_base +{ +public: + HRESULT create(LPDIRECTSOUND dsound) { - public: - buffer() : m_buffer(nullptr) { } - ~buffer() { release(); } + assert(!m_buffer); + DSBUFFERDESC desc; + memset(&desc, 0, sizeof(desc)); + desc.dwSize = sizeof(desc); + desc.dwFlags = DSBCAPS_PRIMARYBUFFER | DSBCAPS_GETCURRENTPOSITION2; + desc.lpwfxFormat = nullptr; + return dsound->CreateSoundBuffer(&desc, &m_buffer, nullptr); + } - ULONG release() - { - ULONG const result = m_buffer ? m_buffer->Release() : 0; - m_buffer = nullptr; - return result; - } + HRESULT get_format(WAVEFORMATEX &format) const + { + assert(m_buffer); + return m_buffer->GetFormat(&format, sizeof(format), nullptr); + } - operator bool() const { return m_buffer; } + HRESULT set_format(WAVEFORMATEX const &format) const + { + assert(m_buffer); + return m_buffer->SetFormat(&format); + } +}; - protected: - LPDIRECTSOUNDBUFFER m_buffer; - }; - class primary_buffer : public buffer +class stream_buffer : public buffer_base +{ +public: + HRESULT create(LPDIRECTSOUND dsound, DWORD size, WAVEFORMATEX &format) { - public: - HRESULT create(LPDIRECTSOUND dsound) - { - assert(!m_buffer); - DSBUFFERDESC desc; - memset(&desc, 0, sizeof(desc)); - desc.dwSize = sizeof(desc); - desc.dwFlags = DSBCAPS_PRIMARYBUFFER | DSBCAPS_GETCURRENTPOSITION2; - desc.lpwfxFormat = nullptr; - return dsound->CreateSoundBuffer(&desc, &m_buffer, nullptr); - } + assert(!m_buffer); + DSBUFFERDESC desc; + memset(&desc, 0, sizeof(desc)); + desc.dwSize = sizeof(desc); + desc.dwFlags = DSBCAPS_CTRLVOLUME | DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2; + desc.dwBufferBytes = size; + desc.lpwfxFormat = &format; + m_size = size; + return dsound->CreateSoundBuffer(&desc, &m_buffer, nullptr); + } - HRESULT get_format(WAVEFORMATEX &format) const - { - assert(m_buffer); - return m_buffer->GetFormat(&format, sizeof(format), nullptr); - } - HRESULT set_format(WAVEFORMATEX const &format) const + HRESULT play_looping() const + { + assert(m_buffer); + return m_buffer->Play(0, 0, DSBPLAY_LOOPING); + } + HRESULT stop() const + { + assert(m_buffer); + return m_buffer->Stop(); + } + HRESULT get_current_positions(DWORD &play_pos, DWORD &write_pos) const + { + assert(m_buffer); + return m_buffer->GetCurrentPosition(&play_pos, &write_pos); + } + HRESULT copy_data(DWORD cursor, DWORD bytes, void const *data) + { + HRESULT result = lock(cursor, bytes); + if (DS_OK != result) + return result; + + assert(m_bytes1); + assert((m_locked1 + m_locked2) >= bytes); + memcpy(m_bytes1, data, std::min(m_locked1, bytes)); + if (m_locked1 < bytes) { - assert(m_buffer); - return m_buffer->SetFormat(&format); + assert(m_bytes2); + memcpy(m_bytes2, (uint8_t const *)data + m_locked1, bytes - m_locked1); } - }; - class stream_buffer : public buffer + unlock(); + return DS_OK; + } + HRESULT clear() { - public: - stream_buffer() : m_size(0), m_bytes1(nullptr), m_bytes2(nullptr), m_locked1(0), m_locked2(0) { } + HRESULT result = lock_all(); + if (DS_OK != result) + return result; - HRESULT create(LPDIRECTSOUND dsound, DWORD size, WAVEFORMATEX &format) - { - assert(!m_buffer); - DSBUFFERDESC desc; - memset(&desc, 0, sizeof(desc)); - desc.dwSize = sizeof(desc); - desc.dwFlags = DSBCAPS_CTRLVOLUME | DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2; - desc.dwBufferBytes = size; - desc.lpwfxFormat = &format; - m_size = size; - return dsound->CreateSoundBuffer(&desc, &m_buffer, nullptr); - } + assert(m_bytes1); + assert(!m_bytes2); + assert(m_size == m_locked1); + assert(0U == m_locked2); + memset(m_bytes1, 0, m_locked1); - HRESULT play_looping() const - { - assert(m_buffer); - return m_buffer->Play(0, 0, DSBPLAY_LOOPING); - } - HRESULT stop() const - { - assert(m_buffer); - return m_buffer->Stop(); - } - HRESULT set_volume(LONG volume) const - { - assert(m_buffer); - return m_buffer->SetVolume(volume); - } - HRESULT set_min_volume() { return set_volume(DSBVOLUME_MIN); } + unlock(); + return DS_OK; + } - HRESULT get_current_positions(DWORD &play_pos, DWORD &write_pos) const - { - assert(m_buffer); - return m_buffer->GetCurrentPosition(&play_pos, &write_pos); - } - HRESULT copy_data(DWORD cursor, DWORD bytes, void const *data) - { - HRESULT result = lock(cursor, bytes); - if (DS_OK != result) - return result; - - assert(m_bytes1); - assert((m_locked1 + m_locked2) >= bytes); - memcpy(m_bytes1, data, std::min(m_locked1, bytes)); - if (m_locked1 < bytes) - { - assert(m_bytes2); - memcpy(m_bytes2, (uint8_t const *)data + m_locked1, bytes - m_locked1); - } - - unlock(); - return DS_OK; - } - HRESULT clear() - { - HRESULT result = lock_all(); - if (DS_OK != result) - return result; - - assert(m_bytes1); - assert(!m_bytes2); - assert(m_size == m_locked1); - assert(0U == m_locked2); - memset(m_bytes1, 0, m_locked1); - - unlock(); - return DS_OK; - } + DWORD size() const { return m_size; } - DWORD size() const { return m_size; } +private: + HRESULT lock(DWORD cursor, DWORD bytes) + { + assert(cursor < m_size); + assert(bytes <= m_size); + assert(m_buffer); + assert(!m_bytes1); + return m_buffer->Lock( + cursor, bytes, + &m_bytes1, + &m_locked1, + &m_bytes2, + &m_locked2, + 0); + } + HRESULT lock_all() { return lock(0, m_size); } + HRESULT unlock() + { + assert(m_buffer); + assert(m_bytes1); + HRESULT const result = m_buffer->Unlock( + m_bytes1, + m_locked1, + m_bytes2, + m_locked2); + m_bytes1 = m_bytes2 = nullptr; + m_locked1 = m_locked2 = 0; + return result; + } - protected: - HRESULT lock(DWORD cursor, DWORD bytes) - { - assert(cursor < m_size); - assert(bytes <= m_size); - assert(m_buffer); - assert(!m_bytes1); - return m_buffer->Lock( - cursor, bytes, - &m_bytes1, - &m_locked1, - &m_bytes2, - &m_locked2, - 0); - } - HRESULT lock_all() { return lock(0, m_size); } - HRESULT unlock() - { - assert(m_buffer); - assert(m_bytes1); - HRESULT const result = m_buffer->Unlock( - m_bytes1, - m_locked1, - m_bytes2, - m_locked2); - m_bytes1 = m_bytes2 = nullptr; - m_locked1 = m_locked2 = 0; - return result; - } + DWORD m_size = 0; + void *m_bytes1 = nullptr, *m_bytes2 = nullptr; + DWORD m_locked1 = 0, m_locked2 = 0; +}; - DWORD m_size; - void *m_bytes1, *m_bytes2; - DWORD m_locked1, m_locked2; - }; +class sound_direct_sound : public osd_module, public sound_module +{ +public: + sound_direct_sound() : + osd_module(OSD_SOUND_PROVIDER, "dsound"), + sound_module(), + m_sample_rate(0), + m_audio_latency(0), + m_bytes_per_sample(0), + m_primary_buffer(), + m_stream_buffer(), + m_stream_buffer_in(0), + m_buffer_underflows(0), + m_buffer_overflows(0) + { + } + + virtual int init(osd_interface &osd, osd_options const &options) override; + virtual void exit() override; + + // sound_module + virtual void stream_sink_update(uint32_t, int16_t const *buffer, int samples_this_frame) override; + +private: HRESULT dsound_init(); void dsound_kill(); HRESULT create_buffers(DWORD size, WAVEFORMATEX &format); void destroy_buffers(); // DirectSound objects - LPDIRECTSOUND m_dsound; + Microsoft::WRL::ComPtr<IDirectSound> m_dsound; + + // configuration + int m_sample_rate; + int m_audio_latency; // descriptors and formats - uint32_t m_bytes_per_sample; + uint32_t m_bytes_per_sample; // sound buffers primary_buffer m_primary_buffer; stream_buffer m_stream_buffer; - uint32_t m_stream_buffer_in; + uint32_t m_stream_buffer_in; // buffer over/underflow counts unsigned m_buffer_underflows; @@ -252,12 +251,16 @@ private: // init //============================================================ -int sound_direct_sound::init(osd_options const &options) +int sound_direct_sound::init(osd_interface &osd, osd_options const &options) { - // attempt to initialize directsound - // don't make it fatal if we can't -- we'll just run without sound - dsound_init(); + m_sample_rate = options.sample_rate(); + m_audio_latency = options.audio_latency(); m_buffer_underflows = m_buffer_overflows = 0; + + // attempt to initialize DirectSound + if (dsound_init() != DS_OK) + return -1; + return 0; } @@ -281,16 +284,16 @@ void sound_direct_sound::exit() m_buffer_underflows); } - LOG(("Sound buffer: overflows=%u underflows=%u\n", m_buffer_overflows, m_buffer_underflows)); + LOG("Sound buffer: overflows=%u underflows=%u\n", m_buffer_overflows, m_buffer_underflows); } //============================================================ -// update_audio_stream +// stream_sink_update //============================================================ -void sound_direct_sound::update_audio_stream( - bool is_throttled, +void sound_direct_sound::stream_sink_update( + uint32_t, int16_t const *buffer, int samples_this_frame) { @@ -307,7 +310,7 @@ void sound_direct_sound::update_audio_stream( if (DS_OK != result) return; -//DWORD orig_write = write_position; + //DWORD orig_write = write_position; // normalize the write position so it is always after the play position if (write_position < play_position) write_position += m_stream_buffer.size(); @@ -323,7 +326,7 @@ void sound_direct_sound::update_audio_stream( // if we're between play and write positions, then bump forward, but only in full chunks while (stream_in < write_position) { -//printf("Underflow: PP=%d WP=%d(%d) SI=%d(%d) BTF=%d\n", (int)play_position, (int)write_position, (int)orig_write, (int)stream_in, (int)m_stream_buffer_in, (int)bytes_this_frame); + //printf("Underflow: PP=%d WP=%d(%d) SI=%d(%d) BTF=%d\n", (int)play_position, (int)write_position, (int)orig_write, (int)stream_in, (int)m_stream_buffer_in, (int)bytes_this_frame); m_buffer_underflows++; stream_in += bytes_this_frame; } @@ -331,7 +334,7 @@ void sound_direct_sound::update_audio_stream( // if we're going to overlap the play position, just skip this chunk if ((stream_in + bytes_this_frame) > (play_position + m_stream_buffer.size())) { -//printf("Overflow: PP=%d WP=%d(%d) SI=%d(%d) BTF=%d\n", (int)play_position, (int)write_position, (int)orig_write, (int)stream_in, (int)m_stream_buffer_in, (int)bytes_this_frame); + //printf("Overflow: PP=%d WP=%d(%d) SI=%d(%d) BTF=%d\n", (int)play_position, (int)write_position, (int)orig_write, (int)stream_in, (int)m_stream_buffer_in, (int)bytes_this_frame); m_buffer_overflows++; return; } @@ -353,26 +356,6 @@ void sound_direct_sound::update_audio_stream( //============================================================ -// set_mastervolume -//============================================================ - -void sound_direct_sound::set_mastervolume(int attenuation) -{ - // clamp the attenuation to 0-32 range - attenuation = std::max(std::min(attenuation, 0), -32); - - // set the master volume - if (m_stream_buffer) - { - if (-32 == attenuation) - m_stream_buffer.set_min_volume(); - else - m_stream_buffer.set_volume(100 * attenuation); - } -} - - -//============================================================ // dsound_init //============================================================ @@ -385,7 +368,7 @@ HRESULT sound_direct_sound::dsound_init() result = DirectSoundCreate(nullptr, &m_dsound, nullptr); if (result != DS_OK) { - osd_printf_error("Error creating DirectSound: %08x\n", (unsigned)result); + osd_printf_error("Error creating DirectSound: %08x\n", result); goto error; } @@ -395,7 +378,7 @@ HRESULT sound_direct_sound::dsound_init() result = m_dsound->GetCaps(&dsound_caps); if (result != DS_OK) { - osd_printf_error("Error getting DirectSound capabilities: %08x\n", (unsigned)result); + osd_printf_error("Error getting DirectSound capabilities: %08x\n", result); goto error; } @@ -404,34 +387,40 @@ HRESULT sound_direct_sound::dsound_init() #ifdef SDLMAME_WIN32 SDL_SysWMinfo wminfo; SDL_VERSION(&wminfo.version); - SDL_GetWindowWMInfo(std::dynamic_pointer_cast<sdl_window_info>(osd_common_t::s_window_list.front())->platform_window(), &wminfo); + if (!SDL_GetWindowWMInfo(dynamic_cast<sdl_window_info &>(*osd_common_t::window_list().front()).platform_window(), &wminfo)) + { + result = DSERR_UNSUPPORTED; // just so it has something to return + goto error; + } HWND const window = wminfo.info.win.window; #else // SDLMAME_WIN32 - HWND const window = std::static_pointer_cast<win_window_info>(osd_common_t::s_window_list.front())->platform_window(); + HWND const window = dynamic_cast<win_window_info &>(*osd_common_t::window_list().front()).platform_window(); #endif // SDLMAME_WIN32 result = m_dsound->SetCooperativeLevel(window, DSSCL_PRIORITY); } if (result != DS_OK) { - osd_printf_error("Error setting DirectSound cooperative level: %08x\n", (unsigned)result); + osd_printf_error("Error setting DirectSound cooperative level: %08x\n", result); goto error; } { // make a format description for what we want WAVEFORMATEX stream_format; - stream_format.wBitsPerSample = 16; stream_format.wFormatTag = WAVE_FORMAT_PCM; stream_format.nChannels = 2; - stream_format.nSamplesPerSec = sample_rate(); + stream_format.nSamplesPerSec = m_sample_rate; + stream_format.wBitsPerSample = 16; stream_format.nBlockAlign = stream_format.wBitsPerSample * stream_format.nChannels / 8; stream_format.nAvgBytesPerSec = stream_format.nSamplesPerSec * stream_format.nBlockAlign; + stream_format.cbSize = 0; // compute the buffer size based on the output sample rate - DWORD stream_buffer_size = stream_format.nSamplesPerSec * stream_format.nBlockAlign * m_audio_latency / 10; + int audio_latency = std::max(m_audio_latency, 1); + DWORD stream_buffer_size = stream_format.nSamplesPerSec * stream_format.nBlockAlign * audio_latency / 10; stream_buffer_size = std::max(DWORD(1024), (stream_buffer_size / 1024) * 1024); - LOG(("stream_buffer_size = %u\n", (unsigned)stream_buffer_size)); + LOG("stream_buffer_size = %u\n", stream_buffer_size); // create the buffers m_bytes_per_sample = stream_format.nBlockAlign; @@ -445,7 +434,7 @@ HRESULT sound_direct_sound::dsound_init() result = m_stream_buffer.play_looping(); if (result != DS_OK) { - osd_printf_error("Error playing: %08x\n", (uint32_t)result); + osd_printf_error("Error playing: %08x\n", result); goto error; } return DS_OK; @@ -465,9 +454,7 @@ error: void sound_direct_sound::dsound_kill() { // release the object - if (m_dsound) - m_dsound->Release(); - m_dsound = nullptr; + m_dsound.Reset(); } @@ -483,10 +470,10 @@ HRESULT sound_direct_sound::create_buffers(DWORD size, WAVEFORMATEX &format) HRESULT result; // create the primary buffer - result = m_primary_buffer.create(m_dsound); + result = m_primary_buffer.create(m_dsound.Get()); if (result != DS_OK) { - osd_printf_error("Error creating primary DirectSound buffer: %08x\n", (unsigned)result); + osd_printf_error("Error creating primary DirectSound buffer: %08x\n", result); goto error; } @@ -494,7 +481,7 @@ HRESULT sound_direct_sound::create_buffers(DWORD size, WAVEFORMATEX &format) result = m_primary_buffer.set_format(format); if (result != DS_OK) { - osd_printf_error("Error setting primary DirectSound buffer format: %08x\n", (unsigned)result); + osd_printf_error("Error setting primary DirectSound buffer format: %08x\n", result); goto error; } @@ -503,20 +490,20 @@ HRESULT sound_direct_sound::create_buffers(DWORD size, WAVEFORMATEX &format) result = m_primary_buffer.get_format(primary_format); if (result != DS_OK) { - osd_printf_error("Error getting primary DirectSound buffer format: %08x\n", (unsigned)result); + osd_printf_error("Error getting primary DirectSound buffer format: %08x\n", result); goto error; } osd_printf_verbose( "DirectSound: Primary buffer: %d Hz, %d bits, %d channels\n", - (int)primary_format.nSamplesPerSec, - (int)primary_format.wBitsPerSample, - (int)primary_format.nChannels); + primary_format.nSamplesPerSec, + primary_format.wBitsPerSample, + primary_format.nChannels); // create the stream buffer - result = m_stream_buffer.create(m_dsound, size, format); + result = m_stream_buffer.create(m_dsound.Get(), size, format); if (result != DS_OK) { - osd_printf_error("Error creating DirectSound stream buffer: %08x\n", (unsigned)result); + osd_printf_error("Error creating DirectSound stream buffer: %08x\n", result); goto error; } @@ -524,7 +511,7 @@ HRESULT sound_direct_sound::create_buffers(DWORD size, WAVEFORMATEX &format) result = m_stream_buffer.clear(); if (result != DS_OK) { - osd_printf_error("Error locking DirectSound stream buffer: %08x\n", (unsigned)result); + osd_printf_error("Error locking DirectSound stream buffer: %08x\n", result); goto error; } @@ -541,7 +528,7 @@ error: // destroy_buffers //============================================================ -void sound_direct_sound::destroy_buffers(void) +void sound_direct_sound::destroy_buffers() { // stop any playback if (m_stream_buffer) @@ -554,8 +541,16 @@ void sound_direct_sound::destroy_buffers(void) m_primary_buffer.release(); } +} // anonymous namespace + +} // namespace osd + + #else // defined(OSD_WINDOWS) || defined(SDLMAME_WIN32) - MODULE_NOT_SUPPORTED(sound_direct_sound, OSD_SOUND_PROVIDER, "dsound") + +namespace osd { namespace { MODULE_NOT_SUPPORTED(sound_direct_sound, OSD_SOUND_PROVIDER, "dsound") } } + #endif // defined(OSD_WINDOWS) || defined(SDLMAME_WIN32) -MODULE_DEFINITION(SOUND_DSOUND, sound_direct_sound) + +MODULE_DEFINITION(SOUND_DSOUND, osd::sound_direct_sound) diff --git a/src/osd/modules/sound/js_sound.cpp b/src/osd/modules/sound/js_sound.cpp index afcf6ec8f2b..1473ef9de2a 100644 --- a/src/osd/modules/sound/js_sound.cpp +++ b/src/osd/modules/sound/js_sound.cpp @@ -11,7 +11,7 @@ #include "sound_module.h" #include "modules/osdmodule.h" -#if (defined(SDLMAME_EMSCRIPTEN)) +#if defined(SDLMAME_EMSCRIPTEN) #include "emscripten.h" @@ -19,36 +19,30 @@ class sound_js : public osd_module, public sound_module { public: - sound_js() - : osd_module(OSD_SOUND_PROVIDER, "js"), sound_module() + sound_js() : osd_module(OSD_SOUND_PROVIDER, "js"), sound_module() { } virtual ~sound_js() { } - virtual int init(const osd_options &options) { return 0; } + virtual int init(osd_interface &osd, const osd_options &options) { return 0; } virtual void exit() { } // sound_module - virtual void update_audio_stream(bool is_throttled, const int16_t *buffer, int samples_this_frame) + virtual void stream_sink_update(uint32_t, const int16_t *buffer, int samples_this_frame) { - EM_ASM_ARGS({ - // Forward audio stream update on to JS backend implementation. - jsmame_update_audio_stream($0, $1); - }, (unsigned int)buffer, samples_this_frame); + EM_ASM_ARGS( + { + // Forward audio stream update on to JS backend implementation. + jsmame_steam_update($1, $2); + }, + (unsigned int)buffer, + samples_this_frame); } - virtual void set_mastervolume(int attenuation) - { - EM_ASM_ARGS({ - // Forward volume update on to JS backend implementation. - jsmame_set_mastervolume($0); - }, attenuation); - } - }; -#else /* SDLMAME_UNIX */ +#else // SDLMAME_EMSCRIPTEN MODULE_NOT_SUPPORTED(sound_js, OSD_SOUND_PROVIDER, "js") -#endif +#endif // SDLMAME_EMSCRIPTEN MODULE_DEFINITION(SOUND_JS, sound_js) diff --git a/src/osd/modules/sound/js_sound.js b/src/osd/modules/sound/js_sound.js index 5cbcc380c1b..25f19231ae9 100644 --- a/src/osd/modules/sound/js_sound.js +++ b/src/osd/modules/sound/js_sound.js @@ -98,30 +98,7 @@ function disconnect_old_event() { eventNode = null; }; -function set_mastervolume ( - // even though it's 'attenuation' the value is negative, so... - attenuation_in_decibels -) { - lazy_init(); - if (!context) return; - - // http://stackoverflow.com/questions/22604500/web-audio-api-working-with-decibels - // seemingly incorrect/broken. figures. welcome to Web Audio - // var gain_web_audio = 1.0 - Math.pow(10, 10 / attenuation_in_decibels); - - // HACK: Max attenuation in JSMESS appears to be 32. - // Hit ' then left/right arrow to test. - // FIXME: This is linear instead of log10 scale. - var gain_web_audio = 1.0 + (+attenuation_in_decibels / +32); - if (gain_web_audio < +0) - gain_web_audio = +0; - else if (gain_web_audio > +1) - gain_web_audio = +1; - - gain_node.gain.value = gain_web_audio; -}; - -function update_audio_stream ( +function stream_sink_update ( pBuffer, // pointer into emscripten heap. int16 samples samples_this_frame // int. number of samples at pBuffer address. ) { @@ -173,10 +150,11 @@ function tick (event) { start = 0; } } - //Pad with silence if we're underrunning: + //Pad with latest if we're underrunning: + var idx = (index == 0 ? bufferSize : index) - 1; while (index < 4096) { - buffers[0][index] = 0; - buffers[1][index++] = 0; + buffers[0][index] = buffers[0][idx]; + buffers[1][index++] = buffers[1][idx]; } //Deep inside the bowels of vendors bugs, //we're using watchdog for a firefox bug, @@ -206,14 +184,12 @@ function sample_count() { } return { - set_mastervolume: set_mastervolume, - update_audio_stream: update_audio_stream, + stream_sink_update: stream_sink_update, get_context: get_context, sample_count: sample_count }; })(); -window.jsmame_set_mastervolume = jsmame_web_audio.set_mastervolume; -window.jsmame_update_audio_stream = jsmame_web_audio.update_audio_stream; +window.jsmame_stream_sink_update = jsmame_web_audio.stream_sink_update; window.jsmame_sample_count = jsmame_web_audio.sample_count; diff --git a/src/osd/modules/sound/none.cpp b/src/osd/modules/sound/none.cpp index 685cdc884c2..bdb219636d0 100644 --- a/src/osd/modules/sound/none.cpp +++ b/src/osd/modules/sound/none.cpp @@ -2,32 +2,35 @@ // copyright-holders:Miodrag Milanovic /*************************************************************************** - none.c + none.cpp Dummy sound interface. *******************************************************************c********/ #include "sound_module.h" + #include "modules/osdmodule.h" + +namespace osd { + +namespace { + class sound_none : public osd_module, public sound_module { public: - sound_none() - : osd_module(OSD_SOUND_PROVIDER, "none"), sound_module() + sound_none() : osd_module(OSD_SOUND_PROVIDER, "none") { } virtual ~sound_none() { } - virtual int init(const osd_options &options) override { return 0; } + virtual int init(osd_interface &osd, const osd_options &options) override { return 0; } 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 { } +} // anonymous namespace -}; +} // namespace osd -MODULE_DEFINITION(SOUND_NONE, sound_none) +MODULE_DEFINITION(SOUND_NONE, osd::sound_none) diff --git a/src/osd/modules/sound/pa_sound.cpp b/src/osd/modules/sound/pa_sound.cpp index 41645318672..68200a9c3f1 100644 --- a/src/osd/modules/sound/pa_sound.cpp +++ b/src/osd/modules/sound/pa_sound.cpp @@ -9,44 +9,50 @@ *******************************************************************c********/ #include "sound_module.h" + #include "modules/osdmodule.h" #ifndef NO_USE_PORTAUDIO -#include <portaudio.h> #include "modules/lib/osdobj_common.h" +#include "osdcore.h" -#include <iostream> -#include <fstream> -#include <sstream> +#include <portaudio.h> + +#include <algorithm> #include <atomic> -#include <cmath> #include <climits> -#include <algorithm> +#include <cmath> +#include <fstream> +#include <iostream> +#include <sstream> -#ifdef WIN32 +#ifdef _WIN32 #include "pa_win_wasapi.h" #endif + +namespace osd { + +namespace { + #define LOG_FILE "pa.log" #define LOG_BUFCNT 0 class sound_pa : public osd_module, public sound_module { public: - sound_pa() - : osd_module(OSD_SOUND_PROVIDER, "portaudio"), sound_module() + sound_pa() : osd_module(OSD_SOUND_PROVIDER, "portaudio") { } virtual ~sound_pa() { } - virtual int init(osd_options const &options) override; + virtual int init(osd_interface &osd, osd_options const &options) override; virtual void exit() override; // sound_module - virtual void update_audio_stream(bool is_throttled, const s16 *buffer, int samples_this_frame) override; - virtual void set_mastervolume(int attenuation) override; + virtual void stream_sink_update(uint32_t, const s16 *buffer, int samples_this_frame) override; private: // Lock free SPSC ring buffer @@ -73,14 +79,14 @@ private: writepos.store((writepos + n) % size); } - int write(const T* src, int n, int attenuation) { + int write(const T* src, int n) { n = std::min<int>(n, size - reserve - count()); if (writepos + n > size) { - att_memcpy(buf + writepos, src, sizeof(T) * (size - writepos), attenuation); - att_memcpy(buf, src + (size - writepos), sizeof(T) * (n - (size - writepos)), attenuation); + memcpy(buf + writepos, src, sizeof(T) * (size - writepos)); + memcpy(buf, src + (size - writepos), sizeof(T) * (n - (size - writepos))); } else { - att_memcpy(buf + writepos, src, sizeof(T) * n, attenuation); + memcpy(buf + writepos, src, sizeof(T) * n); } increment_writepos(n); @@ -121,13 +127,6 @@ private: return n; } - - void att_memcpy(T* dest, const T* data, int n, int attenuation) { - int level = powf(10.0, attenuation / 20.0) * 32768; - n /= sizeof(T); - while (n--) - *dest++ = (*data++ * level) >> 15; - } }; enum @@ -150,7 +149,8 @@ private: PaStream* m_pa_stream; PaError err; - int m_attenuation; + int m_sample_rate; + int m_audio_latency; audio_buffer<s16>* m_ab; @@ -170,8 +170,12 @@ private: #endif }; -int sound_pa::init(osd_options const &options) +int sound_pa::init(osd_interface &osd, osd_options const &options) { + m_sample_rate = options.sample_rate(); + if (!m_sample_rate) + return 0; + PaStreamParameters stream_params; const PaStreamInfo* stream_info; const PaHostApiInfo* api_info; @@ -180,10 +184,6 @@ int sound_pa::init(osd_options const &options) unsigned long frames_per_callback = paFramesPerBufferUnspecified; double callback_interval; - if (!sample_rate()) - return 0; - - m_attenuation = options.volume(); m_underflows = 0; m_overflows = 0; m_has_overflowed = false; @@ -192,7 +192,7 @@ int sound_pa::init(osd_options const &options) m_skip_threshold_ticks = 0; m_osd_tps = osd_ticks_per_second(); m_buffer_min_ct = INT_MAX; - m_audio_latency = std::min<int>(std::max<int>(m_audio_latency, LATENCY_MIN), LATENCY_MAX); + m_audio_latency = std::clamp<int>(options.audio_latency(), LATENCY_MIN, LATENCY_MAX); try { m_ab = new audio_buffer<s16>(m_sample_rate, 2); @@ -216,7 +216,7 @@ int sound_pa::init(osd_options const &options) // 0 = use default stream_params.suggestedLatency = options.pa_latency() ? options.pa_latency() : device_info->defaultLowOutputLatency; -#ifdef WIN32 +#ifdef _WIN32 PaWasapiStreamInfo wasapi_stream_info; // if requested latency is less than 20 ms, we need to use exclusive mode @@ -364,7 +364,7 @@ int sound_pa::callback(s16* output_buffer, size_t number_of_samples) int adjust = m_buffer_min_ct - m_skip_threshold / 2; // if adjustment is less than two milliseconds, don't bother - if (adjust / 2 > sample_rate() / 500) { + if (adjust / 2 > m_sample_rate / 500) { m_ab->increment_playpos(adjust); m_has_overflowed = true; } @@ -379,7 +379,7 @@ int sound_pa::callback(s16* output_buffer, size_t number_of_samples) m_ab->read(output_buffer, buf_ct); std::memset(output_buffer + buf_ct, 0, (number_of_samples - buf_ct) * sizeof(s16)); - // if update_audio_stream has been called, note the underflow + // if stream_sink_update has been called, note the underflow if (m_osd_ticks) m_has_underflowed = true; @@ -389,9 +389,9 @@ int sound_pa::callback(s16* output_buffer, size_t number_of_samples) return paContinue; } -void sound_pa::update_audio_stream(bool is_throttled, const s16 *buffer, int samples_this_frame) +void sound_pa::stream_sink_update(uint32_t, const s16 *buffer, int samples_this_frame) { - if (!sample_rate()) + if (!m_sample_rate) return; #if LOG_BUFCNT @@ -413,20 +413,15 @@ void sound_pa::update_audio_stream(bool is_throttled, const s16 *buffer, int sam m_has_overflowed = false; } - m_ab->write(buffer, samples_this_frame * 2, m_attenuation); + m_ab->write(buffer, samples_this_frame * 2); // for determining buffer overflows, take the sample here instead of in the callback m_osd_ticks = osd_ticks(); } -void sound_pa::set_mastervolume(int attenuation) -{ - m_attenuation = attenuation; -} - void sound_pa::exit() { - if (!sample_rate()) + if (!m_sample_rate) return; #if LOG_BUFCNT @@ -456,8 +451,14 @@ void sound_pa::exit() osd_printf_verbose("Sound: overflows=%d underflows=%d\n", m_overflows, m_underflows); } +} // anonymous namespace + +} // namespace osd + #else - MODULE_NOT_SUPPORTED(sound_pa, OSD_SOUND_PROVIDER, "portaudio") + +namespace osd { namespace { MODULE_NOT_SUPPORTED(sound_pa, OSD_SOUND_PROVIDER, "portaudio") } } + #endif -MODULE_DEFINITION(SOUND_PORTAUDIO, sound_pa) +MODULE_DEFINITION(SOUND_PORTAUDIO, osd::sound_pa) diff --git a/src/osd/modules/sound/pipewire_sound.cpp b/src/osd/modules/sound/pipewire_sound.cpp new file mode 100644 index 00000000000..8bf5b8e0afa --- /dev/null +++ b/src/osd/modules/sound/pipewire_sound.cpp @@ -0,0 +1,823 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert +/*************************************************************************** + + piepewire_sound.c + + PipeWire interface. + +***************************************************************************/ + +#include "sound_module.h" +#include "modules/osdmodule.h" + +#ifndef NO_USE_PIPEWIRE + +#define GNU_SOURCE +#include "modules/lib/osdobj_common.h" + +#include <pipewire/pipewire.h> +#include <pipewire/extensions/metadata.h> +#include <spa/debug/pod.h> +#include <spa/debug/dict.h> +#include <spa/pod/builder.h> +#include <spa/param/audio/raw-utils.h> +#include <rapidjson/document.h> + +#include <map> + +class sound_pipewire : public osd_module, public sound_module +{ +public: + sound_pipewire() + : osd_module(OSD_SOUND_PROVIDER, "pipewire"), sound_module() + { + } + virtual ~sound_pipewire() { } + + virtual int init(osd_interface &osd, osd_options const &options) override; + virtual void exit() override; + + virtual bool external_per_channel_volume() override { return true; } + virtual bool split_streams_per_source() override { return true; } + + 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 uint32_t stream_source_open(uint32_t node, std::string name, uint32_t rate) override; + virtual void stream_set_volumes(uint32_t id, const std::vector<float> &db) 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; + virtual void stream_source_update(uint32_t id, int16_t *buffer, int samples_this_frame) override; + +private: + struct position_info { + uint32_t m_position; + std::array<double, 3> m_coords; + }; + + static const position_info position_infos[]; + + static const char *const typenames[]; + enum { AREC, APLAY }; + + struct node_info { + sound_pipewire *m_wire; + uint32_t m_id, m_osdid; + int m_type; + std::string m_serial; + std::string m_name; + std::string m_text_id; + + // Audio node info + uint32_t m_sinks, m_sources; + std::vector<uint32_t> m_position_codes; + std::vector<std::string> m_port_names; + std::vector<std::array<double, 3>> m_positions; + + osd::audio_rate_range m_rate; + bool m_has_s16; + bool m_has_iec958; + + pw_node *m_node; + spa_hook m_node_listener; + + node_info(sound_pipewire *wire, uint32_t id, uint32_t osdid, int type, std::string serial, std::string name, std::string text_id) : m_wire(wire), m_id(id), m_osdid(osdid), m_type(type), m_serial(serial), m_name(name), m_text_id(text_id), m_sinks(0), m_sources(0), m_rate{0, 0, 0}, m_has_s16(false), m_has_iec958(false), m_node(nullptr) { + spa_zero(m_node_listener); + } + }; + + struct stream_info { + sound_pipewire *m_wire; + bool m_is_output; + uint32_t m_osdid; + uint32_t m_node_id; + node_info *m_node; + uint32_t m_channels; + pw_stream *m_stream; + std::vector<float> m_volumes; + abuffer m_buffer; + + stream_info(sound_pipewire *wire, bool is_output, uint32_t osdid, uint32_t channels) : m_wire(wire), m_is_output(is_output), m_osdid(osdid), m_channels(channels), m_stream(nullptr), m_buffer(channels) {} + }; + + static const pw_core_events core_events; + static const pw_registry_events registry_events; + static const pw_node_events node_events; + static const pw_metadata_events default_events; + static const pw_stream_events stream_sink_events; + static const pw_stream_events stream_source_events; + + std::map<uint32_t, node_info> m_nodes; + std::map<uint32_t, uint32_t> m_node_osdid_to_id; + + std::map<uint32_t, stream_info> m_streams; + + pw_thread_loop *m_loop; + pw_context *m_context; + pw_core *m_core; + spa_hook m_core_listener; + pw_registry *m_registry; + spa_hook m_registry_listener; + pw_metadata *m_default; + spa_hook m_default_listener; + + std::string m_default_audio_sink; + std::string m_default_audio_source; + + uint32_t m_node_current_id, m_stream_current_id; + uint32_t m_generation; + bool m_wait_sync, m_wait_stream; + + void sync(); + + void core_event_done(uint32_t id, int seq); + static void s_core_event_done(void *data, uint32_t id, int seq); + + void register_node(uint32_t id, const spa_dict *props); + void register_port(uint32_t id, const spa_dict *props); + void register_link(uint32_t id, const spa_dict *props); + void register_default_metadata(uint32_t id); + void register_metadata(uint32_t id, const spa_dict *props); + void registry_event_global(uint32_t id, uint32_t permissions, const char *type, uint32_t version, const spa_dict *props); + static void s_registry_event_global(void *data, uint32_t id, uint32_t permissions, const char *type, uint32_t version, const spa_dict *props); + + void registry_event_global_remove(uint32_t id); + static void s_registry_event_global_remove(void *data, uint32_t id); + + void node_event_param(node_info *node, int seq, uint32_t id, uint32_t index, uint32_t next, const spa_pod *param); + static void s_node_event_param(void *data, int seq, uint32_t id, uint32_t index, uint32_t next, const spa_pod *param); + + int default_event_property(uint32_t subject, const char *key, const char *type, const char *value); + static int s_default_event_property(void *data, uint32_t subject, const char *key, const char *type, const char *value); + + void stream_sink_event_process(stream_info *stream); + static void s_stream_sink_event_process(void *data); + + void stream_source_event_process(stream_info *stream); + static void s_stream_source_event_process(void *data); + + void stream_event_param_changed(stream_info *stream, uint32_t id, const spa_pod *param); + static void s_stream_event_param_changed(void *data, uint32_t id, const spa_pod *param); +}; + +// Try to more or less map to speaker.h positions + +const sound_pipewire::position_info sound_pipewire::position_infos[] = { + { SPA_AUDIO_CHANNEL_MONO, { 0.0, 0.0, 1.0 } }, + { SPA_AUDIO_CHANNEL_FL, { -0.2, 0.0, 1.0 } }, + { SPA_AUDIO_CHANNEL_FR, { 0.2, 0.0, 1.0 } }, + { SPA_AUDIO_CHANNEL_FC, { 0.0, 0.0, 1.0 } }, + { SPA_AUDIO_CHANNEL_LFE, { 0.0, -0.5, 1.0 } }, + { SPA_AUDIO_CHANNEL_RL, { -0.2, 0.0, -0.5 } }, + { SPA_AUDIO_CHANNEL_RR, { 0.2, 0.0, -0.5 } }, + { SPA_AUDIO_CHANNEL_RC, { 0.0, 0.0, -0.5 } }, + { SPA_AUDIO_CHANNEL_UNKNOWN, { 0.0, 0.0, 0.0 } } +}; + + +const char *const sound_pipewire::typenames[] = { + "Audio recorder", "Speaker" +}; + +const pw_core_events sound_pipewire::core_events = { + PW_VERSION_CORE_EVENTS, + nullptr, // info + s_core_event_done, + nullptr, // ping + nullptr, // error + nullptr, // remove_id + nullptr, // bound_id + nullptr, // add_mem + nullptr, // remove_mem + nullptr // bound_props +}; + +const pw_registry_events sound_pipewire::registry_events = { + PW_VERSION_REGISTRY_EVENTS, + s_registry_event_global, + s_registry_event_global_remove +}; + +const pw_node_events sound_pipewire::node_events = { + PW_VERSION_NODE_EVENTS, + nullptr, // info + s_node_event_param +}; + +const pw_metadata_events sound_pipewire::default_events = { + PW_VERSION_METADATA_EVENTS, + s_default_event_property +}; + +const pw_stream_events sound_pipewire::stream_sink_events = { + PW_VERSION_STREAM_EVENTS, + nullptr, // destroy + nullptr, // state changed + nullptr, // control info + nullptr, // io changed + s_stream_event_param_changed, + nullptr, // add buffer + nullptr, // remove buffer + s_stream_sink_event_process, + nullptr, // drained + nullptr, // command + nullptr // trigger done +}; + +const pw_stream_events sound_pipewire::stream_source_events = { + PW_VERSION_STREAM_EVENTS, + nullptr, // destroy + nullptr, // state changed + nullptr, // control info + nullptr, // io changed + s_stream_event_param_changed, + nullptr, // add buffer + nullptr, // remove buffer + s_stream_source_event_process, + nullptr, // drained + nullptr, // command + nullptr // trigger done +}; + +void sound_pipewire::register_node(uint32_t id, const spa_dict *props) +{ + const spa_dict_item *cls = spa_dict_lookup_item(props, PW_KEY_MEDIA_CLASS); + const spa_dict_item *desc = spa_dict_lookup_item(props, PW_KEY_NODE_DESCRIPTION); + const spa_dict_item *name = spa_dict_lookup_item(props, PW_KEY_NODE_NAME); + const spa_dict_item *serial = spa_dict_lookup_item(props, PW_KEY_OBJECT_SERIAL); + if(!cls) + return; + int type; + if(!strcmp(cls->value, "Audio/Source")) + type = AREC; + else if(!strcmp(cls->value, "Audio/Sink")) + type = APLAY; + else + return; + + m_node_osdid_to_id[m_node_current_id] = id; + auto &node = m_nodes.emplace(id, node_info(this, id, m_node_current_id++, type, serial->value, desc ? desc->value : "?", name ? name->value : "?")).first->second; + + // printf("node %03x: %s %s %s | %s\n", node.m_id, serial->value, typenames[node.m_type], node.m_name.c_str(), node.m_text_id.c_str()); + + node.m_node = (pw_node *)pw_registry_bind(m_registry, id, PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, 0); + pw_node_add_listener(node.m_node, &node.m_node_listener, &node_events, &node); + pw_node_enum_params(node.m_node, 0, 3, 0, 0xffffffff, nullptr); + m_generation++; +} + +void sound_pipewire::register_port(uint32_t id, const spa_dict *props) +{ + uint32_t node = strtol(spa_dict_lookup_item(props, PW_KEY_NODE_ID)->value, nullptr, 10); + auto ind = m_nodes.find(node); + if(ind == m_nodes.end()) + return; + + const spa_dict_item *channel = spa_dict_lookup_item(props, PW_KEY_AUDIO_CHANNEL); + const spa_dict_item *dir = spa_dict_lookup_item(props, PW_KEY_PORT_DIRECTION); + bool is_in = dir && !strcmp(dir->value, "in") ; + uint32_t index = strtol(spa_dict_lookup_item(props, PW_KEY_PORT_ID)->value, nullptr, 10); + + if(is_in && ind->second.m_sinks <= index) + ind->second.m_sinks = index+1; + if(!is_in && ind->second.m_sources <= index) + ind->second.m_sources = index+1; + + auto &port_names = ind->second.m_port_names; + if(port_names.size() <= index) + port_names.resize(index+1); + + if(is_in || port_names[index].empty()) + port_names[index] = channel ? channel->value : "?"; + + m_generation++; + // printf("port %03x.%d %03x: %s\n", node, index, id, port_names[index].c_str()); +} + +void sound_pipewire::register_link(uint32_t id, const spa_dict *props) +{ + const spa_dict_item *input = spa_dict_lookup_item(props, PW_KEY_LINK_INPUT_NODE); + const spa_dict_item *output = spa_dict_lookup_item(props, PW_KEY_LINK_OUTPUT_NODE); + if(!input || !output) + return; + + uint32_t input_id = strtol(input->value, nullptr, 10); + uint32_t output_id = strtol(output->value, nullptr, 10); + + for(auto &si : m_streams) { + stream_info &stream = si.second; + if(stream.m_is_output && stream.m_node_id == output_id && (stream.m_node && stream.m_node->m_id != input_id)) { + auto ni = m_nodes.find(input_id); + if(ni != m_nodes.end()) { + stream.m_node = &ni->second; + m_generation ++; + return; + } + } + if(!stream.m_is_output && stream.m_node_id == input_id && (stream.m_node && stream.m_node->m_id != output_id)) { + auto ni = m_nodes.find(output_id); + if(ni != m_nodes.end()) { + stream.m_node = &ni->second; + m_generation ++; + return; + } + } + } +} + +void sound_pipewire::register_default_metadata(uint32_t id) +{ + m_default = (pw_metadata *)pw_registry_bind(m_registry, id, PW_TYPE_INTERFACE_Metadata, PW_VERSION_METADATA, 0); + pw_metadata_add_listener(m_default, &m_default_listener, &default_events, this); +} + +void sound_pipewire::register_metadata(uint32_t id, const spa_dict *props) +{ + const spa_dict_item *mn = spa_dict_lookup_item(props, PW_KEY_METADATA_NAME); + if(mn && !strcmp(mn->value, "default")) + register_default_metadata(id); +} + +void sound_pipewire::registry_event_global(uint32_t id, + uint32_t permissions, const char *type, uint32_t version, + const spa_dict *props) +{ + if(!strcmp(type, PW_TYPE_INTERFACE_Node)) + register_node(id, props); + else if(!strcmp(type, PW_TYPE_INTERFACE_Port)) + register_port(id, props); + else if(!strcmp(type, PW_TYPE_INTERFACE_Metadata)) + register_metadata(id, props); + else if(!strcmp(type, PW_TYPE_INTERFACE_Link)) + register_link(id, props); + else { + // printf("type %03x %s\n", id, type); + } +} + +void sound_pipewire::s_registry_event_global(void *data, uint32_t id, + uint32_t permissions, const char *type, uint32_t version, + const spa_dict *props) +{ + ((sound_pipewire *)data)->registry_event_global(id, permissions, type, version, props); +} + +void sound_pipewire::registry_event_global_remove(uint32_t id) +{ + auto ind = m_nodes.find(id); + if(ind == m_nodes.end()) + return; + + for(auto &istream : m_streams) + if(istream.second.m_node == &ind->second) + istream.second.m_node = nullptr; + m_nodes.erase(ind); + m_generation++; +} + +void sound_pipewire::s_registry_event_global_remove(void *data, uint32_t id) +{ + ((sound_pipewire *)data)->registry_event_global_remove(id); +} + +void sound_pipewire::node_event_param(node_info *node, int seq, uint32_t id, uint32_t index, uint32_t next, const spa_pod *param) +{ + if(id == SPA_PARAM_EnumFormat) { + const spa_pod_prop *subtype = spa_pod_find_prop(param, nullptr, SPA_FORMAT_mediaSubtype); + if(subtype) { + uint32_t sval; + if(!spa_pod_get_id(&subtype->value, &sval)) { + if(sval == SPA_MEDIA_SUBTYPE_raw) { + const spa_pod_prop *format = spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_format); + const spa_pod_prop *rate = spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_rate); + const spa_pod_prop *position = spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_position); + + if(format) { + uint32_t *entry; + SPA_POD_CHOICE_FOREACH((spa_pod_choice *)(&format->value), entry) { + if(*entry == SPA_AUDIO_FORMAT_S16) + node->m_has_s16 = true; + } + } + if(rate) { + if(rate->value.type == SPA_TYPE_Choice) { + struct spa_pod_choice_body *b = &((spa_pod_choice *)(&rate->value))->body; + uint32_t *choices = (uint32_t *)(b+1); + node->m_rate.m_default_rate = choices[0]; + if(b->type == SPA_CHOICE_Range) { + node->m_rate.m_min_rate = choices[1]; + node->m_rate.m_max_rate = choices[2]; + } else { + node->m_rate.m_min_rate = node->m_rate.m_default_rate; + node->m_rate.m_max_rate = node->m_rate.m_default_rate; + } + } + } + + if(position) { + uint32_t *entry; + node->m_position_codes.clear(); + node->m_positions.clear(); + SPA_POD_ARRAY_FOREACH((spa_pod_array *)(&position->value), entry) { + node->m_position_codes.push_back(*entry); + for(uint32_t i = 0;; i++) { + if((position_infos[i].m_position == *entry) || (position_infos[i].m_position == SPA_AUDIO_CHANNEL_UNKNOWN)) { + node->m_positions.push_back(position_infos[i].m_coords); + break; + } + } + } + } + } else if(sval == SPA_MEDIA_SUBTYPE_iec958) + node->m_has_iec958 = true; + } + } + m_generation++; + + } else + spa_debug_pod(2, nullptr, param); +} + +void sound_pipewire::s_node_event_param(void *data, int seq, uint32_t id, uint32_t index, uint32_t next, const spa_pod *param) +{ + node_info *n = (node_info *)data; + n->m_wire->node_event_param(n, seq, id, index, next, param); +} + +int sound_pipewire::default_event_property(uint32_t subject, const char *key, const char *type, const char *value) +{ + if(!value) + return 0; + std::string val = value; + if(!strcmp(type, "Spa:String:JSON")) { + rapidjson::Document json; + json.Parse(value); + if(json.IsObject() && json.HasMember("name") && json["name"].IsString()) + val = json["name"].GetString(); + } else + val = value; + + if(!strcmp(key, "default.audio.sink")) + m_default_audio_sink = val; + + else if(!strcmp(key, "default.audio.source")) + m_default_audio_source = val; + + return 0; +} + +int sound_pipewire::s_default_event_property(void *data, uint32_t subject, const char *key, const char *type, const char *value) +{ + return ((sound_pipewire *)data)->default_event_property(subject, key, type, value); +} + +int sound_pipewire::init(osd_interface &osd, osd_options const &options) +{ + spa_zero(m_core_listener); + spa_zero(m_registry_listener); + spa_zero(m_default_listener); + + m_node_current_id = 1; + m_stream_current_id = 1; + m_generation = 1; + + m_wait_sync = false; + m_wait_stream = false; + + pw_init(nullptr, nullptr); + m_loop = pw_thread_loop_new(nullptr, nullptr); + m_context = pw_context_new(pw_thread_loop_get_loop(m_loop), nullptr, 0); + m_core = pw_context_connect(m_context, nullptr, 0); + + if(!m_core) + return 1; + + pw_core_add_listener(m_core, &m_core_listener, &core_events, this); + + m_registry = pw_core_get_registry(m_core, PW_VERSION_REGISTRY, 0); + pw_registry_add_listener(m_registry, &m_registry_listener, ®istry_events, this); + + pw_thread_loop_start(m_loop); + + // The first sync ensures that the initial information request is + // completed, the second that the subsequent ones (parameters + // retrieval) are completed too. + sync(); + sync(); + + return 0; +} + +void sound_pipewire::core_event_done(uint32_t id, int seq) +{ + m_wait_sync = false; + pw_thread_loop_signal(m_loop, false); +} + +void sound_pipewire::s_core_event_done(void *data, uint32_t id, int seq) +{ + ((sound_pipewire *)data)->core_event_done(id, seq); +} + +void sound_pipewire::sync() +{ + pw_thread_loop_lock(m_loop); + m_wait_sync = true; + pw_core_sync(m_core, PW_ID_CORE, 0); + while(m_wait_sync) + pw_thread_loop_wait(m_loop); + pw_thread_loop_unlock(m_loop); +} + +void sound_pipewire::exit() +{ + pw_thread_loop_stop(m_loop); + for(const auto &si : m_streams) + pw_stream_destroy(si.second.m_stream); + for(const auto &ni : m_nodes) + pw_proxy_destroy((pw_proxy *)ni.second.m_node); + pw_proxy_destroy((pw_proxy *)m_default); + pw_proxy_destroy((pw_proxy *)m_registry); + pw_core_disconnect(m_core); + pw_context_destroy(m_context); + pw_thread_loop_destroy(m_loop); + pw_deinit(); +} + +uint32_t sound_pipewire::get_generation() +{ + pw_thread_loop_lock(m_loop); + uint32_t result = m_generation; + pw_thread_loop_unlock(m_loop); + return result; +} + +osd::audio_info sound_pipewire::get_information() +{ + osd::audio_info result; + pw_thread_loop_lock(m_loop); + result.m_nodes.resize(m_nodes.size()); + result.m_default_sink = 0; + result.m_default_source = 0; + result.m_generation = m_generation; + uint32_t node = 0; + for(auto &inode : m_nodes) { + result.m_nodes[node].m_name = inode.second.m_name; + result.m_nodes[node].m_id = inode.second.m_osdid; + result.m_nodes[node].m_rate = inode.second.m_rate; + result.m_nodes[node].m_sinks = inode.second.m_sinks; + result.m_nodes[node].m_sources = inode.second.m_sources; + result.m_nodes[node].m_port_names = inode.second.m_port_names; + result.m_nodes[node].m_port_positions = inode.second.m_positions; + + if(inode.second.m_text_id == m_default_audio_sink) + result.m_default_sink = inode.second.m_osdid; + if(inode.second.m_text_id == m_default_audio_source) + result.m_default_source = inode.second.m_osdid; + node ++; + } + + for(auto &istream : m_streams) + if(istream.second.m_node) + result.m_streams.emplace_back(osd::audio_info::stream_info { istream.second.m_osdid, istream.second.m_node->m_osdid, istream.second.m_volumes }); + + pw_thread_loop_unlock(m_loop); + return result; +} + +void sound_pipewire::stream_sink_event_process(stream_info *stream) +{ + pw_buffer *buffer = pw_stream_dequeue_buffer(stream->m_stream); + if(!buffer) + return; + + spa_buffer *sbuf = buffer->buffer; + stream->m_buffer.get((int16_t *)(sbuf->datas[0].data), buffer->requested); + + sbuf->datas[0].chunk->offset = 0; + sbuf->datas[0].chunk->stride = stream->m_channels * 2; + sbuf->datas[0].chunk->size = buffer->requested * stream->m_channels * 2; + + pw_stream_queue_buffer(stream->m_stream, buffer); +} + +void sound_pipewire::s_stream_sink_event_process(void *data) +{ + stream_info *info = (stream_info *)(data); + info->m_wire->stream_sink_event_process(info); +} + +void sound_pipewire::stream_source_event_process(stream_info *stream) +{ + pw_buffer *buffer = pw_stream_dequeue_buffer(stream->m_stream); + if(!buffer) + return; + + spa_buffer *sbuf = buffer->buffer; + stream->m_buffer.push((int16_t *)(sbuf->datas[0].data), sbuf->datas[0].chunk->size / stream->m_channels / 2); + pw_stream_queue_buffer(stream->m_stream, buffer); +} + +void sound_pipewire::s_stream_source_event_process(void *data) +{ + stream_info *info = (stream_info *)(data); + info->m_wire->stream_source_event_process(info); +} + +void sound_pipewire::stream_event_param_changed(stream_info *stream, uint32_t id, const spa_pod *param) +{ + if(id == SPA_PARAM_Props) { + const spa_pod_prop *vols = spa_pod_find_prop(param, nullptr, SPA_PROP_channelVolumes); + if(vols) { + bool initial = stream->m_volumes.empty(); + stream->m_volumes.clear(); + float *entry; + SPA_POD_ARRAY_FOREACH((spa_pod_array *)(&vols->value), entry) { + stream->m_volumes.push_back(osd::linear_to_db(*entry)); + } + if(!stream->m_volumes.empty()) { + if(initial) { + m_wait_stream = false; + pw_thread_loop_signal(m_loop, false); + } else + m_generation++; + } + } + } +} + +void sound_pipewire::s_stream_event_param_changed(void *data, uint32_t id, const spa_pod *param) +{ + stream_info *info = (stream_info *)(data); + info->m_wire->stream_event_param_changed(info, id, param); +} + +uint32_t sound_pipewire::stream_sink_open(uint32_t node, std::string name, uint32_t rate) +{ + pw_thread_loop_lock(m_loop); + auto ni = m_node_osdid_to_id.find(node); + if(ni == m_node_osdid_to_id.end()) { + pw_thread_loop_unlock(m_loop); + return 0; + } + node_info &snode = m_nodes.find(ni->second)->second; + + uint32_t id = m_stream_current_id++; + auto &stream = m_streams.emplace(id, stream_info(this, true, id, snode.m_sinks)).first->second; + + stream.m_stream = pw_stream_new_simple(pw_thread_loop_get_loop(m_loop), + name.c_str(), + pw_properties_new(PW_KEY_MEDIA_TYPE, "Audio", + PW_KEY_MEDIA_CATEGORY, "Playback", + PW_KEY_MEDIA_ROLE, "Game", + PW_KEY_TARGET_OBJECT, snode.m_serial.c_str(), + nullptr), + &stream_sink_events, + &stream); + stream.m_node = &snode; + + const spa_pod *params; + spa_audio_info_raw format = { + SPA_AUDIO_FORMAT_S16, + 0, + rate, + stream.m_channels + }; + for(uint32_t i=0; i != snode.m_sinks; i++) + format.position[i] = snode.m_position_codes[i]; + + uint8_t buffer[1024]; + spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + params = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &format); + + pw_stream_connect(stream.m_stream, + PW_DIRECTION_OUTPUT, + PW_ID_ANY, + pw_stream_flags(PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS), + ¶ms, 1); + + m_wait_stream = true; + while(m_wait_stream) + pw_thread_loop_wait(m_loop); + + stream.m_node_id = pw_stream_get_node_id(stream.m_stream); + pw_thread_loop_unlock(m_loop); + + return id; +} + +uint32_t sound_pipewire::stream_source_open(uint32_t node, std::string name, uint32_t rate) +{ + pw_thread_loop_lock(m_loop); + auto ni = m_node_osdid_to_id.find(node); + if(ni == m_node_osdid_to_id.end()) { + pw_thread_loop_unlock(m_loop); + return 0; + } + node_info &snode = m_nodes.find(ni->second)->second; + + uint32_t id = m_stream_current_id++; + auto &stream = m_streams.emplace(id, stream_info(this, false, id, snode.m_sources)).first->second; + + stream.m_stream = pw_stream_new_simple(pw_thread_loop_get_loop(m_loop), + name.c_str(), + pw_properties_new(PW_KEY_MEDIA_TYPE, "Audio", + PW_KEY_MEDIA_CATEGORY, "Record", + PW_KEY_MEDIA_ROLE, "Game", + PW_KEY_TARGET_OBJECT, snode.m_serial.c_str(), + nullptr), + &stream_source_events, + &stream); + stream.m_node = &snode; + + const spa_pod *params; + spa_audio_info_raw format = { + SPA_AUDIO_FORMAT_S16, + 0, + rate, + stream.m_channels + }; + for(uint32_t i=0; i != snode.m_sources; i++) + format.position[i] = snode.m_position_codes[i]; + + uint8_t buffer[1024]; + spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + params = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &format); + + pw_stream_connect(stream.m_stream, + PW_DIRECTION_INPUT, + PW_ID_ANY, + pw_stream_flags(PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS), + ¶ms, 1); + + m_wait_stream = true; + while(m_wait_stream) + pw_thread_loop_wait(m_loop); + + stream.m_node_id = pw_stream_get_node_id(stream.m_stream); + pw_thread_loop_unlock(m_loop); + + return id; +} + +void sound_pipewire::stream_set_volumes(uint32_t id, const std::vector<float> &db) +{ + pw_thread_loop_lock(m_loop); + auto si = m_streams.find(id); + if(si == m_streams.end()) { + pw_thread_loop_unlock(m_loop); + return; + } + stream_info &stream = si->second; + stream.m_volumes = db; + std::vector<float> linear; + for(float db1 : db) + linear.push_back(osd::db_to_linear(db1)); + pw_stream_set_control(stream.m_stream, SPA_PROP_channelVolumes, linear.size(), linear.data(), 0); + pw_thread_loop_unlock(m_loop); +} + +void sound_pipewire::stream_close(uint32_t id) +{ + pw_thread_loop_lock(m_loop); + auto si = m_streams.find(id); + if(si == m_streams.end()) { + pw_thread_loop_unlock(m_loop); + return; + } + stream_info &stream = si->second; + pw_stream_destroy(stream.m_stream); + m_streams.erase(si); + pw_thread_loop_unlock(m_loop); +} + +void sound_pipewire::stream_sink_update(uint32_t id, const int16_t *buffer, int samples_this_frame) +{ + pw_thread_loop_lock(m_loop); + auto si = m_streams.find(id); + if(si == m_streams.end()) { + pw_thread_loop_unlock(m_loop); + return; + } + si->second.m_buffer.push(buffer, samples_this_frame); + pw_thread_loop_unlock(m_loop); +} + +void sound_pipewire::stream_source_update(uint32_t id, int16_t *buffer, int samples_this_frame) +{ + pw_thread_loop_lock(m_loop); + auto si = m_streams.find(id); + if(si == m_streams.end()) { + pw_thread_loop_unlock(m_loop); + return; + } + si->second.m_buffer.get(buffer, samples_this_frame); + pw_thread_loop_unlock(m_loop); +} + +#else + MODULE_NOT_SUPPORTED(sound_pipewire, OSD_SOUND_PROVIDER, "pipewire") +#endif + +MODULE_DEFINITION(SOUND_PIPEWIRE, sound_pipewire) diff --git a/src/osd/modules/sound/pulse_sound.cpp b/src/osd/modules/sound/pulse_sound.cpp new file mode 100644 index 00000000000..820ca149c39 --- /dev/null +++ b/src/osd/modules/sound/pulse_sound.cpp @@ -0,0 +1,533 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert +/*************************************************************************** + + pulse_sound.c + + PulseAudio interface. + +*******************************************************************c********/ + +#include "sound_module.h" +#include "modules/osdmodule.h" + +#ifndef NO_USE_PULSEAUDIO + +#define GNU_SOURCE + +#include <pulse/pulseaudio.h> +#include <map> + +#include "modules/lib/osdobj_common.h" + +class sound_pulse : public osd_module, public sound_module +{ +public: + sound_pulse() + : osd_module(OSD_SOUND_PROVIDER, "pulse"), sound_module() + { + } + virtual ~sound_pulse() { } + + virtual int init(osd_interface &osd, osd_options const &options) override; + virtual void exit() override; + + virtual bool external_per_channel_volume() override { return false; } + virtual bool split_streams_per_source() override { return true; } + + 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_set_volumes(uint32_t id, const std::vector<float> &db) override; + virtual void stream_close(uint32_t id) override; + virtual void stream_sink_update(uint32_t, const int16_t *buffer, int samples_this_frame) override; + +private: + struct position_info { + pa_channel_position_t m_position; + std::array<double, 3> m_coords; + }; + + static const position_info position_infos[]; + static const char *const typenames[]; + enum { AREC, APLAY }; + + struct node_info { + sound_pulse *m_pulse; + uint32_t m_id, m_osdid; + int m_type; + std::string m_name, m_desc; + + // Audio node info + std::vector<pa_channel_position_t> m_position_codes; + std::vector<std::string> m_position_names; + std::vector<std::array<double, 3>> m_positions; + uint32_t m_sink_port_count, m_source_port_count; + + osd::audio_rate_range m_rate; + + node_info(sound_pulse *pulse, uint32_t id, uint32_t osdid, int type, std::string name, std::string desc) : m_pulse(pulse), m_id(id), m_osdid(osdid), m_type(type), m_name(name), m_desc(desc), m_sink_port_count(0), m_source_port_count(0), m_rate{0, 0, 0} { + } + }; + + struct stream_info { + sound_pulse *m_pulse; + uint32_t m_osdid; + uint32_t m_pulse_id; + node_info *m_target_node; + uint32_t m_channels; + pa_stream *m_stream; + std::vector<float> m_volumes; + abuffer m_buffer; + + stream_info(sound_pulse *pulse, uint32_t osdid, uint32_t channels) : m_pulse(pulse), m_osdid(osdid), m_pulse_id(0), m_channels(channels), m_stream(nullptr), m_volumes(channels), m_buffer(channels) {} + }; + + std::map<uint32_t, node_info> m_nodes; + std::map<uint32_t, uint32_t> m_node_osdid_to_id; + + std::map<uint32_t, stream_info> m_streams; + std::map<uint32_t, uint32_t> m_stream_pulse_id_to_osdid; + + pa_threaded_mainloop *m_mainloop; + pa_context *m_context; + uint32_t m_node_current_id, m_stream_current_id; + uint32_t m_generation; + bool m_wait_stream, m_wait_init; + + std::string m_default_audio_sink; + std::string m_default_audio_source; + + static void i_server_info(pa_context *, const pa_server_info *i, void *self); + void server_info(const pa_server_info *i); + static void i_context_notify(pa_context *, void *self); + void context_notify(); + static void i_context_subscribe(pa_context *, pa_subscription_event_type_t t, uint32_t idx, void *self); + void context_subscribe(pa_subscription_event_type_t t, uint32_t idx); + static void i_stream_notify(pa_stream *, void *self); + void stream_notify(stream_info *stream); + static void i_stream_write_request(pa_stream *, size_t size, void *self); + void stream_write_request(stream_info *stream, size_t size); + static void i_source_info(pa_context *, const pa_source_info *i, int eol, void *self); + void source_info(const pa_source_info *i, int eol); + static void i_sink_info_new(pa_context *, const pa_sink_info *i, int eol, void *self); + void sink_info_new(const pa_sink_info *i, int eol); + static void i_sink_input_info_change(pa_context *, const pa_sink_input_info *i, int eol, void *self); + void sink_input_info_change(stream_info *stream, const pa_sink_input_info *i, int eol); + + void generic_error(const char *msg); + void generic_pa_error(const char *msg, int err); +}; + +// Try to more or less map to speaker.h positions + +const sound_pulse::position_info sound_pulse::position_infos[] = { + { PA_CHANNEL_POSITION_MONO, { 0.0, 0.0, 1.0 } }, + { PA_CHANNEL_POSITION_FRONT_LEFT, { -0.2, 0.0, 1.0 } }, + { PA_CHANNEL_POSITION_FRONT_RIGHT, { 0.2, 0.0, 1.0 } }, + { PA_CHANNEL_POSITION_FRONT_CENTER, { 0.0, 0.0, 1.0 } }, + { PA_CHANNEL_POSITION_LFE, { 0.0, -0.5, 1.0 } }, + { PA_CHANNEL_POSITION_REAR_LEFT, { -0.2, 0.0, -0.5 } }, + { PA_CHANNEL_POSITION_REAR_RIGHT, { 0.2, 0.0, -0.5 } }, + { PA_CHANNEL_POSITION_REAR_CENTER, { 0.0, 0.0, -0.5 } }, + { PA_CHANNEL_POSITION_MAX, { 0.0, 0.0, 0.0 } } +}; + + +const char *const sound_pulse::typenames[] = { + "Audio recorder", "Speaker" +}; + +void sound_pulse::generic_error(const char *msg) +{ + perror(msg); + ::exit(1); +} + +void sound_pulse::generic_pa_error(const char *msg, int err) +{ + fprintf(stderr, "%s: %s\n", msg, pa_strerror(err)); + ::exit(1); +} + +void sound_pulse::context_notify() +{ + pa_context_state state = pa_context_get_state(m_context); + if(state == PA_CONTEXT_READY) { + pa_context_subscribe(m_context, PA_SUBSCRIPTION_MASK_ALL, nullptr, this); + pa_context_get_sink_info_list(m_context, i_sink_info_new, (void *)this); + + } else if(state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) { + m_generation = 0x80000000; + pa_threaded_mainloop_signal(m_mainloop, 0); + } +} + +void sound_pulse::i_context_notify(pa_context *, void *self) +{ + static_cast<sound_pulse *>(self)->context_notify(); +} + +void sound_pulse::stream_notify(stream_info *stream) +{ + pa_stream_state state = pa_stream_get_state(stream->m_stream); + + if(state == PA_STREAM_READY || state == PA_STREAM_FAILED || state == PA_STREAM_TERMINATED) { + m_wait_stream = false; + pa_threaded_mainloop_signal(m_mainloop, 0); + } +} + +void sound_pulse::i_stream_notify(pa_stream *, void *self) +{ + stream_info *si = static_cast<stream_info *>(self); + si->m_pulse->stream_notify(si); +} + +void sound_pulse::stream_write_request(stream_info *stream, size_t size) +{ + // This is called with the thread locked + while(size) { + void *buffer; + size_t bsize = size; + int err = pa_stream_begin_write(stream->m_stream, &buffer, &bsize); + if(err) + generic_pa_error("stream begin write", err); + uint32_t frames = bsize/2/stream->m_channels; + uint32_t bytes = frames*2*stream->m_channels; + stream->m_buffer.get((int16_t *)buffer, frames); + err = pa_stream_write(stream->m_stream, buffer, bytes, nullptr, 0, PA_SEEK_RELATIVE); + if(err) + generic_pa_error("stream write", err); + size -= bytes; + } +} + +void sound_pulse::i_stream_write_request(pa_stream *, size_t size, void *self) +{ + stream_info *si = static_cast<stream_info *>(self); + si->m_pulse->stream_write_request(si, size); +} + + +void sound_pulse::server_info(const pa_server_info *i) +{ + m_default_audio_sink = i->default_sink_name; + m_default_audio_source = i->default_source_name; + m_generation++; + if(m_wait_init) { + m_wait_init = false; + pa_threaded_mainloop_signal(m_mainloop, 0); + } +} + +void sound_pulse::i_server_info(pa_context *, const pa_server_info *i, void *self) +{ + static_cast<sound_pulse *>(self)->server_info(i); +} + +void sound_pulse::source_info(const pa_source_info *i, int eol) +{ + if(eol) { + if(m_wait_init) + pa_context_get_server_info(m_context, i_server_info, (void *)this); + return; + } + auto ni = m_nodes.find(i->index); + if(ni != m_nodes.end()) { + // Add the monitoring sources to the node + ni->second.m_source_port_count = i->channel_map.channels; + return; + } + + m_node_osdid_to_id[m_node_current_id] = i->index; + auto &node = m_nodes.emplace(i->index, node_info(this, i->index, m_node_current_id++, AREC, i->name, i->description)).first->second; + + node.m_source_port_count = i->channel_map.channels; + for(int chan=0; chan != i->channel_map.channels; chan++) { + pa_channel_position_t pos = i->channel_map.map[chan]; + node.m_position_codes.push_back(pos); + node.m_position_names.push_back(pa_channel_position_to_pretty_string(pos)); + for(uint32_t j = 0;; j++) { + if((position_infos[j].m_position == pos) || (position_infos[j].m_position == PA_CHANNEL_POSITION_MAX)) { + node.m_positions.push_back(position_infos[j].m_coords); + break; + } + } + } +} + +void sound_pulse::i_source_info(pa_context *, const pa_source_info *i, int eol, void *self) +{ + static_cast<sound_pulse *>(self)->source_info(i, eol); +} + +void sound_pulse::sink_info_new(const pa_sink_info *i, int eol) +{ + if(eol) { + if(m_wait_init) + pa_context_get_source_info_list(m_context, i_source_info, (void *)this); + return; + } + + m_node_osdid_to_id[m_node_current_id] = i->index; + auto &node = m_nodes.emplace(i->index, node_info(this, i->index, m_node_current_id++, APLAY, i->name, i->description)).first->second; + + node.m_sink_port_count = i->channel_map.channels; + for(int chan=0; chan != i->channel_map.channels; chan++) { + pa_channel_position_t pos = i->channel_map.map[chan]; + node.m_position_codes.push_back(pos); + node.m_position_names.push_back(pa_channel_position_to_pretty_string(pos)); + for(uint32_t j = 0;; j++) { + if((position_infos[j].m_position == pos) || (position_infos[j].m_position == PA_CHANNEL_POSITION_MAX)) { + node.m_positions.push_back(position_infos[j].m_coords); + break; + } + } + } + m_generation++; +} + +void sound_pulse::i_sink_info_new(pa_context *, const pa_sink_info *i, int eol, void *self) +{ + static_cast<sound_pulse *>(self)->sink_info_new(i, eol); +} + + +void sound_pulse::sink_input_info_change(stream_info *stream, const pa_sink_input_info *i, int eol) +{ + if(eol) + return; + + auto ni = m_nodes.find(i->sink); + if(ni != m_nodes.end()) + stream->m_target_node = &ni->second; + + for(uint32_t port = 0; port != stream->m_channels; port++) + stream->m_volumes[port] = pa_sw_volume_to_dB(i->volume.values[port]); + + m_generation++; +} + +void sound_pulse::i_sink_input_info_change(pa_context *, const pa_sink_input_info *i, int eol, void *self) +{ + stream_info *stream = static_cast<stream_info *>(self); + stream->m_pulse->sink_input_info_change(stream, i, eol); +} + +void sound_pulse::context_subscribe(pa_subscription_event_type_t t, uint32_t idx) +{ + // This is called with the thread locked + switch(int(t)) { + case PA_SUBSCRIPTION_EVENT_REMOVE | PA_SUBSCRIPTION_EVENT_SINK: + case PA_SUBSCRIPTION_EVENT_REMOVE | PA_SUBSCRIPTION_EVENT_SOURCE: { + auto si = m_nodes.find(idx); + if(si == m_nodes.end()) + break; + for(auto &istream : m_streams) + if(istream.second.m_target_node == &si->second) + istream.second.m_target_node = nullptr; + m_nodes.erase(si); + m_generation++; + break; + } + + case PA_SUBSCRIPTION_EVENT_NEW | PA_SUBSCRIPTION_EVENT_SINK: + pa_context_get_sink_info_by_index(m_context, idx, i_sink_info_new, this); + break; + + case PA_SUBSCRIPTION_EVENT_NEW | PA_SUBSCRIPTION_EVENT_SOURCE: + pa_context_get_source_info_by_index(m_context, idx, i_source_info, this); + break; + + case PA_SUBSCRIPTION_EVENT_CHANGE | PA_SUBSCRIPTION_EVENT_SERVER: + pa_context_get_server_info(m_context, i_server_info, (void *)this); + break; + + case PA_SUBSCRIPTION_EVENT_CHANGE | PA_SUBSCRIPTION_EVENT_SINK_INPUT: { + auto si1 = m_stream_pulse_id_to_osdid.find(idx); + if(si1 == m_stream_pulse_id_to_osdid.end()) + break; + auto si = m_streams.find(si1->second); + if(si == m_streams.end()) + break; + pa_context_get_sink_input_info(m_context, idx, i_sink_input_info_change, (void *)&si->second); + break; + } + + default: + break; + } +} + +void sound_pulse::i_context_subscribe(pa_context *, pa_subscription_event_type_t t, uint32_t idx, void *self) +{ + static_cast<sound_pulse *>(self)->context_subscribe(t, idx); +} + + + +int sound_pulse::init(osd_interface &osd, osd_options const &options) +{ + m_node_current_id = 1; + m_stream_current_id = 1; + m_generation = 0; + m_wait_stream = false; + m_wait_init = true; + + m_mainloop = pa_threaded_mainloop_new(); + m_context = pa_context_new(pa_threaded_mainloop_get_api(m_mainloop), "MAME"); + pa_context_set_state_callback(m_context, i_context_notify, this); + pa_context_set_subscribe_callback(m_context, i_context_subscribe, this); + pa_context_connect(m_context, nullptr, PA_CONTEXT_NOFLAGS, nullptr); + pa_threaded_mainloop_start(m_mainloop); + + pa_threaded_mainloop_lock(m_mainloop); + while(m_wait_init) + pa_threaded_mainloop_wait(m_mainloop); + pa_threaded_mainloop_unlock(m_mainloop); + if(m_generation >= 0x80000000) + return 1; + + return 0; +} + +uint32_t sound_pulse::get_generation() +{ + pa_threaded_mainloop_lock(m_mainloop); + uint32_t result = m_generation; + pa_threaded_mainloop_unlock(m_mainloop); + return result; +} + +osd::audio_info sound_pulse::get_information() +{ + osd::audio_info result; + pa_threaded_mainloop_lock(m_mainloop); + result.m_nodes.resize(m_nodes.size()); + result.m_default_sink = 0; + result.m_default_source = 0; + result.m_generation = m_generation; + uint32_t node = 0; + for(auto &inode : m_nodes) { + result.m_nodes[node].m_name = inode.second.m_desc; + result.m_nodes[node].m_id = inode.second.m_osdid; + result.m_nodes[node].m_rate = inode.second.m_rate; + result.m_nodes[node].m_sinks = inode.second.m_sink_port_count; + result.m_nodes[node].m_sources = inode.second.m_source_port_count; + result.m_nodes[node].m_port_names = inode.second.m_position_names; + result.m_nodes[node].m_port_positions = inode.second.m_positions; + + if(inode.second.m_name == m_default_audio_sink) + result.m_default_sink = inode.second.m_osdid; + if(inode.second.m_name == m_default_audio_source) + result.m_default_source = inode.second.m_osdid; + node ++; + } + + for(auto &istream : m_streams) + if(istream.second.m_target_node) + result.m_streams.emplace_back(osd::audio_info::stream_info { istream.second.m_osdid, istream.second.m_target_node->m_osdid, istream.second.m_volumes }); + + pa_threaded_mainloop_unlock(m_mainloop); + return result; +} + +uint32_t sound_pulse::stream_sink_open(uint32_t node, std::string name, uint32_t rate) +{ + pa_threaded_mainloop_lock(m_mainloop); + auto ni = m_node_osdid_to_id.find(node); + if(ni == m_node_osdid_to_id.end()) { + pa_threaded_mainloop_unlock(m_mainloop); + return 0; + } + node_info &snode = m_nodes.find(ni->second)->second; + + uint32_t id = m_stream_current_id++; + auto &stream = m_streams.emplace(id, stream_info(this, id, snode.m_sink_port_count)).first->second; + + pa_sample_spec ss; +#ifdef LSB_FIRST + ss.format = PA_SAMPLE_S16LE; +#else + ss.format = PA_SAMPLE_S16BE; +#endif + ss.rate = rate; + ss.channels = stream.m_channels; + stream.m_stream = pa_stream_new(m_context, name.c_str(), &ss, nullptr); + pa_stream_set_state_callback(stream.m_stream, i_stream_notify, &stream); + pa_stream_set_write_callback(stream.m_stream, i_stream_write_request, &stream); + + pa_buffer_attr battr; + battr.fragsize = uint32_t(-1); + battr.maxlength = 1024; + battr.minreq = uint32_t(-1); + battr.prebuf = uint32_t(-1); + battr.tlength = uint32_t(-1); + + int err = pa_stream_connect_playback(stream.m_stream, snode.m_name.c_str(), &battr, pa_stream_flags_t(PA_STREAM_ADJUST_LATENCY|PA_STREAM_START_UNMUTED), nullptr, nullptr); + if(err) + generic_pa_error("stream connect playback", err); + + stream.m_target_node = &snode; + + m_wait_stream = true; + while(m_wait_stream) + pa_threaded_mainloop_wait(m_mainloop); + + stream.m_pulse_id = pa_stream_get_index(stream.m_stream); + m_stream_pulse_id_to_osdid[stream.m_pulse_id] = id; + + pa_threaded_mainloop_unlock(m_mainloop); + + return id; +} + +void sound_pulse::stream_sink_update(uint32_t id, const int16_t *buffer, int samples_this_frame) +{ + pa_threaded_mainloop_lock(m_mainloop); + auto si = m_streams.find(id); + if(si == m_streams.end()) { + pa_threaded_mainloop_unlock(m_mainloop); + return; + } + si->second.m_buffer.push(buffer, samples_this_frame); + pa_threaded_mainloop_unlock(m_mainloop); +} + +void sound_pulse::stream_set_volumes(uint32_t id, const std::vector<float> &db) +{ +} + +void sound_pulse::stream_close(uint32_t id) +{ + pa_threaded_mainloop_lock(m_mainloop); + auto si = m_streams.find(id); + if(si == m_streams.end()) { + pa_threaded_mainloop_unlock(m_mainloop); + return; + } + stream_info &stream = si->second; + pa_stream_set_state_callback(stream.m_stream, nullptr, &stream); + pa_stream_set_write_callback(stream.m_stream, nullptr, &stream); + pa_stream_disconnect(stream.m_stream); + m_streams.erase(si); + pa_threaded_mainloop_unlock(m_mainloop); +} + +void sound_pulse::exit() +{ + for(const auto &si : m_streams) { + pa_stream_disconnect(si.second.m_stream); + pa_stream_unref(si.second.m_stream); + } + + pa_context_unref(m_context); + pa_threaded_mainloop_free(m_mainloop); +} + +#else + MODULE_NOT_SUPPORTED(sound_pulse, OSD_SOUND_PROVIDER, "pulse") +#endif + +MODULE_DEFINITION(SOUND_PULSEAUDIO, sound_pulse) diff --git a/src/osd/modules/sound/sdl_sound.cpp b/src/osd/modules/sound/sdl_sound.cpp index 5ff8f2e6821..35c11eae86d 100644 --- a/src/osd/modules/sound/sdl_sound.cpp +++ b/src/osd/modules/sound/sdl_sound.cpp @@ -9,458 +9,231 @@ //============================================================ #include "sound_module.h" + #include "modules/osdmodule.h" #if (defined(OSD_SDL) || defined(USE_SDL_SOUND)) +#include "modules/lib/osdobj_common.h" +#include "osdcore.h" + // standard sdl header #include <SDL2/SDL.h> -// MAME headers -#include "emu.h" -#include "emuopts.h" - -#include "../../sdl/osdsdl.h" - #include <algorithm> +#include <cmath> #include <fstream> #include <memory> +#include <map> -//============================================================ -// DEBUGGING -//============================================================ -#define LOG_SOUND 0 +namespace osd { -//============================================================ -// CLASS -//============================================================ +namespace { 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; + osd_module(OSD_SOUND_PROVIDER, "sdl"), sound_module() + { } + virtual ~sound_sdl() { } - virtual int init(const osd_options &options) override; + 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 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; - + 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; } -//============================================================ -// sound_sdl - destructor -//============================================================ - -//============================================================ -// lock_buffer -//============================================================ -void sound_sdl::lock_buffer() +void sound_sdl::exit() { - if (!buf_locked) - SDL_LockAudio(); - buf_locked++; - - if (LOG_SOUND) - *sound_log << "locking\n"; + SDL_QuitSubSystem(SDL_INIT_AUDIO); } -//============================================================ -// unlock_buffer -//============================================================ -void sound_sdl::unlock_buffer() +uint32_t sound_sdl::get_generation() { - buf_locked--; - if (!buf_locked) - SDL_UnlockAudio(); - - if (LOG_SOUND) - *sound_log << "unlocking\n"; - + // sdl2 is not dynamic w.r.t devices + return 1; } -//============================================================ -// Apply attenuation -//============================================================ +osd::audio_info sound_sdl::get_information() +{ + 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 } + }; -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::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]); + } } + return result; } -//============================================================ -// copy_sample_data -//============================================================ - -void sound_sdl::copy_sample_data(bool is_throttled, const int16_t *data, int bytes_to_copy) +uint32_t sound_sdl::stream_sink_open(uint32_t node, std::string name, uint32_t rate) { - 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"; -} + 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(); -//============================================================ -// 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::max(std::min(_attenuation, 0), -32); - - if (stream_in_initialized) - { - if (attenuation == -32) - SDL_PauseAudio(1); - else - SDL_PauseAudio(0); - } + stream->m_sdl_id = SDL_OpenAudioDevice(dev.m_name.c_str(), 0, &dspec, &ospec, 0); + if(!stream->m_sdl_id) + 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_callback -//============================================================ -void sound_sdl::sdl_callback(void *userdata, Uint8 *stream, int len) +void sound_sdl::stream_close(uint32_t id) { - 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); + auto si = m_streams.find(id); + if(si == m_streams.end()) 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; - char audio_driver[16] = ""; - - 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"); - strncpy(audio_driver, SDL_GetCurrentAudioDriver(), sizeof(audio_driver)); - osd_printf_verbose("Audio: Driver is %s\n", audio_driver); - - 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::max(std::min(m_audio_latency, MAX_AUDIO_LATENCY), 1); - - // 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_CloseAudioDevice(si->second->m_sdl_id); + m_streams.erase(si); } - - -//============================================================ -// sdl_kill -//============================================================ - -void sound_sdl::exit() +void sound_sdl::stream_sink_update(uint32_t id, const int16_t *buffer, int samples_this_frame) { - // 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(); - } + 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); } - - -//============================================================ -// dsound_create_buffers -//============================================================ - -int sound_sdl::sdl_create_buffers() +void sound_sdl::sink_callback(void *userdata, uint8_t *data, int len) { - 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; + stream_info *stream = reinterpret_cast<stream_info *>(userdata); + stream->m_buffer.get((int16_t *)data, len / 2 / stream->m_buffer.channels()); } -//============================================================ -// sdl_destroy_buffers -//============================================================ +} // anonymous namespace + +} // namespace osd -void sound_sdl::sdl_destroy_buffers() -{ - // release the buffer - stream_buffer.reset(); -} +#else // (defined(OSD_SDL) || defined(USE_SDL_SOUND)) +namespace osd { namespace { MODULE_NOT_SUPPORTED(sound_sdl, OSD_SOUND_PROVIDER, "sdl") } } -#else /* SDLMAME_UNIX */ - MODULE_NOT_SUPPORTED(sound_sdl, OSD_SOUND_PROVIDER, "sdl") #endif -MODULE_DEFINITION(SOUND_SDL, sound_sdl) +MODULE_DEFINITION(SOUND_SDL, osd::sound_sdl) diff --git a/src/osd/modules/sound/sound_module.cpp b/src/osd/modules/sound/sound_module.cpp new file mode 100644 index 00000000000..5dc13eb3246 --- /dev/null +++ b/src/osd/modules/sound/sound_module.cpp @@ -0,0 +1,61 @@ +// license:BSD-3-Clause +// copyright-holders:O. Galibert + + +#include "emu.h" +#include "sound_module.h" + +void sound_module::abuffer::get(int16_t *data, uint32_t samples) +{ + uint32_t pos = 0; + while(pos != samples) { + if(m_buffers.empty()) { + while(pos != samples) { + memcpy(data, m_last_sample.data(), m_channels*2); + data += m_channels; + pos ++; + } + break; + } + + auto &buf = m_buffers.front(); + if(buf.m_data.empty()) { + m_buffers.erase(m_buffers.begin()); + continue; + } + + uint32_t avail = buf.m_data.size() / m_channels - buf.m_cpos; + if(avail > samples - pos) { + avail = samples - pos; + memcpy(data, buf.m_data.data() + buf.m_cpos * m_channels, avail * 2 * m_channels); + buf.m_cpos += avail; + break; + } + + memcpy(data, buf.m_data.data() + buf.m_cpos * m_channels, avail * 2 * m_channels); + m_buffers.erase(m_buffers.begin()); + pos += avail; + data += avail * m_channels; + } +} + +void sound_module::abuffer::push(const int16_t *data, uint32_t samples) +{ + m_buffers.resize(m_buffers.size() + 1); + auto &buf = m_buffers.back(); + buf.m_cpos = 0; + buf.m_data.resize(samples * m_channels); + memcpy(buf.m_data.data(), data, samples * 2 * m_channels); + memcpy(m_last_sample.data(), data + (samples-1) * m_channels, 2 * m_channels); + + if(m_buffers.size() > 10) + // If there are way too many buffers, drop some so only 10 are left (roughly 0.2s) + m_buffers.erase(m_buffers.begin(), m_buffers.begin() + m_buffers.size() - 10); + + else if(m_buffers.size() >= 5) + // If there are too many buffers, remove five samples per buffer + // to slowly resync to reduce latency (4 seconds to + // compensate one buffer, roughly) + buf.m_cpos = 5; +} + diff --git a/src/osd/modules/sound/sound_module.h b/src/osd/modules/sound/sound_module.h index d9f597ad0d9..2498db84107 100644 --- a/src/osd/modules/sound/sound_module.h +++ b/src/osd/modules/sound/sound_module.h @@ -4,33 +4,76 @@ * sound_module.h * */ +#ifndef MAME_OSD_SOUND_SOUND_MODULE_H +#define MAME_OSD_SOUND_SOUND_MODULE_H -#ifndef SOUND_MODULE_H_ -#define SOUND_MODULE_H_ +#pragma once -#include "osdepend.h" -#include "modules/osdmodule.h" +#include <osdepend.h> -//============================================================ -// CONSTANTS -//============================================================ +#include <cstdint> +#include <array> +#include <vector> +#include <string> #define OSD_SOUND_PROVIDER "sound" class sound_module { public: - sound_module() : m_sample_rate(0), m_audio_latency(1) { } + virtual ~sound_module() = default; - virtual ~sound_module() { } + virtual uint32_t get_generation() { return 1; } + virtual osd::audio_info get_information() { + osd::audio_info result; + result.m_generation = 1; + result.m_default_sink = 1; + result.m_default_source = 0; + result.m_nodes.resize(1); + result.m_nodes[0].m_name = ""; + result.m_nodes[0].m_id = 1; + result.m_nodes[0].m_rate.m_default_rate = 0; // Magic value meaning "use configured sample rate" + result.m_nodes[0].m_rate.m_min_rate = 0; + result.m_nodes[0].m_rate.m_max_rate = 0; + result.m_nodes[0].m_sinks = 2; + result.m_nodes[0].m_sources = 0; + result.m_nodes[0].m_port_names.push_back("L"); + result.m_nodes[0].m_port_names.push_back("R"); + result.m_nodes[0].m_port_positions.emplace_back(std::array<double, 3>({ -0.2, 0.0, 1.0 })); + result.m_nodes[0].m_port_positions.emplace_back(std::array<double, 3>({ 0.2, 0.0, 1.0 })); + result.m_streams.resize(1); + result.m_streams[0].m_id = 1; + result.m_streams[0].m_node = 1; + return result; + } + virtual bool external_per_channel_volume() { return false; } + virtual bool split_streams_per_source() { return false; } - virtual void update_audio_stream(bool is_throttled, const int16_t *buffer, int samples_this_frame) = 0; - virtual void set_mastervolume(int attenuation) = 0; + virtual uint32_t stream_sink_open(uint32_t node, std::string name, uint32_t rate) { return 1; } + virtual uint32_t stream_source_open(uint32_t node, std::string name, uint32_t rate) { return 0; } + virtual void stream_set_volumes(uint32_t id, const std::vector<float> &db) {} + virtual void stream_close(uint32_t id) {} + virtual void stream_sink_update(uint32_t id, const int16_t *buffer, int samples_this_frame) {} + virtual void stream_source_update(uint32_t id, int16_t *buffer, int samples_this_frame) {} - int sample_rate() const { return m_sample_rate; } +protected: + class abuffer { + public: + abuffer(uint32_t channels) : m_channels(channels), m_last_sample(channels, 0) {} + void get(int16_t *data, uint32_t samples); + void push(const int16_t *data, uint32_t samples); + uint32_t channels() const { return m_channels; } - int m_sample_rate; - int m_audio_latency; + private: + struct buffer { + uint32_t m_cpos; + std::vector<int16_t> m_data; + }; + + uint32_t m_channels; + std::vector<int16_t> m_last_sample; + std::vector<buffer> m_buffers; + }; }; -#endif /* FONT_MODULE_H_ */ +#endif // MAME_OSD_SOUND_SOUND_MODULE_H diff --git a/src/osd/modules/sound/xaudio2_sound.cpp b/src/osd/modules/sound/xaudio2_sound.cpp index 1d9e1036f9a..a865f2527fe 100644 --- a/src/osd/modules/sound/xaudio2_sound.cpp +++ b/src/osd/modules/sound/xaudio2_sound.cpp @@ -7,9 +7,24 @@ //==================================================================== #include "sound_module.h" + #include "modules/osdmodule.h" -#if defined(OSD_WINDOWS) || defined(OSD_UWP) +#if defined(OSD_WINDOWS) | defined(SDLMAME_WIN32) + +// OSD headers +#include "modules/lib/osdlib.h" +#include "modules/lib/osdobj_common.h" +#include "osdcore.h" +#include "osdepend.h" +#include "windows/winutil.h" + +// stdlib includes +#include <algorithm> +#include <chrono> +#include <mutex> +#include <queue> +#include <thread> // standard windows headers #include <windows.h> @@ -19,21 +34,10 @@ // XAudio2 include #include <xaudio2.h> -#undef interface -// stdlib includes -#include <mutex> -#include <thread> -#include <queue> -#include <chrono> +namespace osd { -// MAME headers -#include "emu.h" -#include "osdepend.h" - -#include "winutil.h" - -#include "modules/lib/osdlib.h" +namespace { //============================================================ // Constants @@ -180,29 +184,6 @@ public: // The main class for the XAudio2 sound module implementation class sound_xaudio2 : public osd_module, public sound_module, public IXAudio2VoiceCallback { -private: - Microsoft::WRL::ComPtr<IXAudio2> m_xAudio2; - mastering_voice_ptr m_masterVoice; - src_voice_ptr m_sourceVoice; - DWORD m_sample_bytes; - std::unique_ptr<BYTE[]> m_buffer; - DWORD m_buffer_size; - DWORD m_buffer_count; - DWORD m_writepos; - std::mutex m_buffer_lock; - HANDLE m_hEventBufferCompleted; - HANDLE m_hEventDataAvailable; - HANDLE m_hEventExiting; - std::thread m_audioThread; - std::queue<xaudio2_buffer> m_queue; - std::unique_ptr<bufferpool> m_buffer_pool; - uint32_t m_overflows; - uint32_t m_underflows; - BOOL m_in_underflow; - BOOL m_initialized; - OSD_DYNAMIC_API(xaudio2, "XAudio2_9.dll", "XAudio2_8.dll"); - OSD_DYNAMIC_API_FN(xaudio2, HRESULT, WINAPI, XAudio2Create, IXAudio2 **, uint32_t, XAUDIO2_PROCESSOR); - public: sound_xaudio2() : osd_module(OSD_SOUND_PROVIDER, "xaudio2"), @@ -210,6 +191,8 @@ public: m_xAudio2(nullptr), m_masterVoice(nullptr), m_sourceVoice(nullptr), + m_sample_rate(0), + m_audio_latency(0), m_sample_bytes(0), m_buffer(nullptr), m_buffer_size(0), @@ -226,26 +209,23 @@ public: { } - virtual ~sound_xaudio2() { } - bool probe() override; - int init(osd_options const &options) override; + int init(osd_interface &osd, osd_options const &options) override; void exit() override; // sound_module - void update_audio_stream(bool is_throttled, int16_t const *buffer, int samples_this_frame) override; - void set_mastervolume(int attenuation) override; + void stream_sink_update(uint32_t, int16_t const *buffer, int samples_this_frame) override; +private: // Xaudio callbacks - void STDAPICALLTYPE OnVoiceProcessingPassStart(uint32_t bytes_required) override; - void STDAPICALLTYPE OnVoiceProcessingPassEnd() override {} - void STDAPICALLTYPE OnStreamEnd() override {} - void STDAPICALLTYPE OnBufferStart(void* pBufferContext) override {} - void STDAPICALLTYPE OnLoopEnd(void* pBufferContext) override {} - void STDAPICALLTYPE OnVoiceError(void* pBufferContext, HRESULT error) override {} - void STDAPICALLTYPE OnBufferEnd(void *pBufferContext) override; + void STDAPICALLTYPE OnVoiceProcessingPassStart(uint32_t bytes_required) noexcept override; + void STDAPICALLTYPE OnVoiceProcessingPassEnd() noexcept override {} + void STDAPICALLTYPE OnStreamEnd() noexcept override {} + void STDAPICALLTYPE OnBufferStart(void* pBufferContext) noexcept override {} + void STDAPICALLTYPE OnLoopEnd(void* pBufferContext) noexcept override {} + void STDAPICALLTYPE OnVoiceError(void* pBufferContext, HRESULT error) noexcept override {} + void STDAPICALLTYPE OnBufferEnd(void *pBufferContext) noexcept override; -private: void create_buffers(const WAVEFORMATEX &format); HRESULT create_voices(const WAVEFORMATEX &format); void process_audio(); @@ -253,6 +233,31 @@ private: void submit_needed(); void roll_buffer(); BOOL submit_next_queued(); + + Microsoft::WRL::ComPtr<IXAudio2> m_xAudio2; + mastering_voice_ptr m_masterVoice; + src_voice_ptr m_sourceVoice; + int m_sample_rate; + int m_audio_latency; + DWORD m_sample_bytes; + std::unique_ptr<BYTE[]> m_buffer; + DWORD m_buffer_size; + DWORD m_buffer_count; + DWORD m_writepos; + std::mutex m_buffer_lock; + HANDLE m_hEventBufferCompleted; + HANDLE m_hEventDataAvailable; + HANDLE m_hEventExiting; + std::thread m_audioThread; + std::queue<xaudio2_buffer> m_queue; + std::unique_ptr<bufferpool> m_buffer_pool; + uint32_t m_overflows; + uint32_t m_underflows; + BOOL m_in_underflow; + BOOL m_initialized; + + OSD_DYNAMIC_API(xaudio2, "XAudio2_9.dll", "XAudio2_8.dll"); + OSD_DYNAMIC_API_FN(xaudio2, HRESULT, WINAPI, XAudio2Create, IXAudio2 **, uint32_t, XAUDIO2_PROCESSOR); }; //============================================================ @@ -268,14 +273,9 @@ bool sound_xaudio2::probe() // init //============================================================ -int sound_xaudio2::init(osd_options const &options) +int sound_xaudio2::init(osd_interface &osd, osd_options const &options) { - HRESULT result; - WAVEFORMATEX format = {0}; - auto init_start = std::chrono::system_clock::now(); - std::chrono::milliseconds init_time; - - CoInitializeEx(nullptr, COINIT_MULTITHREADED); + auto const init_start = std::chrono::system_clock::now(); // Make sure our XAudio2Create entrypoint is bound if (!OSD_DYNAMIC_API_TEST(XAudio2Create)) @@ -284,6 +284,13 @@ int sound_xaudio2::init(osd_options const &options) return 1; } + HRESULT result; + std::chrono::milliseconds init_time; + WAVEFORMATEX format = { 0 }; + + m_sample_rate = options.sample_rate(); + m_audio_latency = options.audio_latency(); + // Create the IXAudio2 object HR_GOERR(OSD_DYNAMIC_CALL(XAudio2Create, m_xAudio2.GetAddressOf(), 0, XAUDIO2_DEFAULT_PROCESSOR)); @@ -291,7 +298,7 @@ int sound_xaudio2::init(osd_options const &options) format.wBitsPerSample = 16; format.wFormatTag = WAVE_FORMAT_PCM; format.nChannels = 2; - format.nSamplesPerSec = sample_rate(); + format.nSamplesPerSec = m_sample_rate; format.nBlockAlign = format.wBitsPerSample * format.nChannels / 8; format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; @@ -310,10 +317,10 @@ int sound_xaudio2::init(osd_options const &options) HR_GOERR(m_sourceVoice->Start()); // Start the thread listening - m_audioThread = std::thread([](sound_xaudio2* self) { self->process_audio(); }, this); + m_audioThread = std::thread([] (sound_xaudio2 *self) { self->process_audio(); }, this); init_time = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - init_start); - osd_printf_verbose("Sound: XAudio2 initialized. %d ms.\n", static_cast<int>(init_time.count())); + osd_printf_verbose("Sound: XAudio2 initialized. %d ms.\n", init_time.count()); m_initialized = TRUE; return 0; @@ -368,15 +375,15 @@ void sound_xaudio2::exit() } //============================================================ -// update_audio_stream +// stream_sink_update //============================================================ -void sound_xaudio2::update_audio_stream( - bool is_throttled, +void sound_xaudio2::stream_sink_update( + uint32_t, int16_t const *buffer, int samples_this_frame) { - if (!m_initialized || sample_rate() == 0 || !m_buffer) + if (!m_initialized || m_sample_rate == 0 || !m_buffer) return; uint32_t const bytes_this_frame = samples_this_frame * m_sample_bytes; @@ -406,37 +413,11 @@ void sound_xaudio2::update_audio_stream( } //============================================================ -// set_mastervolume -//============================================================ - -void sound_xaudio2::set_mastervolume(int attenuation) -{ - if (!m_initialized) - return; - - assert(m_sourceVoice); - - HRESULT result; - - // clamp the attenuation to 0-32 range - attenuation = std::max(std::min(attenuation, 0), -32); - - // Ranges from 1.0 to XAUDIO2_MAX_VOLUME_LEVEL indicate additional gain - // Ranges from 0 to 1.0 indicate a reduced volume level - // 0 indicates silence - // We only support a reduction from 1.0, so we generate values in the range 0.0 to 1.0 - float scaledVolume = (32.0f + attenuation) / 32.0f; - - // set the master volume - HR_RETV(m_sourceVoice->SetVolume(scaledVolume)); -} - -//============================================================ // IXAudio2VoiceCallback::OnBufferEnd //============================================================ // The XAudio2 voice callback triggered when a buffer finishes playing -void sound_xaudio2::OnBufferEnd(void *pBufferContext) +void sound_xaudio2::OnBufferEnd(void *pBufferContext) noexcept { BYTE* completed_buffer = static_cast<BYTE*>(pBufferContext); if (completed_buffer != nullptr) @@ -453,7 +434,7 @@ void sound_xaudio2::OnBufferEnd(void *pBufferContext) //============================================================ // The XAudio2 voice callback triggered on every pass -void sound_xaudio2::OnVoiceProcessingPassStart(uint32_t bytes_required) +void sound_xaudio2::OnVoiceProcessingPassStart(uint32_t bytes_required) noexcept { if (bytes_required == 0) { @@ -479,7 +460,8 @@ void sound_xaudio2::create_buffers(const WAVEFORMATEX &format) { // Compute the buffer size // buffer size is equal to the bytes we need to hold in memory per X tenths of a second where X is audio_latency - float audio_latency_in_seconds = m_audio_latency / 10.0f; + int audio_latency = std::max(m_audio_latency, 1); + float audio_latency_in_seconds = audio_latency / 10.0f; uint32_t format_bytes_per_second = format.nSamplesPerSec * format.nBlockAlign; uint32_t total_buffer_size = format_bytes_per_second * audio_latency_in_seconds * RESAMPLE_TOLERANCE; @@ -526,7 +508,7 @@ HRESULT sound_xaudio2::create_voices(const WAVEFORMATEX &format) m_xAudio2->CreateMasteringVoice( &temp_master_voice, format.nChannels, - sample_rate())); + m_sample_rate)); m_masterVoice = mastering_voice_ptr(temp_master_voice); @@ -693,9 +675,15 @@ void sound_xaudio2::roll_buffer() } } +} // anonymous namespace + +} // namespace osd + #else -MODULE_NOT_SUPPORTED(sound_xaudio2, OSD_SOUND_PROVIDER, "xaudio2") + +namespace osd { namespace { MODULE_NOT_SUPPORTED(sound_xaudio2, OSD_SOUND_PROVIDER, "xaudio2") } } + #endif -MODULE_DEFINITION(SOUND_XAUDIO2, sound_xaudio2) +MODULE_DEFINITION(SOUND_XAUDIO2, osd::sound_xaudio2) |