コンテンツへ移動

200ms未満のリアルタイムスピーチtoテキスト:アーキテクチャガイド

公開日
最終更新日

聴くこの記事を聴く

リアルタイムのスピーチtoテキスト(STT)は、話している間に音声を逐次文字起こしし、数百ミリ秒以内にテキストとして返します。しかし、STTのレイテンシーを低く保つことは、モデルだけでなくアーキテクチャの課題でもあります。エンジニアは、転送、チャンク分割、エンドポインティング、キャプチャ経路のすべてを設計する必要があり、それぞれがレイテンシーに加わります。どれか1つでも非効率だと、200msの予算を超えてしまいます。

このガイドでは、転送レイヤーからリアルタイムのスピーチtoテキストパイプラインを構築するための実践的な仕組みを紹介します。基準とするのはScribe v2 Realtimeです。モデルレイテンシー約150msで部分文字起こしを生成し、90以上の言語に対応。PCM(8kHz~48kHz)とμ-law音声を受け付け、セグメントを確定するためのVoice Activity Detectionと手動コミット制御を提供します。

音声がどのようにサーバーへ届くか、仮説がどのように確定テキストへ変化するか、ストリーム内機能にどの程度のコストがかかるか、そして音声を正しくキャプチャして転送する方法を見ていきます。

概要

  • リアルタイムのスピーチtoテキストシステムを構築するには、パイプライン全体で低レイテンシーを保つためのアーキテクチャ調整が必要です。
  • ほとんどのパイプラインではWebSocketが適したデフォルトです。WebRTCにはさまざまな利点がありますが、より複雑です。
  • Voice Activity Detectionはハンズフリーのセグメンテーションを処理し、手動コミットにより、ターンの終了をアプリケーションが認識している場合に上書きできます。
  • 部分結果は暫定、最終結果は確定済みです。そのため、それぞれ異なる形で表示する必要があります。 
  • 約100msの小さなPCMチャンクにより、最初の部分結果までのレイテンシーを最小限に抑えられます。

リアルタイムのスピーチtoテキストにおけるWebSocketとWebRTC

文字起こしを行う前に、音声をソースから認識エンジンまで送る必要があります。選択するチャネルによって、その後のすべての処理におけるレイテンシーの下限が決まります。音声を文字起こしレイヤーへ届ける方法には、実用的な選択肢が2つあります。

WebSocketは、TCP上で動作する、長時間維持される順序保証付き・信頼性の高い双方向チャネルです。接続を開き、バイナリ音声フレームを送信し、文字起こしイベントを受信します。クライアント側でもサーバー側でも扱いやすく、すでにHTTPSを許可している企業プロキシやファイアウォールを通過でき、すべてのブラウザとサーバーランタイムでサポートされています。 

WebSocketの制約は、TCP上で動作することです。パケットが失われると、TCPは再送し、欠落分が埋まるまで後続データを待機させます。ネットワーク状態が良ければ問題になりません。しかしパケットロスが発生すると、ヘッドオブラインブロッキングが生じます。これは、音声が一時的に滞留し、その後まとめて到着する短い停止です。

WebRTCはリアルタイムメディア向けに構築されています。UDP(SRTP経由)でメディアを転送するため、パケットが失われてもストリームは停止せず、パイプラインは継続します。パケット到着時刻のばらつきを吸収するジッターバッファー、ルーター配下のピア同士を接続するためのICE/STUN/TURNによるNATトラバーサル、独自の音声キャプチャおよびエンコード機構も備えています。 

直接接続できないクライアントには通常TURNサーバーが必要であり、サーバー側ではバイトストリームを読むのではなく、メディアストリームを終端する必要があります。

主なトレードオフは次のとおりです。

WebSocket
Transport
TCP (reliable, ordered)
Behavior under packet loss
Head-of-line blocking, bursty recovery
Jitter handling
Your responsibility
NAT traversal
Not needed (client-initiated)
Browser support
Universal, trivial
Server complexity
Low
WebRTC
Transport
UDP/SRTP (real-time, loss-tolerant)
Behavior under packet loss
Graceful degradation
Jitter handling
Built-in jitter buffer
NAT traversal
Requires ICE/STUN/TURN
Browser support
Universal, but more API surface
Server complexity
High (media server or SFU)

ほとんどのユースケースでは、WebSocketが適切です。クライアントの接続性が十分で、キャプチャ経路を制御できる場合に使ってください。たとえば、サーバー間パイプライン、デスクトップアプリ、ブロードバンド環境のブラウザアプリ、別の手段で音声がすでにサーバーへ届く大半のコンタクトセンターバックエンドなどです。 

不安定なモバイルネットワーク上の一般消費者向けデバイスから直接キャプチャする場合、双方向音声用のWebRTCスタックをすでに運用している場合(応答音声も返すボイスエージェントなど)、または実装の簡単さより低損失のリアルタイム動作を重視する場合は、WebRTCを選択してください。

このガイドの以降では、認識エンジンへの接続にWebSocket転送を使用します。構成要素を把握しやすく、多くのチームにとって適切な出発点だからです。内容はWebSocket固有ではないため、後から前段にWebRTCメディア経路を追加し、サーバー上で音声をPCMへデコードして、同じチャンクをパイプラインへ転送できます。

部分文字起こしと最終文字起こし:中間結果の解説

リアルタイム認識エンジンは、完全な文になるまで待ってから出力するわけではありません。代わりに、音声が増えるにつれて精度が上がる推測を連続して出力し、その後に確定します。この2つの状態の違いを理解することが、生き生きと感じられる文字起こしと壊れたように感じられる文字起こしを分けます。 

部分(中間)仮説とは、これまでに受信した音声に基づくモデルの最良の推測です。部分結果は設計上、不安定です。音声が追加されると、モデルは前の単語を修正します。たとえば「チケットがほしい」は、後続の文脈によって「チケットを2枚ほしい」に変わることがあります。部分結果は高速に到着し(約150msというレイテンシーはこれを指します)、上書きされることを前提としています。

最終仮説は、以後変わらない確定済みセグメントです。セグメントが確定されると、認識エンジンは次に進み、以降の仮説は後続の音声を対象にします。最終結果は、永続化、LLMへの送信、文字起こしとしての保存に使います。

部分結果と最終結果の違いは、混同すると間違いやすい次の3点に影響します。

  • ユーザー体験: 部分結果を表示すると、文字起こしがライブで進んでいるように感じられます。話している間に単語が表示されるため、マイクが機能し、システムが聞いていることを確認できます。
  • エンドポインティング: 部分結果は、発話アクティビティの継続的なシグナルを提供します。VADと組み合わせれば、話者が実際に話し終えたタイミングを判断できます。
  • 下流処理のタイミング: ボイスエージェントのパイプラインでは、音声入力、スピーチtoテキスト、LLM、テキスト読み上げ、音声出力の順に処理されます。部分結果に基づいて推測的な処理を始め、最終結果で確認すれば、推測的な処理を破棄することがある代わりに、体感応答時間を短縮できます。

部分結果と最終結果は異なる形で表示してください。シンプルで効果的なパターンは、最新の部分結果に紐づく変更可能な「現在の行」を1つ保持し、最終結果が届いたときに追記専用の文字起こしへ確定する方法です。

type TranscriptState = {
  committed: string[]; // finalized segments, never rewritten
  current: string;     // latest partial, overwritten on each update
};

const onPartial = (s: TranscriptState, text: string): TranscriptState =>
  ({ ...s, current: text });

const onFinal = (s: TranscriptState, text: string): TranscriptState =>
  ({ committed: [...s.committed, text], current: "" });

見た目では、確定済みテキストは固定されたスタイルで、現在のテキストは薄い色やイタリック体で表示します。これにより、現在のテキストはまだ変わる可能性があることをユーザーに伝えられます。

エンドポインティングとVoice Activity Detection(VAD)

何が話されたかを知るだけでは十分ではありません。認識エンジンは、発話がいつ終わったかも知る必要があります。この判断によって、セグメントをいつ確定するか、またエージェントではシステムがいつ応答を始めるかが決まります。 

エンドポインティングとは、発話が終わったと判断することです。確定が早すぎると、文の途中でユーザーを遮ってしまいます。遅すぎると、ユーザーが明らかに話し終えているにもかかわらず、エージェントは無言のままになります。

Scribe v2 Realtimeには、補完し合う2つの仕組みがあります。

  • Voice Activity Detectionによる無音ベースの音声セグメンテーション: 認識エンジンは、発話が継続的な無音に移行したことを検出し、その境界で自動的にセグメントを確定します。VADは、手動でタイミングを追跡しなくても自然な発話リズムに適応するため、会話型インターフェースの適切なデフォルトです。
  • 手動コミット制御: 手動コミット制御では、無音とは関係なく、現在のセグメントをいつ確定するかをアプリケーションが決められます。コミットシグナルを送ると、認識エンジンは現在のセグメントを閉じて最終結果を出力します。プッシュ・ツー・トークボタンを離すとき、「送信」操作時、外部のターンテイキングポリシーなど、アプリケーションがすでにターンの終了を把握している場合に適しています。

この2つは相性よく組み合わせられます。一般的なボイスエージェントでは、ハンズフリー操作にはVADを使い、手動コミットを上書き手段として提供します。これにより、考えるために間を置いたユーザーを遮らず、ボタンをタップしたユーザーには即座に境界を設定できます。

無音しきい値には、普遍的に正しい値がない本質的なトレードオフがあります。

  • 短い発話終了タイムアウト(たとえば約200~400msの無音後に確定)は、システムの応答性を高く感じさせます。一方で、節と節の間に自然な間を取るユーザーを遮り、1つの考えを複数のセグメントに分割してしまうことがあります。エージェントでは、早すぎる応答を引き起こす場合もあります。
  • 長いタイムアウト(たとえば約800~1200ms)は自然な間を許容し、発話を途切れずに保てますが、システムが反応するまでに目立つ遅れが生じます。

ここで使えるグローバルな定数はありません。しきい値はインタラクションに合わせて調整してください。

  • ディクテーションやメモ取りでは、ユーザーは文の途中で考えるため、長めの間を許容できます。長いタイムアウトを優先し、VADを活用してください。
  • コマンド&コントロールやトランザクション型エージェントでは、ターンが短く明確なため、短いタイムアウトと手動コミットの組み合わせが効果的です。
  • 多言語話者や非ネイティブ話者は間を置くことが多いため、確定前により長い無音時間を見込んでください。

これらのヒントを活用すれば、効果的なエンドポインティングシステムを構築し、リアルタイムのスピーチtoテキストに近づけます。

ストリーム内機能:言語検出と話者ダイアライゼーション

ストリーミング認識は、単語を生成するだけではありません。ただし、追加で求めるシグナルはすべて、レイテンシーと安定性に影響します。目安として、ライブ体験に必要な機能だけを有効にし、残りはバッチ処理へ回してください。

自動言語認識では、事前に言語を指定しなくても、Scribe v2 Realtimeが対応する90以上の言語から話されている言語を識別できます。ただし、モデルが確信を持って判断するには短時間の音声が必要なため、言語が確定するまでストリームの最初の部分結果は不安定になることがあります。言語がすでに分かっている場合は、指定することで曖昧さをなくし、初期の部分結果をより安定させられます。

話者ダイアライゼーションは、誰が何を話したかを識別し、発話を話者ごとに紐付けます。バッチ文字起こしではモデルがファイル全体を確認できるため、比較的容易です。ストリーミングではより困難になります。認識エンジンは、それまでに得られた音声だけから話者ラベルを割り当てる必要があり、後でその話者の音声をさらに聞くと、初期音声に割り当てたラベルの修正が必要になる場合があります。ストリーミングの話者ラベルも、部分テキストと同じように扱ってください。セグメントが確定するまでは暫定的なものです。

単語レベルのタイミング情報とエンティティコンテキストも同じ考え方です。トークンごとのメタデータを多く求めるほど、モデルと通信経路の両方で運ぶ情報量が増えます。ほとんどのリアルタイムUIでは、ライブで必要なのはテキストとセグメント境界だけです。詳細なメタデータは、Scribe v2を使った通話後のバッチ処理に回せます。

ストリーミング用の音声フォーマット:PCMとμ-law

転送と認識ロジックに注目が集まりがちですが、実際のバグの意外に多くは、その1つ下のレイヤー、つまり音声のエンコード方法とチャンク分割方法に起因します。フォーマットとチャンクサイズを正しく設定することは、最も低コストで実現できるスピーチtoテキストのレイテンシー改善です。

PCM(リニア、16ビット符号付き、リトルエンディアン)は、キャプチャを制御できる場合に使うフォーマットです。サンプルレートが高いほど、より多くの音響的な詳細を含みます。16kHzは音声認識の標準的な下限で、通常は十分です。8kHzは電話品質であり、高周波成分が失われます。ソースに合ったレートを使ってください。8kHzの電話音声を48kHzへアップサンプリングしても、復元すべき情報がないため意味はありません。

8kHzのμ-lawは電話向けフォーマットです。Twilioのようなプロバイダーから通話を取り込む場合、音声は8kHz μ-lawで届くため、二重にトランスコードするのではなく、その形式のまま転送してください。ソースフォーマットに合わせることで、リサンプリングアーティファクトと不要な変換処理を避けられます。

チャンクサイズは、体感レイテンシーを最も直接的に左右する要素です。音声はチャンク単位で送信し、認識エンジンはチャンクの到着に合わせて部分結果を生成します。小さいチャンクでは更新頻度が上がり、最初の部分結果までのレイテンシーが下がります。大きいチャンクではメッセージ数が減り、推論ごとの文脈がやや増えます。実用的な範囲は、1チャンクあたり20~250msの音声です。具体例として、16kHzモノラル16ビットPCMでは1秒の音声は32,000バイトなので、100msチャンクは約3,200バイトです。

ブラウザでマイク入力をキャプチャする

ブラウザでは、Web Audio APIとAudioWorkletを使うのが適切です。ワークレットは音声レンダリングスレッドで動作し、小さなフレームで音声を受け取ります。古いScriptProcessorNodeとは異なり、メインスレッドのジャンクの影響を受けません。その役割は、ブラウザ標準の浮動小数点サンプルを16ビットPCMへ変換し、WebSocket経由で転送するメインスレッドへ渡すことです。

ワークレットプロセッサーの中核となるのは、floatからPCMへの変換です。

// pcm-worklet.ts - registered via audioContext.audioWorklet.addModule()
class PCMWorklet extends AudioWorkletProcessor {
  process(inputs: Float32Array[][]) {
    const channel = inputs[0]?.[0]; // mono; Float32, range [-1, 1]
    if (!channel) return true;
    const pcm = new Int16Array(channel.length);
    for (let i = 0; i < channel.length; i++) {
      const s = Math.max(-1, Math.min(1, channel[i]));
      pcm[i] = s < 0 ? s * 0x8000 : s * 0x7fff;
    }
    // Transfer the buffer to the main thread without copying.
    this.port.postMessage(pcm.buffer, [pcm.buffer]);
    return true;
  }
}
registerProcessor("pcm-worklet", PCMWorklet);

コードで見るパイプライン

パイプラインには3つの構成要素があります。マイクをキャプチャしてPCMをサーバーへストリーミングするブラウザクライアント、音声をScribe v2 Realtimeへ中継して文字起こしを返すNodeサーバー、ファイルまたは電話ブリッジからPCMをストリーミングするスクリプト可能なクライアントです。

認識エンジンをブラウザへ直接公開せず、サーバーで中継する重要な理由が1つあります。ElevenLabs APIキーは秘密情報であり、クライアント側のコードに絶対に含めてはなりません。キーはサーバーで保持します。ブラウザから認識エンジンへ直接接続する必要がある場合は、サーバー側で短期間有効な使い捨てトークンを発行し、APIキーの代わりにクライアントへ渡してください。

ブラウザクライアント

クライアントはサーバーへのWebSocketを開き、上記のワークレットでマイクをキャプチャして、生成されたPCMフレームをそれぞれ転送します。受信イベント(サーバー側で既に{ type, text }へ正規化済み)は、先ほどの部分結果/最終結果の状態を更新します。

// client.ts - runs in the browser. ws is an open WebSocket to your server.
const audioContext = new AudioContext({ sampleRate: 16000 });
await audioContext.audioWorklet.addModule("pcm-worklet.js");

const mediaStream = await navigator.mediaDevices.getUserMedia({
  audio: { channelCount: 1, echoCancellation: true, noiseSuppression: true },
});

const source = audioContext.createMediaStreamSource(mediaStream);
const worklet = new AudioWorkletNode(audioContext, "pcm-worklet");

// Forward each PCM frame to the server the moment it is produced.
worklet.port.onmessage = (e: MessageEvent<ArrayBuffer>) => {
  if (ws.readyState === WebSocket.OPEN) ws.send(e.data);
};
source.connect(worklet);

// Manual commit: tell the server to finalize the current segment.
const commit = () => ws.send(JSON.stringify({ type: "commit" }));

サーバー中継

サーバーはクライアントごとに1つの認識エンジン接続を開き、APIキーをサーバー上に保持し、バイナリPCMをそのまま転送します。また、認識エンジンのイベントを、クライアントが受け取る安定した{ type, text }形式へ正規化します。

// server.ts - Node, using the `ws` library. ELEVENLABS_API_KEY and the
// recognizer URL come from the environment; see the Speech to Text reference
// for the exact path and query parameters.
import { WebSocketServer, WebSocket } from "ws";

new WebSocketServer({ port: 8080 }).on("connection", (client) => {
  // The API key stays on the server, never on the wire to the browser.
  const recognizer = new WebSocket(process.env.RECOGNIZER_WSS_URL!, {
    headers: { "xi-api-key": process.env.ELEVENLABS_API_KEY! },
  });

  // Browser -> recognizer: forward binary PCM, translate control messages.
  client.on("message", (data, isBinary) => {
    if (recognizer.readyState !== WebSocket.OPEN) return;
    if (isBinary) recognizer.send(data); // raw PCM bytes
    else if (JSON.parse(data.toString()).type === "commit")
      recognizer.send(sendCommit());
  });

  // Recognizer -> browser: normalize events into a stable shape.
  recognizer.on("message", (raw) => {
    const event = parseRecognizerEvent(raw.toString());
    if (event && client.readyState === WebSocket.OPEN)
      client.send(JSON.stringify(event));
  });

  // ... open handshake, queueing pre-open audio, and teardown on close/error
});

エンドポイント固有の処理は、以下の2つのアダプター関数に限定されます。フィールド名をスピーチtoテキストのリファレンスに記載された正確な名前へ置き換えてください。パイプラインの残りは変わりません。

// The single place that knows the recognizer's wire format.
const sendCommit = (): string => JSON.stringify({ type: "commit" });

type NormalizedEvent =
  | { type: "partial"; text: string }
  | { type: "final"; text: string }
  | { type: "vad"; speaking: boolean };

function parseRecognizerEvent(raw: string): NormalizedEvent | null {
  const msg = JSON.parse(raw);
  if (msg.is_final === true || msg.type === "final")
    return { type: "final", text: msg.text ?? "" };
  if (msg.type === "vad") return { type: "vad", speaking: !!msg.speaking };
  if (typeof msg.text === "string")
    return { type: "partial", text: msg.text };
  return null;
}

スクリプト可能なバックエンドクライアント

バックエンドパイプラインと以下のベンチマークでは、同じ認識エンジン接続をブラウザなしで使用できます。任意のソースからPCMを読み取り、リアルタイムのチャンク間隔で送信し、イベントを受信します。APIキーとURLは、サーバーと同様に環境変数から取得します。

// stream-stt.ts - pace ~100ms chunks at real time, then commit the tail.
const SAMPLE_RATE = 16000, CHUNK_MS = 100;
const CHUNK_BYTES = (SAMPLE_RATE * 2 * CHUNK_MS) / 1000; // 3200 bytes
const ws = new WebSocket(process.env.RECOGNIZER_WSS_URL!, {
  headers: { "xi-api-key": process.env.ELEVENLABS_API_KEY! },
});

// Send: walk the PCM buffer in 100ms chunks, sleeping between to mimic a
// live source. For audio that already arrives in real time, drop the sleep.
async function sendAudio(pcm: Buffer) {
  for (let off = 0; off < pcm.length; off += CHUNK_BYTES) {
    ws.send(pcm.subarray(off, off + CHUNK_BYTES));
    await new Promise((r) => setTimeout(r, CHUNK_MS));
  }
  ws.send(JSON.stringify({ type: "commit" })); // finalize the trailing segment
}

// Receive: print partials in place, append finals.
ws.on("message", (raw) => {
  const e = parseRecognizerEvent(raw.toString());
  if (e?.type === "final") console.log(`[final]   ${e.text}`);
  else if (e?.type === "partial") process.stdout.write(`[partial] ${e.text}\r`);
});

スピーチtoテキストのレイテンシーと単語誤り率のベンチマーク

レイテンシーと単語誤り率は、話者、言語、音響条件、音声の長さ、各プロバイダーの最寄りリージョンまでのネットワーク経路、各サービスの現在の負荷によって変動します。 

ある都市のノートPCで測定した結果を、別の都市の本番環境全体へ一般化することはできません。本番環境に近いインフラから、実際の入力に近い音声でハーネスを実行し、単一の数値ではなく範囲と分布を報告してください。

重要なのは、本番環境に近いインフラから自分の音声で測定したレイテンシーと精度の数値だけです。ここでは、スピーチtoテキストのレイテンシーをベンチマークするためのガイドを紹介します。

スピーチtoテキストのレイテンシーで測定すべき項目

リアルタイムのスピーチtoテキストのレイテンシーをベンチマークする際に測定すべき主な指標は次のとおりです。

  • 最初の部分結果までの時間: 最初の音声チャンクの送信から、空でない最初の部分結果の受信までの時間です。
  • 部分結果から最終結果までの遅延: 発話の最後の音声チャンクから最終仮説までの時間です。
  • 単語誤り率(WER): 確定した文字起こしと人手による正解データを比較したWERです。すべてのシステムで同じ方法により計算します。
  • 安定性の変動: 確定前に部分結果が何回書き換えられたかを示します。この指標は、ライブUIがどの程度変化して見えるかの代理指標です。

統制条件

信頼性の低いデータを避けるため、実験には一貫性を保つための複数の統制条件を取り入れてください。

スピーチtoテキストのレイテンシーベンチマークで使用する主な統制条件は次のとおりです。

  • 同一の音声: すべてのシステムに、同じファイル、同じサンプルレート、同じエンコードを入力します。
  • 同一の送信間隔: すべてのシステムを同じリアルタイムのチャンク間隔(たとえば100msチャンク)でストリーミングします。
  • 繰り返し実行して分布を報告: 各ファイルを1日のさまざまな時間帯に何度も実行し、中央値とテール値(p50/p95)を報告します。
  • 同一の正解データとスコアリング: WERを計算する前に、テキストを同じ方法で正規化します(大文字・小文字、句読点、数値)。
  • リージョンとネットワークの開示: ハーネスを実行した場所と、各プロバイダーまでの経路を明記します。

これらすべての要素を揃えることで、より正確な指標を得られます。

ハーネスの基本構成

測定の中核ではプロバイダーアダプターを受け取り、最初の部分結果までの時間、確定遅延、部分結果の変動を記録します。

// benchmark.ts - measurement core; one StreamFn adapter per provider.
type StreamFn = (
  audioPath: string,
  onEvent: (kind: "partial" | "final", text: string) => void,
  result: RunResult
) => Promise<void>; // adapter sets result.lastChunkSentAt on the final chunk

interface RunResult {
  firstPartialMs?: number;
  finalLagMs?: number;
  hypothesis: string;
  partialEdits: number;
  lastChunkSentAt: number;
  startedAt: number;
}

async function measure(streamFn: StreamFn, audioPath: string): Promise<RunResult> {
  const result: RunResult = {
    hypothesis: "", partialEdits: 0, lastChunkSentAt: 0,
    startedAt: performance.now(),
  };
  let prevPartial = "";

  await streamFn(audioPath, (kind, text) => {
    const now = performance.now();
    if (kind === "partial") {
      if (text && result.firstPartialMs === undefined)
        result.firstPartialMs = now - result.startedAt;
      if (text !== prevPartial) { result.partialEdits++; prevPartial = text; }
    } else { // final
      result.hypothesis = result.hypothesis ? `${result.hypothesis} ${text}` : text;
      if (result.lastChunkSentAt)
        result.finalLagMs = now - result.lastChunkSentAt;
    }
  }, result);

  return result;
}

単語誤り率は、正規化したテキストに対する標準的なトークンレベルのレーベンシュタイン距離です。計算前に、正解データと仮説の両方を同じ方法で小文字化し、句読点を削除してください。そうしないと、モデルではなく正規化処理を測定することになります。この測定を、プロバイダーごとに各ファイルを約10回実行するループでラップし、最初の部分結果までの時間とWERの中央値(p50/p95)を報告してください。単一のサンプルはネットワークのばらつきに大きく左右されるためです。

実行するには、2つのものを用意します。まず、システムごとにStreamFnアダプターを1つ作成します。上記のスクリプト可能なクライアントはすでにその1つであり、他のアダプターも同じ(audioPath, onEvent, result)契約に従い、最後の音声チャンクを送信したときにresult.lastChunkSentAtを設定します。次に、音声ファイルと正解データを読み込み、それらに対してmeasureを呼び出します。デプロイ環境を代表するマシンから、ユーザーを代表する音声で実行すれば、再現可能な比較結果を得られます。

リアルタイムのスピーチtoテキストを実現する方法のまとめ 

この記事では、システムを段階的に改善し、リアルタイムのスピーチtoテキストへ近づけるための多くのアーキテクチャ変更を取り上げました。

本番環境向けのリアルタイムSTTシステムは、いくつかの判断に集約されます。

  • 転送: 簡単さと制御されたネットワークを重視するならWebSocketを選び、損失への耐性が必要で一般消費者向けデバイスからキャプチャするならWebRTCを選びます。
  • 部分結果と最終結果: 部分結果は暫定、最終結果は確定済みとして扱い、ユーザーがライブテキストを信頼できるように異なる形で表示します。
  • エンドポインティング: ハンズフリーのセグメンテーションにはVADを使用し、手動コミットを上書き手段として用い、固定値ではなくインタラクションに合わせて無音しきい値を調整します。
  • ストリーム内機能: ライブ体験で必要な場合にのみストリーム内機能を有効にし、残りはScribe v2によるバッチ処理に回します。
  • 音声フォーマット: 小さなPCMフレームでキャプチャし、約100msのチャンクで送信し、電話音声ではソースフォーマットに合わせます。
  • ベンチマーク: 自分の音声と目標指標に対して、精度とレイテンシーの調整項目を実測に基づいて設定します。
  • APIセキュリティ: APIキーはサーバーに保持するか、クライアントから直接接続する場合は使い捨てトークンを発行します。

ボイスエージェントのレイテンシーを最適化する方法については、別のガイドもご用意しています。

Scribe v2 Realtimeでリアルタイムのスピーチtoテキストシステムを構築

Scribe v2 Realtimeは、モデルレイテンシー約150msで部分結果を生成します。ユーザーがこの数値を体感できるか、それ以上の遅延を感じるかは、制御可能な周辺アーキテクチャにかかっています。この記事で紹介した戦略を使えば、レイテンシーを削減し、顧客体験を向上させる強化されたパイプラインアーキテクチャを構築できます。 

さらに詳しく知りたい場合は、スピーチtoテキストの機能の概要をご覧ください。全機能と言語の一覧はモデルリファレンスで確認できます。リアルタイムのプロダクトページもご覧ください:リアルタイムスピーチtoテキストAPIリアルタイムスピーチtoテキスト

構築の準備ができたら、無料のElevenLabsアカウントを作成して、今日から最初の文字起こしをストリーミングしましょう。

リアルタイムのスピーチtoテキストのレイテンシーFAQ

関連記事

最高品質のAIオーディオで創造する