Eleven v4を発表これまでで最も感情豊かなモデル、Eleven v4をご紹介します。 10月12日まで、Creator+には3倍のクレジットが含まれます

コンテンツへ移動

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

公開日
最終更新日

聴くこの記事を聴く

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

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

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

概要

  • リアルタイムのスピーチtoテキストシステムを構築するには、パイプライン全体で低レイテンシーを維持するアーキテクチャの微調整が必要です。
  • WebSocketはほとんどのパイプラインに適した標準の選択肢です。WebRTCにはさまざまな利点がありますが、その分複雑になります。
  • 音声アクティビティ検出はハンズフリーのセグメント化を処理し、手動コミットではターンの終了をアプリケーションが把握している場合に上書きできます。
  • 部分結果は暫定的で、最終結果は確定済みです。そのため、表示方法を分ける必要があります。
  • 約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つの状態の違いを理解することで、生きたように感じられる文字起こしと、壊れて見える文字起こしを分けられます。

部分(中間)仮説は、それまでに受信した音声に基づくモデルの最良の推測です。部分結果は設計上、不安定です。音声がさらに到着すると、モデルは先の単語を修正します。たとえば、「I want to」は、後続の文脈で曖昧さが解消されると「I want two tickets」に変わることがあります。部分結果は高速に到着し(約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: "" });

視覚的には、確定済みを通常のテキストで、現在行を薄い色または斜体で表示し、まだ変更される可能性があることをユーザーに伝えます。

エンドポイント検出と音声アクティビティ検出(VAD)

何を話したかを把握するだけでは不十分です。認識器は、発話がいつ終わったかも把握する必要があります。この判断によって、セグメントを確定するタイミングと、エージェントにおけるシステムの応答開始タイミングが決まります。

エンドポイント検出とは、発話が終了したと判断することです。早すぎる確定はユーザーの文の途中を切り、遅すぎる確定はユーザーが明らかに話し終えた後もエージェントを沈黙させます。

Scribe v2 Realtimeには、相互補完的な2つの仕組みがあります。

  • 音声アクティビティ検出による無音ベースの音声セグメント化: 認識器は、発話が継続的な無音へ移行したタイミングを検出し、その境界でセグメントを自動的に確定します。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を使用するのが適切です。workletは音声レンダリングスレッドで実行され、小さなフレームで音声を受け取ります。古いScriptProcessorNodeとは異なり、メインスレッドのジャンクの影響を受けません。その役割は、ブラウザネイティブのfloatサンプルを16ビットPCMに変換し、メインスレッドへ渡すことです。メインスレッドはこれをWebSocket経由で転送します。

workletプロセッサーの中核となるのが、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を開き、上記のworkletを通じてマイクをキャプチャし、生成された各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

執筆者

Tadas Petraは、ElevenLabsのプロダクトスイートを使った開発をサポートするデベロッパーリレーションズエンジニアです。また、デベロッパー向け教育プラットフォーム「Hungrimind」の創設者でもあります。以前はAgoraでリードデベロッパーアドボケイト、Zero To Masteryでインストラクターを務めていました。

関連記事

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