版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2020.08.13 星期四 |
前言
AVFoundation
框架是ios中很重要的框架奥此,所有與視頻音頻相關(guān)的軟硬件控制都在這個框架里面账锹,接下來這幾篇就主要對這個框架進行介紹和講解。感興趣的可以看我上幾篇蔚袍。
1. AVFoundation框架解析(一)—— 基本概覽
2. AVFoundation框架解析(二)—— 實現(xiàn)視頻預(yù)覽錄制保存到相冊
3. AVFoundation框架解析(三)—— 幾個關(guān)鍵問題之關(guān)于框架的深度概括
4. AVFoundation框架解析(四)—— 幾個關(guān)鍵問題之AVFoundation探索(一)
5. AVFoundation框架解析(五)—— 幾個關(guān)鍵問題之AVFoundation探索(二)
6. AVFoundation框架解析(六)—— 視頻音頻的合成(一)
7. AVFoundation框架解析(七)—— 視頻組合和音頻混合調(diào)試
8. AVFoundation框架解析(八)—— 優(yōu)化用戶的播放體驗
9. AVFoundation框架解析(九)—— AVFoundation的變化(一)
10. AVFoundation框架解析(十)—— AVFoundation的變化(二)
11. AVFoundation框架解析(十一)—— AVFoundation的變化(三)
12. AVFoundation框架解析(十二)—— AVFoundation的變化(四)
13. AVFoundation框架解析(十三)—— 構(gòu)建基本播放應(yīng)用程序
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框架解析(二十一)—— 一個簡單的視頻流預(yù)覽和播放示例之解析(一)
22. AVFoundation框架解析(二十二)—— 一個簡單的視頻流預(yù)覽和播放示例之源碼(二)
23. AVFoundation框架解析(二十三) —— 向視頻層添加疊加層和動畫(一)
24. AVFoundation框架解析(二十四) —— 向視頻層添加疊加層和動畫(二)
開始
首先看下主要內(nèi)容:
在本教程中页响,了解在帶有
AV Foundation
的iOS
上使用視頻的基礎(chǔ)知識篓足。 您將播放,錄制甚至進行一些簡短的視頻編輯闰蚕。內(nèi)容來自翻譯栈拖。
接著看一下寫作環(huán)境
Swift 5, iOS 13, Xcode 11
下面就是正文了。
以編程方式錄制視頻并播放是您可以用手機完成的最酷的事情之一没陡。但是涩哟,幾乎沒有足夠的應(yīng)用程序提供此功能索赏,您可以使用AV Foundation
框架輕松添加。
自2010
年OS X Lion(10.7)
和iOS 4
起贴彼,AV Foundation
就已成為macOS
和iOS
的一部分潜腻。自那時以來,它的發(fā)展相當(dāng)可觀器仗,迄今為止已有100
多個類融涣。
本教程通過介紹媒體播放和一些輕量編輯,使您開始使用AV Foundation
精钮。特別是威鹿,您將學(xué)習(xí)如何:
- 從媒體庫中選擇并播放視頻。
- 錄制視頻并將其保存到媒體庫轨香。
- 將多個剪輯合并為一個具有自定義
soundtrack
的完整視頻忽你。
避免在模擬器上運行本教程中的代碼,因為您將無法采集視頻臂容。另外科雳,您需要找出一種將視頻手動添加到媒體庫的方法。換句話說脓杉,您確實需要在設(shè)備上測試此代碼糟秘!
為此,您需要是注冊的Apple開發(fā)人員registered Apple developer丽已。免費帳戶可以在本教程中正常工作蚌堵。
下面就開始啦!
打開入門項目沛婴,簡單看一下吼畏。 該項目包含一個storyboard
和幾個帶有UI的視圖控制器,用于簡單的視頻播放和錄制應(yīng)用程序嘁灯。
主屏幕包含下面的三個按鈕泻蚊,這些按鈕可用于segue
到其他視圖控制器:
- 選擇并播放視頻
- 錄制并保存視頻
- 合并視頻
構(gòu)建并運行和測試按鈕。 初始場景中只有三個按鈕可以執(zhí)行任何操作丑婿,但是您很快就會對其進行更改性雄!
Selecting and Playing Video
主屏幕上的Select and Play Video
按鈕可以連接到PlayVideoController
。 在本教程的這一部分中羹奉,您將添加代碼以選擇一個視頻文件并進行播放秒旋。
首先打開PlayVideoViewController.swift
,然后在文件頂部添加以下import
語句:
import AVKit
import MobileCoreServices
導(dǎo)入AVKit
可讓您訪問AVPlayer
诀拭,用以播放選定的視頻迁筛。 MobileCoreServices
包含預(yù)定義的常量,例如kUTTypeMovie
耕挨,稍后將需要它們细卧。
接下來尉桩,在文件末尾添加以下類擴展名。 確保將它們添加到文件的最底部贪庙,類聲明的花括號之外:
// MARK: - UIImagePickerControllerDelegate
extension PlayVideoViewController: UIImagePickerControllerDelegate {
}
// MARK: - UINavigationControllerDelegate
extension PlayVideoViewController: UINavigationControllerDelegate {
}
這些擴展將PlayVideoViewController
設(shè)置為遵循UIImagePickerControllerDelegate
和UINavigationControllerDelegate
協(xié)議蜘犁。
您將使用系統(tǒng)提供的UIImagePickerController
來讓用戶瀏覽照片庫中的視頻。 該類通過這些委托協(xié)議傳達回您的應(yīng)用程序止邮。 盡管該班級被稱為image picker
这橙,但請放心,它也可以用于視頻农尖!
接下來析恋,回到PlayVideoViewController
的主類定義,然后將以下代碼添加到playVideo(_ :)
:
VideoHelper.startMediaBrowser(delegate: self, sourceType: .savedPhotosAlbum)
這是對VideoHelper
中名為startMediaBrowser(delegate:sourceType :)
的helper
方法的調(diào)用盛卡。此調(diào)用將打開圖像選擇器,將委托設(shè)置為self
筑凫。 .savedPhotosAlbum
的源類型選擇從相機膠卷中選擇圖像滑沧。稍后,您將在VideoHelper
中添加自己的幫助器工具巍实。
要查看此方法的含義滓技,請打開VideoHelper.swift
。它執(zhí)行以下操作:
- 1) 檢查源在設(shè)備上是否存在棚潦。來源包括相機膠卷令漂,相機本身和完整的照片庫。每當(dāng)您使用
UIImagePickerController
選擇媒體時丸边,此檢查都是必不可少的叠必。如果不這樣做,則可能會嘗試從不存在的來源中選擇媒體妹窖,這通常會導(dǎo)致崩潰纬朝。 - 2) 如果所需的源可用,它將創(chuàng)建
UIImagePickerController
并設(shè)置其源和媒體類型骄呼。由于只想選擇視頻共苛,因此代碼將類型限制為kUTTypeMovie
。 - 3) 最后蜓萄,它以模態(tài)形式呈現(xiàn)
UIImagePickerController
隅茎。
現(xiàn)在,您準(zhǔn)備好為您的項目再一次嘗試嫉沽!構(gòu)建并運行辟犀。在第一個屏幕上點擊Select and Play Video
,然后在第二個屏幕上點擊Play Video
耻蛇。相機膠卷將像這樣彈出:
看到視頻列表后踪蹬,選擇一個胞此。 您將轉(zhuǎn)到另一個屏幕,其中詳細顯示了視頻以及Cancel
跃捣,Play
和Choose
按鈕漱牵。 點擊Play
按鈕,毫不奇怪疚漆,視頻將播放酣胀。
但是,如果點擊Choose
按鈕娶聘,則該應(yīng)用程序僅返回到Play Video
屏幕闻镶! 這是因為您尚未實現(xiàn)任何委托方法來處理從選擇器中選擇視頻。
返回Xcode丸升,再次打開PlayVideoViewController.swift
并找到UIImagePickerControllerDelegate
擴展铆农。 然后添加以下委托方法實現(xiàn):
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) 接下來墩剖,關(guān)閉圖像選擇器。
- 3) 在完成塊
(completion block)
中夷狰,創(chuàng)建一個AVPlayerViewController
來播放媒體岭皂。
構(gòu)建并運行。 點擊Select and Play Video
沼头,然后點擊Play Video
爷绘,然后從列表中選擇一個視頻。 該視頻將在媒體播放器中播放进倍。
Recording Video
現(xiàn)在您可以播放視頻了土至,現(xiàn)在該使用設(shè)備的攝像機錄制視頻并將其保存到媒體庫中了。
打開RecordVideoViewController.swift
并添加以下導(dǎo)入:
import MobileCoreServices
然后背捌,將以下內(nèi)容添加到文件末尾:
// MARK: - UIImagePickerControllerDelegate
extension RecordVideoViewController: UIImagePickerControllerDelegate {
}
// MARK: - UINavigationControllerDelegate
extension RecordVideoViewController: UINavigationControllerDelegate {
}
它采用與PlayVideoViewController
相同的協(xié)議毙籽。
接下來,將以下代碼添加到record(_ :)
:
VideoHelper.startMediaBrowser(delegate: self, sourceType: .camera)
它使用與PlayVideoViewController
中相同的helper
方法毡庆,除了它訪問.camera
以指示圖像選擇器以內(nèi)置照相機模式打開坑赡。
構(gòu)建并運行以查看您到目前為止所擁有的。
轉(zhuǎn)到Record
屏幕么抗,然后點擊Record Video
毅否。 代替相機圖庫,將打開相機用戶界面蝇刀。 當(dāng)alert
對話框詢問攝像機權(quán)限和麥克風(fēng)權(quán)限時螟加,單擊OK
。
最后,點擊屏幕底部的紅色錄制按鈕開始錄制視頻捆探; 完成錄制后然爆,再次點按它。
現(xiàn)在黍图,您有兩個選擇:使用錄制的視頻或重錄曾雕。 點擊Use Video
。 您會注意到助被,它只是關(guān)閉了視圖控制器剖张。 這是因為-您猜對了-您尚未實現(xiàn)適當(dāng)?shù)奈蟹椒▉韺浿频囊曨l保存到媒體庫。
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)
}
不必擔(dān)心該錯誤-很快就會解決搔弄。
- 1) 和以前一樣,委托方法為您提供指向視頻的
URL
丰滑。 - 2) 驗證該應(yīng)用程序可以將文件保存到設(shè)備的相冊中顾犹。
- 3) 如果可以,請保存吨枉。
SDK
所提供的UISaveVideoAtPathToSavedPhotosAlbum
功能可將視頻保存到設(shè)備的相冊中蹦渣。 您向其傳遞要保存的視頻的路徑以及要回調(diào)的目標(biāo)和操作,這將使您知道保存操作的狀態(tài)貌亭。
接下來,將回調(diào)的實現(xiàn)添加到主類定義中:
@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)
}
回調(diào)(callback)
方法僅向用戶顯示alert
认臊,并根據(jù)錯誤狀態(tài)宣布是否保存了視頻文件圃庭。
構(gòu)建并運行。 錄制視頻失晴,并在錄制完成后選擇Use Video
剧腻。 如果詢問您是否有權(quán)保存到視頻庫,請點擊OK
涂屁。 當(dāng)Video was saved
彈窗彈出時书在,您就成功將視頻保存到了照片庫!
現(xiàn)在您可以播放視頻和錄制視頻了拆又,該是下一步了儒旬,嘗試一些簡單的視頻編輯了。
Merging Videos
該應(yīng)用程序的最后一項功能是進行一些編輯帖族。您的用戶將從音樂庫中選擇兩個視頻和一首歌曲栈源,然后該應(yīng)用將合并兩個視頻并混入音樂。
該項目已經(jīng)在MergeVideoViewController.swift
中實現(xiàn)了入門實施竖般,其代碼類似于您編寫的用于播放視頻的代碼甚垦。最大的區(qū)別是,合并時,用戶必須選擇兩個視頻艰亮。該部分已經(jīng)設(shè)置好闭翩,因此用戶可以進行兩個選擇,這些選擇將存儲在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)
上面的代碼創(chuàng)建一個新的MPMediaPickerController
實例,并將其顯示為模式視圖控制器骇钦。
構(gòu)建并運行宛渐。 現(xiàn)在,點擊Merge Video
眯搭,然后點擊Load Audio
以訪問設(shè)備上的音頻庫窥翩。
當(dāng)然,您的設(shè)備上需要一些音頻文件鳞仙。 否則寇蚊,列表將為空。 這些歌曲還必須實際顯示在設(shè)備上棍好,因此請確保您不嘗試從云中加載歌曲仗岸。
從列表中選擇一首歌曲,您會發(fā)現(xiàn)沒有任何反應(yīng)借笙。 那就對了扒怖! MPMediaPickerController
需要委托方法!
要實現(xiàn)它們业稼,請在文件底部找到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) 在取消媒體選擇器的情況下吃嘿,只需關(guān)閉視圖控制器即可。
構(gòu)建并運行梦重,然后轉(zhuǎn)到Merge Videos
屏幕兑燥。 選擇一個音頻文件,您將看到Audio Loaded
消息琴拧。
現(xiàn)在降瞳,您的所有資源都已正確加載,是時候?qū)⒏鞣N媒體文件合并為一個文件了蚓胸。 但是在進入該代碼之前挣饥,您需要進行一些設(shè)置。
2. Merging Completion Handler
您將很快編寫代碼以合并您的資產(chǎn)沛膳。 這將需要一個完成處理程序扔枫,以將最終視頻保存到相冊中。 您將首先添加它锹安。
在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
,用于在處理資產(chǎn)時進行動畫處理叹哭。 這將停止spinner
忍宋,然后清除資源并準(zhǔn)備選擇新資源。 - 2) 確保處理完成风罩,并且有結(jié)果視頻的
URL
糠排。 - 3) 創(chuàng)建一個閉包
- 4) 告訴照片庫從顯示的視頻發(fā)出
create request
,然后顯示alert
以表明成功或失敗超升。 - 5) 檢查是否有訪問照片庫的權(quán)限乳讥。 如果沒有權(quán)限,則在運行保存視頻的閉包之前要求它廓俭。 否則,只要已授予許可唉工,就立即運行閉包研乒。
現(xiàn)在,您將添加一些代碼到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) 您創(chuàng)建一個
AVMutableComposition
來保存您的視頻和音頻軌道。 - 2) 接下來继谚,為視頻創(chuàng)建一個
AVMutableCompositionTrack
并將其添加到您的AVMutableComposition
中烈菌。 - 3) 然后,將視頻從第一個視頻資產(chǎn)插入到此軌道。
請注意芽世,insertTimeRange(_:ofTrack:atStartTime :)
允許您將視頻的一部分而不是整個部分插入到您的主要合成作品中挚赊。這樣,您可以將視頻修剪到您選擇的時間范圍济瓢。
在這種情況下荠割,您要插入整個視頻,因此創(chuàng)建一個從CMTime.zero
到視頻資源持續(xù)時間的時間范圍旺矾。
- 4) 接下來蔑鹦,您對第二個視頻資產(chǎn)執(zhí)行相同的操作。
請注意箕宙,代碼是如何在時間.zero
插入firstAsset
的嚎朽,然后在第一個視頻的末尾插入secondAsset
的。這是因為本教程假定您要一個接一個地使用視頻資源扒吁,但是您也可以通過播放時間范圍來重疊資產(chǎn)火鼻。
- 5)
// TODO: PASTE CODE A
是一個標(biāo)記-您將在下一部分中用代碼替換此行。
在此步驟中雕崩,您將設(shè)置兩個單獨的AVMutableCompositionTrack
實例】鳎現(xiàn)在,您需要將AVMutableVideoCompositionLayerInstruction
應(yīng)用于每個軌道盼铁,以便進行一些編輯粗蔚。
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
這是這段代碼中發(fā)生的事情:
- 6) 首先,設(shè)置
mainInstruction
來包裝整個instructions
肤寝。請注意当辐,此處的總時間是第一個資源的總長與第二個資源的總長之和。 - 7) 接下來鲤看,您設(shè)置了兩個
instructions
缘揪,每個資源對應(yīng)一個instructions
。第一個視頻的instructions
需要額外添加:您將其不透明度最后設(shè)置為0义桂,以便在第二個視頻開始時不可見找筝。 - 8) 現(xiàn)在,您已經(jīng)有了第一條和第二條軌道的
AVMutableVideoCompositionLayerInstruction
實例慷吊,您只需將它們添加到mainInstruction
中袖裕。接下來,將mainInstruction
添加到AVMutableVideoComposition
實例的指令屬性溉瓶。您還可以將合成的幀頻設(shè)置為30
幀/秒急鳄。 - 9)
// TODO: PASTE CODE B
是一個標(biāo)記-您將在下一部分中用代碼替換此行谤民。
好的,現(xiàn)在您已經(jīng)合并了兩個視頻文件攒岛。是時候為他們添加一些聲音了赖临!
Merging the Audio: Step 3
要使片段具有音樂風(fēng)格,請將以下代碼添加到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) 與視頻軌道相似兢榨,您可以為音頻創(chuàng)建一個新軌道并將其添加到主要
composition
中。您將音頻時間范圍設(shè)置為第一和第二視頻的時長之和顺饮,因為這將是視頻的完整長度吵聪。 - 11) 在保存最終視頻之前,需要一個保存文件的路徑兼雄。根據(jù)當(dāng)前日期和時間創(chuàng)建一個唯一的文件名吟逝,該文件名指向
documents
文件夾中的文件。 - 12) 渲染并導(dǎo)出合并的視頻赦肋。為此块攒,您將創(chuàng)建一個
AVAssetExportSession
,它將對合成的內(nèi)容進行轉(zhuǎn)碼以創(chuàng)建由指定的導(dǎo)出預(yù)設(shè)描述的格式輸出佃乘。由于您已經(jīng)配置了AVMutableVideoComposition
囱井,因此您只需將其分配給導(dǎo)出器即可。 - 13) 在使用包含源媒體趣避,導(dǎo)出
presetName
和outputFileType
的資源初始化導(dǎo)出會話后庞呕,可以通過調(diào)用exportAsynchronously()
運行導(dǎo)出。
因為代碼異步執(zhí)行導(dǎo)出程帕,所以此方法立即返回住练。無論導(dǎo)出失敗,完成還是用戶取消愁拭,代碼都會調(diào)用您提供給exportAsynchronously()
的完成處理程序柑土。
完成后振亮,導(dǎo)出的狀態(tài)status
屬性指示導(dǎo)出是否成功完成芒珠。如果失敗鸥跟,則導(dǎo)出器的error
屬性的值將提供有關(guān)失敗原因的其他信息峰搪。
AVComposition
組合了來自多個基于文件的來源的媒體數(shù)據(jù)伍掀。在最高層偎快,AVComposition
是軌道的集合囊咏,每個軌道都呈現(xiàn)特定類型的媒體株茶,例如音頻或視頻来涨。 AVCompositionTrack
的實例表示單個軌道。
同樣启盛,AVMutableComposition
和AVMutableCompositionTrack
也提供了用于構(gòu)建合成的更高級別的接口蹦掐。這些對象提供了您之前見過的插入技羔,移除和縮放操作,這些操作將再次出現(xiàn)卧抗。
最后藤滥,構(gòu)建并運行。
選擇兩個視頻和一個音頻文件社裆,然后合并選定的文件拙绊。您會看到一條Video Saved
消息,表明合成成功泳秀。此時标沪,您的新視頻將出現(xiàn)在相冊中。
轉(zhuǎn)到相冊或使用應(yīng)用程序中的Select and Play Video
屏幕瀏覽嗜傅,您可能會注意到合并視頻中的一些方向問題金句。 豎屏視頻可能處于橫向模式,有時視頻會倒置吕嘀。
這是由于默認的AVAsset
方向违寞。 使用默認的iPhone
相機應(yīng)用程序記錄的所有電影和圖像文件都將視頻幀設(shè)置為橫向,因此iPhone
以橫向模式保存媒體偶房。 接下來趁曼,您將解決這些問題。
Orienting Video
AVAsset
有一個PreferredTransform
蝴悉,其中包含媒體方向信息彰阴。 每當(dāng)您使用Photos
應(yīng)用程序或QuickTime
查看媒體文件時,它會將其應(yīng)用于媒體文件拍冠。
在上面的代碼中尿这,您尚未對AVAsets
進行轉(zhuǎn)換,因此沒有定向問題庆杜。 幸運的是射众,這很容易解決。
但是晃财,在執(zhí)行此操作之前叨橱,需要在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) 您創(chuàng)建
AVMutableVideoCompositionLayerInstruction
并將其與軌道關(guān)聯(lián)。 - 2) 接下來命迈,從您的
AVAsset
創(chuàng)建AVAssetTrack
贩绕。AVAssetTrack
為所有資源提供跟蹤級別的檢查接口火的。您需要此對象來訪問資源的尺寸和preferredTransform
。 - 3) 然后淑倾,保存首選的變換
(preferred transform)
和使視頻適合當(dāng)前屏幕所需的縮放比例馏鹤。您將在以下步驟中使用這些值。 - 4) 如果視頻是縱向視頻娇哆,則需要重新計算比例因子-默認計算是橫向視頻湃累。然后,您要做的就是應(yīng)用方向旋轉(zhuǎn)和比例變換迂尝。
- 5) 如果視頻是橫向播放脱茉,則可以采用類似的步驟來應(yīng)用縮放和變換。但是垄开,有一項額外的檢查琴许,因為用戶可能會以左橫向或右橫向制作視頻。
由于有兩個橫向溉躲,因此縱橫比將匹配榜田,但視頻可能會旋轉(zhuǎn)180度。對于.down
視頻方向的額外檢查可以解決這種情況锻梳。
設(shè)置好helper
方法后箭券,在MergeVideoViewController.swift
中找到merge(_ :)
。找到創(chuàng)建firstInstruction
和secondInstruction
的位置疑枯,并將它們替換為以下內(nèi)容:
let firstInstruction = VideoHelper.videoCompositionInstruction(
firstTrack,
asset: firstAsset)
let secondInstruction = VideoHelper.videoCompositionInstruction(
secondTrack,
asset: secondAsset)
上面的更改將使用新的helper
函數(shù)并實現(xiàn)所需的旋轉(zhuǎn)修復(fù)程序辩块。
哇-就是這樣!
構(gòu)建并運行荆永。 通過將兩個視頻(以及一個音頻文件)結(jié)合在一起來創(chuàng)建新視頻废亭,當(dāng)您播放視頻時,您會發(fā)現(xiàn)方向問題消失了具钥。
在播放視頻時豆村,AV Foundation
為您提供了很大的靈活性。 您還可以應(yīng)用任何類型的CGAffineTransform
合并骂删,縮放或定位視頻掌动。
如果您尚未這樣做,請看一下AV Foundation
上的WWDC videos宁玫,例如WWDC 2016 session 503, Advances in AVFoundation Playback
粗恢。
另外,請務(wù)必查看Apple AVFoundation Framework documentation欧瘪。
后記
本篇主要講述了播放适滓、錄制和合并視頻簡單示例,感興趣的給個贊或者關(guān)注~~~