iOS视频录制、压缩、上传(整理)

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可以用来选择照片,拍照和录制视频。

  1. sourceType: 拾取源类型,sourceType是枚举类型:

UIImagePickerControllerSourceTypePhotoLibrary:照片库,默认值
UIImagePickerControllerSourceTypeCamera:摄像头
UIImagePickerControllerSourceTypeSavedPhotosAlbum:相簿

  1. mediaTypes:媒体类型,默认情况下此数组包含kUTTypeImage,所以拍照时可以不用设置;但是当要录像的时候必须设置,可以设置为kUTTypeVideo(视频,但不带声音)或者kUTTypeMovie(视频并带有声音)

  2. videoMaximumDuration:视频最大录制时长,默认为10 s

  3. videoQuality:视频质量,枚举类型:
    UIImagePickerControllerQualityTypeHigh:高清质量
    UIImagePickerControllerQualityTypeMedium:中等质量,适合WiFi传输
    UIImagePickerControllerQualityTypeLow:低质量,适合蜂窝网传输
    UIImagePickerControllerQualityType640x480:640480
    UIImagePickerControllerQualityTypeIFrame1280x720:1280
    720
    UIImagePickerControllerQualityTypeIFrame960x540:960*540

  4. cameraDevice:摄像头设备,cameraDevice是枚举类型:
    UIImagePickerControllerCameraDeviceRear:前置摄像头
    UIImagePickerControllerCameraDeviceFront:后置摄像头

  5. cameraFlashMode:闪光灯模式,枚举类型: UIImagePickerControllerCameraFlashModeOff:关闭闪光灯UIImagePickerControllerCameraFlashModeAuto:闪光灯自动UIImagePickerControllerCameraFlashModeOn:打开闪光灯

  6. (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
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

建立视频拍摄的步骤如下:

  1. 创建AVCaptureSession对象。
// 创建会话 (AVCaptureSession) 对象。
_captureSession = [[AVCaptureSession alloc] init];
if ([_captureSession canSetSessionPreset:AVCaptureSessionPreset640x480]) {
    // 设置会话的 sessionPreset 属性, 这个属性影响视频的分辨率
    [_captureSession setSessionPreset:AVCaptureSessionPreset640x480];
}
  1. 使用AVCaptureDevice的静态方法获得需要使用的设备,例如拍照和录像就需要获得摄像头设备,录音就要获得麦克风设备。
// 获取摄像头输入设备, 创建 AVCaptureDeviceInput 对象
// 在获取摄像头的时候,摄像头分为前后摄像头,我们创建了一个方法通过用摄像头的位置来获取摄像头 
AVCaptureDevice *videoCaptureDevice = [self getCameraDeviceWithPosition:AVCaptureDevicePositionBack];
if (!captureDevice) {
    NSLog(@"---- 取得后置摄像头时出现问题---- ");
    return;
}

// 添加一个音频输入设备
// 直接可以拿数组中的数组中的第一个
AVCaptureDevice *audioCaptureDevice = [[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] firstObject];
  1. 利用输入设备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;
}
  1. 初始化输出数据管理对象,如果要拍照就初始化AVCaptureStillImageOutput对象;如果拍摄视频就初始化AVCaptureMovieFileOutput对象。
// 拍摄视频输出对象
// 初始化输出设备对象,用户获取输出数据
_caputureMovieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
  1. 将数据输入对象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;
    }
}
  1. 创建视频预览图层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];
  1. 将捕获的音频或视频数据输出到指定文件。
    //创建一个拍摄的按钮,当我们点击这个按钮就会触发视频录制,并将这个录制的视频放到 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视频压缩处理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值