Android MediaCodec似乎缓冲H264帧

5
我手动读取RTP/H264流并将H264帧传递给Android MediaCodec。我使用“markerBit”作为帧的边界。MediaCodec绑定到OpenGL纹理(SurfaceTexture)。总体而言,一切都正常。但解码器似乎会缓冲帧。如果我将一帧放入解码器中,则不会立即将其呈现到纹理上。在我将2-3帧放入解码器后,第一帧才会呈现到纹理上。
我正在针对Android 4.4.4实现。
private static final int INFINITE_TIMEOUT = -1;
private static final int TIMEOUT_OUTPUT_BUFFER_MEDIA_CODEC = 1000;
...
int bufferIndex = codec.dequeueInputBuffer(INFINITE_TIMEOUT);
if (bufferIndex < 0) {
  throw new RuntimeException("Error");
}

ByteBuffer inputBuffer = inputBuffers[bufferIndex];
inputBuffer.clear();

// Copy H264 data to inputBuffer
h264Frame.fill(inputBuffer);

codec.queueInputBuffer(bufferIndex, 0, inputBuffer.position(), 0, 0);
drainOutputBuffers();
...

并且

private boolean drainOutputBuffers() {
MediaCodec.BufferInfo buffInfo = new MediaCodec.BufferInfo();

int outputBufferIndex = codec.dequeueOutputBuffer(buffInfo, TIMEOUT_OUTPUT_BUFFER_MEDIA_CODEC);

if (outputBufferIndex >= 0) {
  codec.releaseOutputBuffer(outputBufferIndex, true);
  return true;
}

switch (outputBufferIndex) {
  case MediaCodec.INFO_TRY_AGAIN_LATER:
    LOG.debug("Could not dequeue output buffer. Try again later");
    break;
  case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
    LOG.warn("The output format has changed.");
    break;
  case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
    LOG.warn("The output buffers has changed.");
    break;
  default:
    LOG.warn("The output buffer index was negative: {}", outputBufferIndex);
}
return false;
}

在渲染方面,我使用“onFrameAvailable”回调函数来检查是否需要在OpenGL线程上更新纹理。我用一个锁(同步)来保护用于检查的标志。
我怀疑演示时间戳可能会影响渲染。但我将其设置为0。因此,我认为帧应该无延迟地被渲染。
我希望能够在不添加其他帧的情况下将帧渲染到纹理中。

1
我认为这不是h264解码器的本质问题,很可能是MediaCodec中的缺陷。如果我在PC上使用ffmpeg进行此操作,则没有延迟。 - user3667089
3个回答

2
从MediaCodec 文档中:

执行状态有三个子状态:Flushed、Running和End-of-Stream。在调用start()后,编解码器处于Flushed子状态,在此状态下它保留所有缓冲区。一旦第一个输入缓冲区被出队,编解码器将进入Running子状态,在此状态下它会花费大部分时间。当您使用带有end-of-stream标记的输入缓冲区排队时,编解码器将转换为End-of-Stream子状态。在此状态下,编解码器不再接受进一步的输入缓冲区,但仍会生成输出缓冲区,直到在输出上达到end-of-stream。您可以使用flush()在执行状态下任何时候返回到Flushed子状态。

您需要“使用end-of-stream标记的输入缓冲区排队”。请确保将其与您提供给解码器的第一帧一起使用(请确保它是关键帧)。

这一点是告诉解码器不要再期望更多的帧,因此立即开始播放。否则,在看到任何内容之前,正常情况下需要提供3或4个帧。这是所有 MPEG 解码器的期望,与 Android 无关。

当我在第一个关键帧发送一个流结束标志时,Mediacodec 就停止工作了。 - Soccertrash
1
在我发布的那个**链接**中,向下滚动到“状态”并仔细阅读。考虑在解码器上使用flush()。重点是:每一帧都是一个GOP(组图片)的一部分(例如:I帧后跟P和B帧)。P和B始终需要I(关键帧),因此将其保留供参考,同时在您看到任何内容之前发送3或4个(P或B)帧。 “流结束”和“刷新”的意义相当于说“我的关键帧后没有任何内容,请立即显示它”。 - VC.One
1
我的意思是阅读链接并应用文档中所述的内容。你的问题在于MPEG解码器将一个关键帧(完整画面)保留作为某些P和B类型帧的参考(这些帧具有较少的图像信息,因此它们从关键帧获取丢失的部分)。如果逆序列化流,则该解码器将排队。解决方案是以某种方式告诉系统,在发送给解码器进行解码的一帧之后,流现在已经结束了,因此它会将数据发送到解码器(显示图像)... - VC.One
@user3667089,请将您的P/B帧与I帧(关键帧)一起作为一个大块字节流发送。请确保在发送的字节流中,第一帧始终是关键帧。 - VC.One
@VC.One 是的,但是在你的例子中,这意味着我将有30帧的延迟。当我有可用的帧时,由于实时要求,我需要立即显示它。 - user3667089
显示剩余6条评论

1

如果有人在2021年看到这个: Mediacodec可能会或可能不会保留帧(当然,我指的是I-frames)。这完全取决于设备。如果你想确保帧被释放,例如当你计划解码单个帧时,你可以使用

mediaCodec.stop

每次将数据放入输入缓冲区以释放帧。然后,您需要再次启动mediaCodec以获取下一帧。

-2

Mediacodec解码器在输出第一个解码输出帧之前会缓冲6-7帧。这似乎是Mediacodec的缺陷。这将成为流应用程序中的问题。 到目前为止,我的调试显示使用Mediacodec解码H264时,在流开始时会有6-7帧的延迟。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接