Android MediaCodec可以同时解码为两个Surface吗?

时间:2016-08-19 02:29:15

标签: android mediacodec mediaextractor grafika

我正在尝试改进应用程序的帧提取。基本上我所做的就是将Grafika的MoviePlayer解决方案与前向搜索和BigFlake的ExtractMpegFramesTest相结合来提取框架。对于提取,我寻找回到前一个关键帧,然后向前解码并仅保存最后一帧。类似这样的(有关更完整的解释,请参阅我的previous question):

decoder.releaseOutputBuffer(decoderStatus, doRender);
    if (doRender) {
        if (VERBOSE) Log.d(TAG, "awaiting decode of frame " + decodeCount);
        outputSurface.awaitNewImage();
        outputSurface.drawImage(false);

        if(extractor.getSampleTime() == mPosition){ 
            Log.d(TAG, "sampleTime: " + extractor.getSampleTime() + " mPosition: " + mPosition + "----- EXTRACTING FRAME");
            long startWhen = System.currentTimeMillis();
            outputSurface.saveFrame();
            long frameSaveTime = System.currentTimeMillis() - startWhen;
            Log.d(TAG, "sampleTime: frame saved in: " + frameSaveTime + " millisecond");
            return;
         }
         decodeCount++;
     }

问题有时是在向后搜索然后向前解码时从extractor.getSampleTime()检索的采样时间似乎与直接搜索中的那个不匹配。

我已经包含了一个日志,以使其更清晰:

position is the seeking position in microsecond
sampleTime: 12112100 -- position: 12139000 ----- FORWARD
sampleTime: 12120441 -- position: 12139000 ----- FORWARD
sampleTime: 12128783 -- position: 12139000 ----- FORWARD
sampleTime: 12137125 -- position: 12139000 ----- FORWARD

sampleTime: 12012000 -- position: 12139000 ----- BACKWARD
sampleTime: 12020341 -- position: 12139000 ----- BACKWARD
sampleTime: 12028683 -- position: 12139000 ----- BACKWARD
sampleTime: 12037025 -- position: 12139000 ----- BACKWARD
sampleTime: 12045366 -- position: 12139000 ----- BACKWARD
sampleTime: 12053708 -- position: 12139000 ----- BACKWARD
sampleTime: 12062050 -- position: 12139000 ----- BACKWARD
sampleTime: 12070391 -- position: 12139000 ----- BACKWARD
sampleTime: 12078733 -- position: 12139000 ----- BACKWARD
sampleTime: 12087075 -- position: 12139000 ----- BACKWARD
sampleTime: 12095416 -- position: 12139000 ----- BACKWARD
sampleTime: 12103758 -- position: 12139000 ----- BACKWARD
sampleTime: 12112100 -- position: 12139000 ----- BACKWARD
sampleTime: 12120441 -- position: 12139000 ----- BACKWARD
sampleTime: 12128783 -- position: 12139000 ----- BACKWARD

正如您所看到的,在向前搜索时,extractor.getSampleTime()可以在找回然后向前解码时到达位置12137125,它只能到达12128783 。我不确定它为什么会发生,但这会导致表示帧和提取帧之间的不匹配。此方法也不是非常有效,因为每次我需要提取帧时,我必须设置EGLSurface并对其进行解码。根据前一个关键帧所需帧的距离,此操作可能需要3到5秒,对于多次提取肯定太长。

我想问一下,是否有可能同时解码器解码到两个表面(一个SurfaceView用于显示,一个EGLSurface用于帧检索),这样我就可以解决这两个问题。这些准确性和性能问题。

我之前尝试过使用FFmpeg检索帧,性能大致相同。如果有一种比使用OpenGL更好的方法来检索帧,我非常愿意尝试。

编辑:经过进一步测试后,我可以匹配两种方法中的extractor.getSampleTime(),即使检索到的帧有时会与显示帧不匹配。

编辑2:关于显示的帧与提取的帧之间的不匹配,它实际上非常简单,但如果你不知道MediaCodec是如何工作的,那么它起初很混乱。我必须重读每一个fadden的评论以更好地理解这个问题(this是给我“啊哈”时刻的那个。)

简而言之,解码器喜欢在吐出任何表示缓冲区之前使用多个缓冲区。因此,当前显示的那个与当前extractor.getSampleTime()位置的那个不同。因此,在显示和提取之间同步的正确值应该是输出缓冲区的presentationTime,如下所示:

mCurrentSampleTime = mBufferInfo.presentationTimeUs;

理解这个有助于解决许多多个神秘问题(例如为什么第一帧不在0位置?)。希望这会对某人有所帮助。

1 个答案:

答案 0 :(得分:1)

不是我问题的具体答案,但我找到了一种方法来改善帧提取时间。基本上,如果您对PNG格式没有任何严格要求,那么只需将输出图像压缩为jpeg,如下所示:

outputBitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos);

这将使用硬件加速而不是PNG中的纯软件压缩,而且速度要快得多。整个操作我得到~600ms,压缩位大约需要200ms。与使用PNG压缩的前5秒相比,这是一个非常巨大的改进。

理论上,如果您不关心透明度,则可以使用Bitmap.Config.RGB_565作为输出图像而不是Bitmap.Config.ARGB_8888来获得更高的性能。但是,在实践中我遇到了两个阻止我这样做的问题:

  • 输出图像的颜色搞砸了。
  • 提取图像实际上需要更长的时间。