Android端MediaCodec解码H264流超时求助:dequeueOutputBuffer返回-1
哇,卡三周真的太闹心了!我帮你梳理下MediaCodec解码H264时dequeueOutputBuffer一直返回-1的常见坑,一步步排查解决:
排查MediaCodec输出超时问题
1. 确认Surface与MediaCodec的绑定时机是否正确
你用的是TextureView,必须等它的SurfaceTexture完全可用后,再初始化MediaCodec并绑定Surface——也就是要在SurfaceTextureListener的onSurfaceTextureAvailable回调里做初始化操作,不能在onCreate里提前搞!
比如正确的初始化逻辑应该是这样:
@Override public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) { Surface surface = new Surface(surfaceTexture); try { m_codec = MediaCodec.createDecoderByType("video/avc"); MediaFormat format = MediaFormat.createVideoFormat("video/avc", width, height); // 重点!拆分SPS和PPS,分别放到csd-0和csd-1参数里 // 假设你的byte_SPSPPS是「起始码+SPS+起始码+PPS」的格式 int spsEnd = findNextStartCodeIndex(byte_SPSPPS, 4); // 跳过第一个起始码 byte[] sps = Arrays.copyOfRange(byte_SPSPPS, 4, spsEnd); byte[] pps = Arrays.copyOfRange(byte_SPSPPS, spsEnd + 4, byte_SPSPPS.length); format.setByteBuffer("csd-0", ByteBuffer.wrap(sps)); format.setByteBuffer("csd-1", ByteBuffer.wrap(pps)); m_codec.configure(format, surface, null, 0); m_codec.start(); } catch (IOException e) { e.printStackTrace(); } } // 辅助方法:找到下一个00 00 00 01起始码的位置 private int findNextStartCodeIndex(byte[] data, int start) { for (int i = start; i < data.length - 3; i++) { if (data[i] == 0 && data[i+1] == 0 && data[i+2] == 0 && data[i+3] == 1) { return i; } } return data.length; }
这里最容易踩的坑:把SPS和PPS混在一起塞给csd参数,解码器根本识别不了,直接罢工不输出。
2. 检查输入帧的格式与时间戳
MediaCodec对输入的H264帧要求很严格:
- 必须是完整的NAL单元,带正确的起始码(
00 00 00 01或00 00 01) - 一定要设置合理的时间戳(presentationTimeUs)!如果时间戳为0、乱序或者间隔不合理,解码器会卡住不输出
正确的输入缓冲区处理逻辑:
// 在接收帧的线程里处理 if (byte_Frame != null && m_codec != null) { int inputBufferIndex = m_codec.dequeueInputBuffer(1000); if (inputBufferIndex >= 0) { ByteBuffer inputBuffer = m_codec.getInputBuffer(inputBufferIndex); inputBuffer.clear(); inputBuffer.put(byte_Frame); // 用当前时间转成微秒作为时间戳,或者用服务器传来的帧时间戳更准确 long pts = System.currentTimeMillis() * 1000; m_codec.queueInputBuffer(inputBufferIndex, 0, byte_Frame.length, pts, 0); byte_Frame = null; // 处理完清空,避免重复解码 } }
3. 确保输出缓冲区的正确处理
就算解码器有输出,如果你忘了释放缓冲区,也会导致后续无法获取新的输出。处理输出的逻辑要完整:
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); int outputBufferIndex = m_codec.dequeueOutputBuffer(info, 100000); switch (outputBufferIndex) { case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED: // 第一次输出前会回调这个,正常现象 Log.d("H264Player", "输出格式更新:" + m_codec.getOutputFormat()); break; case MediaCodec.INFO_TRY_AGAIN_LATER: // 超时了,先检查输入是否正常(有没有传对SPS/PPS、帧数据是否完整) Log.d("H264Player", "等待输出超时,检查输入数据"); break; default: // 渲染到Surface并释放缓冲区,第二个参数必须为true m_codec.releaseOutputBuffer(outputBufferIndex, true); // 如果收到EOS标记,停止解码 if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { // 做停止解码的逻辑 } break; }
4. 线程安全问题要注意
你用ZMQ接收数据是在单独线程吧?要确保:
- MediaCodec的所有操作(初始化、输入、输出)要么在同一个线程,要么通过Handler切换到同一线程执行,不能多线程同时调用MediaCodec的方法
- 接收线程要等MediaCodec完全初始化完成后,再开始传帧数据,避免空指针或者无效操作
5. 验证SPS/PPS的有效性
如果SPS或PPS本身是错的,解码器根本无法正常初始化。你可以把分离出的SPS/PPS数据保存成文件,用ffprobe工具验证:
ffprobe -show_streams -i sps_pps.bin
如果工具识别不出H264参数,说明你的SPS/PPS分离有问题,得重新检查数据解析逻辑。
内容的提问来源于stack exchange,提问作者user1381760
相关产品推荐
相关产品推荐

