UIImageView显示视频流

最近有个功能需要拿到视频流的帧转换成图片,对图片做处理再显示出来。先了解AVFoundation的一些基本类,后面会贴上代码,也会把遇到的坑写出来。

关键类
  • AVCaptureSession
    媒体(音、视频)捕捉会话,负责把捕捉的音频视频数据输出到输出设备中。它是连接AVCaptureInput和AVCaptureOutput的桥梁。
  • AVCaptureDevice
    代表抽象的物理设备,比如相机camera与麦克风。microphone。
  • AVCaptureDeviceInput
    设备输入数据管理对象,可以根据AVCaptureDevice创建对应的AVCaptureDeviceInput对象。
  • AVCaptureOutput
    代表输出数据,输出的可以是图片(AVCaptureStillImageOutput)或者视频(AVCaptureMovieFileOutput)。
  • AVCaptureVideoPreviewLayer
    预览层,用它显示图片或者视频。
输出类

需要的功能不同,用到的数据输出管理对象也不同。

  • AVCaptureMovieFileOutput
    用于视频输出到指定文件中保存
  • AVCaptureStillImageOutput
    用于图片输出
  • AVCaptureVideoDataOutput
    用于视频数据输出
  • AVCaptureAudioDataOutput
    用于音频数据输出
初始化
-(void)viewDidLoad
{
    AVCaptureSession *session = [[AVCaptureSession alloc] init];
    [session setSessionPreset:AVCaptureSessionPreset640x480];

    
    AVCaptureDevice *captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    AVCaptureDeviceInput *deviceInput = [AVCaptureDeviceInput deviceInputWithDevice:captureDevice error:nil];
    if ( [session canAddInput:deviceInput] )
        [session addInput:deviceInput];
    AVCaptureVideoPreviewLayer *previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:session];
    [previewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
    
    
    AVCaptureVideoDataOutput *dataOutput = [AVCaptureVideoDataOutput new];
    //一定得注意这个值kCVPixelBufferPixelFormatTypeKey 不然后面的sampleBuffer会有问题的
    dataOutput.videoSettings = [NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedInt:kCVPixelFormatType_32BGRA] forKey:(NSString *)kCVPixelBufferPixelFormatTypeKey];
    [dataOutput setAlwaysDiscardsLateVideoFrames:YES];
    if ( [session canAddOutput:dataOutput])
        [session addOutput:dataOutput];
    dispatch_queue_t queue =dispatch_queue_create("VideoDataOutputQueue", DISPATCH_QUEUE_SERIAL);
    [dataOutput setSampleBufferDelegate:self queue:queue];
    //设置方向
    for (AVCaptureVideoDataOutput* output in session.outputs) {
        for (AVCaptureConnection * av in output.connections) {
            av.videoOrientation = UIDeviceOrientationPortrait;
        }
    }
    
    //不加这个代理回调只执行一次
    CALayer *rootLayer = [[self view] layer];
    [rootLayer setMasksToBounds:YES];
    [previewLayer setFrame:CGRectMake(0, 0, rootLayer.bounds.size.width, rootLayer.bounds.size.height)];
    [rootLayer insertSublayer:previewLayer atIndex:0];
    
    [session startRunning];
}

代理协议回调

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
    CVImageBufferRef imageBuffer =  CMSampleBufferGetImageBuffer(sampleBuffer);
    CVPixelBufferLockBaseAddress(imageBuffer, 0);
    
    uint8_t *baseAddress =CVPixelBufferGetBaseAddress(imageBuffer);
    size_t width = CVPixelBufferGetWidthOfPlane(imageBuffer, 0);
    size_t height = CVPixelBufferGetHeightOfPlane(imageBuffer, 0);
    size_t bytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 0);
    CGColorSpaceRef colorSpace= CGColorSpaceCreateDeviceRGB();
    CGContextRef cgContext= CGBitmapContextCreate(baseAddress, width, height, 8, bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);

    CGImageRef cgImage = CGBitmapContextCreateImage(cgContext);
    UIImage *image=[UIImage imageWithCGImage:cgImage];
    dispatch_sync(dispatch_get_main_queue(), ^{
//        self.imageView.layer.contents = (__bridge id _Nullable)(cgImage);
            self.imageView.image=image;
    });

    CGImageRelease(cgImage);
    CGContextRelease(cgContext);
    CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
}
遇到的坑
  • 使用GPUImage遇到uint8_t ∗ baseAddress拿到的值为空,void ∗ baseAddress可以,但后面的处理需要uint8_t 。
  • 使用GPUImage,CGColorSpaceRef只能选gray,不然CGContextRef cgContext为空,最终也就拿不到图片。
  • 视频流输出格式一定要明白自己需要的是啥,kCVPixelBufferPixelFormatTypeKey设置错了的话,后面也会获取不到图片的。
  • 获得的图片是旋转90°的,设置下videoOrientation。
 for (AVCaptureVideoDataOutput* output in session.outputs) {
        for (AVCaptureConnection * av in output.connections) {
            av.videoOrientation = UIDeviceOrientationPortrait;
        }
    }
  • 需要把AVCaptureVideoPreviewLayer添加到自己的显示layer层上,不然代理协议回调只执行一次。
  • 记得释放掉CGImageRef与CGContextRef,不然内存过大会crash掉。
  • 给UIImageView赋图片时,得异步放到主线程中,要不图片是不会及时刷新的。

最后想说一句,网上CMSampleBufferRef转UIImage的例子有很多,直接拿过来回发现获取不到图片,官网的代码也是如此,不是这些代码有问题,而是可能自己的某些设置有问题,就像我遇到的那些坑。实在获取不到图片的话,使用下面的代码吧:

    CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
    CIImage *ciImage=[CIImage imageWithCVPixelBuffer:imageBuffer];
    UIImage *image=[UIImage imageWithCIImage:ciImage];

这种方法生成的图片不能获取到NSData,或者用

    CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
    CIImage *ciImage=[CIImage imageWithCVPixelBuffer:imageBuffer];
    CGImageRef cgImage=[[[CIContext alloc]init] createCGImage:ciImage fromRect:[ciImage extent]];
    UIImage *image=[UIImage imageWithCGImage:cgImage];

你可能感兴趣的:(UIImageView显示视频流)