內(nèi)容來(lái)源
要管理從設(shè)備(比如:攝像或麥克風(fēng))捕獲的數(shù)據(jù)浮梢,你需要一些類(lèi)來(lái)代表輸入和輸出惩妇。并且還要一個(gè)AVCaptureSession
的實(shí)例來(lái)協(xié)調(diào)input和ouput之間的數(shù)據(jù)流。
最少你需要以下幾點(diǎn):
- 一個(gè)
AVCaptureDevice
的實(shí)例來(lái)表示輸入設(shè)備常侦,比如攝像頭或麥克風(fēng)刊苍。 - 一個(gè)
AVCaptureInput
子類(lèi)的實(shí)例,去配置輸入設(shè)備的端口。(AVCaptureInput
為抽象類(lèi)蒋失,不能直接實(shí)例化) - 一個(gè)
AVCaptureOutput
子類(lèi)的實(shí)例,去管理輸出為電影文件或是靜態(tài)圖片桐玻。(AVCaptureOutput
為抽象類(lèi)篙挽,不能直接實(shí)例化) - 一個(gè)
AVCaptureSessioin
的實(shí)例,去協(xié)調(diào)從輸入到輸出的數(shù)據(jù)流镊靴。
如果要展示你的攝像頭錄了什么铣卡,你可以用一個(gè)AVCaptureVideoPreviewLayer
(CALayer
的子類(lèi))的實(shí)例
你可以用一個(gè)session來(lái)協(xié)調(diào)多個(gè)inputs和outputs.如下圖:

一個(gè)session里的一個(gè)輸入和一個(gè)輸出之間的聯(lián)系是AVCaptureConnection
對(duì)象。inputs(AVCaptureInput
實(shí)例)有一個(gè)或多個(gè)端口(AVCaptureInputPort
實(shí)例)偏竟。outputs(AVCaptureOutput
實(shí)例)能從一個(gè)或多個(gè)源訪問(wèn)數(shù)據(jù)(比如:AVCaptureMovieFileOutput
對(duì)象可以訪問(wèn)音頻和視頻數(shù)據(jù))煮落。
當(dāng)你要增加一個(gè)輸入或輸出到session時(shí),session會(huì)在所有兼容的輸入端口和輸出之間形成連接(connections),如下圖踊谋。輸入和輸出之間的連接可以用AVCaptureConnection
對(duì)象表示蝉仇。

你可以用connection去開(kāi)啟或關(guān)閉一個(gè)從input來(lái)的或到output去的數(shù)據(jù)流。你也可以用一個(gè)connection去跟蹤一個(gè)音頻的平均或峰值音量。
媒體捕獲設(shè)備不支持同時(shí)得到前置和后置兩個(gè)攝像頭轿衔。
用Capture Session和協(xié)調(diào)數(shù)據(jù)流
一個(gè)AVCaptureSession
對(duì)象是你可以用來(lái)管理數(shù)據(jù)捕獲的中央?yún)f(xié)調(diào)對(duì)象沉迹。你可以用一個(gè)實(shí)例去協(xié)調(diào)從AV Input devices到輸出的數(shù)據(jù)流。你可以增加你想要的捕獲設(shè)備和輸出到session,然后發(fā)送startRunning
消息到session去開(kāi)始數(shù)據(jù)流害驹,并且你也可以發(fā)送stopRunning
消息去停止數(shù)據(jù)流鞭呕。
AVCaptureSession *session = [[AVCaptureSession alloc] init];
// Add inputs and outputs.
[session startRunning];
配置一個(gè)session
你可以用preset
(預(yù)設(shè))到session,來(lái)具體指明你想要的圖片質(zhì)量和象素宛官。一個(gè)preset是一個(gè)常量葫松,它是標(biāo)識(shí)了多個(gè)可能配置的其中一個(gè)。某些情況下特定的設(shè)備是用來(lái)特定的配置底洗。
Symbol | Resolution | Comments |
---|---|---|
AVCaptureSessionPresetHigh | High | Highest recording quality.This varies per device. |
AVCaptureSessionPresetMedium | Medium | Suitable for Wi-Fi sharing.The actual values may change. |
AVCaptureSessionPresetLow | Low | Suitable for 3G sharing.The actual values may change. |
AVCaptureSessionPreset640x480 | 640x480 | VGA. |
AVCaptureSessionPreset1280x720 | 1280x720 | 720p HD. |
AVCaptureSessionPresetPhoto | Photo | Full photo resolution.This is not supported for video output. |
如果你要設(shè)置一個(gè)特定尺寸的配置給媒體幀进宝,你應(yīng)該在設(shè)置之前檢查它是否支持。如下
if ([session canSetSessionPreset:AVCaptureSessionPreset1280x720]) {
session.sessionPreset = AVCaptureSessionPreset1280x720;
}
else {
// Handle the failure.
}
如果你需要調(diào)整session屬性到比preset
更高的粒層枷恕,或你改變一個(gè)正在運(yùn)行的session.你應(yīng)該把你的改變包含在beginConfiguration
和commitConfiguration
方法之間党晋。這兩個(gè)方法確保設(shè)備的改變做為一個(gè)組,最小的可見(jiàn)性或狀態(tài)的不一致性徐块。在調(diào)用beginConfiguration
之后未玻,你可以增加或移除輸出,替換sessionPreset
屬性胡控,或配置單獨(dú)的輸入或輸出屬性扳剿。沒(méi)有一個(gè)改變真正的實(shí)現(xiàn),直到你調(diào)用commitConfiguration
,每一個(gè)它們都是成對(duì)出現(xiàn)的昼激。
[session beginConfiguration];
// Remove an existing capture device.
// Add a new capture device.
// Reset the preset.
[session commitConfiguration];
跟蹤C(jī)apture Session狀態(tài)
capture session會(huì)發(fā)送一些你可以觀察到的notifications
,比如庇绽,當(dāng)它開(kāi)始或結(jié)束運(yùn)行時(shí),或當(dāng)它被打斷時(shí)都會(huì)發(fā)送notifications
橙困。當(dāng)一個(gè)運(yùn)行時(shí)錯(cuò)誤產(chǎn)生時(shí)瞧掺,你可以注冊(cè)去接收一個(gè)AVCaptureSessionRuntimeErrorNotification
.你也可以查詢session的running
屬性去看一下它是否在運(yùn)行,它的interrupted
屬性去看一下它是否中斷凡傅。額外的辟狈,running
和interrupted
屬性也是兼容key-value observing,它們的notifications也會(huì)在主線程發(fā)送夏跷。
一個(gè)AVCaptureDevice對(duì)象代表一個(gè)輸入設(shè)備
一個(gè)AVCaptureDevice
對(duì)象抽象自一個(gè)物理捕獲設(shè)備哼转,這個(gè)設(shè)備提供了輸入數(shù)據(jù)(比如:音頻或視頻)到AVCaptureSession
對(duì)象。每一個(gè)輸入設(shè)備都有一個(gè)對(duì)象槽华,比如壹蔓,兩個(gè)視頻輸入——一個(gè)前置攝像頭,一個(gè)后置攝像頭——還有一個(gè)用麥克風(fēng)的聲音輸入猫态。
你可以用AVCaptureDevices
的class method devices
和devicesWithMediaType:
來(lái)找出哪個(gè)捕獲設(shè)備是當(dāng)前有效的佣蓉。還有煮纵,如果需要,你也可以找出iPhone, iPad,iPod提供了什么功能偏螺。有效的設(shè)備列表是可能會(huì)變的。當(dāng)前有輸入設(shè)備也有可能變成無(wú)效的(如果被其它的app使用)匆光。并且新的輸入設(shè)備有可能變成有效的(如果它們被其它app放開(kāi))套像。你應(yīng)該注冊(cè)并接收AVCaptureDeviceWasConnectedNotification
和AVCaptureDeviceWasDisconnectedNotification
,當(dāng)新的有效設(shè)備列表改變時(shí)终息,notifications就會(huì)通知夺巩。
設(shè)備特征
你可以得到一個(gè)設(shè)備的不同特征。你也可以測(cè)試它是否提供了特定的媒體類(lèi)型或它是否支持一個(gè)capture sessioin的preset,分別可以hasMediaType:
和supportsAVCaptureSessionPreset:
驗(yàn)證周崭。你可找出設(shè)備的位置(前置或后置)和它的本地名字給用戶柳譬。有一點(diǎn)非常有用,你可以提供一個(gè)捕獲設(shè)備的列表給用戶選擇续镇。
以下代碼遍歷了所有的有效設(shè)備并找印了它們的名字:
NSArray *devices = [AVCaptureDevice devices];
for (AVCaptureDevice *device in devices) {
NSLog(@"Device name: %@", [device localizedName]);
if ([device hasMediaType:AVMediaTypeVideo]) {
if ([device position] == AVCaptureDevicePositionBack) {
NSLog(@"Device position : back");
}
else {
NSLog(@"Device position : front");
}
}
}
額外的美澳,你也可以找出設(shè)備的model ID和它的唯一ID
Device Capture Settings
不同的設(shè)備有不同的功能。比如:一些支持不同的焦點(diǎn)或閃光模式摸航。一些可能支持聚集一個(gè)興趣點(diǎn)制跟。
以下代碼片斷展示的你怎么找出支持手電筒模式和一個(gè)給定的capture session preset:
NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
NSMutableArray *torchDevices = [[NSMutableArray alloc] init];
for (AVCaptureDevice *device in devices) {
[if ([device hasTorch] &&
[device supportsAVCaptureSessionPreset:AVCaptureSessionPreset640x480]) {
[torchDevices addObject:device];
}
}
如果你找到多個(gè)符合要求的設(shè)備,你可能需要讓用戶選擇哪個(gè)是需要的酱虎。要展示一個(gè)設(shè)備的描述雨膨,你可以用localizedName
屬性。
你可以用相似的方式來(lái)使用不同的功能读串。那里有一些常量來(lái)表明一些具體的模式聊记,你可以詢問(wèn)一個(gè)設(shè)備它是否支持那個(gè)特定的模式。在一些情況下恢暖,當(dāng)一個(gè)功能改變時(shí)排监,你可以通過(guò)觀察一個(gè)屬性來(lái)得到一個(gè)通知。在所有的情況下杰捂,當(dāng)你要改變一個(gè)特定功能的模式時(shí)社露,你應(yīng)該鎖定設(shè)備。
焦點(diǎn)興趣點(diǎn)和曝光點(diǎn)是相互排斥的琼娘,焦點(diǎn)模式和曝光模式也是相互排斥的(Focus mode and exposure mode)峭弟。
Focus Modes(焦點(diǎn))
Exposure Modes(爆光點(diǎn))
Flash Modes(閃光點(diǎn))
Torch Mode(手電筒)
Video Stabilization(視頻穩(wěn)定)
White Balance(白平衡)
設(shè)置設(shè)備方向
你可以在AVCaptureConnection
里設(shè)置你想要的方向,去指明在輸出AVCaptureOutput
(AVCaptureMovieFileOutput
, AVCaptureStillImageOutput
and AVCaptureVideoDataOutput
)上圖片的朝向。
用AVCaptureConnectionsupportsVideoOrientation
屬性來(lái)查明那個(gè)設(shè)備是否支持改變視頻的方向的改變脱拼。還有videoOrientatioin
屬性表明你要哪個(gè)圖片朝向到輸出端口瞒瘸。
以下代碼是設(shè)置AVCaptureConnection
的朝向到AVCaptureVideoOrientationLandscapeLeft
AVCaptureConnection *captureConnection = <#A capture connection#>;
if ([captureConnection isVideoOrientationSupported])
{
AVCaptureVideoOrientation orientation = AVCaptureVideoOrientationLandscapeLeft;
[captureConnection setVideoOrientation:orientation];
}
配置一個(gè)設(shè)備
要設(shè)置一個(gè)設(shè)備的屬性,你必須首先使用lockForConfiguration:
來(lái)獲得在這個(gè)設(shè)備上的鎖熄浓。這個(gè)就避免了其它App的設(shè)置沖突情臭。以下的代碼就是展示了一種合理的方式來(lái)改變?cè)O(shè)備上的focus mode省撑。首先查明是否支持這個(gè)模式,再去鎖這個(gè)設(shè)備去再配置俯在。只當(dāng)鎖定設(shè)備時(shí)focus mode才改變竟秫,然后立刻釋放鎖。
if ([device isFocusModeSupported:AVCaptureFocusModeLocked]) {
NSError *error = nil;
if ([device lockForConfiguration:&error]) {
device.focusMode = AVCaptureFocusModeLocked;
[device unlockForConfiguration];
}
else {
// Respond to the failure as appropriate.
你應(yīng)該只在要改變?cè)O(shè)備屬性時(shí)保持設(shè)備鎖跷乐,保持設(shè)備鎖的目地是不被其它App改變屬性肥败。如果在不必要是還保持鎖,那其它的App就不能改變屬性了愕提。
不同設(shè)備之間的切換
有時(shí)你會(huì)要允許用戶去切換不同的輸入設(shè)備——比如:從前置攝像頭切換到后置的馒稍。為了避免暫停或磕磕絆絆浅侨,你要在運(yùn)行時(shí)就重配置session,你應(yīng)該把你的配置修改用beginConfiguration
和commitConfiguration
來(lái)包裹起來(lái):
AVCaptureSession *session = <#A capture session#>;
[session beginConfiguration];
[session removeInput:frontFacingCameraDeviceInput];
[session addInput:backFacingCameraDeviceInput];
[session commitConfiguration];
當(dāng)最外層的commitConfiguration
被調(diào)用纽谒,所有的改變都會(huì)同時(shí)發(fā)生。這就保證了一個(gè)平滑的過(guò)渡如输。
通過(guò)Capture Inputs增加Capture Device到一個(gè)session里
要增加一個(gè)capture device到capture session,你要使用AVCaptureDeviceInput
(抽象類(lèi)AVCaptureInput
的具體子類(lèi))實(shí)例鼓黔。這個(gè)Capture device input要管理設(shè)備的端口。
NSError *error;
AVCaptureDeviceInput *input =
[AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if (!input) {
// Handle the error appropriately.
}
一個(gè)AVCaptureInput
運(yùn)送一個(gè)或多個(gè)媒體數(shù)據(jù)流不见。比如:input devices 可以提供包括聲頻和視頻數(shù)據(jù)请祖。輸入設(shè)備提供的每一個(gè)媒體流都可以被AVCaptureInputPort
對(duì)象來(lái)代表。一個(gè)capture session使用一個(gè)AVCaptureConnection
對(duì)象來(lái)定義一個(gè)AVCaptureInputPort
對(duì)象的集合和單個(gè)AVCaputreOutput
對(duì)象的聯(lián)系脖祈。
通過(guò)Capture Outputs從Session得到輸出
要從Capture Session得到輸出肆捕,你要增加一個(gè)或多個(gè)Outputs.一個(gè)輸出就是AVCaptureOutput
具體子類(lèi)的實(shí)例。你使用:
-
AVCaptureMovieFileOutput
輸出一個(gè)電影文件盖高。 -
AVCaptureVideoDataOuput
如果你要處理捕獲視頻里的每一幀慎陵,比如:你要?jiǎng)?chuàng)建你自己的view layer.(捕獲人臉,活體檢測(cè)可以用這個(gè)) -
AVCaptureAudioDataOutput
可以讓你處理捕獲的音頻數(shù)據(jù)喻奥。 -
AVCaptureStillImageOutput
可以讓你獲取帶有元數(shù)據(jù)的表態(tài)圖片席纽。
你可以使用addOutput:
去增加output到Capture Session。你可以用canAddOutput:
檢查是否這個(gè)output是否兼容這個(gè)已存在的session.你也可以在session運(yùn)行時(shí)撞蚕,按要求增加和移除outputs.
AVCaptureSession *captureSession = <#Get a capture session#>;
AVCaptureMovieFileOutput *movieOutput = <#Create and configure a movie output#>;
if ([captureSession canAddOutput:movieOutput]) {
[captureSession addOutput:movieOutput];
}
else {
// Handle the failure.
}
保存一個(gè)電影文件
你可以使用AVCaptureMovieFileOutput
對(duì)象來(lái)保存電影數(shù)據(jù)(這個(gè)類(lèi)是抽象類(lèi)AVCaptureFileOutput
的具體子類(lèi)润梯,這個(gè)抽象類(lèi)定義了一些基礎(chǔ)行為)。你可以配置電影文件輸出的多個(gè)方面甥厦,比如最大錄制時(shí)長(zhǎng)纺铭,或它的最大文件大小。你可以在磁盤(pán)空間不夠時(shí)停止錄制刀疙。
AVCaptureMovieFileOutput *aMovieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
CMTime maxDuration = <#Create a CMTime to represent the maximum duration#>;
aMovieFileOutput.maxRecordedDuration = maxDuration;
aMovieFileOutput.minFreeDiskSpaceLimit = <#An appropriate minimum given the quality of the movie format and the duration#>;
輸出的分辨率和比特率是依賴于capture session的sessioinPreset
舶赔。
視頻編碼通常是H.264,音頻的編碼通常是AAC.實(shí)際值因設(shè)備而異。
開(kāi)始錄制
你可以使用startRecordingToOutputFileURL:recordingDelegate:
開(kāi)始錄制一段Quick Time電影谦秧。你需要一個(gè)基于文件的URL和一個(gè)delegate竟纳。這個(gè)URL不能指向一個(gè)已存在的文件撵溃,因?yàn)殡娪拔募敵霾荒苤貙?xiě)已存在的源。你必須有權(quán)限去在特定位置去寫(xiě)東西锥累。這個(gè)delegate必須遵守AVCaptureFileOutputRecordingDelegate
協(xié)議缘挑,并必須實(shí)現(xiàn)captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error:
方法。
AVCaptureMovieFileOutput *aMovieFileOutput = <#Get a movie file output#>;
NSURL *fileURL = <#A file URL that identifies the output location#>;
[aMovieFileOutput startRecordingToOutputFileURL:fileURL recordingDelegate:<#The delegate#>];
在這個(gè)captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error:
實(shí)現(xiàn)里桶略,這個(gè)delegate可能會(huì)把錄制的電影文件寫(xiě)到相冊(cè)里语淘。它也應(yīng)該檢查任何已經(jīng)產(chǎn)生的錯(cuò)誤。
確保文件已被成功的寫(xiě)入
為了查明你的文件是否被成功的保存删性,在captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error:
實(shí)現(xiàn)里你不僅要檢查error,還要檢查在error的user info dictionary里AVErrorRecordingSuccessfullyFinishedKey
的值:
- (void)captureOutput:(AVCaptureFileOutput *)captureOutput
didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL
fromConnections:(NSArray *)connections
error:(NSError *)error {
BOOL recordedSuccessfully = YES;
if ([error code] != noErr) {
// A problem occurred: Find out if the recording was successful.
id value = [[error userInfo] objectForKey:AVErrorRecordingSuccessfullyFinishedKey];
if (value) {
recordedSuccessfully = [value boolValue];
}
}
// Continue as appropriate...
你應(yīng)該檢查error里的user info dictionary里的AVErrorRecordingSuccessfullyFinishedKey
的值。因?yàn)檫@個(gè)文件你可能保存成功了焕窝,但你還是得到一個(gè)error.這個(gè)error可能是指向一個(gè)你錄制的限制蹬挺,比如:AVErrorMaximumDurationReached
或AVErrorMaximumFileSizeReached
.其它停止錄制的原因:
- 磁盤(pán)滿了——
AVErrorDiskFull
- 錄制設(shè)備失去連接——
AVErrorDeviceWasDisconnected
- session被中斷(比如:電話進(jìn)來(lái)了)——
AVErrorSessionWasInterrupted
加元數(shù)據(jù)到文件
你可以在任何時(shí)間為一個(gè)電影文件設(shè)置元數(shù)據(jù),即使是錄制中它掂。這對(duì)有些情況非常有用巴帮,比如:當(dāng)開(kāi)始錄制時(shí)信息無(wú)效了,可能是位置信息的情況虐秋。對(duì)于一個(gè)文件輸出來(lái)說(shuō)Metadata被一個(gè)AVMetadataItem
的對(duì)象數(shù)據(jù)來(lái)表示榕茧。你可使用它的可變子類(lèi)實(shí)例AVMutableMetadataItem
,去創(chuàng)建你自己的metadata
AVCaptureMovieFileOutput *aMovieFileOutput = <#Get a movie file output#>;
NSArray *existingMetadataArray = aMovieFileOutput.metadata;
NSMutableArray *newMetadataArray = nil;
if (existingMetadataArray) {
newMetadataArray = [existingMetadataArray mutableCopy];
}
else {
newMetadataArray = [[NSMutableArray alloc] init];
}
AVMutableMetadataItem *item = [[AVMutableMetadataItem alloc] init];
item.keySpace = AVMetadataKeySpaceCommon;
item.key = AVMetadataCommonKeyLocation;
CLLocation *location - <#The location to set#>;
item.value = [NSString stringWithFormat:@"%+08.4lf%+09.4lf/"
location.coordinate.latitude, location.coordinate.longitude];
[newMetadataArray addObject:item];
aMovieFileOutput.metadata = newMetadataArray;
處理視頻幀
一個(gè)AVCaptureVideoDataOutput
對(duì)象使用delegation來(lái)運(yùn)送視頻幀。你可以使用setSampleBufferDelegate:queue:
來(lái)設(shè)置delegate客给。除了設(shè)置delegate,你還可以指定一個(gè)串行隊(duì)列來(lái)調(diào)用這個(gè)delegate用押。你必須使用串行隊(duì)列去確保視頻幀是用合適的順序派發(fā)給delegate的。您可以使用隊(duì)列來(lái)修改提供和處理視頻幀的優(yōu)先級(jí)靶剑◎卟Γ看一下簡(jiǎn)單實(shí)現(xiàn)的例子 SquareCam
視頻幀是是做為一個(gè)CMSampleBufferRef
類(lèi)型的實(shí)例,被delegate方法captureOutput:didOutputSampleBuffer:fromConnection:
里顯示的桩引。默認(rèn)的缎讼,這些buffers是被相機(jī)以最有效的格式發(fā)出。你可以使用videoSettings
屬性去指定一個(gè)自定義的輸出格式坑匠。這個(gè)view settings屬性是個(gè)dictionary血崭。目前,唯一支持的key是kCVPixelBufferPixelFormatTypeKey
厘灼。這個(gè)availableVideoCVPixelFormatTypes
屬性返回推薦的象素格式夹纫,并且availableVideoCodecTypes
屬性返回支持的值。Core Graphics和OpenGL都支持BGRA格式:
AVCaptureVideoDataOutput *videoDataOutput = [AVCaptureVideoDataOutput new];
NSDictionary *newSettings =
@{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA) };
videoDataOutput.videoSettings = newSettings;
// discard if the data output queue is blocked (as we process the still image
[videoDataOutput setAlwaysDiscardsLateVideoFrames:YES];)
// create a serial dispatch queue used for the sample buffer delegate as well as when a still image is captured
// a serial dispatch queue must be used to guarantee that video frames will be delivered in order
// see the header doc for setSampleBufferDelegate:queue: for more information
videoDataOutputQueue = dispatch_queue_create("VideoDataOutputQueue", DISPATCH_QUEUE_SERIAL);
[videoDataOutput setSampleBufferDelegate:self queue:videoDataOutputQueue];
AVCaptureSession *captureSession = <#The Capture Session#>;
if ( [captureSession canAddOutput:videoDataOutput] )
[captureSession addOutput:videoDataOutput];
處理視頻的性能考慮
您應(yīng)該將session output設(shè)置為應(yīng)用程序的最低實(shí)際分辨率设凹。 將輸出設(shè)置為比必要的更高分辨率浪費(fèi)處理周期捷凄,并且不必要地消耗功率。
你必須確保你的captureOutput:didOutputSampleBuffer:fromConnection:
實(shí)現(xiàn)能夠在分配給一個(gè)幀的時(shí)間量?jī)?nèi)處理sample buffered围来。如果你花了太長(zhǎng)的時(shí)間并且持有視頻幀跺涤,AV Foundation就會(huì)停止遞送視頻幀匈睁,不只是你的delegate,還有其它輸入,比如:preview layer(預(yù)覽)桶错。
你可以使用capture video data ouputs的minFrameDuration
屬性去確保你有足夠的時(shí)間去處理一幀航唆,以更小的幀率來(lái)代替。你也可能要確保alwaysDiscardsLateVideoFrames
屬性為true
(默認(rèn))院刁。這個(gè)屬性保證任何遲來(lái)的視頻幀都將被丟棄糯钙,而不是處理它。相反的退腥,如果你正在錄制任岸,不介意輸出幀有點(diǎn)遲并且最好是全部保留,你要設(shè)置這個(gè)屬性為false
狡刘。這是并不意味著不會(huì)丟失幀(幀也是依舊會(huì)被丟棄)享潜,但是它們不會(huì)被過(guò)早或過(guò)于高效的丟棄。
獲取靜態(tài)圖片
如果你想要一個(gè)帶有metadata的靜態(tài)圖片嗅蔬,你可以使用AVCaptureStillImageOutput
輸出剑按。這張圖片的分辨率依賴于session的preset,還有設(shè)備。
象素和編碼格式
不同的設(shè)備支持不同的圖片格式澜术。你可以使用availableImageDataCVPixelFormatTypes
和availableImageDataCodecTypes
找出這個(gè)設(shè)備支持什么象素和編解碼器類(lèi)型艺蝴。每個(gè)方法都會(huì)返回一個(gè)特定設(shè)備的支持類(lèi)型數(shù)組。你可以設(shè)置outputSettings
dictionary,指明哪個(gè)圖片格式是你需要的鸟废。如下:
AVCaptureStillImageOutput *stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
NSDictionary *outputSettings = @{ AVVideoCodecKey : AVVideoCodecJPEG};
[stillImageOutput setOutputSettings:outputSettings];
如果你要獲取JPEG圖片猜敢,你通常不應(yīng)該指明你自己的壓縮格式。相反 盒延,你應(yīng)該讓靜態(tài)圖片output為你做壓縮锣枝,因?yàn)樗膲嚎s是硬件加速的。如果你需要圖片的數(shù)據(jù)代表兰英,你可以使用jpegStillImageNSDataRepresentation:
得到一個(gè)NSData
對(duì)象而不重新壓縮數(shù)據(jù)撇叁,即使你修改圖片的metadata。
獲取圖片
當(dāng)你要獲取一張圖片時(shí)畦贸,你要發(fā)送captureStillImageAsynchronouslyFromConnection:completionHandler:
方法給output陨闹。第一個(gè)形參是你在capture里用的connection.你需要的是查看一下connection里哪個(gè)輸入端口是收集視頻的:
AVCaptureConnection *videoConnection = nil;
for (AVCaptureConnection *connection in stillImageOutput.connections) {
for (AVCaptureInputPort *port in [connection inputPorts]) {
if ([[port mediaType] isEqual:AVMediaTypeVideo] ) {
videoConnection = connection;
break;
}
}
if (videoConnection) { break; }
}
第二個(gè)形參是一個(gè)block
,它帶了兩個(gè)參數(shù):CMSampleBuffer
類(lèi)型包含了image data,還有一個(gè)error
。sample buffer本身可以包含諸如EXIF字典的元數(shù)據(jù)作為附件薄坏。您可以根據(jù)需要修改附件趋厉,但請(qǐng)注意像素和編碼格式中討論的JPEG圖像的優(yōu)化。
[stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler:
^(CMSampleBufferRef imageSampleBuffer, NSError *error) {
CFDictionaryRef exifAttachments =
CMGetAttachment(imageSampleBuffer, kCGImagePropertyExifDictionary, NULL);
if (exifAttachments) {
// Do something with the attachments.
}
// Continue as appropriate.
}];
展示給用戶錄制了什么
你可以為用戶提供錄制東西的預(yù)覽胶坠,比如用preview layer預(yù)覽攝像頭錄制的東西君账,或跟蹤麥克風(fēng)錄制的聲音軌道。
Video Preview
你可以通過(guò)AVCaptureVideoPreviewLayer
對(duì)象預(yù)覽錄制的東西沈善。AVCaptureVideoPreviewLayer
是CALayer
的子類(lèi)乡数。你不需要任何outputs去展示預(yù)覽椭蹄。使用AVCaptureVideoDataOutput類(lèi)為客戶端應(yīng)用程序提供訪問(wèn)視頻像素的能力,然后才能呈現(xiàn)給用戶净赴。
不像capture output绳矩,一個(gè)視頻preview layer保持了一個(gè)強(qiáng)引用到 相關(guān)的session。這就保證了當(dāng)preview layer還在顯示視頻時(shí)玖翅,session不會(huì)被釋放翼馆。這就是初始化preview layer就反映出來(lái)了:
AVCaptureSession *captureSession = <#Get a capture session#>;
CALayer *viewLayer = <#Get a layer from the view in which you want to present the preview#>;
AVCaptureVideoPreviewLayer *captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:captureSession];
[viewLayer addSublayer:captureVideoPreviewLayer];
通常來(lái)說(shuō),preview layer在渲染樹(shù)的行為就像其它的CALayer對(duì)象一樣(可以查看Core Animation Programming Guide).你可像任何Layer一樣縮放圖像并執(zhí)行轉(zhuǎn)換,旋轉(zhuǎn)等操作.一個(gè)不同點(diǎn)是你需要設(shè)置layer的orientation
屬性,去表明怎么旋轉(zhuǎn)從攝像頭過(guò)來(lái)的圖像.另外,你可以通過(guò)查詢supportsVideoMirroring
屬性來(lái)測(cè)試視頻鏡像的設(shè)備支持.盡管當(dāng)automaticallyAdjustsVideoMirroring
屬性被 設(shè)為true
(默認(rèn)的),這個(gè)鏡像值會(huì)在session配置的基礎(chǔ)上自動(dòng)設(shè)置,但你還是可以在需要時(shí)設(shè)置videoMirrored
屬性.
視頻重力模式
preview layer支持你用videoGravity
設(shè)置的三種重力模式:
-
AVLayerVideoGravityResizeAspect
:保留高寬比,把視頻未填充的地方顯示黑條. -
AVLayerVideoGravityResizeAspectFill
:保留高寬比,但填充所有的有效屏幕區(qū)域,必要時(shí)可以裁剪視頻. -
AVLayerVideoGravityResize
:這簡(jiǎn)單地延伸視頻以填充可用的屏幕區(qū)域金度,即使這樣做使扭曲圖像
在preview里使用"點(diǎn)擊聚焦"
實(shí)施點(diǎn)對(duì)焦與預(yù)覽圖層時(shí)应媚,您需要注意。 您必須考慮圖層的預(yù)覽方向和重力猜极,以及可能鏡像預(yù)覽的可能性中姜。 請(qǐng)參閱示例代碼項(xiàng)目AVCam-iOS:Using AVFoundation to Capture Images and Movies 以實(shí)現(xiàn)此功能
展示聲音電平
在capture connection里要跟蹤聲音頻道的平均和峰值電平,你要使用AVCaptureAudioChannel
對(duì)象.因?yàn)槁曇綦娖讲皇荎VO(key-value observabel),所以你必須要像更新用戶界面一樣輪詢更新電平(比如:1秒10次).
AVCaptureAudioDataOutput *audioDataOutput = <#Get the audio data output#>;
NSArray *connections = audioDataOutput.connections;
if ([connections count] > 0) {
// There should be only one connection to an AVCaptureAudioDataOutput.
AVCaptureConnection *connection = [connections objectAtIndex:0];
NSArray *audioChannels = connection.audioChannels;
for (AVCaptureAudioChannel *channel in audioChannels) {
float avg = channel.averagePowerLevel;
float peak = channel.peakHoldLevel;
// Update the level meter user interface.
}
}
把它們放到集合起來(lái):獲取視頻幀作為 UIImage 對(duì)象
下面的例子展示了你怎么樣獲取視頻并把幀轉(zhuǎn)成你要的UIImage對(duì)象.
- 創(chuàng)建一個(gè)
AVCaptureSession
對(duì)象來(lái)協(xié)調(diào)AV輸入設(shè)備到輸出的數(shù)據(jù)流. - 找一個(gè)
AVCaptureDevice
對(duì)象作為你想要的輸入類(lèi)型 - 為輸入設(shè)備創(chuàng)建一個(gè)
AVCaptureDeviceInput
對(duì)象 - 創(chuàng)建
AVCaptureVideoDataOutput
對(duì)象來(lái)產(chǎn)生視頻幀 - 實(shí)現(xiàn)
AVCaputreVideoDataOutput
對(duì)象的delegate
來(lái)處理視頻幀 - 實(shí)現(xiàn)一個(gè)把從
delegate
接收到的CMSampleBuffer
轉(zhuǎn)成UIImage
對(duì)象的方法.
創(chuàng)建和配置一個(gè)Capture Session
創(chuàng)建一個(gè)AVCaptureSession
對(duì)象來(lái)協(xié)調(diào)AV輸入設(shè)備到輸出的數(shù)據(jù)流,并配置它來(lái)產(chǎn)生中等分辨率的視頻幀.
AVCaptureSession *session = [[AVCaptureSession alloc] init];
session.sessionPreset = AVCaptureSessionPresetMedium;
創(chuàng)建并配置Device和Device Input
用AVCaptureDevice
對(duì)象來(lái)代表Capture devices;這個(gè)類(lèi)提供了一些方法,可以用來(lái)接收你想要的輸入類(lèi)型的對(duì)象.一個(gè)設(shè)備可以有多個(gè)端口,可以用AVCaptureInput
對(duì)象來(lái)配置.通常,你都會(huì)用它的默認(rèn)配置來(lái)配置你使用Capture Input.
找一個(gè)視頻捕獲Device,并創(chuàng)建為這個(gè)Device創(chuàng)建一個(gè)Device Input,再加到session里.如果找不到 合適的設(shè)備,那時(shí)deviceInputWithDevice:error:
方法就會(huì)返回一個(gè)error
引用
AVCaptureDevice *device =
[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
NSError *error = nil;
AVCaptureDeviceInput *input =
[AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if (!input) {
// Handle the error appropriately.
}
[session addInput:input];
創(chuàng)建并配置Video data output
你使用AVCaptureVideoDataOutput
對(duì)象來(lái)處理捕獲視頻里非壓縮的幀.你通常會(huì)為output配置多個(gè)方面.對(duì)于視頻來(lái)說(shuō),比如,你會(huì)用VideoSettings
屬性來(lái)指明象素格式,并且會(huì)通過(guò)設(shè)置minFrameDuration
屬性來(lái)限制幀速率.
為一個(gè)視頻數(shù)據(jù)創(chuàng)建并配置output,并把它加到session;通過(guò)設(shè)置minFrameDuration
屬性為1/15秒,來(lái)限制幀速率和15fps:
AVCaptureVideoDataOutput *output = [[AVCaptureVideoDataOutput alloc] init];
[session addOutput:output];
output.videoSettings =
@{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA) };
output.minFrameDuration = CMTimeMake(1, 15);
這個(gè)data output對(duì)象使用delegate
來(lái)發(fā)送視頻幀.這個(gè)delegate
必須采用AVCaptureVideoDataOutputSampleBufferDelegate
協(xié)議.當(dāng)你設(shè)置data output的delegate
時(shí),你必須要提供一個(gè)queue來(lái)調(diào)用回調(diào)方法:
dispatch_queue_t queue = dispatch_queue_create("MyQueue", NULL);
[output setSampleBufferDelegate:self queue:queue];
dispatch_release(queue);
你可以使用queue來(lái)修改正在發(fā)送和處理中的視頻幀的優(yōu)先級(jí).
實(shí)現(xiàn)Sample Buffer的代理方法
在delegate class里,實(shí)現(xiàn)captureOutput:didOutputSampleBuffer:fromConnection:
方法,這個(gè)方法只有在sample buffer被寫(xiě)入時(shí)才被調(diào)用.video output對(duì)象以CMSampleBuffer
類(lèi)型的形式發(fā)送幀,所有你需要把CMSampleBuffer
opaque type轉(zhuǎn)成UIImage
對(duì)象.
- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection *)connection {
UIImage *image = imageFromSampleBuffer(sampleBuffer);
// Add your code here that uses the image.
}
// Create a UIImage from sample buffer data
- (UIImage *) imageFromSampleBuffer:(CMSampleBufferRef) sampleBuffer
{
// Get a CMSampleBuffer's Core Video image buffer for the media data
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
// Lock the base address of the pixel buffer
CVPixelBufferLockBaseAddress(imageBuffer, 0);
// Get the number of bytes per row for the pixel buffer
void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer);
// Get the number of bytes per row for the pixel buffer
size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);
// Get the pixel buffer width and height
size_t width = CVPixelBufferGetWidth(imageBuffer);
size_t height = CVPixelBufferGetHeight(imageBuffer);
// Create a device-dependent RGB color space
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
// Create a bitmap graphics context with the sample buffer data
CGContextRef context = CGBitmapContextCreate(baseAddress, width, height, 8,
bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
// Create a Quartz image from the pixel data in the bitmap graphics context
CGImageRef quartzImage = CGBitmapContextCreateImage(context);
// Unlock the pixel buffer
CVPixelBufferUnlockBaseAddress(imageBuffer,0);
// Free up the context and color space
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
// Create an image object from the Quartz image
UIImage *image = [UIImage imageWithCGImage:quartzImage];
// Release the Quartz image
CGImageRelease(quartzImage);
return (image);
}
開(kāi)始和結(jié)束錄制
在配置capture session結(jié)束后,你應(yīng)該確保相機(jī)有權(quán)限根據(jù)用戶的喜好進(jìn)行錄制.
NSString *mediaType = AVMediaTypeVideo;
[AVCaptureDevice requestAccessForMediaType:mediaType completionHandler:^(BOOL granted) {
if (granted)
{
//Granted access to mediaType
[self setDeviceAuthorized:YES];
}
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];
});
}
}];
如果攝像機(jī)session被配置完成,并且用戶允許去訪問(wèn)攝像頭(必要時(shí),可以是麥克風(fēng)),發(fā)送一個(gè)startRunning
消息去開(kāi)始錄制.
重要:這個(gè)
startRunning
方法是一個(gè)阻塞式的調(diào)用,可能會(huì)耗費(fèi)一點(diǎn)時(shí)間.因此,你應(yīng)該在一個(gè)串行隊(duì)列(serial queue)里執(zhí)行session的配置,讓main queue不阻塞,保持UI的及時(shí)響應(yīng).權(quán)威的實(shí)現(xiàn)你可以看AVCam-iOS: Using AVFoundation to Capture Images and Movies
[session startRunning];
要停止錄制,你可以發(fā)送stopRunning
消息給session.
視頻采集的高幀速率
整個(gè)AVFoundation框架支持高幀速率內(nèi)容.
你要用AVCaptureDeviceFormat
類(lèi)來(lái)查明設(shè)備的采集能力.此類(lèi)具有返回支持的媒體類(lèi)型扯键,幀速率,視場(chǎng)任洞,最大縮放因子查牌,是否支持視頻穩(wěn)定等的方法.
- Capture支持全屏720p(1280 x 720像素)分辨率,每秒60幀(fps)芥玉,包括視頻穩(wěn)定和可放置的P幀(H264編碼電影的功能,即使在較慢和較舊的硬件上也能讓電影播放順暢。 )
- playback增強(qiáng)了對(duì)慢速和快速播放的音頻支持辞色,從而可以以更慢或更快的速度保留音頻的時(shí)間間隔
- 編輯完全支持可變組合中的縮放編輯
- 當(dāng)支持60 fps電影時(shí)導(dǎo)出提供兩個(gè)選項(xiàng)。 可以保留可變幀速率浮定,慢速或快速運(yùn)動(dòng)相满,或者將影片轉(zhuǎn)換為任意較慢的幀速率,例如每秒30幀
播放
一個(gè)AVPlayer
實(shí)例通過(guò)設(shè)置setRate:
方法管理著大部分的播放速度.這個(gè)值被用作多倍播放速度.值1.0表示正常的播放速度,0.5表示一半的播放速度,5.0表示比正常播放快5倍的速度,等等.
AVPlayerItem
對(duì)象支持audioTimePitchAlgorithm
屬性.這屬性表明了當(dāng)電影在多個(gè)幀速度下播放時(shí),聲音是怎么播放的.使用Time Pitch Algorithm Settings常量.
Time pitch algorithm | Quality | Snaps to specific frame rate | Rate range |
---|---|---|---|
AVAudioTimePitchAlgorithmLowQualityZeroLatency | Low quality,suitable for fast-forward, rewind, or low quality voice. | YES | 0.5, 0.666667, 0.8, 1.0, 1.25, 1.5, 2.0 rates. |
AVAudioTimePitchAlgorithmTimeDomain | Modest quality, less expensive computationally, suitable for voice. | NO | 0.5–2x rates. |
AVAudioTimePitchAlgorithmSpectral | Highest quality, most expensive computationally, preserves the pitch of the original item. | NO | 1/32–32 rates. |
AVAudioTimePitchAlgorithmVarispeed | High-quality playback with no pitch correction. | NO | 1/32–32 rates. |