Skip to content

GPU HDMI audio vs a USB DAC - measuring output latency through an HDMI audio extractor

The USB audio latency guide ended on an open question: would a non-USB output beat the D70 over USB? This page answers it for the one non-USB output I had, the GPU’s HD Audio over HDMI, by putting an HDMI audio extractor between the GPU and the D70’s optical input so the same DAC and the same recorder measure both paths. At the default shared-mode period, which is what most games get, the GPU path is 3.3-4.6 ms faster whichever driver it runs on. I had set a 5 ms bar before measuring, so for games it’s a tie and the D70 stays on USB. The bigger difference is the driver’s smallest period. On NVIDIA’s driver the GPU path can’t go below 10 ms and the D70 wins at small buffers. Moved to Microsoft’s inbox HD Audio driver, the same GPU output gets 20.28-20.55 ms for an app that asks for small buffers, against 35.75-35.76 ms for the D70 at its minimum in the same session. That is where the community’s claim that onboard audio is much faster than USB comes from (Part 6).

You’ll need the loopback setup and audio-loopback.ps1 from the USB guide, an HDMI audio extractor with a 2-channel EDID setting, a Toslink cable, and a DAC with an optical input. Everything was measured on one machine on 2026-11-03.

PieceDetail
OSWindows 10 22H2 (build 19045), Enterprise edition during the test
GPUNVIDIA GeForce RTX 5090, driver 617.42; primary display 2560x1440 at 360 Hz over DisplayPort
ExtractorJ-Tech JTD18G-H5CH (HDMI 2.0b, optical out up to LPCM 2CH or Dolby Digital/DTS 5.1, EDID switch TV / 5ch / 2ch1) on a spare HDMI port of the GPU, EDID switch on 2ch, HDMI Out empty, optical out into the D70
DACTopping D70 Pro Sabre on Microsoft’s USB Audio 2.0 class driver (usbaudio2), USB and optical inputs
RecorderSSL 2, buffer 128 samples: Input 2 fed from the D70’s left XLR output, Input 1 from the extractor’s 3.5 mm output (left channel)
USBD70 and SSL 2 behind a VIA VL822 hub (2109:0822) on the chipset controller 1022:43F7 (not USB4)
Format48 kHz, 2 channels, WASAPI shared mode; the extractor’s endpoint at 16-bit (Part 4 explains why), the D70’s at 32-bit
InterruptsUSB controller 1022:43F7 (D70 and SSL 2) pinned to CPU 4, the GPU to CPU 2, the GPU’s HD Audio controller (10DE:22E8, line interrupt) on the default

Windows names the extractor after its EDID. Read with IAudioClient3::GetSharedModeEnginePeriod, as in the USB guide:

EndpointDriverDefaultFundamentalMinimum
Speakers (D70 Pro SABRE)usbaudio2480 frames (10 ms)1336 frames (7 ms)
HDMI SPLITTER (NVIDIA High Definition Audio)NVIDIA HD Audio480 frames (10 ms)480480 frames (10 ms)
HDMI SPLITTER (High Definition Audio Device), after Part 4Microsoft inbox HD Audio (hdaudio.inf)480 frames (10 ms)32128 frames (2.67 ms)

The USB guide’s period table showed 10 ms everywhere because it was measured with the D70 on Topping’s driver; back on usbaudio2 the 7 ms minimum is there again. Chromium reads each endpoint’s minimum with GetSharedModeEnginePeriod and, when a smaller buffer is requested, opens the stream with IAudioClient3::InitializeSharedAudioStream2 at that period3; most games take the default. SDL added the same call to its WASAPI backend in May 20244, so games built on recent SDL can get the smaller period too.

The D70 is the DAC on both paths and the SSL 2’s Input 2 records both, so the analog stage, the cable and the recorder’s delay are common to both and cancel out of the difference. What changes is the Windows render path (the USB class driver against NVIDIA’s HD Audio driver), the transport (USB against HDMI, the extractor and Toslink) and the D70’s input receiver. The extractor’s delay is inside the GPU number. Input 1 records the extractor’s own 3.5 mm output, which takes the D70’s optical input out of the GPU path for comparison.

audio-loopback.ps1(WASAPI shared)usbaudio2(D70 endpoint)path ANVIDIA HD Audio(HDMI SPLITTER endpoint)path BD70 Pro Sabre(input USB or OPT)USBHDMI audio extractor(EDID 2ch)HDMIToslinkSSL 2Input 2 / Input 13.5 mm (Input 1)XLR (Input 2)USB, packet QPC stamp

Text version: path A plays the click through the D70’s USB endpoint; path B plays it through the GPU’s HDMI endpoint, the extractor strips the audio onto Toslink, and the D70 takes it on its optical input. Either way the D70’s XLR output goes into SSL 2 Input 2, and the script times the click from release to capture. The dashed line is the extractor’s own analog output into Input 1, used to check the D70’s optical input.

  1. Set the EDID switch to 2ch before connecting. In TV mode the extractor passes on the EDID of whatever is on its HDMI Out, and with nothing there the GPU gets no EDID and Windows sees no display.
  2. Power the extractor (5 V barrel; HDMI does not power it), connect the GPU to HDMI In and the optical out to the DAC, leave HDMI Out empty.
  3. If no display appears, unplug and replug the HDMI cable at the extractor to force a hotplug. Mine showed up after that as HDMI SPLITTER (manufacturer code HDC) at 1920x1080 at 60 Hz.
  4. Extend the desktop to it (Win+P, Extend). The audio endpoint HDMI SPLITTER (NVIDIA High Definition Audio) exists only while that display is part of the desktop.
  5. Play something to the new endpoint, switch the DAC to its optical input and check it locks and plays.

I wrote the pass rule down before the extractor arrived, so the numbers could not shape it:

  • Only the default period counts, because that is what games get.
  • The GPU path wins only if its median is at least 5 ms lower than the USB path’s and its p75 sits below the USB path’s p25. Anything less is a tie, and a tie means staying on USB, which has no extra display to live with (Part 7).
  • Runs alternate A, B, A, B so drift over the session lands on both paths.

5 ms was my judgement of a gap worth an extra display or a motherboard change, not a perceptual threshold.

With the USB guide’s script, switching the DAC’s input before each block:

Terminal window
# D70 input on USB
.\audio-loopback.ps1 -Render 'D70' -Capture 'Input 2 (SSL 2' -Clicks 50
.\audio-loopback.ps1 -Render 'D70' -Capture 'Input 2 (SSL 2' -Clicks 50 -MinPeriod
# D70 input on OPT
.\audio-loopback.ps1 -Render 'HDMI SPLITTER' -Capture 'Input 2 (SSL 2' -Clicks 50
.\audio-loopback.ps1 -Render 'HDMI SPLITTER' -Capture 'Input 2 (SSL 2' -Clicks 50 -MinPeriod

Set both endpoints to 100 % in Windows; the optical level is fixed digital, so the DAC’s volume sets the analog level on both paths. Watch the capture peak: one of my runs clipped at 1.0 and I re-ran it. Every run I kept peaked at 0.817.

Two sessions 40 minutes apart, two rounds each, 50 clicks per run, every click found. The first used the PowerShell script; the second a Go reimplementation of the same method, which also checks that the reimplementation measures the same thing. Medians in ms, p25-p75 in brackets:

Path, periodSession 1, round 1Session 1, round 2Session 2, round 1Session 2, round 2
USB, default47.58 (47.32-47.86)48.24 (47.97-48.52)48.13 (47.86-48.39)48.40 (48.10-48.64)
USB, minimum (7 ms)35.09 (34.83-35.33)35.19 (34.92-35.46)35.54 (35.27-35.78)35.77 (35.48-36.03)
GPU, default44.27 (44.13-44.38)43.79 (43.64-43.90)43.73 (43.57-43.86)44.43 (44.29-44.56)
GPU, minimum (10 ms)44.39 (44.25-44.53)44.27 (44.10-44.39)43.75 (43.59-43.88)44.42 (44.28-44.55)

The script also splits each click at the moment the render stream’s IAudioClock says it is playing:

Path, periodApp -> driver says playing-> captured
USB, default42.96-42.97 ms4.63-5.42 ms
USB, minimum30.34-30.35 ms4.75-5.41 ms
GPU, default and minimum31.96-31.99 ms11.75-12.46 ms

Per round, the GPU path at the default period came in 3.31, 4.45, 4.40 and 3.97 ms ahead. Both scripts’ verdicts were TIE: 3.79 ms in session 1, 4.40 ms in session 2, each comparing the lower of the two rounds’ medians on each side.

  • At the default period the distributions do not overlap (every GPU p75 is below every USB p25), so the GPU path is faster, by less than the bar.
  • At the minimum period the order flips on NVIDIA’s driver. The D70 drops 12.49-13.05 ms per round to 35.09-35.77 ms; the GPU path cannot move, because NVIDIA’s driver offers one period. An app that asks for small buffers is 8.2-9.3 ms better off on USB. Part 4 changes that by changing the driver.
  • The split differs by driver: NVIDIA reports a click playing 11 ms sooner than usbaudio2 does, then 7 ms more passes before it is recorded. Only the totals compare across drivers, as in the USB guide. To see whether the D70’s optical input accounts for that 7 ms, I looped the extractor’s own 3.5 mm output into Input 1: 43.75 and 43.78 ms at the default period on NVIDIA’s driver, against 43.73-44.43 ms through the D70, and the two outputs stay within a millisecond or two of each other on both drivers in Part 4. So the D70’s optical receiver and DAC add nothing I could measure over the extractor’s own DAC, and the extra time sits upstream of both: the driver, the HDMI link or the extractor’s HDMI receiver.
  • The session 2 per-condition means moved -0.25 to +0.52 ms against session 1, and no round differed by more than 0.64 ms, so the reimplementation reproduces the script.
  • One asymmetry I have not removed: the NVIDIA endpoint carries Microsoft’s default enhancement APOs (WMALFXGFXDSP.dll, LFX and GFX), the D70 endpoint none. I found no measurement of what they cost with no enhancement selected. If they cost more than 1.21 ms (the 5 ms bar minus the 3.79 ms verdict gap), the GPU path would clear the bar. The re-test is “Disable all enhancements” on the extractor’s endpoint and one more GPU run.

Part 4: swap the GPU’s HDMI audio to Microsoft’s inbox driver

Section titled “Part 4: swap the GPU’s HDMI audio to Microsoft’s inbox driver”

Microsoft’s inbox HD Audio driver supports small buffers for apps that ask5, and it installs on the GPU’s HDMI audio function as well as on an onboard codec. With the extractor’s display attached:

  1. Device Manager > Sound, video and game controllers > NVIDIA High Definition Audio (the GPU’s) > Update driver > Browse my computer for drivers > Let me pick from a list of available drivers on my computer > High Definition Audio Device > Next, and accept the compatibility warning. Mine asked for a restart.
  2. The swap reset the extractor’s endpoint to 44.1 kHz. Set it back under Sound control panel > Playback > HDMI SPLITTER > Properties > Advanced. Use 16-bit, 48000 Hz: at 24-bit the extractor’s 3.5 mm output went silent (capture peak 0.0003) while its optical output kept working, and 16-bit brought it back.
  3. Check the periods with GetSharedModeEnginePeriod: the endpoint should report a 128-frame minimum, as in the endpoint table above.
  4. To go back: Properties > Driver > Roll Back Driver. A GeForce driver install also puts NVIDIA’s driver back.

Then the full set again in one sitting, with Discord, Telegram, the browser and a local model closed: the inbox driver first, then the D70 over USB, then NVIDIA’s driver after the rollback and a restart. Two rounds per condition, 50 clicks per run, every click found. Medians in ms:

PathDefault (480 frames)Minimum period
GPU, inbox driver, optical into the D7043.77 / 43.7920.28 / 20.33 (128 frames)
GPU, inbox driver, extractor 3.5 mm42.20 / 44.0220.50 / 20.55 (128 frames)
D70 over USB48.39 / 48.3635.75 / 35.76 (336 frames)
GPU, NVIDIA driver, optical into the D7044.38 / 44.3244.33 / 44.33 (480 frames, the only period)
GPU, NVIDIA driver, extractor 3.5 mm44.34 / 44.3544.36 / 44.12 (480 frames)
Path, periodApp -> driver says playing-> captured
GPU, inbox driver, default32.97-32.98 ms9.21-11.05 ms
GPU, inbox driver, minimum9.49-9.50 ms10.79-11.05 ms
GPU, NVIDIA driver, both33.18-33.25 ms10.88-11.20 ms
D70 over USB, default42.97 ms5.38-5.42 ms
D70 over USB, minimum30.35 ms5.42 ms
  • At the default period the driver makes no difference: the GPU path is 4.59 ms ahead of the D70 on the inbox driver and 4.02 ms on NVIDIA’s (optical, mean of the two rounds). Still a tie for games.
  • At the minimum period the inbox driver’s 128 frames take the GPU path to 20.28-20.55 ms: 15.2-15.5 ms faster than the D70 at its minimum, 28.07 ms faster than the D70 at the default, and 24.05 ms faster than the same output on NVIDIA’s driver.
  • All of that is in the first column. Windows keeps a few periods queued ahead of the driver, so a smaller period saves a multiple of its own length: from how each total moved with the period, about 3.2 periods on the HD Audio path ((32.98 - 9.49) / (10 - 2.67)) and 4.2 on usbaudio2 ((42.97 - 30.35) / (10 - 7)). After “playing”, the USB path is the quicker one, 5.4 ms against about 11 ms.
  • Two earlier inbox-driver sets, before the format fix: at 44.1 kHz the minimum-period runs gave 21.74-22.62 ms, and at 24-bit 48 kHz the optical runs gave 18.80 and 20.39 ms. The 16-bit set above is the one to compare.
  • Closing the apps changed nothing I can see: the D70’s default runs (48.36 and 48.39 ms) sit inside the morning’s 47.58-48.40 ms with them open.

Part 5: hold the small period for every app

Section titled “Part 5: hold the small period for every app”

Microsoft’s low-latency page says that once one app requests small buffers, “all applications that use the same endpoint and mode will automatically switch to that small buffer size”, until it exits5. REAL6 uses this: it opens a stream at the minimum period and keeps it open. The same thing in PowerShell, playing nothing:

Terminal window
<#
hold-min-period.ps1 - open a WASAPI shared stream at the endpoint's minimum period and hold it,
the trick REAL (https://github.com/miniant-git/REAL) uses. Microsoft: once one app uses small
buffers, all apps on that endpoint and mode switch to that buffer size until it exits
(https://learn.microsoft.com/en-us/windows-hardware/drivers/audio/low-latency-audio, FAQ).
No admin. Plays nothing (silence). Usage: .\hold-min-period.ps1 -Render 'HDMI SPLITTER' -Seconds 120
#>
param([Parameter(Mandatory)][string]$Render, [int]$Seconds = 120)
$ErrorActionPreference = 'Stop'
Add-Type -TypeDefinition @"
using System; using System.Runtime.InteropServices; using System.Threading;
[ComImport, Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")] class MMDE {}
[Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] interface IMMDeviceEnumerator { int EnumAudioEndpoints(int f, int s, out IMMDeviceCollection c); }
[Guid("0BD7A1BE-7A1A-44DB-8397-CC5392387B5E"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] interface IMMDeviceCollection { int GetCount(out int n); int Item(int i, out IMMDevice d); }
[Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] interface IMMDevice { int Activate(ref Guid iid, int ctx, IntPtr p, [MarshalAs(UnmanagedType.IUnknown)] out object o); int OpenPropertyStore(int a, out IPropertyStore ps); }
[Guid("886d8eeb-8cf2-4446-8d02-cdba1dbdcf99"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] interface IPropertyStore { int GetCount(out int c); int GetAt(int i, out PK k); int GetValue(ref PK k, out PV v); }
[StructLayout(LayoutKind.Sequential)] struct PK { public Guid f; public int p; }
[StructLayout(LayoutKind.Sequential)] struct PV { public short vt; short a,b,c; public IntPtr p; IntPtr q; }
[Guid("7ED4EE07-8E67-4CD4-8C1A-2B7A5987AD42"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] interface IAudioClient3 {
int Initialize(); int GetBufferSize(out uint n); int GetStreamLatency(); int GetCurrentPadding(); int IsFormatSupported(); int GetMixFormat(out IntPtr f); int GetDevicePeriod(); int Start(); int Stop(); int Reset(); int SetEventHandle(); int GetService();
int IsOffloadCapable(); int SetClientProperties(); int GetBufferSizeLimits();
int GetSharedModeEnginePeriod(IntPtr f, out uint d, out uint fu, out uint mn, out uint mx);
int GetCurrentSharedModeEnginePeriod(out IntPtr f, out uint cur);
int InitializeSharedAudioStream(uint flags, uint period, IntPtr f, IntPtr session); }
public static class H { public static string Hold(string match, int seconds) {
var e=(IMMDeviceEnumerator)new MMDE(); IMMDeviceCollection c; e.EnumAudioEndpoints(0,1,out c); int n; c.GetCount(out n);
for(int i=0;i<n;i++){ IMMDevice d; c.Item(i,out d); IPropertyStore ps; d.OpenPropertyStore(0,out ps); var k=new PK{f=new Guid("a45c254e-df1c-4efd-8020-67d146a850e0"),p=14}; PV v; ps.GetValue(ref k,out v); string name=Marshal.PtrToStringUni(v.p);
if(name.IndexOf(match,StringComparison.OrdinalIgnoreCase)<0) continue;
Guid g=typeof(IAudioClient3).GUID; object o; Marshal.ThrowExceptionForHR(d.Activate(ref g,23,IntPtr.Zero,out o)); var ac=(IAudioClient3)o;
IntPtr f; ac.GetMixFormat(out f); uint de,fu,mn,mx; Marshal.ThrowExceptionForHR(ac.GetSharedModeEnginePeriod(f,out de,out fu,out mn,out mx));
Marshal.ThrowExceptionForHR(ac.InitializeSharedAudioStream(0,mn,f,IntPtr.Zero)); Marshal.ThrowExceptionForHR(ac.Start());
IntPtr f2; uint cur; ac.GetCurrentSharedModeEnginePeriod(out f2,out cur);
Console.WriteLine(string.Format("HOLDING {0}: min {1} frames (default {2}), engine now {3} frames, for {4} s", name, mn, de, cur, seconds)); Console.Out.Flush();
Thread.Sleep(seconds*1000); ac.Stop(); return "released"; }
return "no endpoint matches " + match; } }
"@
[H]::Hold($Render, $Seconds)

I ran it in the background and measured normal default-period streams on the same endpoint, two rounds of 40 clicks:

PathDefault, aloneDefault with the hold runningGain
GPU, inbox driver, optical into the D7043.77 / 43.7936.71 / 36.966.95 ms
GPU, inbox driver, extractor 3.5 mm42.20 / 44.0237.04 / 37.036.08 ms
D70 over USB (hold at 336 frames)48.39 / 48.3642.63 / 42.735.70 ms

The gain is a fraction of what the minimum-period stream itself gets. The engine does run at the small period (the GPU path’s “driver says playing” fell from 32.98 to 24.65 ms), but the default-period app still allocates its own 1056-frame (22 ms) buffer and keeps it filled, so most of that queue is still in front of each click. How much a particular game gains depends on how full it keeps its buffer, which this test doesn’t cover. Microsoft’s page also notes the trade-off: smaller buffers wake the CPU more often.

Part 6: where the community’s onboard-vs-USB gap comes from

Section titled “Part 6: where the community’s onboard-vs-USB gap comes from”

The community numbers behind the claim that onboard audio is about 35 ms faster than any USB DAC come from a browser round-trip test: the page plays a beep through the speakers and listens for it on the microphone7. A browser asks for the endpoint’s minimum period, so that test runs every output at the smallest buffer its driver allows, which a game at the default period never sees, and the number includes the microphone’s capture path as well as the output.

The largest collection of these results, the ALTResults page from the Blur Busters forum8, lists the driver for most entries. The fast results are on Microsoft’s “High Definition Audio Device” driver, including GPU audio: one entry has a GTX 1070’s DisplayPort and HDMI audio on that driver at 34-47 ms round trip, while an RX 580’s HDMI and DisplayPort audio, listed as “AMD High Definition Audio Device”, came in at 75-108 ms on the same test. Another entry has the same Apple USB-C dongle at 67-76 ms on an ASMedia USB controller and 83-94 ms on a Renesas one.

Part 4 measures the mechanism on one output. The same GPU output was 24.05 ms faster on the inbox driver’s 128 frames than on NVIDIA’s 10 ms, with the hardware unchanged. Many USB drivers also stop at 10 ms (Topping’s and the SSL 2’s in the USB guide), and stock Realtek drivers tend to lock to 10 ms exact9, so an onboard codec on Microsoft’s driver against a USB DAC on a 10 ms driver would show a gap like that on the output alone, before the microphone side of a round trip. Against Microsoft’s USB class driver, which goes to 7 ms, the gap here was 15.2-15.5 ms. None of it isolates USB transport: after the driver’s queue, the USB path was the faster one.

The endpoint lives and dies with the display. Two consequences:

  • An extra 60 Hz display next to a 360 Hz primary made the desktop visibly choppy: minimise and maximise animations stuttered while the extractor was attached and were smooth again after detaching it (Win+P, PC screen only). That is an eyeball check at 60 Hz only; the extractor supports 1080p at 120 and 144 Hz1, which I did not try.
  • “Remove display from desktop” (Settings, Advanced display) would keep a display out of the desktop, but it needs driver support. NVIDIA’s own sample says “the NVIDIA driver implementation of this feature is only supported on Windows 11 and later”10. On this machine DisplayConfigGetDeviceInfo with DISPLAYCONFIG_DEVICE_INFO_GET_MONITOR_SPECIALIZATION (type 1211) returns 87 (ERROR_INVALID_PARAMETER) for every display, the DisplayPort primary included, while other info types on the same targets succeed. Switching to Enterprise for the toggle bought nothing on Windows 10.

The D70 had been behind a hub through all of this, so I moved it to a rear port with no external hub. It landed on the CPU’s USB 2.0 controller (1022:15B8, one PCI bridge from the root complex against three for the chipset controller); the SSL 2 stayed on the chipset controller. Five rounds of default and minimum period, 50 clicks each:

Chipset controller behind the hubCPU controller, no hub
Default period47.58-48.40 ms48.02-48.58 ms (steady runs)
Minimum period35.09-35.77 ms35.32-35.74 ms
Runs with a slip or lost clicks0 of 84 of 10
  • Latency did not change, the hub and the extra bridges cost nothing I could measure, which fits the latency being mostly Windows’ and the driver’s buffering.
  • Stability did. Three default runs on the new port started 6 ms early (41.80 ms at click 1, creeping to 42.30 ms), lost click 36, then ran at 47.92-48.15 ms for clicks 37-50, which pulled their medians to 42.15-42.41 ms. One minimum-period run found 28 of 50 clicks at a normal peak. A click lost at a normal level is a break in the stream, which you would hear as a tick or dropout. I have not isolated the cause: the controller, the board’s own hub on that port, or the DAC and recorder now running on different controllers.
  • The split moved with the port (31.93-31.97 + 16.11-16.62 ms in the steady default runs, instead of 42.97 + 5.14 ms) while the totals did not, so the split looks like each driver’s position reporting more than hardware delay.
CheckHowExpected
Extractor display presentWin+P Extend, then Settings > Displaya second display named after the extractor’s EDID
Extractor endpoint presentaudio-loopback.ps1 -ListHDMI SPLITTER (NVIDIA High Definition Audio)
DAC on the class driverGet-PnpDevice -Class MEDIA, driver INFusbaudio2.inf
GPU audio on the inbox driver (Part 4)Get-PnpDevice -Class MEDIA, driver INFHigh Definition Audio Device on hdaudio.inf; minimum period 128 frames
Extractor analog output alivecapture peak on Input 1about 0.05 at 16-bit; near zero at 24-bit
Hold active (Part 5)the script’s HOLDING lineengine now 128 frames (336 on the D70)
Right input for the blockcapture peak in each runabout 0.8 with the volumes above; near zero means the DAC is on the other input
Clicks foundRESULT n/5050/50
Display detached afterwardsWin+P, PC screen onlythe endpoint shows as unplugged
  • The EDID switch decides whether the GPU sees anything. TV mode with an empty HDMI Out gives no display and no endpoint; 2ch plus a replug did it.
  • Prompts do not wait when the script is launched from WSL. A wrapper that asked me to switch the DAC’s input with Read-Host got redirected stdin through WSL interop, skipped every prompt, and timed a USB block with the DAC still on optical. The capture peak was 0.002 and the “clicks” it found were noise. Run interactive measurements from a Windows terminal, and treat a peak under 0.02 as no signal.
  • Windows asked for a restart halfway through. A removal of the USB root hub the DAC and recorder hang off had been vetoed by audiodg.exe and the SSL control panel (Kernel-PnP event 225 in the System log); no update or file-rename reboot was pending. A vetoed removal completes at the next restart and does not touch a running stream; the USB default-period numbers (47.58-48.40 ms) overlap the October baseline (47.33-47.88 ms in the USB guide).
  • Do not compare the “driver says playing” column across drivers or USB ports; it moved by 11 ms between ports with the total unchanged.
  • An idle extra display is not free. Detach the extractor when you are not measuring through it.
  • Moving the DAC to a “better” port can make it worse. Check for lost clicks, not only the median.
  • A driver swap resets the endpoint’s format. Mine came back at 44.1 kHz, with a 448-frame (10.16 ms) default period and every number 1-2 ms off the 48 kHz set.
  • The extractor’s analog output mutes on a 24-bit stream while the optical output carries on. Check the capture peak on both inputs after any format change.
  • The monitor’s own headphone jack would have been a GPU test without the extractor, but it was silent even with headphones, while DDC/CI reported the monitor at volume 70, unmuted, on DisplayPort 1. I didn’t find the cause.
  • A hold stream only lasts while its process runs. Kill it before measuring anything else on that endpoint, or every later default-period run comes out 6-7 ms low.
Path or objectWhat it is
audio-loopback.ps1the cable-loop script from the USB audio guide; -Render picks the path, -MinPeriod the minimum period
hold-min-period.ps1Part 5: holds an endpoint at its minimum period; -Render picks the endpoint, -Seconds how long
hdaudio.infMicrosoft’s inbox HD Audio driver (High Definition Audio Device)
IAudioClient3::GetSharedModeEnginePerioddefault, fundamental and minimum period per endpoint
DisplayConfigGetDeviceInfo type 12monitor specialisation state; error 87 means the driver has no support
Kernel-PnP event 225 (System log)an application vetoed a device removal; the removal waits for a restart
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\MMDevices\Audio\Renderthe endpoint store: one subkey per endpoint, DeviceState 1 = active, 8 = unplugged

The method, the recorder bounds and the driver comparison are in Measuring USB audio output latency on Windows with a loopback cable. The same machine keeps its 8 kHz mouse on its own CPU-attached USB controller, away from the audio devices, as described in Fixing merged reports from an 8 kHz mouse on Windows.

  1. J-Tech Digital, “4K 60Hz HDMI Audio Extractor with Optical and 3.5mm Outputs (JTD18G-H5CH),” J-Tech Digital. https://www.jtechdigital.com/products/jtd18g-h5ch-jtd-322-audio-extractor-4k-60hz-hdmi-audio-extractor ↩ ↩2

  2. Microsoft, “IAudioClient3::InitializeSharedAudioStream method (audioclient.h),” Microsoft Learn. https://learn.microsoft.com/en-us/windows/win32/api/audioclient/nf-audioclient-iaudioclient3-initializesharedaudiostream ↩

  3. Chromium, “media/audio/win/core_audio_util_win.cc,” chromium/chromium on GitHub, commit 02a2143. https://github.com/chromium/chromium/blob/02a214368ef9f9c2e2557f6412e763e43b992246/media/audio/win/core_audio_util_win.cc ↩

  4. SDL, “wasapi: Use IAudioClient3 if possible to make use of sample_frames,” GitHub pull request 9683, merged 2024-05-07. https://github.com/libsdl-org/SDL/pull/9683 ↩

  5. Microsoft, “Low latency audio,” Microsoft Learn. https://learn.microsoft.com/en-us/windows-hardware/drivers/audio/low-latency-audio ↩ ↩2

  6. miniant-git, “REAL - Reduce audio latency on Windows 10,” GitHub. https://github.com/miniant-git/REAL ↩

  7. Superpowered, “Superpowered Web Browser Audio Latency Test,” Superpowered. https://superpowered.com/webbrowserlatency ↩

  8. “ALTResults,” community results for the Superpowered browser latency test, linked from the Blur Busters forum. https://rentry.co/ALTResults ↩

  9. NVIDIA, “vk_ddisplay,” nvpro-samples on GitHub (README, system requirements). https://github.com/nvpro-samples/vk_ddisplay ↩

  10. Microsoft, “DISPLAYCONFIG_DEVICE_INFO_TYPE enumeration (wingdi.h),” Microsoft Learn. https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ne-wingdi-displayconfig_device_info_type ↩