//! H.264 encode via openh264 (pure-Rust-friendly, statically linked C core) — //! the portable software path that works on every Windows PC with no driver or //! GPU dependency. BGRA (from DXGI capture) is converted to I420 and encoded as //! Annex-B access units: keyframes carry SPS+PPS+IDR, deltas carry a P slice, //! exactly what the viewer's openh264/Media Foundation decoder expects. //! //! Hardware encoders (NVENC / QSV / AMF) layer in later behind this same //! `EncodedFrame` output; software-first guarantees the host always works. use anyhow::{anyhow, Result}; use openh264::encoder::{Encoder, EncoderConfig, FrameType, RateControlMode, UsageType}; use openh264::formats::{BgraSliceU8, YUVBuffer}; use openh264::OpenH264API; /// One encoded access unit ready to hand to `core::host`, which prefixes the /// video channel + keyframe byte and encrypts it. pub struct EncodedFrame { pub data: Vec, pub keyframe: bool, } /// A live H.264 encoder for one resolution. Resolution changes re-init the /// encoder automatically (openh264 does this on dimension change). pub struct H264Encoder { enc: Encoder, width: usize, height: usize, } impl H264Encoder { pub fn new(width: usize, height: usize, fps: u32, bitrate_bps: u32) -> Result { let config = EncoderConfig::new() .set_bitrate_bps(bitrate_bps) .max_frame_rate(fps as f32) .rate_control_mode(RateControlMode::Bitrate) .usage_type(UsageType::ScreenContentRealTime) .enable_skip_frame(true) .set_multiple_thread_idc(0); // auto: use all cores let enc = Encoder::with_api_config(OpenH264API::from_source(), config) .map_err(|e| anyhow!("openh264 encoder init: {e}"))?; Ok(Self { enc, width, height }) } /// Encode one tightly-packed BGRA frame (`width*height*4`). Returns `None` /// when rate control skipped this frame (nothing to send this tick). pub fn encode(&mut self, bgra: &[u8]) -> Result> { let src = BgraSliceU8::new(bgra, (self.width, self.height)); let yuv = YUVBuffer::from_rgb_source(src); let bs = self.enc.encode(&yuv).map_err(|e| anyhow!("encode: {e}"))?; let ft = bs.frame_type(); if ft == FrameType::Skip || ft == FrameType::Invalid { return Ok(None); } let data = bs.to_vec(); if data.is_empty() { return Ok(None); } let keyframe = matches!(ft, FrameType::IDR | FrameType::I); Ok(Some(EncodedFrame { data, keyframe })) } /// Force the next encoded frame to be a keyframe (viewer asked for a refresh /// after a drop, or a new viewer joined). pub fn request_keyframe(&mut self) { self.enc.force_intra_frame(); } pub fn width(&self) -> usize { self.width } pub fn height(&self) -> usize { self.height } }