MPEG-TS格式视频ffmpeg传输:直播推流封装原理与避坑指南
简介面向嵌入式系统开发者与流媒体技术爱好者该资源包围绕MPEG-TS视频在FFmpeg下的传输与分段处理展开。MPEG-TS作为数字电视广播、IPTV及网络流媒体中常用的容器格式通过固定大小的数据包承载音视频信息能够较好适应复杂网络环境作者在嵌入式ARM平台上交叉编译通过并用C语言实现了将TS流分割为符合HLS、DASH协议要求的短片段为资源受限设备上的实时视频传输提供了可落地的参考。压缩包共3个文件包括C语言源码、Makefile自动化构建脚本与COPYING许可证说明整体仅10KB结构紧凑便于阅读和二次移植。已有1554人学习。借助该资源可快速掌握FFmpeg在嵌入式环境下的编译配置要点深入理解TS分段处理的实现逻辑为监控系统、车载娱乐及自定义流媒体服务开发打下基础。1. mpeg-ts 格式视频ffmpeg传输直播推流绕不开的 188 字节封装和传输通路做视频传输的人迟早会撞上 mpeg-ts 格式视频ffmpeg传输这个组合。MPEG-TS 把音视频切成 188 字节一包的流式封装自带时间戳和节目表丢几个包不至于整体黑屏ffmpeg 则是把这路流打包、推送到 UDP、TCP 或 HTTP 上最顺手的工具。这篇笔记只讲三件事TS 流里实际在跑什么、用什么命令把视频推出去、推出去之后哪些参数最容易翻车。适合正在做直播推流、局域网视频分发或者被播放器黑屏花屏折磨的人。新手能照着命令跑通一条链路熟手能对着参数表排查值不值得投入看完你应该已经有判断。2. MPEG-TS 传输的底层逻辑188 字节包里装了什么为什么它能扛抖动传输和封装不是一回事。MP4 面向本地存储元数据可能散落在文件各处网络流拉到一半解析不出来MPEG-TS 是为实时传输设计的把音视频切成固定 188 字节的小包每个包都能独立对齐、独立丢弃不需要等整个文件到位。这个差异决定了直播、广电、IP 分发这些场景都选 TS 而不是 MP4。理解这一点后面调参数时才不会把 MP4 的思维带进 TS 链路。2.1 四字节头里的信息同步字节、PID 与连续计数器每个 TS 包固定 188 字节前 4 字节是头。第一个字节永远是 0x47接收端抓到一个 0x47 后连续校验几个包就能锁定包边界即使中间丢了包也能快速重新同步。接着是 13 比特的 PID告诉接收端这个包属于哪一路流PID 0 是 PAT节目关联表PAT 里写出 PMT 的 PIDPMT 再写明视频、音频分别在哪几个 PID 上。播放器先解 PAT再解 PMT才能真正拿到音视频数据这也是后面黑屏排查里第一个要查的对象。头里还有一个 4 比特的 continuity counter从 0 到 15 循环递增。接收端发现计数器不连续就知道中间丢了 TS 包能不能恢复画面取决于丢的是不是关键帧所在的分片。固定包长加连续计数就是 TS 抗丢包的核心设计它不求不丢包只求丢包后能快速恢复边界并且不会因为一个包坏掉而丢掉整段节目。想亲眼看一个 TS 流里的节目结构用 ffprobe 直接读ffprobe -show_programs -show_streams -of json input.ts这段命令把 PAT/PMT 解析结果和每条流的编码信息、PID、time_base 一次性打出来。实际排查时我一般先看 program 里有没有完整的音视频流再看每条 stream 的 idPID和 codec_name。如果这里就缺流后面播放器黑屏大概率是转封装阶段的问题不用一开始怀疑播放端。2.2 PTS/DTS 与 90kHz 时基ffmpeg 转 TS 时时间戳怎么换算TS 的时间体系建立在 27MHz 的系统时钟上节目时钟参考 PCR 用它而 PTS/DTS 是 33 比特、90kHz 刻度也就是 1/90000 秒的精度。为什么是 90kHz它和 27MHz 正好是 300 倍关系一个 PCR 域里能放 300 个 90k 时钟周期换算整数化不容易有精度损失。ffmpeg 内部每个 stream 有自己的 time_base比如 H.264 常见 1/90000AAC 在不同封装下可能是 1/1000 或 1/90000。进 mpegts muxer 时所有时间戳统一换算到 90kHz这一步由封装器自动完成。这里最容易翻车的不是换算本身而是输入流的时间戳就是坏的录屏工具录出来的素材 PTS 跳变、dts 倒序转封装时会原样带进 TS。时间戳处理里有两个容易混淆的操作-fflags genpts 表示输入没有可信 PTS 时按帧率重建-copyts 则是把源时间戳原样保留不按输出起始点归零。直播推流我一般不加 -copyts让 muxer 从起点重新排列时间戳播放器切台、重连时更容易对齐只有录制归档需要保留原始绝对时间戳的场景才用 -copyts。TS 特有参数里值得记住的是 GOP 长度。-g 会直接影响播放器丢包后的恢复速度。TS 的随机接入点是 IDR 帧播放器持续丢包时在下一个 IDR 之前画面只能处于等待状态。把 -g 设成 50大约 2 秒一个关键帧是推流场景的常见起点画面内容基本静止时可以加到 100 省码率但丢包概率高的链路宁可短一点。2.3 传输承载怎么选UDP 组播、TCP 长连与 HTTP 拉流TS 只定义封装不定义传输。相同的一路 TS 流可以用 UDP、TCP、HTTP 三种方式送出去选型的核心是丢包策略和延迟预算。UDP 没有重传丢包直接体现在画面上但延迟能做到几十毫秒局域网组播推流是典型用法。TCP 有重传丢包不丢数据代价是延迟会随重传增大适合跨网、对完整性敏感的链路。HTTP 拉流更接近点播语义客户端按需连接但 ffmpeg 自身做 HTTP server 的能力有限生产里更多是推给流媒体服务器再转 HLS 分发那是另一个话题。我维护某跨平台系统的传输链路时局域网设备间用 UDP 组播跨网用 TCP公网对延迟不敏感的回看场景才考虑 HTTP。三者延迟预期分别是毫秒级、数百毫秒、秒级起。如果你的播放器同时支持多种输入先用 ffplay 把各承载实测一遍看卡顿和延迟再定方案别只看协议文档。承载方式丢包处理典型延迟适合场景UDP 组播无重传丢包即花屏靠下个 IDR 恢复几十毫秒局域网直播分发、多接收端TCP 长连重传延迟随丢包率增大数百毫秒跨网、Wi-Fi、完整性优先HTTP 拉流重传按需连接秒级点播、回看、公网分发3. 用 ffmpeg 做 TS 传输的最小命令转封装、UDP 推流与 TCP 拉流原理落到命令就三件事把素材变成 TS、把 TS 推到网络、把网络上的 TS 接住。下面这套命令是模拟项目X 里反复用的基底参数少到能背下来但每个参数都有理由。你能看到 -c copy 快在哪、-re 为什么不能省、pkt_size1316 为什么比默认值可靠。3.1 本地文件转 TS 封装-c copy 与 Annex B 转换ffmpeg -i input.mp4 -c copy -f mpegts -bsf:v h264_mp4toannexb output.ts逻辑说明-c copy不做转码只改封装速度快、画质无损-f mpegts指定输出封装格式-bsf:v h264_mp4toannexb把 H.264 从 MP4 的 AVCC 长度前缀结构转成 TS 要求的 Annex B 起始码结构。新版 ffmpeg 在-c copy到 mpegts 时会自动插入这个 bitstream filter日志里会出现 Auto-inserting bitstream filter 字样老版本不会自动处理显式加上-bsf:v才能保证所有版本行为一致。H.265 对应的是h265_mp4toannexbAAC 音频不需要类似转换。如果素材本身有 B 帧拷贝封装后 PTS 顺序和 DTS 顺序不一致是正常的TS 里允许这种重排播放器按 PTS 显示。真正要检查的是读出后的 dts 是否单调递增ffprobe -show_packets -select_streams v -show_entries packetpts,dts -of csv output.ts | head -20从输出里看 dts 有没有回退。如果回退严重说明源素材时间戳是坏的直接-c copy会把坏戳带进 TS这时候先给输入加-fflags genpts重建时间戳再转。3.2 UDP 组播推流-re 与 pkt_size1316 的真实意义ffmpeg -re -i input.mp4 -c:v libx264 -preset veryfast -b:v 2M \ -maxrate 2M -bufsize 4M -g 50 -c:a aac -b:a 128k \ -f mpegts udp://239.0.0.1:1234?pkt_size1316逻辑说明-re让 ffmpeg 按源文件的真实帧率读取输入否则它会以最快速度把整段推完播放器看到的是一阵狂轰后断流。pkt_size1316是 TS over UDP 的推荐包大小1316 正好是 7 个 TS 包7×188加上 UDP 和 IP 头仍在标准 MTU 1500 之内IP 层不会分片避免分片重组失败导致的整包丢弃。组播地址用 239.0.0.0/4 段接收端必须在同一网段有些网络设备需要 IGMP 配置局域网直连交换机通常默认透传组播。编码参数上-preset veryfast适合推流机 CPU 有限的场景-bufsize 4M配合-maxrate 2M约束码率不要瞬间冲高TS 传输中码率尖峰过高会撑爆接收端缓冲区。提示pkt_size是 UDP 协议的 URL 参数必须写在udp://地址的?后面写到-f mpegts后面会被当作 muxer 参数直接报错。3.3 拉流侧与 TCP 推流把 TS 从 UDP 换到可靠通道接收端最小命令ffplay -fflags nobuffer -i udp://239.0.0.1:1234-fflags nobuffer让播放器不做大缓冲延迟降到最低局域网内花屏明显时把 nobuffer 去掉让播放器多缓存一点。要顺便落盘就把输出换成文件ffmpeg -i udp://239.0.0.1:1234 -c copy -f mpegts record.tsUDP 换到 TCP 推流的场景ffmpeg 支持 TCP 监听模式ffmpeg -re -i input.mp4 -c copy -f mpegts \ tcp://0.0.0.0:8080?listen1 -loglevel info逻辑说明listen1让 ffmpeg 作为服务端监听一个端口等客户端连进来后再开始推流接收端用tcp://本机IP:8080去拉。TCP 有重传保护适合 Wi-Fi 或跨网这种丢包率高的链路。注意 TCP 推流只能服务一个客户端多路分发还是要回到 UDP 组播或者流媒体服务器这是 TCP 长连方案的边界别指望一个监听端口带一堆接收端。注意推流脚本里把 loglevel 固定下来。默认 info 够用排查断流时改成 debug 能看到 muxer 的每包输出但平时别开日志量太大反而掩盖问题。4. MPEG-TS 传输避坑指南时间戳跳变、PID 漂移与黑屏花屏排查传输链路翻车极少只有一个原因但反复遇到的集中在下面四类。每一条按现象、原因、解决的顺序写你对照现场现象直接跳到你命中那一条就行。4.1 花屏MP4 拷贝成 TS 后 H.264 结构不兼容现象input.mp4 -c copy -f mpegts出来的文件在电脑播放器上正常换到某个嵌入式播放器就花屏或者起播就是黑屏。原因MP4 里的 H.264 用 AVCC 长度前缀表示 NALUTS 里要求 Annex B 起始码。新版 ffmpeg 会自动插入 h264_mp4toannexb但嵌入式播放器对输入码流要求更严格只要转换链路里有一环没完成转换就会花屏。老版本 ffmpeg 或者脚本里-bsf:v参数位置写错转换会被静默跳过这是最隐蔽的坑。解决转封装命令显式加-bsf:v h264_mp4toannexb不依赖自动插入转完后用 2.1 节的 ffprobe 命令确认码流结构正常。如果播放端对 H.264 profile 也挑剔顺手用-profile:v baseline做一次硬转码牺牲一点画质换兼容性。4.2 黑屏PAT/PMT 变化或 PID 漂移现象推流进程第一次起播正常重启后播放器一直黑屏手动拖进度条能恢复自动播放恢复不了。原因mpegts muxer 在每次 ffmpeg 进程重启时重新分配 PID视频常落在 256、音频 257但不会保证不变PAT/PMT 也会重建。播放器第一次启动时解析到旧 PID进程重启后还守着旧 PID 等数据TS 层没有重新触发 PAT 扫描于是黑屏。这类问题不在 TS 数据本身而在播放器的节目表状态机。解决播放端在对应 PID 上连续 N 秒收不到包时强制重新解析 PAT/PMT推流端保持节目号、音视频顺序和编码参数不变让 PMT 内容和之前尽量一致降低播放器重建节目的成本。你说它是玄学也行但每次都是同一套机制。4.3 音画不同步时间戳被污染没重建现象直播 20 分钟后音画差距超过 500ms录制的 TS 文件反复拖进度条后同步恢复。原因输入源是录屏或拼接素材PTS 在拼接处跳变-c copy模式把坏戳原样带进 TS播放器按跳变后的 PTS 显示误差积累成明显不同步。拖进度条能恢复是因为播放器在 seek 时做了一次重同步掩盖了问题。解决推流命令加-fflags genpts如果源是拼接素材先单独跑一遍 ffprobe 检测 dts 跳变跳变超过 1 秒就不要贪快用 copy直接走一次转码重新生成时间戳。音画不同步是最像玄学的一类问题排查时一定要先确认时间戳而不是去调播放器的缓冲。4.4 UDP 推流在局域网也花屏包太小与注入太快现象千兆局域网环境UDP 推流依然马赛克接收端 CPU 不高网络也没断。原因默认 TS 推流按 188 字节一个 UDP 包发送2Mbps 码率一秒就是 1300 多个包接收端 socket 缓冲区瞬间被灌满丢包从缓冲区溢出开始。另一个常见原因是漏了-reffmpeg 把文件里几分钟的内容几十秒推完接收端根本处理不过来丢包率直接飙升。解决推流端加pkt_size1316接收端加大缓冲区并允许溢出后继续运行ffmpeg -i udp://239.0.0.1:1234?buffer_size1Moverrun_nonfatal1 -c copy -f mpegts out.tsoverrun_nonfatal1让接收端在缓冲区溢出时丢掉来不及处理的包而不是终止进程buffer_size给到 1M 提供足够暂存。这一对参数是 UDP 传输链路里最常用的容错组合比在播放器里调缓冲有效得多。5. 从单路到多路TS 复用、tee 分流与双进程分发的组合玩法单路推流跑通后下一步一般是多路复用和同时录制。TS 天生支持多节目一路 188 字节流里可以同时装多路视频和多路音频ffmpeg 对应有一套组合技巧。这个能力在直播场景很实用一路组播端口送出去接收端按节目号选择自己想要的那一路网络层不用为每路视频单独开端口。5.1 一个节目挂多路音视频map 与 -programffmpeg -i main.mp4 -i audio.m4a -map 0:v -map 0:a -map 1:a \ -c:v copy -c:a aac -f mpegts -program program_num1:st0:st1:st2 multi.ts逻辑说明-map 0:v选主视频-map 0:a选主文件里的原音轨-map 1:a再引入第二个文件的音轨。把三条流塞进 program 1接收端在 PMT 里能看到三个 stream播放器可以选择音轨适合多语言直播。这里-program是较新 ffmpeg 的能力老版本只支持默认单节目多路流堆在同一个节目里播放器按 PID 区分一样能选轨只是节目表结构没有分得那么清楚。多路视频共用一路 TS 也是常见需求但要注意参与复用的视频必须参数接近尤其是分辨率和帧率差太多时播放器切换节目会明显卡顿。复用本身不转码参数差异只能由上游统一。5.2 一路码流同时推流与落盘tee muxerffmpeg -re -i input.mp4 -c:v libx264 -preset veryfast \ -b:v 2M -g 50 -c:a aac -f tee \ [fmpegts]udp://239.0.0.1:1234?pkt_size1316|[fmpegts]record.ts逻辑说明tee 把编码后的同一路码流分发到多个输出输出之间用|分隔。左边推 UDP右边落盘不用跑两个 ffmpeg 进程CPU 省一半。-f tee后面的方括号里是每个输出各自的封装和地址配置落盘文件那路如果磁盘慢可以加-use_fifo 1开启异步写盘避免磁盘抖动拖住推流。tee 的边界要讲清楚所有输出拿到的是同一份编码结果做不到“推流走 1Mbps、落盘存 5Mbps”。遇到这种需求要么开两个进程分别编码要么先存高码率推流端再从录制文件转一份低码率出来没有第三条捷径。5.3 不同目标不同码率双进程与中间文件ffmpeg -re -i input.mp4 -c:v libx264 -b:v 1M -f mpegts udp://239.0.0.1:1234?pkt_size1316 ffmpeg -re -i input.mp4 -c:v libx264 -b:v 5M -f mpegts record.ts wait两个进程后台并行各自解码一次原始文件码率互不影响。代价是 CPU 翻倍。CPU 不够时改成中间文件方案先转一次高码率 TS 落地再让两个进程都从这个中间文件-c copy取流一个推一个存ffmpeg -i input.mp4 -c:v libx264 -b:v 5M -preset medium -f mpegts mid.ts ffmpeg -re -i mid.ts -c copy -f mpegts udp://239.0.0.1:1234?pkt_size1316 ffmpeg -re -i mid.ts -c copy -f mpegts record.ts wait中间文件选 TS 而不是 MP4喂给-c copy时不用再担心 Annex B 转换问题少一层坑。双进程还有一个隐藏注意点两个进程同时读同一个输入文件时如果源文件在推流过程中被切割或覆盖两路会同时翻车。我一般给源文件加只读权限或者干脆用独立的输入副本这个习惯救过我好几次。6. 推流之后别急着走验证 TS 链路的三道自查命令推流完成不等于传输没问题。我每次搭完一条 mpeg-ts 传输链路都跑三道自查全过才敢交给下游。这三道命令都不改链路本身只是观测但能过滤掉大部分封装层面的隐患。第一道错误烟测。让 ffmpeg 拉流 30 秒丢弃输出只留错误日志ffmpeg -v error -i udp://239.0.0.1:1234 -t 30 -f null -30 秒内没有任何错误输出说明封装基本健康如果反复出现 Packet corrupt 或 cur_dts is invalid说明上游时间戳或 PID 结构有问题直接回到第 4 章对应条目查。第二道节目与包层面检查ffprobe -show_programs -show_streams -of json udp://239.0.0.1:1234重点看 program 数量、每个 stream 的 codec_type 和 id和推流端脚本里预期的 PID 对照。这里有个血泪经验推流脚本开头一定要写参数注释GOP、码率、pkt_size、是否-re一行都不能省。没有这张参数卡片链路出问题时你连自己刚才推的是什么配置都想不起来。第三道连续性抽样。拉一段包的 PTS/DTS 和关键帧标记ffprobe -show_packets -select_streams v -show_entries packetpts,dts,flags -of csv udp://239.0.0.1:1234 | head -50看 dts 是否单调、关键帧 flags 是否标 K。这两项过了再配第一道的零报错这条 TS 链路我才会放心交接。我的习惯是三道命令写进同一个验证脚本每次改完推流参数就重跑一遍比靠眼睛盯播放器窗口可靠得多。希望帮到你。本文还有配套的精品资源点击获取