個人開発

ブラウザだけでゲーム画面をPCに映して録画するツールを作った — OBSも要らず「サクッと映す」

机の上のノートPCとゲーム機、キャプチャ用のUSBドングルとケーブル

何を作ったか

てちこまツール — キャプチャ録画スタジオ を追加しました。

USBキャプチャボードに繋いだゲーム機の映像を、ブラウザで開いたページにそのまま映せるツールです。Webカメラのワイプ(ワイプ=ピクチャインピクチャの小窓ですね)を重ねたり、キャプチャ音声とマイクをミックスしたり、全画面で映したり、そのままローカルに録画したり——という配信まわりの基本セットを、ブラウザの中だけで完結させています。

インストール不要、ブラウザで開くだけ。映像も音声もこの端末の中だけで処理していて、サーバーには何も送りません。ホスティングは既存の「てちこまツール」と同じく Astro + Cloudflare なので、運用コストもほぼ0のままラインナップに足せました。

なぜ作ったか

きっかけは、仲間内でちょっとゲーム画面を見せ合いたいときとか、PCに映してワイワイ遊びたいときに、毎回その準備が面倒だったことです。

ちゃんと配信するなら OBS を立ち上げて、シーンを組んで、ソースを追加して、音声を整えて……となります。セカンドモニタを用意したり、配線を引き回したりも要る。でも自分がやりたいのは「ガチ配信」じゃなくて、「いまこの画面、PCにパッと映したい」くらいの気軽な瞬間なんですよね。

そのテンションのときに OBS のセットアップから始めるのは、率直に言って腰が重い。だから 「ブラウザで開くだけ・配線最小・ローカル完結で、パッと映してパッと録れる」 に振り切ったツールを作りました。ガチ配信勢の置き換えを狙ったものではなくて、「ちょっと映したい」を最短にするためのツール、という位置づけです。

キャプチャボードはブラウザから「Webカメラ」に見える

仕組みの根っこはここです。

USBキャプチャボードの多くは UVC(USB Video Class) という規格に対応していて、専用ドライバなしで繋がります。そしてUVC機器は、ブラウザからは普通のWebカメラと同じものに見えます。つまり、ゲーム機 → HDMI → キャプチャボード → USB → PC と繋ぐと、ブラウザの getUserMedia で「カメラのひとつ」として映像を取れてしまうわけです。

const stream = await navigator.mediaDevices.getUserMedia({
  video: { deviceId: { exact: captureDeviceId } },
});

ここに気づけたのが出発点でした。「キャプチャボードの映像をブラウザで扱える」なら、あとは合成と録画をブラウザの中で組み立てればいい。

設計の方向転換:OBSに渡すのをやめた

最初に考えていたのは、こうでした。

ブラウザで「クリーンなゲーム映像のウィンドウ」を出して、それを OBS のウィンドウキャプチャで取り込む。

つまりブラウザはあくまで「きれいな映像窓」担当で、録画や配信は OBS に任せる構成です。でも作っているうちに、「OBS に渡す必要すらなくないか? ツール自体が録画まで完結すればいいのでは」と思い直しました。

合成も録画もブラウザでできるなら、わざわざ別アプリに橋渡しする意味が薄い。OBS を間に挟まない分、起動するものも減ります。そこで方針を 「合成してローカル録画まで、ぜんぶこのツールでやる」 に切り替えました。

越えられなかった線引き:生配信(RTMP)はできない

ここは正直に書いておきます。

ブラウザ単体では、Twitch や YouTube への生配信(RTMP送出)はできません。RTMP はブラウザのAPIの外にあるプロトコルで、ブラウザのJavaScriptからは叩けないからです。

なので「合成して、ローカルに録画するところまで」で割り切りました。録ったものを後で編集してアップする、という使い方ですね。生配信したい人にとってはここが物足りないはずで、その用途ならやっぱり OBS が要ります。「ブラウザだけで配信まで全部」とは言えない、というのはツールの説明にもそのまま書いています。

実装ハイライト

ワイプを使わないときは生映像を直接表示する

合成って便利な反面、canvas を一枚かませる分だけ遅延が増えます。ワイプもオーバーレイも使っていないのに、ずっと canvas 合成を通すのはもったいない。

そこで、ワイプを使っていないときは合成 canvas をバイパスして、生の映像をそのまま表示する ようにしました。ブラウザ側で増える遅延を最小にするためです。ワイプをONにしたときだけ canvas 合成に切り替わって(数フレーム増えます)、OFFに戻すとまた生映像に戻る。これが自動で切り替わります。

プレビューの <video> 要素には data-mode="raw"(生映像直表示)か composite(canvas合成)の状態を持たせて、ワイプの有無で出し分けています。

音声でいちばんハマった話

実は映像より音声のほうが手こずりました。読み物として面白いので残しておきます。

ブラウザの getUserMedia は、音声を取るときに 既定でマイク向けの音声処理 をかけます。エコーキャンセル・ノイズ抑制・オートゲインコントロールの3点セットです。通話やボイチャ用途ならありがたい機能なんですが、これを キャプチャ音声(ライン入力)にかけると最悪 でした。

具体的には、音量が勝手に上下するポンピングが起きたり、音がこもったり、BGMの一部が「環境ノイズ」と判定されて削られたりします。要するにライン音源が激しく劣化する。

対処は、これらの処理を全部オフにすることでした。

const audio = await navigator.mediaDevices.getUserMedia({
  audio: {
    deviceId: { exact: captureAudioDeviceId },
    echoCancellation: false,
    noiseSuppression: false,
    autoGainControl: false,
    channelCount: 2,
    sampleRate: 48000,
  },
});

エコーキャンセル等を切って、原音のまま・ステレオ・48kHz で取るようにしたら、キャプチャ音声がちゃんと素直に出るようになりました。

もうひとつ落とし穴があって、キャプチャ音声はキャプチャボードの音声デバイス(UACデバイス)から来る という点です。これは映像とは別デバイス扱いになります。最初これに気づかず、PC内蔵のマイクを拾ってしまっていました。「映像はゲーム画面なのに、音は部屋の生活音」みたいな状態ですね。キャプチャ音声用に、キャプチャボードの音声デバイスを明示的に選んで取得する必要がありました。

ツール側では、映像デバイス・キャプチャ音声デバイス・マイクデバイスをそれぞれ別に選べるようにして、このあたりを吸収しています。さらに、つないだキャプチャボードを自動で見つけて初期選択するようにしたので、たいていは何も選ばなくても、そのまま映って音も出ます。

Web Audio で2系統をミックスする

キャプチャ音声とマイクの2系統を、Web Audio API でミキサーに通しています。各系統に GainNode を挟んで音量とミュートを制御し、AnalyserNode でレベルメーターを出す、という素直な構成です。

const ctx = new AudioContext();
const captureGain = ctx.createGain();
const micGain = ctx.createGain();
// それぞれの GainNode を destination に合流させ、
// AnalyserNode でレベルを可視化する

UI上は、キャプチャ音声とマイクそれぞれにフェーダーとミュートボタン、そしてレベルメーターが並びます。配信ソフトのオーディオミキサーの、いちばん小さい版という感じです。

手元モニターは録音と別で使える。ヘッドホン推奨

キャプチャ音声をPCのスピーカーで聞きたいときは「手元モニター」をオンにします。ポイントは、録音をオンにしていなくても、モニターだけ単独で使える ところです。「録るわけじゃないけど、いまPCで音を確認したい」というときですね。オンにすると、聞きたいキャプチャ音声を選ぶダイアログが出て、選ぶと再生が始まります。

ただ、自分のプレイ中の音をPCのスピーカーから出すと、その音がマイクに回り込んでハウリングしやすくなります。なのでヘッドホン推奨にしています。

ちなみにこのツールは画面共有をしない限り、音声が通話に乗ることはありません。全部ローカルで処理しているので、Discord通話などをしながら使っても、合成した音がそのまま相手に流れるわけではない設計です。

録画・全画面・スリープ防止

録画は MediaRecorder で、合成後のストリームを WebM として書き出します。全画面表示はクリーンな映像だけを出せるようにしてあります。

設定(選んだデバイスやワイプの位置など)は localStorage に持たせて、次に開いたときも同じ状態で始められるようにしました。あと長時間の録画中にPCがスリープすると困るので、Screen Wake Lock API でスリープを抑止しています。

ロジック部分(音声処理の設定生成、合成の状態判定、時間フォーマットなど)は Vitest でユニットテストを書いています。

実機で得た学び

設計の話だけだとふわっとするので、実際に繋いで分かったことも書いておきます。

  • 安価なキャプチャボードでも 1080p60 がちゃんとブラウザに来た。今回使ったのは UGREEN のUSBキャプチャボードという安めの機種ですが、1080p60 の映像が問題なくブラウザに届きました。
  • HDMI入力が無いと、キャプチャボード自身が「No Signal」画面を出す。これに最初ちょっと焦りました。ツールのせいかと思ったら、単に挿す穴(HDMI IN)を間違えていたり、ゲーム機側の電源が入っていなかったり、というオチです。映らないときはまずソース側を疑うのが正解でした。
  • Switch はHDMI音声が落ちることがある。省電力モードや、解像度の再ネゴシエーションのタイミングで、HDMIの音声だけ落ちることがありました。これはHDMIケーブルを挿し直すと復活します。
  • パススルーなしでも、案外ふつうに遊べた。本来はキャプチャ機のパススルー出力にモニターを繋いで、そっちを見ながらプレイするのが定石です。でも試しに、キャプチャした映像(ブラウザのプレビュー)だけを見て操作してみたら、遅延はそこまで気にならずに遊べました。コンマ数秒を争うようなシビアな対戦は別ですが、のんびり系のゲームなら、プレビューを見ながらでも十分いけます。これがありがたくて、パススルー先のモニタや配線を別途用意しなくていい ぶん、机周りがすっきりします。ゲーム機 → HDMIケーブル → キャプチャボード → USBケーブル → PC、これだけです。気軽さがひとつ上がりました。

安いUSBキャプチャボードを買ったレビュー(ドライバ不要・パススルーあり)

使い方の流れ

最後に、実際の操作はこれだけです。

  1. ブラウザで キャプチャ録画スタジオ を開く
  2. 「カメラを許可して開始」を押す
  3. キャプチャボードを映像デバイスとして選ぶ
  4. 必要なら、WebカメラのワイプをON・音声ミックスをON
  5. 録画・全画面・スクショ
キャプチャ録画スタジオの画面全体。上部にプレビュー、下にキャプチャ・Webカメラワイプ・音声ミックス・オーディオミキサーの設定パネルが並ぶ
開始後の画面。プレビューの下に、映像デバイス選択・ワイプ・音声ミックス・ミキサーが並ぶ(プレビューのゲーム画面は著作権配慮でぼかしています)

ワイプをONにすると、メインの映像に小窓が合成されます。下の画像は右下にWebカメラのワイプを出した状態です。

メイン映像の右下に小さなワイプ(ピクチャインピクチャ)が合成されたプレビュー
ワイプON時。右下にWebカメラの小窓が合成される(ゲーム画面と顔はぼかしています)

まとめ

  • キャプチャボードはUVCなので、ブラウザから「Webカメラ」として映像を取れる。そこを起点に、合成・音声ミックス・録画までブラウザ内で完結させた
  • 生配信(RTMP)はブラウザ単体ではできないので、「合成してローカル録画まで」に割り切った
  • 音声はマイク向けの処理(エコーキャンセル等)を切って、キャプチャ音声は専用デバイスから原音で取るのがポイントだった
  • ワイプを使わないときは生映像直表示で遅延を抑えている

「ガッツリ配信するほどじゃないけど、PCにサクッとゲーム画面を映したい・録りたい」——そういう気軽な瞬間に絞れば、ブラウザだけでもここまでできます。よかったら使ってみてください。

てちこまツール — キャプチャ録画スタジオ