前言
苹果手机录制的视频在非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