AVFoundation和 GPUImage初探
from:http://vonglo.me/2014/08/24/AVFoundation%E5%92%8C-GPUImage%E5%88%9D%E6%8E%A2/
最近在做視頻相關的東西,然后熟悉了一下AVFoundation框架,以及強大的開源庫GPUImage。在這里記錄這個過程中遇到的一些問題,以及解決的方法。
AVFoundation的一些基本概念
根據蘋果的官方文檔,AVFoundation是用來播放和創建實時的視聽媒體數據的框架,同時提供Objective-C接口來操作這些視聽數據,比如編輯,旋轉,重編碼。本文著重講的是視頻的錄制和編輯和GPUImage的一些簡單使用,其他的都是一筆帶過。來看下蘋果文檔的一個框架圖。
相關類
- AVAsset
- AVAssetTrack
- AVComposition
- AVVideoComposition
- AVAudioMix
- AVMutableAudioMixInputParameter
- AVMutableVideoCompositionInstrution
- AVMutableVideoCompositionLayerInstrution
簡單的播放可以使用MPMoviePlayerController或者MPMovieViewController就行,簡單的錄
制可以直接使用UIImagePickerController。同樣簡單的聲音播放直接使用AVAudioPlayer,簡單的錄制直接使用AVAduioRecorder。如果你想要有更多的操作,可使用各種復雜的方式來控制播放,比如在同一時刻為同一個asset的不同片段使用不同的分辨率渲染,playitem來管理asset的呈現狀態和方式,playitemtrack管理asset中的軌道(track)狀態。
在AVFoudation框架中最核心的類就是AVAsset,他是由一系列的媒體數據組成的,包括但不限于:時間、大小(size)、標題、字幕等。其中每一個單獨的媒體數據稱為軌道(track)。同樣剪輯操作中,AVMutableComposition是一個核心類。
這里又一個重要的東西就是CMTime,它是一個結構體,定義如下:
typedef struct
{
CMTimeValue value;
CMTimeScale timescale;
CMTimeFlags flags;
CMTimeEpoch epoch;
} CMTime;
通常時間是等于value/timescale的,所以兩個有相同時間的CMTime它們的timescale并不一定相同。關于更多CMTime的內容可以看這里。
進階
視頻的錄制
這里用的是系統原生錄制,關于錄制通常用到的幾個類就是AVCaptureDevice、
AVCaptureSession、AVCaptureDeviceInput、AVCaptureOutput,同樣,來看一張圖。
一般來說,如果你想修改視頻的相關信息,如拍攝地點等,可以拿到output的metadata來修改。大致代碼如下:
| 1 2 3 4 5 6 7 | NSMutableArray *array = [output.metadta mutableCopy]; AVMutableMetadataItem *item = [[AVMutableMetadataItem alloc] init]; item.keyspace = ...; item.key = ...; item.value = ...; [array addObject:item]; output.metadata = array; |
如果錄制時候想要得到指定的視頻size必須先指定分辨率,像這樣
| 1 2 3 4 5 6 7 | if ([session canSetSessionPreset:AVCaptureSessionPreset640x480]){ session.sessionPreset = AVCaptureSessionPreset640x480; } else { //設置失敗 } |
切換攝像頭或其他輸入源必須在beginConfiguration和commitConfiguration之間來處理,大致是這樣
| 1 2 3 4 5 6 | [session beginConfiguration]; //移除某個輸入源 //再添加某個輸入源 //再為新添加的輸入源進行必要的相關設置 //...其他操作 [session commitConfiguration]; |
如果想對實時視頻幀進行相關的渲染操作,通過 setSampleBufferDelegate:queue:方法來為output設置代理,同時必須指定queue,代理方法將會在這些queue上面被調用。可以在自己的類里面實現AVCaptureVideoDataOutput的協議方法,通過實現
captureOutput:didOutputSampleBuffer:fromConnection:來拿到視頻的每一幀,默認情況下這些視頻幀會被用最有效的格式來輸出到output,當然也可以在拍攝之前就為output進行相關設置。
| 1 2 3 | AVCaptureVideoDataOutput *videoDataOutput = [AVCaptureVideoDataOutput new]; NSDictionary *newSettings =@{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA) }; videoDataOutput.videoSettings = newSettings; |
說了這么多,感覺很虛,還是直接上代碼,將以上部分銜接起來
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | //自定義方法,小演示只添加了視頻,沒有添加聲音,添加聲音類似 - (void)yourCustomMethodName{ AVCaptureSession *session = [[AVCaptureSession alloc] init]; if ([session canSetSessionPreset:AVCaptureSessionPreset640x480]){ session.sessionPreset = AVCaptureSessionPreset640x480; } else { //設置失敗 } AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; NSError *error = nil; AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error]; if (!input) { // Handle the error appropriately. } if(session canAddInput:input){ [session addInput:input]; } AVCaptureVideoDataOutput *output = [[AVCaptureVideoDataOutput alloc] init]; if(session canAddOutput:output){ [session addOutput:output]; } output.videoSettings =@{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA) }; //設置幀率(FPS),每秒15幀 output.minFrameDuration = CMTimeMake(1, 15); dispatch_queue_t queue = dispatch_queue_create("CustomQueue", NULL); [output setSampleBufferDelegate:self queue:queue]; dispatch_release(queue) NSString *mediaType = AVMediaTypeVideo; //用來顯示錄制的實時畫面 AVCaptureVideoPreviewLayer *captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:session]; [self.view.layer addSublayer:captureVideoPreviewLayer]; //用戶是否允許啟用攝像頭 [AVCaptureDevice requestAccessForMediaType:mediaType completionHandler:^(BOOL granted) { if (granted) { //Granted access to mediaType [self setDeviceAuthorized:YES]; [session startRunning]; } else { //Not granted access to mediaType dispatch_async(dispatch_get_main_queue(), ^{ [[[UIAlertView alloc] initWithTitle:@"AVCam!" message:@"AVCam doesn't have permission to use Camera, please change privacy settings" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil] show]; [self setDeviceAuthorized:NO]; }); } }]; } //協議方法,獲取每一幀,將每一幀轉換成圖片,你也可以進行其他的渲染操作 - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { UIImage *image = imageFromSampleBuffer(sampleBuffer); } |
上面演示了如何取得每一幀實時畫面,如果想要直接存成視頻可使用AVCaptureMovieFileOutput,如下
| 1 2 3 4 5 6 | AVCaptureMovieFileOutput *movieFileOutput = [[AVCaptureMovieFileOutput alloc] init]; NSURL *fileURL = ...; //存放位置 //指定代理 [aMovieFileOutput startRecordingToOutputFileURL:fileURL recordingDelegate:delete]; //也可以為其指定outputSettings 同樣代理必須實現協議方法captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error:, |
當然還有其他各種具體的設置,如對焦、曝光、閃光燈以及白平衡等等均可以通過KVO來設置,每次設置前都加一個判斷,是否支持指定模式,在這里不做詳細敘述了,這里你可以看到更多。
視頻的剪輯
視頻的剪輯包括但不限于:裁剪、旋轉(改變transform)、添加水印、添加字幕、合并等。關于剪輯,無非就是取出視頻中的軌道(視頻和音頻),然后對軌道進行一系列的操作變可以得到各種想要的效果。首先我們先來看下面一張圖
AVMutableComposition是整個視頻剪輯過程中的一個核心,下面著重講解這個類。AVMutableComposition和AVAsset一樣含有多個視/音頻軌道,但是更重要的是,它可以將多個AVAssetTrack合并到一起,比如在視頻合并時,可以直接將多段視頻拼接到一個軌道(AVMutableCompositonTrcak),音頻也一樣。通過借助AVMutableVideoComposition和AVMutableAudioMix來設置每一段的視/音頻的屬性,從而達到想要的視聽效果,比如視頻切換處的淡入淡出,聲音的漸變,字幕等等。
關于上圖的解釋:首先通過將asset里面的軌道加載到composition的各軌道,然后通過audioMix和videoComposition對某個軌道進行對應操作,設置其相關屬性。其中要用到的具體方法可以參見這里。
其中圖中1,2,3用到的方法為
| 1 2 3 4 5 6 | [1] [mutableComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid] [2] [AVMutableAudioMixInputParameters audioMixInputParametersWithTrack:mutableCompositionAudioTrack];` [3] [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:mutableCompositionVideoTrack] |
關于視頻的剪輯的代碼可以參見蘋果給出的官方Demo以及Raywendrich上的兩篇文章1,2。
GPUImage
什么?!你沒聽說過GPUImage?!那你趕緊去看看它的相關介紹。GPUImage是一個基于OpenGL ES的一個強大的圖像/視頻處理庫,封裝好了各種濾鏡同時也可以編寫自定義的濾鏡。至于他到底是如何強大,用了就知道。在這篇文章不是為了介紹它,而是列出一些我在使用過程中遇到的問題和解決方法。
分段錄制
在使用GPUImageVideoCamera來錄制的時候,可能需要分段錄制,在GPUImage給出的視頻錄制Demo中直接只是錄制一次,然而有時候需求可能是要錄制多次,如果此時按照Demo的方法每次錄制都要創建一個movieWriter,這樣子的話每次都會在重新創建movieWriter并將它設置為videoCamera的audioEncodingTarget時候,界面都會卡頓一下,這是什么原因呢?因為videoCamera默認是不錄制聲音的,而每次創建movieWriter的時候都用到了movieWriter.hasAudioTrack = YES;,吊用這個之后videoCamera會自動去添加聲音輸入源,準備一些數據,所以這個過程會導致界面卡頓一下?這該怎么辦呢?如果你有進到videoCamera的頭文件去看的話你會發現這么一個方法和它的注釋
| 1 2 3 4 5 6 7 | //Add audio capture to the session. Adding inputs and outputs freezes //the capture session momentarily, so you can use this method to add //the audio inputs and outputs early, if you’re going to set the //audioEncodingTarget later. Returns YES is the audio inputs and //outputs were added, or NO if they had already been added. -(BOOL)addAudioInputsAndOutputs; |
注釋的大意是:錄制的時候添加聲音,添加輸入源和輸出源會暫時會使錄制暫時卡住,所以在要使用聲音的情況下要先調用該方法來防止錄制被卡住。這不剛好就解決了上面的這個問題嗎?所以問題就迎刃而解了,因為沒看到這個,走了不少彎路,浪費了好長時間。
關于分段錄制,可能有這么一個需求就是所有片段都是存于一個文件中而不是錄制完成后將各段合并到一個視頻文件中。這兩個東西或許會幫到你分段錄制的實現,GPUImageExtend。前者是基于系統的分段錄制的實現,后者是GPUImageMoiveWriter的一個子類。
所見即所得
在錄制的時候,使用GPUImageView來顯示,因為給GPUImageView設置的大小是320*320的,如果不設置它的填充模式(fillMode)它是默認使用kGPUImageFillModePreserveAspectRatio即保持長寬比,其余空白處使用背景色填充,如果要設置成方形就得使用kGPUImageFillModePreserveAspectRatioAndFill,但是這個時候問題又來了假設你是用的錄制分辨率是960x540,顯示的畫面則只會顯示中間的540x540的畫面,這個時候如果movieWriter的size設置為540x540,則最后保存的視頻是失真的因為960被壓到了540,整個畫面變扁了。這個時候有兩種解決方案
- 1.使用GPUImageCropFilter,通過設置其cropRegion來裁出中間540x540部分。關于cropRegion要注意它是一個CGRect,它對坐標系做了一個歸一化處理,所以讓所有的取值都在0.0~1.0范圍內,比如960x540裁剪至中間540x540部分則cropRegion為(0,((960-540)/2)/960,1,540/960)
- 2.改變videoComposition的perferTransfom使其只顯示中間的540x540。
這樣就完成了所見即所得。
關于GPUImage的實時濾鏡添加或給已存在的視頻添加濾鏡,Demo都給出了詳細過程,依葫蘆畫瓢即可。有一點要注意的是,在一些操作完成的時候注意removeTarget,還有就是在使用movieFile來播放已存在視頻并添加濾鏡的時候是沒有聲音的,這是這個庫的一個缺陷,Github上有人提了這個issue和一些解決辦法。同時在用movieFile處理視頻的時候在切換濾鏡的時候最好先cancelProcessing不然會有黑屏或卡頓現象出現。同樣如果你是用老版本的GPUImage的時候,可能會遇到第一幀是紅色的現象,有人提出這個issue后,作者修復了這個bug,切換到最新版的時候就不會有這種情況發生。發生這種情況的原因是視頻掉幀,導致音頻和視頻不同步。
總結
AVFoundation還是有很多東西去做深層次的挖掘,GPUImage也是一樣,有了這個強大的庫,解決一些事情節省了大量時間。這次僅僅是一個小小的嘗試,對于很多東西都是淺嘗則止,文中難免會有錯誤,歡迎在評論中指正。如果你在使用GPUImage和AVFoundation有什么好的心得或者對一些問題有相應的解決方案,不妨在評論中分享一下。
版權聲明:我已將本文在微信公眾平臺的發表權「獨家代理」給 iOS開發( iOSDevTip ) 微信公眾號。掃下方二維碼即可關注「iOS 開發」:
總結
以上是生活随笔為你收集整理的AVFoundation和 GPUImage初探的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: GPUImage简单说明
- 下一篇: GPUImage滤镜中的shader代码