ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
AVFoundationやMetalによるHDR動画のEDR表示
AVFoundationやMetalを活用して効率的なEDRパイプラインを構築する方法を紹介します。そして、AVPlayerを使用してHDR動画をEDRで表示したり、ビデオ再生をAppビューに追加したり、Metalでレンダリングしたり、Core ImageやカスタムMetalシェーダを使用してキーイングやカラーマネジメントなどのビデオエフェクトを追加したりする方法について解説します。ゲーム開発でもPro App開発でも、使用するフレームワーク選びで役立つことでしょう。さらに、トランスポート、カラースペース、ピクセルバッファのフォーマット選びに関するベストプラクティスも紹介します。
リソース
関連ビデオ
WWDC23
Tech Talks
WWDC22
WWDC21
WWDC20
-
ダウンロード
♪ ♪
こんにちは! WWDC2022へようこそ Ken Greenebaum,です ApppleのColor and Display Technologiesチームにいます 今年は3つのEDRのセッションをでき とても嬉しく思います iOSのEDR APIサポートを 発表した「iOSのEDRの詳細」 と「Core Image Metal SwiftUI でのEDRコンテンツの表示」 はご確認いただけました でしょうか 昨年のEDRセッションを見た方も いると思いますが AVPlayerで EDRを使い HDR映像を再生する 方法を実演しています
本セッションでは さらに踏み込んで Core Media インターフェイスを使用して EDR再生だけでなく HDRビデオを独自の EDRレイヤやビューに デコードして再生する 方法について説明します
さらに 単にコンテンツを 再生するだけでなく Core Videoのディスプレイ リンクを介して デコードされたビデオフレーム リアルタイムでアクセスし そのフレームをCoreImage FiltersやMetal Shaderに送って カラーマネジメントや 視覚効果 その他の 信号処理を加え 最後にMetalに結果を送ってレンダ リングする方法について説明します EDRと互換性のある ビデオメディアフレームワークを確認し App要件に最も適したものを 選択するのに役立てます
次に HDRビデオの 再生に必要なすべての 作業を行うことができる ハイレベルのフレーム ワークであるAVKitと AVFoundationに ついて簡単に説明します
最後に Core Video Metalでデコードした ビデオフレームを EDRの再生 編集 画像処理エンジンで 使用するベストプラクティス について説明します
まず Appleビデオフレームワーク を簡単に調査しましょう 最も使いやすい最上位の インターフェイスから始まり より多くの機会を提供する 下位フレームワークへと続きますが その代償に コードは複雑になります 自動的に提供される 最適化を利用するには できるだけ高いレベルの フレームワークの使用がベストです EDRの単純な再生から デコードしたビデオフレームを CoreImageや Metalでリアルタイム処理する 高度なプラミングまで さまざまなシナリオを 検討します 最高レベルでは AVKitがあります AVKitを使用すると トランスポートコントロール チャプタナビゲーション ピクチャインピクチャサポート 字幕やクローズド キャプション表示を備えた メディア再生用のユーザインターフェース を作成することができます AVKitは AVPlayerViewControllerを 使用してデモを行うように HDRコンテンツをEDRとして 再生することができます しかし Appでビデオフレームを さらに処理する必要がある場合 パイプラインを より詳細に制御できる メディアフレームワーク を使用する必要があります 次に AVFoundationです AVFoundationはApple プラットフォーム上でタイムベースの オーディオビジュアルメディアを扱う フル機能を備えたフレームワークです AVFoundationを使うと QuickTimeムービやMPEG 4の再生 作成 編集 HLSストリーム の再生が 容易になり Appに強力なメディア機能 を構築できます 本セッションでは AVPlayer とそれに関連する AVPlayerLayerインターフェース の使用方法を紹介します Core Videoはデジタル映像のパイプライン モデルを提供するフレームワークです プロセスを個別の ステップに分割することで ビデオでの作業方法を 簡素化します また Core Videoでは データ型間の 変換や表示の同期を 気にすることなく 個々のフレームに アクセスして 操作することが 容易にできます Core Imageを使用した DisplayLinkと CVPixelBufferの デモを行います CVMetalTextureCache もMetalで 次に VideoToolboxです ハードウェアのエンコーダや デコーダに直接アクセスする ローレベルの フレームワークです VideoToolboxは ビデオの圧縮 伸張や Core Videoの ピクセルバッファに格納された ラスター画像フォーマット間の 変換を行う サービスを提供します VTDecompressionSessionは 強力なローレベルインターフェースで このセッションの範囲外ですが 上級開発者はさらに 調ベてみるとよいでしょう 最後に Core Mediaです こレは AVFoundationや 他の ハイレベルメディアフレームワークで 使用される メディアパイプライン を定義しています Core Mediaのローレベルデータ型と インターフェースを使えば 常に効率的に メディアサンプルを処理し メディアデータのキューを 管理することができます 今回は このフレームワークを どのように そしていつ みなさんのAppで使うかを 説明します まず AVKitと AVFoundationを使って EDRとしてレンダリングされた HDR映像を再生する方法です AVPlayerのより 洗練された一連のApp: 独自のレイヤへの レンダリング CADisplayLink経由で 個別デコードされたフレームへアクセス 結果のCVPixelBufferを Core Imageに送って処理 最後に デコードフレームを Metalテクスチャとして CVMetalTextureCache経由で アクセスしMetalで処理 レンダリングします Apple プラットフォームのビデオメディア レイヤの概要を理解したところで AVKit と AVFoundation フレームワークに焦点を当てます まず AVFoundationの AVPlayer インターフェイスを使った HDR映像の再生 について説明します AVPlayerは メディアアセットの 再生とタイミングを 管理するのに使われる コントローラオブジェクトです AVPlayerインターフェースは HDR映像の高性能再生に利用でき 可能な限り自動的にEDRとして 結果をレンダリングします AVPlayerを使うとQuickTimeムービ などのローカルおよびリモートの ファイルベースのメディアや HLSを使用して提供される ストリーミングメディアを 再生することができます AVPlayerは一度に1つのメディア アセットを再生するのに使われます プレーヤのインスタンスを再利用し メディアアセットを連続的に再生したり 複数の例を作り 複数のアセットを 同時に再生することもできますが AVPlayerは一度に一つのメディアアセット のみ再生するように管理されます AVFoundationフレームワークは AVQueuePlayerという AVPlayerのサブクラスも 提供しており これを使って 連続したHDRメディアアセットの キューと再生を作成 管理できます EDRでレンダリングされた HDRビデオメディアを シンプルに再生するAppなら AVPlayerとAVPlayerViewControllerが 最適アプローチでしょう AVPlayerLayer を使って iOS や macOS で 自作のビューを再生します
以上が AVPlayerの最も 分かりやすい使い方です 両者の例を 見てみましょう AVKitのAVPlayerViewController と組み合わせて AVFoundationのAVPlayerインタフェース を使用する方法を見ていきます まずメディアのURLから AVPlayerをインスタンス化します
次に AVPlayerViewController を作成し ビューコントローラの playerプロパティに 先ほどメディアのURLから 作成したplayerを設定します
動画の再生を開始するためビュー コントローラをモーダルに提示します AVKitはすべての 詳細を管理し EDRをサポートする ディスプレイ上で 自動的にHDRビデオをEDR として再生できます Appによっては HDRの映像メディアを 自分のビューに 再生する必要があります AVPlayerLayerでAVPlayerを使い これを実現する方法を見ましょう HDRビデオメディアをEDRとし 自分のビューで再生するために 再びメディアのURLを指定し AVPlayerを作ることから始めます しかし今回は 先ほど作成したプレーヤで AVPlayerLayerを インスタンス化します 次に ビューから取得した プレイヤレイヤに 境界線を設定します プレイヤレイヤにビューから の境界ができたので プレイヤレイヤをサブレイヤとして ビューに追加します 最後に HDRビデオメディアを 再生するために AVPlayerのplayメソッド を呼び出します 以上で AVPlayerとAVPlayerLayerを 使い HDRビデオメディアを 独自のレイヤでEDRとして 再生が可能になります AVPlayerを使った 最もわかりやすい 2つのHDR動画再生の ワークフローを紹介しました しかし 多くのAppでは単純な メディア再生以上の機能を必要とします
例えば 映像にカラーグレーディング やクロマキー処理などの 画像処理が必要な Appがあります AVPlayerからデコードされた ビデオフレームを取得し CoreImageフィルタやMetalシェーダを リアルタイムに適用して 結果をEDRとしてレンダリングする ワークフローを考えてみましょう AVPlayerと AVPlayerItemを使って HDRビデオメディアから EDRフレームをデコードし Core Videoディスプレイリンクから デコードフレームにアクセスし 結果のピクセルバッファを Core ImageまたはMetalに送り処理し EDRをサポートしている ディスプレイでCAMetalLayerに 結果をレンダリングする 方法をデモします HDRメディアをEDRとして 正しくレンダリングするため 必要なCAMetalLayerの 主要プロパティを 設定する方法を説明します まず HDRビデオコンテンツ をレンダリングする CAMetalLayerを取得する 必要があります そのレイヤでは wantsExtendedDynamicRangeContent フラグをtrueに設定して EDRにオプトインします
必ずExtended Dynamic Range コンテンツに対応した画素形式を使います この後のAVPlayerの例では CAMetalLayerが ハーフフロートピクセル形式を 使用するように設定します PQまたはHLG転送関数と 組み合わせて 10ビット形式を使用しても 動作できます また 結果をSDRに 限定しないために レイヤをEDR互換の 拡張レンジカラースペース に設定する必要があります
この例では ハーフフロート メタルテクスチャを「拡張リニア ディスプレイP3」の 色空間に設定します EDR 色空間 ピクセル バッファフォーマットについては まだ表面を削ったに 過ぎません 詳しくは 昨年の私の回 「EDRによるHDRレンダリング」 今年の「iOSのEDRの詳細」 をご確認ください
CAMetalLayerの基本的な プロパティを設定したので Core ImageまたはMetal シェーダを 使用してリアルタイム画像処理を 追加し デモを続けましょう AVPlayerと連携したディスプレイリンク を使ってデコードされた ビデオフレームにリアルタイム でアクセスすることになります
このワークフローでは まずAVPlayer ItemからAVPlayerを作成します 次に EDRに適したピクセル バッファフォーマットと色空間を 設定したAVPlayerItem VideoOutputをインスタンス化します そして Displayリンクを作成し 設定します そして最後に Displayリンクを実行し ピクセルバッファを Core Imageまたは Metalに取得して処理します iOSで使われるCADisplayLink をデモします macOSで開発する場合は 同等のCV Displayリンクインターフェイスを使います 今回は メディアのURLから AVPlayerItemを作成し 作成した AVPlayerItemで AVPlayerをインスタンス化 する方法を選択しました デコードしたフレームの 色空間とピクセルバッファの フォーマットを指定するために 一組の辞書を作成します 最初の辞書である ビデオカラープロパティは 色空間と伝達関数を 指定するところである この例では ほとんどの Apple製ディスプレイの色空間に 相当する Display P3色空間と AVFoundationがEDRに 必要な拡張範囲値を維持できる 線形伝達関数を 要求しています
2つ目の辞書であるoutput VideoSettingsは ピクセルバッファ フォーマットの特性を指定し 先ほど作成したビデオ カラープロパティ辞書への 参照も提供します ここでは ワイドカラーとハーフフロートピクセル バッファフォーマットを要求しています AVPlayerItemVideoOutputは 出力設定辞書で指定した ピクセルバッファ形式に ビデオをデコード するだけでなく ピクセル転送セッションで 必要な色変換も 自動的に行うので 非常に便利です 動画には複数の クリップが含まれ 異なる色空間を持つ可能性が あることを思い出してください AVFoundationは これらを自動的に管理します これから説明するように この動作により デコードされた ビデオフレームを ディスプレイの色空間への 自動変換を 行わない Metalなどのローレベルの フレームワークに 送信することもできます ここで AVPlayerItemVideoOutputを outputVideoSettings 辞書で作成します 第3段階として デコードされたフレームに リアルタイムでアクセスするため Displayリンクを設定します CADisplayLinkは更新毎に 実行されるコールバックを取ります ここでは 処理のためにCoreImage に送るCVPixelBufferを 得るため これから説明する ローカル関数を呼び出します 次に ビデオプレーヤアイテムの オブザーバを作成し 指定されたプレーヤ項目の プロパティの変更を処理できます
この例では プレイヤ アイテムの状態が 変化するたびに このコードが実行されます
プレイヤアイテムのステータスが ready to playに変わったら 先ほど返された 新しいAVPlayerItem にAVPlayerItemVideoOutput を追加し メインランループをコモンモードに 設定してCADisplayLinkを登録し ビデオプレイヤで 再生を呼び出すことで HDRビデオのリアルタイム デコードを開始します
最後に CADisplayLink コールバックの実装例を紹介します 先ほど「displayLinkCopy PixelBuffers」 ローカル関数として 紹介しました HDR映像の再生が始まると ディスプレイ更新 のたびにCADisplayLinkの コールバック関数が呼び出されます 例えば 一般的なディスプレイの場合 1秒間に60回呼び出されるかも 新しい CVPixelBuffer があった 場合に表示される フレームを 更新するためのコードです 各表示コールバックで 現在のウォールクロック時刻に 表示されるデコードされた ビデオフレームを含む CVPixelBufferを コピーしようとします しかし copyPixelBufferの 呼び出しは失敗するかもしれません 特に 画面のリフレッシュレートが 再生しているビデオの リフレッシュレートを 超えている場合 ディスプレイのリフレッシュごとに新しい CVPixelBufferが使えるとは限らないからです 新しい CVPixelBuffer がない場合 呼び出しは失敗し レンダリングをスキップします これにより 直前のフレームが再度 表示更新されるまで画面上に残ります しかし コピーが成功すれば CVPixelBufferに 新しい映像の フレームができます この新しいフレームを処理し レンダリング方法はいくつもあります CVPixelBuffer を Core Imageに送信して 処理することも可能です Core Imageは 1つまたは 複数のCIFilterをつなぎ合わせて ビデオフレームにGPUアクセラレーション による画像処理を提供できます
ただし すべてのCIFilterが EDRに対応している訳ではなく HDRコンテンツでは SDRにクランプされるなど トラブルが発生する可能性が あることをご了承ください コアイメージではEDR対応 フィルタを多数用意しています EDR対応CoreImage フィルタを列挙するには CICategoryHighDynamicRange の付いたフィルタ名を使用します この例では シンプルな セピア調のエフェクトを追加します では例に戻って Core Imageを統合しましょう 新しいCVPixelBufferを生成する 表示リンクのコールバックごとに そのPixel Bufferから CIImageを生成します
CIFilterのインスタンスを作り 目的の効果を実現します パラメータのないシンプルなセピア調 のフィルタを使用していますが システムには多くのCIFilter が組み込まれており 自分で作成 することも簡単です CIFilterの入力画像に 先ほど作成したCIImageを設定します
そして処理されたビデオ結果は フィルタの出力画像として 利用できるようになります CIFilterは 必要な数だけ 連結して使用できます その後 CIRender Destinationを使用して Appのビューコードに 結果をレンダリングします
このワークフローの詳細については WWDC 2020のセッション 「動画AppのためのCore Image パイプラインの最適化」を もう一つの方法は Metalと カスタムMetalシェーダを使い CVPixelBufferを処理し レンダリングすることです CVPixelBufferを MetalTextureに変換する処理を 簡単に説明します しかし 最高のパフォーマンスを 維持するのにこの変換を実装するのは 別のセッションに譲った方が 良い深いテーマです その代わりに CoreVideoの Metalテクスチャキャッシュから Metalテクスチャを 派生させることを推奨しており このセッションの最後の例として そのプロセスを説明します 一般的には CVPixelBufferから IOSurfaceを取得し MetalTextureDescriptorを作成し MetalDeviceかnewTextureWithDescriptor でMetalTextureを作成する という流れになります
しかし 慎重にロックを かけないと テクスチャが再利用され 過剰に描画される危険性があります すべてのPixelBufferフォーマットが MetalTextureでネイティブに サポートされているわけではないので この例ではhalf floatを使用します このような複雑な問題のため CoreVideoから直接Metalの テクスチャにアクセスします Core VideoとMetalを さらに掘り下げましょう 前述のように CVMetalTexture CacheはCVPixelBufferを Metalと共に使用するための 直接的かつ効率的な方法です CVMetalTextureCacheは 更なる変換を必要とせずに キャッシュから直接Metal Textureを取得でき便利です CVMetalTextureCacheは CVPixelBufferと MetalTextureの間の ブリッジを自動的に行い コードの簡素化と 高速化を実現します CVPixelBufferPoolsと共に CVMetalTextureCache もMetal TextureからIOSurfaceへのマッピングを 維持することで パフォーマンス上の利点を提供します
最後に CVMetalTextureCacheを 使用することで IOSurfaceを手動で追跡する 必要性がなくなります 今回のセッションの最後の例です: CVMetalTextureCache を用いてCore Videoから 直接 Metalテクスチャ を抽出する方法です システムデフォルトのメタルデバイスを 取得することから始めます それを使って MetalTexture cacheを作成し MetalTexture cacheに関連する Core Cideo MetalTexture Cache をインスタンス化します これを使いデコードされたビデオフレームを Metalのテクスチャとしてアクセスでき 便利なことに Metalエンジンで 直接利用することができます この例ではMetal system default deviceを作成し使用します 次にCVMetalTexture CacheCreateで 先ほど作成したMetal デバイスを指定して CVMetalTextureCache を作成します Core Video Metalの テクスチャを作成するために 必要なCVPixelBufferの 高さと幅を取得します 次に「CVMetalTextureCacheCreate TextureFromImage」を呼び出し CVMetalTexture オブジェクトを インスタンス化し CVPixelBufferと関連付けます 最後に「CVMetal TextureGetTexture」を呼び出し 目的のMetalテクスチャを 取得します Swift AppはCVMetalTexture の強い参照を使うべきですが Objective Cを使う場合は CV MetalTextureRefをリリースする前に Metalがみなさんのテクスチャを使い終わった ことを確認する必要があります メタルコマンドバッファの完了 ハンドラを使い実現できます
以上です みなさんお疲れ様でした! 今回は HDRビデオメディアを EDRにレンダリングし 再生 編集 画像処理を 行うワークフローを いくつか紹介しました
HDRメディアを再生するためAVPlayer からAVKitのAVPlayerViewControllerに 移行する方法を学びました また AVPlayerと AVPlayerLayerを使用して HDRメディアを自分の視界に 表示する方法を学びました そして最後に 再生中にリアルタイムで エフェクトを加える方法を探りました AVFoundationの AVPlayerをCoreVideoに接続し さらにMetalに接続して レンダリングします CoreImageのフィルタや メタルシェーダーを使って リアルタイムにエフェクト をかけることも
さらに深く掘り下げるなら ビデオワークフローの構築や HDRメディアと EDRの統合 に関するWWDCの回が おすすめです 特におすすめは 「AVFoundationを使用した HDR動画の編集と再生」という セッションです HDRメディアにエフェクトをかける 「applyCIFiltersWithHandler」 を使ったAVVideoComposition の使い方を紹介します このセッションでは 各ビデオフレームが処理可能になった際 CVPixelBufferで使用できる カスタムコンポジターの 使用方法についても 学びます 今年はEDRに関する回として EDR APIのサポートが iOSにも拡大したことを 発表した「iOSのEDRの詳細」と EDRと他のメディアフレーム ワークとの統合をさらに追求した 「CoreImage Metal SwiftUIによる EDRでのHDRコンテンツ表示」 を発表しています macOSとiOSの両方で EDR 対応AppにHDRビデオを 組み込んでいただけると 幸いです ご視聴ありがとう ございいました
-
-
6:58 - Playing media using AVPlayerViewController
// Playing media using AVPlayerViewController let player = AVPlayer(URL: videoURL) // Creating a player view controller var playerViewController = AVPlayerViewController() playerViewController.player = player self.presentViewController(playerViewController, animated: true) { playerViewController.player!.play() }
-
7:38 - Playing media using AVPlayer and AVPlayerLayer
// Playing media using AVPlayer and AVPlayerLayer let player = AVPlayer(URL: videoURL) var playerLayer = AVPlayerLayer(player: player) playerLayer.frame = self.view.bounds self.view.layer.addSublayer(playerLayer) player.play()
-
9:28 - CAMetalLayer Properties
// Opt into using EDR let layer: CAMetalLayer layer.wantsExtendedDynamicRangeContent = true // Use half-float pixel format layer.pixelFormat = MTLPixelFormatRGBA16Float // Use extended linear display P3 color space layer.colorspace = kCGColorSpaceExtendedLinearDisplayP3
-
11:33 - Create an AVPlayerItemVideoOutput
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] // Create a player item video output let videoPlayerItemOutput = AVPlayerItemVideoOutput(outputSettings: outputVideoSettings)
-
13:02 - Create a display link
// Create a display link lazy var displayLink: CADisplayLink = CADisplayLink(target: self, selector: #selector(displayLinkCopyPixelBuffers(link:))) var statusObserver: NSKeyValueObservation? statusObserver = videoPlayerItem.observe(\.status, options: [.new, .old], changeHandler: { playerItem, change in if playerItem.status == .readyToPlay { playerItem.add(videoPlayerItemOutput) displayLink.add(to: .main, forMode: .common) videoPlayer?.play() } }) }
-
14:16 - Run DisplayLink to get pixel buffers
@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 output = filter.outputImage ?? CIImage.empty() // use context to render to you CIRenderDestination } } }
-
15:53 - Integrate 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 output = filter.outputImage ?? CIImage.empty() // use context to render to your CIRenderDestination } } }
-
19:13 - Using CVMetalTextureCache
// Create a CVMetalTextureCacheRef let mtlDevice = MTLCreateSystemDefaultDevice() var mtlTextureCache: CVMetalTextureCache? = nil CVMetalTextureCacheCreate(allocator: kCFAllocatorDefault, cacheAttributes: nil, metalDevice: mtlDevice, textureAttributes: nil, cacheOut: &mtlTextureCache) // Create a CVMetalTextureRef using metalTextureCache and our pixelBuffer 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) // In Obj-C, release CVMetalTextureRef in Metal command buffer completion handlers
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。