SPU: Add time stretched audio output

This commit is contained in:
Connor McLaughlin
2022-07-28 00:42:41 +10:00
parent f54e32ff01
commit 68b5dd869c
27 changed files with 1165 additions and 808 deletions

View File

@ -66,14 +66,6 @@
#include <mmsystem.h>
#endif
namespace FrontendCommon {
#ifdef _WIN32
std::unique_ptr<AudioStream> CreateXAudio2AudioStream();
#endif
} // namespace FrontendCommon
Log_SetChannel(CommonHostInterface);
namespace CommonHost {
@ -148,26 +140,27 @@ void CommonHost::ReleaseHostDisplayResources()
//
}
std::unique_ptr<AudioStream> Host::CreateAudioStream(AudioBackend backend)
std::unique_ptr<AudioStream> Host::CreateAudioStream(AudioBackend backend, u32 sample_rate, u32 channels, u32 buffer_ms,
u32 latency_ms, AudioStretchMode stretch)
{
switch (backend)
{
case AudioBackend::Null:
return AudioStream::CreateNullAudioStream();
return AudioStream::CreateNullStream(sample_rate, channels, buffer_ms);
#ifndef _UWP
case AudioBackend::Cubeb:
return CubebAudioStream::Create();
return CommonHost::CreateCubebAudioStream(sample_rate, channels, buffer_ms, latency_ms, stretch);
#endif
#ifdef _WIN32
case AudioBackend::XAudio2:
return FrontendCommon::CreateXAudio2AudioStream();
return CommonHost::CreateXAudio2Stream(sample_rate, channels, buffer_ms, latency_ms, stretch);
#endif
#ifdef WITH_SDL2
case AudioBackend::SDL:
return SDLAudioStream::Create();
return CommonHost::CreateSDLAudioStream(sample_rate, channels, buffer_ms, latency_ms, stretch);
#endif
default:
@ -927,7 +920,7 @@ DEFINE_HOTKEY("AudioMute", TRANSLATABLE("Hotkeys", "Audio"), TRANSLATABLE("Hotke
{
g_settings.audio_output_muted = !g_settings.audio_output_muted;
const s32 volume = System::GetAudioOutputVolume();
g_spu.GetOutputStream()->SetOutputVolume(volume);
// g_spu.GetOutputStream()->SetOutputVolume(volume);
if (g_settings.audio_output_muted)
{
Host::AddKeyedOSDMessage("AudioControlHotkey", Host::TranslateStdString("OSDMessage", "Volume: Muted"), 2.0f);
@ -959,7 +952,7 @@ DEFINE_HOTKEY("AudioVolumeUp", TRANSLATABLE("Hotkeys", "Audio"), TRANSLATABLE("H
const s32 volume = std::min<s32>(System::GetAudioOutputVolume() + 10, 100);
g_settings.audio_output_volume = volume;
g_settings.audio_fast_forward_volume = volume;
g_spu.GetOutputStream()->SetOutputVolume(volume);
// g_spu.GetOutputStream()->SetOutputVolume(volume);
Host::AddKeyedFormattedOSDMessage("AudioControlHotkey", 2.0f, Host::TranslateString("OSDMessage", "Volume: %d%%"),
volume);
}
@ -973,7 +966,7 @@ DEFINE_HOTKEY("AudioVolumeDown", TRANSLATABLE("Hotkeys", "Audio"), TRANSLATABLE(
const s32 volume = std::max<s32>(System::GetAudioOutputVolume() - 10, 0);
g_settings.audio_output_volume = volume;
g_settings.audio_fast_forward_volume = volume;
g_spu.GetOutputStream()->SetOutputVolume(volume);
// g_spu.GetOutputStream()->SetOutputVolume(volume);
Host::AddKeyedFormattedOSDMessage("AudioControlHotkey", 2.0f,
Host::TranslateString("OSDMessage", "Volume: %d%%"), volume);
}

View File

@ -1,9 +1,13 @@
#pragma once
#include "core/system.h"
#include <memory>
#include <mutex>
class SettingsInterface;
class AudioStream;
enum class AudioStretchMode : u8;
namespace CommonHost {
/// Initializes configuration.
void UpdateLogSettings();
@ -25,6 +29,19 @@ void OnGameChanged(const std::string& disc_path, const std::string& game_serial,
void PumpMessagesOnCPUThread();
bool CreateHostDisplayResources();
void ReleaseHostDisplayResources();
#ifdef _WIN32
std::unique_ptr<AudioStream> CreateXAudio2Stream(u32 sample_rate, u32 channels, u32 buffer_ms, u32 latency_ms,
AudioStretchMode stretch);
#endif
#ifdef WITH_SDL2
std::unique_ptr<AudioStream> CreateSDLAudioStream(u32 sample_rate, u32 channels, u32 buffer_ms, u32 latency_ms,
AudioStretchMode stretch);
#endif
#ifndef _UWP
std::unique_ptr<AudioStream> CreateCubebAudioStream(u32 sample_rate, u32 channels, u32 buffer_ms, u32 latency_ms,
AudioStretchMode stretch);
#endif
} // namespace CommonHost
namespace ImGuiManager {

View File

@ -1,6 +1,11 @@
#include "cubeb_audio_stream.h"
#include "common/assert.h"
#include "common/log.h"
#include "common/string_util.h"
#include "common_host.h"
#include "core/host.h"
#include "core/host_settings.h"
#include "cubeb/cubeb.h"
Log_SetChannel(CubebAudioStream);
#ifdef _WIN32
@ -9,154 +14,188 @@ Log_SetChannel(CubebAudioStream);
#pragma comment(lib, "Ole32.lib")
#endif
CubebAudioStream::CubebAudioStream() = default;
static void StateCallback(cubeb_stream* stream, void* user_ptr, cubeb_state state);
CubebAudioStream::CubebAudioStream(u32 sample_rate, u32 channels, u32 buffer_ms, AudioStretchMode stretch)
: AudioStream(sample_rate, channels, buffer_ms, stretch)
{
}
CubebAudioStream::~CubebAudioStream()
{
if (IsOpen())
CubebAudioStream::CloseDevice();
DestroyContextAndStream();
}
bool CubebAudioStream::OpenDevice()
void CubebAudioStream::LogCallback(const char* fmt, ...)
{
Assert(!IsOpen());
std::va_list ap;
va_start(ap, fmt);
std::string msg(StringUtil::StdStringFromFormatV(fmt, ap));
va_end(ap);
Log_DevPrintf("(Cubeb): %s", msg.c_str());
}
void CubebAudioStream::DestroyContextAndStream()
{
if (stream)
{
cubeb_stream_stop(stream);
cubeb_stream_destroy(stream);
stream = nullptr;
}
if (m_context)
{
cubeb_destroy(m_context);
m_context = nullptr;
}
#ifdef _WIN32
if (m_com_initialized_by_us)
{
CoUninitialize();
m_com_initialized_by_us = false;
}
#endif
}
bool CubebAudioStream::Initialize(u32 latency_ms)
{
#ifdef _WIN32
HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
m_com_initialized_by_us = SUCCEEDED(hr);
if (FAILED(hr) && hr != RPC_E_CHANGED_MODE && hr != S_FALSE)
if (FAILED(hr) && hr != RPC_E_CHANGED_MODE)
{
Log_ErrorPrintf("Failed to initialize COM");
Host::ReportErrorAsync("Error", "Failed to initialize COM for Cubeb");
return false;
}
#endif
int rv = cubeb_init(&m_cubeb_context, "DuckStation", nullptr);
cubeb_set_log_callback(CUBEB_LOG_NORMAL, LogCallback);
std::string backend(Host::GetStringSettingValue("Audio", "CubebBackend"));
int rv = cubeb_init(&m_context, "DuckStation", backend.empty() ? nullptr : backend.c_str());
if (rv != CUBEB_OK)
{
Log_ErrorPrintf("Could not initialize cubeb context: %d", rv);
Host::ReportFormattedErrorAsync("Error", "Could not initialize cubeb context: %d", rv);
return false;
}
cubeb_stream_params params = {};
params.format = CUBEB_SAMPLE_S16LE;
params.rate = m_output_sample_rate;
params.rate = m_sample_rate;
params.channels = m_channels;
params.layout = CUBEB_LAYOUT_UNDEFINED;
params.prefs = CUBEB_STREAM_PREF_PERSIST;
params.prefs = CUBEB_STREAM_PREF_NONE;
u32 latency_frames = 0;
rv = cubeb_get_min_latency(m_cubeb_context, &params, &latency_frames);
u32 latency_frames = GetBufferSizeForMS(m_sample_rate, (latency_ms == 0) ? m_buffer_ms : latency_ms);
u32 min_latency_frames = 0;
rv = cubeb_get_min_latency(m_context, &params, &min_latency_frames);
if (rv == CUBEB_ERROR_NOT_SUPPORTED)
{
Log_WarningPrintf("Cubeb backend does not support latency queries, using buffer size of %u.", m_buffer_size);
latency_frames = m_buffer_size;
Log_DevPrintf("(Cubeb) Cubeb backend does not support latency queries, using latency of %d ms (%u frames).",
m_buffer_ms, latency_frames);
}
else
{
if (rv != CUBEB_OK)
{
Log_ErrorPrintf("Could not get minimum latency: %d", rv);
DestroyContext();
Log_ErrorPrintf("(Cubeb) Could not get minimum latency: %d", rv);
DestroyContextAndStream();
return false;
}
Log_InfoPrintf("Minimum latency in frames: %u", latency_frames);
if (latency_frames > m_buffer_size)
const u32 minimum_latency_ms = GetMSForBufferSize(m_sample_rate, min_latency_frames);
Log_DevPrintf("(Cubeb) Minimum latency: %u ms (%u audio frames)", minimum_latency_ms, min_latency_frames);
if (latency_ms == 0)
{
Log_WarningPrintf("Minimum latency is above buffer size: %u vs %u, adjusting to compensate.", latency_frames,
m_buffer_size);
if (!SetBufferSize(latency_frames))
{
Log_ErrorPrintf("Failed to set new buffer size of %u frames", latency_frames);
DestroyContext();
return false;
}
// use minimum
latency_frames = min_latency_frames;
}
else
else if (minimum_latency_ms > latency_ms)
{
latency_frames = m_buffer_size;
Log_WarningPrintf("(Cubeb) Minimum latency is above requested latency: %u vs %u, adjusting to compensate.",
min_latency_frames, latency_frames);
latency_frames = min_latency_frames;
}
}
char stream_name[32];
std::snprintf(stream_name, sizeof(stream_name), "AudioStream_%p", this);
BaseInitialize();
m_volume = 100;
m_paused = false;
rv = cubeb_stream_init(m_cubeb_context, &m_cubeb_stream, stream_name, nullptr, nullptr, nullptr, &params,
latency_frames, DataCallback, StateCallback, this);
char stream_name[32];
std::snprintf(stream_name, sizeof(stream_name), "%p", this);
rv = cubeb_stream_init(m_context, &stream, stream_name, nullptr, nullptr, nullptr, &params, latency_frames,
&CubebAudioStream::DataCallback, StateCallback, this);
if (rv != CUBEB_OK)
{
Log_ErrorPrintf("Could not create stream: %d", rv);
DestroyContext();
Log_ErrorPrintf("(Cubeb) Could not create stream: %d", rv);
DestroyContextAndStream();
return false;
}
rv = cubeb_stream_start(stream);
if (rv != CUBEB_OK)
{
Log_ErrorPrintf("(Cubeb) Could not start stream: %d", rv);
DestroyContextAndStream();
return false;
}
cubeb_stream_set_volume(m_cubeb_stream, static_cast<float>(m_output_volume) / 100.0f);
return true;
}
void CubebAudioStream::PauseDevice(bool paused)
void StateCallback(cubeb_stream* stream, void* user_ptr, cubeb_state state)
{
if (paused == m_paused)
// noop
}
long CubebAudioStream::DataCallback(cubeb_stream* stm, void* user_ptr, const void* input_buffer, void* output_buffer,
long nframes)
{
static_cast<CubebAudioStream*>(user_ptr)->ReadFrames(static_cast<s16*>(output_buffer), static_cast<u32>(nframes));
return nframes;
}
void CubebAudioStream::SetPaused(bool paused)
{
if (paused == m_paused || !stream)
return;
int rv = paused ? cubeb_stream_stop(m_cubeb_stream) : cubeb_stream_start(m_cubeb_stream);
const int rv = paused ? cubeb_stream_stop(stream) : cubeb_stream_start(stream);
if (rv != CUBEB_OK)
{
Log_ErrorPrintf("cubeb_stream_%s failed: %d", paused ? "stop" : "start", rv);
Log_ErrorPrintf("Could not %s stream: %d", paused ? "pause" : "resume", rv);
return;
}
m_paused = paused;
}
void CubebAudioStream::CloseDevice()
{
Assert(IsOpen());
if (!m_paused)
{
cubeb_stream_stop(m_cubeb_stream);
m_paused = true;
}
cubeb_stream_destroy(m_cubeb_stream);
m_cubeb_stream = nullptr;
DestroyContext();
}
long CubebAudioStream::DataCallback(cubeb_stream* stm, void* user_ptr, const void* input_buffer, void* output_buffer,
long nframes)
{
CubebAudioStream* const this_ptr = static_cast<CubebAudioStream*>(user_ptr);
this_ptr->ReadFrames(reinterpret_cast<SampleType*>(output_buffer), static_cast<u32>(nframes), false);
return nframes;
}
void CubebAudioStream::StateCallback(cubeb_stream* stream, void* user_ptr, cubeb_state state) {}
void CubebAudioStream::FramesAvailable() {}
void CubebAudioStream::SetOutputVolume(u32 volume)
{
AudioStream::SetOutputVolume(volume);
cubeb_stream_set_volume(m_cubeb_stream, static_cast<float>(m_output_volume) / 100.0f);
if (volume == m_volume)
return;
int rv = cubeb_stream_set_volume(stream, static_cast<float>(volume) / 100.0f);
if (rv != CUBEB_OK)
{
Log_ErrorPrintf("cubeb_stream_set_volume() failed: %d", rv);
return;
}
m_volume = volume;
}
void CubebAudioStream::DestroyContext()
std::unique_ptr<AudioStream> CommonHost::CreateCubebAudioStream(u32 sample_rate, u32 channels, u32 buffer_ms,
u32 latency_ms, AudioStretchMode stretch)
{
cubeb_destroy(m_cubeb_context);
m_cubeb_context = nullptr;
#ifdef _WIN32
if (m_com_initialized_by_us)
CoUninitialize();
#endif
}
std::unique_ptr<AudioStream> CubebAudioStream::Create()
{
return std::make_unique<CubebAudioStream>();
std::unique_ptr<CubebAudioStream> stream(
std::make_unique<CubebAudioStream>(sample_rate, channels, buffer_ms, stretch));
if (!stream->Initialize(latency_ms))
stream.reset();
return stream;
}

View File

@ -1,34 +1,30 @@
#pragma once
#include "cubeb/cubeb.h"
#include "util/audio_stream.h"
#include <cstdint>
class CubebAudioStream final : public AudioStream
struct cubeb;
struct cubeb_stream;
class CubebAudioStream : public AudioStream
{
public:
CubebAudioStream();
CubebAudioStream(u32 sample_rate, u32 channels, u32 buffer_ms, AudioStretchMode stretch);
~CubebAudioStream();
static std::unique_ptr<AudioStream> Create();
protected:
bool IsOpen() const { return m_cubeb_stream != nullptr; }
bool OpenDevice() override;
void PauseDevice(bool paused) override;
void CloseDevice() override;
void FramesAvailable() override;
void SetPaused(bool paused) override;
void SetOutputVolume(u32 volume) override;
void DestroyContext();
bool Initialize(u32 latency_ms);
private:
static void LogCallback(const char* fmt, ...);
static long DataCallback(cubeb_stream* stm, void* user_ptr, const void* input_buffer, void* output_buffer,
long nframes);
static void StateCallback(cubeb_stream* stream, void* user_ptr, cubeb_state state);
cubeb* m_cubeb_context = nullptr;
cubeb_stream* m_cubeb_stream = nullptr;
bool m_paused = true;
void DestroyContextAndStream();
cubeb* m_context = nullptr;
cubeb_stream* stream = nullptr;
#ifdef _WIN32
bool m_com_initialized_by_us = false;

View File

@ -3152,18 +3152,18 @@ void FullscreenUI::DrawAudioSettingsPage()
"The audio backend determines how frames produced by the emulator are submitted to the host.",
"Audio", "Backend", Settings::DEFAULT_AUDIO_BACKEND, &Settings::ParseAudioBackend,
&Settings::GetAudioBackendName, &Settings::GetAudioBackendDisplayName, AudioBackend::Count);
DrawIntRangeSetting("Buffer Size",
DrawIntRangeSetting("Latency",
"The buffer size determines the size of the chunks of audio which will be pulled by the host.",
"Audio", "BufferSize", Settings::DEFAULT_AUDIO_BUFFER_SIZE, 1024, 8192, "%d Frames");
"Audio", "Latency", Settings::DEFAULT_AUDIO_BUFFER_MS, 10, 500, "%d ms");
DrawToggleSetting("Sync To Output",
"Throttles the emulation speed based on the audio backend pulling audio "
"frames. Enable to reduce the chances of crackling.",
"Audio", "Sync", true);
DrawToggleSetting(
"Resampling",
"When running outside of 100% speed, resamples audio from the target speed instead of dropping frames.", "Audio",
"Resampling", true);
"Time Stretching",
"When running outside of 100% speed, adjusts tempo on audio from the target speed instead of dropping frames.",
"Audio", "TimeStretching", true);
EndMenuButtons();
}

View File

@ -13,6 +13,7 @@
#include "core/host_display.h"
#include "core/host_settings.h"
#include "core/settings.h"
#include "core/spu.h"
#include "core/system.h"
#include "fmt/chrono.h"
#include "fmt/format.h"
@ -23,6 +24,7 @@
#include "imgui_internal.h"
#include "imgui_manager.h"
#include "input_manager.h"
#include "util/audio_stream.h"
#include <atomic>
#include <chrono>
#include <cmath>
@ -172,6 +174,16 @@ void ImGuiManager::DrawPerformanceOverlay()
FormatProcessorStat(text, System::GetSWThreadUsage(), System::GetSWThreadAverageTime());
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
}
#if 0
{
AudioStream* stream = g_spu.GetOutputStream();
const u32 frames = stream->GetBufferedFramesRelaxed();
text.Clear();
text.Fmt("Audio: {:<4u}f/{:<3u}ms", frames, AudioStream::GetMSForBufferSize(stream->GetSampleRate(), frames));
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
}
#endif
}
if (g_settings.display_show_status_indicators)

View File

@ -1,11 +1,15 @@
#include "sdl_audio_stream.h"
#include "common/assert.h"
#include "common/log.h"
#include "common_host.h"
#include "sdl_initializer.h"
#include <SDL.h>
Log_SetChannel(SDLAudioStream);
SDLAudioStream::SDLAudioStream() = default;
SDLAudioStream::SDLAudioStream(u32 sample_rate, u32 channels, u32 buffer_ms, AudioStretchMode stretch)
: AudioStream(sample_rate, channels, buffer_ms, stretch)
{
}
SDLAudioStream::~SDLAudioStream()
{
@ -13,12 +17,16 @@ SDLAudioStream::~SDLAudioStream()
SDLAudioStream::CloseDevice();
}
std::unique_ptr<SDLAudioStream> SDLAudioStream::Create()
std::unique_ptr<AudioStream> CommonHost::CreateSDLAudioStream(u32 sample_rate, u32 channels, u32 buffer_ms,
u32 latency_ms, AudioStretchMode stretch)
{
return std::make_unique<SDLAudioStream>();
std::unique_ptr<SDLAudioStream> stream(std::make_unique<SDLAudioStream>(sample_rate, channels, buffer_ms, stretch));
if (!stream->OpenDevice(latency_ms))
stream.reset();
return stream;
}
bool SDLAudioStream::OpenDevice()
bool SDLAudioStream::OpenDevice(u32 latency_ms)
{
DebugAssert(!IsOpen());
@ -31,22 +39,15 @@ bool SDLAudioStream::OpenDevice()
}
SDL_AudioSpec spec = {};
spec.freq = m_output_sample_rate;
spec.freq = m_sample_rate;
spec.channels = static_cast<Uint8>(m_channels);
spec.format = AUDIO_S16;
spec.samples = static_cast<Uint16>(m_buffer_size);
spec.samples = static_cast<Uint16>(GetBufferSizeForMS(m_sample_rate, (latency_ms == 0) ? m_buffer_ms : latency_ms));
spec.callback = AudioCallback;
spec.userdata = static_cast<void*>(this);
SDL_AudioSpec obtained_spec = {};
#ifdef SDL_AUDIO_ALLOW_SAMPLES_CHANGE
const u32 allowed_change_flags = SDL_AUDIO_ALLOW_SAMPLES_CHANGE;
#else
const u32 allowed_change_flags = 0;
#endif
m_device_id = SDL_OpenAudioDevice(nullptr, 0, &spec, &obtained_spec, allowed_change_flags);
m_device_id = SDL_OpenAudioDevice(nullptr, 0, &spec, &obtained_spec, SDL_AUDIO_ALLOW_SAMPLES_CHANGE);
if (m_device_id == 0)
{
Log_ErrorPrintf("SDL_OpenAudioDevice() failed: %s", SDL_GetError());
@ -54,25 +55,23 @@ bool SDLAudioStream::OpenDevice()
return false;
}
if (obtained_spec.samples > spec.samples)
{
Log_WarningPrintf("Requested buffer size %u, got buffer size %u. Adjusting to compensate.", spec.samples,
obtained_spec.samples);
Log_DevPrintf("Requested %u frame buffer, got %u frame buffer", spec.samples, obtained_spec.samples);
if (!SetBufferSize(obtained_spec.samples))
{
Log_ErrorPrintf("Failed to set new buffer size of %u", obtained_spec.samples);
CloseDevice();
return false;
}
}
BaseInitialize();
m_volume = 100;
m_paused = false;
SDL_PauseAudioDevice(m_device_id, 0);
return true;
}
void SDLAudioStream::PauseDevice(bool paused)
void SDLAudioStream::SetPaused(bool paused)
{
if (m_paused == paused)
return;
SDL_PauseAudioDevice(m_device_id, paused ? 1 : 0);
m_paused = paused;
}
void SDLAudioStream::CloseDevice()
@ -87,7 +86,13 @@ void SDLAudioStream::AudioCallback(void* userdata, uint8_t* stream, int len)
SDLAudioStream* const this_ptr = static_cast<SDLAudioStream*>(userdata);
const u32 num_frames = len / sizeof(SampleType) / this_ptr->m_channels;
this_ptr->ReadFrames(reinterpret_cast<SampleType*>(stream), num_frames, true);
this_ptr->ReadFrames(reinterpret_cast<SampleType*>(stream), num_frames);
}
void SDLAudioStream::FramesAvailable() {}
void SDLAudioStream::SetOutputVolume(u32 volume)
{
if (m_volume == volume)
return;
Panic("Fixme");
}

View File

@ -5,19 +5,18 @@
class SDLAudioStream final : public AudioStream
{
public:
SDLAudioStream();
SDLAudioStream(u32 sample_rate, u32 channels, u32 buffer_ms, AudioStretchMode stretch);
~SDLAudioStream();
static std::unique_ptr<SDLAudioStream> Create();
void SetPaused(bool paused) override;
void SetOutputVolume(u32 volume) override;
bool OpenDevice(u32 latency_ms);
void CloseDevice();
protected:
ALWAYS_INLINE bool IsOpen() const { return (m_device_id != 0); }
bool OpenDevice() override;
void PauseDevice(bool paused) override;
void CloseDevice() override;
void FramesAvailable() override;
static void AudioCallback(void* userdata, uint8_t* stream, int len);
u32 m_device_id = 0;

View File

@ -1,6 +1,7 @@
#include "xaudio2_audio_stream.h"
#include "common/assert.h"
#include "common/log.h"
#include "common_host.h"
#include <VersionHelpers.h>
#include <xaudio2.h>
Log_SetChannel(XAudio2AudioStream);
@ -9,12 +10,15 @@ Log_SetChannel(XAudio2AudioStream);
#pragma comment(lib, "xaudio2.lib")
#endif
XAudio2AudioStream::XAudio2AudioStream() = default;
XAudio2AudioStream::XAudio2AudioStream(u32 sample_rate, u32 channels, u32 buffer_ms, AudioStretchMode stretch)
: AudioStream(sample_rate, channels, buffer_ms, stretch)
{
}
XAudio2AudioStream::~XAudio2AudioStream()
{
if (IsOpen())
XAudio2AudioStream::CloseDevice();
CloseDevice();
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
if (m_xaudio2_library)
@ -25,8 +29,20 @@ XAudio2AudioStream::~XAudio2AudioStream()
#endif
}
bool XAudio2AudioStream::Initialize()
std::unique_ptr<AudioStream> CommonHost::CreateXAudio2Stream(u32 sample_rate, u32 channels, u32 buffer_ms,
u32 latency_ms, AudioStretchMode stretch)
{
std::unique_ptr<XAudio2AudioStream> stream(
std::make_unique<XAudio2AudioStream>(sample_rate, channels, buffer_ms, stretch));
if (!stream->OpenDevice(latency_ms))
stream.reset();
return stream;
}
bool XAudio2AudioStream::OpenDevice(u32 latency_ms)
{
DebugAssert(!IsOpen());
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
m_xaudio2_library = LoadLibraryW(XAUDIO2_DLL_W);
if (!m_xaudio2_library)
@ -36,13 +52,6 @@ bool XAudio2AudioStream::Initialize()
}
#endif
return true;
}
bool XAudio2AudioStream::OpenDevice()
{
DebugAssert(!IsOpen());
HRESULT hr;
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
using PFNXAUDIO2CREATE =
@ -70,7 +79,7 @@ bool XAudio2AudioStream::OpenDevice()
return false;
}
hr = m_xaudio->CreateMasteringVoice(&m_mastering_voice, m_channels, m_output_sample_rate, 0, nullptr);
hr = m_xaudio->CreateMasteringVoice(&m_mastering_voice, m_channels, m_sample_rate, 0, nullptr);
if (FAILED(hr))
{
Log_ErrorPrintf("CreateMasteringVoice() failed: %08X", hr);
@ -79,10 +88,10 @@ bool XAudio2AudioStream::OpenDevice()
WAVEFORMATEX wf = {};
wf.cbSize = sizeof(wf);
wf.nAvgBytesPerSec = m_output_sample_rate * m_channels * sizeof(s16);
wf.nAvgBytesPerSec = m_sample_rate * m_channels * sizeof(s16);
wf.nBlockAlign = static_cast<WORD>(sizeof(s16) * m_channels);
wf.nChannels = static_cast<WORD>(m_channels);
wf.nSamplesPerSec = m_output_sample_rate;
wf.nSamplesPerSec = m_sample_rate;
wf.wBitsPerSample = sizeof(s16) * 8;
wf.wFormatTag = WAVE_FORMAT_PCM;
hr = m_xaudio->CreateSourceVoice(&m_source_voice, &wf, 0, 1.0f, this);
@ -99,13 +108,27 @@ bool XAudio2AudioStream::OpenDevice()
return false;
}
m_enqueue_buffer_size = std::max<u32>(INTERNAL_BUFFER_SIZE, GetBufferSizeForMS(m_sample_rate, latency_ms));
Log_DevPrintf("Allocating %u buffers of %u frames", NUM_BUFFERS, m_enqueue_buffer_size);
for (u32 i = 0; i < NUM_BUFFERS; i++)
m_buffers[i] = std::make_unique<SampleType[]>(m_buffer_size * m_channels);
m_enqueue_buffers[i] = std::make_unique<SampleType[]>(m_enqueue_buffer_size * m_channels);
BaseInitialize();
m_volume = 100;
m_paused = false;
hr = m_source_voice->Start(0, 0);
if (FAILED(hr))
{
Log_ErrorPrintf("Start() failed: %08X", hr);
return false;
}
EnqueueBuffer();
return true;
}
void XAudio2AudioStream::PauseDevice(bool paused)
void XAudio2AudioStream::SetPaused(bool paused)
{
if (m_paused == paused)
return;
@ -124,6 +147,9 @@ void XAudio2AudioStream::PauseDevice(bool paused)
}
m_paused = paused;
if (!m_buffer_enqueued)
EnqueueBuffer();
}
void XAudio2AudioStream::CloseDevice()
@ -139,29 +165,20 @@ void XAudio2AudioStream::CloseDevice()
m_source_voice = nullptr;
m_mastering_voice = nullptr;
m_xaudio.Reset();
m_buffers = {};
m_enqueue_buffers = {};
m_current_buffer = 0;
m_paused = true;
}
void XAudio2AudioStream::FramesAvailable()
{
if (!m_buffer_enqueued)
{
m_buffer_enqueued = true;
EnqueueBuffer();
}
}
void XAudio2AudioStream::EnqueueBuffer()
{
SampleType* samples = m_buffers[m_current_buffer].get();
ReadFrames(samples, m_buffer_size, false);
SampleType* samples = m_enqueue_buffers[m_current_buffer].get();
ReadFrames(samples, m_enqueue_buffer_size);
const XAUDIO2_BUFFER buf = {
static_cast<UINT32>(0), // flags
static_cast<UINT32>(sizeof(s16) * m_channels * m_buffer_size), // bytes
reinterpret_cast<const BYTE*>(samples) // data
static_cast<UINT32>(0), // flags
static_cast<UINT32>(sizeof(s16) * m_channels * m_enqueue_buffer_size), // bytes
reinterpret_cast<const BYTE*>(samples) // data
};
HRESULT hr = m_source_voice->SubmitSourceBuffer(&buf, nullptr);
@ -173,10 +190,14 @@ void XAudio2AudioStream::EnqueueBuffer()
void XAudio2AudioStream::SetOutputVolume(u32 volume)
{
AudioStream::SetOutputVolume(volume);
HRESULT hr = m_mastering_voice->SetVolume(static_cast<float>(m_output_volume) / 100.0f);
HRESULT hr = m_mastering_voice->SetVolume(static_cast<float>(m_volume) / 100.0f);
if (FAILED(hr))
{
Log_ErrorPrintf("SetVolume() failed: %08X", hr);
return;
}
m_volume = volume;
}
void __stdcall XAudio2AudioStream::OnVoiceProcessingPassStart(UINT32 BytesRequired) {}
@ -195,16 +216,3 @@ void __stdcall XAudio2AudioStream::OnBufferEnd(void* pBufferContext)
void __stdcall XAudio2AudioStream::OnLoopEnd(void* pBufferContext) {}
void __stdcall XAudio2AudioStream::OnVoiceError(void* pBufferContext, HRESULT Error) {}
namespace FrontendCommon {
std::unique_ptr<AudioStream> CreateXAudio2AudioStream()
{
std::unique_ptr<XAudio2AudioStream> stream = std::make_unique<XAudio2AudioStream>();
if (!stream->Initialize())
return {};
return stream;
}
} // namespace FrontendCommon

View File

@ -14,45 +14,42 @@
class XAudio2AudioStream final : public AudioStream, private IXAudio2VoiceCallback
{
public:
XAudio2AudioStream();
XAudio2AudioStream(u32 sample_rate, u32 channels, u32 buffer_ms, AudioStretchMode stretch);
~XAudio2AudioStream();
bool Initialize();
void SetPaused(bool paused) override;
void SetOutputVolume(u32 volume) override;
protected:
bool OpenDevice(u32 latency_ms);
void CloseDevice();
void EnqueueBuffer();
private:
enum : u32
{
NUM_BUFFERS = 2
NUM_BUFFERS = 2,
INTERNAL_BUFFER_SIZE = 512,
};
ALWAYS_INLINE bool IsOpen() const { return static_cast<bool>(m_xaudio); }
bool OpenDevice() override;
void PauseDevice(bool paused) override;
void CloseDevice() override;
void FramesAvailable() override;
// Inherited via IXAudio2VoiceCallback
virtual void __stdcall OnVoiceProcessingPassStart(UINT32 BytesRequired) override;
virtual void __stdcall OnVoiceProcessingPassEnd(void) override;
virtual void __stdcall OnStreamEnd(void) override;
virtual void __stdcall OnBufferStart(void* pBufferContext) override;
virtual void __stdcall OnBufferEnd(void* pBufferContext) override;
virtual void __stdcall OnLoopEnd(void* pBufferContext) override;
virtual void __stdcall OnVoiceError(void* pBufferContext, HRESULT Error) override;
void EnqueueBuffer();
void __stdcall OnVoiceProcessingPassStart(UINT32 BytesRequired) override;
void __stdcall OnVoiceProcessingPassEnd(void) override;
void __stdcall OnStreamEnd(void) override;
void __stdcall OnBufferStart(void* pBufferContext) override;
void __stdcall OnBufferEnd(void* pBufferContext) override;
void __stdcall OnLoopEnd(void* pBufferContext) override;
void __stdcall OnVoiceError(void* pBufferContext, HRESULT Error) override;
Microsoft::WRL::ComPtr<IXAudio2> m_xaudio;
IXAudio2MasteringVoice* m_mastering_voice = nullptr;
IXAudio2SourceVoice* m_source_voice = nullptr;
std::array<std::unique_ptr<SampleType[]>, NUM_BUFFERS> m_buffers;
std::array<std::unique_ptr<SampleType[]>, NUM_BUFFERS> m_enqueue_buffers;
u32 m_enqueue_buffer_size = 0;
u32 m_current_buffer = 0;
bool m_buffer_enqueued = false;
bool m_paused = true;
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
HMODULE m_xaudio2_library = {};