MediaCodec在onInputBufferAvailable回调调用getInputBuffer抛IllegalStateException
MP3解码在部分设备抛出IllegalStateException问题
问题现象
解码MP3文件时,先将文件分割为1000字节的ByteArray块存入环形数组,再通过MediaCodec Callback逐块解码。该逻辑在三星设备运行正常,但在Vivo、Pixel 3a等设备上,onInputBufferAvailable回调中调用mediaCodec.getInputBuffer(index)时抛出IllegalStateException导致崩溃,且已确认设备Codec支持audio/mpeg类型。
崩溃截图:
相关代码
解码代码
var decoder: MediaCodec = MediaCodec.createDecoderByType("audio/mpeg") decoder.configure(format, null, null, 0) decoder.setCallback(object : MediaCodec.Callback() { override fun onInputBufferAvailable(mediaCodec: MediaCodec, i: Int) { while (true) { if (circularArray!!.size() > 0) { val data: ByteArray = circularArray.popFirst() val info = MediaCodec.BufferInfo() val buffer = mediaCodec.getInputBuffer(i) buffer!!.put(data, 0, data.size) mediaCodec.queueInputBuffer(i, 0, data.size, 0, 0) break } } } override fun onOutputBufferAvailable(mediaCodec: MediaCodec, i: Int, info: MediaCodec.BufferInfo) { //DECODING PACKET ENDED val outBuffer = mediaCodec.getOutputBuffer(i) val chunk = ByteArray(info.size) outBuffer!![chunk] // Read the buffer all at once outBuffer!!.clear() Log.d(TAG, "onOutputBufferAvailable: ${info.size}") audioTrack!!.write(chunk, info.offset, info.offset + info.size) mediaCodec.releaseOutputBuffer(i, false) } override fun onError(mediaCodec: MediaCodec, e: MediaCodec.CodecException) {} override fun onOutputFormatChanged(mediaCodec: MediaCodec, mediaFormat: MediaFormat) {} }) decoder!!.start()
文件转换代码
val tempBuf = ByteArray(1000) var byteRead: Int try { val bufferedInputStream = BufferedInputStream(FileInputStream(mp3File)) while (bufferedInputStream.read(tempBuf).also { byteRead = it } != -1) { circularArray.addLast(tempBuf.copyOf()) } bufferedInputStream.close() Thread(aacDecoderAndPlayRunnable).start() } catch (e: java.lang.Exception) { Log.d(TAG, "fileToInputStream: ${e.message}") e.printStackTrace() null }
问题原因与修复方案
核心问题
- MP3帧结构被破坏:MP3基于帧编码,固定1000字节分割会拆分完整帧,导致Codec无法解析,触发内部状态异常。
- 回调线程被阻塞:
onInputBufferAvailable中的无限循环会卡住MediaCodec回调线程,导致缓冲区状态校验失败。 - 并发与缓冲区有效性未校验:环形数组无同步保护,且未检查缓冲区是否有效,部分设备的Codec实现会严格校验缓冲区状态,抛出异常。
修复步骤
1. 按MP3帧边界分割数据
MP3必须按完整帧拆分,通过检测帧头(同步字0xFFE/0xFFF)分割:
fun splitMp3IntoFrames(inputStream: InputStream): List<ByteArray> { val frames = mutableListOf<ByteArray>() val buffer = ByteArray(4096) var offset = 0 while (true) { val read = inputStream.read(buffer, offset, buffer.size - offset) if (read == -1) break offset += read var pos = 0 while (pos <= offset - 4) { // 检测MP3帧头 if ((buffer[pos] and 0xFF.toByte()) == 0xFF.toByte() && (buffer[pos+1] and 0xE0.toByte()) == 0xE0.toByte()) { val frameLength = calculateMp3FrameLength(buffer, pos) if (pos + frameLength <= offset) { frames.add(buffer.copyOfRange(pos, pos + frameLength)) pos += frameLength } else { // 剩余数据移到缓冲区开头,等待后续读取 System.arraycopy(buffer, pos, buffer, 0, offset - pos) offset = offset - pos pos = offset } } else { pos++ } } } return frames } // 根据MP3格式规范计算帧长度 fun calculateMp3FrameLength(buffer: ByteArray, pos: Int): Int { val bitrateIndex = (buffer[pos+2] shr 4) and 0x0F val sampleRateIndex = (buffer[pos+2] shr 2) and 0x03 val paddingBit = (buffer[pos+2] shr 1) and 0x01 // 参考MP3帧长度公式完成计算,此处省略具体实现 return 0 }
2. 修正MediaCodec回调逻辑
- 移除无限循环,避免阻塞回调线程
- 校验缓冲区有效性,添加异常捕获
- 处理帧结束标记,规范Codec生命周期
decoder.setCallback(object : MediaCodec.Callback() { override fun onInputBufferAvailable(mediaCodec: MediaCodec, index: Int) { try { if (mediaCodec.isReleased) return val buffer = mediaCodec.getInputBuffer(index) ?: run { mediaCodec.queueInputBuffer(index, 0, 0, 0, 0) return } circularArray?.let { array -> if (array.size() > 0) { val data = array.popFirst() buffer.clear() buffer.put(data) val flags = if (array.size() == 0) MediaCodec.BUFFER_FLAG_END_OF_STREAM else 0 mediaCodec.queueInputBuffer(index, 0, data.size, 0, flags) } else { mediaCodec.queueInputBuffer(index, 0, 0, 0, 0) } } } catch (e: IllegalStateException) { e.printStackTrace() } } override fun onOutputBufferAvailable(mediaCodec: MediaCodec, index: Int, info: MediaCodec.BufferInfo) { try { val outBuffer = mediaCodec.getOutputBuffer(index) ?: run { mediaCodec.releaseOutputBuffer(index, false) return } if (info.size > 0) { val chunk = ByteArray(info.size) outBuffer.position(info.offset) outBuffer.get(chunk) audioTrack?.write(chunk, 0, chunk.size) } mediaCodec.releaseOutputBuffer(index, false) if (info.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) { decoder.stop() decoder.release() } } catch (e: Exception) { e.printStackTrace() mediaCodec.releaseOutputBuffer(index, false) } } override fun onError(mediaCodec: MediaCodec, e: MediaCodec.CodecException) { e.printStackTrace() mediaCodec.stop() mediaCodec.release() // 添加Codec重新初始化逻辑 } override fun onOutputFormatChanged(mediaCodec: MediaCodec, mediaFormat: MediaFormat) { // 根据需要更新AudioTrack格式 } })
3. 线程安全优化
给环形数组添加同步锁,避免并发读写问题:
val circularArray = object : CircularArray<ByteArray>() { private val lock = Object() override fun addLast(element: ByteArray) { synchronized(lock) { super.addLast(element) } } override fun popFirst(): ByteArray? { synchronized(lock) { return if (size() > 0) super.popFirst() else null } } override fun size(): Int { synchronized(lock) { return super.size() } } }
内容的提问来源于stack exchange,提问作者Shan Ahmad
相关产品推荐
相关产品推荐

