iOS 视频转码处理

简介: 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,均来自三方库**KJExtensionHandler**,如有需要的朋友可自行pod 'KJExtensionHandler'引入即可


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

相关文章
|
4月前
|
图形学 Android开发 iOS开发
穿越数字洪流,揭秘Unity3d中的视频魔法!Windows、Android和iOS如何征服RTSP与RTMP的终极指南!
【8月更文挑战第15天】在数字媒体的海洋中,实时视频流是连接世界的桥梁。对于那些渴望在Unity3d中搭建这座桥梁的开发者来说,本文将揭示如何在Windows、Android和iOS平台上征服RTSP与RTMP的秘密。我们将深入探讨这两种协议的特性,以及在不同平台上实现流畅播放的技巧。无论你是追求稳定性的RTSP拥趸,还是低延迟的RTMP忠实粉丝,这里都有你需要的答案。让我们一起穿越数字洪流,探索Unity3d中视频魔法的世界吧!
78 2
|
视频直播 API iOS开发
微信团队分享:详解iOS版微信视频号直播中因帧率异常导致的功耗问题
功耗优化一直是 app 性能优化中让人头疼的问题,尤其是在直播这种用户观看时长特别久的场景。怎样能在不影响主体验的前提下,进一步优化微信iOS端视频号直播的功耗占用,本文给出了一个不太一样的答案。
178 0
|
JavaScript Android开发 iOS开发
layui框架实战案例(6):上传图片和视频自动调用IOS或安卓系统的摄像头功能
layui框架实战案例(6):上传图片和视频自动调用IOS或安卓系统的摄像头功能
534 0
|
iOS开发
iOS MachineLearning 系列(6)—— 视频中的物体轨迹分析
轨迹分析是比物体追踪更上层的一种应用。Vision框架中提供了检测视频中多个物体的运动轨迹等能力,在健身,体育类应用中非常有用。
158 0
|
算法 API vr&ar
iOS MachineLearning 系列(5)—— 视频中的物体运动追踪
本系列的前面几篇文章中,我们将静态图片分析相关的API做了详尽的介绍。在Vision框架中,还提供了视频中物体追踪的能力。 仔细想来,其实视频的分析和静态图片的分析本质上并无太大的区别,我们可以将视频拆解成图片帧,之后再对图片进行静态分析。将所有图片帧的分析结果反馈到视频上,即实现了对视频的分析能力。 视频中物体运动的跟踪常在一些AR游戏中应用,这些现实增强类的应用常常需要实时追踪显示中的物体。
210 0
|
iOS开发
iOS视频镜像处理
在网上找了很多资料和dome都没有处理镜像的,于是就自己研究了下。在官方说明文档中找到了
342 0
iOS视频镜像处理
|
编解码 iOS开发 内存技术
iOS 录音、音频的拼接剪切以及边录边压缩转码
iOS 录音、音频的拼接剪切以及边录边压缩转码
849 0
iOS 录音、音频的拼接剪切以及边录边压缩转码
|
编解码 iOS开发
iOS拍摄视频,压缩并上传服务器
iOS拍摄视频,压缩并上传服务器
486 0
|
存储 iOS开发
iOS wkwebview嵌入优酷视频,显示“请允许cookie存储”解决方法
iOS wkwebview嵌入优酷视频,显示“请允许cookie存储”解决方法
620 0
|
iOS开发
iOS 只有视频横屏解决方法
iOS 只有视频横屏解决方法
219 0