Initial re-upload of spice2x-24-08-24
This commit is contained in:
1068
hooks/audio/implementations/asio.cpp
Normal file
1068
hooks/audio/implementations/asio.cpp
Normal file
File diff suppressed because it is too large
Load Diff
153
hooks/audio/implementations/asio.h
Normal file
153
hooks/audio/implementations/asio.h
Normal file
@@ -0,0 +1,153 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
|
||||
#include "external/asio/asio.h"
|
||||
#include "external/asio/iasiodrv.h"
|
||||
#include "external/readerwriterqueue/readerwriterqueue.h"
|
||||
#include "hooks/audio/audio_private.h"
|
||||
#include "hooks/audio/buffer.h"
|
||||
|
||||
#include "backend.h"
|
||||
|
||||
struct AsioBackend;
|
||||
|
||||
extern AsioBackend *ASIO_BACKEND;
|
||||
|
||||
struct BufferEntry {
|
||||
BYTE *buffer;
|
||||
size_t length;
|
||||
size_t read;
|
||||
};
|
||||
|
||||
struct AsioInstanceInfo {
|
||||
long inputs = 0;
|
||||
long outputs = 0;
|
||||
long buffer_min_size = 0;
|
||||
long buffer_max_size = 0;
|
||||
long buffer_preferred_size = 0;
|
||||
long buffer_granularity = 0;
|
||||
long input_latency = 0;
|
||||
long output_latency = 0;
|
||||
};
|
||||
|
||||
struct AsioBackend final : AudioBackend {
|
||||
public:
|
||||
explicit AsioBackend();
|
||||
|
||||
~AsioBackend() final;
|
||||
|
||||
const WAVEFORMATEXTENSIBLE &format() const noexcept override;
|
||||
|
||||
HRESULT on_initialize(
|
||||
AUDCLNT_SHAREMODE *ShareMode,
|
||||
DWORD *StreamFlags,
|
||||
REFERENCE_TIME *hnsBufferDuration,
|
||||
REFERENCE_TIME *hnsPeriodicity,
|
||||
const WAVEFORMATEX *pFormat,
|
||||
LPCGUID AudioSessionGuid) noexcept override;
|
||||
|
||||
HRESULT on_get_buffer_size(uint32_t *buffer_frames) noexcept override;
|
||||
HRESULT on_get_stream_latency(REFERENCE_TIME *latency) noexcept override;
|
||||
HRESULT on_get_current_padding(std::optional<uint32_t> &padding_frames) noexcept override;
|
||||
|
||||
HRESULT on_is_format_supported(
|
||||
AUDCLNT_SHAREMODE *ShareMode,
|
||||
const WAVEFORMATEX *pFormat,
|
||||
WAVEFORMATEX **ppClosestMatch) noexcept override;
|
||||
|
||||
HRESULT on_get_mix_format(WAVEFORMATEX **pp_device_format) noexcept override;
|
||||
|
||||
HRESULT on_get_device_period(
|
||||
REFERENCE_TIME *default_device_period,
|
||||
REFERENCE_TIME *minimum_device_period) noexcept override;
|
||||
|
||||
HRESULT on_start() noexcept override;
|
||||
HRESULT on_stop() noexcept override;
|
||||
HRESULT on_set_event_handle(HANDLE *event_handle) noexcept override;
|
||||
|
||||
HRESULT on_get_buffer(uint32_t num_frames_requested, BYTE **pp_data) noexcept override;
|
||||
HRESULT on_release_buffer(uint32_t num_frames_written, DWORD dwFlags) noexcept override;
|
||||
|
||||
// for overlay
|
||||
inline const AsioDriverInfo &driver_info() const noexcept {
|
||||
return this->driver_info_;
|
||||
}
|
||||
inline const std::vector<AsioChannelInfo> &channel_info() const noexcept {
|
||||
return this->asio_channel_info_;
|
||||
}
|
||||
inline const AsioInstanceInfo &asio_info() const noexcept {
|
||||
return this->asio_info_;
|
||||
}
|
||||
void open_control_panel();
|
||||
|
||||
std::atomic<uint32_t> queued_frames = 0;
|
||||
std::atomic<size_t> queued_bytes = 0;
|
||||
|
||||
private:
|
||||
using AsioFunction = std::function<AsioError()>;
|
||||
|
||||
enum class AsioThreadState {
|
||||
Closed,
|
||||
Failed,
|
||||
Running,
|
||||
ShuttingDown,
|
||||
};
|
||||
struct AsioThreadMessage {
|
||||
AsioFunction fn;
|
||||
bool result_needed;
|
||||
};
|
||||
|
||||
void set_thread_state(AsioThreadState state);
|
||||
bool load_driver();
|
||||
bool update_driver_info();
|
||||
bool update_latency();
|
||||
bool set_initial_format(WAVEFORMATEXTENSIBLE &target);
|
||||
bool init();
|
||||
bool unload_driver();
|
||||
void reset();
|
||||
AsioError run_on_asio_thread(AsioFunction fn, bool result_needed = true);
|
||||
|
||||
// ASIO callbacks
|
||||
static void buffer_switch(long double_buffer_index, AsioBool direct_process);
|
||||
static void sample_rate_did_change(AsioSampleRate sample_rate);
|
||||
static long asio_message(long selector, long value, void *message, double *opt);
|
||||
|
||||
// helper methods
|
||||
static bool is_supported_subformat(const WAVEFORMATEXTENSIBLE &format_ex) noexcept;
|
||||
REFERENCE_TIME compute_ref_time() const;
|
||||
REFERENCE_TIME compute_latency_ref_time() const;
|
||||
|
||||
std::thread asio_thread;
|
||||
std::atomic_bool asio_thread_initialized = false;
|
||||
std::mutex asio_thread_state_lock;
|
||||
// TODO: use `std::atomic<T>::wait` when stabilized in MSVC
|
||||
std::condition_variable asio_thread_state_cv;
|
||||
std::atomic<AsioThreadState> asio_thread_state = AsioThreadState::Closed;
|
||||
moodycamel::BlockingReaderWriterQueue<AsioThreadMessage> asio_msg_queue_func;
|
||||
moodycamel::BlockingReaderWriterQueue<AsioError> asio_msg_queue_result;
|
||||
|
||||
moodycamel::ReaderWriterQueue<BufferEntry> queue;
|
||||
std::optional<HANDLE> relay_handle = std::nullopt;
|
||||
|
||||
IAsio *asio_driver = nullptr;
|
||||
AsioCallbacks asio_callbacks {};
|
||||
AsioDriverInfo driver_info_ {};
|
||||
AsioInstanceInfo asio_info_;
|
||||
std::vector<AsioChannelInfo> asio_channel_info_;
|
||||
std::vector<AsioBufferInfo> asio_buffers;
|
||||
SampleType asio_sample_type = SampleType::UNSUPPORTED;
|
||||
|
||||
std::atomic_bool started = false;
|
||||
WAVEFORMATEXTENSIBLE format_ {};
|
||||
WAVEFORMATEXTENSIBLE last_checked_format {};
|
||||
|
||||
//std::vector<BYTE> last_sound_buffer;
|
||||
std::vector<double> conversion_sound_buffer;
|
||||
BYTE *active_sound_buffer = nullptr;
|
||||
};
|
||||
55
hooks/audio/implementations/backend.h
Normal file
55
hooks/audio/implementations/backend.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
#include <windows.h>
|
||||
#include <audioclient.h>
|
||||
#include <ksmedia.h>
|
||||
|
||||
struct WrappedIAudioClient;
|
||||
|
||||
struct AudioBackend {
|
||||
public:
|
||||
virtual ~AudioBackend() = default;
|
||||
|
||||
[[nodiscard]] virtual const WAVEFORMATEXTENSIBLE &format() const noexcept = 0;
|
||||
|
||||
#pragma region IAudioClient
|
||||
virtual HRESULT on_initialize(
|
||||
AUDCLNT_SHAREMODE *ShareMode,
|
||||
DWORD *StreamFlags,
|
||||
REFERENCE_TIME *hnsBufferDuration,
|
||||
REFERENCE_TIME *hnsPeriodicity,
|
||||
const WAVEFORMATEX *pFormat,
|
||||
LPCGUID AudioSessionGuid) = 0;
|
||||
|
||||
virtual HRESULT on_get_buffer_size(uint32_t *buffer_frames) = 0;
|
||||
|
||||
virtual HRESULT on_get_stream_latency(REFERENCE_TIME *latency) = 0;
|
||||
|
||||
virtual HRESULT on_get_current_padding(std::optional<uint32_t> &padding_frames) = 0;
|
||||
|
||||
virtual HRESULT on_is_format_supported(
|
||||
AUDCLNT_SHAREMODE *ShareMode,
|
||||
const WAVEFORMATEX *pFormat,
|
||||
WAVEFORMATEX **ppClosestMatch) = 0;
|
||||
|
||||
virtual HRESULT on_get_mix_format(WAVEFORMATEX **pp_device_format) = 0;
|
||||
|
||||
virtual HRESULT on_get_device_period(
|
||||
REFERENCE_TIME *default_device_period,
|
||||
REFERENCE_TIME *minimum_device_period) = 0;
|
||||
|
||||
virtual HRESULT on_start() = 0;
|
||||
|
||||
virtual HRESULT on_stop() = 0;
|
||||
|
||||
virtual HRESULT on_set_event_handle(HANDLE *event_handle) = 0;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region IAudioRenderClient
|
||||
virtual HRESULT on_get_buffer(uint32_t num_frames_requested, BYTE **ppData) = 0;
|
||||
virtual HRESULT on_release_buffer(uint32_t num_frames_written, DWORD dwFlags) = 0;
|
||||
#pragma endregion
|
||||
};
|
||||
224
hooks/audio/implementations/wave_out.cpp
Normal file
224
hooks/audio/implementations/wave_out.cpp
Normal file
@@ -0,0 +1,224 @@
|
||||
#include "wave_out.h"
|
||||
|
||||
#include "hooks/audio/audio.h"
|
||||
#include "hooks/audio/backends/wasapi/audio_client.h"
|
||||
#include "hooks/audio/backends/wasapi/defs.h"
|
||||
|
||||
static REFERENCE_TIME WASAPI_TARGET_REFTIME = TARGET_REFTIME;
|
||||
|
||||
HRESULT WaveOutBackend::init(uint32_t buffer_size) {
|
||||
auto &format = hooks::audio::FORMAT.Format;
|
||||
format.wFormatTag = WAVE_FORMAT_PCM;
|
||||
|
||||
log_info("audio::wave_out", "initializing waveOut backend with {} channels, {} Hz, {}-bit",
|
||||
format.nChannels,
|
||||
format.nSamplesPerSec,
|
||||
format.wBitsPerSample);
|
||||
log_info("audio::wave_out", "... nBlockAlign : {} bytes", format.nBlockAlign);
|
||||
log_info("audio::wave_out", "... nAvgBytesPerSec : {} bytes", format.nAvgBytesPerSec);
|
||||
log_info("audio::wave_out", "... buffer reftime : {} ms", WASAPI_TARGET_REFTIME / 10000.f);
|
||||
log_info("audio::wave_out", "... buffer count : {} buffers", _countof(this->hdrs));
|
||||
|
||||
MMRESULT ret = waveOutOpen(
|
||||
&this->handle,
|
||||
WAVE_MAPPER,
|
||||
reinterpret_cast<const WAVEFORMATEX *>(&hooks::audio::FORMAT.Format),
|
||||
reinterpret_cast<DWORD_PTR>(this->dispatcher_event),
|
||||
reinterpret_cast<DWORD_PTR>(nullptr),
|
||||
CALLBACK_EVENT);
|
||||
|
||||
if (ret != MMSYSERR_NOERROR) {
|
||||
log_warning("audio::wave_out", "failed to initialize waveOut backend, hr={:#08x}",
|
||||
static_cast<unsigned>(ret));
|
||||
|
||||
return static_cast<HRESULT>(ret);
|
||||
}
|
||||
|
||||
// initialize buffers
|
||||
for (auto &hdr : this->hdrs) {
|
||||
memset(&hdr, 0, sizeof(hdr));
|
||||
hdr.lpData = new char[buffer_size] {};
|
||||
hdr.dwBufferLength = buffer_size;
|
||||
hdr.dwBytesRecorded = 0;
|
||||
hdr.dwUser = 0;
|
||||
hdr.dwFlags = 0;
|
||||
hdr.dwLoops = 0;
|
||||
hdr.lpNext = nullptr;
|
||||
ret = waveOutPrepareHeader(this->handle, &hdr, sizeof(hdr));
|
||||
|
||||
if (ret != MMSYSERR_NOERROR) {
|
||||
log_warning("audio::wave_out", "failed to prepare waveOut header, hr=0x{:08x}",
|
||||
static_cast<unsigned>(ret));
|
||||
|
||||
return static_cast<HRESULT>(ret);
|
||||
}
|
||||
|
||||
ret = waveOutWrite(this->handle, &hdr, sizeof(hdr));
|
||||
|
||||
if (ret != MMSYSERR_NOERROR) {
|
||||
log_warning("audio::wave_out", "failed to write waveOut header, hr=0x{:08x}",
|
||||
static_cast<unsigned>(ret));
|
||||
|
||||
return static_cast<HRESULT>(ret);
|
||||
}
|
||||
}
|
||||
|
||||
// mark as initialized
|
||||
this->initialized = true;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
const WAVEFORMATEXTENSIBLE &WaveOutBackend::format() const noexcept {
|
||||
return hooks::audio::FORMAT;
|
||||
}
|
||||
|
||||
HRESULT WaveOutBackend::on_initialize(
|
||||
AUDCLNT_SHAREMODE *ShareMode,
|
||||
DWORD *StreamFlags,
|
||||
REFERENCE_TIME *hnsBufferDuration,
|
||||
REFERENCE_TIME *hnsPeriodicity,
|
||||
const WAVEFORMATEX *pFormat,
|
||||
LPCGUID AudioSessionGuid) noexcept
|
||||
{
|
||||
*ShareMode = AUDCLNT_SHAREMODE_SHARED;
|
||||
*StreamFlags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK |
|
||||
AUDCLNT_STREAMFLAGS_RATEADJUST |
|
||||
AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM |
|
||||
AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY;
|
||||
*hnsBufferDuration = WASAPI_TARGET_REFTIME;
|
||||
*hnsPeriodicity = WASAPI_TARGET_REFTIME;
|
||||
|
||||
// this backend only supports stereo audio
|
||||
if (pFormat->nChannels > 2) {
|
||||
return AUDCLNT_E_UNSUPPORTED_FORMAT;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
HRESULT WaveOutBackend::on_get_buffer_size(uint32_t *buffer_frames) noexcept {
|
||||
*buffer_frames = _countof(this->hdrs);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
HRESULT WaveOutBackend::on_get_stream_latency(REFERENCE_TIME *latency) noexcept {
|
||||
*latency = WASAPI_TARGET_REFTIME;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
HRESULT WaveOutBackend::on_get_current_padding(std::optional<uint32_t> &padding_frames) noexcept {
|
||||
size_t queued_bytes = 0;
|
||||
|
||||
for (auto &hdr : this->hdrs) {
|
||||
if (hdr.dwFlags & WHDR_DONE) {
|
||||
queued_bytes += static_cast<unsigned>(hdr.dwBufferLength);
|
||||
}
|
||||
}
|
||||
|
||||
auto frames = static_cast<uint32_t>(queued_bytes / hooks::audio::FORMAT.Format.nBlockAlign);
|
||||
//log_info("audio::wave_out", "queued_bytes = {}, frames = {}", queued_bytes, frames);
|
||||
|
||||
padding_frames = frames;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
HRESULT WaveOutBackend::on_is_format_supported(
|
||||
AUDCLNT_SHAREMODE *ShareMode,
|
||||
const WAVEFORMATEX *pFormat,
|
||||
WAVEFORMATEX **ppClosestMatch) noexcept
|
||||
{
|
||||
// always support 44.1 kHz, stereo, 16-bits per channel with custom backends
|
||||
if (*ShareMode == AUDCLNT_SHAREMODE_EXCLUSIVE &&
|
||||
pFormat->nChannels == 2 &&
|
||||
pFormat->nSamplesPerSec == 44100 &&
|
||||
pFormat->wBitsPerSample == 16)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return AUDCLNT_E_UNSUPPORTED_FORMAT;
|
||||
}
|
||||
HRESULT WaveOutBackend::on_get_mix_format(WAVEFORMATEX **pp_device_format) noexcept {
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
HRESULT WaveOutBackend::on_get_device_period(
|
||||
REFERENCE_TIME *default_device_period,
|
||||
REFERENCE_TIME *minimum_device_period)
|
||||
{
|
||||
*default_device_period = WASAPI_TARGET_REFTIME;
|
||||
*minimum_device_period = WASAPI_TARGET_REFTIME;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
HRESULT WaveOutBackend::on_start() noexcept {
|
||||
return S_OK;
|
||||
}
|
||||
HRESULT WaveOutBackend::on_stop() noexcept {
|
||||
return S_OK;
|
||||
}
|
||||
HRESULT WaveOutBackend::on_set_event_handle(HANDLE *event_handle) {
|
||||
this->relay_event = *event_handle;
|
||||
this->dispatcher_event = CreateEvent(nullptr, true, false, nullptr);
|
||||
|
||||
*event_handle = this->dispatcher_event;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT WaveOutBackend::on_get_buffer(uint32_t num_frames_requested, BYTE **ppData) {
|
||||
auto buffer_size = hooks::audio::FORMAT.Format.nBlockAlign * num_frames_requested;
|
||||
|
||||
if (!this->initialized) {
|
||||
this->init(buffer_size);
|
||||
}
|
||||
|
||||
// wait for a free slot
|
||||
WaitForSingleObject(this->dispatcher_event, INFINITE);
|
||||
|
||||
// allocate temporary sound buffer
|
||||
this->active_sound_buffer = reinterpret_cast<BYTE *>(CoTaskMemAlloc(buffer_size));
|
||||
|
||||
// hand the buffer to the callee
|
||||
*ppData = this->active_sound_buffer;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
HRESULT WaveOutBackend::on_release_buffer(uint32_t num_frames_written, DWORD dwFlags) {
|
||||
bool written = false;
|
||||
|
||||
// reset the dispatcher event
|
||||
ResetEvent(this->dispatcher_event);
|
||||
|
||||
while (!written) {
|
||||
for (WAVEHDR &hdr : this->hdrs) {
|
||||
if (hdr.dwFlags & WHDR_DONE) {
|
||||
memcpy(hdr.lpData, this->active_sound_buffer, hdr.dwBufferLength);
|
||||
|
||||
// write the data to the device now
|
||||
MMRESULT ret = waveOutWrite(this->handle, &hdr, sizeof(hdr));
|
||||
|
||||
if (ret != MMSYSERR_NOERROR) {
|
||||
log_warning("audio::wave_out", "failed to write waveOut data, hr={:#08x}",
|
||||
static_cast<unsigned>(ret));
|
||||
}
|
||||
|
||||
written = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// avoid pegging the CPU
|
||||
if (!written) {
|
||||
Sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
// free temporary sound buffer
|
||||
CoTaskMemFree(this->active_sound_buffer);
|
||||
this->active_sound_buffer = nullptr;
|
||||
|
||||
// trigger game audio callback
|
||||
SetEvent(this->relay_event);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
58
hooks/audio/implementations/wave_out.h
Normal file
58
hooks/audio/implementations/wave_out.h
Normal file
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include <mmdeviceapi.h>
|
||||
#include <mmsystem.h>
|
||||
|
||||
#include "backend.h"
|
||||
|
||||
#define WASAPI_BUFFER_COUNT 3
|
||||
#define TARGET_REFTIME (100000) // 10 ms
|
||||
|
||||
struct WaveOutBackend final : AudioBackend {
|
||||
public:
|
||||
~WaveOutBackend() final = default;
|
||||
|
||||
HRESULT init(uint32_t buffer_size);
|
||||
|
||||
const WAVEFORMATEXTENSIBLE &format() const noexcept override;
|
||||
|
||||
HRESULT on_initialize(
|
||||
AUDCLNT_SHAREMODE *ShareMode,
|
||||
DWORD *StreamFlags,
|
||||
REFERENCE_TIME *hnsBufferDuration,
|
||||
REFERENCE_TIME *hnsPeriodicity,
|
||||
const WAVEFORMATEX *pFormat,
|
||||
LPCGUID AudioSessionGuid) noexcept override;
|
||||
|
||||
HRESULT on_get_buffer_size(uint32_t *buffer_frames) noexcept override;
|
||||
HRESULT on_get_stream_latency(REFERENCE_TIME *latency) noexcept override;
|
||||
HRESULT on_get_current_padding(std::optional<uint32_t> &padding_frames) noexcept override;
|
||||
|
||||
HRESULT on_is_format_supported(
|
||||
AUDCLNT_SHAREMODE *ShareMode,
|
||||
const WAVEFORMATEX *pFormat,
|
||||
WAVEFORMATEX **ppClosestMatch) noexcept override;
|
||||
|
||||
HRESULT on_get_mix_format(WAVEFORMATEX **pp_device_format) noexcept override;
|
||||
|
||||
HRESULT on_get_device_period(
|
||||
REFERENCE_TIME *default_device_period,
|
||||
REFERENCE_TIME *minimum_device_period) override;
|
||||
|
||||
HRESULT on_start() noexcept override;
|
||||
HRESULT on_stop() noexcept override;
|
||||
HRESULT on_set_event_handle(HANDLE *event_handle) override;
|
||||
|
||||
HRESULT on_get_buffer(uint32_t num_frames_requested, BYTE **ppData) override;
|
||||
HRESULT on_release_buffer(uint32_t num_frames_written, DWORD dwFlags) override;
|
||||
|
||||
private:
|
||||
WrappedIAudioClient *client;
|
||||
|
||||
bool initialized = false;
|
||||
HANDLE relay_event = nullptr;
|
||||
HANDLE dispatcher_event = nullptr;
|
||||
HWAVEOUT handle = nullptr;
|
||||
WAVEHDR hdrs[WASAPI_BUFFER_COUNT] {};
|
||||
BYTE *active_sound_buffer = nullptr;
|
||||
};
|
||||
Reference in New Issue
Block a user