首先介紹下實現(xiàn)拍照和錄制視頻需要用到的類:
-
AVCaptureVideoPreviewLayer
:捕獲視頻預覽層析桥。 -
AVCaptureSession
:捕獲會話類巡蘸。 -
AVCaptureDevice
:捕獲設備類壤巷。 -
AVCaptureDeviceInput
:捕獲設備輸入類。 -
AVCapturePhotoOutput
:捕獲照片輸出類。 -
AVCaptureMovieFileOutput
:捕獲電影文件輸出類。 -
AVCaptureConnection
:捕獲連接類喝检。 -
AVCapturePhotoSettings
:捕獲照片設置類。 -
AVAsset
:資產(chǎn)類撼泛。 -
AVAssetImageGenerator
:資產(chǎn)圖片生成器類停蕉。
首先來看下AVCaptureSession
初始化的流程:
通過該流程圖可以看出,AVCaptureSession
的初始化配置需要:
1锨并、視頻輸入設備 。
2译暂、音頻輸入設備。
3撩炊、照片輸出對象 外永。
3、電影文件輸出對象拧咳。
看核心代碼:
- (BOOL)setupSession:(NSError **)error {
self.captureSession = [[AVCaptureSession alloc] init];
self.captureSession.sessionPreset = AVCaptureSessionPresetHigh;
//視頻輸入設備
AVCaptureDevice *videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
AVCaptureDeviceInput *videoDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:videoDevice error:error];
if (videoDeviceInput) {
if ([self.captureSession canAddInput:videoDeviceInput]) {
[self.captureSession addInput:videoDeviceInput];
self.activeVideoInput = videoDeviceInput;
} else {
return NO;
}
} else {
return NO;
}
//音頻輸入設備
AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
AVCaptureDeviceInput *audioDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:audioDevice error:error];
if (audioDeviceInput) {
if ([self.captureSession canAddInput:audioDeviceInput]) {
[self.captureSession addInput:audioDeviceInput];
} else {
return NO;
}
} else {
return NO;
}
//從實例攝像頭中捕捉靜態(tài)圖片
self.photoOutput = [[AVCapturePhotoOutput alloc] init];
if ([self.captureSession canAddOutput:self.photoOutput]) {
[self.captureSession addOutput:self.photoOutput];
}
//用于將電影錄制到文件系統(tǒng)
self.movieOutput = [[AVCaptureMovieFileOutput alloc] init];
if ([self.captureSession canAddOutput:self.movieOutput]) {
[self.captureSession addOutput:self.movieOutput];
}
self.videoQueue = dispatch_queue_create("CQCamera.Video.Queue", NULL);
return YES;
}
這段代碼最后我們還創(chuàng)建了個全局的串行隊列videoQueue
伯顶,在后面開始捕獲和錄制時需要使用。
從上圖中我們還看到骆膝,在AVCaptureSession
初始化配置結(jié)束后又做了兩個操作祭衩。
1、我們將AVCaptureVideoPreviewLayer
的session
設置為AVCaptureSession
阅签。
[(AVCaptureVideoPreviewLayer*)self.layer setSession:session];
- 將捕捉數(shù)據(jù)直接輸出到圖層中掐暮,并確保與會話狀態(tài)同步。
2政钟、開始捕獲
- (void)startSession {
if (![self.captureSession isRunning]) {
dispatch_async(self.videoQueue, ^{
[self.captureSession startRunning];
});
}
}
下面看下如何將捕獲的內(nèi)容生產(chǎn)圖片路克。
一、拍照
同樣先看流程圖:
很明顯拍照我們需要使用AVCapturePhotoOutput
捕獲照片輸出對象养交。
看代碼:
- (void)captureStillImage {
AVCaptureConnection *connection = [self.photoOutput connectionWithMediaType:AVMediaTypeVideo];
if (connection.isVideoOrientationSupported) {
connection.videoOrientation = [self currentVideoOrientation];
}
self.photoSettings = [AVCapturePhotoSettings photoSettingsWithFormat:@{AVVideoCodecKey:AVVideoCodecTypeJPEG}];
[self.photoOutput capturePhotoWithSettings:self.photoSettings delegate:self];
}
- 拍照時我們需要拿到捕獲連接對象(
AVCaptureConnection
)精算,設置視頻的方向,否則在橫豎屏切換時會出現(xiàn)問題碎连。 - 在代理方法中我們利用捕獲連接對象(
AVCaptureConnection
)調(diào)用fileDataRepresentation
方法獲取二進制圖片灰羽。
獲取到圖片后需要利用Photos
庫將圖片保存到相冊。
Photos
將圖片保存到相冊我們首先要判斷是否有權(quán)限鱼辙,這個需要在plist
文件中配置在這就不多說了廉嚼。
下面我們來看下將圖片添加到指定相冊的流程:
第一步:添加圖片到【相機膠卷】。
1.1:UIImageWriteToSavedPhotosAlbum
函數(shù)
1.2:AssetsLibrary
框架(已過期,一般不用了)
1.3:Photos
框架(推薦)第二步:擁有一個【自定義相冊】
2.1:AssetsLibrary
框架
2.2:Photos
框架(推薦)第三步:將剛才添加到【相機膠卷】的圖片座每,引用(添加)到【自定義相冊】
3.1:AssetsLibrary
框架
3.2:Photos
框架(推薦)
Photos
框架相關(guān)類須知:
1前鹅、PHAsset
:一個PHAsset
對象代表一張圖片或者一個視頻文件。
負責查詢一堆的圖片或者視頻文件(PHAsset
對象)峭梳。
2舰绘、PHAssetCollection
:一個PHAssetCollection
對象代表一個相冊。
負責查詢一堆的相冊(PHAssetCollection
對象)葱椭。
3捂寿、PHAssetChangeRequest
: 負責執(zhí)行對PHAsset
(照片或視頻)的【增刪改】操作。
這個類只能放在-[PHPhotoLibrary performChanges:completionHandler:]
或者 -[PHPhotoLibrary performChangesAndWait:error:]
方法的block
中使用孵运。
4秦陋、PHAssetCollectionChangeRequest
:負責執(zhí)行對PHAssetCollection(相冊)的【增刪改】操作。
這個類只能放在-[PHPhotoLibrary performChanges:completionHandler:]
或者 -[PHPhotoLibrary performChangesAndWait:error:]
方法的block
中使用治笨。
- 保存圖片到 相機膠卷:
+ (PHFetchResult<PHAsset *> *)savePhoto:(UIImage *)image {
__block NSString *createdAssetId = nil;
// Synchronously 同步執(zhí)行操作
NSError *error;
[[PHPhotoLibrary sharedPhotoLibrary] performChangesAndWait:^{
createdAssetId = [PHAssetChangeRequest creationRequestForAssetFromImage:image].placeholderForCreatedAsset.localIdentifier;
} error:&error];
if (error == nil) {
NSLog(@"保存成功");
} else {
NSLog(@"保存圖片Error: %@", error.localizedDescription);
return nil;
}
// // Asynchronously 異步執(zhí)行操作
// [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
// [PHAssetChangeRequest creationRequestForAssetFromImage:image];
// } completionHandler:^(BOOL success, NSError * _Nullable error) {
// if (success) {
// NSLog(@"保存成功");
// } else {
// NSLog(@"保存圖片Error: %@", error.localizedDescription);
// }
// }];
//PHAsset:查詢圖片/視屏
PHFetchOptions *options = nil;
PHFetchResult<PHAsset *> *createdAssets = [PHAsset fetchAssetsWithLocalIdentifiers:@[createdAssetId] options:options];
return createdAssets;
}
- 獲取指定相冊:
+ (PHAssetCollection *)getAlbumWithTitle:(NSString *)title {
__block PHAssetCollection *createdCollection = nil;// 已經(jīng)創(chuàng)建的自定義相冊
//PHAssetCollection: 查詢所有的自定義相冊
PHFetchResult<PHAssetCollection *> *collections = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil];
[collections enumerateObjectsUsingBlock:^(PHAssetCollection * _Nonnull collection, NSUInteger idx, BOOL * _Nonnull stop) {
if ([collection.localizedTitle isEqualToString:title]) {
createdCollection = collection;
*stop = YES;
}
}];
if (!createdCollection) { // 沒有創(chuàng)建過相冊
__block NSString *createdCollectionId = nil;
[[PHPhotoLibrary sharedPhotoLibrary] performChangesAndWait:^{
//PHAssetCollectionChangeRequest:【增】相冊
createdCollectionId = [PHAssetCollectionChangeRequest creationRequestForAssetCollectionWithTitle:title].placeholderForCreatedAssetCollection.localIdentifier;
} error:nil];
//PHAssetCollection:【查】出相冊
createdCollection = [PHAssetCollection fetchAssetCollectionsWithLocalIdentifiers:@[createdCollectionId] options:nil].firstObject;
}
return createdCollection;
}
- 保存圖片到 指定相冊:
+ (BOOL)addAssetsToAlbumWithAssets:(id<NSFastEnumeration>)assets withAlbum:(PHAssetCollection *)assetCollection {
// 將剛才添加到【相機膠卷】的圖片驳概,引用(添加)到【自定義相冊】
NSError *errorCollection = nil;
[[PHPhotoLibrary sharedPhotoLibrary] performChangesAndWait:^{
PHAssetCollectionChangeRequest *request = [PHAssetCollectionChangeRequest changeRequestForAssetCollection:assetCollection];
// 自定義相冊封面默認保存第一張圖,所以使用以下方法把最新保存照片設為封面
[request insertAssets:assets atIndexes:[NSIndexSet indexSetWithIndex:0]];
} error:&errorCollection];
// 保存結(jié)果
if (errorCollection) {
NSLog(@"保存到指定 相冊 失敵嘟馈!");
return NO;
} else {
NSLog(@"保存到指定 相冊 成功顺又!");
return YES;
}
}
二更卒、錄頻
看下錄頻的操作:
錄頻核心代碼:
- (void)startRecording {
if (self.isRecording) return;
AVCaptureDevice *device = [self activeCamera];
//平滑對焦,減緩攝像頭對焦速度稚照。移動拍攝時蹂空,攝像頭會嘗試快速對焦
if (device.isSmoothAutoFocusEnabled) {
NSError *error;
if ([device lockForConfiguration:&error]) {
device.smoothAutoFocusEnabled = YES;
[device unlockForConfiguration];
} else {
[self.delegate deviceConfigurationFailedWithError:error];
}
}
AVCaptureConnection *connection = [self.movieOutput connectionWithMediaType:AVMediaTypeVideo];
if (connection.isVideoOrientationSupported) {
connection.videoOrientation = [self currentVideoOrientation];
}
//判斷是否支持視頻穩(wěn)定。提高視頻的質(zhì)量果录。
if (connection.isVideoStabilizationSupported) {
connection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeAuto;
}
self.outputURL = [self uniqueURL];
[self.movieOutput startRecordingToOutputFileURL:self.outputURL recordingDelegate:self];
}
- 1上枕、我們首先需要拿到設置
AVCaptureSession
是創(chuàng)建的 視頻捕獲設備輸入(AVCaptureDeviceInput
),然后取出設備(AVCaptureDevice
)弱恒,配置設備的平滑對焦屬性辨萍。 - 2、然后同樣需要拿到捕獲連接對象(AVCaptureConnection)斤彼,設置視頻的方向分瘦,否則在橫豎屏切換時會出現(xiàn)問題。并且需要設置
preferredVideoStabilizationMode
屬性提高視頻的質(zhì)量琉苇。 - 3、調(diào)用
startRecordingToOutputFileURL:recordingDelegate:
方法開始錄屏悦施。 - 4并扇、停止錄屏。
- 5抡诞、錄屏結(jié)束后在代理方法中獲取到我們的視頻地址穷蛹。
錄屏結(jié)束后我們可能需要獲取視頻的某一幀圖片,用來顯示到UI上昼汗‰妊看下操作步驟:
流程圖很簡單,看下代碼:
//生成視頻縮略圖
- (void)generateThumbnailForVideoAtURL:(NSURL *)videoURL {
dispatch_async(self.videoQueue, ^{
AVAsset *asset = [AVAsset assetWithURL:videoURL];
AVAssetImageGenerator *imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:asset];
//設置maximumSize 寬為100顷窒,高為0 根據(jù)視頻的寬高比來計算圖片的高度
imageGenerator.maximumSize = CGSizeMake(100.0, 0.0);
//捕捉視頻縮略圖會考慮視頻的變化(如視頻的方向變化)蛙吏,如果不設置,縮略圖的方向可能出錯.
imageGenerator.appliesPreferredTrackTransform = YES;
NSError *error;
CGImageRef imageRef = [imageGenerator copyCGImageAtTime:kCMTimeZero actualTime:NULL error:&error];
if (imageRef == nil) {
NSLog(@"imageRefError: %@", error);
}
UIImage *image = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
});
}
到此我們的拍照和錄屏的核心功能已經(jīng)實現(xiàn)了鞋吉。下面介紹一下跟拍照錄屏相關(guān)的一些功能:切換攝像頭鸦做、聚焦、曝光谓着、閃光燈泼诱、手電筒。
切換攝像頭
我們現(xiàn)在的手機設備一般都有前置和后置攝像頭赊锚,所以我們這里就是對前置和后置攝像頭的切換治筒。
- (BOOL)switchCameras {
AVCaptureDevice *currentDevice = [self activeCamera];
AVCaptureDevice *device;
if (currentDevice.position == AVCaptureDevicePositionBack) {
device = [self cameraWithPosition:AVCaptureDevicePositionFront];
} else {
device = [self cameraWithPosition:AVCaptureDevicePositionBack];
}
if (device == nil) { return NO; }
NSError *error;
AVCaptureDeviceInput *deviceInput = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if (deviceInput) {
[self.captureSession beginConfiguration];
[self.captureSession removeInput:self.activeVideoInput];
if ([self.captureSession canAddInput:deviceInput]) {
[self.captureSession addInput:deviceInput];
self.activeVideoInput = deviceInput;
} else {
[self.captureSession addInput:self.activeVideoInput];
}
//配置完成后. 會分批的將所有變更整合在一起屉栓。
[self.captureSession commitConfiguration];
return YES;
} else {
[self.delegate deviceConfigurationFailedWithError:error];
return NO;
}
}
- 1、先拿到攝像頭設備
device
耸袜。 - 2友多、將攝像頭包裝到
AVCaptureDeviceInput
類型的對象中。 - 3句灌、一定要先調(diào)用
beginConfiguration
方法夷陋,準備配置。 - 4胰锌、
removeInput:
移除原來的捕獲設備輸入對象(`AVCaptureDeviceInput )骗绕。 - 5、判斷能否添加
canAddInput:
新的捕獲設備輸入對象(`AVCaptureDeviceInput )资昧。 - 6酬土、如果可以就添加
addInput:
,設置為當前正在使用的捕獲設備輸入對象格带。 - 7撤缴、如果不可以添加,再將原來的捕獲設備輸入對象(`AVCaptureDeviceInput )添加進去叽唱。
- 8屈呕、最后調(diào)用
commitConfiguration
方法,分批的將所有變更整合在一起棺亭。
獲取前置或后置攝像頭的代碼:
- (AVCaptureDevice *)cameraWithPosition:(AVCaptureDevicePosition)position {
//AVCaptureDeviceTypeBuiltIn Microphone:話筒
//AVCaptureDeviceTypeBuiltIn WideAngleCamera:廣角照相機
//AVCaptureDeviceTypeBuiltIn TelephotoCamera:長焦照相機
//AVCaptureDeviceTypeBuiltIn UltraWideCamera:超寬攝影機
//AVCaptureDeviceTypeBuiltIn DualCamera:雙攝像頭
//AVCaptureDeviceTypeBuiltIn DualWideCamera:雙寬攝像頭
//AVCaptureDeviceTypeBuiltIn TripleCamera:三重攝影機
//AVCaptureDeviceTypeBuiltIn TrueDepthCamera:真深度照相機
//AVCaptureDeviceTypeBuiltIn DuoCamera:雙后置攝像頭
NSArray<AVCaptureDeviceType> *deviceTypes =@[
AVCaptureDeviceTypeBuiltInMicrophone,
AVCaptureDeviceTypeBuiltInTelephotoCamera,
AVCaptureDeviceTypeBuiltInWideAngleCamera,
AVCaptureDeviceTypeBuiltInDualCamera
];
AVCaptureDeviceDiscoverySession *deviceDiscoverySession = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:deviceTypes mediaType:AVMediaTypeVideo position:position];
return deviceDiscoverySession.devices.firstObject;
}
聚焦 & 曝光
- 聚焦
- (void)focusAtPoint:(CGPoint)point {
AVCaptureDevice *device = [self activeCamera];
//是否支持興趣點聚焦 和 自動聚焦
if (device.isFocusPointOfInterestSupported && [device isFocusModeSupported:AVCaptureFocusModeAutoFocus]) {
NSError *error;
if ([device lockForConfiguration:&error]) {//鎖定設備
device.focusPointOfInterest = point;//聚焦點
device.focusMode = AVCaptureFocusModeAutoFocus;//設置為自動聚焦
[device unlockForConfiguration];//解鎖設備
}
}
}
- 曝光
- (void)exposeAtPoint:(CGPoint)point {
AVCaptureDevice *device = [self activeCamera];
AVCaptureExposureMode exposureMode = AVCaptureExposureModeContinuousAutoExposure;
//是否支持興趣點曝光 和 持續(xù)自動曝光虎眨。
if (device.isExposurePointOfInterestSupported && [device isExposureModeSupported:exposureMode]) {
NSError *error;
if ([device lockForConfiguration:&error]) {
//配置期望值
device.exposurePointOfInterest = point;
device.exposureMode = exposureMode;
//判斷設備是否支持鎖定曝光的模式。
if ([device isExposureModeSupported:AVCaptureExposureModeLocked]) {
//支持镶摘,則使用kvo確定設備的adjustingExposure屬性的狀態(tài)嗽桩。
[device addObserver:self forKeyPath:@"adjustingExposure" options:NSKeyValueObservingOptionNew context:&THCameraAdjustingExposureContext];
}
[device unlockForConfiguration];
}
}
}
這里曝光用到了kvo
進行監(jiān)聽屬性的狀態(tài):
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if (context == &THCameraAdjustingExposureContext) {
AVCaptureDevice *device = (AVCaptureDevice *)object;
//判斷設備是否不再調(diào)整曝光等級,
//確認設備的exposureMode是否可以設置為AVCaptureExposureModeLocked
if(!device.isAdjustingExposure && [device isExposureModeSupported:AVCaptureExposureModeLocked]) {
//移除作為adjustingExposure 的self凄敢,就不會得到后續(xù)變更的通知
[object removeObserver:self forKeyPath:@"adjustingExposure" context:&THCameraAdjustingExposureContext];
dispatch_async(dispatch_get_main_queue(), ^{
NSError *error;
if ([device lockForConfiguration:&error]) {
//修改exposureMode
device.exposureMode = AVCaptureExposureModeLocked;
[device unlockForConfiguration];
}
});
}
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
閃光燈 & 手電筒
- 閃光燈
- (void)setFlashMode:(AVCaptureFlashMode)flashMode {
if ([self.photoOutput.supportedFlashModes containsObject:@(flashMode)]) {
self.photoSettings.flashMode = flashMode;
}
}
- 手電筒
- (void)setTorchMode:(AVCaptureTorchMode)torchMode {
AVCaptureDevice *device = [self activeCamera];
if ([device isTorchModeSupported:torchMode]) {
NSError *error;
if ([device lockForConfiguration:&error]) {
device.torchMode = torchMode;
[device unlockForConfiguration];
}
}
}