INFO
この記事のパッチは https://github.com/atty303/proton-ge-custom で公開している。
前提
- 使用: GE-Proton10-9
- 検証ゲーム: beatmania IIDX INFINITAS
問題
Proton(Wine+GStreamer)ではWindowsにおけるWMAデコードに比較して、先頭に40msの無音が追加され、末尾の40msが削除されている。beatmaniaはシーケンサーのようにごく短いオーディオを音楽に合わせて再生するので、ディレイが顕著に分かる。
デモ動画
シンセリードがプツプツと途切れて再生されるのが確認できる。
解析
WMAのデコードは以下のフローでコンポーネントを跨いでいく。
- ゲームはMedia Fondation’s MediaSource APIでWMAをデコードする
- Wineはwinegstreamer.dllでMediaFoundationを実装しており、GStreamerを使ってデコードする
- GStreamerはlibavプラグインを使ってデコードする
- FFmpegがWMAのデコードする
FFmpeg
https://github.com/FFmpeg/FFmpeg/commit/19802d170a304f5853d92e01d0513b9e06897d61 このコミットでWMAのギャップレス再生(エンコーダーディレイの補正)に対応している。 GE-Proton10-9が参照しているFFmpegにはこれが入っていないので、まずこれを導入する必要がある。先頭のサンプルを削る処理はFFmpegより後でも可能だが、末尾のサンプルをデコードすることはFFmpegでしかできない。
GStreamer
https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/3117
このMRで(1)のFFmpegのギャップレス対応をホットフィックス的に無効化している。(FFmpegはフラグAV_CODEC_FLAG2_SKIP_MANUAL
が設定されているWMVデコーダーのギャップ情報を無視する) これは最新のGStreamerでも変わっておらず、つまりGStreamerのlibavプラグインはギャップレス再生に対応していないと言える。
通常時のGStreamerが期待する動作
Input : [ Frame1][Frame2][Frame3][EOS]
Output: [lead-in + PCM1][ PCM2][ PCM3]
↑ ↑ ↑
Maintain a one-to-one correspondence
GStreamerは各入力フレームに対応する出力PCMサンプルの正確な対応付けを期待する。
ギャップレス時の動作
Input : [Frame1][Frame2][Frame3][EOS] → キューは空
Output: [ PCM1][ PCM2][ PCM3][DelayFrame]
↑ ↑
Lead-in is removed There is no corresponding input frame
DelayFrameに対応する入力フレームが存在しなくてエラーが発生する。これは原理的に発生しえる状況なので、それを考慮できていないGStreamerのアーキテクチャ上の問題だと私は思う。
解決
DelayFrameに対して最後のFrame3を対応づけてエラーが発生しないようにする。PTSは正しく設定できて私のユースケースでは動作も問題ないが、これがGStreamerとして一般的に許容できる振る舞いであるかは判断できない。
パッチ
patches/ffmpeg-19802d170a304f5853d92e01d0513b9e06897d61.patch
上流ffmpegのn5.0.0に取り込まれているが、GE-Protonは現在n4.xを参照しており、さすがにアップデートしただけではビルドに失敗する。いろいろ依存関係を調整しないといけなさそうでそれは本題ではないので、WMAのパッチだけ適用する。
patches/gstreamer-fix-wma-gapless.patch
- libavプラグインでWMAのギャップレス対応の無効化を削除する。
- コア音声プラグインでドレイン中にフレームが発生することを許容する。
GStreamerのアーキテクチャを無視したワークアラウンドなので、Wine以外で大丈夫な保証がなく、おそらく上流へマージリクエストしても拒否される。