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