iOS AVFoundation自定義視頻壓縮 自定義視頻 比特率 幀率 寬高等

最近做視頻壓縮上傳功能 剛開始用的蘋果已經(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方式打開的話 這樣配置:


info中相冊相機權(quán)限

配置好了 開始寫代碼:
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;
}

githubDemo鏈接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容