DEV Community

Cover image for Microsoft’s MAI-Transcribe-2-Streaming and the 100 ms WASAPI Capture Budget
devs
devs

Posted on

Microsoft’s MAI-Transcribe-2-Streaming and the 100 ms WASAPI Capture Budget

On 1 October 2026 Microsoft shipped MAI-Transcribe-2-Streaming, its first real-time speech-to-text model. It returns the first partial hypotheses in just over 100 ms of receiving audio, finalizes with roughly 0.13 s of post-speech latency, and posts a 2.5 % word-error rate on the Artificial Analysis streaming leaderboard—ahead of the previous Grok Voice Transcribe 2.0 streaming entry. The model covers 60 languages with continuous automatic language detection and is priced at an introductory $0.54 per audio hour through the end of 2026. It is available through Microsoft Foundry, an OpenAI Realtime-compatible WebSocket endpoint, and the Azure Speech SDK.

That number matters because the Windows capture path has its own fixed costs. Once you add device period, shared-mode mixing, and the round-trip to a remote endpoint, the theoretical 100 ms partial can easily become 250–400 ms of end-to-end delay. For a live interview or call transcription overlay the difference between “words appear while the speaker is still finishing the clause” and “words appear after the sentence is already over” is the difference between useful and decorative.

The capture budget on Windows

WASAPI (Windows Audio Session API) is the modern path. Shared mode goes through the audio engine (audiodg.exe / audioeng.dll), which mixes streams, applies APOs (audio processing objects), and presents a single format to the driver. Exclusive mode bypasses the engine and talks closer to the endpoint buffer, but only one client can hold the device and you lose system sounds and other applications.

Typical shared-mode periods on modern hardware are 10 ms. You can request a smaller period if the driver supports it; many USB and built-in devices will accept 5 ms or even 3 ms under the low-latency path introduced in Windows 10. The application still has to fill or drain that buffer on schedule. Miss a period and you get a glitch; the engine does not magically stretch time for you.

A minimal capture loop looks roughly like this (error handling stripped for clarity):

IMMDeviceEnumerator* enumerator = nullptr;
CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL,
                 __uuidof(IMMDeviceEnumerator), (void**)&enumerator);

IMMDevice* device = nullptr;
enumerator->GetDefaultAudioEndpoint(eCapture, eConsole, &device);

IAudioClient* client = nullptr;
device->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr, (void**)&client);

WAVEFORMATEX* format = nullptr;
client->GetMixFormat(&format); // usually 48 kHz, 32-bit float, stereo

REFERENCE_TIME period = 0;
client->GetDevicePeriod(nullptr, &period); // 100-ns units

client->Initialize(AUDCLNT_SHAREMODE_SHARED,
                   AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
                   period, 0, format, nullptr);

IAudioCaptureClient* capture = nullptr;
client->GetService(__uuidof(IAudioCaptureClient), (void**)&capture);

HANDLE event = CreateEvent(nullptr, FALSE, FALSE, nullptr);
client->SetEventHandle(event);
client->Start();

while (running) {
    WaitForSingleObject(event, INFINITE);
    UINT32 packetSize = 0;
    capture->GetNextPacketSize(&packetSize);
    while (packetSize > 0) {
        BYTE* data;
        UINT32 frames;
        DWORD flags;
        capture->GetBuffer(&data, &frames, &flags, nullptr, nullptr);
        // push frames to STT ring buffer (float32 or convert to 16 kHz mono PCM)
        capture->ReleaseBuffer(frames);
        capture->GetNextPacketSize(&packetSize);
    }
}
Enter fullscreen mode Exit fullscreen mode

The event-driven path keeps you out of a busy loop. You still need a lock-free ring buffer between the audio thread and the network/STT thread; a mutex here will eventually cause a glitch under load.

Where the 100 ms disappears

  • Device period: 5–10 ms
  • Audio engine mixing + APO chain: another few milliseconds in shared mode
  • Resampling to the 16 kHz or 24 kHz mono PCM that most streaming STT endpoints expect: 1–2 ms if you do it on the audio thread, more if you offload it poorly
  • Network RTT to the Foundry or OpenAI-compatible endpoint: 20–80 ms depending on geography and congestion
  • Model first-partial time: ~100–120 ms after the server receives the audio packet
  • Client-side rendering of the partial into an overlay: another few milliseconds

Add them up and a well-tuned path can still land the first useful text under 200 ms from the moment the speaker’s mouth moved. A poorly tuned path (default 10 ms period, blocking resample, HTTP instead of persistent WebSocket, UI thread doing the work) lands at 400 ms or worse. At that point the partials no longer give you an advantage over waiting for a final result.

Tradeoffs that actually show up in shipping code

Shared mode is usually the right default for an overlay that must coexist with the call software (Teams, Zoom, browser, etc.). Exclusive mode gives you lower latency and a deterministic format, but you will fight every other application that wants the microphone. For a tool that sits beside an interview or a support call, shared mode plus a small period is the practical compromise.

You also have to decide how aggressive the partials are. Microsoft’s model emits evolving hypotheses. If you paint every partial into the overlay you get a flickering UI. Most usable implementations keep a short sliding window and only commit a word once it has been stable for two or three partials, or once a silence gap appears. That adds a few dozen milliseconds but keeps the displayed text readable.

Language detection is continuous, which is useful when a speaker code-switches, but it is not free. The model can flip languages mid-utterance if the acoustic evidence is ambiguous. For a known interview language pair you may still want to pin the language ID and only fall back to auto-detect on long silence.

Local on-device options (Windows AI Speech Recognition API preview, Whisper.cpp, Ollama with audio models, etc.) avoid the network hop entirely. Their first-token latency is often higher than Microsoft’s streaming partials on a good connection, and the accuracy gap on noisy call audio is still visible. Hybrid setups—local for the first rough partial, remote for the final or for the harder languages—are appearing in production tools, but they add complexity in the buffer management layer.

Practical pipeline for live call overlays

  1. Capture at the mix format (usually 48 kHz float32).
  2. Downmix and resample to 16 kHz mono 16-bit PCM on the audio thread or a dedicated high-priority thread. Use a small fixed-size FIR or the Windows Media Foundation resampler if you want to stay in the Microsoft stack.
  3. Packetize into 100–200 ms chunks (or smaller if the endpoint accepts them) and push over a persistent WebSocket.
  4. On each partial, update a lock-free structure that the UI thread reads.
  5. On final, optionally run a second pass (punctuation, translation, or a small local LLM) if the latency budget still has room.
  6. Keep the audio thread at THREAD_PRIORITY_TIME_CRITICAL or use the Real-Time Work Queue API so Windows does not starve it when the system is under load.

The same pattern works whether the downstream consumer is a cloud streaming model or a local one. The hard part is never the model; it is keeping the capture side from introducing jitter and keeping the UI from blocking.

Windows audio stack simplified

(If the diagram above is unavailable, the canonical description lives in the Windows driver docs under “Windows Audio Architecture” and “Low-latency audio”.)

Microsoft’s 100 ms partial is real, but only if the rest of the pipeline does not eat the budget. On Windows that means treating WASAPI period, shared-mode overhead, resampling, and the WebSocket path as first-class latency contributors instead of afterthoughts. The teams shipping usable live transcription overlays already do this accounting; everyone else is still waiting for the sentence to finish.

I'm building Talkaside, a Windows app for job interviews and calls in another language.
Drafted with AI assistance from this project's code and logs.

Top comments (0)