Skip to content

feat: ブラウザ経由の音声入力対応 #187

@windschord

Description

@windschord

背景

Claude Codeに公式の音声入力機能(Voice Mode)がロールアウト中。

Voice mode is rolling out now in Claude Code. It's live for ~5% of users today, and will be ramping through the coming weeks.
/voice to toggle it on!

-- Thariq (@trq212)

  • /voice コマンドで有効化
  • スペースバー長押しでPush-to-Talk
  • 現在約5%のユーザーに展開中、今後数週間で段階的にロールアウト

ClaudeWorkでの課題

ClaudeWorkはサーバー側PTYでClaude Codeを実行し、XTerm.jsでブラウザに表示するアーキテクチャのため、Claude Codeの /voice 機能はサーバー側のマイクを使おうとする。ブラウザのマイクからは直接使えない

方針: 公式 /voice + 仮想マイク転送

Claude Codeの公式 /voice 機能をそのまま活用する方針。ブラウザのマイク音声をサーバーに転送し、PulseAudio仮想マイクデバイス経由でClaude Codeに認識させる。

アーキテクチャ

ブラウザのマイク
  → MediaRecorder API (WebM/Opus)
  → WebSocket (audio-stream メッセージ)
  → サーバー: 音声データ受信
  → FFmpeg でPCM変換
  → PulseAudio 仮想デバイスに書き込み
  → module-null-sink + module-remap-source(仮想マイク)
  → Claude Code /voice がデフォルトマイクとして認識

実装要素

1. ブラウザ側: マイクキャプチャと転送

// MediaRecorder APIで音声キャプチャ
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const mediaRecorder = new MediaRecorder(stream, {
  mimeType: 'audio/webm;codecs=opus'
});

// WebSocket経由でサーバーに送信
mediaRecorder.ondataavailable = (event) => {
  ws.send(JSON.stringify({
    type: 'audio-stream',
    data: arrayBufferToBase64(event.data),
    mimeType: 'audio/webm'
  }));
};
mediaRecorder.start(250); // 250msごとにチャンク送信

2. サーバー側: PulseAudio仮想マイク設定

# セッションごとに仮想マイクデバイスを作成
pactl load-module module-null-sink sink_name=virtmic_session_<id> \
  sink_properties=device.description="VirtualMic-Session-<id>"

# モニターソースをマイクデバイスとして公開
pactl load-module module-remap-source \
  master=virtmic_session_<id>.monitor \
  source_name=virtmic_src_<id> \
  source_properties=device.description="VirtualMic-Source-<id>"

3. サーバー側: 音声データの仮想デバイスへの書き込み

// WebSocketで受信した音声データをFFmpegで変換し、PulseAudioに送信
import { spawn } from 'child_process';

const ffmpeg = spawn('ffmpeg', [
  '-i', 'pipe:0',           // stdinから入力
  '-f', 's16le',            // PCM 16-bit出力
  '-ar', '44100',           // サンプルレート
  '-ac', '1',               // モノラル
  'pipe:1'                  // stdoutに出力
]);

const pacat = spawn('pacat', [
  '--playback',
  '-d', `virtmic_session_${sessionId}`,  // 仮想シンクに出力
  '--format=s16le',
  '--rate=44100',
  '--channels=1'
]);

ffmpeg.stdout.pipe(pacat.stdin);

// WebSocketから受信した音声データをFFmpegに渡す
ws.on('message', (data) => {
  const msg = JSON.parse(data);
  if (msg.type === 'audio-stream') {
    const buffer = Buffer.from(msg.data, 'base64');
    ffmpeg.stdin.write(buffer);
  }
});

4. Docker環境対応

# docker-compose.yml に追加
services:
  app:
    volumes:
      - /run/user/${UID}/pulse/native:/run/user/1000/pulse/native
      - ${HOME}/.config/pulse/cookie:/home/app/.config/pulse/cookie:ro
    environment:
      - PULSE_SERVER=unix:/run/user/1000/pulse/native
    # PulseAudioクライアントライブラリが必要
    # Dockerfileに追加: apt-get install -y pulseaudio-utils ffmpeg

5. Claude Code PTYへの /voice コマンド送信

// セッション開始時またはユーザー操作時に/voiceを送信
ptySession.write('/voice\n');

// PULSE_SOURCEでセッション固有の仮想マイクを指定
// PTYプロセスの環境変数として設定
const env = {
  ...process.env,
  PULSE_SOURCE: `virtmic_src_${sessionId}`
};

セッション分離

各セッションに独立した仮想マイクデバイスを割り当て、複数ユーザーの同時利用に対応する:

  • セッション作成時: module-null-sink + module-remap-source をロード
  • セッション終了時: ロードしたモジュールをアンロード(pactl unload-module <module-id>
  • Claude Code PTYのPULSE_SOURCE環境変数でセッション固有のデバイスを指定

未確認事項・リスク

項目 内容 影響度
/voice のマイク選択方式 デフォルトデバイスのみか、PULSE_SOURCE環境変数に従うか
ロールアウト状況 現在~5%、GA時期は未定
Docker内PulseAudio ソケット共有でコンテナ内のClaude Codeが認識するか
オーディオレイテンシー ブラウザ→WebSocket→FFmpeg→PulseAudio→Claude Code
ブラウザ互換性 MediaRecorder APIはChrome/Firefox/Safari対応

実装フェーズ

Phase 0: 検証(POC)

  • HOST環境でClaude Codeの /voice がPULSE_SOURCEを認識するか検証
  • PulseAudio仮想マイクに音声を流してClaude Codeが認識するか検証
  • Docker環境でPulseAudioソケット共有が機能するか検証

Phase 1: 基盤実装

  • ブラウザ側: MediaRecorder + WebSocket音声ストリーミング
  • サーバー側: PulseAudio仮想マイク管理サービス
  • サーバー側: FFmpeg + pacat による音声パイプライン
  • WebSocket: audio-stream メッセージタイプ追加

Phase 2: UI/UX

  • マイクボタンUI(録音開始/停止)
  • 音声入力状態のインジケーター
  • /voice コマンドの自動送信
  • ブラウザマイク権限のハンドリング

Phase 3: Docker対応

  • Dockerfileにpulseaudio-utils, ffmpegを追加
  • docker-compose.ymlにPulseAudioソケットマウント追加
  • コンテナ内PulseAudio接続テスト

代替案(フォールバック)

Phase 0の検証でClaude Codeの /voice が仮想マイクを認識しない場合のフォールバック:

Web Speech API方式: ブラウザ側で音声をテキストに変換し、既存の type: 'input' メッセージとしてWebSocket送信。サーバー側変更不要、実装シンプル。

参考リソース

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions