因為近期項目需要實現(xiàn)類似iOS 10相冊中的回憶功能:利用照片或圖片合成制作視頻的功能器紧。
這篇文章的目的是做一個簡單的分享,如何利用CALayer自脯、CAAnimation和視頻合成的相關(guān)知識點來完成這一功能的開發(fā)之景。
設(shè)計思路:
流程上首先我們需要獲取系統(tǒng)相冊里的照片和視頻,選擇好后經(jīng)過一些列處理將他們合成為一個視頻膏潮,我們還需要對原視頻進(jìn)行逐幀分解锻狗,為什么這樣做我會再后面進(jìn)行解答。
省略掉從系統(tǒng)相冊獲取視頻和圖片的部分焕参,我們分別從處理圖片和視頻開始轻纪。
我需要做的是將圖片合成視頻并能夠和系統(tǒng)相冊中的視頻再合并成一個視頻,并能夠在預(yù)覽的時候有一個很好的交互叠纷。我在這里考慮使用CALayer和CAAnimation刻帚。通過給一個view的layer添加Animation實現(xiàn)內(nèi)容切換大小改變甚至是淡入淡出的漸變效果。
CALayer
CALayer是一個管理基于圖像的內(nèi)容的對象涩嚣,它可以使我們在該內(nèi)容上執(zhí)行動畫的對象崇众。圖層的主要工作是管理我們的視覺內(nèi)容,但圖層本身具有可設(shè)置的視覺屬性航厚,例如背景顏色顷歌,邊框和陰影。除了管理可視內(nèi)容之外幔睬,該層還可以用于維護(hù)在屏幕上呈現(xiàn)的幾何信息(例如其位置眯漩,大小和變換)。一個圖層對象包含了持續(xù)時間和它的走向麻顶,它基于支持CAMediaTiming協(xié)議的動畫赦抖,定義了這個圖層的時間信息。
CAAnimation
CAAnimation 是核心動畫的抽象超類辅肾。它為CAMediaTiming和CAAction協(xié)議提供基本的支持队萤。
我們要做的事情就是利用CAAnimation在CALayer上執(zhí)行各個動畫。
我們有一個UIImageView宛瞄,我們將會把我們的圖片按照順序依次顯示在這個UIImageView上浮禾。
圖片處理
我們首先是定義一個動畫組Group來放入我們所有的動畫。
CAAnimationGroup *group = [CAAnimationGroup animation];
設(shè)置某一幀的關(guān)鍵動畫顯示這張圖片
CAKeyframeAnimation * contentsAnimation;
contentsAnimation = [CAKeyframeAnimation animationWithKeyPath:@"contents"];
contentsAnimation.duration = 0.5f;
contentsAnimation.removedOnCompletion = NO;
contentsAnimation.fillMode = kCAFillModeForwards;
UIImage *image = /**你的圖片**/;
contentsAnimation.values = @[(__bridge UIImage*)image.CGImage];
contentsAnimation.beginTime = totalDuration;
[animations addObject:contentsAnimation];
這是某一張圖片的淡入效果:
CAKeyframeAnimation * showAnimation;
showAnimation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"];
showAnimation.duration = 0.5;
showAnimation.removedOnCompletion = NO;
showAnimation.fillMode = kCAFillModeForwards;
showAnimation.values = @[[NSNumber numberWithFloat:0.0],[NSNumber numberWithFloat:1.0]];
showAnimation.beginTime = AVCoreAnimationBeginTimeAtZero;
[animations addObject:showAnimation];
為了防止圖片變形和拉伸份汗,記得設(shè)置此時顯示圖片的Layer的大小
CAKeyframeAnimation * boundsAnimation;
boundsAnimation = [CAKeyframeAnimation animationWithKeyPath:@"bounds"];
boundsAnimation.duration = 0.5f;
boundsAnimation.removedOnCompletion = NO;
boundsAnimation.fillMode = kCAFillModeForwards;
boundsAnimation.values = @[[NSValue valueWithCGRect:CGRectMake(xPoint,yPoint,imageWidth,imageHeight)]];
boundsAnimation.beginTime = AVCoreAnimationBeginTimeAtZero;
[animations addObject:boundsAnimation];
這之后0.5s后給它加個效果吧,比如放大
CAKeyframeAnimation *scaleAnimation;
scaleAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"];
scaleAnimation.duration = 1.0f;
scaleAnimation.removedOnCompletion = NO;
scaleAnimation.fillMode = kCAFillModeForwards;
scaleAnimation.values = @[[NSNumber numberWithFloat:1],[NSNumber numberWithFloat:2.0]];
scaleAnimation.beginTime = 0.5f;
[animations addObject:scaleAnimation];
在展示了這一張圖片后蝴簇,我們該讓它淡出舞臺了
CAKeyframeAnimation * dissAnimation;
dissAnimation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"];
dissAnimation.duration = 0.5;
dissAnimation.removedOnCompletion = NO;
dissAnimation.fillMode = kCAFillModeForwards;
dissAnimation.values = @[[NSNumber numberWithFloat:1.0],[NSNumber numberWithFloat:0.8],[NSNumber numberWithFloat:0.4],[NSNumber numberWithFloat:0.0]];
dissAnimation.beginTime = totalDuration;
[animations addObject:dissAnimation];
如此循環(huán)杯活,我們就可以展示一系列的圖片了。
PS:補(bǔ)充一下 animationWithKeyPath可以使用的值:
transform.scale
transform.scale.x
transform.scale.y
transform.rotation.z
opacity
margin
zPosition
backgroundColor
cornerRadius
borderWidth
bounds
contents
contentsRect
cornerRadius
frame
hidden
mask
masksToBounds
opacity
position
shadowColor
shadowOffset
shadowOpacity
shadowRadius
視頻處理
下面我將開始處理視頻熬词。
因為之前在利用CALayer和CAAnimation處理一些動畫效果時旁钧,用上了gif圖片來展示一系列的動畫效果吸重。這里我們也運用類似的方式把視頻放入我們的動畫Group中。
處理視頻首先我們是需要逐幀分解這個視頻:
CGSize targetSize = CGSizeMake(600,600);
int fps = 20;
AVAssetImageGenerator *imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:asset];
imageGenerator.requestedTimeToleranceBefore = kCMTimeZero;
imageGenerator.requestedTimeToleranceAfter = kCMTimeZero;
imageGenerator.apertureMode = AVAssetImageGeneratorApertureModeProductionAperture;
imageGenerator.appliesPreferredTrackTransform = YES;
imageGenerator.maximumSize = CGSizeMake(targetSize.width/2.0, targetSize.height/2.0);
CMTime cmtime = asset.duration; //視頻時間信息結(jié)構(gòu)體
Float64 durationSeconds = CMTimeGetSeconds(cmtime)>3?3:CMTimeGetSeconds(cmtime); //視頻總秒數(shù)歪今,這里我們只取3秒以內(nèi)的部分嚎幸。
NSMutableArray *times = [NSMutableArray array];
Float64 totalFrames = durationSeconds * fps; //獲得視頻總幀數(shù)
CMTime timeFrame;
for (int i = 1; i <= totalFrames; i++) {
timeFrame = CMTimeMake(i, fps); //第i幀 幀率
NSValue *timeValue = [NSValue valueWithCMTime:timeFrame];
[times addObject:timeValue];
}
NSMutableArray *videoThumbArray = [NSMutableArray array];
[imageGenerator generateCGImagesAsynchronouslyForTimes:times
completionHandler:^(CMTime requestedTime,
CGImageRef _Nullable image,
CMTime actualTime,
AVAssetImageGeneratorResult result,
NSError * _Nullable error)
{
if (result == AVAssetImageGeneratorSucceeded)
{
UIImage *tempImage = [UIImage imageWithCGImage:image];
[videoThumbArray addObject:tempImage];
if (requestedTime.value == times.count)
{
NSLog(@"搞定");
}
}
else
{
NSLog(@"獲取視頻截圖出錯");
}
}];
然后我們便拿到了分解出來的視頻逐幀圖片數(shù)組。類似圖片處理的部分寄猩,我們將它放入我們的Group并設(shè)定好beginTime嫉晶。
CAKeyframeAnimation * contentsAnimation;
contentsAnimation = [CAKeyframeAnimation animationWithKeyPath:@"contents"];
contentsAnimation.duration = /**視頻的時長**/;
contentsAnimation.removedOnCompletion = NO;
contentsAnimation.fillMode = kCAFillModeForwards;
NSMutableArray *values = [NSMutableArray array];
for (int v = 0; v<videoThumbArray.count; v++)
{
UIImage *tempImage = videoThumbArray[v];
[values addObject:(__bridge UIImage*)tempImage.CGImage];
}
contentsAnimation.values = values;
contentsAnimation.beginTime = /**開始時間**/;
[animations addObject:contentsAnimation];
我們也可以像圖片那樣給這段視頻的前后加入淡入淡出的轉(zhuǎn)換動畫。完成后我們便可以把這個Group加入我們想要用于顯示的Layer中了田篇。
播放
接下來就是要實現(xiàn)如何播放這一些列的動畫了替废。但這之前還是要對幾個屬性和概念進(jìn)行了解。
CAMediaTiming協(xié)議
該協(xié)議通過涂層和動畫實現(xiàn)泊柬,它構(gòu)建了一個分層的計時系統(tǒng)椎镣,當(dāng)中的所有對象都描述了其從父對象中的時間值到本地時間的映射。
進(jìn)行這樣一個映射或轉(zhuǎn)換兽赁,需要兩個步驟:
(1)轉(zhuǎn)換為活動的本地時間状答。該活動時間包括了該對象出現(xiàn)在父級時間軸上的點,以及與父級的相對速度刀崖。
(2)從活動時間轉(zhuǎn)換為本地基本時間惊科。時間模型允許對象多次重復(fù)其基本持續(xù)時間,并且可選地在重復(fù)之前向后播放蒲跨。
當(dāng)把這個讓人摸不著頭腦的協(xié)議拿出來談著后译断,我們實際需要的是它的屬性,speed和timeOffset或悲。
speed
圖層的速度孙咪,用于將父類時間縮放至本地時間。例如巡语,若當(dāng)前對象或圖層速度為2翎蹈,那么它相對于父類其速度是父類的2倍。
timeOffset
活動時間的偏移值男公。父類時間轉(zhuǎn)換至活動的本地時間存在著這么一個公式:t = (tp - begin) * speed + offset.
提到這兩個屬性是因為我們需要它們來暫停和播放我們的預(yù)覽視頻荤堪。我們將所有的動畫放入Group中,把它添加在一個speed為0的圖層上枢赔,然后運用類似Timer的方式澄阳,以60幀速率播放每一幀的動畫,即設(shè)置這個圖層的timeOffset踏拜,從而實現(xiàn)我們對這些動畫的播放碎赢、暫停和定位。
CADisplayLink
在這里速梗,我們使用CADisplayLink來作為Timer來刷新我們的動畫肮塞。CADisplayLink是一個與屏幕刷新率相同的Timer類襟齿。之所以使用它是因為相較于NSTimer,CADisplayLink的觸發(fā)時間更加精準(zhǔn),更適合用于一幀一幀地播放我們Group中的動畫枕赵。在播放時我們只需要如上文所述調(diào)整圖層的timeOffset即可猜欺。
target.layer.timeOffset += 1.0/60.0;
CADisplayLink也可以很方便地進(jìn)行暫停等操作。
在完成了圖片合成視頻的預(yù)覽之后拷窜,我們可以通過AVComposition和AVAssetExportSession真正地合成完整的視頻文件开皿,還可以加入音樂等處理。
這個是本文章DEMO的地址:Github