AVFoundatioin-獲取靜態(tài)和動(dòng)態(tài)媒體數(shù)據(jù)

內(nèi)容來(lái)源

https://developer.apple.com/library/content/documentation/AudioVideo/Conceptual/AVFoundationPG/Articles/04_MediaCapture.html#//apple_ref/doc/uid/TP40010188-CH5-SW4

要管理從設(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è)AVCaptureVideoPreviewLayerCALayer的子類(lèi))的實(shí)例

你可以用一個(gè)session來(lái)協(xié)調(diào)多個(gè)inputs和outputs.如下圖:


 A single session can configure multiple inputs and outputs
A single session can configure multiple inputs and 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ì)象表示蝉仇。

 AVCaptureConnection represents a connection between an input and output
AVCaptureConnection represents a connection between an input and output

你可以用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)該把你的改變包含在beginConfigurationcommitConfiguration方法之間党晋。這兩個(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屬性去看一下它是否中斷凡傅。額外的辟狈,runninginterrupted屬性也是兼容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 devicesdevicesWithMediaType:來(lái)找出哪個(gè)捕獲設(shè)備是當(dāng)前有效的佣蓉。還有煮纵,如果需要,你也可以找出iPhone, iPad,iPod提供了什么功能偏螺。有效的設(shè)備列表是可能會(huì)變的。當(dāng)前有輸入設(shè)備也有可能變成無(wú)效的(如果被其它的app使用)匆光。并且新的輸入設(shè)備有可能變成有效的(如果它們被其它app放開(kāi))套像。你應(yīng)該注冊(cè)并接收AVCaptureDeviceWasConnectedNotificationAVCaptureDeviceWasDisconnectedNotification,當(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)該把你的配置修改用beginConfigurationcommitConfiguration來(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è)你錄制的限制蹬挺,比如:AVErrorMaximumDurationReachedAVErrorMaximumFileSizeReached.其它停止錄制的原因:

  • 磁盤(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è)備支持不同的圖片格式澜术。你可以使用availableImageDataCVPixelFormatTypesavailableImageDataCodecTypes找出這個(gè)設(shè)備支持什么象素和編解碼器類(lèi)型艺蝴。每個(gè)方法都會(huì)返回一個(gè)特定設(shè)備的支持類(lèi)型數(shù)組。你可以設(shè)置outputSettingsdictionary,指明哪個(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ù)覽錄制的東西沈善。AVCaptureVideoPreviewLayerCALayer的子類(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ā)送幀,所有你需要把CMSampleBufferopaque 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.

Editing

Export

Recording

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末桦卒,一起剝皮案震驚了整個(gè)濱河市立美,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌方灾,老刑警劉巖建蹄,帶你破解...
    沈念sama閱讀 219,366評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異裕偿,居然都是意外死亡洞慎,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)嘿棘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)劲腿,“玉大人,你說(shuō)我怎么就攤上這事鸟妙〗谷耍” “怎么了挥吵?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,689評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)垃瞧。 經(jīng)常有香客問(wèn)我蔫劣,道長(zhǎng),這世上最難降的妖魔是什么个从? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,925評(píng)論 1 295
  • 正文 為了忘掉前任脉幢,我火速辦了婚禮,結(jié)果婚禮上嗦锐,老公的妹妹穿的比我還像新娘嫌松。我一直安慰自己,他們只是感情好奕污,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布萎羔。 她就那樣靜靜地躺著,像睡著了一般碳默。 火紅的嫁衣襯著肌膚如雪贾陷。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,727評(píng)論 1 305
  • 那天嘱根,我揣著相機(jī)與錄音髓废,去河邊找鬼。 笑死该抒,一個(gè)胖子當(dāng)著我的面吹牛慌洪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播凑保,決...
    沈念sama閱讀 40,447評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼冈爹,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了欧引?” 一聲冷哼從身側(cè)響起频伤,我...
    開(kāi)封第一講書(shū)人閱讀 39,349評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎芝此,沒(méi)想到半個(gè)月后憋肖,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,820評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡癌蓖,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評(píng)論 3 337
  • 正文 我和宋清朗相戀三年瞬哼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片租副。...
    茶點(diǎn)故事閱讀 40,127評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡坐慰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情结胀,我是刑警寧澤赞咙,帶...
    沈念sama閱讀 35,812評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站糟港,受9級(jí)特大地震影響攀操,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜秸抚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評(píng)論 3 331
  • 文/蒙蒙 一速和、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧剥汤,春花似錦颠放、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,017評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至鹿驼,卻和暖如春欲低,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背畜晰。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,142評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工砾莱, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人舷蟀。 一個(gè)月前我還...
    沈念sama閱讀 48,388評(píng)論 3 373
  • 正文 我出身青樓恤磷,卻偏偏與公主長(zhǎng)得像面哼,于是被迫代替她去往敵國(guó)和親野宜。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容