☰
MPEG-TS格式视频ffmpeg传输:直播推流封装原理与避坑指南
2026/10/14 8:56:28 网站建设 项目流程

简介:面向嵌入式系统开发者与流媒体技术爱好者,该资源包围绕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 的 PID,PMT 再写明视频、音频分别在哪几个 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 的 id(PID)和 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/90000,AAC 在不同封装下可能是 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_size=1316 为什么比默认值可靠。

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_mp4toannexb,AAC 音频不需要类似转换。

如果素材本身有 B 帧,拷贝封装后 PTS 顺序和 DTS 顺序不一致是正常的,TS 里允许这种重排,播放器按 PTS 显示。真正要检查的是读出后的 dts 是否单调递增:

ffprobe -show_packets -select_streams v -show_entries packet=pts,dts -of csv output.ts | head -20

从输出里看 dts 有没有回退。如果回退严重,说明源素材时间戳是坏的,直接-c copy会把坏戳带进 TS,这时候先给输入加-fflags +genpts重建时间戳再转。

3.2 UDP 组播推流:-re 与 pkt_size=1316 的真实意义

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_size=1316"

逻辑说明:-re让 ffmpeg 按源文件的真实帧率读取输入,否则它会以最快速度把整段推完,播放器看到的是一阵狂轰后断流。pkt_size=1316是 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.ts

UDP 换到 TCP 推流的场景,ffmpeg 支持 TCP 监听模式:

ffmpeg -re -i input.mp4 -c copy -f mpegts \ "tcp://0.0.0.0:8080?listen=1" -loglevel info

逻辑说明:listen=1让 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 长度前缀表示 NALU,TS 里要求 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 缓冲区瞬间被灌满,丢包从缓冲区溢出开始。另一个常见原因是漏了-re,ffmpeg 把文件里几分钟的内容几十秒推完,接收端根本处理不过来,丢包率直接飙升。

解决:推流端加pkt_size=1316,接收端加大缓冲区并允许溢出后继续运行:

ffmpeg -i "udp://239.0.0.1:1234?buffer_size=1M&overrun_nonfatal=1" -c copy -f mpegts out.ts

overrun_nonfatal=1让接收端在缓冲区溢出时丢掉来不及处理的包,而不是终止进程;buffer_size给到 1M 提供足够暂存。这一对参数是 UDP 传输链路里最常用的容错组合,比在播放器里调缓冲有效得多。

5. 从单路到多路:TS 复用、tee 分流与双进程分发的组合玩法

单路推流跑通后,下一步一般是多路复用和同时录制。TS 天生支持多节目,一路 188 字节流里可以同时装多路视频和多路音频,ffmpeg 对应有一套组合技巧。这个能力在直播场景很实用:一路组播端口送出去,接收端按节目号选择自己想要的那一路,网络层不用为每路视频单独开端口。

5.1 一个节目挂多路音视频:map 与 -program

ffmpeg -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_num=1:st=0:st=1:st=2 multi.ts

逻辑说明:-map 0:v选主视频,-map 0:a选主文件里的原音轨,-map 1:a再引入第二个文件的音轨。把三条流塞进 program 1,接收端在 PMT 里能看到三个 stream,播放器可以选择音轨,适合多语言直播。这里-program是较新 ffmpeg 的能力,老版本只支持默认单节目,多路流堆在同一个节目里,播放器按 PID 区分一样能选轨,只是节目表结构没有分得那么清楚。

多路视频共用一路 TS 也是常见需求,但要注意:参与复用的视频必须参数接近,尤其是分辨率和帧率差太多时,播放器切换节目会明显卡顿。复用本身不转码,参数差异只能由上游统一。

5.2 一路码流同时推流与落盘:tee muxer

ffmpeg -re -i input.mp4 -c:v libx264 -preset veryfast \ -b:v 2M -g 50 -c:a aac -f tee \ "[f=mpegts]udp://239.0.0.1:1234?pkt_size=1316|[f=mpegts]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_size=1316 & 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_size=1316" & 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 packet=pts,dts,flags -of csv udp://239.0.0.1:1234 | head -50

看 dts 是否单调、关键帧 flags 是否标 K。这两项过了,再配第一道的零报错,这条 TS 链路我才会放心交接。我的习惯是三道命令写进同一个验证脚本,每次改完推流参数就重跑一遍,比靠眼睛盯播放器窗口可靠得多。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询