最近有个功能需要拿到视频流的帧转换成图片,对图片做处理再显示出来。先了解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];