news 2026/10/26 0:13:03

ffmpeg转封装TS流:不重编码实现UDP/TCP推流与播放验证

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ffmpeg转封装TS流:不重编码实现UDP/TCP推流与播放验证

简介:MPEG-TS是数字电视广播、IPTV与流媒体中常见的容器格式,其将音视频分包为固定大小单元以适配网络抖动和带宽波动;本方案以FFmpeg在嵌入式ARM平台上的编译实践为基础,为智能家居、车载系统等资源受限设备提供视频传输与切片的可行示例,尤其适合掌握C语言并希望深入FFmpeg流媒体开发的中级工程师。压缩包共3个文件,包括一个C语言源码文件、一个构建脚本及一份开源许可证说明,整体仅10KB,结构紧凑;其中构建脚本体现了交叉编译配置要点,许可证明确了GPL/LGPL类使用权限,而C源码演示了如何将MPEG-TS流切分为可供HLS/DASH等协议使用的短片段。已有1554人学习该资源,通过阅读C源码可掌握TS流分段逻辑,理解分段器在高延迟网络中的缓冲作用;结合构建脚本可复现ARM板卡上的工具链构建流程,并可将示例改造用于实时监控、个性化流媒体等场景,是切入嵌入式视频处理领域的低成本起点。

1. mpeg-ts 与 ffmpeg 传输:不重编码把视频送出去的最短路径

做视频传输调试绕不开 mpeg-ts 和 ffmpeg 这对组合。TS(Transport Stream)是广播级流媒体的老牌封装,ffmpeg 则是处理它的主力工具,两者相遇解决的是同一个痛点:如何把一段已经压制好的 H.264/AAC 视频,在不需要重新编码的前提下,跨越网络送到播放端。调流媒体的时候你会发现,直接把 MP4 扔给播放器经常被拒收,但转成 TS 之后走 UDP 或 TCP,绝大多数播放端都能秒认。这篇文章想把我实际拆分这个流程时踩过的路子讲清楚:TS 封装的结构、ffmpeg 转封装与推流的具体参数、拉流验证的方法,以及几个反复出现的坑。适合正在做直播推流、视频采集传输或者刚开始接触流媒体协议的开发者。

2. TS 封装的时间基与 PSI 表:188 字节小包如何守住音画同步

做传输的人最容易忽略的是 TS 不是一种简单的文件格式,而是一套为“有损信道”设计的传输协议。你以为是在处理文件,其实是在处理一系列固定长度的包。理解这层之后,ffmpeg 的很多参数设置就好办了。

2.1 PES 与 188 字节包:TS 流的组装与拆分

TS 流的最小单位是 188 字节的包,这个数字在流媒体协议里几乎成了铁律。每个 TS 包由 4 字节的包头和 184 字节的负载组成,包头里最前面是同步字节 0x47,播放器靠它来找包的起始位置。包头里还有一个关键字段 PID,用来区分这个包属于哪个流——视频流、音频流、还是 PSI 表。

那音视频数据是怎么装进这 188 字节的?原始编码数据先打包成 PES,也就是 Packetized Elementary Stream。PES 包的长度是不固定的,通常一个 PES 包装一帧或者几帧的编码数据,然后 PES 包又被切成 184 字节一块塞进 TS 包。拆的时候则反过来:播放器从 TS 包里拼回 PES,再送到解码器。这个“先拼 PES 再喂解码器”的流程,比直接处理裸 ES 流多了一步,但换来的是对丢包的容忍度——丢一两个 TS 包,顶多影响一帧的局部数据,PES 头还在就能继续解码。

我一般在排查问题时会先看一眼 PID 分布,命令行里的 ffprobe 就能列出流信息。但要注意的是,TS 的包头只包含 PID、加扰控制、适配字段控制这些,真正的 PTS/DTS 时间戳在 PES 头里,需要把完整 PES 包重组出来才能看到。

ffprobe -show_packets -select_streams v output.ts | grep -E "pts=|dts=" | head -20

这段命令抽取视频流的前 20 个包,打印 PTS 和 DTS。如果看到 PTS 长时间不递增或者跳变,说明源文件的时间戳本身就不干净,后面转封装大概率会出音画问题。

参数说明:-select_streams v限定只看视频流;-show_packets让 ffprobe 逐包输出信息;head -20只是控制输出量,实际排查时可以去掉,观察全链路的时间戳跳变点。

2.2 PAT/PMT 与 PCR:播放器识别节目和锁时钟的关键

TS 流里除了音视频数据,还有两类必须存在的控制信息,叫 PSI 表。一个是 PAT,Program Association Table,固定住在 PID 0 上,它告诉你这个 TS 流里有哪些节目,每个节目的 PMT 在哪个 PID 上。另一个是 PMT,Program Map Table,它进一步告诉你这个节目包含哪几条流,每条流的 PID 和编码格式是什么。

播放器拿到 TS 流之后的第一件事,就是先找 PAT,再按图索骥找 PMT,最后才去拼接音视频 PES。所以 PAT/PMT 必须周期性地重发,不能只在流开始时发一遍。广播标准里一般要求每隔几十毫秒到一百毫秒重发一次,接收端才能在新接入时快速完成频道识别。ffmpeg 的 mpegts muxer 默认会周期插入 PAT/PMT,但如果你手动改过某些参数,可能导致这个周期异常,后面我会专门讲这个坑。

PCR 是另一个被忽略但极其重要的东西。PCR,Program Clock Reference,是编码器侧 27MHz 系统时钟的采样值,插入在 TS 包的适配字段里。解码端拿到 PCR 后,用它恢复出本地的 27MHz 参考时钟,再据此解释 PTS/DTS。如果 PCR 抖动太大或者间隔太远,解码器的时钟锁不住,表现出来就是音画不同步慢慢变大。ffmpeg 的 mpegts muxer 里pcr_period参数就是控制这个间隔的。

ffmpeg -i input.mp4 -c copy -f mpegts -pcr_period 20 output.ts

这里-pcr_period 20表示每 20 毫秒插入一次 PCR,这是常见的默认值。如果你发现解码端时钟不稳,可以把这个值调小到 10 试试,代价是 PCR 占用更多带宽。

参数说明:-pcr_period单位是毫秒,取值太小会增加 TS 流的开销,太大会导致接收端时钟漂移,直播场景建议控制在 10-40 毫秒之间。-c copy是流拷贝,不做重编码。

2.3 为什么不直接推 MP4/MKV:流式传输的容器适配差异

这个问题在我刚接触流媒体时也困惑过:明明 MP4 也能装 H.264,为什么播放器收到 MP4 文件流却不能立即播放?原因在于 MP4 的索引结构。MP4 的元数据集中在 moov box 里,有些 MP4 的 moov 在文件末尾,播放器要下载完整文件才能拿到索引开始解码。MP4 还有一种 fMP4 的流式变体,把 moov 放前面并分片,但兼容性和 TS 还是没法比。

MKV 的问题在于 Cues 索引结构,它的定位依赖文件尾部的索引块,对于实时流来说索引永远在变,不适合做流式传输。而 TS 是定长小包、周期重发控制表、时间戳带在包内的设计,天生就能从任意位置接入解码,丢包也只影响局部。这就是为什么 DVB、ATSC 这些广播标准和 HLS 的底层切片都用 TS。

这并不代表 TS 没有缺点,它的冗余度比 MP4 高,同样内容体积大约大 5%-10%。但换来的容错性在实时传输里值这个开销。如果你只是做本地存档,当然可以继续用 MP4;一旦要推流,TS 是更稳的选择。我骨子里还是偏向“先搞清楚格式为什么存在,再去选参数”,否则出了问题只能靠玄学。

3. ffmpeg 转封装与推流:从本地文件到 UDP/TCP 裸流

这一章是可抄作业的部分。我们的目标是让一段本地视频变成一条能实时播放的 TS 流。ffmpeg 里实现这个目标分两步:先转封装,再选择传输通道。也可以一步完成,但拆开讲更容易定位问题。

3.1 本地转封装:-c copy 零重编码生成 .ts

先处理本地的单文件转封装。假设你有一段 input.mp4,里面是 H.264 视频和 AAC 音频,想把它变成 output.ts,直接执行:

ffmpeg -i input.mp4 -c copy -f mpegts output.ts

一句话解释:读入 input.mp4,视频和音频都走copy模式,也就是不重新编码,只重新封装成 mpegts 格式写到 output.ts。这个命令的核心是-c copy,它保证了你不会因为重编码而损失画质,也不会因为 CPU 性能不够而卡住实时推送。

但有个细节必须注意,ffmpeg 在封装到 TS 时,会自动把 H.264 从 MP4 里的 AVCC 格式转换为 TS 需要的 Annex-B 格式,转换动作由 muxer 内部自动选择的 bitstream filter 完成。老版本 ffmpeg 上偶尔不会自动带这个 filter,如果转出来的 TS 在播放器里黑屏,可以手动补上:

ffmpeg -i input.mp4 -c copy -bsf:v h264_mp4toannexb -f mpegts output.ts

参数说明:-bsf:v指定视频流的 bitstream filter,h264_mp4toannexb把 H.264 的起始码从长度前缀模式改成 Annex-B 模式。如果你的源是 H.265(HEVC),对应的是hevc_mp4toannexb。

转封装完成后先用 ffprobe 验证一下流信息,确认视频流编码、音频流编码都还在,再去做推流。不要跳过这步,因为后续所有问题排查都要基于“文件本身是好的”这个前提。

3.2 UDP/TCP 推流:mpegts muxer 参数与地址选择

单文件转封装只是热身,真实场景是把流推到网络上。最常见的做法是推到本机的 UDP 端口或者组播地址:

ffmpeg -re -i input.mp4 -c copy -f mpegts udp://127.0.0.1:1234

-re是关键参数,它让 ffmpeg 按文件的原帧率读取输入,而不是全速跑完。不加-re的话,ffmpeg 会以最快速度把整个文件推完,接收端只看到瞬间一大波数据,实际并没有形成稳定的直播流。推流到组播地址则把 127.0.0.1 换成 239.0.0.1 这类组播段,但组播需要交换机支持 IGMP 协议,局域网调试建议先用单播。

如果嫌弃 UDP 丢包不可控,可以改用 TCP 通道。ffmpeg 的 TCP 推流是主动连出,也可以做成监听模式:

ffmpeg -re -i input.mp4 -c copy -f mpegts "tcp://127.0.0.1:6666?listen=1"

URL 末尾的listen=1表示 ffmpeg 监听 6666 端口,等待拉流端接入。TCP 模式的好处是每个连接都有确认和重传,丢包不会造成花屏,缺点是一旦网络抖动严重会导致延迟变大,甚至卡顿。

mpegts muxer 还有几个参数值得记下来,我整理成表方便查阅:

参数作用默认值适用场景
muxrate设置恒定码率,muxer 会用空包填充输出0(VBR)卫星/有线等要求 CBR 的链路
pcr_periodPCR 插入周期(毫秒)20接收端时钟不稳时调小到 10
packetsizeTS 包长(字节)188一般不动,特殊链路用 204
flush_packets是否立即刷出封好的包1低延迟直播建议保持 1

其中muxrate在实时推流里最容易被误解。默认 0 表示输出码率跟随源文件的码率波动,这对多数 IP 网络直播没问题。但某些广播级接收设备要求输入是恒定码率,这时可以指定-muxrate 2500K,ffmpeg 会用空 TS 包填充到 2500Kbps。代价是带宽浪费,好处是接收端时钟锁定更简单。

ffmpeg -re -i input.mp4 -c copy -f mpegts -muxrate 2500K -pcr_period 20 udp://127.0.0.1:1234

参数说明:-muxrate 2500K强制输出恒定 2500Kbps;-pcr_period 20明确 PCR 周期为 20 毫秒,避免使用默认值在不同 ffmpeg 版本间的细微差异。

3.3 转码兜底:当源编码不被 TS 播放端接受时

拷贝模式虽然高效,但它继承源文件的编码格式。如果你的源视频是 H.264 但码率特别高,比如 50Mbps 的 4K 素材,播放端网络带宽可能顶不住。更麻烦的是音频如果是无损格式,TS 封装里很多播放端不支持。这时候就得走转码路线。

ffmpeg -re -i input.mp4 -c:v libx264 -preset veryfast -tune zerolatency -c:a aac -b:a 128k -f mpegts udp://127.0.0.1:1234

这条命令把视频重编码为 H.264,音频转换为 AAC。-preset veryfast控制编码速度和压缩率的折中,直播里通常不用太慢的 preset,因为编码器跟不上实时帧率反而会卡。-tune zerolatency是 x264 的直播专用调优项,去掉编码器内部的延时缓冲,换来的代价是压缩率略有下降、码率波动更明显。音频-b:a 128k固定 AAC 码率为 128Kbps,适合大多数语音和音乐场景。

参数说明:-preset取值从 ultrafast 到 placebo,ultrafast 最快但压缩率最低,veryfast 是直播场景里的常见平衡点;-tune zerolatency只对 libx264 生效,对硬件编码器如 h264_nvenc 需要用各自的低延迟参数。如果你用的是 NVIDIA 显卡,可以换-c:v h264_nvenc -rc vbr -cq 23,但要注意硬编和软编的参数并不通用。

转码兜底的意义在于:TS 封装本身不挑编码,但 TS 的接收端往往很挑。H.264 High Profile 在老旧设备上可能解不动,AAC-LC 是最稳的音频格式。遇到兼容性问题时,优先把视频 profile 降到 Main,音频固定为 AAC-LC,基本能覆盖绝大多数播放环境。

4. 拉流验证与播放器选型:用 ffprobe 确认封装细节

推完流之后,验证是必须的。不要只盯着发送端的输出日志,接收端能不能播放才是最终标准。这一章我按“从命令行工具到播放器”的顺序来讲。

4.1 ffplay 收 UDP 流:最直接的验证方式

第一步验证用 ffplay,这是 ffmpeg 项目自带的播放器,能直接读取 UDP 流:

ffplay -fflags nobuffer -analyzeduration 20000 udp://127.0.0.1:1234

-fflags nobuffer告诉 ffplay 不要做过多缓冲,能显著降低启动延迟。-analyzeduration 20000表示只分析前 20 毫秒的数据就尝试播放,避免因为分析时间过长让人误以为流没推上来。如果你看到画面正常输出、音画基本同步,说明推流链路已经通了。

注意 ffplay 对 UDP 流默认不设超时,如果流没推上来,窗口会一直黑着但不退出。这时先回发送端确认 ffmpeg 进程还在跑,再用-timeout 3000000给 ffplay 加一个 3 秒的超时限制,单位是微秒。

ffplay -fflags nobuffer -timeout 3000000 udp://127.0.0.1:1234

参数说明:-timeout对 UDP 协议也生效,超过指定时间没收到数据就退出,避免调试时挂着一个半死不活的窗口。

4.2 ffprobe 查 PAT/PMT 与时间戳:定位问题靠数据

ffplay 能播不代表底层细节是对的,尤其是做二次开发时,你需要确认 TS 流里的表结构是否完整。ffprobe 在这种情况下比 ffplay 更有用,因为它可以直接读流而不依赖解码器。

ffprobe -show_format -show_streams udp://127.0.0.1:1234

这条命令会输出整个流的格式信息,包括容器格式名、码率、时长,以及每个流的编码参数。如果输出里看不到视频流或者音频流,说明 PAT/PMT 的映射有问题,接收端拿不到完整的节目信息。

想进一步确认时间戳是否连续,可以用-show_packets:

ffprobe -select_streams v -show_packets udp://127.0.0.1:1234 2>/dev/null | grep pts_time | head -10

这里2>/dev/null把 ffprobe 的日志信息丢掉,只保留可解析的包数据。grep pts_time提取每帧的时间戳。连续几帧的 pts_time 应该是递增的,间隔约等于帧率倒数的整数倍,比如 25fps 视频间隔约 0.04 秒。如果看到 pts_time 前后跳动超过 0.1 秒,说明源端时间戳不干净,播放端迟早会出现音画问题。

参数说明:-select_streams v限定只处理视频流;-show_packets输出逐包信息;head -10控制输出前 10 行。

4.3 播放器与协议选型:UDP/TCP/HLS 各管一段

验证完命令行工具之后,要考虑你的实际播放客户端。不同播放器对 TS 流协议的支持差异很大,提前选型能省掉大量兼容性调试。

ffplay 和 VLC 是最省心的两支。VLC 直接打开网络串流地址udp://@:1234就能播放,但是它对 TS 流的 PCR 要求相对宽松,也就是说 VLC 能播的流用 ffplay 不一定能播,反过来也一样。我建议以 ffplay 为主要验证播放器,因为它的错误提示比 VLC 透明,会直接报 PID 缺失或时间戳跳变。

浏览器是另一套逻辑。原生 HTML5 video 标签不支持裸 TS 流,也不支持 UDP 协议。浏览器端的方案要么是把 TS 流再做一层 HLS 封装,要么在前端引入 transmuxer 库把 TS 转成 fMP4 通过 MSE 播放。这两种方案都只支持 HTTP 拉流,UDP/TCP 裸流到不了浏览器。所以如果你的最终用户是网页端,推流节点上必须部署一层 HTTP 服务,这也是下一章避坑内容的重点。

协议选择上,UDP 适合局域网内部传输,延迟可低到几十毫秒,但跨公网基本不可用;TCP 适合一对一可靠传输,延迟在百毫秒级;HLS 适合一对多的网页分发,延迟以秒计但兼容性最好。三者不是替代关系,而是不同网络场景下的三种选择。

5. 避坑与排查:TS 推流常见的五个翻车现场

这一章写我实际调试时撞过的问题,有些是查了很久才定位到根因的。每条按现象、原因、解决来写,可以直接对照排查。

5.1 音画不同步:PTS/DTS 不连续引发的播放器策略

现象:播放 TS 流大约 10 秒后,画面和声音开始错位,时间越长越明显,重启播放器会短暂恢复。

原因:源文件的时间戳本身不干净。常见于从非编软件导出的 MP4,时间戳存在微小丢帧或跳变,拷贝进 TS 时 ffmpeg 没有重新生成时间戳,而是原样搬运,问题就被放大。TS 要求 PTS 严格递增,一旦出现倒退,解码器会触发丢帧或重复帧逻辑,音画就乱了。

解决:转封装前先用-fflags +genpts强制生成新的 PTS。这个参数让 ffmpeg 根据 DTS 和帧率推导出 PTS,而不是信任源文件的时间戳。

ffmpeg -fflags +genpts -i input.mp4 -c copy -f mpegts output.ts

参数说明:-fflags +genpts在读取阶段生效,对后续所有处理都有效。注意这个参数只适用于部分容器,如果你的源流是裸 H.264,genpts 可能无法工作,那就需要先做一次封装转换把它变成 MP4。

如果 genpts 之后问题依然存在,说明 PTS 的跳变幅度超过了播放器的容忍范围,这时只能对视频或音频做重编码。通常保留视频不重编码,只重编码音频:

ffmpeg -i output.ts -c:v copy -c:a aac -b:a 128k output_fixed.ts

参数说明:-c:v copy保持视频原始编码,-c:a aac只重编码音频。音频编码过程会生成全新的时间戳,播放器对音频 PTS 的信任度更高,能掩盖部分源文件的问题。

5.2 UDP 丢包花屏:网络抖动下的传输层选择

现象:UDP 推流后播放端出现马赛克、画面撕裂,或者直接卡死在某一帧。发送端日志一切正常,CPU 也没有飙升。

原因:UDP 是无连接协议,没有重传机制。局域网丢包率在正常情况下极低,但网线接触不良、交换机拥塞、Wi-Fi 干扰都会造成随机丢包。TS 流虽然能容忍局部丢包,但丢包率超过 1% 时解码器就会因为参考帧损坏而出现持续花屏,直到下一个关键帧到达才能恢复。

解决:先确认丢包率和关键帧间隔。

ffprobe -show_packets -select_streams v udp://127.0.0.1:1234 -show_entries packet=flags -of csv 2>/dev/null | grep -c K

这条命令统计视频流里关键帧个数,grep -c K统计带 K 标志的包。如果一分钟内关键帧个数很少,比如超过 2 秒才一个关键帧,丢包后的恢复时间就会很长。手动设置关键帧间隔:

ffmpeg -re -i input.mp4 -c:v libx264 -preset veryfast -g 50 -c:a aac -f mpegts udp://127.0.0.1:1234

参数说明:-g 50表示每 50 帧一个关键帧,对 25fps 来说就是 2 秒一遇。关键帧越多,花屏恢复越快,但码率开销也越大,25fps 视频用 50 是常见折中。

如果网络环境实在恶劣,直接把传输层切换成 TCP。TCP 的重传机制会保证数据完整性,只是延迟会上升。实时性要求不高时,TCP 比 UDP 省心得多。

5.3 Web 端接裸 TS 流:为什么直接喂浏览器必黑屏

现象:TS 流在 ffplay、VLC 里都正常,但放到网页上用 video 标签播放,直接黑屏,控制台报 MEDIA_ERR_SRC_NOT_SUPPORTED。

原因:浏览器原生不支持 TS 容器。HTML5 video 把 MP4 作为首选格式,对 TS 完全没有内置解封装能力,而 MSE(Media Source Extensions)也没有原生的 TS 支持,必须通过 JavaScript 把 TS 转成 fMP4 之后才能喂给播放器。

解决:两种路线。第一种是用 mpegts.js 这类 transmuxer 库,在前端做 TS 到 fMP4 的实时转换,然后交给 video 元素播放。第二种是把后端 TS 流再切成 HLS,用 hls.js 播放,后端改动小但延迟会增加。mpegts.js 的接入方式是把网络流地址直接交给库内部的 fetch 或 WebSocket 连接处理,浏览器全局只需要初始化一个实例,代码大致如下:

const video = document.getElementById('player'); if (mpegts.isSupported()) { const player = mpegts.createPlayer({ type: 'mpegts', url: 'http://127.0.0.1:8080/live/stream.ts' }); player.attachMediaElement(video); player.load(); player.play(); }

代码逻辑说明:先检查浏览器是否支持 MSE,然后创建 mpegts 播放器实例,把 video 元素和流地址绑定。attachMediaElement负责建立数据通路,load启动拉流,play开始播放。这种方式对后端最友好,前端相当于做了一次容器转换。

注意 mpegts.js 只解决容器兼容问题,流地址必须是 HTTP 或 WebSocket,UDP 这种裸 socket 协议在浏览器里没有 API 可用,所以部署时依然需要一个 HTTP 服务来中转流数据。

5.4 ffprobe 正常但画面不出:SPS/PPS 与 PAT/PMT 周期

现象:使用-c copy从 MP4 转 TS,ffprobe 能看到视频和音频流存在,编码参数也正确,但播放器黑屏且不报错,或者要等很久才开始显示。

原因:这个场景我排查过多起,根因往往不是流表缺失,而是 H.264 的 SPS/PPS 参数集没有随流携带。MP4 容器里 SPS/PPS 通常在 moov 的 extradata 里,播放器必须先读容器元数据才能拿到。转成 TS 后这类带内数据如果没有被正确转换,播放器拿到 PES 后无法初始化解码器,当然黑屏。另一个原因是 PAT/PMT 的重发周期太长,播放器在启动后等不到节目表,也表现为黑屏。

解决:先确认 ffmpeg 版本是否自动附加了 h264_mp4toannexb,再手动指定 bitstream filter 强制转换,并主动设置 PAT/PMT 重发:

ffmpeg -i input.mp4 -c copy -bsf:v h264_mp4toannexb -mpegts_flags system_b -f mpegts output.ts

参数说明:-bsf:v h264_mp4toannexb强制将 H.264 转换为 Annex-B 格式,使 SPS/PPS 以带内方式出现在 TS 流中。-mpegts_flags system_b设置 TS 的兼容标志,某些播放器对非 system_b 的 TS 流支持不好。如果源是 HEVC,则换成-bsf:v hevc_mp4toannexb。

判断是否生效,用 ffprobe 看包里的 SPS 是否出现:

ffprobe -show_packets -select_streams v output.ts 2>/dev/null | grep -c "SPS"

一下就能看出关键信息有没有随流携带。SPS 出现次数大于 0 才说明参数集在带内正常传输。

5.5 转封装后音轨丢失:map 参数与音频编码格式

现象:-c copy转 TS 后,视频正常,但没有声音。ffprobe 看音频流显示编码格式是 MP3 或者 Opus。

原因:TS 容器本身可以承载 MP3,但很多播放器对 TS 里的 MP3 音频解析支持很差。还有一个可能,源文件里有多条音轨,ffmpeg 默认只挑第一条,而第一条恰好是空音轨或纯音乐轨道。转封装时没有用 map 参数筛选,就会把不需要的音轨带进 TS。

解决:先看源文件里有几条音轨:

ffprobe -show_streams -select_streams a input.mp4

确认之后用 map 精确指定音频轨:

ffmpeg -i input.mp4 -map 0:v:0 -map 0:a:0 -c copy -f mpegts output.ts

参数说明:-map 0:v:0表示取输入文件的第一条视频流,-map 0:a:0取第一条音频流。如果有两条音轨,需要第二条就写0:a:1。-c copy保持拷贝不重编码。

如果源音频格式是 Opus,建议直接重编码为 AAC,TS 播放端对 AAC 的支持最广泛:

ffmpeg -i input.mp4 -map 0:v:0 -map 0:a:0 -c:v copy -c:a aac -b:a 128k -f mpegts output.ts

参数说明:-c:a aac把音频转换为 AAC-LC,-b:a 128k设置码率。这一步会重新编码音频,但不会碰视频。

6. 进阶:TS 流切片与二次分发技巧

前面的内容解决了“一条 TS 流怎么推、怎么拉”的基本问题,最后一章讲我实际项目里一定会用的两个进阶手段:HLS 切片和二次分发。这两招解决的是同一个矛盾——直播流是持续不断的,但播放端是断断续续接入的,服务端必须有办法让新加入的播放器快速追上正在进行的流。

6.1 HLS 切片:一条命令让 TS 流兼容 Web

前面 5.3 提到浏览器不能直接播 TS,最常见的补救方式就是把 TS 流切分成 HLS。ffmpeg 从任意输入都能直接生成 HLS 切片,包括实时流:

ffmpeg -i udp://127.0.0.1:1234 -c copy -f hls -hls_time 4 -hls_list_size 0 -segment_format mpegts live.m3u8

参数说明:-f hls启用 HLS muxer;-hls_time 4表示每 4 秒生成一个切片;-hls_list_size 0表示保留所有切片文件的索引记录,不会自动清理旧切片,用于点播回看;-segment_format mpegts显式指定切片封装为 TS。如果不指定,HLS muxer 也会默认用 TS,但写出来更明确。

这条命令的意义在于字面上很直白,你把udp://127.0.0.1:1234换成任何已存在的流地址即可。生成出来的 m3u8 索引文件可以直接扔给 hls.js 或原生 video 标签播放,延迟约 6-10 秒,换来的是浏览器兼容性。

如果不想保留旧切片,把-hls_list_size 0改成-hls_list_size 5,只保留最近 5 个切片。注意切片时长和关键帧间隔要对齐,否则播放端可能在切片边界等待关键帧,造成卡顿。

6.2 对在播 TS 流做二次分发:-re 与 -copyts 的配合

第二个技巧更偏工程部署:你已经在推一套 UDP 流,但另一个接收方需要 TCP 拉流。最简单的方案是同时跑两个 ffmpeg 进程,但这很浪费资源。更好的做法是让一个进程把收到的 UDP 流直接再分发成 TCP:

ffmpeg -i udp://127.0.0.1:1234 -c copy -f mpegts -copyts "tcp://127.0.0.1:6666?listen=1"

参数说明:-i udp://127.0.0.1:1234从现有 UDP 流读取;-copyts表示保留原始时间戳不做偏移;TCP 目标地址的listen=1让它作为服务端等待连接。这样一条命令就把流从 UDP 通道搬到了 TCP 通道,拉流方只要连接tcp://127.0.0.1:6666就能收到完整数据。

-copyts是关键,不加的话 ffmpeg 默认会重新调整时间戳基准,可能造成播放端 PTS 从零开始,反而不如保留原始时间戳。做二次分发时还建议保留-re,把它加在-i之前,让输出速率跟随输入源,避免缓冲堆积。

要注意的是,HLS 切片和二次分发本质上是两种不同的输出策略,一个是生成文件供 HTTP 拉取,一个是直接维持网络连接。两者不冲突,你可以同时输出多路,ffmpeg 允许一条命令里写多个输出。从那以后我每次搭推流链路都强制走一遍这套流程:先 ffprobe 验输入时间戳,再-c copy转 TS,然后分别用 ffplay 和 ffprobe 验接收端,最后根据播放端类型决定加不加 HLS 切片。顺序不乱,问题就少了一半。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/25 10:57:42

Leaflet离线地图案例Demo集合:瓦片准备、坐标转换与内网部署实战

简介:面向GIS开发者与前端工程师,这份压缩包系统汇集了LeafLet离线地图从基础到进阶的典型开发案例。资源共723个文件,以JS、HTML、CSS源码为主,辅以PNG、SVG图标、Markdown说明文档和LeafDoc注释,整体体积仅14.08MB&a…

作者头像 李华
网站建设 2026/10/25 14:56:50

运维知识库建设:从经验到团队资产的高效转化

1. 运维知识库的价值与痛点去年处理工单时遇到个典型场景:某业务系统凌晨突然出现数据库连接池耗尽,值班同事花了3小时才定位到是连接泄漏问题。但翻聊天记录发现,同样的问题半年前就发生过,当时老张用10分钟就解决了。这种"…

作者头像 李华
网站建设 2026/10/24 18:22:03

C++11强枚举、静态断言与tuple实战解析

1. C11新特性概览C11标准为这门经典语言注入了全新的活力,其中强枚举类型、静态断言和tuple堪称最具实用价值的三大特性。这些特性并非简单的语法糖,而是从根本上改变了我们编写C代码的方式。作为一名长期奋战在一线的C开发者,我亲历了从C98到…

作者头像 李华
网站建设 2026/10/25 12:41:06

SpringBoot+SSM构建宠物行业管理系统的实践

1. 项目概述"云宠之家管理系统"是一套面向宠物服务行业的综合性管理平台,采用Java技术栈构建。这个系统解决了宠物医院、宠物店、寄养中心等机构在日常运营中遇到的客户管理混乱、服务记录分散、预约排班低效等痛点。我在实际开发过程中发现,传…

作者头像 李华
网站建设 2026/10/22 16:13:31

artcraft 创作工具实战:素材管理、风格化处理与导出适配全流程

1. 从“artcraft”这个名字说起:一个被低估的创作工具定位问题 第一次看到“artcraft”这个词,我脑子里蹦出来的不是某个具体软件,而是一类工具的统称——把“art”和“craft”拼在一起,本身就暗示了一种定位:它既不是…

作者头像 李华