NetDesk 1.5.0 — source release
This commit is contained in:
commit
37aa876908
227 files changed
+80694
No files matched your search
@@ -0,0 +1,74 @@
|
||||
//! 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<u8>,
|
||||
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<Self> {
|
||||
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<Option<EncodedFrame>> {
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user