Back to Blog
Testing
September 25, 20266 min read

Glass-to-Glass Latency: WebRTC vs RTSP vs Cloudflare (Real Measurements)

We pointed a drone camera at a millisecond clock and measured end-to-end video latency over five paths: WHEP over Tailscale, QGroundControl, RTSP, and Cloudflare in QGC and the browser. 25 screenshots, every frame shown.

A
AirCast Team
Author
Latency
WebRTC
RTSP
Cloudflare
QGroundControl

Latency numbers on spec sheets are easy to write and hard to trust. So we measured ours the simplest honest way: point the drone camera at a screen running a millisecond clock, put the received video next to that same clock, and take screenshots. Each screenshot freezes both clocks in one frame, so latency = reference clock − clock seen in the video. We took five screenshots per path and show every one of them below.

Results

PathCameraPlayerGlass-to-glass
WHEP in the browser, direct over TailscaleOpenIPCBrowser (WHEP)~339 ms
QGroundControlOpenIPCQGC~356 ms
QGroundControl over RTSPOpenIPCQGC (RTSP)~365 ms
Cloudflare, played in QGroundControlViewPro (1080p25, ~3.2 Mbps)QGC~300 ms
Cloudflare, played in the browserOpenIPCBrowser (WHEP)~265 ms

What the numbers say

  • Cloudflare is the fastest path. Same OpenIPC camera, same browser player: Cloudflare lands at ~265 ms against ~339 ms direct over Tailscale, about 75 ms faster. The Tailscale path was almost certainly relayed through a DERP server rather than peer-to-peer.
  • QGC and the browser are on par. QGC measured ~15-20 ms slower than the browser, which is inside the measurement error.
  • RTSP costs ~25-30 ms over WebRTC and is a little less stable, most likely from the receive-side jitter buffer rather than the network.
  • Most of the budget is upstream. Roughly 250 ms is common to every path: sensor readout, encoder buffering and GOP, plus the receiver's jitter buffer. That's where the next big wins are.

How to read the screenshots

The large green clock is the reference. The camera feed shows the same clock filmed off a laptop screen, and because the laptop is also showing the stream, you can often see a second, recursive clock inside the feed that confirms the reading. Expect roughly ±20-30 ms of error from the camera frame interval, display refresh and exposure time. Where the last digit was motion-blurred we mark it x and round.

WHEP in the browser, direct over Tailscale: ~339 ms

Camera: OpenIPC. Player: Browser (WHEP).

FrameReferenceIn videoLatency
104:25.98204:25.648334 ms
204:35.52004:35.185335 ms
304:41.87104:41.536335 ms
404:48.93804:48.588350 ms
5n/acamera turned awayn/a
  • Mean about 339 ms with very low jitter (334-350 ms).
  • The recursive clocks inside the feed agree: in frame 2 the inner clock reads about 04:34.85, roughly 335 ms behind 04:35.185.
WHEP in the browser, direct over Tailscale, frame 1: reference 04:25.982, video 04:25.648
Frame 1: 04:25.982 − 04:25.648 = 334 ms
WHEP in the browser, direct over Tailscale, frame 2: reference 04:35.520, video 04:35.185
Frame 2: 04:35.520 − 04:35.185 = 335 ms
WHEP in the browser, direct over Tailscale, frame 3: reference 04:41.871, video 04:41.536
Frame 3: 04:41.871 − 04:41.536 = 335 ms
WHEP in the browser, direct over Tailscale, frame 4: reference 04:48.938, video 04:48.588
Frame 4: 04:48.938 − 04:48.588 = 350 ms
WHEP in the browser, direct over Tailscale, frame 5: reference n/a, video camera turned away
Frame 5: n/a − camera turned away = n/a

QGroundControl: ~356 ms

Camera: OpenIPC. Player: QGC.

FrameReferenceIn videoLatency
105:29.38505:29.015370 ms
205:37.80205:37.434368 ms
305:46.28605:45.92x~362 ms
405:52.44005:52.091349 ms
505:59.24005:58.91x~330 ms
  • Mean about 356 ms, spread 330-370 ms. Frames 3 and 5 have a motion-blurred last digit (±10 ms).
  • About 15-20 ms slower than the browser player, which is within measurement error.
QGroundControl, frame 1: reference 05:29.385, video 05:29.015
Frame 1: 05:29.385 − 05:29.015 = 370 ms
QGroundControl, frame 2: reference 05:37.802, video 05:37.434
Frame 2: 05:37.802 − 05:37.434 = 368 ms
QGroundControl, frame 3: reference 05:46.286, video 05:45.92x
Frame 3: 05:46.286 − 05:45.92x = ~362 ms
QGroundControl, frame 4: reference 05:52.440, video 05:52.091
Frame 4: 05:52.440 − 05:52.091 = 349 ms
QGroundControl, frame 5: reference 05:59.240, video 05:58.91x
Frame 5: 05:59.240 − 05:58.91x = ~330 ms

QGroundControl over RTSP: ~365 ms

Camera: OpenIPC. Player: QGC (RTSP).

FrameReferenceIn videoLatency
108:52.35308:51.986367 ms
208:59.30408:58.934370 ms
309:05.04109:04.62x~418 ms
409:10.55709:10.207350 ms
509:17.27609:16.926350 ms
  • Median about 365 ms; the mean is about 370 ms because frame 3 spiked (a jitter spike or a blurred digit).
  • The rest sit in two clusters, 350 and 367-370 ms, half a frame apart at 30 fps, so that spread is frame quantization.
QGroundControl over RTSP, frame 1: reference 08:52.353, video 08:51.986
Frame 1: 08:52.353 − 08:51.986 = 367 ms
QGroundControl over RTSP, frame 2: reference 08:59.304, video 08:58.934
Frame 2: 08:59.304 − 08:58.934 = 370 ms
QGroundControl over RTSP, frame 3: reference 09:05.041, video 09:04.62x
Frame 3: 09:05.041 − 09:04.62x = ~418 ms
QGroundControl over RTSP, frame 4: reference 09:10.557, video 09:10.207
Frame 4: 09:10.557 − 09:10.207 = 350 ms
QGroundControl over RTSP, frame 5: reference 09:17.276, video 09:16.926
Frame 5: 09:17.276 − 09:16.926 = 350 ms

Cloudflare, played in QGroundControl: ~300 ms

Camera: ViewPro (1080p25, ~3.2 Mbps). Player: QGC.

FrameReferenceIn videoLatency
140:50.59040:50.305285 ms
240:57.04140:56.742299 ms
341:03.74241:03.42x~315 ms
441:09.56041:09.311249 ms
541:15.46341:15.12x~335 ms
  • Mean about 296 ms, median about 299 ms. The 250-335 ms spread is roughly ±2 frames at 25 fps, jitterier than the other runs.
  • The camera changed for this run, so part of the gain may come from the camera rather than Cloudflare.
Cloudflare, played in QGroundControl, frame 1: reference 40:50.590, video 40:50.305
Frame 1: 40:50.590 − 40:50.305 = 285 ms
Cloudflare, played in QGroundControl, frame 2: reference 40:57.041, video 40:56.742
Frame 2: 40:57.041 − 40:56.742 = 299 ms
Cloudflare, played in QGroundControl, frame 3: reference 41:03.742, video 41:03.42x
Frame 3: 41:03.742 − 41:03.42x = ~315 ms
Cloudflare, played in QGroundControl, frame 4: reference 41:09.560, video 41:09.311
Frame 4: 41:09.560 − 41:09.311 = 249 ms
Cloudflare, played in QGroundControl, frame 5: reference 41:15.463, video 41:15.12x
Frame 5: 41:15.463 − 41:15.12x = ~335 ms

Cloudflare, played in the browser: ~265 ms

Camera: OpenIPC. Player: Browser (WHEP).

FrameReferenceIn videoLatency
159:18.41759:18.167250 ms
259:24.96959:24.702267 ms
359:30.68759:30.437250 ms
459:35.47159:35.171300 ms
559:42.04059:41.789251 ms
  • Mean about 264 ms, median about 250 ms. Three of five frames land on 250 ms, the likely steady state; frame 4 is a one-frame hiccup.
  • The inner clocks cross-check: frame 3 gives 30.437 − 30.188 = 249 ms.
Cloudflare, played in the browser, frame 1: reference 59:18.417, video 59:18.167
Frame 1: 59:18.417 − 59:18.167 = 250 ms
Cloudflare, played in the browser, frame 2: reference 59:24.969, video 59:24.702
Frame 2: 59:24.969 − 59:24.702 = 267 ms
Cloudflare, played in the browser, frame 3: reference 59:30.687, video 59:30.437
Frame 3: 59:30.687 − 59:30.437 = 250 ms
Cloudflare, played in the browser, frame 4: reference 59:35.471, video 59:35.171
Frame 4: 59:35.471 − 59:35.171 = 300 ms
Cloudflare, played in the browser, frame 5: reference 59:42.040, video 59:41.789
Frame 5: 59:42.040 − 59:41.789 = 251 ms

Caveats

  • The Cloudflare-in-QGC run used a different camera (ViewPro instead of OpenIPC), so it isn't a clean transport comparison. It came in ~35 ms slower than Cloudflare in the browser; the camera, the QGC player, or both account for that.
  • Five frames per path is a spot check, not a statistical benchmark.
  • Screen sharing over Google Meet was used to capture the frames. It doesn't affect the numbers because both clocks are in the same shared frame.

Want to reproduce this on your own setup? Start with streaming drone video over 4G/LTE.