lesavka/client/src/app/uplink_media/video_keyframes.rs

91 lines
3.5 KiB
Rust

#[cfg(not(coverage))]
/// Detect whether an Annex-B HEVC access unit contains an intra recovery point.
///
/// Inputs: one encoded HEVC packet in byte-stream form. Output: `true` when the
/// packet carries an IRAP NAL unit. Why: the freshness-first uplink may drop
/// predictive frames, and the server decoder should only resume from a frame
/// that can rebuild a clean picture.
fn contains_hevc_irap(data: &[u8]) -> bool {
let mut offset = 0;
while let Some((start, prefix_len)) = find_annex_b_start_code(data, offset) {
let nal_index = start + prefix_len;
if nal_index + 1 < data.len() {
let nal_type = (data[nal_index] >> 1) & 0x3f;
if (16..=23).contains(&nal_type) {
return true;
}
}
offset = nal_index.saturating_add(2);
}
false
}
#[cfg(not(coverage))]
/// Locate the next Annex-B start code in an encoded video packet.
///
/// Inputs: encoded bytes plus the search offset. Output: the start index and
/// prefix length. Why: both three-byte and four-byte start codes appear in
/// HEVC streams, and keyframe recovery must handle both without allocating.
fn find_annex_b_start_code(data: &[u8], from: usize) -> Option<(usize, usize)> {
let mut index = from;
while index + 3 < data.len() {
if data[index] == 0 && data[index + 1] == 0 {
if data[index + 2] == 1 {
return Some((index, 3));
}
if index + 4 < data.len() && data[index + 2] == 0 && data[index + 3] == 1 {
return Some((index, 4));
}
}
index += 1;
}
None
}
#[cfg(not(coverage))]
/// Decide whether a bundled HEVC packet is safe after a freshness drop.
///
/// Inputs: the recovery state and the next outbound bundle. Output: `true`
/// when the bundle should be held back. Why: sending a non-keyframe immediately
/// after dropping older HEVC packets can make the server decode from missing
/// references, which shows up as tearing or grey corrupted frames.
fn should_hold_hevc_bundle_for_keyframe_recovery(
waiting_for_keyframe: bool,
bundle: &UpstreamMediaBundle,
) -> bool {
waiting_for_keyframe
&& bundle
.video
.as_ref()
.is_some_and(|video| !contains_hevc_irap(&video.data))
}
#[cfg(not(coverage))]
/// Report whether a bundle carries the HEVC recovery frame we were waiting for.
///
/// Inputs: an outbound media bundle. Output: true when its video packet can
/// restart HEVC prediction. Why: the freshness policy should clear recovery
/// mode as soon as the next clean picture reaches the server.
fn bundle_has_hevc_recovery_keyframe(bundle: &UpstreamMediaBundle) -> bool {
bundle
.video
.as_ref()
.is_some_and(|video| contains_hevc_irap(&video.data))
}
#[cfg(not(coverage))]
/// Resolve whether the active upstream camera codec needs HEVC recovery.
///
/// Inputs: the negotiated camera config plus optional env fallback. Output:
/// `true` only for HEVC/H.265. Why: MJPEG frames are independent, so the extra
/// keyframe wait should only apply to predictive video transport.
fn upstream_camera_uses_hevc(camera_cfg: Option<crate::input::camera::CameraConfig>) -> bool {
if camera_cfg.is_some_and(|cfg| matches!(cfg.codec, crate::input::camera::CameraCodec::Hevc)) {
return true;
}
std::env::var("LESAVKA_CAM_CODEC")
.ok()
.map(|value| value.trim().to_ascii_lowercase())
.is_some_and(|value| matches!(value.as_str(), "hevc" | "h265" | "h.265"))
}