2013-03-19 53 views
5

我的目标是使用Android MediaCodec解码视频流,然后使用输出图像进行本机代码中的进一步图像处理。使用硬件加速的本机代码访问冲突Android MediaCodec解码器

平台:华硕tf700t android 4.1.1。 测试流:H.264全高清@ 24 frm/s

随着Tegra-3 SoC的内部,我计算硬件支持视频解码。在功能上,我的应用程序的行为如预期:我确实可以访问解码器图像 并正确处理它们。但是,我经历了非常高的解码器CPU负载。

在下面的实验中,过程/螺纹加载通过adb shell中的“top -m 32 -t”来测量。为了从“顶部”获得可靠的输出,通过运行几个线程永久以最低优先级循环,所有4个cpu内核被强制激活。这通过反复执行“cat/sys/devices/system/cpu/cpu [0-3]/online”来确认。为了简单起见,只有视频解码,没有音频;并且没有时序控制,所以解码器运行得尽可能快。

第一个实验:运行应用程序,调用JNI处理函数,但所有进一步处理调用都被注释掉。结果:

  • 吞吐量:25 FRM /秒
  • 1%应用过程/系统/ bin中的螺纹Binder_3的
  • 24%负载/媒体服务器

它的螺纹VideoDecoder的负载似乎解码速度是CPU限制的(四核CPU的25%)... 启用输出处理时,解码图像是正确的,并且应用程序工作。唯一的问题:用于解码的CPU负载过高。

经过大量的实验后,我考虑给MediaCodec一个表面来绘制其结果。在所有其他方面,代码是相同的。结果:

  • 吞吐量55 FRM /秒(很好!!)的应用程序的线程的VideoDecoder
  • 2%负载
  • 过程/系统/ bin中/媒体服务器的螺纹媒体服务器的
  • 1%负载

确实,视频显示在提供的Surface上。由于几乎没有任何CPU负载,这必须是硬件加速...

看来,MediaCodec是只使用硬件加速,如果提供表面?

到目前为止,这么好。我已经倾向于将Surface用作解决方案(不是必需的,但在某些情况下甚至是非常好的)。但是,如果提供表面,我无法访问输出图像!结果是本机代码中的访问冲突。

这真令我困惑!我没有看到访问限制的任何概念,或文档http://developer.android.com/reference/android/media/MediaCodec.html中的任何内容。 也没有在这方面提到在谷歌I/O介绍http://www.youtube.com/watch?v=RQws6vsoav8

所以:如何使用硬件加速的Android MediaCodec解码器和访问本地代码中的图像?如何避免访问违规?任何帮助都附带了!还有任何解释或提示。

我很肯定MediaExtractor和MediaCodec使用得当,因为应用程序 功能正常(只要我没有提供Surface)。 它仍然是相当实验性,和良好的API设计是待办事项列表;-)

注意两个实验之间的唯一区别是可变的mSurface上:空或“mDecoder.configure的实际表面 (mediaFormat ,mSurface,null,0);“

初始化代码:

mExtractor = new MediaExtractor(); 
mExtractor.setDataSource(mPath); 

// Locate first video stream 
for (int i = 0; i < mExtractor.getTrackCount(); i++) { 
    mediaFormat = mExtractor.getTrackFormat(i); 
    String mime = mediaFormat.getString(MediaFormat.KEY_MIME); 
    Log.i(TAG, String.format("Stream %d/%d %s", i, mExtractor.getTrackCount(), mime)); 
    if (streamId == -1 && mime.startsWith("video/")) { 
     streamId = i; 
    } 
} 

if (streamId == -1) { 
    Log.e(TAG, "Can't find video info in " + mPath); 
    return; 
} 

mExtractor.selectTrack(streamId); 
mediaFormat = mExtractor.getTrackFormat(streamId); 

mDecoder = MediaCodec.createDecoderByType(mediaFormat.getString(MediaFormat.KEY_MIME)); 
mDecoder.configure(mediaFormat, mSurface, null, 0); 

width = mediaFormat.getInteger(MediaFormat.KEY_WIDTH); 
height = mediaFormat.getInteger(MediaFormat.KEY_HEIGHT); 
Log.i(TAG, String.format("Image size: %dx%d format: %s", width, height, mediaFormat.toString())); 
JniGlue.decoutStart(width, height); 

解码器环路(在单独的线程中运行):

ByteBuffer[] inputBuffers = mDecoder.getInputBuffers(); 
ByteBuffer[] outputBuffers = mDecoder.getOutputBuffers(); 

while (!isEOS && !Thread.interrupted()) { 
    int inIndex = mDecoder.dequeueInputBuffer(10000); 
    if (inIndex >= 0) { 
     // Valid buffer returned 
     int sampleSize = mExtractor.readSampleData(inputBuffers[inIndex], 0); 
     if (sampleSize < 0) { 
      Log.i(TAG, "InputBuffer BUFFER_FLAG_END_OF_STREAM"); 
      mDecoder.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); 
      isEOS = true; 
     } else { 
      mDecoder.queueInputBuffer(inIndex, 0, sampleSize, mExtractor.getSampleTime(), 0); 
      mExtractor.advance(); 
     } 
    } 

    int outIndex = mDecoder.dequeueOutputBuffer(info, 10000); 
    if (outIndex >= 0) { 
     // Valid buffer returned 
     ByteBuffer buffer = outputBuffers[outIndex]; 
     JniGlue.decoutFrame(buffer, info.offset, info.size); 
     mDecoder.releaseOutputBuffer(outIndex, true); 
    } else { 
     // Some INFO_* value returned 
     switch (outIndex) { 
     case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED: 
      Log.i(TAG, "RunDecoder: INFO_OUTPUT_BUFFERS_CHANGED"); 
      outputBuffers = mDecoder.getOutputBuffers(); 
      break; 
     case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED: 
      Log.i(TAG, "RunDecoder: New format " + mDecoder.getOutputFormat()); 
      break; 
     case MediaCodec.INFO_TRY_AGAIN_LATER: 
      // Timeout - simply ignore 
      break; 
     default: 
      // Some other value, simply ignore 
      break; 
     } 
    } 

    if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { 
     Log.d(TAG, "RunDecoder: OutputBuffer BUFFER_FLAG_END_OF_STREAM"); 
     isEOS = true; 
    } 
} 
+0

仍然没有解决方案。任何建议仍然受欢迎。此外还提供实验以增加理解的建议。任何人使用MediaCodec获得硬件解码工作?也许在其他平台上? – Bram 2013-03-25 10:53:51

+0

布拉姆,我试图解决完全相同的问题。看起来这种放缓不是解码缓冲区的多个副本。当解码数据意图呈现给本地表面时,它看起来有一些直接的数据路径,它使用TILER(平铺渲染)。当你需要访问完整的YUV帧(例如你想访问已解码的缓冲区)时,解码器需要完成一些额外的任务,比如将所有数据渲染到内存缓冲区并复制,这会使得缓慢。我从字面上浪费了一生的时间来解决这个问题,但似乎没有什么可以解决的。 – Pavel 2013-06-23 21:20:05

+0

更重要的是,在我的情况下,我有一个720p @ 30fps,我无法实时解码,而本地播放器没有问题付费。 – Pavel 2013-06-23 21:21:33

回答

3

如果配置的输出表面,解码的数据被写入到该可被用作一个OpenGL ES纹理(通过“外部纹理”扩展名)的图形缓冲器。硬件的各个部分以他们喜欢的格式获取数据,并且CPU不必复制数据。

如果您未配置曲面,则输出会进入java.nio.ByteBuffer。至少有一个缓冲区副本可以将数据从MediaCodec分配的缓冲区中获取到您的ByteByffer,并且可能是另一个副本,以便将数据返回到您的JNI代码中。我期望你看到的是开销成本,而不是软件解码成本。

可能能够通过输出发送到SurfaceTexture,撕心裂肺到FBO或p缓冲器,然后用glReadPixels提取数据,以提高问题。如果您从本地代码读入“直接”ByteBuffer或调用glReadPixels,则可以减少JNI开销。这种方法的缺点是您的数据将采用RGB而不是YCbCr。 (OTOH,如果您想要的转换可以在GLES 2.0片段着色器来表达,你可以得到GPU做的工作,而不是CPU的。)

正如另一个答案指出,在不同的设备上输出ByteBuffer数据解码器以不同的格式,如果可移植性对您很重要,那么在软件中解释数据可能不可行。

编辑:Grafika现在有一个使用GPU做图像处理的例子。您可以看到演示视频here

+0

谢谢!所以你用@fadden说,使用一个表面我们互斥来访问ByteBuffer。缺少[dequeueOutputBuffer]中的文档(http://developer.android.com/reference/android/media/MediaCodec.html#dequeueOutputBuffer%28android.media.MediaCodec.BufferInfo,%20long%29)?!?!那么,对我而言,我需要YCbCr的本地代码,所以移动到GLES并没有帮助。此外,表面仅用于测试。关于复制数据?那需要40ms(25 frm/s)?在我的本地代码中,我可以在11 ms内完成此操作。所以,仍然太多的CPU负载。对?顺便说一句。便携性不是我最关心的问题。 – Bram 2013-04-09 12:31:11

+1

'MediaCodec'文档可能会更好。很难知道所有时间在哪里都看不到你的设备正在做什么 - 它们都有点不同。 Surface所使用的“原生”格式可能是疯狂的,他们会在通往ByteBuffer的路上将其转码为更简单的YUV。从25fps到55fps是40-18 = 22ms的差异 - 两个缓冲区副本。有时在logcat输出中,您可以看到它打开(或不打开)硬件解码器设备。无论如何,我没有看到你在做什么错。 – fadden 2013-04-09 16:47:00

+0

检查logcat输出。与没有表面的情况相比,它不会显示任何差异(当启动和停止解码器时确实会显示一些信息)。因此,这证实了您的陈述:我们的应用程序不会受软件解码的影响,而是来自开销。所以这关闭了当前的话题。下一个话题显然将是:如何减少这种开销?如果android本身可以通过几乎0的CPU负载将图像显示到显示器上,那么我怎样才能在我的应用程序中使用几乎0的cpu负载来获取它们? – Bram 2013-04-10 13:24:43

0

我使用的Nexus 4 mediacodec API和得到QOMX_COLOR_FormatYUV420PackedSemiPlanar64x32Tile2m8ka的输出颜色格式。我认为这种格式是一种硬件格式,只能通过硬件渲染呈现。有趣的是,当我使用null和实际Surface为MediaCodec配置表面时,输出缓冲区长度将分别变为实际值和0。我不知道为什么。我认为你可以在不同设备上做一些实验以获得更多结果。 关于硬件加速可以看到 http://www.saschahlusiak.de/2012/10/hardware-acceleration-on-sgs2-with-android-4-0/

+0

感谢您暗示色彩格式。没有表面,我的'mDecoder.getOutputFormat()。getInteger(“color-format”)'是19(COLOR_FormatYUV420Planar in [link](http://developer.android.com/reference/android/media/MediaCodecInfo.CodecCapabilities.html ))。对于表面,它是256.不知道这意味着什么......进一步,对于实际的Surface,info.size变为0.显然,我不应该尝试在JniGlue.decoutFrame()中读取大小为0的缓冲区。这可以解释这次事故。但仍然...提供表面只是解决方案,让硬件解码运行... – Bram 2013-04-04 16:02:34

+0

输出到表面,你不会在'ByteBuffer'中得到任何数据。你仍然可以从'dequeueOutputBuffer()'得到一个索引,这样你就可以得到一个框架可用的通知,并选择是否用'render' arg把它渲染到'releaseOutputBuffer()'。无法渲染Surface的情况下无法触摸实际位。 – fadden 2013-04-04 21:38:12

+0

是否设置了decoder.configure(format,null,null,0); OR decoder.configure(format,surface,null,0);我得到的颜色格式是相同的 - COLOR_QCOM_FormatYUV420SemiPlanar - 恒定值:2141391872(0x7fa30c00)Nexus 7和COLOR_TI_FormatYUV420PackedSemiPlanar - 恒定值:2130706688(0x7f000100)Galaxy Nexus。为什么? – Harkish 2013-10-14 20:47:25