三种数据类型
编解码器处理三种数据:压缩数据
、原始音频数据
和原始视频数据
。
所有三种类型的数据都可以使用ByteBuffer进行处理,但是您应该使用原始视频数据的surface来提高编解码器的性能。Surface使用native video buffer
,而无需将它们映射或复制到ByteBuffers;因此,它的效率更高。
在使用Surface时,通常无法访问原始视频数据,但可以使用ImageReader类访问raw视频帧。这可能仍然比使用ByteBuffers更有效,因为一些native buffer可能映射到ByteBuffer#isDirect ByteBuffers。使用ByteBuffer模式时,可以使用Image类和getInput/OutputImage(int)访问原始视频帧。
现在的基本上api版本都是大于21的,所以用的较多的就后面两种:
- Synchronous API using buffers
- Asynchronous API using buffers
Processing Mode | API version <= 20 | API version >= 21 |
---|---|---|
Synchronous API using buffer arrays | Supported | Deprecated |
Synchronous API using buffers | Not Available | Supported |
Asynchronous API using buffers | Not Available | Supported |
Raw Video Buffers
在ByteBuffer模式下,video buffer由MediaFormat::KEY_COLOR_FORMAT来确定。video codec支持三种颜色格式:
native raw video format:
这是由COLOR_FormatSurface指定,可以与输入或输出surface一起使用。flexible YUV buffers:
COLOR_FormatYUV420Flexible,这些可以用于输入/输出surface,也可以在ByteBuffer模式下使用getInput/OutputImage.other, specific formats:
通常仅在ByteBuffer模式下支持这些格式。某些颜色格式是特定于供应商的,可以使用getInput/OutputImage.
MediaCodec的同步方式
java实现
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();
MediaCodec的异步实现方式
java实现
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();
MediaCodec EOS
当需要结束时,必须向编解码器发送EOS信号,可以通过queueInputBuffer调用,指定buffer的flag为BUFFER_FLAG_END_OF_STREAM
,可以在最后一个有效的输入输出buffer上执行,也可以通过指定一个额外的空输入输出buffer设置EOS flag
来执行此操作,如果使用空buffer,时间戳会被忽略。
编解码器将继续返回输出buffer,直到它通过在dequeueOutputBuffer中的BufferInfo中获得相同EOS标志或通过Callback#onOutputBufferAvailable
返回,最终发出EOS信号。这可以在最后一个有效输出buffer上设置,也可以在最后一个有效输出buffer之后的空缓冲区上设置,空buffer的时间戳需要忽略掉。
在发出输入end-of-stream信号后,不要提交额外的输入缓冲区,除非编解码器已经被flushed, or或者stopped and restarted。
Codec-specific Data
一些格式,尤其是AAC音频和MPEG4、H.264和H.265视频格式,需要在实际数据前有一个Codec-specific Data。在处理这种压缩格式时,这些数据必须在start()之后和实际frame数据之前提交到编解码器。在对queueInputBuffer的调用中,必须使用标志BUFFER_FLAG_CODEC_CONFIG来标记这些数据。
Codec-specific data也可以包含在key“csd-0”
、“csd-1”
等在ByteBuffer条目中配置的格式中。这些key始终包含在从MediaExtractor#getTrackFormat
获得的track MediaFormat中。格式中的Codec-specific data在start()时自动提交到编解码器,您不能explicitly提交此数据。如果格式不包含Codec-specific数据,则可以根据格式要求选择使用指定数量的buffer按正确顺序提交。对于H.264 AVC,还可以连接所有Codec-specific data,并将其作为单个codec-config buffer提交给编解码器。
MediaCodec Surface
使用输入Surface
使用Input Surface时,没有可访问的输入buffer,因为buffer会自动从输入到Surface传递到编解码器。调用dequeueInputBuffer将引发IllegalStateException
,getInputBuffers()返回一个不能写入的伪ByteBuffer[]数组。
调用signalEndOfInputStream()
以发出流结束的信号。在此调用之后,输入Surface将立即停止向编解码器submit数据。
使用输出Surface
当使用输出Surface,数据处理与ByteBuffer模式几乎相同;但是,输出buffer将不可访问,并且表示为空值。getOutputBuffer/Image将返回null,getOutputBuffers()将返回仅包含null-s的数组。
使用输出surface,可以选择是否在surface上渲染每个输出buffer,您有三个选择:
- Do not render the buffer: Call
releaseOutputBuffer(bufferId, false)
- Render the buffer with the default timestamp: Call
releaseOutputBuffer(bufferId, true)
- Render the buffer with a specific timestamp: Call
releaseOutputBuffer(bufferId, timestamp)