25huizengek1 / ViTune

An Android application for seamless music streaming
https://vitune.app
GNU General Public License v3.0
462 stars 28 forks source link

Normalization compresses songs #444

Open gechoto opened 1 month ago

gechoto commented 1 month ago

Steps to reproduce the bug

  1. Enable Loudness normalization in settings
  2. Listen to a song you know well and which has a high dynamic range

Expected behavior

The dynamics of the song should not be affected by normalization

Actual behavior

Normalization does make the song less dynamic

Screenshots/Screen recordings

No response

Logs

No response

ViTune version

1.0.8

What kind of build are you using?

Release (GitHub / F-Droid)

Android version

Android 14

Device info

-

Upstream reproducibility

idk ViMusic seems dead anyway

Additional information

We noticed the problem over at InnerTune, for more details see this: https://github.com/z-huang/InnerTune/issues/1526

Quick summary: the LoudnessEnhancer you are using is not a great fit for normalization

Checklist

25huizengek1 commented 1 month ago

Thank you for reporting this (not really a bug though).

I've read the conversation you mentioned (https://github.com/z-huang/InnerTune/issues/1526) and I'm afraid ExoPlayer (the player ViTune and Innertune use) doesn't really have a good solution to this as of right now.

LoudnessEnhancer even states this in the docs:

LoudnessEnhancer is an audio effect for increasing audio loudness. The processing is parametrized by a target gain value, which determines the maximum amount by which an audio signal will be amplified; signals amplified outside of the sample range supported by the platform are compressed.

(platform limitations cause compression)

25huizengek1 commented 1 month ago

I noticed that @z-huang decided to revert to the 'old' normalization method, which is by just adjusting the player volume. The problem is that ExoPlayer doesn't support player volume above 1.0, which means that only loud songs can get quieter, but really quiet songs cannot get amplified, on top of the fact that this is not really 'normalization' AFAIK. For now, I'll keep it as it is right now. Maybe (but I'm not an audio expert so I probably can't do this entirely by myself) I could write my own AudioEffect`AudioProcessor`

Edit: All AudioEffects (e.g. LoudnessEnhancer) are implemented by the Android system, which is why I can't implement such AudioEffects myself. The implementation could even be vendor-specific, so I don't even know if every device is facing these issues. I didn't notice any kind of compression on my device, so I'm afraid this is the case.

gechoto commented 1 month ago

not really a bug though

idk the other options were "Feature request" and "Report a security vulnerability" so a bug is the best match I guess

LoudnessEnhancer is an audio effect for increasing audio loudness.

Yeah exactly, to my understanding this is something different than normalization.

[...] signals amplified outside of the sample range supported by the platform are compressed.

(platform limitations cause compression)

Not sure what they mean with the sample range of the platform because it seems to compress even before reaching 0 dB while "the platform" / my device clearly supports samples up to 0 dB

I noticed that @z-huang decided to revert to the 'old' normalization method, which is by just adjusting the player volume.

Yeah this is one way to do normalization. This applies a constant gain to the whole song so it keeps the dynamic range of the song which is correct AFAIK.

Quote from Wikipedia:

Normalization differs from dynamic range compression, which applies varying levels of gain over a recording to fit the level within a minimum and maximum range. Normalization adjusts the gain by a constant value across the entire recording.

The problem is that ExoPlayer doesn't support player volume above 1.0, which means that only loud songs can get quieter, but really quiet songs cannot get amplified

Thanks for pointing that out, yeah, this is really not optimal.

My guess is that this might be less of a problem because most songs these days are mixed very loud so most of the time you only need to lower the volume anyway. Still not perfect, I agree.

Do you know an example song which is very quiet? I would like to test what the original YT Music app does to it. Maybe this can give us some inspiration on how to deal with this.

For now, I'll keep it as it is right now.

In case you keep the LoudnessEnhancer I suggest renaming the option in the UI to better reflect what it does.

gechoto commented 1 month ago

I didn't notice any kind of compression on my device

I tested more and did a comparison of No normalization vs ViTune normalization (at default 5.00 dB base gain) vs InnerTune normalization (version 0.5.10).

All recordings have been level-matched so that they are at the same volume in the region marked in yellow. This makes them easier to compare. Look at the louder part beginning around the middle of the screenshot to see the loss in dynamics.

My guess is that - since ViTune applies less compression - it is maybe so little that you didn't notice. A small bit of compression might be not that obvious to everyone immediately - also depends on the song, the quality of your headphones and how well your ears are trained. But if you listen closely, you should be able to notice it, even at the default setting of 5.00 dB.


I looked for what caused the difference between ViTune and InnerTune. They both use the same base gain of 5 dB, right?

InnerTune: https://github.com/z-huang/InnerTune/blob/ba3a3a0fe9d3499205a7fc91649938091cad75b8/app/src/main/java/com/zionhuang/music/playback/MusicService.kt#L315

ViTune: https://github.com/25huizengek1/ViTune/blob/5d95b3b0eca93e728b9ea737867448595f386cb5/app/src/main/kotlin/app/vitune/android/service/PlayerService.kt#L660

where volumeNormalizationBaseGain has a default of 5: https://github.com/25huizengek1/ViTune/blob/5d95b3b0eca93e728b9ea737867448595f386cb5/app/src/main/kotlin/app/vitune/android/preferences/PlayerPreferences.kt#L20

Yet ViTune has less compression... due to this:

https://github.com/25huizengek1/ViTune/blob/5d95b3b0eca93e728b9ea737867448595f386cb5/providers/innertube/src/main/kotlin/app/vitune/providers/innertube/models/PlayerResponse.kt#L27

ViTune does add 7 dB to the loudness whereas InnerTune does not.

@25huizengek1 can you explain why this was done and how you arrived at the value of 7?


could even be vendor-specific

As for this: idk. I don't have another device around for testing :/ Are you able to test this with your device?


ExoPlayer doesn't support player volume above 1.0, which means that only loud songs can get quieter, but really quiet songs cannot get amplified

Btw. I found a quiet song and just tested it. What you described is exactly how the normalization in the official YT Music app works: It only makes loud songs quieter. If a song is really quiet it will always stay quiet. The original app does not amplify it.

gechoto commented 1 month ago

Another interesting find:

The LoudnessEnhancer does already add compression even without any amplification.

Even with forced target gain of 0:

loudnessEnhancer?.setTargetGain(0)

It does this:

(These recordings are NOT level-matched. These are raw directly how they are played back by the device.)

Not sure if this is the way it is supposed to be. Maybe the LoudnessEnhancer effect in Android is horribly broken. Maybe I just don't understand the docs correctly. Idk. But it looks like this even less suitable for doing normalization as initially thought because it already compresses without adding gain.

gechoto commented 1 month ago

@25huizengek1

problem is that ExoPlayer doesn't support player volume above 1.0, which means that only loud songs can get quieter, but really quiet songs cannot get amplified

Other professionally made apps target a lower overall output volume. This helps with the problem because it leaves enough headroom for quiet songs.

For example you could play loud songs with ExoPlayer volume set to 0.5 and play a very quiet song with ExoPlayer volume set to 1.0 to match their output volume.

FYI: https://github.com/z-huang/InnerTune/issues/1526#issuecomment-2395471736