feat(video): 事务化切换与前端统一编排,增强视频输入格式支持

- 后端:切换事务+transition_id,/stream/mode 返回 switching/transition_id 与实际 codec

- 事件:新增 mode_switching/mode_ready,config/webrtc_ready/mode_changed 关联事务

- 编码/格式:扩展 NV21/NV16/NV24/RGB/BGR 输入与转换链路,RKMPP direct input 优化

- 前端:useVideoSession 统一切换,失败回退真实切回 MJPEG,菜单格式同步修复

- 清理:useVideoStream 降级为 MJPEG-only
This commit is contained in:
mofeng-git
2026-01-11 10:41:57 +08:00
parent 9feb74b72c
commit 206594e292
110 changed files with 3955 additions and 2251 deletions

View File

@@ -56,7 +56,10 @@ fn build_common(builder: &mut Build) {
// Unsupported platforms
if target_os != "windows" && target_os != "linux" {
panic!("Unsupported OS: {}. Only Windows and Linux are supported.", target_os);
panic!(
"Unsupported OS: {}. Only Windows and Linux are supported.",
target_os
);
}
// tool
@@ -103,7 +106,9 @@ mod ffmpeg {
use std::process::Command;
// Check if static linking is requested
let use_static = std::env::var("FFMPEG_STATIC").map(|v| v == "1").unwrap_or(false);
let use_static = std::env::var("FFMPEG_STATIC")
.map(|v| v == "1")
.unwrap_or(false);
let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default();
// Try custom library path first:
@@ -142,7 +147,7 @@ mod ffmpeg {
// VAAPI for x86_64
println!("cargo:rustc-link-lib=va");
println!("cargo:rustc-link-lib=va-drm");
println!("cargo:rustc-link-lib=va-x11"); // Required for vaGetDisplay
println!("cargo:rustc-link-lib=va-x11"); // Required for vaGetDisplay
println!("cargo:rustc-link-lib=mfx");
} else {
// RKMPP for ARM
@@ -172,10 +177,7 @@ mod ffmpeg {
for lib in &libs {
// Get cflags
if let Ok(output) = Command::new("pkg-config")
.args(["--cflags", lib])
.output()
{
if let Ok(output) = Command::new("pkg-config").args(["--cflags", lib]).output() {
if output.status.success() {
let cflags = String::from_utf8_lossy(&output.stdout);
for flag in cflags.split_whitespace() {
@@ -193,10 +195,7 @@ mod ffmpeg {
vec!["--libs", lib]
};
if let Ok(output) = Command::new("pkg-config")
.args(&pkg_config_args)
.output()
{
if let Ok(output) = Command::new("pkg-config").args(&pkg_config_args).output() {
if output.status.success() {
let libs_str = String::from_utf8_lossy(&output.stdout);
for flag in libs_str.split_whitespace() {
@@ -221,7 +220,9 @@ mod ffmpeg {
panic!("pkg-config failed for {}. Install FFmpeg development libraries: sudo apt install libavcodec-dev libavutil-dev", lib);
}
} else {
panic!("pkg-config not found. Install pkg-config and FFmpeg development libraries.");
panic!(
"pkg-config not found. Install pkg-config and FFmpeg development libraries."
);
}
}
@@ -301,7 +302,10 @@ mod ffmpeg {
// ARM (aarch64, arm): no X11 needed, uses RKMPP/V4L2
v
} else {
panic!("Unsupported OS: {}. Only Windows and Linux are supported.", target_os);
panic!(
"Unsupported OS: {}. Only Windows and Linux are supported.",
target_os
);
};
for lib in dyn_libs.iter() {
@@ -312,10 +316,9 @@ mod ffmpeg {
fn ffmpeg_ffi() {
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let ffmpeg_ram_dir = manifest_dir.join("cpp").join("common");
let ffi_header = ffmpeg_ram_dir
.join("ffmpeg_ffi.h")
.to_string_lossy()
.to_string();
let ffi_header_path = ffmpeg_ram_dir.join("ffmpeg_ffi.h");
println!("cargo:rerun-if-changed={}", ffi_header_path.display());
let ffi_header = ffi_header_path.to_string_lossy().to_string();
bindgen::builder()
.header(ffi_header)
.rustified_enum("*")
@@ -340,8 +343,6 @@ mod ffmpeg {
.write_to_file(Path::new(&env::var_os("OUT_DIR").unwrap()).join("ffmpeg_ram_ffi.rs"))
.unwrap();
builder.files(
["ffmpeg_ram_encode.cpp"].map(|f| ffmpeg_ram_dir.join(f)),
);
builder.files(["ffmpeg_ram_encode.cpp"].map(|f| ffmpeg_ram_dir.join(f)));
}
}

View File

@@ -14,11 +14,15 @@
enum AVPixelFormat {
AV_PIX_FMT_YUV420P = 0,
AV_PIX_FMT_YUYV422 = 1,
AV_PIX_FMT_RGB24 = 2,
AV_PIX_FMT_BGR24 = 3,
AV_PIX_FMT_YUV422P = 4, // planar YUV 4:2:2
AV_PIX_FMT_YUVJ420P = 12, // JPEG full-range YUV420P (same layout as YUV420P)
AV_PIX_FMT_YUVJ422P = 13, // JPEG full-range YUV422P (same layout as YUV422P)
AV_PIX_FMT_NV12 = 23,
AV_PIX_FMT_NV21 = 24,
AV_PIX_FMT_NV16 = 101,
AV_PIX_FMT_NV24 = 188,
};
int av_log_get_level(void);
@@ -26,4 +30,4 @@ void av_log_set_level(int level);
void hwcodec_set_av_log_callback();
void hwcodec_set_flag_could_not_find_ref_with_poc();
#endif
#endif

View File

@@ -388,7 +388,9 @@ private:
}
_exit:
av_packet_unref(pkt_);
return encoded ? 0 : -1;
// If no packet is produced for this input frame, treat it as EAGAIN.
// This is not a fatal error: encoders may buffer internally (e.g., startup delay).
return encoded ? 0 : AVERROR(EAGAIN);
}
int fill_frame(AVFrame *frame, uint8_t *data, int data_length,
@@ -511,4 +513,4 @@ extern "C" void ffmpeg_ram_request_keyframe(FFmpegRamEncoder *encoder) {
} catch (const std::exception &e) {
LOG_ERROR(std::string("ffmpeg_ram_request_keyframe failed, ") + std::string(e.what()));
}
}
}

View File

@@ -84,7 +84,7 @@ pub fn setup_parent_death_signal() {
pub fn child_exit_when_parent_exit(child_process_id: u32) -> bool {
unsafe {
extern "C" {
fn add_process_to_new_job(child_process_id: u32) -> i32;
fn add_process_to_new_job(child_process_id: u32) -> i32;
}
let result = add_process_to_new_job(child_process_id);
result == 0

View File

@@ -3,7 +3,8 @@ use crate::{
ffmpeg::{init_av_log, AVPixelFormat},
ffmpeg_ram::{
ffmpeg_linesize_offset_length, ffmpeg_ram_encode, ffmpeg_ram_free_encoder,
ffmpeg_ram_new_encoder, ffmpeg_ram_request_keyframe, ffmpeg_ram_set_bitrate, CodecInfo, AV_NUM_DATA_POINTERS,
ffmpeg_ram_new_encoder, ffmpeg_ram_request_keyframe, ffmpeg_ram_set_bitrate, CodecInfo,
AV_NUM_DATA_POINTERS,
},
};
use log::trace;
@@ -123,6 +124,12 @@ impl Encoder {
self.frames as *const _ as *const c_void,
ms,
);
// ffmpeg_ram_encode returns AVERROR(EAGAIN) when the encoder accepts the frame
// but does not output a packet yet (e.g., startup delay / internal buffering).
// Treat this as a successful call with an empty output list.
if result == -11 {
return Ok(&mut *self.frames);
}
if result != 0 {
return Err(result);
}
@@ -358,7 +365,8 @@ impl Encoder {
if frames[0].key == 1 && elapsed < TEST_TIMEOUT_MS as _ {
debug!(
"Encoder {} test passed on attempt {}",
codec.name, attempt + 1
codec.name,
attempt + 1
);
res.push(codec.clone());
passed = true;