Test: cover audio format variants in the mock-game stress suite
mock_game_test now launches coop_mock_game at 44100/48000/96000 (PCM + float) and asserts the hook measures each variant's rate through the full inject path (+ non-silent capture) -- the "all audio variants work" part of the suite. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
10
README.md
10
README.md
@@ -214,10 +214,12 @@ ctest --test-dir build -C Debug --output-on-failure
|
|||||||
hook's shared video texture, and **decodes the frame number out of the captured pixels**
|
hook's shared video texture, and **decodes the frame number out of the captured pixels**
|
||||||
to assert the mirror sees a *monotonic, advancing* sequence for both backends (the bar
|
to assert the mirror sees a *monotonic, advancing* sequence for both backends (the bar
|
||||||
for no dropped / stale / out-of-order frames — what the DX12 rotating-backbuffer bug
|
for no dropped / stale / out-of-order frames — what the DX12 rotating-backbuffer bug
|
||||||
broke). It then injects with audio + video, checks both stream, cycles the audio
|
broke). It launches the game at several **audio formats** (44100/48000/96000, PCM +
|
||||||
subsystem off/on (hook/unhook stress), and confirms the game never crashes and capture
|
float) and asserts the hook measures each one's rate through the full inject path, then
|
||||||
resumes. This suite drove out five real audio races (see Lessons learned). Skips cleanly
|
injects with audio + video, checks both stream, cycles the audio subsystem off/on
|
||||||
without a D3D11 device.
|
(hook/unhook stress), and confirms the game never crashes and capture resumes. This
|
||||||
|
suite drove out five real audio races (see Lessons learned). Skips cleanly without a
|
||||||
|
D3D11 device.
|
||||||
|
|
||||||
### Debugging the hooks against a real game
|
### Debugging the hooks against a real game
|
||||||
|
|
||||||
|
|||||||
@@ -295,6 +295,93 @@ void test_video_capture(const char* backend, ID3D11Device* device)
|
|||||||
game.kill();
|
game.kill();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Launch the mock game rendering audio at `rate`/`channels`/`bits`/`fmt`, inject the audio
|
||||||
|
// hook (late, so it's the guessed path), and verify the hook MEASURES the right sample rate
|
||||||
|
// for this variant and captures non-silent audio. (Channels/bit-depth aren't recoverable for
|
||||||
|
// a guessed stream -- that's the documented limitation -- so the rate is the variant check.)
|
||||||
|
void test_audio_variant(unsigned rate, unsigned channels, unsigned bits, const wchar_t* fmt)
|
||||||
|
{
|
||||||
|
wchar_t args[64];
|
||||||
|
swprintf(args, static_cast<int>(std::size(args)), L"dx11 30 %u %u %u %ls", rate, channels, bits, fmt);
|
||||||
|
std::printf("== audio variant: %u Hz %u ch %u-bit %ls ==\n", rate, channels, bits, fmt);
|
||||||
|
MockGame game = MockGame::launch(args);
|
||||||
|
if (!game.ok)
|
||||||
|
{
|
||||||
|
check(false, "launch coop_mock_game (audio variant)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Sleep(800);
|
||||||
|
|
||||||
|
// Audio subsystem only.
|
||||||
|
SharedMemory shm;
|
||||||
|
const std::uint32_t disabled = (1u << HookSubsys_Input) | (1u << HookSubsys_Focus) |
|
||||||
|
(1u << HookSubsys_Video) | (1u << HookSubsys_Mkb);
|
||||||
|
SharedBlock* block = make_ipc(shm, game.pid(), disabled);
|
||||||
|
SharedMemory ring_shm;
|
||||||
|
AudioRingHeader* ring = nullptr;
|
||||||
|
if (ring_shm.create(audio_ring_name(game.pid()), audio_ring_total_size(kAudioRingCapacity)))
|
||||||
|
{
|
||||||
|
ring = ring_shm.as<AudioRingHeader>();
|
||||||
|
audio_ring_init(*ring, kAudioRingCapacity);
|
||||||
|
ring->capture_enabled.store(1, std::memory_order_release);
|
||||||
|
}
|
||||||
|
if (block == nullptr || ring == nullptr || !inject_retry(game.pid()))
|
||||||
|
{
|
||||||
|
check(false, "inject into mock game (audio variant)");
|
||||||
|
game.kill();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the rate to be measured + published (Measured / LowConfidence / Exact), then
|
||||||
|
// drain to prove non-silent capture.
|
||||||
|
std::vector<std::uint8_t> drain(kAudioRingCapacity);
|
||||||
|
std::uint32_t measured = 0;
|
||||||
|
std::uint32_t state = 0;
|
||||||
|
double peak = 0.0;
|
||||||
|
for (int i = 0; i < 120 && game.alive(); ++i) // up to ~6 s
|
||||||
|
{
|
||||||
|
Sleep(50);
|
||||||
|
std::uint32_t got = 0;
|
||||||
|
while ((got = audio_ring_pop(*ring, drain.data(), static_cast<std::uint32_t>(drain.size()))) > 0)
|
||||||
|
{
|
||||||
|
if (ring->bits == 32 && ring->format_tag == 3)
|
||||||
|
{
|
||||||
|
const auto* f = reinterpret_cast<const float*>(drain.data());
|
||||||
|
for (std::uint32_t k = 0; k < got / 4; ++k)
|
||||||
|
{
|
||||||
|
peak = std::max(peak, static_cast<double>(std::fabs(f[k])));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (ring->bits == 16)
|
||||||
|
{
|
||||||
|
const auto* s = reinterpret_cast<const std::int16_t*>(drain.data());
|
||||||
|
for (std::uint32_t k = 0; k < got / 2; ++k)
|
||||||
|
{
|
||||||
|
peak = std::max(peak, std::abs(s[k]) / 32768.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (got < drain.size())
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state = block->status.audio_streams[0].format_state;
|
||||||
|
if (state == AudioFormat_Measured || state == AudioFormat_LowConfidence || state == AudioFormat_Exact)
|
||||||
|
{
|
||||||
|
measured = block->status.audio_streams[0].sample_rate;
|
||||||
|
if (measured != 0 && peak > 0.01)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::printf(" measured rate=%u Hz (state=%u) peak=%.4f\n", measured, state, peak);
|
||||||
|
check(measured == rate, "hook measured this variant's sample rate");
|
||||||
|
check(peak > 0.01, "captured non-silent audio for this variant");
|
||||||
|
|
||||||
|
game.kill();
|
||||||
|
}
|
||||||
|
|
||||||
// Inject with audio + video, verify both stream, then cycle the audio subsystem off/on a
|
// Inject with audio + video, verify both stream, then cycle the audio subsystem off/on a
|
||||||
// few times (hook/unhook stress) and confirm the game stays alive (heartbeat advances).
|
// few times (hook/unhook stress) and confirm the game stays alive (heartbeat advances).
|
||||||
void test_av_and_hook_cycles(ID3D11Device* device)
|
void test_av_and_hook_cycles(ID3D11Device* device)
|
||||||
@@ -432,6 +519,12 @@ int main()
|
|||||||
|
|
||||||
test_video_capture("dx11", device);
|
test_video_capture("dx11", device);
|
||||||
test_video_capture("dx12", device);
|
test_video_capture("dx12", device);
|
||||||
|
|
||||||
|
// Audio variants: the hook must measure each variant's rate through the full inject path.
|
||||||
|
test_audio_variant(44100, 2, 16, L"pcm");
|
||||||
|
test_audio_variant(48000, 2, 32, L"float");
|
||||||
|
test_audio_variant(96000, 2, 32, L"float");
|
||||||
|
|
||||||
test_av_and_hook_cycles(device);
|
test_av_and_hook_cycles(device);
|
||||||
|
|
||||||
device->Release();
|
device->Release();
|
||||||
|
|||||||
Reference in New Issue
Block a user