Android平台Codec2编解码实现遇音频失真问题求助
Android Codec2编码解码音频失真问题修复
问题根源分析
你的代码存在三个核心问题,直接导致音频严重失真:
- 编码时重复处理同一帧数据:录音缓冲区包含4帧有效数据(640样本/160样本每帧),但你每次编码都传入整个缓冲区,相当于重复编码第一帧4次,导致数据冗余错乱。
- 用CharArray存储编码字节流:Codec2输出的是原始二进制字节数据,用CharArray存储会引入字符编码转换问题,导致解码时字节值丢失或异常。
- 解码时字节转换逻辑错误:将CharArray转ByteArray的方式会破坏原始字节结构,比如高位字节被截断,导致解码出的PCM数据完全错误。
修复后的代码实现
companion object { public const val codec2mode = Codec2.CODEC2_MODE_3200 private const val sampleRate = 8000 private const val audioFormat = AudioFormat.ENCODING_PCM_16BIT private const val monoStereoIn = AudioFormat.CHANNEL_IN_MONO private const val monoStereoOut = AudioFormat.CHANNEL_OUT_MONO } private val minBufSize = AudioRecord.getMinBufferSize(sampleRate, monoStereoIn, audioFormat) private val c2instance = Codec2.create(codec2mode) private val frameBytesSize = Codec2.getBitsSize(c2instance) // 注意:返回值是每帧编码后的字节数 private val samplesPerFrame = Codec2.getSamplesPerFrame(c2instance) fun recordData(): ByteArray { Timber.i("recordData frameBytes:$frameBytesSize, samplesPerFrame:$samplesPerFrame, minBufSize:$minBufSize") val recorder = getRecorder() val recorderBuffer = ShortArray(minBufSize) val framesPerBuffer = recorderBuffer.size / samplesPerFrame Timber.i("startRecording recorderBuffer size:${recorderBuffer.size} framesPerBuffer:$framesPerBuffer") recorder.startRecording() val start = System.currentTimeMillis() val encodedData = mutableListOf<Byte>() var it = 50 while (it > 0) { // 必须处理read返回值,确保读取到有效数据 val readCount = recorder.read(recorderBuffer, 0, recorderBuffer.size) if (readCount <= 0) { Timber.w("Failed to read audio data") it-- continue } // 分帧处理每一段样本 for (i in 0 until framesPerBuffer) { val frameStart = i * samplesPerFrame // 避免越界:如果读取的样本数不足一整帧则跳过 if (frameStart + samplesPerFrame > readCount) break val frameSamples = recorderBuffer.copyOfRange(frameStart, frameStart + samplesPerFrame) val encodedFrame = ByteArray(frameBytesSize) Codec2.encode(c2instance, frameSamples, encodedFrame) encodedData.addAll(encodedFrame.toList()) } it-- } recorder.stop() recorder.release() Timber.i( "encode finished in ${System.currentTimeMillis() - start}ms, data length:${encodedData.size}" ) return encodedData.toByteArray() } fun playData(encodedBuffer: ByteArray) { Timber.i("playData frameBytes:$frameBytesSize, samplesPerFrame:$samplesPerFrame, minBufSize:$minBufSize") val start = System.currentTimeMillis() val audioTrack = getAudioTrack().apply { play() } var offset = 0 var it = 0 while (offset + frameBytesSize <= encodedBuffer.size) { // 截取当前帧的编码字节 val encodedFrame = encodedBuffer.copyOfRange(offset, offset + frameBytesSize) val playbackAudioBuffer = ShortArray(samplesPerFrame) Codec2.decode(c2instance, playbackAudioBuffer, encodedFrame) audioTrack.write(playbackAudioBuffer, 0, playbackAudioBuffer.size) offset += frameBytesSize it++ } audioTrack.stop() audioTrack.release() Timber.i( "decode and play finished in ${System.currentTimeMillis() - start}ms, iterations:$it" ) } // 在合适的生命周期方法中释放Codec2实例,避免内存泄漏 fun releaseCodec2() { Codec2.destroy(c2instance) }
额外注意事项
- 资源释放:AudioRecord、AudioTrack使用完毕后必须调用
release(),避免占用系统音频资源。 - 数据有效性检查:AudioRecord的
read()返回值可能为负(表示读取错误),必须处理这种情况,防止无效数据进入编码流程。 - Native资源管理:Codec2实例基于Native实现,不用时必须调用
destroy()释放,防止内存泄漏。
内容的提问来源于stack exchange,提问作者snachmsm
相关产品推荐
相关产品推荐

