21、视频处理:FFmpeg跨平台封装、解码/编码、硬件加速适配
视频处理,说白了就是跟像素和比特流打交道。我做了这么多年跨平台开发,FFmpeg 几乎成了绕不开的坎。你想想看,Windows 上用 MediaFoundation,macOS 上有 VideoToolbox,Linux 下是 V4L2,每个平台都有自己的视频 API。但 FFmpeg 不一样,它把这些全包了,给你一个统一的接口。
嗯,今天我们就来聊聊怎么把 FFmpeg 封装成跨平台的视频处理模块。我会把解码、编码、硬件加速这些实战中踩过的坑,一个一个说清楚。
为什么选 FFmpeg?
我个人习惯,选库之前先看三点:社区活跃度、跨平台能力、License 友好度。FFmpeg 三点全占。
- 社区活跃:几乎每周都有更新,新格式支持很快
- 跨平台:Windows/Linux/macOS/Android/iOS 全支持
- License:LGPL 或 GPL,商业项目注意区分
我在项目中遇到过好几次,客户要求同时支持 H.264 和 H.265 解码,还要在 Intel 和 NVIDIA 显卡上都能跑。用 FFmpeg 一套接口搞定,省了不少事。
核心架构:解码/编码流程
先画一张图,把 FFmpeg 的核心流程理清楚。这张图我每次培训都会拿出来讲,因为它把最关键的几个环节串起来了。
流程其实不复杂:输入文件 → 解封装拿到压缩数据 → 解码成原始帧 → 滤镜处理 → 编码 → 封装输出。但每个环节都有坑,尤其是跨平台和硬件加速这块。
跨平台封装:接口设计
我建议把 FFmpeg 的操作封装成几个核心类,这样上层业务不用关心底层实现。下面是我常用的设计:
// 视频解码器封装
class VideoDecoder {
public:
virtual ~VideoDecoder() = default;
// 打开解码器
virtual bool Open(const std::string& url,
const DecoderConfig& config) = 0;
// 解码一帧
virtual bool DecodeFrame(uint8_t* data, size_t size,
VideoFrame& frame) = 0;
// 获取硬件加速类型
virtual HardwareAccelType GetHardwareAccel() const = 0;
// 释放资源
virtual void Close() = 0;
};
// 工厂模式创建平台相关实例
class VideoDecoderFactory {
public:
static std::unique_ptr<VideoDecoder> Create(
PlatformType platform,
HardwareAccelType accel = HardwareAccelType::Auto);
};
嗯,这里要注意:工厂模式是跨平台封装的利器。你可以在 Windows 上返回 DXVA2 加速的解码器,macOS 上返回 VideoToolbox 的,Linux 上返回 VAAPI 的。上层代码完全不用改。
核心原则:接口统一,实现分离。把平台相关的代码藏在工厂后面,业务层只看到纯虚函数。
解码实战:从文件到帧
解码这块,我踩过最大的坑是内存管理。FFmpeg 的 AVFrame 有自己的引用计数,跨线程传递时很容易出问题。
// 解码循环 - 带硬件加速支持
bool DecodePacket(AVPacket* pkt, AVCodecContext* ctx,
AVFrame* frame, int* got_frame) {
int ret = avcodec_send_packet(ctx, pkt);
if (ret < 0) {
// 我曾经在这里漏了错误处理,结果线上崩溃
return false;
}
ret = avcodec_receive_frame(ctx, frame);
if (ret == 0) {
*got_frame = 1;
// 如果是硬件加速帧,需要转回 CPU 内存
if (frame->format == AV_PIX_FMT_DXVA2_VLD ||
frame->format == AV_PIX_FMT_VAAPI) {
AVFrame* sw_frame = av_frame_alloc();
av_hwframe_transfer_data(sw_frame, frame, 0);
// 使用 sw_frame 做后续处理
av_frame_free(&sw_frame);
}
return true;
}
return false;
}
避坑指南:硬件加速帧的 data 指针指向 GPU 内存,不能直接读取像素数据。必须用 av_hwframe_transfer_data() 转回 CPU 内存。我曾经因为直接读取硬件帧,导致内存访问违例,查了两天才找到原因。
编码实战:从帧到文件
编码相对解码简单一些,但参数配置很关键。同样的源素材,参数配不好,要么文件太大,要么画质太差。
// 编码器配置 - H.264 示例
AVCodecContext* CreateEncoderContext(int width, int height,
int fps, int bitrate) {
const AVCodec* codec = avcodec_find_encoder(AV_CODEC_ID_H264);
AVCodecContext* ctx = avcodec_alloc_context3(codec);
ctx->width = width;
ctx->height = height;
ctx->time_base = (AVRational){1, fps};
ctx->framerate = (AVRational){fps, 1};
ctx->bit_rate = bitrate;
ctx->pix_fmt = AV_PIX_FMT_YUV420P;
// 编码预设 - 平衡速度和画质
av_opt_set(ctx->priv_data, "preset", "medium", 0);
av_opt_set(ctx->priv_data, "tune", "zerolatency", 0);
// 打开编码器
if (avcodec_open2(ctx, codec, nullptr) < 0) {
// 错误处理
avcodec_free_context(&ctx);
return nullptr;
}
return ctx;
}
个人经验:编码参数里,preset 选 "medium" 或 "fast" 比较稳妥。"slow" 虽然压缩率高,但编码时间翻倍。直播场景一定要开 "zerolatency",否则延迟会让你崩溃。
硬件加速适配:三平台实战
硬件加速是视频处理性能的关键。我整理了一个表格,方便你对照:
| 平台 | 加速接口 | 支持的编码 | 初始化方式 |
|---|---|---|---|
| Windows | DXVA2 / D3D11VA | H.264, H.265, VP9 | av_hwdevice_ctx_create(AV_HWDEVICE_TYPE_DXVA2) |
| macOS/iOS | VideoToolbox | H.264, H.265, HEVC | av_hwdevice_ctx_create(AV_HWDEVICE_TYPE_VIDEOTOOLBOX) |
| Linux | VAAPI / VDPAU | H.264, H.265, MPEG-2 | av_hwdevice_ctx_create(AV_HWDEVICE_TYPE_VAAPI) |
| Android | MediaCodec | H.264, H.265, VP8 | av_hwdevice_ctx_create(AV_HWDEVICE_TYPE_MEDIACODEC) |
初始化硬件加速的代码,我建议这样写:
// 自动选择硬件加速类型
AVBufferRef* CreateHardwareDevice(AVCodecContext* ctx) {
AVHWDeviceType type = AV_HWDEVICE_TYPE_NONE;
// 按优先级尝试
#ifdef _WIN32
type = AV_HWDEVICE_TYPE_DXVA2;
#elif defined(__APPLE__)
type = AV_HWDEVICE_TYPE_VIDEOTOOLBOX;
#elif defined(__linux__)
type = AV_HWDEVICE_TYPE_VAAPI;
#endif
AVBufferRef* hw_device_ref = nullptr;
int ret = av_hwdevice_ctx_create(&hw_device_ref, type,
nullptr, nullptr, 0);
if (ret < 0) {
// 回退到软件解码
return nullptr;
}
ctx->hw_device_ctx = av_buffer_ref(hw_device_ref);
return hw_device_ref;
}
注意:硬件加速不是万能的。有些老显卡不支持 H.265 解码,有些编码器对分辨率有限制。我建议在初始化时做一次能力检测,不行就回退到软件解码。别让用户看到黑屏。
性能优化:几个关键点
视频处理性能优化,说白了就是减少拷贝、减少同步。我总结了几条实战经验:
- 零拷贝:用 AVFrame 的引用计数,避免 memcpy 原始帧数据
- 线程模型:解码线程和渲染线程分离,用队列缓冲帧
- 内存池:预分配 AVFrame 池,避免频繁 malloc/free
- 硬件加速:能走 GPU 就走 GPU,CPU 留给业务逻辑
我记得有一次优化直播推流,原来 CPU 占用 80%,换成硬件编码后直接降到 15%。效果立竿见影。
常见问题与避坑
做 FFmpeg 跨平台封装,有几个坑我几乎每次都会遇到:
- 版本兼容性:FFmpeg 4.x 和 5.x 的 API 有变化,编译时注意版本检测
- License 问题:如果用了 GPL 的模块,商业发布要小心
- 日志处理:FFmpeg 默认输出到 stderr,建议用 av_log_set_callback 接管
- 内存泄漏:av_frame_free、av_packet_free 一个都不能少
我的建议:写一个 RAII 包装类,把 AVFrame、AVPacket 的生命周期管起来。C++ 的析构函数天然适合做这事,别浪费了。
好了,关于 FFmpeg 跨平台封装和解码编码的内容,今天就聊到这里。硬件加速这块,每个平台的细节其实挺多的,但核心思路是一致的:统一接口、按需适配、做好回退。你动手试试,遇到问题再回来翻翻这篇文章。
公众号:蓝海资料掘金营,微信deep3321