项目中集成了短视频录制,UI和映客很像,在这里贴下代码搬运流程[迷之微笑]
基于系统提供框架
#import <AVFoundation/AVFoundation.h>
初始化必要属性
/**
* AVCaptureSession对象来执行输入设备和输出设备之间的数据传递
*/
@property (nonatomic, strong) AVCaptureSession* session;
/**
* 视频输入设备
*/
@property (nonatomic, strong) AVCaptureDeviceInput* videoInput;
/**
* 声音输入
*/
@property (nonatomic, strong) AVCaptureDeviceInput* audioInput;
/**
* 视频输出流
*/
@property(nonatomic,strong)AVCaptureMovieFileOutput *movieFileOutput;
/**
* 预览图层
*/
@property (nonatomic, strong) AVCaptureVideoPreviewLayer* previewLayer;
#pragma mark - private mehtod
- (void)initAVCaptureSession{
self.session = [[AVCaptureSession alloc] init];
//这里根据需要设置 可以设置4K
self.session.sessionPreset = AVCaptureSessionPreset1280x720;
NSError *error;
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
//更改这个设置的时候必须先锁定设备,修改完后再解锁,否则崩溃
[device lockForConfiguration:nil];
//设置闪光灯为自动
[device setFlashMode:AVCaptureFlashModeAuto];
[device unlockForConfiguration];
self.videoInput = [[AVCaptureDeviceInput alloc] initWithDevice:device error:&error];
self.audioInput = [[AVCaptureDeviceInput alloc] initWithDevice:[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio] error:nil];
if (error) {
DLog(@"error%@",error);
}
self.movieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
if ([self.session canAddInput:self.videoInput]) {
[self.session addInput:self.videoInput];
}
if ([self.session canAddInput:self.audioInput]) {
[self.session addInput:self.audioInput];
}
if ([self.session canAddOutput:self.movieFileOutput]) {
[self.session addOutput:self.movieFileOutput];
}
[self basicViewSet];
}
开始录制前先要获取下权限
AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
if (authStatus == AVAuthorizationStatusRestricted || authStatus ==AVAuthorizationStatusDenied)
{
return;
}
//判断用户是否允许访问麦克风权限
authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
if (authStatus == AVAuthorizationStatusRestricted || authStatus ==AVAuthorizationStatusDenied)
{
//无权限
[SVProgressHUD showWithStatus:@"未授予麦克风权限"];
return;
}
到了重点了-开始录制
- (void)startSession{
if (![self.session isRunning]) {
[self.session startRunning];
}
}
AVCaptureConnection *movieConnection = [self.movieFileOutput connectionWithMediaType:AVMediaTypeVideo];
AVCaptureVideoOrientation avcaptureOrientation = AVCaptureVideoOrientationPortrait;
[movieConnection setVideoOrientation:avcaptureOrientation];
[movieConnection setVideoScaleAndCropFactor:1.0];
// 保存按URL
self.url = [[RAFileManager defaultManager] filePathUrlWithUrl:[self getVideoSaveFilePathString]];
[self.movieFileOutput startRecordingToOutputFileURL:self.url recordingDelegate:self];
按路径保存的方法是自己写的
- (NSURL*)filePathUrlWithUrl:(NSString*)url
{
return [NSURL fileURLWithPath:[self filePathWithUrl:url]];
}
呵呵 其实是调用的系统方法
到这里录制的部分已经完了,视频已经按路径保存了
如果录制完成后就做些操作的话可以用这个
- (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error{}
这个代理
<AVCaptureFileOutputRecordingDelegate>
解释说是 调用此方法时,文件输出已经写完所有数据到一个文件的记录了。。。来自某道翻译
就先这样吧
除了视频的压缩上传、反复播放、圆环动画就没有了呼呼呼
有机会再接着搬。。。
老板要加短拍美颜-_-!,我就把我原生的代码都一块搬过来,做个纪念
#import "XNLMakVideoController.h"
#import <AVFoundation/AVFoundation.h>
#import <AssetsLibrary/AssetsLibrary.h>
#import "XNLVideoPlayerController.h"
#import "RAFileManager.h"
#define TIMER_INTERVAL 0.05 //定时器间隔
#define VIDEO_RECORDER_MAX_TIME 60 //视频最大时长 (单位/秒)
#define VIDEO_RECORDER_MIN_TIME 30 //最短视频时长 (单位/秒)
@interface XNLMakVideoController ()<AVCaptureFileOutputRecordingDelegate>
@property (nonatomic) dispatch_queue_t sessionQueue;
/**
* AVCaptureSession对象来执行输入设备和输出设备之间的数据传递
*/
@property (nonatomic, strong) AVCaptureSession* session;
/**
* 视频输入设备
*/
@property (nonatomic, strong) AVCaptureDeviceInput* videoInput;
/**
* 声音输入
*/
@property (nonatomic, strong) AVCaptureDeviceInput* audioInput;
/**
* 视频输出流
*/
@property(nonatomic,strong)AVCaptureMovieFileOutput *movieFileOutput;
/**
* 预览图层
*/
@property (nonatomic, strong) AVCaptureVideoPreviewLayer* previewLayer;
/**
* 记录录制时间
*/
@property (nonatomic, strong) NSTimer* timer;
/**
录制完成的视频路径
*/
@property (nonatomic,strong)NSURL *url;
/**
开始View
*/
@property (nonatomic,strong) UIView *begView;
/**
圆环的底环及填充环
*/
@property (strong, nonatomic)CAShapeLayer *ovalSShapeLayer;
@property (strong, nonatomic)CAShapeLayer *redShapeLayer;
@property (nonatomic,strong)UIView *centerCircle;// 中心圆
@end
@implementation XNLMakVideoController{
//时间长度
CGFloat timeLength;
}
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:YES];
[self startSession];
//开启定时器
[self.timer setFireDate:[NSDate distantPast]];
}
//页面消失,进入后台不显示该页面,关闭定时器
-(void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
//关闭定时器
[self.timer setFireDate:[NSDate distantFuture]];
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self initAVCaptureSession];
}
#pragma mark - private mehtod
- (void)initAVCaptureSession{
self.session = [[AVCaptureSession alloc] init];
//这里根据需要设置 可以设置4K
if ([self.session canSetSessionPreset:AVCaptureSessionPreset1280x720]) {//设置分辨率
self.session.sessionPreset = AVCaptureSessionPreset1280x720;
}
NSError *error;
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
//更改这个设置的时候必须先锁定设备,修改完后再解锁,否则崩溃
[device lockForConfiguration:nil];
//设置闪光灯为自动
[device setFlashMode:AVCaptureFlashModeAuto];
[device unlockForConfiguration];
self.videoInput = [[AVCaptureDeviceInput alloc] initWithDevice:device error:&error];
self.audioInput = [[AVCaptureDeviceInput alloc] initWithDevice:[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio] error:nil];
if (error) {
DLog(@"error%@",error);
}
self.movieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
//它的默认值就是10秒。将这个值禁用后就好了--- 坑我不轻-_-!
self.movieFileOutput.movieFragmentInterval = kCMTimeInvalid;
if ([self.session canAddInput:self.videoInput]) {
[self.session addInput:self.videoInput];
}
if ([self.session canAddInput:self.audioInput]) {
[self.session addInput:self.audioInput];
}
if ([self.session canAddOutput:self.movieFileOutput]) {
[self.session addOutput:self.movieFileOutput];
}
[self basicViewSet];
}
- (void)startSession{
if (![self.session isRunning]) {
[self.session startRunning];
}
}
- (void)stopSession{
if ([self.session isRunning]) {
[self.session stopRunning];
}
}
#pragma mark - 视频输出
- (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error{
if (CMTimeGetSeconds(captureOutput.recordedDuration) < VIDEO_RECORDER_MIN_TIME) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"时间少于30s,不予保存"
message:nil
delegate:self
cancelButtonTitle:@"确定"
otherButtonTitles:nil, nil];
[alert show];
return;
}
WS(weakSelf);
XNLVideoPlayerController *playerVC = [XNLVideoPlayerController new];
playerVC.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
playerVC.url = self.url;
playerVC.videoURL = ^(){
// 圆环重置
weakSelf.redShapeLayer.strokeEnd = 0;
weakSelf.centerCircle.backgroundColor = kRGBA(255, 255, 255, 0.8);
// 删除视频
[[RAFileManager defaultManager] removeFileWithUrl:weakSelf.url block:^(BOOL success) {
DLog(@"----删除视频-----success-----%d",success);
}];
};
[self presentViewController:playerVC animated:NO completion:^{}];
}
#pragma mark -- 定时器设置
- (void)timerFired{
timeLength = 0;
self.timer = [NSTimer scheduledTimerWithTimeInterval:TIMER_INTERVAL target:self selector:@selector(timerRecord) userInfo:nil repeats:YES];
}
- (void)timerStop{
if ([self.timer isValid]) {
[self.timer invalidate];
self.timer = nil;
}
}
#pragma mark - response method 录制按钮点击触发
- (void)takeVideoButtonPress:(UILongPressGestureRecognizer *)sender {
AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
if (authStatus == AVAuthorizationStatusRestricted || authStatus ==AVAuthorizationStatusDenied)
{
[SMGlobalMethod showViewCenter:kKeyWindow.center message:@"未授予摄像头权限"];
return;
}
//判断用户是否允许访问麦克风权限
authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
if (authStatus == AVAuthorizationStatusRestricted || authStatus ==AVAuthorizationStatusDenied)
{
//无权限
[SVProgressHUD showWithStatus:@"未授予麦克风权限"];
return;
}
switch (sender.state) {
case UIGestureRecognizerStateBegan:
[self startVideoRecorder];
break;
case UIGestureRecognizerStateCancelled:
[self stopVideoRecorder];
break;
case UIGestureRecognizerStateEnded:
[self stopVideoRecorder];
break;
case UIGestureRecognizerStateFailed:
[self stopVideoRecorder];
break;
default:
break;
}
}
#pragma 视频名以当前日期为名
- (NSString*)getVideoSaveFilePathString
{
NSDateFormatter* formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"yyyyMMddHHmmss";
NSString* nowTimeStr = [formatter stringFromDate:[NSDate dateWithTimeIntervalSinceNow:0]];
return nowTimeStr;
}
#pragma mark 开始录制和结束录制
- (void)startVideoRecorder{
AVCaptureConnection *movieConnection = [self.movieFileOutput connectionWithMediaType:AVMediaTypeVideo];
AVCaptureVideoOrientation avcaptureOrientation = AVCaptureVideoOrientationPortrait;
[movieConnection setVideoOrientation:avcaptureOrientation];
[movieConnection setVideoScaleAndCropFactor:1.0];
// 保存按URL
self.url = [[RAFileManager defaultManager] filePathUrlWithUrl:[self getVideoSaveFilePathString]];
[self.movieFileOutput startRecordingToOutputFileURL:self.url recordingDelegate:self];
// 圆环弹性动画
[UIView animateWithDuration:0.3 delay:0 usingSpringWithDamping:0.1 initialSpringVelocity:15 options:UIViewAnimationOptionLayoutSubviews animations:^{
self.begView.transform = CGAffineTransformScale(self.begView.transform, 1.2, 1.2);
} completion:^(BOOL finished) {
}];
self.redShapeLayer.strokeEnd = 0;
[self timerFired];
}
// 录制结束后的方法
- (void)stopVideoRecorder{
[self.movieFileOutput stopRecording];
[self timerStop];
self.begView.transform = CGAffineTransformIdentity;
//strokeStart为0时是从3点钟方向开始,故将其旋转270度从12点钟方向开始
self.begView.transform = CGAffineTransformMakeRotation((M_PI * 2) / 4 * 3);
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
// 定时器调用方法
- (void)timerRecord{
timeLength += (TIMER_INTERVAL/VIDEO_RECORDER_MAX_TIME);
self.redShapeLayer.strokeEnd = timeLength;
self.centerCircle.backgroundColor = kRGBA(255, 255*(1-timeLength), 255*(1-timeLength), 0.8);
if (timeLength >= 1.0) {
[self stopVideoRecorder];
[self timerStop];
}
}
#pragma mark -- 前后摄像头切换
- (AVCaptureDevice *)cameraWithPosition:(AVCaptureDevicePosition) position {
NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
for (AVCaptureDevice *device in devices) {
if ([device position] == position) {
return device;
}
}
return nil;
}
- (void)swapFrontAndBackCameras {
// Assume the session is already running
NSArray *inputs = self.session.inputs;
for ( AVCaptureDeviceInput *input in inputs ) {
AVCaptureDevice *device = input.device;
if ( [device hasMediaType:AVMediaTypeVideo] ) {
AVCaptureDevicePosition position = device.position;
AVCaptureDevice *newCamera = nil;
AVCaptureDeviceInput *newInput = nil;
if (position == AVCaptureDevicePositionFront)
newCamera = [self cameraWithPosition:AVCaptureDevicePositionBack];
else
newCamera = [self cameraWithPosition:AVCaptureDevicePositionFront];
newInput = [AVCaptureDeviceInput deviceInputWithDevice:newCamera error:nil];
// beginConfiguration ensures that pending changes are not applied immediately
[self.session beginConfiguration];
[self.session removeInput:input];
[self.session addInput:newInput];
// Changes take effect once the outermost commitConfiguration is invoked.
[self.session commitConfiguration];
break;
}
}
}
#pragma mark -- 关闭
- (void)shutDownClick
{
[self stopSession];
[self dismissViewControllerAnimated:YES completion:nil];
}
#pragma mark -- 几个控件的布局
- (void)basicViewSet
{
//初始化预览图层
self.previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.session];
// _previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
[self.previewLayer setVideoGravity:AVLayerVideoGravityResizeAspect];
self.previewLayer.frame = CGRectMake(0, 0,kScreenWidth, kScreenHeight);
[self.view.layer insertSublayer:self.previewLayer atIndex:0];
// 圆环、圆的父View
self.begView = [[UIView alloc]initWithFrame:CGRectMake(kScreenWidth/2-48*kScreenW, kScreenHeight-130*kScreenH, 96*kScreenW, 96*kScreenW)];
[self.view addSubview:self.begView];
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(takeVideoButtonPress:)];
[self.begView addGestureRecognizer:longPress];
// 圆环
[self creatLayer];
// 摄像头切换
UIButton *fan = [UIButton buttonWithType:UIButtonTypeCustom];
[fan setImage:kGetImage(@"shortvideo_icon_down_switch") forState:UIControlStateNormal];
[self.view addSubview:fan];
[fan mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.mas_equalTo(self.view).mas_offset(kScreenWidth/2 - 40*kScreenW);
make.bottom.mas_equalTo(self.view.mas_bottom).mas_offset(-20*kScreenH);
make.size.mas_equalTo(CGSizeMake(60*kScreenW, 60*kScreenW));
}];
[fan addTarget:self action:@selector(swapFrontAndBackCameras) forControlEvents:UIControlEventTouchUpInside];
// 关闭
UIButton *shutDown = [UIButton buttonWithType:UIButtonTypeCustom];
[shutDown setImage:kGetImage(@"XNLlive_yemian5_close") forState:UIControlStateNormal];
[self.view addSubview:shutDown];
[shutDown mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.mas_equalTo(self.view).mas_offset(kScreenWidth/2 - 40*kScreenW);
make.top.mas_equalTo(self.view.mas_top).mas_offset(20*kScreenW);
make.size.mas_equalTo(CGSizeMake(50*kScreenW, 50*kScreenW));
}];
[shutDown addTarget:self action:@selector(shutDownClick) forControlEvents:UIControlEventTouchUpInside];
}
// 圆环的设置
-(void)creatLayer
{
//添加底层白色圆环
self.ovalSShapeLayer = [[CAShapeLayer alloc]init];
//圆环颜色
self.ovalSShapeLayer.strokeColor = DEFAULT_BACKGROUND_COLOR.CGColor;
//内部填充颜色
self.ovalSShapeLayer.fillColor = [UIColor clearColor].CGColor;
//线宽
self.ovalSShapeLayer.lineWidth = 6*kScreenW;
//半径
CGFloat ovalRadius = self.begView.frame.size.height/2;
self.ovalSShapeLayer.path =[UIBezierPath bezierPathWithOvalInRect:CGRectMake(self.begView.frame.size.width/2 - ovalRadius, self.begView.frame.size.height/2 - ovalRadius, ovalRadius * 2, ovalRadius * 2)].CGPath;
self.ovalSShapeLayer.lineCap = kCALineCapRound;
[self.begView.layer addSublayer:self.ovalSShapeLayer];
//添加填充圆环
self.redShapeLayer = [[CAShapeLayer alloc]init];
self.redShapeLayer.strokeColor = [UIColor redColor].CGColor;
self.redShapeLayer.fillColor = [UIColor clearColor].CGColor;
self.redShapeLayer.lineWidth = 6*kScreenW;
self.redShapeLayer.cornerRadius = 3.0*kScreenW;
self.redShapeLayer.lineCap = kCALineCapRound;
self.redShapeLayer.path =[UIBezierPath bezierPathWithOvalInRect:CGRectMake(self.begView.frame.size.width/2 - ovalRadius, self.begView.frame.size.height/2 - ovalRadius, ovalRadius * 2, ovalRadius * 2)].CGPath;
self.redShapeLayer.strokeStart = 0;
self.redShapeLayer.strokeEnd = 0;
[self.begView.layer addSublayer:self.redShapeLayer];
// 中心圆
self.centerCircle = [UIView new];
self.centerCircle.backgroundColor = kRGBA(255, 255, 255, 0.8);
self.centerCircle.layer.cornerRadius = 40*kScreenW;
self.centerCircle.clipsToBounds = YES;
[self.begView addSubview:self.centerCircle];
[self.centerCircle mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.mas_equalTo(self.begView);
make.size.mas_equalTo(CGSizeMake(80*kScreenW, 80*kScreenW));
}];
self.begView.transform = CGAffineTransformMakeRotation((M_PI * 2) / 4 * 3);
}