AVFoundation.framework學(xué)習(xí)(2)

AVFoundation.framework學(xué)習(xí)(1)是對(duì)AVFoundation的基本介紹,第二使用Asset和播放功能。
接下來我們開始學(xué)習(xí)編輯等功能

編輯

AVFoundation 框架提供了一組功能豐富的類,以便于編輯asset。AVFoundation編輯的核心是組合。一個(gè)composition 是一個(gè)或者多個(gè)asset的tracks的集合。
AVMutableComposition類提供用于插入和刪除track以及管理時(shí)間順序的接口。

用AVMutableAudioMix 類,我們可以處理在合成的中的音頻。目前,我們可以設(shè)置音軌指定的最大音量或者設(shè)置音量斜坡。

看上面的圖,其實(shí)AVMutableAudioMix 相當(dāng)于插件一樣。來設(shè)置音頻的相關(guān)參數(shù)而已。

我們可以使用AVMutableVideoComposition類直接編輯視頻的tracks.

這個(gè)圖中的AVMutableVideoCompositionInstruction,應(yīng)該這么理解,AVMutableVideoCompositionInstruction是AVMutableVideoComposition類自帶屬性。

對(duì)只有一個(gè)視頻的composition,我們可以輸出視頻指定所需要渲染的大小以及比例或者幀持續(xù)時(shí)間。通過AVMutableVideoCompositionInstruction類,我們可以修改視頻的背景顏色并應(yīng)用到layer層。
AVMutableVideoCompositionLayerInstruction類可以用于變換,變換斜坡,不透明不透明斜坡用于track中。

我們知道composition是一個(gè)音視頻的組合體。打包在一起還是需要AVAssetExportSession類。

創(chuàng)建AVMutableComposition

AVMutableComposition *mutableComposition = [AVMutableComposition composition];
// Create the video composition track.
AVMutableCompositionTrack *mutableCompositionVideoTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
// Create the audio composition track.
AVMutableCompositionTrack *mutableCompositionAudioTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];

創(chuàng)建很簡(jiǎn)單,但是要是想要給AVMutableComposition添加媒體數(shù)據(jù),必須添加一個(gè)活多個(gè)合成track。用AVMutableCompositionTrack標(biāo)示。

初始化的可選參數(shù)

當(dāng)我們需要添加track到AVMutableComposition對(duì)象中的時(shí)候,我們必須提供track的媒體類型和track ID.雖然音頻和視頻是最常用的媒體類型,但是我們也可以指定其他的媒體類型,例如:AVMediaTypeSubtitleAVMediaTypeText

每個(gè)track都有唯一標(biāo)識(shí)符。如果我們指定kCMPersistentTrackID_Invalid作為首選的track id,那么會(huì)自動(dòng)生成與track關(guān)聯(lián)的唯一標(biāo)識(shí)符。

AVMutableComposition 對(duì)象添加數(shù)據(jù)

一旦我們擁有帶有一個(gè)或者多個(gè)track的AVMutableComposition,我們就可以將媒體數(shù)據(jù)添加到響應(yīng)的track中。要將媒體數(shù)據(jù)添加到相應(yīng)的track中,我們就需要訪問asset中的track數(shù)據(jù)。我們可以使用可變的組合track接口將同一類型的多個(gè)track放在同一個(gè)track上。

// You can retrieve AVAssets from a number of places, like the camera roll for example.
AVAsset *videoAsset = <#AVAsset with at least one video track#>;
AVAsset *anotherVideoAsset = <#another AVAsset with at least one video track#>;
// Get the first video track from each asset.
AVAssetTrack *videoAssetTrack = [[videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
AVAssetTrack *anotherVideoAssetTrack = [[anotherVideoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
// Add them both to the composition.
[mutableCompositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero,videoAssetTrack.timeRange.duration) ofTrack:videoAssetTrack atTime:kCMTimeZero error:nil];
[mutableCompositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero,anotherVideoAssetTrack.timeRange.duration) ofTrack:anotherVideoAssetTrack atTime:videoAssetTrack.timeRange.duration error:nil];

上面的例子只是實(shí)現(xiàn)了兩個(gè)視頻資源的連接播放。

track對(duì)象的兼容性檢查

一般情況下,每種媒體類型只有一個(gè)合成軌道。兼容的asset track可以降低資源的使用。我們?cè)诤喜⒚襟w類型的時(shí)候,應(yīng)該將相同類型的track放在同一個(gè)track上進(jìn)行合并。我們可以查詢AVMutableComposition 對(duì)象是否存在可以與所需asset相兼容的track。

AVMutableCompositionTrack *compatibleCompositionTrack = [mutableComposition mutableTrackCompatibleWithTrack:<#the AVAssetTrack you want to insert#>];
if (compatibleCompositionTrack) {
    // Implementation continues.
}

注意:在同一個(gè)軌道上放置多個(gè)視頻片段可能會(huì)導(dǎo)致視頻片段之間轉(zhuǎn)換播放過程中丟幀,尤其是在嵌入式設(shè)備上。

生成聲音ramp(滑坡)- 我個(gè)人理解就是調(diào)整音量大小

單個(gè)AVMutableAudioMix對(duì)象可以單獨(dú)的將所有的track進(jìn)行自定義處理。我們可以使用audioMix類方法創(chuàng)建音頻混合,使用AVMutableAudioMixInputParameters類的實(shí)例將音頻混合與合成中特定track相關(guān)聯(lián)。音頻混合可用于改變track的音量。

AVMutableAudioMix *mutableAudioMix = [AVMutableAudioMix audioMix];
// Create the audio mix input parameters object.
AVMutableAudioMixInputParameters *mixParameters = [AVMutableAudioMixInputParameters audioMixInputParametersWithTrack:mutableCompositionAudioTrack];
// Set the volume ramp to slowly fade the audio out over the duration of the composition.
[mixParameters setVolumeRampFromStartVolume:1.f toEndVolume:0.f timeRange:CMTimeRangeMake(kCMTimeZero, mutableComposition.duration)];
// Attach the input parameters to the audio mix.
mutableAudioMix.inputParameters = @[mixParameters];

執(zhí)行自定義視頻處理

與音頻混合一樣,我們只需要一個(gè)AVMutableVideoComposition對(duì)象即可,在合成的視頻track上執(zhí)行所有的自定義視頻處理即可。使用視頻的和傳承,我們可以直接為合成的視頻track設(shè)置適當(dāng)?shù)匿秩敬笮?,比例和幀速率?/p>

更改構(gòu)圖的背景顏色

所有的視頻合成必須具有包含一個(gè)視頻的合成指令的AVVideoCompositionInstruction對(duì)象的數(shù)組。我們可以使用AVMutableVideoCompositionInstruction類來創(chuàng)建自己的視頻合成指令。使用視頻合成指令,我們可以徐工合成的背景顏色,指定是否需要后期處理或者應(yīng)用layer 指令。

AVMutableVideoCompositionInstruction *mutableVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
mutableVideoCompositionInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, mutableComposition.duration);
mutableVideoCompositionInstruction.backgroundColor = [[UIColor redColor] CGColor];
應(yīng)用不透明ramps

視頻合成指令也可以用于用于視頻合成層指令。AVMutableVideoCompositionLayerInstruction對(duì)象可以使用transform,transform ramps,不透明,不透明的ramp,將其合成到某個(gè)視頻的track。視頻合成指令的layerInstructions數(shù)組中的層指令的順序決定了如何在該合成指令的持續(xù)時(shí)間內(nèi)對(duì)來自源track的視頻幀進(jìn)行分層和組合。

AVAsset *firstVideoAssetTrack = <#AVAssetTrack representing the first video segment played in the composition#>;
AVAsset *secondVideoAssetTrack = <#AVAssetTrack representing the second video segment played in the composition#>;
// Create the first video composition instruction.
AVMutableVideoCompositionInstruction *firstVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
// Set its time range to span the duration of the first video track.
firstVideoCompositionInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, firstVideoAssetTrack.timeRange.duration);
// Create the layer instruction and associate it with the composition video track.
AVMutableVideoCompositionLayerInstruction *firstVideoLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:mutableCompositionVideoTrack];
// Create the opacity ramp to fade out the first video track over its entire duration.
[firstVideoLayerInstruction setOpacityRampFromStartOpacity:1.f toEndOpacity:0.f timeRange:CMTimeRangeMake(kCMTimeZero, firstVideoAssetTrack.timeRange.duration)];
// Create the second video composition instruction so that the second video track isn't transparent.
AVMutableVideoCompositionInstruction *secondVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
// Set its time range to span the duration of the second video track.
secondVideoCompositionInstruction.timeRange = CMTimeRangeMake(firstVideoAssetTrack.timeRange.duration, CMTimeAdd(firstVideoAssetTrack.timeRange.duration, secondVideoAssetTrack.timeRange.duration));
// Create the second layer instruction and associate it with the composition video track.
AVMutableVideoCompositionLayerInstruction *secondVideoLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:mutableCompositionVideoTrack];
// Attach the first layer instruction to the first video composition instruction.
firstVideoCompositionInstruction.layerInstructions = @[firstVideoLayerInstruction];
// Attach the second layer instruction to the second video composition instruction.
secondVideoCompositionInstruction.layerInstructions = @[secondVideoLayerInstruction];
// Attach both of the video composition instructions to the video composition.
AVMutableVideoComposition *mutableVideoComposition = [AVMutableVideoComposition videoComposition];
mutableVideoComposition.instructions = @[firstVideoCompositionInstruction, secondVideoCompositionInstruction];

以下代碼片段顯示如何在轉(zhuǎn)換到第二個(gè)視頻之前設(shè)置不透明度漸變以慢慢淡出合成中的第一個(gè)視頻

結(jié)合core Animation 效果

視頻合成可以通過animationTool屬性將CoreAnimation的強(qiáng)大功能添加到合成中。通過此動(dòng)畫工具,我們可以完成諸如水印視頻和添加標(biāo)題活動(dòng)畫疊加層等任務(wù)。核心動(dòng)畫可以通過通過兩種不同的方式用于視頻合成:我們可以將核心動(dòng)畫的layer添加到期自己的合成軌跡,或者我們可以直接將合成動(dòng)畫效果渲染到合成的視頻幀中。

CALayer *watermarkLayer = <#CALayer representing your desired watermark image#>;
CALayer *parentLayer = [CALayer layer];
CALayer *videoLayer = [CALayer layer];
parentLayer.frame = CGRectMake(0, 0, mutableVideoComposition.renderSize.width, mutableVideoComposition.renderSize.height);
videoLayer.frame = CGRectMake(0, 0, mutableVideoComposition.renderSize.width, mutableVideoComposition.renderSize.height);
[parentLayer addSublayer:videoLayer];
watermarkLayer.position = CGPointMake(mutableVideoComposition.renderSize.width/2, mutableVideoComposition.renderSize.height/4);
[parentLayer addSublayer:watermarkLayer];
mutableVideoComposition.animationTool = [AVVideoCompositionCoreAnimationTool videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer];

將上面的技術(shù)組合成完全的例子

我們將兩個(gè)視頻asset track和一個(gè)音頻 asset track組合來創(chuàng)建單個(gè)視頻,步驟如下:

  • 1.創(chuàng)建AVMutableComposition對(duì)象并添加多個(gè)AVMutableCompositionTrack對(duì)象
  • 2.將AVAssetTrack對(duì)象的時(shí)間范圍添加到組合的track中
  • 3.檢查視頻asset track的preferredTransform屬性以確定視頻的方向
    1. AVMutableVideoCompositionLayerInstruction對(duì)象將transform應(yīng)用于合成的視頻track。
  • 5.為視頻合成的renderSize和frameDuration設(shè)置適當(dāng)?shù)闹祵?dǎo)出到視頻文件時(shí),將合成與視頻合成結(jié)合使用
  • 6.保存視頻文件到相機(jī)膠卷,也可以是本地啦。

下列是完整代碼

-(void)edit{
    
    ///1
    AVMutableComposition *mutableComposition = [AVMutableComposition composition];
    AVMutableCompositionTrack *videoCompositionTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
    AVMutableCompositionTrack *audioCompositionTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
    ///2
    AVURLAsset *firstVideoAsset = [self getAVAssetA];
    AVURLAsset *secondVideoAsset = [self getAVAssetABC];
    AVURLAsset * audioAsset = [self getAVASssetAAA];
    AVAssetTrack *firstVideoAssetTrack = [[firstVideoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
    AVAssetTrack *secondVideoAssetTrack = [[secondVideoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
    AVAssetTrack *audioAssetTrack = [[audioAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];
    
    [videoCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, firstVideoAssetTrack.timeRange.duration) ofTrack:firstVideoAssetTrack atTime:kCMTimeZero error:nil];
    [videoCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, secondVideoAssetTrack.timeRange.duration) ofTrack:secondVideoAssetTrack atTime:firstVideoAssetTrack.timeRange.duration error:nil];
    [audioCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, CMTimeAdd(firstVideoAssetTrack.timeRange.duration, secondVideoAssetTrack.timeRange.duration)) ofTrack:audioAssetTrack atTime:kCMTimeZero error:nil];
    ///3
    BOOL isFirstVideoPortrait = NO;
    CGAffineTransform firstTransform = firstVideoAssetTrack.preferredTransform;
    // Check the first video track's preferred transform to determine if it was recorded in portrait mode.
    if (firstTransform.a == 0 && firstTransform.d == 0 && (firstTransform.b == 1.0 || firstTransform.b == -1.0) && (firstTransform.c == 1.0 || firstTransform.c == -1.0)) {
        isFirstVideoPortrait = YES;
    }
    BOOL isSecondVideoPortrait = NO;
    CGAffineTransform secondTransform = secondVideoAssetTrack.preferredTransform;
    // Check the second video track's preferred transform to determine if it was recorded in portrait mode.
    if (secondTransform.a == 0 && secondTransform.d == 0 && (secondTransform.b == 1.0 || secondTransform.b == -1.0) && (secondTransform.c == 1.0 || secondTransform.c == -1.0)) {
        isSecondVideoPortrait = YES;
    }
    if ((isFirstVideoPortrait && !isSecondVideoPortrait) || (!isFirstVideoPortrait && isSecondVideoPortrait)) {
        UIAlertView *incompatibleVideoOrientationAlert = [[UIAlertView alloc] initWithTitle:@"Error!" message:@"Cannot combine a video shot in portrait mode with a video shot in landscape mode." delegate:self cancelButtonTitle:@"Dismiss" otherButtonTitles:nil];
        [incompatibleVideoOrientationAlert show];
        return;
    }
    ///4
    AVMutableVideoCompositionInstruction *firstVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
    // Set the time range of the first instruction to span the duration of the first video track.
    firstVideoCompositionInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, firstVideoAssetTrack.timeRange.duration);
    AVMutableVideoCompositionInstruction * secondVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
    // Set the time range of the second instruction to span the duration of the second video track.
    secondVideoCompositionInstruction.timeRange = CMTimeRangeMake(firstVideoAssetTrack.timeRange.duration, CMTimeAdd(firstVideoAssetTrack.timeRange.duration, secondVideoAssetTrack.timeRange.duration));
    
    AVMutableVideoCompositionLayerInstruction *firstVideoLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoCompositionTrack];
    // Set the transform of the first layer instruction to the preferred transform of the first video track.
    [firstVideoLayerInstruction setTransform:firstTransform atTime:kCMTimeZero];
    AVMutableVideoCompositionLayerInstruction *secondVideoLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoCompositionTrack];
    // Set the transform of the second layer instruction to the preferred transform of the second video track.
    [secondVideoLayerInstruction setTransform:secondTransform atTime:firstVideoAssetTrack.timeRange.duration];
    firstVideoCompositionInstruction.layerInstructions = @[firstVideoLayerInstruction];
    secondVideoCompositionInstruction.layerInstructions = @[secondVideoLayerInstruction];
    AVMutableVideoComposition *mutableVideoComposition = [AVMutableVideoComposition videoComposition];
    mutableVideoComposition.instructions = @[firstVideoCompositionInstruction, secondVideoCompositionInstruction];
    ///5
    CGSize naturalSizeFirst, naturalSizeSecond;
    // If the first video asset was shot in portrait mode, then so was the second one if we made it here.
    if (isFirstVideoPortrait) {
        // Invert the width and height for the video tracks to ensure that they display properly.
        naturalSizeFirst = CGSizeMake(firstVideoAssetTrack.naturalSize.height, firstVideoAssetTrack.naturalSize.width);
        naturalSizeSecond = CGSizeMake(secondVideoAssetTrack.naturalSize.height, secondVideoAssetTrack.naturalSize.width);
    }
    else {
        // If the videos weren't shot in portrait mode, we can just use their natural sizes.
        naturalSizeFirst = firstVideoAssetTrack.naturalSize;
        naturalSizeSecond = secondVideoAssetTrack.naturalSize;
    }
    float renderWidth, renderHeight;
    // Set the renderWidth and renderHeight to the max of the two videos widths and heights.
    if (naturalSizeFirst.width > naturalSizeSecond.width) {
        renderWidth = naturalSizeFirst.width;
    }
    else {
        renderWidth = naturalSizeSecond.width;
    }
    if (naturalSizeFirst.height > naturalSizeSecond.height) {
        renderHeight = naturalSizeFirst.height;
    }
    else {
        renderHeight = naturalSizeSecond.height;
    }
    mutableVideoComposition.renderSize = CGSizeMake(renderWidth, renderHeight);
    // Set the frame duration to an appropriate value (i.e. 30 frames per second for video).
    mutableVideoComposition.frameDuration = CMTimeMake(1,30);
    ///6
    // Create a static date formatter so we only have to initialize it once.
    static NSDateFormatter *kDateFormatter;
    if (!kDateFormatter) {
        kDateFormatter = [[NSDateFormatter alloc] init];
        kDateFormatter.dateStyle = NSDateFormatterMediumStyle;
        kDateFormatter.timeStyle = NSDateFormatterShortStyle;
    }
    // Create the export session with the composition and set the preset to the highest quality.
    AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:mutableComposition presetName:AVAssetExportPresetHighestQuality];
    // Set the desired output URL for the file created by the export process.
    exporter.outputURL = [NSURL fileURLWithPath:self.path];
    // Set the output file type to be a QuickTime movie.
    exporter.outputFileType = AVFileTypeQuickTimeMovie;
    exporter.shouldOptimizeForNetworkUse = YES;
    exporter.videoComposition = mutableVideoComposition;
    // Asynchronously export the composition to a video file and save this file to the camera roll once export completes.
    [exporter exportAsynchronouslyWithCompletionHandler:^{
        switch ([exporter status]) {
            case AVAssetExportSessionStatusFailed:
                NSLog(@"%@",[exporter error]);
                NSLog(@"Export failed: %@", [[exporter error] localizedDescription]);
                break;
            case AVAssetExportSessionStatusCancelled:
                NSLog(@"Export canceled");
                break;
            case AVAssetExportSessionStatusCompleted:{
                dispatch_async(dispatch_get_main_queue(), ^{
         
                });
            }
            default:
                
                break;
        }
    }];
}

上述代碼親測(cè)有效,不過就是導(dǎo)出到文件的過程很慢,大家在導(dǎo)出的回調(diào)打斷點(diǎn)可能需要幾分鐘的時(shí)間才能回調(diào)回來
我用了兩個(gè)mp4 文件和一個(gè)mp3 文件
這里必須保持mp4 文件的方向一樣才行

總結(jié)下上述代碼結(jié)構(gòu)


上述代碼沒涉及到水印,和音頻編輯。


靜態(tài)和視頻媒體捕獲

上一篇大概只是簡(jiǎn)單的講解了這部分,下面詳細(xì)講解下

要管理來自設(shè)備(如攝像頭或者麥克風(fēng))的捕獲,您需要組裝對(duì)象以表示輸入和輸出,并使用AVCaptureSession來協(xié)調(diào)他們之間的數(shù)據(jù)流?;玖鞒倘缦拢?/p>

  • 1.初始化對(duì)象 AVCaptureDevice,該對(duì)象代表輸入設(shè)備,例如攝像頭和麥克風(fēng)
  • 2.AVCaptureInput的子類用于配置輸入設(shè)備的端口
  • 3.AVCaptureOutput 的具體子類,用于管理電影文件或者靜止圖像的輸出
  • 4.初始化AVCaptureSession對(duì)象,用于協(xié)調(diào)從輸入到輸出的數(shù)據(jù)流。
配置多個(gè)輸入輸出源的效果圖

我們可以用一個(gè)捕獲連接來開啟或者關(guān)閉從輸入到輸出設(shè)備的數(shù)據(jù)流。我們還可以使用連接來監(jiān)控音頻通道中的平均功率和峰值功率。

媒體捕獲不能同時(shí)支持前置攝像頭和后置攝像頭

使用捕獲會(huì)話來協(xié)調(diào)數(shù)據(jù)流

一個(gè)AVCaptureSession對(duì)象用于管理數(shù)據(jù)捕獲的中央?yún)f(xié)調(diào)對(duì)象,理解為指揮員吧。我們可以用該對(duì)象來協(xié)調(diào)從輸入設(shè)備到輸出設(shè)備的數(shù)據(jù)流。我們只需要將所需要的捕獲設(shè)備和輸出設(shè)備添加到AVCaptureSession對(duì)象中,然后通過調(diào)用startRunning來啟動(dòng)數(shù)據(jù)流,發(fā)送stopRunning 來停止數(shù)據(jù)流。

AVCaptureSession *session = [[AVCaptureSession alloc] init];
// Add inputs and outputs.
[session startRunning];

配置 AVCaptureSession對(duì)象

我們通過AVCaptureSession對(duì)象可以設(shè)置圖像的質(zhì)量和分辨率。預(yù)設(shè)是一個(gè)常數(shù),用于便是多種配置中的一種;具體配置是根據(jù)需求而來的。

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è)定上述任一的幀大小配置,我們需要設(shè)置之前判斷該設(shè)備是否支持

if ([session canSetSessionPreset:AVCaptureSessionPreset1280x720]) {
    session.sessionPreset = AVCaptureSessionPreset1280x720;
}
else {
    // Handle the failure.
}

如果需要調(diào)整比預(yù)設(shè)更精細(xì)級(jí)別的會(huì)話參數(shù),或者您需要對(duì)正在運(yùn)行的會(huì)話進(jìn)行更改,則可以使用beginConfiguration和commitConfiguration方法來包圍更改。beginConfiguration和commitConfiguration必須成對(duì)出現(xiàn)。調(diào)用beginConfiguration方法,我們可以添加或者刪除輸出,更改sessionPreset屬性或者配置單個(gè)捕獲輸入和輸出屬性。在調(diào)用commitConfiguration之前其實(shí)不會(huì)進(jìn)行任何更改,調(diào)用該方法就是將其一起應(yīng)用。

[session beginConfiguration];
// Remove an existing capture device.
// Add a new capture device.
// Reset the preset.
[session commitConfiguration];
監(jiān)控捕獲會(huì)話的狀態(tài)

捕獲會(huì)話會(huì)發(fā)送通知,我們可以增加觀察者接收,例如:?jiǎn)?dòng)和停止運(yùn)行或者中斷的通知。如果發(fā)生運(yùn)行時(shí)錯(cuò)誤,我們可以注冊(cè)AVCaptureSessionRuntimeErrorNotification通知。我們還可以詢問會(huì)話的運(yùn)行屬性以查明它是否正在運(yùn)行,以及它的中斷屬性以查明它是否被中斷。除此之外,運(yùn)行和中斷屬性都符合KVO,并且通知在主線程上。

AVCaptureDevice對(duì)象代表輸入設(shè)備

AVCaptureDevice對(duì)象可以抽象為物理捕獲設(shè)備,該設(shè)備向AVCaptureSession對(duì)象提供輸入數(shù)據(jù),例如音頻或者視頻。每個(gè)輸入設(shè)備都有一個(gè)與之對(duì)應(yīng)的對(duì)象。

您可以使用AVCaptureDevice 的device和devicesWithMediaType方法來獲取當(dāng)前可用的捕獲設(shè)備。但是,有時(shí)候設(shè)備列表可能發(fā)生變化。單錢的輸入設(shè)備可能變的不可用(比如被另一個(gè)設(shè)備使用),并且新的輸入設(shè)備可能變的可用(如果被另一個(gè)設(shè)備放棄)。我們應(yīng)該注冊(cè)接收AVCaptureDeviceWasConnectedNotification和AVCaptureDeviceWasDisconnectedNotification通知,以便在可用設(shè)備列表更改時(shí)候就到通知。

設(shè)備特征

我們可以向設(shè)備詢問其不同的特征。我們也可以使用hasMediaType:和supportsAVaptureSessionPreset來測(cè)試輸入設(shè)備是否提供特定媒體類型或者支持給定的捕獲會(huì)話預(yù)設(shè)。要向用戶提供信息,您可以找到捕獲設(shè)備的位置,以及其本地化化的名稱。如果要顯示捕獲設(shè)備列表以允許用戶選擇一個(gè)捕獲設(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è)備捕獲功能

不同的設(shè)備具有不同的功能,例如,有些設(shè)備可能支持不同的焦點(diǎn)或者閃光功能;有些人可能會(huì)對(duì)一個(gè)聚焦到一點(diǎn)感興趣。

NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
NSMutableArray *torchDevices = [[NSMutableArray alloc] init];
for (AVCaptureDevice *device in devices) {
    [if ([device hasTorch] &&
         [device supportsAVCaptureSessionPreset:AVCaptureSessionPreset640x480]) {
        [torchDevices addObject:device];
    }
}

如果我們發(fā)現(xiàn)多個(gè)設(shè)備符合條件,我們可以讓用戶選擇他們想要使用的設(shè)備。向用戶顯示設(shè)備的描述,可以使用localizedName屬性。

我們以類似的方式使用各種不同的功能。有常量指定的特定模式,我們可以詢問設(shè)備是否支持特定模式。在某些情況下,我們可以觀察在要素發(fā)生變化時(shí)要通知的屬性。不管在什么情況下,我們應(yīng)該在更改特定功能的模式之前鎖定設(shè)備,如配置設(shè)備中的描述。

聚焦模式

有三種聚焦模式

1.AVCaptureFocusModeLocked焦點(diǎn)位置是固定的。
當(dāng)想要允許用戶撰寫場(chǎng)景然后鎖定焦點(diǎn),這非常有用。

  1. AVCaptureFocusModeAutoFocus 相機(jī)執(zhí)行單次掃描,然后恢復(fù)鎖定。
    這適用于想要選擇要聚焦的特定項(xiàng)目然后保持對(duì)該項(xiàng)目的焦點(diǎn)的情況,即使它不在場(chǎng)景的中心
  2. AVCaptureFocusModeContinuousAutoFocus相機(jī)根據(jù)需要連續(xù)自動(dòng)對(duì)焦

可以使用isFocusModeSupported:方法確定設(shè)備是否支持給定的聚焦模式,然后使用focusMode屬性設(shè)置模式。

此外,設(shè)備可以支持關(guān)注焦點(diǎn)。我們可以使用focusPointOfInterestSupported測(cè)試設(shè)備是否支持。如果支持,則使用focusPointOfInterest設(shè)置焦點(diǎn)。如果我們傳入CGPoint={0,0},那么標(biāo)示圖片區(qū)域的左上角,{1,1}表示橫向模式的右下角。

我們可以使用adjustmentFocus屬性來判斷當(dāng)前設(shè)備是否正在聚焦。我們可以使用KVO來觀察屬性,一遍在設(shè)備啟動(dòng)和停止聚焦的時(shí)候收到通知。

if ([currentDevice isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) {
    CGPoint autofocusPoint = CGPointMake(0.5f, 0.5f);
    [currentDevice setFocusPointOfInterest:autofocusPoint];
    [currentDevice setFocusMode:AVCaptureFocusModeContinuousAutoFocus];
}

曝光模式

有兩種曝光模式

1.AVCaptureExposureModeContinuousAutoExposure設(shè)備會(huì)根據(jù)需要自動(dòng)調(diào)整曝光級(jí)別
2.AVCaptureExposureModeLocked 曝光級(jí)別固定為當(dāng)前級(jí)別
使用isExposureModeSupported 方法確定設(shè)備是否支持給定的曝光模式,然后使用exposureMode屬性設(shè)置模式

此外,設(shè)備可能支持感興趣的曝光點(diǎn)。我們可以使用exposurePointOfInterestSupported測(cè)試是否支持。如果支持,那么使用exposurePointOfInterest設(shè)置曝光點(diǎn)。如果我們傳入CGPoint={0,0},那么標(biāo)示圖片區(qū)域的左上角,{1,1}表示橫向模式的右下角。

我們可以使用adjustExposure屬性來判斷當(dāng)前設(shè)備是否正在更改曝光點(diǎn)。我們可以使用KVO來觀察屬性,以便在設(shè)備啟動(dòng)和停止更改曝光點(diǎn)時(shí)候收到通知。

if ([currentDevice isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]) {
    CGPoint exposurePoint = CGPointMake(0.5f, 0.5f);
    [currentDevice setExposurePointOfInterest:exposurePoint];
    [currentDevice setExposureMode:AVCaptureExposureModeContinuousAutoExposure];
}
閃光模式

有三種閃光模式

  • AVCaptureFlashModeOff 不開啟閃光
  • AVCaptureFlashModeOn 始終開啟閃光
  • AVCaptureFlashModeAuto閃光燈將根據(jù)環(huán)境光線條件反射
    我們可以使用hasFlash 函數(shù)測(cè)試設(shè)備是否有閃光功能。如果是YES,就是有閃光功能。然后使用flashMode設(shè)置相應(yīng)的閃光模式
火炬模式(手電筒)

有三種模式

  • AVCaptureTorchModeOff手電筒關(guān)
  • AVCaptureTorchModeOn 手電筒開
  • AVCaptureTorchModeAuto 根據(jù)需要自動(dòng)打開或者關(guān)閉手電筒
    我們可以使用hasTorch來確定設(shè)備是否具有手電筒功能。使用isTorchModeSupported判斷是否支持給定的手電筒模式。使用torchMode模式來設(shè)置手電筒

視頻穩(wěn)定(去除抖動(dòng))

電影視頻穩(wěn)定功能適用于視頻操作的連接,具體取決于特定的設(shè)備硬件。即便如此,也不是所有的源格式和視頻分辨率都支持。

啟用電影視頻穩(wěn)定功能還可能會(huì)在視頻捕獲管道中引入額外的延遲,要檢測(cè)什么時(shí)候使用視頻穩(wěn)定,使用videoStabilizationEnabled屬性。enableVideoStabilizationWhenAvailable 屬性允許app在歙縣頭支持的情況下自動(dòng)啟用視頻穩(wěn)定功能。默認(rèn)情況下,該功能是被禁止的。

白平衡

有兩種白平衡

這個(gè)具體有啥用大家可以自已搜索。

白平衡,字面上的理解是白色的平衡。白平衡是描述顯示器中紅、綠、藍(lán)三基色混合生成后白色精確度的一項(xiàng)指標(biāo)。白平衡是電視攝像領(lǐng)域一個(gè)非常重要的概念,通過它可以解決色彩還原色調(diào)處理的一系列問題。白平衡是隨著電子影像再現(xiàn)色彩真實(shí)而產(chǎn)生的,在專業(yè)攝像領(lǐng)域白平衡應(yīng)用的較早, [1] 現(xiàn)在家用電子產(chǎn)品(家用攝像機(jī)、數(shù)碼照相機(jī))中也廣泛地使用,然而技術(shù)的發(fā)展使得白平衡調(diào)整變得越來越簡(jiǎn)單容易,但許多使用者還不甚了解白平衡的工作原理,理解上存在諸多誤區(qū)。它是實(shí)現(xiàn)攝像機(jī)圖像能精確反映被攝物的色彩狀況,有手動(dòng)白平衡和自動(dòng)白平衡等方式。許多人在使用數(shù)碼攝像機(jī)拍攝的時(shí)候都會(huì)遇到這樣的問題:在日光燈的房間里拍攝的影像會(huì)顯得發(fā)綠,在室內(nèi)鎢絲燈光下拍攝出來的景物就會(huì)偏黃,而在日光陰影處拍攝到的照片則莫名其妙地偏藍(lán),其原因就在于白平衡的設(shè)置上。 [1]

我們可以使用isWhiteBalanceModeSupported 方法確定設(shè)備是否支持白平衡模式。然后用whiteBalanceMode設(shè)置白平衡

我們可以使用adjustWhiteBalance屬性來確定當(dāng)前是否正在更改其白平衡設(shè)置。我們可以使用KVO來觀察屬性,以便在設(shè)備啟動(dòng)時(shí)通知并停止更改其白平衡設(shè)置。

設(shè)置設(shè)備方向

我們可以在AVCaptureConnection上設(shè)置所需要的方向,已指定我們希望如何在AVCaptureOutput中定向圖像以進(jìn)行連接。

使用AVCaptureConnectionsupportsVideoOrientation屬性確定設(shè)備是否支持更改視頻的方向,使用videoOrientation屬性指定您希望圖像在輸出端口中的方向。

AVCaptureConnection *captureConnection = <#A capture connection#>;
if ([captureConnection isVideoOrientationSupported])
{
    AVCaptureVideoOrientation orientation = AVCaptureVideoOrientationLandscapeLeft;
    [captureConnection setVideoOrientation:orientation];
}
配置設(shè)備

要在設(shè)備上設(shè)置捕獲屬性,必須首選使用lockForConfiguration獲取設(shè)備上的鎖。這樣可以避免其他程序正在更改該設(shè)置。

if ([device isFocusModeSupported:AVCaptureFocusModeLocked]) {
    NSError *error = nil;
    if ([device lockForConfiguration:&error]) {
        device.focusMode = AVCaptureFocusModeLocked;
        [device unlockForConfiguration];
    }
    else {

}
}
設(shè)備切換

有時(shí)候我們希望允許用戶在輸入設(shè)備之前切換。例如,從使用前置攝像頭切換到后置攝像頭。為了避免暫停,我們可以在會(huì)話運(yùn)行時(shí)進(jìn)行重新配置會(huì)話,這里我們需要使用beginConfiguration 和commitConfiguration 來組合配置更改

AVCaptureSession *session = <#A capture session#>;
[session beginConfiguration];
 
[session removeInput:frontFacingCameraDeviceInput];
[session addInput:backFacingCameraDeviceInput];
 
[session commitConfiguration];

這里在調(diào)用commitConfiguration的時(shí)候,所有的更新才能生效。

使用捕獲輸入將捕獲設(shè)備添加到會(huì)話(session)中

要將捕獲設(shè)備添加到捕獲會(huì)話中,我們應(yīng)該使用AVCaptureDeviceInput的實(shí)例。捕獲設(shè)備輸入管理設(shè)備的端口。

NSError *error;
AVCaptureDeviceInput *input =
        [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if (!input) {
    // Handle the error appropriately.
}

我們調(diào)用addInput 向會(huì)話中添加輸入。添加之前最好調(diào)用下canAddInput判斷是否能增加

AVCaptureSession *captureSession = <#Get a capture session#>;
AVCaptureDeviceInput *captureDeviceInput = <#Get a capture device input#>;
if ([captureSession canAddInput:captureDeviceInput]) {
    [captureSession addInput:captureDeviceInput];
}
else {
    // Handle the failure.
}

AVCaptureInput出售一個(gè)或者多個(gè)媒體流數(shù)據(jù)。例如:輸入設(shè)備可以提供音頻和視頻數(shù)據(jù)。由輸入提供的每個(gè)媒體流由AVCaptureInputPort對(duì)象表示。捕獲會(huì)話使用AVCaptureConnection對(duì)象來定義一組AVCaptureInputPort 對(duì)象與單個(gè)AVCaptureOutput之間的映射

使用捕獲輸出從會(huì)話中獲取輸出

要從捕獲會(huì)話獲取輸出,請(qǐng)?zhí)砑右粋€(gè)或者多個(gè)輸出。輸出的類是AVCaptureOutput

  • AVCaptureMovieFileOutput輸出一個(gè)電影文件
  • AVCaptureVideoDataOutput如果我們要處理正在捕獲的視頻中幀,例如創(chuàng)建自己的自定義視圖層。
  • AVCaptureAudioDataOutput 如果我們要處理正在捕獲的音頻數(shù)據(jù)
  • AVCaptureStillImageOutput 如果要捕獲包含元數(shù)據(jù)的靜止圖像

我們可以使用addOutput:將輸出添加到AVCaptureSession對(duì)象中。我們可以通過使用canAddOutput 來判斷輸出對(duì)象是否與當(dāng)前會(huì)話兼容。我們也可以在會(huì)話運(yùn)行時(shí)根據(jù)需要添加和刪除輸出。

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.
}
保存電影文件

使用AVCaptureMovieFileOutput對(duì)象將影片數(shù)據(jù)保存到文件。AVCaptureMovieFileOutput也是AVCaptureFileOutput的子類。我們可以配置電影文件輸出的各個(gè)方面,例如錄制的最長(zhǎng)持續(xù)時(shí)間獲其最大文件大小。如果剩余磁盤空間少于一定數(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#>;

輸出的分辨率和比特率取決于捕獲會(huì)話的sessionPreset。視頻編碼通常是H.264,音頻編碼通常是AAC。實(shí)際值因設(shè)備而已。

開始錄制

我們開始使用startRecordingToOutputFileURL:recordingDelegate錄制一個(gè)QuickTime影片。我們需要提供基于文件的url和委托。url不能表示現(xiàn)有的文件,因?yàn)殡娪拔募敵霾粫?huì)覆蓋現(xiàn)有資源。我們還必須具有寫入指定位置的權(quán)限才行。代理必須符合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#>];

在captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error的實(shí)現(xiàn)中,委托可能會(huì)將生成的影片寫入到相機(jī)相冊(cè)中。我們應(yīng)該檢查可能發(fā)生的錯(cuò)誤。

確保文件寫入成功

要確保文件是否已成功保存,請(qǐng)執(zhí)行captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error。我們?cè)诟幕卣{(diào)函數(shù)中不僅要檢查錯(cuò)誤,還需要檢查錯(cuò)誤的用戶信息字典中的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的信息字典中的AVErrorRecordingSuccessfullyFinishedKeykey值,因?yàn)榧词刮覀兪盏藉e(cuò)誤,文件也可能保存成功。該錯(cuò)誤可能表示已經(jīng)到達(dá)我們?cè)O(shè)置的摸個(gè)記錄約束。例如:AVErrorMaximumDurationReached或者AVErrorMaximumFileSizeReached。
可能導(dǎo)致錄制停止的原因如下:

  • 磁盤滿了 - AVErrorDiskFull
  • 錄音設(shè)備已斷開連接-AVErrorDeviceWasDisconnected
  • 會(huì)話被中斷(比如收到電話)- AVErrorSessionWasInterrupted
將元數(shù)據(jù)添加到文件

我們可以隨時(shí)設(shè)置電影文件的元數(shù)據(jù),即使在錄制時(shí)也是如此。這對(duì)于在記錄開始時(shí)信息不可能用的情況很有用,如位置信息的情況。文件輸出的元數(shù)據(jù)由AVMetadataItem對(duì)象數(shù)組表示;我們可以使用其可變子類AVMutableMetadataItem的實(shí)例來創(chuàng)建自己的元數(shù)據(jù)。

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;
處理視頻幀

AVCaptureVideoDataOutput對(duì)象使用委托來銷售視頻幀。您可以使用setSampleBufferDelegate:queue:設(shè)置委托。除了設(shè)置委托外,還指定了一個(gè)調(diào)用他們委派方法的串行隊(duì)列。您必須使用串行隊(duì)列來確保以正確的順序?qū)瑐鬟f給委托。您可以使用隊(duì)列來修改交付和處理視頻幀的優(yōu)先級(jí)。

這些幀在captureOutput:didOutputSampleBuffer:fromConnection中回調(diào),作為CMSampleBufferRef 不透明類型的實(shí)例。默認(rèn)情況下,緩存區(qū)是以最有效的格式發(fā)出的。我們可以使用videoSettings屬性指定自定義輸出格式。視頻設(shè)置屬性是字典;目前唯一指定的key是kCVPixelBufferPixelFormatTypeKey。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];
處理視頻的性能注意事項(xiàng)

我們應(yīng)該將會(huì)話輸出設(shè)置為應(yīng)用程序的最低實(shí)際分辨率。將輸出設(shè)置設(shè)置為高于必要的分辨率會(huì)浪費(fèi)處理周期并且不比要的消耗功率。

我們必須保證captureOutput:didOutputSampleBuffer:fromConnection的實(shí)現(xiàn)能夠在分配幀的時(shí)間內(nèi)處理樣本緩沖區(qū)。如果花費(fèi)太長(zhǎng)時(shí)間來保存視頻幀,avfoundation會(huì)停止提供幀,不僅你的delegate還有其他輸出也會(huì)停止,如:預(yù)覽圖層。

我們可以使用捕獲視頻數(shù)據(jù)輸出的minFrameDuration屬性來確保我們有足夠的時(shí)間來處理視頻幀,代價(jià)就是幀速率低于其他情況。我們可以設(shè)置alwaysDiscardsLateVideoFrames是yes.(默認(rèn)值也是yes).這可以保證丟棄掉任何延遲的視頻幀而不是傳遞給我們進(jìn)行處理。或者如果我們正在錄制并且輸出的幀有點(diǎn)晚但是你希望獲取得到所有的這些,我們應(yīng)該設(shè)置這個(gè)屬性為NO。這并不意味這幀不會(huì)丟失,這只能保證他們可能不會(huì)被提前或者有效的丟棄。

拍照(捕獲靜態(tài)圖像)

如果想要捕獲元數(shù)據(jù)的靜態(tài)圖像,請(qǐng)使用AVCaptureStillImageOutput輸出。圖像的分辨率取決于會(huì)話的預(yù)設(shè)以及設(shè)備

像素和編碼格式

不同的設(shè)備不同的圖片格式。沃恩可以使用availableImageDataCVPixelFormatTypes和availableImageDataCodecTypes 找出設(shè)備支持的像素和編解碼器類型。每個(gè)方法返回特定設(shè)備支持的值的數(shù)組。我們可以設(shè)置outputSettings字典以指定所需的圖像格式。

AVCaptureStillImageOutput *stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
NSDictionary *outputSettings = @{ AVVideoCodecKey : AVVideoCodecJPEG};
[stillImageOutput setOutputSettings:outputSettings];

如果我們捕獲jpeg圖像,通常不應(yīng)指定自己的壓縮格式。相反的,我們應(yīng)該讓靜止圖像輸出為我們進(jìn)行壓縮,因?yàn)檫@樣的壓縮是硬件加速的。如果需要圖像的data數(shù)據(jù),盡管修改了圖像的元數(shù)據(jù),但是也應(yīng)該用jpegStillImageNSDataRepresentation:方法來獲取data數(shù)據(jù)。

拍照

如果要拍照,我們應(yīng)該向輸出發(fā)送captureStillImageAsynchronouslyFromConnection:completionHandler:message函數(shù)。第一個(gè)參數(shù)是您用于捕獲的連接。這里我們需要查找連接視頻的端口。

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; }
}

captureStillImageAsynchronouslyFromConnection:completionHandler: 的第二個(gè)參數(shù)一個(gè)block,這個(gè)block帶有兩個(gè)參數(shù),一個(gè)參數(shù)是CMSampleBuffer對(duì)象,該對(duì)象包含靜態(tài)圖片,另一個(gè)參數(shù)是NSError對(duì)象。樣本緩存區(qū)包含元數(shù)據(jù),例如exif 字典作為附件。我們可以根據(jù)需要修改附件,這里我們需要注意像素和編碼格式中討論的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.
    }];

向用戶展示正在錄制的內(nèi)容

我們可以為用戶提供相機(jī)或者麥克風(fēng)錄制內(nèi)容的預(yù)覽。

視頻預(yù)覽

我們可以使用AVCaptureVideoPreviewLayer對(duì)象向用戶提供正在錄制的內(nèi)容的預(yù)覽。AVCaptureVideoPreviewLayer是CALayer的子類。

使用AVCaptureVideoDataOutput類為app提供提供了將視頻像素呈現(xiàn)給用戶之前訪問他們的能力。

與捕獲輸出不同,視頻預(yù)覽層維護(hù)對(duì)其他關(guān)聯(lián)的會(huì)話的強(qiáng)引用。這是為了確保在layer嘗試顯示視頻時(shí)不會(huì)釋放會(huì)話。

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];

通常,預(yù)覽layer的行為與渲染樹中的任何其他的CAlayer對(duì)象相同。我們可以對(duì)推向縮放或者變換旋轉(zhuǎn)等操作。這里的區(qū)別在于,我們可能需要設(shè)置layer的方向?qū)傩砸灾付☉?yīng)該如何旋轉(zhuǎn)來自相機(jī)的圖像。除此之外,我們可以查詢supportsVideoMirroring屬性來測(cè)試視頻鏡像的設(shè)備支持。我們可以根據(jù)需要設(shè)置videoMirrored屬性,但是當(dāng)automaticAdjustsVideoMirroring設(shè)置為yes(默認(rèn)值是yes),將會(huì)根據(jù)會(huì)話配置自動(dòng)設(shè)置鏡像值。

視頻權(quán)重模式

預(yù)覽layer支持權(quán)重模式:

    1. AVLayerVideoGravityResizeAspect 保留縱橫比,在視頻為填充區(qū)域留下黑條
    1. AVLayerVideoGravityResizeAspectFill 保留縱橫比,會(huì)充滿整個(gè)屏幕,必要時(shí)會(huì)裁剪視頻
    1. AVLayerVideoGravityResize 充滿全屏,圖像可能發(fā)生形變。
使用點(diǎn)擊聚焦預(yù)覽

與預(yù)覽圖層一起實(shí)現(xiàn)的點(diǎn)擊聚焦是需要注意。我們必須考慮layer的預(yù)覽方向和權(quán)重,以及可能鏡像預(yù)覽的可能性。

顯示音頻級(jí)別

要監(jiān)視捕獲連接中音頻通道的平均功率和峰值功率級(jí)別,使用AVCaptureAudioChannel對(duì)象。音頻級(jí)別不是KVO可以觀察的,因此沃恩需要輪訓(xùn)更新級(jí)別,以便更新用戶界面。(例如每秒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.
    }
}

整體應(yīng)用

步驟

  • 1.創(chuàng)建AVCaptureSession對(duì)象以協(xié)調(diào)輸入設(shè)備到輸出設(shè)備的數(shù)據(jù)流。
  • 2.找到所需輸入類型的AVCaptureDevice對(duì)象。
  • 3.為設(shè)備創(chuàng)建AVCaptureDeviceInput對(duì)象
  • 4.創(chuàng)建AVCaptureVideoDataOutput對(duì)象已生成真視頻
  • 5.給AVCaptureVideoDataOutput設(shè)置一個(gè)代理對(duì)象
  • 6.實(shí)現(xiàn)代理對(duì)象的代理函數(shù),將代理對(duì)象接受的CMSampleBuffer對(duì)象轉(zhuǎn)換成UIImage對(duì)象。
-(void)capture{
    ///1
    AVCaptureSession *session = [[AVCaptureSession alloc] init];
    session.sessionPreset = AVCaptureSessionPresetMedium;
    ///2 3視頻對(duì)象
   AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    [device lockForConfiguration:nil];
    [device setActiveVideoMinFrameDuration: CMTimeMake(1, 15)];
    [device unlockForConfiguration];
    NSError *error = nil;
    AVCaptureDeviceInput *input =[AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
    if (!input) {
        // Handle the error appropriately.
        return;
    }
    [session addInput:input];
    ///4
    AVCaptureVideoDataOutput *output = [[AVCaptureVideoDataOutput alloc] init];
    [session addOutput:output];
    output.videoSettings =
    @{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA) };
//    output.minFrameDuration = CMTimeMake(1, 15);///這里設(shè)置的是一秒15幀
    ///5
    dispatch_queue_t queue = dispatch_queue_create("MyQueue", NULL);
    [output setSampleBufferDelegate:self queue:queue];

    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];
            });
        }
    }];
    
    [session startRunning];
    self.captureSession = session;
}


- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
       fromConnection:(AVCaptureConnection *)connection {
    UIImage *image = [self imageFromSampleBuffer:sampleBuffer];
    dispatch_async(dispatch_get_main_queue(), ^{
 
    static UIImageView *imageView=nil;
    if (!imageView) {
       imageView =[[UIImageView alloc]initWithFrame:self.view.bounds];
        [self.view addSubview:imageView];
    }
    
//        CIImage *ciImage = [CIImage imageWithCVPixelBuffer:imageBuffer];

        imageView.image = image;
        
    });
    
    // Add your code here that uses the image.
}


- (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);
}

- (void)viewDidLoad {
    [super viewDidLoad];
    [self capture];
}

親自測(cè)試有效

這里遇到的坑, UIImage *image = [self imageFromSampleBuffer:sampleBuffer];調(diào)用不能轉(zhuǎn)移到主隊(duì)列中,因?yàn)榈街麝?duì)列中,sampleBuffer buffer就被釋放掉了。導(dǎo)致崩潰
我們一定要保持對(duì)AVCaptureSession對(duì)象的強(qiáng)制引用

本來想在做些具體的demo呢。篇幅限制,部分內(nèi)容的demo移動(dòng)到下一篇的結(jié)尾部分。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容