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

标签 android android-5.0-lollipop video-encoding android-mediacodec android-5.1.1-lollipop

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

在诸如 Big Flake 之类的网站上有许多在同步模式下执行此操作的示例。 , 谷歌的 Grafika ,以及 StackOverflow 上的几十个答案,但没有一个支持异步模式。

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

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

我可以将视频解码为 TextureView ,但共享 Surface用编码器代替屏幕一直没有成功。

我设置 MediaCodec.Callback() s 用于我的两个编解码器。我认为一个问题是我不知道在编码器的回调中做什么 onInputBufferAvailable()功能。我不知道(或知道如何)从 Surface 复制数据。进入编码器 - 这应该自动发生(就像在解码器输出上所做的那样 codec.releaseOutputBuffer(outputBufferId, true); )。然而,我相信 onInputBufferAvailable需要调用 codec.queueInputBuffer为了发挥作用。我只是不知道如何在不从 MediaExtractor 之类的东西获取数据的情况下设置参数在解码端使用。

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

=== 编辑 ===

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

Android MediaCodec

最佳答案

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

当您调用 decoder.releaseOutputBuffer(outputBufferId, true); (在同步和异步模式下),这在内部(使用您提供的 Surface)从表面上将输入缓冲区出列,将输出渲染到其中,并将其排回表面(到编码器)。同步和异步模式之间的唯一区别在于缓冲区事件如何在公共(public) 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供引用。

据我所知,问题在于 awaitNewImagehttps://android.googlesource.com/platform/cts/+/jb-mr2-release/tests/tests/media/src/android/media/cts/OutputSurface.java#240 .如果 onFrameAvailable回调应该在主线程上调用,如果 awaitNewImage call 也在主线程上运行。如果 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);

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

EDIT2:
此外,由于 GL 渲染可能在 onOutputBufferAvailable 内完成。回调,这可能是与设置 EGL 上下文的线程不同的线程。因此,在这种情况下,需要在设置它的线程中释放 EGL 上下文,如下所示:
mEGL.eglMakeCurrent(mEGLDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);

并在渲染之前将其重新附加到另一个线程中:
mEGL.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext);

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

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

关于Android MediaCodec 在异步模式下编码和解码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35884600/

相关文章:

java - Android 5.x ClassNotFoundException 在 6.0+ 上运行良好

c++ - 使用 Sink Writer 编码音频

android - 如何在没有用户操作的情况下更新第 3 方 Android 应用程序?

Android L 获取铃声模式类型 : priority and sound

android - 将 Recyclerview 与 DiffUtil.ItemCallback 一起使用不会滚动到顶部?

android - 方案主机无法在 android Lollipop 上运行,单击链接以打开应用程序

ffmpeg - 图像序列到视频质量

compression - H.264 或视频编码器通常如何计算两帧的残差图像?

java - 访问远程服务功能

java - 搜索栏监听器