美文网首页iOS相关视频开发
数数GPUImage里那些未知的坑(一)

数数GPUImage里那些未知的坑(一)

作者: CoderHenry | 来源:发表于2018-04-19 17:11 被阅读151次

       记录一些实际开发中,比较难碰到与遇见的坑。大多是较深的操作,一般项目都碰不上,只有对自定义视频定制性较高的项目才会碰上。其他的像首帧黑帧,常见崩溃之类问题的社区有大把现成解决方案,不提也罢。本文着重于记录,并提供个人的一些解决问题思路,不喜勿喷~ 写得不好,还请多多包涵~

1、GPUImageVideoCamera 的回调

    videoCamera提供了willOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer的代理方法,并在videoProcessQueue串行队列里回调上层做视频帧处理。值得注意的是

(1)、出现willOutputSampleBuffer回调的帧数与实际设置的帧数相差太远

captureOuput的回调是在cameraProcessingQueue里,并用信号量进行控制,先看源码:

(GPUImaeVideoCamera源码)

看代码可知,如果上层处理视频帧时间过久,就会产生丢帧。如果上层处理时间每次都超时,会导致每两帧都会掉一帧,卡顿就会很明显。具体解决方案就要根据产品的需求和上层处理时长来定了,我们假设第二帧回调到时,信号量仍未释放,则丢弃了该帧,但很快信号量释放了,在信号量释放到第三帧到达之前,时间是浪费的,所以可以尝试调大相机的帧数,或者优化上层处理时间。要想完美控制帧数,只能在录制时调节recordWriter的帧数来实现。

(2)、willOutputSampleBuffer:回调的buffer与你想像中一样吗?

     通过GPU...Camera拿到的buffer,我们用GPUImageView来显示,一般我们设置outputImageOrientation = UIDeviceOrientationPortrait,因为我们看到的是竖的视频帧,我们就以为得到的是竖的视频帧,但实际上这是个坑。

     系统默认采集方向为向左转90度,GPU...Camera是通过改变输出方向来调整,也就是你此时采集到的帧其实是翻转的,只不过GPU..Camera通过outputImageOrientation,在输出的时候又帮你做相应的翻转。所以此时,如果你拿看到的是竖的视频帧做处理,就会出问题了。坑就坑在你看到的和你想的完全是两回事。

    解决方法:使用AVCaptureConnection自己控制视频帧的方向,由于GPU...Camera的videoOutput只有子类有权限使用,那就继承它,

记得把outputImageOrientation 设为UIDeviceOrientationUnknown; 翻转摄像头时,记得做相应切换操作,要不会恢复左翻转90度。

ps:仅对sampleBuffer有特殊要求时才需要这么做

2、非主线程执行同步主队列操作导致死锁

    我们先来看一段代码,谈论下他的可行性

   咋一看,老铁,没毛病。非主队列同步操作主队列没问题。博主在之前也是这么认为的。那我们把这段代码放到GPUVideoImageCamera的回调中试试:

你会发现,主线程卡死了!在非主队列,同步主队列,这有毛病吗?明面没毛病。那为什么主线程卡死,而且还不崩?只有一种情况:互等 -- 我等你,你等我(思路)。GPU...Camera的回调的串行队列(videoProcessQueue)在等主队列同步完成后回调,那主队列在等谁呢?我们来看看堆栈:

(主队列同步串行队列videoProcessQueue)

主队列同步等待串行队列videoProcessQueue!这就形成了互等,主队列同步等待串行队列,串行队列同步等待主队列,死锁。故意在串行队列sleep(1)也就是为了卡这个bug(实际上操作都可能卡死主线程,操作时间越长越容易出现)。看来,在非主队列同步调用主队列也并不是一个绝对安全的做法。此处,也咨询过其他iOS开发者,同步操作前有没办法判断是不是在等待另一个队列,得到的答案都是NO,若有有缘人知道安全的做法,还请留言赐教!

解决方案:此处不宜通过同步主队列的方法来处理事件,一定要同步处理的话,可以自己创建一个队列,或者用GPU...Camera的videoProcessQueue.

3、队列的同步异步操作判断

GPUImage提供了自建队列同步的操作方法

同步操作队列:

runSynchronouslyOnVideoProcessingQueue(void (^block)(void))

runSynchronouslyOnContextQueue(GPUImageContext *context, void (^block)(void))

我们再来看看实现:

runSynchronouslyOnContextQueue实现方法类似,不再贴图。重点就在这句:dispatch_get_specific([GPUImageContext contextKey]);--  dispatch_get_specific与dispatch_set_specific,先通过dispatch_set_specific给一个队列做一个标识,然后在当前队列dispatch_get_specific当前队列是不是该标识的队列,不明白的自己科普。

我们看看[GPUImageContext contextKey],该标识是openGLESContextQueueKey.

通过[GPUImageContext contextKey]拿到的key都是一样的,这会有什么后果呢?就是在videoProcessingQueue或与contextQueue的队列上通过dispatch_get_specific([GPUImageContext contextKey]),他一定会返回YES。这就会出问题了:

假设我在contextQueue里希望同步到一个里videoProcessingQueue操作,我们调用runSynchronouslyOnVideoProcessingQueue,但其实由于dispatch_get_specific([GPUImageContext contextKey])一定返回YES,block里的操作会直接进行,你所希望的操作将会在contextQueue里进行,而不会在videoProcessingQueue中进行。因为没法同步videoProcessingQueue,如果此时你有希望在videoProcessingQueue同步的操作,就会出现偏差了。GPU的异步队列大同小异,就不再重复举例。

        解决方案:(二选一)

        1、改写源码 

        2、同等对待这两个queue,把他们当同个queue(实际上GPUImage在同步上就是这么对待的)。

4、recordWriter 与 imageMovie配合的坑

大部分人都是直接Ctrl + c , Ctrl + V,加上,项目用得少,也基本不会有问题。但当你随意调换顺序或是自定义的时候,就可能会出现你摸不着头脑的坑。先简单说下imageMovie的创建方式(算有点小坑):

1、- (id)initWithAsset:(AVAsset *)asset;(有图像和声音回调,但不会播放声音,得自己添加播放,但可以直接与recordWriter配合)

2、- (id)initWithPlayerItem:(AVPlayerItem *)playerItem;(自己创建AVPlayer去解析和控制item,可以做到和播放器一样,播放也有声音。但只有图像回调,没有声音回调,因为系统只提供了AVPlayerItemVideoOutput。如果和recordWriter配合,制作完成后得自己添加音轨)

3、- (id)initWithURL:(NSURL *)url;本质就是initWithAsset,有兴趣自己看底层

下面说说坑(出现概率低于1%。):

(一)、野指针,出现如图

(野指针一) (野指针二)

            bug排查:synchronizedMovieWriter或movie已经被销毁,很绕,这也是GPUImage出问题难排查的原因,全是block,你调我,我调他,他还调你,重点你还不知道他什么时候会调,不想看的(看了还得研究源码)直接看解决方案。只分析野指针一,二的原理差不多。

        首先:movieWriter通过 

        videoQueue = dispatch_queue_create("com.sunsetlakesoftware.GPUImage.videoReadingQueue", GPUImageDefaultQueueAttribute());

        和块:videoInputReadyCallback()进行下一帧的读取操作movie里的readNextVideoFrameFromOutput,而终止录制用的是runSynchronouslyOnContextQueue;也就是存在掉用finish后继续读取下一帧的可能,

       当movie里的synchronizedMovieWriter赋值videoInputReadyCallback,同时读取下一帧,此时发现isKeepLooping为NO,在block里调用本身的endProcessing(见下图),此时来到崩溃的地方,由于block并没有强引用,而此时如果synchronizedMovieWriter已经置空(上层销毁),就会产生野指针。野指针二出现的情况就是movie先置空,writer后置空

            解决方案:      

      调了这两句后,上层保证你的recordMovie,movieWriter不会马上致空(其实大概也就0.01秒的时间)即可

   (二)、上层渲染没问题,record完成却全部变黑帧

        出现这情况,逻辑上是先判断是否有videoTrack,没videoTrack肯定是业务逻辑出问题,上层排查。有videoTrack,渲染没问题,却全部黑帧:时间戳出问题(思路),定位

        [assetWriter startSessionAtSourceTime:frameTime];

        正常这里应该是0,看看frameTime是否异常,或者是这里根本没有走就直接进行write了。

        再定位

        [assetWriterPixelBufferInput appendPixelBuffer:pixel_buffer         withPresentationTime:frameTime],打印frameTime一步步排查

        可能出现此异常的操作:先processing,再startRecording

        [self.recordMovie startProcessing];

        [self.movieWriter startRecording];

        出现概率低,需要天时地利人和,但他就是存在。

        解决方案:把这两句代码顺序调过来就可以了

        PS:此处出现状况较多,仅提供排查思路

  (三)、录制完成后,通过主线程回调上层,通过视频地址获取的视频马上播放后,发现无法播放,隔一段时间又能播放了(出现概率极低,当处理较大的视频是概率稍大)

        bug排查:估计还是这两货出问题(不确定)

            [self.recordMovie startProcessing];

            [self.movieWriter startRecording];

        由于马上回调上层,可能movieWriter还在操作该路径,导致马上播放失败

        解决方案:

        延时回调主线程(0.1秒);

(四)、设置_movie.audioEncodingTarget = _writer后,出现

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[AVAssetWriterInput appendSampleBuffer:] Cannot append sample buffer: Input buffer must be in an uncompressed format when outputSettings is not nil'

设置outputSetting支持PCM,两个方法,一个改源码,不想破坏源码的写子类,重写GPUImageMovie里的createAssetReader:

原创文章,转载请注明出处


相关文章

  • 数数GPUImage里那些未知的坑(一)

    记录一些实际开发中,比较难碰到与遇见的坑。大多是较深的操作,一般项目都碰不上,只有对自定义视频定制性较高的项...

  • 数数伪智能硬件产品那些坑

    引言: 智能硬件,一个被认为能颠覆人类生活的行业,几年来如火如荼的向前推进。特别是2013年以来,互联网巨头积极布...

  • iOS:AVFoundation本地视频叠加(画中画)

    之前使用GPUImage叠加本地视频,有很多坑,所以换了个思路,使用AVMutableVideoCompositi...

  • 将GPUImage添加到工程里

    将GPUImage添加到工程里 GPUImage提供图像处理滤镜,并且支持照相机和摄像机的实时滤镜GPUImage...

  • 关于GPUImagemovie 解码部分视频资源出现灰色阴影等颜

    最近由于公司项目需要,用上了GPUImage 框架进行视频处理,其中踩了不少的坑。 GPUIMageMovie 解...

  • GPUImage填坑心得

    项目用到GPUImage录制视频,同时还要加水印录制,录制的教程大把,但是这个库15年就不维护了,导致很多坑没有补...

  • GPUImage2 的导入

    首先,GPUImage有3个版本分别是:GPUImage,GPUImage2,GPUImage3 GPUImage...

  • GPUImage 解析

    GPUImage解析(一) —— 基本概览(一)GPUImage解析(二) —— 基本概览(二)GPUImage解...

  • 使用GPUImage 一些坑

    1:首先理解了安装,作者推荐动态库安装,好处就是特么的,你看到的source都是输入源,filters都是滤镜过程...

  • GPUImage概览

    读GPUImage源码,深入了解GPUImage原理及OpenGL ES。 关于GPUImage GPUImage...

网友评论

本文标题:数数GPUImage里那些未知的坑(一)

本文链接:https://www.haomeiwen.com/subject/wffwkftx.html