【Vision Pro】フレーム圧縮率とReality Kit投影への影響
Immersive Video再生プレイヤーを開発している中で、フレームの色味や複雑さによってMetalおよびReality Kitを通じての画面投影時間が変動することに気づいたので、備忘録として要因をまとめます。
ビットレートとは
- ビットレート(bitrate) は、単位時間あたりに処理・転送されるデータ量を表す指標です。
- 映像におけるビットレートは「1秒あたり何ビットの映像データを使っているか」を示し、画質やファイルサイズに直結します。
ビットレートが多くなる要因は様々ですが、一般的には「フレームレート」「動きの複雑さ」「コーデックの種類」「色深度」などが影響してきます。
フレーム圧縮率
実はビットレートいうのはあくまで結果であって、元を辿ると「映像フレームをどれだけ効率的に圧縮できるか」に依存します。
例えば以下のFFPMEGコマンドでHEVC動画を作ってみましょう。
ffmpeg -i input1.mov -c:v hevc_videotoolbox -global_quality 25 -profile:v main10 -tag:v hvc1 -map_metadata 0 -c:a copy output1.mov
次に違う動画で全く同じFFMPEGコマンドを打ってみましょう。
ffmpeg -i input2.mov -c:v hevc_videotoolbox -global_quality 25 -profile:v main10 -tag:v hvc1 -map_metadata 0 -c:a copy output2.mov
ouput1.movとouput2.movのビットレートは同じでしょうか?
同じになる可能性もありますが、倍以上違う可能性もあります。
例えば、input1.movの動画は単色が多く使われていて、input2.movには自然がたくさん映った赤や緑の細かいテクスチャが多く含まれていると、outpu1.movが150Mbps、output2.movが400Mbpsになったりします。
前後のフレームの動きの激しさによっても変動しますが、この後で映像デコード後のVision ProでのReality Kitへの投影処理の話にフォーカスしたいため、一旦、フレーム単体での要因に話を絞ります。
フレーム圧縮の手順
フレーム圧縮する時に、エンコーダはフレームを8x8ピクセルのブロックに分解します。
(正確にはHEVCは可変ブロックですがここでは話をシンプルにするため割愛)
DCT処理
次にDCT(離散コサイン変換:Discrete Cosine Transform)という公式を使って、ブロックを「周波数成分」に分解します。
x(i,j):ブロック内の i 行 j 列目の画素値
F(u,v):そのブロックに含まれる周波数 (DCT 後の係数)
この公式では、空などの単色ブロックは低周波に、木の葉っぱのギザギザや草の一本一本を高周波になるようになっています。
(行列のそれぞれの意味は複雑すぎるので割愛)
ここで重要なのは単色が多く使われているフレームほどビット数が削減できて、多くの色が使われているブロックが多いほどビット数が大きくなるということです。
低周波行列
⎡A 0 0 0 0 0 0 0⎤
⎢0 0 0 0 0 0 0 0⎥
⎢0 0 0 0 0 0 0 0⎥
⎢0 0 0 0 0 0 0 0⎥
⎢0 0 0 0 0 0 0 0⎥
⎢0 0 0 0 0 0 0 0⎥
⎢0 0 0 0 0 0 0 0⎥
⎣0 0 0 0 0 0 0 0⎦
高周波行列
[ 45, 30, 20, 15, 10, 8, 5, 3 ]
[ 30, 25, 22, 18, 15, 12, 8, 5 ]
[ 20, 22, 20, 18, 15, 12, 8, 5 ]
[ 15, 18, 18, 17, 14, 10, 7, 4 ]
[ 10, 15, 15, 14, 12, 10, 7, 4 ]
[ 8, 12, 12, 10, 10, 8, 5, 3 ]
[ 5, 8, 8, 7, 7, 5, 3, 2 ]
[ 3, 5, 5, 4, 4, 3, 2, 1 ]
量子化処理
次に、周波数成分の生の係数をそのまま符号化するとビット数が膨大になるため、量子化テーブルを使って高周波を削減します。
背景として、低周波の変化は人間の目に気づかれやすいが、高周波の変化は気付かれにくいという性質があります。例えば空などの単色はバンディングが目立ちやすいのと同じ原理です。
低周波行列の量子化後
⎡ A 0 0 0 0 0 0 0⎤
⎢ 0 0 0 0 0 0 0 0⎥
⎢ 0 0 0 0 0 0 0 0⎥
⎢ 0 0 0 0 0 0 0 0⎥
⎢ 0 0 0 0 0 0 0 0⎥
⎢ 0 0 0 0 0 0 0 0⎥
⎢ 0 0 0 0 0 0 0 0⎥
⎣ 0 0 0 0 0 0 0 0⎦
高周波行列の量子化後
[45, 15, 7, 4, 2, 1, 1, 0]
[15, 8, 6, 4, 2, 2, 1, 1]
[ 7, 6, 4, 3, 2, 2, 1, 0]
[ 4, 4, 3, 2, 2, 1, 1, 0]
[ 2, 2, 2, 2, 1, 1, 1, 0]
[ 1, 2, 2, 1, 1, 1, 0, 0]
[ 1, 1, 1, 1, 1, 0, 0, 0]
[ 0, 1, 0, 0, 0, 0, 0, 0]
ここで、どれだけの数が非0係数として残存するかで、この後のフレーム圧縮率およびフレームビット数に影響します。
ジグザグ走査
量子化後の 8×8 周波数係数行列を非0から順に一次配列に並べます。
低周波
[ A, 0, 0, 0, 0, 0, 0, 0, ... 0 ] (64 要素中、先頭に A、その後は 0 が 63 個)
高周波
[45, 15, 15, 7, 8, 7, 4, 6,
6, 4, 2, 4, 4, 4, 2, 1,
2, 3, 3, 2, 2, 2, 2, 2,
2, 3, 2, 1, …]
ランレングス符号化(RLE)
一次元配列から「何個スキップして次の値」というペア (run, level) を作成します。
低周波
- 最初に非ゼロ「A」が現れるまでの 0 が 0 個 → (run=0, level=A)
- 次に非ゼロがないので、残りすべてが 0 → 通常は EOB (End Of Block) として終了
(0, A), EOB
高周波
- (run=0, level=45)
- (run=0, level=15)
- (run=0, level=15)
- (run=0, level=7)
- …
ほぼ毎回 run=0 で level が続く
末尾で「残りゼロが連続」したところをまとめて EOB
(0,45),(0,15),(0,15),(0,7),(0,8),(0,7),(0,4),…,(0,1),EOB
エントロピー符号化(Entropy Coding)
ランレングス符号化の出力 (run, level) ペア列を、さらに可変長符号化してビットストリームにします。
使用する符号テーブルによって変わりますが、例として以下のようなビット列になります。
低周波
元の RLE 出力:
(0,A) → (0,16) と仮定
EOB
テーブル参照:
(0,16)→ 例えば 11001
EOB → 1
ビットストリーム:
11001 1
→ 6 ビットで完結
高周波
元の RLE 出力(先頭だけ抜粋):
(0,45), (0,15), (0,15), (0,7), (0,8), (0,7), (0,4), … , EOB
テーブル参照:
(0,45) → 11011
(0,15) → 11010
(0,7) → 1100
(0,8) → 1010
(0,4) → 101
先頭パートのビット列
11011 ← (0,45)
11010 ← (0,15)
11010 ← (0,15)
1100 ← (0,7)
1010 ← (0,8)
1100 ← (0,7)
101 ← (0,4)
… ← さらに多数のペア
1 ← EOB
それぞれのペアが 4~5 ビットかかり、合計で30~40 ペア×約5ビット ≒ 150~200 ビットほどになります。
このように低周波と高周波で必要なビット数がかなり変わってきます。
エンコードを例に単色または複雑な色味によって必要なビット数が変わることを説明してきました。
エンコード処理としてはさらに必要な工程があるのですが、Reality Kit投影への影響を解説したいので、フレーム圧縮の話はここまでとします。
Reality Kit投影への影響
この記事を書くきっかけとして、Metalで描画計算したフレームをReality Kitへ投影する時に、フレームによってGPU処理開始から投影完了までの時間が変わることに気づいた、ということがあります。
なぜ、MetalのFragmentシェーダーの計算速度は色によって差分が出ないはずなのに、フレームによって投影完了速度が変わるのでしょうか?
Apple Silicon GPUのタイルベースレンダリング
Apple Silicon GPUではフレームを32x32ピクセルの小さなタイルに分割し、各タイルごとにオンチップ SRAM(Tile Buffer)上でレンダリングを完結させ、タイル処理終了後にまとめて DRAM に書き戻します。
-----ここから先は別途追記予定-----
Discussion