美文网首页iOS视频开发iOS开发技术iOS
iOS开发中音视频的获取、压缩上传

iOS开发中音视频的获取、压缩上传

作者: 键盘上的演绎者 | 来源:发表于2016-11-13 23:41 被阅读3458次

    随着市场直播的日益发展,现在市场上媒体流的重要性被越来越多的开发者重视。本菜鸟在上个项目的开发中,很是尴尬的也遇到的视频流以及音频流的处理、上传。当中也遇到的不少坑,因为当初的项目是社交类的服务项目,需要用户能够上传视频、语音等,所以涉及到音视频的处理。

    时间过得很快,2016年马上过去,最近又比较闲。所有用有限的时间总结一下这一年多来遇到的一些比较有意思的知识点与大家共勉,希望各路大神多多指教,不喜勿喷。谢谢。本菜鸟必将再去奋斗。

    这边文章分享一些本菜鸟的对视频的一些处理,音频后面有时间在上来分享共勉。

    视频获取上传方式
    1:直接摄像头拍摄,压缩上传给服务端
    2:从相册中选取已有视频,压缩上传给服务端。

    第一种方法好处理,拍摄完直接拿到视频流和路径了,这个应该不难。
    第二种方法,我在做项目的时候遇到不少坑(项目兼容7.0以上),所有在获取视频流的时候,8.0以上、8.0以下的方法不一样,被坑了。

    下面直接贴代码并解释,但愿有遇到的朋友可以互勉,本菜鸟也是菜鸟一个,做的也不是很好。

    -------------------1:直接拍摄视频,上传服务端--------------------

    #pragma mark 用户拍摄视频
    -(void)shotVideoEvent
    {
        if([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
            UIImagePickerController *videoPicker = [[UIImagePickerController alloc] init];
            videoPicker.delegate = self;
            videoPicker.allowsEditing = YES;
            videoPicker.sourceType = UIImagePickerControllerSourceTypeCamera;
            NSArray *mediaTypes = [UIImagePickerController availableMediaTypesForSourceType:UIImagePickerControllerSourceTypeCamera];
            videoPicker.videoQuality = UIImagePickerControllerQualityTypeMedium;
            videoPicker.videoMaximumDuration = 1200.0f;//1200秒  20分钟   本项目只允许最长时间
            //videoPicker.mediaTypes = mediaTypes;
            videoPicker.mediaTypes = [NSArray arrayWithObject:@"public.movie"];
            [self presentViewController:videoPicker animated:YES completion:nil];
        }else{
            UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:@"设备不支持拍照" message:nil delegate:nil cancelButtonTitle:@"知道了" otherButtonTitles:nil, nil];
            [alertView show];
        }
    }
    #pragma mark 拍摄视频完的方法
    - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
    {
        NSURL *videoURL = [info objectForKey:UIImagePickerControllerMediaURL];//视频路径
        NSData *beforeVideoData = [NSData dataWithContentsOfURL:videoURL];//未压缩的视频流
        //这里是视频流处理的方法   下面会解释
        VideoServer *videoServer = [[VideoServer alloc] init];
        
        NSString *randomName = @"随便给视频取个名字吧";//当然自己的项目不是这样写的,这里为了解释
        
        [videoServer compressVideo:videoURL andVideoName:randomName andSave:YES successCompress:^(NSData *resultData) {
            //这里就是视频流压缩后回调的方法
            if (resultData == nil || resultData.length == 0 ) {
                //如果压缩失败,我就把未压缩的视频流直接上传给服务端咯
                [self postFormData:beforeVideoData AndName:[NSString stringWithFormat:@"%@%@",randomName,@".mp4"]];
            }else{
                //压缩成功的视频流   上传给服务端
                [self postFormData:resultData AndName:[NSString stringWithFormat:@"%@%@",randomName,@".mp4"]];
            }//这里我发现拍摄的视频,压缩前压缩后大小差不多,会不会是拍摄完苹果已经做处理?
        }];
        [picker dismissViewControllerAnimated:YES completion:nil];
    }
    

    -------------------2:从相册选取视频,上传服务端--------------------

    #pragma mark 用户选择好了视频   这里项目需求用到了一个第三方的选取照片和视频《TZImagePickerController》,感谢作者,但是在使用的时候也遇到有bug,不能满足项目需求,自己做了部分修改,谢谢作者分享这个选取照片视频的
    - (void)imagePickerController:(TZImagePickerController *)picker didFinishPickingVideo:(UIImage *)coverImage sourceAssets:(id)asset
    {
        NSString *randomName = @"随便给视频取个名字吧";//当然自己的项目不是这样写的,这里为了解释
        //下面本菜鸟遇到的坑-->视频流路径竟然不一样,分iOS 8 以上、以下视频流的获取处理
        if ([UIDevice currentDevice].systemVersion.floatValue >= 8.0f) { //如果是iOS 8以上
            PHAsset *myAsset = asset;
            PHVideoRequestOptions *options = [[PHVideoRequestOptions alloc] init];
            [[PHImageManager defaultManager] requestAVAssetForVideo:myAsset options:options resultHandler:^(AVAsset * _Nullable asset, AVAudioMix * _Nullable audioMix, NSDictionary * _Nullable info) {
                NSURL *fileRUL = [asset valueForKey:@"URL"];
                
                NSData *beforeVideoData = [NSData dataWithContentsOfURL:fileRUL];//未压缩的视频流
                
                VideoServer *videoServer = [[VideoServer alloc] init];
                [videoServer compressVideo:fileRUL andVideoName:randomName andSave:NO successCompress:^(NSData *resultData) {
                    if (resultData == nil || resultData.length == 0 ) {
                        [self postFormData:beforeVideoData AndName:[NSString stringWithFormat:@"%@%@",randomName,@".mp4"]];
                    }else{
                        [self postFormData:resultData AndName:[NSString stringWithFormat:@"%@%@",randomName,@".mp4"]];
                    }
                }];
            }];
            
        }else{//如果是iOS 8以下获取视频流
            ALAssetRepresentation *rep = [asset defaultRepresentation];//视频流路径
            VideoServer *videoServer = [[VideoServer alloc] init];
            [videoServer compressVideo:rep.url andVideoName:randomName andSave:NO successCompress:^(NSData *resultData) {
                if (resultData == nil || resultData.length == 0 ) {
                    //iOS 8 以下 压缩失败 直接获取未压缩的视频流
                    ALAssetRepresentation *rep = [asset defaultRepresentation];
                    Byte *buffer = (Byte *)malloc(rep.size);
                    NSUInteger buffered = [rep getBytes:buffer fromOffset:0.0 length:rep.size error:nil];
                    NSData *videoData = [NSData dataWithBytesNoCopy:buffer length:buffered freeWhenDone:YES];
                    [self postFormData:videoData AndName:[NSString stringWithFormat:@"%@%@",randomName,@".mp4"]];
                }else{
                    //如果压缩成功  就用压缩的上传服务端,但是注意了   这里如果不用异步的,上传的时候程序挂了
                    
                    //ios 8 以下一定必须加入这个再去请求,否则会挂
                    dispatch_async(dispatch_get_main_queue(),^{
                        [self postFormData:resultData AndName:[NSString stringWithFormat:@"%@%@",randomName,@".mp4"]];
                    });
                }
            }];
        }
    }
    

    ----------------下面是视频压缩的方法以及获取视频首帧缩略图的类-----------------

    #import <Foundation/Foundation.h>
    
    @interface VideoServer : NSObject
    
    ///视频名字
    @property (nonatomic,strong) NSString *videoName;
    
    /// 压缩视频
    -(void)compressVideo:(NSURL *)path andVideoName:(NSString *)name andSave:(BOOL)saveState
              successCompress:(void(^)(NSData *))successCompress;
    
    /// 获取视频的首帧缩略图
    - (UIImage *)imageWithVideoURL:(NSURL *)url;
    
    @end
    
    #import "VideoServer.h"
    #import <Photos/Photos.h>
    #import <AssetsLibrary/AssetsLibrary.h>
    
    @implementation VideoServer
    //压缩视频
    -(void)compressVideo:(NSURL *)path andVideoName:(NSString *)name andSave:(BOOL)saveState
              successCompress:(void(^)(NSData *))successCompress  //saveState 是否保存视频到相册
    {
        self.videoName = name;
        AVURLAsset *avAsset = [[AVURLAsset alloc] initWithURL:path options:nil];
        NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:avAsset];
        if ([compatiblePresets containsObject:AVAssetExportPresetLowQuality]) {
            
            AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:avAsset presetName:AVAssetExportPreset640x480];
            exportSession.outputURL = [self compressedURL];//设置压缩后视频流导出的路径
            exportSession.shouldOptimizeForNetworkUse = true;
            //转换后的格式
            exportSession.outputFileType = AVFileTypeMPEG4;
            //异步导出
            [exportSession exportAsynchronouslyWithCompletionHandler:^{
                // 如果导出的状态为完成
                if ([exportSession status] == AVAssetExportSessionStatusCompleted) {
                    //NSLog(@"视频压缩成功,压缩后大小 %f MB",[self fileSize:[self compressedURL]]);
                    if (saveState) {
                        [self saveVideo:[self compressedURL]];//保存视频到相册
                    }
                    //压缩成功视频流回调回去
                    successCompress([NSData dataWithContentsOfURL:[self compressedURL]].length > 0?[NSData dataWithContentsOfURL:[self compressedURL]]:nil);
                }else{
                    //压缩失败的回调
                    successCompress(nil);
                }
            }];
        }
    }
    
    #pragma mark 保存压缩
    - (NSURL *)compressedURL
    {
        return [NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true) lastObject] stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.mp4",self.videoName]]];
    }
    
    #pragma mark 计算视频大小
    - (CGFloat)fileSize:(NSURL *)path
    {
        return [[NSData dataWithContentsOfURL:path] length]/1024.00 /1024.00;
    }
    
    #pragma mark 保存视频到相册
    - (void)saveVideo:(NSURL *)outputFileURL
    {
        ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
        [library writeVideoAtPathToSavedPhotosAlbum:outputFileURL completionBlock:^(NSURL *assetURL, NSError *error) {
            if (error) {
                //(@"保存视频失败:%@",error);
            } else {
                //NSLog(@"保存视频到相册成功");
            }
        }];
    }
    
    /**
     *  通过视频的URL,获得视频缩略图
     *  @param url 视频URL
     *  @return首帧缩略图
     */
    #pragma mark 获取视频的首帧缩略图
    - (UIImage *)imageWithVideoURL:(NSURL *)url
    {
        NSDictionary *opts = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO] forKey:AVURLAssetPreferPreciseDurationAndTimingKey];
        AVURLAsset *urlAsset = [AVURLAsset URLAssetWithURL:url options:opts];
        // 根据asset构造一张图
        AVAssetImageGenerator *generator = [AVAssetImageGenerator assetImageGeneratorWithAsset:urlAsset];
        // 设定缩略图的方向
        // 如果不设定,可能会在视频旋转90/180/270°时,获取到的缩略图是被旋转过的,而不是正向的(自己的理解)
        generator.appliesPreferredTrackTransform = YES;
        // 设置图片的最大size(分辨率)
        generator.maximumSize = CGSizeMake(600, 450);
        NSError *error = nil;
        // 根据时间,获得第N帧的图片
        // CMTimeMake(a, b)可以理解为获得第a/b秒的frame
        CGImageRef img = [generator copyCGImageAtTime:CMTimeMake(0, 10000) actualTime:NULL error:&error];
        UIImage *image = [UIImage imageWithCGImage: img];
        return image;
    }
    
    @end
    

    ---------------------------------总结一下-------------------------------
    本菜鸟的一些总结,当然分享出来的跟自己项目的做了适当的修改,目的就是分享共勉,希望大神多多指教,不喜勿喷,如果可以对您有好处,那本菜鸟已心满意足。
    获取视频的首帧缩略图,是因为本菜鸟的项目需要,服务端需要客户端上传一张图片,用于后面页面显示视频的,所有...

    相关文章

      网友评论

      • 路有点颠簸:大佬、我那个PHAsset视频压缩一直不成功,if ([exportSession status] == AVAssetExportSessionStatusCompleted)没成功,怎么办
      • 问岳:大神,你好,我发现压缩后反而更大了,这是什么问题呢:dizzy_face: 求回复
      • b869facd5ce7:为什么我的总是压缩失败呢?压缩进度怎么获取啊? 大神
        b869facd5ce7:@键盘上的演绎者 好滴 发一下微信或扣扣:joy:
        键盘上的演绎者:@兵临城下_bce0 加我
      • b869facd5ce7:写的一目了然 不错不错
      • 小小夕舞:给个demo呗
        小小夕舞:哦哦哦,知道了,不好意思
        小小夕舞:VideoServer这个是什么
        键盘上的演绎者:这个无demo,上面的方法直接使用就可以
      • Pusswzy:NSURL *videoURL = [info objectForKey:UIImagePickerControllerMediaURL];//视频路径
        NSData *beforeVideoData = [NSData dataWithContentsOfURL:videoURL];//未压缩的视频流

        其实beforeVideoData已经自动被系统压缩过了.. 您有处理方法么
        键盘上的演绎者:您好,这个是区分iOS 8之前之后,现在项目一般都是8开始。这个方法没在维护,项目没有再用到了。
      • 旭丶Joy:用到了, 感谢
        键盘上的演绎者:@旭丶Joy 共勉
      • 逐日追星看月亮:视频直接读入内存内存不会爆吗
      • 梦随兴飞:下面这段代码有问题么?AVAsset有可能不是AVURLAsset可能是AVComposition这样就得不到URL了
        resultHandler:^(AVAsset * _Nullable asset, AVAudioMix * _Nullable audioMix, NSDictionary * _Nullable info) {
        NSURL *fileRUL = [asset valueForKey:@"URL"];

        NSData *beforeVideoData = [NSData dataWithContentsOfURL:fileRUL];//未压缩的视频流
        键盘上的演绎者:@梦随兴飞 好的,谢谢,互勉
        梦随兴飞:@键盘上的演绎者 我换了一种方法了,因为我需要适配不同平台。这样不同的平台都能播放我的视频。http://www.jianshu.com/p/2af1c0365ee4
        键盘上的演绎者:要根据版本判断,版本不一样导致路径不一样
      • 小蜜蜂Bee:有没有 demo 啊?发个 demo 来看看呗,834537795@qq.com
        manajay:视频最大20分钟?, 我项目中的视频测试30分钟,就无法获取到视频的Data数据了,
        使用的方法是Data(contentsOf: avAsset.url, options: Data.ReadingOptions.mappedIfSafe)
        谁先注册了阿K:音视频中的音频压缩呢???????????????
        键盘上的演绎者:@小蜜蜂Bee 不好意思哈,暂无独立的demo,这是项目抽出来的。
      • 米诺mino:你们现在项目换了啊
        键盘上的演绎者:@米诺mino 不懂您的意思,什么项目换了,您是不是认错人了...

      本文标题:iOS开发中音视频的获取、压缩上传

      本文链接:https://www.haomeiwen.com/subject/quocpttx.html