コンテンツへ移動

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

公開日
最終更新日

聴くこの記事を聴く

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

このガイドでは、転送レイヤーからリアルタイムスピーチtoテキストのパイプラインを構築するための実践的な方法を紹介します。基準とするのはScribe v2 Realtimeです。約150msのモデルレイテンシーで部分文字起こしを生成し、90以上の言語に対応、PCM(8kHz~48kHz)とmu-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とmu-law

転送と認識ロジックに注目が集まりがちですが、実運用で起こるバグの多くは、その1つ下のレイヤー、つまり音声のエンコード方法とチャンク化方法に起因します。適切な形式とチャンクサイズを選ぶことは、最も手軽に得られるスピーチtoテキストのレイテンシー改善です。

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

8kHzのmu-lawは電話用の形式です。Twilioなどのプロバイダーから通話を取り込む場合、音声は8kHz mu-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オーディオで創造する