记录<一次点播的无法seek的问题跟踪>的bug的完整排查思路

一. 问题背景

长达26个小时的KPL直播.Seek到中间偏后的时候会卡死不动.但是前面1-2个小时可以Seek.

二. 问题跟踪

大家有兴趣可以先看看Mac上终端调试FFmpeg任意版本源码的最佳实践

这个问题刚开始跟踪的时候,首先想到的是是不是视频源的问题.是用vlc和flv去尝试.奇怪的是vlc也不支持.但是ffmpeg支持.
此时我们来跟踪问题,发现到了JitterBuffer层之后无法往解码层送数据

// 正在 seek
if (packet->pts - packet->start_pts < seek_to_timestamp_) {
    
    
LOG_DEBUG("OnAudioPacketDemuxed, drop audio packet : %lld, start : %lld, seek time : %lld", packet->pts, packet->start_pts, seek_to_timestamp_);
return true;
}

两者相减为负数,我们要去seek的pts不对.到底为什么不对呢?

1. 首先怀疑的是解析pts出错了?

因为此流地址是

http://xxx/playlist_eof.m3u8

我们下载后看里面ts文件可知

流的第一个ts分片:
http://xxx_1.ts?start=0&end=2475019&type=mpegts
pts=8171962796

中间的某一个:
http://xxx_525.ts?start=24645672&end=26180503&type=mpegts
pts=4399514004

最后一个:
http://xxx_1302.ts?start=24598860&end=25023363&type=mpegts
pts=6771267804

现在通过ffmpeg工具查看ts分片后得知:

ffplay http://xxx_1.ts\?start\=0\&end\=2475019\&type\=mpegts

可正常播放.
然后用命令行看一下pts.

ffprobe -show_packets http://xxx_1.ts\?start\=0\&end\=2475019\&type\=mpegts

或者

ffprobe -of xml -show_streams http://xxx_1.ts\?start\=0\&end\=2475019\&type\=mpegts

可以看到start_pts是8171962796

<stream index="0" codec_name="h264" codec_long_name="H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10" profile="High" codec_type="video" codec_time_base="1/100" codec_tag_string="[27][0][0][0]" codec_tag="0x001b" width="1920" height="1080" coded_width="1920" coded_height="1080" has_b_frames="1" sample_aspect_ratio="1:1" display_aspect_ratio="16:9" pix_fmt="yuv420p" level="42" chroma_location="left" field_order="progressive" refs="1" is_avc="false" nal_length_size="0" id="0x100" r_frame_rate="50/1" avg_frame_rate="50/1" time_base="1/90000" start_pts="8171962796" start_time="90799.586622" duration_ts="529200" duration="5.880000" bits_per_raw_sample="8">

看起来也没有pts也和上面和代码解复用之后拿到的pts是一样的.那么就是说解析到的pts是对的.那么为什么会出现pts先逐渐增大,然后变小后再增大的问题呢?

2. ffmpeg解析的pts是多少呢?

起始值一样.但是我seek拖到中途之后pts竞然没有变小.通过

static int64_t wrap_timestamp(const AVStream *st, int64_t timestamp)
{
    
    
if (st->pts_wrap_behavior != AV_PTS_WRAP_IGNORE &&
st->pts_wrap_reference != AV_NOPTS_VALUE && timestamp != AV_NOPTS_VALUE) {
    
    
if (st->pts_wrap_behavior == AV_PTS_WRAP_ADD_OFFSET &&
timestamp < st->pts_wrap_reference)
return timestamp + (1ULL << st->pts_wrap_bits);
else if (st->pts_wrap_behavior == AV_PTS_WRAP_SUB_OFFSET &&
timestamp >= st->pts_wrap_reference)
return timestamp - (1ULL << st->pts_wrap_bits);
}
return timestamp;
}

打断点可知,这里对pts如果小区<start_pts的时候,加了一个值.这个值是1LL<33.恍然大悟.因为ts分片的最长能存储的极限是33bit,那么如果超过这个值的时候会减掉1LL<33从0开始.

对时间戳循环到头的处理:
ts流中的三个时间戳,pts(33bit),dts(33bit),pcr(42bit)
当时间戳增加到语法无法容纳时,产生时间戳循环,时间线开始变小,循环点计算方式是:将要设置的值减去时间戳最大值,将差值写入语法结构。
编码器设置时钟,以及解码器自己的系统时钟在达到最大值后,都根据此方式计算,不会产生时钟的不连续。
现在加上这一段即可

//ts流中的时间戳,pts(33bit),dts(33bit)
//当时间戳增加到语法无法容纳时,产生时间戳循环,时间线开始变小,循环点计算方式是:将要设置的值减去时间戳最大值,将差值写入语法结构。
int64_t TsParser::getVideoRevisedPts(int64_t pts){
    
    
if(video_packet_){
    
    
int64_t startPts_Real = video_start_pts_*kTimeBase; //转换成ms之前的从流中获取的时间戳
if(pts < startPts_Real){
    
    
pts += kMaxPtsFrame;
}
}
return pts;
}

int64_t TsParser::getAudioRevisedPts(int64_t pts){
    
    
if(audio_packet_){
    
    
int64_t startPts_Real = audio_start_pts_*kTimeBase; //转换成ms之前的从流中获取的时间戳
if(pts < startPts_Real){
    
    
pts += kMaxPtsFrame;
}
}
return pts;
}

关于更多的格式的具体细节可以参考30分钟学会FLV和HLS

参考文档:

1.总结TS中的pcr、dts、pts问题

猜你喜欢

转载自blog.csdn.net/weixin_45581597/article/details/127869816