こんにちは、皆さん。
会話の録音から、誰かが話している部分だけを取り出したいと思ったことはありませんか?
文字起こしの前処理や、長い音声を扱いやすい単位へ分割するとき、無音区間を見つけるだけでも後段の処理を軽くできます。
さて、今日は Silero VAD の ONNX モデルを使い、14 秒ほどの会話音声から発話区間を検出して、区間ごとの WAV ファイルへ切り出します。
今回検証する内容
検証では、2 人の会話を収録した MP3 を FFmpeg で 16 kHz、mono の波形へ変換し、Silero VAD へ 32 ms ずつ入力します。
確認するのは次の点です。
- ONNX Runtime の CPU 実行だけで発話区間を検出できるか
- 14.171 秒の音声から何個の区間が得られるか
- 検出処理にどれくらい時間がかかるか
- 検出した区間を個別の WAV ファイルとして保存できるか
完全なコードと実行環境は、kiarina/labs の silero-vad lab で公開しています。
なお、VAD(Voice Activity Detection)が判定するのは、音声中に発話があるかどうかです。2 人のうち誰が話しているかを識別する話者ダイアライゼーションは、今回の検証には含まれません。
検証環境を準備する
実行には、次のツールが必要です。
次のコマンドで lab だけを取得し、共有テスト音声をダウンロードして実行できます。
git clone --depth 1 --filter=blob:none --sparse \
https://github.com/kiarina/labs.git
cd labs
git sparse-checkout set .gitignore .mise/tasks Makefile mise.toml \
2026/07/03/silero-vad
mise -C 2026/07/03/silero-vad run
初回実行時には、Silero VAD の公式リポジトリから silero_vad.onnx をダウンロードします。その後、uv が Python の依存パッケージを用意し、検出処理を実行します。
モデルのライセンス
今回使用した Silero VAD は、配布元で MIT License と表示されています。利用や再配布を行う場合は、配布元で最新のライセンス条件を確認してください。
どのように発話区間を検出するか
入力には、共有テストアセットの次のファイルを使いました。
tests/assets/mp3/conversation_2speaker_14s_16k.mp3
音声のシナリオは次のとおりです。
Speaker 1: もしもし、もう駅に着いた?
Speaker 2: うん。今、改札出たところ。そっちは?
Speaker 1: こっちはまだ電車。あと5分くらいかな。
Speaker 2: 了解。じゃあ、カフェの前で待ってるね。
Speaker 1: 助かる。今日は結構寒いね。
Speaker 2: ほんと、マフラー持ってきて正解だった。
FFmpeg でデコードした波形を 512 samples、時間にすると 32 ms のチャンクに分け、Silero VAD から各チャンクの発話確率を取得します。
設定は次のとおりです。
| 項目 | 値 |
|---|---|
| sample rate | 16 kHz |
| chunk size | 512 samples(32 ms) |
| speech threshold | 0.5 |
| negative threshold | 0.35 |
| minimum silence | 100 ms |
| minimum speech | 250 ms |
| speech padding | 30 ms |
発話の開始には 0.5、終了候補の判定には 0.35 という異なる閾値を使います。確率が境界付近を揺れたとき、区間が細かく開閉するのを抑えるためです。
発話確率が 0.5 以上になると区間を開始します。その後 0.35 を下回る状態が 100 ms 以上続いた位置を終了とし、250 ms 未満の短い区間は除外します。最後に前後へ 30 ms の余白を加えます。
Silero VAD はチャンクを独立に処理するのではなく、ONNX Runtime の state と直前 64 samples の context を次のチャンクへ引き継ぎます。短い単位で順に入力しながら、前後関係を保つストリーミング向けの処理です。
検出後は FFmpeg で元の MP3 から各区間を切り出し、PCM 16-bit、16 kHz、mono の WAV として output/ に保存します。再実行時には既存の output/ を削除して作り直します。
実行結果
Mac Studio で実行した結果、14.171 秒の入力から 12 個の発話区間が検出されました。
audio duration: 14.171s
VAD elapsed: 0.028s
VAD real-time factor: 0.002x
speech segments: 12
001: 0.162s - 1.726s ( 1.564s)
002: 2.050s - 2.462s ( 0.412s)
003: 2.626s - 3.934s ( 1.308s)
004: 4.130s - 4.638s ( 0.508s)
005: 4.930s - 6.014s ( 1.084s)
006: 6.178s - 7.262s ( 1.084s)
007: 7.458s - 7.998s ( 0.540s)
008: 8.194s - 9.598s ( 1.404s)
009: 9.794s - 10.430s ( 0.636s)
010: 10.530s - 11.806s ( 1.276s)
011: 11.970s - 12.510s ( 0.540s)
012: 12.610s - 14.171s ( 1.561s)
output/ には speech_001.wav から speech_012.wav までの 12 ファイルが生成され、すべて PCM 16-bit、16 kHz、mono であることも確認できました。切り出された区間の合計は約 11.917 秒で、入力音声の約 84.1% です。
各ファイルを聴いて台本と照合すると、次のように分割されていました。
| file | speech |
|---|---|
speech_001.wav |
もしもし、もう駅に着いた? |
speech_002.wav |
うん。 |
speech_003.wav |
今、改札出たところ。 |
speech_004.wav |
そっちは? |
speech_005.wav |
こっちはまだ電車。 |
speech_006.wav |
あと5分くらいかな。 |
speech_007.wav |
了解。 |
speech_008.wav |
じゃあ、カフェの前で待ってるね。 |
speech_009.wav |
助かる。 |
speech_010.wav |
今日は結構寒いね。 |
speech_011.wav |
ほんと、 |
speech_012.wav |
マフラー持ってきて正解だった。 |
句点や疑問符に対応する自然な間で綺麗に区切られ、最後の「ほんと、」だけは、その後の短い間によって独立した区間になりました。「うん」「了解」「助かる」「ほんと」のような短い応答も除外されていません。
実行環境は次のとおりです。
- machine: Mac Studio(Mac16,9)
- chip: Apple M4 Max、16 cores(12 performance + 4 efficiency)
- memory: 128 GB
- OS: macOS 26.5.1(25F80)、arm64
- Python: 3.12.11
- ONNX Runtime: 1.27.0
- execution provider: CPUExecutionProvider
VAD elapsed は、Silero VAD の推論と区間判定だけにかかった時間です。モデルの初期化、FFmpeg によるデコード、WAV の切り出しは含みません。ウォームアップなしの 1 回の測定なので厳密なベンチマークではありませんが、入力の長さに対する real-time factor は 0.002x でした。
結果を考える
今回の条件では、GPU を使わずに 14.171 秒の音声を 0.028 秒で判定できました。録音後の一括処理だけでなく、マイク入力を順に処理する用途にも十分な余裕がある結果です。ただし、マシンが変われば処理時間も変わるため、この数値をそのまま別の環境へ適用はできません。
32 ms ごとの確率を、そのまま個別の発話として扱っているわけではありません。開始と終了で閾値を分け、100 ms の無音を待ち、短すぎる発話を除外し、前後へ余白を加えることで、後段で扱いやすい区間にまとめています。実際のアプリケーションでは、モデルの推論だけでなく、この区間化の条件が結果を大きく左右します。
今回の入力では、台本の句読点と会話中の自然な間に沿って、内容を欠かさず分割できました。250 ms 未満の区間を除外する設定でも、短い相づちを残せた点は実用上嬉しい結果です。
一方、今回の音声には時刻単位の正解ラベルがないため、適合率や再現率は評価していません。台本との聴感上の照合では良好でしたが、あらゆる音声で同じ設定が適切とは限りません。雑音、音楽、ささやき声、長い間、話者同士の重なりなどを含むデータでは、閾値や最小無音時間を調整し、正解ラベルと比較する必要があります。
試してみた感想
Silero VAD は、音声認識の前に置く小さな部品としてかなり扱いやすいと感じました。ONNX モデルは約 2.3 MB で、CPU だけでも十分に速く、処理の中心部分も「チャンクを入力して確率を受け取る」というわかりやすい形です。
同時に、VAD の出力は完成した音声分割ではなく、分割を組み立てるための材料でもあります。短い相づちを残したいのか、文章単位で長めにまとめたいのかによって、100 ms や 250 ms という設定の意味は変わります。
次は雑音を加えた音声や複数の録音環境で検証し、閾値による区間の変化を比較してみたいです。文字起こしや話者ダイアライゼーションと組み合わせたときに、この軽い前処理が全体の速度と精度へどう影響するかも気になります。