Android MediaCodec在异步模式下编码和解码

Dav*_*arl 14 android video-encoding mediacodec android-5.0-lollipop android-5.1.1-lollipop

我正在尝试解码文件中的视频,并使用API Level 21及更高版本(Android OS 5.0 Lollipop)支持MediaCodec的新异步模式将其编码为不同的格式.

Big Flake,Google的Grafika等网站上以同步模式执行此操作有很多示例,并且StackOverflow上有许多答案,但它们都不支持异步模式.

我不需要在此过程中显示视频.

我相信一般的程序是用一个读取文件MediaExtractor作为MediaCodec(解码器)的输入,允许解码器的输出渲染成一个Surface也是共享输入到MediaCodec(编码器),然后最后写入编码器输出文件通过MediaMuxer.它Surface是在编码器设置期间创建的,并与解码器共享.

我可以将视频解码为a TextureView,但Surface与编码器共享而不是屏幕也没有成功.

MediaCodec.Callback()为我的两个编解码器设置了s.我认为问题在于我不知道在Encoder的回调onInputBufferAvailable()函数中该怎么做.我不知道(或知道如何)将数据从Surface编码器复制到编码器中 - 这应该自动发生(就像在解码器输出上一样codec.releaseOutputBuffer(outputBufferId, true);).然而,我认为onInputBufferAvailable需要打电话codec.queueInputBuffer才能运作.我只是不知道如何设置参数而不从像MediaExtractorDecode端使用的那样获取数据.

如果您有一个打开视频文件MediaCodec示例,对其进行解码,使用异步回调将其编码为不同的分辨率或格式,然后将其保存为文件,请分享您的示例代码.

=== 编辑 ===

下面是我在异步模式下尝试做的同步模式的一个工作示例:ExtractDecodeEditEncodeMuxTest.java:https://android.googlesource.com/platform/cts/+/jb-mr2-release/tests/tests/media /src/android/media/cts/ExtractDecodeEditEncodeMuxTest.java这个例子在我的应用程序中工作

Android MediaCodec

mst*_*sjo 14

我相信你不需要在编码器的onInputBufferAvailable()回调中做任何事情- 你不应该打电话encoder.queueInputBuffer().就像在同步模式下进行Surface输入编码时从未调用encoder.dequeueInputBuffer()encoder.queueInputBuffer()手动一样,也不应该在异步模式下进行.

当您调用decoder.releaseOutputBuffer(outputBufferId, true);(在同步和异步模式下)时,内部(使用Surface您提供的)将输入缓冲区从曲面中取出,将输出渲染到其中,然后将其排列回曲面(到编码器).同步模式和异步模式之间的唯一区别在于缓冲区事件在公共API中的公开方式,但在使用Surface输入时,它使用不同的(内部)API来访问它,因此同步模式和异步模式对于这一点.

所以据我所知(尽管我自己没有尝试过),你应该把onInputBufferAvailable()回调留空为编码器.

编辑: 所以,我自己尝试这样做,它(几乎)就像上面描述的一样简单.

如果编码器输入表面直接配置为解码器的输出(中间没有SurfaceTexture),那么事情就会起作用,同步解码编码循环转换为异步解码.

但是,如果使用SurfaceTexture,则可能会遇到小问题.有一个问题是如何等待帧到达与调用线程相关的SurfaceTexture,请参阅https://android.googlesource.com/platform/cts/+/jb-mr2-release/tests/tests/media /src/android/media/cts/DecodeEditEncodeTest.java#106https://android.googlesource.com/platform/cts/+/jb-mr2-release/tests/tests/media/src/android/media/cts /EncodeDecodeTest.java#104https://android.googlesource.com/platform/cts/+/jb-mr2-release/tests/tests/media/src/android/media/cts/OutputSurface.java#113以获取参考对此.

就我看来,问题awaitNewImage出在https://android.googlesource.com/platform/cts/+/jb-mr2-release/tests/tests/media/src/android/media/cts/ OutputSurface.java#240.如果onFrameAvailable应该在主线程上调用回调,那么如果awaitNewImage调用也在主线程上运行,则会出现问题.如果onOutputBufferAvailable回调也在主线程上调用并且你awaitNewImage从那里调用,那么我们就会遇到一个问题,因为你最终会等待一个回调(用一个wait()阻塞整个线程的回调)直到当前方法才能运行回报.

因此,我们需要确保onFrameAvailable回调与调用的线程不同awaitNewImage.一个非常简单的方法是创建一个新的单独线程,除了为onFrameAvailable回调提供服务之外什么都不做.要做到这一点,你可以这样做:

    private HandlerThread mHandlerThread = new HandlerThread("CallbackThread");
    private Handler mHandler;
...
        mHandlerThread.start();
        mHandler = new Handler(mHandlerThread.getLooper());
...
        mSurfaceTexture.setOnFrameAvailableListener(this, mHandler);
Run Code Online (Sandbox Code Playgroud)

我希望这足以让您能够解决您的问题,如果您需要我编辑其中一个公共示例以在那里实现异步回调,请告诉我.

EDIT2: 此外,由于GL渲染可能是在onOutputBufferAvailable回调中完成的,因此这可能与设置EGL上下文的线程不同.所以在这种情况下,需要在设置它的线程中释放EGL上下文,如下所示:

mEGL.eglMakeCurrent(mEGLDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
Run Code Online (Sandbox Code Playgroud)

并在渲染之前将其重新附加到另一个线程中:

mEGL.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext);
Run Code Online (Sandbox Code Playgroud)

EDIT3: 此外,如果在同一线程上接收到编码器和解码器回调,则onOutputBufferAvailable执行渲染的解码器可以阻止编码器回调被传递.如果它们未被传递,则可以无限制地阻止渲染,因为编码器没有获得返回的输出缓冲区.这可以通过确保在不同的线程上接收视频解码器回调来解决,这避免了onFrameAvailable回调的问题.

我尝试在这之上实现所有这些ExtractDecodeEditEncodeMuxTest,并使其工作看起来很好,看看https://github.com/mstorsjo/android-decodeencodetest.我最初导入了未更改的测试,并转换为异步模式并分别修复了棘手的细节,以便于查看提交日志中的各个修复程序.