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.
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
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, ®istered),
|
||||
[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)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user