ポストギャップの切り詰め処理が実行されない
RustAudio/lewton で短い ogg vorbis ファイルをデコードすると、デコード後のサンプル数が一致しないという現象が発生するという issue が立っていました。
Ogg Vorbis decoding fails to truncate final packet in short (single-page) files #106
で、これはどういうときに発生するかというと、オーディオファイルが短すぎて、論理ストリーム全体が ogg コンテナの単一ページに収まってしまう場合に発生するようです。
The issue is that if audio starts and stops in the first OggStreamReader::read_dec_packet_generic() call to return data, the OggStreamReader::cur_absgp (assumed to be initialized in the previous page and incremented per packet?) is None so decoded_pck.truncate never gets called.
極端に短いオーディオファイルを扱う必要があるのは BMS くらいだと思うので、あんまり問題になりにくいのだと思います。それに、末尾に多少の無音があっても大した問題じゃないかもしれないし。
プリギャップの切り詰め
で、さらに言うと Ogg Vorbis にはプリギャップもあるらしい。
Ogg Vorbis, Opusにおけるプリ・ポストギャップ – Aoyumi Notes
OggVorbisはプリギャップ、ポストギャップ量ともにOggコンテナの “granule position” の値で表します。ストリームの最初のページの同値がマイナス値であればそれがプリギャップのサンプル数を表します。またストリームの最後のページの値が総サンプル数を表すため、そこでポストギャップのサンプル数が判明します。
この画像のように、音声ファイルの先頭で 0.002 秒ほどのズレが発生してしまっています。
ただ、ポストギャップは Ogg Vorbis の圧縮アルゴリズム上どうしても発生してしまうことが多いのに対し、プリギャップは無いことも多いみたいなので、ポストギャップと比べると問題になりにくいのかもしれません。(ちゃんと調べたわけではない)
対処法
- 先頭ページのデコード後の PCM サンプル数
- 先頭ページの Granule Position
- 最終ページの Granule Position
これらの値が分かれば、先頭と末尾で切り詰めるサンプル数が分かります。「先頭ページのデコード後の PCM サンプル数」については、先頭ページを read_dec_packet_itl でパケットごとに読み取り、 get_last_absgp が None ではなくなるまでの間、合計することで計算できます。
先ほどの引用にあった「ストリームの最初のページの同値がマイナス値であれば」という説明は多分正確ではなくて、Granule Position はページの終わりのサンプル位置を表すので、「Granule Position から PCM サンプル数を引いた結果がマイナスであれば」と言ったほうが正確かなと思います。
まとめ
ということで、 Rust によるメモリ安全性よりもデコード処理の正確性が重要な場合は、 RustAudio/lewton クレートは現時点ではあまり使用しない方が良いかもしれません。(← いや、プルリクエストを送れよ ← それはそう ← Ogg Vorbis の仕様の全ての分野に精通しているわけではないので……)
自分のプログラムのバグだったらそのときはマジでごめんなさい!!!!!!!!!!
