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.雖然音頻和視頻是最常用的媒體類型,但是我們也可以指定其他的媒體類型,例如:AVMediaTypeSubtitle 和AVMediaTypeText
每個(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屬性以確定視頻的方向
- 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è)捕獲連接來開啟或者關(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),這非常有用。
- AVCaptureFocusModeAutoFocus 相機(jī)執(zhí)行單次掃描,然后恢復(fù)鎖定。
這適用于想要選擇要聚焦的特定項(xiàng)目然后保持對(duì)該項(xiàng)目的焦點(diǎn)的情況,即使它不在場(chǎng)景的中心- 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)情況下,該功能是被禁止的。
白平衡
有兩種白平衡
- 1.AVCaptureWhiteBalanceModeLocked 白平時(shí)固定的
- 2.AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance 相機(jī)根據(jù)需要自動(dòng)平衡
這個(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)重模式:
- AVLayerVideoGravityResizeAspect 保留縱橫比,在視頻為填充區(qū)域留下黑條
- AVLayerVideoGravityResizeAspectFill 保留縱橫比,會(huì)充滿整個(gè)屏幕,必要時(shí)會(huì)裁剪視頻
- 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é)尾部分。