OBSはカクカクスケスケ、CasparCGはヌルヌル黒ずみ。だから「ヌルヌルスケスケ」なHTMLテロップ送出機をC++で自作した
OBS StudioやCasparCGには「ブラウザーソース」という機能があり、透明な背景のHTMLページを読み込ませることで、Web技術をベースにしたテロップやアニメーション(オンエアグラフィックス)を簡単に取り込むことができます。
さらに、これらのツールはBlackmagic Design社が提供する DeckLink デバイス(UltraStudio HD Miniなど)を介して、日本のテレビ放送標準である 1920×1080 / 59.94i のインターレース信号として、Fill(映像)とKey(アルファマスク)に分けてSDIで送出することができます。安価で手軽な送出機として非常に普及していますが、放送・ライブ配信の現場でテロップを突き詰めていくと、既存のツールにはどうしても無視できない「あともう少しこうだったら…」という壁がありました。
それは、**「文字スライド時のカクカク感」と「半透明エッジのスケスケ感(黒ずみ)」**の両立問題です。
🧐 既存ツールのジレンマ:カクカクスケスケ vs ヌルヌル黒ずみ
プロ用ビデオモニターで、画面端からスイーっとスライドインしてくるテキストテロップを表示したとき、何が起きるでしょうか。
1. OBSの場合 ──「カクカクスケスケ」
- カクカク: OBSは 59.94i(インターレース)でSDI出力はしてくれますが、内部のレンダリング(ブラウザーソースの描画など)は1秒間に30枚(30fps)しか絵を描いていません。そのため、第1フィールドと第2フィールドで同じ絵が送られてしまい、プロ用モニターで見るとテキストのスクロールが激しくカクついて(コマ落ちして)見えます。
- スケスケ: 一方で、OBSには優秀な「Unmultiplied Filter(非乗算化フィルタ)」アドオンがあるため、半透明のボケやエッジを綺麗な透過状態でスイッチャーへ送出できます(後述のアルファ二重掛け問題が起きない)。
- 結果:動きはカクカクだが、半透明は綺麗。
2. CasparCGの場合 ──「ヌルヌル黒ずみ(ズケズケ)」
- ヌルヌル: CasparCGは優秀で、内部的にも1秒間に60枚(60fps)の絵を描いて送出します。第1フィールドと第2フィールドで異なるタイミングの映像が乗るため、スクロールテキストが吸い付くように非常に滑らか(ヌルヌル)に動きます。
- 黒ずみ: しかし、CasparCGには「Unmultiplied(非乗算化)」の処理を入れる仕組みが標準で存在しません。Webブラウザ(Chromium)は内部でRGBAの絵を描く際、すでにRGBにアルファ値(A)を掛け算した状態(Premultiplied Alpha: $R \times A, G \times A, B \times A, A$)で画像データを出力します。これをそのままDeckLinkからFill($R \times A, G \times A, B \times A$)とKey($A$)として出力し、外部スイッチャーのキーヤーで合成すると、アルファ値が二重に掛け算されてしまい、テロップの半透明エッジやボケ足の部分が黒ずんでスケスケ感が失われてしまいます。
- 結果:動きはヌルヌルだが、半透明の縁が黒ずんで汚い。
| ツール | 動きの滑らかさ (59.94i) | 半透明エッジ (透過品質) | 特徴 |
|---|---|---|---|
| OBS Studio | 30fps描画(カクカク) | 非乗算化対応(綺麗に抜ける) | 🔴 カクカクスケスケ |
| CasparCG | 60fps描画(ヌルヌル) | そのまま送出(アルファ二重掛けで黒ずむ) | 🔴 ヌルヌル黒ずみ |
AIに相談すると「キーヤー(スイッチャー)側の設定で『Premultiplied(乗算済みアルファ)』として抜けば綺麗に合成できるよ」なんて簡単に言ってくれますが、そんな便利なプロ設定ができる高級な外部キーヤーは持っていません。
私が出したかったのは、**「ヌルヌル動きつつ、半透明もしっかり綺麗に透ける(スケスケな)スライドインテロップ」**でした。
しかし、探せど探せどその両方を満たしてくれるソフトウェアが見当たりません。
「無いなら作るしかない」 ── そう思って開発したのが、この CEFDecklink です。
🛠️ 「ヌルヌルスケスケ」を実現する仕組み
開発したアプリケーションは、Chromiumのレンダリングエンジンである CEF (Chromium Embedded Framework) と DeckLink SDK をC++でダイレクトに繋ぎ、GPUでリアルタイムにピクセル操作を行うことで、この問題を解決しています。
1. 60fps完全同期による「ヌルヌル」インターレース生成
CEFのオフスクリーンレンダリング(OSR)を用いて、Webページのアニメーションを毎秒60フレームでレンダリングします。
これを受け取り、送出処理側で奇数ライン(第1フィールド)と偶数ライン(第2フィールド)に対して、それぞれ時間軸が異なる1秒間60フレーム分のデータから適切なピクセルをインターレースとして再構成してDeckLinkへ送ります。これにより、CasparCGのような「吸い付くような滑らかなテロップの動き」を実現しています。
2. D3D11 Compute Shaderによるリアルタイム「Unpremultiply(非乗算化)」
CEFから受け取るピクセルデータは、前述の通りアルファが乗算された状態(Premultiplied)です。
これをスイッチャーで合成した際に黒ずまないよう、出力直前のバッファでRGB値をアルファ値で割り算して元のRGBA(Unpremultiplied)に戻す処理をリアルタイムで行います。
$$R_{unmult} = \frac{R_{premult}}{A}, \quad G_{unmult} = \frac{G_{premult}}{A}, \quad B_{unmult} = \frac{B_{premult}}{A}$$
この割り算をフルHD解像度(1920x1080)で毎秒60フレーム処理し続けるのは非常に計算負荷が高いため、本システムでは Direct3D 11の Compute Shader(HLSL) を使用してGPU側で一瞬で超高速に並列処理しています。
// YUVConvert.hlsl より一部抜粋
[numthreads(16, 16, 1)]
void main(uint3 DTid : SV_DispatchThreadID)
{
uint x = DTid.x;
uint y = DTid.y;
// CEFから渡されたピクセルをサンプル
float4 pixel = InputTexture.Load(int3(x, y, 0));
// --- Unpremultiply: アルファで除算して元のRGBに戻す ---
if (pixel.a > alphaThreshold) {
pixel.rgb /= pixel.a;
}
// --- 出力用にパッキング (ARGB形式) ---
uint A = (uint)(saturate(pixel.a) * 255.0f);
uint R = (uint)(saturate(pixel.r) * 255.0f);
...
OutputBuffer[uint2(x, y)] = packed;
}
これにより、一般的な仕様のキーヤーで**「半透明の境界が一切黒ずまない、完璧に透き通ったテロップ」**を合成できるようになりました。
💡 まとめ
既存ツールの「帯に短し襷に長し」だった部分を、ネイティブC++の強みを活かして一本に繋ぐことで、念願の 「ヌルヌルかつスケスケなHTMLテロップ送出システム」 が完成しました。
「HTMLの表現力」と「放送局の要求する品質(インターレース+正確なアルファ透過)」の両方を諦めたくない方の参考になれば幸いです。
注: 開発したソフトは GitHubリポジトリ(tanaka-ryuya/CEFDecklinkDemo) にて公開しています。