美文网首页iOS开发攻城狮的集散地iOS直播视频
笔记-GPUImage(三)短视频录制实时滤镜以及滤镜的切换

笔记-GPUImage(三)短视频录制实时滤镜以及滤镜的切换

作者: 佐_笾 | 来源:发表于2019-03-29 22:32 被阅读0次

    短视频实时滤镜(GPUImageVideoCamera)

    demo下载地址:https://github.com/SXDgit/ZB_GPUImageVideoCamera

    先看效果图:

    image

    直接上代码,后面解释:

    - (void)createVideoCamera {
        // 创建画面捕获器
        self.videoCamera = [[GPUImageVideoCamera alloc]initWithSessionPreset:AVCaptureSessionPreset640x480 cameraPosition:AVCaptureDevicePositionBack];
        // 输出方向为竖屏
        self.videoCamera.outputImageOrientation = UIInterfaceOrientationPortrait;
        self.videoCamera.horizontallyMirrorRearFacingCamera = NO;
        self.videoCamera.horizontallyMirrorFrontFacingCamera = NO;
        self.videoCamera.runBenchmark = YES;
        
        // 构建组合滤镜
        [self addGPUImageFilter:self.sepiaFilter];
        [self addGPUImageFilter:self.monochromeFilter];
        
        // 创建画面呈现控件
        self.filterView = [[GPUImageView alloc]initWithFrame:self.view.frame];
        self.filterView.fillMode = kGPUImageFillModePreserveAspectRatio;
        self.view = self.filterView;
        
        [self.videoCamera addTarget:self.filterView];
        // 相机运行
        [self.videoCamera startCameraCapture];
        [self configMovie];
    }
    
    - (void)configMovie {
        // 设置写入地址
        self.pathToMovie = [NSHomeDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"Documents/ZBMovied%u.mp4", arc4random() % 1000]];
        // movieUrl指视频写入的地址
        self.moviewURL = [NSURL fileURLWithPath:_pathToMovie];
        self.movieWriter = [[GPUImageMovieWriter alloc]initWithMovieURL:_moviewURL size:CGSizeMake(480.0, 640.0)];
        _movieWriter.encodingLiveVideo = YES;
        // 设置声音
        _videoCamera.audioEncodingTarget = _movieWriter;
    }
    
    - (void)addGPUImageFilter:(GPUImageFilter *)filter {
        [self.filterGroup addFilter:filter];
        
        GPUImageOutput<GPUImageInput> *newTerminalFilter = filter;
        NSInteger count = self.filterGroup.filterCount;
        if (count == 1) {
            self.filterGroup.initialFilters = @[newTerminalFilter];
            self.filterGroup.terminalFilter = newTerminalFilter;
        }else {
            GPUImageOutput<GPUImageInput> *terminalFilter = self.filterGroup.terminalFilter;
            NSArray *initialFilters = self.filterGroup.initialFilters;
            [terminalFilter addTarget:newTerminalFilter];
            self.filterGroup.initialFilters = @[initialFilters[0]];
            self.filterGroup.terminalFilter = newTerminalFilter;
        }
    }
    
    - (void)switchButtonAction {
        // 切换摄像头前后翻转
        [self.videoCamera rotateCamera];
        self.switchButton.selected = !self.switchButton.selected;
    }
    

    GPUImageFilter是用来接收源图像,通过自定义的顶点、片元着色器来渲染新的图像,并在绘制完成后通知响应链的下一个对象。
    GPUImageVideoCamera提供来自摄像头的图像数据作为源数据,是GPUImageOutput的子类,一般是响应链的源头。
    GPUImageView一般用于显示GPUImage的图像,是响应链的终点。
    GPUImageFilterGroup是多个filter的集合,terminalFilter为最终的filter,initialFilter是filter数组。本身不绘制图像,对它的添加删除Target操作,都会转为terminalFilter的操作。

    image
    GPUImageMovieWriter是和GPUImageView处于同一地位的,都是视频输出类,只不过一个是输出到文件,一个输出到屏幕。
    GPUImageBeautifyFilter基于GPUImage的实时美颜滤镜中的美颜滤镜,来自琨君。它是继承于GPUImageFilterGroup。包括了GPUImageBilateralFilterGPUImageCannyEdgeDetectionFilterGPUImageCombinationFilterGPUImageHSBFilter

    绘制流程

    来自GPUImage详细解析(三)- 实时美颜滤镜

    image
    • 1、GPUImageVideoCamera捕获摄像头图像调用newFrameReadyAtTime: atIndex:通知GPUImageBeautifyFilter
    • 2、GPUImageBeautifyFilter调用newFrameReadyAtTime: atIndex:通知GPUImageBilateralFliter输入纹理已经准备好;
    • 3、GPUImageBilateralFliter 绘制图像后在informTargetsAboutNewFrameAtTime(),调用setInputFramebufferForTarget: atIndex:把绘制的图像设置为GPUImageCombinationFilter输入纹理,并通知GPUImageCombinationFilter纹理已经绘制完毕;
    • 4、GPUImageBeautifyFilter调用newFrameReadyAtTime: atIndex:通知 GPUImageCannyEdgeDetectionFilter输入纹理已经准备好;
    • 5、同3,GPUImageCannyEdgeDetectionFilter 绘制图像后,把图像设置为GPUImageCombinationFilter输入纹理;
    • 6、GPUImageBeautifyFilter调用newFrameReadyAtTime: atIndex:通知 GPUImageCombinationFilter输入纹理已经准备好;
    • 7、GPUImageCombinationFilter判断是否有三个纹理,三个纹理都已经准备好后调用GPUImageThreeInputFilter的绘制函数renderToTextureWithVertices: textureCoordinates:,图像绘制完后,把图像设置为GPUImageHSBFilter的输入纹理,通知GPUImageHSBFilter纹理已经绘制完毕;
    • 8、GPUImageHSBFilter调用renderToTextureWithVertices: textureCoordinates:绘制图像,完成后把图像设置为GPUImageView的输入纹理,并通知GPUImageView输入纹理已经绘制完毕;
    • 9、GPUImageView把输入纹理绘制到自己的帧缓存,然后通过[self.context presentRenderbuffer:GL_RENDERBUFFER]显示到UIView上。

    核心思路

    通过GPUImageVideoCamera采集音视频的信息,音频信息直接发送给GPUImageMovieWriter,视频信息传入响应链作为源头,渲染后的视频信息再写入GPUImageMovieWriter,同时GPUImageView显示再屏幕上。

    image

    通过源码可以知道GPUImage是使用AVFoundation框架来获取视频的。
    AVCaptureSession类从AV输入设备的采集数据到制定的输出。
    为了实现实时的图像捕获,要实现AVCaptureSession类,添加合适的输入(AVCaptureDeviceInput)和输出(比如AVCaptureMovieFileOutput)调用startRunning开始输入到输出的数据流,调用stopRunning停止数据流。需要注意的是startingRunning函数会花费一定的时间,所以不能在主线程调用,防止卡顿。

    image
    流程解析:
    1、找到物理设备摄像头_inputCamera、麦克风_microphone,创建摄像头输入videoInput和麦克风输入audioInput
    2、设置videoInputaudioInput_captureSession的输入,同时设置videoOutputaudioOutput_captureSession的输出,并且设置videoOutputaudioOutput的输出delegate
    3、_captureSession调用startRunning,开始捕获信号。
    4、音频数据到达,把数据转发给之前设置的audioEncodingTarget,并通过调用assetWriterAudioInputappendSampleBuffer方法写入音频数据。
    5、视频数据到达,视频数据传入响应链,经过处理后通过assetWriterPixelBufferInputappendSampleBuffer方法写入视频数据。
    6、视频录制完成,保存写入手机相册。

    踩过的坑

    1、录制后保存在相册里的视频是白屏?
    在初始化movieWriter的过程中,使用addTarget:增加了滤镜导致。

    2、录制完视频后,再次点击录制,会crash?
    报错的原因是[AVAssetWriter startWriting] Cannot call method when status is 3,报错是在[self.movieWriter startRecording];这行代码,追溯源码,可以看到GPUImageMovieWriter是对AssetWriter进行了一次封装,其核心的写文件还是由AssetWriter完成。
    通过源码可以发现[self.movieWriter finishRecording];以后并没有新建一个AssetWriter实例。所以可以保存视频到相册成功后,加入下面几行代码:

    - (void)videoCameraReset {
        [_videoCamera removeTarget:_movieWriter];
        [[NSFileManager defaultManager] removeItemAtURL:_moviewURL error:nil];
        [self initMovieWriter];
        [_videoCamera addTarget:_movieWriter];
    }
    
    - (void)initMovieWriter {
        _movieWriter = [[GPUImageMovieWriter alloc]initWithMovieURL:_moviewURL size:CGSizeMake(480.0, 640.0)];
        _movieWriter.encodingLiveVideo = YES;
    }
    

    1、摄像头实例取消对GPUImageMovieWriter的绑定,因为重新实例化新的GPUImageMovieWriter以后原来的实例就没用了。
    2、删除原来已经写好的影片文件,如果新的实例直接写入已存在的文件会报错AVAssetWriterStatusFailed
    3、重新实例化一个GPUImageMovieWriter
    4、把新的GPUImageMovieWriter绑定到摄像头实例。

    这样以后就可以不同的录制保存了。参考[绍棠] GPUImageMovieWriter 无法2次录像 报错:[AVAssetWriter startWriting] Cannot call method when status is 3

    参考资料:落影大佬的GPUImage文集

    相关文章

      网友评论

        本文标题:笔记-GPUImage(三)短视频录制实时滤镜以及滤镜的切换

        本文链接:https://www.haomeiwen.com/subject/egkdbqtx.html