Swift音频与视频处理
一、Swift 音频处理基础
1.1 音频框架概述
在 Swift 中进行音频处理,最常用的框架是 AVFoundation
。AVFoundation
提供了一个全面的、基于对象的框架,用于处理基于时间的视听媒体数据。它涵盖了从简单的音频播放到复杂的音频编辑和录制等广泛功能。例如,AVAudioPlayer
类用于播放音频,而 AVAudioRecorder
类用于录制音频。
1.2 播放音频 - AVAudioPlayer
- 初始化与设置
要使用
AVAudioPlayer
播放音频,首先需要获取音频文件的路径并初始化播放器。假设我们有一个名为example.mp3
的音频文件放在项目的资源目录中:
import AVFoundation
class AudioPlayerViewController: UIViewController {
var audioPlayer: AVAudioPlayer?
override func viewDidLoad() {
super.viewDidLoad()
guard let url = Bundle.main.url(forResource: "example", withExtension: "mp3") else {
print("音频文件未找到")
return
}
do {
audioPlayer = try AVAudioPlayer(contentsOf: url)
audioPlayer?.prepareToPlay()
} catch let error {
print("初始化音频播放器时出错: \(error.localizedDescription)")
}
}
}
在上述代码中,我们首先通过 Bundle.main.url(forResource:withExtension:)
方法获取音频文件的 URL。然后使用这个 URL 初始化 AVAudioPlayer
。prepareToPlay()
方法会在播放音频之前进行一些必要的准备工作,比如分配音频资源。
- 控制播放 一旦初始化完成,就可以控制音频的播放了。常见的控制操作包括播放、暂停和停止:
// 播放音频
audioPlayer?.play()
// 暂停音频
audioPlayer?.pause()
// 停止音频
audioPlayer?.stop()
此外,还可以设置音频的音量、播放次数等属性:
// 设置音量,范围是 0.0 到 1.0
audioPlayer?.volume = 0.5
// 设置循环次数,0 表示不循环,-1 表示无限循环
audioPlayer?.numberOfLoops = -1
1.3 录制音频 - AVAudioRecorder
- 设置音频会话与录制设置 在录制音频之前,需要配置音频会话,以确保应用程序能够访问音频输入设备。同时,还需要设置录制的参数,如音频格式、采样率等。
import AVFoundation
class AudioRecorderViewController: UIViewController, AVAudioRecorderDelegate {
var audioRecorder: AVAudioRecorder?
override func viewDidLoad() {
super.viewDidLoad()
let audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(.record, mode:.default)
try audioSession.setActive(true)
} catch let error {
print("设置音频会话时出错: \(error.localizedDescription)")
}
let recordSettings: [String: Any] = [
AVFormatIDKey: kAudioFormatMPEG4AAC,
AVSampleRateKey: 44100,
AVNumberOfChannelsKey: 1,
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
]
guard let url = FileManager.default.urls(for:.documentDirectory, in:.userDomainMask).first?.appendingPathComponent("recorded_audio.m4a") else {
print("无法获取录制文件的路径")
return
}
do {
audioRecorder = try AVAudioRecorder(url: url, settings: recordSettings)
audioRecorder?.delegate = self
audioRecorder?.prepareToRecord()
} catch let error {
print("初始化音频录制器时出错: \(error.localizedDescription)")
}
}
}
在上述代码中,我们首先设置音频会话的类别为 .record
并激活它。然后定义了录制设置,这里使用了 AAC 音频格式,采样率为 44100Hz,单声道,高质量编码。接着获取了应用程序文档目录中的一个路径,用于保存录制的音频文件。最后初始化 AVAudioRecorder
并设置代理和准备录制。
- 控制录制 控制音频录制的操作包括开始录制、暂停录制和停止录制:
// 开始录制
audioRecorder?.record()
// 暂停录制
audioRecorder?.pause()
// 停止录制
audioRecorder?.stop()
当录制完成后,可以通过代理方法获取录制的结果:
extension AudioRecorderViewController {
func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) {
if flag {
print("音频录制成功,文件路径: \(recorder.url)")
} else {
print("音频录制失败")
}
}
}
二、Swift 音频处理进阶
2.1 音频编辑
- 音频剪辑
音频剪辑是音频编辑中常见的操作之一。在
AVFoundation
中,可以使用AVMutableComposition
类来实现音频剪辑。假设我们要从一个音频文件中截取一段音频:
import AVFoundation
func clipAudio(sourceURL: URL, start: CMTime, duration: CMTime) -> URL? {
let composition = AVMutableComposition()
guard let audioAsset = AVAsset(url: sourceURL),
let audioTrack = composition.addMutableTrack(withMediaType:.audio, preferredTrackID: kCMPersistentTrackID_Invalid) else {
return nil
}
do {
try audioTrack.insertTimeRange(CMTimeRange(start: start, duration: duration), of: audioAsset.tracks(withMediaType:.audio)[0], at: CMTime.zero)
} catch let error {
print("插入音频片段时出错: \(error.localizedDescription)")
return nil
}
let outputURL = FileManager.default.temporaryDirectory.appendingPathComponent("clipped_audio.m4a")
if FileManager.default.fileExists(atPath: outputURL.path) {
try? FileManager.default.removeItem(at: outputURL)
}
let exporter = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetAppleM4A)
exporter?.outputURL = outputURL
exporter?.outputFileType =.m4a
exporter?.exportAsynchronously(completionHandler: {
switch exporter?.status {
case.finished:
print("音频剪辑成功,输出路径: \(outputURL)")
case.failed:
print("音频剪辑失败: \(String(describing: exporter?.error))")
default:
break
}
})
return outputURL
}
在上述代码中,我们首先创建了一个 AVMutableComposition
对象,然后从源音频文件的 AVAsset
中获取音频轨道,并将指定时间范围的音频片段插入到 AVMutableComposition
的音频轨道中。接着设置输出文件的路径,并使用 AVAssetExportSession
将合成的音频导出为新的文件。
- 音频拼接
音频拼接是将多个音频文件连接在一起。同样可以使用
AVMutableComposition
来实现:
func concatenateAudios(urls: [URL]) -> URL? {
let composition = AVMutableComposition()
guard let audioTrack = composition.addMutableTrack(withMediaType:.audio, preferredTrackID: kCMPersistentTrackID_Invalid) else {
return nil
}
var currentTime = CMTime.zero
for url in urls {
guard let audioAsset = AVAsset(url: url) else {
continue
}
let audioDuration = audioAsset.duration
do {
try audioTrack.insertTimeRange(CMTimeRange(start: CMTime.zero, duration: audioDuration), of: audioAsset.tracks(withMediaType:.audio)[0], at: currentTime)
} catch let error {
print("插入音频片段时出错: \(error.localizedDescription)")
return nil
}
currentTime = CMTimeAdd(currentTime, audioDuration)
}
let outputURL = FileManager.default.temporaryDirectory.appendingPathComponent("concatenated_audio.m4a")
if FileManager.default.fileExists(atPath: outputURL.path) {
try? FileManager.default.removeItem(at: outputURL)
}
let exporter = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetAppleM4A)
exporter?.outputURL = outputURL
exporter?.outputFileType =.m4a
exporter?.exportAsynchronously(completionHandler: {
switch exporter?.status {
case.finished:
print("音频拼接成功,输出路径: \(outputURL)")
case.failed:
print("音频拼接失败: \(String(describing: exporter?.error))")
default:
break
}
})
return outputURL
}
此代码中,我们遍历传入的音频文件 URL 数组,将每个音频文件的音频片段依次插入到 AVMutableComposition
的音频轨道中。插入的起始时间根据上一个音频片段的结束时间来确定。最后同样使用 AVAssetExportSession
导出拼接后的音频文件。
2.2 音频特效处理
- 音量调节特效
音量调节是一种基本的音频特效。在
AVFoundation
中,可以通过修改音频轨道的增益来实现音量调节。假设我们有一个AVMutableComposition
对象,要对其中的音频轨道进行音量调节:
func adjustVolume(composition: AVMutableComposition, gain: Float) {
guard let audioTrack = composition.tracks(withMediaType:.audio).first else {
return
}
let audioProcessingSettings: [String: Any] = [
AVVolumeAdjustmentKey: gain
]
let audioEffect = AVAudioUnitEQ()
audioEffect.loadFactoryPreset(.flat)
audioEffect.bypass = false
audioEffect.globalGain = gain
let audioMixer = AVAudioMixerNode()
let audioEngine = AVAudioEngine()
audioEngine.attach(audioEffect)
audioEngine.attach(audioMixer)
audioEngine.connect(audioTrack, to: audioEffect, format: audioTrack.format)
audioEngine.connect(audioEffect, to: audioMixer, format: audioTrack.format)
audioEngine.connect(audioMixer, to: audioEngine.mainMixerNode, format: audioTrack.format)
do {
try audioEngine.start()
} catch let error {
print("启动音频引擎时出错: \(error.localizedDescription)")
}
}
在上述代码中,我们首先获取 AVMutableComposition
中的音频轨道。然后创建了一个 AVAudioUnitEQ
对象,并设置其全局增益为指定的 gain
值。接着构建了一个简单的音频处理链,包括音频轨道、音频效果单元和音频混合器,最后启动音频引擎来应用音量调节效果。
- 回声特效
回声特效可以通过
AVAudioUnitReverb
来实现。以下是为音频添加回声特效的代码示例:
func addEcho(composition: AVMutableComposition) {
guard let audioTrack = composition.tracks(withMediaType:.audio).first else {
return
}
let audioEffect = AVAudioUnitReverb()
audioEffect.loadFactoryPreset(.cathedral)
audioEffect.wetDryMix = 50
audioEffect.density = 100
audioEffect.decay = 10
let audioMixer = AVAudioMixerNode()
let audioEngine = AVAudioEngine()
audioEngine.attach(audioEffect)
audioEngine.attach(audioMixer)
audioEngine.connect(audioTrack, to: audioEffect, format: audioTrack.format)
audioEngine.connect(audioEffect, to: audioMixer, format: audioTrack.format)
audioEngine.connect(audioMixer, to: audioEngine.mainMixerNode, format: audioTrack.format)
do {
try audioEngine.start()
} catch let error {
print("启动音频引擎时出错: \(error.localizedDescription)")
}
}
在这段代码中,我们创建了 AVAudioUnitReverb
对象,并加载了一个预设的回声效果(这里是“cathedral”预设)。通过调整 wetDryMix
、density
和 decay
等属性来控制回声的强度、密度和衰减时间。同样构建音频处理链并启动音频引擎来应用回声特效。
三、Swift 视频处理基础
3.1 视频框架简介
在 Swift 中进行视频处理,AVFoundation
依然是核心框架。它不仅能处理音频,还能处理视频的播放、录制、编辑等操作。此外,Core Video
框架提供了底层的视频处理支持,Core Image
框架则用于视频的图像特效处理。
3.2 播放视频 - AVPlayer
- 初始化与设置
使用
AVPlayer
播放视频,首先要创建AVPlayerItem
,它包含了视频的源。假设我们有一个名为example.mp4
的视频文件在项目资源目录中:
import AVFoundation
import AVKit
class VideoPlayerViewController: UIViewController {
var player: AVPlayer?
var playerViewController: AVPlayerViewController?
override func viewDidLoad() {
super.viewDidLoad()
guard let url = Bundle.main.url(forResource: "example", withExtension: "mp4") else {
print("视频文件未找到")
return
}
let playerItem = AVPlayerItem(url: url)
player = AVPlayer(playerItem: playerItem)
playerViewController = AVPlayerViewController()
playerViewController?.player = player
addChild(playerViewController!)
view.addSubview(playerViewController!.view)
playerViewController!.view.frame = view.bounds
playerViewController!.didMove(toParent: self)
}
}
在上述代码中,我们获取视频文件的 URL 并创建 AVPlayerItem
。然后使用 AVPlayerItem
初始化 AVPlayer
,接着创建 AVPlayerViewController
并将 AVPlayer
关联到它。最后将 AVPlayerViewController
的视图添加到当前视图控制器的视图中。
- 控制播放 控制视频播放与音频播放类似,有播放、暂停和停止操作:
// 播放视频
player?.play()
// 暂停视频
player?.pause()
// 停止视频
player?.seek(to: CMTime.zero)
此外,还可以监听视频的播放状态,比如是否正在播放、是否播放完成等:
let keyPath = "status"
player?.addObserver(self, forKeyPath: keyPath, options:.new, context: nil)
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "status", let player = object as? AVPlayer {
switch player.status {
case.readyToPlay:
print("视频准备好播放")
case.failed:
print("视频播放失败: \(String(describing: player.error))")
case.unknown:
print("视频状态未知")
}
}
}
3.3 录制视频 - AVCaptureSession
- 配置捕获会话
要录制视频,需要配置
AVCaptureSession
,它管理输入设备(如摄像头)和输出(如文件输出)之间的数据流动。
import AVFoundation
class VideoRecorderViewController: UIViewController, AVCaptureFileOutputRecordingDelegate {
var captureSession: AVCaptureSession?
var videoOutput: AVCaptureMovieFileOutput?
var previewLayer: AVCaptureVideoPreviewLayer?
override func viewDidLoad() {
super.viewDidLoad()
captureSession = AVCaptureSession()
captureSession?.sessionPreset =.high
guard let videoDevice = AVCaptureDevice.default(for:.video),
let videoInput = try? AVCaptureDeviceInput(device: videoDevice),
let audioDevice = AVCaptureDevice.default(for:.audio),
let audioInput = try? AVCaptureDeviceInput(device: audioDevice) else {
print("无法获取输入设备")
return
}
if captureSession?.canAddInput(videoInput) == true {
captureSession?.addInput(videoInput)
}
if captureSession?.canAddInput(audioInput) == true {
captureSession?.addInput(audioInput)
}
videoOutput = AVCaptureMovieFileOutput()
if captureSession?.canAddOutput(videoOutput!) == true {
captureSession?.addOutput(videoOutput!)
}
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession!)
previewLayer?.frame = view.bounds
view.layer.addSublayer(previewLayer!)
captureSession?.startRunning()
}
}
在上述代码中,我们首先创建了 AVCaptureSession
并设置其预设为高质量。然后获取摄像头和麦克风设备,并创建对应的输入。将视频和音频输入添加到捕获会话中,接着添加 AVCaptureMovieFileOutput
作为输出。创建预览层并将其添加到视图层,最后启动捕获会话。
- 控制录制 控制视频录制的操作包括开始录制和停止录制:
func startRecording() {
guard let output = videoOutput,
let connection = output.connection(with:.video) else {
return
}
let outputURL = FileManager.default.temporaryDirectory.appendingPathComponent("recorded_video.mp4")
if FileManager.default.fileExists(atPath: outputURL.path) {
try? FileManager.default.removeItem(at: outputURL)
}
output.startRecording(to: outputURL, recordingDelegate: self)
}
func stopRecording() {
videoOutput?.stopRecording()
}
extension VideoRecorderViewController {
func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {
if let error = error {
print("视频录制失败: \(error.localizedDescription)")
} else {
print("视频录制成功,文件路径: \(outputFileURL)")
}
}
}
四、Swift 视频处理进阶
4.1 视频编辑
- 视频剪辑
视频剪辑与音频剪辑类似,使用
AVMutableComposition
。假设我们要从一个视频文件中截取一段视频:
import AVFoundation
func clipVideo(sourceURL: URL, start: CMTime, duration: CMTime) -> URL? {
let composition = AVMutableComposition()
guard let videoAsset = AVAsset(url: sourceURL),
let videoTrack = composition.addMutableTrack(withMediaType:.video, preferredTrackID: kCMPersistentTrackID_Invalid),
let audioTrack = composition.addMutableTrack(withMediaType:.audio, preferredTrackID: kCMPersistentTrackID_Invalid) else {
return nil
}
do {
try videoTrack.insertTimeRange(CMTimeRange(start: start, duration: duration), of: videoAsset.tracks(withMediaType:.video)[0], at: CMTime.zero)
try audioTrack.insertTimeRange(CMTimeRange(start: start, duration: duration), of: videoAsset.tracks(withMediaType:.audio)[0], at: CMTime.zero)
} catch let error {
print("插入视频和音频片段时出错: \(error.localizedDescription)")
return nil
}
let outputURL = FileManager.default.temporaryDirectory.appendingPathComponent("clipped_video.mp4")
if FileManager.default.fileExists(atPath: outputURL.path) {
try? FileManager.default.removeItem(at: outputURL)
}
let exporter = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetHighestQuality)
exporter?.outputURL = outputURL
exporter?.outputFileType =.mp4
exporter?.exportAsynchronously(completionHandler: {
switch exporter?.status {
case.finished:
print("视频剪辑成功,输出路径: \(outputURL)")
case.failed:
print("视频剪辑失败: \(String(describing: exporter?.error))")
default:
break
}
})
return outputURL
}
此代码中,我们创建 AVMutableComposition
并从源视频 AVAsset
中获取视频和音频轨道,将指定时间范围的视频和音频片段插入到 AVMutableComposition
的相应轨道中。最后使用 AVAssetExportSession
导出剪辑后的视频文件。
- 视频拼接
视频拼接同样使用
AVMutableComposition
。假设我们有多个视频文件要拼接在一起:
func concatenateVideos(urls: [URL]) -> URL? {
let composition = AVMutableComposition()
guard let videoTrack = composition.addMutableTrack(withMediaType:.video, preferredTrackID: kCMPersistentTrackID_Invalid),
let audioTrack = composition.addMutableTrack(withMediaType:.audio, preferredTrackID: kCMPersistentTrackID_Invalid) else {
return nil
}
var currentVideoTime = CMTime.zero
var currentAudioTime = CMTime.zero
for url in urls {
guard let videoAsset = AVAsset(url: url) else {
continue
}
let videoDuration = videoAsset.duration
let audioDuration = videoAsset.tracks(withMediaType:.audio).first?.duration?? CMTime.zero
do {
try videoTrack.insertTimeRange(CMTimeRange(start: CMTime.zero, duration: videoDuration), of: videoAsset.tracks(withMediaType:.video)[0], at: currentVideoTime)
if let audioTrackOfAsset = videoAsset.tracks(withMediaType:.audio).first {
try audioTrack.insertTimeRange(CMTimeRange(start: CMTime.zero, duration: audioDuration), of: audioTrackOfAsset, at: currentAudioTime)
}
} catch let error {
print("插入视频和音频片段时出错: \(error.localizedDescription)")
return nil
}
currentVideoTime = CMTimeAdd(currentVideoTime, videoDuration)
currentAudioTime = CMTimeAdd(currentAudioTime, audioDuration)
}
let outputURL = FileManager.default.temporaryDirectory.appendingPathComponent("concatenated_video.mp4")
if FileManager.default.fileExists(atPath: outputURL.path) {
try? FileManager.default.removeItem(at: outputURL)
}
let exporter = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetHighestQuality)
exporter?.outputURL = outputURL
exporter?.outputFileType =.mp4
exporter?.exportAsynchronously(completionHandler: {
switch exporter?.status {
case.finished:
print("视频拼接成功,输出路径: \(outputURL)")
case.failed:
print("视频拼接失败: \(String(describing: exporter?.error))")
default:
break
}
})
return outputURL
}
在这段代码中,我们遍历视频文件 URL 数组,依次将每个视频的视频和音频片段插入到 AVMutableComposition
的相应轨道中。注意处理音频轨道可能不存在的情况。最后导出拼接后的视频文件。
4.2 视频特效处理
- 添加滤镜特效
使用
Core Image
框架可以为视频添加滤镜特效。假设我们要为视频添加一个高斯模糊滤镜:
import AVFoundation
import CoreImage
import CoreVideo
func addBlurEffectToVideo(sourceURL: URL) -> URL? {
let composition = AVMutableComposition()
guard let videoAsset = AVAsset(url: sourceURL),
let videoTrack = composition.addMutableTrack(withMediaType:.video, preferredTrackID: kCMPersistentTrackID_Invalid) else {
return nil
}
let videoDuration = videoAsset.duration
do {
try videoTrack.insertTimeRange(CMTimeRange(start: CMTime.zero, duration: videoDuration), of: videoAsset.tracks(withMediaType:.video)[0], at: CMTime.zero)
} catch let error {
print("插入视频片段时出错: \(error.localizedDescription)")
return nil
}
let outputURL = FileManager.default.temporaryDirectory.appendingPathComponent("blurred_video.mp4")
if FileManager.default.fileExists(atPath: outputURL.path) {
try? FileManager.default.removeItem(at: outputURL)
}
let exporter = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetHighestQuality)
exporter?.outputURL = outputURL
exporter?.outputFileType =.mp4
let blurFilter = CIFilter(name: "CIGaussianBlur")!
blurFilter.setValue(10, forKey: kCIInputRadiusKey)
let videoComposition = AVMutableVideoComposition(propertiesOf: videoAsset)
videoComposition.frameDuration = CMTimeMake(1, 30)
videoComposition.renderSize = CGSize(width: 1280, height: 720)
videoComposition.animationTool = AVVideoCompositionCoreAnimationTool(postProcessingAsVideoLayer: { (request) -> CALayer in
let source = CIImage(cvPixelBuffer: request.sourceFrame.imageBuffer)
blurFilter.setValue(source, forKey: kCIInputImageKey)
let output = blurFilter.outputImage!
let ciContext = CIContext()
let cgImage = ciContext.createCGImage(output, from: output.extent)!
let layer = CALayer()
layer.contents = cgImage
layer.frame = CGRect(x: 0, y: 0, width: request.sourceFrame.displaySize.width, height: request.sourceFrame.displaySize.height)
return layer
}, in: { (request) -> AVAsynchronousCIImageFilteringRequestCompletionHandler in
return { (filterOutput: CIImage?, _: Error?) in
if let filterOutput = filterOutput {
request.finish(with: filterOutput, context: nil)
} else {
request.finish(with: request.sourceFrame.imageBuffer, context: nil)
}
}
})
exporter?.videoComposition = videoComposition
exporter?.exportAsynchronously(completionHandler: {
switch exporter?.status {
case.finished:
print("添加模糊特效成功,输出路径: \(outputURL)")
case.failed:
print("添加模糊特效失败: \(String(describing: exporter?.error))")
default:
break
}
})
return outputURL
}
在上述代码中,我们首先创建 AVMutableComposition
并插入视频片段。然后创建一个高斯模糊滤镜 CIGaussianBlur
并设置其半径。接着构建 AVMutableVideoComposition
,并使用 AVVideoCompositionCoreAnimationTool
来应用滤镜特效。最后使用 AVAssetExportSession
导出添加滤镜后的视频文件。
- 添加转场特效
添加转场特效可以通过
AVVideoComposition
来实现。假设我们要在两个视频片段之间添加一个淡入淡出的转场特效:
func addFadeTransitionVideos(urls: [URL]) -> URL? {
let composition = AVMutableComposition()
guard let videoTrack = composition.addMutableTrack(withMediaType:.video, preferredTrackID: kCMPersistentTrackID_Invalid),
let audioTrack = composition.addMutableTrack(withMediaType:.audio, preferredTrackID: kCMPersistentTrackID_Invalid) else {
return nil
}
var currentVideoTime = CMTime.zero
var currentAudioTime = CMTime.zero
for (index, url) in urls.enumerated() {
guard let videoAsset = AVAsset(url: url) else {
continue
}
let videoDuration = videoAsset.duration
let audioDuration = videoAsset.tracks(withMediaType:.audio).first?.duration?? CMTime.zero
if index > 0 {
let transitionDuration = CMTimeMake(1, 2)
let startOfNextClip = currentVideoTime
let endOfPreviousClip = CMTimeSubtract(startOfNextClip, transitionDuration)
let fadeOutVideoInstruction = AVMutableVideoCompositionInstruction()
fadeOutVideoInstruction.timeRange = CMTimeRange(start: endOfPreviousClip, duration: transitionDuration)
fadeOutVideoInstruction.layerInstructions = [AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)]
fadeOutVideoInstruction.layerInstructions[0].setOpacityRamp(fromStart: 1.0, toEnd: 0.0, timeRange: fadeOutVideoInstruction.timeRange)
let fadeInVideoInstruction = AVMutableVideoCompositionInstruction()
fadeInVideoInstruction.timeRange = CMTimeRange(start: startOfNextClip, duration: transitionDuration)
fadeInVideoInstruction.layerInstructions = [AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)]
fadeInVideoInstruction.layerInstructions[0].setOpacityRamp(fromStart: 0.0, toEnd: 1.0, timeRange: fadeInVideoInstruction.timeRange)
let videoComposition = AVMutableVideoComposition(propertiesOf: videoAsset)
videoComposition.frameDuration = CMTimeMake(1, 30)
videoComposition.renderSize = CGSize(width: 1280, height: 720)
videoComposition.instructions = [fadeOutVideoInstruction, fadeInVideoInstruction]
let exporter = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetHighestQuality)
exporter?.outputURL = FileManager.default.temporaryDirectory.appendingPathComponent("transition_video.mp4")
exporter?.outputFileType =.mp4
exporter?.videoComposition = videoComposition
exporter?.exportAsynchronously(completionHandler: {
switch exporter?.status {
case.finished:
print("添加转场特效成功,输出路径: \(exporter?.outputURL?? URL(fileURLWithPath: ""))")
case.failed:
print("添加转场特效失败: \(String(describing: exporter?.error))")
default:
break
}
})
}
do {
try videoTrack.insertTimeRange(CMTimeRange(start: CMTime.zero, duration: videoDuration), of: videoAsset.tracks(withMediaType:.video)[0], at: currentVideoTime)
if let audioTrackOfAsset = videoAsset.tracks(withMediaType:.audio).first {
try audioTrack.insertTimeRange(CMTimeRange(start: CMTime.zero, duration: audioDuration), of: audioTrackOfAsset, at: currentAudioTime)
}
} catch let error {
print("插入视频和音频片段时出错: \(error.localizedDescription)")
return nil
}
currentVideoTime = CMTimeAdd(currentVideoTime, videoDuration)
currentAudioTime = CMTimeAdd(currentAudioTime, audioDuration)
}
return FileManager.default.temporaryDirectory.appendingPathComponent("transition_video.mp4")
}
在这段代码中,我们遍历视频文件 URL 数组,当处理第二个及以后的视频片段时,创建淡入淡出的转场指令 AVMutableVideoCompositionInstruction
。通过设置透明度渐变来实现淡入淡出效果。然后构建 AVMutableVideoComposition
并将转场指令添加进去,最后使用 AVAssetExportSession
导出添加转场特效后的视频文件。