内容简介:在解码带B帧的视频编码码流中,由于B帧的存在,导致parse出来的数据时间戳本身不是连续的,因为B帧需要可能需要参考后面的帧.因此,解码完成后需要对码流解码做一个重排序首先分析parse出来的数据的时间戳,后面得出的结论是除了第一帧时间戳为0,总体时间戳是增长的,后面每4帧为一组,每组中的顺序需要重新排序,排序后即可实现时间戳连续递增.考虑内存开销,我们每次缓存4帧视频数据,将其放入线性表中,然后使用排序算法对这4帧数据根据pts做一个排序,排好序后即可传给渲染模块将其渲染到屏幕上.
需求
在解码带B帧的视频编码码流中,由于B帧的存在,导致parse出来的数据时间戳本身不是连续的,因为B帧需要可能需要参考后面的帧.因此,解码完成后需要对码流解码做一个重排序
实现原理
首先分析parse出来的数据的时间戳,后面得出的结论是除了第一帧时间戳为0,总体时间戳是增长的,后面每4帧为一组,每组中的顺序需要重新排序,排序后即可实现时间戳连续递增.
考虑内存开销,我们每次缓存4帧视频数据,将其放入线性表中,然后使用 排序 算法对这4帧数据根据pts做一个排序,排好序后即可传给渲染模块将其渲染到屏幕上.
注意: 使用FFmpeg硬解无需关心排序问题,因为FFmpeg内部有缓存排序机制,即从FFmepg拿到解码后的数据可直接渲染.
阅读前提
代码地址 : iOS解码关于视频中带B帧排序问题
掘金地址 : iOS解码关于视频中带B帧排序问题
简书地址 : iOS解码关于视频中带B帧排序问题
博客地址 : iOS解码关于视频中带B帧排序问题
TODO
因为我们无法判断码流中的B帧是否依赖后面的视频帧,而是 经过分析可得视频帧的时间戳是总体增长,每组4帧,需要对4帧数据进行排序,目前暂时未知是否有非4帧一组的视频帧,如果有则此Demo会有问题,因此如果有了解此方面大神望评论告知更好的解决方案.
总体架构
本文基于已实现解码一个包含带B帧的H.265码流文件.因此,这里不对parse,解码,渲染做过多说明,如需了解参考阅读前提中的链接.
下面是从FFmpeg parse H.265文件得到的时间戳,我们可以看到,parse出来的时间戳总体是递增的,除了第一帧时间戳为0的数据外,剩下的时间戳均以4个一组,每组内时间戳不是递增的,因为解码时我们仍需按照Parse出来的数据送去解码,所以,我们只能对解码后的数据做一个排序,即将每组数据装入一个线性表,然后使用任一一种 排序算法 对其排序.
+0800 XDXVideoDecoder[1489:223381] Test - 0.000000 2019-06-25 12:38:09.656282+0800 XDXVideoDecoder[1489:223381] Test - 0.133333 2019-06-25 12:38:09.659350+0800 XDXVideoDecoder[1489:223381] Test - 0.066667 2019-06-25 12:38:09.660900+0800 XDXVideoDecoder[1489:223381] Test - 0.033333 2019-06-25 12:38:09.662889+0800 XDXVideoDecoder[1489:223381] Test - 0.100000 2019-06-25 12:38:09.664786+0800 XDXVideoDecoder[1489:223381] Test - 0.266667 2019-06-25 12:38:09.666900+0800 XDXVideoDecoder[1489:223381] Test - 0.200000 2019-06-25 12:38:09.668196+0800 XDXVideoDecoder[1489:223381] Test - 0.166667 2019-06-25 12:38:09.669825+0800 XDXVideoDecoder[1489:223381] Test - 0.233333 2019-06-25 12:38:09.670368+0800 XDXVideoDecoder[1489:223381] Test - 0.400000 2019-06-25 12:38:09.670948+0800 XDXVideoDecoder[1489:223381] Test - 0.333333 2019-06-25 12:38:09.671806+0800 XDXVideoDecoder[1489:223381] Test - 0.300000 2019-06-25 12:38:09.673082+0800 XDXVideoDecoder[1489:223381] Test - 0.366667 2019-06-25 12:38:09.673899+0800 XDXVideoDecoder[1489:223381] Test - 0.533333 2019-06-25 12:38:09.674961+0800 XDXVideoDecoder[1489:223381] Test - 0.466667 2019-06-25 12:38:09.675637+0800 XDXVideoDecoder[1489:223381] Test - 0.433333 2019-06-25 12:38:09.676451+0800 XDXVideoDecoder[1489:223381] Test - 0.500000
快速使用
-
初始化
- (void)viewDidLoad { self.sortHandler = [[XDXSortFrameHandler alloc] init]; self.sortHandler.delegate = self; }
-
在解码回调中将解码出来数据送给排序模块
- (void)getVideoDecodeDataCallback:(CMSampleBufferRef)sampleBuffer isFirstFrame:(BOOL)isFirstFrame { if (self.isH265File) { // Note : the first frame not need to sort. if (isFirstFrame) { CVPixelBufferRef pix = CMSampleBufferGetImageBuffer(sampleBuffer); [self.previewView displayPixelBuffer:pix]; return; } [self.sortHandler addDataToLinkList:sampleBuffer]; }else { CVPixelBufferRef pix = CMSampleBufferGetImageBuffer(sampleBuffer); [self.previewView displayPixelBuffer:pix]; } }
-
排序后通过代理方法拿到排过序的每帧数据将其渲染到屏幕
- (void)getSortedVideoNode:(CMSampleBufferRef)sampleBuffer { int64_t pts = (int64_t)(CMTimeGetSeconds(CMSampleBufferGetPresentationTimeStamp(sampleBuffer)) * 1000); static int64_t lastpts = 0; NSLog(@"Test marigin - %lld",pts - lastpts); lastpts = pts; [self.previewView displayPixelBuffer:CMSampleBufferGetImageBuffer(sampleBuffer)]; }
具体实现
1. 定义一个线性表用来缓存视频帧
使用一个装有 CMSampleBufferRef
指针的数据来缓存视频帧.因为每4帧排一次序,所以长度为4,使用索引Index记录每个视频帧的位置.
const static int g_maxSize = 4; struct XDXSortLinkList { CMSampleBufferRef dataArray[g_maxSize]; int index; };
2. 初始化
@interface XDXSortFrameHandler () { XDXSortLinkList _sortLinkList; } ...... - (instancetype)init { if (self = [super init]) { XDXSortLinkList linkList = { .index = 0, .dataArray = {0}, }; _sortLinkList = linkList; } return self; }
3.将视频帧存入线性表
CMSampleBufferRef
实际即为一个指针,首先使用 CFRetain
函数使其引用计数+1以维持我们使用期间视频帧不会被销毁,当线性表没有装满前一直为其添加视频帧,当线性表装满后使用插入排序对其进行排序,排好后将数据依次以代理方法的形式传出以供渲染模块使用.使用完成后使用 CFRelease
减少原始数据的引用计数.
- (void)addDataToLinkList:(CMSampleBufferRef)sampleBufferRef { CFRetain(sampleBufferRef); _sortLinkList.dataArray[_sortLinkList.index] = sampleBufferRef; _sortLinkList.index++; if (_sortLinkList.index == g_maxSize) { _sortLinkList.index = 0; // sort [self selectSortWithLinkList:&_sortLinkList]; for (int i = 0; i < g_maxSize; i++) { if ([self.delegate respondsToSelector:@selector(getSortedVideoNode:)]) { [self.delegate getSortedVideoNode:_sortLinkList.dataArray[i]]; CFRelease(_sortLinkList.dataArray[i]); } } } } - (void)selectSortWithLinkList:(XDXSortLinkList *)sortLinkList { for (int i = 0; i < g_maxSize; i++) { int64_t minPTS = i; for (int j = i + 1; j < g_maxSize; j++) { if ([self getPTS:sortLinkList->dataArray[j]] < [self getPTS:sortLinkList->dataArray[minPTS]]) { minPTS = j; } } if (i != minPTS) { void *tmp = sortLinkList->dataArray[i]; sortLinkList->dataArray[i] = sortLinkList->dataArray[minPTS]; sortLinkList->dataArray[minPTS] = tmp; } } } - (int64_t)getPTS:(CMSampleBufferRef)sampleBufferRef { int64_t pts = (int64_t)(CMTimeGetSeconds(CMSampleBufferGetPresentationTimeStamp(sampleBufferRef)) * 1000); return pts; }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Netty-解码器架构与常用解码器
- Glide 缓存与解码复用
- 音频解码 Audio Converter
- golang之Json编码解码
- 解码器 与 编码器
- WeIdentity 源码分析 | 狗哥解码(一)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。