【问题标题】:Android MediaCodec appears to buffer H264 framesAndroid MediaCodec 似乎可以缓冲 H264 帧
【发布时间】:2016-11-07 01:11:02
【问题描述】:

我正在手动读取 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。因此我假设应该立即渲染帧。

我想将帧渲染到纹理上,而不必放置额外的帧。

【问题讨论】:

  • 我认为这不是 h264 解码器的本质,它很可能是 MediaCodec 中的一个缺陷。如果我在使用 ffmpeg 的 PC 上执行此操作,则没有延迟。

标签: android opengl-es h.264 android-mediacodec


【解决方案1】:

来自 MediaCodec documentation

Executing 状态包含三个子状态:Flushed、Running 和 流结束。在start() 之后,编解码器立即在 Flushed 中 子状态,它保存所有缓冲区。只要第一次输入 缓冲区出队,编解码器移动到运行子状态,它 度过了它一生的大部分时间。 当您将输入缓冲区与 流结束标记,编解码器转换到End-of-Stream 子状态。在这种状态下,编解码器不再接受进一步的输入 缓冲区,但仍会生成输出缓冲区,直到流结束 达到输出。您可以在以下位置移回 Flushed 子状态 在处于执行状态的任何时间使用flush()。

您需要“使用end-of-stream 标记对输入缓冲区进行排队”。对您提供给解码器的第一帧执行此操作(确保它是关键帧)。

这一点是为了告诉解码器不要期待更多的帧,因此立即开始播放。否则,在看到任何东西之前喂 3 或 4 帧是正常的。这是所有 MPEG 解码器的预期,与 Android 无关。

【讨论】:

  • 当我发送一个流结束标志和第一个关键帧时,媒体编解码器停止工作。
  • 在我发布的那个 link 中,向下滚动到“States”,然后仔细阅读。考虑在解码器上也使用flush()。重点是:每一帧都是GOf Pictures的一部分(例如:I PB 帧)。 P & B 总是需要 I(关键帧),因此在您发送 3 或 4 个(P 或 B)帧之前,它会被保留以供参考。 “end of stream”和“flush”的点就等于说“我的关键帧之后什么都没有,现在显示”。
  • 我的意思是阅读链接并应用文档所说的内容。您的问题在于 MPEG 解码器将关键帧(全图)放在一边以供某些 P 和 B 类型帧参考(那些图像信息较少,因此它们会从关键帧中丢失部分)。如果有帮助,请使流出队。解决方案是以某种方式告诉系统,在您发送解码的一帧之后,流现在结束了,因此它将数据发送到解码器(显示图片)......
  • @VC.One 这种方法似乎只在每帧都是I帧时才有效。流中有P帧时如何解决?
  • @user3667089,将您的 P/B 帧与 I 帧(关键帧)一起作为一大块字节(流)发送。在您发送的字节中始终将关键帧作为第一帧。
【解决方案2】:

Mediacodec 解码器在输出第一个解码输出帧之前缓冲 6-7 帧。媒体编解码器似乎存在缺陷。这将是流应用程序中的问题。 到目前为止,我的调试显示使用 mediacodec 解码 H264 在流启动期间有 6-7 帧延迟。

【讨论】:

    【解决方案3】:

    如果有人在 2021 年看到这个: Mediacodec 不保存关键帧(又名 iFrame)。 pFrames 不会被渲染,所以它们看起来是缓冲的。 MediaCodec 解码器使用的缓冲区数量在某种程度上取决于设备,根据我的 2017 Android 手机:= 0。

    【讨论】:

      猜你喜欢
      • 2014-02-06
      • 1970-01-01
      • 2013-04-25
      • 1970-01-01
      • 1970-01-01
      • 2013-10-21
      • 2014-08-17
      • 1970-01-01
      • 2017-09-15
      相关资源
      最近更新 更多