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'引入即可

視頻轉碼處理介紹就到此完畢,後面有相關再補充,寫文章不容易,還請點個小星星傳送門

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章