美文网首页
视频帧处理中的性能问题

视频帧处理中的性能问题

作者: 迷路的安然和无恙 | 来源:发表于2020-03-16 18:16 被阅读0次

    前言

    在音视频处理中,如稍微不太注意代码的实现方式,可能会导致内存泄漏以及内存占用飙升的问题,具体问题具体分析,来探究下为什么都能实现功能的前提下,不同的实现方式,会有不同的内存收益。

    举例

    以给视频中添加字幕为例,视频添加字幕通常有两种方式。一是在layer层添加字幕的layer,二是如水印一样,将文字渲染到视频流中。这里,重点说的是第二种方式。

    视频帧处理

    给视频加入字幕,实际上是读取视频帧,对满足条件的帧进行渲染。大致流程如下:


    image.png

    使用AVAssetReader可以获取到CVSampleBuffer,通过CVSampleBuffer又可以获取到CVPixelBufferRefCVPixelBufferRef 是一种像素图片类型。
    获取CVPixelBufferRef 后,通常需要将CVPixelBufferRef 转成UIImage
    然后对UIImage做额外的绘制工作,比如添加文本。

    CVSampleBufferUIImage有两种方法
    // 基础参数
    CMSampleBufferRef buffer =....
    __block CVImageBufferRef CVPixelBuffer = CMSampleBufferGetImageBuffer(buffer);
    
    // 方式一
    
    - (UIImage *)imageFromSampleBuffer:(CMSampleBufferRef)sampleBuffer {
        if (!sampleBuffer) {
            return nil;
        }
        // Get a CMSampleBuffer's Core Video image buffer for the media data
        CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
        // Lock the base address of the pixel buffer
        CVPixelBufferLockBaseAddress(imageBuffer, 0);
    
        // Get the number of bytes per row for the pixel buffer
        void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer);
    
        // Get the number of bytes per row for the pixel buffer
        size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);
        // Get the pixel buffer width and height
        size_t width = CVPixelBufferGetWidth(imageBuffer);
        size_t height = CVPixelBufferGetHeight(imageBuffer);
    
        // Create a device-dependent RGB color space
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    
        // Create a bitmap graphics context with the sample buffer data
        CGContextRef context = CGBitmapContextCreate(baseAddress, width, height, 8,
                                                     bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
        // Create a Quartz image from the pixel data in the bitmap graphics context
        CGImageRef quartzImage = CGBitmapContextCreateImage(context);
    
    
        // Unlock the pixel buffer
        CVPixelBufferUnlockBaseAddress(imageBuffer,0);
    
        // Free up the context and color space
        CGContextRelease(context);
        CGColorSpaceRelease(colorSpace);
    
        // Create an image object from the Quartz image
    //    UIImage *image = [UIImage imageWithCGImage:quartzImage];
        UIImage *image = [UIImage imageWithCGImage:quartzImage scale:1.0f orientation:UIImageOrientationUp];
        // Release the Quartz image
        CGImageRelease(quartzImage);
    
        return image;
    
    }
    

    方式二

    - (UIImage*)imageFromSampleBuffer:(CVPixelBufferRef)p {
        CIImage* ciImage = [CIImage imageWithCVPixelBuffer:p];
    
        CIContext* context = [CIContext contextWithOptions:@{kCIContextUseSoftwareRenderer : @(YES)}];
    
        CGRect rect = CGRectMake(0, 0, CVPixelBufferGetWidth(p), CVPixelBufferGetHeight(p));
        CGImageRef videoImage = [context createCGImage:ciImage fromRect:rect];
    
        UIImage* image = [UIImage imageWithCGImage:videoImage];
        CGImageRelease(videoImage);
    
        return image;
    }
    

    两种方式,都可以将CVSampleBuffer转成UIImage,但是,两种方法是基于不同的框架,内存消耗上,工作原理上有不同表现。

    以处理一分钟的的视频为例

    1.在使用CVSampleBufferUIImage的过程中,使用方法一也就是CoreVideo+Core Graphics的组合,与直接使用CoreImage并没有太大的内存出入,也并不能充分体现CoreImage的优势。
    经测试,在iPhone7上,方式一的内存峰值为36M,平均值25.6M,方式二的内存峰值是26.1M,平均值25.1M

    2.接下来,是对每一帧的视频画面,添加文本,将计算好的文本,使用Core Graphics渲染到每一帧上。
    代码示例如下:

    - (UIImage*)addText:(NSString*)text addToView:(UIImage*)image{
        
        int w = image.size.width;
        int h = image.size.height;
        
        UIGraphicsBeginImageContext(image.size);
        [image drawInRect:CGRectMake(0, 0, w, h)];
        
        NSMutableParagraphStyle *textStyle = [[NSMutableParagraphStyle defaultParagraphStyle] mutableCopy];
        textStyle.lineBreakMode = NSLineBreakByWordWrapping;
        textStyle.alignment = NSTextAlignmentCenter;//水平居中
        UIFont* font = [UIFont systemFontOfSize:40];
        
        NSDictionary *attr = @{NSFontAttributeName: font, NSForegroundColorAttributeName : [UIColor whiteColor], NSParagraphStyleAttributeName:textStyle,NSKernAttributeName:@(2),NSBackgroundColorAttributeName:[UIColor orangeColor]};
            
        [text drawInRect:CGRectMake(0, h - 240, w, 60) withAttributes:attr];
        
        UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        
        return newImage;
    }
    

    3.将UIImage再次转成CVPixelBufferRef写入到新的视频文件中

    此时,依旧有上面的两种方式。继续使用Core Gragrahic+CoreVide的方式和CIImage

    方式一的代码示例如下:

    //CGImageRef --> CVPixelBufferRef
    - (CVPixelBufferRef) pixelBufferFromCGImage: (CGImageRef) image {
        
        CGSize frameSize = CGSizeMake(CGImageGetWidth(image), CGImageGetHeight(image));
        
        NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                                 [NSNumber numberWithBool:NO], kCVPixelBufferCGImageCompatibilityKey,
                                 [NSNumber numberWithBool:NO], kCVPixelBufferCGBitmapContextCompatibilityKey,
                                 nil];
        CVPixelBufferRef pxbuffer = NULL;
        CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault, frameSize.width,
                                              frameSize.height,  kCVPixelFormatType_32BGRA, (__bridge CFDictionaryRef) options,
                                              &pxbuffer);
        NSParameterAssert(status == kCVReturnSuccess && pxbuffer != NULL);
        CVPixelBufferLockBaseAddress(pxbuffer, 0);
        void *pxdata = CVPixelBufferGetBaseAddress(pxbuffer);
        CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
        // kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst 需要转换成需要的32BGRA空间
        CGContextRef context = CGBitmapContextCreate(pxdata, frameSize.width,
                                                     frameSize.height, 8, 4*frameSize.width, rgbColorSpace,
                                                     kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
        CGContextDrawImage(context, CGRectMake(0, 0, CGImageGetWidth(image),
                                               CGImageGetHeight(image)), image);
        
        CGColorSpaceRelease(rgbColorSpace);
        CGContextRelease(context);
        CVPixelBufferUnlockBaseAddress(pxbuffer, 0);
        
        return pxbuffer;
    }
    

    此时内存会逐渐从25M -> 上升至650M,好在两种方式都是基于GPU的。但这肯定是不能接受的了,那,为什么会这样?

    在这个过程中,CoreGrahics都做了什么?

    首先,该方式先获取了Image的尺寸,之后,创建CVPixelBuffer,这些流程其实都不好内存。真正消耗内存的方法,是在CVPixelBufferLockBaseAddress()之后,使用CGBitmapContextCreate次方法解码Image获取bitmapbitmap的大小是可以计算的,以一张宽高1280 * 720的图,内存占用即可达到 1280 * 720 * 4 (32位RGBA) = 3.5M。

    CoreImage代码演示

    // 将一个处理过的图像渲染到 pixelBuffer
    CIImage *result = [CIImage imageWithCGImage:image_text.CGImage];
    CVPixelBufferLockBaseAddress(CVPixelBuffer, 0);
    CGColorSpaceRef cSpace = CGColorSpaceCreateDeviceRGB();
    [self.ciContext render:result toCVPixelBuffer:CVPixelBuffer bounds:result.extent colorSpace:cSpace];
    CVPixelBufferUnlockBaseAddress(CVPixelBuffer, 0);
    

    使用这种方式,内存消耗会控制在25M左右,和650M相差了进30倍。
    保持一个CIContext的引用,它提供一个桥梁来连接我们的Core Image对象和 OpenGL上下文。我们创建一次就可以一直使用它。这个上下文允许Core Image在后台做优化,比如缓存和重用纹理之类的资源等。重要的是这个上下文我们一直在重复使用。
    CoreImage既可以运行在CPU也可以是GPU,区别在于使用不同的创建方式,及API。

    未完......

    相关文章

      网友评论

          本文标题:视频帧处理中的性能问题

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