iOS中实时视频帧处理的小tips

如何计算当前的帧率

在苹果的官方demo中, 有一个很好的计算方法


...
    NSMutableArray *_previousSecondTimestamps = [[NSMutableArray alloc] init];
...

- (void)calculateFramerateAtTimestamp:(CMTime)timestamp
{
    [_previousSecondTimestamps addObject:[NSValue valueWithCMTime:timestamp]];
    
    CMTime oneSecond = CMTimeMake( 1, 1 );
    CMTime oneSecondAgo = CMTimeSubtract( timestamp, oneSecond );
    
    while( CMTIME_COMPARE_INLINE( [_previousSecondTimestamps[0] CMTimeValue], <, oneSecondAgo ) ) {
        [_previousSecondTimestamps removeObjectAtIndex:0];
    }
    
    if ( [_previousSecondTimestamps count] > 1 ) {
        const Float64 duration = CMTimeGetSeconds( CMTimeSubtract( [[_previousSecondTimestamps lastObject] CMTimeValue], [_previousSecondTimestamps[0] CMTimeValue] ) );
        const float newRate = (float)( [_previousSecondTimestamps count] - 1 ) / duration;
        self.previewFrameRate = newRate;
        NSLog(@"FrameRate - %f", newRate);
    }
}


//在调用时候:

    CMTime timestamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
    [self calculateFramerateAtTimestamp:timestamp];

实时处理视频帧过程中如何丢帧

iOS的AVCaptureSession的delegate回调方法- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection会把视频帧和音频帧通过该回调方法返回.

我们将视频帧CMSampleBufferRef抛到自建线程中进行实时处理, 该线程每次只处理一帧数据, 处理过程中, 其他的视频帧回调过来就丢弃, 具体的可以用以下两种方法:

方法一, 使用static变量控制, 该处理线程,每次最多只能处理1帧数据.如果当前正在处理数据, 则直接返回

    static NSInteger nProcessFrame = 0;
    if (nProcessFrame > 0) {
        return;    //last frame not finished
    }
    nProcessFrame ++;
    __block CMSampleBufferRef currentBuffer;
    CMSampleBufferCreateCopy(kCFAllocatorDefault, sampleBuffer, &currentBuffer);
    CMTime timestamp = CMSampleBufferGetPresentationTimeStamp( currentBuffer );
    [self calculateFramerateAtTimestamp:timestamp];
    dispatch_async(_bufferQueue, ^{
        [self.delegate captureService:self processSampleBuffer:currentBuffer];
        CFRelease(currentBuffer);
        nProcessFrame1--;
        return;
    });

方法二, apple 的官方demo RosyWriter中的方法:

/**
 // call under @synchronized( self ) 

 注意这里使用的丢弃帧的方法.
 
 在videoDataOutput 的 delegate queue中获取 缓存一个previewPixelBuffer, 然后在外部输出的_delegateCallbackQueue中获取之前准备好的pixelBuffer. 这样可以丢弃部分内容

 主要由于 videoDataOutput queue 的速度 和delegatecallback queue 的速度决定的
 */
- (void)outputPreviewPixelBuffer:(CVPixelBufferRef)previewPixelBuffer {
    // Keep preview latency low by dropping stale frames that have not been picked up by the delegate yet
    // Note that access to currentPreviewPixelBuffer is protected by the @synchronized lock

    // 通过丢弃那些还没有被delegate处理的帧, 保证预览的低延迟, 这里缓存一个pixelBuffer, 在delegate里面完成获取要显示的pixelBuffer

    // 为什么这里注释 - 可以通过丢帧来保证界面显示的低预览延迟.因为这里虽然使用self.currentPreviewPixelBuffer 获取缓存了一个最近使用的pixelBuffer, 但是如果操作过快. 在进入delegate queue 以后,有可能这个方法已经调用了好几次. 在delegate queue里面拿到的pixelbuffer 一定是最新的那一个, 之前传递进来的数据帧就被丢弃了!!!!!!!
    self.currentPreviewPixelBuffer = previewPixelBuffer;


    // 低速通道
    [self invokeDelegateCallbackAsync:^{
        CVPixelBufferRef currentPreviewPixelBuffer = NULL;
        // 在delegate queue里面处理时候,对currentPreviewPixelBuffer上锁. 在delegate queue中使用一个局部变量获取之前缓存的那个previewPixelBuffer
        // 这里给 self 加锁,为了 获取currentPreviewPixelBuffer
        @synchronized(self) {
            // 如果当前有最优的current previewPixelBuffer, 用一个local 变量获取它,并把local变量抛出去
            currentPreviewPixelBuffer = self.currentPreviewPixelBuffer;
            if ( currentPreviewPixelBuffer ) {
                CFRetain( currentPreviewPixelBuffer );
                self.currentPreviewPixelBuffer = NULL;
            }
        }
        
        if (currentPreviewPixelBuffer) {// 如果当前存在最近一个的pixel buffer, 那么抛出去准备完成界面绘制
            // 通过delegate方法将准备好的pixelBuffer 通过代理抛出去
            [self.delegate captureService:self processSampleBuffer:currentBuffer];
            CFRelease(currentPreviewPixelBuffer);
        }
    }];
}

CMSampleBufferRef转化UIImage的性能问题

前一段时间在开发刷脸的过程中, 由于视频帧(CMSampleBufferRef -> UIImage)性能瓶颈导致, 进行实时的人脸检测处理帧率太慢, 在性能较弱的机器比如iPhone4s, iPhone5中, 检测帧率太低(不到10fps),导致(动作活体, 反光活体)检测效果不好.

在检查了各个函数的运行时间以后,确定了问题出现的原因: 视频帧 -> UIImage 方法耗时严重, 因此导致帧率下降. 在优图原来使用的将视频帧 -> 图片的方法是用的CoreImage的函数, 具体代码如下:

-(UIImage *) imageFromImageBuffer:(CVImageBufferRef)imageBuffer{
    CIImage *ciImage = [CIImage imageWithCVPixelBuffer:imageBuffer];

    CGImageRef videoImage = [get_temp_context()
                             createCGImage:ciImage
                             fromRect:CGRectMake(0, 0,
                                                 CVPixelBufferGetWidth(imageBuffer),
                                                 CVPixelBufferGetHeight(imageBuffer))];
    
    UIImage *image = [[UIImage alloc] initWithCGImage:videoImage scale:1.0f orientation:UIImageOrientationUpMirrored];
    CGImageRelease(videoImage);
    return image;
}

在使用该方法每帧数据耗时达100+ms, 因此单位时间内处理例如转化后的UIImage图像时, 检测处理帧率会大幅减少, 测试结果显示,iPhone5上动作检测只有10fps.

camera360的一个同行给出建议,切换成CoreGraphic相关函数能够大幅降低处理时间, 具体代码如下:

- (UIImage *) imageFromSampleBuffer:(CMSampleBufferRef) sampleBuffer {
    // 为媒体数据设置一个CMSampleBuffer的Core Video图像缓存对象
    CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
    // 锁定pixel buffer的基地址
    CVPixelBufferLockBaseAddress(imageBuffer, 0);

    // 得到pixel buffer的基地址
    void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer);

    // 得到pixel buffer的行字节数
    size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);
    // 得到pixel buffer的宽和高
    size_t width = CVPixelBufferGetWidth(imageBuffer);
    size_t height = CVPixelBufferGetHeight(imageBuffer);

    // 创建一个依赖于设备的RGB颜色空间
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

    // 用抽样缓存的数据创建一个位图格式的图形上下文(graphics context)对象
    CGContextRef context = CGBitmapContextCreate(baseAddress, width, height, 8,
                                                 bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
    // 根据这个位图context中的像素数据创建一个Quartz image对象
    CGImageRef quartzImage = CGBitmapContextCreateImage(context);
    // 解锁pixel buffer
    CVPixelBufferUnlockBaseAddress(imageBuffer,0);

    // 释放context和颜色空间
    CGContextRelease(context);
    CGColorSpaceRelease(colorSpace);

    // 用Quartz image创建一个UIImage对象image
    UIImage *image = [UIImage imageWithCGImage:quartzImage];

    // 释放Quartz image对象
    CGImageRelease(quartzImage);

    return (image);
}

使用新方法以后, 每帧由视频帧->图片的处理时间降低一个数量级, 在10ms左右, 大大提升了单位时间检测效率.

CoreGraphic的应用很广泛, 基本是iOS中图像绘制最棒的框架, 通常我们调整UIImage,例如改变大小宽度,旋转重绘等等都会使用它.同时我们自定义控件, 例如创建一个圆形进度条 - progressView,时候我们往往在drawRect中绘制当前进度等等.

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

推荐阅读更多精彩内容