Windowsのマイクで録ってMacに届ける — ブラウザを「マイクのドライバ」として使う

開発未分類メモ

MacBook の内蔵マイクでは音声クローンの参照音声としては物足りなかった。そこで Windows 機のマイク(NVIDIA Broadcast でノイズ除去済み)で録り、その音声を Mac に持ってきたい。ファイル共有の設定も、USBメモリも、クラウド経由の同期も使わずに済んだ。Windows の Chrome でページを開いて録音ボタンを押したら、Mac のディレクトリに wav が現れた

やっていることは、種を明かせば拍子抜けするほど単純だ。ただ、この単純さが何に支えられているかを分けて見ておくと、応用が一気に広がる。

起きたこと

  1. Mac で小さな HTTP サーバーを動かす(Node の標準機能だけ、90行ほど)
  2. Windows の Chrome で http://192.168.10.102:7870 を開き、録音ボタンを押して台本を読む
  3. 停止した瞬間、Mac の refs/ref_01.wav に 48kHz モノラルの wav が保存される

保存された音声はそのまま音声合成モデルの参照音声になり、15秒後には自分の声で別のセリフを喋る wav ができていた。

Windowsのマイクで録った音がMacのファイルになるまでの6ステップ。ブラウザが担うのは録音データ化まで、送信は同じWi-Fi内のHTTP POST 1回
図1: 録音が Mac に届いたのは、同じWi-Fi内で HTTP を1回投げただけ。ブラウザが要るのは②の工程だけで、送信そのものにブラウザは要らない

やりとりを1本ずつ追う

「両方の機械にサーバーを立てて、お互いのアドレスで送受信している」と考えたくなるが、実際はもっと非対称だ。立てたサーバーは Mac 側の1台だけで、Windows 側には常駐しているものが何もない。ブラウザでページを開いて送ったら、それで終わりである。

Windowsから Mac へ録音が届くまでのやりとり。サーバーは Mac 側の1台だけで、Wi-Fiルーターは家の中で転送するだけ
図2: サーバーは Mac 側の1台だけ。Windows は送るだけの客で、ルーターは宛先を見て家の中へ配っている

待ち受ける側と、送る側。この非対称さがこの構成の軽さの正体で、送る側には何も用意しなくていい。だから Windows でも iPhone でも、ブラウザさえあれば同じ役をこなせる。

中継しているのはルーターで、インターネットではない

「自分が使っているインターネットを中継点にしている」という言い方は、半分あたっていて半分違う。中継しているのは Wi-Fi ルーター(親機)であって、その先のインターネットではない

192.168.10.102 のような 192.168. で始まるアドレスはプライベートアドレスと呼ばれ、家庭や職場の内側でしか意味を持たない。世界のどこにも公開されておらず、プロバイダの先へ流れていくこともない。ルーターはこの宛先を見て「これは家の中の機械だ」と判断し、そのまま Mac へ渡す。外に出ていく経路には一度も乗らない

これは実務上そこそこ大事な性質になる。

  • 回線が遅くても関係ない。Wi-Fi の速度がそのまま転送速度になる
  • インターネットが落ちていても動く。ルーターが生きていれば足りる
  • 録った声がどこかのサーバーを経由することがない。機械の外にすら出ていない

逆に言うと、同じルーターにつながっていることが唯一の条件になる。片方が有線・片方が Wi-Fi でも、同じルーター配下なら問題ない。つながらないときは、ゲスト用 Wi-Fi のように「同じ建物だが別のネットワーク」に入っていないかをまず疑うことになる。

Chrome は必須なのか

必須ではない。送っている中身は、ごく普通の HTTP POST 1回だ。本文(ボディ)に音声のバイナリがそのまま入っているだけで、特別なプロトコルは使っていない。だから、HTTP を喋れるものなら何でも代わりになる。

たとえば Windows 側で録音済みのファイルがあるなら、コマンド1行で同じことができる。

curl -X POST --data-binary @rec.wav http://192.168.10.102:7870/api/upload/1

録音自体もコマンドでやれる。Windows の ffmpeg なら DirectShow 経由でマイクを掴める。

# マイク名を調べる
ffmpeg -list_devices true -f dshow -i dummy

# 12秒録って、そのまま Mac に投げる
ffmpeg -f dshow -i audio="マイク (NVIDIA Broadcast)" -ar 48000 -ac 1 -t 12 rec.wav
curl -X POST --data-binary @rec.wav http://192.168.10.102:7870/api/upload/1

PowerShell 派なら Invoke-RestMethod -Method Post -InFile rec.wav -Uri http://192.168.10.102:7870/api/upload/1 でも同じことになる。ブラウザは選択肢のひとつであって、経路の必須部品ではない

それでもブラウザを使った理由

ブラウザが引き受けているのは、図の②だけだ。マイクを掴んで、録音データの形に変える。この部分に限ってはブラウザが圧倒的に楽をさせてくれる。

  • マイクの一覧取得と選択が数行で書ける(enumerateDevices で NVIDIA Broadcast の仮想マイクも普通に候補に出る)
  • 録音の開始・停止・エンコードを MediaRecorder API が全部やる。webm/opus に圧縮されたデータが Blob で手に入る
  • 音量メーター、台本表示、録り直しといった画面が HTML と CSS だけで作れる
  • Windows でも Mac でも iPhone でも、インストール作業なしに同じものが動く

つまりこの構成は、ブラウザを「録音アプリ」ではなく「マイクのドライバ」として使っている。音を取るところだけブラウザに任せ、取れたデータは素の HTTP で自分のサーバーに流す。ネイティブアプリを書く必要も、配布する必要もない。

つまずいたのは待ち受けアドレス

実は最初、Windows から開いてもページが表示されなかった。原因は Wi-Fi でもファイアウォールでもなく、サーバーの1行だった。

server.listen(PORT, '127.0.0.1')  // Mac の中からしか見えない
server.listen(PORT, '0.0.0.0')    // 同じネットワークの他の機械からも見える

127.0.0.1(ループバック)で待ち受けているプログラムは、同じ機械の中からの接続しか受け付けない。ポートが開いていようが、プログラムが元気に動いていようが、他の機械から見れば存在しないのと変わらない。0.0.0.0 は「この機械が持っている全てのネットワーク接続口で待つ」という意味で、これに変えて初めて Wi-Fi 越しの接続が届くようになる。

ローカル開発で localhost:3000 を叩いているぶんには一生気づかない違いだが、別の機械から繋ぎたくなった瞬間に最初に疑うべき場所がここになる。

ブラウザを使うと1つだけ制約がつく

マイクとカメラは、「安全な接続」でしか使えないという決まりがブラウザ側にある。具体的には HTTPS か localhost のどちらかで、http://192.168.10.102:7870 はそのどちらでもない。放っておくと録音 API が丸ごと無効になり、navigator.mediaDevices が存在しない状態になる。

盗聴されうる経路でマイクを開かせない、という設計思想なので理屈は正しい。回避策は用意されていて、Chrome なら開発用のフラグに URL を登録すればよい。

  1. chrome://flags/#unsafely-treat-insecure-origin-as-secure を開く
  2. 入力欄に http://192.168.10.102:7870 を入れて Enabled にする
  3. Chrome を再起動する

一度きりの設定で済む。自己署名証明書で HTTPS を立てる方法もあるが、証明書の警告を毎回踏むぶん、こちらのほうが手間が少ない。詳しくは MDN の Secure Contexts の説明が分かりやすい。

そして、この制約はブラウザを使うから発生する。ffmpeg と curl で送る経路を選ぶなら、そもそも関係がない。

他の受け渡し方と何が違うか

同じ「Windows から Mac へファイルを渡す」でも、選択肢はいくつもある。それぞれの重さを並べてみる。

方法事前に要る設定送る操作向いている場面
HTTP POSTサーバーを1本動かすだけ1コマンド、または録音ボタン受け取った直後に処理まで走らせたいとき
ファイル共有(SMB)共有設定とアカウント認証ドラッグ&ドロップ大量のファイルを日常的に行き来させるとき
scp / sftpMac 側のリモートログイン許可と鍵1コマンドすでに SSH を使っている環境
クラウド同期アカウントと同期クライアント保存して待つ機械が同じ場所になくてもよいとき

HTTP POST が軽いのは、受け取った直後に好きな処理を挟めるからだ。今回のサーバーは、受け取った webm をその場で ffmpeg に通し、48kHz モノラルの wav に変換し、前後の無音を削ってから保存している。ファイル共有だと「置かれたことを検知して変換する」仕組みを別に作る必要がある。渡すだけでなく、渡した先で何かを起こしたいなら、HTTP のほうが素直になる。

送っているのは音だけではない — 計算も預けている

同じ経路は逆向きにも使える。ページの下部にテキストを打ち込んで「この声で生成する」を押すと、その文字が Mac へ飛び、Mac が音声を作って返してくる

Windowsで文章を入力してから音声が鳴るまでの流れ。計算はすべてMacのM5 Proが行う
図3: 文字を打ったのは Windows、計算をしたのは Mac の M5 Pro。送る側にモデルは要らない

Windows 側には音声合成のモデルも Python も入っていない。 3.3GB のモデルも、それを動かす GPU も Mac 側にある。Windows がしたのは、文字を打つことと、返ってきた wav を鳴らすことだけだ。実測では、10.9秒の音声を18秒で生成して返している。

やりとりが2往復になっているのは、返すのが音声そのものではなく音声の置き場所(URL)と測定結果だからである。ブラウザはそれを見てから、改めて音声を取りに行く。生成に十数秒かかる処理を1回の通信で待たせると、途中経過を出せないし、失敗したときの扱いも面倒になる。

ここまで来ると、この構成の意味が1つ増える。ブラウザは入力装置を貸してくれるだけでなく、計算をよそに預ける窓口にもなっている。 手元のノートPCが非力でも、母艦のGPUで動かした結果だけが返ってくる。送る側の性能は結果に関係しない。

この形が効くのは録音に限らない

一度この構造で考えると、手元の機械やスマホを、母艦の入力装置として使えることに気づく。ブラウザから取れるものは音だけではない。

  • カメラ(写真・動画): スマホで撮ってその場で Mac の処理に流す
  • 位置情報・加速度センサー: 実機でしか取れない値を集める
  • ファイル選択とドラッグ&ドロップ: 相手の機械に何もインストールさせずに受け取る
  • クリップボードのテキスト: 別の機械で書いた文章をそのまま処理に渡す

iPhone を同じ Wi-Fi につないで同じ URL を開けば、iPhone のマイクで録った音がそのまま Mac に着地する。アプリを書かずに、手持ちの端末が全部クライアントになる

気をつけること

0.0.0.0 で待ち受けるということは、同じネットワークにいる誰でもそのページを開けるし、POST も投げられるということでもある。今回のサーバーには認証がない。自宅の Wi-Fi なら実害は考えにくいが、カフェや社内の共用ネットワークで同じことをするなら話が変わる。

  • 使い終わったらサーバーを止める(lsof -ti:7870 | xargs kill
  • 共用ネットワークで使うなら、合言葉を1つ要求するだけでも実質的な抑止になる
  • 受け取ったデータを外部に送らない作りにしておく。今回の構成では音声は Mac の中で完結し、インターネットには一切出ない

最後の点は、声を扱ううえでは無視できない。録った声が自分の機械から出ていかないという性質は、この構成を選ぶ理由のひとつになる。

まとめ

  • Windows で録った音が Mac に届いたのは、同じWi-Fi内で HTTP POST を1回投げたからであって、特別な技術は使っていない
  • Chrome は必須ではない。ffmpeg と curl で置き換えられる。ブラウザに任せているのは「マイクを掴んでデータにする」ところだけ
  • 別の機械から繋がらないときは、まず待ち受けアドレスが 127.0.0.1 になっていないかを疑う
  • ブラウザでマイクを使うなら「安全な接続」の制約がついてくる。フラグ登録で一度だけ回避できる
  • 逆向きも同じ経路で動く。文字を送れば、母艦のGPUが計算した音声だけが返ってくる。送る側の性能は結果に関係しない
  • この形にしておくと、スマホを含めた手持ちの端末が、そのまま母艦の入力装置になる
#ネットワーク#ブラウザ #音声合成#開発メモ