Open16

whisper 調べ

nukopynukopy

Rust でのオーディオ処理

pure rust で書かれたオーディオ入出力のための低レベルライブラリ。

現在、以下の機能をサポートしている:

  • サポートされているオーディオホストの列挙
  • 利用可能なすべてのオーディオデバイスの列挙
  • 現在のデフォルトの入力・出力デバイスの取得
  • デバイスがサポートする既知の入力・出力ストリームフォーマットの列挙
  • デバイスの現在のデフォルト入力・出力ストリームフォーマットの取得
  • 選択したデバイスで、指定されたストリームフォーマットを使用してPCM入力・出力ストリームの構築と実行

https://github.com/RustAudio/cpal

nukopynukopy

PCM とは?

PCM (Pulse Code Modulation: パルス符号変調) について説明します。

PCMは音声をデジタルデータに変換する最も基本的な方式です。例えるなら、以下のような流れです:

  1. マイクで拾った音(アナログ信号)は波形になっています

    • 例:声を出すと、空気が振動して波が発生します
  2. この波形を一定間隔でサンプリング(測定)します

    • 例:1秒間に44,100回測定するのが一般的なCD品質です
    • これを「サンプリングレート: 44.1kHz」と呼びます
  3. 測定した値を数値に変換します

    • 例:波の高さ(振幅)を0〜65535などの範囲の数値で表現
    • この精度を「ビット深度」と呼び、16ビットや24ビットが一般的です

つまり、「PCM入力」とは:

  • マイクなどから入ってきた音声波形を
  • 一定間隔で区切って
  • 数値列に変換すること

実際のプログラムでは、この数値列が配列やバッファとして扱われます:

// 例:16ビットPCMデータの場合
let pcm_data: Vec<i16> = vec![0, 100, 200, 300, -100, -200]; // 音声波形の数値表現

このデータを処理することで、音声認識や録音、エフェクト処理などが可能になります。

nukopynukopy

サンプリング

サンプリングをできるだけ分かりやすく説明しますね!

アナログの音(波形)からデジタルデータへ変換する処理を「サンプリング」と呼びます。

具体例で説明します:

  1. 波形を時間で区切る
   時間 →
波の高さ
   ↑    
    ∿∿∿∿∿  <- これが実際の音の波(アナログ信号)
    ||||   <- この縦線の間隔が「サンプリング間隔」
  1. 各時点での波の高さを記録
波の高さ
   ↑    
    ∿∿∿∿∿  <- 元の波
    ||||
    ●●●●   <- 測定点(サンプル)
  1. 数値化
例:1秒間に44,100回測定した場合
時間: 0.0000秒 -> 値: 52
時間: 0.0227msec -> 値: 127
時間: 0.0454msec -> 値: 89
...

日常生活の例えで説明すると:

  • 1時間ごとの気温を測るのと似ています
  • 1時間という「間隔」で
  • その時点の「温度」を記録する

音声の場合は:

  • 1秒間に44,100回という「間隔」で(CD品質の場合)
  • その時点の「音の大きさ」を記録します

これがサンプリングの基本的な考え方です。このサンプリングされたデータがPCMデータとなり、コンピュータで処理できるようになります。

nukopynukopy

はい、その通りです!とても良い指摘ですね。

サンプリングの本質は「連続的な信号」を「離散的なデータ」に変換することです。

図で表現すると:

連続的な波形(アナログ):
    ∿∿∿∿∿∿  
    滑らかな曲線で、どの時点でも値が存在する

離散的なデータ(デジタル):
    ● ● ● ● ●
    点の集まりになる(飛び飛びの値)

離散化することで:

  • コンピュータで扱えるようになる(数値の配列として処理可能)
  • ただし、元の連続的な波形の情報の一部は失われる
    • 測定点と測定点の間の情報は捨てられる
    • この「情報の欠落」を最小限に抑えるため、十分な頻度でサンプリングする必要がある

これが「サンプリング定理」という重要な概念につながります:

  • 元の信号の最高周波数の2倍以上の頻度でサンプリングする必要がある
  • 例:人間の可聴域(約20kHz)の音を記録するには、少なくとも40kHz以上のサンプリングレートが必要
  • そのため、CDは44.1kHzを採用している
nukopynukopy

音の高さ

はい、音の高さ(周波数)はサンプリングされたデータで表現できます!

これを説明するため、簡単な例を考えてみましょう:

  1. 低い音(低周波)の場合:
時間 →
振幅
↑    
●    ●    ●    ●    ●    ●
    ∿      ∿      ∿      
(ゆっくりとした波の変化)
  1. 高い音(高周波)の場合:
時間 →
振幅
↑    
● ● ● ● ● ● ● ● ● ● ● ●
∿∿∿∿∿∿
(速い波の変化)

サンプリングされたデータ列で見ると:

  • 低い音: [0, 100, 0, -100, 0, 100, ...]
    • 値がゆっくり変化するパターン
  • 高い音: [0, 100, -100, 0, 100, -100, ...]
    • 値が速く変化するパターン

つまり:

  • 音の高さ(周波数)は、データの変化パターンとして表現される
  • 1秒間の値の変化回数が多いほど、高い音として認識される
  • 1秒間の値の変化回数が少ないほど、低い音として認識される

ただし注意点として:

  • サンプリング周波数の半分より高い周波数は正確に記録できない(ナイキスト周波数)
  • そのため、CDの44.1kHzのサンプリングレートは人間の可聴域(約20kHz)をカバーできるように設定されている
nukopynukopy

音の波長と人間の聴覚

音の波長と人間の聴覚の関係について説明しますね。

まず、物理的な仕組みから見ていきましょう:

  1. 音の伝わり方:
  • 空気の分子が押されて波として伝わる
  • 1秒間に波が繰り返される回数が周波数(Hz)
  • 周波数が高い = 波長が短い = より速く振動している
  1. 耳の構造:
  • 音は外耳→中耳→内耳の蝸牛管に伝わる
  • 蝸牛管の中には基底膜という振動を感知する膜がある
  • この基底膜は場所によって異なる周波数に反応する
    • 入り口に近い部分:高周波(短い波長)に反応
    • 奥の部分:低周波(長い波長)に反応

なぜ高周波を「高い音」と感じるのか:

  • 基底膜の振動場所が異なる
  • 脳がその場所の違いを「高さ」として解釈する
  • これは進化の過程で獲得した認識方法

例えば:

  • ピアノの高音:1秒間に約4000回振動(4000Hz)
  • ピアノの低音:1秒間に約30回振動(30Hz)
  • この振動の速さの違いを、脳が「高い・低い」として解釈している

これは人間の聴覚システムの特徴的な性質で、音の物理的な性質(周波数)を「高さ」という感覚に変換しているのです。

nukopynukopy

ビットレートとは?

https://www.adobe.com/jp/creativecloud/video/discover/audio-bitrate.html

サンプルレートとビット深度

サンプルレートとは、1秒間に音声サンプルを何回取得したかを表します。1秒間に録音機器が音をデータに変換する回数です。ほとんどのデジタル音声はサンプルレートが44.1kHzで、これは音声CDのサンプルレートでもあります。これは録音中、音声が1秒間に44,100回、サンプルとして取得されたことを意味します。音声が再生されると、オーディオ機器は音を1秒間に44,100回再構築します。

こうしたサンプルはそれぞれ情報量が異なります。ビット深度とは1つのサンプルに含まれるビット数のことです。つまり、その44,100個の1つの音声サンプルの情報量の多さを示します。

サンプルレートとビット深度が高ければ、音声ファイルの情報量が増え、ファイルのサイズも大きくなります。解像度の高い写真と同様に、音声ファイルもサンプルレートとビット深度の高いファイルにはより多くの情報量を含んでいます。通常、情報量が多ければ多いほど高いビットレートが必要です。

最良のビットレートは存在しない あるのは適切なビットレートだけ

ファイルにとって適切なビットレートは、ファイルを何の目的で使うか、またどのような方法で音声を再生するかによって決まります。サンプルレートとビット深度が高い場合、通常、高いビットレートにより音質も良くなります。つまり、情報量が多いほど音の質が良くなということです。

オーディオ CDのビットレートは常時、1秒間に1,411キロビット(Kbps)です。MP3形式は96から320Kbpsで、Spotifyなどのストリーミングサービスはだいたい96から160Kbpsです。

オーディオファンは高いビットレートを好みますが、それがいつも良いとは限りません。デジタルオーディオ機器が膨大な情報量を受け入れなければならないことを考慮する必要があります。オーディオ形式をオーディオ機器でダウンロードしたり聴いたりする場合、高いビットレートでも対応できます。ストリーミングの場合は、少し低いビットレートの方が効果的にストリーミングできます。しかし、90Kbps以下になると、人間の耳には音質がかなり悪く聴こえます。聴覚のトレーニングをしても無理でしょう。

高性能のハードウェアを使用していない場合であっても、ビットレートと音のフィデリティ(忠実度)が高いかどうかは関係ありません。市販のイヤホンやヘッドフォンを使っている場合は、結局は原音をすべて忠実に再生することはできないからです。CDの高いビットレートの音は、1,411Kbpsの中に含まれている非常に高い音から非常に低い音までを忠実に再生できる高性能ステレオシステムを使った時に、最高の音を再生することができます。ほとんどのイヤホンや多くのデスクトップPCのスピーカーは、そのような周波数を再生することはできません。

nukopynukopy

例えば、モノラルのアナログ信号をサンプルレート 44.1 kHz、ビットレート 16 bit(/ sample)で 1 秒間サンプリングした場合、44100 [回 / second] * 16 [bit / 回] / 8 = 88,200 [byte / second] となり、最低でも、

  • 1 秒間で 88.2 KB (88,200 bit)
  • 1 分間で 5.3 MB (5,292,000 bit)
  • 1 時間で約 317.5 MB (317,520,000 bit)
  • 1 日で 7.6 GB (7,620,480,000 bit)

の容量が必要。ステレオの場合は 2 倍になる。

1 サンプル 16 bit = 2 byte で f32 が使われることが多い?

nukopynukopy

これって生データのサイズだけど実際の音声ファイルはこの通りではないよね?

その通りです。上記の計算はあくまで生のPCMデータ(Raw Data)のサイズであり、実際の音声ファイルはこれとは異なる場合が多いです。

  • ファイル形式の違い:
    WAVのような非圧縮のフォーマットの場合、ヘッダーやメタデータの分だけオーバーヘッドが加わるものの、生データのサイズに非常に近いサイズになります。一方、MP3やAACのような圧縮フォーマットでは、圧縮アルゴリズムにより生データのサイズよりはるかに小さなファイルになります。

  • 圧縮:
    ロスレス圧縮(例: FLAC)であっても、内容によっては多少サイズが小さくなりますし、ロスイーな圧縮の場合はさらに大幅にサイズが削減されます。

つまり、ファイルサイズは使用するフォーマットや圧縮設定に大きく依存するため、単純に生データのサイズと一致するとは限りません。

nukopynukopy

Rubato によるリサンプリング

https://github.com/HEnquist/rubato

whisper への入力はモノラル、サンプルレート 16 kHz、

非同期リサンプリング

非同期リサンプラーは、アンチエイリアシングフィルター付きと無しの両方が利用可能です。

アンチエイリアシング付きのリサンプリングは、sinc 補間フィルターを用いたバンド制限補間に基づいています。sinc 補間では、調整可能な係数でアップサンプリングを行い、その後、新しいサンプル点はそれらの間を補間することで計算されます。リサンプリング比は任意のタイミングで更新可能です。

アンチエイリアシング無しのリサンプリングは、CPU負荷の高い sinc 補間を省略します。これにより処理は格段に高速になりますが、結果として品質は低くなります。

同期リサンプリング

同期リサンプリングは FFT を用いて実装されています。データに対して FFT を行い、スペクトルを修正した後、逆 FFT を適用してリサンプリングされたデータを得ます。このタイプのリサンプラーは非常に高速ですが、リサンプリング比の変更はサポートしていません。

nukopynukopy

エイリアシングとは、連続信号をデジタル化する際に、サンプリング周波数が不十分なために高周波成分が低周波成分として誤認識され、元の信号とは異なる「偽の」成分が現れる現象です。これは、例えばサンプルレートが信号の最高周波数の2倍(ナイキスト周波数)未満の場合に起こります。

エイリアシングとは?

アンチエイリアシングは、このエイリアシングを防ぐための技術です。具体的には、信号をサンプリングする前に低域通過フィルター(アンチエイリアシングフィルター)を用いて、高周波成分(ナイキスト周波数以上の成分)を取り除くことで、誤った低周波成分が混入しないようにします。

まとめると:

  • エイリアシング: 不十分なサンプリングにより、高い周波数成分が低い周波数成分に「折り返される」現象。
  • アンチエイリアシング: サンプリング前に高周波成分を除去することで、エイリアシングを防ぐ処理。
nukopynukopy

Whisper の入力の仕様

Whisper(および whisper‑rs などの実装)は、基本的に以下の入力仕様を前提としている:

  • チャンネル数:1(モノラル)
    • 複数チャンネルの場合は、まずモノラルに変換する必要がある
  • サンプルレート:16 kHz
    • 学習時もこのサンプルレートが使われているため、最適な結果を得るためにはリサンプリングが必要。
  • データフォーマット:通常、入力は正規化された浮動小数点数(例:–1.0 ~ 1.0)で渡されるが、内部で適切な前処理が行われる。

録音データが異なる仕様(例:44.1 kHz、ステレオ)であれば、事前にモノラル変換と16 kHzへのリサンプリングを実施する必要がある。

nukopynukopy

Whisper の context について

WhisperContext は、Whisper モデル本体とその設定(パラメータ)を保持する重いオブジェクトです。

  • 使い回しが推奨される:
    • モデルのロードや初期化は高コストな処理であるため、一度ロードした WhisperContext はアプリケーション全体で再利用するのが一般的です。
  • State の役割:
    • WhisperContext から生成される state は、実際の音声認識処理のための一時的な「作業領域」や状態を持つものです。認識ごとに state を新たに生成して、処理終了後に破棄するのが通常の使い方となります。

つまり、WhisperContext 自体は毎回作り直す必要はなく、一度生成したものを使い回して、その都度新たな state を生成して音声認識を行うのが適切な運用方法です。

nukopynukopy

README 翻訳

Rubato の使い方

このライブラリが提供するリサンプラーは、オーディオをチャンク単位で処理することを想定しています。最適なチャンクサイズはアプリケーションごとに決まりますが、数百フレームから数千フレーム程度になるのが一般的で、効率とメモリ使用量のバランスが取れた設定となります。

ここでの「フレーム」とはオーディオ信号の特定の瞬間のデータの単位を指す。具体的には:

  • モノラルの場合
    • 1 フレーム = 1 サンプル。つまり、ある時点で記録された 1 つの数値。
  • ステレオの場合
    • 1 フレーム = 左右それぞれ 1 サンプルのペア。つまり、ある時点で左右の 2 つの数値がセットになっている。

リアルタイムでの考慮事項

Resampler::process_into_buffer() メソッドを使用する場合、Rubato はリアルタイムアプリケーションに適しています。このメソッドは出力を事前に確保された出力バッファに格納し、スレッドをブロックする可能性のあるアロケーションなどの操作を行いません。

nukopynukopy

音声ストリームを処理する場合、"Reampling a stream" を読むと良さそう。

https://github.com/HEnquist/rubato?tab=readme-ov-file#resampling-a-stream

ストリームのリサンプリング

ストリームのリサンプリングの場合、処理は通常リアルタイムで行われ、入力または出力は、特定のレートでフレームを提供または消費する API によって実現されます。

例:オーディオ API からファイルへの録音

MacOS の CoreAudio やクロスプラットフォームの cpal クレートなどのオーディオ API は、データ交換にコールバック関数を使用することが多いです。

これらの API から音声をキャプチャする際、アプリケーションは関数を API に渡します。API は定期的にこの関数を呼び出し、新たなオーディオフレームを含むデータバッファへのポインタを渡します。データバッファのサイズは通常各呼び出しごとに同じですが、API によっては異なる場合もあります。

重要なのは、このコールバック関数がブロックしないことです。ブロックしてしまうと、API 内部のループが停止し、オーディオデータの一部が失われる恐れがあります。そのため、コールバック関数はできるだけ軽量に保つべきです。理想的には、API から提供されたバッファからオーディオデータを読み出し、必要に応じてサンプルフォーマット変換などの軽い処理を行い、重い処理(リサンプリングなど)は行わずに、データを共有バッファ(例えば Arc<Mutex<VecDeque<T>>> や、より高度な ringbuf など)に保存します。

次に、メインスレッドまたは別スレッドで動作する独立したループが、この共有バッファからデータを読み出し、リサンプリング処理を行い、ファイルに保存するようにします。

オーディオ API が固定サイズのバッファを提供する場合、そのフレーム数がリサンプラーのチャンクサイズとして適しているでしょう。
サイズが変動する場合は、共有バッファを利用して API とリサンプラーのチャンクサイズを調整することができます。

初期値として、オーディオ API の平均チャンクサイズに近い「わかりやすい」値をリサンプラーのチャンクサイズに設定するのが良いでしょう。
また、ディスクアクセス待ちなどでループがブロックされた場合でも、共有バッファが満杯にならないように、十分な容量を確保しておく必要があります。
このループは、クリップをリサンプリングする場合と同様の手順に従いますが、入力は共有バッファとなります。ループは、必要なフレーム数がバッファに蓄積されるまで待機し、その後読み出してリサンプラーに渡します。

なお、一時出力バッファを省略し、出力を直接目的の出力先に書き込む方法も可能です。非圧縮オーディオフォーマットの読み書きには、hound クレートがよく利用されます。