mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-11 07:29:14 +01:00
playback: apply the gain only from the item flush
Review follow-ups for the apply slice. - Drop the media item transition application. It never supplied a gain the flush had not already supplied, and as a second writer it raced the playback thread: it applied the incoming item's gain while the outgoing item's buffers were still queued, scaling the previous track's tail by the next track's gain (reproduced at +12 dB). The flush is the only writer now. This reverses the earlier decision to keep it as a fallback. That was made on the premise that it was insurance against a flush that could not be resolved. The flush covers every transition, and a period-less flush only happens while the sink is being reset, so the premise does not hold. - Ignore a flush that carries no period rather than reading it as "this item has no gain". Resetting to unity there would have been wrong if a bare flush ever arrived mid-playback; the test that pinned that behaviour now pins the opposite. - Guard the pass-through branch against an empty input, which is the pipeline's own shared buffer and cannot be put into itself. - The controller is written from the playback thread now, so its state updates and the recompute that follows them are synchronized rather than two independent volatile writes. - Add ReplayGainSinkTest, which drives the real DefaultAudioSink and asserts it hands the processor the item being configured. That is the media3 behaviour this whole feature rests on, and it was previously asserted only by comments.
This commit is contained in:
@@ -266,10 +266,6 @@ private fun Bundle.putGain(
|
||||
|
||||
private fun Bundle.gain(key: String): Float? = if (containsKey(key)) getFloat(key) else null
|
||||
|
||||
internal fun ReplayGainController.applyItem(mediaItem: MediaItem?) {
|
||||
setTrack(mediaItem.replayGain())
|
||||
}
|
||||
|
||||
internal fun queueMediaItem(
|
||||
item: QueueItem,
|
||||
local: String?,
|
||||
|
||||
@@ -140,7 +140,6 @@ class PlaybackService : MediaSessionService() {
|
||||
reason: Int,
|
||||
) {
|
||||
observeCurrentSong(mediaItem?.mediaId)
|
||||
replayGain.applyItem(mediaItem)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
package com.subtracks.playback
|
||||
|
||||
import androidx.media3.common.MediaItem
|
||||
import com.subtracks.data.prefs.ReplayGainMode
|
||||
|
||||
internal class ReplayGainController(
|
||||
private val apply: (Float) -> Unit,
|
||||
) {
|
||||
// Written from the playback thread by ReplayGainProcessor.onFlush and from the app thread by
|
||||
// the media item transition fallback.
|
||||
@Volatile
|
||||
private var track = ReplayGain()
|
||||
|
||||
@Volatile
|
||||
private var settings = ReplayGainSettings(ReplayGainMode.Track, 0f)
|
||||
|
||||
fun setTrack(value: ReplayGain?) {
|
||||
track = value ?: ReplayGain()
|
||||
// Called from the playback thread, by ReplayGainProcessor.onFlush.
|
||||
@Synchronized
|
||||
fun applyItem(item: MediaItem) {
|
||||
track = item.replayGain()
|
||||
applyGain()
|
||||
}
|
||||
|
||||
// Called from the app thread, when the preferences change.
|
||||
@Synchronized
|
||||
fun setSettings(value: ReplayGainSettings) {
|
||||
settings = value
|
||||
applyGain()
|
||||
|
||||
@@ -13,7 +13,7 @@ import kotlin.math.roundToInt
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
internal class ReplayGainProcessor(
|
||||
private val onItem: (MediaItem?) -> Unit = {},
|
||||
private val onItem: (MediaItem) -> Unit = {},
|
||||
) : BaseAudioProcessor() {
|
||||
@Volatile
|
||||
private var linearGain = 1f
|
||||
@@ -30,7 +30,9 @@ internal class ReplayGainProcessor(
|
||||
// Player.Listener.onMediaItemTransition, which the app thread receives afterwards.
|
||||
// See androidx/media#418.
|
||||
override fun onFlush(streamMetadata: StreamMetadata) {
|
||||
onItem(streamMetadata.currentItem())
|
||||
// A flush with no period comes from a sink reset rather than an item boundary, so leave the
|
||||
// gain alone rather than resetting it to unity.
|
||||
streamMetadata.currentItem()?.let(onItem)
|
||||
}
|
||||
|
||||
override fun queueInput(inputBuffer: ByteBuffer) {
|
||||
@@ -48,7 +50,9 @@ internal class ReplayGainProcessor(
|
||||
}
|
||||
|
||||
else -> {
|
||||
output.put(inputBuffer)
|
||||
// An empty input would be the pipeline's own shared buffer, which cannot be put into
|
||||
// itself.
|
||||
if (inputBuffer.hasRemaining()) output.put(inputBuffer)
|
||||
}
|
||||
}
|
||||
inputBuffer.position(inputBuffer.limit())
|
||||
|
||||
Reference in New Issue
Block a user