前言
项目Demo,实现了实时滤镜、拍照、录像功能。
最近玩了哈实时滤镜,学到挺多东西的。笔者长得丑,看看有没有机会没那么丑。只挑了几种滤镜,笔者是个钢铁直男,没有美颜效果。
原理
设备获取图像输入流后,经过对该帧处理形成新图像,最后刷新UI。
实现
苹果有简单的UIImagePickerController
,但扩展性差。所以笔者采用的是AVFoundation
框架。其涉及到输入流和输出流,方便我们对每一帧进行处理,显示出来。
如果你对输入和输出相关的类不了解,应该也不影响你理解本文。但笔者还是建议你先看哈苏沫离的博客。比如,OC之输入管理AVCaptureInput,OC之输出管理 AVCaptureOutput。
有一个类AVCaptureMovieFile
,直接把音频和图像结合起来。但由于要处理画面,所以又得拆分开。所以笔者采用的是AVCaptureAudioDataOutput
和AVCaptureVideoDataOutput
,处理完图像再拼。
1. 获取输入流
输入流要通过相关设备初始化。
// 图像_device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];_cameraDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:self.device error:nil];// 音频AVCaptureDevice *micDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];_microphoneDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:micDevice error:nil];复制代码
2. 初始化输出流
_queue = dispatch_queue_create("VideoDataOutputQueue", DISPATCH_QUEUE_SERIAL);// 图像_videoDataOutput = [[AVCaptureVideoDataOutput alloc] init];_videoDataOutput.videoSettings = @{(id)kCVPixelBufferPixelFormatTypeKey : [NSNumber numberWithInteger:kCVPixelFormatType_32BGRA]};_videoDataOutput.alwaysDiscardsLateVideoFrames = YES;[_videoDataOutput setSampleBufferDelegate:self queue:_queue];// 音频_audioDataOutput = [[AVCaptureAudioDataOutput alloc] init];[_audioDataOutput setSampleBufferDelegate:self queue:_queue];复制代码
创建了一个串行队列,以确保每一帧按顺序处理。输出流的回调方法为- (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection;
。
3. 创建会话,连接输入输出
AVCaptureSession
会话起到中间层的作用。
_session = [[AVCaptureSession alloc] init];if ([_session canSetSessionPreset:AVCaptureSessionPreset1280x720]) {[_session setSessionPreset:AVCaptureSessionPreset1280x720];}{ // 把输入输出结合起来if ([_session canAddInput:_cameraDeviceInput]) {[_session addInput:_cameraDeviceInput];}if ([_session canAddOutput:_videoDataOutput]) {[_session addOutput:_videoDataOutput];}}复制代码
4. 开启会话
开启输入流,获取数据到输出流。
//开始启动[_session startRunning];复制代码
要注意的是,如果要修改输入流或者输出流,要在一次提交中完成。比如切换摄像头(修改输入流)。
//输入流AVCaptureDeviceInput *newInput = [AVCaptureDeviceInput deviceInputWithDevice:newCamera error:nil];if (newInput != nil) {[self.session beginConfiguration];//先移除原来的input[self.session removeInput:self.cameraDeviceInput];if ([self.session canAddInput:newInput]) {[self.session addInput:newInput];self.cameraDeviceInput = newInput;} else {[self.session addInput:self.cameraDeviceInput];}[self.session commitConfiguration];}复制代码
5. 输出流数据回调方法
// 在这里处理获取的图像,并且保存每一帧到self.outputImg- (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {@autoreleasepool {if (output == _audioDataOutput && [_audioWriterInput isReadyForMoreMediaData]) {// 处理音频[_audioWriterInput appendSampleBuffer:sampleBuffer];}if (output == self.videoDataOutput) { // 处理视频帧// 处理图片,保存到self.outputImg中[self imageFromSampleBuffer:sampleBuffer];}}}复制代码
在这一步,处理每一帧图像,加上滤镜。
6. 滤镜
这里用到了CIFilter
,是苹果自带的CoreImage框架对图片进行处理
的一个框架。其实现了上百种效果,笔者只选取了其中三种。感兴趣的可以去官方文档查看。
//1.创建基于CPU的CIContext对象 self.context = [CIContext contextWithOptions:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES]forKey:kCIContextUseSoftwareRenderer]];//2.创建基于GPU的CIContext对象 self.context = [CIContext contextWithOptions: nil];//3.创建基于OpenGL优化的CIContext对象,可获得实时性能self.context = [CIContext contextWithEAGLContext:[[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]];// 将UIImage转换成CIImageCIImage *ciImage = [[CIImage alloc] initWithImage:[UIImage imageNamed:@"WechatIMG1.jpeg"]];// 创建滤镜CIFilter *filter = [CIFilter filterWithName:_dataSourse[indexPath.row]keysAndValues:kCIInputImageKey, ciImage, nil];[filter setDefaults];// 获取绘制上下文CIContext *context = [CIContext contextWithOptions:nil];// 渲染并输出CIImageCIImage *outputImage = [filter outputImage];// 创建CGImage句柄CGImageRef cgImage = [self.context createCGImage:outputImagefromRect:[outputImage extent]];imageview.image = [UIImage imageWithCGImage:cgImage];// 释放CGImage句柄CGImageRelease(cgImage);复制代码
拍照和录像
在回调方法里,我们加完滤镜得到每一帧。当点下拍照按钮,把这一张图片保存到相册即完成了拍照功能。
对于录像,我们主要用到AVAssetWriter
以及AVAssetWriterInputPixelBufferAdaptor
。
获取到图像和音频流后,我们将其放到缓冲区内。最终判断时间戳 ,AVAssetWriter将他们合成视频。
后记
遇到了一个问题,也记录一下吧。
前置摄像头镜像问题。网上大多思路都是iOS 前置摄像头镜像问题,但不能处理。原因可能是因为笔者项目对帧进行了处理,数据不是原生的图像。对图片再进行一次镜像处理即可。
if ([[self.cameraDeviceInput device] position] == AVCaptureDevicePositionFront) {// 前置要镜像result = [result imageByApplyingOrientation:UIImageOrientationUpMirrored];}复制代码