最近做視頻壓縮上傳功能 剛開始用的蘋果已經(jīng)簡單封裝好的 AVAssetExportSession 框架 視頻壓縮上傳后 發(fā)現(xiàn)有些視頻壓縮后 反而變大了
后來參考了近期視頻優(yōu)化做的比較不錯的douyin 發(fā)現(xiàn)不論上傳多大的視頻 被douyin壓縮以后比特率(視頻每秒傳輸?shù)拇笮?單位:比特率(bps) / 千比特率(kbps))基本都維持在1500 kbps左右 經(jīng)測試 這是對視頻壓縮后 文件大小 影響最大的一個參數(shù)
另外 還有比如幀率 視頻寬高 等參數(shù) 也對視頻壓縮有一定的影響
用此方法 在項目中實測 基本可以滿足絕大多數(shù)需求
如果你僅僅是想完成需求 不想研究代碼的話
到這里可以結(jié)束閱讀了
直接翻到最下面下載Demo 復(fù)制我封裝好的方法 調(diào)用 就行了
繼續(xù)往下看的話:
代碼用到了訪問相機 訪問相冊 訪問麥克風(fēng) 添加視頻到相冊四個權(quán)限
使用前 先把info中相機相冊對應(yīng)權(quán)限打開
info.plist 用Source Code方式打開的話 這樣寫:
<key>NSCameraUsageDescription</key>
<string>是否允許訪問相機</string>
<key>NSMicrophoneUsageDescription</key>
<string>App需要您的同意才能訪問麥克風(fēng)</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>是否允許訪問相冊</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>是否允許添加視頻或圖片到相冊</string>
info.plist 用Property List方式打開的話 這樣配置:

配置好了 開始寫代碼:
ViewController.m
使用系統(tǒng)的UIImagePickerController選擇或者拍攝對應(yīng)的視頻
使用前 先遵守<UIImagePickerControllerDelegate, UINavigationControllerDelegate>這倆協(xié)議
然后寫這個
@property (nonatomic, strong) UIImagePickerController *imagePicker;
從相冊中選擇視頻 代碼注釋寫的比較詳細(xì)了
//從相冊中選擇視頻
- (IBAction)chooseVideoBtnClick:(id)sender {
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeSavedPhotosAlbum])
{
self.imagePicker = [[UIImagePickerController alloc] init];
self.imagePicker.delegate = self;
/*
* 設(shè)置資源文件來源 圖庫 相機 相冊
* UIImagePickerControllerSourceTypePhotoLibrary,
* UIImagePickerControllerSourceTypeCamera,
* UIImagePickerControllerSourceTypeSavedPhotosAlbum
**/
self.imagePicker.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;
/*
* 設(shè)置媒體類型
* (NSString *)kUTTypeVideo 無聲視頻
* (NSString *)kUTTypeMovie 有聲視頻
* (NSString *)kUTTypeAudio 音頻
* 等...
**/
[self.imagePicker setMediaTypes:@[(NSString *)kUTTypeMovie]];
self.imagePicker.videoQuality = UIImagePickerControllerQualityTypeHigh;
[self presentViewController:self.imagePicker animated:YES completion:nil];
}else{
[self showAlertViewWithTitle:@"相機不可用" message:@"" withCancelButtonTitle:@"知道了"];
}
}
相機拍攝視頻
//拍攝視頻
- (IBAction)recordVideoBtnClick:(id)sender {
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])
{
self.imagePicker = [[UIImagePickerController alloc] init];
self.imagePicker.delegate = self;
/*
* 設(shè)置資源文件來源 圖庫 相機 相冊
* UIImagePickerControllerSourceTypePhotoLibrary,
* UIImagePickerControllerSourceTypeCamera,
* UIImagePickerControllerSourceTypeSavedPhotosAlbum
**/
self.imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
/*
* 設(shè)置媒體類型
* (NSString *)kUTTypeVideo 無聲視頻
* (NSString *)kUTTypeMovie 有聲視頻
* (NSString *)kUTTypeAudio 音頻
* 等...
**/
[self.imagePicker setMediaTypes:@[(NSString *)kUTTypeMovie]];
self.imagePicker.videoQuality = UIImagePickerControllerQualityTypeHigh;
[self presentViewController:self.imagePicker animated:YES completion:nil];
}else{
[self showAlertViewWithTitle:@"相機不可用" message:@"" withCancelButtonTitle:@"知道了"];
}
}
取消選擇或者拍攝時的回調(diào)
UIImagePickerControllerDelegate
-(void)imagePickerControllerDidCancel:(UIImagePickerController *)picker{
[self.imagePicker dismissViewControllerAnimated:YES completion:nil];
[self showAlertViewWithTitle:@"用戶取消操作" message:@"" withCancelButtonTitle:@"好的"];
}
視頻選擇 或者拍攝好以后回調(diào)
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey,id> *)info{
//獲取媒體類型
NSString *type = [info objectForKey:UIImagePickerControllerMediaType];
//媒體類型是視頻時
if ([type isEqualToString:@"public.movie"])
{
NSLog(@"===video URL = %@===", [info objectForKey:UIImagePickerControllerMediaURL]);
//視頻路徑URL
NSURL *outputUrl = [info objectForKey:UIImagePickerControllerMediaURL];
//關(guān)閉相冊界面
[picker dismissViewControllerAnimated:YES completion:^{
//執(zhí)行視頻壓縮功能
[self compressVideoWithVideoUrl:outputUrl];
}];
}
}
//壓縮視頻
/*
* 自定義視頻壓縮
* videoUrl 原視頻url路徑 必傳
* outputBiteRate 壓縮視頻至指定比特率(bps) 可傳nil 默認(rèn)1500kbps
* outputFrameRate 壓縮視頻至指定幀率 可傳nil 默認(rèn)30fps
* outputWidth 壓縮視頻至指定寬度 可傳nil 默認(rèn)960
* outputWidth 壓縮視頻至指定高度 可傳nil 默認(rèn)540
* compressComplete 壓縮后的視頻信息回調(diào) (id responseObjc) 可自行打印查看
**/
- (void)compressVideoWithVideoUrl:(NSURL *)outputUrl{
[VideoCompress compressVideoWithVideoUrl:outputUrl withBiteRate:@(1500 * 1024) withFrameRate:@(30) withVideoWidth:@(960) withVideoHeight:@(540) compressComplete:^(id responseObjc) {
NSString *filePathStr = [responseObjc objectForKey:@"urlStr"];
AVURLAsset *asset = [AVURLAsset assetWithURL:[NSURL fileURLWithPath:filePathStr]];
AVAssetTrack *videoTrack = [asset tracksWithMediaType:AVMediaTypeVideo].firstObject;
//視頻大小 MB
unsigned long long fileSize = [[NSFileManager defaultManager] attributesOfItemAtPath:filePathStr error:nil].fileSize;
float fileSizeMB = fileSize / (1024.0*1024.0);
//視頻寬高
NSInteger videoWidth = videoTrack.naturalSize.width;
NSInteger videoHeight = videoTrack.naturalSize.height;
//比特率
NSInteger kbps = videoTrack.estimatedDataRate / 1024;
//幀率
NSInteger frameRate = [videoTrack nominalFrameRate];
NSLog(@"\nfileSize after compress = %.2f MB,\n videoWidth = %ld,\n videoHeight = %ld,\n video bitRate = %ld\n, video frameRate = %ld", fileSizeMB, videoWidth, videoHeight, kbps, frameRate);
// NSData *videoData = [NSData dataWithContentsOfFile:filePathStr];
// NSData *videoData = [NSData dataWithContentsOfURL:asset.URL];
//在這里上傳或者保存已經(jīng)處理好的視頻文件
//保存視頻至相冊
UISaveVideoAtPathToSavedPhotosAlbum(filePathStr, self, @selector(videoSavedToPhotosAlbum:didFinishSavingWithError:contextInfo:), nil);
}];
}
取消事件的一些彈框
- (void)showAlertViewWithTitle:(NSString*)title message:(NSString*)msg withCancelButtonTitle:(NSString *)cancelButtonTitle{
UIAlertController* alertController = [UIAlertController alertControllerWithTitle:title message:@"" preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:cancelButtonTitle style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
}]];
[self presentViewController:alertController animated:YES completion:nil];
}
//視頻保存成功提示框
#pragma mark 保存視頻后的回調(diào)
- (void)videoSavedToPhotosAlbum:(NSString *)videoUrlStr didFinishSavingWithError:(NSError*)error contextInfo:(id)contextInfo{
NSString*message =@"提示";
if(!error) {
message = @"視頻成功保存到相冊";
}else{
message = [error description];
}
[self showAlertViewWithTitle:@"提示" message:message withCancelButtonTitle:@"確定"];
}
VideoCompress.m
/*
* 自定義視頻壓縮
* videoUrl 原視頻url路徑 必傳
* outputBiteRate 壓縮視頻至指定比特率(bps) 可傳nil 默認(rèn)1500kbps
* outputFrameRate 壓縮視頻至指定幀率 可傳nil 默認(rèn)30fps
* outputWidth 壓縮視頻至指定寬度 可傳nil 默認(rèn)960
* outputWidth 壓縮視頻至指定高度 可傳nil 默認(rèn)540
* compressComplete 壓縮后的視頻信息回調(diào) (id responseObjc) 可自行打印查看
**/
+ (void)compressVideoWithVideoUrl:(NSURL *)videoUrl withBiteRate:(NSNumber * _Nullable)outputBiteRate withFrameRate:(NSNumber * _Nullable)outputFrameRate withVideoWidth:(NSNumber * _Nullable)outputWidth withVideoHeight:(NSNumber * _Nullable)outputHeight compressComplete:(void(^)(id responseObjc))compressComplete{
if (!videoUrl) {
[SVProgressHUD showErrorWithStatus:@"視頻路徑不能為空"];
return;
}
NSLog(@"===videoUrl.abs = %@, videoUrl.path = %@", videoUrl.absoluteString, videoUrl.path);
NSInteger compressBiteRate = outputBiteRate ? [outputBiteRate integerValue] : 1500 * 1024;
NSInteger compressFrameRate = outputFrameRate ? [outputFrameRate integerValue] : 30;
NSInteger compressWidth = outputWidth ? [outputWidth integerValue] : 960;
NSInteger compressHeight = outputHeight ? [outputHeight integerValue] : 540;
//取出原視頻詳細(xì)資料
AVURLAsset *asset = [AVURLAsset assetWithURL:videoUrl];
//視頻時長 S
CMTime time = [asset duration];
NSInteger seconds = ceil(time.value/time.timescale);
if (seconds < 3) {
[SVProgressHUD showErrorWithStatus:@"請上傳3秒以上的視頻"];
return;
}
//壓縮前原視頻大小MB
unsigned long long fileSize = [[NSFileManager defaultManager] attributesOfItemAtPath:videoUrl.path error:nil].fileSize;
float fileSizeMB = fileSize / (1024.0*1024.0);
//取出asset中的視頻文件
AVAssetTrack *videoTrack = [asset tracksWithMediaType:AVMediaTypeVideo].firstObject;
//壓縮前原視頻寬高
NSInteger videoWidth = videoTrack.naturalSize.width;
NSInteger videoHeight = videoTrack.naturalSize.height;
//壓縮前原視頻比特率
NSInteger kbps = videoTrack.estimatedDataRate / 1024;
//壓縮前原視頻幀率
NSInteger frameRate = [videoTrack nominalFrameRate];
NSLog(@"\noriginalVideo\nfileSize = %.2f MB,\n videoWidth = %ld,\n videoHeight = %ld,\n video bitRate = %ld\n, video frameRate = %ld", fileSizeMB, videoWidth, videoHeight, kbps, frameRate);
NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithDictionary:@{@"urlStr" : videoUrl.path}];
//原視頻比特率小于指定比特率 不壓縮 返回原視頻
if (kbps <= (compressBiteRate / 1024)) {
compressComplete(dic);
return;
}
//指定壓縮視頻沙盒根目錄
NSString *cachesDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
//添加文件完整路徑
NSString *outputUrlStr = [[cachesDir stringByAppendingPathComponent:@"videoTest"] stringByAppendingPathExtension:@"mp4"];
NSLog(@"===壓縮視頻存放的指定路徑%@===", outputUrlStr);
//如果指定路徑下已存在其他文件 先移除指定文件
if ([[NSFileManager defaultManager] fileExistsAtPath:outputUrlStr]) {
BOOL removeSuccess = [[NSFileManager defaultManager] removeItemAtPath:outputUrlStr error:nil];
if (!removeSuccess) {
[SVProgressHUD showErrorWithStatus:@"舊文件移除失敗"];
return;
}
}
//創(chuàng)建視頻文件讀取者
AVAssetReader *reader = [AVAssetReader assetReaderWithAsset:asset error:nil];
//從指定文件讀取視頻
AVAssetReaderTrackOutput *videoOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:videoTrack outputSettings:[VideoCompress configVideoOutput]];
//取出原視頻中音頻詳細(xì)資料
AVAssetTrack *audioTrack = [asset tracksWithMediaType:AVMediaTypeAudio].firstObject;
//從音頻資料中讀取音頻
AVAssetReaderTrackOutput *audioOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:audioTrack outputSettings:[VideoCompress configAudioOutput]];
//將讀取到的視頻信息添加到讀者隊列中
if ([reader canAddOutput:videoOutput]) {
[reader addOutput:videoOutput];
}
//將讀取到的音頻信息添加到讀者隊列中
if ([reader canAddOutput:audioOutput]) {
[reader addOutput:audioOutput];
}
//視頻文件寫入者
AVAssetWriter *writer = [AVAssetWriter assetWriterWithURL:[NSURL fileURLWithPath:outputUrlStr] fileType:AVFileTypeMPEG4 error:nil];
//根據(jù)指定配置創(chuàng)建寫入的視頻文件
AVAssetWriterInput *videoInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:[VideoCompress videoCompressSettingsWithBitRate:compressBiteRate withFrameRate:compressFrameRate withWidth:compressWidth WithHeight:compressHeight withOriginalWidth:videoWidth withOriginalHeight:videoHeight]];
//根據(jù)指定配置創(chuàng)建寫入的音頻文件
AVAssetWriterInput *audioInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio outputSettings:[VideoCompress audioCompressSettings]];
if ([writer canAddInput:videoInput]) {
[writer addInput:videoInput];
NSLog(@"videoInput==========videoInput");
}
if ([writer canAddInput:audioInput]) {
[writer addInput:audioInput];
NSLog(@"audioInput==========audioInput");
}
[SVProgressHUD showWithStatus:@"視頻壓縮中..."];
[reader startReading];
[writer startWriting];
[writer startSessionAtSourceTime:kCMTimeZero];
//創(chuàng)建視頻寫入隊列
dispatch_queue_t videoQueue = dispatch_queue_create("Video Queue", DISPATCH_QUEUE_SERIAL);
//創(chuàng)建音頻寫入隊列
dispatch_queue_t audioQueue = dispatch_queue_create("Audio Queue", DISPATCH_QUEUE_SERIAL);
//創(chuàng)建一個線程組
dispatch_group_t group = dispatch_group_create();
//進入線程組
dispatch_group_enter(group);
//隊列準(zhǔn)備好后 usingBlock
[videoInput requestMediaDataWhenReadyOnQueue:videoQueue usingBlock:^{
BOOL completedOrFailed = NO;
while ([videoInput isReadyForMoreMediaData] && !completedOrFailed) {
CMSampleBufferRef sampleBuffer = [videoOutput copyNextSampleBuffer];
if (sampleBuffer != NULL) {
[videoInput appendSampleBuffer:sampleBuffer];
NSLog(@"===%@===", sampleBuffer);
CFRelease(sampleBuffer);
} else {
completedOrFailed = YES;
[videoInput markAsFinished];
dispatch_group_leave(group);
}
}
}];
dispatch_group_enter(group);
//隊列準(zhǔn)備好后 usingBlock
[audioInput requestMediaDataWhenReadyOnQueue:audioQueue usingBlock:^{
BOOL completedOrFailed = NO;
while ([audioInput isReadyForMoreMediaData] && !completedOrFailed) {
CMSampleBufferRef sampleBuffer = [audioOutput copyNextSampleBuffer];
if (sampleBuffer != NULL) {
BOOL success = [audioInput appendSampleBuffer:sampleBuffer];
NSLog(@"===%@===", sampleBuffer);
CFRelease(sampleBuffer);
completedOrFailed = !success;
} else {
completedOrFailed = YES;
}
}
if (completedOrFailed) {
[audioInput markAsFinished];
dispatch_group_leave(group);
}
}];
//完成壓縮
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
if ([reader status] == AVAssetReaderStatusReading) {
[reader cancelReading];
}
switch (writer.status) {
case AVAssetWriterStatusWriting:
{
[SVProgressHUD showSuccessWithStatus:@"視頻壓縮完成"];
[writer finishWritingWithCompletionHandler:^{
[dic setObject:outputUrlStr forKey:@"urlStr"];
compressComplete(dic);
}];
}
break;
case AVAssetWriterStatusCancelled:
[SVProgressHUD showInfoWithStatus:@"取消壓縮"];
break;
case AVAssetWriterStatusFailed:
NSLog(@"===error:%@===", writer.error);
[SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@",writer.error]];
break;
case AVAssetWriterStatusCompleted:
{
[SVProgressHUD showSuccessWithStatus:@"視頻壓縮完成"];
[writer finishWritingWithCompletionHandler:^{
[dic setObject:outputUrlStr forKey:@"urlStr"];
compressComplete(dic);
}];
}
break;
default:
break;
}
});
}
視頻壓縮的參數(shù)配置
+ (NSDictionary *)videoCompressSettingsWithBitRate:(NSInteger)biteRate withFrameRate:(NSInteger)frameRate withWidth:(NSInteger)width WithHeight:(NSInteger)height withOriginalWidth:(NSInteger)originalWidth withOriginalHeight:(NSInteger)originalHeight{
/*
* AVVideoAverageBitRateKey: 比特率(碼率)每秒傳輸?shù)奈募笮?kbps
* AVVideoExpectedSourceFrameRateKey:幀率 每秒播放的幀數(shù)
* AVVideoProfileLevelKey:畫質(zhì)水平
BP-Baseline Profile:基本畫質(zhì)。支持I/P 幀,只支持無交錯(Progressive)和CAVLC;
EP-Extended profile:進階畫質(zhì)。支持I/P/B/SP/SI 幀,只支持無交錯(Progressive)和CAVLC;
MP-Main profile:主流畫質(zhì)。提供I/P/B 幀,支持無交錯(Progressive)和交錯(Interlaced),也支持CAVLC 和CABAC 的支持;
HP-High profile:高級畫質(zhì)。在main Profile 的基礎(chǔ)上增加了8×8內(nèi)部預(yù)測、自定義量化、 無損視頻編碼和更多的YUV 格式;
**/
NSInteger returnWidth = originalWidth > originalHeight ? width : height;
NSInteger returnHeight = originalWidth > originalHeight ? height : width;
NSDictionary *compressProperties = @{
AVVideoAverageBitRateKey : @(biteRate),
AVVideoExpectedSourceFrameRateKey : @(frameRate),
AVVideoProfileLevelKey : AVVideoProfileLevelH264HighAutoLevel
};
if (@available(iOS 11.0, *)) {
NSDictionary *compressSetting = @{
AVVideoCodecKey : AVVideoCodecTypeH264,
AVVideoWidthKey : @(returnWidth),
AVVideoHeightKey : @(returnHeight),
AVVideoCompressionPropertiesKey : compressProperties,
AVVideoScalingModeKey : AVVideoScalingModeResizeAspectFill
};
return compressSetting;
}else {
NSDictionary *compressSetting = @{
AVVideoCodecKey : AVVideoCodecTypeH264,
AVVideoWidthKey : @(returnWidth),
AVVideoHeightKey : @(returnHeight),
AVVideoCompressionPropertiesKey : compressProperties,
AVVideoScalingModeKey : AVVideoScalingModeResizeAspectFill
};
return compressSetting;
}
}
音頻壓縮的參數(shù)配置
//音頻設(shè)置
+ (NSDictionary *)audioCompressSettings{
AudioChannelLayout stereoChannelLayout = {
.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo,
.mChannelBitmap = kAudioChannelBit_Left,
.mNumberChannelDescriptions = 0,
};
NSData *channelLayoutAsData = [NSData dataWithBytes:&stereoChannelLayout length:offsetof(AudioChannelLayout, mChannelDescriptions)];
NSDictionary *audioCompressSettings = @{
AVFormatIDKey : @(kAudioFormatMPEG4AAC),
AVEncoderBitRateKey : @(128000),
AVSampleRateKey : @(44100),
AVNumberOfChannelsKey : @(2),
AVChannelLayoutKey : channelLayoutAsData
};
return audioCompressSettings;
}
讀取音頻參數(shù)配置
/** 音頻解碼 */
+ (NSDictionary *)configAudioOutput
{
NSDictionary *audioOutputSetting = @{
AVFormatIDKey: @(kAudioFormatLinearPCM)
};
return audioOutputSetting;
}
讀取視頻參數(shù)配置
/** 視頻解碼 */
+ (NSDictionary *)configVideoOutput
{
NSDictionary *videoOutputSetting = @{
(__bridge NSString *)kCVPixelBufferPixelFormatTypeKey:[NSNumber numberWithUnsignedInt:kCVPixelFormatType_422YpCbCr8],
(__bridge NSString *)kCVPixelBufferIOSurfacePropertiesKey:[NSDictionary dictionary]
};
return videoOutputSetting;
}