Fix audio falling back to echo in the full app (format not published)
In the full app the host creates the audio ring only when the operator toggles audio mirroring on -- after injection. So the hook registers the game's primary render stream while the ring is still null, and register_render_client_locked skips publishing the format (nothing to publish to). When the ring later attaches via set_audio_ring, the already-registered stream's format was never re-published: format_valid stayed 0, the host's wait_for_format timed out, and it fell back to loopback (the echo) -- on every game, including Phantom Brave. The in-process probe created the ring before injecting, so it never reproduced this. Fix: the hook stores the primary stream's format and republish_audio_format() publishes it whenever a ring is attached but has no format yet -- called from set_audio_ring and once per worker tick (the tick also covers the host re-initializing the ring on a mirror re-toggle, which clears format_valid). coop_audio_probe now creates the ring ~1.5 s AFTER injecting by default (ring_delay_ms arg) to match the app's ordering. Verified against Phantom Brave: the log shows "primary stream set ... no ring yet" at inject, then "republish_audio_format: published 48000Hz/2ch/32bit" when the ring attaches, and the host-shaped consumer then drains real audio with zero overruns. All four tests still pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -89,11 +89,15 @@ int wmain(int argc, wchar_t** argv)
|
||||
{
|
||||
if (argc < 2)
|
||||
{
|
||||
std::printf("usage: coop_audio_probe <pid> [seconds]\n");
|
||||
std::printf("usage: coop_audio_probe <pid> [seconds] [ring_delay_ms]\n"
|
||||
" ring_delay_ms: how long after injecting to create the audio ring\n"
|
||||
" (default 1500 = reproduces the real app, which creates the ring\n"
|
||||
" only when audio mirroring is toggled on; 0 = ring before inject).\n");
|
||||
return 1;
|
||||
}
|
||||
const unsigned long pid = std::wcstoul(argv[1], nullptr, 10);
|
||||
const int seconds = (argc >= 3) ? std::max(1, _wtoi(argv[2])) : 20;
|
||||
const int ring_delay_ms = (argc >= 4) ? std::max(0, _wtoi(argv[3])) : 1500;
|
||||
if (pid == 0)
|
||||
{
|
||||
std::printf("ERROR: invalid pid.\n");
|
||||
@@ -113,17 +117,6 @@ int wmain(int argc, wchar_t** argv)
|
||||
block->sequence.store(0, std::memory_order_relaxed);
|
||||
block->magic = coop::kProtocolMagic;
|
||||
|
||||
// 2) Audio ring, capture enabled (mirrors AudioMirror::thread_main).
|
||||
coop::SharedMemory ring_shm;
|
||||
if (!ring_shm.create(coop::audio_ring_name(pid), coop::audio_ring_total_size(coop::kAudioRingCapacity)))
|
||||
{
|
||||
std::printf("ERROR: create audio ring mapping failed (%lu).\n", GetLastError());
|
||||
return 1;
|
||||
}
|
||||
auto* ring = ring_shm.as<coop::AudioRingHeader>();
|
||||
coop::audio_ring_init(*ring, coop::kAudioRingCapacity);
|
||||
ring->capture_enabled.store(1, std::memory_order_release);
|
||||
|
||||
// Enable the hook's file trace (%TEMP%\coop_hook.log) for this debug session.
|
||||
{
|
||||
wchar_t dir[MAX_PATH] = {};
|
||||
@@ -139,15 +132,49 @@ int wmain(int argc, wchar_t** argv)
|
||||
}
|
||||
}
|
||||
|
||||
// 3) Inject.
|
||||
// Create the audio ring (capture enabled), mirroring AudioMirror::thread_main.
|
||||
// By default we do this *after* injecting so the ordering matches the real app
|
||||
// (the host creates the ring only when audio mirroring is toggled on, which is
|
||||
// after the hook has already been injected and the game's stream registered).
|
||||
coop::SharedMemory ring_shm;
|
||||
coop::AudioRingHeader* ring = nullptr;
|
||||
auto create_ring = [&]() -> bool {
|
||||
if (!ring_shm.create(coop::audio_ring_name(pid),
|
||||
coop::audio_ring_total_size(coop::kAudioRingCapacity)))
|
||||
{
|
||||
std::printf("ERROR: create audio ring mapping failed (%lu).\n", GetLastError());
|
||||
return false;
|
||||
}
|
||||
ring = ring_shm.as<coop::AudioRingHeader>();
|
||||
coop::audio_ring_init(*ring, coop::kAudioRingCapacity);
|
||||
ring->capture_enabled.store(1, std::memory_order_release);
|
||||
return true;
|
||||
};
|
||||
|
||||
if (ring_delay_ms == 0 && !create_ring())
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Inject.
|
||||
std::printf("Injecting coop_hook.dll into pid %lu ...\n", pid);
|
||||
if (!inject(pid, dll_path_next_to_self()))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
std::printf("Injected. Polling for %d s. Hook trace: %%TEMP%%\\coop_hook.log\n\n", seconds);
|
||||
|
||||
// 4) Poll + print. Drain the ring like the real host would (so it doesn't
|
||||
if (ring_delay_ms > 0)
|
||||
{
|
||||
std::printf("Injected. Creating audio ring %d ms later (app-ordering)...\n", ring_delay_ms);
|
||||
Sleep(static_cast<DWORD>(ring_delay_ms));
|
||||
if (!create_ring())
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
std::printf("Polling for %d s. Hook trace: %%TEMP%%\\coop_hook.log\n\n", seconds);
|
||||
|
||||
// Poll + print. Drain the ring like the real host would (so it doesn't
|
||||
// overrun) and measure peak amplitude to prove we captured real audio.
|
||||
const coop::HookStatus& status = block->status;
|
||||
std::uint64_t prev_frames[coop::kMaxAudioStreams] = {};
|
||||
|
||||
Reference in New Issue
Block a user