Files
One-KVM/docs/report/hwcodec/01-api-reference.md
mofeng-git d0e2e13b35 refactor(hwcodec): 简化 hwcodec 库以适配 IP-KVM 场景
移除 IP-KVM 场景不需要的功能模块:
- 移除 VRAM 模块 (GPU 显存直接编解码)
- 移除 Mux 模块 (视频混流)
- 移除 macOS/Android 平台支持
- 移除外部 SDK 依赖 (~9MB)
- 移除开发工具和示例程序

简化解码器为仅支持 MJPEG (采集卡输出格式)
简化 NVIDIA 检测代码 (使用 dlopen 替代 SDK)
更新版本号至 0.8.0
更新相关技术文档
2025-12-31 19:47:08 +08:00

12 KiB
Raw Blame History

hwcodec 编解码器 API 详解

1. 编码器 API

1.1 编码器初始化

EncodeContext 参数

pub struct EncodeContext {
    pub name: String,          // 编码器名称
    pub mc_name: Option<String>, // MediaCodec 名称 (保留字段)
    pub width: i32,            // 视频宽度 (必须为偶数)
    pub height: i32,           // 视频高度 (必须为偶数)
    pub pixfmt: AVPixelFormat, // 像素格式
    pub align: i32,            // 内存对齐 (通常为 0 或 32)
    pub fps: i32,              // 帧率
    pub gop: i32,              // GOP 大小 (关键帧间隔)
    pub rc: RateControl,       // 码率控制模式
    pub quality: Quality,      // 编码质量
    pub kbs: i32,              // 目标码率 (kbps)
    pub q: i32,                // 量化参数 (CQ 模式)
    pub thread_count: i32,     // 编码线程数
}

参数说明

参数 类型 说明 推荐值
name String FFmpeg 编码器名称 见下表
width i32 视频宽度 1920
height i32 视频高度 1080
pixfmt AVPixelFormat 像素格式 NV12 / YUV420P
align i32 内存对齐 0 (自动)
fps i32 帧率 30
gop i32 GOP 大小 30 (1秒)
rc RateControl 码率控制 CBR / VBR
quality Quality 质量级别 Medium
kbs i32 码率 (kbps) 2000-8000
thread_count i32 线程数 4

编码器名称对照表

名称 格式 加速 平台
h264_nvenc H.264 NVIDIA GPU Windows/Linux
hevc_nvenc H.265 NVIDIA GPU Windows/Linux
h264_amf H.264 AMD GPU Windows/Linux
hevc_amf H.265 AMD GPU Windows/Linux
h264_qsv H.264 Intel QSV Windows
hevc_qsv H.265 Intel QSV Windows
h264_vaapi H.264 VAAPI Linux
hevc_vaapi H.265 VAAPI Linux
vp8_vaapi VP8 VAAPI Linux
vp9_vaapi VP9 VAAPI Linux
h264_rkmpp H.264 Rockchip MPP Linux
hevc_rkmpp H.265 Rockchip MPP Linux
h264_v4l2m2m H.264 V4L2 M2M Linux
hevc_v4l2m2m H.265 V4L2 M2M Linux
h264 H.264 软件 (x264) 全平台
hevc H.265 软件 (x265) 全平台
libvpx VP8 软件 全平台
libvpx-vp9 VP9 软件 全平台
mjpeg MJPEG 软件 全平台

1.2 创建编码器

use hwcodec::ffmpeg_ram::encode::{Encoder, EncodeContext};
use hwcodec::ffmpeg::{AVPixelFormat};
use hwcodec::common::{RateControl, Quality};

let ctx = EncodeContext {
    name: "h264_vaapi".to_string(),
    mc_name: None,
    width: 1920,
    height: 1080,
    pixfmt: AVPixelFormat::AV_PIX_FMT_NV12,
    align: 0,
    fps: 30,
    gop: 30,
    rc: RateControl::RC_CBR,
    quality: Quality::Quality_Medium,
    kbs: 4000,
    q: 0,
    thread_count: 4,
};

let encoder = Encoder::new(ctx)?;
println!("Linesize: {:?}", encoder.linesize);
println!("Offset: {:?}", encoder.offset);
println!("Buffer length: {}", encoder.length);

1.3 编码帧

// 准备 YUV 数据
let yuv_data: Vec<u8> = prepare_yuv_frame();

// 编码
let pts_ms: i64 = 0; // 时间戳 (毫秒)
match encoder.encode(&yuv_data, pts_ms) {
    Ok(frames) => {
        for frame in frames.iter() {
            println!("Encoded: {} bytes, pts={}, key={}",
                     frame.data.len(), frame.pts, frame.key);
            // 发送 frame.data
        }
    }
    Err(code) => {
        eprintln!("Encode error: {}", code);
    }
}

1.4 动态调整码率

// 动态调整到 6000 kbps
encoder.set_bitrate(6000)?;

1.5 请求关键帧

// 下一帧强制编码为 IDR 帧
encoder.request_keyframe();

1.6 检测可用编码器

use hwcodec::ffmpeg_ram::encode::{Encoder, EncodeContext};
use hwcodec::ffmpeg_ram::CodecInfo;

let ctx = EncodeContext {
    name: String::new(),
    mc_name: None,
    width: 1920,
    height: 1080,
    pixfmt: AVPixelFormat::AV_PIX_FMT_NV12,
    align: 0,
    fps: 30,
    gop: 30,
    rc: RateControl::RC_DEFAULT,
    quality: Quality::Quality_Default,
    kbs: 4000,
    q: 0,
    thread_count: 4,
};

let available_encoders = Encoder::available_encoders(ctx, None);
for encoder in available_encoders {
    println!("Available: {} (format: {:?}, priority: {})",
             encoder.name, encoder.format, encoder.priority);
}

2. 解码器 API

2.1 IP-KVM 专用设计

在 One-KVM IP-KVM 场景中,解码器仅支持 MJPEG 软件解码。这是因为视频采集卡输出的格式是 MJPEG不需要其他格式的硬件解码支持。

2.2 解码器初始化

DecodeContext 参数

pub struct DecodeContext {
    pub name: String,           // 解码器名称 ("mjpeg")
    pub device_type: AVHWDeviceType, // 硬件设备类型 (NONE)
    pub thread_count: i32,      // 解码线程数
}

2.3 创建解码器

use hwcodec::ffmpeg_ram::decode::{Decoder, DecodeContext};
use hwcodec::ffmpeg::AVHWDeviceType;

let ctx = DecodeContext {
    name: "mjpeg".to_string(),
    device_type: AVHWDeviceType::AV_HWDEVICE_TYPE_NONE,
    thread_count: 4,
};

let decoder = Decoder::new(ctx)?;

2.4 解码帧

// 输入 MJPEG 编码数据
let mjpeg_data: Vec<u8> = receive_mjpeg_frame();

match decoder.decode(&mjpeg_data) {
    Ok(frames) => {
        for frame in frames.iter() {
            println!("Decoded: {}x{}, format={:?}, key={}",
                     frame.width, frame.height, frame.pixfmt, frame.key);

            // 访问 YUV 数据
            let y_plane = &frame.data[0];
            let u_plane = &frame.data[1];
            let v_plane = &frame.data[2];
        }
    }
    Err(code) => {
        eprintln!("Decode error: {}", code);
    }
}

2.5 DecodeFrame 结构体

pub struct DecodeFrame {
    pub pixfmt: AVPixelFormat,  // 输出像素格式
    pub width: i32,             // 帧宽度
    pub height: i32,            // 帧高度
    pub data: Vec<Vec<u8>>,     // 平面数据 [Y, U, V] 或 [Y, UV]
    pub linesize: Vec<i32>,     // 每个平面的行字节数
    pub key: bool,              // 是否为关键帧
}

像素格式与平面布局

像素格式 平面数 data[0] data[1] data[2]
YUV420P 3 Y U V
YUVJ420P 3 Y U V
YUV422P 3 Y U V
NV12 2 Y UV (交错) -
NV21 2 Y VU (交错) -

2.6 获取可用解码器

use hwcodec::ffmpeg_ram::decode::Decoder;

let available_decoders = Decoder::available_decoders();
for decoder in available_decoders {
    println!("Available: {} (format: {:?}, hwdevice: {:?})",
             decoder.name, decoder.format, decoder.hwdevice);
}

// 输出:
// Available: mjpeg (format: MJPEG, hwdevice: AV_HWDEVICE_TYPE_NONE)

3. 码率控制模式

3.1 RateControl 枚举

pub enum RateControl {
    RC_DEFAULT,  // 使用编码器默认
    RC_CBR,      // 恒定码率
    RC_VBR,      // 可变码率
    RC_CQ,       // 恒定质量 (需设置 q 参数)
}

3.2 模式说明

模式 说明 适用场景
RC_CBR 码率恒定,质量随场景变化 网络带宽受限
RC_VBR 质量优先,码率波动 本地存储
RC_CQ 恒定质量,码率波动大 质量敏感场景

3.3 各编码器支持情况

编码器 CBR VBR CQ
nvenc
amf ✓ (低延迟)
qsv
vaapi

4. 质量等级

4.1 Quality 枚举

pub enum Quality {
    Quality_Default, // 使用编码器默认
    Quality_High,    // 高质量 (慢速)
    Quality_Medium,  // 中等质量 (平衡)
    Quality_Low,     // 低质量 (快速)
}

4.2 编码器预设映射

质量 nvenc amf qsv
High - quality veryslow
Medium p4 balanced medium
Low p1 speed veryfast

5. 错误处理

5.1 错误码

错误码 常量 说明
0 HWCODEC_SUCCESS 成功
-1 HWCODEC_ERR_COMMON 通用错误
-2 HWCODEC_ERR_HEVC_COULD_NOT_FIND_POC HEVC 解码参考帧丢失

5.2 常见错误处理

match encoder.encode(&yuv_data, pts) {
    Ok(frames) => {
        // 处理编码帧
    }
    Err(-1) => {
        eprintln!("编码失败,可能是输入数据格式错误");
    }
    Err(code) => {
        eprintln!("未知错误: {}", code);
    }
}

6. 最佳实践

6.1 编码器选择策略

fn select_best_encoder(
    width: i32,
    height: i32,
    format: DataFormat
) -> Option<String> {
    let ctx = EncodeContext {
        width,
        height,
        pixfmt: AVPixelFormat::AV_PIX_FMT_NV12,
        // ... 其他参数
    };

    let encoders = Encoder::available_encoders(ctx, None);

    // 按优先级排序,选择最佳
    encoders.into_iter()
        .filter(|e| e.format == format)
        .min_by_key(|e| e.priority)
        .map(|e| e.name)
}

6.2 帧内存布局

// 获取 NV12 帧布局信息
let (linesize, offset, length) = ffmpeg_linesize_offset_length(
    AVPixelFormat::AV_PIX_FMT_NV12,
    1920,
    1080,
    0,  // align
)?;

// 分配缓冲区
let mut buffer = vec![0u8; length as usize];

// 填充 Y 平面: buffer[0..offset[0]]
// 填充 UV 平面: buffer[offset[0]..length]

6.3 关键帧控制

let mut frame_count = 0;

loop {
    // 每 30 帧强制一个关键帧
    if frame_count % 30 == 0 {
        encoder.request_keyframe();
    }

    encoder.encode(&yuv_data, pts)?;
    frame_count += 1;
}

6.4 线程安全

// Decoder 实现了 Send + Sync
unsafe impl Send for Decoder {}
unsafe impl Sync for Decoder {}

// 可以安全地在多线程间传递
let decoder = Arc::new(Mutex::new(Decoder::new(ctx)?));

7. IP-KVM 典型使用场景

7.1 视频采集和转码流程

USB 采集卡 (MJPEG)
       │
       ▼
┌─────────────────┐
│ MJPEG Decoder   │ ◄── Decoder::new("mjpeg")
│ (软件解码)      │
└────────┬────────┘
         │ YUV420P
         ▼
┌─────────────────┐
│ H264 Encoder    │ ◄── Encoder::new("h264_vaapi")
│ (硬件加速)      │
└────────┬────────┘
         │ H264 NAL
         ▼
    WebRTC 传输

7.2 完整示例

use hwcodec::ffmpeg_ram::decode::{Decoder, DecodeContext};
use hwcodec::ffmpeg_ram::encode::{Encoder, EncodeContext};
use hwcodec::ffmpeg::AVHWDeviceType;

// 创建 MJPEG 解码器
let decode_ctx = DecodeContext {
    name: "mjpeg".to_string(),
    device_type: AVHWDeviceType::AV_HWDEVICE_TYPE_NONE,
    thread_count: 4,
};
let mut decoder = Decoder::new(decode_ctx)?;

// 检测并选择最佳编码器
let encode_ctx = EncodeContext {
    name: String::new(),
    width: 1920,
    height: 1080,
    // ...
};
let available = Encoder::available_encoders(encode_ctx.clone(), None);
let best_h264 = available.iter()
    .filter(|e| e.format == DataFormat::H264)
    .min_by_key(|e| e.priority)
    .expect("No H264 encoder available");

// 使用最佳编码器创建实例
let encode_ctx = EncodeContext {
    name: best_h264.name.clone(),
    ..encode_ctx
};
let mut encoder = Encoder::new(encode_ctx)?;

// 处理循环
loop {
    let mjpeg_frame = capture_frame();

    // 解码 MJPEG -> YUV
    let decoded = decoder.decode(&mjpeg_frame)?;

    // 编码 YUV -> H264
    for frame in decoded {
        let yuv_data = frame.data.concat();
        let encoded = encoder.encode(&yuv_data, pts)?;

        // 发送编码数据
        for packet in encoded {
            send_to_webrtc(packet.data);
        }
    }
}