Phase 2: audio mirror via WASAPI process loopback
Mirror the real game's audio so Steam Remote Play Together (which streams the host's own audio session) carries it to the guest. The host captures the game by PID via WASAPI process loopback and re-renders it on the default endpoint; the game still plays locally too (accepted "double audio" for now). - ProcessLoopbackCapture: process-loopback capture client, frame-sink + stats. The completion handler must be agile (IAgileObject) or ActivateAudioInterfaceAsync rejects every call with E_ILLEGAL_METHOD_CALL. - AudioMirror: wraps capture with an event-driven render client and a primed ring buffer; AudioPanel drives it from the injected game's window/PID. - coop_tone: standalone WASAPI sine-wave process used as a known audio source. - audio_loopback_test (CTest): captures coop_tone by PID and asserts non-silent audio arrives, so the path is verifiable without a second Steam account. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
60
host/src/audio_panel.cpp
Normal file
60
host/src/audio_panel.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
#include "audio_panel.hpp"
|
||||
|
||||
#include "imgui.h"
|
||||
|
||||
namespace coop
|
||||
{
|
||||
|
||||
void AudioPanel::draw_ui()
|
||||
{
|
||||
const bool have_target = target_ != nullptr && IsWindow(target_);
|
||||
DWORD pid = 0;
|
||||
if (have_target)
|
||||
{
|
||||
GetWindowThreadProcessId(target_, &pid);
|
||||
}
|
||||
|
||||
ImGui::SetNextWindowPos(ImVec2(460, 300), ImGuiCond_FirstUseEver);
|
||||
ImGui::SetNextWindowSize(ImVec2(360, 0), ImGuiCond_FirstUseEver);
|
||||
ImGui::Begin("Audio mirror");
|
||||
|
||||
ImGui::BeginDisabled(!have_target);
|
||||
if (ImGui::Checkbox("Mirror game audio", &enabled_))
|
||||
{
|
||||
if (!enabled_)
|
||||
{
|
||||
mirror_.stop();
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
if (!have_target)
|
||||
{
|
||||
ImGui::TextDisabled("Inject into a game first (its audio is the source).");
|
||||
}
|
||||
|
||||
// Start when enabled and the target process changes; stop if it disappears.
|
||||
if (enabled_ && pid != 0 && mirror_.target_pid() != pid)
|
||||
{
|
||||
mirror_.start(pid);
|
||||
}
|
||||
else if (enabled_ && pid == 0 && mirror_.target_pid() != 0)
|
||||
{
|
||||
mirror_.stop();
|
||||
}
|
||||
|
||||
if (mirror_.running())
|
||||
{
|
||||
ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f), "Mirroring %u Hz, %u ch",
|
||||
mirror_.sample_rate(), mirror_.channels());
|
||||
}
|
||||
const std::string status = mirror_.status();
|
||||
if (!status.empty())
|
||||
{
|
||||
ImGui::TextWrapped("%s", status.c_str());
|
||||
}
|
||||
ImGui::TextDisabled("Game audio also plays locally (double audio).");
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
} // namespace coop
|
||||
Reference in New Issue
Block a user