HMplayer 音声データフロー公開仕様

HMplayerでは、音声ファイルをMedia Foundationでデコードし、そのデコード済みPCMデータを基点として「ローカル再生」と「ストリーミング配信」の2つの経路に分岐させています。

再生と配信で同じ音源を使用しますが、出力先と必要な処理が異なるため、分岐後はそれぞれ独立した処理を行います。

1. 音声ファイルの読み込みとデコード

MP3、FLAC、M4Aなどの音声ファイルは、Windows Media FoundationのSource Readerを使用して読み込みます。

圧縮された音声データはMedia Foundationによってデコードされ、WASAPIで再生可能なPCMデータに変換されます。

この段階のPCM形式は、HMplayerが固定値として決めているものではなく、使用しているWindowsオーディオデバイスのMix Formatを基準とします。

例えば環境によっては、以下のような形式になります。

ここで得られたデコード済みPCMが、HMplayer内部における再生処理と配信処理の共通の入力になります。

2. ローカル再生

デコード済みPCMは再生用バッファへ保持され、WASAPI Shared Modeを使用してWindows Audio Engineへ渡されます。

概略としては次の流れです。

Media Foundation → PCM → WASAPI → Windows Audio Engine → オーディオデバイス

HMplayerのメイン画面にあるVolumeは、WASAPI側のISimpleAudioVolumeを使用して制御しています。

そのため、プレイヤーのVolumeはローカル再生音量だけに作用し、後述するストリーミング用のStreaming Gainには影響しません。

逆にStreaming Gainを変更しても、PCのスピーカーやヘッドホンから再生される音量は変化しません。

3. ストリーミング処理への分岐

デコード済みPCMは、必要に応じてストリーミング処理側にも渡されます。

ストリーミングが接続中または接続処理中の場合、デコードされたPCMはStreamingPcmConverterへ送られます。

HMplayerでは配信側の音声形式を固定しており、Audio Resampler MFTを使用して次の形式へ正規化します。

この正規化処理を設けることで、元ファイルやWindows側の再生フォーマットが異なっていても、その後のStreaming GainやMP3エンコード処理では常に同じ形式の音声を扱うことができます。

したがって、Audio Resampler MFTは単にサンプリング周波数を変換するだけでなく、再生環境と配信処理の間を分離する「配信用PCM正規化層」として機能しています。

4. Streaming Gain

正規化されたFloat32 PCMに対して、Streaming Gainを適用します。

Streaming GainはPCMサンプル値そのものに対する増幅・減衰処理であり、ローカル再生用のVolumeとは完全に独立しています。

例えば+6 dBを設定した場合、PCMの振幅はおよそ2倍になります。

Gain適用後に値がFloat PCMの有効範囲を超えた場合は、そのままPCM16へ変換せず、-1.0から+1.0の範囲へClampします。

その後、MP3エンコーダへ渡すために44100 Hz / Stereo / signed 16-bit PCMへ変換します。

5. Level Meter

Level Meterは常時すべてのPCMを解析しているわけではなく、必要な場合だけレベル測定を行います。

また、非配信時と配信時では測定位置が異なります。

非配信時

ストリーミングを行っていない場合、Level Meter Windowが表示されているときだけデコード済みPCMのレベルを測定します。

このとき測定対象となるのはStreaming Gain適用前のPCMです。

したがって非配信時のLevel Meterは、実質的に音源そのもののデジタルレベルを確認するためのメーターとして動作します。

Level Meter Windowが非表示の場合は、このレベル計算自体を行いません。

配信時

ストリーミング中は、Audio Resampler MFTによる正規化とStreaming Gainの適用後にレベルを測定します。

測定位置はClamp処理の前です。

そのため、配信時のLevel Meterは「配信へ送ろうとしているGain適用後の信号」を監視しています。

この構造により、Streaming Gainによってレベルがどの程度変化したかをLevel Meter上で確認できます。

ローカル再生用Volumeはこの測定値には影響しません。

6. Level Meterの表示処理

左右チャンネルのピーク値は音声処理側で計算し、UI側から安全に参照できる値として保持します。

Level Meter Windowは約33 ms周期でその値を取得し、線形PCMレベルをdBFSへ変換して表示します。

つまり、音声処理そのものを33 ms単位で行っているわけではありません。

音声処理は再生処理の流れの中で継続的に行われ、33 msという周期はあくまでLevel Meterの画面更新周期です。

これにより、UI描画処理が直接オーディオ処理のタイミングを支配しない構造になっています。

7. PCMからMP3へのエンコード

Streaming GainおよびClamp処理を終えた音声は、44100 Hz / Stereo / PCM16へ変換されます。

このPCM16データはStreamingRuntime::PushPcmを通じて、現在Onlineになっている各配信接続へ渡されます。

各接続はそれぞれ独立したMP3 Encoderを保持しています。

MP3エンコードにはlame_enc.dllのBladeEnc互換APIを使用しており、PCM16データをMP3 byte streamへ変換します。

この時点を境に、データはPCM波形ではなくMP3圧縮データになります。

そのため、Streaming GainやLevel Meterなどの波形処理はすべてMP3エンコードより前で行われます。

8. 配信Queueと送信Thread

エンコードされたMP3データは、そのままネットワークへ直接送信するのではなく、接続ごとのQueueへ格納されます。

各接続には独立した送信Threadがあり、QueueからMP3データを取り出してlibshoutへ渡します。

概略は次の通りです。

PCM16 → MP3 Encoder → MP3 Queue → Send Thread → libshout → Icecast

Queueを挟むことで、音声生成処理とネットワーク送信処理を分離しています。

ネットワーク通信は一時的な待ち時間や送信速度の変動が発生する可能性があるため、再生・エンコード処理と直接同期させるよりも、このようにQueueと専用Threadを使用して分離する方が安定した構造になります。

9. 複数配信先

HMplayerでは最大3接続まで同時配信できます。

PCM16データまでは共通の配信音声として生成されますが、その後は接続ごとに独立したMP3 Encoder、Queue、Send Threadを持ちます。

そのため概念的には次の構造です。

共通PCM → 各Connection Slot → 個別MP3 Encoder → 個別Queue → 個別Send Thread

これにより、ある配信先への送信処理が他の接続のエンコード状態やQueueを直接共有しない構造になっています。

10. 配信メタデータ

音声データとは別に、現在再生している楽曲のTitle、Artist、Albumなどの情報をストリーミングメタデータとして処理します。

曲の切り替え時にはTagLibから取得したNowPlayingInfoを基に配信用文字列を生成し、設定された文字コードへ変換します。

通常の曲変更ではMetadata Delayの設定に従って送信時刻を調整します。

Delay時間は単純な実時間ではなく、Playing状態で進行した時間を基準として加算されます。

一方、新しいIcecast接続がOnlineになった場合は例外として、現在再生中の曲情報をDelayなしでその接続へ同期します。

最終的なメタデータ更新は送信Thread側でlibshoutを通してIcecastへ送られます。

全体像

HMplayerの音声処理を大きく整理すると、次の構造になります。

音声ファイルMedia FoundationによるデコードPCM

ここから、

PCM → WASAPI → ローカル再生

と、

PCM → 配信用フォーマットへ正規化 → Streaming Gain → Level Meter → PCM16 → MP3 Encode → Queue → Send Thread → Icecast

の2系統へ分かれます。

重要なのは、ローカル再生とストリーミングが同じデコード済みPCMを起点としながら、分岐後は独立した処理系になっている点です。

またLevel Meterも単純にスピーカー出力を測定しているのではなく、非配信時には元のデコード音声、配信時にはStreaming Gain適用後の配信信号を測定する設計になっています。

以下のデータフロー図では、この処理を実際のクラス名・関数名・Thread・Queue・メタデータ処理まで含めて、より詳細に示します。

HMplayerの音声データフロー図。ローカル再生とIcecast配信に分岐する処理を示す
HMplayer 音声データフロー図(クリックすると画像のみを表示します)