docs(avhw): [1/4] 中文注释 FFmpeg/VAAPI 初始化
This commit is contained in:
+148
@@ -1,3 +1,28 @@
|
||||
//! # avhw — FFmpeg / VAAPI 硬件加速 FFI 绑定(FFI 最密集的文件)
|
||||
//!
|
||||
//! 本文件是整个 crate 中 `unsafe` 块密度最高的模块:直接调用 FFmpeg C API。
|
||||
//! `ffmpeg-next` 是 ffmpeg-sys-next(C FFI 绑定)的薄包装;硬件加速相关 API
|
||||
//! (`AVBufferRef`/`av_hwframe_*`)仍是裸 C 接口。
|
||||
//!
|
||||
//! ## FFmpeg 引用计数对象的生命周期
|
||||
//! - `AVBufferRef`:FFmpeg 引用计数句柄,`av_buffer_ref` 加 1、`av_buffer_unref` 减 1。
|
||||
//! `AvHwDevCtx`/`AvHwFrameCtx` 用 RAII 包装:`new` alloc,`Drop` unref。
|
||||
//! - `AVFrame`:硬件帧(`AV_PIX_FMT_VAAPI`)的 `data[0]` 是 GPU 表面指针。
|
||||
//!
|
||||
//! ## `unsafe impl Send` 的存在原因(AGENTS.md 明确警告)
|
||||
//! `*mut AVBufferRef` 默认 `!Send`;wrapper 显式声明 Send 的前提是**外部调用方
|
||||
//! 保证 `&mut self` 独占访问**。AGENTS.md:不要跨线程移动这些 wrapper 而不重新
|
||||
//! 检查 exclusivity 假设。
|
||||
//!
|
||||
//! ## 与 Go cgo 的类比
|
||||
//! - `extern "C" fn` ≈ Go `//export` C 回调;
|
||||
//! - `unsafe { ffi::av_*() }` ≈ Go `C.av_xxx()`;
|
||||
//! - RAII `Drop` ≈ Go `runtime.SetFinalizer`(但 Rust Drop 是确定性的)。
|
||||
|
||||
// std 导入:CString(C 字符串)、mem::zeroed(FFI 零初始化)、RawFd/AsRawFd(fd 桥),
|
||||
// c_void(C void 跨语言)、Path(DRM 设备路径)、ptr(null_mut 等裸指针工具),
|
||||
// slice(从裸指针构造切片)、AtomicBool/Ordering/Arc(跨线程暂停标志,T10b 用),
|
||||
// Instant(编码计时,T10b 用)。本 sub-todo(lines 1-330)仅部分使用。
|
||||
use std::ffi::CString;
|
||||
use std::mem;
|
||||
use std::os::fd::{AsRawFd, RawFd};
|
||||
@@ -9,17 +34,28 @@ use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
|
||||
// anyhow 错误处理:bail!(提前返回 Err)、Result<T>(错误传播)。
|
||||
// ffmpeg-next:稍安全的 API(ff::frame::Video、ff::format::Pixel、ff::codec 等)。
|
||||
// ffmpeg_next::ffi:FFmpeg C 头绑定的裸 API(AVBufferRef、av_hwdevice_ctx_create 等),
|
||||
// 所有 `unsafe` FFI 调用的入口。`packet::Mut as _`:导入 packet 的 Mut trait 但匿名,
|
||||
// 仅用于 trait method 解析,不污染命名空间。
|
||||
use anyhow::{bail, Result};
|
||||
use ffmpeg_next as ff;
|
||||
use ffmpeg_next::ffi;
|
||||
use ffmpeg_next::packet::Mut as _;
|
||||
|
||||
// crate 内部模块:PwDmaBufFrame(PipeWire DMA-BUF 帧元数据:fd/width/height/stride/modifier),
|
||||
// Transform(Wayland wl_output 变换,决定 ROI 是否需要转置处理)。
|
||||
use crate::cap_portal::PwDmaBufFrame;
|
||||
use crate::transform::{transpose_if_transform_transposed, Transform};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Bitrate feedback command (WebRTC BWE → SW encoder)
|
||||
// ---------------------------------------------------------------------------
|
||||
// 跨线程控制信令:WebRTC 线程根据 BWE(bandwidth estimation)估算的可用带宽,
|
||||
// 通过 MPSC channel 把 `BitrateCommand` 发给软件编码线程,由后者调整 x264 参数。
|
||||
// 注意:硬件(VAAPI)路径目前不接受动态码率调整,只有软件(x264)路径消费这些命令。
|
||||
// 类比 Go:`chan BitrateCommand` 单向 channel。
|
||||
|
||||
/// Commands sent from the WebRTC thread to the SW encoder when the
|
||||
/// bandwidth estimate changes significantly.
|
||||
@@ -31,12 +67,26 @@ pub enum BitrateCommand {
|
||||
ForceKeyframe,
|
||||
}
|
||||
|
||||
// 分辨率变更事件:WebRTC 线程根据对端能力(SVC/layered encoding 协商)建议切换分辨率,
|
||||
// 编码线程收到后重建 encoder(x264 不支持运行时改变 width/height,必须销毁重建)。
|
||||
// `#[derive(Clone, Copy, Debug)]`:Copy 表示按位拷贝(小值类型,无需 clone 调用),
|
||||
// 类比 Go 的 `type ResolutionChange struct{...}`(Go 默认值语义即 Copy)。
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct ResolutionChange {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
// 每帧编码耗时快照,由编码线程填好后通过 channel 发给统计线程(stats thread)。
|
||||
// `#[derive(Default, Clone, Copy, Debug)]`:Default 允许 `SwEncodeTiming::default()` 全零初始化;
|
||||
// Copy/Clone 表示小值类型按位拷贝(无堆字段)。
|
||||
//
|
||||
// 字段语义:
|
||||
// - sws_us:libswscale 把 NV12(VAAPI 输出)转 YUV420P(x264 输入)的耗时(微秒)
|
||||
// - encode_us:`avcodec_send_frame` + drain 接收所有 packet 的总耗时(微秒)
|
||||
// - output_bytes:本帧产出的 H.264 字节数(即使下游 WebRTC 因暂停丢帧也计入)
|
||||
//
|
||||
// 类比 Go:`type SwEncodeTiming struct{...}` + atomic store/load 跨 goroutine 传递。
|
||||
/// Per-frame timing snapshot for the software encoder, consumed by the stats
|
||||
/// thread. `sws_us` measures NV12→YUV420P conversion, `encode_us` measures
|
||||
/// `avcodec_send_frame` + drain, and `output_bytes` counts encoded bytes
|
||||
@@ -48,6 +98,10 @@ pub struct SwEncodeTiming {
|
||||
pub output_bytes: usize,
|
||||
}
|
||||
|
||||
// `encode_cpu_frame` 的返回值,编码线程据此判断是否计 encoded_fps(只有 `Encoded`
|
||||
// 才算一次真实编码)。`#[derive(Debug, Clone, Copy, PartialEq, Eq)]`:PartialEq/Eq
|
||||
// 允许 `==` 比较(测试和 stats 聚合用),Copy 表示小值类型按位拷贝。
|
||||
// 类比 Go:`type EncodeOutcome int` + const 枚举值。
|
||||
/// Outcome of a single `encode_cpu_frame` call. Used by the encode thread
|
||||
/// to decide whether to report timing stats (only real encodes tick encoded_fps).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -66,19 +120,39 @@ pub enum EncodeOutcome {
|
||||
// AvHwDevCtx
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// VAAPI 设备上下文的 RAII 包装。`ptr` 是 FFmpeg 引用计数的 `AVBufferRef`,
|
||||
// 包装 `AVHWDeviceContext`(VAAPI 内部的 `VADisplay`)。`new_vaapi` 时 alloc,
|
||||
// `Drop` 时 `av_buffer_unref`;`ref_clone` 用于把内部 ref 传递给编码器的
|
||||
// `AVCodecContext::hw_device_ctx`(FFmpeg 会再 `av_buffer_ref` 一次)。
|
||||
// **独占访问假设**:`unsafe impl Send` 的前提是外部调用方保证 `&mut self`,
|
||||
// 不允许多线程共享 `&AvHwDevCtx` 同时调用 mut method(详见 AGENTS.md 警告)。
|
||||
pub struct AvHwDevCtx {
|
||||
// FFmpeg 内部引用计数句柄,类型擦除为 `*mut AVBufferRef`。
|
||||
// 永不为 null(除非在 Drop 中被置 null)。
|
||||
ptr: *mut ffi::AVBufferRef,
|
||||
}
|
||||
|
||||
// 中文概述:声明 `AvHwDevCtx` 可以跨线程 `Send`。前提是调用方用 `&mut self`
|
||||
// 独占访问(无并发可变借用)。VAAPI 的 `VADisplay` 在硬件驱动层线程安全,
|
||||
// FFmpeg 的 `AVHWDeviceContext` 内部状态在 send/receive 编码模式下也线程安全。
|
||||
// 不修复:保留 AGENTS.md 文档的 exclusivity 假设(这是本 crate 的并发模型基础)。
|
||||
// SAFETY: AvHwDevCtx wraps an FFmpeg AVBufferRef which is not Send by default,
|
||||
// but we guarantee exclusive access through &mut self. The underlying VAAPI
|
||||
// device context is thread-safe for the operations we perform.
|
||||
unsafe impl Send for AvHwDevCtx {}
|
||||
|
||||
impl AvHwDevCtx {
|
||||
// 创建 VAAPI 设备上下文。`drm_device` 通常是 `/dev/dri/renderD128`。
|
||||
// 失败原因:设备不存在、无 VAAPI 驱动、权限不足(用户不在 `video` 组)。
|
||||
// 成功时返回 `AvHwDevCtx`,ptr 持有一个 ref(refcount=1)。
|
||||
pub fn new_vaapi(drm_device: &Path) -> Result<Self> {
|
||||
// CString::new 在路径含内部 NUL 时返回 Err(FFmpeg C API 要求 NUL 结尾)。
|
||||
// `to_str().unwrap()`:路径非 UTF-8 时 panic(Linux DRM 设备路径通常 ASCII)。
|
||||
let device_cstr = CString::new(drm_device.to_str().unwrap())?;
|
||||
// 初始化为 null_mut:FFmpeg 的 out-pointer 约定(调用者置 null,被调者赋值)。
|
||||
let mut p: *mut ffi::AVBufferRef = ptr::null_mut();
|
||||
// 中文概述:调用 FFmpeg C API 创建 VAAPI `AVHWDeviceContext`,写入 `*p`。
|
||||
// 失败返回负的 AVERROR;成功返回 0 且 `p` 指向新分配的 `AVBufferRef`(refcount=1)。
|
||||
// SAFETY: device_cstr is a valid C string for the duration of the call;
|
||||
// p is a valid out-pointer that FFmpeg initializes on success.
|
||||
let ret = unsafe {
|
||||
@@ -100,19 +174,30 @@ impl AvHwDevCtx {
|
||||
Ok(Self { ptr: p })
|
||||
}
|
||||
|
||||
// 返回裸 `*mut AVBufferRef`,调用方负责管理引用计数。
|
||||
// 通常用于传给 FFmpeg API 的 `hw_device_ctx` 字段(FFmpeg 内部会 av_buffer_ref)。
|
||||
pub fn as_ptr(&self) -> *mut ffi::AVBufferRef {
|
||||
self.ptr
|
||||
}
|
||||
|
||||
// 增加 refcount,返回新的 `*mut AVBufferRef`(不复制底层缓冲)。
|
||||
// 用于把设备 ref 传给编码器的 `AVCodecContext::hw_device_ctx`,编码器 Drop 时
|
||||
// 调用 `av_buffer_unref` 释放自己持有的 ref,不影响本 wrapper。
|
||||
pub fn ref_clone(&self) -> *mut ffi::AVBufferRef {
|
||||
// 中文概述:FFmpeg 引用计数 +1,返回新 ref;线程安全(内部原子操作)。
|
||||
// SAFETY: av_buffer_ref atomically increments refcount and returns a new ref.
|
||||
unsafe { ffi::av_buffer_ref(self.ptr) }
|
||||
}
|
||||
}
|
||||
|
||||
// RAII 析构:减少 refcount。如果是最后一个 ref(refcount→0),FFmpeg 释放
|
||||
// 底层的 `AVHWDeviceContext` 和 `VADisplay`。`is_null()` 守卫是为了应对
|
||||
// `Drop` 被多次调用或 `new_vaapi` 失败后 ptr 仍为 null 的边缘情况(实际上
|
||||
// Rust 的 Drop 不会被调用两次,但 FFmpeg 的 av_buffer_unref 接受 null 入参)。
|
||||
impl Drop for AvHwDevCtx {
|
||||
fn drop(&mut self) {
|
||||
if !self.ptr.is_null() {
|
||||
// 中文概述:FFmpeg 引用计数 -1,refcount=0 时释放底层设备上下文。
|
||||
// SAFETY: av_buffer_unref decrements refcount; frees the buffer when it hits zero.
|
||||
unsafe { ffi::av_buffer_unref(&mut self.ptr) };
|
||||
}
|
||||
@@ -123,23 +208,38 @@ impl Drop for AvHwDevCtx {
|
||||
// AvHwFrameCtx
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// VAAPI 硬件帧池的 RAII 包装。`AVHWFramesContext` 是 FFmpeg 的硬件帧分配器,
|
||||
// 编码器通过它分配 `AV_PIX_FMT_VAAPI` 帧(GPU 表面)。`initial_pool_size=4`
|
||||
// 表示池大小为 4(足够 send/receive pipeline:1 帧 encode 中 + 1 帧 queue + 2 缓冲)。
|
||||
// 字段配置(width/height/sw_format)必须在 `av_hwframe_ctx_init` 前设置;
|
||||
// `sw_fmt` 是 CPU 可读的软件格式(VAAPI 内部从该格式上传/下载)。
|
||||
pub struct AvHwFrameCtx {
|
||||
ptr: *mut ffi::AVBufferRef,
|
||||
}
|
||||
|
||||
// 中文概述:声明 `AvHwFrameCtx` 可以跨线程 `Send`。前提是 `&mut self` 独占访问,
|
||||
// 且底层硬件帧池在 send/receive 模式下线程安全。与 `AvHwDevCtx` 同样的 exclusivity 假设。
|
||||
// 不修复:保留 AGENTS.md 文档的并发约束。
|
||||
// SAFETY: AvHwFrameCtx wraps an FFmpeg AVBufferRef to an AVHWFramesContext.
|
||||
// It is only accessed through &mut self, ensuring no concurrent mutation.
|
||||
// The underlying hardware frames pool is thread-safe for the send/receive pattern.
|
||||
unsafe impl Send for AvHwFrameCtx {}
|
||||
|
||||
impl AvHwFrameCtx {
|
||||
// 内部构造函数:分配 `AVHWFramesContext`、配置字段、调用 `av_hwframe_ctx_init`。
|
||||
// 失败时(alloc 或 init 失败)返回 Err,确保 ptr 不泄漏。
|
||||
fn new_inner(hw_dev: &AvHwDevCtx, w: u32, h: u32, sw_fmt: ff::format::Pixel) -> Result<Self> {
|
||||
// 中文概述:分配 `AVHWFramesContext`,refcount=1,关联到 hw_dev 的设备上下文。
|
||||
// 返回的 ref 的 `data` 字段指向未初始化的 `AVHWFramesContext`,需手动配置。
|
||||
// SAFETY: hw_dev is a live AVHWDeviceContext; FFmpeg returns either a valid
|
||||
// frames context ref or null (checked below).
|
||||
let mut p = unsafe { ffi::av_hwframe_ctx_alloc(hw_dev.as_ptr()) };
|
||||
if p.is_null() {
|
||||
bail!("av_hwframe_ctx_alloc returned null");
|
||||
}
|
||||
// 中文概述:通过 `(*p).data as *mut AVHWFramesContext` 访问 FFmpeg 内部类型擦除的
|
||||
// 字段(`AVBufferRef::data` 是 `*mut u8`,实际指向具体 context 类型)。
|
||||
// 直接赋值 `format`/`sw_format`/`width`/`height`/`initial_pool_size` 5 个字段。
|
||||
// SAFETY: p is a valid AVBufferRef from av_hwframe_ctx_alloc.
|
||||
// Its .data field points to an AVHWFramesContext that we must configure.
|
||||
unsafe {
|
||||
@@ -150,10 +250,12 @@ impl AvHwFrameCtx {
|
||||
(*fc).height = h as i32;
|
||||
(*fc).initial_pool_size = 4;
|
||||
}
|
||||
// 中文概述:初始化帧池(实际分配 GPU 表面)。失败时必须手动 unref p 否则泄漏。
|
||||
// SAFETY: p is a valid AVHWFramesContext ref configured above and not yet
|
||||
// transferred or freed.
|
||||
let ret = unsafe { ffi::av_hwframe_ctx_init(p) };
|
||||
if ret < 0 {
|
||||
// 中文概述:init 失败的清理路径——p 仍有效但不可用,unref 释放。
|
||||
// SAFETY: p is valid but init failed; clean up.
|
||||
unsafe { ffi::av_buffer_unref(&mut p) };
|
||||
bail!("av_hwframe_ctx_init failed: {}", ff_err(ret));
|
||||
@@ -161,6 +263,7 @@ impl AvHwFrameCtx {
|
||||
Ok(Self { ptr: p })
|
||||
}
|
||||
|
||||
// 为采集路径创建硬件帧池。`sw_fmt` 通常是 `BGRA`(PipeWire DMA-BUF 像素格式)。
|
||||
pub fn for_capture(
|
||||
hw_dev: &AvHwDevCtx,
|
||||
w: u32,
|
||||
@@ -175,26 +278,35 @@ impl AvHwFrameCtx {
|
||||
}
|
||||
|
||||
pub fn ref_clone(&self) -> *mut ffi::AVBufferRef {
|
||||
// 中文概述:FFmpeg 引用计数 +1,返回新 ref;用于把帧池 ref 传给编码器。
|
||||
// SAFETY: av_buffer_ref atomically increments refcount and returns a new ref.
|
||||
unsafe { ffi::av_buffer_ref(self.ptr) }
|
||||
}
|
||||
}
|
||||
|
||||
// RAII 析构:与 AvHwDevCtx::Drop 同样的模式(unref 释放 ref)。
|
||||
impl Drop for AvHwFrameCtx {
|
||||
fn drop(&mut self) {
|
||||
if !self.ptr.is_null() {
|
||||
// 中文概述:FFmpeg 引用计数 -1,refcount=0 时释放帧池和 GPU 表面。
|
||||
// SAFETY: av_buffer_unref decrements refcount; frees when zero.
|
||||
unsafe { ffi::av_buffer_unref(&mut self.ptr) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 启动时探针:测试 `drm_device` 能否通过 VAAPI 导入 PipeWire 给的 DMA-BUF 帧。
|
||||
// 用于 backend_detect 决定走硬件(VAAPI)还是软件(x264)编码路径。
|
||||
// 失败原因:硬件不支持给定格式/修饰符组合、DRM 设备无 VAAPI、内核 driver 限制。
|
||||
// 成功只是"探针通过",不代表实际编码时一直可用(运行时仍可能因 OOM 等失败)。
|
||||
/// Test whether `drm_device` can import the PipeWire DMA-BUF frame via VAAPI.
|
||||
pub fn test_dma_buf_import(drm_device: &Path, frame: &PwDmaBufFrame) -> Result<()> {
|
||||
let hw_dev = AvHwDevCtx::new_vaapi(drm_device)?;
|
||||
let frames =
|
||||
AvHwFrameCtx::for_capture(&hw_dev, frame.width, frame.height, ff::format::Pixel::BGRA)?;
|
||||
|
||||
// 中文概述:调用下面的 `import_dma_buf_to_vaapi`,用 `av_hwframe_map` 把 DMA-BUF
|
||||
// 映射到 VAAPI 表面(零拷贝)。`as_raw_fd()` 把 `OwnedFd` 转为裸 fd 传给 FFI。
|
||||
// SAFETY: frames is a live VAAPI frames context; frame carries valid DMA-BUF metadata.
|
||||
unsafe {
|
||||
import_dma_buf_to_vaapi(
|
||||
@@ -212,6 +324,10 @@ pub fn test_dma_buf_import(drm_device: &Path, frame: &PwDmaBufFrame) -> Result<(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// 中文概述:把 PipeWire 的 DMA-BUF 帧零拷贝映射为 VAAPI `AVFrame`。
|
||||
// DMA-BUF 是 Linux 内核的 GPU 缓冲共享机制(fd 指向 GPU 内存);`av_hwframe_map`
|
||||
// 在 VAAPI 内部通过 `vaCreateSurfaces` + `vaMapBuffer` 直接 GPU 映射,无需 CPU 拷贝。
|
||||
// 类比 Go:Go 没有等价物——cgo + libavutil 才能实现同样的零拷贝路径。
|
||||
/// Import a DMA-BUF into a VAAPI hardware frame via zero-copy `av_hwframe_map`.
|
||||
///
|
||||
/// # Safety
|
||||
@@ -227,11 +343,16 @@ pub unsafe fn import_dma_buf_to_vaapi(
|
||||
stride: u32,
|
||||
offset: u64,
|
||||
) -> Result<ff::frame::Video> {
|
||||
// `libc::dup` 复制 fd——`AVDRMFrameDescriptor` 持有 dup 后的 fd,
|
||||
// 当 descriptor 释放时 `cleanup_drm_descriptor` 会 `libc::close` 它;
|
||||
// 原始 `raw_fd` 不被本函数消费(仍由调用方拥有,通常是 PipeWire)。
|
||||
let duped_fd = libc::dup(raw_fd);
|
||||
if duped_fd < 0 {
|
||||
bail!("dup(fd) failed: {}", std::io::Error::last_os_error());
|
||||
}
|
||||
|
||||
// `mem::zeroed()`:FFI 零初始化(所有字段置 0),类似 Go 的 `&drmDesc{}`。
|
||||
// SAFETY 后续不再赘述——这是 `unsafe fn` 内部,调用契约由函数签名保证。
|
||||
let mut desc: ffi::AVDRMFrameDescriptor = mem::zeroed();
|
||||
desc.nb_objects = 1;
|
||||
desc.objects[0].fd = duped_fd;
|
||||
@@ -244,9 +365,14 @@ pub unsafe fn import_dma_buf_to_vaapi(
|
||||
desc.layers[0].planes[0].offset = offset as isize;
|
||||
desc.layers[0].planes[0].pitch = stride as isize;
|
||||
|
||||
// `Box::new(desc)` 把栈上的 descriptor 移到堆上;`Box::into_raw` 交出所有权给 FFmpeg。
|
||||
// `av_buffer_create` 会接管这块内存:当 AVBufferRef refcount 归零时调用
|
||||
// `cleanup_drm_descriptor`(自定义 free 回调)→ close fd + Box::from_raw 释放堆内存。
|
||||
let desc_box = Box::new(desc);
|
||||
let desc_ptr = Box::into_raw(desc_box);
|
||||
|
||||
// 创建 FFmpeg 自定义 AVBufferRef:data 指向 descriptor,free 回调为 cleanup_drm_descriptor。
|
||||
// 失败时(极少)需要手动恢复 Box 并 close fd,否则泄漏。
|
||||
let buf_ref = ffi::av_buffer_create(
|
||||
desc_ptr as *mut u8,
|
||||
std::mem::size_of::<ffi::AVDRMFrameDescriptor>(),
|
||||
@@ -260,6 +386,8 @@ pub unsafe fn import_dma_buf_to_vaapi(
|
||||
bail!("av_buffer_create returned null for DRM descriptor");
|
||||
}
|
||||
|
||||
// 构造 src AVFrame:格式为 `AV_PIX_FMT_DRM_PRIME`(FFmpeg 的 DMA-BUF 包装格式),
|
||||
// `data[0]` 指向 AVBufferRef 的 data(即 descriptor),`buf[0]` 持有 ref 防止提前释放。
|
||||
let mut src = ff::frame::Video::empty();
|
||||
{
|
||||
let sp = src.as_mut_ptr();
|
||||
@@ -270,7 +398,10 @@ pub unsafe fn import_dma_buf_to_vaapi(
|
||||
(*sp).buf[0] = buf_ref;
|
||||
}
|
||||
|
||||
// 构造 dst AVFrame:格式为 `AV_PIX_FMT_VAAPI`,挂上 `hw_frames_ctx` 让 FFmpeg 知道用哪个池。
|
||||
let mut dst = ff::frame::Video::empty();
|
||||
// 中文概述:把 dst 的 format 设为 VAAPI、`hw_frames_ctx` 设为新的 ref。
|
||||
// `av_buffer_ref(frames_ctx)` 必须成功,否则 av_hwframe_map 无法定位帧池。
|
||||
// SAFETY: frames_ctx is guaranteed by this unsafe function's contract to be a
|
||||
// valid initialized VAAPI frames context; we set format/hw_frames_ctx on a
|
||||
// freshly allocated dst frame.
|
||||
@@ -282,6 +413,8 @@ pub unsafe fn import_dma_buf_to_vaapi(
|
||||
bail!("av_buffer_ref(frames_ctx) returned null");
|
||||
}
|
||||
}
|
||||
// 中文概述:核心 FFI 调用——FFmpeg 在内部调用 VAAPI `vaCreateSurfaces`+`vaMapBuffer`
|
||||
// 把 DMA-BUF 映射到 GPU 表面,写入 dst。零 CPU 拷贝。
|
||||
// SAFETY: src and dst are initialized AVFrames; dst has a valid hw_frames_ctx
|
||||
// ref and av_hwframe_map fills dst from src.
|
||||
let ret = unsafe {
|
||||
@@ -298,6 +431,11 @@ pub unsafe fn import_dma_buf_to_vaapi(
|
||||
Ok(dst)
|
||||
}
|
||||
|
||||
// `av_buffer_create` 的 free 回调:当自定义 AVBufferRef 的 refcount 归零时被 FFmpeg 调用。
|
||||
// `unsafe extern "C" fn` 是因为 FFmpeg 通过 C 函数指针调用它(C ABI 兼容)。
|
||||
// 职责:(1) close DMA-BUF 的 dup'd fd;(2) `Box::from_raw` 恢复堆内存所有权并立即 drop。
|
||||
// `_opaque`:FFmpeg 的 AVBufferRef 允许附带 opaque 数据(本例未用,故 `_` 前缀)。
|
||||
// 类比 Go:相当于 `runtime.SetFinalizer` 的回调,但是 FFmpeg 主动调用的(确定性)。
|
||||
unsafe extern "C" fn cleanup_drm_descriptor(_opaque: *mut c_void, data: *mut u8) {
|
||||
let desc = data as *mut ffi::AVDRMFrameDescriptor;
|
||||
if !desc.is_null() && (*desc).nb_objects > 0 && (*desc).objects[0].fd >= 0 {
|
||||
@@ -306,19 +444,29 @@ unsafe extern "C" fn cleanup_drm_descriptor(_opaque: *mut c_void, data: *mut u8)
|
||||
let _ = Box::from_raw(data as *mut ffi::AVDRMFrameDescriptor);
|
||||
}
|
||||
|
||||
// 把 FFmpeg 的负数错误码(AVERROR)转成人类可读字符串。
|
||||
// `av_strerror` 是 FFmpeg 的线程安全 strerror 等价物,写入调用方提供的缓冲区。
|
||||
// `pub(crate)` 可见性:仅供本 crate 内部错误格式化使用(不暴露给外部)。
|
||||
/// Convert an FFmpeg error code to a human-readable string.
|
||||
pub(crate) fn av_err_to_string(err: i32) -> String {
|
||||
// 128 字节足够 AVERROR 描述(FFmpeg 内部字符串都很短)。
|
||||
let mut buf = vec![0u8; 128];
|
||||
// 中文概述:调用 FFmpeg `av_strerror`,把错误码描述写入 buf。
|
||||
// 失败(无效错误码)时 buf 仍为全 0,trim 后为空字符串。
|
||||
// SAFETY: buf points to 128 writable bytes and lives for the duration of
|
||||
// av_strerror.
|
||||
unsafe {
|
||||
ffi::av_strerror(err, buf.as_mut_ptr() as *mut i8, buf.len());
|
||||
}
|
||||
// `from_utf8_lossy` 容忍非 UTF-8 字节;`trim_end_matches('\0')` 去掉 C 字符串结尾的 NUL。
|
||||
String::from_utf8_lossy(&buf)
|
||||
.trim_end_matches('\0')
|
||||
.to_string()
|
||||
}
|
||||
|
||||
// 带数字码的错误格式化:`"error -22 (Invalid argument)"`。
|
||||
// 数字码很重要——AVERROR 是负数,但有些 FFmpeg 错误是宏(如 AVERROR(EAGAIN)),
|
||||
// 看数字能区分底层 errno 还是 FFmpeg 自定义错误。
|
||||
/// Format an FFmpeg error code with both numeric value and description.
|
||||
/// Example output: "error -22 (Invalid argument)"
|
||||
pub(crate) fn ff_err(ret: i32) -> String {
|
||||
|
||||
Reference in New Issue
Block a user