我正在尝试解码文件中的视频,并在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此示例在我的应用程序中工作
答案 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#106和https://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以获取对此的引用。
就我看来,问题出现在awaitNewImage
和https://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;
}