mirror of
https://github.com/Xinrea/bili-shadowreplay.git
synced 2025-11-25 04:22:24 +08:00
fix: check encoders list (#218)
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
use std::{collections::HashSet, process::Stdio};
|
||||
use std::{collections::HashSet, process::Stdio, sync::OnceLock};
|
||||
|
||||
use tokio::io::AsyncReadExt;
|
||||
|
||||
@@ -14,6 +14,9 @@ const TARGET_ENCODERS: [&str; 7] = [
|
||||
"h264_v4l2m2m",
|
||||
];
|
||||
|
||||
// 缓存选中的编码器,避免重复检查
|
||||
static ENCODER_CACHE: OnceLock<String> = OnceLock::new();
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
const CREATE_NO_WINDOW: u32 = 0x08000000;
|
||||
#[cfg(target_os = "windows")]
|
||||
@@ -98,46 +101,130 @@ pub async fn list_supported_hwaccels() -> Result<Vec<String>, String> {
|
||||
Ok(hwaccels)
|
||||
}
|
||||
|
||||
/// 依据优先级从支持列表中挑选推荐的硬件编码器。
|
||||
/// 测试指定的编码器是否在当前硬件上真正可用
|
||||
///
|
||||
/// 当前优先级顺序:`h264_nvenc` > `h264_videotoolbox` > `h264_qsv` > `h264_amf` > `h264_mf` > `h264_vaapi` > `h264_v4l2m2m`。
|
||||
pub fn select_preferred_hwaccel(supported: &[String]) -> Option<&'static str> {
|
||||
const PRIORITY: [&str; 7] = [
|
||||
"h264_nvenc",
|
||||
"h264_videotoolbox",
|
||||
"h264_qsv",
|
||||
"h264_amf",
|
||||
"h264_mf",
|
||||
"h264_vaapi",
|
||||
"h264_v4l2m2m",
|
||||
];
|
||||
/// 通过尝试对测试流进行编码来验证编码器可用性
|
||||
async fn test_encoder_availability(encoder: &str) -> bool {
|
||||
let mut command = tokio::process::Command::new(ffmpeg_path());
|
||||
|
||||
PRIORITY
|
||||
.iter()
|
||||
.find(|candidate| {
|
||||
supported
|
||||
.iter()
|
||||
.any(|value| value.eq_ignore_ascii_case(candidate))
|
||||
})
|
||||
.copied()
|
||||
#[cfg(target_os = "windows")]
|
||||
command.creation_flags(CREATE_NO_WINDOW);
|
||||
|
||||
// 使用合成输入源 (testsrc2) 测试编码器
|
||||
// -t 0.1 只编码0.1秒,-frames:v 3 只编码3帧,快速测试
|
||||
// -f null 丢弃输出,不需要实际文件
|
||||
let child = command
|
||||
.arg("-hide_banner")
|
||||
.arg("-loglevel")
|
||||
.arg("error")
|
||||
.arg("-f")
|
||||
.arg("lavfi")
|
||||
.arg("-i")
|
||||
.arg("testsrc2=duration=0.1:size=320x240:rate=1")
|
||||
.arg("-c:v")
|
||||
.arg(encoder)
|
||||
.arg("-frames:v")
|
||||
.arg("3")
|
||||
.arg("-f")
|
||||
.arg("null")
|
||||
.arg("-")
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn();
|
||||
|
||||
match child {
|
||||
Ok(process) => {
|
||||
let output = process.wait_with_output().await;
|
||||
match output {
|
||||
Ok(output) => {
|
||||
// 如果退出码为0,说明编码器可用
|
||||
if output.status.success() {
|
||||
log::debug!("Encoder {encoder} is available");
|
||||
true
|
||||
} else {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
log::debug!("Encoder {encoder} failed: {stderr}");
|
||||
false
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::debug!("Encoder {encoder} test error: {err}");
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::debug!("Failed to spawn ffmpeg process to test {encoder}: {err}");
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the preferred hardware encoder for x264
|
||||
///
|
||||
/// Returns the preferred hardware encoder for x264, or "libx264" if no hardware acceleration is available.
|
||||
/// This function not only checks if the encoder is compiled into ffmpeg, but also verifies it's actually
|
||||
/// usable on the current hardware.
|
||||
///
|
||||
/// The result is cached to avoid repeated checks during the program's lifetime.
|
||||
pub async fn get_x264_encoder() -> &'static str {
|
||||
let mut encoder = "libx264";
|
||||
match list_supported_hwaccels().await {
|
||||
// 先检查缓存,如果已存在直接返回
|
||||
if let Some(encoder) = ENCODER_CACHE.get() {
|
||||
return encoder.as_str();
|
||||
}
|
||||
|
||||
// 执行硬件编码器检测和验证
|
||||
let encoder = match list_supported_hwaccels().await {
|
||||
Ok(hwaccels) => {
|
||||
if let Some(arg) = select_preferred_hwaccel(&hwaccels) {
|
||||
encoder = arg;
|
||||
// 按优先级顺序测试每个硬件编码器
|
||||
const PRIORITY: [&str; 7] = [
|
||||
"h264_nvenc",
|
||||
"h264_videotoolbox",
|
||||
"h264_qsv",
|
||||
"h264_amf",
|
||||
"h264_mf",
|
||||
"h264_vaapi",
|
||||
"h264_v4l2m2m",
|
||||
];
|
||||
|
||||
let mut selected = None;
|
||||
for &candidate in &PRIORITY {
|
||||
// 检查编码器是否在支持列表中
|
||||
if hwaccels
|
||||
.iter()
|
||||
.any(|value| value.eq_ignore_ascii_case(candidate))
|
||||
{
|
||||
// 测试编码器在实际硬件上是否可用
|
||||
if test_encoder_availability(candidate).await {
|
||||
log::info!("Found available hardware encoder: {candidate}");
|
||||
selected = Some(candidate.to_string());
|
||||
break;
|
||||
} else {
|
||||
log::debug!("Hardware encoder {candidate} is compiled in but not usable");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selected.unwrap_or_else(|| {
|
||||
log::info!("No usable hardware encoder found, falling back to libx264");
|
||||
"libx264".to_string()
|
||||
})
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!("Failed to query hardware encoders: {err}");
|
||||
"libx264".to_string()
|
||||
}
|
||||
};
|
||||
|
||||
log::info!("Selected x264 encoder: {}", encoder);
|
||||
|
||||
// 存入缓存,如果设置成功则从缓存返回,否则返回刚才得到的值
|
||||
// 注意:set() 可能被其他线程抢先,但每个线程都会得到相同的 encoder 值
|
||||
match ENCODER_CACHE.set(encoder.clone()) {
|
||||
Ok(_) => ENCODER_CACHE.get().unwrap().as_str(),
|
||||
Err(_) => {
|
||||
// 其他线程已经设置了,返回缓存的值
|
||||
ENCODER_CACHE.get().unwrap().as_str()
|
||||
}
|
||||
}
|
||||
|
||||
log::info!("Selected x264 encoder: {encoder}");
|
||||
encoder
|
||||
}
|
||||
|
||||
@@ -1315,25 +1315,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_select_preferred_hwaccel() {
|
||||
let cases = vec![
|
||||
(vec!["h264_nvenc", "h264_vaapi"], Some("h264_nvenc")),
|
||||
(
|
||||
vec!["h264_videotoolbox", "h264_qsv"],
|
||||
Some("h264_videotoolbox"),
|
||||
),
|
||||
(vec!["h264_vaapi"], Some("h264_vaapi")),
|
||||
(vec!["h264_v4l2m2m"], Some("h264_v4l2m2m")),
|
||||
(vec!["libx264"], None),
|
||||
];
|
||||
|
||||
for (inputs, expected) in cases {
|
||||
let inputs = inputs.into_iter().map(String::from).collect::<Vec<_>>();
|
||||
assert_eq!(super::hwaccel::select_preferred_hwaccel(&inputs), expected);
|
||||
}
|
||||
}
|
||||
|
||||
// 测试字幕生成错误处理
|
||||
#[tokio::test]
|
||||
async fn test_generate_video_subtitle_errors() {
|
||||
|
||||
Reference in New Issue
Block a user