iOS技术收藏

GPUImage学习二(拍照和视频添加滤镜效果)

2018-01-03  本文已影响126人  古子林

本来打算第二篇是作为对 GPUImage 各基类的一个解读的,结果在写的过程中,在网上搜到了一篇写的比较详细且全面的文章,再回头看看自己写,索性都给删了,直接引用前辈的文章吧,这样也能让我省了很多精力
秦明Qinmin GPUImage源码阅读

本篇内容:
1,用 GPUImageStillCamera 实现一个拍照并添加滤镜的效果;
2,用 GPUImageVideoCamera 和 GPUImageMovieWriter 实现录制带滤镜效果的视频,并保存到本地相册。

拍照

效果2.JPG

继承自 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

- (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;

录制视频

GPUImageVideoCamera 继承自 GPUImageOutput,可以调用相机捕获相机画面,我们可以使用 GPUImageView 显示,也可以使用 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)


错误日志.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 来实现。(个人理解可能有误,如有更准确的理解,请劳烦在底部留言纠正)


通过上面方式给视频添加滤镜效果还是很方便的,在录制视频的过程中也能不断的调节滤镜的效果值,来录制渐变效果的视频。因为图像是一帧一帧处理的,每一帧都会渲染当前时刻的滤镜效果。有兴趣的可以做一个看看效果。

上一篇 下一篇

猜你喜欢

热点阅读