NV12徹底解説: YUV420半プランナフォーマットの特徴・メモリレイアウトと実装ポイント

NV12とは — 概要

NV12(エヌブイ12)は、YUV 4:2:0 のセミプランナ(半分割)ピクセルフォーマットの一つで、主に動画処理やハードウェアデコード/エンコードで広く使われています。ピクセルごとに明度(Y)と色差信号(U, V)を持ちますが、U/V(クロマ)は水平方向・垂直方向ともに2画素ごとにサンプリングされる(4:2:0)ため、メモリ消費が少なくかつ処理効率が良いのが特徴です。

メモリレイアウトの詳細

NV12は「Y平面」と「UV平面」の二層構造です。まず幅W×高さHのY(輝度)成分が連続して格納され、その直後にUVが交互に並ぶ半分の高さの平面が続きます。基本的なバイト配列は次のようになります。

  • Y平面:W × H バイト(各ピクセルにつき1バイト、通常8ビット)
  • UV平面:W × H / 2 バイト(UとVが交互に並ぶ。つまり各2×2のYブロックに対して1組のU,Vを持つ)

つまり全体のバイト数は W×H × 1.5(8ビット成分の場合)です。UV平面では各水平方向のクロマペアは「U, V, U, V, ...」の順で格納される点がNV12の定義です(NV21は逆に「V, U」の順)。

ピクセルへのアクセス(インデックス計算)

実装上はストライド(行あたりバイト数、pitch)やアラインメントが入るため注意が必要です。一般的なアクセス方法は次の通りです。

  • Yデータのインデックス: y_index = y * stride_y + x
  • UVデータのインデックス: uv_row = floor(y / 2) ; uv_col = floor(x / 2) * 2 ; uv_index = uv_row * stride_uv + uv_col
  • このとき U = uv_plane[uv_index]、V = uv_plane[uv_index + 1]

幅が奇数、あるいは各行にパディング(strideが幅より大きい)されているケースが多いため、stride_y と stride_uv をAPIから取得して正しく扱うことが重要です。多くのハードウェアでは UV 平面の stride は Y 平面と同じ値(または近い値)にされることがありますが、常に保証されるわけではありません。

NV12 と他フォーマットとの比較

  • I420(YUV420P / YV12 とも関連): 完全プラナ形式で Y, U, V が別々の平面。UとVは分離して並ぶ(U平面、V平面)。NV12はUとVが交互に並ぶ点が異なる。I420 は扱いやすさ(単純なメモリアクセス)で優れるが、NV12 はハードウェア転送やテクスチャアップロードで効率的という利点がある。
  • NV21: NV12 と同様のセミプランナ構造だが、UV 平面が VU の順で格納される。Android のカメラや一部デバイスでよく見られる。
  • RGB フォーマット: 直接表示や処理が簡単だが、メモリ量と帯域が大きい。多くのデコードパイプラインは圧縮率と帯域効率の良い YUV 4:2:0(NV12 等)で内部表現を持ち、最終的に表示時に RGB に変換する。

用途とサポート状況

NV12 は多くのプラットフォームとハードウェアでネイティブにサポートされています。例として以下が挙げられます。

  • ハードウェアデコーダ/エンコーダ(Intel Media SDK, VA-API, NVIDIA, AMD など)
  • Windows の Direct3D/DXGI(DXGI_FORMAT_NV12): ハードウェアデコード出力や共有テクスチャとして利用可能
  • FFmpeg や libav のピクセルフォーマット("nv12")
  • Android のカメラ・映像パイプライン(ImageFormat の派生や YUV_420_888 の内部表現として扱われることがある)
  • Video4Linux2(V4L2)等のビデオキャプチャAPI

そのため、ビデオコーデックのデコード出力やエンコード入力、GPU 上での効率的な処理(シェーダでのサンプリングやピクセル処理)に広く使われます。

色変換(YUV → RGB)と色空間

NV12自体は単にデータ配置を定めるフォーマットであり、どの色空間(BT.601 / BT.709 / full range / limited range)を使うかはコンテナやシステムに依存します。変換式は選ぶ標準で変わりますが、代表的なリミテッドレンジ(テレビ向け、BT.601)の変換は次の形です(8ビット成分の場合の概念式)。

  • R = 1.164*(Y - 16) + 1.596*(V - 128)
  • G = 1.164*(Y - 16) - 0.392*(U - 128) - 0.813*(V - 128)
  • B = 1.164*(Y - 16) + 2.017*(U - 128)

BT.709 やフルレンジ(0–255 を直接使用)では係数が異なります。ハードウェアやAPIは色空間とレンジ情報を別途指定できる場合が多い(例:ハードウェアデコーダが色域メタデータを提供する)。間違った色空間で変換すると色がくすんだり彩度が異常になるので注意してください。

派生・拡張(ビット深度や別バリアント)

NV12 は標準的には 8-bit/成分ですが、10-bit 以上に対応する派生フォーマットもあります。代表例:

  • P010 / P016(半プランナの10/16ビット版): NV12 と同様に UV が interleaved だが、各サンプルは16ビットワードに格納され、実際は10/16ビット有効ビットを持つ。HEVC や高ビット深度のワークフローで使われる。

また NV21 のようなUV順の違い、あるいはハードウェア固有のパディングやタイルレイアウトを持つ実装も存在します。

開発者向けの注意点・最適化

  • ストライド/ピッチを常に確認する:API(例えばFFmpegやV4L2、AndroidのImage)から渡されるstrideを使ってメモリ計算を行うこと。
  • UV の順序に注意:NV12 は U then V、NV21 は V then U。誤ると色が赤と青で入れ替わる。
  • 色域とレンジのメタデータを伝える:BT.601/709 や full/limited の違いを保持・伝搬させないと色ズレが生じる。
  • ハードウェアアクセラレーションを活用:多くのGPU/デコーダは NV12 をネイティブで効率処理できるため、可能な場合はCPUでのフルYUV→RGB変換を避ける。
  • 奇数幅/高さの処理:4:2:0 のサブサンプリングは偶数の幅・高さで扱いやすい。奇数の場合はパディングや丸め処理が必要。
  • メモリバリア・キャッシュ制御:GPUとCPUで共有する場合は同期を正しく行うこと。

実際の利用例とワークフロー

典型的なパイプライン例:

  • デコーダ(ハードウェア/ソフトウェア)がフレームを NV12 で出力
  • GPU に NV12 をそのままアップロードし、シェーダで Y と UV を読み出して画面表示(色変換)やフィルタ処理を実行
  • 必要に応じて最終出力で RGB に変換してディスプレイに表示、あるいは別のフォーマットで保存

この流れにより、メモリコピーや変換オーバーヘッドを最小化できます。

まとめ

NV12 は効率的で汎用性の高いYUV 4:2:0の半プランナフォーマットです。ハードウェア支援の多さ、メモリ効率、GPUでの扱いやすさから動画処理パイプラインで非常に広く使われています。一方で UV の順序、ストライド、色域・レンジの扱いを誤ると正しい色再現ができないため、実装時にはこれらの点に注意する必要があります。高ビット深度が必要な場合には P010 などの派生フォーマットを検討します。

参考文献