AudioStream: Backport changes
This commit is contained in:
@ -217,12 +217,11 @@ if(WIN32)
|
||||
platform_misc_win32.cpp
|
||||
win32_raw_input_source.cpp
|
||||
win32_raw_input_source.h
|
||||
xaudio2_audio_stream.cpp
|
||||
xinput_source.cpp
|
||||
xinput_source.h
|
||||
)
|
||||
target_link_libraries(util PRIVATE d3d12ma)
|
||||
target_link_libraries(util PRIVATE d3d11.lib d3d12.lib d3dcompiler.lib dxgi.lib winmm.lib Dwmapi.lib winhttp.lib xaudio2.lib)
|
||||
target_link_libraries(util PRIVATE d3d11.lib d3d12.lib d3dcompiler.lib dxgi.lib winmm.lib Dwmapi.lib winhttp.lib)
|
||||
|
||||
if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
|
||||
target_link_libraries(util PRIVATE WinPixEventRuntime::WinPixEventRuntime)
|
||||
|
||||
@ -38,6 +38,13 @@ static constexpr const std::array<std::pair<u8, u8>, static_cast<size_t>(AudioEx
|
||||
{u8(8), u8(8)}, // Surround71
|
||||
}};
|
||||
|
||||
AudioStream::DeviceInfo::DeviceInfo(std::string name_, std::string display_name_, u32 minimum_latency_)
|
||||
: name(std::move(name_)), display_name(std::move(display_name_)), minimum_latency_frames(minimum_latency_)
|
||||
{
|
||||
}
|
||||
|
||||
AudioStream::DeviceInfo::~DeviceInfo() = default;
|
||||
|
||||
AudioStream::AudioStream(u32 sample_rate, const AudioStreamParameters& parameters)
|
||||
: m_sample_rate(sample_rate), m_parameters(parameters),
|
||||
m_internal_channels(s_expansion_channel_count[static_cast<size_t>(parameters.expansion_mode)].first),
|
||||
@ -65,22 +72,50 @@ std::unique_ptr<AudioStream> AudioStream::CreateNullStream(u32 sample_rate, u32
|
||||
|
||||
#ifndef __ANDROID__
|
||||
|
||||
std::vector<std::string> AudioStream::GetDriverNames(AudioBackend backend)
|
||||
{
|
||||
std::vector<std::string> ret;
|
||||
switch (backend)
|
||||
{
|
||||
case AudioBackend::Cubeb:
|
||||
ret = GetCubebDriverNames();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<AudioStream::DeviceInfo> AudioStream::GetOutputDevices(AudioBackend backend, const char* driver)
|
||||
{
|
||||
std::vector<AudioStream::DeviceInfo> ret;
|
||||
switch (backend)
|
||||
{
|
||||
case AudioBackend::Cubeb:
|
||||
ret = GetCubebOutputDevices(driver);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioStream> AudioStream::CreateStream(AudioBackend backend, u32 sample_rate,
|
||||
const AudioStreamParameters& parameters, Error* error)
|
||||
const AudioStreamParameters& parameters, const char* driver_name,
|
||||
const char* device_name, Error* error /* = nullptr */)
|
||||
{
|
||||
switch (backend)
|
||||
{
|
||||
case AudioBackend::Cubeb:
|
||||
return CreateCubebAudioStream(sample_rate, parameters, error);
|
||||
return CreateCubebAudioStream(sample_rate, parameters, driver_name, device_name, error);
|
||||
|
||||
case AudioBackend::SDL:
|
||||
return CreateSDLAudioStream(sample_rate, parameters, error);
|
||||
|
||||
#ifdef _WIN32
|
||||
case AudioBackend::XAudio2:
|
||||
return CreateXAudio2Stream(sample_rate, parameters, error);
|
||||
#endif
|
||||
|
||||
case AudioBackend::Null:
|
||||
return CreateNullStream(sample_rate, parameters.buffer_ms);
|
||||
|
||||
@ -118,9 +153,6 @@ static constexpr const std::array s_backend_names = {
|
||||
"AAudio",
|
||||
"OpenSLES",
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
"XAudio2",
|
||||
#endif
|
||||
};
|
||||
static constexpr const std::array s_backend_display_names = {
|
||||
TRANSLATE_NOOP("AudioStream", "Null (No Output)"),
|
||||
@ -131,9 +163,6 @@ static constexpr const std::array s_backend_display_names = {
|
||||
"AAudio",
|
||||
"OpenSL ES",
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
TRANSLATE_NOOP("AudioStream", "XAudio2"),
|
||||
#endif
|
||||
};
|
||||
|
||||
std::optional<AudioBackend> AudioStream::ParseBackendName(const char* str)
|
||||
@ -327,6 +356,19 @@ void AudioStream::ReadFrames(SampleType* samples, u32 num_frames)
|
||||
std::memset(samples + (frames_to_read * m_output_channels), 0, silence_frames * m_output_channels * sizeof(s16));
|
||||
}
|
||||
}
|
||||
|
||||
if (m_volume != 100)
|
||||
{
|
||||
const s32 volume_mult = static_cast<s32>((static_cast<float>(m_volume) / 100.0f) * 32768.0f);
|
||||
|
||||
u32 num_samples = num_frames * m_output_channels;
|
||||
while (num_samples > 0)
|
||||
{
|
||||
*samples = static_cast<s16>((static_cast<s32>(*samples) * volume_mult) >> 15);
|
||||
samples++;
|
||||
num_samples--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioStream::StereoSampleReaderImpl(SampleType* dest, const SampleType* src, u32 num_frames)
|
||||
@ -334,21 +376,6 @@ void AudioStream::StereoSampleReaderImpl(SampleType* dest, const SampleType* src
|
||||
std::memcpy(dest, src, num_frames * 2 * sizeof(SampleType));
|
||||
}
|
||||
|
||||
void AudioStream::ApplyVolume(s16* samples, u32 num_samples)
|
||||
{
|
||||
if (m_volume == 100)
|
||||
return;
|
||||
|
||||
const s32 volume_mult = static_cast<s32>((static_cast<float>(m_volume) / 100.0f) * 32768.0f);
|
||||
|
||||
while (num_samples > 0)
|
||||
{
|
||||
*samples = static_cast<s16>((static_cast<s32>(*samples) * volume_mult) >> 15);
|
||||
samples++;
|
||||
num_samples--;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioStream::InternalWriteFrames(s16* data, u32 num_frames)
|
||||
{
|
||||
const u32 free = m_buffer_size - GetBufferedFramesRelaxed();
|
||||
|
||||
@ -34,9 +34,6 @@ enum class AudioBackend : u8
|
||||
#else
|
||||
AAudio,
|
||||
OpenSLES,
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
XAudio2,
|
||||
#endif
|
||||
Count
|
||||
};
|
||||
@ -93,7 +90,7 @@ struct AudioStreamParameters
|
||||
static constexpr u16 DEFAULT_BUFFER_MS = 100;
|
||||
static constexpr u16 DEFAULT_OUTPUT_LATENCY_MS = 20;
|
||||
#endif
|
||||
static constexpr u16 DEFAULT_EXPAND_BLOCK_SIZE = 1024;
|
||||
static constexpr u16 DEFAULT_EXPAND_BLOCK_SIZE = 2048;
|
||||
static constexpr float DEFAULT_EXPAND_CIRCULAR_WRAP = 90.0f;
|
||||
static constexpr float DEFAULT_EXPAND_SHIFT = 0.0f;
|
||||
static constexpr float DEFAULT_EXPAND_DEPTH = 1.0f;
|
||||
@ -136,6 +133,16 @@ public:
|
||||
static constexpr AudioBackend DEFAULT_BACKEND = AudioBackend::AAudio;
|
||||
#endif
|
||||
|
||||
struct DeviceInfo
|
||||
{
|
||||
std::string name;
|
||||
std::string display_name;
|
||||
u32 minimum_latency_frames;
|
||||
|
||||
DeviceInfo(std::string name_, std::string display_name_, u32 minimum_latency_);
|
||||
~DeviceInfo();
|
||||
};
|
||||
|
||||
public:
|
||||
virtual ~AudioStream();
|
||||
|
||||
@ -169,7 +176,7 @@ public:
|
||||
/// Temporarily pauses the stream, preventing it from requesting data.
|
||||
virtual void SetPaused(bool paused);
|
||||
|
||||
virtual void SetOutputVolume(u32 volume);
|
||||
void SetOutputVolume(u32 volume);
|
||||
|
||||
void BeginWrite(SampleType** buffer_ptr, u32* num_frames);
|
||||
void WriteFrames(const SampleType* frames, u32 num_frames);
|
||||
@ -184,15 +191,13 @@ public:
|
||||
|
||||
void SetStretchMode(AudioStretchMode mode);
|
||||
|
||||
static std::vector<std::string> GetDriverNames(AudioBackend backend);
|
||||
static std::vector<DeviceInfo> GetOutputDevices(AudioBackend backend, const char* driver);
|
||||
static std::unique_ptr<AudioStream> CreateStream(AudioBackend backend, u32 sample_rate,
|
||||
const AudioStreamParameters& parameters, Error* error = nullptr);
|
||||
const AudioStreamParameters& parameters, const char* driver_name,
|
||||
const char* device_name, Error* error = nullptr);
|
||||
static std::unique_ptr<AudioStream> CreateNullStream(u32 sample_rate, u32 buffer_ms);
|
||||
|
||||
#ifndef __ANDROID__
|
||||
static std::vector<std::string> GetCubebDriverNames();
|
||||
static std::vector<std::pair<std::string, std::string>> GetCubebOutputDevices(const char* driver);
|
||||
#endif
|
||||
|
||||
protected:
|
||||
enum ReadChannel : u8
|
||||
{
|
||||
@ -220,10 +225,8 @@ protected:
|
||||
static void SampleReaderImpl(SampleType* dest, const SampleType* src, u32 num_frames);
|
||||
static void StereoSampleReaderImpl(SampleType* dest, const SampleType* src, u32 num_frames);
|
||||
|
||||
void ApplyVolume(SampleType* samples, u32 num_samples);
|
||||
|
||||
u32 m_sample_rate = 0;
|
||||
u32 m_volume = 0;
|
||||
u32 m_volume = 100;
|
||||
AudioStreamParameters m_parameters;
|
||||
u8 m_internal_channels = 0;
|
||||
u8 m_output_channels = 0;
|
||||
@ -237,6 +240,16 @@ private:
|
||||
static constexpr u32 STRETCH_RESET_THRESHOLD = 5;
|
||||
static constexpr u32 TARGET_IPS = 691;
|
||||
|
||||
#ifndef __ANDROID__
|
||||
static std::vector<std::string> GetCubebDriverNames();
|
||||
static std::vector<DeviceInfo> GetCubebOutputDevices(const char* driver);
|
||||
static std::unique_ptr<AudioStream> CreateCubebAudioStream(u32 sample_rate, const AudioStreamParameters& parameters,
|
||||
const char* driver_name, const char* device_name,
|
||||
Error* error);
|
||||
static std::unique_ptr<AudioStream> CreateSDLAudioStream(u32 sample_rate, const AudioStreamParameters& parameters,
|
||||
Error* error);
|
||||
#endif
|
||||
|
||||
ALWAYS_INLINE bool IsExpansionEnabled() const { return m_parameters.expansion_mode != AudioExpansionMode::Disabled; }
|
||||
ALWAYS_INLINE bool IsStretchEnabled() const { return m_parameters.stretch_mode != AudioStretchMode::Off; }
|
||||
|
||||
@ -294,17 +307,6 @@ private:
|
||||
float* m_expand_output_buffer = nullptr;
|
||||
u32 m_expand_buffer_pos = 0;
|
||||
#endif
|
||||
|
||||
#ifndef __ANDROID__
|
||||
static std::unique_ptr<AudioStream> CreateCubebAudioStream(u32 sample_rate, const AudioStreamParameters& parameters,
|
||||
Error* error);
|
||||
static std::unique_ptr<AudioStream> CreateSDLAudioStream(u32 sample_rate, const AudioStreamParameters& parameters,
|
||||
Error* error);
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
static std::unique_ptr<AudioStream> CreateXAudio2Stream(u32 sample_rate, const AudioStreamParameters& parameters,
|
||||
Error* error);
|
||||
#endif
|
||||
};
|
||||
|
||||
template<AudioExpansionMode mode, AudioStream::ReadChannel c0, AudioStream::ReadChannel c1, AudioStream::ReadChannel c2,
|
||||
|
||||
@ -31,9 +31,8 @@ public:
|
||||
~CubebAudioStream();
|
||||
|
||||
void SetPaused(bool paused) override;
|
||||
void SetOutputVolume(u32 volume) override;
|
||||
|
||||
bool Initialize(Error* error);
|
||||
bool Initialize(const char* driver_name, const char* device_name, Error* error);
|
||||
|
||||
private:
|
||||
static void LogCallback(const char* fmt, ...);
|
||||
@ -122,7 +121,7 @@ void CubebAudioStream::DestroyContextAndStream()
|
||||
#endif
|
||||
}
|
||||
|
||||
bool CubebAudioStream::Initialize(Error* error)
|
||||
bool CubebAudioStream::Initialize(const char* driver_name, const char* device_name, Error* error)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
@ -245,8 +244,6 @@ bool CubebAudioStream::Initialize(Error* error)
|
||||
}
|
||||
|
||||
BaseInitialize(channel_setups[static_cast<size_t>(m_parameters.expansion_mode)].second);
|
||||
m_volume = 100;
|
||||
m_paused = false;
|
||||
|
||||
char stream_name[32];
|
||||
std::snprintf(stream_name, sizeof(stream_name), "%p", this);
|
||||
@ -302,26 +299,13 @@ void CubebAudioStream::SetPaused(bool paused)
|
||||
m_paused = paused;
|
||||
}
|
||||
|
||||
void CubebAudioStream::SetOutputVolume(u32 volume)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioStream> AudioStream::CreateCubebAudioStream(u32 sample_rate,
|
||||
const AudioStreamParameters& parameters, Error* error)
|
||||
const AudioStreamParameters& parameters,
|
||||
const char* driver_name, const char* device_name,
|
||||
Error* error)
|
||||
{
|
||||
std::unique_ptr<CubebAudioStream> stream = std::make_unique<CubebAudioStream>(sample_rate, parameters);
|
||||
if (!stream->Initialize(error))
|
||||
if (!stream->Initialize(driver_name, device_name, error))
|
||||
stream.reset();
|
||||
return stream;
|
||||
}
|
||||
@ -335,16 +319,16 @@ std::vector<std::string> AudioStream::GetCubebDriverNames()
|
||||
return names;
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> AudioStream::GetCubebOutputDevices(const char* driver)
|
||||
std::vector<AudioStream::DeviceInfo> AudioStream::GetCubebOutputDevices(const char* driver)
|
||||
{
|
||||
std::vector<std::pair<std::string, std::string>> ret;
|
||||
ret.emplace_back(std::string(), TRANSLATE_STR("CommonHost", "Default Output Device"));
|
||||
std::vector<AudioStream::DeviceInfo> ret;
|
||||
ret.emplace_back(std::string(), TRANSLATE_STR("AudioStream", "Default Output Device"), 0);
|
||||
|
||||
cubeb* context;
|
||||
int rv = cubeb_init(&context, "DuckStation", (driver && *driver) ? driver : nullptr);
|
||||
if (rv != CUBEB_OK)
|
||||
{
|
||||
Log_ErrorPrintf("cubeb_init() failed: %d", rv);
|
||||
Log_ErrorFmt("cubeb_init() failed: {}", GetCubebErrorString(rv));
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -354,19 +338,31 @@ std::vector<std::pair<std::string, std::string>> AudioStream::GetCubebOutputDevi
|
||||
rv = cubeb_enumerate_devices(context, CUBEB_DEVICE_TYPE_OUTPUT, &devices);
|
||||
if (rv != CUBEB_OK)
|
||||
{
|
||||
Log_ErrorPrintf("cubeb_enumerate_devices() failed: %d", rv);
|
||||
Log_ErrorFmt("cubeb_enumerate_devices() failed: {}", GetCubebErrorString(rv));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ScopedGuard devices_cleanup([context, &devices]() { cubeb_device_collection_destroy(context, &devices); });
|
||||
|
||||
// we need stream parameters to query latency
|
||||
cubeb_stream_params params = {};
|
||||
params.format = CUBEB_SAMPLE_S16LE;
|
||||
params.rate = 48000;
|
||||
params.channels = 2;
|
||||
params.layout = CUBEB_LAYOUT_UNDEFINED;
|
||||
params.prefs = CUBEB_STREAM_PREF_NONE;
|
||||
|
||||
u32 min_latency = 0;
|
||||
cubeb_get_min_latency(context, ¶ms, &min_latency);
|
||||
ret[0].minimum_latency_frames = min_latency;
|
||||
|
||||
for (size_t i = 0; i < devices.count; i++)
|
||||
{
|
||||
const cubeb_device_info& di = devices.device[i];
|
||||
if (!di.device_id)
|
||||
continue;
|
||||
|
||||
ret.emplace_back(di.device_id, di.friendly_name ? di.friendly_name : di.device_id);
|
||||
ret.emplace_back(di.device_id, di.friendly_name ? di.friendly_name : di.device_id, min_latency);
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
@ -19,7 +19,6 @@ public:
|
||||
~SDLAudioStream();
|
||||
|
||||
void SetPaused(bool paused) override;
|
||||
void SetOutputVolume(u32 volume) override;
|
||||
|
||||
bool OpenDevice(Error* error);
|
||||
void CloseDevice();
|
||||
@ -120,8 +119,6 @@ bool SDLAudioStream::OpenDevice(Error* error)
|
||||
Log_DevFmt("Requested {} frame buffer, got {} frame buffer", spec.samples, obtained_spec.samples);
|
||||
|
||||
BaseInitialize(sample_readers[static_cast<size_t>(m_parameters.expansion_mode)]);
|
||||
m_volume = 100;
|
||||
m_paused = false;
|
||||
SDL_PauseAudioDevice(m_device_id, 0);
|
||||
|
||||
return true;
|
||||
@ -148,10 +145,4 @@ void SDLAudioStream::AudioCallback(void* userdata, uint8_t* stream, int len)
|
||||
const u32 num_frames = len / sizeof(SampleType) / this_ptr->m_output_channels;
|
||||
|
||||
this_ptr->ReadFrames(reinterpret_cast<SampleType*>(stream), num_frames);
|
||||
this_ptr->ApplyVolume(reinterpret_cast<SampleType*>(stream), num_frames);
|
||||
}
|
||||
|
||||
void SDLAudioStream::SetOutputVolume(u32 volume)
|
||||
{
|
||||
m_volume = volume;
|
||||
}
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
|
||||
<ItemDefinitionGroup>
|
||||
<Link>
|
||||
<AdditionalDependencies>%(AdditionalDependencies);d3d11.lib;d3d12.lib;d3dcompiler.lib;dxgi.lib;Dwmapi.lib;winhttp.lib;xaudio2.lib</AdditionalDependencies>
|
||||
<AdditionalDependencies>%(AdditionalDependencies);d3d11.lib;d3d12.lib;d3dcompiler.lib;dxgi.lib;Dwmapi.lib;winhttp.lib</AdditionalDependencies>
|
||||
<AdditionalDependencies Condition="'$(Platform)'!='ARM64'">%(AdditionalDependencies);opengl32.lib</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
@ -230,7 +230,6 @@
|
||||
<ClCompile Include="wav_writer.cpp" />
|
||||
<ClCompile Include="win32_raw_input_source.cpp" />
|
||||
<ClCompile Include="window_info.cpp" />
|
||||
<ClCompile Include="xaudio2_audio_stream.cpp" />
|
||||
<ClCompile Include="xinput_source.cpp" />
|
||||
<ClCompile Include="zstd_byte_stream.cpp" />
|
||||
</ItemGroup>
|
||||
|
||||
@ -105,7 +105,6 @@
|
||||
<ClCompile Include="platform_misc_win32.cpp" />
|
||||
<ClCompile Include="sdl_input_source.cpp" />
|
||||
<ClCompile Include="win32_raw_input_source.cpp" />
|
||||
<ClCompile Include="xaudio2_audio_stream.cpp" />
|
||||
<ClCompile Include="xinput_source.cpp" />
|
||||
<ClCompile Include="dinput_source.cpp" />
|
||||
<ClCompile Include="input_manager.cpp" />
|
||||
|
||||
@ -1,291 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||
|
||||
#include "util/audio_stream.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/error.h"
|
||||
#include "common/log.h"
|
||||
#include "common/windows_headers.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <wrl/client.h>
|
||||
#include <xaudio2.h>
|
||||
|
||||
Log_SetChannel(XAudio2AudioStream);
|
||||
|
||||
namespace {
|
||||
|
||||
class XAudio2AudioStream final : public AudioStream, private IXAudio2VoiceCallback
|
||||
{
|
||||
public:
|
||||
XAudio2AudioStream(u32 sample_rate, const AudioStreamParameters& parameters);
|
||||
~XAudio2AudioStream();
|
||||
|
||||
void SetPaused(bool paused) override;
|
||||
void SetOutputVolume(u32 volume) override;
|
||||
|
||||
bool OpenDevice(Error* error);
|
||||
void CloseDevice();
|
||||
void EnqueueBuffer();
|
||||
|
||||
private:
|
||||
enum : u32
|
||||
{
|
||||
NUM_BUFFERS = 2,
|
||||
INTERNAL_BUFFER_SIZE = 512,
|
||||
};
|
||||
|
||||
ALWAYS_INLINE bool IsOpen() const { return static_cast<bool>(m_xaudio); }
|
||||
|
||||
// Inherited via IXAudio2VoiceCallback
|
||||
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_enqueue_buffers;
|
||||
u32 m_enqueue_buffer_size = 0;
|
||||
u32 m_current_buffer = 0;
|
||||
bool m_buffer_enqueued = false;
|
||||
bool m_com_initialized_by_us = false;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
XAudio2AudioStream::XAudio2AudioStream(u32 sample_rate, const AudioStreamParameters& parameters)
|
||||
: AudioStream(sample_rate, parameters)
|
||||
{
|
||||
}
|
||||
|
||||
XAudio2AudioStream::~XAudio2AudioStream()
|
||||
{
|
||||
if (IsOpen())
|
||||
CloseDevice();
|
||||
|
||||
if (m_com_initialized_by_us)
|
||||
CoUninitialize();
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioStream> AudioStream::CreateXAudio2Stream(u32 sample_rate, const AudioStreamParameters& parameters,
|
||||
Error* error)
|
||||
{
|
||||
std::unique_ptr<XAudio2AudioStream> stream(std::make_unique<XAudio2AudioStream>(sample_rate, parameters));
|
||||
if (!stream->OpenDevice(error))
|
||||
stream.reset();
|
||||
return stream;
|
||||
}
|
||||
|
||||
bool XAudio2AudioStream::OpenDevice(Error* error)
|
||||
{
|
||||
DebugAssert(!IsOpen());
|
||||
|
||||
if (m_parameters.expansion_mode == AudioExpansionMode::QuadraphonicLFE)
|
||||
{
|
||||
Log_ErrorPrint("QuadraphonicLFE is not supported by XAudio2.");
|
||||
return false;
|
||||
}
|
||||
|
||||
static constexpr const std::array<SampleReader, static_cast<size_t>(AudioExpansionMode::Count)> sample_readers = {{
|
||||
// Disabled
|
||||
&StereoSampleReaderImpl,
|
||||
// StereoLFE
|
||||
&SampleReaderImpl<AudioExpansionMode::StereoLFE, READ_CHANNEL_FRONT_LEFT, READ_CHANNEL_FRONT_RIGHT,
|
||||
READ_CHANNEL_LFE>,
|
||||
// Quadraphonic
|
||||
&SampleReaderImpl<AudioExpansionMode::Quadraphonic, READ_CHANNEL_FRONT_LEFT, READ_CHANNEL_FRONT_RIGHT,
|
||||
READ_CHANNEL_REAR_LEFT, READ_CHANNEL_REAR_RIGHT>,
|
||||
// QuadraphonicLFE
|
||||
nullptr,
|
||||
// Surround51
|
||||
&SampleReaderImpl<AudioExpansionMode::Surround51, READ_CHANNEL_FRONT_LEFT, READ_CHANNEL_FRONT_RIGHT,
|
||||
READ_CHANNEL_FRONT_CENTER, READ_CHANNEL_LFE, READ_CHANNEL_REAR_LEFT, READ_CHANNEL_REAR_RIGHT>,
|
||||
// Surround71
|
||||
&SampleReaderImpl<AudioExpansionMode::Surround71, READ_CHANNEL_FRONT_LEFT, READ_CHANNEL_FRONT_RIGHT,
|
||||
READ_CHANNEL_FRONT_CENTER, READ_CHANNEL_LFE, READ_CHANNEL_REAR_LEFT, READ_CHANNEL_REAR_RIGHT,
|
||||
READ_CHANNEL_SIDE_LEFT, READ_CHANNEL_SIDE_RIGHT>,
|
||||
}};
|
||||
|
||||
HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
m_com_initialized_by_us = SUCCEEDED(hr);
|
||||
if (FAILED(hr) && hr != RPC_E_CHANGED_MODE && hr != S_FALSE)
|
||||
{
|
||||
Error::SetHResult(error, "CoInitializeEx() failed: ", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = XAudio2Create(m_xaudio.ReleaseAndGetAddressOf(), 0, XAUDIO2_DEFAULT_PROCESSOR);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
Error::SetHResult(error, "XAudio2Create() failed: ", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = m_xaudio->CreateMasteringVoice(&m_mastering_voice, m_output_channels, m_sample_rate, 0, nullptr);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
Error::SetHResult(error, "CreateMasteringVoice() failed: ", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: CHANNEL LAYOUT
|
||||
WAVEFORMATEX wf = {};
|
||||
wf.cbSize = sizeof(wf);
|
||||
wf.nAvgBytesPerSec = m_sample_rate * m_output_channels * sizeof(s16);
|
||||
wf.nBlockAlign = static_cast<WORD>(sizeof(s16) * m_output_channels);
|
||||
wf.nChannels = static_cast<WORD>(m_output_channels);
|
||||
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);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
Error::SetHResult(error, "CreateMasteringVoice() failed: ", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = m_source_voice->SetFrequencyRatio(1.0f);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
Error::SetHResult(error, "SetFrequencyRatio() failed: ", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_enqueue_buffer_size =
|
||||
std::max<u32>(INTERNAL_BUFFER_SIZE, GetBufferSizeForMS(m_sample_rate, (m_parameters.output_latency_ms == 0) ?
|
||||
m_parameters.buffer_ms :
|
||||
m_parameters.output_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_enqueue_buffers[i] = std::make_unique<SampleType[]>(m_enqueue_buffer_size * m_output_channels);
|
||||
|
||||
BaseInitialize(sample_readers[static_cast<size_t>(m_parameters.expansion_mode)]);
|
||||
m_volume = 100;
|
||||
m_paused = false;
|
||||
|
||||
hr = m_source_voice->Start(0, 0);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
Error::SetHResult(error, "Start() failed: ", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
EnqueueBuffer();
|
||||
return true;
|
||||
}
|
||||
|
||||
void XAudio2AudioStream::SetPaused(bool paused)
|
||||
{
|
||||
if (m_paused == paused)
|
||||
return;
|
||||
|
||||
if (paused)
|
||||
{
|
||||
HRESULT hr = m_source_voice->Stop(0, 0);
|
||||
if (FAILED(hr))
|
||||
Log_ErrorPrintf("Stop() failed: %08X", hr);
|
||||
}
|
||||
else
|
||||
{
|
||||
HRESULT hr = m_source_voice->Start(0, 0);
|
||||
if (FAILED(hr))
|
||||
Log_ErrorPrintf("Start() failed: %08X", hr);
|
||||
}
|
||||
|
||||
m_paused = paused;
|
||||
|
||||
if (!m_buffer_enqueued)
|
||||
EnqueueBuffer();
|
||||
}
|
||||
|
||||
void XAudio2AudioStream::CloseDevice()
|
||||
{
|
||||
HRESULT hr;
|
||||
if (!m_paused)
|
||||
{
|
||||
hr = m_source_voice->Stop(0, 0);
|
||||
if (FAILED(hr))
|
||||
Log_ErrorPrintf("Stop() failed: %08X", hr);
|
||||
}
|
||||
|
||||
m_source_voice = nullptr;
|
||||
m_mastering_voice = nullptr;
|
||||
m_xaudio.Reset();
|
||||
m_enqueue_buffers = {};
|
||||
m_current_buffer = 0;
|
||||
m_paused = true;
|
||||
}
|
||||
|
||||
void XAudio2AudioStream::EnqueueBuffer()
|
||||
{
|
||||
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_output_channels * m_enqueue_buffer_size), // bytes
|
||||
reinterpret_cast<const BYTE*>(samples), // data
|
||||
0u,
|
||||
0u,
|
||||
0u,
|
||||
0u,
|
||||
0u,
|
||||
nullptr,
|
||||
};
|
||||
|
||||
HRESULT hr = m_source_voice->SubmitSourceBuffer(&buf, nullptr);
|
||||
if (FAILED(hr))
|
||||
Log_ErrorPrintf("SubmitSourceBuffer() failed: %08X", hr);
|
||||
|
||||
m_current_buffer = (m_current_buffer + 1) % NUM_BUFFERS;
|
||||
}
|
||||
|
||||
void XAudio2AudioStream::SetOutputVolume(u32 volume)
|
||||
{
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
void __stdcall XAudio2AudioStream::OnVoiceProcessingPassEnd(void)
|
||||
{
|
||||
}
|
||||
|
||||
void __stdcall XAudio2AudioStream::OnStreamEnd(void)
|
||||
{
|
||||
}
|
||||
|
||||
void __stdcall XAudio2AudioStream::OnBufferStart(void* pBufferContext)
|
||||
{
|
||||
}
|
||||
|
||||
void __stdcall XAudio2AudioStream::OnBufferEnd(void* pBufferContext)
|
||||
{
|
||||
EnqueueBuffer();
|
||||
}
|
||||
|
||||
void __stdcall XAudio2AudioStream::OnLoopEnd(void* pBufferContext)
|
||||
{
|
||||
}
|
||||
|
||||
void __stdcall XAudio2AudioStream::OnVoiceError(void* pBufferContext, HRESULT Error)
|
||||
{
|
||||
}
|
||||
Reference in New Issue
Block a user