版本记录
版本号 | 时间 |
---|---|
V1.0 | 2020.08.13 星期四 |
前言
AVFoundation
框架是ios中很重要的框架,所有与视频音频相关的软硬件控制都在这个框架里面,接下来这几篇就主要对这个框架进行介绍和讲解。感兴趣的可以看我上几篇。
1. AVFoundation框架解析(一)—— 基本概览
2. AVFoundation框架解析(二)—— 实现视频预览录制保存到相册
3. AVFoundation框架解析(三)—— 几个关键问题之关于框架的深度概括
4. AVFoundation框架解析(四)—— 几个关键问题之AVFoundation探索(一)
5. AVFoundation框架解析(五)—— 几个关键问题之AVFoundation探索(二)
6. AVFoundation框架解析(六)—— 视频音频的合成(一)
7. AVFoundation框架解析(七)—— 视频组合和音频混合调试
8. AVFoundation框架解析(八)—— 优化用户的播放体验
9. AVFoundation框架解析(九)—— AVFoundation的变化(一)
10. AVFoundation框架解析(十)—— AVFoundation的变化(二)
11. AVFoundation框架解析(十一)—— AVFoundation的变化(三)
12. AVFoundation框架解析(十二)—— AVFoundation的变化(四)
13. AVFoundation框架解析(十三)—— 构建基本播放应用程序
14. AVFoundation框架解析(十四)—— VAssetWriter和AVAssetReader的Timecode支持(一)
15. AVFoundation框架解析(十五)—— VAssetWriter和AVAssetReader的Timecode支持(二)
16. AVFoundation框架解析(十六)—— 一个简单示例之播放、录制以及混合视频(一)
17. AVFoundation框架解析(十七)—— 一个简单示例之播放、录制以及混合视频之源码及效果展示(二)
18. AVFoundation框架解析(十八)—— AVAudioEngine之基本概览(一)
19. AVFoundation框架解析(十九)—— AVAudioEngine之详细说明和一个简单示例(二)
20. AVFoundation框架解析(二十)—— AVAudioEngine之详细说明和一个简单示例源码(三)
21. AVFoundation框架解析(二十一)—— 一个简单的视频流预览和播放示例之解析(一)
22. AVFoundation框架解析(二十二)—— 一个简单的视频流预览和播放示例之源码(二)
23. AVFoundation框架解析(二十三) —— 向视频层添加叠加层和动画(一)
24. AVFoundation框架解析(二十四) —— 向视频层添加叠加层和动画(二)
开始
首先看下主要内容:
在本教程中,了解在带有
AV Foundation
的iOS
上使用视频的基础知识。 您将播放,录制甚至进行一些简短的视频编辑。内容来自翻译。
接着看一下写作环境
Swift 5, iOS 13, Xcode 11
下面就是正文了。
以编程方式录制视频并播放是您可以用手机完成的最酷的事情之一。但是,几乎没有足够的应用程序提供此功能,您可以使用AV Foundation
框架轻松添加。
自2010
年OS X Lion(10.7)
和iOS 4
起,AV Foundation
就已成为macOS
和iOS
的一部分。自那时以来,它的发展相当可观,迄今为止已有100
多个类。
本教程通过介绍媒体播放和一些轻量编辑,使您开始使用AV Foundation
。特别是,您将学习如何:
- 从媒体库中选择并播放视频。
- 录制视频并将其保存到媒体库。
- 将多个剪辑合并为一个具有自定义
soundtrack
的完整视频。
避免在模拟器上运行本教程中的代码,因为您将无法采集视频。另外,您需要找出一种将视频手动添加到媒体库的方法。换句话说,您确实需要在设备上测试此代码!
为此,您需要是注册的Apple开发人员registered Apple developer。免费帐户可以在本教程中正常工作。
下面就开始啦!
打开入门项目,简单看一下。 该项目包含一个storyboard
和几个带有UI的视图控制器,用于简单的视频播放和录制应用程序。
主屏幕包含下面的三个按钮,这些按钮可用于segue
到其他视图控制器:
- 选择并播放视频
- 录制并保存视频
- 合并视频
构建并运行和测试按钮。 初始场景中只有三个按钮可以执行任何操作,但是您很快就会对其进行更改!
Selecting and Playing Video
主屏幕上的Select and Play Video
按钮可以连接到PlayVideoController
。 在本教程的这一部分中,您将添加代码以选择一个视频文件并进行播放。
首先打开PlayVideoViewController.swift
,然后在文件顶部添加以下import
语句:
import AVKit
import MobileCoreServices
导入AVKit
可让您访问AVPlayer
,用以播放选定的视频。 MobileCoreServices
包含预定义的常量,例如kUTTypeMovie
,稍后将需要它们。
接下来,在文件末尾添加以下类扩展名。 确保将它们添加到文件的最底部,类声明的花括号之外:
// MARK: - UIImagePickerControllerDelegate
extension PlayVideoViewController: UIImagePickerControllerDelegate {
}
// MARK: - UINavigationControllerDelegate
extension PlayVideoViewController: UINavigationControllerDelegate {
}
这些扩展将PlayVideoViewController
设置为遵循UIImagePickerControllerDelegate
和UINavigationControllerDelegate
协议。
您将使用系统提供的UIImagePickerController
来让用户浏览照片库中的视频。 该类通过这些委托协议传达回您的应用程序。 尽管该班级被称为image picker
,但请放心,它也可以用于视频!
接下来,回到PlayVideoViewController
的主类定义,然后将以下代码添加到playVideo(_ :)
:
VideoHelper.startMediaBrowser(delegate: self, sourceType: .savedPhotosAlbum)
这是对VideoHelper
中名为startMediaBrowser(delegate:sourceType :)
的helper
方法的调用。此调用将打开图像选择器,将委托设置为self
。 .savedPhotosAlbum
的源类型选择从相机胶卷中选择图像。稍后,您将在VideoHelper
中添加自己的帮助器工具。
要查看此方法的含义,请打开VideoHelper.swift
。它执行以下操作:
- 1) 检查源在设备上是否存在。来源包括相机胶卷,相机本身和完整的照片库。每当您使用
UIImagePickerController
选择媒体时,此检查都是必不可少的。如果不这样做,则可能会尝试从不存在的来源中选择媒体,这通常会导致崩溃。 - 2) 如果所需的源可用,它将创建
UIImagePickerController
并设置其源和媒体类型。由于只想选择视频,因此代码将类型限制为kUTTypeMovie
。 - 3) 最后,它以模态形式呈现
UIImagePickerController
。
现在,您准备好为您的项目再一次尝试!构建并运行。在第一个屏幕上点击Select and Play Video
,然后在第二个屏幕上点击Play Video
。相机胶卷将像这样弹出:
看到视频列表后,选择一个。 您将转到另一个屏幕,其中详细显示了视频以及Cancel
,Play
和Choose
按钮。 点击Play
按钮,毫不奇怪,视频将播放。
但是,如果点击Choose
按钮,则该应用程序仅返回到Play Video
屏幕! 这是因为您尚未实现任何委托方法来处理从选择器中选择视频。
返回Xcode,再次打开PlayVideoViewController.swift
并找到UIImagePickerControllerDelegate
扩展。 然后添加以下委托方法实现:
func imagePickerController(
_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]
) {
// 1
guard
let mediaType = info[UIImagePickerController.InfoKey.mediaType] as? String,
mediaType == (kUTTypeMovie as String),
let url = info[UIImagePickerController.InfoKey.mediaURL] as? URL
else { return }
// 2
dismiss(animated: true) {
//3
let player = AVPlayer(url: url)
let vcPlayer = AVPlayerViewController()
vcPlayer.player = player
self.present(vcPlayer, animated: true, completion: nil)
}
}
这是您使用此方法所做的事情:
- 1) 您可以获取所选媒体和URL的媒体类型,并确保它是视频。
- 2) 接下来,关闭图像选择器。
- 3) 在完成块
(completion block)
中,创建一个AVPlayerViewController
来播放媒体。
构建并运行。 点击Select and Play Video
,然后点击Play Video
,然后从列表中选择一个视频。 该视频将在媒体播放器中播放。
Recording Video
现在您可以播放视频了,现在该使用设备的摄像机录制视频并将其保存到媒体库中了。
打开RecordVideoViewController.swift
并添加以下导入:
import MobileCoreServices
然后,将以下内容添加到文件末尾:
// MARK: - UIImagePickerControllerDelegate
extension RecordVideoViewController: UIImagePickerControllerDelegate {
}
// MARK: - UINavigationControllerDelegate
extension RecordVideoViewController: UINavigationControllerDelegate {
}
它采用与PlayVideoViewController
相同的协议。
接下来,将以下代码添加到record(_ :)
:
VideoHelper.startMediaBrowser(delegate: self, sourceType: .camera)
它使用与PlayVideoViewController
中相同的helper
方法,除了它访问.camera
以指示图像选择器以内置照相机模式打开。
构建并运行以查看您到目前为止所拥有的。
转到Record
屏幕,然后点击Record Video
。 代替相机图库,将打开相机用户界面。 当alert
对话框询问摄像机权限和麦克风权限时,单击OK
。
最后,点击屏幕底部的红色录制按钮开始录制视频; 完成录制后,再次点按它。
现在,您有两个选择:使用录制的视频或重录。 点击Use Video
。 您会注意到,它只是关闭了视图控制器。 这是因为-您猜对了-您尚未实现适当的委托方法来将录制的视频保存到媒体库。
Saving Video
返回RecordVideoViewController.swift
,将以下方法添加到UIImagePickerControllerDelegate
扩展中:
func imagePickerController(
_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]
) {
dismiss(animated: true, completion: nil)
guard
let mediaType = info[UIImagePickerController.InfoKey.mediaType] as? String,
mediaType == (kUTTypeMovie as String),
// 1
let url = info[UIImagePickerController.InfoKey.mediaURL] as? URL,
// 2
UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(url.path)
else { return }
// 3
UISaveVideoAtPathToSavedPhotosAlbum(
url.path,
self,
#selector(video(_:didFinishSavingWithError:contextInfo:)),
nil)
}
不必担心该错误-很快就会解决。
- 1) 和以前一样,委托方法为您提供指向视频的
URL
。 - 2) 验证该应用程序可以将文件保存到设备的相册中。
- 3) 如果可以,请保存。
SDK
所提供的UISaveVideoAtPathToSavedPhotosAlbum
功能可将视频保存到设备的相册中。 您向其传递要保存的视频的路径以及要回调的目标和操作,这将使您知道保存操作的状态。
接下来,将回调的实现添加到主类定义中:
@objc func video(
_ videoPath: String,
didFinishSavingWithError error: Error?,
contextInfo info: AnyObject
) {
let title = (error == nil) ? "Success" : "Error"
let message = (error == nil) ? "Video was saved" : "Video failed to save"
let alert = UIAlertController(
title: title,
message: message,
preferredStyle: .alert)
alert.addAction(UIAlertAction(
title: "OK",
style: UIAlertAction.Style.cancel,
handler: nil))
present(alert, animated: true, completion: nil)
}
回调(callback)
方法仅向用户显示alert
,并根据错误状态宣布是否保存了视频文件。
构建并运行。 录制视频,并在录制完成后选择Use Video
。 如果询问您是否有权保存到视频库,请点击OK
。 当Video was saved
弹窗弹出时,您就成功将视频保存到了照片库!
现在您可以播放视频和录制视频了,该是下一步了,尝试一些简单的视频编辑了。
Merging Videos
该应用程序的最后一项功能是进行一些编辑。您的用户将从音乐库中选择两个视频和一首歌曲,然后该应用将合并两个视频并混入音乐。
该项目已经在MergeVideoViewController.swift
中实现了入门实施,其代码类似于您编写的用于播放视频的代码。最大的区别是,合并时,用户必须选择两个视频。该部分已经设置好,因此用户可以进行两个选择,这些选择将存储在firstAsset
和secondAsset
中。
下一步是添加功能以选择音频文件。
1. Selecting the Audio File
UIImagePickerController
提供了从媒体库中仅选择视频和图像的功能。要从音乐库中选择音频文件,必须使用MPMediaPickerController
。它的工作原理与UIImagePickerController
相同,但是它访问图像库中的音频文件,而不是图像和视频。
打开MergeVideoViewController.swift
并将以下代码添加到loadAudio(_ :)
:
let mediaPickerController = MPMediaPickerController(mediaTypes: .any)
mediaPickerController.delegate = self
mediaPickerController.prompt = "Select Audio"
present(mediaPickerController, animated: true, completion: nil)
上面的代码创建一个新的MPMediaPickerController
实例,并将其显示为模式视图控制器。
构建并运行。 现在,点击Merge Video
,然后点击Load Audio
以访问设备上的音频库。
当然,您的设备上需要一些音频文件。 否则,列表将为空。 这些歌曲还必须实际显示在设备上,因此请确保您不尝试从云中加载歌曲。
从列表中选择一首歌曲,您会发现没有任何反应。 那就对了! MPMediaPickerController
需要委托方法!
要实现它们,请在文件底部找到MPMediaPickerControllerDelegate
扩展,然后向其中添加以下两个方法:
func mediaPicker(
_ mediaPicker: MPMediaPickerController,
didPickMediaItems mediaItemCollection: MPMediaItemCollection
) {
// 1
dismiss(animated: true) {
// 2
let selectedSongs = mediaItemCollection.items
guard let song = selectedSongs.first else { return }
// 3
let title: String
let message: String
if let url = song.value(forProperty: MPMediaItemPropertyAssetURL) as? URL {
self.audioAsset = AVAsset(url: url)
title = "Asset Loaded"
message = "Audio Loaded"
} else {
self.audioAsset = nil
title = "Asset Not Available"
message = "Audio Not Loaded"
}
// 4
let alert = UIAlertController(
title: title,
message: message,
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
func mediaPickerDidCancel(_ mediaPicker: MPMediaPickerController) {
// 5
dismiss(animated: true, completion: nil)
}
上面的代码类似于UIImagePickerController
的委托方法。 它的作用是:
- 1) 像以前一样
dismiss
选择器。 - 2) 找到选定的歌曲,然后从中找到第一首(如果选择了多首)。
- 3) 获取指向支持歌曲的媒体资源的URL。 然后使
AVAsset
指向所选的歌曲。 - 4) 最后,对于
mediaPicker(_:didPickMediaItems :)
,显示alert
以指示资源是否已成功加载。 - 5) 在取消媒体选择器的情况下,只需关闭视图控制器即可。
构建并运行,然后转到Merge Videos
屏幕。 选择一个音频文件,您将看到Audio Loaded
消息。
现在,您的所有资源都已正确加载,是时候将各种媒体文件合并为一个文件了。 但是在进入该代码之前,您需要进行一些设置。
2. Merging Completion Handler
您将很快编写代码以合并您的资产。 这将需要一个完成处理程序,以将最终视频保存到相册中。 您将首先添加它。
在MergeVideoViewController.swift
文件的顶部添加以下import
语句:
import Photos
然后,将以下方法添加到MergeVideoViewController
:
func exportDidFinish(_ session: AVAssetExportSession) {
// 1
activityMonitor.stopAnimating()
firstAsset = nil
secondAsset = nil
audioAsset = nil
// 2
guard
session.status == AVAssetExportSession.Status.completed,
let outputURL = session.outputURL
else { return }
// 3
let saveVideoToPhotos = {
// 4
let changes: () -> Void = {
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: outputURL)
}
PHPhotoLibrary.shared().performChanges(changes) { saved, error in
DispatchQueue.main.async {
let success = saved && (error == nil)
let title = success ? "Success" : "Error"
let message = success ? "Video saved" : "Failed to save video"
let alert = UIAlertController(
title: title,
message: message,
preferredStyle: .alert)
alert.addAction(UIAlertAction(
title: "OK",
style: UIAlertAction.Style.cancel,
handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
}
// 5
if PHPhotoLibrary.authorizationStatus() != .authorized {
PHPhotoLibrary.requestAuthorization { status in
if status == .authorized {
saveVideoToPhotos()
}
}
} else {
saveVideoToPhotos()
}
}
该代码的作用如下:
- 1) 有一个
spinner
,用于在处理资产时进行动画处理。 这将停止spinner
,然后清除资源并准备选择新资源。 - 2) 确保处理完成,并且有结果视频的
URL
。 - 3) 创建一个闭包
- 4) 告诉照片库从显示的视频发出
create request
,然后显示alert
以表明成功或失败。 - 5) 检查是否有访问照片库的权限。 如果没有权限,则在运行保存视频的闭包之前要求它。 否则,只要已授予许可,就立即运行闭包。
现在,您将添加一些代码到merge(_:)
。 因为有很多代码,所以您将分步完成。
Merging: Step 1
在此步骤中,您将视频合并为一个长视频。
将以下代码添加到merge(_ :)
:
guard
let firstAsset = firstAsset,
let secondAsset = secondAsset
else { return }
activityMonitor.startAnimating()
// 1
let mixComposition = AVMutableComposition()
// 2
guard
let firstTrack = mixComposition.addMutableTrack(
withMediaType: .video,
preferredTrackID: Int32(kCMPersistentTrackID_Invalid))
else { return }
// 3
do {
try firstTrack.insertTimeRange(
CMTimeRangeMake(start: .zero, duration: firstAsset.duration),
of: firstAsset.tracks(withMediaType: .video)[0],
at: .zero)
} catch {
print("Failed to load first track")
return
}
// 4
guard
let secondTrack = mixComposition.addMutableTrack(
withMediaType: .video,
preferredTrackID: Int32(kCMPersistentTrackID_Invalid))
else { return }
do {
try secondTrack.insertTimeRange(
CMTimeRangeMake(start: .zero, duration: secondAsset.duration),
of: secondAsset.tracks(withMediaType: .video)[0],
at: firstAsset.duration)
} catch {
print("Failed to load second track")
return
}
// 5
// TODO: PASTE CODE A
在上面的代码中:
- 1) 您创建一个
AVMutableComposition
来保存您的视频和音频轨道。 - 2) 接下来,为视频创建一个
AVMutableCompositionTrack
并将其添加到您的AVMutableComposition
中。 - 3) 然后,将视频从第一个视频资产插入到此轨道。
请注意,insertTimeRange(_:ofTrack:atStartTime :)
允许您将视频的一部分而不是整个部分插入到您的主要合成作品中。这样,您可以将视频修剪到您选择的时间范围。
在这种情况下,您要插入整个视频,因此创建一个从CMTime.zero
到视频资源持续时间的时间范围。
- 4) 接下来,您对第二个视频资产执行相同的操作。
请注意,代码是如何在时间.zero
插入firstAsset
的,然后在第一个视频的末尾插入secondAsset
的。这是因为本教程假定您要一个接一个地使用视频资源,但是您也可以通过播放时间范围来重叠资产。
- 5)
// TODO: PASTE CODE A
是一个标记-您将在下一部分中用代码替换此行。
在此步骤中,您将设置两个单独的AVMutableCompositionTrack
实例。现在,您需要将AVMutableVideoCompositionLayerInstruction
应用于每个轨道,以便进行一些编辑。
Merging the Videos: Step 2
接下来是将instructions
添加到composition
中,以告诉您如何合并资源。
在merge(_ :)
中的上面的跟踪代码之后添加下一部分代码。将// TODO:PASTE CODE A
替换为以下代码:
// 6
let mainInstruction = AVMutableVideoCompositionInstruction()
mainInstruction.timeRange = CMTimeRangeMake(
start: .zero,
duration: CMTimeAdd(firstAsset.duration, secondAsset.duration))
// 7
let firstInstruction = AVMutableVideoCompositionLayerInstruction(
assetTrack: firstTrack)
firstInstruction.setOpacity(0.0, at: firstAsset.duration)
let secondInstruction = AVMutableVideoCompositionLayerInstruction(
assetTrack: secondTrack)
// 8
mainInstruction.layerInstructions = [firstInstruction, secondInstruction]
let mainComposition = AVMutableVideoComposition()
mainComposition.instructions = [mainInstruction]
mainComposition.frameDuration = CMTimeMake(value: 1, timescale: 30)
mainComposition.renderSize = CGSize(
width: UIScreen.main.bounds.width,
height: UIScreen.main.bounds.height)
// 9
// TODO: PASTE CODE B
这是这段代码中发生的事情:
- 6) 首先,设置
mainInstruction
来包装整个instructions
。请注意,此处的总时间是第一个资源的总长与第二个资源的总长之和。 - 7) 接下来,您设置了两个
instructions
,每个资源对应一个instructions
。第一个视频的instructions
需要额外添加:您将其不透明度最后设置为0,以便在第二个视频开始时不可见。 - 8) 现在,您已经有了第一条和第二条轨道的
AVMutableVideoCompositionLayerInstruction
实例,您只需将它们添加到mainInstruction
中。接下来,将mainInstruction
添加到AVMutableVideoComposition
实例的指令属性。您还可以将合成的帧频设置为30
帧/秒。 - 9)
// TODO: PASTE CODE B
是一个标记-您将在下一部分中用代码替换此行。
好的,现在您已经合并了两个视频文件。是时候为他们添加一些声音了!
Merging the Audio: Step 3
要使片段具有音乐风格,请将以下代码添加到merge(_ :)
。将// TODO:PASTE CODE B
替换为以下代码:
// 10
if let loadedAudioAsset = audioAsset {
let audioTrack = mixComposition.addMutableTrack(
withMediaType: .audio,
preferredTrackID: 0)
do {
try audioTrack?.insertTimeRange(
CMTimeRangeMake(
start: .zero,
duration: CMTimeAdd(
firstAsset.duration,
secondAsset.duration)),
of: loadedAudioAsset.tracks(withMediaType: .audio)[0],
at: .zero)
} catch {
print("Failed to load Audio track")
}
}
// 11
guard
let documentDirectory = FileManager.default.urls(
for: .documentDirectory,
in: .userDomainMask).first
else { return }
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .long
dateFormatter.timeStyle = .short
let date = dateFormatter.string(from: Date())
let url = documentDirectory.appendingPathComponent("mergeVideo-\(date).mov")
// 12
guard let exporter = AVAssetExportSession(
asset: mixComposition,
presetName: AVAssetExportPresetHighestQuality)
else { return }
exporter.outputURL = url
exporter.outputFileType = AVFileType.mov
exporter.shouldOptimizeForNetworkUse = true
exporter.videoComposition = mainComposition
// 13
exporter.exportAsynchronously {
DispatchQueue.main.async {
self.exportDidFinish(exporter)
}
}
上面的代码是这样的:
- 10) 与视频轨道相似,您可以为音频创建一个新轨道并将其添加到主要
composition
中。您将音频时间范围设置为第一和第二视频的时长之和,因为这将是视频的完整长度。 - 11) 在保存最终视频之前,需要一个保存文件的路径。根据当前日期和时间创建一个唯一的文件名,该文件名指向
documents
文件夹中的文件。 - 12) 渲染并导出合并的视频。为此,您将创建一个
AVAssetExportSession
,它将对合成的内容进行转码以创建由指定的导出预设描述的格式输出。由于您已经配置了AVMutableVideoComposition
,因此您只需将其分配给导出器即可。 - 13) 在使用包含源媒体,导出
presetName
和outputFileType
的资源初始化导出会话后,可以通过调用exportAsynchronously()
运行导出。
因为代码异步执行导出,所以此方法立即返回。无论导出失败,完成还是用户取消,代码都会调用您提供给exportAsynchronously()
的完成处理程序。
完成后,导出的状态status
属性指示导出是否成功完成。如果失败,则导出器的error
属性的值将提供有关失败原因的其他信息。
AVComposition
组合了来自多个基于文件的来源的媒体数据。在最高层,AVComposition
是轨道的集合,每个轨道都呈现特定类型的媒体,例如音频或视频。 AVCompositionTrack
的实例表示单个轨道。
同样,AVMutableComposition
和AVMutableCompositionTrack
也提供了用于构建合成的更高级别的接口。这些对象提供了您之前见过的插入,移除和缩放操作,这些操作将再次出现。
最后,构建并运行。
选择两个视频和一个音频文件,然后合并选定的文件。您会看到一条Video Saved
消息,表明合成成功。此时,您的新视频将出现在相册中。
转到相册或使用应用程序中的Select and Play Video
屏幕浏览,您可能会注意到合并视频中的一些方向问题。 竖屏视频可能处于横向模式,有时视频会倒置。
这是由于默认的AVAsset
方向。 使用默认的iPhone
相机应用程序记录的所有电影和图像文件都将视频帧设置为横向,因此iPhone
以横向模式保存媒体。 接下来,您将解决这些问题。
Orienting Video
AVAsset
有一个PreferredTransform
,其中包含媒体方向信息。 每当您使用Photos
应用程序或QuickTime
查看媒体文件时,它会将其应用于媒体文件。
在上面的代码中,您尚未对AVAsets
进行转换,因此没有定向问题。 幸运的是,这很容易解决。
但是,在执行此操作之前,需要在VideoHelper.swift
的VideoHelper
中添加以下帮助器方法:
static func orientationFromTransform(
_ transform: CGAffineTransform
) -> (orientation: UIImage.Orientation, isPortrait: Bool) {
var assetOrientation = UIImage.Orientation.up
var isPortrait = false
let tfA = transform.a
let tfB = transform.b
let tfC = transform.c
let tfD = transform.d
if tfA == 0 && tfB == 1.0 && tfC == -1.0 && tfD == 0 {
assetOrientation = .right
isPortrait = true
} else if tfA == 0 && tfB == -1.0 && tfC == 1.0 && tfD == 0 {
assetOrientation = .left
isPortrait = true
} else if tfA == 1.0 && tfB == 0 && tfC == 0 && tfD == 1.0 {
assetOrientation = .up
} else if tfA == -1.0 && tfB == 0 && tfC == 0 && tfD == -1.0 {
assetOrientation = .down
}
return (assetOrientation, isPortrait)
}
此代码分析仿射变换(affine transform)
,以确定输入视频的方向。
接下来,添加以下import
:
import AVFoundation
以及该类的另一个helper
方法:
static func videoCompositionInstruction(
_ track: AVCompositionTrack,
asset: AVAsset
) -> AVMutableVideoCompositionLayerInstruction {
// 1
let instruction = AVMutableVideoCompositionLayerInstruction(assetTrack: track)
// 2
let assetTrack = asset.tracks(withMediaType: AVMediaType.video)[0]
// 3
let transform = assetTrack.preferredTransform
let assetInfo = orientationFromTransform(transform)
var scaleToFitRatio = UIScreen.main.bounds.width / assetTrack.naturalSize.width
if assetInfo.isPortrait {
// 4
scaleToFitRatio = UIScreen.main.bounds.width / assetTrack.naturalSize.height
let scaleFactor = CGAffineTransform(
scaleX: scaleToFitRatio,
y: scaleToFitRatio)
instruction.setTransform(
assetTrack.preferredTransform.concatenating(scaleFactor),
at: .zero)
} else {
// 5
let scaleFactor = CGAffineTransform(
scaleX: scaleToFitRatio,
y: scaleToFitRatio)
var concat = assetTrack.preferredTransform.concatenating(scaleFactor)
.concatenating(CGAffineTransform(
translationX: 0,
y: UIScreen.main.bounds.width / 2))
if assetInfo.orientation == .down {
let fixUpsideDown = CGAffineTransform(rotationAngle: CGFloat(Double.pi))
let windowBounds = UIScreen.main.bounds
let yFix = assetTrack.naturalSize.height + windowBounds.height
let centerFix = CGAffineTransform(
translationX: assetTrack.naturalSize.width,
y: yFix)
concat = fixUpsideDown.concatenating(centerFix).concatenating(scaleFactor)
}
instruction.setTransform(concat, at: .zero)
}
return instruction
}
该方法获取一个轨道和一个资源,并返回一个AVMutableVideoCompositionLayerInstruction
,其中包装了使视频朝上的必要仿射变换(affine transform)
。这是逐步进行的操作:
- 1) 您创建
AVMutableVideoCompositionLayerInstruction
并将其与轨道关联。 - 2) 接下来,从您的
AVAsset
创建AVAssetTrack
。AVAssetTrack
为所有资源提供跟踪级别的检查接口。您需要此对象来访问资源的尺寸和preferredTransform
。 - 3) 然后,保存首选的变换
(preferred transform)
和使视频适合当前屏幕所需的缩放比例。您将在以下步骤中使用这些值。 - 4) 如果视频是纵向视频,则需要重新计算比例因子-默认计算是横向视频。然后,您要做的就是应用方向旋转和比例变换。
- 5) 如果视频是横向播放,则可以采用类似的步骤来应用缩放和变换。但是,有一项额外的检查,因为用户可能会以左横向或右横向制作视频。
由于有两个横向,因此纵横比将匹配,但视频可能会旋转180度。对于.down
视频方向的额外检查可以解决这种情况。
设置好helper
方法后,在MergeVideoViewController.swift
中找到merge(_ :)
。找到创建firstInstruction
和secondInstruction
的位置,并将它们替换为以下内容:
let firstInstruction = VideoHelper.videoCompositionInstruction(
firstTrack,
asset: firstAsset)
let secondInstruction = VideoHelper.videoCompositionInstruction(
secondTrack,
asset: secondAsset)
上面的更改将使用新的helper
函数并实现所需的旋转修复程序。
哇-就是这样!
构建并运行。 通过将两个视频(以及一个音频文件)结合在一起来创建新视频,当您播放视频时,您会发现方向问题消失了。
在播放视频时,AV Foundation
为您提供了很大的灵活性。 您还可以应用任何类型的CGAffineTransform
合并,缩放或定位视频。
如果您尚未这样做,请看一下AV Foundation
上的WWDC videos,例如WWDC 2016 session 503, Advances in AVFoundation Playback
。
另外,请务必查看Apple AVFoundation Framework documentation。
后记
本篇主要讲述了播放、录制和合并视频简单示例,感兴趣的给个赞或者关注~~~