工作太忙,偷闲分享一个我刚做的一个录制视频工具类,基于GPUImage的带美颜功能的工具,简单易用!
完整录制播放代码链接:https://github.com/SunshineTraveller/LMVideoManager
测试Demo只支持真机,模拟器是无法调用摄像头的!
用法如下:
声明一个属性:
/** manager */
@property (nonatomic,strong)LMVideoManager *manager;
_manager = [[LMVideoManageralloc]init];
_manager.delegate =self;
[_managershowWithFrame:CGRectMake(20,120,LMWID-40,LMHEI/2-1)superView:self.view];
_manager.maxTime =30.0; // 最长录制时间,可忽略。若设置,则超过该时间自动停止录制
在一个点击事件中调用开始录制功能
[_manager startRecording];
想停止的时候调用停止录制功能
[_manager endRecording];
代理方法回调
// 开始录制
-(void)didStartRecordVideo {
[self.viewaddSubview:[LMNoticeLabelmessage:@"开始录制..."delaySecond:2]];
}
// 压缩中回调
-(void)didCompressingVideo {
[self.viewaddSubview:[LMNoticeLabelmessage:@"视频压缩中..."delaySecond:2]];
}
// 录制结束回调,输出压缩路径
-(void)didEndRecordVideoWithTime:(CGFloat)totalTime outputFile:(NSString *)filePath {
[self.viewaddSubview:[LMNoticeLabelmessage:@"录制完毕,时长:%lu 路径:%@"delaySecond:4]];
_filePath = filePath;
}
代码如下:
//
// LMVideoManager.h
// TopicEdit
//
// Created by 张利民 on 2017/8/9.
// Copyright © 2017年 ghostlord. All rights reserved.
//
typedef NS_ENUM(NSUInteger, LMVideoManagerCameraType) {
LMVideoManagerCameraTypeFront = 1,
LMVideoManagerCameraTypeBack,
};
@protocol LMVideoManagerProtocol <NSObject>
/** 开始录制 */
-(void)didStartRecordVideo;
/** 视频压缩中 */
-(void)didCompressingVideo;
/** 结束录制 */
-(void)didEndRecordVideoWithTime:(CGFloat)totalTime outputFile:(NSString *)filePath;
@end
#import "GPUImageBeautifyFilter.h"
#import
@interface LMVideoManager : NSObject
/** 代理 */
@property (nonatomic,weak)id <LMVideoManagerProtocol> delegate;
/** 录制视频区域 */
@property (nonatomic,assign)CGRect frame;
/** 录制视频最大时长 */
@property (nonatomic,assign)CGFloat maxTime;
/**
录制视频单例,若工程中不止一处用到录视频,尺寸有变,直接实例化即可忽略此方法
@return self
*/
+(instancetype)manager;
/**
加载到显示的视图上
@param frame 父视图中的frame
@param superView 父视图
*/
-(void)showWithFrame:(CGRect)frame superView:(UIView *)superView;
/**
开始录制
*/
-(void)startRecording;
/**
结束录制
*/
-(void)endRecording;
/**
暂停录制
*/
-(void)pauseRecording;
/**
继续录制
*/
-(void)resumeRecording;
/**
切换前后摄像头
@param type LMVideoManagerCameraTypeFront 为前置
*/
-(void)changeCameraPosition:(LMVideoManagerCameraType)type;
/**
打开闪光灯
@param on YES开 NO关
*/
-(void)turnTorchOn:(BOOL)on;
@end
.m 文件
//
// LMVideoManager.m
// TopicEdit
//
// Created by 张利民 on 2017/8/9.
// Copyright © 2017年 ghostlord. All rights reserved.
//
#define COMPRESSEDVIDEOPATH [NSHomeDirectory() stringByAppendingFormat:@"/Documents/CompressionVideoField"]
#import "LMVideoManager.h"
@interface LMVideoManager ()<GPUImageVideoCameraDelegate,UIImagePickerControllerDelegate,UINavigationControllerDelegate>
/** 摄像头 */
@property (nonatomic,strong)GPUImageVideoCamera *videoCamera;
/** 美颜滤镜 */
@property (nonatomic,strong)GPUImageBeautifyFilter *beautifyFilter;
/** 视频输出视图 */
@property (nonatomic,strong)GPUImageView *displayView;
/** 视频写入 */
@property (nonatomic,strong)GPUImageMovieWriter *movieWriter;
/** 视频写入的地址URL */
@property (nonatomic,strong)NSURL *movieURL;
/** 视频写入路径 */
@property (nonatomic,copy)NSString *moviePath;
/** 压缩成功后的视频路径 */
@property (nonatomic,copy)NSString *resultPath;
/** 视频时长 */
@property (nonatomic,assign)int seconds;
/** 系统计时器 */
@property (nonatomic,strong)NSTimer *timer;
/** 计时器常量 */
@property (nonatomic,assign)int recordSecond;
@end
@implementation LMVideoManager
static LMVideoManager *_manager;
// 单例
+(instancetype)manager {
staticdispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_manager = [[LMVideoManageralloc]init];
});
return_manager;
}
+(instancetype)allocWithZone:(struct_NSZone *)zone {
staticdispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (_manager ==nil) {
_manager = [superallocWithZone:zone];
}
});
return_manager;
}
// 开始录制
-(void)startRecording {
NSString *defultPath = [selfgetVideoPathCache];
self.moviePath = [defultPathstringByAppendingPathComponent:[selfgetVideoNameWithType:@"mp4"]];
// 录制路径
self.movieURL = [NSURLfileURLWithPath:self.moviePath];
// ?
unlink([self.moviePathUTF8String]);
self.movieWriter = [[GPUImageMovieWriteralloc]initWithMovieURL:self.movieURLsize:CGSizeMake(480.0,640.0)];
self.movieWriter.encodingLiveVideo =YES;
self.movieWriter.shouldPassthroughAudio =YES;
[self.beautifyFilteraddTarget:self.movieWriter];
self.videoCamera.audioEncodingTarget =self.movieWriter;
// 开始录制
[self.movieWriterstartRecording];
if (self.delegate && [self.delegaterespondsToSelector:@selector(didStartRecordVideo)]) {
[self.delegatedidStartRecordVideo];
}
[self.timersetFireDate:[NSDatedistantPast]];
[self.timerfire];
}
// 结束录制
-(void)endRecording {
if ([self.videoCameraisRunning]) {
[self.timerinvalidate];
self.timer =nil;
__weaktypeof(self) weakSelf =self;
[self.movieWriterfinishRecording];
[self.beautifyFilterremoveTarget:self.movieWriter];
self.videoCamera.audioEncodingTarget = nil;
if (self.recordSecond >self.maxTime) {
// 清除录制的视频
}else {
// 压缩中...
if ([self.delegaterespondsToSelector:@selector(didCompressingVideo)]) {
[self.delegatedidCompressingVideo];
}
// 压缩
[selfcompressVideoWithUrl:self.movieURLcompressionType:AVAssetExportPresetMediumQualityfilePath:^(NSString *resultPath,float memorySize,NSString *videoImagePath,int seconds) {
NSData *data = [NSDatadataWithContentsOfFile:resultPath];
CGFloat totalTime = (CGFloat)data.length /1024 / 1024;
// 压缩完回调
if ([weakSelf.delegaterespondsToSelector:@selector(didEndRecordVideoWithTime:outputFile:)]) {
[weakSelf.delegatedidEndRecordVideoWithTime:totalTimeoutputFile:resultPath];
}
}];
}
}
}
// 暂停
-(void)pauseRecording {
if ([_videoCameraisRunning]) {
[self.timerinvalidate];
self.timer =nil;
[_videoCamerapauseCameraCapture];
}
}
// 恢复
-(void)resumeRecording {
[_videoCameraresumeCameraCapture];
[self.timersetFireDate:[NSDatedistantPast]];
[self.timerfire];
}
#pragma mark -- 自定义方法
-(void)showWithFrame:(CGRect)frame superView:(UIView *)superView {
_frame = frame;
[self.beautifyFilteraddTarget:self.displayView];
[self.videoCameraaddTarget:self.beautifyFilter];
[superView addSubview:self.displayView];
[self.videoCamerastartCameraCapture];
}
// 切换前后摄像头
-(void)changeCameraPosition:(LMVideoManagerCameraType)type {
switch (type) {
caseLMVideoManagerCameraTypeFront:{
[_videoCamerarotateCamera];
}
break;
caseLMVideoManagerCameraTypeBack:{
[_videoCamerarotateCamera];
}
break;
default:
break;
}
}
#pragma mark ----摄像头输出代理方法----
- (void)willOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer{
}
// 手电筒开关
-(void)turnTorchOn:(BOOL)on {
if ([_videoCamera.inputCamerahasTorch] && [_videoCamera.inputCamerahasFlash]) {
[_videoCamera.inputCameralockForConfiguration:nil];
if (on) {
[_videoCamera.inputCamerasetTorchMode:AVCaptureTorchModeOn];
[_videoCamera.inputCamerasetFlashMode:AVCaptureFlashModeOn];
}else{
[_videoCamera.inputCamerasetTorchMode:AVCaptureTorchModeOff];
[_videoCamera.inputCamerasetFlashMode:AVCaptureFlashModeOff];
}
[_videoCamera.inputCameraunlockForConfiguration];
}
}
// 获取视频地址
-(NSString *)getVideoPathCache {
NSString *videoCache = [NSTemporaryDirectory()stringByAppendingString:@"videos"];
BOOL isDir =NO;
NSFileManager *fileManager = [NSFileManagerdefaultManager];
BOOL existed = [fileManagerfileExistsAtPath:videoCacheisDirectory:&isDir];
if (!existed) {
[fileManager createDirectoryAtPath:videoCachewithIntermediateDirectories:YESattributes:nilerror:nil];
}
return videoCache;
}
// 获取视频名称
-(NSString *)getVideoNameWithType:(NSString *)fileType {
NSTimeInterval now = [[NSDatedate]timeIntervalSince1970];
NSDateFormatter *formatter = [[NSDateFormatteralloc]init];
[formatter setDateFormat:@"HHmmss"];
NSDate *nowDate = [NSDatedateWithTimeIntervalSince1970:now];
NSString *timeStr = [formatterstringFromDate:nowDate];
NSString *fileName = [NSStringstringWithFormat:@"video_%@.%@",timeStr,fileType];
return fileName;
}
#pragma mark -- 懒加载
// 摄像头
-(GPUImageVideoCamera *)videoCamera {
if (_videoCamera ==nil) {
_videoCamera = [[GPUImageVideoCameraalloc]initWithSessionPreset:AVCaptureSessionPreset640x480cameraPosition:AVCaptureDevicePositionBack];
_videoCamera.outputImageOrientation =UIInterfaceOrientationPortrait;
_videoCamera.horizontallyMirrorFrontFacingCamera =YES;
_videoCamera.delegate =self;
// 可防止允许声音通过的情况下,避免第一帧黑屏
[_videoCameraaddAudioInputsAndOutputs];
}
return_videoCamera;
}
// 滤镜
-(GPUImageBeautifyFilter *)beautifyFilter {
if (_beautifyFilter ==nil) {
_beautifyFilter = [[GPUImageBeautifyFilteralloc]init];
}
return_beautifyFilter;
}
// 展示视图
-(GPUImageView *)displayView {
if (_displayView ==nil) {
_displayView = [[GPUImageViewalloc]initWithFrame:self.frame];
_displayView.fillMode =kGPUImageFillModePreserveAspectRatioAndFill;
}
return_displayView;
}
// 计时器
-(NSTimer *)timer {
if (!_timer) {
_timer = [NSTimerscheduledTimerWithTimeInterval:1target:selfselector:@selector(updateWithTime)userInfo:nilrepeats:YES];
}
return_timer;
}
// 超过最大录制时长结束录制
-(void)updateWithTime {
self.recordSecond++;
if (self.recordSecond >=self.maxTime) {
[selfendRecording];
}
}
// 压缩视频
-(void)compressVideoWithUrl:(NSURL *)url compressionType:(NSString *)type filePath:(void(^)(NSString *resultPath,float memorySize,NSString * videoImagePath,int seconds))resultBlock {
NSString *resultPath;
// 视频压缩前大小
NSData *data = [NSDatadataWithContentsOfURL:url];
CGFloat totalSize = (float)data.length /1024 / 1024;
NSLog(@"压缩前大小:%.2fM",totalSize);
AVURLAsset *avAsset = [AVURLAssetURLAssetWithURL:urloptions:nil];
CMTime time = [avAssetduration];
// 视频时长
int seconds =ceil(time.value / time.timescale);
NSArray *compatiblePresets = [AVAssetExportSessionexportPresetsCompatibleWithAsset:avAsset];
if ([compatiblePresetscontainsObject:type]) {
// 中等质量
AVAssetExportSession *session = [[AVAssetExportSessionalloc]initWithAsset:avAssetpresetName:AVAssetExportPresetMediumQuality];
// 用时间给文件命名防止存储被覆盖
NSDateFormatter *formatter = [[NSDateFormatteralloc]init];
[formatter setDateFormat:@"yyyy-MM-dd-HH:mm:ss"];
// 若压缩路径不存在重新创建
NSFileManager *manager = [NSFileManagerdefaultManager];
BOOL isExist = [managerfileExistsAtPath:COMPRESSEDVIDEOPATH];
if (!isExist) {
[manager createDirectoryAtPath:COMPRESSEDVIDEOPATHwithIntermediateDirectories:YESattributes:nilerror:nil];
}
resultPath = [COMPRESSEDVIDEOPATHstringByAppendingPathComponent:[NSStringstringWithFormat:@"user%outputVideo-%@.mp4",arc4random_uniform(10000),[formatterstringFromDate:[NSDatedate]]]];
session.outputURL = [NSURLfileURLWithPath:resultPath];
session.outputFileType =AVFileTypeMPEG4;
session.shouldOptimizeForNetworkUse =YES;
[session exportAsynchronouslyWithCompletionHandler:^{
switch (session.status) {
caseAVAssetExportSessionStatusUnknown:
break;
caseAVAssetExportSessionStatusWaiting:
break;
caseAVAssetExportSessionStatusExporting:
break;
caseAVAssetExportSessionStatusCancelled:
break;
caseAVAssetExportSessionStatusFailed:
break;
caseAVAssetExportSessionStatusCompleted:{
NSData *data = [NSDatadataWithContentsOfFile:resultPath];
// 压缩过后的大小
float compressedSize = (float)data.length /1024 / 1024;
resultBlock(resultPath,compressedSize,@"",seconds);
NSLog(@"压缩后大小:%.2f",compressedSize);
}
default:
break;
}
}];
}
}
- (void)dealloc {
[self.timerinvalidate];
NSLog(@"销毁了啊");
}
@end