摄像机对于视频的捕捉、预览、存储以及切换摄像头

项目下载链接:

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
发布了79 篇原创文章 · 获赞 42 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/s12117719679/article/details/100585267