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.
110 lines
3.2 KiB
Rust
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, ®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)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|