mirror of
https://github.com/holub/mame
synced 2025-07-04 09:28:51 +03:00
sound/xaudio2_sound.cpp: Better handling of critical errors, more verbose logging.
This commit is contained in:
parent
09426a9e5b
commit
584ede7bea
@ -292,6 +292,25 @@ HRESULT populate_audio_node_info(
|
||||
}
|
||||
|
||||
|
||||
HRESULT get_default_audio_device_id(
|
||||
IMMDeviceEnumerator &enumerator,
|
||||
EDataFlow data_flow,
|
||||
ERole role,
|
||||
co_task_wstr_ptr &value)
|
||||
{
|
||||
HRESULT result;
|
||||
|
||||
mm_device_ptr device;
|
||||
LPWSTR id_raw = nullptr;
|
||||
result = enumerator.GetDefaultAudioEndpoint(data_flow, role, device.GetAddressOf());
|
||||
if (SUCCEEDED(result) && device)
|
||||
result = device->GetId(&id_raw);
|
||||
|
||||
value.reset(std::exchange(id_raw, nullptr));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
HRESULT get_string_property_value(
|
||||
IPropertyStore &properties,
|
||||
REFPROPERTYKEY key,
|
||||
|
@ -92,12 +92,17 @@ HRESULT populate_audio_node_info(
|
||||
std::wstring &device_id,
|
||||
audio_info::node_info &info);
|
||||
|
||||
HRESULT get_default_audio_device_id(
|
||||
IMMDeviceEnumerator &enumerator,
|
||||
EDataFlow data_flow,
|
||||
ERole role,
|
||||
co_task_wstr_ptr &value);
|
||||
|
||||
HRESULT get_string_property_value(
|
||||
IPropertyStore &properties,
|
||||
REFPROPERTYKEY key,
|
||||
std::optional<std::string> &value);
|
||||
|
||||
|
||||
} // namespace osd
|
||||
|
||||
#endif // defined(_WIN32)
|
||||
|
@ -27,9 +27,9 @@
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
@ -72,7 +72,7 @@ constexpr float RESAMPLE_TOLERANCE = 1.20F;
|
||||
#define HR_LOG( CALL, LOGFN, ONFAIL ) do { \
|
||||
result = CALL; \
|
||||
if (FAILED(result)) { \
|
||||
LOGFN(#CALL " failed with error 0x%X\n", (unsigned int)result); \
|
||||
LOGFN("Sound: " #CALL " failed with error 0x%X\n", (unsigned int)result); \
|
||||
ONFAIL; } \
|
||||
} while (0)
|
||||
|
||||
@ -207,13 +207,13 @@ public:
|
||||
virtual int init(osd_interface &osd, osd_options const &options) override;
|
||||
virtual void exit() override;
|
||||
|
||||
// sound_module
|
||||
// sound_module implementation
|
||||
virtual uint32_t get_generation() override;
|
||||
virtual audio_info get_information() override;
|
||||
virtual bool external_per_channel_volume() override { return true; }
|
||||
virtual bool split_streams_per_source() override { return true; }
|
||||
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_set_volumes(uint32_t id, std::vector<float> const &db) override;
|
||||
virtual void stream_close(uint32_t id) override;
|
||||
virtual void stream_sink_update(uint32_t id, int16_t const *buffer, int samples_this_frame) override;
|
||||
|
||||
@ -250,6 +250,7 @@ private:
|
||||
|
||||
void update(int16_t const *buffer, int samples_this_frame);
|
||||
void submit_if_needed();
|
||||
void stop();
|
||||
|
||||
source_voice_ptr voice;
|
||||
audio_info::stream_info info;
|
||||
@ -286,21 +287,22 @@ private:
|
||||
using voice_info_vector = std::vector<voice_info_ptr>;
|
||||
|
||||
// IMMNotificationClient callbacks
|
||||
virtual HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDefaultDeviceId) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR pwstrDeviceId) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR pwstrDeviceId) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDefaultDeviceId) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(LPCWSTR pwstrDefaultDeviceId, PROPERTYKEY const key) override;
|
||||
|
||||
// stub IUnknown implementation
|
||||
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) override;
|
||||
virtual ULONG STDMETHODCALLTYPE AddRef() override { return 1; }
|
||||
virtual ULONG STDMETHODCALLTYPE Release() override { return 1; }
|
||||
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) noexcept override;
|
||||
virtual ULONG STDMETHODCALLTYPE AddRef() noexcept override { return 1; }
|
||||
virtual ULONG STDMETHODCALLTYPE Release() noexcept override { return 1; }
|
||||
|
||||
device_info_vector_iterator find_device(std::wstring_view device_id);
|
||||
HRESULT add_device(device_info_vector_iterator pos, LPCWSTR device_id);
|
||||
HRESULT add_device(device_info_vector_iterator pos, std::wstring_view device_id, mm_device_ptr &&device);
|
||||
HRESULT remove_device(device_info_vector_iterator pos);
|
||||
void purge_voices(uint32_t node);
|
||||
|
||||
void audio_task();
|
||||
void cleanup_task();
|
||||
@ -376,23 +378,17 @@ int sound_xaudio2::init(osd_interface &osd, osd_options const &options)
|
||||
HR_GOERR(m_device_enum->RegisterEndpointNotificationCallback(this));
|
||||
m_endpoint_notifications = true;
|
||||
|
||||
{
|
||||
mm_device_ptr default_device;
|
||||
LPWSTR id_raw = nullptr;
|
||||
HR_GOERR(m_device_enum->GetDefaultAudioEndpoint(eRender, eMultimedia, default_device.GetAddressOf()));
|
||||
HR_GOERR(default_device->GetId(&id_raw));
|
||||
default_id.reset(std::exchange(id_raw, nullptr));
|
||||
}
|
||||
HR_GOERR(get_default_audio_device_id(*m_device_enum.Get(), eRender, eMultimedia, default_id));
|
||||
|
||||
result = enumerate_audio_endpoints(
|
||||
*m_device_enum.Get(),
|
||||
eAll,
|
||||
eRender,
|
||||
DEVICE_STATE_ACTIVE | DEVICE_STATE_UNPLUGGED,
|
||||
[this, &default_id] (HRESULT hr, mm_device_ptr &dev) -> bool
|
||||
{
|
||||
if (FAILED(hr) || !dev)
|
||||
{
|
||||
osd_printf_error("Error getting audio device. Error: 0x%X\n", static_cast<unsigned int>(hr));
|
||||
osd_printf_error("Sound: Error getting audio device. Error: 0x%X\n", static_cast<unsigned int>(hr));
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -401,7 +397,7 @@ int sound_xaudio2::init(osd_interface &osd, osd_options const &options)
|
||||
hr = dev->GetState(&state);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
osd_printf_error("Error getting audio device state. Error: 0x%X\n", static_cast<unsigned int>(hr));
|
||||
osd_printf_error("Sound: Error getting audio device state. Error: 0x%X\n", static_cast<unsigned int>(hr));
|
||||
return true;
|
||||
}
|
||||
if ((DEVICE_STATE_ACTIVE != state) && (DEVICE_STATE_UNPLUGGED != state))
|
||||
@ -426,6 +422,12 @@ int sound_xaudio2::init(osd_interface &osd, osd_options const &options)
|
||||
devinfo->device_id = std::move(device_id);
|
||||
devinfo->device = std::move(dev);
|
||||
devinfo->info = std::move(info);
|
||||
|
||||
osd_printf_verbose(
|
||||
"Sound: Found audio device %s (%s), assigned ID %u.\n",
|
||||
devinfo->info.m_name,
|
||||
osd::text::from_wstring(devinfo->device_id),
|
||||
devinfo->info.m_id);
|
||||
if (!m_default_sink && default_id && (devinfo->device_id == default_id.get()))
|
||||
m_default_sink = devinfo->info.m_id;
|
||||
m_device_info.emplace(pos, std::move(devinfo));
|
||||
@ -437,7 +439,7 @@ int sound_xaudio2::init(osd_interface &osd, osd_options const &options)
|
||||
}
|
||||
if (FAILED(result))
|
||||
{
|
||||
osd_printf_error("Error enumerating audio endpoints. Error: 0x%X\n", static_cast<unsigned int>(result));
|
||||
osd_printf_error("Sound: Error enumerating audio endpoints. Error: 0x%X\n", static_cast<unsigned int>(result));
|
||||
goto Error;
|
||||
}
|
||||
|
||||
@ -464,12 +466,41 @@ Error:
|
||||
|
||||
void sound_xaudio2::exit()
|
||||
{
|
||||
// unsubscribe from device events
|
||||
if (m_endpoint_notifications)
|
||||
{
|
||||
m_device_enum->UnregisterEndpointNotificationCallback(this);
|
||||
m_endpoint_notifications = false;
|
||||
}
|
||||
|
||||
// schedule destruction of voices
|
||||
{
|
||||
std::lock_guard voice_lock(m_voice_mutex);
|
||||
while (!m_voice_info.empty())
|
||||
{
|
||||
{
|
||||
std::lock_guard cleanup_lock(m_cleanup_mutex);
|
||||
m_zombie_voices.emplace_back(std::move(m_voice_info.back()));
|
||||
m_cleanup_condition.notify_all();
|
||||
}
|
||||
m_voice_info.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
// schedule destruction of devices
|
||||
{
|
||||
std::lock_guard device_lock(m_device_mutex);
|
||||
while (!m_device_info.empty())
|
||||
{
|
||||
{
|
||||
std::lock_guard cleanup_lock(m_cleanup_mutex);
|
||||
m_zombie_devices.emplace_back(std::move(m_device_info.back()));
|
||||
m_cleanup_condition.notify_all();
|
||||
}
|
||||
m_device_info.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
// Wait on processing thread to end
|
||||
if (m_hEventExiting)
|
||||
SetEvent(m_hEventExiting);
|
||||
@ -489,32 +520,6 @@ void sound_xaudio2::exit()
|
||||
m_hEventExiting = nullptr;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard voice_lock(m_voice_mutex);
|
||||
while (!m_voice_info.empty())
|
||||
{
|
||||
{
|
||||
std::lock_guard cleanup_lock(m_cleanup_mutex);
|
||||
m_zombie_voices.emplace_back(std::move(m_voice_info.back()));
|
||||
m_cleanup_condition.notify_all();
|
||||
}
|
||||
m_voice_info.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard device_lock(m_device_mutex);
|
||||
while (!m_device_info.empty())
|
||||
{
|
||||
{
|
||||
std::lock_guard cleanup_lock(m_cleanup_mutex);
|
||||
m_zombie_devices.emplace_back(std::move(m_device_info.back()));
|
||||
m_cleanup_condition.notify_all();
|
||||
}
|
||||
m_device_info.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
if (m_cleanup_thread.joinable())
|
||||
{
|
||||
{
|
||||
@ -593,9 +598,13 @@ uint32_t sound_xaudio2::stream_sink_open(uint32_t node, std::string name, uint32
|
||||
m_device_info.begin(),
|
||||
m_device_info.end(),
|
||||
[node] (device_info_ptr const &value) { return value->info.m_id == node; });
|
||||
|
||||
try
|
||||
{
|
||||
// always check for stale argument values
|
||||
if (m_device_info.end() == device)
|
||||
{
|
||||
osd_printf_error("Attempt to open audio stream %s for unknown node %u.\n", name, node);
|
||||
osd_printf_verbose("Sound: Attempt to open audio stream %s for unknown node %u.\n", name, node);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -607,7 +616,7 @@ uint32_t sound_xaudio2::stream_sink_open(uint32_t node, std::string name, uint32
|
||||
{
|
||||
(*device)->engine = nullptr;
|
||||
osd_printf_error(
|
||||
"Error creating XAudio2 engine for audio device %s. Error: 0x%X\n",
|
||||
"Sound: Error creating XAudio2 engine for audio device %s. Error: 0x%X\n",
|
||||
(*device)->info.m_name,
|
||||
static_cast<unsigned int>(result));
|
||||
return 0;
|
||||
@ -618,11 +627,15 @@ uint32_t sound_xaudio2::stream_sink_open(uint32_t node, std::string name, uint32
|
||||
{
|
||||
(*device)->engine = nullptr;
|
||||
osd_printf_error(
|
||||
"Error registering to receive XAudio2 engine callbacks for audio device %s. Error: 0x%X\n",
|
||||
"Sound: Error registering to receive XAudio2 engine callbacks for audio device %s. Error: 0x%X\n",
|
||||
(*device)->info.m_name,
|
||||
static_cast<unsigned int>(result));
|
||||
return 0;
|
||||
}
|
||||
|
||||
osd_printf_verbose(
|
||||
"Sound: Created XAudio2 engine for audio device %s.\n",
|
||||
(*device)->info.m_name);
|
||||
}
|
||||
|
||||
// create a mastering voice if we don't already have one for this device
|
||||
@ -642,21 +655,19 @@ uint32_t sound_xaudio2::stream_sink_open(uint32_t node, std::string name, uint32
|
||||
{
|
||||
(*device)->mastering_voice.reset();
|
||||
osd_printf_error(
|
||||
"Error creating mastering voice for audio device %s. Error: 0x%X\n",
|
||||
"Sound: Error creating mastering voice for audio device %s. Error: 0x%X\n",
|
||||
(*device)->info.m_name,
|
||||
static_cast<unsigned int>(result));
|
||||
return 0;
|
||||
}
|
||||
|
||||
osd_printf_verbose(
|
||||
"Sound: Created XAudio2 mastering voice for audio device %s.\n",
|
||||
(*device)->info.m_name);
|
||||
}
|
||||
|
||||
voice_info_ptr info;
|
||||
// set up desired input format
|
||||
WAVEFORMATEX format;
|
||||
XAUDIO2_SEND_DESCRIPTOR destination;
|
||||
XAUDIO2_VOICE_SENDS sends;
|
||||
|
||||
try
|
||||
{
|
||||
// set up desired input format and destination
|
||||
format.wFormatTag = WAVE_FORMAT_PCM;
|
||||
format.nChannels = (*device)->info.m_sinks;
|
||||
format.nSamplesPerSec = rate;
|
||||
@ -664,20 +675,19 @@ uint32_t sound_xaudio2::stream_sink_open(uint32_t node, std::string name, uint32
|
||||
format.nBlockAlign = 2 * format.nChannels;
|
||||
format.wBitsPerSample = 16;
|
||||
format.cbSize = 0;
|
||||
|
||||
// set up destinations
|
||||
XAUDIO2_SEND_DESCRIPTOR destination;
|
||||
XAUDIO2_VOICE_SENDS sends;
|
||||
destination.Flags = 0;
|
||||
destination.pOutputVoice = (*device)->mastering_voice.get();
|
||||
sends.SendCount = 1;
|
||||
sends.pSends = &destination;
|
||||
|
||||
// create the voice info object
|
||||
info = std::make_unique<voice_info>(*this, format);
|
||||
voice_info_ptr info = std::make_unique<voice_info>(*this, format);
|
||||
info->info.m_node = node;
|
||||
info->info.m_volumes.resize((*device)->info.m_sinks, 0.0F);
|
||||
}
|
||||
catch (std::bad_alloc const &)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// create a source voice for this stream
|
||||
IXAudio2SourceVoice *source_voice_raw = nullptr;
|
||||
@ -693,11 +703,15 @@ uint32_t sound_xaudio2::stream_sink_open(uint32_t node, std::string name, uint32
|
||||
if (FAILED(result) || !info->voice)
|
||||
{
|
||||
osd_printf_error(
|
||||
"Error creating source voice for audio device %s. Error: 0x%X\n",
|
||||
"Sound: Error creating source voice for audio device %s. Error: 0x%X\n",
|
||||
(*device)->info.m_name,
|
||||
static_cast<unsigned int>(result));
|
||||
return 0;
|
||||
}
|
||||
osd_printf_verbose(
|
||||
"Sound: Created XAudio2 source voice for %s on audio device %s.\n",
|
||||
name,
|
||||
(*device)->info.m_name);
|
||||
|
||||
// set the channel mapping
|
||||
result = info->voice->SetOutputMatrix(
|
||||
@ -709,7 +723,7 @@ uint32_t sound_xaudio2::stream_sink_open(uint32_t node, std::string name, uint32
|
||||
if (FAILED(result))
|
||||
{
|
||||
osd_printf_error(
|
||||
"Error setting source voice output matrix for audio device %s. Error: 0x%X\n",
|
||||
"Sound: Error setting source voice output matrix for audio device %s. Error: 0x%X\n",
|
||||
(*device)->info.m_name,
|
||||
static_cast<unsigned int>(result));
|
||||
return 0;
|
||||
@ -720,14 +734,13 @@ uint32_t sound_xaudio2::stream_sink_open(uint32_t node, std::string name, uint32
|
||||
if (FAILED(result))
|
||||
{
|
||||
osd_printf_error(
|
||||
"Error starting source voice for audio device %s. Error: 0x%X\n",
|
||||
"Sound: Error starting source voice for audio device %s. Error: 0x%X\n",
|
||||
(*device)->info.m_name,
|
||||
static_cast<unsigned int>(result));
|
||||
return 0;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// try to add it to the collection
|
||||
std::lock_guard voice_lock(m_voice_mutex);
|
||||
{
|
||||
std::lock_guard cleanup_lock(m_cleanup_mutex);
|
||||
@ -776,7 +789,7 @@ void sound_xaudio2::stream_set_volumes(uint32_t id, const std::vector<float> &db
|
||||
if (FAILED(result))
|
||||
{
|
||||
osd_printf_error(
|
||||
"Error setting source voice output matrix for audio stream %u. Error: 0x%X\n",
|
||||
"Sound: Error setting source voice output matrix for audio stream %u. Error: 0x%X\n",
|
||||
(*pos)->info.m_id,
|
||||
static_cast<unsigned int>(result));
|
||||
}
|
||||
@ -845,19 +858,55 @@ sound_xaudio2::device_info::~device_info()
|
||||
|
||||
void sound_xaudio2::device_info::OnCriticalError(HRESULT error)
|
||||
{
|
||||
x_audio_2_ptr old_engine;
|
||||
mastering_voice_ptr old_mastering_voice;
|
||||
{
|
||||
std::lock_guard device_lock(m_host.m_device_mutex);
|
||||
|
||||
// clean up any voices associated with this device
|
||||
m_host.purge_voices(info.m_id);
|
||||
|
||||
// tear down the XAudio2 engine - it will be recreated next time a stream is opened
|
||||
old_engine = std::move(engine);
|
||||
old_mastering_voice = std::move(mastering_voice);
|
||||
mastering_voice.reset();
|
||||
engine = nullptr;
|
||||
|
||||
// bump this so the sound manager recreates its streams
|
||||
//info.m_id = m_host.m_next_node_id++; FIXME: causes crash in sound_manager::update_osd_streams()
|
||||
|
||||
++m_host.m_generation;
|
||||
|
||||
osd_printf_verbose(
|
||||
"XAudio2 critical error for device %s. Error: 0x%X\n",
|
||||
"Sound: XAudio2 critical error for device %s. Error: 0x%X\n",
|
||||
info.m_name,
|
||||
static_cast<unsigned int>(error));
|
||||
}
|
||||
|
||||
auto const pos = std::find_if(
|
||||
m_host.m_device_info.begin(),
|
||||
m_host.m_device_info.end(),
|
||||
[this] (device_info_ptr const &value) { return value.get() == this; });
|
||||
if (m_host.m_device_info.end() != pos)
|
||||
m_host.remove_device(pos);
|
||||
// try to get another thread to do the cleanup
|
||||
device_info_ptr zombie;
|
||||
try
|
||||
{
|
||||
zombie = std::make_unique<device_info>(m_host);
|
||||
zombie->engine = std::move(old_engine);
|
||||
zombie->mastering_voice = std::move(old_mastering_voice);
|
||||
old_mastering_voice.reset();
|
||||
old_engine = nullptr;
|
||||
{
|
||||
std::lock_guard cleanup_lock(m_host.m_cleanup_mutex);
|
||||
m_host.m_zombie_devices.emplace_back(std::move(zombie));
|
||||
m_host.m_cleanup_condition.notify_one();
|
||||
}
|
||||
}
|
||||
catch (std::bad_alloc const &)
|
||||
{
|
||||
// make sure the cleanup thread wakes up
|
||||
std::lock_guard cleanup_lock(m_host.m_cleanup_mutex);
|
||||
m_host.m_cleanup_condition.notify_one();
|
||||
|
||||
// old engine and mastering voice will fall out of scope and be destroyed
|
||||
// this could block things it shouldn't, but leaking would be worse
|
||||
}
|
||||
}
|
||||
|
||||
//============================================================
|
||||
@ -902,12 +951,8 @@ sound_xaudio2::voice_info::voice_info(sound_xaudio2 &host, WAVEFORMATEX const &f
|
||||
sound_xaudio2::voice_info::~voice_info()
|
||||
{
|
||||
// stop this before any buffers get destructed
|
||||
if (voice)
|
||||
{
|
||||
voice->Stop(0, XAUDIO2_COMMIT_NOW);
|
||||
voice->FlushSourceBuffers();
|
||||
stop();
|
||||
voice.reset();
|
||||
}
|
||||
}
|
||||
|
||||
//============================================================
|
||||
@ -982,6 +1027,23 @@ void sound_xaudio2::voice_info::submit_if_needed()
|
||||
}
|
||||
}
|
||||
|
||||
//============================================================
|
||||
// stop
|
||||
//============================================================
|
||||
|
||||
void sound_xaudio2::voice_info::stop()
|
||||
{
|
||||
while (!m_buffer_queue.empty())
|
||||
m_buffer_queue.pop();
|
||||
m_write_position = 0;
|
||||
m_need_update = false;
|
||||
if (voice)
|
||||
{
|
||||
voice->Stop(0, XAUDIO2_COMMIT_NOW);
|
||||
voice->FlushSourceBuffers();
|
||||
}
|
||||
}
|
||||
|
||||
//============================================================
|
||||
// roll_buffer
|
||||
//============================================================
|
||||
@ -1091,7 +1153,7 @@ void sound_xaudio2::voice_info::OnVoiceError(void *pBufferContext, HRESULT error
|
||||
std::lock_guard voice_lock(m_host.m_voice_mutex);
|
||||
|
||||
osd_printf_verbose(
|
||||
"XAudio2 voice error for stream %u. Error: 0x%X\n",
|
||||
"Sound: XAudio2 voice error for stream %u. Error: 0x%X\n",
|
||||
info.m_id,
|
||||
static_cast<unsigned int>(error));
|
||||
|
||||
@ -1118,23 +1180,46 @@ void sound_xaudio2::voice_info::OnVoiceError(void *pBufferContext, HRESULT error
|
||||
}
|
||||
|
||||
//============================================================
|
||||
// IMMNotificationClient::OnDefaultDeviceChanged
|
||||
// IMMNotificationClient::OnDeviceStateChanged
|
||||
//============================================================
|
||||
|
||||
HRESULT sound_xaudio2::OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDefaultDeviceId)
|
||||
HRESULT sound_xaudio2::OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState)
|
||||
{
|
||||
try
|
||||
{
|
||||
if ((eRender == flow) && (eMultimedia == role))
|
||||
{
|
||||
std::lock_guard device_lock(m_device_mutex);
|
||||
std::wstring_view device_id(pwstrDefaultDeviceId);
|
||||
std::wstring_view device_id(pwstrDeviceId);
|
||||
auto const pos = find_device(device_id);
|
||||
if ((m_device_info.end() != pos) && ((*pos)->device_id == device_id) && ((*pos)->info.m_id != m_default_sink))
|
||||
{
|
||||
m_default_sink = (*pos)->info.m_id;
|
||||
|
||||
++m_generation;
|
||||
if ((DEVICE_STATE_ACTIVE == dwNewState) || (DEVICE_STATE_UNPLUGGED == dwNewState))
|
||||
{
|
||||
if ((m_device_info.end() == pos) || ((*pos)->device_id != device_id))
|
||||
{
|
||||
HRESULT result;
|
||||
|
||||
mm_device_ptr device;
|
||||
result = m_device_enum->GetDevice(pwstrDeviceId, device.GetAddressOf());
|
||||
if (FAILED(result))
|
||||
{
|
||||
osd_printf_error(
|
||||
"Sound: Error getting audio device %s. Error: 0x%X\n",
|
||||
osd::text::from_wstring(device_id),
|
||||
static_cast<unsigned int>(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
result = add_device(pos, device_id, std::move(device));
|
||||
if (FAILED(result))
|
||||
return result;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((m_device_info.end() != pos) && ((*pos)->device_id == device_id))
|
||||
{
|
||||
HRESULT const result = remove_device(pos);
|
||||
if (FAILED(result))
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return S_OK;
|
||||
@ -1162,7 +1247,7 @@ HRESULT sound_xaudio2::OnDeviceAdded(LPCWSTR pwstrDeviceId)
|
||||
if ((m_device_info.end() != pos) && ((*pos)->device_id == device_id))
|
||||
{
|
||||
osd_printf_error(
|
||||
"Added sound device %s appears to already be present.\n",
|
||||
"Sound: Added sound device %s appears to already be present.\n",
|
||||
osd::text::from_wstring(device_id));
|
||||
return S_OK;
|
||||
}
|
||||
@ -1207,46 +1292,23 @@ HRESULT sound_xaudio2::OnDeviceRemoved(LPCWSTR pwstrDeviceId)
|
||||
}
|
||||
|
||||
//============================================================
|
||||
// IMMNotificationClient::OnDeviceStateChanged
|
||||
// IMMNotificationClient::OnDefaultDeviceChanged
|
||||
//============================================================
|
||||
|
||||
HRESULT sound_xaudio2::OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState)
|
||||
HRESULT sound_xaudio2::OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDefaultDeviceId)
|
||||
{
|
||||
try
|
||||
{
|
||||
if ((eRender == flow) && (eMultimedia == role))
|
||||
{
|
||||
std::lock_guard device_lock(m_device_mutex);
|
||||
std::wstring_view device_id(pwstrDeviceId);
|
||||
std::wstring_view device_id(pwstrDefaultDeviceId);
|
||||
auto const pos = find_device(device_id);
|
||||
if ((m_device_info.end() != pos) && ((*pos)->device_id == device_id) && ((*pos)->info.m_id != m_default_sink))
|
||||
{
|
||||
m_default_sink = (*pos)->info.m_id;
|
||||
|
||||
if ((DEVICE_STATE_ACTIVE == dwNewState) || (DEVICE_STATE_UNPLUGGED == dwNewState))
|
||||
{
|
||||
if ((m_device_info.end() == pos) || ((*pos)->device_id != device_id))
|
||||
{
|
||||
HRESULT result;
|
||||
|
||||
mm_device_ptr device;
|
||||
result = m_device_enum->GetDevice(pwstrDeviceId, device.GetAddressOf());
|
||||
if (FAILED(result))
|
||||
{
|
||||
osd_printf_error(
|
||||
"Error getting audio device %s. Error: 0x%X\n",
|
||||
osd::text::from_wstring(device_id),
|
||||
static_cast<unsigned int>(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
result = add_device(pos, device_id, std::move(device));
|
||||
if (FAILED(result))
|
||||
return result;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((m_device_info.end() != pos) && ((*pos)->device_id == device_id))
|
||||
{
|
||||
HRESULT const result = remove_device(pos);
|
||||
if (FAILED(result))
|
||||
return result;
|
||||
++m_generation;
|
||||
}
|
||||
}
|
||||
return S_OK;
|
||||
@ -1280,7 +1342,7 @@ HRESULT sound_xaudio2::OnPropertyValueChanged(LPCWSTR pwstrDeviceId, PROPERTYKEY
|
||||
if (FAILED(result))
|
||||
{
|
||||
osd_printf_error(
|
||||
"Error opening property store for audio device %s. Error: 0x%X\n",
|
||||
"Sound: Error opening property store for audio device %s. Error: 0x%X\n",
|
||||
(*pos)->info.m_name,
|
||||
static_cast<unsigned int>(result));
|
||||
return result;
|
||||
@ -1291,7 +1353,7 @@ HRESULT sound_xaudio2::OnPropertyValueChanged(LPCWSTR pwstrDeviceId, PROPERTYKEY
|
||||
if (FAILED(result))
|
||||
{
|
||||
osd_printf_error(
|
||||
"Error getting updated display name for audio device %s. Error: 0x%X\n",
|
||||
"Sound: Error getting updated display name for audio device %s. Error: 0x%X\n",
|
||||
(*pos)->info.m_name,
|
||||
static_cast<unsigned int>(result));
|
||||
return result;
|
||||
@ -1334,7 +1396,7 @@ HRESULT sound_xaudio2::OnPropertyValueChanged(LPCWSTR pwstrDeviceId, PROPERTYKEY
|
||||
// IUnknown::QueryInterface
|
||||
//============================================================
|
||||
|
||||
HRESULT sound_xaudio2::QueryInterface(REFIID riid, void **ppvObject)
|
||||
HRESULT sound_xaudio2::QueryInterface(REFIID riid, void **ppvObject) noexcept
|
||||
{
|
||||
if (!ppvObject)
|
||||
return E_POINTER;
|
||||
@ -1368,7 +1430,7 @@ HRESULT sound_xaudio2::add_device(device_info_vector_iterator pos, LPCWSTR devic
|
||||
if (FAILED(result))
|
||||
{
|
||||
osd_printf_error(
|
||||
"Error getting audio device %s. Error: 0x%X\n",
|
||||
"Sound: Error getting audio device %s. Error: 0x%X\n",
|
||||
osd::text::from_wstring(device_id),
|
||||
static_cast<unsigned int>(result));
|
||||
return result;
|
||||
@ -1380,7 +1442,7 @@ HRESULT sound_xaudio2::add_device(device_info_vector_iterator pos, LPCWSTR devic
|
||||
if (FAILED(result))
|
||||
{
|
||||
osd_printf_error(
|
||||
"Error getting audio device %s state. Error: 0x%X\n",
|
||||
"Sound: Error getting audio device %s state. Error: 0x%X\n",
|
||||
osd::text::from_wstring(device_id),
|
||||
static_cast<unsigned int>(result));
|
||||
return result;
|
||||
@ -1449,28 +1511,7 @@ HRESULT sound_xaudio2::remove_device(device_info_vector_iterator pos)
|
||||
}
|
||||
|
||||
// clean up any voices associated with this device
|
||||
{
|
||||
std::lock_guard voice_lock(m_voice_mutex);
|
||||
auto it = m_voice_info.begin();
|
||||
while (m_voice_info.end() != it)
|
||||
{
|
||||
if ((*it)->info.m_node == (*pos)->info.m_id)
|
||||
{
|
||||
voice_info_ptr voice = std::move(*it);
|
||||
it = m_voice_info.erase(it);
|
||||
{
|
||||
voice->voice->Stop(0, XAUDIO2_COMMIT_NOW);
|
||||
voice->voice->FlushSourceBuffers();
|
||||
std::lock_guard cleanup_lock(m_cleanup_mutex);
|
||||
m_zombie_voices.emplace_back(std::move(voice));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
purge_voices((*pos)->info.m_id);
|
||||
|
||||
// flag the device for cleanup
|
||||
{
|
||||
@ -1485,6 +1526,29 @@ HRESULT sound_xaudio2::remove_device(device_info_vector_iterator pos)
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
void sound_xaudio2::purge_voices(uint32_t node)
|
||||
{
|
||||
std::lock_guard voice_lock(m_voice_mutex);
|
||||
auto it = m_voice_info.begin();
|
||||
while (m_voice_info.end() != it)
|
||||
{
|
||||
if ((*it)->info.m_node == node)
|
||||
{
|
||||
voice_info_ptr voice = std::move(*it);
|
||||
it = m_voice_info.erase(it);
|
||||
voice->stop();
|
||||
{
|
||||
std::lock_guard cleanup_lock(m_cleanup_mutex);
|
||||
m_zombie_voices.emplace_back(std::move(voice));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sound_xaudio2::audio_task()
|
||||
{
|
||||
bool exiting = FALSE;
|
||||
@ -1510,7 +1574,7 @@ void sound_xaudio2::audio_task()
|
||||
|
||||
void sound_xaudio2::cleanup_task()
|
||||
{
|
||||
// clean up removed devices on a separate thread to avoid deadlocks
|
||||
// clean up removed voices and devices on a separate thread to avoid deadlocks
|
||||
std::unique_lock cleanup_lock(m_cleanup_mutex);
|
||||
while (!m_exiting || !m_zombie_devices.empty() || !m_zombie_voices.empty())
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user