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

短視頻實(shí)時(shí)濾鏡(GPUImageVideoCamera)

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

先看效果圖:

image

直接上代碼,后面解釋?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的操作麦轰。

image

GPUImageMovieWriter是和GPUImageView處于同一地位的,都是視頻輸出類(lèi)砖织,只不過(guò)一個(gè)是輸出到文件款侵,一個(gè)輸出到屏幕。
GPUImageBeautifyFilter基于GPUImage的實(shí)時(shí)美顏濾鏡中的美顏濾鏡侧纯,來(lái)自琨君新锈。它是繼承于GPUImageFilterGroup。包括了GPUImageBilateralFilter眶熬、GPUImageCannyEdgeDetectionFilter妹笆、GPUImageCombinationFilterGPUImageHSBFilter聋涨。

繪制流程

來(lái)自GPUImage詳細(xì)解析(三)- 實(shí)時(shí)美顏濾鏡

image

  • 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顯示再屏幕上稳衬。

image

通過(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)用,防止卡頓板丽。

image

流程解析:
1呈枉、找到物理設(shè)備攝像頭_inputCamera、麥克風(fēng)_microphone埃碱,創(chuàng)建攝像頭輸入videoInput和麥克風(fēng)輸入audioInput猖辫。
2、設(shè)置videoInputaudioInput_captureSession的輸入砚殿,同時(shí)設(shè)置videoOutputaudioOutput_captureSession的輸出啃憎,并且設(shè)置videoOutputaudioOutput的輸出delegate
3似炎、_captureSession調(diào)用startRunning辛萍,開(kāi)始捕獲信號(hào)。
4羡藐、音頻數(shù)據(jù)到達(dá)叹阔,把數(shù)據(jù)轉(zhuǎn)發(fā)給之前設(shè)置的audioEncodingTarget,并通過(guò)調(diào)用assetWriterAudioInputappendSampleBuffer方法寫(xiě)入音頻數(shù)據(jù)传睹。
5、視頻數(shù)據(jù)到達(dá)岸晦,視頻數(shù)據(jù)傳入響應(yīng)鏈欧啤,經(jīng)過(guò)處理后通過(guò)assetWriterPixelBufferInputappendSampleBuffer方法寫(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文集

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末惩激,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蟹演,更是在濱河造成了極大的恐慌风钻,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件酒请,死亡現(xiàn)場(chǎng)離奇詭異骡技,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)布朦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)囤萤,“玉大人,你說(shuō)我怎么就攤上這事是趴√紊幔” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵唆途,是天一觀的道長(zhǎng)富雅。 經(jīng)常有香客問(wèn)我,道長(zhǎng)肛搬,這世上最難降的妖魔是什么没佑? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮滚婉,結(jié)果婚禮上图筹,老公的妹妹穿的比我還像新娘。我一直安慰自己让腹,他們只是感情好远剩,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著骇窍,像睡著了一般瓜晤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上腹纳,一...
    開(kāi)封第一講書(shū)人閱讀 51,182評(píng)論 1 299
  • 那天痢掠,我揣著相機(jī)與錄音,去河邊找鬼嘲恍。 笑死足画,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的佃牛。 我是一名探鬼主播淹辞,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼俘侠!你這毒婦竟也來(lái)了象缀?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤爷速,失蹤者是張志新(化名)和其女友劉穎央星,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體惫东,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡莉给,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片禁谦。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡胁黑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出州泊,到底是詐尸還是另有隱情,我是刑警寧澤漂洋,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布遥皂,位于F島的核電站,受9級(jí)特大地震影響刽漂,放射性物質(zhì)發(fā)生泄漏演训。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一贝咙、第九天 我趴在偏房一處隱蔽的房頂上張望样悟。 院中可真熱鬧,春花似錦庭猩、人聲如沸窟她。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)震糖。三九已至,卻和暖如春趴腋,著一層夾襖步出監(jiān)牢的瞬間吊说,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工优炬, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留颁井,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓蠢护,卻偏偏與公主長(zhǎng)得像雅宾,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子糊余,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353