Improve loudness normalization (#410)

This commit is contained in:
vfsfitvnm 2022-10-27 18:28:00 +02:00
parent 5878b0ebdd
commit a43b6f10e3
2 changed files with 24 additions and 26 deletions

View file

@ -21,6 +21,7 @@ import android.media.AudioManager
import android.media.MediaDescription import android.media.MediaDescription
import android.media.MediaMetadata import android.media.MediaMetadata
import android.media.audiofx.AudioEffect import android.media.audiofx.AudioEffect
import android.media.audiofx.LoudnessEnhancer
import android.media.session.MediaSession import android.media.session.MediaSession
import android.media.session.PlaybackState import android.media.session.PlaybackState
import android.net.Uri import android.net.Uri
@ -103,6 +104,7 @@ import it.vfsfitvnm.vimusic.utils.resumePlaybackWhenDeviceConnectedKey
import it.vfsfitvnm.vimusic.utils.shouldBePlaying import it.vfsfitvnm.vimusic.utils.shouldBePlaying
import it.vfsfitvnm.vimusic.utils.skipSilenceKey import it.vfsfitvnm.vimusic.utils.skipSilenceKey
import it.vfsfitvnm.vimusic.utils.timer import it.vfsfitvnm.vimusic.utils.timer
import it.vfsfitvnm.vimusic.utils.toast
import it.vfsfitvnm.vimusic.utils.trackLoopEnabledKey import it.vfsfitvnm.vimusic.utils.trackLoopEnabledKey
import it.vfsfitvnm.vimusic.utils.volumeNormalizationKey import it.vfsfitvnm.vimusic.utils.volumeNormalizationKey
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -112,9 +114,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.cancellable import kotlinx.coroutines.flow.cancellable
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.plus import kotlinx.coroutines.plus
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@ -150,7 +150,6 @@ class PlayerService : InvincibleService(), Player.Listener, PlaybackStatsListene
private var volumeNormalizationJob: Job? = null private var volumeNormalizationJob: Job? = null
private var isVolumeNormalizationEnabled = false
private var isPersistentQueueEnabled = false private var isPersistentQueueEnabled = false
private var isShowingThumbnailInLockscreen = true private var isShowingThumbnailInLockscreen = true
override var isInvincibilityEnabled = false override var isInvincibilityEnabled = false
@ -158,6 +157,8 @@ class PlayerService : InvincibleService(), Player.Listener, PlaybackStatsListene
private var audioManager: AudioManager? = null private var audioManager: AudioManager? = null
private var audioDeviceCallback: AudioDeviceCallback? = null private var audioDeviceCallback: AudioDeviceCallback? = null
private var loudnessEnhancer: LoudnessEnhancer? = null
private val binder = Binder() private val binder = Binder()
private var isNotificationStarted = false private var isNotificationStarted = false
@ -188,7 +189,6 @@ class PlayerService : InvincibleService(), Player.Listener, PlaybackStatsListene
val preferences = preferences val preferences = preferences
isPersistentQueueEnabled = preferences.getBoolean(persistentQueueKey, false) isPersistentQueueEnabled = preferences.getBoolean(persistentQueueKey, false)
isVolumeNormalizationEnabled = preferences.getBoolean(volumeNormalizationKey, false)
isInvincibilityEnabled = preferences.getBoolean(isInvincibilityEnabledKey, false) isInvincibilityEnabled = preferences.getBoolean(isInvincibilityEnabledKey, false)
isShowingThumbnailInLockscreen = isShowingThumbnailInLockscreen =
preferences.getBoolean(isShowingThumbnailInLockscreenKey, false) preferences.getBoolean(isShowingThumbnailInLockscreenKey, false)
@ -284,6 +284,8 @@ class PlayerService : InvincibleService(), Player.Listener, PlaybackStatsListene
mediaSession.release() mediaSession.release()
cache.release() cache.release()
loudnessEnhancer?.release()
super.onDestroy() super.onDestroy()
} }
@ -456,30 +458,30 @@ class PlayerService : InvincibleService(), Player.Listener, PlaybackStatsListene
} }
private fun maybeNormalizeVolume() { private fun maybeNormalizeVolume() {
if (!isVolumeNormalizationEnabled) { if (!preferences.getBoolean(volumeNormalizationKey, false)) {
loudnessEnhancer?.enabled = false
loudnessEnhancer?.release()
loudnessEnhancer = null
volumeNormalizationJob?.cancel() volumeNormalizationJob?.cancel()
player.volume = 1f player.volume = 1f
return return
} }
if (loudnessEnhancer == null) {
loudnessEnhancer = LoudnessEnhancer(player.audioSessionId)
}
player.currentMediaItem?.mediaId?.let { songId -> player.currentMediaItem?.mediaId?.let { songId ->
volumeNormalizationJob?.cancel() volumeNormalizationJob?.cancel()
volumeNormalizationJob = coroutineScope.launch(Dispatchers.Main) { volumeNormalizationJob = coroutineScope.launch(Dispatchers.Main) {
Database Database.loudnessDb(songId).cancellable().collectLatest { loudnessDb ->
.loudnessDb(songId) try {
.cancellable() loudnessEnhancer?.setTargetGain(-((loudnessDb ?: 0f) * 100).toInt() + 500)
.distinctUntilChanged() loudnessEnhancer?.enabled = true
.filterNotNull() } catch (_: Exception) {
.flowOn(Dispatchers.IO) toast("Couldn't normalize volume!")
.collect { loudnessDb ->
val x = loudnessDb.coerceIn(-10f, 10f)
val x2 = x * x
val x3 = x2 * x
val x4 = x2 * x2
player.volume =
0.0000452661f * x4 - 0.0000870966f * x3 - 0.00251095f * x2 - 0.0336928f * x + 0.427456f
} }
}
} }
} }
} }
@ -622,11 +624,7 @@ class PlayerService : InvincibleService(), Player.Listener, PlaybackStatsListene
persistentQueueKey -> isPersistentQueueEnabled = persistentQueueKey -> isPersistentQueueEnabled =
sharedPreferences.getBoolean(key, isPersistentQueueEnabled) sharedPreferences.getBoolean(key, isPersistentQueueEnabled)
volumeNormalizationKey -> { volumeNormalizationKey -> maybeNormalizeVolume()
isVolumeNormalizationEnabled =
sharedPreferences.getBoolean(key, isVolumeNormalizationEnabled)
maybeNormalizeVolume()
}
resumePlaybackWhenDeviceConnectedKey -> maybeResumePlaybackWhenDeviceConnected() resumePlaybackWhenDeviceConnectedKey -> maybeResumePlaybackWhenDeviceConnected()

View file

@ -100,7 +100,7 @@ fun PlayerSettings() {
SwitchSettingEntry( SwitchSettingEntry(
title = "Loudness normalization", title = "Loudness normalization",
text = "Lower the volume to a standard level", text = "Adjust the volume to a fixed level",
isChecked = volumeNormalization, isChecked = volumeNormalization,
onCheckedChange = { onCheckedChange = {
volumeNormalization = it volumeNormalization = it