1
/
5

【TECH BLOG】WEARに動画投稿を実装した際にハマった事とその解決策

はじめに

こんにちは。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というエラーだとわかりました。

How to use VideoToolbox to decompress H.264 video stream
NALUs: NALUs are simply a chunk of data of varying length that has a NALU start code header 0x00 00 00 01 YY where the first 5 bits of YY tells you what type of NALU this is and therefore what type of data follows the header.
https://stackoverflow.com/a/29543061

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のバージョンによって違いが出る結果になりました。

続きはこちら

株式会社ZOZOからお誘い
この話題に共感したら、メンバーと話してみませんか?
株式会社ZOZOでは一緒に働く仲間を募集しています
1 いいね!
1 いいね!

同じタグの記事

今週のランキング

株式会社 ZOZOさんにいいねを伝えよう
株式会社 ZOZOさんや会社があなたに興味を持つかも