mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-01-30 09:31:53 +08:00
refactor(hwcodec): 简化 hwcodec 库以适配 IP-KVM 场景
移除 IP-KVM 场景不需要的功能模块: - 移除 VRAM 模块 (GPU 显存直接编解码) - 移除 Mux 模块 (视频混流) - 移除 macOS/Android 平台支持 - 移除外部 SDK 依赖 (~9MB) - 移除开发工具和示例程序 简化解码器为仅支持 MJPEG (采集卡输出格式) 简化 NVIDIA 检测代码 (使用 dlopen 替代 SDK) 更新版本号至 0.8.0 更新相关技术文档
This commit is contained in:
@@ -1,6 +1,3 @@
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
use super::Priority;
|
||||
use crate::common::TEST_TIMEOUT_MS;
|
||||
use crate::ffmpeg::{init_av_log, AVHWDeviceType::*};
|
||||
|
||||
use crate::{
|
||||
@@ -8,7 +5,7 @@ use crate::{
|
||||
ffmpeg::{AVHWDeviceType, AVPixelFormat},
|
||||
ffmpeg_ram::{
|
||||
ffmpeg_ram_decode, ffmpeg_ram_free_decoder, ffmpeg_ram_new_decoder, CodecInfo,
|
||||
AV_NUM_DATA_POINTERS,
|
||||
AV_NUM_DATA_POINTERS, Priority,
|
||||
},
|
||||
};
|
||||
use log::error;
|
||||
@@ -16,7 +13,6 @@ use std::{
|
||||
ffi::{c_void, CString},
|
||||
os::raw::c_int,
|
||||
slice::from_raw_parts,
|
||||
time::Instant,
|
||||
vec,
|
||||
};
|
||||
|
||||
@@ -179,188 +175,19 @@ impl Decoder {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns available decoders for IP-KVM scenario.
|
||||
/// Only MJPEG software decoder is supported as IP-KVM captures from video capture cards
|
||||
/// that output MJPEG streams.
|
||||
pub fn available_decoders() -> Vec<CodecInfo> {
|
||||
use log::debug;
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut codecs: Vec<CodecInfo> = vec![];
|
||||
// windows disable nvdec to avoid gpu stuck
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let (nv, _, _) = crate::common::supported_gpu(false);
|
||||
debug!("Linux GPU support detected - NV: {}", nv);
|
||||
if nv {
|
||||
codecs.push(CodecInfo {
|
||||
name: "h264".to_owned(),
|
||||
format: H264,
|
||||
hwdevice: AV_HWDEVICE_TYPE_CUDA,
|
||||
priority: Priority::Good as _,
|
||||
..Default::default()
|
||||
});
|
||||
codecs.push(CodecInfo {
|
||||
name: "hevc".to_owned(),
|
||||
format: H265,
|
||||
hwdevice: AV_HWDEVICE_TYPE_CUDA,
|
||||
priority: Priority::Good as _,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
codecs.append(&mut vec![
|
||||
CodecInfo {
|
||||
name: "h264".to_owned(),
|
||||
format: H264,
|
||||
hwdevice: AV_HWDEVICE_TYPE_D3D11VA,
|
||||
priority: Priority::Best as _,
|
||||
..Default::default()
|
||||
},
|
||||
CodecInfo {
|
||||
name: "hevc".to_owned(),
|
||||
format: H265,
|
||||
hwdevice: AV_HWDEVICE_TYPE_D3D11VA,
|
||||
priority: Priority::Best as _,
|
||||
..Default::default()
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
codecs.append(&mut vec![
|
||||
CodecInfo {
|
||||
name: "h264".to_owned(),
|
||||
format: H264,
|
||||
hwdevice: AV_HWDEVICE_TYPE_VAAPI,
|
||||
priority: Priority::Good as _,
|
||||
..Default::default()
|
||||
},
|
||||
CodecInfo {
|
||||
name: "hevc".to_owned(),
|
||||
format: H265,
|
||||
hwdevice: AV_HWDEVICE_TYPE_VAAPI,
|
||||
priority: Priority::Good as _,
|
||||
..Default::default()
|
||||
},
|
||||
CodecInfo {
|
||||
name: "mjpeg".to_owned(),
|
||||
format: MJPEG,
|
||||
hwdevice: AV_HWDEVICE_TYPE_VAAPI,
|
||||
priority: Priority::Good as _,
|
||||
..Default::default()
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let (_, _, h264, h265) = crate::common::get_video_toolbox_codec_support();
|
||||
debug!(
|
||||
"VideoToolbox decode support - H264: {}, H265: {}",
|
||||
h264, h265
|
||||
);
|
||||
if h264 {
|
||||
codecs.push(CodecInfo {
|
||||
name: "h264".to_owned(),
|
||||
format: H264,
|
||||
hwdevice: AV_HWDEVICE_TYPE_VIDEOTOOLBOX,
|
||||
priority: Priority::Best as _,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
if h265 {
|
||||
codecs.push(CodecInfo {
|
||||
name: "hevc".to_owned(),
|
||||
format: H265,
|
||||
hwdevice: AV_HWDEVICE_TYPE_VIDEOTOOLBOX,
|
||||
priority: Priority::Best as _,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let mut res = Vec::<CodecInfo>::new();
|
||||
let buf264 = &crate::common::DATA_H264_720P[..];
|
||||
let buf265 = &crate::common::DATA_H265_720P[..];
|
||||
|
||||
for codec in codecs {
|
||||
// Skip if this format already exists in results
|
||||
if res
|
||||
.iter()
|
||||
.any(|existing: &CodecInfo| existing.format == codec.format)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
debug!(
|
||||
"Testing decoder: {} (hwdevice: {:?})",
|
||||
codec.name, codec.hwdevice
|
||||
);
|
||||
|
||||
let c = DecodeContext {
|
||||
name: codec.name.clone(),
|
||||
device_type: codec.hwdevice,
|
||||
thread_count: 4,
|
||||
};
|
||||
|
||||
match Decoder::new(c) {
|
||||
Ok(mut decoder) => {
|
||||
debug!("Decoder {} created successfully", codec.name);
|
||||
|
||||
// MJPEG decoder doesn't need test data - just verify it can be created
|
||||
if codec.format == MJPEG {
|
||||
debug!("MJPEG decoder {} test passed (creation only)", codec.name);
|
||||
res.push(codec);
|
||||
continue;
|
||||
}
|
||||
|
||||
let data = match codec.format {
|
||||
H264 => buf264,
|
||||
H265 => buf265,
|
||||
_ => {
|
||||
log::error!("Unsupported format: {:?}, skipping", codec.format);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
match decoder.decode(data) {
|
||||
Ok(_) => {
|
||||
let elapsed = start.elapsed().as_millis();
|
||||
|
||||
if elapsed < TEST_TIMEOUT_MS as _ {
|
||||
debug!("Decoder {} test passed", codec.name);
|
||||
res.push(codec);
|
||||
} else {
|
||||
debug!(
|
||||
"Decoder {} test failed - timeout: {}ms",
|
||||
codec.name, elapsed
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
debug!("Decoder {} test failed with error: {}", codec.name, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
debug!("Failed to create decoder {}", codec.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let soft = CodecInfo::soft();
|
||||
if let Some(c) = soft.h264 {
|
||||
res.push(c);
|
||||
}
|
||||
if let Some(c) = soft.h265 {
|
||||
res.push(c);
|
||||
}
|
||||
|
||||
res
|
||||
// IP-KVM scenario only needs MJPEG decoding
|
||||
// MJPEG comes from video capture cards, software decoding is sufficient
|
||||
vec![CodecInfo {
|
||||
name: "mjpeg".to_owned(),
|
||||
format: MJPEG,
|
||||
hwdevice: AV_HWDEVICE_TYPE_NONE,
|
||||
priority: Priority::Best as _,
|
||||
..Default::default()
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
use crate::{
|
||||
common::{
|
||||
DataFormat::{self, *},
|
||||
Quality, RateControl, TEST_TIMEOUT_MS,
|
||||
},
|
||||
common::DataFormat::{self, *},
|
||||
ffmpeg::{init_av_log, AVPixelFormat},
|
||||
ffmpeg_ram::{
|
||||
ffmpeg_linesize_offset_length, ffmpeg_ram_encode, ffmpeg_ram_free_encoder,
|
||||
@@ -21,6 +18,9 @@ use super::Priority;
|
||||
#[cfg(any(windows, target_os = "linux"))]
|
||||
use crate::common::Driver;
|
||||
|
||||
/// Timeout for encoder test in milliseconds
|
||||
const TEST_TIMEOUT_MS: u64 = 3000;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct EncodeContext {
|
||||
pub name: String,
|
||||
@@ -31,8 +31,8 @@ pub struct EncodeContext {
|
||||
pub align: i32,
|
||||
pub fps: i32,
|
||||
pub gop: i32,
|
||||
pub rc: RateControl,
|
||||
pub quality: Quality,
|
||||
pub rc: crate::common::RateControl,
|
||||
pub quality: crate::common::Quality,
|
||||
pub kbs: i32,
|
||||
pub q: i32,
|
||||
pub thread_count: i32,
|
||||
@@ -175,25 +175,15 @@ impl Encoder {
|
||||
pub fn available_encoders(ctx: EncodeContext, _sdk: Option<String>) -> Vec<CodecInfo> {
|
||||
use log::debug;
|
||||
|
||||
if !(cfg!(windows) || cfg!(target_os = "linux") || cfg!(target_os = "macos")) {
|
||||
if !(cfg!(windows) || cfg!(target_os = "linux")) {
|
||||
return vec![];
|
||||
}
|
||||
let mut codecs: Vec<CodecInfo> = vec![];
|
||||
#[cfg(any(windows, target_os = "linux"))]
|
||||
{
|
||||
let contains = |_vendor: Driver, _format: DataFormat| {
|
||||
#[cfg(all(windows, feature = "vram"))]
|
||||
{
|
||||
if let Some(_sdk) = _sdk.as_ref() {
|
||||
if !_sdk.is_empty() {
|
||||
if let Ok(available) =
|
||||
crate::vram::Available::deserialize(_sdk.as_str())
|
||||
{
|
||||
return available.contains(true, _vendor, _format);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Without VRAM feature, we can't check SDK availability
|
||||
// Just return true and let FFmpeg handle the actual detection
|
||||
true
|
||||
};
|
||||
let (_nv, amf, _intel) = crate::common::supported_gpu(true);
|
||||
@@ -245,7 +235,6 @@ impl Encoder {
|
||||
});
|
||||
}
|
||||
if amf {
|
||||
// sdk not use h265
|
||||
codecs.push(CodecInfo {
|
||||
name: "hevc_amf".to_owned(),
|
||||
format: H265,
|
||||
@@ -322,28 +311,6 @@ impl Encoder {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let (_h264, h265, _, _) = crate::common::get_video_toolbox_codec_support();
|
||||
// h264 encode failed too often, not AV_CODEC_CAP_HARDWARE
|
||||
// if h264 {
|
||||
// codecs.push(CodecInfo {
|
||||
// name: "h264_videotoolbox".to_owned(),
|
||||
// format: H264,
|
||||
// priority: Priority::Best as _,
|
||||
// ..Default::default()
|
||||
// });
|
||||
// }
|
||||
if h265 {
|
||||
codecs.push(CodecInfo {
|
||||
name: "hevc_videotoolbox".to_owned(),
|
||||
format: H265,
|
||||
priority: Priority::Best as _,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// qsv doesn't support yuv420p
|
||||
codecs.retain(|c| {
|
||||
let ctx = ctx.clone();
|
||||
@@ -379,11 +346,7 @@ impl Encoder {
|
||||
let mut passed = false;
|
||||
let mut last_err: Option<i32> = None;
|
||||
|
||||
let max_attempts = if cfg!(all(target_os = "macos", target_arch = "x86_64")) {
|
||||
3
|
||||
} else {
|
||||
1
|
||||
};
|
||||
let max_attempts = 1;
|
||||
for attempt in 0..max_attempts {
|
||||
let pts = (attempt as i64) * 33; // 33ms is an approximation for 30 FPS (1000 / 30)
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
Reference in New Issue
Block a user