playback: remove extraneous encodings
Remove extraneous encodings from ReplayGainAudioProcessor. According to the ExoPlayer developers, an AudioProcessor is only provided 16-bit PCM data. This makes all of the other encodings I had implemented in ReplayGainAudioProcessor useless, as their cases would never run. Remove those extraneous encodings and just just 16-bit PCM.
This commit is contained in:
parent
59a56090e8
commit
0107d9ff32
2 changed files with 22 additions and 127 deletions
|
@ -18,7 +18,6 @@
|
||||||
package org.oxycblt.auxio.playback.system
|
package org.oxycblt.auxio.playback.system
|
||||||
|
|
||||||
import com.google.android.exoplayer2.C
|
import com.google.android.exoplayer2.C
|
||||||
import com.google.android.exoplayer2.Format
|
|
||||||
import com.google.android.exoplayer2.audio.AudioProcessor
|
import com.google.android.exoplayer2.audio.AudioProcessor
|
||||||
import com.google.android.exoplayer2.audio.BaseAudioProcessor
|
import com.google.android.exoplayer2.audio.BaseAudioProcessor
|
||||||
import com.google.android.exoplayer2.metadata.Metadata
|
import com.google.android.exoplayer2.metadata.Metadata
|
||||||
|
@ -58,7 +57,7 @@ class ReplayGainAudioProcessor : BaseAudioProcessor() {
|
||||||
flush()
|
flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// --- REPLAYGAIN PARSING ---
|
// --- REPLAYGAIN PARSING ---
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the rough volume adjustment for [Metadata] with ReplayGain tags. This is based off
|
* Updates the rough volume adjustment for [Metadata] with ReplayGain tags. This is based off
|
||||||
|
@ -190,18 +189,13 @@ class ReplayGainAudioProcessor : BaseAudioProcessor() {
|
||||||
override fun onConfigure(
|
override fun onConfigure(
|
||||||
inputAudioFormat: AudioProcessor.AudioFormat
|
inputAudioFormat: AudioProcessor.AudioFormat
|
||||||
): AudioProcessor.AudioFormat {
|
): AudioProcessor.AudioFormat {
|
||||||
// TODO: Determine if we really need all of these encodings
|
if (inputAudioFormat.encoding == C.ENCODING_PCM_16BIT) {
|
||||||
val encoding = inputAudioFormat.encoding
|
// AudioProcessor is only provided 16-bit PCM audio data, so that's the only
|
||||||
if (encoding != C.ENCODING_PCM_8BIT &&
|
// encoding we need to check for.
|
||||||
encoding != C.ENCODING_PCM_16BIT &&
|
return inputAudioFormat
|
||||||
encoding != C.ENCODING_PCM_16BIT_BIG_ENDIAN &&
|
|
||||||
encoding != C.ENCODING_PCM_24BIT &&
|
|
||||||
encoding != C.ENCODING_PCM_32BIT &&
|
|
||||||
encoding != C.ENCODING_PCM_FLOAT) {
|
|
||||||
throw AudioProcessor.UnhandledAudioFormatException(inputAudioFormat)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return inputAudioFormat
|
throw AudioProcessor.UnhandledAudioFormatException(inputAudioFormat)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun queueInput(inputBuffer: ByteBuffer) {
|
override fun queueInput(inputBuffer: ByteBuffer) {
|
||||||
|
@ -216,78 +210,18 @@ class ReplayGainAudioProcessor : BaseAudioProcessor() {
|
||||||
buffer.put(inputBuffer[i])
|
buffer.put(inputBuffer[i])
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// AudioProcessor supplies us with the raw bytes and the encoding. It's our job
|
for (i in position until limit step 2) {
|
||||||
// to decode and manipulate it. However, the way we muck the bytes into integer
|
val sample = inputBuffer.getLeShort(i)
|
||||||
// types (and vice versa) introduces the possibility for bits to be dropped along
|
// Clamp the values to the minimum and maximum values possible for the
|
||||||
// the way. This is very bad and can result in popping, corrupted audio streams.
|
// encoding. This prevents issues where samples amplified beyond 1 << 16
|
||||||
// Fix this by clamping the values to the possible range of *signed* values, as
|
// will end up becoming truncated during the conversion to a short,
|
||||||
// the PCM data is unsigned and still uses the bit that the JVM interprets as a sign.
|
// resulting in popping.
|
||||||
when (inputAudioFormat.encoding) {
|
val targetSample =
|
||||||
C.ENCODING_PCM_8BIT -> {
|
(sample * volume)
|
||||||
// 8-bit PCM, decode a single byte and multiply it
|
.toInt()
|
||||||
for (i in position until limit) {
|
.clamp(Short.MIN_VALUE.toInt(), Short.MAX_VALUE.toInt())
|
||||||
val sample = inputBuffer.get(i).toInt().and(0xFF)
|
.toShort()
|
||||||
val targetSample =
|
buffer.putLeShort(targetSample)
|
||||||
(sample * volume)
|
|
||||||
.toInt()
|
|
||||||
.clamp(Byte.MIN_VALUE.toInt(), Byte.MAX_VALUE.toInt())
|
|
||||||
.toByte()
|
|
||||||
buffer.put(targetSample)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
C.ENCODING_PCM_16BIT -> {
|
|
||||||
// 16-bit PCM (little endian).
|
|
||||||
for (i in position until limit step 2) {
|
|
||||||
val sample = inputBuffer.getLeShort(i)
|
|
||||||
val targetSample =
|
|
||||||
(sample * volume)
|
|
||||||
.toInt()
|
|
||||||
.clamp(Short.MIN_VALUE.toInt(), Short.MAX_VALUE.toInt())
|
|
||||||
.toShort()
|
|
||||||
buffer.putLeShort(targetSample)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
C.ENCODING_PCM_16BIT_BIG_ENDIAN -> {
|
|
||||||
// 16-bit PCM (big endian)
|
|
||||||
for (i in position until limit step 2) {
|
|
||||||
val sample = inputBuffer.getBeShort(i)
|
|
||||||
val targetSample =
|
|
||||||
(sample * volume)
|
|
||||||
.toInt()
|
|
||||||
.clamp(Short.MIN_VALUE.toInt(), Short.MAX_VALUE.toInt())
|
|
||||||
.toShort()
|
|
||||||
buffer.putBeSort(targetSample)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
C.ENCODING_PCM_24BIT -> {
|
|
||||||
// 24-bit PCM (little endian), decode the data three bytes at a time.
|
|
||||||
// I don't know if the clamping we do here is valid or not. Since the bit
|
|
||||||
// values should not cross over into the sign, we should be able to do a
|
|
||||||
// simple unsigned clamp, but I'm not sure.
|
|
||||||
for (i in position until limit step 3) {
|
|
||||||
val sample = inputBuffer.getLeInt24(i)
|
|
||||||
val targetSample = (sample * volume).toInt().clamp(0, 0xFF_FF_FF)
|
|
||||||
buffer.putLeInt24(targetSample)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
C.ENCODING_PCM_32BIT -> {
|
|
||||||
// 32-bit PCM (little endian).
|
|
||||||
for (i in position until limit step 4) {
|
|
||||||
var sample = inputBuffer.getLeInt32(i)
|
|
||||||
sample = (sample * volume).toInt().clamp(Int.MIN_VALUE, Int.MAX_VALUE)
|
|
||||||
buffer.putLeInt32(sample)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
C.ENCODING_PCM_FLOAT -> {
|
|
||||||
// PCM float. Here we can actually clamp values since the value isn't
|
|
||||||
// bitwise.
|
|
||||||
for (i in position until limit step 4) {
|
|
||||||
val sample = inputBuffer.getFloat(i)
|
|
||||||
val targetSample = (sample * volume).clamp(0f, 1f)
|
|
||||||
buffer.putFloat(targetSample)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
C.ENCODING_INVALID, Format.NO_VALUE -> {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -299,50 +233,11 @@ class ReplayGainAudioProcessor : BaseAudioProcessor() {
|
||||||
return get(at + 1).toInt().shl(8).or(get(at).toInt().and(0xFF)).toShort()
|
return get(at + 1).toInt().shl(8).or(get(at).toInt().and(0xFF)).toShort()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ByteBuffer.getBeShort(at: Int): Short {
|
|
||||||
return get(at).toInt().shl(8).or(get(at + 1).toInt().and(0xFF)).toShort()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ByteBuffer.putLeShort(short: Short) {
|
private fun ByteBuffer.putLeShort(short: Short) {
|
||||||
put(short.toByte())
|
put(short.toByte())
|
||||||
put(short.toInt().shr(8).toByte())
|
put(short.toInt().shr(8).toByte())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ByteBuffer.putBeSort(short: Short) {
|
|
||||||
put(short.toInt().shr(8).toByte())
|
|
||||||
put(short.toByte())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ByteBuffer.getLeInt24(at: Int): Int {
|
|
||||||
return get(at + 2)
|
|
||||||
.toInt()
|
|
||||||
.shl(16)
|
|
||||||
.or(get(at + 1).toInt().shl(8))
|
|
||||||
.or(get(at).toInt().and(0xFF))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ByteBuffer.putLeInt24(int: Int) {
|
|
||||||
put(int.toByte())
|
|
||||||
put(int.shr(8).toByte())
|
|
||||||
put(int.shr(16).toByte())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ByteBuffer.getLeInt32(at: Int): Int {
|
|
||||||
return get(at + 3)
|
|
||||||
.toInt()
|
|
||||||
.shl(24)
|
|
||||||
.or(get(at + 2).toInt().shl(16))
|
|
||||||
.or(get(at + 1).toInt().shl(8))
|
|
||||||
.or(get(at).toInt().and(0xFF))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ByteBuffer.putLeInt32(int: Int) {
|
|
||||||
put(int.toByte())
|
|
||||||
put(int.shr(8).toByte())
|
|
||||||
put(int.shr(16).toByte())
|
|
||||||
put(int.shr(24).toByte())
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val RG_TRACK = "REPLAYGAIN_TRACK_GAIN"
|
private const val RG_TRACK = "REPLAYGAIN_TRACK_GAIN"
|
||||||
private const val RG_ALBUM = "REPLAYGAIN_ALBUM_GAIN"
|
private const val RG_ALBUM = "REPLAYGAIN_ALBUM_GAIN"
|
||||||
|
|
|
@ -81,10 +81,10 @@ class SettingsListFragment : PreferenceFragmentCompat() {
|
||||||
// to override this random method within the class in order to launch the dialog in
|
// to override this random method within the class in order to launch the dialog in
|
||||||
// the first (because apparently you can't just implement some interface that
|
// the first (because apparently you can't just implement some interface that
|
||||||
// automatically provides this behavior), then we also need to use a deprecated method
|
// automatically provides this behavior), then we also need to use a deprecated method
|
||||||
// to adequately supply
|
// to adequately supply a "target fragment" (otherwise we will crash since the dialog
|
||||||
// a "target fragment" (otherwise we will crash since the dialog requires one), and then
|
// requires one), and then we need to actually show the dialog, making sure we use
|
||||||
// we need to actually show the dialog, making sure we use the parent FragmentManager
|
// the parent FragmentManager as again, it will crash if we don't.
|
||||||
// as again, it will crash if we don't do it right. Fragments were a mistake.
|
// Fragments were a mistake.
|
||||||
val dialog = IntListPreferenceDialog.from(preference)
|
val dialog = IntListPreferenceDialog.from(preference)
|
||||||
dialog.setTargetFragment(this, 0)
|
dialog.setTargetFragment(this, 0)
|
||||||
dialog.show(parentFragmentManager, IntListPreferenceDialog.TAG)
|
dialog.show(parentFragmentManager, IntListPreferenceDialog.TAG)
|
||||||
|
|
Loading…
Reference in a new issue