编解码器简单概念
MediaCodec用来使用底层多媒体编解码器。
宽泛讲编解码器处理输入数据生成输出数据,它的处理是异步的并且使用一系列输入输出buffer。简单层面来说,你请求或者获取空的输入buffer,装满输入之后发送到编解码器处理,在处理结束后编解码器再将数据转换到一个输出的空buffer。最后你请求或者获取装满数据的输出buffer,消耗完其内容数据之后将该buffer释放回编解码器。
1.支持的数据类型
编解码器支持三种数据:压缩的数据、原始的音频数据、原始的视频数据。所有的三种数据都可以通过使用ByteBuffers来处理,但是对于原始的视频数据可以使用Surface来提高编解码器的性能同时可以使用ImageReader获取视频的单个帧,即截图。
对于原始视频数据,当你使用ByteBuffers来处理时,其buffer的排列是依据他们的颜色格式。视频编解码器支持三种颜色格式:
native raw video format:通过COLOR_FormatSurface来标记,可以被用作输入或者输出Surface。
flexible YUV buffers(比如 COLOR_FormatYUV420Flexible):可以用作输入输出Surface以及ByteBuffers模式。
other, specific formats:这些只能使用ByteBuffers模式处理。
2.编解码器的状态
编解码器的理论状态是三种:停止、执行、释放。
停止状态由包含三个子状态:错误、为初始化、配置。
执行状态包含三个子状态:刷新、运行、结束流。
当使用工厂方法获得编解码器对象时,起处于未初始化状态。首先需要新建MMediaFormat对象,使用configure方法进行配置,此时处于配置状态。接着调用start方法此时进入执行状态。在这个状态可以通过缓冲buffer操作过程数据。
执行状态包含三个子状态。当调用start进入刷新子状态,该状态持有所有buffer。一旦第一个输入buffer出队列编解码器就进入运行子状态,这个状态最耗时。当你入队列一个输入buffer带有ENDOFSTREAM标记时,编解码器进入结束流子状态,在这个状态不在接收输入buffer,但依然生成输出buffer。
调用stop方法进入为初始化状态,一旦编解码器使用完成你必须调用release进行释放。
编解码器的使用
以下是如何通过代码使用编解码器。编解码器有两种使用方式:异步使用、同步使用。
1.异步使用
MediaCodec codec = MediaCodec.createByCodecName(name);
MediaFormat mOutputFormat; // member variable
codec.setCallback(new MediaCodec.Callback() {
@Override
void onInputBufferAvailable(MediaCodec mc, int inputBufferId) {
ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId);
// fill inputBuffer with valid data
…
codec.queueInputBuffer(inputBufferId, …);
}
@Override
void onOutputBufferAvailable(MediaCodec mc, int outputBufferId, …) {
ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
// bufferFormat is equivalent to mOutputFormat
// outputBuffer is ready to be processed or rendered.
…
codec.releaseOutputBuffer(outputBufferId, …);
}
@Override
void onOutputFormatChanged(MediaCodec mc, MediaFormat format) {
// Subsequent data will conform to new format.
// Can ignore if using getOutputFormat(outputBufferId)
mOutputFormat = format; // option B
}
@Override
void onError(…) {
…
}
});
codec.configure(format, …);
mOutputFormat = codec.getOutputFormat(); // option B
codec.start();
// wait for processing to complete
codec.stop();
codec.release();
2.同步使用
MediaCodec codec = MediaCodec.createByCodecName(name);
codec.configure(format, …);
MediaFormat outputFormat = codec.getOutputFormat(); // option B
codec.start();
for (;;) {
int inputBufferId = codec.dequeueInputBuffer(timeoutUs);
if (inputBufferId >= 0) {
ByteBuffer inputBuffer = codec.getInputBuffer(…);
// fill inputBuffer with valid data
…
codec.queueInputBuffer(inputBufferId, …);
}
int outputBufferId = codec.dequeueOutputBuffer(…);
if (outputBufferId >= 0) {
ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
// bufferFormat is identical to outputFormat
// outputBuffer is ready to be processed or rendered.
…
codec.releaseOutputBuffer(outputBufferId, …);
} else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
// Subsequent data will conform to new format.
// Can ignore if using getOutputFormat(outputBufferId)
outputFormat = codec.getOutputFormat(); // option B
}
}
codec.stop();
codec.release();