短視頻實(shí)時(shí)濾鏡(GPUImageVideoCamera)
demo下載地址:https://github.com/SXDgit/ZB_GPUImageVideoCamera
先看效果圖:
直接上代碼,后面解釋?zhuān)?/p>
- (void)createVideoCamera {
// 創(chuàng)建畫(huà)面捕獲器
self.videoCamera = [[GPUImageVideoCamera alloc]initWithSessionPreset:AVCaptureSessionPreset640x480 cameraPosition:AVCaptureDevicePositionBack];
// 輸出方向?yàn)樨Q屏
self.videoCamera.outputImageOrientation = UIInterfaceOrientationPortrait;
self.videoCamera.horizontallyMirrorRearFacingCamera = NO;
self.videoCamera.horizontallyMirrorFrontFacingCamera = NO;
self.videoCamera.runBenchmark = YES;
// 構(gòu)建組合濾鏡
[self addGPUImageFilter:self.sepiaFilter];
[self addGPUImageFilter:self.monochromeFilter];
// 創(chuàng)建畫(huà)面呈現(xiàn)控件
self.filterView = [[GPUImageView alloc]initWithFrame:self.view.frame];
self.filterView.fillMode = kGPUImageFillModePreserveAspectRatio;
self.view = self.filterView;
[self.videoCamera addTarget:self.filterView];
// 相機(jī)運(yùn)行
[self.videoCamera startCameraCapture];
[self configMovie];
}
- (void)configMovie {
// 設(shè)置寫(xiě)入地址
self.pathToMovie = [NSHomeDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"Documents/ZBMovied%u.mp4", arc4random() % 1000]];
// movieUrl指視頻寫(xiě)入的地址
self.moviewURL = [NSURL fileURLWithPath:_pathToMovie];
self.movieWriter = [[GPUImageMovieWriter alloc]initWithMovieURL:_moviewURL size:CGSizeMake(480.0, 640.0)];
_movieWriter.encodingLiveVideo = YES;
// 設(shè)置聲音
_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 {
// 切換攝像頭前后翻轉(zhuǎn)
[self.videoCamera rotateCamera];
self.switchButton.selected = !self.switchButton.selected;
}
GPUImageFilter
是用來(lái)接收源圖像蛮拔,通過(guò)自定義的頂點(diǎn)述暂、片元著色器來(lái)渲染新的圖像,并在繪制完成后通知響應(yīng)鏈的下一個(gè)對(duì)象建炫。
GPUImageVideoCamera
提供來(lái)自攝像頭的圖像數(shù)據(jù)作為源數(shù)據(jù)畦韭,是GPUImageOutput
的子類(lèi),一般是響應(yīng)鏈的源頭肛跌。
GPUImageView
一般用于顯示GPUImage
的圖像艺配,是響應(yīng)鏈的終點(diǎn)察郁。
GPUImageFilterGroup
是多個(gè)filter的集合,terminalFilter
為最終的filter转唉,initialFilter
是filter數(shù)組皮钠。本身不繪制圖像,對(duì)它的添加刪除Target操作赠法,都會(huì)轉(zhuǎn)為terminalFilter
的操作麦轰。
GPUImageMovieWriter
是和GPUImageView
處于同一地位的,都是視頻輸出類(lèi)砖织,只不過(guò)一個(gè)是輸出到文件款侵,一個(gè)輸出到屏幕。GPUImageBeautifyFilter
是基于GPUImage的實(shí)時(shí)美顏濾鏡中的美顏濾鏡侧纯,來(lái)自琨君新锈。它是繼承于GPUImageFilterGroup
。包括了GPUImageBilateralFilter
眶熬、GPUImageCannyEdgeDetectionFilter
妹笆、GPUImageCombinationFilter
、GPUImageHSBFilter
聋涨。
繪制流程
來(lái)自GPUImage詳細(xì)解析(三)- 實(shí)時(shí)美顏濾鏡
- 1晾浴、
GPUImageVideoCamera
捕獲攝像頭圖像調(diào)用newFrameReadyAtTime: atIndex:
通知GPUImageBeautifyFilter
; - 2牍白、
GPUImageBeautifyFilter
調(diào)用newFrameReadyAtTime: atIndex:
通知GPUImageBilateralFliter
輸入紋理已經(jīng)準(zhǔn)備好脊凰; - 3、
GPUImageBilateralFliter
繪制圖像后在informTargetsAboutNewFrameAtTime()
茂腥,調(diào)用setInputFramebufferForTarget: atIndex
:把繪制的圖像設(shè)置為GPUImageCombinationFilter
輸入紋理狸涌,并通知GPUImageCombinationFilter
紋理已經(jīng)繪制完畢; - 4最岗、
GPUImageBeautifyFilter
調(diào)用newFrameReadyAtTime: atIndex:
通知GPUImageCannyEdgeDetectionFilter
輸入紋理已經(jīng)準(zhǔn)備好帕胆; - 5、同3般渡,
GPUImageCannyEdgeDetectionFilter
繪制圖像后懒豹,把圖像設(shè)置為GPUImageCombinationFilter
輸入紋理; - 6驯用、
GPUImageBeautifyFilter
調(diào)用newFrameReadyAtTime: atIndex:
通知GPUImageCombinationFilter
輸入紋理已經(jīng)準(zhǔn)備好脸秽; - 7、
GPUImageCombinationFilter
判斷是否有三個(gè)紋理蝴乔,三個(gè)紋理都已經(jīng)準(zhǔn)備好后調(diào)用GPUImageThreeInputFilter
的繪制函數(shù)renderToTextureWithVertices: textureCoordinates:
记餐,圖像繪制完后,把圖像設(shè)置為GPUImageHSBFilter
的輸入紋理薇正,通知GPUImageHSBFilter
紋理已經(jīng)繪制完畢片酝; - 8囚衔、
GPUImageHSBFilter
調(diào)用renderToTextureWithVertices: textureCoordinates:
繪制圖像,完成后把圖像設(shè)置為GPUImageView
的輸入紋理雕沿,并通知GPUImageView
輸入紋理已經(jīng)繪制完畢练湿; - 9、
GPUImageView
把輸入紋理繪制到自己的幀緩存晦炊,然后通過(guò)[self.context presentRenderbuffer:GL_RENDERBUFFER]
顯示到UIView
上鞠鲜。
核心思路
通過(guò)GPUImageVideoCamera
采集音視頻的信息,音頻信息直接發(fā)送給GPUImageMovieWriter
断国,視頻信息傳入響應(yīng)鏈作為源頭贤姆,渲染后的視頻信息再寫(xiě)入GPUImageMovieWriter
,同時(shí)GPUImageView
顯示再屏幕上稳衬。
通過(guò)源碼可以知道GPUImage
是使用AVFoundation
框架來(lái)獲取視頻的霞捡。
AVCaptureSession
類(lèi)從AV輸入設(shè)備的采集數(shù)據(jù)到制定的輸出。
為了實(shí)現(xiàn)實(shí)時(shí)的圖像捕獲薄疚,要實(shí)現(xiàn)AVCaptureSession
類(lèi)碧信,添加合適的輸入(AVCaptureDeviceInput
)和輸出(比如AVCaptureMovieFileOutput
)調(diào)用startRunning
開(kāi)始輸入到輸出的數(shù)據(jù)流,調(diào)用stopRunning
停止數(shù)據(jù)流街夭。需要注意的是startingRunning
函數(shù)會(huì)花費(fèi)一定的時(shí)間砰碴,所以不能在主線程調(diào)用,防止卡頓板丽。
流程解析:
1呈枉、找到物理設(shè)備攝像頭
_inputCamera
、麥克風(fēng)_microphone
埃碱,創(chuàng)建攝像頭輸入videoInput
和麥克風(fēng)輸入audioInput
猖辫。2、設(shè)置
videoInput
和audioInput
為_captureSession
的輸入砚殿,同時(shí)設(shè)置videoOutput
和audioOutput
為_captureSession
的輸出啃憎,并且設(shè)置videoOutput
和audioOutput
的輸出delegate
。3似炎、
_captureSession
調(diào)用startRunning
辛萍,開(kāi)始捕獲信號(hào)。4羡藐、音頻數(shù)據(jù)到達(dá)叹阔,把數(shù)據(jù)轉(zhuǎn)發(fā)給之前設(shè)置的
audioEncodingTarget
,并通過(guò)調(diào)用assetWriterAudioInput
的appendSampleBuffer
方法寫(xiě)入音頻數(shù)據(jù)传睹。5、視頻數(shù)據(jù)到達(dá)岸晦,視頻數(shù)據(jù)傳入響應(yīng)鏈欧啤,經(jīng)過(guò)處理后通過(guò)
assetWriterPixelBufferInput
的appendSampleBuffer
方法寫(xiě)入視頻數(shù)據(jù)睛藻。6、視頻錄制完成邢隧,保存寫(xiě)入手機(jī)相冊(cè)店印。
踩過(guò)的坑
1、錄制后保存在相冊(cè)里的視頻是白屏倒慧?
在初始化movieWriter
的過(guò)程中按摘,使用addTarget:
增加了濾鏡導(dǎo)致。
2纫谅、錄制完視頻后炫贤,再次點(diǎn)擊錄制,會(huì)crash付秕?
報(bào)錯(cuò)的原因是[AVAssetWriter startWriting] Cannot call method when status is 3
兰珍,報(bào)錯(cuò)是在[self.movieWriter startRecording];
這行代碼,追溯源碼询吴,可以看到GPUImageMovieWriter
是對(duì)AssetWriter
進(jìn)行了一次封裝掠河,其核心的寫(xiě)文件還是由AssetWriter
完成。
通過(guò)源碼可以發(fā)現(xiàn)[self.movieWriter finishRecording];
以后并沒(méi)有新建一個(gè)AssetWriter
實(shí)例猛计。所以可以保存視頻到相冊(cè)成功后唠摹,加入下面幾行代碼:
- (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、攝像頭實(shí)例取消對(duì)GPUImageMovieWriter
的綁定奉瘤,因?yàn)橹匦聦?shí)例化新的GPUImageMovieWriter
以后原來(lái)的實(shí)例就沒(méi)用了勾拉。
2、刪除原來(lái)已經(jīng)寫(xiě)好的影片文件毛好,如果新的實(shí)例直接寫(xiě)入已存在的文件會(huì)報(bào)錯(cuò)AVAssetWriterStatusFailed
望艺。
3、重新實(shí)例化一個(gè)GPUImageMovieWriter
肌访。
4找默、把新的GPUImageMovieWriter
綁定到攝像頭實(shí)例。
這樣以后就可以不同的錄制保存了吼驶。參考[紹棠] GPUImageMovieWriter 無(wú)法2次錄像 報(bào)錯(cuò):[AVAssetWriter startWriting] Cannot call method when status is 3
參考資料:落影大佬的GPUImage文集