123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298 |
- //
- // SLAvEditExport.m
- // DarkMode
- //
- // Created by wsl on 2019/10/14.
- // Copyright © 2019 wsl. All rights reserved.
- //
- #import "SLAvEditExport.h"
- @interface SLAvEditExport ()
- {
- dispatch_source_t _gcdTimer; //计时器
- }
- @property (nonatomic, strong) AVAsset *asset; //资源文件
- @property (nonatomic, strong) AVAssetExportSession *exportSession; //资源导出会话
- @property (nonatomic, strong) AVMutableComposition *composition; //可变工程文件 合并音视频素材
- @property (nonatomic, strong) AVMutableVideoComposition *videoComposition; //视频成分
- @property (nonatomic, strong) AVMutableAudioMix *audioMix; // 音频混合
- /// 视频大小 注意:单位是px 不是pt frame*[UIScreen mainScreen].scale
- //@property (nonatomic, assign, readonly) CGSize videoSize;
- @end
- @implementation SLAvEditExport
- - (id)initWithAsset:(AVAsset *)asset {
- self = [super init];
- if (self) {
- _asset = asset;
- _timeRange = CMTimeRangeMake(kCMTimeZero, self.asset.duration);
- _rate = 1.f;
- _isNativeAudio = YES;
- }
- return self;
- }
- #pragma mark - Event Handle
- // 导出编辑的视频
- - (void)exportAsynchronouslyWithCompletionHandler:(void (^)(NSError *error))handler progress:(void (^)(float progress))exportProgress {
- [_exportSession cancelExport];
- _exportSession = nil;
- _composition = nil;
- _videoComposition = nil;
- if (_gcdTimer) {
- dispatch_source_cancel(_gcdTimer);
- _gcdTimer = nil;
- }
-
- NSError *error = nil;
- NSFileManager *fm = [NSFileManager new];
- //删除原来剪辑的视频
- if ([fm fileExistsAtPath:self.outputURL.path]) {
- if (![fm removeItemAtURL:self.outputURL error:&error]) {
- NSLog(@"removeTrimPath error: %@ \n",[error localizedDescription]);
- }
- }
- if (self.asset.duration.timescale == 0 || self.exportSession == nil) {
- /** 这个情况AVAssetExportSession会卡死 */
- NSError *failError = [NSError errorWithDomain:@"SLVideoExportSessionError" code:(-100) userInfo:@{NSLocalizedDescriptionKey:@"exportSession init fail"}];
- if (handler) handler(failError);
- return;
- }
-
- [self.exportSession exportAsynchronouslyWithCompletionHandler:^{
-
- dispatch_async(dispatch_get_main_queue(), ^{
- switch ([self.exportSession status]) {
- case AVAssetExportSessionStatusFailed:
- NSLog(@"视频导出失败: %@", [[self.exportSession error] localizedDescription]);
- break;
- case AVAssetExportSessionStatusCancelled:
- NSLog(@"视频导出取消");
- break;
- case AVAssetExportSessionStatusCompleted:
- NSLog(@"视频导出成功");
- break;
- default:
- break;
- }
- if ([self.exportSession status] == AVAssetExportSessionStatusCompleted && [fm fileExistsAtPath:self.outputURL.path]) {
- if (handler) handler(nil);
- } else {
- if (handler) handler(self.exportSession.error);
- }
- });
- }];
-
- [self updateExportProgress:exportProgress];
- }
- // 获取导出视频的进度
- - (void)updateExportProgress:(void (^)(float progress))exportProgress {
- _gcdTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
- //定时器延迟时间
- NSTimeInterval delayTime = 0.f;
- //定时器间隔时间
- NSTimeInterval timeInterval = 0.2f;
- //设置开始时间
- dispatch_time_t startDelayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayTime * NSEC_PER_SEC));
- dispatch_source_set_timer(_gcdTimer, startDelayTime, timeInterval * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
- // __weak typeof(self) weakSelf = self;
- dispatch_source_set_event_handler(_gcdTimer, ^{
- dispatch_async(dispatch_get_main_queue(), ^{
- exportProgress(self.exportSession.progress);
- });
- if (self.exportSession.status == AVAssetExportSessionStatusCancelled || self.exportSession.status == AVAssetExportSessionStatusCompleted || self.exportSession.status == AVAssetExportSessionStatusFailed || self.exportSession.status == AVAssetExportSessionStatusUnknown) {
- //取消计时器
- dispatch_source_cancel(self->_gcdTimer);
- }
- });
- // 启动任务,GCD计时器创建后需要手动启动
- dispatch_resume(_gcdTimer);
- }
- #pragma mark - Getter
- - (AVAssetExportSession *)exportSession {
- if (!_exportSession) {
- //导出会话
- _exportSession = [[AVAssetExportSession alloc] initWithAsset:self.composition presetName:AVAssetExportPresetHighestQuality];
- /** 创建混合视频时开始剪辑 */
- // _exportSession.timeRange = self.timeRange;
- _exportSession.videoComposition = self.videoComposition;
- _exportSession.outputURL = self.outputURL;
- _exportSession.outputFileType = AVFileTypeMPEG4;
- _exportSession.audioMix = self.audioMix;
- }
- return _exportSession;
- }
- - (AVMutableComposition *)composition {
- if (!_composition) {
- _composition = [[AVMutableComposition alloc] init];
- }
- return _composition;
- }
- - (AVMutableVideoComposition *)videoComposition {
- if (!_videoComposition) {
- //资源文件的视频轨道
- AVAssetTrack *assetVideoTrack = nil;
- // 是否包含视频轨道
- if ([[self.asset tracksWithMediaType:AVMediaTypeVideo] count] != 0) {
- assetVideoTrack = [self.asset tracksWithMediaType:AVMediaTypeVideo][0];
- }
- CMTime insertionPoint = kCMTimeZero;
- NSError *error = nil;
- // 添加视频轨道和素材 并裁剪视频
- if (assetVideoTrack != nil) {
- // 视频通道 工程文件中的轨道,有音频轨、视频轨等,里面可以插入各种对应的素材
- AVMutableCompositionTrack *compositionVideoTrack = [self.composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
- // 视频方向
- [compositionVideoTrack setPreferredTransform:assetVideoTrack.preferredTransform];
- // 把视频轨道数据加入到可变轨道中 这部分可以做视频裁剪TimeRange
- [compositionVideoTrack insertTimeRange:self.timeRange ofTrack:assetVideoTrack atTime:insertionPoint error:&error];
- [compositionVideoTrack scaleTimeRange:CMTimeRangeMake(kCMTimeZero, self.timeRange.duration) toDuration:CMTimeMake(self.timeRange.duration.value/self.rate, self.timeRange.duration.timescale)];
- }
- //视频方向
- UIImageOrientation orientation = [self orientationFromAVAssetTrack:assetVideoTrack];
- CGAffineTransform transform = CGAffineTransformIdentity;
- //视频素材原大小 像素大小px 不是pt
- CGSize renderSize = assetVideoTrack.naturalSize;
- switch (orientation) {
- case UIImageOrientationLeft:
- //顺时针旋转270°
- // NSLog(@"视频旋转270度,home按键在右");
- transform = CGAffineTransformTranslate(transform, 0.0, assetVideoTrack.naturalSize.width);
- transform = CGAffineTransformRotate(transform,M_PI_2*3.0);
- renderSize = CGSizeMake(assetVideoTrack.naturalSize.height,assetVideoTrack.naturalSize.width);
- break;
- case UIImageOrientationRight:
- //顺时针旋转90°
- // NSLog(@"视频旋转90度,home按键在左");
- transform = CGAffineTransformTranslate(transform, assetVideoTrack.naturalSize.height, 0.0);
- transform = CGAffineTransformRotate(transform,M_PI_2);
- renderSize = CGSizeMake(assetVideoTrack.naturalSize.height,assetVideoTrack.naturalSize.width);
- break;
- case UIImageOrientationDown:
- //顺时针旋转180°
- // NSLog(@"视频旋转180度,home按键在上");
- transform = CGAffineTransformTranslate(transform, assetVideoTrack.naturalSize.width, assetVideoTrack.naturalSize.height);
- transform = CGAffineTransformRotate(transform,M_PI);
- renderSize = CGSizeMake(assetVideoTrack.naturalSize.width,assetVideoTrack.naturalSize.height);
- break;
- default:
- break;
- }
- // _videoSize = renderSize;
-
- /** iOS9之前的处理方法,之后使用CIFilter ,待学习*/
- //方向
- if (orientation != UIImageOrientationUp || self.graffitiLayer || self.stickerLayers.count) {
- _videoComposition = [AVMutableVideoComposition videoComposition];
- _videoComposition.frameDuration = CMTimeMake(1, 30); // 30 fps
- _videoComposition.renderSize = renderSize;
- AVAssetTrack *videoTrack = [self.composition tracksWithMediaType:AVMediaTypeVideo][0];
- AVMutableVideoCompositionInstruction *roateInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
- roateInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, self.composition.duration);
- AVMutableVideoCompositionLayerInstruction *roateLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack];
- [roateLayerInstruction setTransform:transform atTime:kCMTimeZero];
- roateInstruction.layerInstructions = @[roateLayerInstruction];
- //将视频方向旋转加入到视频处理中
- _videoComposition.instructions = @[roateInstruction];
-
- /** 涂鸦 贴图 文字 */
- if(self.graffitiLayer || self.stickerLayers.count) {
- CALayer *parentLayer = [CALayer layer];
- CALayer *videoLayer = [CALayer layer];
- parentLayer.frame = CGRectMake(0, 0, renderSize.width, renderSize.height);
- videoLayer.frame = CGRectMake(0, 0, renderSize.width, renderSize.height);
- [parentLayer addSublayer:videoLayer];
- //涂鸦层
- if (self.graffitiLayer) {
- [parentLayer addSublayer:self.graffitiLayer];
- }
- //贴画层 和 文本层
- for (CALayer *gifLayer in self.stickerLayers) {
- [parentLayer addSublayer:gifLayer];
- }
-
- _videoComposition.animationTool = [AVVideoCompositionCoreAnimationTool videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer];
- }
- }
-
- }
- return _videoComposition;
- }
- - (AVMutableAudioMix *)audioMix {
- if (!_audioMix) {
- //音频轨道
- AVAssetTrack *assetAudioTrack = nil;
- if ([[self.asset tracksWithMediaType:AVMediaTypeAudio] count] != 0) {
- assetAudioTrack = [self.asset tracksWithMediaType:AVMediaTypeAudio][0];
- }
- CMTime insertionPoint = kCMTimeZero;
- NSError *error = nil;
- if (assetAudioTrack != nil && _isNativeAudio) {
- AVMutableCompositionTrack *compositionAudioTrack = [self.composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
- compositionAudioTrack.preferredTransform = assetAudioTrack.preferredTransform;
- [compositionAudioTrack insertTimeRange:self.timeRange ofTrack:assetAudioTrack atTime:insertionPoint error:&error];
- [compositionAudioTrack scaleTimeRange:CMTimeRangeMake(kCMTimeZero, self.timeRange.duration) toDuration:CMTimeMake(self.timeRange.duration.value/self.rate, self.timeRange.duration.timescale)];
- }
-
- /// 创建额外音轨特效
- NSMutableArray<AVAudioMixInputParameters *> *inputParameters;
- if (self.audioUrls.count) {
- inputParameters = [@[] mutableCopy];
- }
- /// 添加其他音频
- for (NSURL *audioUrl in self.audioUrls) {
- /** 声音采集 */
- AVURLAsset *audioAsset = [[AVURLAsset alloc]initWithURL:audioUrl options:nil];
- AVAssetTrack *additional_assetAudioTrack = nil;
- /** 检查是否有效音轨 */
- if ([[audioAsset tracksWithMediaType:AVMediaTypeAudio] count] != 0) {
- additional_assetAudioTrack = [audioAsset tracksWithMediaType:AVMediaTypeAudio][0];
- }
- if (additional_assetAudioTrack) {
- AVMutableCompositionTrack *additional_compositionAudioTrack = [self.composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
- additional_compositionAudioTrack.preferredTransform = additional_assetAudioTrack.preferredTransform;
- [additional_compositionAudioTrack insertTimeRange:self.timeRange ofTrack:additional_assetAudioTrack atTime:insertionPoint error:&error];
- [additional_compositionAudioTrack scaleTimeRange:CMTimeRangeMake(kCMTimeZero, self.timeRange.duration) toDuration:CMTimeMake(self.timeRange.duration.value/self.rate, self.timeRange.duration.timescale)];
-
- AVMutableAudioMixInputParameters *mixParameters = [AVMutableAudioMixInputParameters audioMixInputParametersWithTrack:additional_compositionAudioTrack];
- mixParameters.audioTimePitchAlgorithm = AVAudioTimePitchAlgorithmTimeDomain;
- [mixParameters setVolumeRampFromStartVolume:1 toEndVolume:0.3 timeRange:CMTimeRangeMake(kCMTimeZero, self.timeRange.duration)];
- [inputParameters addObject:mixParameters];
- }
- }
- if (inputParameters.count) {
- self.audioMix = [AVMutableAudioMix audioMix];
- self.audioMix.inputParameters = inputParameters;
- }
- }
- return _audioMix;
- }
- #pragma mark - Help Methods
- //视频的方向
- - (UIImageOrientation)orientationFromAVAssetTrack:(AVAssetTrack *)videoTrack {
- UIImageOrientation orientation = UIImageOrientationUp;
- CGAffineTransform t = videoTrack.preferredTransform;
- if(t.a == 0 && t.b == 1.0 && t.c == -1.0 && t.d == 0){
- // Portrait
- // degress = 90;
- orientation = UIImageOrientationRight;
- }else if(t.a == 0 && t.b == -1.0 && t.c == 1.0 && t.d == 0){
- // PortraitUpsideDown
- // degress = 270;
- orientation = UIImageOrientationLeft;
- }else if(t.a == 1.0 && t.b == 0 && t.c == 0 && t.d == 1.0){
- // LandscapeRight
- // degress = 0;
- orientation = UIImageOrientationUp;
- }else if(t.a == -1.0 && t.b == 0 && t.c == 0 && t.d == -1.0){
- // LandscapeLeft
- // degress = 180;
- orientation = UIImageOrientationDown;
- }
- return orientation;
- }
- @end
|