WWDC Quick Look 💓 By SwiftGGTeam
Display HDR video in EDR with AVFoundation and Metal

Display HDR video in EDR with AVFoundation and Metal

元の動画を見る

ハイライト

Apple は iOS 上で EDR API をオープンしました。開発者は AVFoundation と Metal を使用して、単純な AVPlayer の再生からフレームごとの抽出、コア イメージ フィルター処理、および Metal シェーダー レンダリングに至るまで、完全な HDR ビデオ再生およびリアルタイム処理パイプラインを構築できます。

主要内容

適切なビデオ フレーム レベルを選択します

HDR ビデオを再生する場合、開発者は選択を迫られます。高レベルのフレームワークを使用して迅速に実装するか、低レベルのフレームワークを使用してより詳細な制御を行うかです。 Apple のビデオ フレームワークは、高レベルから低レベルまで、AVKit、AVFoundation、Core Video、Video Toolbox、Core Media です。高レベルのフレームワークには独自の最適化が備わっているため、原則として、低レベルのフレームワークではなく高レベルのフレームワークを使用します。

AVKit は、プログレス バー、チャプター ナビゲーション、ピクチャ イン ピクチャ、字幕などの完全な再生 UI を提供します。 HDR ビデオを再生する必要があるだけの場合、AVPlayerViewController は 1 行のコードで EDR レンダリングを処理できます。

ただし、多くのアプリケーションでは、カラー グレーディング、キーイング、特殊効果のスーパーインポーズなど、ビデオ フレームのリアルタイム処理が必要です。現時点では、AVFoundation を使用してデコードされたフレームを抽出し、それを Core Image または Metal に渡して処理する必要があります。

シンプルな再生: AVPlayer の 2 つの方法

HDR 再生には 2 つの基本的なオプションがあります。

1 つ目は AVPlayerViewController を使用します。これは、完全な再生コントロールが必要なシナリオに適しています。 (06:58)

let player = AVPlayer(url: videoURL)
var playerViewController = AVPlayerViewController()
playerViewController.player = player
self.present(playerViewController, animated: true) {
    playerViewController.player!.play()
}

キーポイント:

  • AVPlayerViewController はすべての再生 UI と EDR レンダリングを自動的に処理します
  • カスタム処理を必要としない純粋な再生シナリオに適しています

2 番目のタイプは、ビデオをカスタム ビューに埋め込むのに適した AVPlayerLayer を使用します。 (07:38)

let player = AVPlayer(url: videoURL)
var playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = self.view.bounds
self.view.layer.addSublayer(playerLayer)
player.play()

キーポイント:

  • AVPlayerLayer はビデオをカスタム CALayer にレンダリングします
  • EDR も自動的にサポートされるため、追加の構成は必要ありません
  • ビデオをビューの一部として含める必要があるシナリオに適しています

リアルタイム処理: デコードされたフレームからメタル レンダリングまで

再生中に特殊効果を追加する必要がある場合、プロセスは次のようになります。AVPlayer デコード -> CADisplayLink フレーム抽出 -> Core Image または Metal 処理 -> CAMetalLayer レンダリング。

このプロセスの鍵は、CAMetalLayer を正しく設定することです。 EDR を有効にし、半浮動小数点ピクセル形式を使用し、拡張色域を設定する必要があります。 (09:28)

次に、出力形式と色空間を指定して AVPlayerItemVideoOutput を作成します。 AVFoundation は、異なるクリップ間のカラー変換を自動的に処理します。 (11:33)

CADisplayLink がフレームごとにコールバックされると、最新の CVPixelBuffer が VideoOutput からコピーされます。画面のリフレッシュ レートがビデオ フレーム レートよりも高い場合があるため、copyPixelBuffer が毎回成功するとは限らないことに注意してください。失敗した場合は、前のフレームを保持してください。 (13:02)

詳細

EDR レンダリング層を構成する

let layer: CAMetalLayer

// 启用 EDR
layer.wantsExtendedDynamicRangeContent = true

// 使用半浮点像素格式
layer.pixelFormat = MTLPixelFormatRGBA16Float

// 使用扩展线性 Display P3 色域
layer.colorspace = kCGColorSpaceExtendedLinearDisplayP3

キーポイント:

  • wantsExtendedDynamicRangeContenttrue に設定する必要があります。そうでない場合、HDR コンテンツは SDR に圧縮されます。
  • MTLPixelFormatRGBA16FloatEDRで必要な拡張輝度値を格納できる半浮動小数点形式です。
  • kCGColorSpaceExtendedLinearDisplayP3Apple デバイスで最も一般的に使用される広色域であり、線形伝達関数により拡張範囲の値が維持されます。

ビデオ出力と表示リンクを作成する

let videoColorProperties = [
    AVVideoColorPrimariesKey: AVVideoColorPrimaries_P3_D65,
    AVVideoTransferFunctionKey: AVVideoTransferFunction_Linear,
    AVVideoYCbCrMatrixKey: AVVideoYCbCrMatrix_ITU_R_2020
]

let outputVideoSettings = [
    AVVideoAllowWideColorKey: true,
    AVVideoColorPropertiesKey: videoColorProperties,
    kCVPixelBufferPixelFormatTypeKey as String: NSNumber(value: kCVPixelFormatType_64RGBAHalf)
] as [String : Any]

let videoPlayerItemOutput = AVPlayerItemVideoOutput(outputSettings: outputVideoSettings)

キーポイント:

  • AVVideoColorPrimaries_P3_D65ディスプレイ P3 の色域を指定する
  • AVVideoTransferFunction_LinearEDRの鍵となる線形伝達関数を使用して、輝度値の圧縮を回避します
  • kCVPixelFormatType_64RGBAHalf64 ビット RGBA 半浮動小数点形式です
  • AVPlayerItemVideoOutputビデオに色域の異なる複数のクリップが含まれている場合でも、色変換は自動的に処理されます。
lazy var displayLink: CADisplayLink = CADisplayLink(
    target: self,
    selector: #selector(displayLinkCopyPixelBuffers(link:))
)

var statusObserver: NSKeyValueObservation?

statusObserver = videoPlayerItem.observe(\.status, options: [.new, .old]) { playerItem, change in
    if playerItem.status == .readyToPlay {
        playerItem.add(videoPlayerItemOutput)
        displayLink.add(to: .main, forMode: .common)
        videoPlayer?.play()
    }
}

キーポイント:

  • 観察するstatusプロパティ。プレーヤーの準備ができるまで待ってから出力を追加し、リンクの表示を開始します。
  • CADisplayLink はメイン実行ループの共通モードに関連付けられており、スクロール中にコールバックも実行できるようにします。
  • macOS では CADisplayLink の代わりに CVDisplayLink を使用する必要があります

フレームごとの処理: Core Image フィルター

@objc func displayLinkCopyPixelBuffers(link: CADisplayLink) {
    let currentTime = videoPlayerItemOutput.itemTime(forHostTime: CACurrentMediaTime())

    if videoPlayerItemOutput.hasNewPixelBuffer(forItemTime: currentTime) {
        if let buffer = videoPlayerItemOutput.copyPixelBuffer(
            forItemTime: currentTime,
            itemTimeForDisplay: nil
        ) {
            let image = CIImage(cvPixelBuffer: buffer)

            let filter = CIFilter.sepiaTone()
            filter.inputImage = image
            let output = filter.outputImage ?? CIImage.empty()

            // 使用 context 渲染到 CIRenderDestination
        }
    }
}

キーポイント:

  • itemTime(forHostTime:)ホスト時間をビデオ タイムラインにマッピングする
  • hasNewPixelBuffer不必要なコピーを避けるために新しいフレームを確認してください
  • copyPixelBuffer失敗する可能性があります (ビデオ フレーム レートが画面のリフレッシュ レートより低い)、失敗した場合はレンダリングをスキップします
  • 使用CIImage(cvPixelBuffer:)ピクセルバッファから直接画像を作成
  • 注: すべての CIFilter が EDR をサポートしているわけではありません。EDR を使用する必要があります。CICategoryHighDynamicRangeフィルター対応フィルター

フレームごとの処理: 金属テクスチャ

let mtlDevice = MTLCreateSystemDefaultDevice()

var mtlTextureCache: CVMetalTextureCache? = nil
CVMetalTextureCacheCreate(
    allocator: kCFAllocatorDefault,
    cacheAttributes: nil,
    metalDevice: mtlDevice,
    textureAttributes: nil,
    cacheOut: &mtlTextureCache
)

let width = CVPixelBufferGetWidth(pixelBuffer)
let height = CVPixelBufferGetHeight(pixelBuffer)

var cvTexture: CVMetalTexture? = nil
CVMetalTextureCacheCreateTextureFromImage(
    allocator: kCFAllocatorDefault,
    textureCache: mtlTextureCache,
    sourceImage: pixelBuffer,
    textureAttributes: nil,
    pixelFormat: MTLPixelFormatRGBA16Float,
    width: width,
    height: height,
    planeIndex: 0,
    textureOut: &cvTexture
)

let texture = CVMetalTextureGetTexture(cvTexture)

キーポイント:

  • CVMetalTextureCacheIOSurfaceの手動管理を回避するためのCore VideoとMetalを接続するブリッジです。
  • キャッシュはパフォーマンスを向上させるために MTLTexture から IOSurface へのマッピングを維持します。
  • メタル テクスチャはこの形式をネイティブにサポートしているため、半浮動小数点ピクセル形式を使用します。
  • CVMetalTextureRef は、Objective-C の Metal コマンド バッファ完了コールバックで解放される必要があります。

重要ポイント

  • リアルタイム ビデオ色補正ツール: AVPlayerItemVideoOutput + Core Image フィルター チェーンを使用して、HDR をサポートするリアルタイム色補正パネルを作成します。ユーザーはスライダーをスライドして色温度と彩度を調整でき、フィルターはビデオ フレームに直接作用します。エントリーAPIはAVPlayerItemVideoOutputそしてCIFilter

  • HDR ビデオ キーイング合成: メタル シェーダーを使用してクロマ キー キーイングを実装し、グリーン スクリーン ビデオと背景レイヤーをリアルタイムで合成します。 EDR は、合成された HDR コンテンツが輝度情報を失わないことを保証します。エントリーAPIはCVMetalTextureCacheカスタム メタル フラグメント シェーダ。

  • プロのビデオ プレーヤー向けにカスタマイズされたレンダリング エンジン: AVPlayerViewController を放棄し、CADisplayLink + Metal を使用して独自の再生レンダリング パイプラインを構築し、フレームごとの正確な制御と特殊効果のオーバーレイをサポートします。編集ソフトウェアやプロのプレーヤーに適しています。エントリーAPIはCADisplayLinkそしてCAMetalLayer

  • HDR スクリーンショットとフレーム エクスポート: CADisplayLink コールバックで、処理された CVPixelBuffer を HEIF/PNG として保存し、HDR メタデータを保持します。ビデオのフレームごとのエクスポート ツールにすることができます。エントリーAPIはCVPixelBufferそしてCIContext.writeHEIFRepresentation

関連セッション

コメント

GitHub Issues · utterances