pub fn encode_cpu(
stripes: &mut Vec<StripeState>,
raw_pixels: &[u8],
width: i32,
height: i32,
damage_rects: &[Rectangle<i32, Physical>],
settings: &CaptureSettings,
frame_counter: u16,
use_gpu: bool,
hash_damage: bool,
requested_idr: bool,
) -> Vec<EncodedStripe>Expand description
The CPU path’s per-frame entry point: decide per region whether it needs sending, and encode those as JPEG stripes across the rayon pool or as one H.264 frame through the build’s software encoder (libx264 or OpenH264).
JPEG is cut into parallel stripes so its CPU cost spreads over the cores and an unchanged
stripe is skipped on its own; H.264 is one region covering the frame, its encoder kept across
frames for inter-prediction. Persistent StripeState keeps encoders, buffers, and the
motion/paint-over history the decision needs alive across frames. The per-region decision
mirrors decide_hw_fullframe’s policy for the full-frame sessions; it is kept as separate code
because this path also derives its own damage and cleans H.264 up through its software rate
control.
§Arguments
stripes- Persistent per-region state vector (resized as needed, encoder state preserved).raw_pixels- Packed BGRA/RGBA pixel buffer (width * height * 4bytes).width- Frame width in pixels.height- Frame height in pixels.damage_rects- Where the source says the frame changed: what marks a region dirty withouthash_damage(Wayland), and with it where the change lies within each region the hash finds dirty, for its block ledger (empty where the source cannot say).settings- Capture settings (quality, mode, rate control, etc.).frame_counter- Current frame number (wrappingu16).use_gpu-truewhen the source is RGBA (GLES readback);falsefor BGRA (X11 host).hash_damage-truefor X11 content-hash change detection;falsewhen damage rects are provided.requested_idr- Force a keyframe (client join / reset).
§Returns
Vec of EncodedStripe — empty when nothing changed.
- Regions: JPEG takes the core count of stripes, held to
MAX_STRIPESbecause the client decodes one picture per stripe, with no stripe thinner than 64 rows (stripe_count); H.264 takes one region covering the frame. The persistentstripesvector is resized to match, preserving per-region state across frames. - Idle fast path: a frame on which no region can emit anything (no damage / clean hashes, no cleanup due, no burst, no recovery IDR, not streaming) only advances the per-region cleanup bookkeeping inline and returns without dispatching the stripe fan-out, so a static capture never wakes the rayon pool.
- Dirty map: with external compositor damage (
hash_damage == false) eachdamage_rectsrectangle marks every region whose row range it overlaps. Withhash_damage == true(X11, which has no compositor damage) per-region content hashing drives dirtiness instead, in streaming H.264 too, since the cleanup reads a still region from its content. - Per-region cleanup (
pipeline::cleanup_due): a region that changed is cleaned up once it has held still forpaint_over_trigger, or has kept changing only a little for four times that, at the paint-over JPEG quality or, for H.264, at the paint-over quality: under CBR through libx264’s rate control, the frame sent until it codes at the paint-over quantizer or finer in a small frame (pipeline::convergence), or its frames have been slice headers alone forpipeline::EMPTY_S, with no key frame, and where the encoder reports no quantizer (OpenH264) as a refresh and burst at its rate control’s quality; under CRF as the session’s rate factor until it changes again, as main’s paint-over was, a refresh and then a burst, with no key frame either, aspipeline::decide_constant_qualitykeeps it: the frames held at the rate factor refine the picture past what a key frame at it codes (x264 at CRF 24 and paint-over 16 on a scrolled 1440p page, 51.3 dB after the refresh, 48.3 after the 2.7 MB key frame that followed it). The JPEG stripes whose cleanups fall due on one frame are spread overCLEANUP_STAGGER_FRAMESframes, so a screen going still costs no one frame the whole screen’s cleanup. A region is otherwise sent when it is dirty, while its burst runs, or when streaming mode is on, at the base quality; a newly dirty frame cancels its burst. A constant-rate x264 session is floored at the paint-over quantizer while it moves and the floor lifted for its cleanup (H264EncoderWrapper::reconfigure_floor), unless the caller names a floor of its own (video_min_qp): the rate control otherwise spends a generous budget coding motion finer than the still picture it leaves (a 1440p scroll at 30 Mbit/s, 22.3 Mbit/s at 57 dB against 13.5 at 46 dB), where the still screen reaches what the rate control gives it either way, and the blocks a moving frame refines code below the floor. - Recovery IDR (
requested_idr): forces a send even when static so a reconnecting client can resume. A scheduled one (pipeline::periodic_idr_due) waits for the region to change (StripeState::scheduled_key): a hashed change has no known extent, so any counts as motion. For H.264 it forces an IDR at the session’s own quality and arms the burst (held at the paint-over quantizer at a constant quality, as inpipeline::decide_hw_fullframe); for JPEG, where every stripe is already intra, it resends a painted-over stripe at the paint-over quality already on screen so a joining viewer does not see a downgrade. - Encoding:
- JPEG: source byte order is RGBA on the GPU readback path and BGRA on
X11; each worker thread reuses its thread-local TurboJPEG compressor. Header-less output
hands the compressed buffer straight through; otherwise a 6-byte stripe header (
0x03tag, a reserved byte, frame number, y-start) is prepended so the transport can forward the buffer without re-framing. - H.264: the encoder is reused unless the width, height, or (x264) chroma format or
depth changed, in which case it is rebuilt and an IDR forced; otherwise CRF and rate are
reconfigured live. With libx264, ARGB is converted to YUV here (a conversion failure skips
the frame rather than encoding garbage) and the video header is emitted; an 8-bit 4:2:0
frame is converted to NV12 laid out as x264’s own frames, which x264 encodes where it
lies instead of copying it, into the session’s two pictures in turn, since x264 reads the
one before through the next encode; OpenH264 converts
and frames inside
encode_host_argbwith the same header layout, and encodes a 4:4:4 request 4:2:0 (said once per process). The live CBR budget is recomputed here from the bitrate/fps (rate_control) so it rescales with live changes.
- JPEG: source byte order is RGBA on the GPU readback path and BGRA on
X11; each worker thread reuses its thread-local TurboJPEG compressor. Header-less output
hands the compressed buffer straight through; otherwise a 6-byte stripe header (
- Dispatch: H.264 runs inline with
h264_fullframe_threadsencode threads, its color conversion split into as many bands (x264: a whole 2560x1600 frame took 0.98 ms on one thread and 0.62 on four; OpenH264 adds four slices and a four-band conversion of its own), or none where the capture hands over the planes (encode_cpu_from). The slice threads keep the in-frame encode latency inside the frame budget at high resolutions; the cap is four becausezerolatencymakes x264 slice-threaded and more than four slices trips decode glitches in some Chromium builds, and the minus-one leaves headroom for the capture thread. JPEG stripes instead run across the rayon pool, or one after another withoutcpu_encode_threading(stripes_at_once).