项目Demo,实现了实时滤镜、拍照、录像功能。html
最近玩了哈实时滤镜,学到挺多东西的。笔者长得丑,看看有没有机会没那么丑。只挑了几种滤镜,笔者是个钢铁直男,没有美颜效果。git
设备获取图像输入流后,通过对该帧处理造成新图像,最后刷新UI。github
苹果有简单的 UIImagePickerController
,但扩展性差。因此笔者采用的是 AVFoundation
框架。其涉及到输入流和输出流,方便咱们对每一帧进行处理,显示出来。bash
若是你对输入和输出相关的类不了解,应该也不影响你理解本文。但笔者仍是建议你先看哈苏沫离的博客。好比,OC之输入管理AVCaptureInput,OC之输出管理 AVCaptureOutput。session
有一个类 AVCaptureMovieFile
,直接把音频和图像结合起来。但因为要处理画面,因此又得拆分开。因此笔者采用的是 AVCaptureAudioDataOutput
和 AVCaptureVideoDataOutput
,处理完图像再拼。app
输入流要经过相关设备初始化。框架
// 图像
_device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
_cameraDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:self.device error:nil];
// 音频
AVCaptureDevice *micDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
_microphoneDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:micDevice error:nil];
复制代码
_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;
。ide
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];
}
}
复制代码
开启输入流,获取数据到输出流。优化
//开始启动
[_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];
}
复制代码
// 在这里处理获取的图像,而且保存每一帧到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];
}
}
}
复制代码
在这一步,处理每一帧图像,加上滤镜。
这里用到了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];
}
复制代码