You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MediaCodec在onInputBufferAvailable回调调用getInputBuffer抛IllegalStateException

MP3解码在部分设备抛出IllegalStateException问题

问题现象

解码MP3文件时,先将文件分割为1000字节的ByteArray块存入环形数组,再通过MediaCodec Callback逐块解码。该逻辑在三星设备运行正常,但在Vivo、Pixel 3a等设备上,onInputBufferAvailable回调中调用mediaCodec.getInputBuffer(index)时抛出IllegalStateException导致崩溃,且已确认设备Codec支持audio/mpeg类型。

崩溃截图:
Crash

相关代码

解码代码

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
    }

问题原因与修复方案

核心问题

  1. MP3帧结构被破坏:MP3基于帧编码,固定1000字节分割会拆分完整帧,导致Codec无法解析,触发内部状态异常。
  2. 回调线程被阻塞:onInputBufferAvailable中的无限循环会卡住MediaCodec回调线程,导致缓冲区状态校验失败。
  3. 并发与缓冲区有效性未校验:环形数组无同步保护,且未检查缓冲区是否有效,部分设备的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 05:50:23