#![forbid(unsafe_code)] use gstreamer as gst; /// Pick the client-side H.264 decoder in a predictable preference order. /// /// Inputs: none, though operators may override the choice with /// `LESAVKA_H264_DECODER=` or bias automatic fallback order with /// `LESAVKA_H264_DECODER_PREFERENCE=hardware|software`. /// Outputs: the chosen decoder element name, or `decodebin` as a last-resort /// fallback when no explicit decoder is present. /// Why: Lesavka should use GPU decode on NVIDIA/Vulkan/VAAPI/V4L2-capable clients /// when possible, while keeping an explicit CPU route for open-source driver /// comparisons and driver debugging. #[must_use] pub fn pick_h264_decoder() -> String { if let Ok(raw) = std::env::var("LESAVKA_H264_DECODER") { let name = raw.trim(); if name.eq_ignore_ascii_case("decodebin") { return "decodebin".to_string(); } if !name.is_empty() && buildable_decoder(name) { return name.to_string(); } } for name in h264_decoder_preference_order() { if buildable_decoder(name) { return name.to_string(); } } "decodebin".to_string() } /// Return automatic H.264 decoder candidates in selection order. /// /// Inputs: `LESAVKA_H264_DECODER_PREFERENCE`, if set. Output: ordered decoder /// element names. Why: tests and diagnostics need to prove both proprietary /// NVIDIA, Vulkan, and VAAPI/V4L2 routes stay available before CPU fallback. #[must_use] pub fn h264_decoder_preference_order() -> Vec<&'static str> { const HARDWARE: &[&str] = &[ "nvh264dec", "nvh264sldec", "vulkanh264dec", "vah264dec", "vaapih264dec", "v4l2h264dec", "v4l2slh264dec", ]; const SOFTWARE: &[&str] = &["avdec_h264", "openh264dec"]; let prefer_software = std::env::var("LESAVKA_H264_DECODER_PREFERENCE") .ok() .map(|value| { matches!( value.trim().to_ascii_lowercase().as_str(), "software" | "sw" | "cpu" ) }) .unwrap_or(false); let mut candidates = Vec::with_capacity(HARDWARE.len() + SOFTWARE.len()); if prefer_software { candidates.extend_from_slice(SOFTWARE); candidates.extend_from_slice(HARDWARE); } else { candidates.extend_from_slice(HARDWARE); candidates.extend_from_slice(SOFTWARE); } candidates } /// Return a parse-launch fragment for the selected H.264 decoder. /// /// Inputs: decoder element name. Output: a pipeline fragment with a stable /// `decoder` element name. Why: Vulkan decoders output GPU memory, so they need /// an explicit download step before the existing CPU-side sinks can consume /// frames; keeping that in one helper prevents hardware decode from being /// selected and then immediately failing link negotiation. #[must_use] pub fn h264_decoder_launch_fragment(decoder_name: &str) -> String { h264_decoder_launch_fragment_named(decoder_name, "decoder") } /// Return a parse-launch fragment for the selected H.264 decoder with a caller-owned element name. /// /// Inputs: decoder element name plus the element name to put in the pipeline. /// Output: a pipeline fragment. Why: unified downstream rendering needs two /// independent decoder elements, while Vulkan still needs an explicit /// download-to-system-memory stage after each decoder. #[must_use] pub fn h264_decoder_launch_fragment_named(decoder_name: &str, element_name: &str) -> String { match decoder_name { "vulkanh264dec" => concat!( "vulkanh264dec name={element_name} discard-corrupted-frames=true ", "automatic-request-sync-points=true ! vulkandownload" ) .replace("{element_name}", element_name), name => format!("{name} name={element_name}"), } } fn buildable_decoder(name: &str) -> bool { #[cfg(coverage)] if std::env::var("TEST_FAIL_GST_INIT").is_ok() { return false; } if gst::init().is_err() { return false; } #[cfg(coverage)] if std::env::var("TEST_DISABLE_H264_DECODER_FACTORY").is_ok() { return false; } gst::ElementFactory::find(name).is_some() && gst::ElementFactory::make(name).build().is_ok() }