隨著每一代 iPhone 處理能力和相機(jī)硬件配置的提高,使用它來(lái)捕獲視頻也變得更加有意思。它們小巧,輕便,低調(diào),而且與專(zhuān)業(yè)攝像機(jī)之間的差距已經(jīng)變得非常小,小到在某些情況下,iPhone 可以真正替代它們。
這篇文章討論了關(guān)于如何配置視頻捕獲管線 (pipeline) 和最大限度地利用硬件性能的一些不同選擇。 這里有個(gè)使用了不同管線的樣例 app,可以在 GitHub 查看。
UIImagePickerController目前,將視頻捕獲集成到你的應(yīng)用中的最簡(jiǎn)單的方法是使用 UIImagePickerController。這是一個(gè)封裝了完整視頻捕獲管線和相機(jī) UI 的 view controller。
在實(shí)例化相機(jī)之前,首先要檢查設(shè)備是否支持相機(jī)錄制:
if ([UIImagePickerController
isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
NSArray *availableMediaTypes = [UIImagePickerController
availableMediaTypesForSourceType:UIImagePickerControllerSourceTypeCamera];
if ([availableMediaTypes containsObject:(NSString *)kUTTypeMovie]) {
// 支持視頻錄制
}
}
然后創(chuàng)建一個(gè) UIImagePickerController 對(duì)象,設(shè)置好代理便于進(jìn)一步處理錄制好的視頻 (比如存到相冊(cè)) 以及對(duì)于用戶(hù)關(guān)閉相機(jī)作出響應(yīng):
UIImagePickerController *camera = [UIImagePickerController new];
camera.sourceType = UIImagePickerControllerSourceTypeCamera;
camera.mediaTypes = @[(NSString *)kUTTypeMovie];
camera.delegate = self;
這是你實(shí)現(xiàn)一個(gè)功能完善的攝像機(jī)所需要寫(xiě)的所有代碼。
UIImagePickerController 提供了額外的配置選項(xiàng)。
通過(guò)設(shè)置 cameraDevice 屬性可以選擇一個(gè)特定的相機(jī)。這是一個(gè) UIImagePickerControllerCameraDevice 枚舉,默認(rèn)情況下是 UIImagePickerControllerCameraDeviceRear,你也可以把它設(shè)置為 UIImagePickerControllerCameraDeviceFront。每次都應(yīng)事先確認(rèn)你想要設(shè)置的相機(jī)是可用的:
UIImagePickerController *camera = …
if ([UIImagePickerController isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceFront]) {
[camera setCameraDevice:UIImagePickerControllerCameraDeviceFront];
}
videoQuality 屬性用于控制錄制視頻的質(zhì)量。它允許你設(shè)置一個(gè)特定的編碼預(yù)設(shè),從而改變視頻的比特率和分辨率。以下是六種預(yù)設(shè):
enum {
UIImagePickerControllerQualityTypeHigh = 0,
UIImagePickerControllerQualityTypeMedium = 1, // default value
UIImagePickerControllerQualityTypeLow = 2,
UIImagePickerControllerQualityType640x480 = 3,
UIImagePickerControllerQualityTypeIFrame1280x720 = 4,
UIImagePickerControllerQualityTypeIFrame960x540 = 5
};
typedef NSUInteger UIImagePickerControllerQualityType;
前三種為相對(duì)預(yù)設(shè) (low, medium, high)。這些預(yù)設(shè)的編碼配置會(huì)因設(shè)備不同而不同。如果選擇 high,那么你選定的相機(jī)會(huì)提供給你該設(shè)備所能支持的最高畫(huà)質(zhì)。后面三種是特定分辨率的預(yù)設(shè) (640x480 VGA, 960x540 iFrame, 和 1280x720 iFrame)。
就像上面提到的,UIImagePickerController 自帶一套相機(jī) UI,可以直接使用。然而,你也可以自定義相機(jī)的控件,通過(guò)隱藏默認(rèn)控件,然后創(chuàng)建帶有控件的自定義視圖,并覆蓋在相機(jī)預(yù)覽圖層上面:
UIView *cameraOverlay = …
picker.showsCameraControls = NO;
picker.cameraOverlayView = cameraOverlay;
然后你需要將你覆蓋層上的控件關(guān)聯(lián)上 UIImagePickerController 的控制方法 (比如,startVideoCapture 和 stopVideoCapture)。
如果你想要更多關(guān)于處理捕獲視頻的方法,而這些方法是 UIImagePickerController 所不能提供的,那么你需要使用 AVFoundation。
AVFoundation 中關(guān)于視頻捕獲的主要的類(lèi)是 AVCaptureSession。它負(fù)責(zé)調(diào)配影音輸入與輸出之間的數(shù)據(jù)流:
http://wiki.jikexueyuan.com/project/objc/images/23-1.svg" alt="" />
使用一個(gè) capture session,你需要先實(shí)例化,添加輸入與輸出,接著啟動(dòng)從輸入到輸出之間的數(shù)據(jù)流:
AVCaptureSession *captureSession = [AVCaptureSession new];
AVCaptureDeviceInput *cameraDeviceInput = …
AVCaptureDeviceInput *micDeviceInput = …
AVCaptureMovieFileOutput *movieFileOutput = …
if ([captureSession canAddInput:cameraDeviceInput]) {
[captureSession addInput:cameraDeviceInput];
}
if ([captureSession canAddInput:micDeviceInput]) {
[captureSession addInput:micDeviceInput];
}
if ([captureSession canAddOutput:movieFileOutput]) {
[captureSession addOutput:movieFileOutput];
}
[captureSession startRunning];
(為了簡(jiǎn)單起見(jiàn),調(diào)度隊(duì)列 (dispatch queue) 的相關(guān)代碼已經(jīng)從上面那段代碼中省略了。所有對(duì) capture session 的調(diào)用都是阻塞的,因此建議將它們分配到后臺(tái)串行隊(duì)列中。)
capture session 可以通過(guò)一個(gè) sessionPreset 來(lái)進(jìn)一步配置,這可以用來(lái)指定輸出質(zhì)量的等級(jí)。有 11 種不同的預(yù)設(shè)模式:
NSString *const AVCaptureSessionPresetPhoto;
NSString *const AVCaptureSessionPresetHigh;
NSString *const AVCaptureSessionPresetMedium;
NSString *const AVCaptureSessionPresetLow;
NSString *const AVCaptureSessionPreset352x288;
NSString *const AVCaptureSessionPreset640x480;
NSString *const AVCaptureSessionPreset1280x720;
NSString *const AVCaptureSessionPreset1920x1080;
NSString *const AVCaptureSessionPresetiFrame960x540;
NSString *const AVCaptureSessionPresetiFrame1280x720;
NSString *const AVCaptureSessionPresetInputPriority;
第一個(gè)代表高像素圖片輸出。
接下來(lái)的九個(gè)和之前我們?cè)谠O(shè)置 UIImagePickerController 的 videoQuality 時(shí)看到過(guò)的 UIImagePickerControllerQualityType 選項(xiàng)非常相似,不同的是,這里有一些額外可用于 capture session 的預(yù)設(shè)。
最后一個(gè) (AVCaptureSessionPresetInputPriority) 代表 capture session 不去控制音頻與視頻輸出設(shè)置。而是通過(guò)已連接的捕獲設(shè)備的 activeFormat 來(lái)反過(guò)來(lái)控制 capture session 的輸出質(zhì)量等級(jí)。在下一節(jié),我們將會(huì)看到更多關(guān)于設(shè)備和設(shè)備格式的細(xì)節(jié)。
AVCaptureSession 的輸入其實(shí)就是一個(gè)或多個(gè)的 AVCaptureDevice 對(duì)象,這些對(duì)象通過(guò) AVCaptureDeviceInput 連接上 capture session。
我們可以使用 [AVCaptureDevice devices] 來(lái)尋找可用的捕獲設(shè)備。以 iPhone 6 為例:
(
“<AVCaptureFigVideoDevice: 0x136514db0 [Back Camera][com.apple.avfoundation.avcapturedevice.built-in_video:0]>”,
“<AVCaptureFigVideoDevice: 0x13660be80 [Front Camera][com.apple.avfoundation.avcapturedevice.built-in_video:1]>”,
“<AVCaptureFigAudioDevice: 0x174265e80 [iPhone Microphone][com.apple.avfoundation.avcapturedevice.built-in_audio:0]>”
)
配置相機(jī)輸入,需要實(shí)例化一個(gè) AVCaptureDeviceInput 對(duì)象,參數(shù)是你期望的相機(jī)設(shè)備,然后把它添加到 capture session:
AVCaptureSession *captureSession = …
AVCaptureDevice *cameraDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
NSError *error;
AVCaptureDeviceInput *cameraDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice: error:&error];
if ([captureSession canAddInput:input]) {
[captureSession addInput:cameraDeviceInput];
}
如果上面提到的 capture session 預(yù)設(shè)列表里能滿(mǎn)足你的需求,那你就不需要做更多的事情了。如果不夠,比如你想要高的幀率,你將需要配置具體的設(shè)備格式。一個(gè)視頻捕獲設(shè)備有許多設(shè)備格式,每個(gè)都帶有特定的屬性和功能。下面是對(duì)于 iPhone6 的后置攝像頭的一些例子 (一共有 22 種可用格式):
| 格式 | 分辨率 | FPS | HRSI | FOV | VIS | 最大放大比例 | Upscales | AF | ISO | SS | HDR |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 420v | 1280x720 | 5 - 240 | 1280x720 | 54.626 | YES | 49.12 | 1.09 | 1 | 29.0 - 928 | 0.000003-0.200000 | NO |
| 420f | 1280x720 | 5 - 240 | 1280x720 | 54.626 | YES | 49.12 | 1.09 | 1 | 29.0 - 928 | 0.000003-0.200000 | NO |
| 420v | 1920x1080 | 2 - 30 | 3264x1836 | 58.040 | YES | 95.62 | 1.55 | 2 | 29.0 - 464 | 0.000013-0.500000 | YES |
| 420f | 1920x1080 | 2 - 30 | 3264x1836 | 58.040 | YES | 95.62 | 1.55 | 2 | 29.0 - 464 | 0.000013-0.500000 | YES |
| 420v | 1920x1080 | 2 - 60 | 3264x1836 | 58.040 | YES | 95.62 | 1.55 | 2 | 29.0 - 464 | 0.000008-0.500000 | YES |
| 420f | 1920x1080 | 2 - 60 | 3264x1836 | 58.040 | YES | 95.62 | 1.55 | 2 | 29.0 - 464 | 0.000008-0.500000 | YES |
通過(guò)上面的那些格式,你會(huì)發(fā)現(xiàn)如果要錄制 240 幀每秒的視頻的話,可以根據(jù)想要的像素格式選用第一個(gè)或第二個(gè)格式。另外若是要捕獲 1920x1080 的分辨率的視頻的話,是不支持 240 幀每秒的。
配置一個(gè)具體設(shè)備格式,你首先需要調(diào)用 lockForConfiguration: 來(lái)獲取設(shè)備的配置屬性的獨(dú)占訪問(wèn)權(quán)限。接著你簡(jiǎn)單地使用 setActiveFormat: 來(lái)設(shè)置設(shè)備的捕獲格式。這將會(huì)自動(dòng)把 capture session 的預(yù)設(shè)設(shè)置為 AVCaptureSessionPresetInputPriority。
一旦你設(shè)置了預(yù)想的設(shè)備格式,你就可以在這種設(shè)備格式的約束參數(shù)范圍內(nèi)進(jìn)行進(jìn)一步的配置了。
對(duì)于視頻捕獲的對(duì)焦,曝光和白平衡的設(shè)置,與圖像捕獲時(shí)一樣,具體可參考第 21 期“iOS 上的相機(jī)捕捉”。除了那些,這里還有一些視頻特有的配置選項(xiàng)。
你可以用捕獲設(shè)備的 activeVideoMinFrameDuration 和 activeVideoMaxFrameDuration 屬性設(shè)置幀速率,一幀的時(shí)長(zhǎng)是幀速率的倒數(shù)。設(shè)置幀速率之前,要先確認(rèn)它是否在設(shè)備格式所支持的范圍內(nèi),然后鎖住捕獲設(shè)備來(lái)進(jìn)行配置。為了確保幀速率恒定,可以將最小與最大的幀時(shí)長(zhǎng)設(shè)置成一樣的值:
NSError *error;
CMTime frameDuration = CMTimeMake(1, 60);
NSArray *supportedFrameRateRanges = [device.activeFormat videoSupportedFrameRateRanges];
BOOL frameRateSupported = NO;
for (AVFrameRateRange *range in supportedFrameRateRanges) {
if (CMTIME_COMPARE_INLINE(frameDuration, >=, range.minFrameDuration) &&
CMTIME_COMPARE_INLINE(frameDuration, <=, range.maxFrameDuration)) {
frameRateSupported = YES;
}
}
if (frameRateSupported && [device lockForConfiguration:&error]) {
[device setActiveVideoMaxFrameDuration:frameDuration];
[device setActiveVideoMinFrameDuration:frameDuration];
[device unlockForConfiguration];
}
視頻防抖 是在 iOS 6 和 iPhone 4S 發(fā)布時(shí)引入的功能。到了 iPhone 6,增加了更強(qiáng)勁和流暢的防抖模式,被稱(chēng)為影院級(jí)的視頻防抖動(dòng)。相關(guān)的 API 也有所改動(dòng) (目前為止并沒(méi)有在文檔中反映出來(lái),不過(guò)可以查看頭文件)。防抖并不是在捕獲設(shè)備上配置的,而是在 AVCaptureConnection 上設(shè)置。由于不是所有的設(shè)備格式都支持全部的防抖模式,所以在實(shí)際應(yīng)用中應(yīng)事先確認(rèn)具體的防抖模式是否支持:
AVCaptureDevice *device = ...;
AVCaptureConnection *connection = ...;
AVCaptureVideoStabilizationMode stabilizationMode = AVCaptureVideoStabilizationModeCinematic;
if ([device.activeFormat isVideoStabilizationModeSupported:stabilizationMode]) {
[connection setPreferredVideoStabilizationMode:stabilizationMode];
}
iPhone 6 的另一個(gè)新特性就是視頻 HDR (高動(dòng)態(tài)范圍圖像),它是“高動(dòng)態(tài)范圍的視頻流,與傳統(tǒng)的將不同曝光度的靜態(tài)圖像合成成一張高動(dòng)態(tài)范圍圖像的方法完全不同”,它是內(nèi)建在傳感器中的。有兩種方法可以配置視頻 HDR:直接將 capture device 的 videoHDREnabled 設(shè)置為啟用或禁用,或者使用 automaticallyAdjustsVideoHDREnabled 屬性來(lái)留給系統(tǒng)處理。
之前展示的捕獲設(shè)備列表里面只有一個(gè)音頻設(shè)備,你可能覺(jué)得奇怪,畢竟 iPhone 6 有 3 個(gè)麥克風(fēng)。然而因?yàn)橛袝r(shí)會(huì)放在一起使用,便于優(yōu)化性能,因此可能被當(dāng)做一個(gè)設(shè)備來(lái)使用。例如在 iPhone 5 及以上的手機(jī)錄制視頻時(shí),會(huì)同時(shí)使用前置和后置麥克風(fēng),用于定向降噪。
大多數(shù)情況下,設(shè)置成默認(rèn)的麥克風(fēng)配置即可。后置麥克風(fēng)會(huì)自動(dòng)搭配后置攝像頭使用 (前置麥克風(fēng)則用于降噪),前置麥克風(fēng)和前置攝像頭也是一樣。
然而想要訪問(wèn)和配置單獨(dú)的麥克風(fēng)也是可行的。例如,當(dāng)用戶(hù)正在使用后置攝像頭捕獲場(chǎng)景的時(shí)候,使用前置麥克風(fēng)來(lái)錄制解說(shuō)也應(yīng)是可能的。這就要依賴(lài)于 AVAudioSession。
為了變更要訪問(wèn)的音頻,audio session 首先需要設(shè)置為支持這樣做的類(lèi)別。然后我們需要遍歷 audio session 的輸入端口和端口數(shù)據(jù)來(lái)源,來(lái)找到我們想要的麥克風(fēng):
// 配置 audio session
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
[audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
[audioSession setActive:YES error:nil];
// 尋找期望的輸入端口
NSArray* inputs = [audioSession availableInputs];
AVAudioSessionPortDescription *builtInMic = nil;
for (AVAudioSessionPortDescription* port in inputs) {
if ([port.portType isEqualToString:AVAudioSessionPortBuiltInMic]) {
builtInMic = port;
break;
}
}
// 尋找期望的麥克風(fēng)
for (AVAudioSessionDataSourceDescription* source in builtInMic.dataSources) {
if ([source.orientation isEqual:AVAudioSessionOrientationFront]) {
[builtInMic setPreferredDataSource:source error:nil];
[audioSession setPreferredInput:builtInMic error:&error];
break;
}
}
除了設(shè)置非默認(rèn)的麥克風(fēng)配置,你也可以使用 AVAudioSession 來(lái)配置其他音頻設(shè)置,比如音頻增益和采樣率等。
有件事你需要記住,訪問(wèn)相機(jī)和麥克風(fēng)需要先獲得用戶(hù)授權(quán)。當(dāng)你給視頻或音頻創(chuàng)建第一個(gè) AVCaptureDeviceInput 對(duì)象時(shí),iOS 會(huì)自動(dòng)彈出一次對(duì)話框,請(qǐng)求用戶(hù)授權(quán),但你最好還是自己實(shí)現(xiàn)下。之后你就可以在還沒(méi)有被授權(quán)的時(shí)候,使用相同的代碼來(lái)提示用戶(hù)進(jìn)行授權(quán)。當(dāng)用戶(hù)未授權(quán)時(shí),對(duì)于錄制視頻或音頻的嘗試,得到的將是黑色畫(huà)面和無(wú)聲。
輸入配置完了,現(xiàn)在把我們的注意力轉(zhuǎn)向 capture session 的輸出。
AVCaptureMovieFileOutput將視頻寫(xiě)入文件,最簡(jiǎn)單的選擇就是使用 AVCaptureMovieFileOutput 對(duì)象。把它作為輸出添加到 capture session 中,就可以將視頻和音頻寫(xiě)入 QuickTime 文件,這只需很少的配置。
AVCaptureMovieFileOutput *movieFileOutput = [AVCaptureMovieFileOutput new];
if([captureSession canAddOutput:movieFileOutput]){
[captureSession addOutput:movieFileOutput];
}
// 開(kāi)始錄制
NSURL *outputURL = …
[movieFileOutput startRecordingToOutputFileURL:outputURL recordingDelegate:self];
當(dāng)實(shí)際的錄制開(kāi)始或停止時(shí),想要接收回調(diào)的話就必須要一個(gè)錄制代理。當(dāng)錄制停止時(shí),輸出通常還在寫(xiě)入數(shù)據(jù),等它完成之后會(huì)調(diào)用代理方法。
AVCaptureMovieFileOutput 有一些其他的配置選項(xiàng),比如在某段時(shí)間后,在達(dá)到某個(gè)指定的文件尺寸時(shí),或者當(dāng)設(shè)備的最小磁盤(pán)剩余空間達(dá)到某個(gè)閾值時(shí)停止錄制。如果你還需要更多設(shè)置,比如自定義視頻音頻的壓縮率,或者你想要在寫(xiě)入文件之前,處理視頻音頻的樣本,那么你需要一些更復(fù)雜的操作。
AVCaptureDataOutput 和 AVAssetWriter如果你想要對(duì)影音輸出有更多的操作,你可以使用 AVCaptureVideoDataOutput 和 AVCaptureAudioDataOutput 而不是我們上節(jié)討論的 AVCaptureMovieFileOutput。
這些輸出將會(huì)各自捕獲視頻和音頻的樣本緩存,接著發(fā)送到它們的代理。代理要么對(duì)采樣緩沖進(jìn)行處理 (比如給視頻加濾鏡),要么保持原樣傳送。使用 AVAssetWriter 對(duì)象可以將樣本緩存寫(xiě)入文件:
http://wiki.jikexueyuan.com/project/objc/images/23-2.svg" alt="" />
配置一個(gè) asset writer 需要定義一個(gè)輸出 URL 和文件格式,并添加一個(gè)或多個(gè)輸入來(lái)接收采樣的緩沖。我們還需要將輸入的 expectsMediaInRealTime 屬性設(shè)置為 YES,因?yàn)樗鼈冃枰獜?capture session 實(shí)時(shí)獲得數(shù)據(jù)。
NSURL *url = …;
AVAssetWriter *assetWriter = [AVAssetWriter assetWriterWithURL:url fileType:AVFileTypeMPEG4 error:nil];
AVAssetWriterInput *videoInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeVideo outputSettings:nil];
videoInput.expectsMediaDataInRealTime = YES;
AVAssetWriterInput *audioInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeAudio outputSettings:nil];
audioInput.expectsMediaDataInRealTime = YES;
if ([assetWriter canAddInput:videoInput]) {
[assetWriter addInput:videoInput];
}
if ([assetWriter canAddInput:audioInput]) {
[assetWriter addInput:audioInput];
}
(這里推薦將 asset writer 派送到后臺(tái)串行隊(duì)列中調(diào)用。)
在上面的示例代碼中,我們將 asset writer 的 outputSettings 設(shè)置為 nil。這就意味著附加上來(lái)的樣本不會(huì)再被重新編碼。如果你確實(shí)想要重新編碼這些樣本,那么需要提供一個(gè)包含具體輸出參數(shù)的字典。關(guān)于音頻輸出設(shè)置的鍵值被定義在這里, 關(guān)于視頻輸出設(shè)置的鍵值定義在這里。
為了更簡(jiǎn)單點(diǎn),AVCaptureVideoDataOutput 和 AVCaptureAudioDataOutput 分別帶有 recommendedVideoSettingsForAssetWriterWithOutputFileType: 和 recommendedAudioSettingsForAssetWriterWithOutputFileType: 方法,可以生成與 asset writer 兼容的帶有全部鍵值對(duì)的字典。所以你可以通過(guò)在這個(gè)字典里調(diào)整你想要重寫(xiě)的屬性,來(lái)簡(jiǎn)單地定義你自己的輸出設(shè)置。比如,增加視頻比特率來(lái)提高視頻質(zhì)量等。
或者,你也可以使用 AVOutputSettingsAssistant 來(lái)配置輸出設(shè)置的字典,但是從我的經(jīng)驗(yàn)來(lái)看,使用上面的方法會(huì)更好,它們會(huì)提供更實(shí)用的輸出設(shè)置,比如視頻比特率。另外,AVOutputSettingsAssistant 似乎存在一些缺點(diǎn),例如,當(dāng)你改變希望的視頻的幀速率時(shí),視頻的比特率并不會(huì)改變。
當(dāng)使用 AVFoundation 來(lái)做圖像捕獲時(shí),我們必須提供一套自定義的用戶(hù)界面。其中一個(gè)關(guān)鍵的相機(jī)交互組件是實(shí)時(shí)預(yù)覽圖。最簡(jiǎn)單的實(shí)現(xiàn)方式是通過(guò)把 AVCaptureVideoPreviewLayer 對(duì)象作為一個(gè) sublayer 加到相機(jī)圖層上去:
AVCaptureSession *captureSession = ...;
AVCaptureVideoPreviewLayer *previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:captureSession];
UIView *cameraView = ...;
previewLayer.frame = cameraView.bounds;
[cameraView.layer addSublayer:previewLayer];
如果你想要更進(jìn)一步操作,比如,在實(shí)時(shí)預(yù)覽圖加濾鏡,你需要將 AVCaptureVideoDataOutput 對(duì)象加到 capture session,并且使用 OpenGL 展示畫(huà)面,具體可查看該文“iOS 上的相機(jī)捕捉”
有許多不同的方法可以給 iOS 上的視頻捕獲配置管線,從最直接的 UIImagePickerController,到精密配合的 AVCaptureSession 與 AVAssetWriter 。如何抉擇取決于你的項(xiàng)目要求,比如期望的視頻質(zhì)量和壓縮率,或者是你想要展示給用戶(hù)的相機(jī)控件。