Files
One-KVM/docs/report/hwcodec/03-build-integration.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

11 KiB
Raw Permalink Blame History

hwcodec 构建系统与集成指南

1. 项目结构

libs/hwcodec/
├── Cargo.toml           # 包配置
├── Cargo.lock           # 依赖锁定
├── build.rs             # 构建脚本
└── src/                 # Rust 源码
    ├── lib.rs           # 库入口
    ├── common.rs        # 公共定义
    ├── ffmpeg.rs        # FFmpeg 集成
    └── ffmpeg_ram/      # RAM 编解码
        ├── mod.rs
        ├── encode.rs
        └── decode.rs
└── cpp/                 # C++ 源码
    ├── common/          # 公共代码
    │   ├── log.cpp
    │   ├── log.h
    │   ├── util.cpp
    │   ├── util.h
    │   ├── callback.h
    │   ├── common.h
    │   └── platform/
    │       ├── linux/
    │       │   ├── linux.cpp
    │       │   └── linux.h
    │       └── win/
    │           ├── win.cpp
    │           └── win.h
    ├── ffmpeg_ram/      # FFmpeg RAM 实现
    │   ├── ffmpeg_ram_encode.cpp
    │   ├── ffmpeg_ram_decode.cpp
    │   └── ffmpeg_ram_ffi.h
    └── yuv/             # YUV 处理
        └── yuv.cpp

2. Cargo 配置

2.1 Cargo.toml

[package]
name = "hwcodec"
version = "0.8.0"
edition = "2021"
description = "Hardware video codec for IP-KVM (Windows/Linux)"

[features]
default = []

[dependencies]
log = "0.4"              # 日志
serde_derive = "1.0"     # 序列化派生宏
serde = "1.0"            # 序列化
serde_json = "1.0"       # JSON 序列化

[build-dependencies]
cc = "1.0"               # C++ 编译
bindgen = "0.59"         # FFI 绑定生成

[dev-dependencies]
env_logger = "0.10"      # 日志输出

2.2 与原版的区别

特性 原版 (RustDesk) 简化版 (One-KVM)
vram feature ✗ (已移除)
外部 SDK 需要 不需要
版本号 0.7.1 0.8.0
目标平台 Windows/Linux/macOS/Android Windows/Linux

2.3 使用方式

# 在 One-KVM 项目中使用
[dependencies]
hwcodec = { path = "libs/hwcodec" }

3. 构建脚本详解 (build.rs)

3.1 主入口

fn main() {
    let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
    let mut builder = Build::new();

    // 1. 构建公共模块
    build_common(&mut builder);

    // 2. 构建 FFmpeg 相关模块
    ffmpeg::build_ffmpeg(&mut builder);

    // 3. 编译生成静态库
    builder.static_crt(true).compile("hwcodec");
}

3.2 公共模块构建

fn build_common(builder: &mut Build) {
    let common_dir = manifest_dir.join("cpp").join("common");

    // 生成 FFI 绑定
    bindgen::builder()
        .header(common_dir.join("common.h"))
        .header(common_dir.join("callback.h"))
        .rustified_enum("*")
        .generate()
        .write_to_file(OUT_DIR.join("common_ffi.rs"));

    // 平台相关代码
    #[cfg(windows)]
    builder.file(common_dir.join("platform/win/win.cpp"));

    #[cfg(target_os = "linux")]
    builder.file(common_dir.join("platform/linux/linux.cpp"));

    // 工具代码
    builder.files([
        common_dir.join("log.cpp"),
        common_dir.join("util.cpp"),
    ]);
}

3.3 FFmpeg 模块构建

mod ffmpeg {
    pub fn build_ffmpeg(builder: &mut Build) {
        // 生成 FFmpeg FFI 绑定
        ffmpeg_ffi();

        // 链接 FFmpeg 库
        if let Ok(vcpkg_root) = std::env::var("VCPKG_ROOT") {
            link_vcpkg(builder, vcpkg_root.into());
        } else {
            link_system_ffmpeg(builder);  // pkg-config
        }

        // 链接系统库
        link_os();

        // 构建 FFmpeg RAM 模块
        build_ffmpeg_ram(builder);
    }
}

3.4 FFmpeg 链接方式

VCPKG (跨平台静态链接)

fn link_vcpkg(builder: &mut Build, path: PathBuf) -> PathBuf {
    // 目标平台识别
    let target = match (target_os, target_arch) {
        ("windows", "x86_64") => "x64-windows-static",
        ("linux", arch) => format!("{}-linux", arch),
        _ => panic!("unsupported platform"),
    };

    let lib_path = path.join("installed").join(target).join("lib");

    // 链接 FFmpeg 静态库
    println!("cargo:rustc-link-search=native={}", lib_path);
    ["avcodec", "avutil", "avformat"].iter()
        .for_each(|lib| println!("cargo:rustc-link-lib=static={}", lib));
}

pkg-config (Linux 动态链接)

fn link_system_ffmpeg(builder: &mut Build) {
    let libs = ["libavcodec", "libavutil", "libavformat", "libswscale"];

    for lib in &libs {
        // 获取编译标志
        let cflags = Command::new("pkg-config")
            .args(["--cflags", lib])
            .output()?;

        // 获取链接标志
        let libs = Command::new("pkg-config")
            .args(["--libs", lib])
            .output()?;

        // 解析并应用
        for flag in libs.split_whitespace() {
            if flag.starts_with("-L") {
                println!("cargo:rustc-link-search=native={}", &flag[2..]);
            } else if flag.starts_with("-l") {
                println!("cargo:rustc-link-lib={}", &flag[2..]);
            }
        }
    }
}

3.5 系统库链接

fn link_os() {
    let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();

    let libs: Vec<&str> = match target_os.as_str() {
        "windows" => vec!["User32", "bcrypt", "ole32", "advapi32"],
        "linux" => vec!["drm", "X11", "stdc++", "z"],
        _ => panic!("unsupported os"),
    };

    for lib in libs {
        println!("cargo:rustc-link-lib={}", lib);
    }
}

4. FFI 绑定生成

4.1 bindgen 配置

bindgen::builder()
    .header("path/to/header.h")
    .rustified_enum("*")           // 生成 Rust 枚举
    .parse_callbacks(Box::new(Callbacks))  // 自定义回调
    .generate()
    .write_to_file(OUT_DIR.join("ffi.rs"));

4.2 自定义派生

#[derive(Debug)]
struct CommonCallbacks;

impl bindgen::callbacks::ParseCallbacks for CommonCallbacks {
    fn add_derives(&self, name: &str) -> Vec<String> {
        // 为特定类型添加序列化支持
        match name {
            "DataFormat" | "SurfaceFormat" | "API" => {
                vec!["Serialize".to_string(), "Deserialize".to_string()]
            }
            _ => vec![],
        }
    }
}

4.3 生成的文件

文件 来源 内容
common_ffi.rs common.h, callback.h 枚举、常量、回调类型
ffmpeg_ffi.rs ffmpeg_ffi.h FFmpeg 日志级别、函数
ffmpeg_ram_ffi.rs ffmpeg_ram_ffi.h 编解码器函数

5. 平台构建指南

5.1 Linux 构建

# 安装 FFmpeg 开发库
sudo apt install libavcodec-dev libavformat-dev libavutil-dev libswscale-dev

# 安装其他依赖
sudo apt install libdrm-dev libx11-dev pkg-config

# 安装 clang (bindgen 需要)
sudo apt install clang libclang-dev

# 构建
cargo build --release -p hwcodec

5.2 Windows 构建 (VCPKG)

# 安装 VCPKG
git clone https://github.com/microsoft/vcpkg
cd vcpkg
./bootstrap-vcpkg.bat

# 安装 FFmpeg
./vcpkg install ffmpeg:x64-windows-static

# 设置环境变量
$env:VCPKG_ROOT = "C:\path\to\vcpkg"

# 构建
cargo build --release -p hwcodec

5.3 交叉编译

# 安装 cross
cargo install cross --git https://github.com/cross-rs/cross

# ARM64 Linux
cross build --release -p hwcodec --target aarch64-unknown-linux-gnu

# ARMv7 Linux
cross build --release -p hwcodec --target armv7-unknown-linux-gnueabihf

6. 集成到 One-KVM

6.1 依赖配置

# Cargo.toml
[dependencies]
hwcodec = { path = "libs/hwcodec" }

6.2 使用示例

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

// 检测可用编码器
let encoders = Encoder::available_encoders(ctx, None);

// 创建编码器
let encoder = Encoder::new(EncodeContext {
    name: "h264_vaapi".to_string(),
    width: 1920,
    height: 1080,
    pixfmt: AVPixelFormat::AV_PIX_FMT_NV12,
    fps: 30,
    gop: 30,
    kbs: 4000,
    // ...
})?;

// 编码
let frames = encoder.encode(&yuv_data, pts_ms)?;

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

// 解码
let frames = decoder.decode(&mjpeg_data)?;

6.3 日志集成

// hwcodec 使用 log crate与 One-KVM 日志系统兼容
use log::{debug, info, warn, error};

// C++ 层日志通过回调传递到 Rust
#[no_mangle]
pub extern "C" fn hwcodec_av_log_callback(level: i32, message: *const c_char) {
    // 转发到 Rust log 系统
    match level {
        AV_LOG_ERROR => error!("{}", message),
        AV_LOG_WARNING => warn!("{}", message),
        AV_LOG_INFO => info!("{}", message),
        AV_LOG_DEBUG => debug!("{}", message),
        _ => {}
    }
}

7. 故障排除

7.1 编译错误

FFmpeg 未找到:

error: pkg-config failed for libavcodec

解决: 安装 FFmpeg 开发库

sudo apt install libavcodec-dev libavformat-dev libavutil-dev libswscale-dev

bindgen 错误:

error: failed to run custom build command for `hwcodec`

解决: 安装 clang

sudo apt install clang libclang-dev

7.2 链接错误

符号未定义:

undefined reference to `av_log_set_level'

解决: 检查 FFmpeg 库链接顺序,确保 pkg-config 正确配置

动态库未找到:

error while loading shared libraries: libavcodec.so.59

解决:

sudo ldconfig
# 或设置 LD_LIBRARY_PATH
export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH

7.3 运行时错误

硬件编码器不可用:

Encoder h264_vaapi test failed

检查:

  1. 驱动是否正确安装: vainfo
  2. 权限是否足够: ls -la /dev/dri/
  3. 用户是否在 video 组: groups

解码失败:

avcodec_receive_frame failed, ret = -11

解决: 这通常表示需要更多输入数据 (EAGAIN),是正常行为

8. 与原版 RustDesk hwcodec 的构建差异

8.1 移除的构建步骤

步骤 原因
build_mux() 移除了 Mux 模块
build_ffmpeg_vram() 移除了 VRAM 模块
sdk::build_sdk() 移除了外部 SDK 依赖
macOS 框架链接 移除了 macOS 支持
Android NDK 链接 移除了 Android 支持

8.2 简化的构建流程

原版构建流程:
build.rs
├── build_common()
├── ffmpeg::build_ffmpeg()
│   ├── build_ffmpeg_ram()
│   ├── build_ffmpeg_vram()  [已移除]
│   └── build_mux()          [已移除]
└── sdk::build_sdk()         [已移除]

简化版构建流程:
build.rs
├── build_common()
└── ffmpeg::build_ffmpeg()
    └── build_ffmpeg_ram()

8.3 优势

  1. 更快的编译: 无需编译外部 SDK 代码
  2. 更少的依赖: 无需下载 ~9MB 的外部 SDK
  3. 更简单的维护: 代码量减少约 67%
  4. 更小的二进制: 不包含未使用的功能