SSTによる安全なWebサイト運営のためのセキュリティ情報

エンジニアブログ
  • shareSNSでシェア
  • Facebookでシェアする
  • Xでシェアする
  • Pocketに投稿する
  • はてなブックマークに投稿する

Google MeetのWebカメラを加工してみよう!

Google MeetのWebカメラを加工してみよう!

ごにょごにょごにょごにょ… ということでここまで約8時間、快適なオンラインビデオ会議について講義を行ってきましたが、理解できましたか?もうあなた方は立派にGoogle Meeeeetが使えます!

というわけで、今日はGoogle Meetを少しだけ快適に使うハックの話をします。CTOの長谷川です。

Google Meetって?

Google MeetはZoomやSkype、Teamsなどと同様にインターネット経由で手軽にビデオ会議が行える、Googleによるサービスです。過去にはサービス名が「Google ハングアウト」「Google ハングアウト Meet」だったこともあり、オンラインの記事ではあちこち旧称も混ざったりと表記が揺れてますが、この記事ではGoogle Meetに統一します(Meeeeetではないです)。

Google Meetと他のサービスを比べてみると、以下のような差異があります

  • ブラウザーのみで実行できる。専用クライアントのインストールなどは不要
  • バーチャル背景や背景ぼかしのような機能がない

他のソフトウェアをインストールしなくてもブラウザーだけで動くのは手軽でいいのですが、背景を変えたりできないのはちょっと遊び心が足りないですし、遊びのない社内の会議であっても散らかってる部屋が写り込んでしまうのは嫌ですよね。そんなわけで、Google Meetでカメラ映像(とマイク音声)を加工して送信することにチャレンジしてみましょう!

先に書いた通り Google Meetはブラウザーだけで動作していますので、実はすべての機能がJavaScriptで実装されており、カメラやマイクも navigator.MediaDevices.getUserMedia のようなAPIをJavaScriptから呼び出すことで実現されています。ですので、ブラウザー拡張の Content Script としてgetUserMedia 関数をフックするコードを書くことで、本来のカメラ映像に代わり任意の映像をミーティング相手に返すことができるはずです。

Chrome拡張「ビタミン Meeeet」

そんなわけで、getUserMediaのフックを利用して手軽にGoogle Meet内でWebカメラ映像を加工したりマイクに音源ファイルを重ねて配信するためのChrome拡張を作ってみました。Google Meetだけでは足りない栄養を手軽に追加するという意味で「ビタミン Meeeeet」と名付けています。

https://github.com/hasegawayosuke/vitamin-meeeeet

この拡張機能をインストールすると、Google Meetの画面下部に以下のようなコントロールが並びます。(ソースコードからの拡張機能のインストール方法は割愛)

 

Meetに追加されるコントロール

Meetに追加されるコントロール

 

ドロップダウンリストはデフォルトで「カメラ」になっていますが、これを変更することでWebカメラとして配信したい映像の種類を選ぶことができます。「ファイル」にすると動画ファイルや画像ファイルを配信することができます。また「合成」を選ぶとWebカメラ映像にフォトフレームのように背景透過のPNG画像ファイルを重ねた映像を配信することができます(映像の種別やファイルを選んだあとに、いったんカメラをオフ→オンに切り替える必要があります)。

また「効果音」として音源ファイルを選んでおき、右側にあるオーディオコントロールの再生ボタンを押すことで、音源ファイルをマイク音声に重ねて配信することができます。例えば、ボイスパーカッションの音源ファイルをあらかじめ用意しておいてオンライン飲み会の最中に流すとかも、外付け/ソフトウェアのミキサー無しで実現できますね。

現在のところまだ実験段階の実装でもあるため、使える画像ファイルとしては1920×1080固定であったり、ミーティングから退出してすぐに入室を行うと動作がおかしくなったりと、まだまだ様々な不具合や制限がありますが、Webカメラに代えて好きな映像を流すことができますので窮屈な在宅でのオンラインミーティングも少しだけ楽しむことができます。

実際に使ってみたところ

ビタミン Meeeeet をインストールして実際にGoogle Meetで使ってみたときの画面はこのような感じです。

デフォルトだと、名前のテキストやGoogleアカウントに紐づいたプロフィール写真が小さく表示されるだけで味気ないですが、かといってカメラをONにするのも抵抗があるなというときには、味気を感じる写真をカメラの代わりに表示させたりもできます。

 

画面遷移

 

Webカメラ映像に代えてラーメン写真を表示させ(左下)、ミーティング中に飯テロしているところ

カメラをONにしたほうが表情などのノンバーバルな情報も伝わってミーティングが進めやすいものの、背景まで映ってしまうことに抵抗があるという場合でも、顔部分だけ透明にくりぬいた仮想前景の画像ファイルを用意しておくことで、散らかった部屋を気にする必要なくミーティングに臨むことができます。

 

動作確認

 

「合成」でWebカメラと写真を重ねたところ。ちなみに普段はコンタクトレンズだけど今日は在宅勤務なので眼鏡の男の子です

技術的解説:getUserMedia のフック

ここからは少しだけ真面目に技術的な説明をします。

getUserMedia は、ユーザーエージェント(ブラウザー)を経由してカメラやマイクにJavaScriptからアクセスするためのAPIです。getUserMedia 関数の呼び出しにより、映像トラックや音声トラックを含む MediaStream が取得されます。ですので、この getUserMedia 関数をフックしてトラックを差し替えて返すことにより、任意の動画や音声をGoogle Meet上で流すことができるというわけです。

というわけで、getUserMedia をフックするためのコードの要旨は以下のようになります。

const video = document.createElement('video')  // Webカメラ映像として流す動画のためのvideo要素
video.muted = true                       // 音声はマイク経由になるのでミュート再生
video.src = 'https://...'
video.onloadmetadata = () => video.play()
navigator.mediaDevices._getUserMedia = navigator.mediaDevices.getUserMedia   // オリジナルのgetUserMedia を保存
navigator.mediaDevices.getUserMedia = function (constraints) { // getUserMediaをフック
  return new Promise((resolve, reject) => {
    navigator.mediaDevices._getUserMedia(constraints)          // オリジナルのgetUserMediaを呼び出す
      .then((stream) => {
        if (constraints.video) {
          const vt = stream.getVideoTracks()
          if (vt.length) {
            const newStream = video.captureStream()           // video要素からMediaStreamを作成
            stream.removeTrack(vt[0])                         // Webカメラの映像トラックを削除
            stream.addTrack(newStream.getVideoTracks()[0])    // 作成した映像トラックをWebカメラの映像トラック代わりに追加
          }
        }
        resolve(stream)
      })
      .catch((err) => {
        reject(err)
      })
  })
}

これで、video 要素のsrcとして指定した任意の動画をWebカメラ代わりに配信することができます。
ポイントとしては、 video要素からMediaStreamを作成するために video.captureStream() のように呼び出しています。また、 getUserMediaから返すMediaStreamには映像トラックが含まれているので、 stream.removeTrack(stream.getVideoTrack()[0]) のようにして映像トラックを削除し、次に新しい映像トラックを stream.addTrack(newStream.getVideoTracks()[0]) として追加しています。

上記コードでは省略していますが、実際には画面共有を行った際の画面キャプチャも MediaStream を通じて取得されることになるので、 constraints.video.mandatory.chromeMediaSource === 'desktop' のときにはそのままのMediaStreamを返す等の細かな処理も必要です。

動画ファイルではなく静止画の画像ファイルを配信する場合にはvideoではなくcanvas要素を使って canvas.captureStream(10) のようにすることで生成したMediaStreamにより実現できます。

また、音声トラックについても、映像トラックと同じように getUserMediaをフックし Web Audio APIを使って加工した音声トラックを含むMediaStreamを返すことで、生のマイク音声ではなく任意の音声データを配信することもできます。

まとめ

Google Meetはバーチャル背景などもサポートされておらず不自由だと言われることもありますが、実際にはJavaScriptで実装されており自分の力で様々な機能を実装することができるので、もしかするともっとも自由なオンライン会議システムなのかもしれません。厳しい社会情勢のなか閉塞感で息が詰まりそうですが、技術を使い少しでも楽しいオンラインミーティングを!誰しも一人ではないのだよ

  • shareSNSでシェア
  • Facebookでシェアする
  • Xでシェアする
  • Pocketに投稿する
  • はてなブックマークに投稿する

この記事の筆者

筆者:はせがわようすけ

はせがわようすけ

(株)セキュアスカイ・テクノロジー 取締役CTO