【问题标题】:memcpy from graphic buffer is slow in Android来自图形缓冲区的memcpy在Android中很慢
【发布时间】:2014-03-13 15:34:39
【问题描述】:

我想捕获视频中的每一帧以在 Android 设备(例如 Nexus 10)中渲染之前进行一些修改。据我所知,android 使用硬件来解码和渲染特定设备中的帧,所以我应该得到GraphicBuffer 中的帧数据,在渲染之前数据会是 YUV 格式。

我还在 AwesomePlayer.cpp 中编写了一个静态方法来实现捕获帧数据/修改帧/将其写回 GraphicBuffer 以进行渲染。

这是我的演示代码

static void handleFrame(MediaBuffer *buffer) {

    sp<GraphicBuffer> buf = buffer->graphicBuffer();

    size_t width = buf->getWidth();
    size_t height = buf->getHeight();
    size_t ySize = buffer->range_length();
    size_t uvSize = width * height / 2;

    uint8_t *yBuffer = (uint8_t *)malloc(ySize + 1);
    uint8_t *uvBuffer = (uint8_t *)malloc(uvSize + 1);
    memset(yBuffer, 0, ySize + 1);
    memset(uvBuffer, 0, uvSize + 1);

    int const *private_handle = buf->handle->data;

    void *yAddr = NULL;
    void *uvAddr = NULL;

    buf->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN, &yAddr);
    uvAddr = mmap(0, uvSize, PROT_READ | PROT_WRITE, MAP_SHARED, *(private_handle + 1));

    if(yAddr != NULL && uvAddr != NULL) {

      //memcpy data from graphic buffer
      memcpy(yBuffer, yAddr, ySize);
      memcpy(uvBuffer, uvAddr, uvSize);

      //modify the YUV data

      //memcpy data into graphic buffer
      memcpy(yAddr, yBuffer, ySize);
      memcpy(uvAddr, uvBuffer, uvSize);
    }

    munmap(uvAddr, uvSize);
    buf->unlock();

    free(yBuffer);
    free(uvBuffer);

}

我打印了 memcpy 函数的时间戳,我意识到 memcpy from GraphicBuffermemcpy 数据到 GraphicBuffer花费更多的时间>。以分辨率为 1920x1080 的视频为例,memcpy from GraphicBuffer大约需要 30ms,对于正常的视频播放来说是无法接受的。

我不知道为什么要花这么多时间,也许它从 GPU 缓冲区复制数据,但 将数据复制到 GraphicBuffer 看起来很正常。

其他熟悉android中硬件解码的人可以看看这个问题吗? 非常感谢。

更新: 我发现我不必使用GraphicBuffer来获取YUV数据,我只是使用硬件解码视频源并将YUV数据存储到内存中,这样我就可以直接从内存中获取YUV数据,速度非常快。 实际上,您可以在 AOSP 源代码或开源视频显示应用程序中找到类似的解决方案。我只是分配内存缓冲区而不是图形缓冲区,然后使用硬件解码器。 AOSP 中的示例代码:frameworks/av/cmds/stagefright/SimplePlayer.cpp

链接:https://github.com/xdtianyu/android-4.2_r1/tree/master/frameworks/av/cmds/stagefright

【问题讨论】:

  • 这可能是因为它只是很多数据。 1920x1080 是每帧 2.3M,仅 8 位/像素。如果您有完整的 ARGB,预计每帧将达到 8.2M。假设您每秒甚至可以达到 15 帧 - 您要求您的平板电脑每秒移动 124Mbytes。需要在屏幕上移动和绘制大量数据、推送到文件或您正在使用它做什么
  • 感谢您的回复@Martin,也许我没有清楚地描述我的意图。首先,我只是处理 YUV 数据,修改后写入原始缓冲区。另外,我在分配的缓冲区之间测试 memcpy 相同的数据,它只需要很少的时间,同一个视频只需大约 5 毫秒。所以我假设它需要很长时间,因为它从 GPU 复制数据,但是将数据复制回 GPU 花费的时间并不多,大约 6 毫秒或 7 毫秒是没有意义的。很困惑。
  • 由于使用的缓存模型,从图形单元复制数据通常要慢一个数量级。在某些平台(这里我指的是图形设备和系统板)上,您甚至可能无法将其复制回系统内存。
  • 感谢@Calvin,我对硬件级别的机制了解不多,所以找不到根本原因。但我真的尝试将帧数据(YUV)复制到系统内存(就像我用 malloc()分配的上述缓冲区),修改数据,并将其复制回原始 GraphicBuffer,它在 Nexus 10 上运行良好,除了它由于时间延迟很多,丢了很多帧。关于缓存模型,你想告诉我更多吗?我对此一无所知。再次感谢。
  • 如果您可以使用着色器对逻辑进行编程,您可以将内容保存在显存中并避免所有传输。

标签: android c++ multimedia


【解决方案1】:

最有可能的是,从 CPU 到图形内存的数据路径(也称为数据总线)已经过优化。从图形内存到 CPU 的路径可能未优化。优化可能包括不同速度的内部数据总线、1 级或 2 级缓存和等待状态。

电子设备(硬件)设置了将数据从图形内存传输到 CPU 的最大速度。 CPU 的内存可能比您的图形内存慢,因此可能存在等待状态以使图形内存与较慢的 CPU 内存速度相匹配。

另一个问题是所有设备共享数据总线。想象一下城市之间的共享高速公路。为了优化流量,只允许一个方向的流量。交通信号灯或人,监控交通。为了从A市到C市,必须等到交通信号灯或指挥员,清除剩余的交通,并给予A市到C市的路线优先权。在硬件方面,这称为总线仲裁

在大多数平台中,CPU 在寄存器和 CPU 内存之间传输数据。这是在程序中读取和写入变量所必需的。传输数据的慢速路线是 CPU 将内存读入寄存器,然后写入图形内存。一种更有效的方法是在不使用 CPU 的情况下传输数据。可能存在一个设备,DMA(直接内存访问),它可以在不使用 CPU 的情况下传输数据。你告诉它源和目标内存位置,然后启动它。它将在不使用 CPU 的情况下传输数据。

不幸的是,DMA 必须与 CPU 共享数据总线。这意味着您的数据传输将因 CPU 对数据总线的任何请求而减慢。它仍然比使用 CPU 传输数据更快,因为 DMA 可以在 CPU 执行不需要数据总线的指令时传输数据。

总结
如果您没有 DMA 设备,您的内存传输可能会很慢。无论有没有 DMA,数据总线都由多个设备共享,并进行流量仲裁。这设置了传输数据的最大整体速度。存储器芯片的数据传输速度也可能对数据传输率有贡献。硬件方面,有速度限制。

优化
1. 如果可能,请使用 DMA。
2. 如果只使用 CPU,让 CPU 传输尽可能大的块。
这意味着使用专门用于复制内存的指令。
3. 如果您的 CPU 没有专门的复制指令,请使用处理器的字长进行传输。
如果处理器有 32 位字,则使用 1 个字一次传输 4 个字节,而不是使用 4 个 8 位副本。
4. 减少传输过程中的 CPU 需求和中断。
暂停任何应用程序;如果可能,禁用中断。
5. 分工:让一个核心传输数据,而另一个核心正在执行您的程序。
6. 单核上的线程实际上可能会减慢传输速度,因为操作系统会因为调度而参与其中。线程切换需要时间,这增加了传输时间。

【讨论】:

  • 几乎所有 ISA 后的图形单元都支持总线主控传输,所以是的,可以使用 DMA 而不是 CPU(使用 memcpy)进行复制。另请注意,复制到视频内存通常在写入组合模式下缓存,但读取它通常是未缓存的。
  • 非常感谢@ThomasMatthews,您的 cmets 对我非常有用。稍后我会尝试您的建议,如果我能解决这个问题,我会分享我的解决方案。再次感谢您的帮助。
  • 非常感谢@Calvin 的分析,它教会了我很多关于这个问题的知识。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-10-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多