Closed spvkgn closed 4 years ago
deadbeef is literally using opusenc from command line. my only guess is that maybe replaygain got applied during conversion, which is not supposed to happen, but wrong code might have sneaked in by accident.
I just checked this theory, and no - replaygain not applied.
In my case this is not a theory - I have this with any file, even with generated sine wave:
$ ffmpeg -loglevel error -f lavfi -i "sine=frequency=1000:duration=30" -filter_complex "[0:a][0:a]amerge=inputs=2[aout]" -map "[aout]" output.wav
$ bs1770gain --replaygain output.wav
scanning 1
analyzing ...
[1/1] output.wav
integrated (momentary mean): -18.06 dBFS / 0.06 dB
$ deadbeef --queue output.wav
starting deadbeef devel
...
Encoding using libopus 1.3.1 (audio)
-----------------------------------------------------
Input: 44.1kHz 2 channels
Output: 2 channels (2 coupled)
20ms packets, 128kbit/sec VBR
Preskip: 312
Encoding complete
-----------------------------------------------------
Encoded: 30.02 seconds
Runtime: 1e-06 seconds
(3.002e+07x realtime)
Wrote: 736734 bytes, 1501 packets, 33 pages
Bitrate: 194.889kbit/s (without overhead)
Instant rates: 168.4kbit/s to 253.6kbit/s
(421 to 634 bytes per packet)
Overhead: 0.735% (container+metadata)
…
hej-hej!
$ opusinfo output.opus | grep gain
Playback gain: -5 dB
$ bs1770gain --replaygain output.opus
scanning 1
analyzing ...
[1/1] output.opus
integrated (momentary mean): -23.04 dBFS / 5.04 dB
Well, if it does not depend on deadbeef , I close this.
It's weird that you're getting different results between opusenc as used by deadbeef, and opusenc used outside of deadbeef. Command lines are identical, and Playback gain is not something deadbeef is putting in there -- it's an opusenc feature.
I've just found the cause and it seems that it depends on vorbis comments written with tagger. If encode without tag writing, then it writes OPUS_HEADER_GAIN
with 5 dB - gain is 0 dB as it should be. Otherwise it writes REPLAYGAIN_ALBUMGAIN
and OPUS_HEADER_GAIN
with 0 dB both (even if source not contains REPLAYGAIN tags), in which case gain is -5 dB:
$ opusinfo output.0dB.opus | grep gain
Playback gain: 0 dB
$ bs1770gain --replaygain output.0dB.opus
scanning 1
analyzing ...
[1/1] output.0dB.opus
integrated (momentary mean): -18.04 dBFS / 0.04 dB
$ opusinfo output.-5dB.opus | grep gain
Playback gain: -5 dB
$ bs1770gain --replaygain output.-5dB.opus
scanning 1
analyzing ...
[1/1] output.-5dB.opus
integrated (momentary mean): -23.04 dBFS / 5.04 dB
If I understand correctly opus header should not contain REPLAYGAIN_* comments to avoid confusion - they replaced with EBU-R128 standard as described here https://tools.ietf.org/html/draft-ietf-codec-oggopus-06#section-5.2.1
Otherwise it writes REPLAYGAIN_ALBUMGAIN and OPUS_HEADER_GAIN with 0 dB both (even if source not contains REPLAYGAIN tags), in which case gain is -5 dB:
This looks like a bug, and I think this is easy enough to fix. Meanwhile you can use a workaround, which is to delete the bogus replaygain tag from the files.
If I understand correctly opus header should not contain REPLAYGAIN_* comments
I don't think there's anybody on earth who fully understands opus header gain, and how to make this stuff work universally well across various implementations.
I'll go out on a limb and claim I understand opus header gain. As to making it work universally well across all implementations, just not possible. Opus reinvented the wheel with their own approach to replaygain - you either use it or you get into a big mess. If you use it, you can still get in a mess because so many implementations don't use it exactly as specified.
Amongst other things, the Opus spec says not to use the REPLAYGAIN tags that are almost universally recognised by music players, and instead to use R128GAIN tags specific to Opus. In addition it defines the infamous "output gain" field in its header which is not a tag as such, but without really saying when it should be used or what it should be used for. The one thing it does say, extremely strongly, is that every decoder must apply the header gain to any decoded audio, which is impossible given that most music players include an option to ignore any replay gain values. Checkmate. Hence, many music players still add the REPLAYGAIN* tags to Opus files, and many still read them. Whether this works as intended is a bit random depending on whether the encoder and decoder make the same assumptions.
@Lithopsian well this is kind of how I understand it as well. Thanks for the detailed write up :)
Now, the bug. Deadbeef runs opus enc and then it may update the resulting file with tags. If it doesn't update the file then any gain tags created by opusenc will be left. opusenc parses any flac replaygain values, converts to the R128 equivalent, and writes them to the encoded file. I believe that it still writes the album gain into the header gain field and writes the difference between that and the track gain into an R128_TRACK_GAIN tag. As you can imagine, this is not very helpful in the context of Deadbeef.
If Deadbeef is configured to write tags after encoding, then the internal Deadbeef Opus plugin writes an R128_TRACK_GAIN tag containing the track gain value, and R128_ALBUM_GAIN tag containing the album gain value, and a header gain field containing the track gain less 5. That last piece just looks flat wrong to me. I don't know if it is intended to be a conversion from the internal replaygain values (normalised to 89dB, ironically converted from an actual R128 value derived in the replaygain scanner plugin) to the R128 values, or if it is a holdover from something that used to put the album gain into the header. Either way, not right. Probably it should just write zero into the header and write what it really means into the R128 tags.
According to spec, R128_TRACK_GAIN and R128_ALBUM_GAIN are gain values added on top of the header gain. From how I interpret it, it seems like if you omit R128_ALBUM_GAIN, then it assumes the value in the header alone is enough for it, while R128_TRACK_GAIN is applied on top of that for track gain. This doesn't allow for "no gain" (unless you ignore the header gain but I think that's out-of-spec) but because for Opus gain can be applied during decoding, the concept of "no gain" doesn't really have any advantages for playback IMO.
The (Opus encapsulation) specification states that replaygain applied by decoders MUST (their caps, not mine) be calculated by adding the R128_TRACK_GAIN or R128_ALBUM_GAIN to the output gain from the header. That is all you can know for sure. You can't make any assumptions about how much gain of what type is in the header or in either of the two tags, only that the sum of either tag present and the header gain field is the corresponding replaygain value. Earlier versions of the spec did not include the R128_ALBUM_GAIN tag and stated the assumption that when needed it would simply be placed in the header gain, leaving no possibility of knowing whether some or all of the header gain was in fact an album gain, or a track gain, or something else entirely. The R128_ALBUM_GAIN tag was defined specifically to allow the assumption (although not the guarantee, it can still be done the "old" way) that the value it contains is an album replaygain and that anything in the header constitutes some other form of gain. Even getting that to happen was like pulling teeth: Opus was envisaged as "always just working" without having to interpret or misinterpret mysterious metadata, with the header gain allowing for always-applied gain control. The idea that different decoders might want to decode at different levels was not part of the plan.
Deadbeef, in common with many "high-end" music players, has options to play back audio with either track or album replaygain applied, or not. You can argue all day about whether you should want to do not apply replaygain or even whether it has any meaning, the preferences exist so we should try to support them in some form. However Opus makes it near-impossible to determine what the playback level should be "without replaygain", because there is no way to know whether some or all of any replaygain is in the header. Assumptions must be made.
The specification states that the output gain in the header SHOULD be applied by all decoders. Although decoders could ignore this, it is a bad idea since it could contain large gain values unrelated to replaygain. If the assumptions made by the decoder do not match the assumptions made by the encoder then strangeness may happen. Note that the spec also states that the output gain SHOULD be set to zero when audio is encoded, so one possible assumption is that replaygain is only contained in the R128_TRACK_GAIN or R128_ALBUM_GAIN tags, and that anything in the header is something else although that starts to look dubious when the R128_ALBUM_GAIN tag is missing. Helpful practice by encoders is to write both R128_TRACK_GAIN and R128_ALBUM_GAIN tags even if they are zero, to give players a clue that replaygain has been calculated and found to be zero (again no guarantees, there might still be replaygain in the header, however meaningless that distinction might be).
As purely an implementation detail, Deadbeef runs into difficulties if we allow the Opus decoder library to automatically calculate and apply replaygain, or even if we just give it simple instructions like "use album gain". When I first wrote the third-party Opus plugin, the streamer could not easily be persuaded not to apply its own replaygain in line with user preferences although I think this is now better supported. Also, peak capping and preamp settings cannot easily be controlled if replaygain has already been applied to the decoded audio before the streamer gets it (Opus does not officially support any peak tags, but Deadbeef assumes full scale or peak=1 in this case), and there would be several seconds delay for any new playback preferences to be applied to the audio. So just at the code level we really need to decode "without replaygain" and then work it all out ourselves.
It seems like a chicken-egg problem.
There are the following things I came up with, which can help with the problem:
I think that the main confusion of this issue comes from the fact that opusinfo itself doesn't support the replaygain format used in deadbeef. I'm fairly sure that both deadbeef and foobar2k will be able to handle each other's RG tags successfully. I have tested this when developing RG support for opus plugin, but will try to re-test ASAP.
I just verified with foobar, and both tag writing and reading works identical to deadbeef. Closing since this is technically not a bug, but intended behavior. If anybody is interested in getting a "opus spec conformant replaygain support" -- please post another issue.
I'm building a brand new clean copy of Deadbeef and I'll look into this some more. I thought I had a good version, but I'm getting some errors trying to write the replaygain data, so the results are confusing.
@Alexey-Yakovenko
I just verified with foobar, and both tag writing and reading works identical to deadbeef.
I don't see this behavior with foobar2k (just tried under wine) - foobar doesn't write bogus REPLAYGAIN_ALBUMGAIN as deadbeef does.
I'd better attach zip file with samples: test sine wav file and both resulting opus files - from foobar and from deadbeef. Everyone can make sure that these opus files are not identical in gain. output.zip
When writing the last comment and closing, I confused myself, and thought that we're talking about RG-scanner. The issue is about how "default opus header" is written though. It shouldn't contain any RG info, therefore the issue is still valid.
In the Deadbeef track properties dialog, the REPLAYGAIN_TRACKGAIN value shown is the sum of the header gain value any R128_TRACK_GAIN tag, adjusted by 5dB (approximately from -23 LUFS to 89dB loudness), but it is only shown if there is a replaygain tag. The OPUS_HEADER_GAIN is always shown, whether there is an R128_TRACK_GAIN tag or not, adjusted by 5dB. If the header gain value is zero, then "5.00 dB" is shown for OPUS_HEADER_GAIN in the dialog, although this is not used for anything. REPLAYGAIN_ALBUMGAIN is shown when the sum of any R128_ALBUM_GAIN tag and the header gain is other than zero (before being adjusted by 5dB), even if no R128_ALBUM_GAIN exists. This produces some odd results when there is supposedly no replaygain data, for example after removing replaygain information using the Deadbeef command REPLAYGAIN_ALBUMGAIN still appears as 0.00 dB.
If the track gain metadata key exists internally then an R128_TRACK_GAIN tag is written with the value zero, otherwise no tag is written. The gain value written to the header is the track gain minus 5. This is all intentional so far (I think), although I'd suggest not the best solution (putting replaygain in tags seems like a better idea, so players can see what is replaygain and what isn't). However, if the track gain key does not exist (ie. no replaygain data internally) then the header gain is still set to -5dB, which I think is the issue being described? In such cases, no gain tags will be written, the track info dialog will show OPUS_HEADER_GAIN of 0.00 dB (-5dB adjusted by 5dB), no REPLAYGAIN_TRACKGAIN value, but a REPLAYGAIN_ALBUMGAIN value of 0.00 dB. Playback "with gain" will be correct (I think?), but playback without gain ("none" option) will not be at the original volume because the header gain is now -5dB.
Confused yet? I am.
Steps to reproduce the problem
Encode flac to opus using deadbeef converter and opusenc from command line.
What's going on? Describe the problem in as much detail as possible.
When encoding any flac to opus using deadbeef converter, it encodes with -5 dB gain:
When encoding it using opusenc directly, this not occurs:
I guess, encoding using converter should have same result as using opusenc from command line.
Information about the software:
Deadbeef version: devel OS: Ubuntu 18.04 opus-tools 0.1.10 (using libopus 1.3.1)