Closed5

Cartesiaの高速なASRサービス「Ink-Whisper」を試す

kun432kun432

https://x.com/cartesia_ai/status/1932520201183969728

公式ブログ

https://cartesia.ai/blog/introducing-ink-speech-to-text

Claude Sonnet4によるざっくりまとめ

Cartesia「Ink-Whisper」とは?

リアルタイム音声認識の新モデル
Cartesiaが発表した、音声アプリ開発者向けの超高速音声認識(STT)サービス。OpenAIのWhisperを改良し、リアルタイム対話に特化。

なぜ従来のWhisperではダメなのか?

標準Whisperの問題点

  • 30秒の音声チャンクで最適化→会話の短い発話には不向き
  • バルク処理用設計→ライブ対話での遅延が大きい
  • 実環境(雑音、電話音質など)への対応不足

Ink-Whisperの3つの強み

1. 業界最速の応答速度

  • 転写完了時間(TTCT)中央値66ms:話し終わりから転写完了まで
  • 90%のケースで98ms以内(競合は109-829ms)
  • 自然な会話リズムを実現、ボットらしい遅延を解消

2. 実用的な技術改善

  • 動的チャンク処理:固定30秒→意味のある区切りで可変長処理
  • 実環境対応:電話音質、雑音、アクセント、専門用語、言いよどみに強化
  • 無音やギャップでのエラー・幻覚を大幅削減

3. 導入しやすさ

  • 最安価格:1秒1クレジット(時間0.13ドル)
  • 簡単統合:Vapi、Pipecat、LiveKit対応で数分で導入
  • エンタープライズ対応:99.9%稼働率、セキュリティ認証完備

競合との性能比較

項目 Ink-Whisper Deepgram Fireworks Assembly
転写速度(中央値) 66ms 74ms 70ms 737ms
転写速度(P90) 98ms 109ms 189ms 829ms
電話通話精度(WER) 0.19 0.18 0.28 0.23
専門用語精度(WER) 0.065 0.045 0.071 0.044
背景雑音精度(WER) 0.033 0.038 0.099 0.027
言いよどみ精度(WER) 0.064 0.055 0.156 0.137
アクセント精度(WER) 0.015 0.024 0.014 0.016
価格 最安 より高額 より高額 より高額

※WER(単語エラー率)は数値が小さいほど高精度
※太字は最優秀または同等レベル

総合評価:最速の転写速度と競合同等以上の精度を最安価格で実現

こんな用途に最適

  • 音声エージェント:カスタマーサポート、予約システム
  • リアルタイム字幕:会議、配信
  • 音声インターフェース:IoTデバイス、アプリ

Cartesiaの既存TTS「Sonic」と組み合わせることで、完全なリアルタイム音声対話システムを構築可能。

CartesiaのTTSは以前試していて、日本語の発話精度についてはやや不満はあるものの、非常に高速な生成が可能だった。

https://zenn.dev/kun432/scraps/9f0b143b4eae43

ASR側にも同様の高速性が期待できるなら、かなり高速な音声パイプラインが作れるのではないかと期待する。試してみる。

kun432kun432

公式のAPIリファレンス

https://docs.cartesia.ai/2025-04-16/api-reference/stt/stt

サンプルコードは公式のPython SDKのGitHubレポジトリにある。

https://github.com/cartesia-ai/cartesia-python?tab=readme-ov-file#speech-to-text-stt-with-websockets

uvでPython仮想環境を作成

uv init -p 3.12 ink-whisper-work && cd ink-whisper-work

パッケージインストール

uv add cartesia
出力
 + cartesia==2.0.5

APIキーをセット

export CARTESIA_API_KEY=XXXXXXXXXX

まずはファイルからの文字起こし。自分が過去に開催した勉強会のYouTube動画から冒頭5分程度の音声を抜き出したWAVファイルをサンプルとして使う。

https://www.youtube.com/watch?v=Yl2kR6zLRY8

asr_from_file.py
from cartesia import Cartesia
import os

client = Cartesia(api_key=os.getenv("CARTESIA_API_KEY"))

# オーディオファイルをバイトデータで読み込み
with open("voice_lunch_jp_5min.wav", "rb") as f:
    audio_data = f.read()

# オーディオファイルをチャンクに分割
# - ここではストリーミングの例として、20ms分のチャンクに分割。以下のように計算。
#   - サンプルレート: 16kHz、ビット深度: 16ビット
#   - 16000 * 0.02 * 2 = 640 bytes  (2 は 16bit = 2byte を表す)  
#   - ※ ステレオの場合はさらにチャンネル数 2 を掛けて 1280 バイト
chunk_size = 640
audio_chunks = [audio_data[i:i+chunk_size] for i in range(0, len(audio_data), chunk_size)]

# WebSocket接続を確立
ws = client.stt.websocket(
    model="ink-whisper",
    language="ja",           # 音声データの言語に合わせる
    encoding="pcm_s16le",    # 音声データのエンコーディングに合わせる
    sample_rate=16000,       # 音声データのサンプルレートに合わせる
)

# オーディオチャンクを送信
for chunk in audio_chunks:
    ws.send(chunk)

# ストリーム終了を通知
ws.send("finalize")
ws.send("done")

# 音声認識結果を受信
for result in ws.receive():
    if result['type'] == 'transcript':
        print(f"Transcription: {result['text']}")
        if result['is_final']:
            print("Final result received")
    elif result['type'] == 'done':
        break

# 接続をクローズ
ws.close()

実行

 uv run asr.py
出力
Transcription: はいじゃあ始めますちょっとまだ来られてない方
Final result received
Transcription: いらっしゃるんですけど
Final result received
Transcription:
Final result received
Transcription: はい、スタート。
Final result received
Transcription: JP始めます。
Final result received
Transcription: はい
Final result received
Transcription: バイバイ
Final result received
Transcription: はい、日曜日にお集まりいただきましてありがとうございます。
Final result received
Transcription: 今日は久しぶりにですねオフラインということで
Final result received
Transcription: 今日はですねスペシャルなゲストをお二人
Final result received
Transcription: 来ていただいておりますということで
Final result received
Transcription: はい今日ちょっとトピックに回りますけれどもボイスローの仕様である
Final result received
Transcription: レデンリームさんとあとセルフォースの
Final result received
Transcription: カバー・フェジョナルデザインのディレクターである
Final result received
Transcription: ブレック・ベネスさんに来ていただいてます
Final result received
Transcription: ということで
Final result received
Transcription: 日本に来ていただいてありがとうございます
Final result received
Transcription: バイバイ
Final result received
Transcription: 今日はちょっとこのお二人にまた後でいろいろと聞こうという
Final result received
Transcription: コーナーがありますのでそこでまたいろいろ
Final result received
Transcription: 聞きたいと思います
Final result received
Transcription: 今日のアジェンダなんですけどもちょっと時間過ぎちゃいましたがまず最初にボイスランチJP
Final result received
Transcription:
Final result received
Transcription: についてというとあと改造のところですね少し
Final result received
Transcription: ご説明させていただいて1つ目のセッションで
Final result received
Transcription: まず私の方から
Final result received
Transcription: ボイスローの2022年の新機能とかですね
Final result received
Transcription: その辺の話を少しさせていただいてその後
Final result received
Transcription: 2つ目のセッションでグレイデンさんとグルーさんにいろいろ
Final result received
Transcription: かも
Final result received
Transcription: カンバセーショナルデザインですねについて
Final result received
Transcription: 何でも聞こうぜみたいなところを予定しておりますその後
Final result received
Transcription: 15時から15時で一旦終了という形
Final result received
Transcription: させていただいて、ちょっと一応ボイスナッチで確か
Final result received
Transcription: 記念撮影は必須ですよね。なのでそれだけさせていただいて、その後ちょっと
Final result received
Transcription: 1時間ぐらい簡単にお菓子と飲み物を用意してます
Final result received
Transcription: 懇親会というのをそのままさせていただこうと
Final result received
Transcription: 思っています
Final result received
Transcription: で、ボイスランチJPについてなんですけども
Final result received
Transcription: ボイスランチはボイスUIとか音声関連ですね
Final result received
Transcription: そういった技術に実際に携わっている人、もしくは興味がある人。
Final result received
Transcription: 措置のためのグローバルなコミュニティという形になっていて
Final result received
Transcription: ボイスランチの
Final result received
Transcription: 日本人以上という形がボイスランチGPになってますと言って
Final result received
Transcription: オンラインオフラインでいろんな音声
Final result received
Transcription: デザインだったり技術だったりというところで情報とかを共有して
Final result received
Transcription: みんなで業界盛り上げていこうぜというようなことでやっております
Final result received
Transcription: 今日の
Final result received
Transcription: ハッシュタグですね8sharp voice on 8.jpでいろいろ
Final result received
Transcription:
Final result received
Transcription: ケアしてください。
Final result received
Transcription: あと会場ですね今回グラニカ様の
Final result received
Transcription: ご好意で利用させていただいてますありがとうございますぜひ
Final result received
Transcription: こちらもシェアをお願いしたいですと。配信のところも
Final result received
Transcription: はい
Final result received
Transcription: いろいろとやっていただいてますので 非常に感謝しております
Final result received
Transcription: お、見てた。
Final result received
Transcription: 今後
Final result received
Transcription: コロナで会場に来られる方とかもあまりいらないと
Final result received
Transcription: ということでされてないんですけれども
Final result received
Transcription: 通常はここでIoT機器とかガジェットとかを展示されているようなので
Final result received
Transcription: そういったものがあるとき今度ですねまた体験して
Final result received
Transcription: みていただければなと思っていますと
Final result received
Transcription: というところであとすいませんトイレが
Final result received
Transcription: こちらで後たばこ吸われる方はこちらの
Final result received
Transcription: ところになってますのでよろしくお願いします
Final result received
Transcription: はいということで最初の挨拶はこれで
Final result received
Transcription: じゃあまず私の方のセッションから
Final result received
Transcription: させていただきますというところでボイスフローアップデート2022と
Final result received
Transcription: ご視聴ありがとうございました
Final result received
Transcription: というところで、今年の新規の実施設を
Final result received
Transcription: 話をします自己紹介です
Final result received
Transcription: 清水と申します。神戸でインフラのエンジニアをやっています。
Final result received
Transcription: ますので普段はKubernetesとかAWSとかTerra Homeとか
Final result received
Transcription: をいじってまして最近ちょっとフリーランスになります
Final result received
Transcription: ちょっと調べてみたらボイスフロー一番最初に始めたのは2019年
Final result received
Transcription: 1999年の
Final result received
Transcription: 頭ぐらいなんで大体4年弱ぐらいですねいろいろと触って
Final result received
Transcription: まして
Final result received
Transcription: あと音声関連のコミュニティのところではボイスランチ
Final result received
Transcription: JP今回のやつですね以外に
Final result received
Transcription: AJAG Amazon Alexa Japan User Group
Final result received
Transcription: とかあとボイスフローの日本語ユーザーグループ
Final result received
Transcription: ということでVFJUGというのをやっています
Final result received
Transcription: 日本コミュニティの方はFacebookの方でやっています
Final result received
Transcription: ますので、もしよろしければ。
Final result received
Transcription: 見ていただければなと思います
Final result received
Transcription: あと2年ぐらい前にですね 技術書店の方でここに
Final result received
Transcription: 今日スタッフで聞いていただいている
Final result received
Transcription: 皆さんとですね一緒にあの同時にしてくろうぜということで
Final result received
Transcription: 作ったんですけれどももうこれちょっと2年ぐらい経って中身がだいぶ古く
Final result received
Transcription: なってしまっているのですでにちょっと販売は終了しております今日ちょっと持ってきたかったんですけど
Final result received
Transcription: 忘れてしまいました
Final result received
Transcription: はい なのでこういうこともやっています
Final result received
Transcription:
Final result received

うん、多少ハルシネーションはあるが、まあいい感じに思える。

実際のレスポンスをもう少し見てみる。

import json
(snip)

# 音声認識結果を受信
for result in ws.receive():
    print(json.dumps(result, indent=2, ensure_ascii=False))
    #if result['type'] == 'transcript':
    #    print(f"Transcription: {result['text']}")
    #    if result['is_final']:
    #        print("Final result received")
    #elif result['type'] == 'done':
    #    break

ws.close()
出力
{
  "type": "transcript",
  "request_id": "13b4a52c-3433-4fcb-a1fe-ac9c4b4bbfb4",
  "text": "はいじゃあ始めますちょっとまだ来られてない方",
  "is_final": true,
  "duration": 3.256
}
{
  "type": "transcript",
  "request_id": "13b4a52c-3433-4fcb-a1fe-ac9c4b4bbfb4",
  "text": "もいらっしゃるんですけど",
  "is_final": true,
  "duration": 2.4
}
{
  "type": "transcript",
  "request_id": "13b4a52c-3433-4fcb-a1fe-ac9c4b4bbfb4",
  "text": "",
  "is_final": true,
  "duration": 0.292
}
{
  "type": "transcript",
  "request_id": "13b4a52c-3433-4fcb-a1fe-ac9c4b4bbfb4",
  "text": "おいたらしい",
  "is_final": true,
  "duration": 1.1
}
{
  "type": "transcript",
  "request_id": "13b4a52c-3433-4fcb-a1fe-ac9c4b4bbfb4",
  "text": "JP始めます。",
  "is_final": true,
  "duration": 1.328
}
{
  "type": "transcript",
  "request_id": "13b4a52c-3433-4fcb-a1fe-ac9c4b4bbfb4",
  "text": "皆さん見てるの",
  "is_final": true,
  "duration": 1.468
}
{
  "type": "transcript",
  "request_id": "13b4a52c-3433-4fcb-a1fe-ac9c4b4bbfb4",
  "text": "バイバイ",
  "is_final": true,
  "duration": 1.196
}
{
  "type": "transcript",
  "request_id": "13b4a52c-3433-4fcb-a1fe-ac9c4b4bbfb4",
  "text": "はい、日曜日にお集まりいただきましてありがとうございます。",
  "is_final": true,
  "duration": 3.328
}
{
  "type": "transcript",
  "request_id": "13b4a52c-3433-4fcb-a1fe-ac9c4b4bbfb4",
  "text": "今日は久しぶりにですねオフラインということで",
  "is_final": true,
  "duration": 3.184
}
{
  "type": "transcript",
  "request_id": "13b4a52c-3433-4fcb-a1fe-ac9c4b4bbfb4",
  "text": "今日はですねスペシャルなゲストをお二人",
  "is_final": true,
  "duration": 3.016
}
{
  "type": "transcript",
  "request_id": "13b4a52c-3433-4fcb-a1fe-ac9c4b4bbfb4",
  "text": "来ていただいておりますということで",
  "is_final": true,
  "duration": 2.752
}
{
  "type": "transcript",
  "request_id": "13b4a52c-3433-4fcb-a1fe-ac9c4b4bbfb4",
  "text": "はい今日ちょっとトピックに回りますけれどもボイスローの仕様である",
  "is_final": true,
  "duration": 4.184
}
{
  "type": "transcript",
  "request_id": "13b4a52c-3433-4fcb-a1fe-ac9c4b4bbfb4",
  "text": "レデンリームさんとあとセルフォースの",
  "is_final": true,
  "duration": 3.168
}
{
  "type": "transcript",
  "request_id": "13b4a52c-3433-4fcb-a1fe-ac9c4b4bbfb4",
  "text": "カバー・フェジョーラルデザインのディレクターである",
  "is_final": true,
  "duration": 2.828
}
{
  "type": "transcript",
  "request_id": "13b4a52c-3433-4fcb-a1fe-ac9c4b4bbfb4",
  "text": "ブレック・ベネスさんに来ていただいてます",
  "is_final": true,
  "duration": 2.668
}
{
  "type": "transcript",
  "request_id": "13b4a52c-3433-4fcb-a1fe-ac9c4b4bbfb4",
  "text": "ということで",
  "is_final": true,
  "duration": 0.976
}
{
  "type": "transcript",
  "request_id": "13b4a52c-3433-4fcb-a1fe-ac9c4b4bbfb4",
  "text": "日本に来ていただいてありがとうございます",
  "is_final": true,
  "duration": 1.92
}
{
  "type": "transcript",
  "request_id": "13b4a52c-3433-4fcb-a1fe-ac9c4b4bbfb4",
  "text": "ご視聴ありがとうございました",
  "is_final": true,
  "duration": 0.0965625
}
(snip)
{
  "type": "flush_done",
  "request_id": "13b4a52c-3433-4fcb-a1fe-ac9c4b4bbfb4"
}
{
  "type": "done",
  "request_id": "13b4a52c-3433-4fcb-a1fe-ac9c4b4bbfb4"
}

Google STTのようなinterimは飛んでこないのね。となると、発話の終了はVADとかで別に検出する必要がありそう。

あと、改めて見てて気づいたけど、句読点がちょっと少なめかなぁ。ただ認識している単位は文節に近しいと思うので、まあそこで区切ればいいかという印象。

kun432kun432

マイクからのリアルタイムな文字起こしを試してみる。

高速性を確認するために、音声の発話終了〜認識結果の受領のタイミングを知りたいということで、WebRTCVADを使う。ただし、これはあくまでも発話の開始・終了をログに記録するためだけで、Cartesiaへの送信は常時行うこととする。

諸々パッケージ追加

uv add pyaudio loguru webrtcvad setuptools

スクリプト。o3と相談して書いてもらった。

import os
import threading
import queue
import signal
import sys

import pyaudio
import webrtcvad
from cartesia import Cartesia
from loguru import logger

"""リアルタイム音声入力(マイク)→ STT WebSocket 文字起こしサンプル

- マイク入力: 16 kHz / 16-bit / 1ch (PCM S16LE)
- 20 ms (= 320 サンプル = 640 byte) 単位で WebSocket へ送信
- Ctrl-C で録音を終了すると `finalize` → `done` を送り, 残りの結果を受信して終了
"""

RATE = 16_000
CHANNELS = 1
SAMPLE_FORMAT = pyaudio.paInt16  # 16-bit PCM
FRAME_DURATION_SEC = 0.02  # 20ms
FRAMES_PER_BUFFER = int(RATE * FRAME_DURATION_SEC)  # 320
CHUNK_BYTES = FRAMES_PER_BUFFER * 2  # 640 bytes (16-bit = 2 byte)

# WebRTC VAD 設定
#   aggressiveness: 0(ゆるい)〜3(厳しい)  
#   end_silence_frames: 発話終了判定に必要な連続無音フレーム数
VAD_MODE = 3  # 厳し目に判定
END_SILENCE_FRAMES = 1  # 1フレーム(20ms)無音で終了判定

VAD = webrtcvad.Vad(VAD_MODE)

audio_queue: "queue.Queue[bytes]" = queue.Queue()
stop_event = threading.Event()


def record_audio() -> None:
    """マイクから音声を読み取り, バイト列をキューに入れる"""
    pa = pyaudio.PyAudio()
    stream = pa.open(
        format=SAMPLE_FORMAT,
        channels=CHANNELS,
        rate=RATE,
        input=True,
        frames_per_buffer=FRAMES_PER_BUFFER,
    )
    logger.info("録音開始(Ctrl+C で終了)")

    speech_active = False
    non_speech_count = 0  # 連続無音フレーム数

    try:
        while not stop_event.is_set():
            data = stream.read(FRAMES_PER_BUFFER, exception_on_overflow=False)
            audio_queue.put(data)  # WebSocket へ送るためにキューへ

            # VAD 判定 (20ms 固定フレーム)
            try:
                is_speech = VAD.is_speech(data, RATE)
            except Exception:
                # 例外が起きたら speech=False 扱い
                is_speech = False

            if is_speech:
                non_speech_count = 0
                if not speech_active:
                    speech_active = True
                    logger.info("💬 発話開始")
            else:
                if speech_active:
                    non_speech_count += 1
                    # 指定フレーム連続無音で終了判定
                    if non_speech_count >= END_SILENCE_FRAMES:
                        speech_active = False
                        logger.info("🤐 発話終了")
                        non_speech_count = 0

    finally:
        stream.stop_stream()
        stream.close()
        pa.terminate()
        logger.info("録音終了")


def send_audio(ws) -> None:
    """キューから取り出した音声を WebSocket へ送信"""
    while not (stop_event.is_set() and audio_queue.empty()):
        try:
            chunk = audio_queue.get(timeout=0.1)
        except queue.Empty:
            continue
        # 音声チャンク送信
        ws.send(chunk)

    # 入力終了をサーバへ通知
    ws.send("finalize")
    ws.send("done")


def receive_results(ws) -> None:
    """WebSocket から結果を受信して表示"""
    for result in ws.receive():
        if result["type"] == "transcript" and result.get("is_final", False):
            logger.info(f"final: {result['text']}")
        elif result["type"] == "done":
            break


def main() -> None:
    api_key = os.getenv("CARTESIA_API_KEY")
    if not api_key:
        sys.exit("環境変数 CARTESIA_API_KEY が設定されていません")

    logger.info("WebSocket 接続準備中…")
    client = Cartesia(api_key=api_key)
    ws = client.stt.websocket(
        model="ink-whisper",
        language="ja",
        encoding="pcm_s16le",
        sample_rate=RATE,
    )

    # 録音スレッド開始
    rec_thread = threading.Thread(target=record_audio, daemon=True)
    rec_thread.start()

    # 送信・受信をメインスレッドで実行
    try:
        send_thread = threading.Thread(target=send_audio, args=(ws,), daemon=True)
        recv_thread = threading.Thread(target=receive_results, args=(ws,), daemon=True)
        send_thread.start()
        recv_thread.start()

        # Ctrl+C 待ち
        signal.pause()
    except KeyboardInterrupt:
        stop_event.set()
    finally:
        send_thread.join()
        recv_thread.join()
        ws.close()
        logger.info("音声認識終了")


if __name__ == "__main__":
    main()

実行するとこんな感じ。

uv run asr_from_mic.py
出力
2025-06-11 22:15:12.624 | INFO     | __main__:main:115 - WebSocket 接続準備中…
2025-06-11 22:15:13.449 | INFO     | __main__:record_audio:48 - 録音開始(Ctrl+C で終了)
2025-06-11 22:15:14.877 | INFO     | __main__:record_audio:69 - 💬 発話開始
2025-06-11 22:15:16.176 | INFO     | __main__:record_audio:76 - 🤐 発話終了
2025-06-11 22:15:16.256 | INFO     | __main__:record_audio:69 - 💬 発話開始
2025-06-11 22:15:16.580 | INFO     | __main__:record_audio:76 - 🤐 発話終了
2025-06-11 22:15:17.220 | INFO     | __main__:receive_results:105 - final: 音声認識のテストです。
2025-06-11 22:15:18.857 | INFO     | __main__:record_audio:69 - 💬 発話開始
2025-06-11 22:15:19.414 | INFO     | __main__:record_audio:76 - 🤐 発話終了
2025-06-11 22:15:19.638 | INFO     | __main__:record_audio:69 - 💬 発話開始
2025-06-11 22:15:21.637 | INFO     | __main__:record_audio:76 - 🤐 発話終了
2025-06-11 22:15:21.699 | INFO     | __main__:record_audio:69 - 💬 発話開始
2025-06-11 22:15:21.840 | INFO     | __main__:receive_results:105 - final: 今日も新幹線をご利用くださいました。
2025-06-11 22:15:21.856 | INFO     | __main__:record_audio:76 - 🤐 発話終了
2025-06-11 22:15:22.114 | INFO     | __main__:record_audio:69 - 💬 発話開始
2025-06-11 22:15:22.267 | INFO     | __main__:receive_results:105 - final: ご視聴ありがとうございました
2025-06-11 22:15:23.235 | INFO     | __main__:record_audio:76 - 🤐 発話終了
2025-06-11 22:15:23.808 | INFO     | __main__:receive_results:105 - final: ありがとうございます。
2025-06-11 22:15:24.979 | INFO     | __main__:record_audio:69 - 💬 発話開始
2025-06-11 22:15:26.535 | INFO     | __main__:record_audio:76 - 🤐 発話終了
2025-06-11 22:15:26.599 | INFO     | __main__:record_audio:69 - 💬 発話開始
2025-06-11 22:15:27.520 | INFO     | __main__:record_audio:76 - 🤐 発話終了
2025-06-11 22:15:27.883 | INFO     | __main__:receive_results:105 - final: この電車はのぞみ号東京行きです。
2025-06-11 22:15:29.100 | INFO     | __main__:record_audio:69 - 💬 発話開始
2025-06-11 22:15:29.275 | INFO     | __main__:record_audio:76 - 🤐 発話終了
2025-06-11 22:15:29.298 | INFO     | __main__:record_audio:69 - 💬 発話開始
2025-06-11 22:15:30.435 | INFO     | __main__:record_audio:76 - 🤐 発話終了
2025-06-11 22:15:30.805 | INFO     | __main__:receive_results:105 - final: 途中の停車駅は、
2025-06-11 22:15:30.818 | INFO     | __main__:record_audio:69 - 💬 発話開始
2025-06-11 22:15:31.239 | INFO     | __main__:record_audio:76 - 🤐 発話終了
2025-06-11 22:15:31.257 | INFO     | __main__:record_audio:69 - 💬 発話開始
2025-06-11 22:15:31.396 | INFO     | __main__:record_audio:76 - 🤐 発話終了
2025-06-11 22:15:31.818 | INFO     | __main__:record_audio:69 - 💬 発話開始
2025-06-11 22:15:31.836 | INFO     | __main__:receive_results:105 - final: 京都
2025-06-11 22:15:32.399 | INFO     | __main__:record_audio:76 - 🤐 発話終了
2025-06-11 22:15:32.793 | INFO     | __main__:receive_results:105 - final: なごや
2025-06-11 22:15:32.797 | INFO     | __main__:record_audio:69 - 💬 発話開始
2025-06-11 22:15:33.715 | INFO     | __main__:record_audio:76 - 🤐 発話終了
2025-06-11 22:15:33.998 | INFO     | __main__:record_audio:69 - 💬 発話開始
2025-06-11 22:15:34.714 | INFO     | __main__:record_audio:76 - 🤐 発話終了
2025-06-11 22:15:35.282 | INFO     | __main__:receive_results:105 - final: 新横浜、品川です。
2025-06-11 22:15:36.056 | INFO     | __main__:record_audio:69 - 💬 発話開始
2025-06-11 22:15:37.197 | INFO     | __main__:record_audio:76 - 🤐 発話終了
2025-06-11 22:15:37.361 | INFO     | __main__:record_audio:69 - 💬 発話開始
2025-06-11 22:15:38.075 | INFO     | __main__:record_audio:76 - 🤐 発話終了
2025-06-11 22:15:38.117 | INFO     | __main__:record_audio:69 - 💬 発話開始
2025-06-11 22:15:38.574 | INFO     | __main__:record_audio:76 - 🤐 発話終了
2025-06-11 22:15:39.074 | INFO     | __main__:receive_results:105 - final: この電車は全席禁煙となっております。
2025-06-11 22:15:39.676 | INFO     | __main__:record_audio:69 - 💬 発話開始
2025-06-11 22:15:41.135 | INFO     | __main__:record_audio:76 - 🤐 発話終了
2025-06-11 22:15:41.177 | INFO     | __main__:record_audio:69 - 💬 発話開始
2025-06-11 22:15:41.676 | INFO     | __main__:record_audio:76 - 🤐 発話終了
2025-06-11 22:15:41.780 | INFO     | __main__:record_audio:69 - 💬 発話開始
2025-06-11 22:15:41.877 | INFO     | __main__:record_audio:76 - 🤐 発話終了
2025-06-11 22:15:41.955 | INFO     | __main__:record_audio:69 - 💬 発話開始
2025-06-11 22:15:43.637 | INFO     | __main__:record_audio:76 - 🤐 発話終了
2025-06-11 22:15:43.825 | INFO     | __main__:receive_results:105 - final: お煙草を吸われるお客様は喫煙ルームをご利用ください。
2025-06-11 22:15:45.898 | INFO     | __main__:record_audio:69 - 💬 発話開始
2025-06-11 22:15:45.997 | INFO     | __main__:record_audio:76 - 🤐 発話終了
^C2025-06-11 22:15:47.037 | INFO     | __main__:record_audio:83 - 録音終了
2025-06-11 22:15:47.137 | INFO     | __main__:receive_results:105 - final: ご視聴ありがとうございました
2025-06-11 22:15:47.200 | INFO     | __main__:main:143 - 音声認識終了

https://youtu.be/0GeCTPTYECw

WebRTCVADの判定が難しいのだけど、遅くとも500ms以内には返ってきているように見えるし、体感的には過去に試した他のストリーミングASRよりもクイックに認識結果が返ってきているように感じた。

参考までに過去試したストリーミングASRの動画

Google STT

https://youtu.be/NEtnVVTj_Dc

Amazon Transcribe

https://youtu.be/1q-IlgjZj2E

kun432kun432

ログにたまに流れるこれ。

出力
2025-06-11 22:15:47.137 | INFO     | __main__:receive_results:105 - final: ご視聴ありがとうございました

これ、Whisperで小さな音を拾ったときによく起きるなやつだよね。なるほど、Whisperの亜種であることが改めてよく分かる。

以下の記事にあるようなフィルタリングをしたほうがいいかもしれない

https://dev.classmethod.jp/articles/openai-whisper-large-v3-turbo-realtime/

kun432kun432

まとめ

謳い文句まではいかなかったけど、低遅延に定評のあるCartesiaらしいASRと感じた。精度も悪くなさそうだし、これならASR・LLM・TTSのパイプラインを高速化できそう。とりあえずLiveKitやPipecatあたりでASRもTTSもCartesiaにして試してみたいかな。

このスクラップは3ヶ月前にクローズされました