mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-01-28 16:41:52 +08:00
refactor(hwcodec): 精简FFmpeg编译配置并移除解码器
- 优化FFmpeg编译选项,禁用不需要的库(avformat/swscale/swresample/avfilter等) - 禁用所有解码器和大部分编码器,只保留实际使用的H264/H265/VP8/VP9编码器 - 移除hwcodec解码器模块,MJPEG解码改用libyuv实现 - 移除MJPEG编码器支持 - x86_64添加libmfx支持QSV编码器 - 修复H265 RKMPP编码器支持YUYV直接输入
This commit is contained in:
@@ -1,36 +1,29 @@
|
||||
//! MJPEG VAAPI hardware decoder
|
||||
//! MJPEG decoder implementations
|
||||
//!
|
||||
//! Uses hwcodec's FFmpeg VAAPI backend to decode MJPEG to NV12.
|
||||
//! This provides hardware-accelerated JPEG decoding with direct NV12 output,
|
||||
//! which is the optimal format for VAAPI H264 encoding.
|
||||
//! Provides MJPEG decoding using libyuv for SIMD-accelerated decoding.
|
||||
//! All decoders output to standard YUV formats suitable for encoding.
|
||||
|
||||
use std::sync::Once;
|
||||
use tracing::{debug, info, warn};
|
||||
|
||||
use hwcodec::ffmpeg::AVHWDeviceType;
|
||||
use hwcodec::ffmpeg::AVPixelFormat;
|
||||
use hwcodec::ffmpeg_ram::decode::{DecodeContext, DecodeFrame, Decoder};
|
||||
use tracing::{debug, info};
|
||||
|
||||
use crate::error::{AppError, Result};
|
||||
use crate::video::format::Resolution;
|
||||
|
||||
// libyuv for SIMD-accelerated YUV conversion
|
||||
|
||||
static INIT_LOGGING: Once = Once::new();
|
||||
|
||||
/// Initialize hwcodec logging (only once)
|
||||
fn init_hwcodec_logging() {
|
||||
/// Initialize decoder logging (only once)
|
||||
fn init_decoder_logging() {
|
||||
INIT_LOGGING.call_once(|| {
|
||||
debug!("hwcodec MJPEG decoder logging initialized");
|
||||
debug!("MJPEG decoder logging initialized");
|
||||
});
|
||||
}
|
||||
|
||||
/// MJPEG VAAPI decoder configuration
|
||||
/// MJPEG decoder configuration
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MjpegVaapiDecoderConfig {
|
||||
/// Expected resolution (can be updated from decoded frame)
|
||||
pub resolution: Resolution,
|
||||
/// Use hardware acceleration (VAAPI)
|
||||
/// Use hardware acceleration (ignored, kept for API compatibility)
|
||||
pub use_hwaccel: bool,
|
||||
}
|
||||
|
||||
@@ -266,62 +259,34 @@ impl DecodedNv12Frame {
|
||||
}
|
||||
}
|
||||
|
||||
/// MJPEG VAAPI hardware decoder
|
||||
/// MJPEG decoder with NV12 output
|
||||
///
|
||||
/// Decodes MJPEG frames to NV12 format using VAAPI hardware acceleration.
|
||||
/// This is optimal for feeding into VAAPI H264 encoder.
|
||||
/// Uses libyuv for SIMD-accelerated MJPEG decoding to YUV420P,
|
||||
/// then converts to NV12 for hardware encoder compatibility.
|
||||
/// Named "VaapiDecoder" for API compatibility with existing code.
|
||||
pub struct MjpegVaapiDecoder {
|
||||
/// hwcodec decoder instance
|
||||
decoder: Decoder,
|
||||
/// Configuration
|
||||
config: MjpegVaapiDecoderConfig,
|
||||
/// Frame counter
|
||||
frame_count: u64,
|
||||
/// Whether hardware acceleration is active
|
||||
hwaccel_active: bool,
|
||||
}
|
||||
|
||||
impl MjpegVaapiDecoder {
|
||||
/// Create a new MJPEG decoder
|
||||
/// Note: VAAPI does not support MJPEG decoding on most hardware,
|
||||
/// so we use software decoding and convert to NV12 for VAAPI encoding.
|
||||
pub fn new(config: MjpegVaapiDecoderConfig) -> Result<Self> {
|
||||
init_hwcodec_logging();
|
||||
|
||||
// VAAPI doesn't support MJPEG decoding, always use software decoder
|
||||
// The output will be converted to NV12 for VAAPI H264 encoding
|
||||
let device_type = AVHWDeviceType::AV_HWDEVICE_TYPE_NONE;
|
||||
init_decoder_logging();
|
||||
|
||||
info!(
|
||||
"Creating MJPEG decoder with software decoding (VAAPI doesn't support MJPEG decode)"
|
||||
);
|
||||
|
||||
let ctx = DecodeContext {
|
||||
name: "mjpeg".to_string(),
|
||||
device_type,
|
||||
thread_count: 4, // Use multiple threads for software decoding
|
||||
};
|
||||
|
||||
let decoder = Decoder::new(ctx).map_err(|_| {
|
||||
AppError::VideoError("Failed to create MJPEG software decoder".to_string())
|
||||
})?;
|
||||
|
||||
// hwaccel is not actually active for MJPEG decoding
|
||||
let hwaccel_active = false;
|
||||
|
||||
info!(
|
||||
"MJPEG decoder created successfully (software decode, will convert to NV12)"
|
||||
"Creating MJPEG decoder with libyuv (SIMD-accelerated, NV12 output)"
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
decoder,
|
||||
config,
|
||||
frame_count: 0,
|
||||
hwaccel_active,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create with default config (VAAPI enabled)
|
||||
/// Create with default config
|
||||
pub fn with_vaapi(resolution: Resolution) -> Result<Self> {
|
||||
Self::new(MjpegVaapiDecoderConfig {
|
||||
resolution,
|
||||
@@ -329,7 +294,7 @@ impl MjpegVaapiDecoder {
|
||||
})
|
||||
}
|
||||
|
||||
/// Create with software decoding (fallback)
|
||||
/// Create with software decoding (same as with_vaapi, kept for API compatibility)
|
||||
pub fn with_software(resolution: Resolution) -> Result<Self> {
|
||||
Self::new(MjpegVaapiDecoderConfig {
|
||||
resolution,
|
||||
@@ -337,9 +302,9 @@ impl MjpegVaapiDecoder {
|
||||
})
|
||||
}
|
||||
|
||||
/// Check if hardware acceleration is active
|
||||
/// Check if hardware acceleration is active (always false, using libyuv)
|
||||
pub fn is_hwaccel_active(&self) -> bool {
|
||||
self.hwaccel_active
|
||||
false
|
||||
}
|
||||
|
||||
/// Decode MJPEG frame to NV12
|
||||
@@ -357,130 +322,27 @@ impl MjpegVaapiDecoder {
|
||||
|
||||
self.frame_count += 1;
|
||||
|
||||
let frames = self.decoder.decode(jpeg_data).map_err(|e| {
|
||||
AppError::VideoError(format!("MJPEG decode failed: error code {}", e))
|
||||
})?;
|
||||
// Get JPEG dimensions
|
||||
let (width, height) = libyuv::mjpeg_size(jpeg_data)
|
||||
.map_err(|e| AppError::VideoError(format!("Failed to read MJPEG size: {}", e)))?;
|
||||
|
||||
if frames.is_empty() {
|
||||
return Err(AppError::VideoError("Decoder returned no frames".to_string()));
|
||||
}
|
||||
|
||||
let frame = &frames[0];
|
||||
|
||||
// Handle different output formats
|
||||
// VAAPI MJPEG decoder may output NV12, YUV420P, or YUVJ420P (JPEG full-range)
|
||||
if frame.pixfmt == AVPixelFormat::AV_PIX_FMT_NV12
|
||||
|| frame.pixfmt == AVPixelFormat::AV_PIX_FMT_NV21
|
||||
{
|
||||
// NV12/NV21 format: Y plane + UV interleaved plane
|
||||
if frame.data.len() < 2 {
|
||||
return Err(AppError::VideoError("Invalid NV12 frame data".to_string()));
|
||||
}
|
||||
|
||||
return Ok(DecodedNv12Frame {
|
||||
y_plane: frame.data[0].clone(),
|
||||
uv_plane: frame.data[1].clone(),
|
||||
y_linesize: frame.linesize[0],
|
||||
uv_linesize: frame.linesize[1],
|
||||
width: frame.width,
|
||||
height: frame.height,
|
||||
});
|
||||
}
|
||||
|
||||
// YUV420P or YUVJ420P (JPEG full-range) - need to convert to NV12
|
||||
if frame.pixfmt == AVPixelFormat::AV_PIX_FMT_YUV420P
|
||||
|| frame.pixfmt == AVPixelFormat::AV_PIX_FMT_YUVJ420P
|
||||
{
|
||||
return Self::convert_yuv420p_to_nv12_static(frame);
|
||||
}
|
||||
|
||||
// YUV422P or YUVJ422P (JPEG full-range 4:2:2) - need to convert to NV12
|
||||
if frame.pixfmt == AVPixelFormat::AV_PIX_FMT_YUV422P
|
||||
|| frame.pixfmt == AVPixelFormat::AV_PIX_FMT_YUVJ422P
|
||||
{
|
||||
return Self::convert_yuv422p_to_nv12_static(frame);
|
||||
}
|
||||
|
||||
Err(AppError::VideoError(format!(
|
||||
"Unexpected output format: {:?} (expected NV12, YUV420P, YUV422P, or YUVJ variants)",
|
||||
frame.pixfmt
|
||||
)))
|
||||
}
|
||||
|
||||
/// Convert YUV420P frame to NV12 format using libyuv (SIMD accelerated)
|
||||
fn convert_yuv420p_to_nv12_static(frame: &DecodeFrame) -> Result<DecodedNv12Frame> {
|
||||
if frame.data.len() < 3 {
|
||||
return Err(AppError::VideoError("Invalid YUV420P frame data".to_string()));
|
||||
}
|
||||
|
||||
let width = frame.width as i32;
|
||||
let height = frame.height as i32;
|
||||
let y_linesize = frame.linesize[0];
|
||||
let u_linesize = frame.linesize[1];
|
||||
let v_linesize = frame.linesize[2];
|
||||
|
||||
// Allocate packed NV12 output buffer
|
||||
let nv12_size = (width * height * 3 / 2) as usize;
|
||||
let mut nv12_data = vec![0u8; nv12_size];
|
||||
|
||||
// Use libyuv for SIMD-accelerated I420 → NV12 conversion
|
||||
libyuv::i420_to_nv12_planar(
|
||||
&frame.data[0], y_linesize,
|
||||
&frame.data[1], u_linesize,
|
||||
&frame.data[2], v_linesize,
|
||||
&mut nv12_data,
|
||||
width, height,
|
||||
).map_err(|e| AppError::VideoError(format!("libyuv I420→NV12 failed: {}", e)))?;
|
||||
|
||||
// Split into Y and UV planes for DecodedNv12Frame
|
||||
// Decode MJPEG to YUV420P first
|
||||
let y_size = (width * height) as usize;
|
||||
let y_plane = nv12_data[..y_size].to_vec();
|
||||
let uv_plane = nv12_data[y_size..].to_vec();
|
||||
let uv_size = y_size / 4;
|
||||
let yuv420_size = y_size + uv_size * 2;
|
||||
let mut yuv_data = vec![0u8; yuv420_size];
|
||||
|
||||
Ok(DecodedNv12Frame {
|
||||
y_plane,
|
||||
uv_plane,
|
||||
y_linesize: width, // Output is packed, no padding
|
||||
uv_linesize: width,
|
||||
width: frame.width,
|
||||
height: frame.height,
|
||||
})
|
||||
}
|
||||
libyuv::mjpeg_to_i420(jpeg_data, &mut yuv_data, width, height)
|
||||
.map_err(|e| AppError::VideoError(format!("libyuv MJPEG→I420 failed: {}", e)))?;
|
||||
|
||||
/// Convert YUV422P frame to NV12 format using libyuv (SIMD accelerated)
|
||||
/// Pipeline: I422 (YUV422P) → I420 → NV12
|
||||
fn convert_yuv422p_to_nv12_static(frame: &DecodeFrame) -> Result<DecodedNv12Frame> {
|
||||
if frame.data.len() < 3 {
|
||||
return Err(AppError::VideoError("Invalid YUV422P frame data".to_string()));
|
||||
}
|
||||
|
||||
let width = frame.width as i32;
|
||||
let height = frame.height as i32;
|
||||
let y_linesize = frame.linesize[0];
|
||||
let u_linesize = frame.linesize[1];
|
||||
let v_linesize = frame.linesize[2];
|
||||
|
||||
// Step 1: I422 → I420 (vertical chroma downsampling via SIMD)
|
||||
let i420_size = (width * height * 3 / 2) as usize;
|
||||
let mut i420_data = vec![0u8; i420_size];
|
||||
|
||||
libyuv::i422_to_i420_planar(
|
||||
&frame.data[0], y_linesize,
|
||||
&frame.data[1], u_linesize,
|
||||
&frame.data[2], v_linesize,
|
||||
&mut i420_data,
|
||||
width, height,
|
||||
).map_err(|e| AppError::VideoError(format!("libyuv I422→I420 failed: {}", e)))?;
|
||||
|
||||
// Step 2: I420 → NV12 (UV interleaving via SIMD)
|
||||
// Convert I420 to NV12
|
||||
let nv12_size = (width * height * 3 / 2) as usize;
|
||||
let mut nv12_data = vec![0u8; nv12_size];
|
||||
|
||||
libyuv::i420_to_nv12(&i420_data, &mut nv12_data, width, height)
|
||||
libyuv::i420_to_nv12(&yuv_data, &mut nv12_data, width, height)
|
||||
.map_err(|e| AppError::VideoError(format!("libyuv I420→NV12 failed: {}", e)))?;
|
||||
|
||||
// Split into Y and UV planes for DecodedNv12Frame
|
||||
let y_size = (width * height) as usize;
|
||||
// Split into Y and UV planes
|
||||
let y_plane = nv12_data[..y_size].to_vec();
|
||||
let uv_plane = nv12_data[y_size..].to_vec();
|
||||
|
||||
@@ -489,8 +351,8 @@ impl MjpegVaapiDecoder {
|
||||
uv_plane,
|
||||
y_linesize: width,
|
||||
uv_linesize: width,
|
||||
width: frame.width,
|
||||
height: frame.height,
|
||||
width,
|
||||
height,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -600,36 +462,10 @@ impl MjpegTurboDecoder {
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if MJPEG VAAPI decoder is available
|
||||
pub fn is_mjpeg_vaapi_available() -> bool {
|
||||
let ctx = DecodeContext {
|
||||
name: "mjpeg".to_string(),
|
||||
device_type: AVHWDeviceType::AV_HWDEVICE_TYPE_VAAPI,
|
||||
thread_count: 1,
|
||||
};
|
||||
|
||||
match Decoder::new(ctx) {
|
||||
Ok(_) => {
|
||||
info!("MJPEG VAAPI decoder is available");
|
||||
true
|
||||
}
|
||||
Err(_) => {
|
||||
warn!("MJPEG VAAPI decoder is not available");
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_mjpeg_vaapi_availability() {
|
||||
let available = is_mjpeg_vaapi_available();
|
||||
println!("MJPEG VAAPI available: {}", available);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decoder_creation() {
|
||||
let config = MjpegVaapiDecoderConfig::default();
|
||||
|
||||
@@ -92,6 +92,8 @@ pub enum H265InputFormat {
|
||||
Yuv420p,
|
||||
/// NV12 - Y plane + interleaved UV plane (optimal for hardware encoders)
|
||||
Nv12,
|
||||
/// YUYV422 - packed YUV 4:2:2 format (optimal for RKMPP direct input)
|
||||
Yuyv422,
|
||||
}
|
||||
|
||||
impl Default for H265InputFormat {
|
||||
@@ -145,6 +147,23 @@ impl H265Config {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create config for low latency streaming with YUYV422 input (optimal for RKMPP direct input)
|
||||
pub fn low_latency_yuyv422(resolution: Resolution, bitrate_kbps: u32) -> Self {
|
||||
Self {
|
||||
base: EncoderConfig {
|
||||
resolution,
|
||||
input_format: PixelFormat::Yuyv,
|
||||
quality: bitrate_kbps,
|
||||
fps: 30,
|
||||
gop_size: 30,
|
||||
},
|
||||
bitrate_kbps,
|
||||
gop_size: 30,
|
||||
fps: 30,
|
||||
input_format: H265InputFormat::Yuyv422,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create config for quality streaming
|
||||
pub fn quality(resolution: Resolution, bitrate_kbps: u32) -> Self {
|
||||
Self {
|
||||
@@ -311,13 +330,14 @@ impl H265Encoder {
|
||||
let width = config.base.resolution.width;
|
||||
let height = config.base.resolution.height;
|
||||
|
||||
// Software encoders (libx265) require YUV420P, hardware encoders use NV12
|
||||
// Software encoders (libx265) require YUV420P, hardware encoders use NV12 or YUYV422
|
||||
let (pixfmt, actual_input_format) = if is_software {
|
||||
(AVPixelFormat::AV_PIX_FMT_YUV420P, H265InputFormat::Yuv420p)
|
||||
} else {
|
||||
match config.input_format {
|
||||
H265InputFormat::Nv12 => (AVPixelFormat::AV_PIX_FMT_NV12, H265InputFormat::Nv12),
|
||||
H265InputFormat::Yuv420p => (AVPixelFormat::AV_PIX_FMT_YUV420P, H265InputFormat::Yuv420p),
|
||||
H265InputFormat::Yuyv422 => (AVPixelFormat::AV_PIX_FMT_YUYV422, H265InputFormat::Yuyv422),
|
||||
}
|
||||
};
|
||||
|
||||
@@ -548,6 +568,7 @@ impl Encoder for H265Encoder {
|
||||
match self.config.input_format {
|
||||
H265InputFormat::Nv12 => matches!(format, PixelFormat::Nv12),
|
||||
H265InputFormat::Yuv420p => matches!(format, PixelFormat::Yuv420),
|
||||
H265InputFormat::Yuyv422 => matches!(format, PixelFormat::Yuyv),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -411,9 +411,22 @@ impl SharedVideoPipeline {
|
||||
Box::new(H264EncoderWrapper(encoder))
|
||||
}
|
||||
VideoEncoderType::H265 => {
|
||||
let encoder_config = H265Config::low_latency(config.resolution, config.bitrate_kbps);
|
||||
// Determine H265 input format based on backend and input format
|
||||
let encoder_config = if use_yuyv_direct {
|
||||
H265Config::low_latency_yuyv422(config.resolution, config.bitrate_kbps)
|
||||
} else {
|
||||
H265Config::low_latency(config.resolution, config.bitrate_kbps)
|
||||
};
|
||||
|
||||
let encoder = if let Some(ref backend) = config.encoder_backend {
|
||||
let encoder = if use_yuyv_direct {
|
||||
// Force RKMPP backend for YUYV direct input
|
||||
let codec_name = get_codec_name(VideoEncoderType::H265, Some(EncoderBackend::Rkmpp))
|
||||
.ok_or_else(|| AppError::VideoError(
|
||||
"RKMPP backend not available for H.265".to_string()
|
||||
))?;
|
||||
info!("Creating H265 encoder with RKMPP backend for YUYV direct input (codec: {})", codec_name);
|
||||
H265Encoder::with_codec(encoder_config, &codec_name)?
|
||||
} else if let Some(ref backend) = config.encoder_backend {
|
||||
let codec_name = get_codec_name(VideoEncoderType::H265, Some(*backend))
|
||||
.ok_or_else(|| AppError::VideoError(format!(
|
||||
"Backend {:?} does not support H.265", backend
|
||||
|
||||
Reference in New Issue
Block a user