WWDC Quick Look 💓 By SwiftGGTeam
Decode ProRes with AVFoundation and VideoToolbox

Decode ProRes with AVFoundation and VideoToolbox

元の動画を見る

ハイライト

Apple は Mac ビデオ アプリの ProRes デコード パイプラインをデモしました。AVAssetReader はハードウェア デコードと Afterburner を自動的に有効にし、デコードされた CVPixelBuffer をネイティブ ピクセル フォーマットで Metal に渡すことができます。自己管理のデコードが必要な場合、開発者は CMSampleBuffer、VTDecompressionSession、および CVMetalTextureCache を使用して同じパスを制御できます。

主要内容

多くのプロフェッショナル向けビデオ アプリの問題は、「デコードできるかどうか」ではなく、デコード後にフレームを独自のレンダラーに効率的に送信できるかどうかです。ProRes ファイルは、編集、色補正、またはポストプロダクション プロセスから生成されます。アプリ側の共通の目標は、それらを読み取ってフレームにデコードし、プレビュー、フィルタリング、合成、またはエクスポート前のチェックのために Metal に渡すことです。途中でもう 1 回のフォーマット変換ともう 1 回のメモリ コピーが、すべて帯域幅と遅延に変わります。

このセッションでは、パスが 2 つのレベルに分割されます。最初のレベルは、AVFoundation に引き継がせることです。AVAssetReaderソース ファイルを読み取り、プロセス全体でデコーダーに渡すのに適したサンプルを準備し、VideoToolbox を呼び出してデコードし、指定された形式を返します。CVPixelBuffer。現在のシステムは、デフォルトで Afterburner を含む利用可能なハードウェア デコーダを使用します。

2 番目のレベルは、開発者が VideoToolbox を自分で操作するためのものです。このオプションは、既存のファイル リーダーを備えたアプリ、ネットワーク サンプル ソース、またはハードウェア デコード戦略を制御する必要があるアプリケーションに適しています。その代償として、正しく構築する必要があるCMSampleBuffer、作成するVTDecompressionSession、非同期コールバックを処理してから、CVPixelBufferMetal テクスチャに安全にバインドされています。

最後の段落は、パフォーマンスが低下しやすい箇所です。デコーダ出力CVPixelBuffer通常はからCVPixelBufferPool、最下層IOSurface再利用されます。Metal コマンドが実行される前にサーフェスがプールによってリサイクルされると、画面上で誤ったデータが読み取られる可能性があります。セッションには 2 つのパスがあります: 手動管理IOSurfaceカウントを使用するか、優先しますCVMetalTextureCache

詳細

1. AVAssetReader を使用してデフォルトの効率的なパスを選択します

(07:41) 入力がローカル ProRes ムービー ファイルの場合、AVAssetReaderが最短の道です。それはから始まりますAVAssetリーダーを作成して渡すAVAssetReaderTrackOutput出力形式を指定します。

// Constructing an AVAssetReader

// Create an AVAsset with an URL pointing at a local asset
AVAsset *sourceMovieAsset = [AVAsset assetWithURL:sourceMovieURL];

// Create an AVAssetReader for the asset
AVAssetReader *assetReader = [AVAssetReader assetReaderWithAsset:sourceMovieAsset
                                                           error:&error];

キーポイント:

  • AVAssetソース ビデオ ファイルを表し、セッション内のシーンはローカル ProRes ムービー ファイルです。
  • AVAssetReaderアセットからサンプルを読み取り、AVFoundation に後続のデコード リンクを処理させる責任を負います。
  • システムはハードウェア デコーダが利用可能な場合は自動的に使用し、Afterburner はデフォルトのハードウェア デコード パスにも含まれます。

(07:58) デコードされた出力を読み取るときに重要なのは、トラック出力用のデコーダーのネイティブ形式に近いピクセル形式を要求することです。ProRes 4444 では、アルファ付き 16 ビット 4:4:4:4 YCbCr、つまり Y416 を推奨しています。

// Configuring AVAssetReaderTrackOutput

// Copy the array of video tracks from the source movie
NSArray<AVAssetTrack*>  *tracks = [sourceMovieAsset tracksWithMediaType:AVMediaTypeVideo];

// Get the first video track
AVAssetTrack *track = [sourceMovieVideoTracks objectAtIndex:0];

// Create the asset reader track output for this video track, requesting ‘y416’ output
NSDictionary *outputSettings = @{ (id)kCVPixelBufferPixelFormatTypeKey :
                                  @(kCVPixelFormatType_4444AYpCbCr16) };

AVAssetReaderTrackOutput* assetReaderTrackOutput
= [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:track
                                             outputSettings:outputSettings];

// Set the property to instruct the track output to return the samples
// without copying them
assetReaderTrackOutput.alwaysCopiesSampleData = NO;

// Connect the the AVAssetReaderTrackOutput to the AVAssetReader
[assetReader addOutput:assetReaderTrackOutput];

キーポイント:

  • tracksWithMediaType:AVMediaTypeVideoビデオ トラックを取り出し、例の最初のビデオ トラックを選択します。
  • kCVPixelBufferPixelFormatTypeKey出力の指定CVPixelBufferピクセル形式。
  • kCVPixelFormatType_4444AYpCbCr16Y416 に対応し、ProRes 4444 のネイティブ デコーダー出力に適しています。
  • alwaysCopiesSampleData = NO余分なコピーを避けるためにサンプルを返すことができますが、これらのサンプル バッファーは別の場所に保持される可能性があり、変更することはできません。
  • 要求された形式がデコーダにとってネイティブでない場合、AVAssetReader はバッファを変換します。 session は、そのようなコピーを避けることを明示的に推奨します。

(08:57) 読み取りを開始した後、ループ呼び出しcopyNextSampleBuffer。デコードされた出力として設定されている場合、CMSampleBuffer取り出したものは、CVImageBufferRef

// Running AVAssetReader

BOOL success = [assetReader startReading];

if (success) {
   CMSampleBufferRef sampleBuffer = NULL;

   // output is a AVAssetReaderOutput
   while ((sampleBuffer = [output copyNextSampleBuffer]))
   {
       CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);

       if (imageBuffer)
       {
          // Use the image buffer here
          // if imageBuffer is NULL, this is likely a marker sampleBuffer
       }
    }
}

キーポイント:

  • startReadingリーダーを起動します。
  • copyNextSampleBufferサンプルバッファを順番に返します。
  • CMSampleBufferGetImageBuffer値がある場合、それがデコードされた画像フレームであることを意味します。
  • 空の画像バッファーは、メディア パイプラインで時間指定された添付ファイルを運ぶために使用されるマーカー サンプル バッファーである場合があります。

2. 圧縮されたサンプルを VideoToolbox に取得します

(11:40)一部のアプリでは、すべての作業を AVAssetReader に引き渡すことができません。たとえば、独自のファイル読み取りロジックやネットワーク データ ソースがある場合、または VideoToolbox を直接制御する必要がある場合などです。現時点では、AVAssetReader に圧縮データのみを読み込ませることができます。outputSettingsに設定nil

AVAssetReaderTrackOutput* assetReaderTrackOutput
= [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:track
                                             outputSettings:nil];

キーポイント:

  • outputSettings:nilデコードされたピクセル バッファーは要求されませんが、圧縮されたサンプルが取得されることを示します。
  • このパスは AVAssetReader のトラックレベルのセマンティクスを保持しており、編集とフレームの依存関係を処理できます。
  • AVAssetReader によって生成された圧縮サンプルは、VideoToolbox に送信されるクロスプロセス RPC 用に最適化されています。

(12:24) 別のパスは次のとおりです。AVSampleBufferGenerator。それは通りますAVSampleCursorメディアレベルのアクセスに近い、読み取り位置を制御します。セッションリマインダーは、編集とフレームの依存関係を理解し​​ていません。 ProRes シーンはより単純であり、HEVC や H.264 などのフレーム間依存関係のあるコンテンツはより注意する必要があります。

AVSampleCursor* cursor = [assetTrack makeSampleCursorAtFirstSampleInDecodeOrder];

AVSampleBufferRequest* request = [[AVSampleBufferRequest alloc] initWithStartCursor:cursor];

request.direction = AVSampleBufferRequestDirectionForward;
request.preferredMinSampleCount = 1;
request.maxSampleCount = 1;

AVSampleBufferGenerator* generator
= [[AVSampleBufferGenerator alloc] initWithAsset:srcAsset timebase:nil];

BOOL notDone = YES;

while(notDone)
{
   CMSampleBufferRef sampleBuffer = [generator createSampleBufferForRequest:request];

   // do your thing with the sampleBuffer

   [cursor stepInDecodeOrderByCount:1];
}

キーポイント:

  • makeSampleCursorAtFirstSampleInDecodeOrderデコード順の最初のサンプルからカーソルを作成します。
  • AVSampleBufferRequest今回採取するサンプルの数と読み取り方向を説明します。
  • timebase:nilこの例では同期リクエストを使用します。セッションでは、パフォーマンス重視のパスでタイムベースを提供し、非同期リクエストを使用することを推奨します。
  • 毎回createSampleBufferForRequestその後、使用しますstepInDecodeOrderByCount:1サンプルを 1 つ進めます。

(13:40) サンプルが独自のファイル形式またはネットワークからのものである場合は、自分で作成する必要がありますCMSampleBuffer。コンポーネントは次のとおりです。CMBlockBufferCMVideoFormatDescriptionそしてCMSampleTimingInfo

CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault, sampleData, sizeof(sampleData),
                                   kCFAllocatorMalloc, NULL, 0, sizeof(sampleData), 0,
                                   &blockBuffer);

CMVideoFormatDescriptionCreate(kCFAllocatorDefault, kCMVideoCodecType_AppleProRes4444, 1920,
                               1080, extensionsDictionary, &formatDescription);

CMSampleTimingInfo timingInfo;

timingInfo.duration = CMTimeMake(10, 600);
timingInfo.presentationTimeStamp = CMTimeMake(frameNumber * 10, 600);

CMSampleBufferCreateReady(kCFAllocatorDefault, blockBuffer, formatDescription, 1, 1,
                          &timingInfo, 1, &sampleSize, &sampleBuffer);

キーポイント:

  • CMBlockBufferCreateWithMemoryBlock圧縮されたサンプル データを Core Media ブロック バッファにパックします。
  • CMVideoFormatDescriptionCreateコーデック、サイズ、拡張子の情報を説明します。セッションでは、拡張機能辞書にカラータグを入れることについて具体的に言及しています。
  • CMSampleTimingInfo期間とプレゼンテーションのタイムスタンプをサンプルに書き込みます。
  • セルフビルドCMSampleBufferサンドボックス RPC に対する AVFoundation の最適化は自動的には取得されません。

3. VTDecompressionSession でデコードする

17:47)圧縮されたCMSampleBufferその後、作成できますVTDecompressionSession。内部にデコーダと出力がありますCVPixelBufferPool、リクエスト形式が一致しない場合にも使用されますVTPixelTransferSession変換を実行します。

// VTDecompressionSession Creation

CMFormatDescriptionRef formatDesc = CMSampleBufferGetFormatDescription(sampleBuffer);

CFDictionaryRef pixelBufferAttributes = (__bridge CFDictionaryRef)@{
    (id)kCVPixelBufferPixelFormatTypeKey :
    @(kCVPixelFormatType_4444AYpCbCr16) };

VTDecompressionSessionRef decompressionSession;

OSStatus err = VTDecompressionSessionCreate(kCFAllocatorDefault,
                                            formatDesc,
                                            NULL,
                                            pixelBufferAttributes,
                                            NULL,
                                            &decompressionSession);

キーポイント:

  • CMSampleBufferGetFormatDescription取得されたフォーマットの説明は、その後セッションに送信されるサンプルと一致する必要があります。
  • pixelBufferAttributes出力要件について説明します。これには、サイズ、ピクセル形式、またはコア アニメーションとの互換性などの高レベルの要件が含まれます。
  • 3 番目のパラメータはvideoDecoderSpecification;パスの例NULL、VideoToolbox のデフォルトのデコーダ選択を使用することを意味します。
  • ハードウェアデコードを強制する必要がある場合は、デコーダ仕様を通じて設定できます。RequireHardwareAcceleratedVideoDecoder; ハードウェア デコードを無効にするには、次のように設定します。EnableHardwareAcceleratedVideoDecoderは誤りです。
  • プロフェッショナルなビデオワークフローで専用のデコーダへのアクセスが必要な場合、アプリの起動時にデコーダが一度呼び出されますVTRegisterProfessionalVideoWorkflowVideoDecoders

(18:30) セッションでは、DecodeFrame を呼び出して非同期デコードを有効にすることを推奨しています。ブロックベースの API は、ステータス、デコードされた画像バッファー、プレゼンテーション タイムスタンプ、および出力ブロックの継続時間を返します。

// Running a VTDecompressionSession

uint32_t inFlags = kVTDecodeFrame_EnableAsynchronousDecompression;

VTDecompressionOutputHandler  outputHandler
 = ^(OSStatus status,
     VTDecodeInfoFlags infoFlags,
     CVImageBufferRef imageBuffer,
     CMTime presentationTimeStamp,
     CMTime presentationDurationVTDecodeInfoFlags)
 {
     // Handle decoder output in this block
     // Status reports any decoder errors
     // imageBuffer contains the decoded frame if there were no errors
 };

VTDecodeInfoFlags outFlags;

OSStatus err = VTDecompressionSessionDecodeFrameWithOutputHandler(decompressionSession,
                                                   sampleBuffer, inFlags,
                                                   &outFlags, outputHandler);

キーポイント:

  • kVTDecodeFrame_EnableAsynchronousDecompressionパフォーマンス重視のビデオ パイプラインに適した非同期デコードをオンにします。
  • VTDecompressionSessionDecodeFrameWithOutputHandler圧縮されたサンプル バッファを受信し、出力ハンドラーを通じて結果を返します。
  • statusデコードエラーを報告します。エラーがないときは、imageBufferデコードされたフレームが含まれます。
  • デコーダ出力はシリアルに返されます。出力ブロックでは時間のかかる作業を行わないでください。そうしないと、後続のフレームにバック プレッシャーが発生します。

4. CVPixelBuffer を Metal に安全に渡します

20:54CVPixelBuffer通常は以下で構成されますCVPixelBufferPool配布する。バッファが解放されると、基礎となるIOSurface再利用するためにプールに戻されます。メタル テクスチャに直接バインドする場合は、メタルの使用中にサーフェスがリサイクルされないように注意してください。

// CVPixelBuffer to Metal texture: IOSurface

IOSurfaceRef surface = CVPixelBufferGetIOSurface(imageBuffer);

id <MTLTexture> metalTexture = [metalDevice newTextureWithDescriptor:descriptor
                                                           iosurface:surface
                                                               plane:0];

// Mark the IOSurface as in-use so that it won’t be recycled by the CVPixelBufferPool
IOSurfaceIncrementUseCount(surface);

// Set up command buffer completion handler to decrement IOSurface use count again
[cmdBuffer addCompletedHandler:^(id<MTLCommandBuffer> buffer) {
     IOSurfaceDecrementUseCount(surface);
 }];

キーポイント:

  • CVPixelBufferGetIOSurfaceピクセル バッファーの背後にある IOSurface を削除します。
  • newTextureWithDescriptor:iosurface:plane:Metal にこのメモリを直接使用させます。
  • IOSurfaceIncrementUseCountGPU が使い果たされる前にサーフェスが使用されないようにするCVPixelBufferPoolリサイクルします。
  • コマンドバッファが完了した後に呼び出されますIOSurfaceDecrementUseCount、サーフェスをプールに戻します。

(21:42) より簡単なアプローチは、次のとおりです。CVMetalTextureCache。それは置きますCVPixelBuffer到着MTLTextureバインディングは Core Video によって管理されるため、プールが IOSurface を再利用する場合にバインディングを繰り返すコストも削減できます。

// Create a CVMetalTextureCacheRef

CVMetalTextureCacheRef metalTextureCache = NULL;

id <MTLDevice> metalDevice = MTLCreateSystemDefaultDevice();

CVMetalTextureCacheCreate(kCFAllocatorDefault, NULL, metalDevice, NULL, &metalTextureCache);

// Create a CVMetalTextureRef using metalTextureCache and our pixelBuffer
CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
                                          metalTextureCache,
                                          pixelBuffer,
                                          NULL,
                                          pixelFormat,
                                          CVPixelBufferGetWidth(pixelBuffer),
                                          CVPixelBufferGetHeight(pixelBuffer),
                                          0,
                                          &cvTexture);

id <MTLTexture>  texture = CVMetalTextureGetTexture(cvTexture);
// Be sure to release the cvTexture object when the Metal command buffer completes!

キーポイント:

  • CVMetalTextureCacheCreateターゲットをバインドする必要があるMTLDevice
  • CVMetalTextureCacheCreateTextureFromImageからCVPixelBuffer作成するCVMetalTextureRef
  • CVMetalTextureGetTextureMetalで実際に使用されているものを取り出しますMTLTexture
  • CVMetalTextureRefまた、Metal コマンド バッファが完了するまで待ってから解放してください。

重要ポイント

  1. ProRes タイムライン プレビューアーを作成: 使用しますAVAssetReaderProRes ファイルを読み取り、Y416 または v216 を直接リクエストし、プレビューのために既存の Metal レンダラーに渡します。これにより、余分なピクセル形式の変換が回避され、利用可能なハードウェア デコーダが自動的に使用されます。

  2. カスタム ネットワーク サンプル デコーダの作成: ビデオ サンプルがネットワークまたはプライベート コンテナから取得される場合、サンプル データをパッケージ化します。CMBlockBufferそしてCMSampleBuffer、その後、餌を与えますVTDecompressionSession。最初に、形式の説明、タイムスタンプ、およびカラータグが正しいことを確認してから、非同期デコードを最適化します。

  3. デコード性能診断スイッチを作成: 開発版で使用RequireHardwareAcceleratedVideoDecoder使用する前にハードウェアデコードが利用可能かどうかを確認してくださいEnableHardwareAcceleratedVideoDecoderハードソリューションをオフにして、コントロールテストを行ってください。これにより、「デコーダーの選択」と「レンダラーのパフォーマンス」を個別に確認できるようになります。

  4. メタルフレームのライフサイクルカプセル化を行う:CVMetalTextureCache、コマンド バッファー完了ハンドラーとテクスチャー解放は小さなオブジェクトにカプセル化されます。呼び出し側が取得できるのは、MTLTexture, IOSurface の使用回数管理をどこでも手書きする必要はありません。

  5. ProRes 4444 アルファ合成ツールの作成: AVAssetReader リクエストkCVPixelFormatType_4444AYpCbCr16、アルファ付きの ProRes 4444 フレームを Metal コンポジターに送信し、タイトル、特殊効果クリップ、色補正前のプレビューなどのプロフェッショナルなビデオ ワークフローに適しています。

関連セッション

コメント

GitHub Issues · utterances