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

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

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:


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



最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容