MediaCodec.dequeueOutputBuffer在Pixel返回INFO_TRY_AGAIN_LATER问题排查
问题:Pixel 6 Pro上Bitmap转视频时编码器无限循环卡住
我使用bitmap2video库将Bitmap转换为视频,并通过FrameBuilder.kt编码为AVC/H264格式。但在流结束时,Google Pixel 6 Pro上的dequeueOutputBuffer持续返回-1/INFO_TRY_AGAIN_LATER,导致无限循环;而三星S21/S22设备运行完全正常。
设备日志
Pixel 6 Pro日志
15:00:30.299 I event=androidx.camera.video.VideoRecordEvent$Status@3b2c59b 15:00:30.327 I event=androidx.camera.video.VideoRecordEvent$Status@ee2077 15:00:30.588 I event=androidx.camera.video.VideoRecordEvent$Finalize@d1a4d13 // 获取录制视频的URI并解析到MediaRetriever,提取Bitmap帧用于编码 15:00:30.589 I uri=content://media/external/video/media/1000000258 15:00:35.046 D drainCodec(false, encoderStatus=-1 15:00:35.058 D drainCodec(false), encoderStatus=-1 12:35:03.435 D drainCodec(false), encoderStatus=-1 12:35:03.457 D drainCodec(false), encoderStatus=-1 12:35:03.480 D drainCodec(false), encoderStatus=-1 12:35:03.493 D drainCodec(false), encoderStatus=-1 12:35:03.506 D drainCodec(false), encoderStatus=-1 12:35:03.516 D releasing encoder objects 12:35:03.516 D drainCodec(true), encoderStatus=-1 12:35:03.516 D sending EOS to encoder 12:35:03.527 D no output available, spinning to await EOS 12:35:03.537 D no output available, spinning to await EOS 12:35:03.549 D no output available, spinning to await EOS 12:35:03.560 D no output available, spinning to await EOS 12:35:03.571 D no output available, spinning to await EOS 12:35:03.583 D no output available, spinning to await EOS ~ 无限循环持续
三星S21日志
12:49:05.493 I qqq uri=content://media/external/video/media/1000026049 12:49:14.362 D drainCodec(false) 12:49:14.396 D drainCodec(false) 12:49:14.442 D drainCodec(false) 12:49:14.492 D drainCodec(false) 12:49:14.494 D encoder output format changed: {max-bitrate=1500000, crop-right=2159, level=32768, mime=video/avc, profile=8, bitrate=1500000, priority=0, intra-refresh-period=0, color-standard=1, csd-1=java.nio.HeapByteBuffer[pos=0 lim=8 cap=8], color-transfer=3, crop-bottom=3839, prepend-sps-pps-to-idr-frames=0, video-qp-average=0, crop-left=0, width=2160, bitrate-mode=1, color-range=2, crop-top=0, frame-rate=30, height=3840, csd-0=java.nio.HeapByteBuffer[pos=0 lim=22 cap=22]} 12:49:14.498 D {max-bitrate=1500000, crop-right=2159, level=32768, mime=video/avc, profile=8, bitrate=1500000, priority=0, intra-refresh-period=0, color-standard=1, csd-1=java.nio.HeapByteBuffer[pos=0 lim=8 cap=8], color-transfer=3, crop-bottom=3839, prepend-sps-pps-to-idr-frames=0, video-qp-average=0, crop-left=0, width=2160, bitrate-mode=1, color-range=2, crop-top=0, frame-rate=30, height=3840, csd-0=java.nio.HeapByteBuffer[pos=0 lim=22 cap=22]} 12:49:14.507 D ignoring BUFFER_FLAG_CODEC_CONFIG 12:49:14.509 D sent 6605 bytes to muxer 12:49:14.510 D sent 1255 bytes to muxer 12:49:14.517 D sent 2901 bytes to muxer 12:49:14.548 D drainCodec(false) 12:49:14.578 D drainCodec(false) 12:49:14.579 D sent 7208 bytes to muxer 12:49:14.610 D drainCodec(false) 12:49:14.611 D sent 10116 bytes to muxer 12:49:14.646 D drainCodec(false) 12:49:14.648 D sent 18212 bytes to muxer 12:49:14.678 D drainCodec(false) 12:49:14.679 D sent 14077 bytes to muxer 12:49:14.711 D drainCodec(false) 12:49:14.712 D sent 13230 bytes to muxer 12:49:14.745 D drainCodec(false) 12:49:14.746 D sent 13348 bytes to muxer 12:49:14.778 D drainCodec(false) 12:49:14.778 D sent 14504 bytes to muxer 12:49:14.796 D drainCodec(false) 12:49:14.804 D sent 14219 bytes to muxer 12:49:14.814 D releasing encoder objects 12:49:14.814 D drainCodec(true) 12:49:14.814 D sending EOS to encoder 12:49:14.825 D no output available, spinning to await EOS 12:49:14.828 D sent 12867 bytes to muxer 12:49:14.838 D no output available, spinning to await EOS 12:49:14.843 D end of stream reached
相关代码(drainCodec函数)
private fun drainCodec(endOfStream: Boolean) { if (VERBOSE) Log.d(TAG, "drainCodec($endOfStream)") if (endOfStream) { if (VERBOSE) Log.d(TAG, "sending EOS to encoder") mediaCodec.signalEndOfInputStream() } while (true) { val encoderStatus: Int = mediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC.toLong()) if (VERBOSE) Log.d(TAG, "encoderStatus=$encoderStatus") if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) { // no output available yet, stuck here on Google Pixel if (!endOfStream) { break // out of while } else { if (VERBOSE) Log.d(TAG, "no output available, spinning to await EOS") } } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { // should happen before receiving buffers, and should only happen once if (frameMuxer.isStarted()) throw RuntimeException("format changed twice") val newFormat: MediaFormat = mediaCodec.outputFormat Log.d(TAG, "encoder output format changed: $newFormat") // now that we have the Magic Goodies, start the muxer frameMuxer.start(newFormat, audioExtractor) } else { val encodedData = mediaCodec.getOutputBuffer(encoderStatus) ?: throw RuntimeException("encoderOutputBuffer $encoderStatus was null") if (bufferInfo.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG != 0) { // The codec config data was pulled out and fed to the muxer when we got // the INFO_OUTPUT_FORMAT_CHANGED status. Ignore it. if (VERBOSE) Log.d(TAG, "ignoring BUFFER_FLAG_CODEC_CONFIG") bufferInfo.size = 0 } else { if (VERBOSE) Log.d(TAG, "Flags not configured") } if (bufferInfo.size != 0) { if (!frameMuxer.isStarted()) throw RuntimeException("muxer hasn't started") frameMuxer.muxVideoFrame(encodedData, bufferInfo) if (VERBOSE) Log.d(TAG, "sent " + bufferInfo.size + " bytes to muxer") } else { if (VERBOSE) Log.d(TAG, "No bytes sent") } mediaCodec.releaseOutputBuffer(encoderStatus, false) if (bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) { if (!endOfStream) { Log.w(TAG, "reached end of stream unexpectedly") } else { if (VERBOSE) Log.d(TAG, "end of stream reached") } break // out of while } } } }
问题原因
- 硬件编码器差异:Pixel 6 Pro的AVC编码器对EOS(流结束)信号的处理逻辑与三星设备不同,可能编码器内部未正确生成结束帧,导致持续无输出返回。
- 无超时退出机制:当前代码在发送EOS后无限循环等待,没有设置退出阈值,一旦编码器无法响应结束信号就会卡死。
- 帧处理不完整:从日志看,Pixel在发送EOS前的
drainCodec(false)阶段已多次返回INFO_TRY_AGAIN_LATER,说明编码器已无输出,但后续发送EOS后,编码器无法生成结束信号。
解决方案
1. 给EOS等待添加超时机制
修改drainCodec函数,限制等待EOS的次数,避免无限循环:
private fun drainCodec(endOfStream: Boolean) { if (VERBOSE) Log.d(TAG, "drainCodec($endOfStream)") if (endOfStream) { if (VERBOSE) Log.d(TAG, "sending EOS to encoder") mediaCodec.signalEndOfInputStream() } var waitCount = 0 val maxWaitAttempts = 50 // 最多等待50次,对应约500ms while (true) { val encoderStatus: Int = mediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC.toLong()) if (VERBOSE) Log.d(TAG, "encoderStatus=$encoderStatus") if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) { if (!endOfStream) { break } else { if (VERBOSE) Log.d(TAG, "no output available, spinning to await EOS") waitCount++ // 超过等待次数强制退出 if (waitCount >= maxWaitAttempts) { Log.w(TAG, "Timeout waiting for EOS, exiting loop") break } // 短暂休眠,降低CPU占用并给编码器处理时间 Thread.sleep(10) } } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { if (frameMuxer.isStarted()) throw RuntimeException("format changed twice") val newFormat: MediaFormat = mediaCodec.outputFormat Log.d(TAG, "encoder output format changed: $newFormat") frameMuxer.start(newFormat, audioExtractor) } else { val encodedData = mediaCodec.getOutputBuffer(encoderStatus) ?: throw RuntimeException("encoderOutputBuffer $encoderStatus was null") if (bufferInfo.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG != 0) { if (VERBOSE) Log.d(TAG, "ignoring BUFFER_FLAG_CODEC_CONFIG") bufferInfo.size = 0 } if (bufferInfo.size != 0) { if (!frameMuxer.isStarted()) throw RuntimeException("muxer hasn't started") frameMuxer.muxVideoFrame(encodedData, bufferInfo) if (VERBOSE) Log.d(TAG, "sent " + bufferInfo.size + " bytes to muxer") } mediaCodec.releaseOutputBuffer(encoderStatus, false) if (bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) { if (VERBOSE) Log.d(TAG, "end of stream reached") break } } } }
2. 确保所有输入帧处理完成
在调用drainCodec(true)发送EOS前,先排空编码器的所有输出,确保已提交的帧都处理完毕:
// 先排空所有已处理的输出 while (true) { val status = mediaCodec.dequeueOutputBuffer(bufferInfo, 0) if (status == MediaCodec.INFO_TRY_AGAIN_LATER) { break } else if (status == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { // 处理格式变更(如果需要) } else { mediaCodec.releaseOutputBuffer(status, false) } } // 再发送EOS并处理结束逻辑 drainCodec(true)
3. 明确编码器配置参数
初始化编码器时,设置明确的关键参数,避免自动适配的兼容性问题:
val format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, width, height) format.setInteger(MediaFormat.KEY_BIT_RATE, 1500000) format.setInteger(MediaFormat.KEY_FRAME_RATE, 30) format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10) format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface)
内容的提问来源于stack exchange,提问作者MikkelT
相关产品推荐
相关产品推荐

