【问题标题】:AMediaCodec_dequeueOutputBuffer is slow and returns -10000 on one deviceAMediaCodec_dequeueOutputBuffer 很慢,在一台设备上返回 -10000
【发布时间】:2019-12-31 12:32:51
【问题描述】:

我目前正在为 H264 视频流编写解码器。 目标平台是 Android,所以我使用 MediaCodec API (Android OS >= 6.0)。

我已经在 4 台设备上测试了我的代码(所有 4 台设备相同):

  • 在小米红米 5 Plus 上运行良好(实际上速度很快)。
  • 在 Nexus 7 和三星 Galaxy Tab A 上运行缓慢
  • 在三星 Galaxy Tab S2 上失败,出现神秘错误代码 -10000 AMediaCodec_dequeueOutputBufferconfigurestart 返回正确的值 (AMEDIA_OK))。

所以我的问题是:

  1. 我能以某种方式对其进行优化吗?我测试了每个 MediaCodec API 调用的时间性能,看起来 AMediaCodec_dequeueOutputBuffer 在这里是一个巨大的瓶颈(每帧 80%-90% 的时间)。
  2. 对于 Galaxy Tab 2 上的这个 -10000 错误,我能做些什么吗? 我阅读了 MediaCodecs 文档,但那里没有描述。我只在 VLC 的源代码 (modules/codec/omxil/mediacodec_ndk.c) 中找到了 const AMEDIA_ERROR_UNKNOWN = -10000(问题 2.b:他们在哪里找到了这个常量?)。

设备规范(来自 /etc/media_codecs.xml 的解码器):

  • 小米红米5 Plus: 安卓 7.1.2 “video/avc”解码器:OMX.qcom.video.decoder.avc、OMX.qcom.video.decoder.avc.secure

  • Nexus 7(平板电脑) 安卓 6.0.1 “video/avc”编解码器:OMX.qcom.video.decoder.avc、OMX.qcom.video.decoder.avc.secure

  • 三星标签 A
    安卓 7.1.1 “video/avc”解码器:OMX.qcom.video.decoder.avc、OMX.qcom.video.decoder.avc.secure、OMX.SEC.avc.sw.dec

  • 三星 Tab S2:
    安卓7.0 “视频/avc”解码器:OMX.Exynos.avc.dec、OMX.Exynos.avc.dec.secure、OMX.SEC.avc.sw.dec

我可以看到所有执行正常(即使速度很慢)的设备都具有共同的高通解码器。

我的代码:

//initialization (I omitted checks for errors, all initialization is executed without any errors.:
//f contains pointers to functions from libmediandk.so

const char mime[] = "video/avc";
mDecoder = f.createDecoderByType(mime);
AMediaFormat* mFormat = f.createMediaFormat();

const int colorFormat = 19; //COLOR_FormatYUV420Planar
f.setString(mFormat, c.keyMime, mime);
f.setInt32(mFormat,  c.keyWidth, width);
f.setInt32(mFormat,  c.keyHeight, height);
f.setInt32(mFormat,  c.keyColorFormat, colorFormat);
f.setInt32(mFormat, "encoder", 0);
f.setInt32(mFormat, "max-input-size", 0);

//both sps and pps are extracted from the stream
f.setBuffer(mFormat, "csd-0", sps, sizeof(sps));
f.setBuffer(mFormat, "csd-1", pps, sizeof(pps));

media_status_t status = f.configure (mDecoder, mFormat, NULL, NULL, 0);
status = f.start(mDecoder);

f.deleteMediaFormat(mFormat);

lastOutputBufferIdx = -1;
//this is executed every loop
//data -> char* with this frame's H264 encoded data
//I omitted error check for clarity

const int TIMEOUT_US = -1; //-1 -> blocking mode
AMediaCodecBufferInfo info;
char* buf = NULL;

if (lastOutputBufferIdx != -1){
    f.releaseOutputBuffer(mDecoder, lastOutputBufferIdx, false);
    lastOutputBufferIdx = -1;     
}
ssize_t iBufIdx = f.dequeueInputBuffer(mDecoder, TIMEOUT_US);
if (iBufIdx >= 0){
     buf = f.getInputBuffer(mDecoder, iBufIdx, &bufsize);
     int usedBufSize = 0;
     if (buf){
         usedBufSize = dataSize;
         memcpy(buf, data, usedBufSize);
     }
     media_status_t res = f.queueInputBuffer(mDecoder, iBufIdx, 0, usedBufSize, getTimestamp(), 0);
}

//here's my nemesis (this line is both bottleneck and -10000 generator):
ssize_t oBufIdx = f.dequeueOutputBuffer(mDecoder, &info, TIMEOUT_US);

//I am not interested in processing any error codes from {-1,-2,-3}
//INFO_TRY_AGAIN_LATER, INFO_OUTPUT_FORMAT_CHANGED, INFO_OUTPUT_BUFFERS_CHANGED)
while (oBufIdx == -1 || oBufIdx == -2 || oBufIdx == -3){
    oBufIdx = f.dequeueOutputBuffer(mDecoder, &info, TIMEOUT_US);
}

while (oBufIdx >= 0)
{
    buf = f.getOutputBuffer(mDecoder, oBufIdx, &bufsize);
    AMediaFormat format = f.getOutputFormat(mDecoder);
    f.getInt32(format, "width", &width);
    f.getInt32(format, "height", &height);
    f.deleteMediaFormat(format);

    //yuv_ is struct returned by my function
    yuv_.data = buf + info.offset;

    yuv_.size = bufsize;
    yuv_.width = width;
    yuv_.height = height;

    yuv_.yPlane = yuv_.data + info.offset;
    yuv_.uPlane = yuv_.yPlane + height * width;
    yuv_.vPlane = yuv_.uPlane + (height * width) / 4;

    yuv_.yStride = width;
    yuv_.uStride = width / 2;
    yuv_.vStride = width / 2;
}

lastOutputBufferIdx = oBufIdx;

我已经看到 MediaCodec 可以在异步模式下运行(可能会快一点),但我不确定是否可以使用它,因为我正在解码实时流视频而不是从硬盘。 我想说的是(可能)没有同时运行解码的选项。

【问题讨论】:

    标签: android-ndk video-streaming android-mediacodec decoder video-codecs


    【解决方案1】:

    最大的问题是您只向解码器提供一个数据包,然后阻塞,等待返回单个解码帧。

    硬件解码器通常会有一点延迟;如果你一直喂它们,通过解码器传递一帧所需的时间比在单个帧之间传递的时间要长。

    所以不要停下来等待输出,而是尽可能提供更多输入数据包(如果你有可用的话)。从第一个数据包输入到第一个解码输出的时间可能相同,但您应该在此之后更快地获得下一帧。有些解码器甚至根本不会返回任何内容,无论您等待多长时间,直到您为其提供了至少几个输入数据包。

    参见例如有关同一问题的更多讨论和更多链接:https://stackoverflow.com/a/37513916/3115956

    【讨论】:

    • 感谢您的回答。我不能使用异步模式,因为我的应用程序需要在给定输入帧后立即输出(它是实时的)。我意识到一件有趣的事情:在 Redmi5 FullHD 上,使用我目前的方法,帧的解码速度比高清帧快!我有一个假设:也许解码器停止并且不会立即返回所有数据,因为它等待未来的帧,并且在等待一段时间后它决定释放解码的帧。有没有办法让他立即释放框架?我已经从 MC API 尝试了 flush(),但它不起作用(或者我做错了什么)。谢谢。
    • 你不需要使用异步模式,你也可以在同步模式下做不同的事情。在输入一个输入数据包后,检查输出帧,但使用一个非常小的超时,并且只轮询一次。如果解码器还没有要返回给您的帧,您将返回等待再次来自网络的更多输入(并且可能稍后轮询解码器以检查是否还有任何输出)。这应该可以解决“慢得要命”的情况。
    • 这样想解码器(在“慢得要命”的情况下):它可以同时有许多帧通过解码器。解码单个帧需要您无法影响的固定时间量(在某些情况下可能长达 150-200 毫秒)。如果你喂一帧,等待输出,再喂一帧,等待输出,你最多可以达到5-6 fps。但尽管如此,解码器可能一次能够有 6 帧在运行。因此,对于 30 fps 的流,在 33 毫秒后,您可能会获得要解码的下一帧。
    • 当你从流中得到下一帧时,你应该直接把它交给解码器,即使它还没有输出第一帧。这样,在您通过解码器的前 6 帧没有输出后,它将输出第一帧(第一帧被提供给解码器后 200 毫秒),之后将间隔 33 毫秒输出后面的帧。
    • 因此,即使它是实时的并且您希望尽快显示帧,也不应该等待解码器的单个输出阻止您向其提供更多输入。您需要定期检查要输入到解码器的更多内容以及解码器是否有任何输出。
    猜你喜欢
    • 1970-01-01
    • 2021-04-08
    • 1970-01-01
    • 2013-06-01
    • 1970-01-01
    • 2018-03-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多