Improve loudness normalization (#410)
This commit is contained in:
parent
5878b0ebdd
commit
a43b6f10e3
2 changed files with 24 additions and 26 deletions
|
@ -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,29 +458,29 @@ 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()
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue