项目下载链接:
https://github.com/SSYSSK/camera/tree/master/摄像机画面的捕捉和预览
项目结构:
一、总控制类:
ViewController
#import "ViewController.h"
#import "PreviewView.h"
#import "CameraController.h"
@interface ViewController ()
@property (strong, nonatomic) PreviewView *previewView;
@property (strong, nonatomic) CameraController *cameraController;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.cameraController = [[CameraController alloc]init];
[self.cameraController setupSession];
self.previewView = [[PreviewView alloc]initWithFrame:self.view.bounds];
[self.previewView setSession:self.cameraController.captureSession];
[self.view addSubview:self.previewView];
UIButton *button = [[UIButton alloc]initWithFrame:CGRectMake(60, 40, 80, 40)];
[button setTitle:@"开始录制" forState:UIControlStateNormal];
[self.view addSubview:button];
[button addTarget:self action:@selector(stopAction:) forControlEvents:UIControlEventTouchUpInside];
UIButton *button1 = [[UIButton alloc]initWithFrame:CGRectMake(180, 40, 60, 40)];
[button1 setTitle:@"切换摄像头" forState:UIControlStateNormal];
[self.view addSubview:button1];
[button1 addTarget:self action:@selector(changeAction) forControlEvents:UIControlEventTouchUpInside];
}
-(void)changeAction {
[self.cameraController changeCamera];
}
-(void)stopAction:(UIButton *)button {
if([button.titleLabel.text isEqualToString:@"开始录制"]) {
[self.cameraController startRecording];
[button setTitle:@"停止录制" forState:UIControlStateNormal];
}else {
[self.cameraController stopRecording];
[button setTitle:@"开始录制" forState:UIControlStateNormal];
}
}
二、相机控制类:
CameraController
CameraController.h
//
// CameraController.h
// 视频捕捉-切换摄像头
//
// Created by 柯木超 on 2019/9/6.
// Copyright © 2019 柯木超. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <AssetsLibrary/AssetsLibrary.h>
#import <AVFoundation/AVFoundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
* 其实这就是一个统一管理相机属性的类
* 包括:输入,输出,session会话
*/
@interface CameraController : NSObject <AVCaptureFileOutputRecordingDelegate>
@property (strong, nonatomic) AVCaptureSession *captureSession;// 捕捉会话
// 配置回话信息
- (void)setupSession;
// 开始录制
- (void)startRecording;
//停止录制
- (void)stopRecording;
// 切换摄像头
- (void)changeCamera;
@end
NS_ASSUME_NONNULL_END
CameraController.m
//
// CameraController.m
// 视频捕捉-切换摄像头
//
// Created by 柯木超 on 2019/9/6.
// Copyright © 2019 柯木超. All rights reserved.
//
#import "CameraController.h"
#import "NSFileManager+THAdditions.h"
#import <AssetsLibrary/AssetsLibrary.h>
#import <UIKit/UIKit.h>
@interface CameraController()
@property (strong, nonatomic) NSURL *outputURL;
@property (assign, nonatomic) NSUInteger cameraCount; // 当前设备数量
@property (assign, nonatomic) BOOL canSwitchCamera; // 能否切换摄像头
@property (strong, nonatomic) AVCaptureDeviceInput *activeVideoInput;// 当前正在使用的摄像头的输入
@property (strong, nonatomic) AVCaptureDevice *activeCamera;// 当前正在使用的摄像头的输入
@property (strong, nonatomic) dispatch_queue_t videoQueue; //视频队列
@property (strong, nonatomic) AVCaptureMovieFileOutput *movieOutput;
@end
@implementation CameraController
-(void)setupSession {
// 创建secssion
self.captureSession = [[AVCaptureSession alloc]init];
/*
AVCaptureSessionPresetHigh
AVCaptureSessionPresetMedium
AVCaptureSessionPresetLow
AVCaptureSessionPreset640x480
AVCaptureSessionPreset1280x720
AVCaptureSessionPresetPhoto
*/
//设置图像的分辨率
self.captureSession.sessionPreset = AVCaptureSessionPresetHigh;
// 1、添加device 拿到默认视频捕捉设备 iOS系统返回后置摄像头
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
// 2、给device封装 AVCaptureDeviceInput
AVCaptureDeviceInput *deviceInput = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil];
self.activeVideoInput = deviceInput;
// 3、捕捉设备输出
//判断videoInput是否有效
if (deviceInput){
if([self.captureSession canAddInput:deviceInput]) {
[self.captureSession addInput:deviceInput];
}
}
// 5、输出
self.movieOutput = [[AVCaptureMovieFileOutput alloc]init];
if([self.captureSession canAddOutput:self.movieOutput]) {
[self.captureSession addOutput:self.movieOutput];
}
self.videoQueue = dispatch_queue_create("cc.VideoQueue", NULL);
//使用同步调用会损耗一定的时间,则用异步的方式处理
dispatch_async(self.videoQueue, ^{
[self.captureSession startRunning];
});
}
-(void)startRecording {
self.outputURL = [self uniqueURL];
NSLog(@"outputURL=%@",self.outputURL);
//在捕捉输出上调用方法 参数1:录制保存路径 参数2:代理
[self.movieOutput startRecordingToOutputFileURL: self.outputURL recordingDelegate:self];
}
-(void)stopRecording {
[self.movieOutput stopRecording];
}
-(NSUInteger)cameraCount {
return [[AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo] count];
}
-(AVCaptureDevice *)activeCamera {
return self.activeVideoInput.device;
}
-(BOOL)canSwitchCamera {
return [self cameraCount] > 1;
}
-(void)changeCamera {
if ([self canSwitchCamera]) {
[self.movieOutput pauseRecording];
// 如果当前是后置摄像头,就便利设备,找到前置摄像头,进行切换
if (self.activeCamera.position == AVCaptureDevicePositionBack) {
//获取可用视频设备
NSArray *devicess = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
//遍历可用的视频设备 并返回position 参数值
for (AVCaptureDevice *device in devicess)
{
if (device.position == AVCaptureDevicePositionFront) {
AVCaptureDeviceInput *deviceInput = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil];
// 开始配置
if (deviceInput){
[self.captureSession beginConfiguration];
[self.captureSession removeInput:self.activeVideoInput];
[self.captureSession setSessionPreset:AVCaptureSessionPresetHigh];
if([self.captureSession canAddInput:deviceInput]){
[self.captureSession addInput:deviceInput];
self.activeVideoInput = deviceInput;
}else {
[self.captureSession addInput:self.activeVideoInput];
}
[self.captureSession commitConfiguration];
}
}
}
}else {
// 如果当前是前置摄像头,就便利设备,找到后置摄像头,进行切换
//获取可用视频设备
NSArray *devicess = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
//遍历可用的视频设备 并返回 AVCaptureDevicePositionBack
for (AVCaptureDevice *device in devicess)
{
if (device.position == AVCaptureDevicePositionBack) {
AVCaptureDeviceInput *deviceInput = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil];
// 开始配置
if (deviceInput){
[self.captureSession beginConfiguration];
[self.captureSession removeInput:self.activeVideoInput];
[self.captureSession setSessionPreset:AVCaptureSessionPresetHigh];
if([self.captureSession canAddInput:deviceInput]){
[self.captureSession addInput:deviceInput];
self.activeVideoInput = deviceInput;
}else {
[self.captureSession addInput:self.activeVideoInput];
}
[self.captureSession commitConfiguration];
}
}
}
}
}
}
//写入视频唯一文件系统URL
- (NSURL *)uniqueURL {
NSFileManager *fileManager = [NSFileManager defaultManager];
//temporaryDirectoryWithTemplateString 可以将文件写入的目的创建一个唯一命名的目录;
NSString *dirPath = [fileManager temporaryDirectoryWithTemplateString:@"kamera.XXXXXX"];
if (dirPath) {
NSString *filePath = [dirPath stringByAppendingPathComponent:@"kamera_movie.mov"];
return [NSURL fileURLWithPath:filePath];
}
return nil;
}
#pragma AVCaptureFileOutputRecordingDelegate
- (void)captureOutput:(AVCaptureFileOutput *)captureOutput
didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL
fromConnections:(NSArray *)connections
error:(NSError *)error {
NSLog(@"录制完成");
UISaveVideoAtPathToSavedPhotosAlbum([outputFileURL path], nil, nil, nil);
}
#pragma mark - 视频输出代理
-(void)captureOutput:(AVCaptureFileOutput *)captureOutput didStartRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections{
NSLog(@"开始录制...");
}
@end
三、预览面板:
PreviewView
PreviewView.h
//
// PreviewView.h
// 摄像机画面的捕捉和预览
//
// Created by 柯木超 on 2019/9/3.
// Copyright © 2019 柯木超. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
NS_ASSUME_NONNULL_BEGIN
// 相机画面捕捉预览的类
@interface PreviewView : UIView
@property (strong, nonatomic) AVCaptureSession *session;
@end
NS_ASSUME_NONNULL_END
PreviewView.m
//
// PreviewView.m
// 摄像机画面的捕捉和预览
//
// Created by 柯木超 on 2019/9/3.
// Copyright © 2019 柯木超. All rights reserved.
//
#import "PreviewView.h"
@implementation PreviewView
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setupView];
}
return self;
}
+(Class)layerClass {
return [AVCaptureVideoPreviewLayer class];
}
- (AVCaptureSession*)session {
//重写session方法,返回捕捉会话
return [(AVCaptureVideoPreviewLayer*)self.layer session];
}
-(void)setSession:(AVCaptureSession *)session {
//重写session属性的访问方法,在setSession:方法中访问视图layer属性。
//AVCaptureVideoPreviewLayer 实例,并且设置AVCaptureSession 将捕捉数据直接输出到图层中,并确保与会话状态同步。
[(AVCaptureVideoPreviewLayer*)self.layer setSession:session];
}
//关于UI的实现,例如手势,单击、双击 单击聚焦、双击曝光
- (void)setupView {
[(AVCaptureVideoPreviewLayer *)self.layer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
}
// 将屏幕上点击的位置转化成摄像头的位置
-(CGPoint)captureDevicePointForPoint:(CGPoint)point{
AVCaptureVideoPreviewLayer *layer = (AVCaptureVideoPreviewLayer *)self.layer;
//将屏幕上点击的位置转化成摄像头的位置
return [layer captureDevicePointOfInterestForPoint:point];
//将摄像头的位置转化成屏幕上点击的位置
// [layer pointForCaptureDevicePointOfInterest:point];
}
@end
四、关于存储的辅助类
NSFileManager+THAdditions.h
//
// NSFileManager+THAdditions.h
// 摄像机画面的捕捉和预览
//
// Created by 柯木超 on 2019/9/6.
// Copyright © 2019 柯木超. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSFileManager (THAdditions)
- (NSString *)temporaryDirectoryWithTemplateString:(NSString *)templateString;
@end
NS_ASSUME_NONNULL_END
NSFileManager+THAdditions.m
//
// NSFileManager+THAdditions.m
// 摄像机画面的捕捉和预览
//
// Created by 柯木超 on 2019/9/6.
// Copyright © 2019 柯木超. All rights reserved.
//
#import "NSFileManager+THAdditions.h"
@implementation NSFileManager (THAdditions)
- (NSString *)temporaryDirectoryWithTemplateString:(NSString *)templateString {
NSString *mkdTemplate =
[NSTemporaryDirectory() stringByAppendingPathComponent:templateString];
const char *templateCString = [mkdTemplate fileSystemRepresentation];
char *buffer = (char *)malloc(strlen(templateCString) + 1);
strcpy(buffer, templateCString);
NSString *directoryPath = nil;
char *result = mkdtemp(buffer);
if (result) {
directoryPath = [self stringWithFileSystemRepresentation:buffer
length:strlen(result)];
}
free(buffer);
return directoryPath;
}
@end