共计 1729 个字符,预计需要花费 5 分钟才能阅读完成。
1 问题描述
最近碰到一个问题,流媒体服务器写出来的 TS 文件用 ffmpeg、potplayer 播放时长是正确的,但是使用 mediainfo 和 windows 自带的系统播放器播放,就会出现播放时长偏小的问题。而 mediainfo 中音视频播放时长却是正确值。如下图所示:
如上图所示,该 MPEG-TS 文件的播放总时长为 9.268 秒,视频播放时长为 10.865 秒,音频播放时长为 10.755 秒。而该视频在 ffmpeg 中显示播放时长为 10.865 秒。
2 问题原因
针对以上问题,笔者下载了 mediainfo 的源码编译调试发现,mediainfo 解析 MPEG-TS 的播放时长来自于 TS 包中的 PCR(即 program_clock_reference/ 节目时钟参考)。而音视频播放时长来自于 PES 中时间戳。说到这里,我估计有些不熟悉 MPEG-TS 格式的小伙伴已经开始晕了。
3.MPEG2 系统音视频同步方式
MPEG-TS 文件结构分三个层面:
3.1.TS 层
使用固定包大小对数据进行打包,TS 包大小固定为 188 字节(M2TS/ 蓝光高清为 192 字节)。它包括 TS 头数据、有效载荷数据和填充数据三部分。
3.1.1 TS 包头
TS 包头为 4 字节固定大小包头标识,定义如下
typedef struct TS_packet_header
{
unsigned sync_byte : 8; // 同步字节, 固定为 0x47, 表示后面的是一个 TS 分组
unsigned transport_error_indicator : 1; // 传输误码指示符
unsigned payload_unit_start_indicator : 1; // 有效荷载单元起始指示符
unsigned transport_priority : 1; // 传输优先, 1 表示高优先级, 传输机制可能用到,解码用不着
unsigned PID : 13; //PID
unsigned transport_scrambling_control : 2; // 传输加扰控制
unsigned adaption_field_control : 2; // 自适应控制 01 仅含有效负载,10 仅含调整字段,11 含有调整字段和有效负载。为 00 解码器不进行处理
unsigned continuity_counter : 4; // 连续计数器 一个 4bit 的计数器,范围 0 -15
} TS_packet_header; // 总共 32 位,4 个字节
当 adaption_field_control=10 或 11 时:
包头后面紧跟者调整字段,PCR 则是在这个字段中,如下
…]
3.1.2 有效载荷数据
有效载荷数据一般为音视频数据包的 PES 数据包,或是节目映射表 PMT 和节目访问表 PAT 等 TS 节目信息。
3.1.3 填充部分
当 TS 包头和有效载荷数据不总 188 字节时,将会使用 0xff 填充,直到 TS 包满足 188 字节为止。
3.2 PES(Packetize Elementary Stream)
经过打包的基本码流,一般以数据帧为单位对音视频数据进行打包,同时会携带有该数据帧的时间戳和一些其他标志位信息。
3.3 ES(Elementary Stream)
视频基本码流数据。
4 PCR
PCR(Program Clock Reference/ 节目时钟参考)用于恢复出与编码端一致的系统时序时钟(STC/System Time Clock)。MPEG2 系统层标准规定,PTS 的时间间隔不能超过 0.7S,出现在 TS 包头的 PCR 间隔不能超过 0.1S,PCR 最大值位 26:30:43。在 TS 传输过程中,一般 DTS 和 PCR 差值会在一个合适的范围,这个差值就是要设置的音视频 buffer 的大小。一般情况下视频 DTS 和 PCR 的差值在 700ms-1200ms 之间,音频差值在 200ms-700ms 之间。
5 解决方案
现在终于明白了,mediainfo 和系统播放器获取时间是根据 PCR/ 节目时钟参考来计算的,而 ffmpeg 和 potplayer 之类的播放器时使用 PTS 来计算时间戳的。而 PCR 的时间间隔最大不能超过 0.1s,即 100ms,而这个 TS 文件的 PCR 时间间隔明显就大于 1 秒,才会导致这个问题的出现。想要解决这个问题,在每间隔 100ms 插入一个 PCR 就 OK 了。