pub fn encode_cpu(
stripes: &mut Vec<StripeState>,
carrying: &mut f32,
raw_pixels: &[u8],
width: i32,
height: i32,
damage_rects: &[Rectangle<i32, Physical>],
settings: &RustCaptureSettings,
frame_counter: u16,
use_gpu: bool,
hash_damage: bool,
force_idr_all: bool,
) -> Vec<EncodedStripe>Expand description
The software encoder’s per-frame entry point: split the frame into horizontal stripes, decide per stripe whether it needs sending, and encode only those as JPEG or H.264 (libx264 or OpenH264, by build) across the rayon pool.
Two pressures drive the design: CPU H.264/JPEG is expensive, so the frame is cut into parallel stripes; bandwidth is precious, so unchanged stripes are skipped. Each stripe is independently hashed against the previous frame for change detection, and only dirty stripes are encoded. The H.264 path maintains per-stripe encoder state across frames for inter-prediction; the JPEG path is stateless.
§Arguments
stripes- Persistent per-stripe 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- Wayland damage rectangles (empty for X11 hash-based detection).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 stripe-hash change detection;falsewhen damage rects are provided.force_idr_all- Force a keyframe on every stripe (client join / reset / periodic IDR).
§Returns
Vec of EncodedStripe — empty when nothing changed.
repainting a stalled region at full quality, and letting a freshly-joined or reset client recover
a clean picture. Persistent StripeState is what makes both affordable: encoders and buffers
survive across frames instead of being rebuilt, and the motion/paint-over history the decision
needs lives right beside them. The per-stripe decision mirrors decide_hw_fullframe’s policy for
the hardware full-frame encoders; it is kept as separate code here because the striped path also
chooses JPEG-vs-H.264 and derives its own damage.
- Stripe count: the core count, held to
MAX_STRIPESbecause the client decodes one picture per stripe, but collapses to a single full-frame stripe when H.264 full-frame is requested or the frame is shorter than the 64-row minimum, and no stripe is thinner than 64 rows — below that the per-stripe encoder and thread overhead outweighs the parallelism and the tiny H.264 slices compress poorly. The persistentstripesvector is resized to match, preserving per-stripe state across frames. - Idle fast path: a frame on which no stripe can emit anything (no damage / clean hashes, no cleanup due, no burst, no recovery IDR, not streaming) only advances the per-stripe 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 stripe whose row range it overlaps. Withhash_damage == true(X11, which has no compositor damage) per-stripe content hashing drives dirtiness instead, in streaming H.264 too, since the cleanup reads a still stripe from its content. - Per-stripe cleanup (
pipeline::cleanup_due, one region per stripe): a stripe that changed is cleaned up once it has held still forpaint_over_trigger_frames, 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 stripe 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 stripe’s encoder reports no quantizer (OpenH264) as a refresh and burst at its rate control’s quality; under CRF as the stripe’s rate factor until it changes again, as main’s paint-over was, as a key frame after a run of changes and a refresh otherwise, then a burst. The 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 stripe 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. - Recovery IDR (
force_idr_all): forces a send on every stripe even when static so a reconnecting client can resume. For H.264 it forces an IDR at the stripe’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 to match the H.264 path’s native framing so the transport can forward the buffer without re-framing. - H.264: the stripe’s encoder is reused unless the width, height, or
(x264) chroma format 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 stripe rather than encoding garbage) and an 8-byte fixed header (frame
number, y-start, width, height) is emitted; OpenH264 converts and frames inside
encode_stripe_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 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: a single full-frame stripe runs inline (sequential — empirically faster than a
one-element rayon job) with one fewer encode thread than the available cores, clamped to
[1, 4](x264 with a single-band color conversion; OpenH264 adds four slices and a four-band conversion of its own). 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. Multiple stripes instead run across the rayon pool with a single encode thread and one conversion band each, since the parallelism there already comes from encoding the stripes concurrently.