版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2018.08.18 |
前言
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支持(二)
開始
這一篇我們就一起看一下播放缴川、錄制以及混合視頻茉稠。
注意:本文寫作環(huán)境Swift 4, iOS 11, Xcode 9。
錄制視頻把夸,并以編程方式播放它們蜜宪,這是您可以使用手機做的最酷的事情之一械巡,但沒有足夠的應(yīng)用程序使用它腕柜。 要做到這一點,需要AV Foundation框架嘹狞,該框架自OS X Lion(10.7)
以來一直是macOS的一部分,自2010年iOS 4以來一直是iOS的一部分誓竿。
AV Foundation
已經(jīng)有了很大的發(fā)展磅网,目前有超過100個類。 本教程介紹了媒體播放和一些輕量級編輯筷屡,以幫助您開始使用AV Foundation涧偷。 特別是,您將學(xué)習:
- 從媒體庫中選擇并播放視頻毙死。
- 錄制視頻并將其保存到媒體庫燎潮。
- 將多個視頻合并為一個組合視頻,并配有自定義聲音扼倘!
本工程包括一個storyboard和幾個VC确封,UI用來處理簡單的視頻播放以及錄制。
主屏幕包含下面的三個按鈕唉锌,它們與其他VC相對應(yīng):
Select and Play Video
Record and Save Video
Merge Video
Build并Run這個工程隅肥,只有初始場景上的三個按鈕可以做點事情竿奏,但你很快就會改變它袄简!
Select and Play Video - 選擇并播放視頻
主屏幕上的Select and Play Video
按鈕將segues
到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 {
}
這些擴展設(shè)置PlayVideoViewController
以采用UIImagePickerControllerDelegate
和UINavigationControllerDelegate
協(xié)議。 您將使用系統(tǒng)提供的UIImagePickerController
來允許用戶瀏覽照片庫中的視頻炸卑,并且該類通過這些委托協(xié)議與您的應(yīng)用程序進行通信既鞠。 雖然該類被命名為“image picker”
,但請放心盖文,它也適用于視頻嘱蛋!
接下來,返回PlayVideoViewController
的主類定義,并從VideoHelper
添加對helper
方法的調(diào)用以打開圖像選擇器洒敏。 稍后龄恋,您將在VideoHelper中添加自己的幫助工具。 將以下代碼添加到playVideo(_ :)
:
VideoHelper.startMediaBrowser(delegate: self, sourceType: .savedPhotosAlbum)
在上面的代碼中桐玻,您確保點擊Play Video
將打開UIImagePickerController
篙挽,允許用戶從媒體庫中選擇視頻文件。
要查看此方法的內(nèi)容镊靴,請打開VideoHelper.swift
铣卡。它執(zhí)行以下操作:
- 1)檢查設(shè)備上是否有
.savedPhotosAlbum
源。其他來源是相機本身和照片庫偏竟。每當您使用UIImagePickerController選擇媒體時煮落,此檢查都是必不可少的。如果不這樣做踊谋,您可能會嘗試從不存在的媒體庫中選擇媒體蝉仇,從而導(dǎo)致崩潰或其他意外問題。 - 2)如果您想要的源可用殖蚕,它將創(chuàng)建一個
UIImagePickerController
對象并設(shè)置其源和媒體類型轿衔。 - 3)最后,它以模態(tài)方式呈現(xiàn)
UIImagePickerController
睦疫。
現(xiàn)在害驹,Build項目并運行。點擊第一個屏幕上的Select and Play Video
蛤育,然后點擊第二個屏幕上的Play Video
宛官,您應(yīng)該看到您的視頻顯示類似于以下屏幕截圖。
看到視頻列表后瓦糕,請選擇一個底洗。 您將進入另一個詳細顯示視頻的屏幕,以及取消咕娄,播放和選擇的按鈕亥揖。 如果您點按播放按鈕,視頻將會播放圣勒。 但是费变,如果您點擊選擇按鈕,應(yīng)用程序只會返回播放視頻屏幕灾而! 這是因為您沒有實現(xiàn)任何委托方法來處理從選擇器中選擇視頻的問題胡控。
回到Xcode,向下滾動到PlayVideoViewController.swift
中的UIImagePickerControllerDelegate
類擴展旁趟,并添加以下委托方法實現(xiàn):
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [String : Any]) {
// 1
guard
let mediaType = info[UIImagePickerControllerMediaType] as? String,
mediaType == (kUTTypeMovie as String),
let url = info[UIImagePickerControllerMediaURL] 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的媒體類型昼激。 你確保它類型是movie庇绽。
- 2)你dismiss了
image picker
。 - 3)在完成的block中橙困,您將創(chuàng)建一個
AVPlayerViewController
來播放媒體瞧掺。
Build并運行。 點擊Select and Play Video
凡傅,然后Play Video
辟狈,然后從列表中選擇一個視頻。 您應(yīng)該能夠在媒體播放器中看到視頻播放夏跷。
Record and Save Video - 錄制并保存視頻
現(xiàn)在您已經(jīng)進行了視頻播放哼转,現(xiàn)在可以使用設(shè)備的相機錄制視頻并將其保存到媒體庫。
打開RecordVideoViewController.swift
槽华,并添加以下導(dǎo)入:
import MobileCoreServices
您還需要遵守與PlayVideoViewController
相同的協(xié)議壹蔓,方法是在文件末尾添加以下內(nèi)容:
extension RecordVideoViewController: UIImagePickerControllerDelegate {
}
extension RecordVideoViewController: UINavigationControllerDelegate {
}
將以下代碼添加到record(_:)
:
VideoHelper.startMediaBrowser(delegate: self, sourceType: .camera)
它使用與PlayVideoViewController
相同的helper
方法,但它訪問.camera
而不是錄制視頻猫态。
Build并運行以查看到目前為止您所擁有的內(nèi)容佣蓉。
轉(zhuǎn)到Record
屏幕,然后點擊Record Video
亲雪。 相機UI將打開勇凭,而不是照片庫。 當alert對話框詢問攝像機權(quán)限和麥克風權(quán)限時义辕,單擊OK
虾标。 通過點擊屏幕底部的紅色記錄按鈕開始錄制視頻,并在完成錄制后再次點擊它终息。
現(xiàn)在您可以選擇使用錄制的視頻或重新錄制夺巩。 點按Use Video
贞让。 您會注意到它只是取消了視圖控制器周崭。 那是因為 - 您猜對了 - 您還沒有實現(xiàn)適當?shù)奈蟹椒▽浿频囊曨l保存到媒體庫。
將以下方法添加到底部的UIImagePickerControllerDelegate
類擴展中:
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [String : Any]) {
dismiss(animated: true, completion: nil)
guard
let mediaType = info[UIImagePickerControllerMediaType] as? String,
mediaType == (kUTTypeMovie as String),
let url = info[UIImagePickerControllerMediaURL] as? URL,
UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(url.path)
else {
return
}
// Handle a movie capture
UISaveVideoAtPathToSavedPhotosAlbum(
url.path,
self,
#selector(video(_:didFinishSavingWithError:contextInfo:)),
nil)
}
不要擔心最后一行代碼的錯誤喳张,你很快就會解決這個問題续镇。
和以前一樣,委托方法會為您提供指向視頻的URL销部。 您確認應(yīng)用程序可以將文件保存到設(shè)備的相冊中摸航,如果是,請保存舅桩。
UISaveVideoAtPathToSavedPhotosAlbum
是SDK提供的用于將視頻保存到相冊的功能酱虎。 作為參數(shù),您將路徑傳遞給要保存的視頻以及要回調(dià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: UIAlertActionStyle.cancel, handler: nil))
present(alert, animated: true, completion: nil)
}
回調(diào)方法只是向用戶顯示alert彈窗,根據(jù)錯誤狀態(tài)通知是否保存了視頻文件。
Build并運行恢暖。 錄制視頻排监,然后在錄制完成后選擇Use Video
。 如果您被要求獲得保存到視頻庫的權(quán)限杰捂,請點按OK
舆床。 如果彈出Video was saved
彈窗,表明您已將視頻成功保存到照片庫嫁佳!
現(xiàn)在您可以播放視頻和錄制視頻挨队,現(xiàn)在是時候采取下一步并嘗試一些輕量級視頻編輯。
Merging Videos - 混合視頻
該應(yīng)用程序的最后一項功能是進行一些編輯蒿往。您的用戶將從音樂庫中選擇兩個視頻和一首歌曲瞒瘸,該應(yīng)用程序?qū)⒑喜⑦@兩個視頻并混合音樂。
該項目已在MergeVideoViewController.swift
中有一個初始實現(xiàn)熄浓。這里的代碼類似于您為播放視頻而編寫的代碼情臭。最大的區(qū)別在于合并時,用戶需要選擇兩個視頻赌蔑。該部分已經(jīng)設(shè)置好俯在,因此用戶可以進行兩個選擇,這些選擇將存儲在firstAsset
和secondAsset
中娃惯。
下一步是添加選擇音頻文件的功能跷乐。
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
實例,并將其顯示為模態(tài)視圖控制器证膨。
Build并運行如输。 現(xiàn)在點擊Merge Video
,然后點擊Load Audio
以訪問設(shè)備上的音頻庫央勒。 當然不见,您需要在設(shè)備上使用一些音頻文件。 否則崔步,列表將為空稳吮。 歌曲也必須實際存在于設(shè)備上,因此請確保您沒有嘗試從云中加載歌曲井濒。
如果您從列表中選擇一首歌曲灶似,您會發(fā)現(xiàn)沒有任何反應(yīng)慎陵。 那就對了! MPMediaPickerController
需要委托方法喻奥! 在文件底部找到MPMediaPickerControllerDelegate
類擴展席纽,并添加以下兩個方法:
func mediaPicker(_ mediaPicker: MPMediaPickerController,
didPickMediaItems mediaItemCollection: MPMediaItemCollection) {
dismiss(animated: true) {
let selectedSongs = mediaItemCollection.items
guard let song = selectedSongs.first else { return }
let url = song.value(forProperty: MPMediaItemPropertyAssetURL) as? URL
self.audioAsset = (url == nil) ? nil : AVAsset(url: url!)
let title = (url == nil) ? "Asset Not Available" : "Asset Loaded"
let message = (url == nil) ? "Audio Not Loaded" : "Audio Loaded"
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) {
dismiss(animated: true, completion: nil)
}
該代碼與UIImagePickerController
的委托方法非常相似。 在確保它是有效的媒體項目后撞蚕,您可以根據(jù)通過MPMediaPickerController
選擇的媒體項目設(shè)置音頻資源润梯。 請注意,在dismiss當前控制器之后僅顯示新的視圖控制器很重要甥厦,這就是為什么將上面的代碼包裝在完成處理程序中的原因纺铭。
Build并運行。 轉(zhuǎn)到Merge Videos
屏幕刀疙。 選擇一個音頻文件舶赔,如果沒有錯誤,您應(yīng)該看到“Audio Loaded”
消息谦秧。
您現(xiàn)在可以正確加載所有資源竟纳。 是時候?qū)⒏鞣N媒體文件合并到一個文件中了。 但是在你進入代碼之前疚鲤,你必須做一些設(shè)置锥累。
1. Export and Merge - 導(dǎo)出和合并
合并資源的代碼需要完成處理程序才能將最終視頻導(dǎo)出到相冊。
將以下代碼添加到MergeVideoViewController
:
func exportDidFinish(_ session: AVAssetExportSession) {
// Cleanup assets
activityMonitor.stopAnimating()
firstAsset = nil
secondAsset = nil
audioAsset = nil
guard
session.status == AVAssetExportSessionStatus.completed,
let outputURL = session.outputURL
else {
return
}
let saveVideoToPhotos = {
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: outputURL)
}) { saved, error in
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: UIAlertActionStyle.cancel, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
// Ensure permission to access Photo Library
if PHPhotoLibrary.authorizationStatus() != .authorized {
PHPhotoLibrary.requestAuthorization { status in
if status == .authorized {
saveVideoToPhotos()
}
}
} else {
saveVideoToPhotos()
}
}
導(dǎo)出成功完成后集歇,上面的代碼會將新導(dǎo)出的視頻保存到相冊中桶略。 您只需在AssetBrowser
中顯示輸出視頻,但將輸出視頻復(fù)制到相冊會更容易诲宇,這樣您就可以看到最終輸出际歼。
現(xiàn)在,添加以下代碼到merge(_:)
:
guard
let firstAsset = firstAsset,
let secondAsset = secondAsset
else {
return
}
activityMonitor.startAnimating()
// 1 - Create AVMutableComposition object. This object will hold your AVMutableCompositionTrack instances.
let mixComposition = AVMutableComposition()
// 2 - Create two video tracks
guard
let firstTrack = mixComposition.addMutableTrack(withMediaType: AVMediaType.video,
preferredTrackID: Int32(kCMPersistentTrackID_Invalid))
else {
return
}
do {
try firstTrack.insertTimeRange(CMTimeRangeMake(kCMTimeZero, firstAsset.duration),
of: firstAsset.tracks(withMediaType: AVMediaType.video)[0],
at: kCMTimeZero)
} catch {
print("Failed to load first track")
return
}
guard
let secondTrack = mixComposition.addMutableTrack(withMediaType: AVMediaType.video,
preferredTrackID: Int32(kCMPersistentTrackID_Invalid))
else {
return
}
do {
try secondTrack.insertTimeRange(CMTimeRangeMake(kCMTimeZero, secondAsset.duration),
of: secondAsset.tracks(withMediaType: AVMediaType.video)[0],
at: firstAsset.duration)
} catch {
print("Failed to load second track")
return
}
// 3 - Audio track
if let loadedAudioAsset = audioAsset {
let audioTrack = mixComposition.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: 0)
do {
try audioTrack?.insertTimeRange(CMTimeRangeMake(kCMTimeZero,
CMTimeAdd(firstAsset.duration,
secondAsset.duration)),
of: loadedAudioAsset.tracks(withMediaType: AVMediaType.audio)[0] ,
at: kCMTimeZero)
} catch {
print("Failed to load Audio track")
}
}
// 4 - Get path
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")
// 5 - Create Exporter
guard let exporter = AVAssetExportSession(asset: mixComposition,
presetName: AVAssetExportPresetHighestQuality) else {
return
}
exporter.outputURL = url
exporter.outputFileType = AVFileType.mov
exporter.shouldOptimizeForNetworkUse = true
// 6 - Perform the Export
exporter.exportAsynchronously() {
DispatchQueue.main.async {
self.exportDidFinish(exporter)
}
}
以下是上述代碼的逐步細分:
- 1)您創(chuàng)建一個
AVMutableComposition
對象來保存視頻和音頻軌道以及轉(zhuǎn)換效果姑蓝。 - 2)接下來鹅心,為視頻創(chuàng)建
AVMutableCompositionTrack
并將其添加到AVMutableComposition
對象。然后將兩個視頻插入新創(chuàng)建的AVMutableCompositionTrack
它掂。
請注意巴帮,insertTimeRange(_:ofTrack:atStartTime :)
允許您將視頻的一部分插入主要合成而不是整個視頻溯泣。這樣虐秋,您可以將視頻剪裁到您選擇的時間范圍。
在這種情況下垃沦,您要插入整個視頻客给,以便創(chuàng)建從kCMTimeZero
到視頻資源持續(xù)時間的時間范圍。 atStartTime
參數(shù)允許您將視頻/音頻軌道放置在合成中的任何位置肢簿。注意代碼如何在零時插入firstAsset
靶剑,并在第一個視頻的末尾插入secondAsset
蜻拨。本教程假設(shè)您希望視頻資源一個接一個。但您也可以通過播放時間范圍來重疊資源桩引。
要處理時間范圍缎讼,請使用CMTime
結(jié)構(gòu)。 CMTime結(jié)構(gòu)是表示時間的非不透明可變結(jié)構(gòu)坑匠,其中時間可以是時間戳或持續(xù)時間血崭。
- 3)同樣,您可以為音頻創(chuàng)建新軌道并將其添加到主合成中厘灼。這次您將音頻時間范圍設(shè)置為第一個和第二個視頻的持續(xù)時間之和夹纫,因為這將是視頻的完整長度。
- 4)在保存最終視頻之前设凹,您需要一個保存文件的路徑舰讹。因此,創(chuàng)建一個指向文檔文件夾中文件的唯一文件名(基于當前日期)闪朱。
- 5)最后月匣,渲染并導(dǎo)出合并的視頻。為此奋姿,您需要創(chuàng)建一個
AVAssetExportSession
對象桶错,該對象對AVAsset
源對象的內(nèi)容進行轉(zhuǎn)碼,以創(chuàng)建由指定導(dǎo)出預(yù)設(shè)描述的格式的輸出胀蛮。 - 6)使用包含源媒體的資源院刁,導(dǎo)出預(yù)設(shè)名稱
(presetName)
和輸出文件類型(outputFileType)
初始化導(dǎo)出會話后,通過調(diào)用exportAsynchronously()
啟動導(dǎo)出運行粪狼。因為代碼異步執(zhí)行導(dǎo)出退腥,所以此方法立即返回。代碼調(diào)用您為exportAsynchronously()
提供的完成處理程序再榄,無論導(dǎo)出是失敗狡刘,完成還是用戶取消。完成后困鸥,導(dǎo)出器的status
屬性指示導(dǎo)出是否已成功完成嗅蔬。如果失敗,則導(dǎo)出器的error
屬性的值提供有關(guān)失敗原因的其他信息疾就。
AVComposition
實例組合來自多個基于文件的源的媒體數(shù)據(jù)澜术。 在頂層,AVComposition是一組軌道猬腰,每個軌道都呈現(xiàn)特定類型的媒體鸟废,如音頻或視頻。 AVCompositionTrack
的實例表示單個軌道姑荷。
類似地盒延,AVMutableComposition
和AVMutableCompositionTrack
也提供了用于構(gòu)造合成的更高級別的接口缩擂。 這些對象提供了您已經(jīng)看過的插入,移除和縮放操作添寺。
繼續(xù)胯盯,Build并運行您的項目!
選擇兩個視頻和一個音頻文件并合并所選文件计露。 如果合并成功陨闹,您應(yīng)該看到AVComposition實例組合來自多個基于文件的源的媒體數(shù)據(jù)。 在頂層薄坏,AVComposition是一組軌道趋厉,每個軌道都呈現(xiàn)特定類型的媒體,如音頻或視頻胶坠。 AVCompositionTrack的實例表示單個軌道君账。
類似地,AVMutableComposition和AVMutableCompositionTrack也提供了用于構(gòu)造組合的更高級別的接口沈善。 這些對象提供了您已經(jīng)看過的插入乡数,移除和縮放操作,并且會再次出現(xiàn)闻牡。
繼續(xù)净赴,構(gòu)建并運行您的項目!
選擇兩個視頻和一個音頻文件并合并所選文件罩润。 如果合并成功玖翅,您應(yīng)該看到“視頻已保存”
消息。 此時割以,您的新視頻應(yīng)該出現(xiàn)在相冊中金度。消息。 此時严沥,您的新視頻應(yīng)該出現(xiàn)在相冊中猜极。
轉(zhuǎn)到相冊,或使用應(yīng)用程序中的Select and Play Video
屏幕進行瀏覽消玄。 您可能會注意到跟伏,雖然應(yīng)用程序合并了視頻,但仍存在一些方向問題翩瓜。 縱向視頻處于橫向模式受扳,有時視頻會上下顛倒。
這是由于默認的AVAsset
方向奥溺。 使用默認iPhone相機應(yīng)用程序錄制的所有電影和圖像文件都將視頻幀設(shè)置為橫向辞色,因此iPhone會以橫向模式保存媒體。
2. Video Orientation - 視頻方向
AVAsset
具有包含媒體方向信息的preferredTransform
屬性浮定,只要您使用Photos應(yīng)用程序或QuickTime查看媒體文件相满,它就會將其應(yīng)用于媒體文件。 在上面的代碼中桦卒,您尚未將變換應(yīng)用于AVAsset
對象立美,因此導(dǎo)致問題。
您可以通過將必要的變換應(yīng)用于AVAsset
對象來輕松糾正此問題方灾。 但是建蹄,由于您的兩個視頻文件可能具有不同的方向,因此您需要使用兩個單獨的AVMutableCompositionTrack
實例裕偿,而不是您最初使用的實例洞慎。
在執(zhí)行此操作之前,請將以下helper
方法添加到VideoHelper
:
static func orientationFromTransform(_ transform: CGAffineTransform)
-> (orientation: UIImageOrientation, isPortrait: Bool) {
var assetOrientation = UIImageOrientation.up
var isPortrait = false
if transform.a == 0 && transform.b == 1.0 && transform.c == -1.0 && transform.d == 0 {
assetOrientation = .right
isPortrait = true
} else if transform.a == 0 && transform.b == -1.0 && transform.c == 1.0 && transform.d == 0 {
assetOrientation = .left
isPortrait = true
} else if transform.a == 1.0 && transform.b == 0 && transform.c == 0 && transform.d == 1.0 {
assetOrientation = .up
} else if transform.a == -1.0 && transform.b == 0 && transform.c == 0 && transform.d == -1.0 {
assetOrientation = .down
}
return (assetOrientation, isPortrait)
}
此代碼分析仿射變換affine transform
以確定輸入視頻的方向嘿棘。
接下來劲腿,向該類添加一個helper
方法:
static func videoCompositionInstruction(_ track: AVCompositionTrack, asset: AVAsset)
-> AVMutableVideoCompositionLayerInstruction {
let instruction = AVMutableVideoCompositionLayerInstruction(assetTrack: track)
let assetTrack = asset.tracks(withMediaType: .video)[0]
let transform = assetTrack.preferredTransform
let assetInfo = orientationFromTransform(transform)
var scaleToFitRatio = UIScreen.main.bounds.width / assetTrack.naturalSize.width
if assetInfo.isPortrait {
scaleToFitRatio = UIScreen.main.bounds.width / assetTrack.naturalSize.height
let scaleFactor = CGAffineTransform(scaleX: scaleToFitRatio, y: scaleToFitRatio)
instruction.setTransform(assetTrack.preferredTransform.concatenating(scaleFactor), at: kCMTimeZero)
} else {
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: kCMTimeZero)
}
return instruction
}
此方法獲取軌道和資源,并返回AVMutableVideoCompositionLayerInstruction
鸟妙,其封裝了使視頻正面朝上所需的仿射變換焦人。這是正在做的事情,一步一步看一下:
- 您創(chuàng)建
AVMutableVideoCompositionLayerInstruction
并將其與firstTrack
關(guān)聯(lián)重父。 - 接下來花椭,從
AVAsset
創(chuàng)建AVAssetTrack
對象。AVAssetTrack
對象為所有資產(chǎn)提供軌道級別檢查接口房午。您需要此對象才能訪問preferredTransform
和資源的維度矿辽。 - 然后,保存首選變換以及使視頻適合當前屏幕所需的縮放量郭厌。您將在以下步驟中使用這些值嗦锐。
- 如果視頻是縱向的,則需要重新計算比例因子沪曙,因為默認計算適用于橫向視頻奕污。然后,您需要做的就是應(yīng)用方向旋轉(zhuǎn)和縮放變換液走。
- 如果視頻是橫向視圖碳默,則應(yīng)用縮放和變換的步驟類似。有一個額外的檢查缘眶,因為視頻可以在
landscape left
或landscape right
中生成嘱根。因為有“兩個landscape”,寬高比會進行匹配巷懈,但視頻可能會旋轉(zhuǎn)180度该抒。額外檢查.Down的
視頻方向?qū)⑻幚泶朔N情況。
設(shè)置好helper
方法后顶燕,找到merge(_ :)
并在#2和#3部分之間插入以下內(nèi)容:
// 2.1
let mainInstruction = AVMutableVideoCompositionInstruction()
mainInstruction.timeRange = CMTimeRangeMake(kCMTimeZero,
CMTimeAdd(firstAsset.duration, secondAsset.duration))
// 2.2
let firstInstruction = VideoHelper.videoCompositionInstruction(firstTrack, asset: firstAsset)
firstInstruction.setOpacity(0.0, at: firstAsset.duration)
let secondInstruction = VideoHelper.videoCompositionInstruction(secondTrack, asset: secondAsset)
// 2.3
mainInstruction.layerInstructions = [firstInstruction, secondInstruction]
let mainComposition = AVMutableVideoComposition()
mainComposition.instructions = [mainInstruction]
mainComposition.frameDuration = CMTimeMake(1, 30)
mainComposition.renderSize = CGSize(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
首先凑保,設(shè)置兩個單獨的AVMutableCompositionTrack
實例冈爹。這意味著您需要將AVMutableVideoCompositionLayerInstruction
應(yīng)用于每個軌道,以便分別固定方向欧引。
2.1:首先频伤,設(shè)置mainInstruction
來包裝整套instructions
。請注意芝此,此處的總時間是第一個資源的持續(xù)時間和第二個資源的持續(xù)時間之和憋肖。
2.2:接下來,使用您之前定義的helper
方法設(shè)置兩條指令 - 每條資源一條婚苹。第一個視頻的指令需要額外添加一個:在最后將其不透明度設(shè)置為0岸更,以便在第二個視頻啟動時變?yōu)椴豢梢姟?/p>
2.3:現(xiàn)在您已經(jīng)擁有了第一個和第二個軌道的AVMutableVideoCompositionLayerInstruction
實例,只需將它們添加到主AVMutableVideoCompositionInstruction
對象即可膊升。接下來怎炊,將mainInstruction
對象添加到AVMutableVideoComposition
實例的指令屬性中。您還可以將合成的幀速率設(shè)置為30幀/秒用僧。
現(xiàn)在您已經(jīng)配置了AVMutableVideoComposition
對象结胀,您只需將其分配給導(dǎo)出器即可。在第5節(jié)末尾插入以下代碼(就在exportAsynchronously():
之前责循。
exporter.videoComposition = mainComposition
Build并運行您的項目糟港。 如果您通過組合兩個視頻(以及可選的音頻文件)來創(chuàng)建新視頻,則會在播放新的合并視頻時看到方向問題消失院仿。
如果一直細心的看到這里秸抚,您現(xiàn)在應(yīng)該很好地了解如何在您的應(yīng)用中播放視頻,錄制視頻以及合并多個視頻和音頻歹垫。
AV Foundation
在播放視頻時為您提供了很大的靈活性剥汤。 您還可以應(yīng)用任何類型的CGAffineTransform
來合并,縮放或定位視頻排惨。
如果您還不會或者不了解吭敢,我建議您查看AV Foundation上WWDC videos,例如AV Foundation播放中的WWDC 2016 session 503 Advanced
暮芭。 另外鹿驼,請務(wù)必查看 Apple AV Foundation Framework documentation。
題外話:今天是我的生日辕宏,祝自己生日快樂吧畜晰!希望以后的日子會像數(shù)字880818一樣幸運和吉祥宿百,謝謝大家一如既往的支持和鼓勵~~~
后記
本篇主要講述了播放魄揉、錄制以及混合視頻,感興趣的給個贊或者關(guān)注~~~