Files
clipbin-desktop/crates/clipbin-obs/src/encoders.rs
T
harry f2eb7139da feat: add OBS recording engine and validation probe
clipbin-obs starts the pinned libobs on a dedicated thread, opens exactly the
bundled plugin allowlist, and records official capture sources through one
hardware encoder into keyframe-aligned Matroska segments. Status reaches
Recording only once the source has a picture and the segment grows on disk.

clipbin-probe lists and verifies hardware encoders and records from a real
capture source, printing status, health and segment events for validation.
2026-10-08 14:12:06 +02:00

110 lines
3.2 KiB
Rust

use clipbin_core::VideoCodec;
/// Official OBS hardware encoders for each codec, most preferred first.
///
/// Texture-based encoders come first because they take frames straight from
/// the GPU; their counterparts copy each frame through system memory. Software
/// encoders are deliberately absent: a codec without a working hardware
/// encoder is unavailable, never silently software-encoded.
pub fn hardware_candidates(codec: VideoCodec) -> &'static [&'static str] {
#[cfg(windows)]
return match codec {
VideoCodec::H264 => &[
"obs_nvenc_h264_tex",
"h264_texture_amf",
"obs_qsv11_v2",
"obs_nvenc_h264_soft",
"obs_qsv11_soft_v2",
"h264_fallback_amf",
],
VideoCodec::Hevc => &[
"obs_nvenc_hevc_tex",
"h265_texture_amf",
"obs_qsv11_hevc",
"obs_nvenc_hevc_soft",
"obs_qsv11_hevc_soft",
"h265_fallback_amf",
],
VideoCodec::Av1 => &[
"obs_nvenc_av1_tex",
"av1_texture_amf",
"obs_qsv11_av1",
"obs_nvenc_av1_soft",
"obs_qsv11_av1_soft",
"av1_fallback_amf",
],
};
#[cfg(not(windows))]
return match codec {
VideoCodec::H264 => &[
"obs_nvenc_h264_tex",
"ffmpeg_vaapi_tex",
"obs_nvenc_h264_soft",
"ffmpeg_vaapi",
],
VideoCodec::Hevc => &[
"obs_nvenc_hevc_tex",
"hevc_ffmpeg_vaapi_tex",
"obs_nvenc_hevc_soft",
"hevc_ffmpeg_vaapi",
],
VideoCodec::Av1 => &[
"obs_nvenc_av1_tex",
"av1_ffmpeg_vaapi_tex",
"obs_nvenc_av1_soft",
"av1_ffmpeg_vaapi",
],
};
}
/// Registered encoders for `codec`, in preference order.
///
/// Registration only means the plugin loaded. Whether the device can actually
/// encode is proven by starting an output; see `Recorder`.
pub fn registered_candidates<'a>(
codec: VideoCodec,
registered: &[impl AsRef<str>],
) -> Vec<&'a str> {
hardware_candidates(codec)
.iter()
.copied()
.filter(|id| registered.iter().any(|r| r.as_ref() == *id))
.collect()
}
pub const ALL_CODECS: [VideoCodec; 3] = [VideoCodec::H264, VideoCodec::Hevc, VideoCodec::Av1];
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn candidates_keep_preference_order_and_skip_unregistered() {
let first = hardware_candidates(VideoCodec::Hevc)[0];
let third = hardware_candidates(VideoCodec::Hevc)[2];
let registered = [third, "obs_x264", first];
assert_eq!(
registered_candidates(VideoCodec::Hevc, &registered),
[first, third]
);
}
#[test]
fn software_encoders_are_never_candidates() {
for codec in ALL_CODECS {
for id in hardware_candidates(codec) {
assert!(
![
"obs_x264",
"ffmpeg_svt_av1",
"ffmpeg_aom_av1",
"ffmpeg_openh264"
]
.contains(id)
);
}
}
}
}