iOS视频录制、压缩、上传(整理)
我们在项目中有时会碰到视频相关的需求,一般的可以分为几种情况:
1. 简单的视频开发,对界面无要求,可直接使用系统UIImagePickerController。
(1)使用UIImagePickerController视频录制,短视频10秒钟
(2)在UIImagePickerController代理方法 didFinishPickingMediaWithInfo,使用AVAssetExportSession转码MP4(一般要兼容Android播放,iOS默认是mov格式)
(3)使用AFNetWorking上传到服务器
(4)网络请求,使用AVPlayerViewControlller在线播放视频流。(在iOS9之后,苹果已经弃用MPMoviePlayerController)
1.1 UIImagePickerController
UIImagePickerController继承于UINavigationController。UIImagePickerController可以用来选择照片,拍照和录制视频。
- sourceType: 拾取源类型,sourceType是枚举类型:
UIImagePickerControllerSourceTypePhotoLibrary:照片库,默认值
UIImagePickerControllerSourceTypeCamera:摄像头
UIImagePickerControllerSourceTypeSavedPhotosAlbum:相簿
- mediaTypes:媒体类型,默认情况下此数组包含kUTTypeImage,所以拍照时可以不用设置;但是当要录像的时候必须设置,可以设置为kUTTypeVideo(视频,但不带声音)或者kUTTypeMovie(视频并带有声音)
- videoMaximumDuration:视频最大录制时长,默认为10 s
- videoQuality:视频质量,枚举类型:
UIImagePickerControllerQualityTypeHigh:高清质量
UIImagePickerControllerQualityTypeMedium:中等质量,适合WiFi传输
UIImagePickerControllerQualityTypeLow:低质量,适合蜂窝网传输
UIImagePickerControllerQualityType640x480:640480
UIImagePickerControllerQualityTypeIFrame1280x720:1280720
UIImagePickerControllerQualityTypeIFrame960x540:960*540 - cameraDevice:摄像头设备,cameraDevice是枚举类型:
UIImagePickerControllerCameraDeviceRear:前置摄像头
UIImagePickerControllerCameraDeviceFront:后置摄像头 - cameraFlashMode:闪光灯模式,枚举类型: UIImagePickerControllerCameraFlashModeOff:关闭闪光灯UIImagePickerControllerCameraFlashModeAuto:闪光灯自动UIImagePickerControllerCameraFlashModeOn:打开闪光灯
(BOOL)isSourceTypeAvailable:(UIImagePickerControllerSourceType)sourceType
指定的源类型是否可用,sourceType是枚举类型:
UIImagePickerControllerSourceTypePhotoLibrary:照片库
UIImagePickerControllerSourceTypeCamera:摄像头
UIImagePickerControllerSourceTypeSavedPhotosAlbum:相簿
视频录制示例:
//使用UIImagePickerController视频录制
UIImagePickerController *picker = [[UIImagePickerController alloc] init];
picker.delegate = self;
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
picker.sourceType = UIImagePickerControllerSourceTypeCamera;
}
//mediaTypes设置摄影还是拍照
//kUTTypeImage 对应拍照
//kUTTypeMovie 对应摄像
// NSString *requiredMediaType = ( NSString *)kUTTypeImage;
NSString *requiredMediaType1 = ( NSString *)kUTTypeMovie;
NSArray *arrMediaTypes=[NSArray arrayWithObjects:requiredMediaType1,nil];
picker.mediaTypes = arrMediaTypes;
// picker.videoQuality = UIImagePickerControllerQualityTypeHigh;默认是中等
picker.videoMaximumDuration = 60.; //60秒
[self presentViewController:picker animated:YES completion:^{
}];
1.2 AVAsset
AVAsset是一个抽象类和不可变类。定义了媒体资源混合呈现的方式。主要用于获取多媒体信息,既然是一个抽象类,那么当然不能直接使用。可以让开发者在处理流媒体时提供一种简单统一的方式,它并不是媒体资源,但是它可以作为时基媒体的容器。
实际上是创建的是它的子类AVUrlAsset的一个实例。通过AVUrlAsset我们可以创建一个带选项(optional)的asset,以提供更精确的时长和计时信息。比如通过AVURLAsset的duration计算视频时长。
/**
获取视频时长
*/
- (CGFloat)getVideoLength:(NSURL *)url{
AVURLAsset *avUrl = [AVURLAsset assetWithURL:url];
CMTime time = [avUrl duration];
int second = ceil(time.value/time.timescale);
return second;
}
1.3 AVAssetExportSession
AVAssetExportSession是用于为AVAsset源对象进行转码输出,可进行格式转码,压缩等。
AVURLAsset *avAsset = [AVURLAsset URLAssetWithURL:inputURL options:nil];
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:avAsset presetName:AVAssetExportPresetMediumQuality];
exportSession.outputURL = outputURL;
exportSession.outputFileType = AVFileTypeMPEG4;
exportSession.shouldOptimizeForNetworkUse= YES;
[exportSession exportAsynchronouslyWithCompletionHandler:nil];
1.4 AVAssetImageGenerator
AVAssetImageGenerator能够取出视频中每一帧的缩略图或者预览图
// result
UIImage *image = nil;
// AVAssetImageGenerator
AVAsset *asset = [AVAsset assetWithURL:videoURL];
AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:asset];
imageGenerator.appliesPreferredTrackTransform = YES;
// calculate the midpoint time of video
Float64 duration = CMTimeGetSeconds([asset duration]);
// 取某个帧的时间,参数一表示哪个时间(秒),参数二表示每秒多少帧
// 通常来说,600是一个常用的公共参数,苹果有说明:
// 24 frames per second (fps) for film, 30 fps for NTSC (used for TV in North America and
// Japan), and 25 fps for PAL (used for TV in Europe).
// Using a timescale of 600, you can exactly represent any number of frames in these systems
CMTime midpoint = CMTimeMakeWithSeconds(duration / 2.0, 600);
// get the image from
NSError *error = nil;
CMTime actualTime;
// Returns a CFRetained CGImageRef for an asset at or near the specified time.
// So we should mannully release it
CGImageRef centerFrameImage = [imageGenerator copyCGImageAtTime:midpoint
actualTime:&actualTime
error:&error];
if (centerFrameImage != NULL) {
image = [[UIImage alloc] initWithCGImage:centerFrameImage];
// Release the CFRetained image
CGImageRelease(centerFrameImage);
}
return image;
1.5 AVPlayerViewControlller
AVPlayerViewController是UIViewController的子类,它可以用来显示AVPlayer对象的视觉内容和标准的播放控制。
具体AVPlayer自定义下面再说,回到AVPlayerViewController,它不支持UI自定义,实现比较简单,代码如下:
AVPlayer *player = [AVPlayer playerWithURL:self.finalURL];
AVPlayerViewController *playerVC = [[AVPlayerViewController alloc] init];
playerVC.player = player;
playerVC.view.frame = self.view.frame;
[self.view addSubview:playerVC.view];
[playerVC.player play];
2. 复杂的业务需求,需要自定义视频界面
(1)使用AVFoundation拍照和录制视频,自定义界面
(2)使用AVAssetExportSession转码MP4(一般要兼容Android播放,iOS默认是mov格式)
(3)使用AFN上传到服务器
(4)网络请求,使用AVFoundation框架的AVPlayer来自定义播放界面,在线播放视频流。播放又分为先下后播和边下边播。
2.1 AVFoundation
AVFoundation是一个可以用来使用和创建基于时间的视听媒体的框架,它提供了一个能使用基于时间的视听数据的详细级别的Objective-C接口。例如:您可以用它来检查,创建,编辑或是重新编码媒体文件。也可以从设备中获取输入流,在视频实时播放时操作和回放。
AVCaptureSession
:媒体(音、视频)捕获会话,负责把捕获的音视频数据输出到输出设备中。一个AVCaptureSession可以有多个输入输出。
AVCaptureDevice
:输入设备,包括麦克风、摄像头,通过该对象可以设置物理设备的一些属性(例如相机聚焦、白平衡等)。
AVCaptureDeviceInput
:设备输入数据管理对象,可以根据AVCaptureDevice创建对应的AVCaptureDeviceInput对象,该对象将会被添加到AVCaptureSession中管理。
AVCaptureVideoPreviewLayer
:相机拍摄预览图层,是CALayer的子类,使用该对象可以实时查看拍照或视频录制效果,创建该对象需要指定对应的 AVCaptureSession对象。
AVCaptureOutput
:输出数据管理对象,用于接收各类输出数据,通常使用对应的子类AVCaptureAudioDataOutput、AVCaptureStillImageOutput、AVCaptureVideoDataOutput、AVCaptureFileOutput, 该对象将会被添加到AVCaptureSession中管理。
作者:纯情_小火鸡
链接:https://www.jianshu.com/p/c5b71a0c6038
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
建立视频拍摄的步骤如下:
- 创建
AVCaptureSession
对象。
// 创建会话 (AVCaptureSession) 对象。
_captureSession = [[AVCaptureSession alloc] init];
if ([_captureSession canSetSessionPreset:AVCaptureSessionPreset640x480]) {
// 设置会话的 sessionPreset 属性, 这个属性影响视频的分辨率
[_captureSession setSessionPreset:AVCaptureSessionPreset640x480];
}
- 使用
AVCaptureDevice
的静态方法获得需要使用的设备,例如拍照和录像就需要获得摄像头设备,录音就要获得麦克风设备。
// 获取摄像头输入设备, 创建 AVCaptureDeviceInput 对象
// 在获取摄像头的时候,摄像头分为前后摄像头,我们创建了一个方法通过用摄像头的位置来获取摄像头
AVCaptureDevice *videoCaptureDevice = [self getCameraDeviceWithPosition:AVCaptureDevicePositionBack];
if (!captureDevice) {
NSLog(@"---- 取得后置摄像头时出现问题---- ");
return;
}
// 添加一个音频输入设备
// 直接可以拿数组中的数组中的第一个
AVCaptureDevice *audioCaptureDevice = [[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] firstObject];
- 利用输入设备
AVCaptureDevice
初始化AVCaptureDeviceInput
对象。
// 视频输入对象
// 根据输入设备初始化输入对象,用户获取输入数据
_videoCaptureDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:captureDevice error:&error];
if (error) {
NSLog(@"---- 取得设备输入对象时出错 ------ %@",error);
return;
}
// 音频输入对象
//根据输入设备初始化设备输入对象,用于获得输入数据
_audioCaptureDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:audioCaptureDevice error:&error];
if (error) {
NSLog(@"取得设备输入对象时出错 ------ %@",error);
return;
}
- 初始化输出数据管理对象,如果要拍照就初始化
AVCaptureStillImageOutput
对象;如果拍摄视频就初始化AVCaptureMovieFileOutput
对象。
// 拍摄视频输出对象
// 初始化输出设备对象,用户获取输出数据
_caputureMovieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
- 将数据输入对象
AVCaptureDeviceInput
、数据输出对象AVCaptureOutput
添加到媒体会话管理对象AVCaptureSession
中。
// 将视频输入对象添加到会话 (AVCaptureSession) 中
if ([_captureSession canAddInput:_videoCaptureDeviceInput]) {
[_captureSession addInput:_videoCaptureDeviceInput];
}
// 将音频输入对象添加到会话 (AVCaptureSession) 中
if ([_captureSession canAddInput:_captureDeviceInput]) {
[_captureSession addInput:audioCaptureDeviceInput];
AVCaptureConnection *captureConnection = [_caputureMovieFileOutput connectionWithMediaType:AVMediaTypeVideo];
// 标识视频录入时稳定音频流的接受,我们这里设置为自动
if ([captureConnection isVideoStabilizationSupported]) {
captureConnection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeAuto;
}
}
- 创建视频预览图层
AVCaptureVideoPreviewLayer
并指定媒体会话,添加图层到显示容器中,调用AVCaptureSession
的startRuning方法开始捕获。
// 通过会话 (AVCaptureSession) 创建预览层
_captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:_captureSession];
// 显示在视图表面的图层
CALayer *layer = self.viewContrain.layer;
layer.masksToBounds = true;
_captureVideoPreviewLayer.frame = layer.bounds;
_captureVideoPreviewLayer.masksToBounds = true;
_captureVideoPreviewLayer.videoGravity=AVLayerVideoGravityResizeAspectFill;//填充模式
[layer addSublayer:_captureVideoPreviewLayer];
// 让会话(AVCaptureSession)勾搭好输入输出,然后把视图渲染到预览层上
[_captureSession startRunning];
- 将捕获的音频或视频数据输出到指定文件。
//创建一个拍摄的按钮,当我们点击这个按钮就会触发视频录制,并将这个录制的视频放到 temp 文件夹中
- (void)takeMovie:(UIButton *)sender {
[(UIButton *)sender setSelected:![(UIButton *)sender isSelected]];
if ([(UIButton *)sender isSelected]) {
AVCaptureConnection *captureConnection=[self.caputureMovieFileOutput connectionWithMediaType:AVMediaTypeVideo];
// 开启视频防抖模式
AVCaptureVideoStabilizationMode stabilizationMode = AVCaptureVideoStabilizationModeCinematic;
if ([self.captureDeviceInput.device.activeFormat isVideoStabilizationModeSupported:stabilizationMode]) {
[captureConnection setPreferredVideoStabilizationMode:stabilizationMode];
}
//如果支持多任务则则开始多任务
if ([[UIDevice currentDevice] isMultitaskingSupported]) {
self.backgroundTaskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
}
// 预览图层和视频方向保持一致,这个属性设置很重要,如果不设置,那么出来的视频图像可以是倒向左边的。
captureConnection.videoOrientation=[self.captureVideoPreviewLayer connection].videoOrientation;
// 设置视频输出的文件路径,这里设置为 temp 文件
NSString *outputFielPath=[NSTemporaryDirectory() stringByAppendingString:MOVIEPATH];
// 路径转换成 URL 要用这个方法,用 NSBundle 方法转换成 URL 的话可能会出现读取不到路径的错误
NSURL *fileUrl=[NSURL fileURLWithPath:outputFielPath];
// 往路径的 URL 开始写入录像 Buffer ,边录边写
[self.caputureMovieFileOutput startRecordingToOutputFileURL:fileUrl recordingDelegate:self];
}
else {
// 取消视频拍摄
[self.caputureMovieFileOutput stopRecording];
[self.captureSession stopRunning];
[self completeHandle];
}
}
⚠️注:当然我们录制的开始与结束都是有监听方法的,AVCaptureFileOutputRecordingDelegate
这个代理里面就有我们想要做的
- (void)captureOutput:(AVCaptureFileOutput *)captureOutput didStartRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections
{
NSLog(@"---- 开始录制 ----");
}
- (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error
{
NSLog(@"---- 录制结束 ----");
}
2.2 AVPlayer自定义播放器
AVPlayer是用于管理媒体资产的播放和定时的控制器对象,它提供了控制播放器的有运输行为的接口,如它可以在媒体的时限内播放,暂停,和改变播放的速度,并有定位各个动态点的能力。我们可以使用AVPlayer来播放本地和远程的视频媒体文件,如QuickTime影片和MP3音频文件,以及视听媒体使用HTTP流媒体直播服务。
AVPlayer本身并不能显示视频,而且它也不像AVPlayerViewController有一个view属性。如果AVPlayer要显示必须创建一个播放器层AVPlayerLayer用于展示,播放器层继承于CALayer,有了AVPlayerLayer之添加到控制器视图的layer中即可。要使用AVPlayer首先了解一下几个常用的类:
- AVPlayer:播放器,将数据解码处理成为图像和声音。
- AVAsset:主要用于获取多媒体信息,是一个抽象类,不能直接使用。
- AVURLAsset:AVAsset的子类,可以根据一个URL路径创建一个包含媒体信息的AVURLAsset对象。负责网络连接,请求数据。
- AVPlayerItem:一个媒体资源管理对象,管理者视频的一些基本信息和状态,负责数据的获取与分发;一个AVPlayerItem对应着一个视频资源。可以添加观察者监听播放源的3个状态:
//添加观察者
[_playerItem
addObserver:self
forKeyPath:@"status"
options:NSKeyValueObservingOptionNew
context:nil];
//对播放源的三个状态(status)
AVPlayerItemStatusReadyToPlay //播放源准备好
AVPlayerItemStatusUnknown //播放源未知
AVPlayerItemStatusFailed //播放源失败
AVPlayerLayer:图像层,AVPlayer的图像要通过AVPlayerLayer呈现。
使用 AVPlayer 对象控制资产的播放。在播放期间,可以使用一个 AVPlayerItem 实例去管理资产作为一个整体的显示状态,AVPlayerItemTrack 对象来管理一个单独轨道的显示状态。使用 AVPlayerLayer 显示视频。
需要注意的是,AVPlayer的模式是,你不要主动调用play方法播放视频,而是等待AVPlayerItem告诉你,我已经准备好播放了,你现在可以播放了,所以我们要监听AVPlayerItem的状态,通过添加监听者的方式获取AVPlayerItem的状态。当监听到播放器已经准备好播放的时候,就可以调用play方法。
3.获得视频路径方法
- (void)getVideoFromPHAsset:(PHAsset *)asset Complete:(void(^)(NSData *data,NSString *string))result{
if (asset == nil) {
return;
}
NSArray *assetResources = [PHAssetResource assetResourcesForAsset:asset];
PHAssetResource *resource;
for (PHAssetResource *assetRes in assetResources) {
if (assetRes.type == PHAssetResourceTypePairedVideo ||
assetRes.type == PHAssetResourceTypeVideo) {
resource = assetRes;
}
}
NSString *fileName = @"tempAssetVideo.mov";
if (resource.originalFilename) {
fileName = resource.originalFilename;
}
if (asset.mediaType == PHAssetMediaTypeVideo || asset.mediaSubtypes == PHAssetMediaSubtypePhotoLive) {
PHVideoRequestOptions *options = [[PHVideoRequestOptions alloc] init];
options.version = PHImageRequestOptionsVersionCurrent;
options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
NSString *PATH_MOVIE_FILE = [NSTemporaryDirectory() stringByAppendingPathComponent:fileName];
[[NSFileManager defaultManager] removeItemAtPath:PATH_MOVIE_FILE error:nil];
[[PHAssetResourceManager defaultManager] writeDataForAssetResource:resource
toFile:[NSURL fileURLWithPath:PATH_MOVIE_FILE]
options:nil
completionHandler:^(NSError * _Nullable error) {
if (error) {
result(nil, nil);
} else {
NSData *data = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:PATH_MOVIE_FILE]];
result(data, fileName);
}
[[NSFileManager defaultManager] removeItemAtPath:PATH_MOVIE_FILE error:nil];
}];
} else {
result(nil, nil);
}
}
调用
[self getVideoFromPHAsset:asset Complete:^(NSData *data, NSString *string) {
//使用AFN进行视频上传
[weakSelf uploadVideoWithData:data];
}];
如果视频的尺寸大于后台要求的尺寸,可以对视频进行压缩
直接上代码:
+ (void)compressedVideoOtherMethodWithURL:(NSURL *)url compressionType:(NSString *)compressionType compressionResultPath:(CompressionSuccessBlock)resultPathBlock {
NSString *resultPath;
NSData *data = [NSData dataWithContentsOfURL:url];
CGFloat totalSize = (float)data.length / 1024 / 1024;
AVURLAsset *avAsset = [AVURLAsset URLAssetWithURL:url options:nil];
NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:avAsset];
// 所支持的压缩格式中是否有 所选的压缩格式
if ([compatiblePresets containsObject:compressionType]) {
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:avAsset presetName:compressionType];
NSDateFormatter *formater = [[NSDateFormatter alloc] init];// 用时间, 给文件重新命名, 防止视频存储覆盖,
[formater setDateFormat:@"yyyy-MM-dd-HH:mm:ss"];
NSFileManager *manager = [NSFileManager defaultManager];
BOOL isExists = [manager fileExistsAtPath:CompressionVideoPaht];
if (!isExists) {
[manager createDirectoryAtPath:CompressionVideoPaht withIntermediateDirectories:YES attributes:nil error:nil];
}
resultPath = [CompressionVideoPaht stringByAppendingPathComponent:[NSString stringWithFormat:@"outputJFVideo-%@.mov", [formater stringFromDate:[NSDate date]]]];
JFLog(@"resultPath = %@",resultPath);
exportSession.outputURL = [NSURL fileURLWithPath:resultPath];
exportSession.outputFileType = AVFileTypeMPEG4;
exportSession.shouldOptimizeForNetworkUse = YES;
[exportSession exportAsynchronouslyWithCompletionHandler:^(void)
{
if (exportSession.status == AVAssetExportSessionStatusCompleted) {
NSData *data = [NSData dataWithContentsOfFile:resultPath];
float memorySize = (float)data.length / 1024 / 1024;
JFLog(@"视频压缩后大小 %f", memorySize);
resultPathBlock (resultPath, memorySize);
} else {
JFLog(@"压缩失败");
}
}];
} else {
JFLog(@"不支持 %@ 格式的压缩", compressionType);
}
}
/**
* 清楚沙盒文件中, 压缩后的视频所有, 在使用过压缩文件后, 不进行再次使用时, 可调用该方法, 进行删除
*/
+ (void)removeCompressedVideoFromDocuments {
NSFileManager *manager = [NSFileManager defaultManager];
if ([manager fileExistsAtPath:CompressionVideoPaht]) {
[[NSFileManager defaultManager] removeItemAtPath:CompressionVideoPaht error:nil];
}
}
主要学习链接:iOS视频录制压缩与上传iOS视频压缩处理