refactor to reduce complexity decodeAudioToPcm
This commit is contained in:
+134
-76
@@ -136,6 +136,116 @@ class VoiceAnonymizer {
|
|||||||
val duration: Int,
|
val duration: Int,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private data class AudioTrackInfo(
|
||||||
|
val trackIndex: Int,
|
||||||
|
val format: MediaFormat,
|
||||||
|
val mime: String,
|
||||||
|
val sampleRate: Int,
|
||||||
|
val durationUs: Long,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun findAudioTrack(extractor: MediaExtractor): AudioTrackInfo {
|
||||||
|
for (i in 0 until extractor.trackCount) {
|
||||||
|
val trackFormat = extractor.getTrackFormat(i)
|
||||||
|
val mime = trackFormat.getString(MediaFormat.KEY_MIME)
|
||||||
|
if (mime?.startsWith("audio/") == true) {
|
||||||
|
return AudioTrackInfo(
|
||||||
|
trackIndex = i,
|
||||||
|
format = trackFormat,
|
||||||
|
mime = mime,
|
||||||
|
sampleRate = trackFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE),
|
||||||
|
durationUs = trackFormat.getLong(MediaFormat.KEY_DURATION),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw IllegalStateException("No audio track found in file")
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DecoderInputFeeder(
|
||||||
|
private val decoder: MediaCodec,
|
||||||
|
private val extractor: MediaExtractor,
|
||||||
|
private val durationUs: Long,
|
||||||
|
private val onProgress: (Float) -> Unit,
|
||||||
|
) {
|
||||||
|
var isDone = false
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun feedInput() {
|
||||||
|
if (isDone) return
|
||||||
|
|
||||||
|
val inputBufferIndex = decoder.dequeueInputBuffer(10000)
|
||||||
|
if (inputBufferIndex < 0) return
|
||||||
|
|
||||||
|
val inputBuffer = decoder.getInputBuffer(inputBufferIndex)!!
|
||||||
|
val sampleSize = extractor.readSampleData(inputBuffer, 0)
|
||||||
|
|
||||||
|
if (sampleSize < 0) {
|
||||||
|
queueEndOfStream(inputBufferIndex)
|
||||||
|
} else {
|
||||||
|
queueSampleData(inputBufferIndex, sampleSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun queueEndOfStream(inputBufferIndex: Int) {
|
||||||
|
decoder.queueInputBuffer(
|
||||||
|
inputBufferIndex,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
MediaCodec.BUFFER_FLAG_END_OF_STREAM,
|
||||||
|
)
|
||||||
|
isDone = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun queueSampleData(
|
||||||
|
inputBufferIndex: Int,
|
||||||
|
sampleSize: Int,
|
||||||
|
) {
|
||||||
|
val presentationTimeUs = extractor.sampleTime
|
||||||
|
decoder.queueInputBuffer(inputBufferIndex, 0, sampleSize, presentationTimeUs, 0)
|
||||||
|
extractor.advance()
|
||||||
|
reportProgress(presentationTimeUs)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reportProgress(presentationTimeUs: Long) {
|
||||||
|
if (durationUs > 0) {
|
||||||
|
onProgress((presentationTimeUs.toFloat() / durationUs).coerceIn(0f, 1f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DecoderOutputDrainer(
|
||||||
|
private val decoder: MediaCodec,
|
||||||
|
private val pcmSamples: MutableList<Float>,
|
||||||
|
) {
|
||||||
|
private val bufferInfo = MediaCodec.BufferInfo()
|
||||||
|
var isDone = false
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun drainOutput() {
|
||||||
|
val outputBufferIndex = decoder.dequeueOutputBuffer(bufferInfo, 10000)
|
||||||
|
if (outputBufferIndex < 0) return
|
||||||
|
|
||||||
|
extractPcmSamples(outputBufferIndex)
|
||||||
|
decoder.releaseOutputBuffer(outputBufferIndex, false)
|
||||||
|
checkForEndOfStream()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun extractPcmSamples(outputBufferIndex: Int) {
|
||||||
|
val outputBuffer = decoder.getOutputBuffer(outputBufferIndex)!!
|
||||||
|
val shortBuffer = outputBuffer.order(ByteOrder.nativeOrder()).asShortBuffer()
|
||||||
|
while (shortBuffer.hasRemaining()) {
|
||||||
|
pcmSamples.add(shortBuffer.get() / 32768f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkForEndOfStream() {
|
||||||
|
if (bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) {
|
||||||
|
isDone = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun decodeAudioToPcm(
|
private suspend fun decodeAudioToPcm(
|
||||||
inputFile: File,
|
inputFile: File,
|
||||||
onProgress: (Float) -> Unit,
|
onProgress: (Float) -> Unit,
|
||||||
@@ -145,95 +255,43 @@ class VoiceAnonymizer {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
extractor.setDataSource(inputFile.absolutePath)
|
extractor.setDataSource(inputFile.absolutePath)
|
||||||
|
val trackInfo = findAudioTrack(extractor)
|
||||||
|
extractor.selectTrack(trackInfo.trackIndex)
|
||||||
|
|
||||||
var audioTrackIndex = -1
|
decoder =
|
||||||
var format: MediaFormat? = null
|
MediaCodec.createDecoderByType(trackInfo.mime).apply {
|
||||||
for (i in 0 until extractor.trackCount) {
|
configure(trackInfo.format, null, null, 0)
|
||||||
val trackFormat = extractor.getTrackFormat(i)
|
start()
|
||||||
val mime = trackFormat.getString(MediaFormat.KEY_MIME)
|
|
||||||
if (mime?.startsWith("audio/") == true) {
|
|
||||||
audioTrackIndex = i
|
|
||||||
format = trackFormat
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
check(audioTrackIndex != -1 && format != null) { "No audio track found in file" }
|
val estimatedSamples = (trackInfo.sampleRate.toLong() * trackInfo.durationUs / 1_000_000).toInt()
|
||||||
|
|
||||||
extractor.selectTrack(audioTrackIndex)
|
|
||||||
val mime = format.getString(MediaFormat.KEY_MIME) ?: "audio/mp4a-latm"
|
|
||||||
val sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE)
|
|
||||||
val durationUs = format.getLong(MediaFormat.KEY_DURATION)
|
|
||||||
val duration = (durationUs / 1_000_000).toInt()
|
|
||||||
|
|
||||||
decoder = MediaCodec.createDecoderByType(mime)
|
|
||||||
decoder.configure(format, null, null, 0)
|
|
||||||
decoder.start()
|
|
||||||
|
|
||||||
val estimatedSamples = (sampleRate.toLong() * durationUs / 1_000_000).toInt()
|
|
||||||
val pcmSamples = ArrayList<Float>(estimatedSamples)
|
val pcmSamples = ArrayList<Float>(estimatedSamples)
|
||||||
val bufferInfo = MediaCodec.BufferInfo()
|
|
||||||
var inputDone = false
|
|
||||||
var outputDone = false
|
|
||||||
|
|
||||||
while (!outputDone && currentCoroutineContext().isActive) {
|
val inputFeeder = DecoderInputFeeder(decoder, extractor, trackInfo.durationUs, onProgress)
|
||||||
if (!inputDone) {
|
val outputDrainer = DecoderOutputDrainer(decoder, pcmSamples)
|
||||||
val inputBufferIndex = decoder.dequeueInputBuffer(10000)
|
|
||||||
if (inputBufferIndex >= 0) {
|
|
||||||
val inputBuffer = decoder.getInputBuffer(inputBufferIndex)!!
|
|
||||||
val sampleSize = extractor.readSampleData(inputBuffer, 0)
|
|
||||||
if (sampleSize < 0) {
|
|
||||||
decoder.queueInputBuffer(
|
|
||||||
inputBufferIndex,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
MediaCodec.BUFFER_FLAG_END_OF_STREAM,
|
|
||||||
)
|
|
||||||
inputDone = true
|
|
||||||
} else {
|
|
||||||
val presentationTimeUs = extractor.sampleTime
|
|
||||||
decoder.queueInputBuffer(
|
|
||||||
inputBufferIndex,
|
|
||||||
0,
|
|
||||||
sampleSize,
|
|
||||||
presentationTimeUs,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
extractor.advance()
|
|
||||||
if (durationUs > 0) {
|
|
||||||
onProgress((presentationTimeUs.toFloat() / durationUs).coerceIn(0f, 1f))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val outputBufferIndex = decoder.dequeueOutputBuffer(bufferInfo, 10000)
|
while (!outputDrainer.isDone && currentCoroutineContext().isActive) {
|
||||||
if (outputBufferIndex >= 0) {
|
inputFeeder.feedInput()
|
||||||
val outputBuffer = decoder.getOutputBuffer(outputBufferIndex)!!
|
outputDrainer.drainOutput()
|
||||||
val shortBuffer = outputBuffer.order(ByteOrder.nativeOrder()).asShortBuffer()
|
|
||||||
while (shortBuffer.hasRemaining()) {
|
|
||||||
pcmSamples.add(shortBuffer.get() / 32768f)
|
|
||||||
}
|
|
||||||
decoder.releaseOutputBuffer(outputBufferIndex, false)
|
|
||||||
if (bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) {
|
|
||||||
outputDone = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return DecodedAudio(pcmSamples.toFloatArray(), sampleRate, duration)
|
val duration = (trackInfo.durationUs / 1_000_000).toInt()
|
||||||
|
return DecodedAudio(pcmSamples.toFloatArray(), trackInfo.sampleRate, duration)
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
decoder?.safeStopAndRelease()
|
||||||
decoder?.stop()
|
|
||||||
} catch (_: IllegalStateException) {
|
|
||||||
// Decoder was never started
|
|
||||||
}
|
|
||||||
decoder?.release()
|
|
||||||
extractor.release()
|
extractor.release()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun MediaCodec.safeStopAndRelease() {
|
||||||
|
try {
|
||||||
|
stop()
|
||||||
|
} catch (_: IllegalStateException) {
|
||||||
|
// Decoder was never started
|
||||||
|
}
|
||||||
|
release()
|
||||||
|
}
|
||||||
|
|
||||||
private fun processPcmWithTarsos(
|
private fun processPcmWithTarsos(
|
||||||
pcmData: FloatArray,
|
pcmData: FloatArray,
|
||||||
preset: VoicePreset,
|
preset: VoicePreset,
|
||||||
|
|||||||
Reference in New Issue
Block a user