code review: resource leaks and init guard
This commit is contained in:
+148
-141
@@ -112,91 +112,94 @@ class VoiceAnonymizer {
|
|||||||
onProgress: (Float) -> Unit,
|
onProgress: (Float) -> Unit,
|
||||||
): DecodedAudio {
|
): DecodedAudio {
|
||||||
val extractor = MediaExtractor()
|
val extractor = MediaExtractor()
|
||||||
extractor.setDataSource(inputFile.absolutePath)
|
var decoder: MediaCodec? = null
|
||||||
|
|
||||||
var audioTrackIndex = -1
|
try {
|
||||||
var format: MediaFormat? = null
|
extractor.setDataSource(inputFile.absolutePath)
|
||||||
for (i in 0 until extractor.trackCount) {
|
|
||||||
val trackFormat = extractor.getTrackFormat(i)
|
var audioTrackIndex = -1
|
||||||
val mime = trackFormat.getString(MediaFormat.KEY_MIME)
|
var format: MediaFormat? = null
|
||||||
if (mime?.startsWith("audio/") == true) {
|
for (i in 0 until extractor.trackCount) {
|
||||||
audioTrackIndex = i
|
val trackFormat = extractor.getTrackFormat(i)
|
||||||
format = trackFormat
|
val mime = trackFormat.getString(MediaFormat.KEY_MIME)
|
||||||
break
|
if (mime?.startsWith("audio/") == true) {
|
||||||
|
audioTrackIndex = i
|
||||||
|
format = trackFormat
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (audioTrackIndex == -1 || format == null) {
|
if (audioTrackIndex == -1 || format == null) {
|
||||||
extractor.release()
|
throw IllegalStateException("No audio track found in file")
|
||||||
throw IllegalStateException("No audio track found in file")
|
}
|
||||||
}
|
|
||||||
|
|
||||||
extractor.selectTrack(audioTrackIndex)
|
extractor.selectTrack(audioTrackIndex)
|
||||||
val mime = format.getString(MediaFormat.KEY_MIME) ?: "audio/mp4a-latm"
|
val mime = format.getString(MediaFormat.KEY_MIME) ?: "audio/mp4a-latm"
|
||||||
val sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE)
|
val sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE)
|
||||||
val durationUs = format.getLong(MediaFormat.KEY_DURATION)
|
val durationUs = format.getLong(MediaFormat.KEY_DURATION)
|
||||||
val duration = (durationUs / 1_000_000).toInt()
|
val duration = (durationUs / 1_000_000).toInt()
|
||||||
|
|
||||||
val decoder = MediaCodec.createDecoderByType(mime)
|
decoder = MediaCodec.createDecoderByType(mime)
|
||||||
decoder.configure(format, null, null, 0)
|
decoder.configure(format, null, null, 0)
|
||||||
decoder.start()
|
decoder.start()
|
||||||
|
|
||||||
val pcmSamples = mutableListOf<Float>()
|
val pcmSamples = mutableListOf<Float>()
|
||||||
val bufferInfo = MediaCodec.BufferInfo()
|
val bufferInfo = MediaCodec.BufferInfo()
|
||||||
var inputDone = false
|
var inputDone = false
|
||||||
var outputDone = false
|
var outputDone = false
|
||||||
|
|
||||||
while (!outputDone && currentCoroutineContext().isActive) {
|
while (!outputDone && currentCoroutineContext().isActive) {
|
||||||
if (!inputDone) {
|
if (!inputDone) {
|
||||||
val inputBufferIndex = decoder.dequeueInputBuffer(10000)
|
val inputBufferIndex = decoder.dequeueInputBuffer(10000)
|
||||||
if (inputBufferIndex >= 0) {
|
if (inputBufferIndex >= 0) {
|
||||||
val inputBuffer = decoder.getInputBuffer(inputBufferIndex)!!
|
val inputBuffer = decoder.getInputBuffer(inputBufferIndex)!!
|
||||||
val sampleSize = extractor.readSampleData(inputBuffer, 0)
|
val sampleSize = extractor.readSampleData(inputBuffer, 0)
|
||||||
if (sampleSize < 0) {
|
if (sampleSize < 0) {
|
||||||
decoder.queueInputBuffer(
|
decoder.queueInputBuffer(
|
||||||
inputBufferIndex,
|
inputBufferIndex,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
MediaCodec.BUFFER_FLAG_END_OF_STREAM,
|
MediaCodec.BUFFER_FLAG_END_OF_STREAM,
|
||||||
)
|
)
|
||||||
inputDone = true
|
inputDone = true
|
||||||
} else {
|
} else {
|
||||||
val presentationTimeUs = extractor.sampleTime
|
val presentationTimeUs = extractor.sampleTime
|
||||||
decoder.queueInputBuffer(
|
decoder.queueInputBuffer(
|
||||||
inputBufferIndex,
|
inputBufferIndex,
|
||||||
0,
|
0,
|
||||||
sampleSize,
|
sampleSize,
|
||||||
presentationTimeUs,
|
presentationTimeUs,
|
||||||
0,
|
0,
|
||||||
)
|
)
|
||||||
extractor.advance()
|
extractor.advance()
|
||||||
if (durationUs > 0) {
|
if (durationUs > 0) {
|
||||||
onProgress((presentationTimeUs.toFloat() / durationUs).coerceIn(0f, 1f))
|
onProgress((presentationTimeUs.toFloat() / durationUs).coerceIn(0f, 1f))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val outputBufferIndex = decoder.dequeueOutputBuffer(bufferInfo, 10000)
|
||||||
|
if (outputBufferIndex >= 0) {
|
||||||
|
val outputBuffer = decoder.getOutputBuffer(outputBufferIndex)!!
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val outputBufferIndex = decoder.dequeueOutputBuffer(bufferInfo, 10000)
|
return DecodedAudio(pcmSamples.toFloatArray(), sampleRate, duration)
|
||||||
if (outputBufferIndex >= 0) {
|
} finally {
|
||||||
val outputBuffer = decoder.getOutputBuffer(outputBufferIndex)!!
|
decoder?.stop()
|
||||||
val shortBuffer = outputBuffer.order(ByteOrder.nativeOrder()).asShortBuffer()
|
decoder?.release()
|
||||||
while (shortBuffer.hasRemaining()) {
|
extractor.release()
|
||||||
pcmSamples.add(shortBuffer.get() / 32768f)
|
|
||||||
}
|
|
||||||
decoder.releaseOutputBuffer(outputBufferIndex, false)
|
|
||||||
if (bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) {
|
|
||||||
outputDone = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
decoder.stop()
|
|
||||||
decoder.release()
|
|
||||||
extractor.release()
|
|
||||||
|
|
||||||
return DecodedAudio(pcmSamples.toFloatArray(), sampleRate, duration)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processPcmWithTarsos(
|
private fun processPcmWithTarsos(
|
||||||
@@ -320,86 +323,90 @@ class VoiceAnonymizer {
|
|||||||
format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE)
|
format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE)
|
||||||
|
|
||||||
val encoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC)
|
val encoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC)
|
||||||
encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
|
|
||||||
encoder.start()
|
|
||||||
|
|
||||||
val muxer = MediaMuxer(outputFile.absolutePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
|
val muxer = MediaMuxer(outputFile.absolutePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
|
||||||
var audioTrackIndex = -1
|
|
||||||
var muxerStarted = false
|
var muxerStarted = false
|
||||||
|
|
||||||
val bufferInfo = MediaCodec.BufferInfo()
|
try {
|
||||||
var inputOffset = 0
|
encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
|
||||||
var inputDone = false
|
encoder.start()
|
||||||
var outputDone = false
|
|
||||||
val totalSamples = pcmData.size
|
|
||||||
|
|
||||||
while (!outputDone) {
|
var audioTrackIndex = -1
|
||||||
if (!inputDone) {
|
val bufferInfo = MediaCodec.BufferInfo()
|
||||||
val inputBufferIndex = encoder.dequeueInputBuffer(10000)
|
var inputOffset = 0
|
||||||
if (inputBufferIndex >= 0) {
|
var inputDone = false
|
||||||
val inputBuffer = encoder.getInputBuffer(inputBufferIndex)!!
|
var outputDone = false
|
||||||
inputBuffer.clear()
|
val totalSamples = pcmData.size
|
||||||
|
|
||||||
val samplesToWrite = minOf((inputBuffer.capacity() / 2), pcmData.size - inputOffset)
|
while (!outputDone) {
|
||||||
if (samplesToWrite <= 0) {
|
if (!inputDone) {
|
||||||
encoder.queueInputBuffer(
|
val inputBufferIndex = encoder.dequeueInputBuffer(10000)
|
||||||
inputBufferIndex,
|
if (inputBufferIndex >= 0) {
|
||||||
0,
|
val inputBuffer = encoder.getInputBuffer(inputBufferIndex)!!
|
||||||
0,
|
inputBuffer.clear()
|
||||||
0,
|
|
||||||
MediaCodec.BUFFER_FLAG_END_OF_STREAM,
|
val samplesToWrite = minOf((inputBuffer.capacity() / 2), pcmData.size - inputOffset)
|
||||||
)
|
if (samplesToWrite <= 0) {
|
||||||
inputDone = true
|
encoder.queueInputBuffer(
|
||||||
} else {
|
inputBufferIndex,
|
||||||
for (i in 0 until samplesToWrite) {
|
0,
|
||||||
val sample =
|
0,
|
||||||
(pcmData[inputOffset + i] * 32767)
|
0,
|
||||||
.toInt()
|
MediaCodec.BUFFER_FLAG_END_OF_STREAM,
|
||||||
.coerceIn(-32768, 32767)
|
)
|
||||||
.toShort()
|
inputDone = true
|
||||||
inputBuffer.putShort(sample)
|
} else {
|
||||||
|
for (i in 0 until samplesToWrite) {
|
||||||
|
val sample =
|
||||||
|
(pcmData[inputOffset + i] * 32767)
|
||||||
|
.toInt()
|
||||||
|
.coerceIn(-32768, 32767)
|
||||||
|
.toShort()
|
||||||
|
inputBuffer.putShort(sample)
|
||||||
|
}
|
||||||
|
val presentationTimeUs = (inputOffset * 1_000_000L) / sampleRate
|
||||||
|
encoder.queueInputBuffer(
|
||||||
|
inputBufferIndex,
|
||||||
|
0,
|
||||||
|
inputBuffer.position(),
|
||||||
|
presentationTimeUs,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
inputOffset += samplesToWrite
|
||||||
|
onProgress(inputOffset.toFloat() / totalSamples)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val outputBufferIndex = encoder.dequeueOutputBuffer(bufferInfo, 10000)
|
||||||
|
when {
|
||||||
|
outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> {
|
||||||
|
audioTrackIndex = muxer.addTrack(encoder.outputFormat)
|
||||||
|
muxer.start()
|
||||||
|
muxerStarted = true
|
||||||
|
}
|
||||||
|
|
||||||
|
outputBufferIndex >= 0 -> {
|
||||||
|
val outputBuffer = encoder.getOutputBuffer(outputBufferIndex)!!
|
||||||
|
if (muxerStarted && bufferInfo.size > 0) {
|
||||||
|
outputBuffer.position(bufferInfo.offset)
|
||||||
|
outputBuffer.limit(bufferInfo.offset + bufferInfo.size)
|
||||||
|
muxer.writeSampleData(audioTrackIndex, outputBuffer, bufferInfo)
|
||||||
|
}
|
||||||
|
encoder.releaseOutputBuffer(outputBufferIndex, false)
|
||||||
|
if (bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) {
|
||||||
|
outputDone = true
|
||||||
}
|
}
|
||||||
val presentationTimeUs = (inputOffset * 1_000_000L) / sampleRate
|
|
||||||
encoder.queueInputBuffer(
|
|
||||||
inputBufferIndex,
|
|
||||||
0,
|
|
||||||
inputBuffer.position(),
|
|
||||||
presentationTimeUs,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
inputOffset += samplesToWrite
|
|
||||||
onProgress(inputOffset.toFloat() / totalSamples)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
val outputBufferIndex = encoder.dequeueOutputBuffer(bufferInfo, 10000)
|
encoder.stop()
|
||||||
when {
|
encoder.release()
|
||||||
outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> {
|
if (muxerStarted) {
|
||||||
audioTrackIndex = muxer.addTrack(encoder.outputFormat)
|
muxer.stop()
|
||||||
muxer.start()
|
|
||||||
muxerStarted = true
|
|
||||||
}
|
|
||||||
|
|
||||||
outputBufferIndex >= 0 -> {
|
|
||||||
val outputBuffer = encoder.getOutputBuffer(outputBufferIndex)!!
|
|
||||||
if (muxerStarted && bufferInfo.size > 0) {
|
|
||||||
outputBuffer.position(bufferInfo.offset)
|
|
||||||
outputBuffer.limit(bufferInfo.offset + bufferInfo.size)
|
|
||||||
muxer.writeSampleData(audioTrackIndex, outputBuffer, bufferInfo)
|
|
||||||
}
|
|
||||||
encoder.releaseOutputBuffer(outputBufferIndex, false)
|
|
||||||
if (bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) {
|
|
||||||
outputDone = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
muxer.release()
|
||||||
}
|
}
|
||||||
|
|
||||||
encoder.stop()
|
|
||||||
encoder.release()
|
|
||||||
muxer.stop()
|
|
||||||
muxer.release()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+6
-4
@@ -74,10 +74,12 @@ class VoiceReplyViewModel : ViewModel() {
|
|||||||
scope = viewModelScope,
|
scope = viewModelScope,
|
||||||
logTag = "VoiceReplyViewModel",
|
logTag = "VoiceReplyViewModel",
|
||||||
onError = { error ->
|
onError = { error ->
|
||||||
accountViewModel.toastManager.toast(
|
if (::accountViewModel.isInitialized) {
|
||||||
stringRes(Amethyst.instance.appContext, R.string.error),
|
accountViewModel.toastManager.toast(
|
||||||
error.message ?: "Voice anonymization failed",
|
stringRes(Amethyst.instance.appContext, R.string.error),
|
||||||
)
|
error.message ?: "Voice anonymization failed",
|
||||||
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user