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

时间:2016-03-09 06:34:57

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

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

Big Flake,Google Grafika等网站上的同步模式和StackOverflow上的几十个答案中有很多例子可以执行此操作,但它们都不支持异步模式。

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

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

我可以将视频解码为TextureView,但是使用编码器而不是屏幕共享Surface并未成功。

我为我的两个编解码器设置了MediaCodec.Callback()。我认为问题在于我不知道在Encoder的回调函数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

2 个答案:

答案 0 :(得分:12)

我相信您不应该在编码器的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#104以及https://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调用也在主线程上运行,则会出现问题。如果在主线程上调用onOutputBufferAvailable回调,并且从那里调用awaitNewImage,我们就会遇到问题,因为您最终会等待回调({{1}阻止整个线程)在当前方法返回之前无法运行。

因此,我们需要确保wait()回调与调用onFrameAvailable的回调不同。一个非常简单的方法是创建一个新的单独线程,除了为awaitNewImage回调提供服务之外什么都不做。为此,您可以执行以下操作:这样:

onFrameAvailable

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

<强> EDIT2: 此外,由于GL渲染可能是在 private HandlerThread mHandlerThread = new HandlerThread("CallbackThread"); private Handler mHandler; ... mHandlerThread.start(); mHandler = new Handler(mHandlerThread.getLooper()); ... mSurfaceTexture.setOnFrameAvailableListener(this, mHandler); 回调中完成的,因此这可能与设置EGL上下文的线程不同。因此,在这种情况下,需要在设置它的线程中释放EGL上下文,如下所示:

onOutputBufferAvailable

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

mEGL.eglMakeCurrent(mEGLDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);

<强> EDIT3: 另外,如果在同一线程上接收到编码器和解码器回调,则执行渲染的解码器mEGL.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext); 可以阻止编码器回调被传递。如果它们没有交付,则可以无限制地阻止渲染,因为编码器不会返回输出缓冲区。这可以通过确保在不同的线程上接收视频解码器回调来解决,这可以避免onOutputBufferAvailable回调的问题。

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

答案 1 :(得分:1)

还可以在MediaEncoder中设置处理程序。

---&GT; AudioEncoderCallback(aacSamplePreFrameSize),mHandler);

MyAudioCodecWrapper myMediaCodecWrapper;

public MyAudioEncoder(long startRecordWhenNs){
    super.startRecordWhenNs = startRecordWhenNs;
}

@RequiresApi(api = Build.VERSION_CODES.M)
public MyAudioCodecWrapper prepareAudioEncoder(AudioRecord _audioRecord , int aacSamplePreFrameSize)  throws Exception{
    if(_audioRecord==null || aacSamplePreFrameSize<=0)
        throw new Exception();

    audioRecord = _audioRecord;
    Log.d(TAG, "audioRecord:" + audioRecord.getAudioFormat() + ",aacSamplePreFrameSize:" + aacSamplePreFrameSize);

    mHandlerThread.start();
    mHandler = new Handler(mHandlerThread.getLooper());

    MediaFormat audioFormat = new MediaFormat();
    audioFormat.setString(MediaFormat.KEY_MIME, MIMETYPE_AUDIO_AAC);
    //audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE );
    audioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
    audioFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, audioRecord.getSampleRate());//44100
    audioFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, audioRecord.getChannelCount());//1(單身道)
    audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, 128000);
    audioFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 16384);
    MediaCodec codec = MediaCodec.createEncoderByType(MIMETYPE_AUDIO_AAC);
    codec.configure(audioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
    codec.setCallback(new AudioEncoderCallback(aacSamplePreFrameSize),mHandler);
    //codec.start();

    MyAudioCodecWrapper myMediaCodecWrapper = new MyAudioCodecWrapper();
    myMediaCodecWrapper.mediaCodec = codec;

    super.mediaCodec = codec;

    return myMediaCodecWrapper;

}