Skip to main content

encode_cpu

Function encode_cpu 

Source
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 * 4 bytes).
  • 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 (wrapping u16).
  • use_gpu - true when the source is RGBA (GLES readback); false for BGRA (X11 host).
  • hash_damage - true for X11 stripe-hash change detection; false when 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.

  1. Stripe count: the core count, held to MAX_STRIPES because 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 persistent stripes vector is resized to match, preserving per-stripe state across frames.
  2. 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.
  3. Dirty map: with external compositor damage (hash_damage == false) each damage_rects rectangle marks every stripe whose row range it overlaps. With hash_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.
  4. Per-stripe cleanup (pipeline::cleanup_due, one region per stripe): a stripe that changed is cleaned up once it has held still for paint_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 for pipeline::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 over CLEANUP_STAGGER_FRAMES frames, 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.
  5. 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 in pipeline::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.
  6. 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 (0x03 tag, 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_argb with 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.
  7. 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 because zerolatency makes 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.