1. 程式人生 > >iOS開發-自定義相機(仿微信)拍照、視訊錄製

iOS開發-自定義相機(仿微信)拍照、視訊錄製

網上有很多自定義相機的例子,這裡只是我臨時寫的一個小demo,僅供參考:
用到了下面幾個庫:
#import <AVFoundation/AVFoundation.h>
#import <AssetsLibrary/AssetsLibrary.h>

在使用的時候需要在Info.plist中把相關許可權寫進去:
Privacy - Microphone Usage Description
Privacy - Photo Library Usage Description
Privacy - Camera Usage Description

我在寫這個demo時,是按照微信的樣式寫的,同樣是點選拍照、長按錄製視訊,視訊錄製完直接進行播放,這裡封裝了一個簡易的播放器:

m檔案

#import "HAVPlayer.h"
#import <AVFoundation/AVFoundation.h>

@interface HAVPlayer ()

@property (nonatomic,strong) AVPlayer *player;//播放器物件

@end

@implementation HAVPlayer

/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
    // Drawing code
}
*/
- (instancetype)initWithFrame:(CGRect)frame withShowInView:(UIView *)bgView url:(NSURL *)url { if (self = [self initWithFrame:frame]) { //建立播放器層 AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player]; playerLayer.frame = self.bounds; [self
.layer addSublayer:playerLayer]; if (url) { self.videoUrl = url; } [bgView addSubview:self]; } return self; } - (void)dealloc { [self removeAvPlayerNtf]; [self stopPlayer]; self.player = nil; } - (AVPlayer *)player { if (!_player) { _player = [AVPlayer playerWithPlayerItem:[self getAVPlayerItem]]; [self addAVPlayerNtf:_player.currentItem]; } return _player; } - (AVPlayerItem *)getAVPlayerItem { AVPlayerItem *playerItem=[AVPlayerItem playerItemWithURL:self.videoUrl]; return playerItem; } - (void)setVideoUrl:(NSURL *)videoUrl { _videoUrl = videoUrl; [self removeAvPlayerNtf]; [self nextPlayer]; } - (void)nextPlayer { [self.player seekToTime:CMTimeMakeWithSeconds(0, _player.currentItem.duration.timescale)]; [self.player replaceCurrentItemWithPlayerItem:[self getAVPlayerItem]]; [self addAVPlayerNtf:self.player.currentItem]; if (self.player.rate == 0) { [self.player play]; } } - (void) addAVPlayerNtf:(AVPlayerItem *)playerItem { //監控狀態屬性 [playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil]; //監控網路載入情況屬性 [playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:self.player.currentItem]; } - (void)removeAvPlayerNtf { AVPlayerItem *playerItem = self.player.currentItem; [playerItem removeObserver:self forKeyPath:@"status"]; [playerItem removeObserver:self forKeyPath:@"loadedTimeRanges"]; [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (void)stopPlayer { if (self.player.rate == 1) { [self.player pause];//如果在播放狀態就停止 } } /** * 通過KVO監控播放器狀態 * * @param keyPath 監控屬性 * @param object 監視器 * @param change 狀態改變 * @param context 上下文 */ -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{ AVPlayerItem *playerItem = object; if ([keyPath isEqualToString:@"status"]) { AVPlayerStatus status= [[change objectForKey:@"new"] intValue]; if(status==AVPlayerStatusReadyToPlay){ NSLog(@"正在播放...,視訊總長度:%.2f",CMTimeGetSeconds(playerItem.duration)); } }else if([keyPath isEqualToString:@"loadedTimeRanges"]){ NSArray *array=playerItem.loadedTimeRanges; CMTimeRange timeRange = [array.firstObject CMTimeRangeValue];//本次緩衝時間範圍 float startSeconds = CMTimeGetSeconds(timeRange.start); float durationSeconds = CMTimeGetSeconds(timeRange.duration); NSTimeInterval totalBuffer = startSeconds + durationSeconds;//緩衝總長度 NSLog(@"共緩衝:%.2f",totalBuffer); } } - (void)playbackFinished:(NSNotification *)ntf { Plog(@"視訊播放完成"); [self.player seekToTime:CMTimeMake(0, 1)]; [self.player play]; } @end

另外微信下面的按鈕長按會出現圓弧時間條:

m檔案

#import "HProgressView.h"

@interface HProgressView ()

/**
 *  進度值0-1.0之間
 */
@property (nonatomic,assign)CGFloat progressValue;

@property (nonatomic, assign) CGFloat currentTime;

@end

@implementation HProgressView


// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
    // Drawing code
    CGContextRef ctx = UIGraphicsGetCurrentContext();//獲取上下文
    Plog(@"width = %f",self.frame.size.width);
    CGPoint center = CGPointMake(self.frame.size.width/2.0, self.frame.size.width/2.0);  //設定圓心位置
    CGFloat radius = self.frame.size.width/2.0-5;  //設定半徑
    CGFloat startA = - M_PI_2;  //圓起點位置
    CGFloat endA = -M_PI_2 + M_PI * 2 * _progressValue;  //圓終點位置

    UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startA endAngle:endA clockwise:YES];

    CGContextSetLineWidth(ctx, 10); //設定線條寬度
    [[UIColor whiteColor] setStroke]; //設定描邊顏色

    CGContextAddPath(ctx, path.CGPath); //把路徑新增到上下文

    CGContextStrokePath(ctx);  //渲染
}

- (void)setTimeMax:(NSInteger)timeMax {
    _timeMax = timeMax;
    self.currentTime = 0;
    self.progressValue = 0;
    [self setNeedsDisplay];
    self.hidden = NO;
    [self performSelector:@selector(startProgress) withObject:nil afterDelay:0.1];
}

- (void)clearProgress {
    _currentTime = _timeMax;
    self.hidden = YES;
}

- (void)startProgress {
    _currentTime += 0.1;
    if (_timeMax > _currentTime) {
        _progressValue = _currentTime/_timeMax;
        Plog(@"progress = %f",_progressValue);
        [self setNeedsDisplay];
        [self performSelector:@selector(startProgress) withObject:nil afterDelay:0.1];
    }

    if (_timeMax <= _currentTime) {
        [self clearProgress];

    }
}

@end

接下來就是相機的控制器了,由於是臨時寫的,所以用的xib,大家不要直接使用,直接上m檔案程式碼吧:

#import "HVideoViewController.h"
#import <AVFoundation/AVFoundation.h>
#import "HAVPlayer.h"
#import "HProgressView.h"
#import <Foundation/Foundation.h>
#import <AssetsLibrary/AssetsLibrary.h>

typedef void(^PropertyChangeBlock)(AVCaptureDevice *captureDevice);
@interface HVideoViewController ()<AVCaptureFileOutputRecordingDelegate>

//輕觸拍照,按住攝像
@property (strong, nonatomic) IBOutlet UILabel *labelTipTitle;

//視訊輸出流
@property (strong,nonatomic) AVCaptureMovieFileOutput *captureMovieFileOutput;
//圖片輸出流
//@property (strong,nonatomic) AVCaptureStillImageOutput *captureStillImageOutput;//照片輸出流
//負責從AVCaptureDevice獲得輸入資料
@property (strong,nonatomic) AVCaptureDeviceInput *captureDeviceInput;
//後臺任務標識
@property (assign,nonatomic) UIBackgroundTaskIdentifier backgroundTaskIdentifier;

@property (assign,nonatomic) UIBackgroundTaskIdentifier lastBackgroundTaskIdentifier;

@property (weak, nonatomic) IBOutlet UIImageView *focusCursor; //聚焦游標

//負責輸入和輸出裝置之間的資料傳遞
@property(nonatomic)AVCaptureSession *session;

//影象預覽層,實時顯示捕獲的影象
@property(nonatomic)AVCaptureVideoPreviewLayer *previewLayer;

@property (strong, nonatomic) IBOutlet UIButton *btnBack;
//重新錄製
@property (strong, nonatomic) IBOutlet UIButton *btnAfresh;
//確定
@property (strong, nonatomic) IBOutlet UIButton *btnEnsure;
//攝像頭切換
@property (strong, nonatomic) IBOutlet UIButton *btnCamera;

@property (strong, nonatomic) IBOutlet UIImageView *bgView;
//記錄錄製的時間 預設最大60秒
@property (assign, nonatomic) NSInteger seconds;

//記錄需要儲存視訊的路徑
@property (strong, nonatomic) NSURL *saveVideoUrl;

//是否在對焦
@property (assign, nonatomic) BOOL isFocus;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *afreshCenterX;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *ensureCenterX;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *backCenterX;

//視訊播放
@property (strong, nonatomic) HAVPlayer *player;

@property (strong, nonatomic) IBOutlet HProgressView *progressView;

//是否是攝像 YES 代表是錄製  NO 表示拍照
@property (assign, nonatomic) BOOL isVideo;

@property (strong, nonatomic) UIImage *takeImage;
@property (strong, nonatomic) UIImageView *takeImageView;
@property (strong, nonatomic) IBOutlet UIImageView *imgRecord;


@end

//時間大於這個就是視訊,否則為拍照
#define TimeMax 1

@implementation HVideoViewController


-(void)dealloc{
    [self removeNotification];


}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.

    UIImage *image = [UIImage imageNamed:@"sc_btn_take.png"];
    self.backCenterX.constant = -(SCREEN_WIDTH/2/2)-image.size.width/2/2;

    self.progressView.layer.cornerRadius = self.progressView.frame.size.width/2;

    if (self.HSeconds == 0) {
        self.HSeconds = 60;
    }

    [self performSelector:@selector(hiddenTipsLabel) withObject:nil afterDelay:4];
}

- (void)hiddenTipsLabel {
    self.labelTipTitle.hidden = YES;
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    [[UIApplication sharedApplication] setStatusBarHidden:YES];
    [self customCamera];
    [self.session startRunning];
}


-(void)viewDidAppear:(BOOL)animated{
    [super viewDidAppear:animated];
}

-(void)viewDidDisappear:(BOOL)animated{
    [super viewDidDisappear:animated];
    [self.session stopRunning];
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    [[UIApplication sharedApplication] setStatusBarHidden:NO];
}

- (void)customCamera {

    //初始化會話,用來結合輸入輸出
    self.session = [[AVCaptureSession alloc] init];
    //設定解析度 (裝置支援的最高解析度)
    if ([self.session canSetSessionPreset:AVCaptureSessionPresetHigh]) {
        self.session.sessionPreset = AVCaptureSessionPresetHigh;
    }
    //取得後置攝像頭
    AVCaptureDevice *captureDevice = [self getCameraDeviceWithPosition:AVCaptureDevicePositionBack];
    //新增一個音訊輸入裝置
    AVCaptureDevice *audioCaptureDevice=[[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] firstObject];

    //初始化輸入裝置
    NSError *error = nil;
    self.captureDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:captureDevice error:&error];
    if (error) {
        Plog(@"取得裝置輸入物件時出錯,錯誤原因:%@",error.localizedDescription);
        return;
    }

    //新增音訊
    error = nil;
    AVCaptureDeviceInput *audioCaptureDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:audioCaptureDevice error:&error];
    if (error) {
        NSLog(@"取得裝置輸入物件時出錯,錯誤原因:%@",error.localizedDescription);
        return;
    }

    //輸出物件
    self.captureMovieFileOutput = [[AVCaptureMovieFileOutput alloc] init];//視訊輸出

    //將輸入裝置新增到會話
    if ([self.session canAddInput:self.captureDeviceInput]) {
        [self.session addInput:self.captureDeviceInput];
        [self.session addInput:audioCaptureDeviceInput];
        //設定視訊防抖
        AVCaptureConnection *connection = [self.captureMovieFileOutput connectionWithMediaType:AVMediaTypeVideo];
        if ([connection isVideoStabilizationSupported]) {
            connection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeCinematic;
        }
    }

    //將輸出裝置新增到會話 (剛開始 是照片為輸出物件)
    if ([self.session canAddOutput:self.captureMovieFileOutput]) {
        [self.session addOutput:self.captureMovieFileOutput];
    }

    //建立視訊預覽層,用於實時展示攝像頭狀態
    self.previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.session];
    self.previewLayer.frame = self.view.bounds;//CGRectMake(0, 0, self.view.width, self.view.height);
    self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;//填充模式
    [self.bgView.layer addSublayer:self.previewLayer];

    [self addNotificationToCaptureDevice:captureDevice];
    [self addGenstureRecognizer];
}



- (IBAction)onCancelAction:(UIButton *)sender {
    [self dismissViewControllerAnimated:YES completion:^{
        [Utility hideProgressDialog];
    }];
}


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    if ([[touches anyObject] view] == self.imgRecord) {
        Plog(@"開始錄製");
        //根據裝置輸出獲得連線
        AVCaptureConnection *connection = [self.captureMovieFileOutput connectionWithMediaType:AVMediaTypeAudio];
        //根據連線取得裝置輸出的資料
        if (![self.captureMovieFileOutput isRecording]) {
            //如果支援多工則開始多工
            if ([[UIDevice currentDevice] isMultitaskingSupported]) {
                self.backgroundTaskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
            }
            if (self.saveVideoUrl) {
                [[NSFileManager defaultManager] removeItemAtURL:self.saveVideoUrl error:nil];
            }
            //預覽圖層和視訊方向保持一致
            connection.videoOrientation = [self.previewLayer connection].videoOrientation;
            NSString *outputFielPath=[NSTemporaryDirectory() stringByAppendingString:@"myMovie.mov"];
            NSLog(@"save path is :%@",outputFielPath);
            NSURL *fileUrl=[NSURL fileURLWithPath:outputFielPath];
            NSLog(@"fileUrl:%@",fileUrl);
            [self.captureMovieFileOutput startRecordingToOutputFileURL:fileUrl recordingDelegate:self];
        } else {
            [self.captureMovieFileOutput stopRecording];
        }
    }
}


- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    if ([[touches anyObject] view] == self.imgRecord) {
        Plog(@"結束觸控");
        if (!self.isVideo) {
            [self performSelector:@selector(endRecord) withObject:nil afterDelay:0.3];
        } else {
            [self endRecord];
        }
    }
}

- (void)endRecord {
    [self.captureMovieFileOutput stopRecording];//停止錄製
}

- (IBAction)onAfreshAction:(UIButton *)sender {
    Plog(@"重新錄製");
    [self recoverLayout];
}

- (IBAction)onEnsureAction:(UIButton *)sender {
    Plog(@"確定 這裡進行儲存或者傳送出去");
    if (self.saveVideoUrl) {
        WS(weakSelf)
        [Utility showProgressDialogText:@"視訊處理中..."];
        ALAssetsLibrary *assetsLibrary=[[ALAssetsLibrary alloc]init];
        [assetsLibrary writeVideoAtPathToSavedPhotosAlbum:self.saveVideoUrl completionBlock:^(NSURL *assetURL, NSError *error) {
            Plog(@"outputUrl:%@",weakSelf.saveVideoUrl);
            [[NSFileManager defaultManager] removeItemAtURL:weakSelf.saveVideoUrl error:nil];
            if (weakSelf.lastBackgroundTaskIdentifier!= UIBackgroundTaskInvalid) {
                [[UIApplication sharedApplication] endBackgroundTask:weakSelf.lastBackgroundTaskIdentifier];
            }
            if (error) {
                Plog(@"儲存視訊到相簿過程中發生錯誤,錯誤資訊:%@",error.localizedDescription);
                [Utility showAllTextDialog:KAppDelegate.window Text:@"儲存視訊到相簿發生錯誤"];
            } else {
                if (weakSelf.takeBlock) {
                    weakSelf.takeBlock(assetURL);
                }
                Plog(@"成功儲存視訊到相簿.");
                [weakSelf onCancelAction:nil];
            }
        }];
    } else {
        //照片
        UIImageWriteToSavedPhotosAlbum(self.takeImage, self, nil, nil);
        if (self.takeBlock) {
            self.takeBlock(self.takeImage);
        }

        [self onCancelAction:nil];
    }
}

//前後攝像頭的切換
- (IBAction)onCameraAction:(UIButton *)sender {
    Plog(@"切換攝像頭");
    AVCaptureDevice *currentDevice=[self.captureDeviceInput device];
    AVCaptureDevicePosition currentPosition=[currentDevice position];
    [self removeNotificationFromCaptureDevice:currentDevice];
    AVCaptureDevice *toChangeDevice;
    AVCaptureDevicePosition toChangePosition = AVCaptureDevicePositionFront;//前
    if (currentPosition == AVCaptureDevicePositionUnspecified || currentPosition == AVCaptureDevicePositionFront) {
        toChangePosition = AVCaptureDevicePositionBack;//後
    }
    toChangeDevice=[self getCameraDeviceWithPosition:toChangePosition];
    [self addNotificationToCaptureDevice:toChangeDevice];
    //獲得要調整的裝置輸入物件
    AVCaptureDeviceInput *toChangeDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:toChangeDevice error:nil];

    //改變會話的配置前一定要先開啟配置,配置完成後提交配置改變
    [self.session beginConfiguration];
    //移除原有輸入物件
    [self.session removeInput:self.captureDeviceInput];
    //新增新的輸入物件
    if ([self.session canAddInput:toChangeDeviceInput]) {
        [self.session addInput:toChangeDeviceInput];
        self.captureDeviceInput = toChangeDeviceInput;
    }
    //提交會話配置
    [self.session commitConfiguration];
}

- (void)onStartTranscribe:(NSURL *)fileURL {
    if ([self.captureMovieFileOutput isRecording]) {
        -- self.seconds;
        if (self.seconds > 0) {
            if (self.HSeconds - self.seconds >= TimeMax && !self.isVideo) {
                self.isVideo = YES;//長按時間超過TimeMax 表示是視訊錄製
                self.progressView.timeMax = self.seconds;
            }
            [self performSelector:@selector(onStartTranscribe:) withObject:fileURL afterDelay:1.0];
        } else {
            if ([self.captureMovieFileOutput isRecording]) {
                [self.captureMovieFileOutput stopRecording];
            }
        }
    }
}


#pragma mark - 視訊輸出代理
-(void)captureOutput:(AVCaptureFileOutput *)captureOutput didStartRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections{
    Plog(@"開始錄製...");
    self.seconds = self.HSeconds;
    [self performSelector:@selector(onStartTranscribe:) withObject:fileURL afterDelay:1.0];
}


-(void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error{
    Plog(@"視訊錄製完成.");
    [self changeLayout];
    if (self.isVideo) {
        self.saveVideoUrl = outputFileURL;
        if (!self.player) {
            self.player = [[HAVPlayer alloc] initWithFrame:self.bgView.bounds withShowInView:self.bgView url:outputFileURL];
        } else {
            if (outputFileURL) {
                self.player.videoUrl = outputFileURL;
                self.player.hidden = NO;
            }
        }
    } else {
        //照片
        self.saveVideoUrl = nil;
        [self videoHandlePhoto:outputFileURL];
    }

}

- (void)videoHandlePhoto:(NSURL *)url {
    AVURLAsset *urlSet = [AVURLAsset assetWithURL:url];
    AVAssetImageGenerator *imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:urlSet];
    imageGenerator.appliesPreferredTrackTransform = YES;    // 截圖的時候調整到正確的方向
    NSError *error = nil;
    CMTime time = CMTimeMake(0,30);//縮圖建立時間 CMTime是表示電影時間資訊的結構體,第一個引數表示是視訊第幾秒,第二個引數表示每秒幀數.(如果要獲取某一秒的第幾幀可以使用CMTimeMake方法)
    CMTime actucalTime; //縮圖實際生成的時間
    CGImageRef cgImage = [imageGenerator copyCGImageAtTime:time actualTime:&actucalTime error:&error];
    if (error) {
        Plog(@"擷取視訊圖片失敗:%@",error.localizedDescription);
    }
    CMTimeShow(actucalTime);
    UIImage *image = [UIImage imageWithCGImage:cgImage];

    CGImageRelease(cgImage);
    if (image) {
        Plog(@"視訊擷取成功");
    } else {
        Plog(@"視訊擷取失敗");
    }


    self.takeImage = image;//[UIImage imageWithCGImage:cgImage];

    [[NSFileManager defaultManager] removeItemAtURL:url error:nil];

    if (!self.takeImageView) {
        self.takeImageView = [[UIImageView alloc] initWithFrame:self.view.frame];
        [self.bgView addSubview:self.takeImageView];
    }
    self.takeImageView.hidden = NO;
    self.takeImageView.image = self.takeImage;
}

#pragma mark - 通知

//註冊通知
- (void)setupObservers
{
    NSNotificationCenter *notification = [NSNotificationCenter defaultCenter];
    [notification addObserver:self selector:@selector(applicationDidEnterBackground:) name:UIApplicationWillResignActiveNotification object:[UIApplication sharedApplication]];
}

//進入後臺就退出視訊錄製
- (void)applicationDidEnterBackground:(NSNotification *)notification {
    [self onCancelAction:nil];
}

/**
 *  給輸入裝置新增通知
 */
-(void)addNotificationToCaptureDevice:(AVCaptureDevice *)captureDevice{
    //注意新增區域改變捕獲通知必須首先設定裝置允許捕獲
    [self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
        captureDevice.subjectAreaChangeMonitoringEnabled=YES;
    }];
    NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
    //捕獲區域發生改變
    [notificationCenter addObserver:self selector:@selector(areaChange:) name:AVCaptureDeviceSubjectAreaDidChangeNotification object:captureDevice];
}
-(void)removeNotificationFromCaptureDevice:(AVCaptureDevice *)captureDevice{
    NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
    [notificationCenter removeObserver:self name:AVCaptureDeviceSubjectAreaDidChangeNotification object:captureDevice];
}
/**
 *  移除所有通知
 */
-(void)removeNotification{
    NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
    [notificationCenter removeObserver:self];
}

-(void)addNotificationToCaptureSession:(AVCaptureSession *)captureSession{
    NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
    //會話出錯
    [notificationCenter addObserver:self selector:@selector(sessionRuntimeError:) name:AVCaptureSessionRuntimeErrorNotification object:captureSession];
}

/**
 *  裝置連線成功
 *
 *  @param notification 通知物件
 */
-(void)deviceConnected:(NSNotification *)notification{
    NSLog(@"裝置已連線...");
}
/**
 *  裝置連線斷開
 *
 *  @param notification 通知物件
 */
-(void)deviceDisconnected:(NSNotification *)notification{
    NSLog(@"裝置已斷開.");
}
/**
 *  捕獲區域改變
 *
 *  @param notification 通知物件
 */
-(void)areaChange:(NSNotification *)notification{
    NSLog(@"捕獲區域改變...");
}

/**
 *  會話出錯
 *
 *  @param notification 通知物件
 */
-(void)sessionRuntimeError:(NSNotification *)notification{
    NSLog(@"會話發生錯誤.");
}



/**
 *  取得指定位置的攝像頭
 *
 *  @param position 攝像頭位置
 *
 *  @return 攝像頭裝置
 */
-(AVCaptureDevice *)getCameraDeviceWithPosition:(AVCaptureDevicePosition )position{
    NSArray *cameras= [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
    for (AVCaptureDevice *camera in cameras) {
        if ([camera position] == position) {
            return camera;
        }
    }
    return nil;
}

/**
 *  改變裝置屬性的統一操作方法
 *
 *  @param propertyChange 屬性改變操作
 */
-(void)changeDeviceProperty:(PropertyChangeBlock)propertyChange{
    AVCaptureDevice *captureDevice= [self.captureDeviceInput device];
    NSError *error;
    //注意改變裝置屬性前一定要首先呼叫lockForConfiguration:呼叫完之後使用unlockForConfiguration方法解鎖
    if ([captureDevice lockForConfiguration:&error]) {
        //自動白平衡
        if ([captureDevice isWhiteBalanceModeSupported:AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance]) {
            [captureDevice setWhiteBalanceMode:AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance];
        }
        //自動根據環境條件開啟閃光燈
        if ([captureDevice isFlashModeSupported:AVCaptureFlashModeAuto]) {
            [captureDevice setFlashMode:AVCaptureFlashModeAuto];
        }

        propertyChange(captureDevice);
        [captureDevice unlockForConfiguration];
    }else{
        NSLog(@"設定裝置屬性過程發生錯誤,錯誤資訊:%@",error.localizedDescription);
    }
}

/**
 *  設定閃光燈模式
 *
 *  @param flashMode 閃光燈模式
 */
-(void)setFlashMode:(AVCaptureFlashMode )flashMode{
    [self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
        if ([captureDevice isFlashModeSupported:flashMode]) {
            [captureDevice setFlashMode:flashMode];
        }
    }];
}
/**
 *  設定聚焦模式
 *
 *  @param focusMode 聚焦模式
 */
-(void)setFocusMode:(AVCaptureFocusMode )focusMode{
    [self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
        if ([captureDevice isFocusModeSupported:focusMode]) {
            [captureDevice setFocusMode:focusMode];
        }
    }];
}
/**
 *  設定曝光模式
 *
 *  @param exposureMode 曝光模式
 */
-(void)setExposureMode:(AVCaptureExposureMode)exposureMode{
    [self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
        if ([captureDevice isExposureModeSupported:exposureMode]) {
            [captureDevice setExposureMode:exposureMode];
        }
    }];
}
/**
 *  設定聚焦點
 *
 *  @param point 聚焦點
 */
-(void)focusWithMode:(AVCaptureFocusMode)focusMode exposureMode:(AVCaptureExposureMode)exposureMode atPoint:(CGPoint)point{
    [self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
//        if ([captureDevice isFocusPointOfInterestSupported]) {
//            [captureDevice setFocusPointOfInterest:point];
//        }
//        if ([captureDevice isExposurePointOfInterestSupported]) {
//            [captureDevice setExposurePointOfInterest:point];
//        }
        if ([captureDevice isExposureModeSupported:exposureMode]) {
            [captureDevice setExposureMode:exposureMode];
        }
        if ([captureDevice isFocusModeSupported:focusMode]) {
            [captureDevice setFocusMode:focusMode];
        }
    }];
}

/**
 *  新增點按手勢,點按時聚焦
 */
-(void)addGenstureRecognizer{
    UITapGestureRecognizer *tapGesture=[[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapScreen:)];
    [self.bgView addGestureRecognizer:tapGesture];
}

-(void)tapScreen:(UITapGestureRecognizer *)tapGesture{
    if ([self.session isRunning]) {
        CGPoint point= [tapGesture locationInView:self.bgView];
        //將UI座標轉化為攝像頭座標
        CGPoint cameraPoint= [self.previewLayer captureDevicePointOfInterestForPoint:point];
        [self setFocusCursorWithPoint:point];
        [self focusWithMode:AVCaptureFocusModeContinuousAutoFocus exposureMode:AVCaptureExposureModeContinuousAutoExposure atPoint:cameraPoint];
    }
}

/**
 *  設定聚焦游標位置
 *
 *  @param point 游標位置
 */
-(void)setFocusCursorWithPoint:(CGPoint)point{
    if (!self.isFocus) {
        self.isFocus = YES;
        self.focusCursor.center=point;
        self.focusCursor.transform = CGAffineTransformMakeScale(1.25, 1.25);
        self.focusCursor.alpha = 1.0;
        [UIView animateWithDuration:0.5 animations:^{
            self.focusCursor.transform = CGAffineTransformIdentity;
        } completion:^(BOOL finished) {
            [self performSelector:@selector(onHiddenFocusCurSorAction) withObject:nil afterDelay:0.5];
        }];
    }
}

- (void)onHiddenFocusCurSorAction {
    self.focusCursor.alpha=0;
    self.isFocus = NO;
}

//拍攝完成時呼叫
- (void)changeLayout {
    self.imgRecord.hidden = YES;
    self.btnCamera.hidden = YES;
    self.btnAfresh.hidden = NO;
    self.btnEnsure.hidden = NO;
    self.btnBack.hidden = YES;
    if (self.isVideo) {
        [self.progressView clearProgress];
    }
    self.afreshCenterX.constant = -(SCREEN_WIDTH/2/2);
    self.ensureCenterX.constant = SCREEN_WIDTH/2/2;
    [UIView animateWithDuration:0.25 animations:^{
        [self.view layoutIfNeeded];
    }];

    self.lastBackgroundTaskIdentifier = self.backgroundTaskIdentifier;
    self.backgroundTaskIdentifier = UIBackgroundTaskInvalid;
    [self.session stopRunning];
}


//重新拍攝時呼叫
- (void)recoverLayout {
    if (self.isVideo) {
        self.isVideo = NO;
        [self.player stopPlayer];
        self.player