From 8460c56bd52479c3225040b34e66bb4f7ec45161 Mon Sep 17 00:00:00 2001 From: dailz Date: Mon, 22 Jun 2026 17:44:08 +0800 Subject: [PATCH] =?UTF-8?q?docs(avhw):=20[2/4]=20=E4=B8=AD=E6=96=87?= =?UTF-8?q?=E6=B3=A8=E9=87=8A=20EncState=20=E7=BC=96=E7=A0=81=E4=B8=BB?= =?UTF-8?q?=E5=BE=AA=E7=8E=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/avhw.rs | 105 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/src/avhw.rs b/src/avhw.rs index 3bc0950..47da914 100644 --- a/src/avhw.rs +++ b/src/avhw.rs @@ -476,6 +476,20 @@ pub(crate) fn ff_err(ret: i32) -> String { // EncState // --------------------------------------------------------------------------- +// 编码状态机:把 VAAPI 硬件帧 → H.264 ES 流 → MP4 文件的"主循环编排者"。 +// +// 字段角色(按数据流顺序): +// - enc_video : 已打开的 h264_vaapi 编码器句柄(FFmpeg AVCodecContext 包装) +// - video_filter : scale/crop/format 过滤图(BGRA→NV12 + VAAPI 像素格式上传) +// - frames_rgb : 捕获侧 hw frames 池(与 import_dma_buf_to_vaapi 共享) +// - hw_device_ctx : VAAPI 设备上下文(DRM render node 句柄) +// - octx : 输出 muxer 上下文(MP4 文件) +// - starting_timestamp : 首帧 PTS 锚点,用于把绝对时间戳归零(避免 MP4 起始时间漂移) +// - frames_written : 是否已写出过帧(用于决定 flush 时是否写 trailer) +// +// 跨线程约束:state.rs/state_portal.rs 的设计是"单线程驱动"——`&mut EncState` +// 由外层 main 串行化访问,因此 `enc_video` / `octx` 等 FFmpeg 上下文不需要锁。 +// `unsafe impl Send` 仅表达"类型上可以跨线程移动",不代表"可以并发访问"。 pub struct EncState { enc_video: ff::codec::encoder::video::Video, frames_rgb: AvHwFrameCtx, @@ -486,10 +500,26 @@ pub struct EncState { frames_written: bool, } +// 安全说明:FFmpeg 的 AVCodecContext/AVFormatContext 等 C 对象不是自动 Sync 的, +// 但本工程的并发模型保证 EncState 只在单一编码线程内被 `&mut` 访问,因此标记 +// Send(跨线程移动)是安全的;若未来引入并发编码必须重新审视(参见 AGENTS.md +// 关于 avhw.rs 显式 `unsafe impl Send` 与独占性假设的说明)。 unsafe impl Send for EncState {} +// impl EncState:编码主循环对外暴露的方法集合。 +// +// 方法职责(按数据流顺序): +// - new : 构造 VAAPI 设备/帧池/filter 图/编码器/muxer 6 步流水线(FFmpeg 严格顺序) +// - frames_rgb : 暴露内部 hw frames 池给捕获侧 import_dma_buf_to_vaapi 共享 +// - encode_frame : 单帧驱动:送入 filter → 拉过滤后帧 → avcodec_send_frame → drain_encoder +// - flush : EOF 处理:filter drain + 空帧 avcodec_send_frame 触发 encoder flush + write_trailer +// - drain_encoder: 内部辅助:循环 avcodec_receive_packet 直到 EAGAIN/EOF,重缩放 PTS 并写 muxer impl EncState { + // 抑制 clippy::too_many_arguments:构造函数需要 11 个参数(捕获尺寸/编码尺寸/码率/GOP/帧率/变换/共享 hw ctx), + // 拆分为 builder 模式反而会增加 FFI 调用顺序出错的风险(FFmpeg 各步骤有严格依赖关系)。 #[allow(clippy::too_many_arguments)] + // 构造编码器:类比 Go `func NewEncState(...) (*EncState, error)`,11 个参数对应 6 步流水线配置。 + // 返回 `Result` 用 `?` 把任何 FFI/IO 错误传播给调用方(state.rs/state_portal.rs)。 pub fn new( drm_device: &Path, output_path: &Path, @@ -507,6 +537,8 @@ impl EncState { "EncState::new: {width}x{height} enc={enc_width}x{enc_height} transform={transform:?}" ); // 1. VAAPI device — reuse existing context if provided + // `match` 是 Rust 的 exhaustive 模式匹配(类似 Go `switch` 但更强:编译器强制覆盖所有分支)。 + // `Option` 等价于 Go `*AvHwDevCtx`(Some=value,None=nil),但类型系统强制处理 nil 情况。 let hw_device_ctx = match existing_hw_ctx { Some(ctx) => ctx, None => AvHwDevCtx::new_vaapi(drm_device)?, @@ -530,7 +562,11 @@ impl EncState { let mut sink_ctx = video_filter .get("out") + // `ok_or_else(|| ...)` 把 `Option` 转为 `Result`:None 时执行闭包构造错误。 + // 闭包 `|| ...` 延迟构造(类比 Go `if x == nil { return fmt.Errorf(...) }`); + // 末尾 `?` 把 Err 传播给调用方,Ok(t) 则解包继续。 .ok_or_else(|| anyhow::anyhow!("filter 'out' not found"))?; + // 中文概述:从 buffersink 取出硬件帧上下文并 `av_buffer_ref` 提升为拥有引用。 // SAFETY: sink_ctx is a live buffersink; the returned hw_frames_ctx is // borrowed, so av_buffer_ref creates an owned reference. let sink_hw_frames = unsafe { @@ -545,6 +581,8 @@ impl EncState { hw_ref }; + // 中文概述:解引用 sink_hw_frames 检查 filter graph 输出尺寸与编码器期望尺寸是否一致; + // 不一致仅 warn(filter 可能做隐式 scale),不视为硬错误。 // SAFETY: sink_hw_frames is an owned AVBufferRef to an AVHWFramesContext // returned by the validated filter graph. unsafe { @@ -559,9 +597,14 @@ impl EncState { } // 4. Find h264_vaapi encoder + // `ff::encoder::find_by_name("h264_vaapi")` 返回 `Option`:FFmpeg 未编译 VAAPI 时为 None。 + // `.ok_or_else(...)?` 链式:Option → Result → 自动传播。 let codec = ff::encoder::find_by_name("h264_vaapi") .ok_or_else(|| anyhow::anyhow!("h264_vaapi encoder not found"))?; + // 块表达式 `{ ... }` 求值为最后一个表达式(无分号)的值:这里 `ctx.encoder().video()?` + // 返回 `Result>`,`?` 解开为 `Encoder