AVFoundationでの動画編集処理の最小実装と勘所(Swift 5 / iOS 14)
iOS/macOSでAVFoundationを用いて動画編集処理を実装するための参考記事はわりとたくさんある。
が、最小実装といいつつUIViewControllerのコードが混じってたり(動画処理に本質的に関係ない)、Photosでカメラロールに保存する実装が混じってたり、ObjCだったり古いSwiftだったりしてどうにも「これ」という参考記事がなかったので、あらためて記事に書いておく。
あとそもそもAPIとしてわかりにくいところ・落とし穴がいくつかあったので、それについても書いておく。
本記事の内容の応用例はこちら:
AVMutableCompositionとAVMutableVideoComposition
APIとしてわかりにくい・混乱を招きやすいと思った点がこれ。
AVMutableVideoCompositionは、AVMutableCompositionの子クラスではない。同様に、AVVideoCompositionはAVCompositionの子クラスではない。
AVMutableVideoComposition -> AVVideoComposition -> NSObject
という継承関係になっていて、実はまったく関係がない。
僕は当初これを勘違いしていて、以下のように混乱した:
AVMutableCompositionにはこんな感じで動画トラックや音声トラックを追加していって、
let composition = AVMutableComposition()
let compositionVideoTrack = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)!
let compositionAudioTrack = composition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)!
try! compositionVideoTrack.insertTimeRange(timeRange, of: videoTrack, at: startTime)
try! compositionAudioTrack.insertTimeRange(timeRange, of: audioTrack, at: startTime)
「別途」AVMutableVideoCompositionなるものを生成して、
let videoComposition = AVMutableVideoComposition()
videoComposition.renderSize = videoTrack.naturalSize
videoComposition.frameDuration = CMTimeMake(value: 1, timescale: 30)
ここにAVVideoCompositionInstructionオブジェクトを渡し、
videoComposition.instructions = [instruction]
AVAssetExportSession初期化時にAVMutableCompositionオブジェクトをイニシャライザに渡しつつ、videoCompositionプロパティにはAVMutableVideoCompositionオブジェクトを渡す。
let assetExport = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetHighestQuality)!
assetExport.videoComposition = videoComposition
で、AVMutableVideoCompositionはAVMutableCompositionの子クラスだと勘違いしていた自分がこの実装を見たときに思ったのが、
「は?それなら最初からAVMutableVideoCompositionにvideo/audioトラックをつっこんで、instructionも渡せば良くない??」
と。
こんなイメージ(間違っているコードです):
// ※間違っているコード
let videoComposition = AVMutableVideoComposition()
let compositionVideoTrack = videoComposition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)!
let compositionAudioTrack = videoComposition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)!
try! compositionVideoTrack.insertTimeRange(timeRange, of: videoTrack, at: startTime)
try! compositionAudioTrack.insertTimeRange(timeRange, of: audioTrack, at: startTime)
videoComposition.instructions = [instruction]
let assetExport = AVAssetExportSession(asset: videoComposition, presetName: AVAssetExportPresetHighestQuality)!
AVMutableVideoCompositionとAVMutableCompositionはまったく別モノと理解していれば「そういうもの」として元の実装に納得できる。
ちなみになぜvideoやaudioを追加していくAVMutableCompositionとAVMutableVideoCompositionが分かれているのか考えてみると、おそらくAVMutableCompositionは動画だけじゃなく音声のことも考慮されていて、AVMutableVideoCompositionは動画専用の諸々を管理する用なのだろう。
-11841エラーとAVMutableVideoCompositionInstructionの罠
AVAssetExportSessionでエクスポートを実行すると、次のようなエラーが発生。
Error Domain=AVFoundationErrorDomain Code=-11841 "Operation Stopped" UserInfo={NSLocalizedFailureReason=The video could not be composed., ...}}
エラーメッセージに何のヒントもない...
原因はこれだった。
let layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)
このAVMutableVideoCompositionLayerInstructionのイニシャライザは次のように定義されている。
convenience init(assetTrack track: AVAssetTrack)
なるほどー、とAVAssetオブジェクトから抽出したAVAssetTrackオブジェクトを渡すと、上述の-11841エラーとなる。
なんと、
let compositionVideoTrack = videoComposition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)!
で返り値として得られるAVMutableCompositionTrackオブジェクトを渡す必要がある。
let layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: compositionVideoTrack)
それなら最初からイニシャライザの引数の型をAVMutableCompositionTrackとして欲しいが...これも複雑な処理を実装しているうちに設計者の意図が理由できてくるのだろう...
Swift 5 / iOS 14での最小実装
元の動画のAVAssetを、そのままエクスポートし直す(読み込んでもう一度エンコードする)処理の最小実装。
最後まで読んでいただきありがとうございます!もし参考になる部分があれば、スキを押していただけると励みになります。 Twitterもフォローしていただけたら嬉しいです。 https://twitter.com/shu223/