Opusの音量周りがとてもつらい

[Opus](https://www.opus-codec.org/)には音量を調整する機能がコーデックの標準機能として実装されているのですが、それを扱うにあたってつらかったことを書きます。

# 音楽ライブラリの音量を揃えたい

音楽の聴取をする際は、CDのような物理的な媒体を介さず、データを抜き出して1つの聴取装置(スマホ・iPod・PC)にまとめて突っ込んで好きな曲をかいつまんで聴く、というスタイルが普通になりつつあると思います。ここでアルバムを跨いで、特に年代が大きく開いた曲を聴いていると、感じる音の大きさにばらつきがあることに気付くと思います。

昔の音源だと素朴なミキシング・マスタリングにより音が小さすぎ、最近の音源だと音を詰め込んだり潰したりでうるさすぎ([ラウドネス戦争](https://ja.wikipedia.org/wiki/%E3%83%A9%E3%82%A6%E3%83%89%E3%83%8D%E3%82%B9%E3%83%BB%E3%82%A6%E3%82%A9%E3%83%BC))、そんな時に都度ボリュームを上げ下げするのは面倒です。そこで、曲の音の大きさ〜ラウドネス〜を予め測定しておき、再生時にそれに応じて自動的に調整したいという需要が生まれ、それを実現する[ReplayGain](https://en.wikipedia.org/wiki/ReplayGain)という規格がコミュニティによって制定されました。

ところでOpusにはReplayGainに依らない音量調整の仕組みがあり、仕様の一部として組み込まれています。これ単体だけで扱うなら、デコーダ側で対応出来ることもあり便利にも見えるのですが、ReplayGainを採用しているOpus以外の音源と一緒にライブラリに混ぜられているとその仕様の差により大変つらいことになります。

# ReplayGain

音量均一化の仕組みでは一番有名な規格では無いでしょうか。ラウドネスの測定結果をタグとして書き込み、対応プレイヤーにて音量を上げ下げされます。[→仕様](http://wiki.hydrogenaud.io/index.php?title=ReplayGain_2.0_specification)

- タグとして記録されるため、対応していないプレイヤーではスルーされるだけ
- 対応プレイヤーでは、ReplayGainタグを持つ/持たないファイルに対しそれぞれユーザーの希望する追加の音量調整をするUIが提供されるかもしれない
- 対応プレイヤーでもReplayGainの適用は任意である
- ReplayGainの基準ラウドネスは**-18 LUFS**である

という特徴があります。

# Opusの音量調整

Opusの仕様による音量調整は、ヘッダによるものとタグによるものの2段階の過程を経ます。[RFC 7845](https://tools.ietf.org/html/rfc7845)に仕様があります。

## ヘッダの「Output gain」

Opusのヘッダには2バイトの「Output gain」なるフィールドがあり、上下させる音量を1/256 dB単位(Q7.8形式の整数)で書きます。デコーダは出力にこの値を適用すべき(should)であるとされています。また、この値はあらゆる他の音量調整の前に適用されなければなりません(must)。

他のコーデックでは後から音声の音量を変更をする時に再エンコード、もしくはビットストリームを解析して相応の変更を加えるなどの大変な処理が必要でしたが、Opusならヘッダ2バイトとそのコンテナのチェックサム4バイトを書き換えるだけなので高速・確実な音量の管理が出来るようになりました。

この仕組みはなかなか強力で、Xiph謹製の[libopusfile](https://www.opus-codec.org/docs/opusfile_api-0.7/)を使ってOpusファイルをデコードすると、[APIの設定変更](https://www.opus-codec.org/docs/opusfile_api-0.7/group__stream__decoding.html#ga7df927613ccf57996319678e08513289)が無ければOutput gainが**適用された後**のサンプルが出力されます。実質的に、Output gainを適用することは必須の要件で、どのプレイヤーでも動くということが期待できます。

動画投稿サイトでは特に有用であると考えられます。その動画投稿サイトでラウドネス基準を適用させたいとなった場合、音声部分がOpusならば、サイト専用の枠組みで音量を保存し専用プレイヤーで音量を復元するという手間は省かれ、Opus標準の枠組みで解決出来るようになります。

## R128_TRACK_GAIN / R128_ALBUM_GAIN

OpusのタグはVorbis Commentと同じ仕組みで`FIELD=VALUE`の形式で好き勝手に書くことが出来るのですが、音量均一化の仕組みを提供するにあたり新しく見出しにあるタグが定義されています。

`R128_TRACK_GAIN`はReplayGainでいうトラックゲイン、`R128_ALBUM_GAIN`はアルバムゲインに相当するものですが、基準ラウドネスにする音量をOutput gainに対する相対量で、Q7.8形式の整数で指定することが異なります。また、**音量均一化を利用する時の**基準ラウドネスは[EBU R128](https://tech.ebu.ch/loudness)に則り**-23 LUFS**であることとされています。

Opusは以上のような音量調整機構を自前で持っているため、ReplayGainのようなその他の音量調整機構と混ぜて使うことは混乱を来すためすべきではない(should not)としています。

ちなみに、`R128_ALBUM_GAIN`は、[初期の仕様](https://tools.ietf.org/html/draft-terriberry-oggopus-01#page-20)では定義されていませんでした。仕様上では**Output gain単体が参照すべき基準ラウドネスというのは存在しない**(ただ音量を変えるだけ)のですが、音量均一化を利用するとなるとトラックとアルバムの両方を入れることが普通ですから、Output gainに-23 LUFSにするためのアルバムゲインを設定することをほぼ強制されます。

-23 LUFSに揃えるというXiphの姿勢は徹底しており<sup>\[独自研究?]</sup>、ReplayGainタグを持つFLACファイルを[opus-tools](https://packages.debian.org/jessie/opus-tools)の[opusenc(1)](https://mf4.xiph.org/jenkins/view/opus/job/opus-tools/ws/man/opusenc.html)でエンコードすると、[それを読み取ってOutput gainとR128_TRACK_GAINに変換する処理](https://github.com/xiph/opus-tools/blob/dbea7e606341b301daeb401010453cc4764ad321/src/flac.c#L173)があるのですが、それにより音がやたら小さくなります。

# 状況別つらみ

ReplayGain(以下長いのでRG)とOpusの音量均一化(以下長いのでOpusゲイン)で参照する基準ラウドネスが5 dBも違うこと、Opusゲイン用のタグの対応があまり進んでいないことにより、これらを併用するとプレイヤーによって音量が全く変わるという状況が発生しています。

ここでは対応状況を測るプレイヤーとして[foobar2000](http://www.foobar2000.org/)、[VLC](http://www.videolan.org/vlc/)、[GStreamer](https://gstreamer.freedesktop.org/)([Clementine](https://www.clementine-player.org/)などのバックエンド)、[Neutron Music Player](http://neutronmp.com/)(AndroidでOpus再生に対応している数少ないアプリ)を使用しました。

ゲインがどのように適用されるかを説明しやすくするために、とある音声ファイルのラウドネスの測定結果を具体的に挙げます。また、音量均一化のための情報は単曲(トラック)用とアルバム用の両方を入れることにします。

||アルバムゲイン|トラックゲイン|
|:--|:--|:--|
|測定結果|-10.28 LUFS|-9.68 LUFS|
|-18 LUFSオフセット|-7.78 dB|-8.32 dB|
|-23 LUFSオフセット|-12.78 dB|-13.32 dB|

(トラックゲイン) - (アルバムゲイン) = -0.54 dB (Q7.8形式だと-138)

検証用音声ファイルは[こちら](http://js4.in/monooki/turai.tar)。
作品クレジット: [Lovers in metal by Dong (2004)](http://www.16dimensional.com/wp/?p=6) / CC-BY / 検証にあたりコーデックを変更した

## ケース1. Output gainをアルバムゲインとして-23 LUFSに揃える

Opusの初期仕様で想定されていた方法です。ここではOutput gainに-12.78 dBを、`R128_TRACK_GAIN`に-138を指定することになります。

RGとOpusゲインでは基準となるラウドネスが異なるため、どちらかにオフセットを加えて基準値を揃える必要があるのですが、foobar2000ではOpusゲインをRGに合わせるため+5 dBすることでこれを解決しています。具体例だとOutput gainが-12.78 dBになっていたらアルバムゲインを-7.78 dBとして扱うという事です。また、foobar2000でラウドネスのスキャンを行った場合、この形式で書かれます。

VLC・GStreamerではそもそも`R128_TRACK_GAIN`を音量均一化のタグとして認識していないようで、Output gainで下げられた波形がそのまま出力されます。

NeutronはOutput gainで下げられる音量を認識するようですが、均一化するための基準音量を考慮することは無くその値を適用するだけであり、また`R128_TRACK_GAIN`を認識ません。

## ケース2. Output gainを-18 LUFSに、タグで-23 LUFSに揃える

Opusゲインに未対応のプレイヤーでも、アルバムとトラックの違いを選択できなくなるにしても音量均一化させるのを狙った方法です。ここではOutput gainに-7.78 dB、`R128_TRACK_GAIN`に-1418、`R128_ALBUM_GAIN`に-1280を指定することになります(Output gainをアルバムゲインにする場合)。ところがこれはあまり上手くいきません。

<del>foobar2000はv1.3.10の時点で`R128_ALBUM_GAIN`に対応していません。そのため、トラックゲインは意図通り-8.32 dBになるのですがアルバムゲインは5 dB大きい-2.78 dBになります。</del><ins>1.3.11で`R128_ALBUM_GAIN`に対応しました。</ins>

VLC・GStreamerではタグによるゲインに対応していないため-7.78 dBが適用されるだろう……と思いきやそう簡単には行きません。

RGの機能を説明した章にもありますが、RG対応プレイヤーではそのタグを持つ/持たないファイルに対して追加の音量調整をする機能が提供されることがあります。一般的にRGを適用すると音が小さくなる傾向があるので、それを持つファイルに対しては音量を上げ、それ以外では下げるという運用をしている人が多いでしょう。私の場合では、RGを持たないファイルでは-6 dBする設定をしていました。すると「Opusゲインを持つファイルはRGを持たない → 設定によりRGを持たないファイルは-6 dBされる → Output gainと組み合わさって-13.78 dBも音量が下がる」といった事態になります。これは特にケース2だけで起こることではなくケース1でも起こることですが更に音量が下げられることになります。

Neutronではやはりタグによるゲインには対応していないものの、Output gainが何らかの音量調整をするものと認識されていて、VLC・GStreamerのように追加で音量が下げられるという事態は発生しません。

## ケース3. Opus以外の方のRGを-23 LUFSに揃える

これからOpusをメインに据えていきたいが従来のファイルを捨てるわけにも行かないのでOpusに音量を合わせていこうという路線。RGの仕様から外れることになります。

foobar2000はそもそもOpusゲインを+5 dBするのでこの路線は無意味ですね。

GStreamerはこの路線で無難に行けそうに見えますが問題があります。RG自体の仕様にはないのですが、かつてラウドネスの基準が異なっていたことにより、その基準値を`REPLAYGAIN_REFERENCE_LOUDNESS`タグとして入力する習慣が広くあります。そしてGStreamerはこれを認識してラウドネス調整に使おうとします。色々あってRGの基準値の-18 LUFSは`REPLAYGAIN_REFERENCE_LOUDNESS`でいうと89 dBに等しいのですが、この値を-5した84 dBにすると、GStreamerは基準値が5 dB低いことを読み取ってトラック・アルバムゲインもちゃんと+5 dBします。そうなるとOpusの方は相対的に5 dB低くなってしまいます。

この場合でもVLC・Gstreamerはケース2のRGの追加音量の問題にあたってしまいます。

## ケース4. Opusの音量調整機構を使わずRGを書く

Opusの仕様上は良くないのですが、RGは単なるタグですからOpusにも同じように書くことが出来ます。ここではOutput gainを0 dBに、`REPLAYGAIN_ALBUM_GAIN`に-7.78 dB、`REPLAYGAIN_TRACK_GAIN`に-8.32 dBを指定します。

foobar2000ではバージョンによって動作が異なり、1.3.10ぐらいではRGを書いても音量調整が効いたのですが、最近(記事更新時点で1.4.2)ではRGが適用されないように見えます。仕様を尊重させるための措置でしょうか。

他のプレイヤーは意図通りに動きます。しかし、Opusの仕様と相反することをしているわけですから早くOpusゲインに対応して欲しいものです……。

## ケース5. Output gainを-23 LUFSに揃え、ReplayGainで+5 dBする

邪悪ですね。混乱を招くからReplayGainを混ぜるなと仕様にあるというのにそれに真っ向から背く方法です。ここではOutput gainに-12.78 dB、`REPLAYGAIN_ALBUM_GAIN`に5.00 dB、`REPLAYGAIN_TRACK_GAIN`に4.46 dBを指定します。Opusゲインに対応しているプレイヤーでは不正なタグが書かれているとして何が起こっても不思議ではないのですが、未対応のプレイヤーでは意外とうまく行きます。

foobar2000に食わせると、Output gainをすっ飛ばしてRGだけ読みに行く動作になりました。当然クリッピングしまくるため聴くに耐えません。

VLC・GStreamerの場合、Output gainを認識しているわけではなく、ライブラリから出力されたOutput gain適用済みの波形を生データとして扱っているので<sup>\[要出典]</sup>、そこにRGを掛けようが特に問題になることは無いようです。ただこの方法だと下げたサンプルをまた上げるという処理により音が悪い方向に変化するかもしれません。

NeutronはOutput gainを認識し自前でゲインを掛ける仕組みを使っているので(画面上部にOutput gainの値が出る)、なにか不具合出るんじゃないかと不安でしたが、なんと器用に2つの値を組み合わせてトラックゲインなら-7.78 dB、アルバムゲインなら-8.32 dBという値を出してくれました。Neutronすごい。

問題として、`REPLAYGAIN_(TRACK|ALBUM)_PEAK`に何を書いたら良いのかわからんというのがあります。Output gain適用前の値か、それとも後か? `REPLAYGAIN_REFERENCE_LOUDNESS`は89 dB? 84 dB?

まぁケース5はネタとして考えたので使うことは無いでしょうが。

# まとめ

結局どうすればいいか? それは常用するプレイヤーを決めておき、プレイヤーの動作を観察しながら使う音量調整機構を合わせるという場当たりな運用を求められるでしょう。個人用途では使うプレイヤーが多岐にならざるを得ないという状況は避けられるはずです。また、Output gainはReplayGainに対応させる気のないプレイヤーでも音量管理をさせ得る仕組みです。うまくやっていきましょう。

ReplayGain最高!!!!!

# ラウドネスについての記事

* [ダイナミック・レンジとメータの話:Studio Gyokimae](http://pspunch.com/pd/article/dr_meter/)
* [投稿動画のラウドネス音量を考える](https://www.nicovideo.jp/watch/sm26086043)