iOS--相册视频MOV转MP4

相册视频MOV转MP4

最新做的一个功能涉及到了视频的录制、压缩及上传。

根据网上诸多大神的经验,终于算是调通了,但也发现了一些问题,所以把我的经验分享一下。

首先,肯定是调用一下系统的相机或相册,

那么导入哪几个库 就不用我说了吧

打开相册获取视频PHAsset

TZImagePickerController 这个库是个好东西

    TZImagePickerController *imagePickerVc = [[TZImagePickerController alloc] initWithMaxImagesCount:20 delegate:self];
    imagePickerVc.allowPickingVideo = YES;
    imagePickerVc.allowPickingImage = NO;
    // 设置 模态弹出模式。 iOS 13默认非全屏
    imagePicker.modalPresentationStyle = UIModalPresentationFullScreen;
    [self presentViewController:imagePickerVc animated:YES completion:nil];
    

从相册获取到视频PHAsset之后

这时的 (PHAsset*)asset其实就是 AVURLAsset类型

// 选择视频的回调
-(void)imagePickerController:(TZImagePickerController *)picker
       didFinishPickingVideo:(UIImage *)coverImage
                sourceAssets:(PHAsset *)asset{
    NSLog(@"--------- �视频编码 ----------- 开始 ----------");
    [MovEncodeToMpegTool convertMovToMp4FromPHAsset:asset
                      andAVAssetExportPresetQuality:ExportPresetMediumQuality
                  andMovEncodeToMpegToolResultBlock:^(NSURL *mp4FileUrl, NSData *mp4Data, NSError *error) {
        NSLog(@"--------- 视频编码 ----------- 结束 ----------\n{\n  %@,\n   %ld,\n  %@\n}",mp4FileUrl,mp4Data.length,error.localizedDescription);
    }];
    
}

提取MOV文件的AVURLAsset

/// 转码 MOV--MP4
/// @param resourceAsset MOV资源
/// @param exportQuality 预设
/// @param movEncodeToMpegToolResultBlock 转码后的MP4文件链接
+ (void)convertMovToMp4FromPHAsset:(PHAsset*)resourceAsset
     andAVAssetExportPresetQuality:(ExportPresetQuality)exportQuality
 andMovEncodeToMpegToolResultBlock:(MovEncodeToMpegToolResultBlock)movEncodeToMpegToolResultBlock {
    
    /*
     iOS 13 以前
     {
     assetLocalIdentifier = "A99AA1C3-7D59-4E10-A8D3-BF4FAD7A1BC6/L0/001";
     fileSize = 2212572;
     filename = "IMG_0049.MOV";
     size = "1080,1920";
     type = video;
     uti = "com.apple.quicktime-movie";
     }
     iOS 13
     {
     asset = "9B3F7172-14BB-462E-B003-1CDA5583B038/L0/001";
     duration = "20.678";
     filename = "IMG_0031.MOV";
     size = "1080,1920";
     type = video;
     uti = "com.apple.quicktime-movie";
     }
     */
    NSDictionary *videoInfo = [self getVideoInfo:resourceAsset];
    NSLog(@"\n%@", videoInfo);
    
    PHVideoRequestOptions *options = [[PHVideoRequestOptions alloc] init];
    options.version = PHImageRequestOptionsVersionCurrent;
    options.deliveryMode = PHVideoRequestOptionsDeliveryModeAutomatic;
    options.networkAccessAllowed = true;
    
    PHImageManager *manager = [PHImageManager defaultManager];
    [manager requestAVAssetForVideo:resourceAsset
                            options:options
                      resultHandler:^(AVAsset * _Nullable asset, AVAudioMix * _Nullable audioMix, NSDictionary * _Nullable info) {
        
        if ([asset isKindOfClass:[AVURLAsset class]]) {
            AVURLAsset *urlAsset = (AVURLAsset *)asset;
            [MovEncodeToMpegTool convertMovToMp4FromAVURLAsset:urlAsset
                                 andAVAssetExportPresetQuality:exportQuality
                             andMovEncodeToMpegToolResultBlock:movEncodeToMpegToolResultBlock];
        }else{
            NSError *error = [NSError errorWithDomain:@"ConvertMovToMp4ErrorDomain"
                                                 code:10008
                                             userInfo:@{NSLocalizedDescriptionKey:@"resource type error"}];
            movEncodeToMpegToolResultBlock(nil, nil , error);
        }
        
    }];;
}

MOV转码MP4

#pragma mark ### MOV转码MP4
+ (void)convertMovToMp4FromAVURLAsset:(AVURLAsset*)urlAsset
        andAVAssetExportPresetQuality:(ExportPresetQuality)exportQuality
    andMovEncodeToMpegToolResultBlock:(MovEncodeToMpegToolResultBlock)movEncodeToMpegToolResultBlock {
    AVURLAsset *avAsset = [AVURLAsset URLAssetWithURL:urlAsset.URL options:nil];
    
    NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:avAsset];
    
  // 查询是否有匹配的预设
    if ([compatiblePresets containsObject:[self getAVAssetExportPresetQuality:exportQuality]]) {
        
        //  在Documents目录下创建一个名为FileData的文件夹
        NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject] stringByAppendingPathComponent:@"Cache/VideoData"];
        
        NSFileManager *fileManager = [NSFileManager defaultManager];
        BOOL isDir = FALSE;
        BOOL isDirExist = [fileManager fileExistsAtPath:path isDirectory:&isDir];
        if(!(isDirExist && isDir)) {
            BOOL bCreateDir = [fileManager createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
            if(!bCreateDir){
                NSLog(@"创建文件夹失败!%@",path);
            }
            NSLog(@"创建文件夹成功,文件路径%@",path);
        }
        
        NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
        [formatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"zh_CN"]];
        [formatter setDateFormat:@"yyyyMMddHHmmss"]; //每次启动后都保存一个新的文件中
        NSString *dateStr = [formatter stringFromDate:[NSDate date]];
        
        NSString *resultPath = [path stringByAppendingFormat:@"/%@.mp4",dateStr];
        NSLog(@"resultFileName = %@",dateStr);

        AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:avAsset
                                                                               presetName:[self getAVAssetExportPresetQuality:exportQuality]];
        exportSession.outputURL = [NSURL fileURLWithPath:resultPath];
        exportSession.outputFileType = AVFileTypeMPEG4;
        exportSession.shouldOptimizeForNetworkUse = YES;
        
        [exportSession exportAsynchronouslyWithCompletionHandler:^(void) {
            switch (exportSession.status) {
                case AVAssetExportSessionStatusUnknown: {
                    NSError *error = [NSError errorWithDomain:@"ConvertMovToMp4ErrorDomain"
                                                         code:10001
                                                     userInfo:@{NSLocalizedDescriptionKey:@"AVAssetExportSessionStatusUnknown"}];
                    movEncodeToMpegToolResultBlock(nil, nil , error);
                }
                    break;
                case AVAssetExportSessionStatusWaiting: {
                    NSError *error = [NSError errorWithDomain:@"ConvertMovToMp4ErrorDomain"
                                                         code:10002
                                                     userInfo:@{NSLocalizedDescriptionKey:@"AVAssetExportSessionStatusWaiting"}];
                    movEncodeToMpegToolResultBlock(nil, nil , error);
                }
                    break;
                case AVAssetExportSessionStatusExporting: {
                    NSError *error = [NSError errorWithDomain:@"ConvertMovToMp4ErrorDomain"
                                                         code:10003
                                                     userInfo:@{NSLocalizedDescriptionKey:@"AVAssetExportSessionStatusExporting"}];
                    movEncodeToMpegToolResultBlock(nil, nil , error);
                }
                    break;
                case AVAssetExportSessionStatusCompleted: {
                    NSData *mp4Data = [NSData dataWithContentsOfURL:exportSession.outputURL];
                    movEncodeToMpegToolResultBlock(exportSession.outputURL, mp4Data , nil);
                }
                    break;
                case AVAssetExportSessionStatusFailed: {
                    NSError *error = [NSError errorWithDomain:@"ConvertMovToMp4ErrorDomain"
                                                         code:10005
                                                     userInfo:@{NSLocalizedDescriptionKey:@"AVAssetExportSessionStatusFailed"}];
                    movEncodeToMpegToolResultBlock(nil, nil , error);
                }
                    break;
                case AVAssetExportSessionStatusCancelled: {
                    NSError *error = [NSError errorWithDomain:@"ConvertMovToMp4ErrorDomain"
                                                         code:10006
                                                     userInfo:@{NSLocalizedDescriptionKey:@"AVAssetExportSessionStatusCancelled"}];
                    movEncodeToMpegToolResultBlock(nil, nil , error);
                }
                    break;
            }
        }];
    }
    else{
        NSError *error = [NSError errorWithDomain:@"ConvertMovToMp4ErrorDomain"
                                             code:10007
                                         userInfo:@{NSLocalizedDescriptionKey:@"AVAssetExportSessionStatusNoPreset"}];
        movEncodeToMpegToolResultBlock(nil, nil , error);
    }
}

自定义转码预设枚举类型,用以映射系统预设

/*
AVAssetExportPresetLowQuality        低质量 可以通过移动网络分享
AVAssetExportPresetMediumQuality     中等质量 可以通过WIFI网络分享
AVAssetExportPresetHighestQuality    高等质量
AVAssetExportPreset640x480
AVAssetExportPreset960x540
AVAssetExportPreset1280x720    720pHD
AVAssetExportPreset1920x1080   1080pHD
AVAssetExportPreset3840x2160
*/

+ (NSString *const )getAVAssetExportPresetQuality:(ExportPresetQuality)exportPreset {
    switch (exportPreset) {
        case ExportPresetLowQuality:
            return AVAssetExportPresetLowQuality;
        case ExportPresetMediumQuality:
            return AVAssetExportPresetMediumQuality;
        case ExportPresetHighestQuality:
            return AVAssetExportPresetHighestQuality;
        case ExportPreset640x480:
            return AVAssetExportPreset640x480;
        case ExportPreset960x540:
            return AVAssetExportPreset960x540;
        case ExportPreset1280x720:
            return AVAssetExportPreset1280x720;
        case ExportPreset1920x1080:
            return AVAssetExportPreset1920x1080;
        case ExportPreset3840x2160:
            return AVAssetExportPreset3840x2160;
    }
}

请注意

转码方法最好不要在模拟器上执行,哪怕你模拟器相册导入了视频也不要,因为

[AVAssetExportSession exportPresetsCompatibleWithAsset:avAsset];

有概率 无 反 应 !!!!

这句话是其他帖子没写过的,可能他们没有在模拟器执行过,模拟器是可以导入视频和图片的,直接拖进去就OK。

建议真机运行!
建议真机运行!
建议真机运行!

2020年儿童节更新。
目前本文Demo已经迁移至《TZImagePickerControllerDemo

相关链接

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,980评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,422评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,130评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,553评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,408评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,326评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,720评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,373评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,678评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,722评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,486评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,335评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,738评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,283评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,692评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,893评论 2 335