OC AVFoundation视频录制相关(AVCaptureSession+ AVCaptureMovieFileOutput+ AVCaptureVideoPreviewLayer)
参考的博客不过里面添加了一些我自己的总结,
#import "LittleVideoController.h"
#import <AVKit/AVKit.h>
#import <AVFoundation/AVFoundation.h>
@interface LittleVideoController ()<AVCaptureFileOutputRecordingDelegate>
@property(nonatomic,strong) dispatch_source_t timer;
@property (weak, nonatomic) IBOutlet UIButton * startButton;//开始录制
@property(nonatomic,strong) UIButton * videoButton;//播放
@property(nonatomic,strong) AVCaptureSession * captureSession;//捕捉会话对象
@property(nonatomic,strong) AVCaptureMovieFileOutput * captureMovieFileOutput;//
@property(nonatomic,strong) AVCaptureVideoPreviewLayer * captureVideoPreviewLayer;
@property(nonatomic,strong) NSString * videoPath;
@end
@implementation LittleVideoController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self.startButton addTarget:self action:@selector(startCaptureWithSession) forControlEvents:UIControlEventTouchUpInside];
/*
下面是一个有趣的写法,上网搜到这样一段话,补充说明之后能理解这种写法:
这个问题严格上讲和Objective-C没什么太大的关系,这个是GNU C的对C的扩展语法
在理解一下什么是GNU C ,下面是百度给的定义:
GNU C 函式库(GNU C Library,又称为glibc)是一种按照LGPL许可协议发布的,公开源代码的,免费的,方便从网络下载的C的编译程序。 GNU C运行期库,是一种C函式库,是程序运行时使用到的一些API集合,它们一般是已预先编译好,以二进制代码形式存 在Linux类系统中,GNU C运行期库,通常作为GNU C编译程序的一个部分发布。 它最初是自由软件基金会为其GNU操作系统所写,但目前最主要的应用是配合Linux内核,成为GNU/Linux操作系统一个重要的组成部分。
继续解释:
Xcode采用的Clang编译,Clang作为GCC(GCC的初衷是为GNU操作系统专门编写的一款编译器)的替代品,和GCC一样对于GNU C语法完全支持
你可能知道if(condition)后面只能根一条语句,多条语句必须用{}阔起来,这个语法扩展即将一条(多条要用到{})语句外面加一个括号(),
这样的话你就可以在表达式中应用循环、判断甚至本地变量等。
表达式()最后一行应该一个能够计算结果的子表达式加上一个分号(;),这个子表达式作为整个结构的返回结果
这个扩展在代码中最常见的用处在于宏定义中
*/
self.captureSession = ({
// 分辨率设置
AVCaptureSession *session = [[AVCaptureSession alloc] init];
// 先判断这个设备是否支持设置你要设置的分辨率
if ([session canSetSessionPreset:AVCaptureSessionPresetMedium]) {
/*
下面是对你能设置的预设图片的质量和分辨率的说明
AVCaptureSessionPresetHigh High 最高的录制质量,每台设备不同
AVCaptureSessionPresetMedium Medium 基于无线分享的,实际值可能会改变
AVCaptureSessionPresetLow LOW 基于3g分享的
AVCaptureSessionPreset640x480 640x480 VGA
AVCaptureSessionPreset1280x720 1280x720 720p HD
AVCaptureSessionPresetPhoto Photo 完整的照片分辨率,不支持视频输出
*/
[session setSessionPreset:AVCaptureSessionPresetMedium];
}
session;
});
// 初始化一个拍摄输出对象
self.captureMovieFileOutput = ({
//输出一个电影文件
/*
a.AVCaptureMovieFileOutput 输出一个电影文件
b.AVCaptureVideoDataOutput 输出处理视频帧被捕获
c.AVCaptureAudioDataOutput 输出音频数据被捕获
d.AVCaptureStillImageOutput 捕获元数据
*/
AVCaptureMovieFileOutput * output = [[AVCaptureMovieFileOutput alloc]init];
/*
一个ACCaptureConnection可以控制input到output的数据传输。
*/
AVCaptureConnection * connection = [output connectionWithMediaType:AVMediaTypeVideo];
if ([connection isVideoMirroringSupported]) {
/*
视频防抖 是在 iOS 6 和 iPhone 4S 发布时引入的功能。到了 iPhone 6,增加了更强劲和流畅的防抖模式,被称为影院级的视频防抖动。相关的 API 也有所改动 (目前为止并没有在文档中反映出来,不过可以查看头文件)。防抖并不是在捕获设备上配置的,而是在 AVCaptureConnection 上设置。由于不是所有的设备格式都支持全部的防抖模式,所以在实际应用中应事先确认具体的防抖模式是否支持:
typedef NS_ENUM(NSInteger, AVCaptureVideoStabilizationMode) {
AVCaptureVideoStabilizationModeOff = 0,
AVCaptureVideoStabilizationModeStandard = 1,
AVCaptureVideoStabilizationModeCinematic = 2,
AVCaptureVideoStabilizationModeAuto = -1, 自动
} NS_AVAILABLE_IOS(8_0) __TVOS_PROHIBITED;
*/
connection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeAuto;
//预览图层和视频方向保持一致
connection.videoOrientation = [self.captureVideoPreviewLayer connection].videoOrientation;
}
if ([self.captureSession canAddOutput:output]) {
[self.captureSession addOutput:output];
}
output;
});
/*
用于展示制的画面
*/
self.captureVideoPreviewLayer = ({
AVCaptureVideoPreviewLayer * preViewLayer = [[AVCaptureVideoPreviewLayer alloc]initWithSession:self.captureSession];
preViewLayer.frame = CGRectMake(10, 50, 355, 355);
/*
AVLayerVideoGravityResizeAspect:保留长宽比,未填充部分会有黑边
AVLayerVideoGravityResizeAspectFill:保留长宽比,填充所有的区域
AVLayerVideoGravityResize:拉伸填满所有的空间
*/
preViewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
[self.view.layer addSublayer:preViewLayer];
self.view.layer.masksToBounds = YES;
preViewLayer;
});
//如果这块代码写在前面,则打开captureVideoPreviewLayer就显示摄像头的内容,和微信小程序一样
if ([self SetSessioninputs:nil]) {
[self.captureSession startRunning];
}
}
#pragma mark 开始录制
-(void)startCaptureWithSession{
// 先删除之前的视频文件
if ([[NSFileManager defaultManager] fileExistsAtPath:self.videoPath]) {
[[NSFileManager defaultManager] removeItemAtURL:[NSURL fileURLWithPath:self.videoPath] error:NULL];
}
// NSError * error = nil;
// if ([self SetSessioninputs:error]) {
//
// 开始录制
[self startRecordSession];
// }else{
//
// [self.captureSession stopRunning];
// NSLog(@"录制失败:%@",error);
// }
}
-(BOOL)SetSessioninputs:(NSError *)error{
// capture 捕捉 捕获
/*
视频输入类
AVCaptureDevice 捕获设备类
AVCaptureDeviceInput 捕获设备输入类
*/
AVCaptureDevice * captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
AVCaptureDeviceInput * videoInput = [AVCaptureDeviceInput deviceInputWithDevice: captureDevice error: &error];
if (!videoInput) {
return NO;
}
// 给捕获会话类添加输入捕获设备
if ([self.captureSession canAddInput:videoInput]) {
[self.captureSession addInput:videoInput];
}else{
return NO;
}
// 添加音频捕获设备
/*
__block AVCaptureDevice *backCamera = nil;
NSArray *cameras = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
[cameras enumerateObjectsUsingBlock:^(AVCaptureDevice *camera, NSUInteger idx, BOOL * _Nonnull stop) {
//AVCaptureDevicePositionFront 前摄像头
//AVCaptureDevicePositionBack:后摄像头
//AVCaptureDevicePositionUnspecified不指定前值或者后置,用系统当前的
if(camera.position == AVCaptureDevicePositionBack){
backCamera = camera;
}
}];
// 配置曝光模式 设置持续曝光模式
//注意改变设备属性前一定要首先调用lockForConfiguration:调用完之后使用unlockForConfiguration方法解锁
NSError *error = nil;
[backCamera lockForConfiguration:&error];
//AVCaptureExposureModeLocked 直接用当前值就好,不指定
//AVCaptureExposureModeAutoExpose 自动调整曝光一次,然后用系统的
//AVCaptureExposureModeContinuousAutoExposure 需要的时候自行调节
//AVCaptureExposureModeCustom 自定义,需要自己手动设置
if ([backCamera isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]){
[backCamera setExposureMode:AVCaptureExposureModeContinuousAutoExposure];
}
[backCamera unlockForConfiguration];
//闪光灯设计 AVCaptureFlashMode
//手电筒开关--其实就是相机的闪光灯 AVCaptureTorchMode
[backCamera lockForConfiguration:&error];
if([backCamera isTorchModeSupported:AVCaptureTorchModeOn]){
[backCamera setTorchMode:AVCaptureTorchModeOn];
}
[backCamera unlockForConfiguration];
//焦距模式调整AVCaptureFocusMode
//曝光量调节AVCaptureExposureMode
//白平衡 AVCaptureWhiteBalanceMode
//距离调整 AVCaptureAutoFocusRangeRestriction
*/
//如果需要指定的摄像头,
AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
//
AVCaptureDeviceInput * audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:&error];
if (!audioDevice) {
return NO;
}
if ([self.captureSession canAddInput:audioInput]) {
[self.captureSession addInput:audioInput];
}
return YES;
}
-(void)startRuningWithSession{
__block int time = 0;
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
dispatch_source_set_timer(_timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
dispatch_source_set_event_handler(_timer, ^{
time++;
NSLog(@"录制的时长:%d",time);
if (time == 10) {
NSLog(@"录制的时长限制在10秒以内");
[self.captureMovieFileOutput stopRecording];
[self.captureSession stopRunning];
dispatch_source_cancel(_timer);
}
});
dispatch_resume(_timer);
}
#pragma mark --
#pragma mark -- AVCaptureMovieFileOutput 录制视频
-(void)startRecordSession{
[self.captureMovieFileOutput startRecordingToOutputFileURL:({
NSURL * url = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"zhangxu.mov"]];
NSLog(@"视频想要缓存的地址:%@",url);
if ([[NSFileManager defaultManager]fileExistsAtPath:url.path]) {
[[NSFileManager defaultManager] removeItemAtURL:url error:nil];
}
url;
}) recordingDelegate:self];
}
#pragma mark AVCaptureFileOutputRecordingDelegate 代理方法
- (void)captureOutput:(AVCaptureFileOutput *)captureOutput didStartRecordingToOutputFileAtURL:(NSURL *)fileURL
fromConnections:(NSArray *)connections{
//Recording started
NSLog(@"视频录制开始!!");
[self startRuningWithSession]; // 开始计时
}
- (void)captureOutput:(AVCaptureFileOutput *)output didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray<AVCaptureConnection *> *)connections error:(nullable NSError *)error{
NSLog(@"视频录制结束!!");
NSLog(@"视频缓存地址:%@",outputFileURL);
NSLog(@"视频压缩前大小 %f M", [self getFileSize:[outputFileURL path]]);
BOOL recordedSuccessfully = YES;
id captureResult = [[error userInfo]objectForKey:AVErrorRecordingSuccessfullyFinishedKey];
if (captureResult) {
recordedSuccessfully = [captureResult boolValue];
}
if (recordedSuccessfully) {
[self compressVideoWithFileUrl:outputFileURL];
}
}
//此⽅方法可以获取视频⽂文件的大小。
- (CGFloat) getFileSize:(NSString *)path {
NSData * data = [NSData dataWithContentsOfFile:path];
float dataSize = (float)data.length/1024/1024;
NSLog(@"视频压缩qian大小 %f M", dataSize);
return dataSize;
// NSLog(@"%@",path);
// NSFileManager *fileManager = [NSFileManager defaultManager];
// float filesize = -1.0;
// if ([fileManager fileExistsAtPath:path]) {
// NSDictionary *fileDic = [fileManager attributesOfItemAtPath:path error:nil];//获取⽂文件的属性
// unsigned long long size = [[fileDic objectForKey:NSFileSize] longLongValue];
// filesize = 1.0*size/1024/1024;
// }else{ NSLog(@"找不不到⽂文件");
//
//
// }
// return filesize;
}
#pragma mark --
#pragma mark -- 视频压缩方法
-(void)compressVideoWithFileUrl:(NSURL *)fileUrl{
/*
这里需要注意的一点就是在重复的路径上保存文件是不行的,可以选择在点击开始的时候删除之前的
也可以这样按照时间命名不同的文件保存
在后面的AVAssetWriter也要注意这一点
*/
// 压缩后的视频的方法命名
NSDateFormatter * formatter = [[NSDateFormatter alloc]init];
[formatter setDateFormat:@"yyyy-MM-dd-HH:mm:ss"];
// 压缩后的文件路径
self.videoPath = [
NSString stringWithFormat:@"%@/%@.mov",NSTemporaryDirectory(),[formatter stringFromDate:[NSDate date]]];
// 先根据你传入的文件的路径穿件一个AVAsset
AVAsset * asset = [AVAsset assetWithURL:fileUrl];
/*
根据urlAsset创建AVAssetExportSession压缩类
第二个参数的意义:常用 压缩中等质量 AVAssetExportPresetMediumQuality
AVF_EXPORT NSString *const AVAssetExportPresetLowQuality NS_AVAILABLE_IOS(4_0);
AVF_EXPORT NSString *const AVAssetExportPresetMediumQuality NS_AVAILABLE_IOS(4_0);
AVF_EXPORT NSString *const AVAssetExportPresetHighestQuality NS_AVAILABLE_IOS(4_0);
*/
AVAssetExportSession * exportSession = [[AVAssetExportSession alloc]initWithAsset:asset presetName:AVAssetExportPresetMediumQuality];
// 优化压缩,这个属性能使压缩的质量更好
exportSession.shouldOptimizeForNetworkUse = YES;
// 到处的文件的路径
exportSession.outputURL = [NSURL fileURLWithPath:self.videoPath];
// 导出的文件格式
/*!
@constant AVFileTypeMPEG4 mp4格式的 AVFileTypeQuickTimeMovie mov格式的
@abstract A UTI for the MPEG-4 file format.
@discussion
The value of this UTI is @"public.mpeg-4".
Files are identified with the .mp4 extension.
可以看看这个outputFileType格式,比如AVFileTypeMPEG4也可以写成public.mpeg-4,其他类似
*/
exportSession.outputFileType = AVFileTypeQuickTimeMovie;
NSLog(@"视频压缩后的presetName: %@",exportSession.presetName);
// 压缩的方法 export 导出 Asynchronously 异步
[exportSession exportAsynchronouslyWithCompletionHandler:^{
/*
exportSession.status 枚举属性
typedef NS_ENUM(NSInteger, AVAssetExportSessionStatus) {
AVAssetExportSessionStatusUnknown,
AVAssetExportSessionStatusWaiting,
AVAssetExportSessionStatusExporting,
AVAssetExportSessionStatusCompleted,
AVAssetExportSessionStatusFailed,
AVAssetExportSessionStatusCancelled
};
*/
int exportStatus = exportSession.status;
switch (exportStatus) {
case AVAssetExportSessionStatusFailed:
NSLog(@"压缩失败");
break;
case AVAssetExportSessionStatusCompleted:
{
/*
压缩后的大小
也可以利用exportSession的progress属性,随时监测压缩的进度
*/
NSData * data = [NSData dataWithContentsOfFile:self.videoPath];
float dataSize = (float)data.length/1024/1024;
NSLog(@"视频压缩后大小 %f M", dataSize);
}
break;
default:
break;
}
}];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
@end
浙公网安备 33010602011771号