现手机里有一段视频,通过APP给他添加一个水印。iOS提供了在视频上添加layer的接口,添加一个水印还是很方便的(添加水印)。添加完水印有一个渲染过程,在手机这种设备上还是比较慢的,比如:对1分钟的高清视频(960x540)进行渲染需要20秒左右。如何在现有API基础上提高渲染速度,提升用户体验,成了问题。笔者发现两种渲染方法:
        先看图,这有一个6秒的视频,我抓了四张关键帧。只在第2,3两张关键帧上添加字幕(一个关键帧代表1.5秒。所以,两个关键帧就代表是3秒时长)

第一种方案:视频分割 + 逐段渲染 + 合并
            将视频分割为3端,即第1、第2,3、第4。有水印的是一组,没水印也是一组。对第2,3渲染。最后将三段视频合并到一起。
        第二种方案:整体渲染
            水印虽然不是从一开始出现,但是,我们可以对layer添加动画(参考下面OC代码)。
举个例子,假如水印少的情况。比如只对第一帧添加水印。分割渲染方案是3.0秒,整体渲染方案是3.5秒。假如水印多的情况,第1,2,3帧都有水印。分割渲染方案是4.1秒,整体渲染方案还是3.5秒。通过结果得出一点结论:整体渲染,无论水印多还是少,耗时是一样的。分割渲染怎么会有这么个差别呢?
        分割渲染:由于涉及三步:1.分割,2.渲染视频,3.合并视频。每一步都需要时间。根据测试经验,第一步分割视频的时间很少,可以忽略。那么就剩下渲染和合并时间了。就拿刚才6秒的视频来说:一张水印和三张水印。合并视频时间是一样的。都是两段(一段1.5秒,另外一段4.5秒)。但是水印多了渲染时间就长了。
        最后得出结论:水印少的时候,使用分割渲染方法。水印多的情况使用整体渲染。

整体渲染代码:

#define kEffectVideoFileName_Animation @"tmpMov-effect.mov"- (void)renderWholeVideo:(AVAsset *)asset{// 1 - Early exit if there's no video file selectedif (!asset) {UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error" message:@"Please Load a Video Asset First"delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];[alert show];return;}// 2 - Create AVMutableComposition object. This object will hold your AVMutableCompositionTrack instances.AVMutableComposition *mixComposition = [[AVMutableComposition alloc] init];// 3 - Video trackAVMutableCompositionTrack *videoTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideopreferredTrackID:kCMPersistentTrackID_Invalid];[videoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, asset.duration)ofTrack:[[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0]atTime:kCMTimeZero error:nil];AVMutableCompositionTrack *audioTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];[audioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, asset.duration) ofTrack:[asset tracksWithMediaType:AVMediaTypeAudio][0] atTime:kCMTimeZero error:nil];// 3.1 - Create AVMutableVideoCompositionInstructionAVMutableVideoCompositionInstruction *mainInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];mainInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, asset.duration);// 3.2 - Create an AVMutableVideoCompositionLayerInstruction for the video track and fix the orientation.__block     CGSize naturalSize;AVMutableVideoCompositionLayerInstruction *videolayerInstruction = [self transformVideo:asset track:videoTrack isVideoAssetPortrait:^(CGSize finalSize) {naturalSize = finalSize;}];[videolayerInstruction setOpacity:0.0 atTime:asset.duration];// 3.3 - Add instructionsmainInstruction.layerInstructions = [NSArray arrayWithObjects:videolayerInstruction,nil];AVMutableVideoComposition *mainCompositionInst = [AVMutableVideoComposition videoComposition];float renderWidth, renderHeight;renderWidth = naturalSize.width;renderHeight = naturalSize.height;mainCompositionInst.renderSize = CGSizeMake(renderWidth, renderHeight);mainCompositionInst.instructions = [NSArray arrayWithObject:mainInstruction];mainCompositionInst.frameDuration = CMTimeMake(1, 30);[self applyVideoEffectsWithAnimation:mainCompositionInst size:naturalSize];NSString *myPathDocs = [NSTemporaryDirectory() stringByAppendingPathComponent:kEffectVideoFileName_Animation];NSURL *url = [NSURL fileURLWithPath:myPathDocs];/*先移除旧文件*/[PublicUIMethod removeFile:url];// 5 - Create exporterAVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:mixCompositionpresetName:AVAssetExportPresetHighestQuality];[self.exportSessions addObject:exporter];exporter.outputURL=url;exporter.outputFileType = AVFileTypeQuickTimeMovie;exporter.shouldOptimizeForNetworkUse = YES;exporter.videoComposition = mainCompositionInst;weakifyself;[exporter exportAsynchronouslyWithCompletionHandler:^{strongifyself;dispatch_async(dispatch_get_main_queue(), ^{switch ([exporter status]) {case AVAssetExportSessionStatusFailed:DDLogWarn(@"render Export failed: %@ and order : %d", [exporter error], 0);break;case AVAssetExportSessionStatusCancelled:NSLog(@"render Export canceled order : %d", 0);break;default:{NSLog(@"'%@' render finish",[myPathDocs lastPathComponent]);[self pushToPreviePage:myPathDocs];}break;}});}];[self monitorSingleExporter:exporter];
}- (void)applyVideoEffectsWithAnimation:(AVMutableVideoComposition *)composition size:(CGSize)size
{// Set up layerCALayer *parentLayer = [CALayer layer];CALayer *videoLayer = [CALayer layer];parentLayer.frame = CGRectMake(0, 0, size.width, size.height);videoLayer.frame = CGRectMake(0, 0, size.width, size.height);[parentLayer addSublayer:videoLayer];/**/CMTime timeFrame = [self frameTime];CGFloat granularity = CMTimeGetSeconds(timeFrame);/*caption layer*/for (int j=0; j<self.effectsArray.count; j++) {NSArray* effectSeries = (NSArray *)self.effectsArray[j];FSVideoCaptionDescriptionModel *description = [[effectSeries firstObject] as:FSVideoCaptionDescriptionModel.class];NSArray *captions = [description reOrder];if (!captions || captions.count == 0) {//没有字幕就别瞎搞了continue;}FSCaptionModel *captionModel = captions.firstObject;UIImage *image = captionModel.image;/*将水印生成图片,采用图片方法添加水印*/CGFloat scaleY = captionModel.scaleY;CGFloat scaleHeight = captionModel.scaleHeight;CALayer *layer = [CALayer layer];layer.frame = CGRectMake(0, size.height * scaleY, size.width, size.height * scaleHeight);layer.contents = (__bridge id)image.CGImage;/*字幕动画由两个组成:1. 显示所有字幕<动画开始前保持,初始状态。动画时间是0.结束后不移除动画>2. 隐藏字幕,到最后。*/CGFloat showStartTime = description.startIndex * granularity;CGFloat hiddenAginStartTime = showStartTime + effectSeries.count*granularity;CABasicAnimation *animation = nil;if (showStartTime > 0) {animation = [CABasicAnimation animationWithKeyPath:@"opacity"];animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];[animation setFromValue:[NSNumber numberWithFloat:0.0]];[animation setToValue:[NSNumber numberWithFloat:1.0]];[animation setBeginTime:showStartTime];[animation setFillMode:kCAFillModeBackwards];/*must be backwards*/[animation setRemovedOnCompletion:NO];/*must be no*/[layer addAnimation:animation forKey:@"animateOpacityShow"];}/*最后一个字幕片段不是整的1.5s或者5秒。就不隐藏动画了*/if (j != self.effectsArray.count-1) {animation = [CABasicAnimation animationWithKeyPath:@"opacity"];animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];[animation setFromValue:[NSNumber numberWithFloat:1.0]];[animation setToValue:[NSNumber numberWithFloat:0.0]];[animation setBeginTime:hiddenAginStartTime];[animation setRemovedOnCompletion:NO];/*must be no*/[animation setFillMode:kCAFillModeForwards];[layer addAnimation:animation forKey:@"animateOpacityHiddenAgin"];}[parentLayer addSublayer:layer];}parentLayer.geometryFlipped = YES;composition.animationTool = [AVVideoCompositionCoreAnimationToolvideoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer];
}- (void)monitorSingleExporter:(AVAssetExportSession *)exporter{double delay = 1.0;int64_t delta = (int64_t)delay * NSEC_PER_SEC;dispatch_time_t poptime = dispatch_time(DISPATCH_TIME_NOW, delta);dispatch_after(poptime, dispatch_get_main_queue(), ^{if (exporter.status == AVAssetExportSessionStatusExporting) {NSLog(@"whole progress is %f",  exporter.progress);[self monitorSingleExporter:exporter];}});
}
-(AVMutableVideoCompositionLayerInstruction *) transformVideo:(AVAsset *)asset track:(AVMutableCompositionTrack *)firstTrack isVideoAssetPortrait:(void(^)(CGSize size))block{AVMutableVideoCompositionLayerInstruction *videolayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:firstTrack];AVAssetTrack *videoAssetTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];UIImageOrientation videoAssetOrientation_  = UIImageOrientationUp;BOOL isVideoAssetPortrait_  = NO;CGAffineTransform videoTransform = videoAssetTrack.preferredTransform;if (videoTransform.a == 0 && videoTransform.b == 1.0 && videoTransform.c == -1.0 && videoTransform.d == 0) {videoAssetOrientation_ = UIImageOrientationRight;videoTransform = CGAffineTransformMakeRotation(M_PI_2);videoTransform = CGAffineTransformTranslate(videoTransform, 0, -videoAssetTrack.naturalSize.height);isVideoAssetPortrait_ = YES;}if (videoTransform.a == 0 && videoTransform.b == -1.0 && videoTransform.c == 1.0 && videoTransform.d == 0) {videoAssetOrientation_ =  UIImageOrientationLeft;//这个地方很恶心,涉及到reveal看不到的坐标系videoTransform = CGAffineTransformMakeRotation(-M_PI_2);videoTransform = CGAffineTransformTranslate(videoTransform, - videoAssetTrack.naturalSize.width, 0);isVideoAssetPortrait_ = YES;}if (videoTransform.a == 1.0 && videoTransform.b == 0 && videoTransform.c == 0 && videoTransform.d == 1.0) {videoAssetOrientation_ =  UIImageOrientationUp;}if (videoTransform.a == -1.0 && videoTransform.b == 0 && videoTransform.c == 0 && videoTransform.d == -1.0) {videoTransform = CGAffineTransformMakeRotation(-M_PI);videoTransform = CGAffineTransformTranslate(videoTransform, -videoAssetTrack.naturalSize.width, -videoAssetTrack.naturalSize.height);
//        videoTransform = CGAffineTransformRotate(videoTransform, M_PI/180*45);videoAssetOrientation_ = UIImageOrientationDown;}[videolayerInstruction setTransform:videoTransform atTime:kCMTimeZero];CGSize naturalSize;if(isVideoAssetPortrait_){naturalSize = CGSizeMake(videoAssetTrack.naturalSize.height, videoAssetTrack.naturalSize.width);} else {naturalSize = videoAssetTrack.naturalSize;}if(block){block(naturalSize);}return videolayerInstruction;
}

[算法]iOS 视频添加水印,合成视频两种方案(整体渲染和分割渲染)相关推荐

  1. 实现两视频叠加时上层剪辑透明部分不遮挡下层内容的两种方案

    ☞ ░ 前往老猿Python博客 https://blog.csdn.net/LaoYuanPython ░ 一.引言 在<moviepy音视频剪辑:使用rotate函数实现视频变换处理以及参数 ...

  2. IOS平台生成图片缩略图的两种方案

    2013-04-16 15:48 2574人阅读 评论(0) 收藏 举报 IOS平台生成图片缩略图的两种方案 只写出关键部分,其它部分自己添加既可 1.自动缩放到指定大小 + (UIImage *)t ...

  3. iOS活动倒计时的两种实现方式

    代码地址如下: http://www.demodashi.com/demo/11076.html 在做些活动界面或者限时验证码时, 经常会使用一些倒计时突出展现. 现提供两种方案: 一.使用NSTim ...

  4. iOS开发笔记-两种单例模式的写法

    iOS开发笔记-两种单例模式的写法 单例模式是开发中最常用的写法之一,iOS的单例模式有两种官方写法,如下: 不使用GCD #import "ServiceManager.h"st ...

  5. 动态开辟二维数组的两种方案及位体

    动态开辟二维数组的两种方案及位体 一.在静态二维数组中查询数据 二.动态开辟二维数组空间 1.用二级指针的方式开辟 2.用结构体的方式开辟 三.位体 先来回顾一下动态开辟一位数组的方法: #inclu ...

  6. leetcode 349. 两个数组的交集 两种方案,c语言实现

    如题: 给定两个数组,编写一个函数来计算它们的交集.示例 1: 输入: nums1 = [1,2,2,1], nums2 = [2,2] 输出: [2]示例 2: 输入: nums1 = [4,9,5 ...

  7. Net Core下使用RabbitMQ比较完备两种方案(虽然代码有点惨淡,不过我会完善)

    一.前言     上篇说给大家来写C#和Java的方案,最近工作也比较忙,迟到了一些,我先给大家补上C#的方案. 二.使用的插件     HangFire 一个开源的.NET任务调度框架,最大特点在于 ...

  8. 解决IE6、IE7、IE8、Firefox兼容的两种方案

    转自:http://dyclh.iteye.com/blog/845349 浏览器不兼容,你知道源头吗? 凡事都要知其然,才能知其所以然,前端开发的朋友想毕都会碰到浏览器兼容的问题,今天在网上觅到此文 ...

  9. AI实现的两种方案,暴力推演与因果率

    AI实现的两种方案,暴力推演与因果率 学习PYTHON两个月,写个小游戏练手.也为以后找工作做储备. 从最简单的九格棋入手. 九格棋玩法简单,横向,纵向,斜向三子连线则为胜. 基本设计构件有: 一.G ...

最新文章

  1. 【错误记录】Flutter / Android 报错 ( AAPT: error: attribute android:requestLegacyExternalStorage not found )
  2. P1091 合唱队形(LIS)
  3. Java高并发编程:线程池
  4. opencv乱码java_opencv 放置文字 中文乱码处理 putText
  5. React Native 第六天
  6. qt按钮禁用和激活禁用_为什么试探法只是经验法则:禁用按钮的情况
  7. 第一百一十四期:盘点十大最新Web UI测试工具
  8. 大佬为何可以把单片机描述得如此形象生动?
  9. php jquery validate remote,jquery插件validate里面的remote参数用法
  10. 电力系统非线性控制_什么是谐波?电力系统谐波怎么产生的?老司机给你科普一下!...
  11. hbuilder的aptana php插件无法提示命名空间之外函数和对象的解决办法
  12. chrono0.10插件离线版_梁宝川:这11条anki插件的使用常识分享给你
  13. 300 行代码带你秒懂 Java 多线程!| 原力计划
  14. CSU 2151 集训难度(线段树)
  15. liferay开发小结, liferay瘦身一
  16. 解读《视觉SLAM十四讲》,带你一步一步入门视觉SLAM—— 前言
  17. java 实现敏感词汇的过滤
  18. 【Inpho精品教程】任务一:Inpho预处理准备(Pix4d生成未畸变图像、Pix4d生成相机参数文件)
  19. C++编码实现定时任务执行功能
  20. 桌面上计算机快捷方式打不开,桌面快捷方式打不开解决步骤

热门文章

  1. checkOrderInfo(检查订单信息)
  2. iphone5隐藏功能
  3. 文字转换音频的软件有哪些?分享好用的软件给你
  4. js获取canvas 的宽和高,到底是多少?
  5. HTML5期末大作业:蛋糕甜品网站设计——蛋糕甜品(4页) HTML+CSS+JavaScript 美食甜品网页设计`零食小吃成品网页`生鲜水果
  6. 微型计算机 总结,微机室工作总结范文
  7. 半年涨粉千万,谁在追“探店”的风口?
  8. 约瑟夫生者死者小游戏,python实现
  9. AIML知识库数据匹配原理解析
  10. 解决CAA百科全书中search无法加载的问题。