23

我正在尝试使以下 MediaExtractor 示例正常工作:

http://bigflake.com/mediacodec/ - ExtractMpegFramesTest.java(需要 4.1,API 16)

我遇到的问题是 outputSurface.awaitNewImage(); 似乎总是抛出 RuntimeException("frame wait timed out"),只要mFrameSyncObject.wait(TIMEOUT_MS)调用超时就会抛出。无论我设置什么TIMEOUT_MSonFrameAvailable()总是在超时发生后立即被调用。我尝试了 50 毫秒和 30000 毫秒,结果是一样的。

似乎onFrameAvailable()在线程忙时无法完成调用,一旦发生超时导致线程代码执行结束,它就可以解析onFrameAvailable()调用。

有没有人设法让这个例子工作,或者知道 MediaExtractor 应该如何处理 GL 纹理?

编辑:在具有 API 4.4 和 4.1.1 的设备上进行了尝试,两者都发生了同样的情况。

编辑2:

感谢fadden,让它在4.4上工作。问题是ExtractMpegFramesWrapper.runTest()调用的方法th.join();阻塞了主线程并阻止了onFrameAvailable()调用被处理。一旦我发表评论th.join();,它就可以在 4.4 上运行。我想也许它ExtractMpegFramesWrapper.runTest()本身应该在另一个线程上运行,所以主线程没有被阻塞。

在调用 4.1.2 时也有一个小问题codec.configure(),它给出了错误:

A/ACodec(2566): frameworks/av/media/libstagefright/ACodec.cpp:1041 CHECK(def.nBufferSize >= size) failed.
A/libc(2566): Fatal signal 11 (SIGSEGV) at 0xdeadbaad (code=1), thread 2625 (CodecLooper)

我通过在通话前添加以下内容解决了这个问题:

format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 0);

然而,我现在在 4.1.1 (Galaxy S2 GT-I9100) 和 4.1.2 (Samsung Galaxy Tab GT-P3110) 上遇到的问题是它们总是将所有帧的 info.size 设置为 0。这是日志输出:

loop
input buffer not available
no output from decoder available
loop
input buffer not available
no output from decoder available
loop
input buffer not available
no output from decoder available
loop
input buffer not available
no output from decoder available
loop
submitted frame 0 to dec, size=20562
no output from decoder available
loop
submitted frame 1 to dec, size=7193
no output from decoder available
loop
[... skipped 18 lines ...]
submitted frame 8 to dec, size=6531
no output from decoder available
loop
submitted frame 9 to dec, size=5639
decoder output format changed: {height=240, what=1869968451, color-format=19, slice-height=240, crop-left=0, width=320, crop-bottom=239, crop-top=0, mime=video/raw, stride=320, crop-right=319}
loop
submitted frame 10 to dec, size=6272
surface decoder given buffer 0 (size=0)
loop
[... skipped 1211 lines ...]
submitted frame 409 to dec, size=456
surface decoder given buffer 1 (size=0)
loop
sent input EOS
surface decoder given buffer 0 (size=0)
loop
surface decoder given buffer 1 (size=0)
loop
surface decoder given buffer 0 (size=0)
loop
surface decoder given buffer 1 (size=0)
loop
[... skipped 27 lines all with size=0 ...]
surface decoder given buffer 1 (size=0)
loop
surface decoder given buffer 0 (size=0)
output EOS
Saving 0 frames took ? us per frame // edited to avoid division-by-zero error

所以没有图像被保存。然而,相同的代码和视频适用于 4.3。我使用的视频是带有“H264 - MPEG-4 AVC (avc1)”视频编解码器和“MPEG AAAC Audio (mp4a)”音频编解码器的 .mp4 文件。

我也尝试了其他视频格式,但它们似乎在 4.1.x 上死得更快,而两者都在 4.3 上工作。

编辑3:

我按照你的建议做了,它似乎正确保存了帧图像。谢谢你。

关于 KEY_MAX_INPUT_SIZE,我尝试不设置,或将其设置为 0、20、200、... 200000000,所有结果都与 info.size=0 相同。

我现在无法在我的布局上将渲染设置为 SurfaceView 或 TextureView。我尝试替换这一行:

mSurfaceTexture = new SurfaceTexture(mTextureRender.getTextureId());

有了这个,surfaceTexture我的 xml 布局中定义的 SurfaceTexture 在哪里:

mSurfaceTexture = textureView.getSurfaceTexture();
mSurfaceTexture.attachToGLContext(mTextureRender.getTextureId());

但它getMessage()==null在第二行引发了一个奇怪的错误。我找不到任何其他方法让它在某种视图上绘制。如何更改解码器以在 Surface/SurfaceView/TextureView 上显示帧而不是保存它们?

4

1 回答 1

16

工作方式SurfaceTexture使这有点棘手。

文档说可用帧回调“在任意线程上调用”。该类SurfaceTexture有一些代码在初始化时执行以下操作(第 318 行):

if (this thread has a looper) {
    handle events on this thread
} else if (there's a "main" looper) {
    handle events on the main UI thread
} else {
    no events for you
}

frame-available 事件通过通常的Looper/Handler机制传递到您的应用程序。该机制只是一个消息队列,这意味着线程需要坐在Looper事件循环中等待它们到达。问题是,如果你在睡觉awaitNewImage(),你就没有在看Looper队列。所以事件到了,但没有人看到它。最终awaitNewImage()超时,线程返回查看事件队列,在那里它立即发现待处理的“新帧”消息。

因此,诀窍是确保帧可用事件到达与awaitNewImage(). 在ExtractMpegFramesTest示例中,这是通过在新创建的线程(请参阅ExtractMpegFramesWrapper类)中运行测试来完成的,该线程没有Looper. (出于某种原因,执行 CTS 测试的线程有一个循环器。)可用帧事件到达主 UI 线程。

更新(对于“编辑 3”):我有点难过忽略“大小”字段有所帮助,但在 4.3 之前,很难预测设备的行为方式。

如果您只想显示帧,请将Surface您从SurfaceViewor中获得的内容传递TextureViewMediaCodec解码器configure()调用。然后你就不用搞砸了SurfaceTexture——帧会在你解码时显示出来。有关示例,请参见Grafika中的两个“播放视频”活动。

如果你真的想通过 a SurfaceTexture,你需要更改 CodecOutputSurface 以渲染到窗口表面而不是 pbuffer。(屏幕外渲染已完成,因此我们可以glReadPixels()在无头测试中使用。)

于 2014-03-17T17:19:11.917 回答