iOS解码关于视频中带B帧排序问题

1.需求


在解码带B帧的视频编码码流中,由于B帧的存在,导致parse出来的数据时间戳本身不是连续的,因为B帧需要可能需要参考后面的帧.因此,解码完成后需要对码流解码做一个重排序

2.实现原理


首先分析parse出来的数据的时间戳,后面得出的结论是除了第一帧时间戳为0,总体时间戳是增长的,后面每4帧为一组,每组中的顺序需要重新排序,排序后即可实现时间戳连续递增.

考虑内存开销,我们每次缓存4帧视频数据,将其放入线性表中,然后使用排序算法对这4帧数据根据pts做一个排序,排好序后即可传给渲染模块将其渲染到屏幕上.

注意: 使用FFmpeg硬解无需关心排序问题,因为FFmpeg内部有缓存排序机制,即从FFmepg拿到解码后的数据可直接渲染。

3.TODO


因为我们无法判断码流中的B帧是否依赖后面的视频帧,而是 经过分析可得视频帧的时间戳是总体增长,每组4帧,需要对4帧数据进行排序,目前暂时未知是否有非4帧一组的视频帧,如果有则此Demo会有问题,因此如果有了解此方面大神望评论告知更好的解决方案.

4.总体架构


本文基于已实现解码一个包含带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

5.快速使用


  • 初始化

- (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) {

[self.sortHandler cleanLinkList];

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)];

}

6.具体实现


6.1定义一个线性表用来缓存视频帧

使用一个装有CMSampleBufferRef指针的数据来缓存视频帧.因为每4帧排一次序,所以长度为4,使用索引Index记录每个视频帧的位置.

const static int g_maxSize = 4;

struct XDXSortLinkList {

CMSampleBufferRef dataArray[g_maxSize];

int index;

};

6.2初始化

@interfaceXDXSortFrameHandler ()

{

XDXSortLinkList_sortLinkList;

}

......

- (instancetype)init {

if (self= [superinit]) {

XDXSortLinkListlinkList= {

.index=0,

.dataArray= {0},

};

_sortLinkList=linkList;

}

returnself;

}

6.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]);

_sortLinkList.dataArray[i] = NULL;

}

}

}

}

- (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;

}

- (void)cleanLinkList {

_sortLinkList.index = 0;

for (int i = 0; i < g_maxSize; i++) {

if (CMSampleBufferIsValid(_sortLinkList.dataArray[i])) {

CFRelease(_sortLinkList.dataArray[i]);

}

_sortLinkList.dataArray[i] = NULL;

}

}

原文链接:iOS解码关于视频中带B帧排序问题 - 资料 - 我爱音视频网 - 构建全国最权威的音视频技术交流分享论坛

★文末名片可以免费领取音视频开发学习资料,内容包括(FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)以及音视频学习路线图等等。

见下方!↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值