前言
项目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转换成CIImage
CIImage *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];
// 渲染并输出CIImage
CIImage *outputImage = [filter outputImage];
// 创建CGImage句柄
CGImageRef cgImage = [self.context createCGImage:outputImage
fromRect:[outputImage extent]];
imageview.image = [UIImage imageWithCGImage:cgImage];
// 释放CGImage句柄
CGImageRelease(cgImage);
复制代码
拍照和录像
在回调方法里,我们加完滤镜得到每一帧。当点下拍照按钮,把这一张图片保存到相册即完成了拍照功能。
对于录像,我们主要用到 AVAssetWriter
以及 AVAssetWriterInputPixelBufferAdaptor
。
获取到图像和音频流后,我们将其放到缓冲区内。最终判断时间戳 ,AVAssetWriter将他们合成视频。
后记
遇到了一个问题,也记录一下吧。
前置摄像头镜像问题。网上大多思路都是iOS 前置摄像头镜像问题,但不能处理。原因可能是因为笔者项目对帧进行了处理,数据不是原生的图像。对图片再进行一次镜像处理即可。
if ([[self.cameraDeviceInput device] position] == AVCaptureDevicePositionFront) {// 前置要镜像
result = [result imageByApplyingOrientation:UIImageOrientationUpMirrored];
}
复制代码
参考
- iOS+Objective-C: Create a real time photo filter app