refactor to reduce complexity encodePcmToAac

This commit is contained in:
davotoula
2026-01-21 17:35:13 +01:00
parent b8ecb03ddd
commit 1a736f1c8a
@@ -402,44 +402,34 @@ class VoiceAnonymizer {
return waveform return waveform
} }
private fun encodePcmToAac( private class EncoderInputFeeder(
pcmData: FloatArray, private val encoder: MediaCodec,
sampleRate: Int, private val pcmData: FloatArray,
outputFile: File, private val sampleRate: Int,
onProgress: (Float) -> Unit, private val onProgress: (Float) -> Unit,
) { ) {
val format = private var inputOffset = 0
MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, sampleRate, CHANNELS) var isDone = false
format.setInteger( private set
MediaFormat.KEY_AAC_PROFILE,
MediaCodecInfo.CodecProfileLevel.AACObjectLC,
)
format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE)
val encoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC) fun feedInput() {
val muxer = MediaMuxer(outputFile.absolutePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4) if (isDone) return
var muxerStarted = false
try {
encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
encoder.start()
var audioTrackIndex = -1
val bufferInfo = MediaCodec.BufferInfo()
var inputOffset = 0
var inputDone = false
var outputDone = false
val totalSamples = pcmData.size
while (!outputDone) {
if (!inputDone) {
val inputBufferIndex = encoder.dequeueInputBuffer(10000) val inputBufferIndex = encoder.dequeueInputBuffer(10000)
if (inputBufferIndex >= 0) { if (inputBufferIndex < 0) return
val inputBuffer = encoder.getInputBuffer(inputBufferIndex)!! val inputBuffer = encoder.getInputBuffer(inputBufferIndex)!!
inputBuffer.clear() inputBuffer.clear()
val samplesToWrite = minOf((inputBuffer.capacity() / 2), pcmData.size - inputOffset) val samplesToWrite = minOf((inputBuffer.capacity() / 2), pcmData.size - inputOffset)
if (samplesToWrite <= 0) { if (samplesToWrite <= 0) {
queueEndOfStream(inputBufferIndex)
} else {
queueSampleData(inputBufferIndex, inputBuffer, samplesToWrite)
}
}
private fun queueEndOfStream(inputBufferIndex: Int) {
encoder.queueInputBuffer( encoder.queueInputBuffer(
inputBufferIndex, inputBufferIndex,
0, 0,
@@ -447,8 +437,25 @@ class VoiceAnonymizer {
0, 0,
MediaCodec.BUFFER_FLAG_END_OF_STREAM, MediaCodec.BUFFER_FLAG_END_OF_STREAM,
) )
inputDone = true isDone = true
} else { }
private fun queueSampleData(
inputBufferIndex: Int,
inputBuffer: java.nio.ByteBuffer,
samplesToWrite: Int,
) {
writePcmSamplesToBuffer(inputBuffer, samplesToWrite)
val presentationTimeUs = (inputOffset * 1_000_000L) / sampleRate
encoder.queueInputBuffer(inputBufferIndex, 0, inputBuffer.position(), presentationTimeUs, 0)
inputOffset += samplesToWrite
onProgress(inputOffset.toFloat() / pcmData.size)
}
private fun writePcmSamplesToBuffer(
inputBuffer: java.nio.ByteBuffer,
samplesToWrite: Int,
) {
for (i in 0 until samplesToWrite) { for (i in 0 until samplesToWrite) {
val sample = val sample =
(pcmData[inputOffset + i] * 32767) (pcmData[inputOffset + i] * 32767)
@@ -457,54 +464,96 @@ class VoiceAnonymizer {
.toShort() .toShort()
inputBuffer.putShort(sample) inputBuffer.putShort(sample)
} }
val presentationTimeUs = (inputOffset * 1_000_000L) / sampleRate
encoder.queueInputBuffer(
inputBufferIndex,
0,
inputBuffer.position(),
presentationTimeUs,
0,
)
inputOffset += samplesToWrite
onProgress(inputOffset.toFloat() / totalSamples)
}
} }
} }
private class EncoderOutputDrainer(
private val encoder: MediaCodec,
private val muxer: MediaMuxer,
) {
private val bufferInfo = MediaCodec.BufferInfo()
private var audioTrackIndex = -1
var isMuxerStarted = false
private set
var isDone = false
private set
fun drainOutput() {
val outputBufferIndex = encoder.dequeueOutputBuffer(bufferInfo, 10000) val outputBufferIndex = encoder.dequeueOutputBuffer(bufferInfo, 10000)
when { when {
outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> { outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> startMuxer()
outputBufferIndex >= 0 -> processOutputBuffer(outputBufferIndex)
}
}
private fun startMuxer() {
audioTrackIndex = muxer.addTrack(encoder.outputFormat) audioTrackIndex = muxer.addTrack(encoder.outputFormat)
muxer.start() muxer.start()
muxerStarted = true isMuxerStarted = true
} }
outputBufferIndex >= 0 -> { private fun processOutputBuffer(outputBufferIndex: Int) {
val outputBuffer = encoder.getOutputBuffer(outputBufferIndex)!! val outputBuffer = encoder.getOutputBuffer(outputBufferIndex)!!
if (muxerStarted && bufferInfo.size > 0) { writeToMuxerIfReady(outputBuffer)
encoder.releaseOutputBuffer(outputBufferIndex, false)
checkForEndOfStream()
}
private fun writeToMuxerIfReady(outputBuffer: java.nio.ByteBuffer) {
if (isMuxerStarted && bufferInfo.size > 0) {
outputBuffer.position(bufferInfo.offset) outputBuffer.position(bufferInfo.offset)
outputBuffer.limit(bufferInfo.offset + bufferInfo.size) outputBuffer.limit(bufferInfo.offset + bufferInfo.size)
muxer.writeSampleData(audioTrackIndex, outputBuffer, bufferInfo) muxer.writeSampleData(audioTrackIndex, outputBuffer, bufferInfo)
} }
encoder.releaseOutputBuffer(outputBufferIndex, false) }
private fun checkForEndOfStream() {
if (bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) { if (bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) {
outputDone = true isDone = true
} }
} }
} }
private fun createAacFormat(sampleRate: Int): MediaFormat =
MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, sampleRate, CHANNELS).apply {
setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC)
setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE)
} }
} finally {
private fun encodePcmToAac(
pcmData: FloatArray,
sampleRate: Int,
outputFile: File,
onProgress: (Float) -> Unit,
) {
val encoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC)
val muxer = MediaMuxer(outputFile.absolutePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
var muxerStarted = false
try { try {
encoder.stop() encoder.configure(createAacFormat(sampleRate), null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
} catch (_: IllegalStateException) { encoder.start()
// Encoder was never started
val inputFeeder = EncoderInputFeeder(encoder, pcmData, sampleRate, onProgress)
val outputDrainer = EncoderOutputDrainer(encoder, muxer)
while (!outputDrainer.isDone) {
inputFeeder.feedInput()
outputDrainer.drainOutput()
} }
encoder.release() muxerStarted = outputDrainer.isMuxerStarted
if (muxerStarted) { } finally {
muxer.stop() encoder.safeStopAndRelease()
muxer.safeStopAndRelease(muxerStarted)
} }
muxer.release()
} }
private fun MediaMuxer.safeStopAndRelease(wasStarted: Boolean) {
if (wasStarted) {
stop()
}
release()
} }
} }