feat: First working pipewire endpoint
> uses `T_NOTIF_SPICE` experimental endpoint on bmsound-pw's side > latency should be on par with wasapi shared?
This commit is contained in:
Generated
+10
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="MarkdownEnhProjectSettings">
|
||||||
|
<AnnotatorSettings targetHasSpaces="true" linkCaseMismatch="true" wikiCaseMismatch="true" wikiLinkHasDashes="true" notUnderWikiHome="true" targetNotWikiPageExt="true" notUnderSourceWikiHome="true" targetNameHasAnchor="true" targetPathHasAnchor="true" wikiLinkHasSlash="true" wikiLinkHasSubdir="true" wikiLinkHasOnlyAnchor="true" linkTargetsWikiHasExt="true" linkTargetsWikiHasBadExt="true" notUnderSameRepo="true" targetNotUnderVcs="false" linkNeedsExt="true" linkHasBadExt="true" linkTargetNeedsExt="true" linkTargetHasBadExt="true" wikiLinkNotInWiki="true" imageTargetNotInRaw="true" repoRelativeAcrossVcsRoots="true" multipleWikiTargetsMatch="true" unresolvedLinkReference="true" linkIsIgnored="true" anchorIsIgnored="true" anchorIsUnresolved="true" anchorLineReferenceIsUnresolved="true" anchorLineReferenceFormat="true" anchorHasDuplicates="true" abbreviationDuplicates="true" abbreviationNotUsed="true" attributeIdDuplicateDefinition="true" attributeIdNotUsed="true" footnoteDuplicateDefinition="true" footnoteUnresolved="true" footnoteDuplicates="true" footnoteNotUsed="true" macroDuplicateDefinition="true" macroUnresolved="true" macroDuplicates="true" macroNotUsed="true" referenceDuplicateDefinition="true" referenceUnresolved="true" referenceDuplicates="true" referenceNotUsed="true" referenceUnresolvedNumericId="true" enumRefDuplicateDefinition="true" enumRefUnresolved="true" enumRefDuplicates="true" enumRefNotUsed="true" enumRefLinkUnresolved="true" enumRefLinkDuplicates="true" simTocUpdateNeeded="true" simTocTitleSpaceNeeded="true" />
|
||||||
|
<HtmlExportSettings updateOnSave="false" parentDir="" targetDir="" cssDir="css" scriptDir="js" plainHtml="false" imageDir="" copyLinkedImages="false" imagePathType="0" targetPathType="2" targetExt="" useTargetExt="false" noCssNoScripts="false" useElementStyleAttribute="false" linkToExportedHtml="true" exportOnSettingsChange="true" regenerateOnProjectOpen="false" linkFormatType="HTTP_ABSOLUTE" />
|
||||||
|
<LinkMapSettings>
|
||||||
|
<textMaps />
|
||||||
|
</LinkMapSettings>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+71
@@ -0,0 +1,71 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="FlexmarkProjectSettings">
|
||||||
|
<FlexmarkHtmlSettings flexmarkSpecExampleRendering="0" flexmarkSpecExampleRenderHtml="false">
|
||||||
|
<flexmarkSectionLanguages>
|
||||||
|
<option name="1" value="Markdown" />
|
||||||
|
<option name="2" value="HTML" />
|
||||||
|
<option name="3" value="flexmark-ast:1" />
|
||||||
|
</flexmarkSectionLanguages>
|
||||||
|
</FlexmarkHtmlSettings>
|
||||||
|
</component>
|
||||||
|
<component name="MarkdownProjectSettings">
|
||||||
|
<PreviewSettings splitEditorLayout="SPLIT" splitEditorPreview="PREVIEW" useGrayscaleRendering="false" zoomFactor="1.0" maxImageWidth="0" synchronizePreviewPosition="true" highlightPreviewType="LINE" highlightFadeOut="5" highlightOnTyping="true" synchronizeSourcePosition="true" verticallyAlignSourceAndPreviewSyncPosition="true" showSearchHighlightsInPreview="true" showSelectionInPreview="true" lastLayoutSetsDefault="false">
|
||||||
|
<PanelProvider>
|
||||||
|
<provider providerId="com.vladsch.md.nav.editor.swing.html.panel" providerName="Default - Swing" />
|
||||||
|
</PanelProvider>
|
||||||
|
</PreviewSettings>
|
||||||
|
<ParserSettings gitHubSyntaxChange="false" correctedInvalidSettings="false" emojiShortcuts="1" emojiImages="0">
|
||||||
|
<PegdownExtensions>
|
||||||
|
<option name="ATXHEADERSPACE" value="true" />
|
||||||
|
<option name="FENCED_CODE_BLOCKS" value="true" />
|
||||||
|
<option name="INTELLIJ_DUMMY_IDENTIFIER" value="true" />
|
||||||
|
<option name="RELAXEDHRULES" value="true" />
|
||||||
|
<option name="STRIKETHROUGH" value="true" />
|
||||||
|
<option name="TABLES" value="true" />
|
||||||
|
<option name="TASKLISTITEMS" value="true" />
|
||||||
|
</PegdownExtensions>
|
||||||
|
<ParserOptions>
|
||||||
|
<option name="COMMONMARK_LISTS" value="true" />
|
||||||
|
<option name="EMOJI_SHORTCUTS" value="true" />
|
||||||
|
<option name="GFM_TABLE_RENDERING" value="true" />
|
||||||
|
<option name="PRODUCTION_SPEC_PARSER" value="true" />
|
||||||
|
<option name="SIM_TOC_BLANK_LINE_SPACER" value="true" />
|
||||||
|
</ParserOptions>
|
||||||
|
</ParserSettings>
|
||||||
|
<HtmlSettings headerTopEnabled="false" headerBottomEnabled="false" bodyTopEnabled="false" bodyBottomEnabled="false" addPageHeader="false" addAnchorLinks="false" anchorLinksWrapText="false" imageUriSerials="false" addDocTypeHtml="true" noParaTags="false" defaultUrlTitle="false" migratedPlantUml="true" migratedAnchorLinks="true" plantUmlConversion="0">
|
||||||
|
<GeneratorProvider>
|
||||||
|
<provider providerId="com.vladsch.md.nav.editor.text.html.generator" providerName="Unmodified HTML Generator" />
|
||||||
|
</GeneratorProvider>
|
||||||
|
<headerTop />
|
||||||
|
<headerBottom />
|
||||||
|
<bodyTop />
|
||||||
|
<bodyBottom />
|
||||||
|
<fencedCodeConversions>
|
||||||
|
<option name="c4plantuml" value="NONE" />
|
||||||
|
<option name="ditaa" value="NONE" />
|
||||||
|
<option name="erd" value="NONE" />
|
||||||
|
<option name="graphviz" value="NONE" />
|
||||||
|
<option name="latex" value="KATEX" />
|
||||||
|
<option name="math" value="KATEX" />
|
||||||
|
<option name="mermaid" value="NONE" />
|
||||||
|
<option name="nomnoml" value="NONE" />
|
||||||
|
<option name="plantuml" value="NONE" />
|
||||||
|
<option name="puml" value="NONE" />
|
||||||
|
<option name="svgbob" value="NONE" />
|
||||||
|
<option name="umlet" value="NONE" />
|
||||||
|
<option name="vega" value="NONE" />
|
||||||
|
<option name="vegalite" value="NONE" />
|
||||||
|
<option name="wavedrom" value="NONE" />
|
||||||
|
</fencedCodeConversions>
|
||||||
|
</HtmlSettings>
|
||||||
|
<CssSettings previewScheme="UI_SCHEME" cssUri="" isCssUriEnabled="false" isCssUriSerial="true" isCssTextEnabled="false" isDynamicPageWidth="true">
|
||||||
|
<StylesheetProvider>
|
||||||
|
<provider providerId="com.vladsch.md.nav.editor.text.html.css" providerName="No Stylesheet" />
|
||||||
|
</StylesheetProvider>
|
||||||
|
<ScriptProviders />
|
||||||
|
<cssText />
|
||||||
|
<cssUriHistory />
|
||||||
|
</CssSettings>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
-1
@@ -2,6 +2,5 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="VcsDirectoryMappings">
|
<component name="VcsDirectoryMappings">
|
||||||
<mapping directory="" vcs="Git" />
|
<mapping directory="" vcs="Git" />
|
||||||
<mapping directory="$PROJECT_DIR$/external/cmake-various" vcs="Git" />
|
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
+1
-1
@@ -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})
|
add_executable(spicetools_spice64 ${SOURCE_FILES} ${RESOURCE_FILES})
|
||||||
target_link_libraries(spicetools_spice64
|
target_link_libraries(spicetools_spice64
|
||||||
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winscard
|
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 PREFIX "")
|
||||||
set_target_properties(spicetools_spice64 PROPERTIES OUTPUT_NAME "spice64")
|
set_target_properties(spicetools_spice64 PROPERTIES OUTPUT_NAME "spice64")
|
||||||
target_compile_definitions(spicetools_spice64 PRIVATE SPICE64=1)
|
target_compile_definitions(spicetools_spice64 PRIVATE SPICE64=1)
|
||||||
|
|||||||
@@ -1,111 +1,288 @@
|
|||||||
|
#include <winternl.h>
|
||||||
#include "pipewire.h"
|
#include "pipewire.h"
|
||||||
#include "hooks/audio/audio.h"
|
#include "hooks/audio/audio.h"
|
||||||
#include "hooks/audio/backends/wasapi/audio_client.h"
|
#include "hooks/audio/backends/wasapi/audio_client.h"
|
||||||
#include "util/libutils.h"
|
#include "util/libutils.h"
|
||||||
#include "launcher/launcher.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_;
|
return format_;
|
||||||
}
|
}
|
||||||
|
HRESULT PipewireBackend::on_get_stream_latency(REFERENCE_TIME *latency) noexcept
|
||||||
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");
|
log_info("audio::pipewire", "{}", __FUNCTION__);
|
||||||
|
if (client_) *latency = BmswClientWasapiPeriod(client_);
|
||||||
// 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());
|
|
||||||
|
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
HRESULT PipewireBackend::on_get_buffer_size(uint32_t *buffer_frames) noexcept {
|
HRESULT PipewireBackend::on_get_current_padding(std::optional<uint32_t> &padding_frames) noexcept
|
||||||
*buffer_frames = 0;
|
{
|
||||||
|
log_info("audio::pipewire", "{}", __FUNCTION__);
|
||||||
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<uint32_t> &padding_frames) noexcept {
|
|
||||||
|
|
||||||
padding_frames = 0;
|
padding_frames = 0;
|
||||||
|
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
HRESULT PipewireBackend::on_is_format_supported(
|
HRESULT PipewireBackend::on_get_mix_format(WAVEFORMATEX **pp_device_format) noexcept
|
||||||
AUDCLNT_SHAREMODE *ShareMode,
|
|
||||||
const WAVEFORMATEX *pFormat,
|
|
||||||
WAVEFORMATEX **ppClosestMatch) noexcept
|
|
||||||
{
|
{
|
||||||
// only accept 44.1 kHz, stereo, 16-bits per channel
|
log_info("audio::pipewire", "{}", __FUNCTION__);
|
||||||
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;
|
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"))
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,48 +4,30 @@
|
|||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
|
|
||||||
struct PipewireBackend final : AudioBackend {
|
struct PipewireBackend final : AudioBackend
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
|
HANDLE relay_handle_;
|
||||||
|
|
||||||
explicit PipewireBackend();
|
explicit PipewireBackend();
|
||||||
~PipewireBackend() final = default;
|
~PipewireBackend() final;
|
||||||
|
|
||||||
[[nodiscard]] const WAVEFORMATEXTENSIBLE &format() const noexcept override;
|
[[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_buffer_size(uint32_t *buffer_frames) noexcept override;
|
||||||
HRESULT on_get_stream_latency(REFERENCE_TIME *latency) 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_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_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_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_start() noexcept override;
|
||||||
HRESULT on_stop() noexcept override;
|
HRESULT on_stop() noexcept override;
|
||||||
HRESULT on_set_event_handle(HANDLE *event_handle) override;
|
HRESULT on_set_event_handle(HANDLE *event_handle) override;
|
||||||
|
|
||||||
HRESULT on_get_buffer(uint32_t num_frames_requested, BYTE **ppData) 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;
|
HRESULT on_release_buffer(uint32_t num_frames_written, DWORD dwFlags) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
const WAVEFORMATEXTENSIBLE &format_;
|
const WAVEFORMATEXTENSIBLE &format_;
|
||||||
std::thread internal_;
|
void *client_;
|
||||||
HMODULE bmsw_;
|
|
||||||
//BYTE *active_sound_buffer = nullptr;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user