mirror of
https://github.com/WinampDesktop/winamp.git
synced 2025-06-18 12:45:45 -04:00
SPU: Add time stretched audio output
This commit is contained in:
@ -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);
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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, ¶ms, &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, ¶ms, &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, ¶ms,
|
||||
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, ¶ms, 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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
@ -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 = {};
|
||||
|
Reference in New Issue
Block a user