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:
parent
22a18eee1b
commit
bd511fea33
8 changed files with 252 additions and 7 deletions
|
|
@ -24,7 +24,7 @@ use breadcast_caststream_sys::{
|
|||
self as sys, breadcast_caststream_sender_create, breadcast_caststream_sender_destroy,
|
||||
breadcast_caststream_sender_enqueue_frame, breadcast_caststream_sender_estimated_bandwidth_bps,
|
||||
breadcast_caststream_sender_needs_key_frame, breadcast_caststream_sender_negotiate,
|
||||
breadcast_caststream_sender_on_message,
|
||||
breadcast_caststream_sender_on_message, breadcast_caststream_sender_take_stats,
|
||||
};
|
||||
|
||||
/// The Cast Streaming ("Mirroring") receiver app id, pre-installed on every
|
||||
|
|
@ -236,6 +236,22 @@ impl CastStreamSender {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// A snapshot of how the *underlying* `Sender::EnqueueFrame` has been
|
||||
/// answering, plus its in-flight window gauges. Reading resets the
|
||||
/// counters, so polling once a second yields per-second rates.
|
||||
///
|
||||
/// [`Self::enqueue_frame`] deliberately cannot report any of this: it
|
||||
/// returns as soon as the frame is posted to openscreen's TaskRunner,
|
||||
/// before the real accept/reject happens. Any "frames enqueued per
|
||||
/// second" figure derived from its return value is therefore a count of
|
||||
/// *attempts*, and stays pinned at the capture rate even while every
|
||||
/// frame is being rejected downstream and the picture is frozen.
|
||||
pub fn enqueue_stats(&self) -> sys::EnqueueStats {
|
||||
let mut stats = sys::EnqueueStats::default();
|
||||
unsafe { breadcast_caststream_sender_take_stats(self.raw, &mut stats) };
|
||||
stats
|
||||
}
|
||||
|
||||
/// True if the receiver wants a key frame as soon as possible. Cheap to
|
||||
/// poll frequently (e.g. once per captured frame, before encoding it).
|
||||
pub fn needs_key_frame(&self) -> bool {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue