91 lines
3.5 KiB
Rust
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"))
|
|
}
|