一. 引言
視頻變速(Speed Ramp)是視頻編輯中最常見的特效之一:
- 慢動作(Slow Motion):強調細節,讓觀眾捕捉到肉眼難以察覺的瞬間;
- 快動作(Fast Motion):壓縮時長,強化節奏,常用于 vlog、綜藝片段。
在 AVFoundation 中,視頻變速的本質是?對音視頻軌道的時間線進行重新映射。通過調整時間范圍(CMTimeRange)與目標時長(toDuration),即可讓視頻和音頻實現同步的快進或慢放效果。
本文將通過一個完整的?Demo 實戰,展示如何在 iOS 中使用 AVFoundation 實現視頻的變速處理,涵蓋從模型設計到合成器構建的完整流程。
二. 核心思路回顧
要實現變速,必須同時處理?視頻軌道?與?音頻軌道,以保證二者的同步:
1. 視頻軌道
- 借助?AVMutableVideoCompositionInstruction?和?AVMutableVideoCompositionLayerInstruction,保證變速后的視頻能夠正常渲染。
- 關鍵在于:
- videoTrack.scaleTimeRange():調整視頻的時間范圍到新的時長;
- instruction.timeRange:指定變速后的可見區間;
- videoComposition:控制整體渲染(幀率、尺寸等)。
2. 音頻軌道
- 音頻不涉及渲染和圖層,處理方式更簡單。
- 只需調用?audioTrack.scaleTimeRange(),將某一時間段的音頻拉伸或壓縮到新的時長,從而實現變速效果。
三. Demo 架構設計
在本次 Demo 中,我們延續前文的?時間線(TimeLine)驅動合成?的設計思路。通過定義模型與 Builder,將變速邏輯解耦,使其可以靈活擴展。
1. PHSpeedItem —— 變速模型
class PHSpeedItem: PHMediaItem {/// 變速的倍速var scaleSpeed: Float = 2.0
}
PHSpeedItem?繼承自?PHMediaItem,用于描述一段變速操作。它包含了:
- startTime:變速的起始時間點;
- timeRange:變速的持續時長;
- scaleSpeed:變速的倍速(例如?2.0?表示慢兩倍,0.5?表示快兩倍)。
這樣,我們就能精確地定義:從視頻的某個時間點開始,持續多少秒,需要以什么速度播放。
2. PHTimeLine —— 時間線模型
class PHTimeLine: NSObject {var videoItmes = [PHVideoItem]()var audioItems = [PHAudioItem]()var musicItems = [PHMusicItem]()var maskItem = PHMaskItem(text: "PHVideoExample",image: UIImage(named: "mask1"),bounds: CGRect(x: 0, y: 0, width: 1280, height: 720))var seepItems = [PHSpeedItem]()
}
PHTimeLine?是整個編輯流程的核心。它聚合了:
- 視頻軌道(videoItems)
- 音頻軌道(audioItems)
- 背景音樂(musicItems)
- 水印(maskItem)
- 變速效果(seepItems)
通過在?buildTimeLine?時一次性構建這些屬性,后續的 CompositionBuilder 只需要解析?timeLine?即可。
3. PHSpeedCompositionBuilder —— 合成器
class PHSpeedCompositionBuilder: PHComositionBuilder {private let composition = AVMutableComposition()private let timeLine: PHTimeLineprivate var videoComposition: AVMutableVideoCompositionprivate var audioMix: AVAudioMix?init(timeLine: PHTimeLine) {self.timeLine = timeLineself.videoComposition = AVMutableVideoComposition()videoComposition.frameDuration = CMTime(value: 1, timescale: 30)videoComposition.renderSize = CGSize(width: 1280, height: 720)}func buildComposition() -> (any PHComposition)? {// 添加視頻 & 音頻軌道guard let videoTrack = self.addTrack(with: .video, mediaItems: self.timeLine.videoItmes),let audioTrack = self.addTrack(with: .audio, mediaItems: self.timeLine.audioItems) else {return nil}// 遍歷變速片段for seepItem in timeLine.seepItems {self.applySpeed(to: videoTrack,audioTrack: audioTrack,startTime: seepItem.startTime,duration: seepItem.timeRange.duration,scaleSpeed: seepItem.scaleSpeed)}return PHSpeedComposition(composition: composition,videoComposition: videoComposition,audioMix: audioMix)}
}
在?PHSpeedCompositionBuilder?中,我們做了三件核心的事:
- 初始化渲染配置:幀率(30fps)、渲染尺寸(1280x720)。
- 加載軌道:將視頻、音頻素材插入到?AVMutableComposition。
- 應用變速:遍歷?seepItems,對每個變速區間調用?applySpeed,從而實現快動作/慢動作。
四. 變速核心實現
視頻變速的核心邏輯集中在?applySpeed?方法中。它的作用是:
- 調整?視頻軌道?的播放時長,實現快動作或慢動作;
- 調整?音頻軌道?的播放時長,保持與視頻同步;
- 更新?視頻合成指令,確保渲染時長正確。
來看代碼:
/// 應用變速效果
/// - Parameters:
/// - videoTrack: 視頻軌道
/// - audioTrack: 音頻軌道
/// - startTime: 變速開始時間
/// - duration: 變速持續時間
/// - scaleSpeed: 變速比例,例如 2.0 表示慢動作,0.5 表示快動作
private func applySpeed(to videoTrack: AVMutableCompositionTrack,audioTrack: AVMutableCompositionTrack,startTime: CMTime,duration: CMTime,scaleSpeed: Float) {let instruction = AVMutableVideoCompositionInstruction()// 計算變速后的時長let scaledDuration = CMTimeMultiplyByFloat64(duration, multiplier: Float64(scaleSpeed))let totalDuration = CMTimeSubtract(videoTrack.timeRange.duration, duration) + scaledDuration// 設置渲染時長instruction.timeRange = CMTimeRange(start: .zero, duration: totalDuration)let layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)instruction.layerInstructions = [layerInstruction]videoComposition.instructions.append(instruction)// 視頻變速:修改時間區間videoTrack.scaleTimeRange(CMTimeRange(start: startTime, duration: duration),toDuration: scaledDuration)// 音頻變速:保持同步let audioDuration = CMTimeMultiplyByFloat64(duration, multiplier: Float64(scaleSpeed))audioTrack.scaleTimeRange(CMTimeRange(start: startTime, duration: duration),toDuration: audioDuration)print("視頻軌道總時長: \(CMTimeGetSeconds(videoTrack.timeRange.duration)) 秒")
}
1. 核心計算公式
let scaledDuration = duration * scaleSpeed
let totalDuration = (原始總時長 - duration) + scaledDuration
- scaledDuration:表示?變速區間在新速度下的時長;
- totalDuration:表示?整條視頻在變速后新的總時長。
👉 這一步非常關鍵。如果直接把?instruction.timeRange?設置為?scaledDuration,就會出現畫面丟失的問題。必須用?totalDuration?來覆蓋渲染范圍,確保整個視頻能正常播放。
2. 視頻軌道處理
videoTrack.scaleTimeRange(CMTimeRange(start: startTime, duration: duration),toDuration: scaledDuration)
這一步會將?startTime ~ duration?的視頻片段?拉伸/壓縮?到新的時長,實現快/慢動作。
- scaleSpeed = 2.0?→ 時長 *2 → 慢動作;
- scaleSpeed = 0.5?→ 時長 *0.5 → 快動作。
3. 音頻軌道處理
audioTrack.scaleTimeRange(CMTimeRange(start: startTime, duration: duration),toDuration: audioDuration)
音頻的邏輯與視頻相同,只是?不需要渲染指令。通過調整?timeRange,即可保證音視頻同步。
4. 視頻 vs 音頻 的區別
- 視頻軌道:必須結合?AVMutableVideoCompositionInstruction?來更新渲染時長,否則會丟畫面。
- 音頻軌道:只需調整?scaleTimeRange?即可,不需要合成指令。
五. 實戰效果驗證
在前面,我們已經完成了?PHSpeedCompositionBuilder?的核心實現。現在,只需要在 Demo 中構建一個?PHTimeLine,并添加?PHSpeedItem,就能快速驗證效果。
1. 構建 TimeLine
// 構建帶變速的時間線
let timeLine = PHTimeLine.buildTimeLine(with: items)// 假設我們在第 2 秒開始,持續 3 秒,并讓視頻變慢 2 倍
let speedItem = PHSpeedItem()
speedItem.startTime = CMTime(seconds: 2, preferredTimescale: 600)
speedItem.timeRange = CMTimeRange(start: speedItem.startTime,duration: CMTime(seconds: 3, preferredTimescale: 600))
speedItem.scaleSpeed = 2.0timeLine.seepItems = [speedItem]
2. 構建 Composition
let builder = PHSpeedCompositionBuilder(timeLine: timeLine)
guard let composition = builder.buildComposition() else {print("? 構建 Composition 失敗")return
}
3. 播放或導出
如果原始視頻是?15 秒:
- 在第?2 ~ 5 秒的區間變慢 2 倍 → 該區間時長變為 6 秒;
- 總時長 =?15 - 3 + 6 = 18 秒;
- 播放時可以清晰看到?2s → 5s 片段被拉長。
六. 結語
在本文中,我們完整實現了?視頻變速處理,并通過 Demo 驗證了其效果。核心思想是:
- 視頻軌道:通過?scaleTimeRange?拉伸或壓縮片段時長,并結合?AVMutableVideoCompositionInstruction?和?AVMutableVideoCompositionLayerInstruction?確保畫面渲染正確。
- 音頻軌道:相比視頻更為簡單,僅需調整?timeRange?即可保證與視頻保持同步。
在實戰中,我們實現了?局部變速?的支持,例如在第 2 秒 ~ 5 秒區間執行 2 倍慢動作,總時長相應調整為 18 秒。通過這種方式,我們不僅能夠處理?整體變速,也能靈活地在指定區間內應用變速效果。
不過,本篇案例依然有一個簡化假設:從一個起點到一個終點單段變速。而在實際開發中,用戶往往希望在多個不同的區間應用不同的變速效果(例如“先快 → 再慢 → 再恢復正常”)。這會帶來更復雜的軌道管理、區間拼接和指令疊加問題。