2020-12-05 13:08:24 +01:00
|
|
|
#ifndef _WIN32
|
2019-10-22 23:59:09 +02:00
|
|
|
#error "XAudio2 can only be built on Windows."
|
|
|
|
|
#endif
|
2016-07-21 15:41:40 +02:00
|
|
|
|
2020-12-22 09:42:57 +01:00
|
|
|
#include <algorithm>
|
2020-03-07 10:29:23 +01:00
|
|
|
#include "util/logs.hpp"
|
2020-02-17 22:31:49 +01:00
|
|
|
#include "Emu/System.h"
|
2015-10-26 22:09:31 +01:00
|
|
|
|
2018-12-16 18:40:50 +01:00
|
|
|
#include "XAudio2Backend.h"
|
2016-07-21 15:41:40 +02:00
|
|
|
#include <Windows.h>
|
2020-06-20 02:38:15 +02:00
|
|
|
#include <system_error>
|
2015-01-11 00:46:10 +01:00
|
|
|
|
2020-02-17 22:31:49 +01:00
|
|
|
#pragma comment(lib, "xaudio2_9redist.lib")
|
|
|
|
|
|
2020-01-31 13:05:35 +01:00
|
|
|
LOG_CHANNEL(XAudio);
|
|
|
|
|
|
2018-12-16 18:40:50 +01:00
|
|
|
XAudio2Backend::XAudio2Backend()
|
2020-06-20 02:44:32 +02:00
|
|
|
: AudioBackend()
|
2015-01-11 00:46:10 +01:00
|
|
|
{
|
2020-02-17 22:31:49 +01:00
|
|
|
Microsoft::WRL::ComPtr<IXAudio2> instance;
|
|
|
|
|
|
2020-06-20 02:43:08 +02:00
|
|
|
// In order to prevent errors on CreateMasteringVoice, apparently we need CoInitializeEx according to:
|
|
|
|
|
// https://docs.microsoft.com/en-us/windows/win32/api/xaudio2fx/nf-xaudio2fx-xaudio2createvolumemeter
|
2021-08-23 21:33:20 +02:00
|
|
|
HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
2021-11-24 19:41:05 +01:00
|
|
|
if (SUCCEEDED(hr))
|
2021-08-23 21:33:20 +02:00
|
|
|
{
|
2021-11-24 19:41:05 +01:00
|
|
|
m_com_init_success = true;
|
2021-08-23 21:33:20 +02:00
|
|
|
}
|
2020-06-20 02:43:08 +02:00
|
|
|
|
2021-11-24 19:41:05 +01:00
|
|
|
hr = XAudio2Create(instance.GetAddressOf(), 0, XAUDIO2_USE_DEFAULT_PROCESSOR);
|
2020-02-17 22:31:49 +01:00
|
|
|
if (FAILED(hr))
|
|
|
|
|
{
|
2020-06-20 02:38:15 +02:00
|
|
|
XAudio.error("XAudio2Create() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
|
2020-02-17 22:31:49 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2021-11-24 19:41:05 +01:00
|
|
|
hr = instance->RegisterForCallbacks(this);
|
|
|
|
|
if (FAILED(hr))
|
|
|
|
|
{
|
|
|
|
|
// Some error recovery functionality will be lost, but otherwise backend is operational
|
|
|
|
|
XAudio.error("RegisterForCallbacks() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-17 22:31:49 +01:00
|
|
|
// All succeeded, "commit"
|
|
|
|
|
m_xaudio2_instance = std::move(instance);
|
2015-01-11 00:46:10 +01:00
|
|
|
}
|
|
|
|
|
|
2018-12-16 18:40:50 +01:00
|
|
|
XAudio2Backend::~XAudio2Backend()
|
2015-01-11 00:46:10 +01:00
|
|
|
{
|
2021-11-24 19:41:05 +01:00
|
|
|
Close();
|
2020-02-17 22:31:49 +01:00
|
|
|
|
|
|
|
|
if (m_xaudio2_instance != nullptr)
|
|
|
|
|
{
|
|
|
|
|
m_xaudio2_instance->StopEngine();
|
2021-12-02 01:01:10 +01:00
|
|
|
|
|
|
|
|
// TODO: Enabling this might crash afterwards in ComPtr::InternalRelease.
|
|
|
|
|
// Maybe it's both trying to do the same thing?
|
|
|
|
|
//m_xaudio2_instance->Release();
|
2021-11-24 19:41:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_com_init_success)
|
|
|
|
|
{
|
|
|
|
|
CoUninitialize();
|
2020-02-17 22:31:49 +01:00
|
|
|
}
|
2015-01-11 00:46:10 +01:00
|
|
|
}
|
|
|
|
|
|
2021-12-01 22:36:01 +01:00
|
|
|
bool XAudio2Backend::Initialized()
|
|
|
|
|
{
|
|
|
|
|
return m_xaudio2_instance != nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-24 19:41:05 +01:00
|
|
|
bool XAudio2Backend::Operational()
|
|
|
|
|
{
|
2021-12-01 22:36:01 +01:00
|
|
|
if (m_dev_listener.output_device_changed())
|
|
|
|
|
{
|
|
|
|
|
m_reset_req = true;
|
|
|
|
|
}
|
2021-11-24 19:41:05 +01:00
|
|
|
|
|
|
|
|
return m_xaudio2_instance != nullptr && m_source_voice != nullptr && !m_reset_req.observe();
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-16 18:40:50 +01:00
|
|
|
void XAudio2Backend::Play()
|
2015-01-11 00:46:10 +01:00
|
|
|
{
|
2021-12-02 01:01:10 +01:00
|
|
|
if (m_source_voice == nullptr)
|
|
|
|
|
{
|
|
|
|
|
XAudio.error("Play() called uninitialized");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2021-11-24 19:41:05 +01:00
|
|
|
|
|
|
|
|
if (m_playing) return;
|
2020-02-17 22:31:49 +01:00
|
|
|
|
2021-04-09 21:12:47 +02:00
|
|
|
const HRESULT hr = m_source_voice->Start();
|
2020-02-17 22:31:49 +01:00
|
|
|
if (FAILED(hr))
|
|
|
|
|
{
|
2021-11-24 19:41:05 +01:00
|
|
|
XAudio.error("Start() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::lock_guard lock(m_cb_mutex);
|
|
|
|
|
m_playing = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void XAudio2Backend::CloseUnlocked()
|
|
|
|
|
{
|
2021-12-02 01:01:10 +01:00
|
|
|
if (m_source_voice != nullptr)
|
2021-11-24 19:41:05 +01:00
|
|
|
{
|
2021-12-02 01:01:10 +01:00
|
|
|
const HRESULT hr = m_source_voice->Stop();
|
|
|
|
|
if (FAILED(hr))
|
|
|
|
|
{
|
|
|
|
|
XAudio.error("Stop() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_source_voice->DestroyVoice();
|
|
|
|
|
m_source_voice = nullptr;
|
2020-02-17 22:31:49 +01:00
|
|
|
}
|
2021-11-24 19:41:05 +01:00
|
|
|
|
2022-01-10 13:26:33 +01:00
|
|
|
if (m_master_voice != nullptr)
|
|
|
|
|
{
|
|
|
|
|
m_master_voice->DestroyVoice();
|
|
|
|
|
m_master_voice = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-24 19:41:05 +01:00
|
|
|
m_playing = false;
|
|
|
|
|
m_data_buf = nullptr;
|
|
|
|
|
m_data_buf_len = 0;
|
|
|
|
|
memset(m_last_sample, 0, sizeof(m_last_sample));
|
2015-01-11 00:46:10 +01:00
|
|
|
}
|
|
|
|
|
|
2018-12-16 18:40:50 +01:00
|
|
|
void XAudio2Backend::Close()
|
2015-01-11 00:46:10 +01:00
|
|
|
{
|
2021-11-24 19:41:05 +01:00
|
|
|
std::lock_guard lock(m_cb_mutex);
|
|
|
|
|
CloseUnlocked();
|
2015-01-11 00:46:10 +01:00
|
|
|
}
|
|
|
|
|
|
2018-12-16 18:40:50 +01:00
|
|
|
void XAudio2Backend::Pause()
|
2015-01-11 00:46:10 +01:00
|
|
|
{
|
2021-12-02 01:01:10 +01:00
|
|
|
if (m_source_voice)
|
2021-11-24 19:41:05 +01:00
|
|
|
{
|
2021-12-02 01:01:10 +01:00
|
|
|
HRESULT hr = m_source_voice->Stop();
|
|
|
|
|
if (FAILED(hr))
|
|
|
|
|
{
|
|
|
|
|
XAudio.error("Stop() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
|
|
|
|
|
}
|
2021-11-24 19:41:05 +01:00
|
|
|
|
2021-12-02 01:01:10 +01:00
|
|
|
hr = m_source_voice->FlushSourceBuffers();
|
|
|
|
|
if (FAILED(hr))
|
|
|
|
|
{
|
|
|
|
|
XAudio.error("FlushSourceBuffers() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
2020-02-17 22:31:49 +01:00
|
|
|
{
|
2021-12-02 01:01:10 +01:00
|
|
|
XAudio.error("Pause() called uninitialized");
|
2020-02-17 22:31:49 +01:00
|
|
|
}
|
2021-11-24 19:41:05 +01:00
|
|
|
|
|
|
|
|
std::lock_guard lock(m_cb_mutex);
|
|
|
|
|
m_playing = false;
|
2015-01-11 00:46:10 +01:00
|
|
|
}
|
|
|
|
|
|
2021-11-24 19:41:05 +01:00
|
|
|
void XAudio2Backend::Open(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt)
|
2015-01-11 00:46:10 +01:00
|
|
|
{
|
2021-11-24 19:41:05 +01:00
|
|
|
std::lock_guard lock(m_cb_mutex);
|
|
|
|
|
CloseUnlocked();
|
|
|
|
|
|
2021-12-02 01:01:10 +01:00
|
|
|
if (m_xaudio2_instance == nullptr)
|
|
|
|
|
{
|
|
|
|
|
XAudio.error("Open() called unitiliazed");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HRESULT hr = m_xaudio2_instance->CreateMasteringVoice(&m_master_voice);
|
|
|
|
|
if (FAILED(hr))
|
|
|
|
|
{
|
|
|
|
|
XAudio.error("CreateMasteringVoice() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
|
|
|
|
|
XAudio.error("Failed to open audio backend. Make sure that no other application is running that might block audio access (e.g. Netflix).");
|
|
|
|
|
m_reset_req = true;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-24 19:41:05 +01:00
|
|
|
m_sampling_rate = freq;
|
|
|
|
|
m_sample_size = sample_size;
|
|
|
|
|
m_channels = ch_cnt;
|
|
|
|
|
|
2021-08-23 21:33:20 +02:00
|
|
|
WAVEFORMATEX waveformatex{};
|
2021-11-24 19:41:05 +01:00
|
|
|
waveformatex.wFormatTag = get_convert_to_s16() ? WAVE_FORMAT_PCM : WAVE_FORMAT_IEEE_FLOAT;
|
|
|
|
|
waveformatex.nChannels = get_channels();
|
|
|
|
|
waveformatex.nSamplesPerSec = get_sampling_rate();
|
|
|
|
|
waveformatex.nAvgBytesPerSec = static_cast<DWORD>(get_sampling_rate() * get_channels() * get_sample_size());
|
|
|
|
|
waveformatex.nBlockAlign = get_channels() * get_sample_size();
|
|
|
|
|
waveformatex.wBitsPerSample = get_sample_size() * 8;
|
2020-02-17 22:31:49 +01:00
|
|
|
waveformatex.cbSize = 0;
|
|
|
|
|
|
2021-12-02 01:01:10 +01:00
|
|
|
hr = m_xaudio2_instance->CreateSourceVoice(&m_source_voice, &waveformatex, 0, XAUDIO2_DEFAULT_FREQ_RATIO, this);
|
2020-02-17 22:31:49 +01:00
|
|
|
if (FAILED(hr))
|
2018-12-21 03:13:22 +01:00
|
|
|
{
|
2021-11-24 19:41:05 +01:00
|
|
|
XAudio.error("CreateSourceVoice() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
|
2020-02-17 22:31:49 +01:00
|
|
|
return;
|
2018-12-21 03:13:22 +01:00
|
|
|
}
|
|
|
|
|
|
2021-11-24 19:41:05 +01:00
|
|
|
ensure(m_source_voice != nullptr);
|
2020-06-01 17:34:11 +02:00
|
|
|
m_source_voice->SetVolume(1.0f);
|
2021-11-24 19:41:05 +01:00
|
|
|
|
|
|
|
|
m_data_buf_len = get_sampling_rate() * get_sample_size() * get_channels() * INTERNAL_BUF_SIZE_MS * static_cast<u32>(XAUDIO2_DEFAULT_FREQ_RATIO) / 1000;
|
|
|
|
|
m_data_buf = std::make_unique<u8[]>(m_data_buf_len);
|
2015-01-11 00:46:10 +01:00
|
|
|
}
|
|
|
|
|
|
2018-12-16 18:40:50 +01:00
|
|
|
bool XAudio2Backend::IsPlaying()
|
2018-12-20 23:35:49 +01:00
|
|
|
{
|
2021-11-24 19:41:05 +01:00
|
|
|
return m_playing;
|
2018-12-20 23:35:49 +01:00
|
|
|
}
|
|
|
|
|
|
2021-11-24 19:41:05 +01:00
|
|
|
void XAudio2Backend::SetWriteCallback(std::function<u32(u32, void *)> cb)
|
|
|
|
|
{
|
|
|
|
|
std::lock_guard lock(m_cb_mutex);
|
|
|
|
|
m_write_callback = cb;
|
2018-12-20 23:35:49 +01:00
|
|
|
}
|
|
|
|
|
|
2021-11-24 19:41:05 +01:00
|
|
|
f64 XAudio2Backend::GetCallbackFrameLen()
|
2018-12-20 23:35:49 +01:00
|
|
|
{
|
2021-12-02 01:01:10 +01:00
|
|
|
constexpr f64 _10ms = 0.01;
|
|
|
|
|
|
|
|
|
|
if (m_source_voice == nullptr)
|
|
|
|
|
{
|
|
|
|
|
XAudio.error("GetCallbackFrameLen() called uninitialized");
|
|
|
|
|
return _10ms;
|
|
|
|
|
}
|
2021-11-24 19:41:05 +01:00
|
|
|
|
|
|
|
|
void *ext;
|
|
|
|
|
f64 min_latency{};
|
2020-02-17 22:31:49 +01:00
|
|
|
|
2021-11-24 19:41:05 +01:00
|
|
|
HRESULT hr = m_xaudio2_instance->QueryInterface(IID_IXAudio2Extension, &ext);
|
2020-02-17 22:31:49 +01:00
|
|
|
if (FAILED(hr))
|
|
|
|
|
{
|
2021-11-24 19:41:05 +01:00
|
|
|
XAudio.error("QueryInterface() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
|
2020-02-17 22:31:49 +01:00
|
|
|
}
|
2021-11-24 19:41:05 +01:00
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
u32 samples_per_q = 0, freq = 0;
|
|
|
|
|
static_cast<IXAudio2Extension *>(ext)->GetProcessingQuantum(&samples_per_q, &freq);
|
2020-02-17 22:31:49 +01:00
|
|
|
|
2021-11-24 19:41:05 +01:00
|
|
|
if (freq)
|
|
|
|
|
{
|
|
|
|
|
min_latency = static_cast<f64>(samples_per_q) / freq;
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-02-17 22:31:49 +01:00
|
|
|
|
2021-11-24 19:41:05 +01:00
|
|
|
return std::max<f64>(min_latency, _10ms); // 10ms is the minimum for XAudio
|
2018-12-16 18:40:50 +01:00
|
|
|
}
|
|
|
|
|
|
2021-11-24 19:41:05 +01:00
|
|
|
void XAudio2Backend::OnVoiceProcessingPassStart(UINT32 BytesRequired)
|
2018-12-16 22:12:58 +01:00
|
|
|
{
|
2021-11-24 19:41:05 +01:00
|
|
|
std::unique_lock lock(m_cb_mutex, std::defer_lock);
|
|
|
|
|
if (BytesRequired && lock.try_lock() && m_write_callback && m_playing)
|
2020-02-17 22:31:49 +01:00
|
|
|
{
|
2021-11-24 19:41:05 +01:00
|
|
|
ensure(BytesRequired <= m_data_buf_len, "XAudio internal buffer is too small. Report to developers!");
|
|
|
|
|
|
|
|
|
|
const u32 sample_size = get_sample_size() * get_channels();
|
|
|
|
|
u32 written = std::min(m_write_callback(BytesRequired, m_data_buf.get()), BytesRequired);
|
|
|
|
|
written -= written % sample_size;
|
|
|
|
|
|
|
|
|
|
if (written >= sample_size)
|
|
|
|
|
{
|
|
|
|
|
memcpy(m_last_sample, m_data_buf.get() + written - sample_size, sample_size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (u32 i = written; i < BytesRequired; i += sample_size)
|
|
|
|
|
{
|
|
|
|
|
memcpy(m_data_buf.get() + i, m_last_sample, sample_size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
XAUDIO2_BUFFER buffer{};
|
|
|
|
|
buffer.AudioBytes = BytesRequired;
|
|
|
|
|
buffer.LoopBegin = XAUDIO2_NO_LOOP_REGION;
|
|
|
|
|
buffer.pAudioData = static_cast<const BYTE*>(m_data_buf.get());
|
|
|
|
|
|
|
|
|
|
const HRESULT hr = m_source_voice->SubmitSourceBuffer(&buffer);
|
|
|
|
|
if (FAILED(hr))
|
|
|
|
|
{
|
|
|
|
|
XAudio.error("SubmitSourceBuffer() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
|
|
|
|
|
}
|
2020-02-17 22:31:49 +01:00
|
|
|
}
|
2021-11-24 19:41:05 +01:00
|
|
|
}
|
2020-02-17 22:31:49 +01:00
|
|
|
|
2021-11-24 19:41:05 +01:00
|
|
|
void XAudio2Backend::OnCriticalError(HRESULT Error)
|
|
|
|
|
{
|
|
|
|
|
XAudio.error("OnCriticalError() called: %s (0x%08x)", std::system_category().message(Error), static_cast<u32>(Error));
|
|
|
|
|
m_reset_req = true;
|
2018-12-16 22:12:58 +01:00
|
|
|
}
|