GPUImage学习二(拍照和视频添加滤镜效果)
本来打算第二篇是作为对 GPUImage 各基类的一个解读的,结果在写的过程中,在网上搜到了一篇写的比较详细且全面的文章,再回头看看自己写,索性都给删了,直接引用前辈的文章吧,这样也能让我省了很多精力
秦明Qinmin GPUImage源码阅读
本篇内容:
1,用 GPUImageStillCamera 实现一个拍照并添加滤镜的效果;
2,用 GPUImageVideoCamera 和 GPUImageMovieWriter 实现录制带滤镜效果的视频,并保存到本地相册。
拍照
-
效果图:
效果1.JPG
- GPUImageStillCamera
继承自 GPUImageVideoCamera 主要用于拍照
- 实现代码:
@interface StillCameraViewController (){
GPUImageView *_imageView;
GPUImageStillCamera *_stillCamera;
GPUImageSaturationFilter *_filter; // 饱和度滤镜
}
- (void)viewDidLoad {
[super viewDidLoad];
CGFloat width = self.view.bounds.size.width;
CGFloat height = self.view.bounds.size.height;
// GPUImageView 用来显示相机捕获的画面
_imageView = [[GPUImageView alloc] initWithFrame:CGRectMake(0, 0, width, width)];
_imageView.center = self.view.center;
[self.view addSubview:_imageView];
// 饱和度滤镜效果
_filter = [[GPUImageSaturationFilter alloc] init];
_stillCamera = [[GPUImageStillCamera alloc] initWithSessionPreset:AVCaptureSessionPresetPhoto cameraPosition:AVCaptureDevicePositionBack];
_stillCamera.outputImageOrientation = UIInterfaceOrientationPortrait;
// 添加 target(链式串联)
[_stillCamera addTarget:_filter];
[_filter addTarget:_imageView];
// 捕获镜头画面
[_stillCamera startCameraCapture];
// 调解滤镜值
UISlider *slider = [[UISlider alloc] initWithFrame:CGRectMake(50, _imageView.frame.origin.y + _imageView.frame.size.height + 30, width - 100, 30)];
slider.maximumValue = 2;
slider.minimumValue = 0;
slider.value = 1;
[slider addTarget:self action:@selector(sliderValueChanged:) forControlEvents:UIControlEventValueChanged];
[self.view addSubview:slider];
// 拍照按钮
UIButton *takeBtn = [UIButton buttonWithType:UIButtonTypeCustom];
takeBtn.frame = CGRectMake(self.view.bounds.size.width / 2.0 - 80, height - 80, 60, 60);
[takeBtn setTitleColor:[UIColor orangeColor] forState:UIControlStateNormal];
[takeBtn setTitle:@"拍照" forState:UIControlStateNormal];
[takeBtn addTarget:self action:@selector(takeBtnAction:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:takeBtn];
// 切换镜头按钮
UIButton *lensBtn = [UIButton buttonWithType:UIButtonTypeCustom];
lensBtn.frame = CGRectMake(self.view.bounds.size.width / 2.0 + 20, height - 80, 60, 60);
[lensBtn setTitleColor:[UIColor orangeColor] forState:UIControlStateNormal];
[lensBtn setTitle:@"后置" forState:UIControlStateNormal];
[lensBtn setTitle:@"前置" forState:UIControlStateSelected];
lensBtn.selected = YES;
[lensBtn addTarget:self action:@selector(lensBtnAction:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:lensBtn];
}
- (void)sliderValueChanged:(UISlider *)sender{
_filter.saturation = sender.value;
}
- (void)takeBtnAction:(UIButton *)sender{
// _stillCamera 要添加至少一个 filter target,否则捕获的图片是 null
[_stillCamera capturePhotoAsImageProcessedUpToFilter:_filter withCompletionHandler:^(UIImage *processedImage, NSError *error) {
NSLog(@"image:%@",processedImage);
NSLog(@"error:%@",error);
UIImageWriteToSavedPhotosAlbum(processedImage, nil, nil, nil);
}];
}
- (void)lensBtnAction:(UIButton *)sender{
[_stillCamera rotateCamera];
sender.selected = !sender.selected;
}
注意:
GPUImageStillCamera 对象必须要添加至少一个 filter target,否则拍照返回的 image 为 null
- GPUImageStillCamera 提供了很多拍照的方法,可以根据需要获取不同格式的图片或 NSData 对象
- (void)capturePhotoAsSampleBufferWithCompletionHandler:(void (^)(CMSampleBufferRef imageSampleBuffer, NSError *error))block;
- (void)capturePhotoAsImageProcessedUpToFilter:(GPUImageOutput<GPUImageInput> *)finalFilterInChain withCompletionHandler:(void (^)(UIImage *processedImage, NSError *error))block;
- (void)capturePhotoAsImageProcessedUpToFilter:(GPUImageOutput<GPUImageInput> *)finalFilterInChain withOrientation:(UIImageOrientation)orientation withCompletionHandler:(void (^)(UIImage *processedImage, NSError *error))block;
- (void)capturePhotoAsJPEGProcessedUpToFilter:(GPUImageOutput<GPUImageInput> *)finalFilterInChain withCompletionHandler:(void (^)(NSData *processedJPEG, NSError *error))block;
- (void)capturePhotoAsJPEGProcessedUpToFilter:(GPUImageOutput<GPUImageInput> *)finalFilterInChain withOrientation:(UIImageOrientation)orientation withCompletionHandler:(void (^)(NSData *processedJPEG, NSError *error))block;
- (void)capturePhotoAsPNGProcessedUpToFilter:(GPUImageOutput<GPUImageInput> *)finalFilterInChain withCompletionHandler:(void (^)(NSData *processedPNG, NSError *error))block;
- (void)capturePhotoAsPNGProcessedUpToFilter:(GPUImageOutput<GPUImageInput> *)finalFilterInChain withOrientation:(UIImageOrientation)orientation withCompletionHandler:(void (^)(NSData *processedPNG, NSError *error))block;
录制视频
-
效果图
视频效果.gif
-
GPUImageVideoCamera
GPUImageVideoCamera 继承自 GPUImageOutput,可以调用相机捕获相机画面,我们可以使用 GPUImageView 显示,也可以使用 GPUImageMovieWriter 保存为视频文件
- GPUImageMovieWriter
编码音视频并保存为音视频文件,它实现了 GPUImageInput 协议。因此,可以接受帧缓存的输入。GPUImageMovieWriter 在进行音视频录制的时候,主要用到这几个系统类 AVAssetWriter 、AVAssetWriterInput 、AVAssetWriterInputPixelBufferAdaptor。
- 实现代码
@interface VideoCameraViewController (){
GPUImageVideoCamera *_videoCamera; // 捕获画面
GPUImageView *_imageView; // 展示画面
GPUImageMovieWriter *_movieWriter; // 视频编解码
NSString *_temPath; // 录制视频的临时缓存地址
GPUImageToonFilter *_filter; // 滤镜效果
}
- (void)viewDidLoad {
[super viewDidLoad];
CGFloat width = self.view.bounds.size.width;
CGFloat height = self.view.bounds.size.height;
_imageView = [[GPUImageView alloc] initWithFrame:CGRectMake(0, 0, width, width)];
_imageView.center = self.view.center;
[self.view addSubview:_imageView];
// 卡通滤镜效果(黑色描边)
_filter = [[GPUImageToonFilter alloc] init];
_videoCamera = [[GPUImageVideoCamera alloc] initWithSessionPreset:AVCaptureSessionPreset640x480 cameraPosition:AVCaptureDevicePositionFront];
_videoCamera.outputImageOrientation = UIInterfaceOrientationPortrait;
[_videoCamera addTarget:_filter];
[_filter addTarget:_imageView];
// GPUImageVideoCamera 开始捕获画面展示在 GPUImageView
[_videoCamera startCameraCapture];
// 调解滤镜值
UISlider *slider = [[UISlider alloc] initWithFrame:CGRectMake(50, _imageView.frame.origin.y + _imageView.frame.size.height + 30, width - 100, 30)];
slider.value = 0.2;
[slider addTarget:self action:@selector(sliderValueChanged:) forControlEvents:UIControlEventValueChanged];
[self.view addSubview:slider];
UIButton *recordBtn = [UIButton buttonWithType:UIButtonTypeCustom];
recordBtn.frame = CGRectMake(self.view.bounds.size.width / 2.0 - 80, height - 80, 60, 60);
[recordBtn setTitleColor:[UIColor orangeColor] forState:UIControlStateNormal];
[recordBtn setTitle:@"开始" forState:UIControlStateNormal];
[recordBtn setTitle:@"暂停" forState:UIControlStateSelected];
[recordBtn addTarget:self action:@selector(recordBtnAction:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:recordBtn];
UIButton *lensBtn = [UIButton buttonWithType:UIButtonTypeCustom];
lensBtn.frame = CGRectMake(self.view.bounds.size.width / 2.0 + 20, height - 80, 60, 60);
[lensBtn setTitleColor:[UIColor orangeColor] forState:UIControlStateNormal];
[lensBtn setTitle:@"后置" forState:UIControlStateNormal];
[lensBtn setTitle:@"前置" forState:UIControlStateSelected];
[lensBtn addTarget:self action:@selector(lensBtnAction:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:lensBtn];
}
- (void)sliderValueChanged:(UISlider *)sender{
_filter.threshold = sender.value;
}
- (void)recordBtnAction:(UIButton *)sender{
if (sender.selected) { // 录像状态
[_movieWriter finishRecording];
UISaveVideoAtPathToSavedPhotosAlbum(_temPath, nil, nil, nil);
[_videoCamera removeTarget:_movieWriter];
}
else{ // 没有录像
_temPath = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/Movie.mov"];
unlink([_temPath UTF8String]); // 判断路径是否存在,如果存在就删除路径下的文件,否则是没法缓存新的数据的。
NSURL *movieURL = [NSURL fileURLWithPath:_temPath];
_movieWriter = [[GPUImageMovieWriter alloc] initWithMovieURL:movieURL size:CGSizeMake(640.0, 480.0)];
[_movieWriter setHasAudioTrack:YES audioSettings:nil];
_videoCamera.audioEncodingTarget = _movieWriter;
[_filter addTarget:_movieWriter];
[_movieWriter startRecording];
}
sender.selected = !sender.selected;
}
- (void)lensBtnAction:(UIButton *)sender{
[_videoCamera rotateCamera];
sender.selected = !sender.selected;
}
GPUImageVideoCamera 如果不需要滤镜效果也可以不添加 filter target,但是 GPUImageStillCamera 如果想实现拍照功能就必须添加至少一个 filter target(上面拍照部分有说明)
注意
在代码的 recordBtnAction 方法中,
完成录像时: [_videoCamera removeTarget:_movieWriter],移除 _movieWriter 对象;
开始录像时:重新初始化 _movieWriter 对象,并给 _videoCamera赋值。
我最开始是直接在 viewDidLoad 最后设置的 _movieWriter,第一次录制是没有问题的,但再次录制就会崩溃,报错([AVAssetWriter startWriting] Cannot call method when status is 3)
![](https://img.haomeiwen.com/i2195215/6d4b09be8139c8d9.png)
这个错误状态可能是0、1、2、3、4。该状态的枚举如下:(我在测试的过程中遇到了2、3的状态)
enum {
AVAssetWriterStatusUnknown = 0,
AVAssetWriterStatusWriting,
AVAssetWriterStatusCompleted,
AVAssetWriterStatusFailed,
AVAssetWriterStatusCancelled
};
个人理解
0、1、3、4就不解释了,至于状态 2(Completed)是因为 _movieWriter 中 AVAssetWriter 完成了一次写入过程这个对象就不能再往同一个路径下写入内容了。而 GPUImageMovieWriter 的 assetWriter 属性又只有读的属性没有写的属性,所以没办法修改assetWriter,只能通过重新初始化 GPUImageMovieWriter 来实现。(个人理解可能有误,如有更准确的理解,请劳烦在底部留言纠正)
通过上面方式给视频添加滤镜效果还是很方便的,在录制视频的过程中也能不断的调节滤镜的效果值,来录制渐变效果的视频。因为图像是一帧一帧处理的,每一帧都会渲染当前时刻的滤镜效果。有兴趣的可以做一个看看效果。