FFmpeg 编译支持 H265 的 RTMP 版本,这句话看起来像一道编译题,但在直播开发群里隔三差五就有人问一次。默认装好的 FFmpeg 影音能力再强,遇到“H.265 + RTMP”这个组合就只能干瞪眼:libx265 编码器能出 HEVC 码流,RTMP 推流也正常,但中间加上 FLV 封装这层,直接告诉你“不兼容”。原因不复杂,标准 FLV/RTMP 生态只给 H.264(AVC)留了正式标签位,H.265(HEVC)没有官方 CodecID,得靠扩展实现。这篇文章围绕“怎么编译出一个支持 H.265 的 RTMP 版本 FFmpeg”来写,把协议扩展、源码改动点、configure 参数、推流命令和实战中踩过的坑一次讲完。适合正在搭直播服务、要压缩带宽成本、或者自研流媒体组件的同学参考,照着操作基本能产出一套可用的二进制。
1. 为什么自带的 FFmpeg 推不了 H265 的 RTMP 流
1.1 RTMP 与 FLV:老协议的老限制
RTMP 本身是一个消息块流协议,负责在客户端和服务器之间搬运 AMF 数据。直播场景下,视频内容实际上是以 FLV Tag 的形态塞进 RTMP 消息里的,所以问题表面在 RTMP,真正的瓶颈在 FLV 封装格式。FLV 的 VideoTagHeader 第一个字节被拆成两部分:高 4 位是 FrameType,低 4 位是 CodecID。标准 FLV 规范里,CodecID 只有一组固定取值,JPEG 是 1,H.263 是 2,VP6 是 4,而 AVC(H.264)是 7,HEVC 在标准里根本不存在。
于是出现了尴尬的局面:FLV 的 muxer 在写视频 Tag 时,遇到 H.264 就知道该写 CodecID=7,然后跟着写 AVCPacketType 和 CompositionTime;遇到 H.265 时它不认识,很多原版 FFmpeg 直接抛错“Tag: HEVC incompatible with flv”,连数据都不给你发出去。RTMP 协议本身并不限制你传什么,是 FLV 这层规范把 H.265 拒之门外。可以这么理解:RTMP 是一条高速公路,FLV 是规定尺寸的集装箱,H.264 是刚好能放进箱子的标准货物,H.265 是尺寸超标的新型号,不改造集装箱就装不进去。
这一点决定了后续所有做法的走向:要么让 FFmpeg 的 FLV muxer 认识 HEVC,要么绕开 FLV 直接自定义封装。实际工程里大家基本都选了前者,因为服务端、播放端、CDN 链路全都建立在 RTMP/FLV 之上,改动越少越安全。
1.2 直播行业为什么非要 HEVC
有人会问:H.264 用得好好的,为什么一定要折腾 H.265?核心就三个字:带宽成本。H.265 在同等主观画质下,码率通常比 H.264 低 30% 到 50%,分辨率越高、帧率越高,差距越明显。直播平台烧钱大头就是带宽,4K 直播、体育赛事、游戏直播这种高码率场景,换成 H.265 能直接省下一大截流量费用。
同时观众端设备对 HEVC 硬解的支持已经非常普及,手机中端以上基本都有硬件解码单元,功耗和发热可控。既然编码端能用 x265 软编、设备端能硬解,中间只差一个 RTMP 封装层的支持,那大家自然会想办法把这条链路补齐。这也是“FFmpeg 编译支持 H265 的 RTMP 版本”这种需求会被反复提起的商业背景。注意,这个需求里“编译”不是普通的 ./configure && make,而是带着定制补丁去编译,核心目的就是让 FFmpeg 在封装 FLV 和走 RTMP 时,能像对待 H.264 一样对待 H.265 流。
2. 支持 H.265 的 RTMP 是怎么“钻空子”实现的
2.1 两条扩展路线:Enhanced-RTMP 与私有 CodecID=12
既然 FLV 标准没给 H.265 留位置,大家只能自己定义。目前市面上能见到的方案分两条路线,很多刚接触的同学容易混淆:
| 维度 | Enhanced-RTMP 扩展 | 私有 CodecID=12 扩展 |
|---|---|---|
| 头部结构 | 4 字节 exVideoTagHeader,带 FourCC | 沿用 AVC 老结构,1 字节头 + AVCPacketType + CompositionTime |
| 编码标识 | FourCC 字段写 "HEVC"、 "AV1" 等 | CodecID 字段直接写 12 |
| 兼容策略 | 老播放器读到不认识的四字节头可尝试跳过 | 改动最小,服务器好透传,但无官方兜底 |
| 主要场景 | 社区开源推流、部分新版本播放器 | 国内直播平台、云厂商、自建 CDN 链路 |
| FFmpeg 支持情况 | 新版部分支持解析,muxer 输出仍常需补丁 | 基本全靠个人 patch |
Enhanced-RTMP 是社区提出的一套增强广播方案,思路比较彻底:把 VideoTagHeader 重构成带 FourCC 的扩展头,除了 HEVC 之外还能承载 AV1 等新编码。私有 CodecID=12 方案则更务实,基本是照抄 AVC 在 FLV 里的封装套路,只把 CodecID 从 7 换成 12。两条路线互不兼容:服务器按私有 CodecID=12 解析时,Enhanced-RTMP 的 FourCC 包会被当成无效包丢掉;反过来,Enhanced-RTMP 服务器遇到老结构的扩展头也可能解析异常。所以拿到一个“支持 H.265 的 RTMP 版本 FFmpeg”时,第一件事不是测推流,而是问清楚它打的是哪套协议的补丁,对接方用的又是哪套。
2.2 FFmpeg 源码里到底改了什么
FFmpeg 源码对这个需求来说是核心战场。常见补丁主要动四个文件,理解改动点能帮你排查很多诡异问题:
- libavformat/flv.h:定义扩展相关的宏,最关键的是
FLV_CODECID_HEVC 12。 - libavformat/flvenc.c:FLV muxer 写入端,遇到 AV_CODEC_ID_HEVC 时写 CodecID=12,并把 VPS/SPS/PPS 作为 sequence start 发送。
- libavformat/flvdec.c:FLV demuxer 读取端,解析到 CodecID=12 时按 HEVC 流程处理,重建 extradata 给解码器。
- libavformat/rtmpproto.c:RTMP 协议层要能透传新扩展的 VideoTagHeader,不被已有的 AVC 私有扩展逻辑误伤。
flv.h 的改动代码很简洁,示意如下:
/* libavformat/flv.h:补丁中常见的扩展定义 */ #define FLV_CODECID_HEVC 12 #define FLV_VIDEO_PACKET_TYPE_SEQUENCE_START 0 #define FLV_VIDEO_PACKET_TYPE_CODED_FRAMES 1flvenc.c 里的写入逻辑大概长这样:
/* libavformat/flvenc.c:示意逻辑,非原始补丁 */ if (par->codec_id == AV_CODEC_ID_HEVC) { /* 写扩展 CodecID,并把 HEVCDecoderConfigurationRecord 拆出来 */ *codec_id = FLV_CODECID_HEVC; /* 之后走 AVC 类似的流程:先送 sequence header,再送编码帧 */ }demuxer 侧则是反向操作:读 Video Tag 时取字节低四位判断 CodecID 是否为 12,如果是就创建 HEVC 的解析器,从 sequence header 里读 VPS/SPS/PPS 并填充 extradata,这样后端解码器才能正确初始化。整个链路并不复杂,难点在细节兼容,比如 B 帧、CompositionTime 的符号处理、关键帧起始位置,这些在排查问题时往往比想象中的更纠缠。
3. 动手编译:从源码到可用的 bin
3.1 环境准备与依赖
编译环境以 Debian/Ubuntu 这类发行版为例。先装基础工具链和编解码依赖库:
sudo apt update sudo apt install -y build-essential git yasm nasm cmake \ libx264-dev libx265-dev libmp3lame-dev libvpx-dev \ libsdl2-dev libssl-dev逐个说一下为什么需要:build-essential 提供 gcc 和 make;yasm 和 nasm 是汇编器,x264、x265 的优化汇编必须靠它们,缺了 configure 阶段会提示找不到汇编器;libx264-dev 和 libx265-dev 提供编码库,前者做对照组,后者是 H.265 编码核心;libsdl2-dev 是 FFplay 的依赖,不装就编译不出本地播放器,后面测拉流会不方便;libssl-dev 给 RTMPS 和 HTTPS 用,是否需要可以自己取舍。如果你坚持要用效果更好的 AAC 编码器,可以再装 libfdk-aac-dev,但要明确:fdk_aac 是 nonfree 协议,商用项目慎用,拿不准就直接用 FFmpeg 内置的-c:a aac,大部分直播场景完全够用。
3.2 拉源码、打补丁
代码托管平台拉取 FFmpeg 源码后,不要直接编译最新 master 分支,因为补丁大多针对特定 release 版本。比较稳妥的做法是先切到补丁作者标注的基线版本,这里以 n5.1.4 为例:
git clone https://git.ffmpeg.org/ffmpeg.git cd ffmpeg git checkout n5.1.4补丁文件一般会随博文或仓库 release 一起发布,下载到本地后先检查能不能打上:
git apply --check rtmp-h265.patch如果这一步没有任何输出,说明补丁能干净打上;如果提示 Hunk #x FAILED,大概率是版本不匹配,先回去切换分支版本,不要硬怼。确认无误再执行:
git apply rtmp-h265.patch打完之后再用git diff --stat看一眼,应该能看到 flv.h、flvenc.c、flvdec.c 这几个核心文件有改动。这一步非常关键,我见过有人明明下了补丁却忘了应用就开始 ./configure,折腾半天最后报错一堆。
3.3 configure、编译与产物验证
configure 参数是编译环节的重头戏。给出一个经过实测的可行组合:
./configure \ --prefix=$HOME/ffmpeg-h265 \ --enable-gpl \ --enable-libx264 \ --enable-libx265 \ --enable-libmp3lame \ --enable-libvpx \ --enable-libfdk-aac \ --enable-nonfree \ --enable-openssl \ --enable-ffplay \ --extra-cflags=-I$HOME/ffmpeg-h265/include \ --extra-ldflags=-L$HOME/ffmpeg-h265/lib解释几个容易忽略的开关:--enable-gpl 必须开,因为 libx265 和 libx264 都是 GPL 许可,不开会链接失败;--enable-libx265 是 H.265 编码核心,漏掉它就没有 h265 编码器;--enable-ffplay 需要刚才装的 SDL2;--enable-nonfree 和 libfdk-aac 配套出现,去掉这个开关 configure 会直接报错。如果你不需要 fdk_aac,把这组代码里的--enable-libfdk-aac和--enable-nonfree一起删掉即可。真正编译时还可以去掉 --enable-libvpx、--enable-libmp3lame 这些用不到的库,编译速度更快、体积更小,我这里保留是为了复用方便。
接下来编译安装:
make -j$(nproc) make install全量编译大概几分钟到十几分钟,取决于机器核数和磁盘性能。编完之后验证产物:
export PATH=$HOME/ffmpeg-h265/bin:$PATH ffmpeg -version ffmpeg -codecs | grep -i 265预期能在编码器列表看到libx265和 HEVC 相关内容。更直接验证 muxer 补丁是否生效的方法是本地封装一个 FLV 文件:
ffmpeg -i test.mp4 -c:v libx265 -c:a aac -f flv /tmp/h265test.flv原版 FFmpeg 到这里会报Tag: HEVC incompatible with flv之类的错,补丁生效的话应该能正常生成文件。这一步比直接推 RTMP 更干净,能排除网络和服务器的干扰。
4. 推流验证:把 H.265 视频推到 RTMP 服务器
4.1 推流命令拆解
编译好之后,推流命令本身并不复杂,但参数选择很讲究:
ffmpeg -re -stream_loop -1 -i test.mp4 \ -c:v libx265 -preset medium \ -x265-params "keyint=60:min-keyint=60:bframes=0:scenecut=0" \ -c:a aac -b:a 128k -ar 44100 -ac 2 \ -f flv rtmp://127.0.0.1:1935/live/h265test逐项解释:
-re是直播推流的关键,它让 FFmpeg 按源文件的原始帧率读取和编码,否则几秒钟就把整个文件读完了,后台刷屏式推流根本停不下来。-stream_loop -1表示无限循环源文件,适合长时间压测和验证稳定性。-preset medium是 x265 编码速度和压缩率的平衡点。机器性能不足时可以降到 ultrafast、veryfast,但码率会明显上涨。-x265-params里最关键的是keyint=60:min-keyint=60。在 30fps 的帧率下,60 帧就是 2 秒一个关键帧,直播场景一般建议 1 到 2 秒一个关键帧,既保证起播速度又不至于让关键帧开销过大。bframes=0很多人会忽略。H.265 默认开 B 帧,但很多私有 CodecID=12 扩展对 B 帧的 CompositionTime 支持并不好,直播链路里 B 帧还会引入额外延迟,直接关掉最稳。scenecut=0禁止场景切换触发关键帧,配合 keyint 可以保持 GOP 严格均匀,方便服务端做切片和追帧。
如果实测中发现起播黑屏或者断流后恢复慢,可以在 x265-params 里加repeat-headers=1,让每个 IDR 帧前都重复携带 VPS/SPS/PPS。代价是每个关键帧会多一点点额外开销,换取的是弱网下的强健壮性,值得一试。
4.2 拉流验证与播放端兼容
推流成功不等于链路通了,必须拉流验证。用自己编译的 FFplay 拉自己的流是最直接的闭环测试:
ffplay -fflags nobuffer -analyzeduration 1000000 \ rtmp://127.0.0.1:1935/live/h265test-fflags nobuffer是降低播放缓冲延迟,-analyzeduration设小一点可以避免长时间黑屏等待。如果这里能流畅出画面,说明编码、封装、推送、拉取、解封装、解码这条链路完整走通了。
播放端的兼容性需要单独强调:FFplay、VLC 这类播放器能不能播,取决于其内置 FFmpeg 对扩展协议的支持程度;浏览器原生不支持 RTMP,网页播放通常要转成 HTTP-FLV 或走 WebRTC 链路;回看录制文件还要考虑封装格式变成 MP4 时 HEVC 是否被目标平台接受。所以“能推出来”和“能播出去”是两件事,动手前先把播放端支持范围列清楚,不然推了一路流,始终没有观众能看进来。
5. 编译和推流中经常踩的坑
5.1 高频报错排查表
下面这些报错是我整理的高频问题,基本覆盖了从编译到推流的大部分翻车场景:
| 现象 | 原因 | 解法 |
|---|---|---|
Tag: HEVC incompatible with flv | 用的是原版 FFmpeg,muxer 不认 HEVC | 确认补丁已生效,用编译后的 bin |
Could not write header for RTMP | RTMP 服务器不支持扩展头,或地址填写错误 | 换支持扩展的服务器,检查 rtmp 地址 |
h264 @ ... SPS/PPS not present | 源流截取时没落在关键帧起点 | 用-ss选关键帧位置,或先转码再推 |
libx265 not found | configure 时 x265 库没识别到 | 安装 libx265-dev,确认 --enable-libx265 |
error while loading shared libraries: libx265.so.xxx | 自定义 prefix 后动态库路径没加 | export LD_LIBRARY_PATH 指向自定义 lib 目录 |
gcc: internal compiler error: Killed | make -j 并行数过高导致内存爆掉 | 降低并行度,或临时增加 swap |
打 patch 时Hunk #x FAILED | FFmpeg 版本和补丁基线不一致 | checkout 到补丁对应的 release 分支再打 |
| 推流成功但播放黑屏 | 播放端不支持这种扩展封装方式 | 换支持对应扩展的播放器,或转封装转发 |
5.2 容易被忽略的细节
第一件事永远是确认对接方的协议规范。之前遇到过类似情况:服务器明明支持 HEVC over RTMP,用的是私有 CodecID=12 方案,结果推流端打的是 Enhanced-RTMP 补丁,两边互相不认,推流不报错,播放全黑。排查到最后才发现是协议模式不同,非常浪费时间。
保留 libx264 编码支持也很重要。链路出问题时,用-c:v libx264推一路同样的 RTMP 做对照,能快速判断是 H.265 补丁问题、服务器问题还是网络问题。这个对照测试我屡试不爽,比抓包分析快得多。
交叉编译是另一个容易被忽略的需求。如果你想把这套 FFmpeg 编到 ARM 服务器或嵌入式设备上,需要额外的交叉编译工具链,比如 gcc-aarch64-linux-gnu,configure 时加上--enable-cross-compile --arch=aarch64 --cross-prefix=aarch64-linux-gnu-。这里最容易踩的坑是 pkg-config 把依赖库指引到了宿主机 x86 平台的目录,导致链接失败。小型设备建议直接--disable-shared --enable-static做静态编译,把 x265 一起编进去,省得部署时拷贝一堆 .so 文件。
最后提醒一点:编译参数里加的 --enable-libx265 这类选项越多,产物越臃肿。实际生产环境完全可以裁剪掉 ffplay、libvpx、libmp3lame 这些非必需组件,只保留 RTMP 协议、FLV muxer/demuxer、libx265 和 AAC 编码器,编译时间能缩短一截,二进制体积也更干净。
再多说一句个人体会:这套带补丁的 FFmpeg 编译版本我至今还在用,每次直播相关项目都直接拿编译好的 bin 开工,绝不反复折腾源码。宁可多花半小时在工程化验证上,也别在推流时被这些“协议细节”杀个措手不及。