feat: 适配 RK 原生 HDMI IN 适配采集

This commit is contained in:
mofeng-git
2026-04-01 21:28:15 +08:00
parent 51d7d8b8be
commit abb319068b
36 changed files with 1382 additions and 406 deletions

View File

@@ -13,7 +13,7 @@ use std::sync::Once;
use tracing::{debug, error, info, warn};
use hwcodec::common::{Quality, RateControl};
use hwcodec::ffmpeg::AVPixelFormat;
use hwcodec::ffmpeg::{resolve_pixel_format, AVPixelFormat};
use hwcodec::ffmpeg_ram::encode::{EncodeContext, Encoder as HwEncoder};
use hwcodec::ffmpeg_ram::CodecInfo;
@@ -195,7 +195,7 @@ pub fn get_available_encoders(width: u32, height: u32) -> Vec<CodecInfo> {
mc_name: None,
width: width as i32,
height: height as i32,
pixfmt: AVPixelFormat::AV_PIX_FMT_YUV420P,
pixfmt: resolve_pixel_format("yuv420p", AVPixelFormat::AV_PIX_FMT_YUV420P),
align: 1,
fps: 30,
gop: 30,
@@ -273,16 +273,17 @@ impl H264Encoder {
let height = config.base.resolution.height;
// Select pixel format based on config
let pixfmt = match config.input_format {
H264InputFormat::Nv12 => AVPixelFormat::AV_PIX_FMT_NV12,
H264InputFormat::Nv21 => AVPixelFormat::AV_PIX_FMT_NV21,
H264InputFormat::Nv16 => AVPixelFormat::AV_PIX_FMT_NV16,
H264InputFormat::Nv24 => AVPixelFormat::AV_PIX_FMT_NV24,
H264InputFormat::Yuv420p => AVPixelFormat::AV_PIX_FMT_YUV420P,
H264InputFormat::Yuyv422 => AVPixelFormat::AV_PIX_FMT_YUYV422,
H264InputFormat::Rgb24 => AVPixelFormat::AV_PIX_FMT_RGB24,
H264InputFormat::Bgr24 => AVPixelFormat::AV_PIX_FMT_BGR24,
let (pixfmt_name, pixfmt_fallback) = match config.input_format {
H264InputFormat::Nv12 => ("nv12", AVPixelFormat::AV_PIX_FMT_NV12),
H264InputFormat::Nv21 => ("nv21", AVPixelFormat::AV_PIX_FMT_NV21),
H264InputFormat::Nv16 => ("nv16", AVPixelFormat::AV_PIX_FMT_NV16),
H264InputFormat::Nv24 => ("nv24", AVPixelFormat::AV_PIX_FMT_NV24),
H264InputFormat::Yuv420p => ("yuv420p", AVPixelFormat::AV_PIX_FMT_YUV420P),
H264InputFormat::Yuyv422 => ("yuyv422", AVPixelFormat::AV_PIX_FMT_YUYV422),
H264InputFormat::Rgb24 => ("rgb24", AVPixelFormat::AV_PIX_FMT_RGB24),
H264InputFormat::Bgr24 => ("bgr24", AVPixelFormat::AV_PIX_FMT_BGR24),
};
let pixfmt = resolve_pixel_format(pixfmt_name, pixfmt_fallback);
info!(
"Creating H.264 encoder: {} at {}x{} @ {} kbps (input: {:?})",

View File

@@ -11,7 +11,7 @@ use std::sync::Once;
use tracing::{debug, error, info, warn};
use hwcodec::common::{DataFormat, Quality, RateControl};
use hwcodec::ffmpeg::AVPixelFormat;
use hwcodec::ffmpeg::{resolve_pixel_format, AVPixelFormat};
use hwcodec::ffmpeg_ram::encode::{EncodeContext, Encoder as HwEncoder};
use hwcodec::ffmpeg_ram::CodecInfo;
@@ -198,7 +198,7 @@ pub fn get_available_h265_encoders(width: u32, height: u32) -> Vec<CodecInfo> {
mc_name: None,
width: width as i32,
height: height as i32,
pixfmt: AVPixelFormat::AV_PIX_FMT_NV12,
pixfmt: resolve_pixel_format("nv12", AVPixelFormat::AV_PIX_FMT_NV12),
align: 1,
fps: 30,
gop: 30,
@@ -310,24 +310,45 @@ impl H265Encoder {
let height = config.base.resolution.height;
// 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)
let (pixfmt_name, pixfmt_fallback, actual_input_format) = if is_software {
(
"yuv420p",
AVPixelFormat::AV_PIX_FMT_YUV420P,
H265InputFormat::Yuv420p,
)
} else {
match config.input_format {
H265InputFormat::Nv12 => (AVPixelFormat::AV_PIX_FMT_NV12, H265InputFormat::Nv12),
H265InputFormat::Nv21 => (AVPixelFormat::AV_PIX_FMT_NV21, H265InputFormat::Nv21),
H265InputFormat::Nv16 => (AVPixelFormat::AV_PIX_FMT_NV16, H265InputFormat::Nv16),
H265InputFormat::Nv24 => (AVPixelFormat::AV_PIX_FMT_NV24, H265InputFormat::Nv24),
H265InputFormat::Yuv420p => {
(AVPixelFormat::AV_PIX_FMT_YUV420P, H265InputFormat::Yuv420p)
H265InputFormat::Nv12 => {
("nv12", AVPixelFormat::AV_PIX_FMT_NV12, H265InputFormat::Nv12)
}
H265InputFormat::Yuyv422 => {
(AVPixelFormat::AV_PIX_FMT_YUYV422, H265InputFormat::Yuyv422)
H265InputFormat::Nv21 => {
("nv21", AVPixelFormat::AV_PIX_FMT_NV21, H265InputFormat::Nv21)
}
H265InputFormat::Nv16 => {
("nv16", AVPixelFormat::AV_PIX_FMT_NV16, H265InputFormat::Nv16)
}
H265InputFormat::Nv24 => {
("nv24", AVPixelFormat::AV_PIX_FMT_NV24, H265InputFormat::Nv24)
}
H265InputFormat::Yuv420p => (
"yuv420p",
AVPixelFormat::AV_PIX_FMT_YUV420P,
H265InputFormat::Yuv420p,
),
H265InputFormat::Yuyv422 => (
"yuyv422",
AVPixelFormat::AV_PIX_FMT_YUYV422,
H265InputFormat::Yuyv422,
),
H265InputFormat::Rgb24 => {
("rgb24", AVPixelFormat::AV_PIX_FMT_RGB24, H265InputFormat::Rgb24)
}
H265InputFormat::Bgr24 => {
("bgr24", AVPixelFormat::AV_PIX_FMT_BGR24, H265InputFormat::Bgr24)
}
H265InputFormat::Rgb24 => (AVPixelFormat::AV_PIX_FMT_RGB24, H265InputFormat::Rgb24),
H265InputFormat::Bgr24 => (AVPixelFormat::AV_PIX_FMT_BGR24, H265InputFormat::Bgr24),
}
};
let pixfmt = resolve_pixel_format(pixfmt_name, pixfmt_fallback);
info!(
"Creating H.265 encoder: {} at {}x{} @ {} kbps (input: {:?})",

View File

@@ -1,6 +1,6 @@
//! JPEG encoder implementation
//!
//! Provides JPEG encoding for raw video frames (YUYV, NV12, RGB, BGR)
//! Provides JPEG encoding for raw video frames (YUYV, NV12, NV16, NV24, RGB, BGR)
//! Uses libyuv for SIMD-accelerated color space conversion to I420,
//! then turbojpeg for direct YUV encoding (skips internal color conversion).
@@ -14,7 +14,7 @@ use crate::video::format::{PixelFormat, Resolution};
///
/// Encoding pipeline (all SIMD accelerated):
/// ```text
/// YUYV/NV12/BGR24/RGB24 ──libyuv──> I420 ──turbojpeg──> JPEG
/// YUYV/NV12/NV16/NV24/BGR24/RGB24 ──libyuv──> I420 ──turbojpeg──> JPEG
/// ```
///
/// Note: This encoder is NOT thread-safe due to turbojpeg limitations.
@@ -24,6 +24,10 @@ pub struct JpegEncoder {
compressor: turbojpeg::Compressor,
/// I420 buffer for YUV encoding (Y + U + V planes)
i420_buffer: Vec<u8>,
/// Scratch buffer for split chroma planes when converting semiplanar 4:2:2 / 4:4:4 input.
uv_split_buffer: Vec<u8>,
/// BGRA buffer used when a source format needs explicit YUV matrix expansion before JPEG.
bgra_buffer: Vec<u8>,
}
impl JpegEncoder {
@@ -34,6 +38,8 @@ impl JpegEncoder {
let height = resolution.height as usize;
// I420: Y = width*height, U = width*height/4, V = width*height/4
let i420_size = width * height * 3 / 2;
let max_uv_plane_size = width * height;
let bgra_size = width * height * 4;
let mut compressor = turbojpeg::Compressor::new().map_err(|e| {
AppError::VideoError(format!("Failed to create turbojpeg compressor: {}", e))
@@ -47,6 +53,8 @@ impl JpegEncoder {
config,
compressor,
i420_buffer: vec![0u8; i420_size],
uv_split_buffer: vec![0u8; max_uv_plane_size * 2],
bgra_buffer: vec![0u8; bgra_size],
})
}
@@ -93,6 +101,36 @@ impl JpegEncoder {
))
}
/// Encode BGRA buffer to JPEG using turbojpeg's RGB path.
#[inline]
fn encode_bgra_to_jpeg(&mut self, sequence: u64) -> Result<EncodedFrame> {
let width = self.config.resolution.width as usize;
let height = self.config.resolution.height as usize;
self.compressor
.set_subsamp(turbojpeg::Subsamp::Sub2x2)
.map_err(|e| AppError::VideoError(format!("Failed to set JPEG subsampling: {}", e)))?;
let image = turbojpeg::Image {
pixels: self.bgra_buffer.as_slice(),
width,
pitch: width * 4,
height,
format: turbojpeg::PixelFormat::BGRA,
};
let jpeg_data = self
.compressor
.compress_to_vec(image)
.map_err(|e| AppError::VideoError(format!("JPEG compression failed: {}", e)))?;
Ok(EncodedFrame::jpeg(
Bytes::from(jpeg_data),
self.config.resolution,
sequence,
))
}
/// Encode YUYV (YUV422) frame to JPEG
pub fn encode_yuyv(&mut self, data: &[u8], sequence: u64) -> Result<EncodedFrame> {
let width = self.config.resolution.width as usize;
@@ -135,6 +173,101 @@ impl JpegEncoder {
self.encode_i420_to_jpeg(sequence)
}
/// Encode NV16 frame to JPEG
pub fn encode_nv16(&mut self, data: &[u8], sequence: u64) -> Result<EncodedFrame> {
let width = self.config.resolution.width as usize;
let height = self.config.resolution.height as usize;
let y_size = width * height;
let uv_size = y_size;
let expected_size = y_size + uv_size;
if data.len() < expected_size {
return Err(AppError::VideoError(format!(
"NV16 data too small: {} < {}",
data.len(),
expected_size
)));
}
let src_uv = &data[y_size..expected_size];
let chroma_plane_size = y_size / 2;
let (u_plane_422, rest) = self.uv_split_buffer.split_at_mut(chroma_plane_size);
let (v_plane_422, _) = rest.split_at_mut(chroma_plane_size);
libyuv::split_uv_plane(
src_uv,
width as i32,
u_plane_422,
(width / 2) as i32,
v_plane_422,
(width / 2) as i32,
(width / 2) as i32,
height as i32,
)
.map_err(|e| AppError::VideoError(format!("libyuv NV16 split failed: {}", e)))?;
libyuv::i422_to_i420_planar(
&data[..y_size],
width as i32,
u_plane_422,
(width / 2) as i32,
v_plane_422,
(width / 2) as i32,
&mut self.i420_buffer,
width as i32,
height as i32,
)
.map_err(|e| AppError::VideoError(format!("libyuv NV16→I420 failed: {}", e)))?;
self.encode_i420_to_jpeg(sequence)
}
/// Encode NV24 frame to JPEG
pub fn encode_nv24(&mut self, data: &[u8], sequence: u64) -> Result<EncodedFrame> {
let width = self.config.resolution.width as usize;
let height = self.config.resolution.height as usize;
let y_size = width * height;
let uv_size = y_size * 2;
let expected_size = y_size + uv_size;
if data.len() < expected_size {
return Err(AppError::VideoError(format!(
"NV24 data too small: {} < {}",
data.len(),
expected_size
)));
}
let src_uv = &data[y_size..expected_size];
let chroma_plane_size = y_size;
let (u_plane_444, rest) = self.uv_split_buffer.split_at_mut(chroma_plane_size);
let (v_plane_444, _) = rest.split_at_mut(chroma_plane_size);
libyuv::split_uv_plane(
src_uv,
(width * 2) as i32,
u_plane_444,
width as i32,
v_plane_444,
width as i32,
width as i32,
height as i32,
)
.map_err(|e| AppError::VideoError(format!("libyuv NV24 split failed: {}", e)))?;
libyuv::h444_to_bgra(
&data[..y_size],
u_plane_444,
v_plane_444,
&mut self.bgra_buffer,
width as i32,
height as i32,
)
.map_err(|e| AppError::VideoError(format!("libyuv NV24(H444)→BGRA failed: {}", e)))?;
self.encode_bgra_to_jpeg(sequence)
}
/// Encode RGB24 frame to JPEG
pub fn encode_rgb(&mut self, data: &[u8], sequence: u64) -> Result<EncodedFrame> {
let width = self.config.resolution.width as usize;
@@ -192,6 +325,8 @@ impl crate::video::encoder::traits::Encoder for JpegEncoder {
match self.config.input_format {
PixelFormat::Yuyv | PixelFormat::Yvyu => self.encode_yuyv(data, sequence),
PixelFormat::Nv12 => self.encode_nv12(data, sequence),
PixelFormat::Nv16 => self.encode_nv16(data, sequence),
PixelFormat::Nv24 => self.encode_nv24(data, sequence),
PixelFormat::Rgb24 => self.encode_rgb(data, sequence),
PixelFormat::Bgr24 => self.encode_bgr(data, sequence),
_ => Err(AppError::VideoError(format!(
@@ -211,6 +346,8 @@ impl crate::video::encoder::traits::Encoder for JpegEncoder {
PixelFormat::Yuyv
| PixelFormat::Yvyu
| PixelFormat::Nv12
| PixelFormat::Nv16
| PixelFormat::Nv24
| PixelFormat::Rgb24
| PixelFormat::Bgr24
)

View File

@@ -11,7 +11,7 @@ use std::time::Duration;
use tracing::{debug, info, warn};
use hwcodec::common::{DataFormat, Quality, RateControl};
use hwcodec::ffmpeg::AVPixelFormat;
use hwcodec::ffmpeg::{resolve_pixel_format, AVPixelFormat};
use hwcodec::ffmpeg_ram::encode::{EncodeContext, Encoder as HwEncoder};
use hwcodec::ffmpeg_ram::CodecInfo;
@@ -309,7 +309,7 @@ impl EncoderRegistry {
mc_name: None,
width: width as i32,
height: height as i32,
pixfmt: AVPixelFormat::AV_PIX_FMT_NV12,
pixfmt: resolve_pixel_format("nv12", AVPixelFormat::AV_PIX_FMT_NV12),
align: 1,
fps: 30,
gop: 30,

View File

@@ -11,7 +11,7 @@ use std::sync::Once;
use tracing::{debug, error, info, warn};
use hwcodec::common::{DataFormat, Quality, RateControl};
use hwcodec::ffmpeg::AVPixelFormat;
use hwcodec::ffmpeg::{resolve_pixel_format, AVPixelFormat};
use hwcodec::ffmpeg_ram::encode::{EncodeContext, Encoder as HwEncoder};
use hwcodec::ffmpeg_ram::CodecInfo;
@@ -133,7 +133,7 @@ pub fn get_available_vp8_encoders(width: u32, height: u32) -> Vec<CodecInfo> {
mc_name: None,
width: width as i32,
height: height as i32,
pixfmt: AVPixelFormat::AV_PIX_FMT_NV12,
pixfmt: resolve_pixel_format("nv12", AVPixelFormat::AV_PIX_FMT_NV12),
align: 1,
fps: 30,
gop: 30,
@@ -244,16 +244,25 @@ impl VP8Encoder {
let height = config.base.resolution.height;
// Software encoders (libvpx) require YUV420P, hardware (VAAPI) uses NV12
let (pixfmt, actual_input_format) = if is_software {
(AVPixelFormat::AV_PIX_FMT_YUV420P, VP8InputFormat::Yuv420p)
let (pixfmt_name, pixfmt_fallback, actual_input_format) = if is_software {
(
"yuv420p",
AVPixelFormat::AV_PIX_FMT_YUV420P,
VP8InputFormat::Yuv420p,
)
} else {
match config.input_format {
VP8InputFormat::Nv12 => (AVPixelFormat::AV_PIX_FMT_NV12, VP8InputFormat::Nv12),
VP8InputFormat::Yuv420p => {
(AVPixelFormat::AV_PIX_FMT_YUV420P, VP8InputFormat::Yuv420p)
VP8InputFormat::Nv12 => {
("nv12", AVPixelFormat::AV_PIX_FMT_NV12, VP8InputFormat::Nv12)
}
VP8InputFormat::Yuv420p => (
"yuv420p",
AVPixelFormat::AV_PIX_FMT_YUV420P,
VP8InputFormat::Yuv420p,
),
}
};
let pixfmt = resolve_pixel_format(pixfmt_name, pixfmt_fallback);
info!(
"Creating VP8 encoder: {} at {}x{} @ {} kbps (input: {:?})",

View File

@@ -11,7 +11,7 @@ use std::sync::Once;
use tracing::{debug, error, info, warn};
use hwcodec::common::{DataFormat, Quality, RateControl};
use hwcodec::ffmpeg::AVPixelFormat;
use hwcodec::ffmpeg::{resolve_pixel_format, AVPixelFormat};
use hwcodec::ffmpeg_ram::encode::{EncodeContext, Encoder as HwEncoder};
use hwcodec::ffmpeg_ram::CodecInfo;
@@ -133,7 +133,7 @@ pub fn get_available_vp9_encoders(width: u32, height: u32) -> Vec<CodecInfo> {
mc_name: None,
width: width as i32,
height: height as i32,
pixfmt: AVPixelFormat::AV_PIX_FMT_NV12,
pixfmt: resolve_pixel_format("nv12", AVPixelFormat::AV_PIX_FMT_NV12),
align: 1,
fps: 30,
gop: 30,
@@ -244,16 +244,25 @@ impl VP9Encoder {
let height = config.base.resolution.height;
// Software encoders (libvpx-vp9) require YUV420P, hardware (VAAPI) uses NV12
let (pixfmt, actual_input_format) = if is_software {
(AVPixelFormat::AV_PIX_FMT_YUV420P, VP9InputFormat::Yuv420p)
let (pixfmt_name, pixfmt_fallback, actual_input_format) = if is_software {
(
"yuv420p",
AVPixelFormat::AV_PIX_FMT_YUV420P,
VP9InputFormat::Yuv420p,
)
} else {
match config.input_format {
VP9InputFormat::Nv12 => (AVPixelFormat::AV_PIX_FMT_NV12, VP9InputFormat::Nv12),
VP9InputFormat::Yuv420p => {
(AVPixelFormat::AV_PIX_FMT_YUV420P, VP9InputFormat::Yuv420p)
VP9InputFormat::Nv12 => {
("nv12", AVPixelFormat::AV_PIX_FMT_NV12, VP9InputFormat::Nv12)
}
VP9InputFormat::Yuv420p => (
"yuv420p",
AVPixelFormat::AV_PIX_FMT_YUV420P,
VP9InputFormat::Yuv420p,
),
}
};
let pixfmt = resolve_pixel_format(pixfmt_name, pixfmt_fallback);
info!(
"Creating VP9 encoder: {} at {}x{} @ {} kbps (input: {:?})",