From 6c53848ea435152b3f7177fc5bdab69c3e0db048 Mon Sep 17 00:00:00 2001
From: "[ ]" <[ ]>
Date: Mon, 9 Oct 2023 01:55:17 +0900
Subject: [PATCH] feat: First working pipewire endpoint
> uses `T_NOTIF_SPICE` experimental endpoint on bmsound-pw's side
> latency should be on par with wasapi shared?
---
.idea/markdown-navigator-enh.xml | 10 +
.idea/markdown-navigator.xml | 71 +++++
.idea/vcs.xml | 1 -
CMakeLists.txt | 2 +-
hooks/audio/implementations/pipewire.cpp | 359 +++++++++++++++++------
hooks/audio/implementations/pipewire.h | 38 +--
6 files changed, 360 insertions(+), 121 deletions(-)
create mode 100644 .idea/markdown-navigator-enh.xml
create mode 100644 .idea/markdown-navigator.xml
diff --git a/.idea/markdown-navigator-enh.xml b/.idea/markdown-navigator-enh.xml
new file mode 100644
index 0000000..a8fcc84
--- /dev/null
+++ b/.idea/markdown-navigator-enh.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/markdown-navigator.xml b/.idea/markdown-navigator.xml
new file mode 100644
index 0000000..d7e995c
--- /dev/null
+++ b/.idea/markdown-navigator.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index 6cc311d..35eb1dd 100644
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -2,6 +2,5 @@
-
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e8c0e02..2946fcd 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -573,7 +573,7 @@ set(RESOURCE_FILES build/manifest.manifest build/manifest64.rc build/icon.rc cfg
add_executable(spicetools_spice64 ${SOURCE_FILES} ${RESOURCE_FILES})
target_link_libraries(spicetools_spice64
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winscard
- PRIVATE fmt-header-only discord-rpc imgui hash-library minhook openvr_api64 lua_static imm32 dwmapi)
+ PRIVATE fmt-header-only discord-rpc imgui hash-library minhook openvr_api64 lua_static imm32 dwmapi ntdll)
set_target_properties(spicetools_spice64 PROPERTIES PREFIX "")
set_target_properties(spicetools_spice64 PROPERTIES OUTPUT_NAME "spice64")
target_compile_definitions(spicetools_spice64 PRIVATE SPICE64=1)
diff --git a/hooks/audio/implementations/pipewire.cpp b/hooks/audio/implementations/pipewire.cpp
index dd85d0e..6a78b27 100644
--- a/hooks/audio/implementations/pipewire.cpp
+++ b/hooks/audio/implementations/pipewire.cpp
@@ -1,111 +1,288 @@
+#include
#include "pipewire.h"
#include "hooks/audio/audio.h"
#include "hooks/audio/backends/wasapi/audio_client.h"
#include "util/libutils.h"
#include "launcher/launcher.h"
+#include "hooks/audio/util.h"
+#include "hooks/audio/backends/wasapi/util.h"
-/* Imports (refer to bmsound-wine.dll.spec) */
-typedef void(*BmswClientInit_t)(const char*);
-const WAVEFORMATEXTENSIBLE &PipewireBackend::format() const noexcept {
+/*
+... ShareMode : AUDCLNT_SHAREMODE_EXCLUSIVE <- backend to reimplement, THIS is the audio engine, GAME is client https://learn.microsoft.com/en-us/windows/win32/coreaudio/audclnt-streamflags-xxx-constants
+... StreamFlags : AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST <- first is the whole reason for relay_handle_ requirement, second flag not tested
+... hnsBufferDuration : 10000 <- wasapi people inventing new time units, 1ms (since 1unit==100ns, 10000*0.0001ms)
+... hnsPeriodicity : 10000 <- same as above (1ms)
+... nChannels : 2 <- channel_count
+... nSamplesPerSec : 44100 <- bitrate
+... nAvgBytesPerSec : 176400 <- raw buffer size per second (bitrate * stride)(bytes)
+... nBlockAlign : 4 <- stride (bytes)
+... wBitsPerSample : 16 <- sample size (bits)
+... wValidBitsPerSample : 16 <- same as above
+... dwChannelMask : SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT <- position config
+ _INFO: Code pipeline
+ INIT: on_is_format_supported()?->on_initialize()->on_set_event_handle()-:(LOOPx1):->on_start()
+ LOOP: if relay_handle_: on_get_buffer_size()->on_get_buffer()->..->on_release_buffer()
+ DEINIT: on_stop()
+
+ _REV: POSSIBLE ALTERNATIVES:
+ Check if the stream is driving. The stream needs to have the
+ * PW_STREAM_FLAG_DRIVER set. When the stream is driving,
+ * pw_stream_trigger_process() needs to be called when data is
+ * available (output) or needed (input). Since 0.3.34
+ bool pw_stream_is_driving(struct pw_stream *stream);
+ * */
+
+/* Imports/Exports (refer to bmsound-wine.dll.spec) */
+typedef void (*BmswExperimentalProfile_t)(int);
+typedef void *(*BmswClientCreate_t)(const char *, void *, void *);
+typedef int(*BmswClientStart_t)(void *);
+typedef int(*BmswClientStop_t)(void *);
+typedef int(*BmswClientDestroy_t)(void *);
+typedef unsigned char *(*BmswClientGetBuffer_t)(void *, uint32_t);
+typedef int(*BmswClientReleaseBuffer_t)(void *, uint32_t);
+typedef int(*BmswClientFormatSupported_t)(DWORD, DWORD, DWORD);
+typedef REFERENCE_TIME(*BmswClientWasapiPeriod_t)(void *);
+typedef void (*BmswClientUpdateCallback_t)(void *, void *, void *);
+static BmswExperimentalProfile_t BmswExperimentalProfile;
+static BmswClientCreate_t BmswClientCreate;
+static BmswClientStart_t BmswClientStart;
+static BmswClientStop_t BmswClientStop;
+static BmswClientDestroy_t BmswClientDestroy;
+static BmswClientGetBuffer_t BmswClientGetBuffer;
+static BmswClientReleaseBuffer_t BmswClientReleaseBuffer;
+static BmswClientFormatSupported_t BmswClientFormatSupported;
+static BmswClientWasapiPeriod_t BmswClientWasapiPeriod;
+static BmswClientUpdateCallback_t BmswClientUpdateCallback;
+static HMODULE bmsw_ = nullptr;
+enum profile_exp
+{
+ T_NONE = 0,
+ T_AUDIO_FORMAT,
+ T_NOTIF_CALLBACK,
+ T_NOTIF_SPICE,
+ T_TWIN_CURSOR,
+ T_SPOOFED_LOOP,
+ T_STATIC_SINE,
+ T_LAST
+};
+
+/* Audio init (unless specified otherwise, run once at start) */
+// Reports to game whether each requested audio format is available for device (first success return will be used)
+HRESULT PipewireBackend::on_is_format_supported(AUDCLNT_SHAREMODE *ShareMode, const WAVEFORMATEX *pFormat, WAVEFORMATEX **ppClosestMatch) noexcept
+{
+ // Format reporting and filtering
+ log_info("audio::pipewire", "{}", __FUNCTION__);
+ log_misc("audio::pipewire", "Checking backend support for {} channels, {} Hz, {}-bit",
+ pFormat->nChannels,
+ pFormat->nSamplesPerSec,
+ pFormat->wBitsPerSample);
+
+ // IIDX? will always request format that was last checked through this function
+ if (*ShareMode != AUDCLNT_SHAREMODE_EXCLUSIVE) return AUDCLNT_E_UNSUPPORTED_FORMAT;
+
+ // Request format support from real backend (only accepts 44.1 kHz, stereo, 16-bits per channel for now)
+ return BmswClientFormatSupported(pFormat->nSamplesPerSec, pFormat->nChannels, pFormat->wBitsPerSample) == 0 ? S_OK : AUDCLNT_E_UNSUPPORTED_FORMAT;
+}
+// Populate hnsPeriodicity, _INFO: runs before on_initialize, requires configured client stream data
+HRESULT PipewireBackend::on_get_device_period(REFERENCE_TIME *default_device_period, REFERENCE_TIME *minimum_device_period)
+{
+ log_info("audio::pipewire", "{}", __FUNCTION__);
+ if (client_)
+ {
+ REFERENCE_TIME ref_time = BmswClientWasapiPeriod(client_);
+ *default_device_period = ref_time;
+ *minimum_device_period = ref_time;
+ }
+
+ return S_OK;
+}
+// This expects to be run once on_is_format_supported() succeeds, initializes backend based on passed arguments (and updates them if needed)
+HRESULT PipewireBackend::on_initialize(AUDCLNT_SHAREMODE *ShareMode, DWORD *StreamFlags, REFERENCE_TIME *hnsBufferDuration, REFERENCE_TIME *hnsPeriodicity, const WAVEFORMATEX *pFormat, LPCGUID AudioSessionGuid) noexcept
+{
+ log_info("audio::pipewire", "{}", __FUNCTION__);
+
+ // Initialize pipewire client (without starting the thread)
+ client_ = BmswClientCreate(GAME_INSTANCE->title(), nullptr, nullptr);//(void *)callback_notify,this
+
+ if (!client_)
+ log_fatal("audio::pipewire", "Client could not be initialized");
+ log_info("audio::pipewire", "Client initialized: '{}'", fmt::ptr(client_));
+
+ // Adjust WASAPI configuration visible to game (passed arguments are populated and should match that of last on_is_format_supported call)
+ auto ref_time = BmswClientWasapiPeriod(client_); //_INFO: this affects reported latency (1:100ns)
+ *hnsBufferDuration = ref_time;
+ *hnsPeriodicity = ref_time;
+
+ //_TODO: Init info
+ log_info("audio::asio", "Device Info:");
+ log_info("audio::pipewire", "... hnsBufferDuration : {}", *hnsBufferDuration);
+ log_info("audio::pipewire", "... hnsPeriodicity : {}", *hnsPeriodicity);
+
+ return S_OK;
+}
+// This takes ownership over shared event handle exclusive to AUDCLNT_STREAMFLAGS_EVENTCALLBACK _INFO: first loop iteration runs directly after this, before on_start() call
+HRESULT PipewireBackend::on_set_event_handle(HANDLE *event_handle)
+{
+ log_info("audio::pipewire", "{}", __FUNCTION__);
+ this->relay_handle_ = *event_handle; // take over WASAPI's owned handle pre-initialized by client
+ //BmswClientUpdateCallback(client_, (void *) 0, this->relay_handle_);
+ *event_handle = CreateEvent(nullptr, true, false, nullptr); // replace WASAPI's handle with always off handle
+
+ return S_OK;
+
+ ULONG ntbuf_size = sizeof(OBJECT_NAME_INFORMATION) + 1024;
+ char ntbuf[ntbuf_size];
+ OBJECT_NAME_INFORMATION *ntinfo = (OBJECT_NAME_INFORMATION *) ntbuf;
+ NTSTATUS status = NtQueryObject(*event_handle, ObjectNameInformation, ntinfo, ntbuf_size, &ntbuf_size);
+ if (NT_SUCCESS(status))
+ {
+ // possible to rename anonymous handle to access by name
+ }
+}
+HRESULT PipewireBackend::on_start() noexcept
+{
+ log_info("audio::pipewire", "{}", __FUNCTION__);
+ BmswClientStart(client_);
+ return S_OK;
+}
+PipewireBackend::PipewireBackend() : format_(hooks::audio::FORMAT), client_(nullptr)
+{
+ log_info("audio::pipewire", "{}", __FUNCTION__);
+
+ // Initialize bmsound-wine.dll once
+ if (bmsw_) return;
+ if ((bmsw_ = libutils::try_library(MODULE_PATH / "bmsound-wine.dll")))
+ {
+ BmswExperimentalProfile = (BmswExperimentalProfile_t) GetProcAddress(bmsw_, "BmswExperimentalProfile");
+ BmswClientCreate = (BmswClientCreate_t) GetProcAddress(bmsw_, "BmswClientCreate");
+ BmswClientStart = (BmswClientStart_t) GetProcAddress(bmsw_, "BmswClientStart");
+ BmswClientStop = (BmswClientStop_t) GetProcAddress(bmsw_, "BmswClientStop");
+ BmswClientDestroy = (BmswClientDestroy_t) GetProcAddress(bmsw_, "BmswClientDestroy");
+ BmswClientGetBuffer = (BmswClientGetBuffer_t) GetProcAddress(bmsw_, "BmswClientGetBuffer");
+ BmswClientReleaseBuffer = (BmswClientReleaseBuffer_t) GetProcAddress(bmsw_, "BmswClientReleaseBuffer");
+ BmswClientFormatSupported = (BmswClientFormatSupported_t) GetProcAddress(bmsw_, "BmswClientFormatSupported");
+ BmswClientWasapiPeriod = (BmswClientWasapiPeriod_t) GetProcAddress(bmsw_, "BmswClientWasapiPeriod");
+ BmswClientUpdateCallback = (BmswClientUpdateCallback_t) GetProcAddress(bmsw_, "BmswClientUpdateCallback");
+
+ // Switch library to specific test API
+ BmswExperimentalProfile(T_NOTIF_SPICE);
+
+ return;
+ }
+ log_fatal("audio::pipewire", "Library not found: '{}'", (MODULE_PATH / "bmsound-wine.dll").string());
+}
+
+/* Audio callback loop (reacts to state of relay_handle_, most likely separate thread */
+// _REV: Synchronizing client and backend with nanotime may be necessary (bmsound-pw endpoint dependant)
+inline static void callback_notify(void *self)
+{
+ //log_info("audio::pipewire", "{}", __FUNCTION__);
+ auto *self_ = (PipewireBackend *) self;
+ //std::this_thread::sleep_for(std::chrono::nanoseconds(10 * 1000 * 1000 - 227));
+ if (!SetEvent(self_->relay_handle_)) // has to be called for game-side audio loop to continue
+ {
+ DWORD last_error = GetLastError();
+
+ log_warning("audio::asio", "AsioBackend::buffer_switch: SetEvent failed: {} ({})",
+ last_error,
+ std::system_category().message(last_error));
+ }
+}
+// Amount in frames of data we may handle at once (which should always end up being what gets send by client, unless overrun happens)
+HRESULT PipewireBackend::on_get_buffer_size(uint32_t *buffer_frames) noexcept
+{
+ static int iterc = -1;
+ if (iterc == -1)
+ {
+ log_info("audio::pipewire", "{}, frames: {} FIRST TIME", __FUNCTION__, *buffer_frames);
+ }
+ iterc++;
+ if (iterc > 999999)
+ {
+ log_info("audio::pipewire", "on_get_buffer({}), frames: {}", iterc, *buffer_frames);
+ iterc = 0;
+ }
+
+ *buffer_frames = 411;
+
+ return S_OK;
+}
+// Wants a raw stream buffer to be assigned into *ppData, this stream will have x amount of sound frames stored into it by a client and assumes exclusive access until next on_release_buffer
+HRESULT PipewireBackend::on_get_buffer(uint32_t num_frames_requested, BYTE **ppData)
+{
+ static int iterc = -1;
+ if (iterc == -1)
+ {
+ log_info("audio::pipewire", "{}, frames: {} FIRST TIME", __FUNCTION__, num_frames_requested);
+ }
+ iterc++;
+ if (iterc > 999999)
+ {
+ log_info("audio::pipewire", "on_get_buffer({}), frames: {}", iterc, num_frames_requested);
+ iterc = 0;
+ }
+
+ *ppData = BmswClientGetBuffer(client_, num_frames_requested);
+
+ return S_OK;
+}
+// This releases access to buffer from last on_get_buffer and implies x amount of frames being valid data to be streamed
+HRESULT PipewireBackend::on_release_buffer(uint32_t num_frames_written, DWORD dwFlags)
+{
+ static int iterc = -1;
+ if (iterc == -1)
+ {
+ log_info("audio::pipewire", "{}, frames: {} FIRST TIME", __FUNCTION__, num_frames_written);
+ }
+ iterc++;
+ if (iterc > 999999)
+ {
+ log_info("audio::pipewire", "on_release_buffer({}), frames: {}", iterc, num_frames_written);
+ iterc = 0;
+ }
+
+ BmswClientReleaseBuffer(client_, num_frames_written);
+ callback_notify(this);
+
+ return S_OK;
+}
+
+/* Audio deinit (unless specified otherwise, run once at termination) */
+HRESULT PipewireBackend::on_stop() noexcept
+{
+ log_info("audio::pipewire", "{}", __FUNCTION__);
+ BmswClientStop(client_);
+ return S_OK;
+}
+PipewireBackend::~PipewireBackend()
+{
+ log_info("audio::pipewire", "~PipewireBackend");
+ if (client_) BmswClientDestroy(client_);
+}
+
+/* Unsorted (unknown callers, should be considered as unimplemented/untested) *///_BUG: does this need implementation?
+const WAVEFORMATEXTENSIBLE &PipewireBackend::format() const noexcept
+{
+ log_info("audio::pipewire", "{}", __FUNCTION__);
return format_;
}
-
-HRESULT PipewireBackend::on_initialize(
- AUDCLNT_SHAREMODE *ShareMode,
- DWORD *StreamFlags,
- REFERENCE_TIME *hnsBufferDuration,
- REFERENCE_TIME *hnsPeriodicity,
- const WAVEFORMATEX *pFormat,
- LPCGUID AudioSessionGuid) noexcept
+HRESULT PipewireBackend::on_get_stream_latency(REFERENCE_TIME *latency) noexcept
{
- log_info("audio::pipewire", "on_initialize");
-
- // WASAPI init
- *ShareMode = AUDCLNT_SHAREMODE_SHARED;
- *StreamFlags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK |
- AUDCLNT_STREAMFLAGS_RATEADJUST |
- AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM |
- AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY;
- *hnsBufferDuration = 100000;
- *hnsPeriodicity = 100000;
-
- // Start pipewire thread
- if (!bmsw_) log_fatal("audio::pipewire", "Library not found: '{}'", (MODULE_PATH / "bmsound-wine.dll").string());
- auto pipewireinit = (BmswClientInit_t)GetProcAddress(bmsw_, "BmswClientInit");
- if (!pipewireinit) log_fatal("audio::pipewire", "Library invalid: '{}'", (MODULE_PATH / "bmsound-wine.dll").string());
- internal_ = std::thread(pipewireinit,GAME_INSTANCE->title());
+ log_info("audio::pipewire", "{}", __FUNCTION__);
+ if (client_) *latency = BmswClientWasapiPeriod(client_);
return S_OK;
}
-HRESULT PipewireBackend::on_get_buffer_size(uint32_t *buffer_frames) noexcept {
- *buffer_frames = 0;
-
- return S_OK;
-}
-HRESULT PipewireBackend::on_get_stream_latency(REFERENCE_TIME *latency) noexcept {
- *latency = 100000;
-
- return S_OK;
-}
-HRESULT PipewireBackend::on_get_current_padding(std::optional &padding_frames) noexcept {
-
+HRESULT PipewireBackend::on_get_current_padding(std::optional &padding_frames) noexcept
+{
+ log_info("audio::pipewire", "{}", __FUNCTION__);
padding_frames = 0;
return S_OK;
}
-HRESULT PipewireBackend::on_is_format_supported(
- AUDCLNT_SHAREMODE *ShareMode,
- const WAVEFORMATEX *pFormat,
- WAVEFORMATEX **ppClosestMatch) noexcept
+HRESULT PipewireBackend::on_get_mix_format(WAVEFORMATEX **pp_device_format) noexcept
{
- // only accept 44.1 kHz, stereo, 16-bits per channel
- if (*ShareMode == AUDCLNT_SHAREMODE_EXCLUSIVE &&
- pFormat->nChannels == 2 &&
- pFormat->nSamplesPerSec == 44100 &&
- pFormat->wBitsPerSample == 16)
- {
- return S_OK;
- }
-
- return AUDCLNT_E_UNSUPPORTED_FORMAT;
-}
-HRESULT PipewireBackend::on_get_mix_format(WAVEFORMATEX **pp_device_format) noexcept {
+ log_info("audio::pipewire", "{}", __FUNCTION__);
return E_NOTIMPL;
}
-HRESULT PipewireBackend::on_get_device_period(
- REFERENCE_TIME *default_device_period,
- REFERENCE_TIME *minimum_device_period)
-{
- *default_device_period = 10000;
- *minimum_device_period = 10000;
-
- return S_OK;
-}
-HRESULT PipewireBackend::on_start() noexcept {
- return S_OK;
-}
-HRESULT PipewireBackend::on_stop() noexcept {
- return S_OK;
-}
-HRESULT PipewireBackend::on_set_event_handle(HANDLE *event_handle) {
-
- *event_handle = CreateEvent(nullptr, true, false, nullptr);
-
- return S_OK;
-}
-
-HRESULT PipewireBackend::on_get_buffer(uint32_t num_frames_requested, BYTE **ppData) {
- static BYTE buf[10000];
- *ppData = buf;
-
- return S_OK;
-}
-HRESULT PipewireBackend::on_release_buffer(uint32_t num_frames_written, DWORD dwFlags) {
-
- return S_OK;
-}
-PipewireBackend::PipewireBackend() : format_(hooks::audio::FORMAT), bmsw_(libutils::try_library(MODULE_PATH / "bmsound-wine.dll"))
-{
-
-}
diff --git a/hooks/audio/implementations/pipewire.h b/hooks/audio/implementations/pipewire.h
index e491dfd..15bdb2f 100644
--- a/hooks/audio/implementations/pipewire.h
+++ b/hooks/audio/implementations/pipewire.h
@@ -4,48 +4,30 @@
#include
-struct PipewireBackend final : AudioBackend {
+struct PipewireBackend final : AudioBackend
+{
public:
+ HANDLE relay_handle_;
+
explicit PipewireBackend();
- ~PipewireBackend() final = default;
-
+ ~PipewireBackend() final;
[[nodiscard]] 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_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 &padding_frames) noexcept override;
-
- HRESULT on_is_format_supported(
- AUDCLNT_SHAREMODE *ShareMode,
- const WAVEFORMATEX *pFormat,
- WAVEFORMATEX **ppClosestMatch) 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_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:
-
const WAVEFORMATEXTENSIBLE &format_;
- std::thread internal_;
- HMODULE bmsw_;
- //BYTE *active_sound_buffer = nullptr;
+ void *client_;
+
};