はじめに
こんにちは。WEAR部iOSチームの坂倉です。先日、WEARにコーディネート動画の投稿機能を実装しました。
iOSで動画を扱うにはAVFoundationを使う必要がありますが、原因がわかりにくいエラーを引き起こすことが多々あり、実装になかなか苦労しました。
この記事では、動画投稿の開発中に起きた問題とその解決法をお伝えします。
WEARの動画投稿には以下の機能が存在します。
- 動画を選択する
- 動画をプレビューする
- 動画に付与する音楽を選択する
- 動画に付与する音楽の範囲を指定してトリミングする
- 動画に関する情報を付与する
- 動画と音楽をミックスしてエンコードする
- 完成した動画を投稿する
これらを実装する中で、2つの問題に直面しました。
- 特定の動画が原因不明のエラーでエンコードできない
- 音楽の再生と合わせて一部の波形のみをアニメーション付きで着色する処理の設計
それぞれの解決方法を下記にてお伝えいたします。
特定の動画が原因不明のエラーでエンコードできない
動画といっても色々な種類があり、この動画のエンコードは問題ないが、あの動画はエラーが出てしまうといったことが多々ありました。
ここでは、その時の対処法について解説します。
ちなみに、WEARで使用しているエンコードのコードは以下の通りです(一部省略)。
// 動画と音楽のURLからAVURLAssetを生成
let videoURLAsset: AVURLAsset = .init(url: videoPath)
let audioURLAsset: AVURLAsset = .init(url: audioPath)
guard let videoAssetTrack = videoURLAsset.tracks(withMediaType: .video).first,
let audioAssetTrack = audioURLAsset.tracks(withMediaType: .audio).first
else {
return
}
// AVURLAssetから動画+音楽を追加し、AVAssetExportSessionに必要なコンポジションを作成
let composition: AVMutableComposition = .init()
// 動画をAVMutableCompositionに追加
guard let videoTrack = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid) else {
return
}
try? videoTrack.insertTimeRange(videoAssetTrack.timeRange, of: videoAssetTrack, at: .zero)
// 範囲を指定し音楽をAVMutableCompositionに追加(ここでは音楽の長さは動画の長さと同じにする)
guard let audioTrack = composition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid) else {
return
}
let audioTimeRange: CMTimeRange = .init(start: .zero, end: videoTrack.timeRange.end)
try? audioTrack.insertTimeRange(audioTimeRange, of: audioAssetTrack, at: .zero)
// 動画を回転させるためにAVMutableVideoCompositionLayerInstructionを生成する
let videoCompositionLayerInstruction: AVMutableVideoCompositionLayerInstruction = .init(assetTrack: videoTrack)
let transform = makeTransform(with: videoTrack)
videoCompositionLayerInstruction.setTransform(transform, at: .zero)
// AVMutableVideoCompositionInstructionに動画の時間と回転情報を渡す
let videoCompositionInstruction: AVMutableVideoCompositionInstruction = .init()
videoCompositionInstruction.timeRange = videoTrack.timeRange
videoCompositionInstruction.layerInstructions = [videoCompositionLayerInstruction]
// 動画の解像度やframeDurationカラー情報をAVAssetExportSessionに渡すためのAVMutableVideoCompositionを生成
let videoComposition: AVMutableVideoComposition = .init()
// iOSの画面収録で撮った動画がiOS 14でエンコードできないで解説します
videoComposition.colorPrimaries = AVVideoColorPrimaries_ITU_R_709_2
videoComposition.colorTransferFunction = AVVideoTransferFunction_ITU_R_709_2
videoComposition.colorYCbCrMatrix = AVVideoYCbCrMatrix_ITU_R_709_2
// 写真アプリでトリミングした動画がエンコード出来ないで解説します
let fps = max(videoAssetTrack.nominalFrameRate, 1.0)
videoComposition.frameDuration = CMTime(value: 1, timescale: CMTimeScale(fps))
videoComposition.renderSize = CGSize(width: 1080.0, height: 1920.0) // 解像度を指定
videoComposition.instructions = [videoCompositionInstruction]
// AVMutableCompositionとAVMutableVideoCompositionで動画+音楽ソース+解像度などの詳細を渡し、動画を生成
guard let assetExportSession: AVAssetExportSession = .init(asset: composition, presetName: AVAssetExportPresetHighestQuality) else {
return
}
guard let documentPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first else {
return
}
assetExportSession.videoComposition = videoComposition
// AVMutableVideoCompositionInstructionにも指定しているがここでも指定しないとエラーが出る
assetExportSession.timeRange = videoTrack.timeRange
assetExportSession.outputFileType = .mp4
let videoFilePath = "\(documentPath)/tmp.mp4"
assetExportSession.outputURL = URL(fileURLWithPath: videoFilePath)
assetExportSession.shouldOptimizeForNetworkUse = true
assetExportSession.exportAsynchronously {
switch assetExportSession.status {
case .completed:
print(assetExportSession.outputURL!)
@unknown default:
return
}
}
iOSの画面収録で撮った動画がiOS 14でエンコードできない
色々な動画のエンコードを試す中で、どうしてもエンコードできない動画がありました。それは、iOSの画面収録で撮影した動画です。
AVAssetExportSessionはエラーを出力してくれますが、エラーを見ても原因を突き止めるのが困難な内容でした。
Error Domain=AVFoundationErrorDomain Code=-11800 "操作を完了できませんでした" UserInfo={NSLocalizedFailureReason=原因不明のエラーが起きました(-12212), NSLocalizedDescription=操作を完了できませんでした, NSUnderlyingError=xxxx {Error Domain=NSOSStatusErrorDomain Code=-12212 "(null)"}}
そのため、エラーコードに注目しました。-12212 と表示されていたので調べたところkVTColorCorrectionPixelTransferFailedErrというエラーだとわかりました。
kVTColorSyncTransformConvertFailedErr - Apple Developer Documentation
色に問題ありということで、問題の動画をQuickTime Playerのムービーインスペクタで確認しました。
すると、エンコードできる動画と比べ、画面収録で撮った動画はTransfer FunctionがsRGBとなっており、Appleの Setting Color Properties for a Specific Resolution に記述されている設定例に無い値になっていました。
そのため、「この動画はAVAssetExportSessionに対応していない」という仮説を立て、色指定を変更することでエンコード出来るか試してみました。
AVVideoCompositionは、動画の色空間情報を設定するためのプロパティを3つ持っています。
これらを、Appleの Setting Color Properties for a Specific Resolution に記述されている例を元に設定してみました。(10-bit wide gamut HDはAVVideoSettingsのドキュメントコメントに記述されています)。
なんと、OSのバージョンによって違いが出る結果になりました。
続きはこちら