视频编码帧内刷新Intra refresh
简介
帧内刷新能够解决IDR过大带来的码流不平稳问题,传统的视频码流通常是IPPPIPPP码流结构,即每个GOP的第一帧为全Intra predicted(I 帧),这样即使该GOP中某一帧丢失,最坏的情况下也只影响一个GOP内的帧,等到下一个GOP视频即可恢复正常,但是这种GOP结构码率不够平稳,在I帧的时候导致码率突然变高,非常不利于网络传输。帧内刷新技术可以使码率平稳,降低码流传输时延,并且仍然可以在传输出错的情况下快速恢复。
帧内刷新原理
帧内刷新的结构为IPPPPPP…,除了第一帧为I帧,后面全部是P帧,选择一组帧作为刷新周期,这组帧里面的所有P帧中都有一条,强制其在编码的时候使用帧内预测。其他部分使用率失真优化进行预测模式选择即可。
如图所示,假设刷新周期为4,第一帧的第一个竖条强制使用帧内预测,第二帧的第二条强制使用帧内预测,直到第四帧的第四条,此时完成一轮刷新。当然这些帧都是P帧。
帧内刷新的优点
1、码率稳定。所有的P帧由于都有一条区域使用帧内预测模式,其他区域运行率失真优化选择最优模式,因此每个P帧的大小波动不会太大。
2、降低时延。时延降低主要体现在没有I帧上,I帧通常较大,在网络传输的时候需要将一帧分成很多个包来传输,而在解码端必须等所有的包都到达后才能开始解码,所以I帧是造成视频码流传输时延大的主要原因,也就是说瓶颈在I帧这里。在帧内刷新技术上,虽然P帧比传统GOP结构的P帧要大一些,但总体趋于平稳,并且比I帧要小很多,因此可以快速传完一帧并解码。根本上来讲其实就是类似传输一个大文件耗时较长,而小文件较快。
3、关于错误恢复,这一点想了很久才明白。我们先考虑一种恢复较快的情况,即运动向量是0的情况,每一帧中的区块都是参考其前一帧的相应位置的块。假设第一帧丢失,那么第二帧只有第二条区域可以正常解码,第三帧的第二条参考第二帧的第二条,那么第三帧的第二条和第三条可以正常解码,同理,第四帧的二、三、四条可以正常解码,到第五帧时,整个帧都可以正常解码,至此视频恢复正常播放,只需要一个刷新周期就恢复了。再来考虑极端情况,还是从第二帧开始,第二帧只有第二条正常解码,加入第三帧的除第三条外的其他区域都是参考的第二帧的非第二条区域,那么第三帧就只有第三条可以正常解码,同理第四帧只有第四条正常,这样是永远也无法恢复的。但是现实是不可能这样的,总有参考到前一帧的帧内刷新条上的块,这样正常的块就会越来越多,从而慢慢恢复正常。
参考代码
/*---# P帧帧内刷新,降低 I 帧过大带来的网络冲击------------------------------------------------------------*/
VENC_INTRA_REFRESH_S pstIntraRefresh;
ret = HI_MPI_VENC_GetIntraRefresh(encChn,&pstIntraRefresh);
if (HI_SUCCESS != ret)
{
ERROR_LOG("HI_MPI_VENC_GetIntraRefresh (%d) fail: %#x!\n", encChn, ret);
return HLE_RET_ERROR;
}
pstIntraRefresh.bRefreshEnable = HI_TRUE;
pstIntraRefresh.enIntraRefreshMode = INTRA_REFRESH_ROW; //按行刷新
/*满足条件:u32RefreshNum * MaxRefreshFrameInGop >= (u32PicHeight+15)/16;
其中,无高级跳帧参考时,MaxRefreshFrameInGop = Gop(30),则u32RefreshNum >= (u32PicHeight+15)/(16*MaxRefreshFrameInGop)
1920*1080: (u32PicHeight+15)/(16*MaxRefreshFrameInGop) = (1080+15)/(16*30) = 2.281
960*544: (u32PicHeight+15)/(16*MaxRefreshFrameInGop) = (544+15)/(16*30) = 1.164
所以:主码流通道,u32RefreshNum 取 3
次码流通道,u32RefreshNum 取 2
*/
pstIntraRefresh.u32RefreshNum = (0 == stream_index)? 3 : 2;
pstIntraRefresh.u32ReqIQp = 40;
ret = HI_MPI_VENC_SetIntraRefresh(encChn,&pstIntraRefresh);
if (HI_SUCCESS != ret)
{
ERROR_LOG("HI_MPI_VENC_SetIntraRefresh (%d) fail: %#x!\n", encChn, ret);
return HLE_RET_ERROR;
}