常见编码方式H264
H.264又称为MPEG-4 AVC,是MPEG-4家族的一部分,是由ITU-T视频编码专家组(VCEG)和ISO/IEC动态图像专家组(MPEG)联合组成的联合视频组(JVT,Joint Video Team)提出的高度压缩数字视频编解码器标准。由于其低码率,高质量图像,强容错能力以及强网络适应性,使得其在数字电视广播,实时视频通信,网络流媒体等领域有很广泛的应用。
H264的帧类型
I帧:帧内编码帧(intra picture),I帧通常是每个GOP(MPEG所使用的一种视频压缩技术)的第一个帧,经过适度地压缩,作为随机访问的参考点,可以当成静态图像。I帧可以看作一个图像经过压缩后的产物,I帧压缩可去掉视频的空间冗余信息,下面即将介绍的P帧和B帧是为了去掉时间冗余信息。说了这么多只需要记住I帧自身可以通过视频解压算法解压成一张单独的完整的图片。
P帧:前向预测编码帧(predictive-frame),通过将图像序列中前面已编码帧的时间冗余信息充分去除来压缩传输数据量的编码图像,也称为预测帧。P帧表示的是这一帧跟之前的一个I帧(或P帧)的差别,解码时需要用之前缓存的画面叠加上本帧定义的差别,生成最终画面。(也就是差别帧,P帧没有完整画面数据,只有与前一帧的画面差别的数据)需要参考其前面的一个 I 帧 或者 P 帧来生成一张完整的图片。
B帧:双向预测内插编码帧(bi-directional interpolated prediction frame),以前面的I或P帧和后面的P帧为参考帧,“找出”B帧“某点”的预测值和两个运动矢量,并取预测差值和运动矢量传送。接收端根据运动矢量在两个参考帧中“找出(算出)”预测值并与差值求和,得到B帧“某点”样值,从而可得到完整的B帧。换言之,要解码B帧,不仅要取得之前的缓存画面,还要解码之后的画面,通过前后画面的与本帧数据的叠加取得最终的画面。B帧压缩率高,但是解码时CPU的负荷会比较大。
PTS和DTS
DTS(Decoding Time Stamp):即解码时间戳,这个时间戳的意义在于告诉播放器该在什么时候解码这一帧的数据。
PTS(Presentation Time Stamp):即显示时间戳,这个时间戳用来告诉播放器该在什么时候显示这一帧的数据。
如 采集到的帧如下:
I B B B P B B B P B B B I B B B P
那对应的pts顺序如下
PTS:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
dts顺序如下
DTS: 1 5 2 3 4 9 5 6 7 8 13 10 11 12 17 13 14 15 16
通过分析ffmpeg推流rtsp的抓包,发现dts是递增的,pts会有波动,将上面的顺序按照ffmpeg的推流顺序进行调整:
那对应的pts顺序如下
PTS:1 3 4 5 2 7 8 9 6 11 12 13 10 15 16 17 14
dts顺序如下
DTS: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
c++代码实现:
if (_sorter_max_size == 1) {
// 没有B帧,dts就等于pts [AUTO-TRANSLATED:9cfae4ea]
// There is no B frame, dts is equal to pts
dts = pts;
return true;
}
if (!_sorter_max_size) {
// 尚未计算出pts排序列队长度(也就是P帧间B帧个数) [AUTO-TRANSLATED:8bedb754]
// The length of the pts sorting queue (that is, the number of B frames between P frames) has not been calculated yet
if (pts > _last_max_pts) {
// pts时间戳增加了,那么说明这帧画面不是B帧(说明是P帧或关键帧) [AUTO-TRANSLATED:4c5ef2b8]
// The pts timestamp has increased, which means that this frame is not a B frame (it means it is a P frame or a key frame)
if (_frames_since_last_max_pts && _count_sorter_max_size++ > 0) {
// 已经出现多次非B帧的情况,那么我们就能知道P帧间B帧的个数 [AUTO-TRANSLATED:fd747b3c]
// There have been multiple non-B frames, so we can know the number of B frames between P frames
_sorter_max_size = _frames_since_last_max_pts;
// 我们记录P帧间时间间隔(也就是多个B帧时间戳增量累计) [AUTO-TRANSLATED:66c0cc14]
// We record the time interval between P frames (that is, the cumulative increment of multiple B frame timestamps)
_dts_pts_offset = (pts - _last_max_pts);
// 除以2,防止dts大于pts [AUTO-TRANSLATED:52b5b3ab]
// Divide by 2 to prevent dts from being greater than pts
_dts_pts_offset /= 2;
}
// 遇到P帧或关键帧,连续B帧计数清零 [AUTO-TRANSLATED:537bb54d]
// When encountering a P frame or a key frame, the continuous B frame count is cleared
_frames_since_last_max_pts = 0;
// 记录上次非B帧的pts时间戳(同时也是dts),用于统计连续B帧时间戳增量 [AUTO-TRANSLATED:194f8cdb]
// Record the pts timestamp of the last non-B frame (which is also dts), used to count the continuous B frame timestamp increment
_last_max_pts = pts;
}
// 如果pts时间戳小于上一个P帧,那么断定这个是B帧,我们记录B帧连续个数 [AUTO-TRANSLATED:1a7e33e2]
// If the pts timestamp is less than the previous P frame, then it is determined that this is a B frame, and we record the number of consecutive B frames
++_frames_since_last_max_pts;
}
// pts放入排序缓存列队,缓存列队最大等于连续B帧个数 [AUTO-TRANSLATED:ff598a97]
// Put pts into the sorting cache queue, the maximum cache queue is equal to the number of consecutive B frames
_pts_sorter.emplace(pts);
// 对前面的_sorter_max_size个pts排序结果,取出最早的那个,作为该帧的dts基准 [AUTO-TRANSLATED:6632922a]
// Take the earliest pts sorting result of the previous _sorter_max_size pts, and use it as the dts baseline for this frame
// 因为dts是递增的,可以将排序后的pts当作dts用,加上时间偏移量是为了避免dts和pts相差太大 [AUTO-TRANSLATED:74c97de1]
// Since dts is incremented, we can use the sorted pts as dts, and add the timestamp offset to avoid the difference between dts and pts being too large
if (_sorter_max_size && _pts_sorter.size() > _sorter_max_size) {
// 如果启用了pts排序(意味着存在B帧),并且pts排序缓存列队长度大于连续B帧个数, [AUTO-TRANSLATED:002c0d03]
// If pts sorting is enabled (meaning there are B frames), and the length of the pts sorting cache queue is greater than the number of consecutive B frames,
// 意味着后续的pts都会比最早的pts大,那么说明可以取出最早的pts了,这个pts将当做该帧的dts基准 [AUTO-TRANSLATED:86b8f679]
// It means that the subsequent pts will be larger than the earliest pts, which means that the earliest pts can be taken out, and this pts will be used as the dts baseline for this frame
auto it = _pts_sorter.begin();
// 由于该pts是前面偏移了个_sorter_max_size帧的pts(也就是那帧画面的dts), [AUTO-TRANSLATED:eb3657aa]
// Since this pts is the pts of the previous _sorter_max_size frames (that is, the dts of that frame),
// 那么我们加上时间戳偏移量,基本等于该帧的dts [AUTO-TRANSLATED:245aac6e]
// Then we add the timestamp offset, which is basically equal to the dts of this frame
dts = *it + _dts_pts_offset;
if (dts > pts) {
// dts不能大于pts(基本不可能到达这个逻辑) [AUTO-TRANSLATED:847c4531]
// dts cannot be greater than pts (it is basically impossible to reach this logic)
dts = pts;
}
// pts排序缓存出列 [AUTO-TRANSLATED:8b580191]
// pts sorting cache dequeue
_pts_sorter.erase(it);
return true;
}
// 排序缓存尚未满 [AUTO-TRANSLATED:3f502460]
// The sorting cache is not full yet
return false;