iOS 自定義相機(jī), UIImagePickerController && AVCaptureSession (附微信小視頻模仿demo)

今天介紹自定義相機(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è),分別是UINavigationControllerDelegateUIImagePickerControllerDelegate

@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è)方法組beginConfigurationcommitConfiguration,最后,咱們把錄制過(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.
         
         */
最后編輯于
?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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