mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-02-02 02:51:53 +08:00
feat: 支持 MJPEG 解码与 MSD 目录配置
- FFmpeg/hwcodec 增加 RKMPP MJPEG 解码与 RAM FFI,ARM 构建启用对应解码器 - 共享视频管线新增 MJPEG 解码路径(RKMPP/TurboJPEG),优化 WebRTC 发送与 MJPEG 去重 - MSD 配置改为 msd_dir 并自动创建子目录,接口与前端设置同步更新 - 更新包依赖与版本号
This commit is contained in:
95
src/video/decoder/mjpeg_rkmpp.rs
Normal file
95
src/video/decoder/mjpeg_rkmpp.rs
Normal file
@@ -0,0 +1,95 @@
|
||||
//! MJPEG decoder using RKMPP via hwcodec (FFmpeg RAM).
|
||||
|
||||
use hwcodec::ffmpeg::AVPixelFormat;
|
||||
use hwcodec::ffmpeg_ram::decode::{DecodeContext, Decoder};
|
||||
use tracing::warn;
|
||||
|
||||
use crate::error::{AppError, Result};
|
||||
use crate::video::convert::Nv12Converter;
|
||||
use crate::video::format::Resolution;
|
||||
|
||||
pub struct MjpegRkmppDecoder {
|
||||
decoder: Decoder,
|
||||
resolution: Resolution,
|
||||
nv16_to_nv12: Option<Nv12Converter>,
|
||||
last_pixfmt: Option<AVPixelFormat>,
|
||||
}
|
||||
|
||||
impl MjpegRkmppDecoder {
|
||||
pub fn new(resolution: Resolution) -> Result<Self> {
|
||||
let ctx = DecodeContext {
|
||||
name: "mjpeg_rkmpp".to_string(),
|
||||
width: resolution.width as i32,
|
||||
height: resolution.height as i32,
|
||||
sw_pixfmt: AVPixelFormat::AV_PIX_FMT_NV12,
|
||||
thread_count: 1,
|
||||
};
|
||||
let decoder = Decoder::new(ctx).map_err(|_| {
|
||||
AppError::VideoError("Failed to create mjpeg_rkmpp decoder".to_string())
|
||||
})?;
|
||||
Ok(Self {
|
||||
decoder,
|
||||
resolution,
|
||||
nv16_to_nv12: None,
|
||||
last_pixfmt: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn decode_to_nv12(&mut self, mjpeg: &[u8]) -> Result<Vec<u8>> {
|
||||
let frames = self
|
||||
.decoder
|
||||
.decode(mjpeg)
|
||||
.map_err(|e| AppError::VideoError(format!("mjpeg_rkmpp decode failed: {}", e)))?;
|
||||
if frames.is_empty() {
|
||||
return Err(AppError::VideoError(
|
||||
"mjpeg_rkmpp decode returned no frames".to_string(),
|
||||
));
|
||||
}
|
||||
if frames.len() > 1 {
|
||||
warn!(
|
||||
"mjpeg_rkmpp decode returned {} frames, using last",
|
||||
frames.len()
|
||||
);
|
||||
}
|
||||
let frame = frames
|
||||
.pop()
|
||||
.ok_or_else(|| AppError::VideoError("mjpeg_rkmpp decode returned empty".to_string()))?;
|
||||
|
||||
if frame.width as u32 != self.resolution.width
|
||||
|| frame.height as u32 != self.resolution.height
|
||||
{
|
||||
warn!(
|
||||
"mjpeg_rkmpp output size {}x{} differs from expected {}x{}",
|
||||
frame.width, frame.height, self.resolution.width, self.resolution.height
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(last) = self.last_pixfmt {
|
||||
if frame.pixfmt != last {
|
||||
warn!(
|
||||
"mjpeg_rkmpp output pixfmt changed from {:?} to {:?}",
|
||||
last, frame.pixfmt
|
||||
);
|
||||
}
|
||||
} else {
|
||||
self.last_pixfmt = Some(frame.pixfmt);
|
||||
}
|
||||
|
||||
let pixfmt = self.last_pixfmt.unwrap_or(frame.pixfmt);
|
||||
match pixfmt {
|
||||
AVPixelFormat::AV_PIX_FMT_NV12 => Ok(frame.data),
|
||||
AVPixelFormat::AV_PIX_FMT_NV16 => {
|
||||
if self.nv16_to_nv12.is_none() {
|
||||
self.nv16_to_nv12 = Some(Nv12Converter::nv16_to_nv12(self.resolution));
|
||||
}
|
||||
let conv = self.nv16_to_nv12.as_mut().unwrap();
|
||||
let nv12 = conv.convert(&frame.data)?;
|
||||
Ok(nv12.to_vec())
|
||||
}
|
||||
other => Err(AppError::VideoError(format!(
|
||||
"mjpeg_rkmpp output pixfmt {:?} (expected NV12/NV16)",
|
||||
other
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
54
src/video/decoder/mjpeg_turbo.rs
Normal file
54
src/video/decoder/mjpeg_turbo.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
//! MJPEG decoder using TurboJPEG (software) -> RGB24.
|
||||
|
||||
use turbojpeg::{Decompressor, Image, PixelFormat as TJPixelFormat};
|
||||
|
||||
use crate::error::{AppError, Result};
|
||||
use crate::video::format::Resolution;
|
||||
|
||||
pub struct MjpegTurboDecoder {
|
||||
decompressor: Decompressor,
|
||||
resolution: Resolution,
|
||||
}
|
||||
|
||||
impl MjpegTurboDecoder {
|
||||
pub fn new(resolution: Resolution) -> Result<Self> {
|
||||
let decompressor = Decompressor::new().map_err(|e| {
|
||||
AppError::VideoError(format!("Failed to create turbojpeg decoder: {}", e))
|
||||
})?;
|
||||
Ok(Self {
|
||||
decompressor,
|
||||
resolution,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn decode_to_rgb(&mut self, mjpeg: &[u8]) -> Result<Vec<u8>> {
|
||||
let header = self
|
||||
.decompressor
|
||||
.read_header(mjpeg)
|
||||
.map_err(|e| AppError::VideoError(format!("turbojpeg read_header failed: {}", e)))?;
|
||||
|
||||
if header.width as u32 != self.resolution.width
|
||||
|| header.height as u32 != self.resolution.height
|
||||
{
|
||||
return Err(AppError::VideoError(format!(
|
||||
"turbojpeg size mismatch: {}x{} (expected {}x{})",
|
||||
header.width, header.height, self.resolution.width, self.resolution.height
|
||||
)));
|
||||
}
|
||||
|
||||
let pitch = header.width * 3;
|
||||
let mut image = Image {
|
||||
pixels: vec![0u8; header.height * pitch],
|
||||
width: header.width,
|
||||
pitch,
|
||||
height: header.height,
|
||||
format: TJPixelFormat::RGB,
|
||||
};
|
||||
|
||||
self.decompressor
|
||||
.decompress(mjpeg, image.as_deref_mut())
|
||||
.map_err(|e| AppError::VideoError(format!("turbojpeg decode failed: {}", e)))?;
|
||||
|
||||
Ok(image.pixels)
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,11 @@
|
||||
//! Video decoder implementations
|
||||
//!
|
||||
//! This module provides video decoding capabilities.
|
||||
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
|
||||
pub mod mjpeg_rkmpp;
|
||||
pub mod mjpeg_turbo;
|
||||
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
|
||||
pub use mjpeg_rkmpp::MjpegRkmppDecoder;
|
||||
pub use mjpeg_turbo::MjpegTurboDecoder;
|
||||
|
||||
Reference in New Issue
Block a user