Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 65 additions & 12 deletions app/src/main/kotlin/com/metrolist/music/playback/MusicService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -406,10 +406,20 @@ class MusicService :
@Volatile
private var loudnessLevelCached: LoudnessLevel = LoudnessLevel.BALANCED

@Volatile
private var cachedNormalizationGainMb: Int? = null

@Volatile
private var cachedNormalizationEnabled: Boolean = false

@Volatile private var discordRpcEnabled = false
@Volatile
private var cachedMeasuredLufs: Double? = null

@Volatile
private var cachedMeasuredLufsMediaId: String? = null

@Volatile
private var discordRpcEnabled = false
private val screenOffHandler = Handler(Looper.getMainLooper())
private val screenOffTimeout = Runnable {
Timber.tag("DiscordSvc").i("screenOffTimeout: isPlaying=%s, isReady=%s",
Expand Down Expand Up @@ -2113,6 +2123,42 @@ class MusicService :
)
}

private fun applyNormalizationFromLoudnessData(
mediaId: String,
loudnessDb: Double?,
perceptualLoudnessDb: Double?,
) {
Handler(Looper.getMainLooper()).post {
val measuredLufs = perceptualLoudnessDb
?: loudnessDb?.let { it + LoudnessLevel.AGGRESSIVE.targetLufs }

cachedMeasuredLufs = measuredLufs
cachedMeasuredLufsMediaId = mediaId

if (!normalizationEnabledCached) {
playerNormalizationProcessors.values.forEach { it.enabled = false }
return@post
}

val targetLufs = loudnessLevelCached.targetLufs
if (measuredLufs != null) {
val loudnessDelta = measuredLufs - targetLufs
val targetGain = (-loudnessDelta * 100.0).toInt()
val clampedGain = targetGain.coerceIn(MIN_GAIN_MB, MAX_GAIN_MB)

cachedNormalizationGainMb = clampedGain
cachedNormalizationEnabled = true

if (isCrossfading) return@post

playerNormalizationProcessors.values.forEach {
it.setTargetGain(clampedGain)
it.enabled = true
}
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

private fun applyCachedAudioNormalizationNow() {
if (isCrossfading) return
try {
Expand Down Expand Up @@ -2144,16 +2190,21 @@ class MusicService :
val normalizeAudio = normalizationEnabledCached

if (normalizeAudio && currentMediaId != null) {
val format = withContext(Dispatchers.IO) {
database.format(currentMediaId).first()
}

val targetLufs = loudnessLevelCached.targetLufs

Timber.tag(TAG).d("Audio normalization enabled: $normalizeAudio")

val measuredLufs: Double? = format?.perceptualLoudnessDb
?: format?.loudnessDb?.let { it + LoudnessLevel.AGGRESSIVE.targetLufs }
var isFormatNull = false
val measuredLufs: Double? = if (cachedMeasuredLufsMediaId == currentMediaId) {
cachedMeasuredLufs
} else {
null
} ?: run {
val format = withContext(Dispatchers.IO) {
database.format(currentMediaId).first()
}
isFormatNull = format == null
format?.perceptualLoudnessDb
?: format?.loudnessDb?.let { it + LoudnessLevel.AGGRESSIVE.targetLufs }
}

withContext(Dispatchers.Main) {
if (!isActive || requestGeneration != loudnessSetupGeneration) return@withContext
Expand All @@ -2162,8 +2213,8 @@ class MusicService :

when {
measuredLufs != null -> {
val loudnessDb = measuredLufs - targetLufs
val targetGain = (-loudnessDb * 100.0).toInt()
val loudnessDelta = measuredLufs - targetLufs
val targetGain = (-loudnessDelta * 100.0).toInt()
val clampedGain = targetGain.coerceIn(MIN_GAIN_MB, MAX_GAIN_MB)

cachedNormalizationGainMb = clampedGain
Expand All @@ -2173,10 +2224,11 @@ class MusicService :
it.enabled = true
}
}
format == null -> {
isFormatNull -> {
Timber.tag(TAG).d("Loudness row not ready yet; keeping cached normalization state")
}
else -> {
Timber.tag(TAG).d("Loudness data not available; resetting to neutral")
cachedNormalizationGainMb = null
cachedNormalizationEnabled = false
playerNormalizationProcessors.values.forEach { it.enabled = false }
Expand Down Expand Up @@ -3554,6 +3606,7 @@ class MusicService :
),
)
}
applyNormalizationFromLoudnessData(mediaId, loudnessDb, perceptualLoudnessDb)
scope.launch(Dispatchers.IO) { recoverSong(mediaId, nonNullPlayback) }

// Clear bypass flag now that we've fetched fresh stream
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class VolumeNormalizationAudioProcessor : AudioProcessor {
}
}

private var buffer: ByteBuffer = EMPTY_BUFFER
private var outputBuffer: ByteBuffer = EMPTY_BUFFER
private var inputEnded = false

Expand Down Expand Up @@ -83,66 +84,55 @@ class VolumeNormalizationAudioProcessor : AudioProcessor {
inputBuffer.order(ByteOrder.LITTLE_ENDIAN)
out.order(ByteOrder.LITTLE_ENDIAN)

when (encoding) {
C.ENCODING_PCM_16BIT -> {
repeat(sampleCount) {
val sample = inputBuffer.getShort()
val processed = if (applyGain) {
(sample * gain.linearGain)
if (!applyGain) {
out.put(inputBuffer)
} else {
when (encoding) {
C.ENCODING_PCM_16BIT -> {
repeat(sampleCount) {
val sample = inputBuffer.getShort()
val processed = (sample * gain.linearGain)
.coerceIn(-32768.0, 32767.0)
.toInt()
.toShort()
} else {
sample
out.putShort(processed)
}
out.putShort(processed)
}
}

C.ENCODING_PCM_24BIT -> {
repeat(sampleCount) {
val b0 = inputBuffer.get().toInt() and 0xFF
val b1 = inputBuffer.get().toInt() and 0xFF
val b2 = inputBuffer.get().toInt()
val sample = (b2 shl 16) or (b1 shl 8) or b0
C.ENCODING_PCM_24BIT -> {
repeat(sampleCount) {
val b0 = inputBuffer.get().toInt() and 0xFF
val b1 = inputBuffer.get().toInt() and 0xFF
val b2 = inputBuffer.get().toInt()
val sample = (b2 shl 16) or (b1 shl 8) or b0

val processed = if (applyGain) {
(sample * gain.linearGain)
val processed = (sample * gain.linearGain)
.coerceIn(-8388608.0, 8388607.0)
.toInt()
} else {
sample

out.put((processed and 0xFF).toByte())
out.put(((processed shr 8) and 0xFF).toByte())
out.put(((processed shr 16) and 0xFF).toByte())
}
out.put((processed and 0xFF).toByte())
out.put(((processed shr 8) and 0xFF).toByte())
out.put(((processed shr 16) and 0xFF).toByte())
}
}

C.ENCODING_PCM_32BIT -> {
repeat(sampleCount) {
val sample = inputBuffer.getInt()
val processed = if (applyGain) {
(sample * gain.linearGain)
C.ENCODING_PCM_32BIT -> {
repeat(sampleCount) {
val sample = inputBuffer.getInt()
val processed = (sample * gain.linearGain)
.coerceIn(-2147483648.0, 2147483647.0)
.toLong()
.toInt()
} else {
sample
out.putInt(processed)
}
out.putInt(processed)
}
}

C.ENCODING_PCM_FLOAT -> {
repeat(sampleCount) {
val sample = inputBuffer.getFloat()
val processed = if (applyGain) {
(sample * gain.linearGain.toFloat()).coerceIn(-1.0f, 1.0f)
} else {
sample
C.ENCODING_PCM_FLOAT -> {
repeat(sampleCount) {
val sample = inputBuffer.getFloat()
val processed = (sample * gain.linearGain.toFloat()).coerceIn(-1.0f, 1.0f)
out.putFloat(processed)
}
out.putFloat(processed)
}
}
}
Expand All @@ -155,9 +145,9 @@ class VolumeNormalizationAudioProcessor : AudioProcessor {
}

override fun getOutput(): ByteBuffer {
val buffer = outputBuffer
val buf = outputBuffer
outputBuffer = EMPTY_BUFFER
return buffer
return buf
}

override fun isEnded(): Boolean {
Expand All @@ -173,6 +163,7 @@ class VolumeNormalizationAudioProcessor : AudioProcessor {
@Deprecated("Deprecated in AudioProcessor")
override fun reset() {
flush()
buffer = EMPTY_BUFFER
sampleRate = 0
channelCount = 0
encoding = C.ENCODING_INVALID
Expand All @@ -182,11 +173,12 @@ class VolumeNormalizationAudioProcessor : AudioProcessor {
}

private fun replaceOutputBuffer(size: Int): ByteBuffer {
if (outputBuffer.capacity() < size) {
outputBuffer = ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder())
if (buffer.capacity() < size) {
buffer = ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder())
} else {
outputBuffer.clear()
buffer.clear()
}
outputBuffer = buffer
return outputBuffer
}

Expand Down
Loading