AVAssetExportSessionの罠?
拙作のアプリふぉとむぐでは、AVAssetExportSession
を用いて多数の動画を合成する機能を実装したのですが、そこで思わぬ罠にはまってしまいました。
はじめに結果を申し上げると、この内容をApple Developer Technical Supportに報告したところ、iOS 10.3 の時点では不具合(というより制限?)のため、回避策を試して欲しいとのことでした。
ただ回答で来た方法ではうまくいかなったため、これをアレンジして回避することができたので、ここにその方法をまとめることにしました。
起こった問題
従来は動画の合成を行う際、下記の図のようにしていました。
- 各入力動画(
AVAsset
)からAVAssetTrack
を取り出し、その都度AVCompositionTrack
を追加
(つまり動画の数だけAVCompositionTrack
を生成していた) - 各
AVCompositionTrack
に対応するAVVideoCompositionLayerInstruction
を生成し、TransformやOpacityRampを適用 - ひとつの
AVVideoCompositionInstruction
に、各入力動画の対応するAVVideoCompositionLayerInstruction
を設定し、これを書き出す
このような方法で動画の合成を行うと、 多数の動画を入力したとき以下のエラーで書き出しに失敗します!
Error Domain=AVFoundationErrorDomain Code=-11839 "Cannot Decode" UserInfo={NSUnderlyingError=0x174247020 {Error Domain=NSOSStatusErrorDomain Code=-12913 "(null)"}, NSLocalizedFailureReason=The decoder required for this media is busy., NSLocalizedRecoverySuggestion=Stop any other actions that decode media and try again., NSLocalizedDescription=Cannot Decode}
(iPhone 6sでは15〜16個の動画合成で発生。シミュレータでは発生しない)
エラー内容とAppleからの回答から、AVCompositionTrack
はビデオデコーダのリソースを抑えたまま解放しない問題があるらしく、多量にインスタンス化するとリソース不足でエラーが起きてしまうようです。
解決策
お察しのとおり、AVCompositionTrack
を使い回して、必要以上に生成しなければ解決できます。
Appleからの回答では、2つのAVCompositionTrack
を交互に使えばいいとのことでしたがうまくいかなかったため、以下の図のようにしました。
-
AVCompositionTrack
を3つ用意する- Main: トランジション時間にかからない、各動画単独再生のトラック
- Transition Source: トランジション中の遷移元となる動画用トラック
- Transition Destination: トランジション中の遷移先となる動画用トラック
-
AVVideoCompositionInstruction
はトランジション時間ごとに別々に用意する- 非トランジション区間はMainトラックに追加した
AVVideoCompositionLayerInsturction
のみ参照 - トランジション区間はSource, Destinationトラック2つの
AVVideoCompositionLayerInsturction
を参照
- 非トランジション区間はMainトラックに追加した
その他・注意しておきたいところ
AVAssetExportSession
では他にも、こんなところが躓きやすい...
export中のエラー
時間の指定が間違っていないか確認しましょう。
-
AVVideoCompositionInstruction
のtimeRange
は"空白の区間"があってはいけない。つまりprevInstruction.timeRange.end == instruction.timeRange.start
である必要がある -
AVVideoCompositionInstruction
のtimeRange
と、insertTimeRange()
で追加した動画の位置と長さは一致していなければならない(おそらく)
exportは成功したけど動画が真っ黒
AVVideoCompositionLayerInstruction
のTransformを正しく設定しないとイイ位置に来てくれません。
- Live Photosから取り出した
AVAssetTrack
のpreferredTransform.tx, ty
は画像解像度に準拠しない値になっていることがある
(Live Photos動画の解像度とフレームレートが、リリース当初と最近で仕様が違うため?) - 出力解像度を変える(スケーリングする)場合、CropRectangleを指定しないとスケールしない場合がある