AVAssetExportSessionで多数の動画を合成するときにハマッたお話


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を参照

その他・注意しておきたいところ

AVAssetExportSessionでは他にも、こんなところが躓きやすい...

export中のエラー

時間の指定が間違っていないか確認しましょう。

  • AVVideoCompositionInstructiontimeRangeは"空白の区間"があってはいけない。つまりprevInstruction.timeRange.end == instruction.timeRange.startである必要がある
  • AVVideoCompositionInstructiontimeRangeと、insertTimeRange()で追加した動画の位置と長さは一致していなければならない(おそらく)

exportは成功したけど動画が真っ黒

AVVideoCompositionLayerInstructionのTransformを正しく設定しないとイイ位置に来てくれません。

  • Live Photosから取り出したAVAssetTrackpreferredTransform.tx, tyは画像解像度に準拠しない値になっていることがある (Live Photos動画の解像度とフレームレートが、リリース当初と最近で仕様が違うため?)
  • 出力解像度を変える(スケーリングする)場合、CropRectangleを指定しないとスケールしない場合がある