筆記-GPUImage(三)短視頻錄製實時濾鏡以及濾鏡的切換

短視頻實時濾鏡(GPUImageVideoCamera)

demo下載地址:https://github.com/SXDgit/ZB_GPUImageVideoCameragit

先看效果圖:github

直接上代碼,後面解釋:chrome

- (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的操做。 數組

GPUImageMovieWriter是和 GPUImageView處於同一地位的,都是視頻輸出類,只不過一個是輸出到文件,一個輸出到屏幕。 GPUImageBeautifyFilter基於GPUImage的實時美顏濾鏡中的美顏濾鏡,來自琨君。它是繼承於 GPUImageFilterGroup。包括了 GPUImageBilateralFilterGPUImageCannyEdgeDetectionFilterGPUImageCombinationFilterGPUImageHSBFilter

繪製流程

來自GPUImage詳細解析(三)- 實時美顏濾鏡 緩存

  • 一、GPUImageVideoCamera捕獲攝像頭圖像調用newFrameReadyAtTime: atIndex:通知GPUImageBeautifyFilter
  • 二、GPUImageBeautifyFilter調用newFrameReadyAtTime: atIndex:通知GPUImageBilateralFliter輸入紋理已經準備好;
  • 三、GPUImageBilateralFliter 繪製圖像後在informTargetsAboutNewFrameAtTime(),調用setInputFramebufferForTarget: atIndex:把繪製的圖像設置爲GPUImageCombinationFilter輸入紋理,並通知GPUImageCombinationFilter紋理已經繪製完畢;
  • 四、GPUImageBeautifyFilter調用newFrameReadyAtTime: atIndex:通知 GPUImageCannyEdgeDetectionFilter輸入紋理已經準備好;
  • 五、同3,GPUImageCannyEdgeDetectionFilter 繪製圖像後,把圖像設置爲GPUImageCombinationFilter輸入紋理;
  • 六、GPUImageBeautifyFilter調用newFrameReadyAtTime: atIndex:通知 GPUImageCombinationFilter輸入紋理已經準備好;
  • 七、GPUImageCombinationFilter判斷是否有三個紋理,三個紋理都已經準備好後調用GPUImageThreeInputFilter的繪製函數renderToTextureWithVertices: textureCoordinates:,圖像繪製完後,把圖像設置爲GPUImageHSBFilter的輸入紋理,通知GPUImageHSBFilter紋理已經繪製完畢;
  • 八、GPUImageHSBFilter調用renderToTextureWithVertices: textureCoordinates:繪製圖像,完成後把圖像設置爲GPUImageView的輸入紋理,並通知GPUImageView輸入紋理已經繪製完畢;
  • 九、GPUImageView把輸入紋理繪製到本身的幀緩存,而後經過[self.context presentRenderbuffer:GL_RENDERBUFFER]顯示到UIView上。

核心思路

經過GPUImageVideoCamera採集音視頻的信息,音頻信息直接發送給GPUImageMovieWriter,視頻信息傳入響應鏈做爲源頭,渲染後的視頻信息再寫入GPUImageMovieWriter,同時GPUImageView顯示再屏幕上。 bash

經過源碼能夠知道GPUImage是使用AVFoundation框架來獲取視頻的。
AVCaptureSession類從AV輸入設備的採集數據到制定的輸出。
爲了實現實時的圖像捕獲,要實現AVCaptureSession類,添加合適的輸入(AVCaptureDeviceInput)和輸出(好比AVCaptureMovieFileOutput)調用startRunning開始輸入到輸出的數據流,調用stopRunning中止數據流。須要注意的是startingRunning函數會花費必定的時間,因此不能在主線程調用,防止卡頓。 app

流程解析:
一、找到物理設備攝像頭 _inputCamera、麥克風 _microphone,建立攝像頭輸入 videoInput和麥克風輸入 audioInput
二、設置 videoInputaudioInput_captureSession的輸入,同時設置 videoOutputaudioOutput_captureSession的輸出,而且設置 videoOutputaudioOutput的輸出 delegate
三、 _captureSession調用 startRunning,開始捕獲信號。
四、音頻數據到達,把數據轉發給以前設置的 audioEncodingTarget,並經過調用 assetWriterAudioInputappendSampleBuffer方法寫入音頻數據。
五、視頻數據到達,視頻數據傳入響應鏈,通過處理後經過 assetWriterPixelBufferInputappendSampleBuffer方法寫入視頻數據。
六、視頻錄製完成,保存寫入手機相冊。

踩過的坑

一、錄製後保存在相冊裏的視頻是白屏?
在初始化movieWriter的過程當中,使用addTarget:增長了濾鏡致使。框架

二、錄製完視頻後,再次點擊錄製,會crash?
報錯的緣由是[AVAssetWriter startWriting] Cannot call method when status is 3,報錯是在[self.movieWriter startRecording];這行代碼,追溯源碼,能夠看到GPUImageMovieWriter是對AssetWriter進行了一次封裝,其核心的寫文件仍是由AssetWriter完成。 經過源碼能夠發現[self.movieWriter finishRecording];之後並無新建一個AssetWriter實例。因此能夠保存視頻到相冊成功後,加入下面幾行代碼:dom

- (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;
}
複製代碼

一、攝像頭實例取消對GPUImageMovieWriter的綁定,由於從新實例化新的GPUImageMovieWriter之後原來的實例就沒用了。
二、刪除原來已經寫好的影片文件,若是新的實例直接寫入已存在的文件會報錯AVAssetWriterStatusFailed
三、從新實例化一個GPUImageMovieWriter
四、把新的GPUImageMovieWriter綁定到攝像頭實例。ide

這樣之後就能夠不一樣的錄製保存了。參考[紹棠] GPUImageMovieWriter 沒法2次錄像 報錯:[AVAssetWriter startWriting] Cannot call method when status is 3

參考資料:落影大佬的GPUImage文集

相關文章
相關標籤/搜索