Skip to main content

encode_cpu

Function encode_cpu 

Source
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 * 4 bytes).
  • width - Frame width in pixels.
  • height - Frame height in pixels.
  • damage_rects - Where the source says the frame changed: what marks a region dirty without hash_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 (wrapping u16).
  • use_gpu - true when the source is RGBA (GLES readback); false for BGRA (X11 host).
  • hash_damage - true for X11 content-hash change detection; false when damage rects are provided.
  • requested_idr - Force a keyframe (client join / reset).

§Returns

Vec of EncodedStripe — empty when nothing changed.

  1. Regions: JPEG takes the core count of stripes, held to MAX_STRIPES because 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 persistent stripes vector is resized to match, preserving per-region state across frames.
  2. 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.
  3. Dirty map: with external compositor damage (hash_damage == false) each damage_rects rectangle marks every region whose row range it overlaps. With hash_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.
  4. Per-region cleanup (pipeline::cleanup_due): a region that changed is cleaned up once it has held still for paint_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 for pipeline::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, as pipeline::decide_constant_quality keeps 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 over CLEANUP_STAGGER_FRAMES frames, 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.
  5. 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 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 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_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 (rate_control) so it rescales with live changes.
  7. Dispatch: H.264 runs inline with h264_fullframe_threads encode 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 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. JPEG stripes instead run across the rayon pool, or one after another without cpu_encode_threading (stripes_at_once).