AVFoundation实现相机和使用ALAssetsLibrary
//
// ViewController.m
// AVFoundtion
//
// Created by King on 16/8/26.
// Copyright © 2016年 King. All rights reserved.
//
#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
#import <AssetsLibrary/AssetsLibrary.h>
typedef void(^PropertyChangeBlock) (AVCaptureDevice *captureDevice);
@interface ViewController ()<AVCaptureFileOutputRecordingDelegate>
/** 负责输入和输出设备之间的数据传递 */
@property (nonatomic,strong) AVCaptureSession *captureSession;
/** 负责从AVCaptureDevice获得输入数据 */
@property (nonatomic,strong) AVCaptureDeviceInput *captureDeviceInput;
/** 视频输出流 */
@property (nonatomic,strong) AVCaptureMovieFileOutput *captureMovieFileOutput;
/** 相机拍摄预览图层 */
@property (nonatomic,strong) AVCaptureVideoPreviewLayer *captureVideoPreviewLayer;
/** 是否允许屏幕旋转(在录制屏幕中禁止旋转) */
@property (nonatomic,assign) BOOL enableRotation;
/** 旋转前大小 */
@property (nonatomic,assign) CGRect *lastBounds;
/** 后台任务标识 */
@property (nonatomic,assign) UIBackgroundTaskIdentifier backgroundTaskIdentifier;
@property (nonatomic , strong) UIView *viewContainer;
/** 自动闪光按钮 */
@property (nonatomic , strong) UIButton *flashAutoButton;
/** 打开闪光按钮 */
@property (nonatomic , strong) UIButton *flashOnButton;
/** 关闭闪光按钮 */
@property (nonatomic , strong) UIButton *flashOffButton;
/** 聚焦框 */
@property (nonatomic , strong) UIImageView *focusCursor;
/** 摄影按钮 */
@property (nonatomic , strong) UIButton *takeButton1;
@end
@implementation ViewController
#pragma mark - 视图控制器方法
- (void)viewDidLoad {
[super viewDidLoad];
self.viewContainer = [[UIView alloc] initWithFrame:CGRectMake(0, 24, self.view.frame.size.width, self.view.frame.size.height - 24 - 100)];
[self.view addSubview:self.viewContainer];
self.flashAutoButton = [UIButton buttonWithType:UIButtonTypeCustom];
[self.flashAutoButton setFrame:CGRectMake(10, self.viewContainer.frame.size.height + 50, 80, 40)];
[self.flashAutoButton setTitle:@"自动闪光" forState:UIControlStateNormal];
[self.flashAutoButton setBackgroundColor:[UIColor blackColor]];
[self.flashAutoButton addTarget:self action:@selector(flashAutoClick1:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.flashAutoButton];
self.takeButton1 = [UIButton buttonWithType:UIButtonTypeCustom];
[self.takeButton1 setFrame:CGRectMake(100, self.viewContainer.frame.size.height + 50, 80, 40)];
[self.takeButton1 setTitle:@"视频录制" forState:UIControlStateNormal];
[self.takeButton1 setTitle:@"结束录制" forState:UIControlStateSelected];
[self.takeButton1 setBackgroundColor:[UIColor redColor]];
[self.takeButton1 addTarget:self action:@selector(takeButtonClick11:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.takeButton1];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// 初始化会话
_captureSession = [[AVCaptureSession alloc] init];
if ([_captureSession canSetSessionPreset:AVCaptureSessionPreset1280x720]) { // 设置分辨率
_captureSession.sessionPreset = AVCaptureSessionPreset1280x720;
}
// 获得输入设备
AVCaptureDevice *captureDevice = [self getCameraDeviceWithPosition:AVCaptureDevicePositionBack]; // 得道后置摄像头
if (!captureDevice) {
NSLog(@"取得后置摄像头出现错误");
return;
}
// 添加一个音频输入设备
AVCaptureDevice *audioCaptureDevice = [[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] firstObject];
NSError *error = nil;
// 根据输入设备初始化输入对象,用于获得输入数据
_captureDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:captureDevice error:&error];
if (error) {
NSLog(@"取得设备输入对象时出错,错误原因:%@",error.localizedDescription);
return;
}
AVCaptureDeviceInput *audioCaptureDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:audioCaptureDevice error:&error];
if (error) {
NSLog(@"取得设备输入对象时出错,错误原因:%@",error.localizedDescription);
return;
}
// 初始化输出设备对象,用于获得输出数据
_captureMovieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
// 将设备输入添加到会话中
if ([_captureSession canAddInput:_captureDeviceInput]) {
[_captureSession addInput:_captureDeviceInput];
[_captureSession addInput:audioCaptureDeviceInput];
AVCaptureConnection *captureConnection=[_captureMovieFileOutput connectionWithMediaType:AVMediaTypeVideo];
if ([captureConnection isVideoStabilizationSupported ]) {
captureConnection.preferredVideoStabilizationMode=AVCaptureVideoStabilizationModeAuto;
}
}
// 将设备输出添加到会话中
if ([_captureSession canAddOutput:_captureMovieFileOutput]) {
[_captureSession addOutput:_captureMovieFileOutput];
}
// 创建视频预览层,用于时实展示摄像头状态
_captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.captureSession];
CALayer *layer = self.viewContainer.layer;
layer.masksToBounds = YES; // 设置图层的圆角属性
_captureVideoPreviewLayer.frame = layer.bounds;
_captureVideoPreviewLayer.videoGravity = AVLayerVideoGravityResize; // 填充模式显示在layer上
// 将视频预览层添加到界面中
[layer insertSublayer:_captureVideoPreviewLayer below:self.focusCursor.layer];
_enableRotation=NO;
[self addNotificationToCaptureDevice:captureDevice]; // 给输入设备添加通知
[self addGenstureRecognizer]; // 添加手势
[self setFlashModeButtonStatus]; // 设置闪光灯按钮状态
}
// 在控制器视图展示和视图离开界面时启动,停止会话
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self.captureSession startRunning];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[self.captureSession stopRunning];
}
- (BOOL)shouldAutorotate
{
return self.enableRotation;
}
// 旋转屏幕时调整视频预览图层的方向
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
AVCaptureConnection *captureConnection = [self.captureVideoPreviewLayer connection];
captureConnection.videoOrientation = (AVCaptureVideoOrientation)toInterfaceOrientation;
}
// 旋转后重新设置大小
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
_captureVideoPreviewLayer.frame = self.viewContainer.bounds;
}
-(void)dealloc{
[self removeNotification];
}
#pragma -mark 通知
/**
* 给输入设备添加通知
*
*/
- (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(@"会话发生错误.");
}
#pragma -mark 私有方法
/**
* 取得指定位置的摄像头
*
* @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]) {
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 isFocusModeSupported:focusMode]) {
[captureDevice setFocusMode:AVCaptureFocusModeAutoFocus];
}
if ([captureDevice isFocusPointOfInterestSupported]) {
[captureDevice setFocusPointOfInterest:point];
}
if ([captureDevice isExposureModeSupported:exposureMode]) {
[captureDevice setExposureMode:AVCaptureExposureModeAutoExpose];
}
if ([captureDevice isExposurePointOfInterestSupported]) {
[captureDevice setExposurePointOfInterest:point];
}
}];
}
/**
* 添加手势:点按时聚焦
*
*/
- (void)addGenstureRecognizer
{
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapScreen:)];
[self.viewContainer addGestureRecognizer:tapGesture];
}
- (void)tapScreen:(UITapGestureRecognizer *)tapGesture
{
CGPoint point= [tapGesture locationInView:self.viewContainer];
NSLog(@"(%f,%f)",point.x,point.y);
//将UI坐标转化为摄像头坐标
CGPoint cameraPoint= [self.captureVideoPreviewLayer captureDevicePointOfInterestForPoint:point];
[self setFocusCursorWithPoint:point];
[self focusWithMode:AVCaptureFocusModeAutoFocus exposureMode:AVCaptureExposureModeAutoExpose atPoint:cameraPoint];
}
/**
* 设置聚焦光标位置
*
* @param point 光标位置
*/
-(void)setFocusCursorWithPoint:(CGPoint)point{
self.focusCursor.transform=CGAffineTransformMakeScale(1.5, 1.5);
self.focusCursor.alpha=1.0;
[self.focusCursor setBackgroundColor:[UIColor redColor]];
[UIView animateWithDuration:1.0 animations:^{
self.focusCursor.transform=CGAffineTransformIdentity;
} completion:^(BOOL finished) {
self.focusCursor.alpha=0.5;
}];
}
/**
* 设置闪光灯按钮状态
*/
-(void)setFlashModeButtonStatus{
AVCaptureDevice *captureDevice=[self.captureDeviceInput device];
AVCaptureFlashMode flashMode=captureDevice.flashMode;
if([captureDevice isFlashAvailable]){ // 如果当前闪光可用(后置摄像头时闪光灯可用,前置摄像头时闪光灯不可用)
self.flashAutoButton.hidden=NO;
self.flashOnButton.hidden=NO;
self.flashOffButton.hidden=NO;
self.flashAutoButton.enabled=YES;
self.flashOnButton.enabled=YES;
self.flashOffButton.enabled=YES;
switch (flashMode) {
case AVCaptureFlashModeAuto:
self.flashAutoButton.enabled=NO;
break;
case AVCaptureFlashModeOn:
self.flashOnButton.enabled=NO;
break;
case AVCaptureFlashModeOff:
self.flashOffButton.enabled=NO;
break;
default:
break;
}
}else{
self.flashAutoButton.hidden=YES;
self.flashOnButton.hidden=YES;
self.flashOffButton.hidden=YES;
}
}
#pragma mark -按钮处理
#pragma mark 切换前后摄像头
- (IBAction)toggleButtonClick1:(UIButton *)sender
{
// 得到当前的输入设备并删除其上面的通知
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.captureSession beginConfiguration];
//移除原有输入对象
[self.captureSession removeInput:self.captureDeviceInput];
//添加新的输入对象
if ([self.captureSession canAddInput:toChangeDeviceInput]) {
[self.captureSession addInput:toChangeDeviceInput];
self.captureDeviceInput=toChangeDeviceInput;
}
//提交会话配置
[self.captureSession commitConfiguration];
[self setFlashModeButtonStatus];
}
#pragma mark 录制视频
- (void)takeButtonClick11:(UIButton *)sender {
sender.selected = !sender.selected;
if (sender.selected) {
//根据设备输出获得连接
AVCaptureConnection *captureConnection=[self.captureMovieFileOutput connectionWithMediaType:AVMediaTypeVideo];
//根据连接取得设备输出的数据
if (![self.captureMovieFileOutput isRecording]) { // 如果此时没有在录屏
self.enableRotation=NO;
//如果支持多任务则则开始多任务
if ([[UIDevice currentDevice] isMultitaskingSupported]) {
self.backgroundTaskIdentifier=[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
}
//预览图层和视频方向保持一致
captureConnection.videoOrientation=[self.captureVideoPreviewLayer 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];//停止录制
}
}else {
[self.captureMovieFileOutput stopRecording];//停止录制
}
}
#pragma mark 自动闪光灯开启
- (void)flashAutoClick1:(UIButton *)sender {
[self setFlashMode:AVCaptureFlashModeAuto];
[self setFlashModeButtonStatus];
}
#pragma mark 打开闪光灯
- (void)flashOnClick1:(UIButton *)sender {
[self setFlashMode:AVCaptureFlashModeOn];
[self setFlashModeButtonStatus];
}
#pragma mark 关闭闪光灯
- (void)flashOffClick1:(UIButton *)sender {
[self setFlashMode:AVCaptureFlashModeOff];
[self setFlashModeButtonStatus];
}
#pragma mark -AVCaptureFileOutputRecordingDelegate 视频输出代理中的方法
// optional
// 当数据开始写入文件的时候调用,如果数据写入错误,则该方法不会被调用
- (void)captureOutput:(AVCaptureFileOutput *)captureOutput didStartRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections
{
NSLog(@"开始录制");
}
// required
// 当数据写入完成时调用该方法
- (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error
{
NSLog(@"视频录制完成");
//视频录制完成之后在后台将视频存储到相簿
self.enableRotation = NO;
UIBackgroundTaskIdentifier lastBackgroundTaskIdentifier = self.backgroundTaskIdentifier;
self.backgroundTaskIdentifier = UIBackgroundTaskInvalid;
ALAssetsLibrary *assetsLibrary = [[ALAssetsLibrary alloc] init];
[assetsLibrary writeVideoAtPathToSavedPhotosAlbum:outputFileURL completionBlock:^(NSURL *assetURL, NSError *error) {
if (error) {
NSLog(@"保存视频到相簿的过程发生错误,错误信息:%@",error.localizedDescription);
}
NSLog(@"outputURL:%@",outputFileURL);
[[NSFileManager defaultManager] removeItemAtURL:outputFileURL error:nil];
if (lastBackgroundTaskIdentifier != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:lastBackgroundTaskIdentifier];
}
NSLog(@"成功保存视频到相簿");
}];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end