fix(android): block direct audio without passthrough
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
package com.edde746.plezy.exoplayer
|
||||
|
||||
import androidx.media3.common.MimeTypes
|
||||
|
||||
internal fun isPassthroughAudioMimeType(mimeType: String): Boolean = when (mimeType) {
|
||||
"audio/ac3",
|
||||
"audio/eac3",
|
||||
"audio/eac3-joc",
|
||||
"audio/ac4",
|
||||
"audio/vnd.dts",
|
||||
"audio/vnd.dts.hd",
|
||||
"audio/vnd.dts.uhd",
|
||||
MimeTypes.AUDIO_TRUEHD -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
internal fun shouldBlockDirectOutputForPassthrough(mimeType: String, audioPassthroughEnabled: Boolean): Boolean {
|
||||
return !audioPassthroughEnabled && isPassthroughAudioMimeType(mimeType)
|
||||
}
|
||||
@@ -188,10 +188,11 @@ class ExoPlayerCore(private val activity: Activity) : Player.Listener {
|
||||
// Loudness normalization (#1289): audiofx effects only process non-tunneled
|
||||
// PCM mixer streams, so while enabled we block direct/bitstream output and
|
||||
// disable tunneling.
|
||||
private var audioPassthroughEnabled: Boolean = false
|
||||
private var audioNormalizationEnabled: Boolean = false
|
||||
private val audioNormalization = AudioNormalizationEffect(::emitLog)
|
||||
private var pendingAudioRendererBounce: Boolean = false
|
||||
private val audioBounceTimeout = Runnable { completeAudioRendererBounce("audio-normalization bounce timeout") }
|
||||
private val audioBounceTimeout = Runnable { completeAudioRendererBounce("audio renderer bounce timeout") }
|
||||
private var lastSeekable: Boolean? = null
|
||||
private var forceSeekable: Boolean = false
|
||||
|
||||
@@ -370,13 +371,18 @@ class ExoPlayerCore(private val activity: Activity) : Player.Listener {
|
||||
return decision.mode
|
||||
}
|
||||
|
||||
fun initialize(bufferSizeBytes: Int? = null, tunnelingEnabled: Boolean = true): Boolean {
|
||||
fun initialize(
|
||||
bufferSizeBytes: Int? = null,
|
||||
tunnelingEnabled: Boolean = true,
|
||||
audioPassthroughEnabled: Boolean = false
|
||||
): Boolean {
|
||||
if (isInitialized) {
|
||||
Log.d(TAG, "Already initialized")
|
||||
return true
|
||||
}
|
||||
|
||||
tunnelingUserEnabled = tunnelingEnabled
|
||||
this.audioPassthroughEnabled = audioPassthroughEnabled
|
||||
this.dvMode = getConfiguredDvMode()
|
||||
DoviBridge.logSupportSummary(activity)
|
||||
Log.i(
|
||||
@@ -1840,6 +1846,7 @@ class ExoPlayerCore(private val activity: Activity) : Player.Listener {
|
||||
val mimeType = format.sampleMimeType ?: return false
|
||||
// Loudness normalization needs decoded PCM for the audiofx chain to act on.
|
||||
if (audioNormalizationEnabled && isEncodedAudioMimeType(mimeType)) return true
|
||||
if (shouldBlockDirectOutputForPassthrough(mimeType, audioPassthroughEnabled)) return true
|
||||
if (directAudioOutputBlockedAfterFailure.contains(mimeType)) {
|
||||
if (loggedDirectAudioRecoveryBlocks.add("$mimeType|$reason")) {
|
||||
emitLog(
|
||||
@@ -2720,6 +2727,25 @@ class ExoPlayerCore(private val activity: Activity) : Player.Listener {
|
||||
}
|
||||
}
|
||||
|
||||
fun setAudioPassthrough(enabled: Boolean) {
|
||||
if (audioPassthroughEnabled == enabled) return
|
||||
audioPassthroughEnabled = enabled
|
||||
emitLog("info", "audio", "Audio passthrough ${if (enabled) "enabled" else "disabled"}")
|
||||
|
||||
if (exoPlayer == null) return
|
||||
val outputEncoding = lastAudioTrackConfig?.encoding
|
||||
val selectedMime = selectedAudioFormat()?.sampleMimeType
|
||||
val needsBounce = outputEncoding != null &&
|
||||
selectedMime != null &&
|
||||
isPassthroughAudioMimeType(selectedMime) &&
|
||||
(if (enabled) isPcmEncoding(outputEncoding) else !isPcmEncoding(outputEncoding))
|
||||
if (needsBounce) {
|
||||
startAudioRendererBounce("audio-passthrough")
|
||||
} else {
|
||||
updateTunnelingState("audio-passthrough")
|
||||
}
|
||||
}
|
||||
|
||||
private fun attachNormalizationEffect() {
|
||||
val sessionId = exoPlayer?.audioSessionId ?: C.AUDIO_SESSION_ID_UNSET
|
||||
if (sessionId == C.AUDIO_SESSION_ID_UNSET) {
|
||||
@@ -2737,7 +2763,7 @@ class ExoPlayerCore(private val activity: Activity) : Player.Listener {
|
||||
private fun startAudioRendererBounce(reason: String) {
|
||||
if (pendingAudioRendererBounce) return
|
||||
pendingAudioRendererBounce = true
|
||||
emitLog("info", "audio-normalization", "Bouncing audio renderer to re-evaluate output path (reason=$reason)")
|
||||
emitLog("info", "audio", "Bouncing audio renderer to re-evaluate output path (reason=$reason)")
|
||||
applyTrackSelectorPolicy(reason = "$reason (audio renderer off)", audioDisabled = true)
|
||||
handler.postDelayed(audioBounceTimeout, AUDIO_BOUNCE_TIMEOUT_MS)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.edde746.plezy.exoplayer
|
||||
|
||||
import androidx.media3.common.MimeTypes
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class AudioOutputPolicyTest {
|
||||
|
||||
@Test
|
||||
fun passthroughDisabledBlocksBitstreamFormats() {
|
||||
val bitstreamFormats = listOf(
|
||||
"audio/ac3",
|
||||
"audio/eac3",
|
||||
"audio/eac3-joc",
|
||||
"audio/ac4",
|
||||
"audio/vnd.dts",
|
||||
"audio/vnd.dts.hd",
|
||||
"audio/vnd.dts.uhd",
|
||||
MimeTypes.AUDIO_TRUEHD
|
||||
)
|
||||
|
||||
for (mimeType in bitstreamFormats) {
|
||||
assertTrue(mimeType, shouldBlockDirectOutputForPassthrough(mimeType, audioPassthroughEnabled = false))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun passthroughEnabledAllowsBitstreamFormats() {
|
||||
assertFalse(shouldBlockDirectOutputForPassthrough("audio/ac3", audioPassthroughEnabled = true))
|
||||
assertFalse(shouldBlockDirectOutputForPassthrough(MimeTypes.AUDIO_TRUEHD, audioPassthroughEnabled = true))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun passthroughDisabledLeavesDecodedFormatsAvailable() {
|
||||
val decodedFormats = listOf(
|
||||
MimeTypes.AUDIO_AAC,
|
||||
MimeTypes.AUDIO_OPUS,
|
||||
MimeTypes.AUDIO_RAW,
|
||||
MimeTypes.AUDIO_FLAC
|
||||
)
|
||||
|
||||
for (mimeType in decodedFormats) {
|
||||
assertFalse(mimeType, shouldBlockDirectOutputForPassthrough(mimeType, audioPassthroughEnabled = false))
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user