iOS 视频转码处理

前言

苹果手机录制的视频在非Safari浏览器和安卓机器上面都无法直接播放,原因是因为直接录制的视频默认是mov格式,这是需要转换一下格式来处理
其中包含多种转码方式
[KJVideoFileTypeMov] = @".mov",
[KJVideoFileTypeMp4] = @".mp4",
[KJVideoFileTypeWav] = @".wav",
[KJVideoFileTypeM4v] = @".m4v",
[KJVideoFileTypeM4a] = @".m4a",
[KJVideoFileTypeCaf] = @".caf",
[KJVideoFileTypeAif] = @".aif",
[KJVideoFileTypeMp3] = @".mp3",

这里提供一套视频转码的工具

简单调用

/// 视频格式转换处理
+ (void)kj_videoConvertEncodeInfo:(KJVideoEncodeInfo*(^)(KJVideoEncodeInfo*info))infoblock Block:(kVideoEncodeBlock)block;

KJVideoEncodeInfo传入相对应的参数即可

简单直接上代码

#import <Foundation/Foundation.h>
#import <Photos/Photos.h>
typedef NS_ENUM(NSInteger, KJExportPresetQualityType) {
    KJExportPresetQualityTypeLowQuality = 0, //低质量 可以通过移动网络分享
    KJExportPresetQualityTypeMediumQuality,  //中等质量 可以通过WIFI网络分享
    KJExportPresetQualityTypeHighestQuality, //高等质量
    KJExportPresetQualityType640x480,
    KJExportPresetQualityType960x540,
    KJExportPresetQualityType1280x720,
    KJExportPresetQualityType1920x1080,
    KJExportPresetQualityType3840x2160,
};
typedef NS_ENUM(NSInteger, KJVideoFileType) {
    KJVideoFileTypeMov = 0, /// .mov
    KJVideoFileTypeMp4, /// .mp4
    KJVideoFileTypeWav,
    KJVideoFileTypeM4v,
    KJVideoFileTypeM4a,
    KJVideoFileTypeCaf,
    KJVideoFileTypeAif,
    KJVideoFileTypeMp3,
};
static NSString * const _Nonnull KJVideoFileTypeStringMap[] = {
    [KJVideoFileTypeMov] = @".mov",
    [KJVideoFileTypeMp4] = @".mp4",
    [KJVideoFileTypeWav] = @".wav",
    [KJVideoFileTypeM4v] = @".m4v",
    [KJVideoFileTypeM4a] = @".m4a",
    [KJVideoFileTypeCaf] = @".caf",
    [KJVideoFileTypeAif] = @".aif",
    [KJVideoFileTypeMp3] = @".mp3",
};
typedef void(^kVideoEncodeBlock)(NSURL * _Nullable mp4URL, NSError * _Nullable error);
NS_ASSUME_NONNULL_BEGIN
@interface KJVideoEncodeInfo : NSObject
@property(nonatomic,strong)NSString *videoName;
@property(nonatomic,strong)NSString *videoPath;
@property(nonatomic,assign)KJExportPresetQualityType qualityType;
@property(nonatomic,assign)KJVideoFileType videoType; /// 视频转换格式,默认.mp4
/// URL、PHAsset、AVURLAsset互斥三者选其一
@property(nonatomic,strong)PHAsset *PHAsset;
@property(nonatomic,strong)NSURL *URL;
@property(nonatomic,strong)AVURLAsset *AVURLAsset;
/// 获取导出质量
+ (NSString * const)getAVAssetExportPresetQuality:(KJExportPresetQualityType)qualityType;
/// 获取导出格式
+ (NSString * const)getVideoFileType:(KJVideoFileType)videoType;
@end
@interface KJVideoEncodeTool : NSObject
/// 视频格式转换处理
+ (void)kj_videoConvertEncodeInfo:(KJVideoEncodeInfo*(^)(KJVideoEncodeInfo*info))infoblock Block:(kVideoEncodeBlock)block;
@end

NS_ASSUME_NONNULL_END
#import "KJVideoEncodeTool.h"
@implementation KJVideoEncodeInfo
- (instancetype)init{
    if (self==[super init]) {
        self.qualityType = KJExportPresetQualityTypeHighestQuality;
        self.videoType = KJVideoFileTypeMp4;
    }
    return self;
}
/// 获取导出质量
+ (NSString * const)getAVAssetExportPresetQuality:(KJExportPresetQualityType)qualityType{
    switch (qualityType) {
        case KJExportPresetQualityTypeLowQuality:return AVAssetExportPresetLowQuality;
        case KJExportPresetQualityTypeMediumQuality:return AVAssetExportPresetMediumQuality;
        case KJExportPresetQualityTypeHighestQuality:return AVAssetExportPresetHighestQuality;
        case KJExportPresetQualityType640x480:return AVAssetExportPreset640x480;
        case KJExportPresetQualityType960x540:return AVAssetExportPreset960x540;
        case KJExportPresetQualityType1280x720:return AVAssetExportPreset1280x720;
        case KJExportPresetQualityType1920x1080:return AVAssetExportPreset1920x1080;
        case KJExportPresetQualityType3840x2160:return AVAssetExportPreset3840x2160;
    }
}
/// 获取导出格式
+ (NSString * const)getVideoFileType:(KJVideoFileType)videoType{
    switch (videoType) {
        case KJVideoFileTypeMov:return AVFileTypeQuickTimeMovie;
        case KJVideoFileTypeMp4:return AVFileTypeMPEG4;
        case KJVideoFileTypeWav:return AVFileTypeWAVE;
        case KJVideoFileTypeM4v:return AVFileTypeAppleM4V;
        case KJVideoFileTypeM4a:return AVFileTypeAppleM4A;
        case KJVideoFileTypeCaf:return AVFileTypeCoreAudioFormat;
        case KJVideoFileTypeAif:return AVFileTypeAIFF;
        case KJVideoFileTypeMp3:return AVFileTypeMPEGLayer3;
    }
}
@end
@implementation KJVideoEncodeTool
+ (void)kj_videoConvertEncodeInfo:(KJVideoEncodeInfo*(^)(KJVideoEncodeInfo*))infoblock Block:(kVideoEncodeBlock)block{
    KJVideoEncodeInfo *info = [KJVideoEncodeInfo new];
    if (infoblock) info = infoblock(info);
    __block NSString *presetName = [KJVideoEncodeInfo getAVAssetExportPresetQuality:info.qualityType];
    __block NSString *videoType  = [KJVideoEncodeInfo getVideoFileType:info.videoType];
    NSString *suffix = KJVideoFileTypeStringMap[info.videoType];
    if (![info.videoName hasSuffix:suffix]) {
        info.videoName = [info.videoName stringByAppendingString:suffix];
    }
    if (info.URL == nil && info.PHAsset == nil && info.AVURLAsset == nil) {
        NSError *error = [self kj_setErrorCode:10008 DescriptionKey:@"Lack of material"];
        block(nil, error);
    }else if (info.URL) {
        AVURLAsset *avAsset = [AVURLAsset URLAssetWithURL:info.URL options:nil];
        [self convertAVURLAsset:avAsset Name:info.videoName Path:info.videoPath FileType:videoType PresetName:presetName Block:block];
    }else if (info.AVURLAsset) {
        [self convertAVURLAsset:info.AVURLAsset Name:info.videoName Path:info.videoPath FileType:videoType PresetName:presetName Block:block];
    }else if (info.PHAsset) {
        __block NSString *name = info.videoName;
        __block NSString *path = info.videoPath;
        PHVideoRequestOptions *options = [[PHVideoRequestOptions alloc] init];
        options.version = PHImageRequestOptionsVersionCurrent;
        options.deliveryMode = PHVideoRequestOptionsDeliveryModeAutomatic;
        options.networkAccessAllowed = true;
        PHImageManager *manager = [PHImageManager defaultManager];
        [manager requestAVAssetForVideo:info.PHAsset options:options resultHandler:^(AVAsset *asset, AVAudioMix *audioMix, NSDictionary *info) {
            if ([asset isKindOfClass:[AVURLAsset class]]) {
                [self convertAVURLAsset:(AVURLAsset*)asset Name:name Path:path FileType:videoType PresetName:presetName Block:block];
            }else{
                NSError *error = [self kj_setErrorCode:10008 DescriptionKey:@"PHAsset error"];
                block(nil, error);
            }
        }];
    }
}
#pragma mark - MOV转码MP4
+ (void)convertAVURLAsset:(AVURLAsset*)asset Name:(NSString*)name Path:(NSString*)path FileType:(NSString*)fileType PresetName:(NSString*)presetName Block:(kVideoEncodeBlock)block{
    AVURLAsset *avAsset = [AVURLAsset URLAssetWithURL:asset.URL options:nil];
    NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:avAsset];
    if ([compatiblePresets containsObject:presetName]) {
        NSFileManager *fileManager = [NSFileManager defaultManager];
        BOOL isDirectory = FALSE;
        BOOL isDirExist  = [fileManager fileExistsAtPath:path isDirectory:&isDirectory];
        if(!(isDirExist && isDirectory)) [fileManager createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
        NSString *resultPath = path;
        if (![path hasSuffix:name]) resultPath = [path stringByAppendingFormat:@"/%@",name];
        AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:avAsset presetName:presetName];
        exportSession.outputURL = [NSURL fileURLWithPath:resultPath];
        exportSession.outputFileType = fileType;
        exportSession.shouldOptimizeForNetworkUse = YES;
        [exportSession exportAsynchronouslyWithCompletionHandler:^(void) {
            switch (exportSession.status) {
                case AVAssetExportSessionStatusUnknown:
                case AVAssetExportSessionStatusWaiting:
                case AVAssetExportSessionStatusExporting:
                case AVAssetExportSessionStatusFailed:
                case AVAssetExportSessionStatusCancelled:{
                    NSString *key  = [self kj_getDescriptionKey:exportSession.status];
                    NSError *error = [self kj_setErrorCode:exportSession.status+10001 DescriptionKey:key];
                    block(nil,error);
                }
                    break;
                case AVAssetExportSessionStatusCompleted:
                    block(exportSession.outputURL,nil);
                    break;
            }
        }];
    }else{
        NSError *error = [self kj_setErrorCode:10007 DescriptionKey:@"AVAssetExportSessionStatusNoPreset"];
        block(nil, error);
    }
}
+ (NSString*)kj_getDescriptionKey:(AVAssetExportSessionStatus)status{
    switch (status) {
        case AVAssetExportSessionStatusUnknown:return @"AVAssetExportSessionStatusUnknown";
        case AVAssetExportSessionStatusWaiting:return @"AVAssetExportSessionStatusWaiting";
        case AVAssetExportSessionStatusExporting:return @"AVAssetExportSessionStatusExporting";
        case AVAssetExportSessionStatusCompleted:return @"AVAssetExportSessionStatusCompleted";
        case AVAssetExportSessionStatusFailed:return @"AVAssetExportSessionStatusFailed";
        case AVAssetExportSessionStatusCancelled:return @"AVAssetExportSessionStatusCancelled";
    }
}
+ (NSError*)kj_setErrorCode:(NSInteger)code DescriptionKey:(NSString*)key{
    return [NSError errorWithDomain:@"ConvertErrorDomain" code:code userInfo:@{NSLocalizedDescriptionKey:key}];
}
+ (NSDictionary*)getVideoInfo:(PHAsset*)asset{
    PHAssetResource * resource = [[PHAssetResource assetResourcesForAsset: asset] firstObject];
    NSMutableArray *resourceArray = nil;
    if (@available(iOS 13.0, *)) {
        NSString *string1 = [resource.description stringByReplacingOccurrencesOfString:@" - " withString:@" "];
        NSString *string2 = [string1 stringByReplacingOccurrencesOfString:@": " withString:@"="];
        NSString *string3 = [string2 stringByReplacingOccurrencesOfString:@"{" withString:@""];
        NSString *string4 = [string3 stringByReplacingOccurrencesOfString:@"}" withString:@""];
        NSString *string5 = [string4 stringByReplacingOccurrencesOfString:@", " withString:@" "];
        resourceArray = [NSMutableArray arrayWithArray:[string5 componentsSeparatedByString:@" "]];
        [resourceArray removeObjectAtIndex:0];
        [resourceArray removeObjectAtIndex:0];
    }else {
        NSString *string1 = [resource.description stringByReplacingOccurrencesOfString:@"{" withString:@""];
        NSString *string2 = [string1 stringByReplacingOccurrencesOfString:@"}" withString:@""];
        NSString *string3 = [string2 stringByReplacingOccurrencesOfString:@", " withString:@","];
        resourceArray = [NSMutableArray arrayWithArray:[string3 componentsSeparatedByString:@" "]];
        [resourceArray removeObjectAtIndex:0];
        [resourceArray removeObjectAtIndex:0];
    }
    NSMutableDictionary *videoInfo = [[NSMutableDictionary alloc] init];
    for (NSString *string in resourceArray) {
        NSArray *array = [string componentsSeparatedByString:@"="];
        videoInfo[array[0]] = array[1];
    }
    videoInfo[@"duration"] = @(asset.duration).description;
    return videoInfo;
}

@end

使用示例

#import "KJVideoEncodeVC.h"
#import <AssetsLibrary/AssetsLibrary.h>
#import <MobileCoreServices/MobileCoreServices.h>
#import <AVFoundation/AVFoundation.h>
#import "KJVideoEncodeTool.h"
@interface KJVideoEncodeVC ()<UIImagePickerControllerDelegate,UINavigationControllerDelegate>

@end

@implementation KJVideoEncodeVC

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    _weakself;
    UIButton *button = [UIButton buttonWithType:(UIButtonTypeCustom)];
    [button setTitle:@"拍摄视频" forState:(UIControlStateNormal)];
    [button setTitleColor:UIColor.blueColor forState:(UIControlStateNormal)];
    button.frame = CGRectMake(0, 0, 100, 30);
    [self.view addSubview:button];
    button.center = self.view.center;
    [button kj_addAction:^(UIButton * _Nonnull kButton) {
        UIImagePickerController *pickerCon = [[UIImagePickerController alloc]init];
        pickerCon.sourceType = UIImagePickerControllerSourceTypeCamera;
        pickerCon.mediaTypes = @[(NSString*)kUTTypeMovie];//设定相机为视频
        pickerCon.cameraDevice = UIImagePickerControllerCameraDeviceRear;//设置相机后摄像头
        pickerCon.videoMaximumDuration = 5;//最长拍摄时间
        pickerCon.videoQuality = UIImagePickerControllerQualityTypeIFrame1280x720;//拍摄质量
        pickerCon.allowsEditing = NO;//是否可编辑
        pickerCon.delegate = weakself;
        [weakself presentViewController:pickerCon animated:YES completion:nil];
    }];
    
    UIButton *button2 = [UIButton buttonWithType:(UIButtonTypeCustom)];
    [button2 setTitle:@"转换视频" forState:(UIControlStateNormal)];
    [button2 setTitleColor:UIColor.blueColor forState:(UIControlStateNormal)];
    button2.frame = CGRectMake(0, 0, 100, 30);
    [self.view addSubview:button2];
    button2.center = self.view.center;
    button2.centerY += 50;
    [button2 kj_addAction:^(UIButton * _Nonnull kButton) {
        UIImagePickerController *pickerController = [[UIImagePickerController alloc]init];
        pickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
        pickerController.mediaTypes = @[(NSString*)kUTTypeMovie];
        pickerController.allowsEditing = NO;
        pickerController.delegate = weakself;
        [weakself presentViewController:pickerController animated:YES completion:nil];
    }];
}

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info{
    UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];
    [picker dismissViewControllerAnimated:YES completion:nil];
    NSString *mediaType = info[UIImagePickerControllerMediaType];
    if ([mediaType isEqualToString:(NSString*)kUTTypeImage]) {
        if (image!=nil) {
            NSURL *imageURL = [info objectForKey:UIImagePickerControllerReferenceURL];
            NSLog(@"URL:%@",imageURL);
            NSData * imageData = UIImageJPEGRepresentation(image,0.1);
            NSLog(@"压缩到0.1的图片大小:%lu",[imageData length]);
        }
    }else if([mediaType isEqualToString:(NSString*)kUTTypeMovie]){
        NSLog(@"进入视频环节!!!");
        NSURL *URL = info[UIImagePickerControllerMediaURL];
        NSData *file = [NSData dataWithContentsOfURL:URL];
        NSLog(@"输出视频的大小:%lu",(unsigned long)[file length]);
        NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject] stringByAppendingPathComponent:@"Cache/VideoData"];
        NSTimeInterval now = [[NSDate date] timeIntervalSince1970];
        NSDateFormatter * formatter = [[NSDateFormatter alloc] init];
        [formatter setDateFormat:@"yyyyMMddHHmmssSSS"];
        NSDate * NowDate = [NSDate dateWithTimeIntervalSince1970:now];
        NSString * timeStr = [formatter stringFromDate:NowDate];
        [KJVideoEncodeTool kj_videoConvertEncodeInfo:^KJVideoEncodeInfo * _Nonnull(KJVideoEncodeInfo * _Nonnull info) {
            info.URL = URL;
            info.videoName = timeStr;
            info.videoPath = path;
            return info;
        } Block:^(NSURL * _Nullable mp4URL, NSError * _Nullable error) {
            NSLog(@"--%@",mp4URL);
            ALAssetsLibrary *assetLibrary = [[ALAssetsLibrary alloc] init];
            [assetLibrary writeVideoAtPathToSavedPhotosAlbum:mp4URL completionBlock:^(NSURL *assetURL, NSError *error){

            }];
        }];
    }
}

- (void)imagePickerControllerDidCancel:(UIImagePickerController*)picker {
    [picker dismissViewControllerAnimated:YES completion:^{}];
    NSLog(@"视频录制取消了...");
}

@end
备注:本文用到的部分函数方法和Demo,均来自三方库KJCategories,如有需要的朋友可自行pod 'KJCategories'引入即可

视频转码处理介绍就到此完毕,后面有相关再补充,写文章不容易,还请点个小星星传送门

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