diff --git a/CMakeLists.txt b/CMakeLists.txt index 60002df..e8c0e02 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -418,6 +418,7 @@ set(SOURCE_FILES ${SOURCE_FILES} hooks/audio/implementations/asio.cpp hooks/audio/implementations/wave_out.cpp hooks/audio/implementations/none.cpp + hooks/audio/implementations/pipewire.cpp hooks/avshook.cpp hooks/cfgmgr32hook.cpp hooks/debughook.cpp diff --git a/hooks/audio/audio.h b/hooks/audio/audio.h index 0c9fe06..0dcb8c6 100644 --- a/hooks/audio/audio.h +++ b/hooks/audio/audio.h @@ -11,6 +11,7 @@ namespace hooks::audio { enum class Backend { Asio, WaveOut, + Pipewire, None }; @@ -28,6 +29,8 @@ namespace hooks::audio { return Backend::Asio; } else if (_stricmp(value, "waveout") == 0) { return Backend::WaveOut; + } else if (_stricmp(value, "pipewire") == 0) { + return Backend::Pipewire; } else if (_stricmp(value, "none") == 0) { return Backend::None; } diff --git a/hooks/audio/backends/wasapi/audio_client.cpp b/hooks/audio/backends/wasapi/audio_client.cpp index dfa0204..6aa0d8d 100644 --- a/hooks/audio/backends/wasapi/audio_client.cpp +++ b/hooks/audio/backends/wasapi/audio_client.cpp @@ -9,6 +9,7 @@ #include "hooks/audio/backends/wasapi/util.h" #include "hooks/audio/implementations/asio.h" #include "hooks/audio/implementations/wave_out.h" +#include "hooks/audio/implementations/pipewire.h" #include "hooks/audio/implementations/none.h" //#include "util/co_task_mem_ptr.h" @@ -161,6 +162,9 @@ IAudioClient *wrap_audio_client(IAudioClient *audio_client) { case hooks::audio::Backend::WaveOut: backend = new WaveOutBackend(); break; + case hooks::audio::Backend::Pipewire: + backend = new PipewireBackend(); + break; case hooks::audio::Backend::None: backend = new NoneBackend(); break; diff --git a/hooks/audio/implementations/pipewire.cpp b/hooks/audio/implementations/pipewire.cpp new file mode 100644 index 0000000..664af26 --- /dev/null +++ b/hooks/audio/implementations/pipewire.cpp @@ -0,0 +1,111 @@ +#include "pipewire.h" +#include "hooks/audio/audio.h" +#include "hooks/audio/backends/wasapi/audio_client.h" +#include "util/libutils.h" +#include "launcher/launcher.h" + +/* Imports (refer to bmsound-wine.dll.spec) */ +typedef void(*BmswClientInit_t)(const char*); + +const WAVEFORMATEXTENSIBLE &PipewireBackend::format() const noexcept { + return format_; +} + +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", "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 TITLE"); + + 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 { + + padding_frames = 0; + + return S_OK; +} +HRESULT PipewireBackend::on_is_format_supported( + AUDCLNT_SHAREMODE *ShareMode, + const WAVEFORMATEX *pFormat, + WAVEFORMATEX **ppClosestMatch) 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 { + 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 new file mode 100644 index 0000000..e491dfd --- /dev/null +++ b/hooks/audio/implementations/pipewire.h @@ -0,0 +1,51 @@ +#pragma once + +#include "backend.h" +#include + + +struct PipewireBackend final : AudioBackend { +public: + explicit PipewireBackend(); + ~PipewireBackend() final = default; + + [[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_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_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: + + const WAVEFORMATEXTENSIBLE &format_; + std::thread internal_; + HMODULE bmsw_; + //BYTE *active_sound_buffer = nullptr; +}; + diff --git a/launcher/options.cpp b/launcher/options.cpp index 374ded7..bfaad75 100644 --- a/launcher/options.cpp +++ b/launcher/options.cpp @@ -1108,7 +1108,7 @@ static const std::vector OPTION_DEFINITIONS = { "Game must be outputting to WASAPI Exclusive mode for audio hook to work.", .type = OptionType::Enum, .category = "Audio", - .elements = {{"asio", "ASIO"}, {"waveout", "waveOut"},{"none", "None"}}, + .elements = {{"asio", "ASIO"}, {"waveout", "waveOut"},{"pipewire", "Pipewire (Linux)"},{"none", "None"}}, }, { .title = "Spice Audio Hook ASIO Driver ID",