iOS短视频:多滤镜,背景音乐及贴纸的添加

当根据https://blog.csdn.net/weixin_42433480/article/details/90112917录制完视频并根据https://blog.csdn.net/weixin_42433480/article/details/90109873将断点的视频结合在一起导出后,就要开始编辑视频。

整个编辑过程分为五大部分,下面我们一一列举出来。

(一)预览视频:

这里使用的是GPUImageMovie,这个类仅仅支持本地视频文件播放,不支持在线视频播放。

核心代码:

movieFile = [[GPUImageMovie alloc] initWithPlayerItem:playerItem];
    //这使当前视频处于基准测试的模式,记录并输出瞬时和平均帧时间到控制台 每隔一段时间打印: Current frame time : 51.256001 ms,直到播放或加滤镜等操作完毕
    movieFile.runBenchmark = YES;
    //控制GPUImageView预览视频时的速度是否要保持真实的速度。
    //如果设为NO,则会将视频的所有帧无间隔渲染,导致速度非常快。
    //设为YES,则会根据视频本身时长计算出每帧的时间间隔,然后每渲染一帧,就sleep一个时间间隔,从而达到正常的播放速度。
    movieFile.playAtActualSpeed = YES;
    
    filter = [[LFGPUImageEmptyFilter alloc] init];
    
    _filtClassName = @"LFGPUImageEmptyFilter";
    [movieFile addTarget:filter];
    _filterView = [[GPUImageView alloc] initWithFrame:self.view.bounds];
    [self.view addSubview:_filterView];
    [filter addTarget:_filterView];

由于之前filter和GPUImageView在前两篇文章已经详细介绍,这里就不赘述了。关于GPUIMageMovie我们需要注意两点,一是当我们要把runBenchmark这个属性设为YES,这样我们可以使当前视频处于基准测试的模式,记录并输出瞬时和平均帧时间到控制台每隔一段时间打印: Current frame time : 51.256001 ms,直到播放或加滤镜等操作完毕。二是playAtActualSpeed属性需要设置为YES,这个属性是用于控制GPUImageView预览视频时的速度是否要保持真实的速度,如果设为NO,则会将视频的所有帧无间隔渲染,导致速度非常快,设为YES,则会根据视频本身时长计算出每帧的时间间隔,然后每渲染一帧,就sleep一个时间间隔,从而达到正常的播放速度。

(二)多滤镜的添加

首先我们要创建多个滤镜(用可滑动的UICollectionView展示):

-(NSArray*)creatFilterData
{
    FilterData* filter1 = [self createWithName:@"Empty" andFlieName:@"LFGPUImageEmptyFilter" andValue:nil];
    filter1.isSelected = YES;
    FilterData* filter2 = [self createWithName:@"Amatorka" andFlieName:@"GPUImageAmatorkaFilter" andValue:nil];
    FilterData* filter3 = [self createWithName:@"MissEtikate" andFlieName:@"GPUImageMissEtikateFilter" andValue:nil];
    FilterData* filter4 = [self createWithName:@"Sepia" andFlieName:@"GPUImageSepiaFilter" andValue:nil];
    FilterData* filter5 = [self createWithName:@"Sketch" andFlieName:@"GPUImageSketchFilter" andValue:nil];
    FilterData* filter6 = [self createWithName:@"SoftElegance" andFlieName:@"GPUImageSoftEleganceFilter" andValue:nil];
    FilterData* filter7 = [self createWithName:@"Toon" andFlieName:@"GPUImageToonFilter" andValue:nil];

    FilterData* filter8 = [[FilterData alloc] init];
    filter8.name = @"Saturation0";
    filter8.iconPath = [[NSBundle mainBundle] pathForResource:@"GPUImageSaturationFilter0" ofType:@"png"];
    filter8.fillterName = @"GPUImageSaturationFilter";
    filter8.value = @"0";
    
    FilterData* filter9 = [[FilterData alloc] init];
    filter9.name = @"Saturation2";
    filter9.iconPath = [[NSBundle mainBundle] pathForResource:@"GPUImageSaturationFilter2" ofType:@"png"];
    filter9.fillterName = @"GPUImageSaturationFilter";
    filter9.value = @"2";
    
    return [NSArray arrayWithObjects:filter1,filter2,filter3,filter4,filter5,filter6,filter7,filter8,filter9, nil];
    
}

点击代表每一个滤镜的UICollectionViewCell来添加滤镜显示的代码:

 if (_lastFilterIndex.row != _nowFilterIndex.row) {
            
            //1.修改数据源
            FilterData* dataNow = [_filterAry objectAtIndex:indexPath.row];
            dataNow.isSelected = YES;
            [_filterAry replaceObjectAtIndex:indexPath.row withObject:dataNow];
            FilterData* dataLast = [_filterAry objectAtIndex:_lastFilterIndex.row];
            dataLast.isSelected = NO;
            [_filterAry replaceObjectAtIndex:_lastFilterIndex.row withObject:dataLast];
            //2.刷新collectionView
            [_collectionView reloadData];
            _lastFilterIndex = indexPath;
            
        }
        
        if (indexPath.row == 0) {
            [movieFile removeAllTargets]; 
            FilterData* data = [_filterAry objectAtIndex:indexPath.row];
            _filtClassName = data.fillterName;
            filter = [[NSClassFromString(_filtClassName) alloc] init];
            [movieFile addTarget:filter];
            [filter addTarget:_filterView];

            
        }else
        {
            [movieFile removeAllTargets];
            FilterData* data = [_filterAry objectAtIndex:indexPath.row];
            _filtClassName = data.fillterName;
            
            if ([data.fillterName isEqualToString:@"GPUImageSaturationFilter"]) {
                GPUImageSaturationFilter* xxxxfilter = [[NSClassFromString(_filtClassName) alloc] init];
                xxxxfilter.saturation = [data.value floatValue];
                _saturationValue = [data.value floatValue];
                filter = xxxxfilter;

            }else{
                filter = [[NSClassFromString(_filtClassName) alloc] init];
            }
            [movieFile addTarget:filter];
            
            // Only rotate the video for display, leave orientation the same for recording
            [filter addTarget:_filterView];
        }

这的实现原理其实是移除原有的滤镜,根据你点击的UICollectionViewCell所代表的滤镜给GPUImageMovie添加新的滤镜。

(三)背景音乐的添加

首先我们要先获得背景音乐的数据源:

-(NSArray*)creatMusicData
{
    
    NSString *configPath = [[NSBundle mainBundle] pathForResource:@"music2" ofType:@"json"];
    NSData *configData = [NSData dataWithContentsOfFile:configPath];
    NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:configData options:NSJSONReadingAllowFragments error:nil];
    NSArray *items = dic[@"music"];
    int i = 529 ;
    NSMutableArray *array = [NSMutableArray array];
    
    MusicData *effect = [[MusicData alloc] init];
    effect.name = @"原始";
    effect.iconPath = [[NSBundle mainBundle] pathForResource:@"nilMusic" ofType:@"png"];
    effect.isSelected = YES;
    [array addObject:effect];
    
    for (NSDictionary *item in items) {
        //        NSString *path = [baseDir stringByAppendingPathComponent:item[@"resourceUrl"]];
        MusicData *effect = [[MusicData alloc] init];
        effect.name = item[@"name"];
        effect.eid = item[@"id"];
        effect.musicPath = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"audio%d",i] ofType:@"mp3"];
        effect.iconPath = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"icon%d",i] ofType:@"png"];
        [array addObject:effect];
        i++;
    }
    
    return array;
}

这里的数据源主要是本地模拟JSON数据(music2.json):

{
    "music": [{
        "resourceUrl": "music/No Limit",
        "id": 1,
        "name": "No Limit"
    }, {
        "resourceUrl": "music/皮皮虾我们走",
        "id": 2,
        "name": "皮皮虾我们走"
      }, {
      "resourceUrl": "music/Dragostea Din Teï",
      "id": 3,
      "name": "Dragostea Din Teï"
              }, {
              "resourceUrl": "music/还不是因为你长得不好看",
              "id": 4,
              "name": "还不是因为你长得不好看"
              }, {
              "resourceUrl": "music/Grass Studio",
              "id": 5,
              "name": "Grass Studio"
              }, {
              "resourceUrl": "music/Secret",
              "id": 6,
              "name": "一人饮酒醉"
              }, {
              "resourceUrl": "music/WHISTLE",
              "id": 7,
              "name": "WHISTLE"
              }, {
              "resourceUrl": "music/稻香",
              "id": 8,
              "name": "稻香"
              }, {
              "resourceUrl": "music/北京小妞",
              "id": 9,
              "name": "北京小妞"
              }, {
              "resourceUrl": "music/刀剑如梦",
              "id": 10,
              "name": "刀剑如梦"
              }, {
              "resourceUrl": "music/Glad You Came",
              "id": 11,
              "name": "Glad You Came"
              }, {
              "resourceUrl": "music/减肥",
              "id": 12,
              "name": "减肥"
              }, {
              "resourceUrl": "music/We Don't Talk Anymore",
              "id": 13,
              "name": "We Don't Talk Anymore"
              }, {
              "resourceUrl": "music/叫我女王",
              "id": 14,
              "name": "叫我女王"
              }, {
              "resourceUrl": "music/可惜不是你",
              "id": 15,
              "name": "可惜不是你"
              }, {
              "resourceUrl": "music/岁月神偷",
              "id": 16,
              "name": "岁月神偷"
              }, {
              "resourceUrl": "music/我好想你",
              "id": 17,
              "name": "我好想你"
              }, {
              "resourceUrl": "music/Show Me Your Bba Sae",
              "id": 18,
              "name": "Show Me Your Bba Sae"
              }, {
              "resourceUrl": "music/Candy★Night",
              "id": 19,
              "name": "Candy★Night"
              }, {
              "resourceUrl": "music/Again",
              "id": 20,
              "name": "Again"
              }],
}

点击代表每一个背景音乐的UICollectionViewCell来播放背景音乐的代码:

_nowMusicIndex = indexPath;
        if (_lastMusicIndex.row != _nowMusicIndex.row) {
            
            //1.修改数据源
            FilterData* dataNow = [_musicAry objectAtIndex:indexPath.row];
            dataNow.isSelected = YES;
            [_musicAry replaceObjectAtIndex:indexPath.row withObject:dataNow];
            FilterData* dataLast = [_musicAry objectAtIndex:_lastMusicIndex.row];
            dataLast.isSelected = NO;
            [_musicAry replaceObjectAtIndex:_lastMusicIndex.row withObject:dataLast];
            //刷新collectionView
            [_musicCollectionView reloadData];
            _lastMusicIndex = indexPath;
            
        }
        
        if (indexPath.row == 0) {
            _audioPath = nil;
            [_audioPlayer pause];
                
            _editTheOriginaBtn.hidden = YES;
            _editTheOriginaSwitch.hidden = YES;
            [mainPlayer setVolume:1];
            _editTheOriginaBtn.selected = NO;
            _editTheOriginaSwitch.on = NO;
            
        }else
        {
            MusicData* data = [_musicAry objectAtIndex:indexPath.row];
            _audioPath = data.musicPath;
            [self playMusic];
            _editTheOriginaBtn.hidden = NO;
            _editTheOriginaSwitch.hidden = NO;

        }
    }
-(void)playMusic
{
    // 路径
    
    NSURL *audioInputUrl = [NSURL fileURLWithPath:_audioPath];
    // 声音来源  
    
    audioPlayerItem =[AVPlayerItem playerItemWithURL:audioInputUrl];
    
    [_audioPlayer replaceCurrentItemWithPlayerItem:audioPlayerItem];//_audioPlayer = [[AVPlayer alloc ]init];
    
    [_audioPlayer play];
}

这里还加了一个比较特别的可以剔除原声的功能:

//mainPlayer = [[AVPlayer alloc] init];playerItem = [[AVPlayerItem alloc] initWithURL:videoURL];[mainPlayer replaceCurrentItemWithPlayerItem:playerItem];
if (!_editTheOriginaBtn.selected) {
        _editTheOriginaBtn.selected = YES;
        [mainPlayer setVolume:0];//剔除原声
    }else
    {
        [mainPlayer setVolume:1];//恢复原声
        _editTheOriginaBtn.selected = NO;
    }

注意:[mainPlayer setVolume:0]用于剔除原声,而[mainPlayer setVolume:1]用于恢复原声。

(四)添加贴纸

和背景音乐一样,也是用本地的JSON文件(stickers.json)模仿数据源:

-(NSArray*)creatStickersData
{
    NSString *configPath = [[NSBundle mainBundle] pathForResource:@"stickers" ofType:@"json"];
    NSData *configData = [NSData dataWithContentsOfFile:configPath];
    NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:configData options:NSJSONReadingAllowFragments error:nil];
    NSArray *items = dic[@"stickers"];
    int i = 529 ;
    NSMutableArray *array = [NSMutableArray array];
    
    for (NSDictionary *item in items) {
        //        NSString *path = [baseDir stringByAppendingPathComponent:item[@"resourceUrl"]];
        StickersData* stickersItem = [[StickersData alloc] init];
        stickersItem.name = item[@"name"];
        stickersItem.StickersImgPaht = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"stickers%d",i] ofType:@"jpg"];
        [array addObject:stickersItem];
        i++;
    }
    
    return array;
}

stickers.json文件:

{
"stickers": [
    {
    "resourceUrl": "music/Athena",
    "name": "胸口碎大石"
    },
    {
    "resourceUrl": "music/Athena",
    "name": "我不胖"
    },
    {
    "resourceUrl": "music/Athena",
    "name": "完美"
    },
    {
    "resourceUrl": "music/Athena",
    "name": "我的脑残"
    },
    {
    "resourceUrl": "music/Athena",
    "name": "变美光波"
    },
    {
    "resourceUrl": "music/Athena",
    "name": "点个赞"
    },
    {
    "resourceUrl": "music/Athena",
    "name": "冷漠"
    },
    {
    "resourceUrl": "music/Athena",
    "name": "么么哒"
    },
    {
    "resourceUrl": "music/Athena",
    "name": "怕不怕"
    },
    {
    "resourceUrl": "music/Athena",
    "name": "出去浪"
    },
    {
    "resourceUrl": "music/Athena",
    "name": "给你小鱼干"
    },
    {
    "resourceUrl": "music/Athena",
    "name": "小祖宗"
    },
    {
    "resourceUrl": "music/Athena",
    "name": "好耀眼"
    },
    {
    "resourceUrl": "music/Athena",
    "name": "约吗"
    },
    {
    "resourceUrl": "music/Athena",
    "name": "打飞机"
    },
    {
    "resourceUrl": "music/Athena",
    "name": "辣眼睛"
    },
    {
    "resourceUrl": "music/Athena",
    "name": "喀嗞~喀嗞"
    },
    {
    "resourceUrl": "music/Athena",
    "name": "不给"
    },
    {
    "resourceUrl": "music/Athena",
    "name": "包裹到了"
    },
    {
    "resourceUrl": "music/Athena",
    "name": "哎呦~"
    },
    {
    "resourceUrl": "music/Athena",
    "name": "关注我哦"
    },
    {
    "resourceUrl": "music/Athena",
    "name": "我美吗"
    },
    {
    "resourceUrl": "music/Athena",
    "name": "帅成狗"
    },
    {
    "resourceUrl": "music/Athena",
    "name": "女王大人"
    },
    {
    "resourceUrl": "music/Athena",
    "name": "厉害了我的哥"
    },
    {
    "resourceUrl": "music/Athena",
    "name": "原地爆炸"
    },
    {
    "resourceUrl": "music/Athena",
    "name": "我经历了什么"
    },
    {
    "resourceUrl": "music/Athena",
    "name": "小淑女"
    },
    {
    "resourceUrl": "music/Athena",
    "name": "美少女"
    },
    {
    "resourceUrl": "music/Athena",
    "name": "你丑你先睡"
    },
    {
    "resourceUrl": "music/Athena",
    "name": "没穿秋裤"
    },
    {
    "resourceUrl": "music/Athena",
    "name": "颜值爆表耶"
    },
    {
    "resourceUrl": "music/Athena",
    "name": "吻我"
    },
    {
    "resourceUrl": "music/Athena",
    "name": "老鲜肉"
    },
    {
    "resourceUrl": "music/Athena",
    "name": "高冷"
    },
    {
    "resourceUrl": "music/Athena",
    "name": "要抱抱"
    }

               ],
}

点击代表每一个贴纸的UICollectionViewCell来添加贴纸的代码:

_nowStickersIndex = indexPath;
        if (_lastStickersIndex.row != _nowStickersIndex.row) {
            
            //1.修改数据源
//            FilterData* dataNow = [_filterAry objectAtIndex:indexPath.row];
            StickersData* dataNow = [_stickersAry objectAtIndex:indexPath.row];
            dataNow.isSelected = YES;
            [_stickersAry replaceObjectAtIndex:indexPath.row withObject:dataNow];
            StickersData* dataLast = [_stickersAry objectAtIndex:_lastStickersIndex.row];
            dataLast.isSelected = NO;
            [_stickersAry replaceObjectAtIndex:_lastStickersIndex.row withObject:dataLast];
            //2.刷新collectionView
            [_stickersCollectionView reloadData];
            _lastStickersIndex = indexPath;
            
        }else
        {
            _stickersImgView.center = CGPointMake(SCREEN_WIDTH/2, SCREEN_HEIGHT/2);
        }
        StickersData* data = [_stickersAry objectAtIndex:indexPath.row];
        _stickersImgView.image = [UIImage imageWithContentsOfFile:data.StickersImgPaht];
        _stickersImgView.hidden = NO;

这里实现的原理是每当点击代表贴纸的UICollectionViewCell时,就会将贴纸对象存储的image路径转化为图片并赋值给之前隐藏的UIImageView,这样我们就可以看到贴纸图片,但是这还不够,想要拖动贴纸,就需要添加手势代码了:

- (void) panView:(UIPanGestureRecognizer *)panGestureRecognizer
{
    UIView *view = panGestureRecognizer.view;
    if (panGestureRecognizer.state == UIGestureRecognizerStateBegan) {
        _musicBottomBar.hidden = YES;//_musicBottomBar是包含滤镜,音乐,贴纸三个UICollectionView的View
    }
    
    if ( panGestureRecognizer.state == UIGestureRecognizerStateChanged) {
        CGPoint translation = [panGestureRecognizer translationInView:view.superview];
        [view setCenter:(CGPoint){view.center.x + translation.x, view.center.y + translation.y}];
        [panGestureRecognizer setTranslation:CGPointZero inView:view.superview];
    }
    if (panGestureRecognizer.state == UIGestureRecognizerStateEnded) {
        _musicBottomBar.hidden = NO;//_musicBottomBar是包含滤镜,音乐,贴纸三个UICollectionView的View
    }
}

(五)音视频合成与压缩

由于音视频合成在前两篇文章已经详细叙述过了,那么接下来我们只说一下压缩视频。毋庸置疑,当我们拍摄完视频要上传时就必须要压缩视频,由于系统的压缩方法达不到质量大小与清晰度相匹配的需求,所以使用SDAVAssetExportSession

/* Create Output File Url */
    NSString *documentsDirectory = NSTemporaryDirectory();
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    formatter.dateFormat = @"yyyyMMddHHmmss";
    NSString *nowTimeStr = [formatter stringFromDate:[NSDate dateWithTimeIntervalSinceNow:0]];
    NSString *finalVideoURLString = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"compressedVideo%@.mp4",nowTimeStr]];
    NSURL *outputVideoUrl = ([[NSURL URLWithString:finalVideoURLString] isFileURL] == 1)?([NSURL URLWithString:finalVideoURLString]):([NSURL fileURLWithPath:finalVideoURLString]); // Url Should be a file Url, so here we check and convert it into a file Url
    NSDictionary* options = @{AVURLAssetPreferPreciseDurationAndTimingKey:@YES};
    AVAsset* asset = [AVURLAsset URLAssetWithURL:inputVideoUrl options:options];
    NSArray* keys = @[@"tracks",@"duration",@"commonMetadata"];
    //loadValuesAsynchronouslyForKeys: https://blog.csdn.net/hdfqq188816190/article/details/72930381
    [asset loadValuesAsynchronouslyForKeys:keys completionHandler:^{
        //系统的压缩方法达不到质量大小与清晰度相匹配的需求,所以使用SDAVAssetExportSession:视频压缩https://www.jianshu.com/p/873ac13b6ce3
        SDAVAssetExportSession *compressionEncoder = [SDAVAssetExportSession.alloc initWithAsset:asset]; // provide inputVideo Url Here
        compressionEncoder.outputFileType = AVFileTypeMPEG4;
        compressionEncoder.outputURL = outputVideoUrl; //Provide output video Url here
        compressionEncoder.videoSettings = @
        {
        AVVideoCodecKey: AVVideoCodecH264,
        AVVideoWidthKey: @720,   //Set your resolution width here
        AVVideoHeightKey: @1280,  //set your resolution height here
        AVVideoCompressionPropertiesKey: @
            {
                //2000*1000  建议800*1000-5000*1000
                //AVVideoAverageBitRateKey: @2500000, // Give your bitrate here for lower size give low values
            AVVideoAverageBitRateKey: _bit,
            AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel,
            AVVideoAverageNonDroppableFrameRateKey: _frameRate,
            },
        };
        compressionEncoder.audioSettings = @
        {
        AVFormatIDKey: @(kAudioFormatMPEG4AAC),
        AVNumberOfChannelsKey: @2,
        AVSampleRateKey: @44100,
        AVEncoderBitRateKey: @128000,
        };
        [compressionEncoder exportAsynchronouslyWithCompletionHandler:^
         {
             dispatch_async(dispatch_get_main_queue(), ^{
                 //更新UI操作
                 //.....
                 if (compressionEncoder.status == AVAssetExportSessionStatusCompleted)
                 {
                     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                         HUD.hidden = YES;
                         [[NSNotificationCenter defaultCenter] removeObserver:self];
                         EditingPublishingDynamicViewController* cor = [[EditingPublishingDynamicViewController alloc] init];
                         cor.videoURL = compressionEncoder.outputURL;
//                         [[AppDelegate appDelegate] pushViewController:cor animated:YES];
                         [self.rt_navigationController pushViewController:cor animated:YES complete:nil];
                       
                     });
                     
                 }
                 else if (compressionEncoder.status == AVAssetExportSessionStatusCancelled)
                 {
//                     HUD.labelText = @"Compression Failed";
                     HUD.label.text = @"Compression Failed";
                     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                         [[NSNotificationCenter defaultCenter] removeObserver:self];
//                         [[NSNotificationCenter defaultCenter] postNotificationName:kTabBarHiddenNONotification object:self];
                         [self.navigationController popToRootViewControllerAnimated:YES];
                         
                     });
                 }
                 else
                 {
                     HUD.label.text = @"ompression Failed";
                     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                         [[NSNotificationCenter defaultCenter] removeObserver:self];
//                         [[NSNotificationCenter defaultCenter] postNotificationName:kTabBarHiddenNONotification object:self];
                         [self.navigationController popToRootViewControllerAnimated:YES];
                     });
                 }
             });
         }];
    }];

注意:这里的实现原理是我们先要根据url获取到AVAsset,然后将AVAsset进行压缩,这里我们会首先走loadValuesAsynchronouslyForKeys这个方法来判断你想要的值是否获取成功(https://blog.csdn.net/hdfqq188816190/article/details/72930381这里有较为详细的解释)。

然后通过SDAVAssetExportSession进行压缩,这里要设置outputFileType即压缩文件类型为AVFileTypeMPEG4,videoSettings包含视频格式为H264,分辨率为(720, 1280),还有传递给视频编码器的属性,比如AVVideoAverageBitRateKey(视频尺寸*比率)、AVVideoProfileLevelKey(画质,这里设为高画质) 、AVVideoAverageNonDroppableFrameRateKey(设置每秒不可掉落的视频的帧数), 这里我们要说下为何会有不可掉落帧:因为一些视频编码器可以产生一个灵活的混合非下降帧和下降帧。这两种类型的区别在于,为了成功解码后续帧,视频解码器需要解码一个不可下降帧,而可下降帧是可选的,可以跳过,不影响后续帧的解码。在一个序列中拥有一定比例的可丢弃帧对于时间可伸缩性具有优势:在回放时,可以根据播放速率解码更多或更少的帧。此属性要求编码器发出不可下降帧和可下降帧的总体比例,以便每秒有指定数量的不可下降帧。

此外还要对音频进行设置,比如audioSettings包含AVFormatIDKey(音频格式为aac)、AVNumberOfChannelsKey(通道数,这里设为2)、AVSampleRateKey(采样率,这里44100)、AVEncoderBitRateKey(比特采样率, 这里设为128000),关于SDAVAssetExportSession更详细的使用,https://www.jianshu.com/p/873ac13b6ce3这里有较为详细的解释,当这一切做完,我们再在主线程更新UI和跳转下一个发布界面就大功告成了。

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值