序
今天介紹自定義相機(jī)的兩種方式,一種是UIImagePickerController,一種是AVCaptureSession.
UIImagePickerController
UIImagePickerController非常方便簡(jiǎn)單,是蘋(píng)果自己封裝好了的一套API,效果如下:
AVCaptureSession
但是上面的 API只能進(jìn)行簡(jiǎn)單的相機(jī)視圖修改,有時(shí)無(wú)法滿足我們的需求.例如我們需要更加復(fù)雜的OverlayerView(自定義相機(jī)視圖),這時(shí)候我們就要自定義一個(gè)相機(jī)了.AVCaptureSession能幫助我們.
code
心急的人,完整Demo在最下邊兒.其他朋友可依次看下去
UIImagePickerController
屬性
首先看到,需要遵守的協(xié)議有2個(gè),分別是UINavigationControllerDelegate和UIImagePickerControllerDelegate
@property(nullable,nonatomic,weak) id <UINavigationControllerDelegate, UIImagePickerControllerDelegate> delegate; //需要遵守的協(xié)議有2個(gè)
還有一些基本設(shè)置,如輸入源/媒體類(lèi)型,還有allowsEditing為 YES 即可允許用戶(hù)拍照后立即進(jìn)行編輯,并且出現(xiàn)編輯框
//基本設(shè)置
@property(nonatomic) UIImagePickerControllerSourceType sourceType; //圖片輸入源,相機(jī)或者相冊(cè),圖庫(kù)
@property(nonatomic,copy) NSArray<NSString *> *mediaTypes; //媒體類(lèi)型
@property(nonatomic) BOOL allowsEditing //是否允許用戶(hù)編輯
@property(nonatomic) BOOL allowsImageEditing //廢棄,用上面的即可
這2個(gè)屬性和視頻相關(guān)
@property(nonatomic) NSTimeInterval videoMaximumDuration //最長(zhǎng)攝制時(shí)間
@property(nonatomic) UIImagePickerControllerQualityType videoQuality //視頻攝制質(zhì)量
這2個(gè)和自定義相機(jī)視圖有關(guān),注意,下面的只有當(dāng)輸入源為UIImagePickerControllerSourceTypeCamera才可用,否則崩潰
@property(nonatomic) BOOL showsCameraControls //默認(rèn) YES, 設(shè)置為 NO 即可關(guān)閉所有默認(rèn) UI
@property(nullable, nonatomic,strong) __kindof UIView *cameraOverlayView //自定義相機(jī)覆蓋視圖
@property(nonatomic) CGAffineTransform cameraViewTransform //拍攝時(shí)屏幕view的transform屬性,可以實(shí)現(xiàn)旋轉(zhuǎn),縮放功能
@property(nonatomic) UIImagePickerControllerCameraCaptureMode cameraCaptureMode //設(shè)置模式為拍照或者攝像
@property(nonatomic) UIImagePickerControllerCameraDevice cameraDevice //默認(rèn)開(kāi)啟前置/后置攝像頭
@property(nonatomic) UIImagePickerControllerCameraFlashMode cameraFlashMode //設(shè)置默認(rèn)的閃光燈模式,有開(kāi)/關(guān)/自動(dòng)
方法
第一個(gè)是判斷輸入源的,一般用來(lái)判斷是否支持拍照
typedef NS_ENUM(NSInteger, UIImagePickerControllerSourceType) {
UIImagePickerControllerSourceTypePhotoLibrary, //圖庫(kù)
UIImagePickerControllerSourceTypeCamera, //相機(jī)-->這個(gè)就是拍照
UIImagePickerControllerSourceTypeSavedPhotosAlbum //相冊(cè)
};
+ (BOOL)isSourceTypeAvailable:(UIImagePickerControllerSourceType)sourceType; //判斷是否支持某種輸入源
這個(gè)是判斷是否支持前/后置攝像頭
typedef NS_ENUM(NSInteger, UIImagePickerControllerCameraDevice) {
UIImagePickerControllerCameraDeviceRear, //后置
UIImagePickerControllerCameraDeviceFront //前置
};
+ (BOOL)isCameraDeviceAvailable:(UIImagePickerControllerCameraDevice)cameraDevice //是否支持前/后置攝像頭
拍照/攝像的方法
- (void)takePicture; //手動(dòng)調(diào)取拍照的方法,也可當(dāng)做`-imagePickerController:didFinishPickingMediaWithInfo:`代理的回調(diào),無(wú)法捕捉動(dòng)態(tài)圖像
- (BOOL)startVideoCapture; //開(kāi)始攝像
- (void)stopVideoCapture; //停止攝像
代理相關(guān)
注意,UIImagePickerController不能直接取消,必須在收到以下代理方法后才可以 dismiss
@protocol UIImagePickerControllerDelegate<NSObject>
@optional
//這個(gè)已經(jīng)廢棄了,不用管
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)image editingInfo:(nullable NSDictionary<NSString *,id> *)editingInfo;
//主要是下面這2個(gè)
//拍照后,點(diǎn)擊使用照片和向此選取照片后都會(huì)調(diào)用以下代理方法
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info;
//點(diǎn)擊取消的時(shí)候會(huì)調(diào)用,通常在此 dismiss
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker;
@end
保存照片/視頻到手機(jī),三個(gè)比較重要的方法,記得導(dǎo)入AssetsLibrary.framework,并且在需要調(diào)用存儲(chǔ)方法的的地方導(dǎo)入<AssetsLibrary/AssetsLibrary.h>
// 將照片保存到相冊(cè),回調(diào)方法必須用 - (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo;
UIKIT_EXTERN void UIImageWriteToSavedPhotosAlbum(UIImage *image, __nullable id completionTarget, __nullable SEL completionSelector, void * __nullable contextInfo);
//是否可以將視頻保存到相冊(cè),通常在調(diào)用下面的方法之前會(huì)調(diào)用這個(gè)
UIKIT_EXTERN BOOL UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(NSString *videoPath) NS_AVAILABLE_IOS(3_1);
// 將視頻保存到相冊(cè),回調(diào)方法必須用 - (void)video:(NSString *)videoPath didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo;
UIKIT_EXTERN void UISaveVideoAtPathToSavedPhotosAlbum(NSString *videoPath, __nullable id completionTarget, __nullable SEL completionSelector, void * __nullable contextInfo) NS_AVAILABLE_IOS(3_1);
用法如下
- (void)savePic
{
UIImageWriteToSavedPhotosAlbum(imgPath, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);
}
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo{
if (error) {
NSLog(@"保存照片過(guò)程中發(fā)生錯(cuò)誤,錯(cuò)誤信息:%@",error.localizedDescription);
}else{
NSLog(@"照片保存成功.");
}
}
- (void)saveVideo
{
UISaveVideoAtPathToSavedPhotosAlbum(videoPath,self, @selector(video:didFinishSavingWithError:contextInfo:), nil);//保存視頻到相簿
}
- (void)video:(NSString *)videoPath didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo
{
if (error) {
NSLog(@"保存視頻過(guò)程中發(fā)生錯(cuò)誤,錯(cuò)誤信息:%@",error.localizedDescription);
}else{
NSLog(@"視頻保存成功.");
}
}
另外一個(gè)是ALAssetsLibrary,不過(guò)在 iOS9.0 已經(jīng)全部被廢棄了,改成了PHPhotoLibrary(這是 Photos 框架的一個(gè)組成部分,完整的可以點(diǎn)這個(gè)鏈接,關(guān)于這個(gè)框架的用法,改天再寫(xiě)一個(gè)吧)
9.0及以前的寫(xiě)法
//保存照片到相冊(cè)
[[[ALAssetsLibrary alloc]init] writeImageToSavedPhotosAlbum:[img CGImage] orientation:(ALAssetOrientation)img.imageOrientation completionBlock:^(NSURL *assetURL, NSError *error) {
if (error) {
NSLog(@"Save image fail:%@",error);
}else{
NSLog(@"Save image succeed.");
}
}];
//保存視頻到相冊(cè)
[[[ALAssetsLibrary alloc]init] writeVideoAtPathToSavedPhotosAlbum:[NSURL URLWithString:videoPath] completionBlock:^(NSURL *assetURL, NSError *error) {
if (error) {
NSLog(@"Save video fail:%@",error);
}else{
NSLog(@"Save video succeed.");
}
}];
AVCaptureSession
先放上一個(gè)微信小視頻的模仿 Demo, 因?yàn)橹荒苷鏅C(jī),所以比較嘈雜~大家別介意

流程
AVCaptureSession通過(guò)把設(shè)備的麥克風(fēng)/攝像頭(AVCaptureDevice)實(shí)例化成數(shù)據(jù)流輸入對(duì)象(AVCaptureDeviceInput)后,再通過(guò)建立連接(AVCaptionConnection)將錄制數(shù)據(jù)通過(guò)數(shù)據(jù)流輸出對(duì)象(AVCaptureOutput)導(dǎo)出,而錄制的時(shí)候咱們可以同步預(yù)覽當(dāng)前的錄制界面(AVCaptureVideoPreviewLayer).
`AVCaptureSession`是一個(gè)會(huì)話對(duì)象,是設(shè)備音頻/視頻整個(gè)錄制期間的管理者.
`AVCaptureDevice`其實(shí)是咱們的物理設(shè)備映射到程序中的一個(gè)對(duì)象.咱們可以通過(guò)其來(lái)操作:閃光燈,手電筒,聚焦模式等
`AVCaptureDeviceInput`是錄制期間輸入流數(shù)據(jù)的管理對(duì)象.
`AVCaptionConnection`是將輸入流/輸出流連接起來(lái)的連接對(duì)象,視頻/音頻穩(wěn)定,預(yù)覽與錄制方向一致都在這里設(shè)置,還有audioChannels聲道
`AVCaptureOutput`是輸出流數(shù)據(jù)的管理對(duì)象,通過(guò)頭文件可以看到有很多子類(lèi),而我們通常也使用其子類(lèi)
`AVCaptureVideoPreviewLayer`是一個(gè) `CALyer` ,可以讓我們預(yù)覽拍攝過(guò)程中的圖像
詳解各部分
此處,咱們就模仿一個(gè)微信小視頻的 demo, 然后把各個(gè)主要步驟寫(xiě)在下面.
授權(quán)
首先獲取授權(quán)AVAuthorizationStatus,我們需要獲取哪個(gè)設(shè)備的使用權(quán)限,就進(jìn)行請(qǐng)求,需要注意的是,如果用戶(hù)未進(jìn)行授權(quán)授權(quán)選擇,咱們還要重復(fù)請(qǐng)求一次.
//獲取授權(quán)
- (void)getAuthorization
{
//此處獲取攝像頭授權(quán)
switch ([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo])
{
case AVAuthorizationStatusAuthorized: //已授權(quán),可使用 The client is authorized to access the hardware supporting a media type.
{
break;
}
case AVAuthorizationStatusNotDetermined: //未進(jìn)行授權(quán)選擇 Indicates that the user has not yet made a choice regarding whether the client can access the hardware.
{
//則再次請(qǐng)求授權(quán)
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
if(granted){ //用戶(hù)授權(quán)成功
return;
} else { //用戶(hù)拒絕授權(quán)
return;
}
}];
break;
}
default: //用戶(hù)拒絕授權(quán)/未授權(quán)
{
break;
}
}
}
相關(guān)枚舉
/*
AVAuthorizationStatusNotDetermined = 0,// 未進(jìn)行授權(quán)選擇
AVAuthorizationStatusRestricted, // 未授權(quán),且用戶(hù)無(wú)法更新,如家長(zhǎng)控制情況下
AVAuthorizationStatusDenied, // 用戶(hù)拒絕App使用
AVAuthorizationStatusAuthorized, // 已授權(quán),可使用
*/
根據(jù)流程創(chuàng)建對(duì)象
AVCaptureSession
先創(chuàng)建本次小視頻的會(huì)話對(duì)象_captureSession,設(shè)置視頻分辨率.注意,這個(gè)地方設(shè)置的模式/分辨率大小將影響你后面拍攝照片/視頻的大小
- (void)addSession
{
_captureSession = [[AVCaptureSession alloc] init];
if ([_captureSession canSetSessionPreset:AVCaptureSessionPreset640x480]) {
[_captureSession setSessionPreset:AVCaptureSessionPreset640x480];
}
}
相關(guān)枚舉
/* 通常支持如下格式
(
AVAssetExportPresetLowQuality,
AVAssetExportPreset960x540,
AVAssetExportPreset640x480,
AVAssetExportPresetMediumQuality,
AVAssetExportPreset1920x1080,
AVAssetExportPreset1280x720,
AVAssetExportPresetHighestQuality,
AVAssetExportPresetAppleM4A
)
*/
AVCaptureDevice
然后咱們要把設(shè)備接入進(jìn)來(lái)了,依次是_videoDevice和_audioDevice,還有注意,在給會(huì)話信息添加設(shè)備對(duì)象的時(shí)候,需要調(diào)用_captureSession的一個(gè)方法組beginConfiguration和commitConfiguration,最后,咱們把錄制過(guò)程中的預(yù)覽圖層PreviewLayer也添加進(jìn)來(lái),設(shè)置完畢后,開(kāi)啟會(huì)話startRunning-->注意,不等于開(kāi)始錄制,在不再需要使用會(huì)話相關(guān)時(shí),還需要stopRunning
[_captureSession beginConfiguration];
[self addVideo];
[self addAudio];
[self addPreviewLayer];
[_captureSession commitConfiguration];
[_captureSession startRunning];
video 相關(guān)
- (void)addVideo
{
// 獲取攝像頭輸入設(shè)備, 創(chuàng)建 AVCaptureDeviceInput 對(duì)象
_videoDevice = [self deviceWithMediaType:AVMediaTypeVideo preferringPosition:AVCaptureDevicePositionBack];
[self addVideoInput];
[self addMovieOutput];
}
相關(guān)枚舉
/* MediaType
AVF_EXPORT NSString *const AVMediaTypeVideo NS_AVAILABLE(10_7, 4_0); //視頻
AVF_EXPORT NSString *const AVMediaTypeAudio NS_AVAILABLE(10_7, 4_0); //音頻
AVF_EXPORT NSString *const AVMediaTypeText NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVMediaTypeClosedCaption NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVMediaTypeSubtitle NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVMediaTypeTimecode NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVMediaTypeMetadata NS_AVAILABLE(10_8, 6_0);
AVF_EXPORT NSString *const AVMediaTypeMuxed NS_AVAILABLE(10_7, 4_0);
*/
/* AVCaptureDevicePosition
typedef NS_ENUM(NSInteger, AVCaptureDevicePosition) {
AVCaptureDevicePositionUnspecified = 0,
AVCaptureDevicePositionBack = 1, //后置攝像頭
AVCaptureDevicePositionFront = 2 //前置攝像頭
} NS_AVAILABLE(10_7, 4_0) __TVOS_PROHIBITED;
*/
下面是獲取攝像頭的方法
#pragma mark 獲取攝像頭-->前/后
- (AVCaptureDevice *)deviceWithMediaType:(NSString *)mediaType preferringPosition:(AVCaptureDevicePosition)position
{
NSArray *devices = [AVCaptureDevice devicesWithMediaType:mediaType];
AVCaptureDevice *captureDevice = devices.firstObject;
for ( AVCaptureDevice *device in devices ) {
if ( device.position == position ) {
captureDevice = device;
break;
}
}
return captureDevice;
}
//下面這2個(gè)也可以獲取前后攝像頭,不過(guò)有一定的風(fēng)險(xiǎn),假如手機(jī)又問(wèn)題,找不到對(duì)應(yīng)的 UniqueID 設(shè)備,則呵呵了
//- (AVCaptureDevice *)frontCamera
//{
// return [AVCaptureDevice deviceWithUniqueID:@"com.apple.avfoundation.avcapturedevice.built-in_video:1"];
//}
//
//- (AVCaptureDevice *)backCamera
//{
// return [AVCaptureDevice deviceWithUniqueID:@"com.apple.avfoundation.avcapturedevice.built-in_video:0"];
//}
添加視頻輸入對(duì)象AVCaptureDeviceInput,根據(jù)輸入設(shè)備初始化輸入對(duì)象,用戶(hù)獲取輸入數(shù)據(jù),將視頻輸入對(duì)象添加到會(huì)話 (AVCaptureSession) 中
- (void)addVideoInput
{
NSError *videoError;
_videoInput = [[AVCaptureDeviceInput alloc] initWithDevice:_videoDevice error:&videoError];
if (videoError) {
NSLog(@"---- 取得攝像頭設(shè)備時(shí)出錯(cuò) ------ %@",videoError);
return;
}
if ([_captureSession canAddInput:_videoInput]) {
[_captureSession addInput:_videoInput];
}
}
接下來(lái)是視頻輸出對(duì)象AVCaptureMovieFileOutput及連接管理對(duì)象AVCaptureConnection,還有視頻穩(wěn)定設(shè)置preferredVideoStabilizationMode,視頻旋轉(zhuǎn)方向setVideoOrientation等
- (void)addMovieOutput
{
_movieOutput = [[AVCaptureMovieFileOutput alloc] init];
if ([_captureSession canAddOutput:_movieOutput]) {
[_captureSession addOutput:_movieOutput];
AVCaptureConnection *captureConnection = [_movieOutput connectionWithMediaType:AVMediaTypeVideo];
if ([captureConnection isVideoStabilizationSupported]) {
captureConnection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeAuto;
}
captureConnection.videoScaleAndCropFactor = captureConnection.videoMaxScaleAndCropFactor;
}
}
相關(guān)枚舉
//設(shè)置視頻旋轉(zhuǎn)方向
/*
typedef NS_ENUM(NSInteger, AVCaptureVideoOrientation) {
AVCaptureVideoOrientationPortrait = 1,
AVCaptureVideoOrientationPortraitUpsideDown = 2,
AVCaptureVideoOrientationLandscapeRight = 3,
AVCaptureVideoOrientationLandscapeLeft = 4,
} NS_AVAILABLE(10_7, 4_0) __TVOS_PROHIBITED;
*/
audio 相關(guān)
咱們?cè)俳又砑右纛l相關(guān)的,包括音頻輸入設(shè)備AVCaptureDevice,音頻輸入對(duì)象AVCaptureDeviceInput,并且將音頻輸入對(duì)象添加到會(huì)話
- (void)addAudio
{
NSError *audioError;
_audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
_audioInput = [[AVCaptureDeviceInput alloc] initWithDevice:_audioDevice error:&audioError];
if (audioError) {
NSLog(@"取得錄音設(shè)備時(shí)出錯(cuò) ------ %@",audioError);
return;
}
if ([_captureSession canAddInput:_audioInput]) {
[_captureSession addInput:_audioInput];
}
}
AVCaptureVideoPreviewLayer
通過(guò)會(huì)話AVCaptureSession創(chuàng)建預(yù)覽層AVCaptureVideoPreviewLayer,設(shè)置填充模式videoGravity,預(yù)覽圖層方向videoOrientation,并且設(shè)置 layer 想要顯示的位置
- (void)addPreviewLayer
{
_captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:_captureSession];
_captureVideoPreviewLayer.frame = self.view.layer.bounds;
// _captureVideoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
_captureVideoPreviewLayer.connection.videoOrientation = [_movieOutput connectionWithMediaType:AVMediaTypeVideo].videoOrientation;
_captureVideoPreviewLayer.position = CGPointMake(self.view.width*0.5,self.videoView.height*0.5);
CALayer *layer = self.videoView.layer;
layer.masksToBounds = true;
[self.view layoutIfNeeded];
[layer addSublayer:_captureVideoPreviewLayer];
}
相關(guān)枚舉
/* 填充模式
Options are AVLayerVideoGravityResize, AVLayerVideoGravityResizeAspect and AVLayerVideoGravityResizeAspectFill. AVLayerVideoGravityResizeAspect is default.
*/
最后,開(kāi)始錄制視頻,結(jié)束錄制視頻,重新錄制
- (void)startRecord
{
[_movieOutput startRecordingToOutputFileURL:[self outPutFileURL] recordingDelegate:self];
}
- (NSURL *)outPutFileURL
{
return [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@%@", NSTemporaryDirectory(), @"outPut.mov"]];
}
- (void)stopRecord
{
[_movieOutput stopRecording];
}
錄制相關(guān)delegate
包括開(kāi)始錄制,錄制結(jié)束等
- (void)captureOutput:(AVCaptureFileOutput *)captureOutput didStartRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections
{
NSLog(@"---- 開(kāi)始錄制 ----");
}
- (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error
{
NSLog(@"---- 錄制結(jié)束 ----%@ ",captureOutput.outputFileURL);
if (self.canSave) {
[self pushToPlay:captureOutput.outputFileURL];
self.canSave = NO;
}
}
壓縮/保存視頻
咱們需要把錄制完畢的視頻保存下來(lái).而通常錄制完畢的視頻是很大的,咱們需要壓縮一下再保存.
可以通過(guò)AVAssetExportSession來(lái)進(jìn)行壓縮,并且可以?xún)?yōu)化網(wǎng)絡(luò)shouldOptimizeForNetworkUse,設(shè)置轉(zhuǎn)后的格式outputFileType,并且開(kāi)啟異步壓縮exportAsynchronouslyWithCompletionHandler
#pragma mark 保存壓縮
- (NSURL *)compressedURL
{
return [NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true) lastObject] stringByAppendingPathComponent:[NSString stringWithFormat:@"compressed.mp4"]]];
}
- (CGFloat)fileSize:(NSURL *)path
{
return [[NSData dataWithContentsOfURL:path] length]/1024.00 /1024.00;
}
// 壓縮視頻
- (IBAction)compressVideo:(id)sender
{
NSLog(@"開(kāi)始?jí)嚎s,壓縮前大小 %f MB",[self fileSize:self.videoUrl]);
AVURLAsset *avAsset = [[AVURLAsset alloc] initWithURL:self.videoUrl options:nil];
NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:avAsset];
if ([compatiblePresets containsObject:AVAssetExportPresetLowQuality]) {
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:avAsset presetName:AVAssetExportPreset640x480];
exportSession.outputURL = [self compressedURL];
exportSession.shouldOptimizeForNetworkUse = true;
exportSession.outputFileType = AVFileTypeMPEG4;
[exportSession exportAsynchronouslyWithCompletionHandler:^{
if ([exportSession status] == AVAssetExportSessionStatusCompleted) {
NSLog(@"壓縮完畢,壓縮后大小 %f MB",[self fileSize:[self compressedURL]]);
[self saveVideo:[self compressedURL]];
}else{
NSLog(@"當(dāng)前壓縮進(jìn)度:%f",exportSession.progress);
}
}];
}
}
- (void)saveVideo:(NSURL *)outputFileURL
{
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library writeVideoAtPathToSavedPhotosAlbum:outputFileURL
completionBlock:^(NSURL *assetURL, NSError *error) {
if (error) {
NSLog(@"保存視頻失敗:%@",error);
} else {
NSLog(@"保存視頻到相冊(cè)成功");
}
}];
}
播放錄制完的視頻以及重復(fù)播放
播放錄制的視頻,以及重復(fù)播放
- (void)create
{
_playItem = [AVPlayerItem playerItemWithURL:self.videoUrl];
_player = [AVPlayer playerWithPlayerItem:_playItem];
_playerLayer =[AVPlayerLayer playerLayerWithPlayer:_player];
_playerLayer.frame = CGRectMake(200, 200, 100, 100);
_playerLayer.videoGravity=AVLayerVideoGravityResizeAspectFill;//視頻填充模式
[self.view.layer addSublayer:_playerLayer];
[_player play];
}
-(void)playbackFinished:(NSNotification *)notification
{
[_player seekToTime:CMTimeMake(0, 1)];
[_player play];
}
交互相關(guān)
接下來(lái),是一些和交互相關(guān)的,比如切換攝像頭,開(kāi)關(guān)閃光燈,還有白平衡啥的.
注意,改變?cè)O(shè)備屬性前一定要首先調(diào)用lockForConfiguration方法加鎖,調(diào)用完之后使用unlockForConfiguration方法解鎖.
意義是---進(jìn)行設(shè)備屬性修改期間,先鎖定設(shè)備,防止多處同時(shí)修改設(shè)備.因?yàn)榭赡苡卸嗵幉煌男薷?咱們將其封裝起來(lái)最好
-(void)changeDevicePropertySafety:(void (^)(AVCaptureDevice *captureDevice))propertyChange{
//也可以直接用_videoDevice,但是下面這種更好
AVCaptureDevice *captureDevice= [_videoInput device];
NSError *error;
BOOL lockAcquired = [captureDevice lockForConfiguration:&error];
if (!lockAcquired) {
NSLog(@"鎖定設(shè)備過(guò)程error,錯(cuò)誤信息:%@",error.localizedDescription);
}else{
[_captureSession beginConfiguration];
propertyChange(captureDevice);
[captureDevice unlockForConfiguration];
[_captureSession commitConfiguration];
}
}
開(kāi)/關(guān)閃光燈
閃光模式開(kāi)啟后,并無(wú)明顯感覺(jué),所以還需要開(kāi)啟手電筒,并且開(kāi)啟前先判斷是否自持,否則崩潰
- (IBAction)changeFlashlight:(UIButton *)sender {
BOOL con1 = [_videoDevice hasTorch]; //支持手電筒模式
BOOL con2 = [_videoDevice hasFlash]; //支持閃光模式
if (con1 && con2)
{
[self changeDevicePropertySafety:^(AVCaptureDevice *captureDevice) {
if (_videoDevice.flashMode == AVCaptureFlashModeOn) //閃光燈開(kāi)
{
[_videoDevice setFlashMode:AVCaptureFlashModeOff];
[_videoDevice setTorchMode:AVCaptureTorchModeOff];
}else if (_videoDevice.flashMode == AVCaptureFlashModeOff) //閃光燈關(guān)
{
[_videoDevice setFlashMode:AVCaptureFlashModeOn];
[_videoDevice setTorchMode:AVCaptureTorchModeOn];
}
}];
sender.selected=!sender.isSelected;
}else{
NSLog(@"不能切換閃光模式");
}
}
切換攝像頭
根據(jù)現(xiàn)在正在使用的攝像頭來(lái)判斷需要切換的攝像頭
- (IBAction)changeCamera{
switch (_videoDevice.position) {
case AVCaptureDevicePositionBack:
_videoDevice = [self deviceWithMediaType:AVMediaTypeVideo preferringPosition:AVCaptureDevicePositionFront];
break;
case AVCaptureDevicePositionFront:
_videoDevice = [self deviceWithMediaType:AVMediaTypeVideo preferringPosition:AVCaptureDevicePositionBack];
break;
default:
return;
break;
}
[self changeDevicePropertySafety:^(AVCaptureDevice *captureDevice) {
NSError *error;
AVCaptureDeviceInput *newVideoInput = [[AVCaptureDeviceInput alloc] initWithDevice:_videoDevice error:&error];
if (newVideoInput != nil) {
//必選先 remove 才能詢(xún)問(wèn) canAdd
[_captureSession removeInput:_videoInput];
if ([_captureSession canAddInput:newVideoInput]) {
[_captureSession addInput:newVideoInput];
_videoInput = newVideoInput;
}else{
[_captureSession addInput:_videoInput];
}
} else if (error) {
NSLog(@"切換前/后攝像頭失敗, error = %@", error);
}
}];
}
聚焦模式,曝光模式,拉近/遠(yuǎn)鏡頭(焦距)
同時(shí)存在單擊和雙擊的手勢(shì),咱們?nèi)缦略O(shè)置,requireGestureRecognizerToFail的作用就是每次只生效一個(gè)手勢(shì)
-(void)addGenstureRecognizer{
UITapGestureRecognizer *singleTapGesture=[[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(singleTap:)];
singleTapGesture.numberOfTapsRequired = 1;
singleTapGesture.delaysTouchesBegan = YES;
UITapGestureRecognizer *doubleTapGesture=[[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(doubleTap:)];
doubleTapGesture.numberOfTapsRequired = 2;
doubleTapGesture.delaysTouchesBegan = YES;
[singleTapGesture requireGestureRecognizerToFail:doubleTapGesture];
[self.videoView addGestureRecognizer:singleTapGesture];
[self.videoView addGestureRecognizer:doubleTapGesture];
}
單擊修改聚焦模式setFocusMode及聚焦點(diǎn)setFocusPointOfInterest,還有曝光模式setExposureMode及曝光點(diǎn)setExposurePointOfInterest
注意,攝像頭的點(diǎn)范圍是0~1,咱們需要把點(diǎn)轉(zhuǎn)化一下,使用captureDevicePointOfInterestForPoint
-(void)singleTap:(UITapGestureRecognizer *)tapGesture{
CGPoint point= [tapGesture locationInView:self.videoView];
CGPoint cameraPoint= [_captureVideoPreviewLayer captureDevicePointOfInterestForPoint:point];
[self setFocusCursorAnimationWithPoint:point];
[self changeDevicePropertySafety:^(AVCaptureDevice *captureDevice) {
//聚焦
if ([captureDevice isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) {
[captureDevice setFocusMode:AVCaptureFocusModeContinuousAutoFocus];
}else{
NSLog(@"聚焦模式修改失敗");
}
//聚焦點(diǎn)的位置
if ([captureDevice isFocusPointOfInterestSupported]) {
[captureDevice setFocusPointOfInterest:cameraPoint];
}
//曝光模式
if ([captureDevice isExposureModeSupported:AVCaptureExposureModeAutoExpose]) {
[captureDevice setExposureMode:AVCaptureExposureModeAutoExpose];
}else{
NSLog(@"曝光模式修改失敗");
}
//曝光點(diǎn)的位置
if ([captureDevice isExposurePointOfInterestSupported]) {
[captureDevice setExposurePointOfInterest:cameraPoint];
}
}];
}
下面是雙擊設(shè)置焦距videoZoomFactor
-(void)doubleTap:(UITapGestureRecognizer *)tapGesture{
NSLog(@"雙擊");
[self changeDevicePropertySafety:^(AVCaptureDevice *captureDevice) {
if (captureDevice.videoZoomFactor == 1.0) {
CGFloat current = 1.5;
if (current < captureDevice.activeFormat.videoMaxZoomFactor) {
[captureDevice rampToVideoZoomFactor:current withRate:10];
}
}else{
[captureDevice rampToVideoZoomFactor:1.0 withRate:10];
}
}];
}
相關(guān)枚舉
/*
@constant AVCaptureFocusModeLocked 鎖定在當(dāng)前焦距
Indicates that the focus should be locked at the lens' current position.
@constant AVCaptureFocusModeAutoFocus 自動(dòng)對(duì)焦一次,然后切換到焦距鎖定
Indicates that the device should autofocus once and then change the focus mode to AVCaptureFocusModeLocked.
@constant AVCaptureFocusModeContinuousAutoFocus 當(dāng)需要時(shí).自動(dòng)調(diào)整焦距
Indicates that the device should automatically focus when needed.
*/
/*
@constant AVCaptureExposureModeLocked 曝光鎖定在當(dāng)前值
Indicates that the exposure should be locked at its current value.
@constant AVCaptureExposureModeAutoExpose 曝光自動(dòng)調(diào)整一次然后鎖定
Indicates that the device should automatically adjust exposure once and then change the exposure mode to AVCaptureExposureModeLocked.
@constant AVCaptureExposureModeContinuousAutoExposure 曝光自動(dòng)調(diào)整
Indicates that the device should automatically adjust exposure when needed.
@constant AVCaptureExposureModeCustom 曝光只根據(jù)設(shè)定的值來(lái)
Indicates that the device should only adjust exposure according to user provided ISO, exposureDuration values.
*/