iOS 使用圖片制作視頻(一)

因為近期項目需要實現(xiàn)類似iOS 10相冊中的回憶功能:利用照片或圖片合成制作視頻的功能器紧。
這篇文章的目的是做一個簡單的分享,如何利用CALayer自脯、CAAnimation和視頻合成的相關(guān)知識點來完成這一功能的開發(fā)之景。
設(shè)計思路:


流程.png

流程上首先我們需要獲取系統(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

參考資料

Core Animation

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末装黑,一起剝皮案震驚了整個濱河市副瀑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌恋谭,老刑警劉巖糠睡,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異疚颊,居然都是意外死亡狈孔,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門材义,熙熙樓的掌柜王于貴愁眉苦臉地迎上來均抽,“玉大人,你說我怎么就攤上這事其掂∮突樱” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵款熬,是天一觀的道長深寥。 經(jīng)常有香客問我,道長贤牛,這世上最難降的妖魔是什么惋鹅? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮殉簸,結(jié)果婚禮上闰集,老公的妹妹穿的比我還像新娘。我一直安慰自己般卑,他們只是感情好武鲁,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蝠检,像睡著了一般洞坑。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蝇率,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天迟杂,我揣著相機(jī)與錄音,去河邊找鬼本慕。 笑死排拷,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的锅尘。 我是一名探鬼主播监氢,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼藤违!你這毒婦竟也來了浪腐?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤顿乒,失蹤者是張志新(化名)和其女友劉穎议街,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體璧榄,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡特漩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了骨杂。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片涂身。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖搓蚪,靈堂內(nèi)的尸體忽然破棺而出蛤售,到底是詐尸還是另有隱情,我是刑警寧澤妒潭,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布悴能,位于F島的核電站,受9級特大地震影響杜耙,放射性物質(zhì)發(fā)生泄漏搜骡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一佑女、第九天 我趴在偏房一處隱蔽的房頂上張望记靡。 院中可真熱鬧,春花似錦团驱、人聲如沸摸吠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽寸痢。三九已至,卻和暖如春紊选,著一層夾襖步出監(jiān)牢的瞬間啼止,已是汗流浹背道逗。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留献烦,地道東北人滓窍。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像巩那,于是被迫代替她去往敵國和親吏夯。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容