iOS AVFoundation自定义视频压缩 自定义视频 比特率 帧率 宽高等

释放双眼,带上耳机,听听看~!

最近做视频压缩上传功能 刚开始用的苹果已经简单封装好的 AVAssetExportSession 框架 视频压缩上传后 发现有些视频压缩后 反而变大了
后来参考了近期视频优化做的比较不错的douyin 发现不论上传多大的视频 被douyin压缩以后比特率(视频每秒传输的大小 单位:比特率(bps) / 千比特率(kbps))基本都维持在1500 kbps左右 经测试 这是对视频压缩后 文件大小 影响最大的一个参数
另外 还有比如帧率 视频宽高 等参数 也对视频压缩有一定的影响
用此方法 在项目中实测 基本可以满足绝大多数需求
如果你仅仅是想完成需求 不想研究代码的话
到这里可以结束阅读了
直接翻到最下面下载Demo 复制我封装好的方法 调用 就行了
继续往下看的话:
代码用到了访问相机 访问相册 访问麦克风 添加视频到相册四个权限
使用前 先把info中相机相册对应权限打开
info.plist 用Source Code方式打开的话 这样写:

 

    <key>NSCameraUsageDescription</key>
    <string>是否允许访问相机</string>
    <key>NSMicrophoneUsageDescription</key>
    <string>App需要您的同意才能访问麦克风</string>
    <key>NSPhotoLibraryUsageDescription</key>
    <string>是否允许访问相册</string>
    <key>NSPhotoLibraryAddUsageDescription</key>
    <string>是否允许添加视频或图片到相册</string>

info.plist 用Property List方式打开的话 这样配置:

info中相册相机权限

配置好了 开始写代码:
ViewController.m
使用系统的UIImagePickerController选择或者拍摄对应的视频
使用前 先遵守<UIImagePickerControllerDelegate, UINavigationControllerDelegate>这俩协议
然后写这个

 

@property (nonatomic, strong) UIImagePickerController *imagePicker;

从相册中选择视频 代码注释写的比较详细了

 

//从相册中选择视频
- (IBAction)chooseVideoBtnClick:(id)sender {
    if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeSavedPhotosAlbum])
    {
        self.imagePicker = [[UIImagePickerController alloc] init];
        self.imagePicker.delegate = self;
        /*
         * 设置资源文件来源 图库 相机 相册
         * UIImagePickerControllerSourceTypePhotoLibrary,
         * UIImagePickerControllerSourceTypeCamera,
         * UIImagePickerControllerSourceTypeSavedPhotosAlbum
         **/
        self.imagePicker.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;
        /*
         * 设置媒体类型
         * (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;
        /*
         * 设置资源文件来源 图库 相机 相册
         * UIImagePickerControllerSourceTypePhotoLibrary,
         * UIImagePickerControllerSourceTypeCamera,
         * UIImagePickerControllerSourceTypeSavedPhotosAlbum
         **/
        self.imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
        /*
         * 设置媒体类型
         * (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:@"知道了"];
    }
}

取消选择或者拍摄时的回调
UIImagePickerControllerDelegate

 

-(void)imagePickerControllerDidCancel:(UIImagePickerController *)picker{
    [self.imagePicker dismissViewControllerAnimated:YES completion:nil];
    [self showAlertViewWithTitle:@"用户取消操作" message:@"" withCancelButtonTitle:@"好的"];

}

视频选择 或者拍摄好以后回调

 

-(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];
        //关闭相册界面
        [picker dismissViewControllerAnimated:YES completion:^{
        //执行视频压缩功能            
        [self compressVideoWithVideoUrl:outputUrl];
        }];
    }
}

//压缩视频

 

/*
 * 自定义视频压缩
 * videoUrl 原视频url路径 必传
 * outputBiteRate 压缩视频至指定比特率(bps) 可传nil 默认1500kbps
 * outputFrameRate 压缩视频至指定帧率 可传nil 默认30fps
 * outputWidth 压缩视频至指定宽度 可传nil 默认960
 * outputWidth 压缩视频至指定高度 可传nil 默认540
 * compressComplete 压缩后的视频信息回调 (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];
        //在这里上传或者保存已经处理好的视频文件
        //保存视频至相册
        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 保存视频后的回调
- (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 默认1500kbps
 * outputFrameRate 压缩视频至指定帧率 可传nil 默认30fps
 * outputWidth 压缩视频至指定宽度 可传nil 默认960
 * outputWidth 压缩视频至指定高度 可传nil 默认540
 * compressComplete 压缩后的视频信息回调 (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;
    //取出原视频详细资料
    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;
        }
    }
    //创建视频文件读取者
    AVAssetReader *reader = [AVAssetReader assetReaderWithAsset:asset error:nil];
    //从指定文件读取视频
    AVAssetReaderTrackOutput *videoOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:videoTrack outputSettings:[VideoCompress configVideoOutput]];
    //取出原视频中音频详细资料
    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];
    //根据指定配置创建写入的视频文件
    AVAssetWriterInput *videoInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:[VideoCompress videoCompressSettingsWithBitRate:compressBiteRate withFrameRate:compressFrameRate withWidth:compressWidth WithHeight:compressHeight withOriginalWidth:videoWidth withOriginalHeight:videoHeight]];
    //根据指定配置创建写入的音频文件
    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];
    //创建视频写入队列
    dispatch_queue_t videoQueue = dispatch_queue_create("Video Queue", DISPATCH_QUEUE_SERIAL);
    //创建音频写入队列
    dispatch_queue_t audioQueue = dispatch_queue_create("Audio Queue", DISPATCH_QUEUE_SERIAL);
    //创建一个线程组
    dispatch_group_t group = dispatch_group_create();
    //进入线程组
    dispatch_group_enter(group);
    //队列准备好后 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);
    //队列准备好后 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;
        }
    });
}

视频压缩的参数配置

 

+ (NSDictionary *)videoCompressSettingsWithBitRate:(NSInteger)biteRate withFrameRate:(NSInteger)frameRate withWidth:(NSInteger)width WithHeight:(NSInteger)height withOriginalWidth:(NSInteger)originalWidth withOriginalHeight:(NSInteger)originalHeight{
    /*
     * AVVideoAverageBitRateKey: 比特率(码率)每秒传输的文件大小 kbps
     * AVVideoExpectedSourceFrameRateKey:帧率 每秒播放的帧数
     * AVVideoProfileLevelKey:画质水平
     BP-Baseline Profile:基本画质。支持I/P 帧,只支持无交错(Progressive)和CAVLC;
     EP-Extended profile:进阶画质。支持I/P/B/SP/SI 帧,只支持无交错(Progressive)和CAVLC;
     MP-Main profile:主流画质。提供I/P/B 帧,支持无交错(Progressive)和交错(Interlaced),也支持CAVLC 和CABAC 的支持;
     HP-High profile:高级画质。在main Profile 的基础上增加了8×8内部预测、自定义量化、 无损视频编码和更多的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;
    }
}

音频压缩的参数配置

 

//音频设置
+ (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;
}

读取音频参数配置

 

/** 音频解码 */
+ (NSDictionary *)configAudioOutput
{
    NSDictionary *audioOutputSetting = @{
                                         AVFormatIDKey: @(kAudioFormatLinearPCM)
                                         };
    return audioOutputSetting;
}

读取视频参数配置

 

/** 视频解码 */
+ (NSDictionary *)configVideoOutput
{
    NSDictionary *videoOutputSetting = @{
                                         (__bridge NSString *)kCVPixelBufferPixelFormatTypeKey:[NSNumber numberWithUnsignedInt:kCVPixelFormatType_422YpCbCr8],
                                         (__bridge NSString *)kCVPixelBufferIOSurfacePropertiesKey:[NSDictionary dictionary]
                                         };
    
    return videoOutputSetting;
}

githubDemo链接

 

人已赞赏
iOS文章

iOS 红包雨实现

2019-12-21 18:32:14

iOS文章

iOS生成二维码和条形码

2019-12-22 12:34:47

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
有新消息 消息中心
搜索