SLAvEditExport.m 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. //
  2. // SLAvEditExport.m
  3. // DarkMode
  4. //
  5. // Created by wsl on 2019/10/14.
  6. // Copyright © 2019 wsl. All rights reserved.
  7. //
  8. #import "SLAvEditExport.h"
  9. @interface SLAvEditExport ()
  10. {
  11. dispatch_source_t _gcdTimer; //计时器
  12. }
  13. @property (nonatomic, strong) AVAsset *asset; //资源文件
  14. @property (nonatomic, strong) AVAssetExportSession *exportSession; //资源导出会话
  15. @property (nonatomic, strong) AVMutableComposition *composition; //可变工程文件 合并音视频素材
  16. @property (nonatomic, strong) AVMutableVideoComposition *videoComposition; //视频成分
  17. @property (nonatomic, strong) AVMutableAudioMix *audioMix; // 音频混合
  18. /// 视频大小 注意:单位是px 不是pt frame*[UIScreen mainScreen].scale
  19. //@property (nonatomic, assign, readonly) CGSize videoSize;
  20. @end
  21. @implementation SLAvEditExport
  22. - (id)initWithAsset:(AVAsset *)asset {
  23. self = [super init];
  24. if (self) {
  25. _asset = asset;
  26. _timeRange = CMTimeRangeMake(kCMTimeZero, self.asset.duration);
  27. _rate = 1.f;
  28. _isNativeAudio = YES;
  29. }
  30. return self;
  31. }
  32. #pragma mark - Event Handle
  33. // 导出编辑的视频
  34. - (void)exportAsynchronouslyWithCompletionHandler:(void (^)(NSError *error))handler progress:(void (^)(float progress))exportProgress {
  35. [_exportSession cancelExport];
  36. _exportSession = nil;
  37. _composition = nil;
  38. _videoComposition = nil;
  39. if (_gcdTimer) {
  40. dispatch_source_cancel(_gcdTimer);
  41. _gcdTimer = nil;
  42. }
  43. NSError *error = nil;
  44. NSFileManager *fm = [NSFileManager new];
  45. //删除原来剪辑的视频
  46. if ([fm fileExistsAtPath:self.outputURL.path]) {
  47. if (![fm removeItemAtURL:self.outputURL error:&error]) {
  48. NSLog(@"removeTrimPath error: %@ \n",[error localizedDescription]);
  49. }
  50. }
  51. if (self.asset.duration.timescale == 0 || self.exportSession == nil) {
  52. /** 这个情况AVAssetExportSession会卡死 */
  53. NSError *failError = [NSError errorWithDomain:@"SLVideoExportSessionError" code:(-100) userInfo:@{NSLocalizedDescriptionKey:@"exportSession init fail"}];
  54. if (handler) handler(failError);
  55. return;
  56. }
  57. [self.exportSession exportAsynchronouslyWithCompletionHandler:^{
  58. dispatch_async(dispatch_get_main_queue(), ^{
  59. switch ([self.exportSession status]) {
  60. case AVAssetExportSessionStatusFailed:
  61. NSLog(@"视频导出失败: %@", [[self.exportSession error] localizedDescription]);
  62. break;
  63. case AVAssetExportSessionStatusCancelled:
  64. NSLog(@"视频导出取消");
  65. break;
  66. case AVAssetExportSessionStatusCompleted:
  67. NSLog(@"视频导出成功");
  68. break;
  69. default:
  70. break;
  71. }
  72. if ([self.exportSession status] == AVAssetExportSessionStatusCompleted && [fm fileExistsAtPath:self.outputURL.path]) {
  73. if (handler) handler(nil);
  74. } else {
  75. if (handler) handler(self.exportSession.error);
  76. }
  77. });
  78. }];
  79. [self updateExportProgress:exportProgress];
  80. }
  81. // 获取导出视频的进度
  82. - (void)updateExportProgress:(void (^)(float progress))exportProgress {
  83. _gcdTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
  84. //定时器延迟时间
  85. NSTimeInterval delayTime = 0.f;
  86. //定时器间隔时间
  87. NSTimeInterval timeInterval = 0.2f;
  88. //设置开始时间
  89. dispatch_time_t startDelayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayTime * NSEC_PER_SEC));
  90. dispatch_source_set_timer(_gcdTimer, startDelayTime, timeInterval * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
  91. // __weak typeof(self) weakSelf = self;
  92. dispatch_source_set_event_handler(_gcdTimer, ^{
  93. dispatch_async(dispatch_get_main_queue(), ^{
  94. exportProgress(self.exportSession.progress);
  95. });
  96. if (self.exportSession.status == AVAssetExportSessionStatusCancelled || self.exportSession.status == AVAssetExportSessionStatusCompleted || self.exportSession.status == AVAssetExportSessionStatusFailed || self.exportSession.status == AVAssetExportSessionStatusUnknown) {
  97. //取消计时器
  98. dispatch_source_cancel(self->_gcdTimer);
  99. }
  100. });
  101. // 启动任务,GCD计时器创建后需要手动启动
  102. dispatch_resume(_gcdTimer);
  103. }
  104. #pragma mark - Getter
  105. - (AVAssetExportSession *)exportSession {
  106. if (!_exportSession) {
  107. //导出会话
  108. _exportSession = [[AVAssetExportSession alloc] initWithAsset:self.composition presetName:AVAssetExportPresetHighestQuality];
  109. /** 创建混合视频时开始剪辑 */
  110. // _exportSession.timeRange = self.timeRange;
  111. _exportSession.videoComposition = self.videoComposition;
  112. _exportSession.outputURL = self.outputURL;
  113. _exportSession.outputFileType = AVFileTypeMPEG4;
  114. _exportSession.audioMix = self.audioMix;
  115. }
  116. return _exportSession;
  117. }
  118. - (AVMutableComposition *)composition {
  119. if (!_composition) {
  120. _composition = [[AVMutableComposition alloc] init];
  121. }
  122. return _composition;
  123. }
  124. - (AVMutableVideoComposition *)videoComposition {
  125. if (!_videoComposition) {
  126. //资源文件的视频轨道
  127. AVAssetTrack *assetVideoTrack = nil;
  128. // 是否包含视频轨道
  129. if ([[self.asset tracksWithMediaType:AVMediaTypeVideo] count] != 0) {
  130. assetVideoTrack = [self.asset tracksWithMediaType:AVMediaTypeVideo][0];
  131. }
  132. CMTime insertionPoint = kCMTimeZero;
  133. NSError *error = nil;
  134. // 添加视频轨道和素材 并裁剪视频
  135. if (assetVideoTrack != nil) {
  136. // 视频通道 工程文件中的轨道,有音频轨、视频轨等,里面可以插入各种对应的素材
  137. AVMutableCompositionTrack *compositionVideoTrack = [self.composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
  138. // 视频方向
  139. [compositionVideoTrack setPreferredTransform:assetVideoTrack.preferredTransform];
  140. // 把视频轨道数据加入到可变轨道中 这部分可以做视频裁剪TimeRange
  141. [compositionVideoTrack insertTimeRange:self.timeRange ofTrack:assetVideoTrack atTime:insertionPoint error:&error];
  142. [compositionVideoTrack scaleTimeRange:CMTimeRangeMake(kCMTimeZero, self.timeRange.duration) toDuration:CMTimeMake(self.timeRange.duration.value/self.rate, self.timeRange.duration.timescale)];
  143. }
  144. //视频方向
  145. UIImageOrientation orientation = [self orientationFromAVAssetTrack:assetVideoTrack];
  146. CGAffineTransform transform = CGAffineTransformIdentity;
  147. //视频素材原大小 像素大小px 不是pt
  148. CGSize renderSize = assetVideoTrack.naturalSize;
  149. switch (orientation) {
  150. case UIImageOrientationLeft:
  151. //顺时针旋转270°
  152. // NSLog(@"视频旋转270度,home按键在右");
  153. transform = CGAffineTransformTranslate(transform, 0.0, assetVideoTrack.naturalSize.width);
  154. transform = CGAffineTransformRotate(transform,M_PI_2*3.0);
  155. renderSize = CGSizeMake(assetVideoTrack.naturalSize.height,assetVideoTrack.naturalSize.width);
  156. break;
  157. case UIImageOrientationRight:
  158. //顺时针旋转90°
  159. // NSLog(@"视频旋转90度,home按键在左");
  160. transform = CGAffineTransformTranslate(transform, assetVideoTrack.naturalSize.height, 0.0);
  161. transform = CGAffineTransformRotate(transform,M_PI_2);
  162. renderSize = CGSizeMake(assetVideoTrack.naturalSize.height,assetVideoTrack.naturalSize.width);
  163. break;
  164. case UIImageOrientationDown:
  165. //顺时针旋转180°
  166. // NSLog(@"视频旋转180度,home按键在上");
  167. transform = CGAffineTransformTranslate(transform, assetVideoTrack.naturalSize.width, assetVideoTrack.naturalSize.height);
  168. transform = CGAffineTransformRotate(transform,M_PI);
  169. renderSize = CGSizeMake(assetVideoTrack.naturalSize.width,assetVideoTrack.naturalSize.height);
  170. break;
  171. default:
  172. break;
  173. }
  174. // _videoSize = renderSize;
  175. /** iOS9之前的处理方法,之后使用CIFilter ,待学习*/
  176. //方向
  177. if (orientation != UIImageOrientationUp || self.graffitiLayer || self.stickerLayers.count) {
  178. _videoComposition = [AVMutableVideoComposition videoComposition];
  179. _videoComposition.frameDuration = CMTimeMake(1, 30); // 30 fps
  180. _videoComposition.renderSize = renderSize;
  181. AVAssetTrack *videoTrack = [self.composition tracksWithMediaType:AVMediaTypeVideo][0];
  182. AVMutableVideoCompositionInstruction *roateInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
  183. roateInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, self.composition.duration);
  184. AVMutableVideoCompositionLayerInstruction *roateLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack];
  185. [roateLayerInstruction setTransform:transform atTime:kCMTimeZero];
  186. roateInstruction.layerInstructions = @[roateLayerInstruction];
  187. //将视频方向旋转加入到视频处理中
  188. _videoComposition.instructions = @[roateInstruction];
  189. /** 涂鸦 贴图 文字 */
  190. if(self.graffitiLayer || self.stickerLayers.count) {
  191. CALayer *parentLayer = [CALayer layer];
  192. CALayer *videoLayer = [CALayer layer];
  193. parentLayer.frame = CGRectMake(0, 0, renderSize.width, renderSize.height);
  194. videoLayer.frame = CGRectMake(0, 0, renderSize.width, renderSize.height);
  195. [parentLayer addSublayer:videoLayer];
  196. //涂鸦层
  197. if (self.graffitiLayer) {
  198. [parentLayer addSublayer:self.graffitiLayer];
  199. }
  200. //贴画层 和 文本层
  201. for (CALayer *gifLayer in self.stickerLayers) {
  202. [parentLayer addSublayer:gifLayer];
  203. }
  204. _videoComposition.animationTool = [AVVideoCompositionCoreAnimationTool videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer];
  205. }
  206. }
  207. }
  208. return _videoComposition;
  209. }
  210. - (AVMutableAudioMix *)audioMix {
  211. if (!_audioMix) {
  212. //音频轨道
  213. AVAssetTrack *assetAudioTrack = nil;
  214. if ([[self.asset tracksWithMediaType:AVMediaTypeAudio] count] != 0) {
  215. assetAudioTrack = [self.asset tracksWithMediaType:AVMediaTypeAudio][0];
  216. }
  217. CMTime insertionPoint = kCMTimeZero;
  218. NSError *error = nil;
  219. if (assetAudioTrack != nil && _isNativeAudio) {
  220. AVMutableCompositionTrack *compositionAudioTrack = [self.composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
  221. compositionAudioTrack.preferredTransform = assetAudioTrack.preferredTransform;
  222. [compositionAudioTrack insertTimeRange:self.timeRange ofTrack:assetAudioTrack atTime:insertionPoint error:&error];
  223. [compositionAudioTrack scaleTimeRange:CMTimeRangeMake(kCMTimeZero, self.timeRange.duration) toDuration:CMTimeMake(self.timeRange.duration.value/self.rate, self.timeRange.duration.timescale)];
  224. }
  225. /// 创建额外音轨特效
  226. NSMutableArray<AVAudioMixInputParameters *> *inputParameters;
  227. if (self.audioUrls.count) {
  228. inputParameters = [@[] mutableCopy];
  229. }
  230. /// 添加其他音频
  231. for (NSURL *audioUrl in self.audioUrls) {
  232. /** 声音采集 */
  233. AVURLAsset *audioAsset = [[AVURLAsset alloc]initWithURL:audioUrl options:nil];
  234. AVAssetTrack *additional_assetAudioTrack = nil;
  235. /** 检查是否有效音轨 */
  236. if ([[audioAsset tracksWithMediaType:AVMediaTypeAudio] count] != 0) {
  237. additional_assetAudioTrack = [audioAsset tracksWithMediaType:AVMediaTypeAudio][0];
  238. }
  239. if (additional_assetAudioTrack) {
  240. AVMutableCompositionTrack *additional_compositionAudioTrack = [self.composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
  241. additional_compositionAudioTrack.preferredTransform = additional_assetAudioTrack.preferredTransform;
  242. [additional_compositionAudioTrack insertTimeRange:self.timeRange ofTrack:additional_assetAudioTrack atTime:insertionPoint error:&error];
  243. [additional_compositionAudioTrack scaleTimeRange:CMTimeRangeMake(kCMTimeZero, self.timeRange.duration) toDuration:CMTimeMake(self.timeRange.duration.value/self.rate, self.timeRange.duration.timescale)];
  244. AVMutableAudioMixInputParameters *mixParameters = [AVMutableAudioMixInputParameters audioMixInputParametersWithTrack:additional_compositionAudioTrack];
  245. mixParameters.audioTimePitchAlgorithm = AVAudioTimePitchAlgorithmTimeDomain;
  246. [mixParameters setVolumeRampFromStartVolume:1 toEndVolume:0.3 timeRange:CMTimeRangeMake(kCMTimeZero, self.timeRange.duration)];
  247. [inputParameters addObject:mixParameters];
  248. }
  249. }
  250. if (inputParameters.count) {
  251. self.audioMix = [AVMutableAudioMix audioMix];
  252. self.audioMix.inputParameters = inputParameters;
  253. }
  254. }
  255. return _audioMix;
  256. }
  257. #pragma mark - Help Methods
  258. //视频的方向
  259. - (UIImageOrientation)orientationFromAVAssetTrack:(AVAssetTrack *)videoTrack {
  260. UIImageOrientation orientation = UIImageOrientationUp;
  261. CGAffineTransform t = videoTrack.preferredTransform;
  262. if(t.a == 0 && t.b == 1.0 && t.c == -1.0 && t.d == 0){
  263. // Portrait
  264. // degress = 90;
  265. orientation = UIImageOrientationRight;
  266. }else if(t.a == 0 && t.b == -1.0 && t.c == 1.0 && t.d == 0){
  267. // PortraitUpsideDown
  268. // degress = 270;
  269. orientation = UIImageOrientationLeft;
  270. }else if(t.a == 1.0 && t.b == 0 && t.c == 0 && t.d == 1.0){
  271. // LandscapeRight
  272. // degress = 0;
  273. orientation = UIImageOrientationUp;
  274. }else if(t.a == -1.0 && t.b == 0 && t.c == 0 && t.d == -1.0){
  275. // LandscapeLeft
  276. // degress = 180;
  277. orientation = UIImageOrientationDown;
  278. }
  279. return orientation;
  280. }
  281. @end