AVSampleBufferDisplayLayer----转
http://blog.csdn.net/fernandowei/article/details/52179631
目前大多数iOS端的视频渲染都使用OpenGLES,但如果仅仅为了渲染而不做其他的例如美颜等效果,其实可以使用iOS8.0新出的AVSampleBufferDisplayLayer。对AVSampleBufferDisplayLayer,官方说明中有一句话,“The AVSampleBufferDisplayLayer class is a subclass of CALayer that displays compressed or uncompressed video frames.”,即AVSampleBufferDisplayLayer既可以用来渲染解码后的视频图片,也可以直接把未解码的视频帧送给它,完成先解码再渲染出去的步骤。
由于本人在使用AVSampleBufferDisplayLayer之前已经videotoolbox中相关api完成了h264视频的硬解,所以这里仅仅使用AVSampleBufferDisplayLayer来渲染,即送给它pixelBuffer。
个人选择了UIImageView作为渲染的view(没有直接使用UIView的原因后面会提到),而且也没有重载UIView的layerClass函数来使AVSampleBufferDisplayLayer成为这个view的默认layer(不这么做的原因后面提到)。
具体做法,首先,建立AVSampleBufferDisplayLayer并把它添加成为当前view的子layer:
- self.sampleBufferDisplayLayer = [[AVSampleBufferDisplayLayer alloc] init];
- self.sampleBufferDisplayLayer.frame = self.bounds;
- self.sampleBufferDisplayLayer.position = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
- self.sampleBufferDisplayLayer.videoGravity = AVLayerVideoGravityResizeAspect;
- self.sampleBufferDisplayLayer.opaque = YES;
- [self.layer addSublayer:self.sampleBufferDisplayLayer];
其次,把得到的pixelbuffer包装成CMSampleBuffer并设置时间信息:
- //把pixelBuffer包装成samplebuffer送给displayLayer
- - (void)dispatchPixelBuffer:(CVPixelBufferRef) pixelBuffer
- {
- if (!pixelBuffer){
- return;
- }
- @synchronized(self) {
- if (self.previousPixelBuffer){
- CFRelease(self.previousPixelBuffer);
- self.previousPixelBuffer = nil;
- }
- self.previousPixelBuffer = CFRetain(pixelBuffer);
- }
- //不设置具体时间信息
- CMSampleTimingInfo timing = {kCMTimeInvalid, kCMTimeInvalid, kCMTimeInvalid};
- //获取视频信息
- CMVideoFormatDescriptionRef videoInfo = NULL;
- OSStatus result = CMVideoFormatDescriptionCreateForImageBuffer(NULL, pixelBuffer, &videoInfo);
- NSParameterAssert(result == 0 && videoInfo != NULL);
- CMSampleBufferRef sampleBuffer = NULL;
- result = CMSampleBufferCreateForImageBuffer(kCFAllocatorDefault,pixelBuffer, true, NULL, NULL, videoInfo, &timing, &sampleBuffer);
- NSParameterAssert(result == 0 && sampleBuffer != NULL);
- CFRelease(pixelBuffer);
- CFRelease(videoInfo);
- CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, YES);
- CFMutableDictionaryRef dict = (CFMutableDictionaryRef)CFArrayGetValueAtIndex(attachments, 0);
- CFDictionarySetValue(dict, kCMSampleAttachmentKey_DisplayImmediately, kCFBooleanTrue);
- [self enqueueSampleBuffer:sampleBuffer toLayer:self.sampleBufferDisplayLayer];
- CFRelease(sampleBuffer);
这里不设置具体时间信息且设置kCMSampleAttachmentKey_DisplayImmediately为true,是因为这里只需要渲染不需要解码,所以不必根据dts设置解码时间、根据pts设置渲染时间。
最后,数据送给AVSampleBufferDisplayLayer渲染就可以了。
- <p class="p1"><pre name="code" class="objc">- (void)enqueueSampleBuffer:(CMSampleBufferRef) sampleBuffer toLayer:(AVSampleBufferDisplayLayer*) layer
- {
- if (sampleBuffer){
- CFRetain(sampleBuffer);
- [layer enqueueSampleBuffer:sampleBuffer];
- CFRelease(sampleBuffer);
- if (layer.status == AVQueuedSampleBufferRenderingStatusFailed){
- NSLog(@"ERROR: %@", layer.error);
- if (-11847 == layer.error.code){
- [self rebuildSampleBufferDisplayLayer];
- }
- }else{
- // NSLog(@"STATUS: %i", (int)layer.status);
- }
- }else{
- NSLog(@"ignore null samplebuffer");
- }
- }
可以看到,使用AVSampleBufferDisplayLayer进行视频渲染比使用OpenGLES简单了许多。不过遗憾的是,这里有一个iOS系统级的bug,AVSampleBufferDisplayLayer会在遇到后台事件等一些打断事件时失效,即如果视频正在渲染,这个时候摁home键或者锁屏键,再回到视频的渲染界面,就会显示渲染失败,错误码就是上述代码中的-11847。
个人在遇到上述问题后,联想到之前使用videotoolbox解码视频时遇到类似后台事件时VTDecompressionSession会失效从而需要撤销当前VTDecompressionSession来重新建立VTDecompressionSession的过程,在AVSampleBufferDisplayLayer失效时,也去撤销当前这个AVSampleBufferDisplayLayer再重建一个;这里说到之前卖的一个关子,如果这个AVSampleBufferDisplayLayer是view的默认layer,这时就没法只撤销layer而不动view,所以把AVSampleBufferDisplayLayer作为view的子layer更方便,撤销重建的过程如下:
- - (void)rebuildSampleBufferDisplayLayer{
- @synchronized(self) {
- [self teardownSampleBufferDisplayLayer];
- [self setupSampleBufferDisplayLayer];
- }
- }
- - (void)teardownSampleBufferDisplayLayer
- {
- if (self.sampleBufferDisplayLayer){
- [self.sampleBufferDisplayLayer stopRequestingMediaData];
- [self.sampleBufferDisplayLayer removeFromSuperlayer];
- self.sampleBufferDisplayLayer = nil;
- }
- }
- - (void)setupSampleBufferDisplayLayer{
- if (!self.sampleBufferDisplayLayer){
- self.sampleBufferDisplayLayer = [[AVSampleBufferDisplayLayer alloc] init];
- self.sampleBufferDisplayLayer.frame = self.bounds;
- self.sampleBufferDisplayLayer.position = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
- self.sampleBufferDisplayLayer.videoGravity = AVLayerVideoGravityResizeAspect;
- self.sampleBufferDisplayLayer.opaque = YES;
- [self.layer addSublayer:self.sampleBufferDisplayLayer];
- }else{
- [CATransaction begin];
- [CATransaction setDisableActions:YES];
- self.sampleBufferDisplayLayer.frame = self.bounds;
- self.sampleBufferDisplayLayer.position = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
- [CATransaction commit];
- }
- [self addObserver];
- }
当然,需要监听后台事件,如下:
- - (void)addObserver{
- if (!hasAddObserver){
- NSNotificationCenter * notificationCenter = [NSNotificationCenter defaultCenter];
- [notificationCenter addObserver: self selector:@selector(didResignActive) name:UIApplicationWillResignActiveNotification object:nil];
- [notificationCenter addObserver: self selector:@selector(didBecomeActive) name:UIApplicationDidBecomeActiveNotification object:nil];
- hasAddObserver = YES;
- }
- }
做到这里,基本的问题都解决了,视频可以正常渲染了;不过还有一个稍令人不悦的小问题,即app被切到后台再切回来时,由于这个时候AVSampleBufferDisplayLayer已经失效,所以这个时候渲染的view会是黑屏,这会有一到两秒的时间,直到layer重新建立好并开始渲染。那怎么让这个时候不出现黑屏呢?就需要前面提到的UIImageView,做法如下:
首先,对于每个到来的pixelbuffer,要保留它直到下一个pixelbuffer到来,如下函数中粗体所示:
- - (void)dispatchPixelBuffer:(CVPixelBufferRef) pixelBuffer
- {
- if (!pixelBuffer){
- return;
- }
- <strong> @synchronized(self) {
- if (self.previousPixelBuffer){
- CFRelease(self.previousPixelBuffer);
- self.previousPixelBuffer = nil;
- }
- self.previousPixelBuffer = CFRetain(pixelBuffer);
- }</strong>
- ...........略去其他
- }
其次,当切后台事件resignActive事件到来时,用当前最新保存的pixelbuffer去设置UIImageView的image,当然pixelbuffer要先转化成UIImage,方法如下:
- - (UIImage*)getUIImageFromPixelBuffer:(CVPixelBufferRef)pixelBuffer
- {
- UIImage *uiImage = nil;
- if (pixelBuffer){
- CIImage *ciImage = [CIImage imageWithCVPixelBuffer:pixelBuffer];
- uiImage = [UIImage imageWithCIImage:ciImage];
- UIGraphicsBeginImageContext(self.bounds.size);
- [uiImage drawInRect:self.bounds];
- uiImage = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
- }
- return uiImage;
- }
然后在resignActive事件处理函数中,设置UIImageView的image,如下:
- - (void)didResignActive{
- NSLog(@"resign active");
- [self setupPlayerBackgroundImage];
- }
- - (void) setupPlayerBackgroundImage{
- if (self.isVideoHWDecoderEnable){
- @synchronized(self) {
- if (self.previousPixelBuffer){
- self.image = [self getUIImageFromPixelBuffer:self.previousPixelBuffer];
- CFRelease(self.previousPixelBuffer);
- self.previousPixelBuffer = nil;
- }
- }
- }
- }
这样,切完后台回来前台,在layer还没有重新建立好之前,看到的就是设置的UIImageView的image而不是黑屏了,而这个image就是切后台开始时渲染的最后一帧画面。
对于前面说到的AVSampleBufferDisplayLayer失效后重建导致的黑屏时间,个人通过验证发现,如果这个重建动作,即下面这句代码,
- [[AVSampleBufferDisplayLayer alloc] init]
发生在app刚从后台会到前台就会非常耗时,接近两秒,而如果是正在前台正常播放的过程中执行这句话,只需要十几毫秒;前者如此耗时的原因,经过请教其他iOS开发的同事,可能是这个时候系统优先恢复整个app的UI,其他操作被delay;
AVSampleBufferDisplayLayer----转的更多相关文章
- iOS8系统H264视频硬件编解码说明
公司项目原因,接触了一下视频流H264的编解码知识,之前项目使用的是FFMpeg多媒体库,利用CPU做视频的编码和解码,俗称为软编软解.该方法比较通用,但是占用CPU资源,编解码效率不高.一般系统都会 ...
- 第11月第3天 直播 rtmp yuv
1. LiveVideoCoreSDK AudioUnitRender ==> MicSource::inputCallback ==> GenericAudioMixer::pushBu ...
- 02:H.264学习笔记
H.264组成 1.网络提取层 (Network Abstraction Layer,NAL) 2.视讯编码层 (Video Coding Layer,VCL) a.H.264/AVC影像格式阶层架构 ...
- 01:***VideoToolbox硬编码H.264
最近接触了一些视频流H264的编解码知识,之前项目使用的是FFMpeg多媒体库,利用CPU做视频的编码和解码,俗称为软编软解.该方法比较通用,但是占用CPU资源,编解码效率不高.一般系统都会提供GPU ...
- Direct Access to Video Encoding and Decoding
来源:http://asciiwwdc.com/2014/sessions/513 Direct Access to Video Encoding and Decoding Session 5 ...
- How to decode a H.264 frame on iOS by hardware decoding?
来源:http://stackoverflow.com/questions/25197169/how-to-decode-a-h-264-frame-on-ios-by-hardware-decodi ...
- How to use VideoToolbox to decompress H.264 video stream
来源:http://stackoverflow.com/questions/29525000/how-to-use-videotoolbox-to-decompress-h-264-video-str ...
随机推荐
- Eclipse版本android 65535解决方案(原理等同android studio现在的分包方式)
由于工作的需要看了下Eclipse下android65535的解决方案,查了好多文档,真心的发自内心的说一句请不要再拷贝别人的博客了,害人,真害人. 接下来我说下我的实现方式,首先说下65535的最可 ...
- SQL2008代理作业出现错误: c001f011维护计划创建失败的解决方法
SQL2008数据库总会出现从 IClassFactory 为 CLSID 为 {17BCA6E8-A95D-497E-B2F9-AF6AA475916F} 的 COM 组件创建实例失败,原因是出现以 ...
- 货运APP产品魔力=卓越功能×情感诉求
货运APP产品魔力=卓越功能×情感诉求 [导语]在滴滴打车.快的打车软件风靡全国之际,很多企业也在探索如何将其应用到公路货运领域用以整合社会运力资源.但一方面大多数人聚焦的是干线运输中的长途货车运 ...
- aspose words 介绍
Aspose.Words是一个商业.NET类库,可以使得应用程序处理大量的文件任务.Aspose.Words支持Doc,Docx,RTF,HTML,OpenDocument,PDF,XPS,EPUB和 ...
- xshell5激活
Xshell是一个用于MS Windows平台的强大的SSH,TELNET,和RLOGIN终端仿真软件.它使得用户能轻松和安全地从Windows PC上访问Unix/Linux主机.以上内容全部为广告 ...
- ODBC简介
加载驱动 1 oracle Class.forName("oracle.JDBC.driver.OracleDriver") 2 DB2 Class.forName("c ...
- STM32下FatFs的移植,实现了坏块管理,硬件ECC,ECC纠错,并进行擦写均衡分析
最近因项目需要,做一个数据采集的单片机平台.需要移植 FatFs .现在把最后成果贴上来. 1.摘要 在 STM32 单片机上,成功移植 FatFs 0.12b,使用的 Nand Flash 芯片为 ...
- HTML5的Video标签的属性,方法和事件汇总
<video>标签的属性 src :视频的属性 poster:视频封面,没有播放时显示的图片 preload:预加载 autoplay:自动播放 loop:循环播放 controls:浏览 ...
- python :开关,开灯关灯
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/ ...
- fso查找被删除的文件
<html> <head> </head> <body> 源目录:<input id="txtOld" value=" ...