Measure real EnqueueFrame outcomes, then size the send window to fit RTT

Every "fix" for the mirroring freezes so far has been reasoned from code
rather than measured, because the one counter that could have falsified any
of them was blind by construction: `enqueue_frame` returns as soon as a frame
is *posted* to openscreen's TaskRunner, long before `Sender::EnqueueFrame`
decides whether to accept it. The frame pump's `enqueued_fps` therefore read a
healthy 30fps through every freeze.

Add `BreadcastEnqueueStats` (new FFI accessor, no behaviour change): per-second
counts of OK / MAX_DURATION_IN_FLIGHT / REACHED_ID_SPAN_LIMIT /
PAYLOAD_TOO_LARGE, plus the in-flight window gauges and RTT sampled at the
enqueue attempt, all surfaced on the existing "frame pump rate" line as
`accepted_fps` / `rejected_*`.

Measured against the real Chromecast, that settles it: 12.2% of frames were
being rejected with MAX_DURATION_IN_FLIGHT, in 85% of all seconds -- steady,
not just during visible freezes. Since breadcast enqueues already-encoded
frames, each rejection silently breaks the H.264 reference chain rather than
merely dropping a frame.

The measurement also corrects the diagnosis. The send window is
clamp(2*RTT, kMinSenderInFlight, target_playout_delay/3); the assumption was
that a LAN pins it to the 66ms floor. It does not -- RTT to this receiver runs
42-189ms, so 2*RTT is 84-378ms and the window was pinned at the *ceiling*,
133ms at a 400ms playout delay. The ceiling was the binding constraint, so
raising the floor alone would have changed nothing.

So raise both, ceiling first: target playout delay 400ms -> 1200ms (ceiling
133ms -> 400ms) and kMinSenderInFlight 66ms -> 200ms for RTT dips. Measured
over a matched 65s steady-state window, rejections fall 12.2% -> 4.3% and
seconds containing a broken reference chain 85% -> 40%. Costs ~800ms of
added latency, which is unnoticeable for mirroring to a TV.

This is an improvement, not a cure. The residual rejections are bursts
(in-flight seen at 433ms against a 200ms window, RTT spiking to 221ms), and no
static window survives those. The real fix is the backpressure contract
sender.h documents and this facade still doesn't implement: consult
GetInFlightMediaDuration()/GetMaxInFlightMediaDuration() and throttle *before*
encoding, so a skipped frame never leaves a dangling reference behind.
This commit is contained in:
Breadway 2026-08-06 13:52:53 +08:00
parent 22a18eee1b
commit bd511fea33
8 changed files with 252 additions and 7 deletions

View file

@ -62,3 +62,25 @@ There are no release/API-stability guarantees upstream. To update:
(pulled in separately via gclient/DEPS in a full Chromium checkout).
Reimplemented on `EVP_EncodeBlock`/`EVP_DecodeBlock` from system OpenSSL
instead, same public interface.
6. **`cast/streaming/impl/sender_impl.cc`** — raised `kMinSenderInFlight`
from upstream's 66ms to 200ms. This is a behaviour patch, not a
portability one, and is the only one here that changes what goes on the
wire — so unlike the others it should *not* be silently re-applied when
rolling the pin without re-measuring first.
`GetMaxInFlightMediaDuration()` sizes the sender's send window as
`clamp(2*RTT, kMinSenderInFlight, target_playout_delay/3)`. Upstream's
66ms floor assumes the RTT is negligible, which holds for Chrome's own
usage but not for breadcast's measured case: instrumenting real
`Sender::EnqueueFrame` result codes (see `BreadcastEnqueueStats` in
`../../src/facade.h`) against a Chromecast over Wi-Fi showed RTT of
57-145ms and a steady 3-40% of frames per second rejected with
`MAX_DURATION_IN_FLIGHT`. Because breadcast enqueues already-encoded
frames, each such rejection silently breaks the H.264 reference chain
rather than merely dropping a frame, which is what the user-visible
multi-second picture freezes turned out to be.
Pairs with `kTargetPlayoutDelay` in `../../src/session.cc`, which raises
the *ceiling* of that same clamp (the ceiling, not this floor, was the
binding constraint at 400ms playout delay). Both are needed: this floor
covers RTT dips, that ceiling covers the normal case.