sound/xaudio2_sound.cpp: Initial support for new sound system features.

This commit is contained in:
Vas Crabb 2025-05-23 07:49:09 +10:00
parent 571bc03fcc
commit aa1a83e481
5 changed files with 1543 additions and 384 deletions

View File

@ -132,6 +132,8 @@ function osdmodulesbuild()
MAME_DIR .. "src/osd/modules/sound/coreaudio_sound.cpp",
MAME_DIR .. "src/osd/modules/sound/direct_sound.cpp",
MAME_DIR .. "src/osd/modules/sound/js_sound.cpp",
MAME_DIR .. "src/osd/modules/sound/mmdevice_helpers.cpp",
MAME_DIR .. "src/osd/modules/sound/mmdevice_helpers.h",
MAME_DIR .. "src/osd/modules/sound/none.cpp",
MAME_DIR .. "src/osd/modules/sound/pa_sound.cpp",
MAME_DIR .. "src/osd/modules/sound/pulse_sound.cpp",

View File

@ -5,20 +5,28 @@
*
*/
#include "modules/osdmodule.h"
#include "monitor_module.h"
#include "modules/osdmodule.h"
#if defined(OSD_WINDOWS)
// local headers
#include "monitor_common.h"
// OSD headers
#include "osdcore.h"
#include "strconv.h"
#include "window.h"
#include "windows/video.h"
// standard windows headers
#include <windows.h>
#undef interface
#include "osdcore.h"
#include "strconv.h"
#include "windows/video.h"
#include "window.h"
#include "monitor_common.h"
namespace osd {
namespace {
class win32_monitor_module;
@ -28,10 +36,10 @@ private:
MONITORINFOEX m_info;
public:
win32_monitor_info(monitor_module& module, const HMONITOR handle, const char* monitor_device, float aspect)
: osd_monitor_info(module, std::uintptr_t(handle), monitor_device, aspect)
win32_monitor_info(monitor_module& module, const HMONITOR handle, std::string &&monitor_device, float aspect)
: osd_monitor_info(module, std::uintptr_t(handle), std::move(monitor_device), aspect)
{
win32_monitor_info::refresh();
refresh();
}
void refresh() override
@ -93,11 +101,9 @@ protected:
EnumDisplayMonitors(nullptr, nullptr, monitor_enum_callback, reinterpret_cast<std::intptr_t>(this));
// if we're verbose, print the list of monitors
for (const auto &monitor : list())
{
for (const auto &monitor : list())
{
osd_printf_verbose("Video: Monitor %u = \"%s\" %s\n", monitor->oshandle(), monitor->devicename(), monitor->is_primary() ? "(primary)" : "");
}
osd_printf_verbose("Video: Monitor %u = \"%s\" %s\n", monitor->oshandle(), monitor->devicename(), monitor->is_primary() ? "(primary)" : "");
}
return 0;
@ -106,7 +112,7 @@ protected:
private:
static BOOL CALLBACK monitor_enum_callback(HMONITOR handle, HDC dc, LPRECT rect, LPARAM data)
{
auto* self = reinterpret_cast<win32_monitor_module*>(data);
auto *const self = reinterpret_cast<win32_monitor_module *>(data);
MONITORINFOEX info;
BOOL result;
@ -116,14 +122,14 @@ private:
assert(result);
(void)result; // to silence gcc 4.6
// guess the aspect ratio assuming square pixels
float aspect = static_cast<float>(info.rcMonitor.right - info.rcMonitor.left) / static_cast<float>(info.rcMonitor.bottom - info.rcMonitor.top);
// guess the aspect ratio assuming square pixels
float aspect = float(info.rcMonitor.right - info.rcMonitor.left) / float(info.rcMonitor.bottom - info.rcMonitor.top);
// allocate a new monitor info
auto temp = osd::text::from_tstring(info.szDevice);
// copy in the data
auto monitor = std::make_shared<win32_monitor_info>(*self, handle, temp.c_str(), aspect);
auto monitor = std::make_shared<win32_monitor_info>(*self, handle, std::move(temp), aspect);
// hook us into the list
self->add_monitor(monitor);
@ -133,8 +139,14 @@ private:
}
};
} // anonymous namespace
} // namespace osd
#else
MODULE_NOT_SUPPORTED(win32_monitor_module, OSD_MONITOR_PROVIDER, "win32")
namespace osd { namespace { MODULE_NOT_SUPPORTED(win32_monitor_module, OSD_MONITOR_PROVIDER, "win32") } }
#endif
MODULE_DEFINITION(MONITOR_WIN32, win32_monitor_module)
MODULE_DEFINITION(MONITOR_WIN32, osd::win32_monitor_module)

View File

@ -0,0 +1,337 @@
// license:BSD-3-Clause
// copyright-holders:Vas Crabb
#include "mmdevice_helpers.h"
#if defined(_WIN32)
#include "osdcore.h"
#include "strconv.h"
#include "util/coretmpl.h"
#include <array>
#include <string_view>
#include <utility>
#include <vector>
#include <combaseapi.h>
#include <oleauto.h>
#include <mmreg.h>
#include <functiondiscoverykeys_devpkey.h>
namespace osd {
namespace {
using mm_endpoint_ptr = Microsoft::WRL::ComPtr<IMMEndpoint>;
char const *const f_speaker_names[] ={
"FL", // SPEAKER_FRONT_LEFT
"FR", // SPEAKER_FRONT_RIGHT
"FC", // SPEAKER_FRONT_CENTER
"LFE", // SPEAKER_LOW_FREQUENCY
"BL", // SPEAKER_BACK_LEFT
"BR", // SPEAKER_BACK_RIGHT
"FCL", // SPEAKER_FRONT_LEFT_OF_CENTER
"FCR", // SPEAKER_FRONT_RIGHT_OF_CENTER
"BC", // SPEAKER_BACK_CENTER
"SL", // SPEAKER_SIDE_LEFT
"SR", // SPEAKER_SIDE_RIGHT
"TC", // SPEAKER_TOP_CENTER
"TFL", // SPEAKER_TOP_FRONT_LEFT
"TFC", // SPEAKER_TOP_FRONT_CENTER
"TFR", // SPEAKER_TOP_FRONT_RIGHT
"TBL", // SPEAKER_TOP_BACK_LEFT
"TBC", // SPEAKER_TOP_BACK_CENTER
"TBR" }; // SPEAKER_TOP_BACK_RIGHT
std::array<double, 3> const f_speaker_positions[] = {
{ -0.2, 0.0, 1.0 }, // SPEAKER_FRONT_LEFT
{ 0.2, 0.0, 1.0 }, // SPEAKER_FRONT_RIGHT
{ 0.0, 0.0, 1.0 }, // SPEAKER_FRONT_CENTER
{ 0.0, -0.5, 1.0 }, // SPEAKER_LOW_FREQUENCY
{ -0.2, 0.0, -0.5 }, // SPEAKER_BACK_LEFT
{ 0.2, 0.0, -0.5 }, // SPEAKER_BACK_RIGHT
{ -0.1, 0.0, 1.0 }, // SPEAKER_FRONT_LEFT_OF_CENTER
{ 0.1, 0.0, 1.0 }, // SPEAKER_FRONT_RIGHT_OF_CENTER
{ 0.0, 0.0, -0.5 }, // SPEAKER_BACK_CENTER
{ -0.2, 0.0, 0.0 }, // SPEAKER_SIDE_LEFT
{ 0.2, 0.0, 0.0 }, // SPEAKER_SIDE_RIGHT
{ 0.0, 0.5, 0.0 }, // SPEAKER_TOP_CENTER
{ -0.2, 0.5, 1.0 }, // SPEAKER_TOP_FRONT_LEFT
{ 0.0, 0.5, 1.0 }, // SPEAKER_TOP_FRONT_CENTER
{ 0.2, 0.5, 1.0 }, // SPEAKER_TOP_FRONT_RIGHT
{ -0.2, 0.5, -0.5 }, // SPEAKER_TOP_BACK_LEFT
{ 0.0, 0.5, -0.5 }, // SPEAKER_TOP_BACK_CENTER
{ 0.2, 0.5, -0.5 } }; // SPEAKER_TOP_BACK_RIGHT
} // anonymous namespace
HRESULT populate_audio_node_info(
IMMDevice &device,
std::wstring &device_id,
audio_info::node_info &info)
{
HRESULT result;
// get the device ID
co_task_wstr_ptr device_id_w;
std::string id_string;
{
LPWSTR id_raw = nullptr;
result = device.GetId(&id_raw);
if (FAILED(result) || !id_raw)
{
osd_printf_error(
"Error getting ID for audio device. Error: 0x%X\n",
static_cast<unsigned int>(result));
return FAILED(result) ? result : E_POINTER;
}
device_id_w.reset(std::exchange(id_raw, nullptr));
try
{
osd::text::from_wstring(id_string, device_id_w.get());
}
catch (std::bad_alloc const &)
{
return E_OUTOFMEMORY;
}
}
// get the property store (needed for various important things)
property_store_ptr properties;
result = device.OpenPropertyStore(STGM_READ, properties.GetAddressOf());
if (FAILED(result) || !properties)
{
osd_printf_error(
"Error opening property store for audio device %s. Error: 0x%X\n",
id_string,
static_cast<unsigned int>(result));
return FAILED(result) ? result : E_POINTER;
}
// get the display name
std::string device_name;
{
std::optional<std::string> name_string;
result = get_string_property_value(*properties.Get(), PKEY_Device_FriendlyName, name_string);
if (FAILED(result) || !name_string)
{
// fall back to using device ID
osd_printf_error(
"Error getting display name for audio device %s. Error: 0x%X\n",
id_string,
static_cast<unsigned int>(result));
try
{
device_name = id_string;
}
catch (std::bad_alloc const &)
{
return E_OUTOFMEMORY;
}
}
else
{
device_name = std::move(*name_string);
}
}
// see whether it's an input or output
EDataFlow data_flow;
{
mm_endpoint_ptr endpoint;
result = device.QueryInterface(endpoint.GetAddressOf());
if (FAILED(result) || !endpoint)
{
osd_printf_error(
"Error getting endpoint information for audio device %s. Error: 0x%X\n",
device_name,
static_cast<unsigned int>(result));
return FAILED(result) ? result : E_POINTER;
}
result = endpoint->GetDataFlow(&data_flow);
if (FAILED(result))
{
osd_printf_error(
"Error getting data flow direction for audio device %s. Error: 0x%X\n",
device_name,
static_cast<unsigned int>(result));
return result;
}
if ((eRender != data_flow) && (eCapture != data_flow))
{
osd_printf_error(
"Invalid data flow direction for audio device %s. Value: %u\n",
device_name,
std::underlying_type_t<EDataFlow>(data_flow));
return E_INVALIDARG;
}
}
// get format information
prop_variant_helper format_property;
result = properties->GetValue(PKEY_AudioEngine_DeviceFormat, &format_property.value);
if (FAILED(result))
{
osd_printf_error(
"Error getting stream format for audio device %s. Error: 0x%X\n",
device_name,
static_cast<unsigned int>(result));
return result;
}
else if (VT_BLOB != format_property.value.vt)
{
// you can get VT_EMPTY when a device is initially added - don't warn about it
if (VT_EMPTY != format_property.value.vt)
{
osd_printf_error(
"Stream format has invalid data type for audio device %s. Type: %u\n",
device_name,
std::underlying_type_t<VARENUM>(format_property.value.vt));
}
return E_INVALIDARG;
}
auto const format = reinterpret_cast<WAVEFORMATEX const *>(format_property.value.blob.pBlobData);
// get the channel mask for speaker positions
std::optional<std::uint32_t> channel_mask;
{
prop_variant_helper speakers_property;
result = properties->GetValue(PKEY_AudioEndpoint_PhysicalSpeakers, &speakers_property.value);
if (FAILED(result))
{
osd_printf_error(
"Error getting speaker arrangement for audio device %s. Error: 0x%X\n",
device_name,
static_cast<unsigned int>(result));
}
else switch (speakers_property.value.vt)
{
case VT_EMPTY:
break;
case VT_UI4:
channel_mask = speakers_property.value.ulVal;
break;
case VT_UINT:
channel_mask = speakers_property.value.uintVal;
break;
default:
osd_printf_error(
"Speaker arrangement has invalid data type for audio device %s. Type: %u\n",
device_name,
std::underlying_type_t<VARENUM>(speakers_property.value.vt));
}
}
if (!channel_mask && (WAVE_FORMAT_EXTENSIBLE == format->wFormatTag))
{
auto const extensible_format = reinterpret_cast<WAVEFORMATEXTENSIBLE const *>(format);
channel_mask = extensible_format->dwChannelMask;
}
// set up channel info
std::vector<std::string> channel_names;
std::vector<std::array<double, 3> > channel_positions;
try
{
channel_names.reserve(format->nChannels);
channel_positions.reserve(format->nChannels);
DWORD i = 0;
if ((eRender == data_flow) && channel_mask)
{
static_assert(std::size(f_speaker_names) == std::size(f_speaker_positions));
DWORD b = 0;
while ((format->nChannels > i) && (std::size(f_speaker_names) > b))
{
if (util::BIT(*channel_mask, b))
{
channel_names.emplace_back(f_speaker_names[b]);
channel_positions.emplace_back(f_speaker_positions[b]);
++i;
}
++b;
}
}
while (format->nChannels > i)
{
channel_names.emplace_back(util::string_format("Channel %u", i + 1));
++i;
}
channel_positions.resize(format->nChannels, std::array<double, 3>{ 0.0, 0.0, 0.0 });
}
catch (std::bad_alloc const &)
{
return E_OUTOFMEMORY;
}
// set results
try
{
device_id = device_id_w.get();
info.m_name = std::move(device_name);
info.m_rate.m_default_rate = format->nSamplesPerSec;
info.m_rate.m_min_rate = format->nSamplesPerSec;
info.m_rate.m_max_rate = format->nSamplesPerSec;
info.m_port_names = std::move(channel_names);
info.m_port_positions = std::move(channel_positions);
info.m_sinks = (eRender == data_flow) ? format->nChannels : 0;
info.m_sources = (eCapture == data_flow) ? format->nChannels : 0;
}
catch (std::bad_alloc const &)
{
return E_OUTOFMEMORY;
}
return S_OK;
}
HRESULT get_string_property_value(
IPropertyStore &properties,
REFPROPERTYKEY key,
std::optional<std::string> &value)
{
prop_variant_helper property_value;
HRESULT const result = properties.GetValue(key, &property_value.value);
if (FAILED(result))
return result;
try
{
switch (property_value.value.vt)
{
case VT_EMPTY:
value = std::nullopt;
return result;
case VT_BSTR:
value = osd::text::from_wstring(std::wstring_view(property_value.value.bstrVal, SysStringLen(property_value.value.bstrVal)));
return result;
case VT_LPSTR:
value = osd::text::from_astring(property_value.value.pszVal);
return result;
case VT_LPWSTR:
value = osd::text::from_wstring(property_value.value.pwszVal);
return result;
default:
return E_INVALIDARG;
}
}
catch (std::bad_alloc const &)
{
return E_OUTOFMEMORY;
}
}
} // namespace osd
#endif // defined(_WIN32)

View File

@ -0,0 +1,105 @@
// license:BSD-3-Clause
// copyright-holders:Vas Crabb
#ifndef MAME_OSD_SOUND_MMDEVICE_HELPERS_H
#define MAME_OSD_SOUND_MMDEVICE_HELPERS_H
#pragma once
#if defined(_WIN32)
#include "interface/audio.h"
#include <memory>
#include <optional>
#include <string>
#include <windows.h>
#include <objbase.h>
#include <propidl.h>
#include <propsys.h>
#include <mmdeviceapi.h>
#include <wrl/client.h>
namespace osd {
struct prop_variant_helper
{
PROPVARIANT value;
prop_variant_helper() { PropVariantInit(&value); }
~prop_variant_helper() { PropVariantClear(&value); }
prop_variant_helper(prop_variant_helper const &) = delete;
prop_variant_helper &operator=(prop_variant_helper const &) = delete;
};
struct co_task_mem_deleter
{
template <typename T>
void operator()(T *obj) const
{
if (obj)
CoTaskMemFree(obj);
}
};
using co_task_wstr_ptr = std::unique_ptr<wchar_t, co_task_mem_deleter>;
using mm_device_ptr = Microsoft::WRL::ComPtr<IMMDevice>;
using mm_device_collection_ptr = Microsoft::WRL::ComPtr<IMMDeviceCollection>;
using mm_device_enumerator_ptr = Microsoft::WRL::ComPtr<IMMDeviceEnumerator>;
using property_store_ptr = Microsoft::WRL::ComPtr<IPropertyStore>;
template <typename T>
HRESULT enumerate_audio_endpoints(
IMMDeviceEnumerator &enumerator,
EDataFlow data_flow,
DWORD state_mask,
T &&action)
{
HRESULT result;
// get devices
mm_device_collection_ptr devices;
result = enumerator.EnumAudioEndpoints(data_flow, state_mask, devices.GetAddressOf());
if (FAILED(result))
return result;
// count devices
UINT count;
result = devices->GetCount(&count);
if (FAILED(result))
return result;
// enumerate devices
for (UINT i = 0; count > i; ++i)
{
mm_device_ptr device;
result = devices->Item(i, device.GetAddressOf());
if (!action(result, device))
break;
}
return S_OK;
}
HRESULT populate_audio_node_info(
IMMDevice &device,
std::wstring &device_id,
audio_info::node_info &info);
HRESULT get_string_property_value(
IPropertyStore &properties,
REFPROPERTYKEY key,
std::optional<std::string> &value);
} // namespace osd
#endif // defined(_WIN32)
#endif // MAME_OSD_SOUND_MMDEVICE_HELPERS_H

File diff suppressed because it is too large Load Diff