// coop_tone: a minimal WASAPI render process that plays a continuous sine wave on // the default output endpoint. Used as a known audio source for the audio-mirror // integration test (a real process actively rendering audio to capture from). // // coop_tone [seconds] [frequencyHz] // // Default: runs ~3 s at 440 Hz. Prints "TONE_RENDERING" once audio is flowing so // a parent can synchronize before it starts capturing. #include #include #include #include #include #include #include #include #include namespace { constexpr double kPi = 3.14159265358979323846; template void release(T*& p) { if (p) { p->Release(); p = nullptr; } } } // namespace int wmain(int argc, wchar_t** argv) { const double seconds = (argc > 1) ? _wtof(argv[1]) : 3.0; const double freq = (argc > 2) ? _wtof(argv[2]) : 440.0; if (FAILED(CoInitializeEx(nullptr, COINIT_MULTITHREADED))) { std::fprintf(stderr, "CoInitializeEx failed\n"); return 1; } IMMDeviceEnumerator* enumerator = nullptr; IMMDevice* endpoint = nullptr; IAudioClient* client = nullptr; IAudioRenderClient* render = nullptr; WAVEFORMATEX* fmt = nullptr; int rc = 1; do { if (FAILED(CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), reinterpret_cast(&enumerator)))) { break; } if (FAILED(enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &endpoint))) { break; } if (FAILED(endpoint->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr, reinterpret_cast(&client)))) { break; } if (FAILED(client->GetMixFormat(&fmt))) { break; } HANDLE buffer_event = CreateEventW(nullptr, FALSE, FALSE, nullptr); constexpr REFERENCE_TIME kBuffer = 30 * 10000; // 30 ms if (FAILED(client->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, kBuffer, 0, fmt, nullptr))) { break; } client->SetEventHandle(buffer_event); if (FAILED(client->GetService(__uuidof(IAudioRenderClient), reinterpret_cast(&render)))) { break; } UINT32 buffer_frames = 0; client->GetBufferSize(&buffer_frames); const bool is_float = fmt->wFormatTag == WAVE_FORMAT_IEEE_FLOAT || (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE && reinterpret_cast(fmt)->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT); const unsigned channels = fmt->nChannels; const double rate = fmt->nSamplesPerSec; const double step = 2.0 * kPi * freq / rate; auto write_frames = [&](UINT32 frames, double& phase) { BYTE* data = nullptr; if (FAILED(render->GetBuffer(frames, &data))) { return; } for (UINT32 i = 0; i < frames; ++i) { const double s = std::sin(phase) * 0.25; // -12 dB, gentle phase += step; if (phase > 2.0 * kPi) { phase -= 2.0 * kPi; } for (unsigned c = 0; c < channels; ++c) { if (is_float) { reinterpret_cast(data)[i * channels + c] = static_cast(s); } else { reinterpret_cast(data)[i * channels + c] = static_cast(s * 32767.0); } } } render->ReleaseBuffer(frames, 0); }; double phase = 0.0; write_frames(buffer_frames, phase); // pre-roll client->Start(); std::printf("TONE_RENDERING pid=%lu %.0fHz %s %.0fHz %uch\n", GetCurrentProcessId(), freq, is_float ? "float" : "pcm16", rate, channels); std::fflush(stdout); const DWORD end_tick = GetTickCount() + static_cast(seconds * 1000.0); while (GetTickCount() < end_tick) { if (WaitForSingleObject(buffer_event, 200) != WAIT_OBJECT_0) { continue; } UINT32 padding = 0; if (FAILED(client->GetCurrentPadding(&padding))) { break; } write_frames(buffer_frames - padding, phase); } client->Stop(); CloseHandle(buffer_event); rc = 0; } while (false); release(render); release(client); release(endpoint); release(enumerator); if (fmt) { CoTaskMemFree(fmt); } CoUninitialize(); return rc; }