在iOS中給視頻添加濾鏡的方法示例
「眾所周知,視頻可以 P」,今天我們來(lái)學(xué)習(xí)怎么給視頻添加濾鏡。
在 iOS 中,對(duì)視頻進(jìn)行圖像處理一般有兩種方式: GPUImage
和 AVFoundation
。
一、GPUImage
在之前的文章中,我們對(duì) GPUImage 已經(jīng)有了一定的了解。之前一般使用它對(duì)攝像頭采集的圖像數(shù)據(jù)進(jìn)行處理,然而,它對(duì)本地視頻的處理也一樣方便。
直接看代碼:
// movie NSString *path = [[NSBundle mainBundle] pathForResource:@"sample" ofType:@"mp4"]; NSURL *url = [NSURL fileURLWithPath:path]; GPUImageMovie *movie = [[GPUImageMovie alloc] initWithURL:url]; // filter GPUImageSmoothToonFilter *filter = [[GPUImageSmoothToonFilter alloc] init]; // view GPUImageView *imageView = [[GPUImageView alloc] initWithFrame:CGRectMake(0, 80, self.view.frame.size.width, self.view.frame.size.width)]; [self.view addSubview:imageView]; // chain [movie addTarget:filter]; [filter addTarget:imageView]; // processing [movie startProcessing];
核心代碼一共就幾行。 GPUImageMovie 負(fù)責(zé)視頻文件的讀取, GPUImageSmoothToonFilter 負(fù)責(zé)濾鏡效果處理, GPUImageView 負(fù)責(zé)最終圖像的展示。
通過(guò)濾鏡鏈將三者串起來(lái),然后調(diào)用 GPUImageMovie 的 startProcessing 方法開(kāi)始處理。
雖然 GPUImage 在使用上簡(jiǎn)單,但是存在著 沒(méi)有聲音 、 在非主線程調(diào)用 UI 、 導(dǎo)出文件麻煩 、 無(wú)法進(jìn)行播放控制 等諸多缺點(diǎn)。
小結(jié):GPUImage 雖然使用很方便,但是存在諸多缺點(diǎn),不滿足生產(chǎn)環(huán)境需要。
二、AVFoundation
1、 AVPlayer 的使用
首先來(lái)復(fù)習(xí)一下 AVPlayer 最簡(jiǎn)單的使用方式:
NSURL *url = [[NSBundle mainBundle] URLForResource:@"sample" withExtension:@"mp4"]; AVURLAsset *asset = [AVURLAsset assetWithURL:url]; AVPlayerItem *playerItem = [[AVPlayerItem alloc] initWithAsset:asset]; AVPlayer *player = [[AVPlayer alloc] initWithPlayerItem:playerItem]; AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
第一步先構(gòu)建 AVPlayerItem ,然后通過(guò) AVPlayerItem 創(chuàng)建 AVPlayer ,最后通過(guò) AVPlayer 創(chuàng)建 AVPlayerLayer 。
AVPlayerLayer 是 CALayer 的子類(lèi),可以把它添加到任意的 Layer 上。當(dāng) AVPlayer 調(diào)用 play 方法時(shí), AVPlayerLayer 上就能將圖像渲染出來(lái)。
AVPlayer 的使用方式十分簡(jiǎn)單。但是,按照上面的方式,最終只能在 AVPlayerLayer 上渲染出最原始的圖像。如果我們希望在播放的同時(shí),對(duì)原始圖像進(jìn)行處理,則需要修改 AVPlayer 的渲染過(guò)程。
2、修改 AVPlayer 的渲染過(guò)程
修改 AVPlayer 的渲染過(guò)程,要從 AVPlayerItem 下手,主要分為 四步 :
第一步:自定義 AVVideoCompositing 類(lèi)
AVVideoCompositing 是一個(gè)協(xié)議,我們的自定義類(lèi)要實(shí)現(xiàn)這個(gè)協(xié)議。在這個(gè)自定義類(lèi)中,可以獲取到每一幀的原始圖像,進(jìn)行處理并輸出。
在這個(gè)協(xié)議中,最關(guān)鍵是 startVideoCompositionRequest 方法的實(shí)現(xiàn):
// CustomVideoCompositing.m - (void)startVideoCompositionRequest:(AVAsynchronousVideoCompositionRequest *)asyncVideoCompositionRequest { dispatch_async(self.renderingQueue, ^{ @autoreleasepool { if (self.shouldCancelAllRequests) { [asyncVideoCompositionRequest finishCancelledRequest]; } else { CVPixelBufferRef resultPixels = [self newRenderdPixelBufferForRequest:asyncVideoCompositionRequest]; if (resultPixels) { [asyncVideoCompositionRequest finishWithComposedVideoFrame:resultPixels]; CVPixelBufferRelease(resultPixels); } else { // print error } } } }); }
通過(guò) newRenderdPixelBufferForRequest 方法從 AVAsynchronousVideoCompositionRequest 中獲取到處理后的 CVPixelBufferRef 后輸出,看下這個(gè)方法的實(shí)現(xiàn):
// CustomVideoCompositing.m - (CVPixelBufferRef)newRenderdPixelBufferForRequest:(AVAsynchronousVideoCompositionRequest *)request { CustomVideoCompositionInstruction *videoCompositionInstruction = (CustomVideoCompositionInstruction *)request.videoCompositionInstruction; NSArray<AVVideoCompositionLayerInstruction *> *layerInstructions = videoCompositionInstruction.layerInstructions; CMPersistentTrackID trackID = layerInstructions.firstObject.trackID; CVPixelBufferRef sourcePixelBuffer = [request sourceFrameByTrackID:trackID]; CVPixelBufferRef resultPixelBuffer = [videoCompositionInstruction applyPixelBuffer:sourcePixelBuffer]; if (!resultPixelBuffer) { CVPixelBufferRef emptyPixelBuffer = [self createEmptyPixelBuffer]; return emptyPixelBuffer; } else { return resultPixelBuffer; } }
在這個(gè)方法中,我們通過(guò) trackID 從 AVAsynchronousVideoCompositionRequest 中獲取到 sourcePixelBuffer ,也就是當(dāng)前幀的原始圖像。
然后調(diào)用 videoCompositionInstruction 的 applyPixelBuffer 方法,將 sourcePixelBuffer 作為輸入,得到處理后的結(jié)果 resultPixelBuffer 。也就是說(shuō),我們對(duì)圖像的處理操作,都發(fā)生在 applyPixelBuffer 方法中。
在 newRenderdPixelBufferForRequest 這個(gè)方法中,我們已經(jīng)拿到了當(dāng)前幀的原始圖像 sourcePixelBuffer ,其實(shí)也可以直接在這個(gè)方法中對(duì)圖像進(jìn)行處理。
那為什么還需要把處理操作放在 CustomVideoCompositionInstruction 中呢?
因?yàn)樵趯?shí)際渲染的時(shí)候,自定義 AVVideoCompositing 類(lèi)的實(shí)例創(chuàng)建是系統(tǒng)內(nèi)部完成的。也就是說(shuō),我們?cè)L問(wèn)不到最終的 AVVideoCompositing 對(duì)象。所以無(wú)法進(jìn)行一些渲染參數(shù)的動(dòng)態(tài)修改。而從 AVAsynchronousVideoCompositionRequest 中,可以獲取到 AVVideoCompositionInstruction 對(duì)象,所以我們需要自定義 AVVideoCompositionInstruction ,這樣就可以間接地通過(guò)修改 AVVideoCompositionInstruction 的屬性,來(lái)動(dòng)態(tài)修改渲染參數(shù)。
第二步:自定義 AVVideoCompositionInstruction
這個(gè)類(lèi)的關(guān)鍵點(diǎn)是 applyPixelBuffer 方法的實(shí)現(xiàn):
// CustomVideoCompositionInstruction.m - (CVPixelBufferRef)applyPixelBuffer:(CVPixelBufferRef)pixelBuffer { self.filter.pixelBuffer = pixelBuffer; CVPixelBufferRef outputPixelBuffer = self.filter.outputPixelBuffer; CVPixelBufferRetain(outputPixelBuffer); return outputPixelBuffer; }
這里把 OpenGL ES 的處理細(xì)節(jié)都封裝到了 filter 中。這個(gè)類(lèi)的實(shí)現(xiàn)細(xì)節(jié)可以先忽略,只需要知道它接受 原始的 CVPixelBufferRef ,返回 處理后的 CVPixelBufferRef 。
第三步:構(gòu)建 AVMutableVideoComposition
構(gòu)建的代碼如下:
self.videoComposition = [self createVideoCompositionWithAsset:self.asset]; self.videoComposition.customVideoCompositorClass = [CustomVideoCompositing class]; - (AVMutableVideoComposition *)createVideoCompositionWithAsset:(AVAsset *)asset { AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoCompositionWithPropertiesOfAsset:asset]; NSArray *instructions = videoComposition.instructions; NSMutableArray *newInstructions = [NSMutableArray array]; for (AVVideoCompositionInstruction *instruction in instructions) { NSArray *layerInstructions = instruction.layerInstructions; // TrackIDs NSMutableArray *trackIDs = [NSMutableArray array]; for (AVVideoCompositionLayerInstruction *layerInstruction in layerInstructions) { [trackIDs addObject:@(layerInstruction.trackID)]; } CustomVideoCompositionInstruction *newInstruction = [[CustomVideoCompositionInstruction alloc] initWithSourceTrackIDs:trackIDs timeRange:instruction.timeRange]; newInstruction.layerInstructions = instruction.layerInstructions; [newInstructions addObject:newInstruction]; } videoComposition.instructions = newInstructions; return videoComposition; }
構(gòu)建 AVMutableVideoComposition 的過(guò)程 主要做兩件事情 。
第一件事情,把 videoComposition 的 customVideoCompositorClass 屬性,設(shè)置為我們自定義的 CustomVideoCompositing 。
第二件事情,首先通過(guò)系統(tǒng)提供的方法 videoCompositionWithPropertiesOfAsset 構(gòu)建出 AVMutableVideoComposition 對(duì)象,然后將它的 instructions 屬性修改為自定義的 CustomVideoCompositionInstruction 類(lèi)型。(就像「第一步」提到的,后續(xù)可以在 CustomVideoCompositing 中,拿到 CustomVideoCompositionInstruction 對(duì)象。)
注意:這里可以把 CustomVideoCompositionInstruction 保存下來(lái),然后通過(guò)修改它的屬性,去修改渲染參數(shù)。
第四步:構(gòu)建 AVPlayerItem
有了 AVMutableVideoComposition 之后,后面的事情就簡(jiǎn)單多了。
只需要在創(chuàng)建 AVPlayerItem 的時(shí)候,多賦值一個(gè) videoComposition 屬性。
self.playerItem = [[AVPlayerItem alloc] initWithAsset:self.asset]; self.playerItem.videoComposition = self.videoComposition;
這樣,整條鏈路就串起來(lái)了, AVPlayer 在播放時(shí),就能在 CustomVideoCompositionInstruction 的 applyPixelBuffer 方法中接收到 原始圖像的 CVPixelBufferRef 。
3、應(yīng)用濾鏡效果
這一步要做的事情是: 在 CVPixelBufferRef 上添加濾鏡效果,并輸出處理后的 CVPixelBufferRef 。
要做到這件事情,有很多種方式。包括但不限定于: OpenGL ES 、 CIImage 、 Metal 、 GPUImage 等。
為了同樣使用前面用到的 GPUImageSmoothToonFilter ,這里介紹一下 GPUImage 的方式。
關(guān)鍵代碼如下:
- (CVPixelBufferRef)renderByGPUImage:(CVPixelBufferRef)pixelBuffer { CVPixelBufferRetain(pixelBuffer); __block CVPixelBufferRef output = nil; runSynchronouslyOnVideoProcessingQueue(^{ [GPUImageContext useImageProcessingContext]; // (1) GLuint textureID = [self.pixelBufferHelper convertYUVPixelBufferToTexture:pixelBuffer]; CGSize size = CGSizeMake(CVPixelBufferGetWidth(pixelBuffer), CVPixelBufferGetHeight(pixelBuffer)); [GPUImageContext setActiveShaderProgram:nil]; // (2) GPUImageTextureInput *textureInput = [[GPUImageTextureInput alloc] initWithTexture:textureID size:size]; GPUImageSmoothToonFilter *filter = [[GPUImageSmoothToonFilter alloc] init]; [textureInput addTarget:filter]; GPUImageTextureOutput *textureOutput = [[GPUImageTextureOutput alloc] init]; [filter addTarget:textureOutput]; [textureInput processTextureWithFrameTime:kCMTimeZero]; // (3) output = [self.pixelBufferHelper convertTextureToPixelBuffer:textureOutput.texture textureSize:size]; [textureOutput doneWithTexture]; glDeleteTextures(1, &textureID); }); CVPixelBufferRelease(pixelBuffer); return output; }
(1)一開(kāi)始讀入的視頻幀是 YUV 格式的,首先把 YUV 格式的 CVPixelBufferRef 轉(zhuǎn)成 OpenGL 紋理。
(2)通過(guò) GPUImageTextureInput 來(lái)構(gòu)造濾鏡鏈起點(diǎn), GPUImageSmoothToonFilter 來(lái)添加濾鏡效果, GPUImageTextureOutput 來(lái)構(gòu)造濾鏡鏈終點(diǎn),最終也是輸出 OpenGL 紋理。
(3)將處理后的 OpenGL 紋理轉(zhuǎn)化為 CVPixelBufferRef 。
另外,由于 CIImage 使用簡(jiǎn)單,也順便提一下用法。
關(guān)鍵代碼如下:
- (CVPixelBufferRef)renderByCIImage:(CVPixelBufferRef)pixelBuffer { CVPixelBufferRetain(pixelBuffer); CGSize size = CGSizeMake(CVPixelBufferGetWidth(pixelBuffer), CVPixelBufferGetHeight(pixelBuffer)); // (1) CIImage *image = [[CIImage alloc] initWithCVPixelBuffer:pixelBuffer]; // (2) CIImage *filterImage = [CIImage imageWithColor:[CIColor colorWithRed:255.0 / 255 green:245.0 / 255 blue:215.0 / 255 alpha:0.1]]; // (3) image = [filterImage imageByCompositingOverImage:image]; // (4) CVPixelBufferRef output = [self.pixelBufferHelper createPixelBufferWithSize:size]; [self.context render:image toCVPixelBuffer:output]; CVPixelBufferRelease(pixelBuffer); return output; }
(1)將 CVPixelBufferRef 轉(zhuǎn)化為 CIImage 。
(2)創(chuàng)建一個(gè)帶透明度的 CIImage 。
(3)用系統(tǒng)方法將 CIImage 進(jìn)行疊加。
(4)將疊加后的 CIImage 轉(zhuǎn)化為 CVPixelBufferRef 。
4、導(dǎo)出處理后的視頻
視頻處理完成后,最終都希望能導(dǎo)出并保存。
導(dǎo)出的代碼也很簡(jiǎn)單:
self.exportSession = [[AVAssetExportSession alloc] initWithAsset:self.asset presetName:AVAssetExportPresetHighestQuality]; self.exportSession.videoComposition = self.videoComposition; self.exportSession.outputFileType = AVFileTypeMPEG4; self.exportSession.outputURL = [NSURL fileURLWithPath:self.exportPath]; [self.exportSession exportAsynchronouslyWithCompletionHandler:^{ // 保存到相冊(cè) // ... }];
這里關(guān)鍵的地方在于將 videoComposition 設(shè)置為前面構(gòu)造的 AVMutableVideoComposition 對(duì)象,然后設(shè)置好輸出路徑和文件格式后就可以開(kāi)始導(dǎo)出。導(dǎo)出成功后,可以將視頻文件轉(zhuǎn)存到相冊(cè)中。
小結(jié): AVFoundation 雖然使用比較繁瑣,但是功能強(qiáng)大,可以很方便地導(dǎo)出視頻處理的結(jié)果,是用來(lái)做視頻處理的不二之選。
源碼
請(qǐng)到 GitHub 上查看完整代碼。
到此這篇關(guān)于在iOS中給視頻添加濾鏡的方法示例的文章就介紹到這了,更多相關(guān)iOS 視頻添加濾鏡內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java C++ 算法題解leetcode1582二進(jìn)制矩陣特殊位置
這篇文章主要為大家介紹了Java C++ 算法題解leetcode1582二進(jìn)制矩陣特殊位置示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09C++如何實(shí)現(xiàn)定長(zhǎng)內(nèi)存池詳解
內(nèi)存池根據(jù)存儲(chǔ)的元素的長(zhǎng)度是否可變,分為變長(zhǎng),與定長(zhǎng)兩種內(nèi)存池,這篇文章主要給大家介紹了關(guān)于C++如何實(shí)現(xiàn)定長(zhǎng)內(nèi)存池的相關(guān)資料,需要的朋友可以參考下2021-09-09用C語(yǔ)言實(shí)現(xiàn)從文本文件中讀取數(shù)據(jù)后進(jìn)行排序的功能
這是一個(gè)十分可靠的程序,這個(gè)程序的查錯(cuò)能力非常強(qiáng)悍。程序包含了文件操作,歸并排序和字符串輸入等多種技術(shù)。對(duì)大家學(xué)習(xí)C語(yǔ)言很有幫助,有需要的一起來(lái)看看。2016-08-08C語(yǔ)言實(shí)現(xiàn)經(jīng)典windows游戲掃雷的示例代碼
今天我們會(huì)用C語(yǔ)言實(shí)現(xiàn)一個(gè)經(jīng)典的windows小游戲:掃雷。掃雷是一款單機(jī)小游戲,每次通關(guān)最高難度的關(guān)卡都會(huì)開(kāi)心好一陣?,F(xiàn)在學(xué)會(huì)了C語(yǔ)言,總算可以自己實(shí)現(xiàn)掃雷了。話不多說(shuō),咱們開(kāi)始吧2022-10-10C語(yǔ)言實(shí)現(xiàn)猜數(shù)字小游戲的示例代碼
猜數(shù)字小游戲是我們小時(shí)候喜歡我們一個(gè)經(jīng)典小游戲。這篇文章將利用C語(yǔ)言中的循環(huán)語(yǔ)句、分支語(yǔ)句和函數(shù)實(shí)現(xiàn)這一游戲,需要的可以參考一下2022-10-10對(duì)比C語(yǔ)言中的setbuf()函數(shù)和setvbuf()函數(shù)的使用
這篇文章主要介紹了對(duì)比C語(yǔ)言中的setbuf()函數(shù)和setvbuf()函數(shù)的使用,涉及到緩沖區(qū)與流的相關(guān)知識(shí),需要的朋友可以參考下2015-08-08探討:將兩個(gè)鏈表非降序合并為一個(gè)鏈表并依然有序的實(shí)現(xiàn)方法
本篇文章是對(duì)將兩個(gè)鏈表非降序合并為一個(gè)鏈表并依然有序的實(shí)現(xiàn)方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05