因近期工作调整,关于Mediacodec部分的翻译会暂停,后续有时间一定补上,非常抱歉。
本文章为根据Android Mediacodec官方英文版的原创翻译,转载请注明出处:http://www.cnblogs.com/xiaoshubao/p/5368183.html
从API 16开始,Android提供了Mediacodec类以便开发者更加灵活的处理音视频的编解码,较MediaPlay提供了更加完善、丰富的操作接口,具体参见API原始地址。
因为原始文章较长且没有找到对应较为完善的中文说明,所以自己尝试来完成这项工作,主要目的是加深自己对该类的理解也同时希望对其他朋友有所帮助。
关于使用 Mediacodec较好的参考:
(1)DEMO1,由这位作者编写,直接将"DecodeActivity.java"类拷贝到自己的项目中就可以使用。
(2)英文案例、讲解学习参考资料。
文章开始前有几点说明:
(1)本人也非专业的翻译人员且能力有限,文章中部分由{.....}包含的内容翻译可能存在问题请见谅,文章有不足之处请大家指正;
(2)本次翻译分为两部分,此文章为第一部分针对该类的说明内容进行翻译,后续将提供第二部分的翻译主要内容为属性、方法的定义及说明,后面将考虑与MediaCodec相关的类,例如MediaExtractor是否进行翻译;
(3)随着项目的推进,将从项目中总结一些DMEO并发布。
翻译开始:
MediaCodec class can be used to access low-level media codecs, i.e. encoder/decoder components. It is part of the Android low-level multimedia support infrastructure (normally used together withMediaExtractor
, MediaSync
, MediaMuxer
, MediaCrypto
, MediaDrm
, Image
, Surface
, and AudioTrack
.)
MediaCodec类可用于访问Android底层的媒体编解码器,例如,编码/解码组件。它是Android为多媒体支持提供的底层接口的一部分(通常与MediaExtractor, MediaSync, MediaMuxer, MediaCrypto, MediaDrm, Image, Surface, 以及AudioTrack一起使用)。
In broad terms, a codec processes input data to generate output data. It processes data asynchronously and uses a set of input and output buffers. At a simplistic level, you request (or receive) an empty input buffer, fill it up with data and send it to the codec for processing. The codec uses up the data and transforms it into one of its empty output buffers. Finally, you request (or receive) a filled output buffer, consume its contents and release it back to the codec.
从广义上讲,一个编解码器通过处理输入数据来产生输出数据。它通过异步方式处理数据,并且使用了一组输入输出buffers。在简单层面,你请求(或接收)到一个空的输入buffer,向里面填满数据并将它传递给编解码器处理。这个编解码器将使用完这些数据并向所有空的输出buffer中的一个填充这些数据。最终,你请求(或接受)到一个填充了数据的buffer,你可以使用其中的数据内容,并且在使用完后将其释放回编解码器。
Data Types
数据类型
Codecs operate on three kinds of data: compressed data, raw audio data and raw video data. All three kinds of data can be processed using ByteBuffers
, but you should use a Surface
for raw video data to improve codec performance. Surface uses native video buffers without mapping or copying them to ByteBuffers; thus, it is much more efficient. You normally cannot access the raw video data when using a Surface, but you can use the ImageReader
class to access unsecured decoded (raw) video frames. This may still be more efficient than using ByteBuffers, as some native buffers may be mapped into direct ByteBuffers. When using ByteBuffer mode, you can access raw video frames using the Image
class and getInput
/OutputImage(int)
.
编解码器处理三种类型的数据:压缩数据,原始音频数据,原始视频数据。上述三种数据都可以通过ByteBuffers进行处理,但是你需要原始视频数据提供一个Surface来为提高编解码体验(译者注:显示视频图像)。Surface直接使用本地视频数据buffers,而不是通过映射或复制的方式;因此,这样做的显得更加高效。通常在使用Surface的时候你不能够直接访问原始视频数据,{但是你可以使用ImageReader类来访问不可靠的解码后(或原始)的视频帧}。这可能仍然比使用ByteBuffers更加有效率,一些原始的buffers可能已经映射到了 direct ByteBuffers。当使用ByteBuffer模式,你可以通过使用Image类和getInput/OutputImage(int)来访问到原始视频数据帧。
Compressed Buffers
压缩Buffers
Input buffers (for decoders) and output buffers (for encoders) contain compressed data according to the format's type. For video types this is a single compressed video frame. For audio data this is normally a single access unit (an encoded audio segment typically containing a few milliseconds of audio as dictated by the format type), but this requirement is slightly relaxed in that a buffer may contain multiple encoded access units of audio. In either case, buffers do not start or end on arbitrary byte boundaries, but rather on frame/access unit boundaries.
输入的buffers(用于解码)和输出的buffers(用于编码)包含的压缩数据是由媒体格式决定的。针对视频类型是一个压缩的单帧。针对音频数据通常是一个单个可访问单元(一个编码后的音频区段通常包含由特定格式类型决定的几毫秒音频数据),但这种通常也不是十分严格,一个buffer可能包含多个可访问的音频单元。在这两种情况下,buffers通常不开始或结束于任意的字节边界,而是结束于帧/可访问单元的边界。
Raw Audio Buffers
原始音频Buffers
Raw audio buffers contain entire frames of PCM audio data, which is one sample for each channel in channel order. Each sample is a 16-bit signed integer in native byte order.
原始的音频数据buffers包含整个PCM音频帧数据,这是在通道顺序下每一个通道的样本。每一个样本就是一个 16-bit signed integer in native byte order。
1 short[] getSamplesForChannel(MediaCodec codec, int bufferId, int channelIx) {
2 ByteBuffer outputBuffer = codec.getOutputBuffer(bufferId);
3 MediaFormat format = codec.getOutputFormat(bufferId);
4 ShortBuffer samples = outputBuffer.order(ByteOrder.nativeOrder()).asShortBuffer();
5 int numChannels = formet.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
6 if (channelIx < 0 || channelIx >= numChannels) {
7 return null;
8 }
9 short[] res = new short[samples.remaining() / numChannels];
10 for (int i = 0; i < res.length; ++i) {
11 res[i] = samples.get(i * numChannels + channelIx);
12 }
13 return res;
14 }
Raw Video Buffers
原始视频Buffers
In ByteBuffer mode video buffers are laid out according to their color format. You can get the supported color formats as an array fromgetCodecInfo()
.
getCapabilitiesForType(…)
.
colorFormats
. Video codecs may support three kinds of color formats:
ByteBuffer模式下视频buffers的的展现是由他们的 color format确定的。你可以通过调用 getCodecInfo().getCapabilitiesForType(…).colorFormats方法获得其支持的颜色格式数组。视频编解码器可能支持三种类型的颜色格式:
-
native raw video format: This is marked by
COLOR_FormatSurface
and it can be used with an input or output Surface. - 本地原始视频格式:被COLOR_FormatSurface标记,其可与输入或输出Surface一起使用。
-
flexible YUV buffers (such as
COLOR_FormatYUV420Flexible
): These can be used with an input/output Surface, as well as in ByteBuffer mode, by usinggetInput
/OutputImage(int)
. 灵活的YUV buffers(例如COLOR_FormatYUV420Flexible):这些与输入/输出Surface一起使用,以及在ByteBuffer模式中,通过调用getInput/OutputImage(int)方法
-
other, specific formats: These are normally only supported in ByteBuffer mode. Some color formats are vendor specific. Others are defined in
MediaCodecInfo.CodecCapabilities
. For color formats that are equivalent to a flexible format, you can still usegetInput
/OutputImage(int)
. - 其他,具体的格式:通常只在ByteBuffer模式下被支持。有些颜色格式是特定供应商指定的。其他的一些被定义在 MediaCodecInfo.CodecCapabilities中。颜色格式是一个很灵活的格式,你仍然可以使用 getInput/OutputImage(int)方法。
All video codecs support flexible YUV 4:2:0 buffers since LOLLIPOP_MR1
.
从LOLLIPOP_MR1 API起所有视频编解码器支持灵活的YUV 4:2:0 buffers.
States
状态
During its life a codec conceptually exists in one of three states: Stopped, Executing or Released. The Stopped collective state is actually the conglomeration of three states: Uninitialized, Configured and Error, whereas the Executing state conceptually progresses through three sub-states: Flushed, Running and End-of-Stream.
从概念上讲在整个生命周期中编解码器对象存在于三种状态之一:Stopped, Executing 或 Released。整体的Stoped状态实际是由三种状态的集成:Uninitialized, Configured以及 Error,而从概念上将Executing状态的执行时通过三个子状态:Flushed, Running 以及 End-of-Stream。
When you create a codec using one of the factory methods, the codec is in the Uninitialized state. First, you need to configure it via configure(…)
, which brings it to the Configured state, then callstart()
to move it to the Executing state. In this state you can process data through the buffer queue manipulation described above.
当你使用工厂方法之一创建一个编解码器的时候,它的状态是处于Uninitialized状态。首先,你需要通过configure(…)方法配置它,以此进入Configured 状态。然后,通过调用start()方法转入Executing 状态。在这个状态下你可以通过上述buffer队列操作过程数据。
The Executing state has three sub-states: Flushed, Running and End-of-Stream. Immediately after start()
the codec is in the Flushed sub-state, where it holds all the buffers. As soon as the first input buffer is dequeued, the codec moves to the Running sub-state, where it spends most of its life. When you queue an input buffer with the end-of-stream marker, the codec transitions to the End-of-Stream sub-state. In this state the codec no longer accepts further input buffers, but still generates output buffers until the end-of-stream is reached on the output. You can move back to the Flushed sub-state at any time while in the Executing state using flush()
.
Executing状态包含三个子状态: Flushed, Running 以及 End-of-Stream。在调用start()方法后编解码器立即进入Flushed 的子状态,其同时包含所有的buffers。当第一个输入buffer一旦出队列,编解码器就转入Running 的子状态,这个状态占了编解码器的大部分生命周期时间。当你以end-of-stream marker标记一个入队列的输入buffer,则编解码器就转入End-of-Stream 子状态。在这个状态,编解码器不在接收以后传入的输入buffers,但它仍然产生输出buffers直到输出buffer到达end-of-stream状态。你可以在Executing状态的任何时候通过调用flush()状态返回Flushed 的子状态。
Call stop()
to return the codec to the Uninitialized state, whereupon it may be configured again. When you are done using a codec, you must release it by calling release()
.
或者通过调用stop()方法返回编解码器的Uninitialized 状态,因此这个编解码器需要再次configured 。当你使用完编解码器后,你必须调用release()方法释放其资源.
On rare occasions the codec may encounter an error and move to the Error state. This is communicated using an invalid return value from a queuing operation, or sometimes via an exception. Callreset()
to make the codec usable again. You can call it from any state to move the codec back to the Uninitialized state. Otherwise, call release()
to move to the terminal Released state.
在极少情况下编解码器可能会遇到错误并进入Error 状态。这个错误可能是在队列操作时返回一个错误的值或者有时候产生了一个异常导致的。通过调用reset()方法使编解码器再次可用。你可以在任何状态调用reset()方法使编解码器返回Uninitialized 状态。否则,调用release()方法进入最终的Released 状态。
Creation
创建
Use MediaCodecList
to create a MediaCodec for a specific MediaFormat
. When decoding a file or a stream, you can get the desired format from MediaExtractor.getTrackFormat
. Inject any specific features that you want to add using MediaFormat.setFeatureEnabled
, then call MediaCodecList.findDecoderForFormat
to get the name of a codec that can handle that specific media format. Finally, create the codec using createByCodecName(String)
.
通过MediaCodecList创建一个指定MediaFormat的MediaCodec对象。在解码文件或流时,你可以通过调用MediaExtractor.getTrackFormat方法获得所需的格式。通过调用MediaFormat.setFeatureEnabled方法你可以注入任意想要添加的特定特性,然后调用MediaCodecList.findDecoderForFormat方法获得可以处理这种特定媒体格式的编解码器的名字。最后,通过调用createByCodecName(String)方法创建一个编解码器。
Note: On LOLLIPOP
, the format to MediaCodecList.findDecoder
/EncoderForFormat
must not contain a frame rate. Use format.setString(MediaFormat.KEY_FRAME_RATE, null)
to clear any existing frame rate setting in the format.
注意,在API LOLLIPOP上,传递给MediaCodecList.findDecoder/EncoderForFormat的格式必须不能包含帧率。通过调用format.setString(MediaFormat.KEY_FRAME_RATE, null)方法清除任何存在于当前格式中的帧率。
You can also create the preferred codec for a specific MIME type using createDecoder
/EncoderByType(String)
. This, however, cannot be used to inject features, and may create a codec that cannot handle the specific desired media format.
你也可以通过调用createDecoder/EncoderByType(String)方法创建一个首选的MIME类型的编解码器。然而,不能够用于注入特性,以及创建了一个不能处理期望的特定媒体格式的编解码器。
Creating secure decoders
创建secure 解码器
On versions KITKAT_WATCH
and earlier, secure codecs might not be listed in MediaCodecList
, but may still be available on the system. Secure codecs that exist can be instantiated by name only, by appending ".secure"
to the name of a regular codec (the name of all secure codecs must end in ".secure"
.) createByCodecName(String)
will throw an IOException
if the codec is not present on the system.
在版本API KITKAT_WATCH 及以前,secure 编解码器在MediaCodecList中没有列出来,但是仍然可以在这个系统中使用。secure 编解码器的存在只能够通过名字实例化,通过在通常的编解码器添加".secure"(所有的secure 解码器名称必须以".secure"结尾),如果系统上不存在指定的编解码器则createByCodecName(String)方法将抛出一个IOException 异常。
From LOLLIPOP
onwards, you should use the FEATURE_SecurePlayback
feature in the media format to create a secure decoder.
从API LOLLIPOP版本及以后,你可以使用FEATURE_SecurePlayback属性在媒体格式中创建一个secure 编解码器。
Initialization
初始化
After creating the codec, you can set a callback using setCallback
if you want to process data asynchronously. Then, configure the codec using the specific media format. This is when you can specify the output Surface
for video producers – codecs that generate raw video data (e.g. video decoders). This is also when you can set the decryption parameters for secure codecs (seeMediaCrypto
). Finally, since some codecs can operate in multiple modes, you must specify whether you want it to work as a decoder or an encoder.
在创建了编解码器后,如果你想异步地处理数据那么可以通过setCallback方法设置一个回调方法。然后,通过指定的媒体格式configure 这个编解码器。这段时间你可以为视频原始数据产生者(例如视频解码器)指定输出Surface。此时你也可以为secure 编解码器设置解码参数(详见MediaCrypto) 。最后,因为有些编解码器可以操作于多种模式,你必须指定是想让他作为一个解码器或编码器运行。
Since LOLLIPOP
, you can query the resulting input and output format in the Configured state. You can use this to verify the resulting configuration, e.g. color formats, before starting the codec.
从API LOLLIPOP起,你可以在Configured 状态查询输入和输出格式的结果。在开始编解码前你可以通过这个结果来验证配置的结果,例如,颜色格式。
If you want to process raw input video buffers natively with a video consumer – a codec that processes raw video input, such as a video encoder – create a destination Surface for your input data using createInputSurface()
after configuration. Alternately, set up the codec to use a previously created persistent input surface by calling setInputSurface(Surface)
.
如果你想通过视频处理者处理原始输入视频buffers,一个处理原始视频输入的编解码器,例如视频编码器,在配置完成后通过调用createInputSurface()方法为你的输入数据创建一个目标Surface。通过先前创建的persistent input surface调用setInputSurface(Surface)配置这个编解码器。
Codec-specific Data
Codec-specific数据
Some formats, notably AAC audio and MPEG4, H.264 and H.265 video formats require the actual data to be prefixed by a number of buffers containing setup data, or codec specific data. When processing such compressed formats, this data must be submitted to the codec after start()
and before any frame data. Such data must be marked using the flag BUFFER_FLAG_CODEC_CONFIG
in a call to queueInputBuffer
.
有些格式,特别是ACC音频和MPEG4,H.264和H.265视频格式要求以包含特定数量的构建数据buffers或者codec-specific数据为前缀的实际数据。当处理这样的压缩格式时,这些数据必须在start()方法后和任何帧数据之前提交给编解码器。这些数据必须在调用queueInputBuffer方法时用BUFFER_FLAG_CODEC_CONFIG标记。
Codec-specific data can also be included in the format passed to configure
in ByteBuffer entries with keys "csd-0", "csd-1", etc. These keys are always included in the track MediaFormat
obtained from the MediaExtractor
. Codec-specific data in the format is automatically submitted to the codec upon start()
; you MUST NOT submit this data explicitly. If the format did not contain codec specific data, you can choose to submit it using the specified number of buffers in the correct order, according to the format requirements. Alternately, you can concatenate all codec-specific data and submit it as a single codec-config buffer.
Codec-specific数据也可以被包含在传递给configure的ByteBuffer的格式里面,包含的keys是 "csd-0", "csd-1"等。这些keys通常包含在通过MediaExtractor获得的轨道MediaFormat中。这个格式中的Codec-specific数据将在接近start()方法时自动提交给编解码器;你不能显示的提交这些数据。如果这个格式不包含编解码器指定的数据,你也可以选择在这个编解码器中以这个格式所要求的并以正确的顺序传递特定数量的buffers来提交这些数据。还有,你也可以连接所有的codec-specific数据并作为一个单独的codec-config buffer提交。
Android uses the following codec-specific data buffers. These are also required to be set in the track format for proper MediaMuxer
track configuration. Each parameter set and the codec-specific-data sections marked with (*) must start with a start code of "\x00\x00\x00\x01"
.
Android 使用以下codec-specific数据buffers。{这些也被要求在轨道配置的格式轨道属性MediaMuxer中进行配置}。所有设置的参数以及被标记为(*)的codec-specific-data必须以 "\x00\x00\x00\x01"字符开头。
Note: care must be taken if the codec is flushed immediately or shortly after start, before any output buffer or output format change has been returned, as the codec specific data may be lost during the flush. You must resubmit the data using buffers marked with BUFFER_FLAG_CODEC_CONFIG
after such flush to ensure proper codec operation.
注意:当编解码器被立即flushed 或start之后不久,并且在任何输出buffer或输出格式变化被返回前需要特别地小心,编解码器的code specific 数据可能会在flush过程中丢失。为保证编解码器的正常运行,你必须在刷新后通过buffers再次提交被标记为BUFFER_FLAG_CODEC_CONFIGbuffers的这些数据。
Encoders (or codecs that generate compressed data) will create and return the codec specific data before any valid output buffer in output buffers marked with the codec-config flag. Buffers containing codec-specific-data have no meaningful timestamps.
编码器(或者产生压缩数据的编解码器)将在任何合法的输出buffer前创建并返回被标记为 codec-config flag的codec specific data 。包含codec-specific-data 的Buffers含有没有意义的时间戳。
Data Processing
数据处理
Each codec maintains a set of input and output buffers that are referred to by a buffer-ID in API calls. After a successful call to start()
the client "owns" neither input nor output buffers. In synchronous mode, call dequeueInput
/OutputBuffer(…)
to obtain (get ownership of) an input or output buffer from the codec. In asynchronous mode, you will automatically receive available buffers via the MediaCodec.Callback.onInput
/OutputBufferAvailable(…)
callbacks.
在API中每一个编解码器维护一组被一个buffer-ID引用的输入和输出buffers。当成功调用start()方法后客户端将“拥有”输入和输出buffers。在同步模式下,通过调用dequeueInput/OutputBuffer(…) 从编解码器获得(具有所有权的)一个输入或输出buffer。在异步模式下,你可以通过MediaCodec.Callback.onInput/OutputBufferAvailable(…)的回调方法自动地获得可用的buffers.
Upon obtaining an input buffer, fill it with data and submit it to the codec using queueInputBuffer
– or queueSecureInputBuffer
if using decryption. Do not submit multiple input buffers with the same timestamp (unless it is codec-specific data marked as such).
在获得一个输入buffer,在使用解密方式下通过queueInputBuffer或queueSecureInputBuffer向编解码器填充相应数据。不要提交多个具有相同时间戳的输入bufers(除非他的codec-specific 数据时那样标记的)。
The codec in turn will return a read-only output buffer via the onOutputBufferAvailable
callback in asynchronous mode, or in response to a dequeuOutputBuffer
call in synchronous mode. After the output buffer has been processed, call one of the releaseOutputBuffer
methods to return the buffer to the codec.
在异步模式下,编解码器将通过onOutputBufferAvailable的回调返回一个只读的输出buffer,或者在同步模式下响应dequeuOutputBuffer的调用。在输出buffer被处理后,调用releaseOutputBuffer方法中其中一个将这个buffer返回给编解码器。
While you are not required to resubmit/release buffers immediately to the codec, holding onto input and/or output buffers may stall the codec, and this behavior is device dependent. Specifically, it is possible that a codec may hold off on generating output buffers until all outstanding buffers have been released/resubmitted. Therefore, try to hold onto to available buffers as little as possible.
你不需要立即向编解码器重新提交或释放buffers,{获得的输入或输出buffers可能失去编解码器},当然这些行为依赖于设备情况。具体地说,编解码器可能推迟产生输出buffers直到输出的buffers被释放或重新提交。因此,尽可能保存可用的buffers。
Depending on the API version, you can process data in three ways:
根据API版本情况,你有三种方式处理相关数据:
Asynchronous Processing using Buffers
异步处理使用的Buffers
Since LOLLIPOP
, the preferred method is to process data asynchronously by setting a callback before calling configure
. Asynchronous mode changes the state transitions slightly, because you must call start()
after flush()
to transition the codec to the Running sub-state and start receiving input buffers. Similarly, upon an initial call to start
the codec will move directly to the Running sub-state and start passing available input buffers via the callback.
从LOLLIPOP API版本开始,首选的异步处理数据的方法是通过在调用configure前设置异步的回调方法。异步模式将间接地修改状态转换情况,因为你必须在flush()方法后调用start()方法将编解码器的状态转换为Running 子状态并开始接收输入buffers。同样,初始化调用start方法将编解码器的状态直接变化为Running 子状态并通过回调方法开始传递可用的输入buufers。
MediaCodec is typically used like this in asynchronous mode:
下面是MediaCodec 在异步模式下的典型应用:
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();
Synchronous Processing using Buffers
同步处理使用的Buffers
Since LOLLIPOP
, you should retrieve input and output buffers using getInput
/OutputBuffer(int)
and/or getInput
/OutputImage(int)
even when using the codec in synchronous mode. This allows certain optimizations by the framework, e.g. when processing dynamic content. This optimization is disabled if you call getInput
/OutputBuffers()
.
从LOLLIPOP API版本开始,在同步模式下使用编解码器你应该通过getInput/OutputBuffer(int) 和/或 getInput/OutputImage(int) 检索输入和输出buffers。这允许通过框架进行某些优化,例如,在处理动态内容过程中。如果你调用getInput/OutputBuffers()方法这种优化是不可用的。
Note: do not mix the methods of using buffers and buffer arrays at the same time. Specifically, only call getInput
/OutputBuffers
directly after start()
or after having dequeued an output buffer ID with the value of INFO_OUTPUT_FORMAT_CHANGED
.
注意,不要在同时使用buffers和buffer时产生混淆。特别地,仅仅在调用start()方法后或取出一个值为 INFO_OUTPUT_FORMAT_CHANGED的输出buffer ID后你才可以直接调用getInput/OutputBuffers方法。
MediaCodec is typically used like this in synchronous mode:
下面是MediaCodec在同步模式下的典型应用:
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();
Synchronous Processing using Buffer Arrays (deprecated)
同步处理使用的Buffer 数组(已弃用)
In versions KITKAT_WATCH
and before, the set of input and output buffers are represented by the ByteBuffer[]
arrays. After a successful call to start()
, retrieve the buffer arrays usinggetInput
/OutputBuffers()
. Use the buffer ID-s as indices into these arrays (when non-negative), as demonstrated in the sample below. Note that there is no inherent correlation between the size of the arrays and the number of input and output buffers used by the system, although the array size provides an upper bound.
在KITKAT_WATCH 及以前的API中,这一组输入或输出buffers使用ByteBuffer[]数组表示的。在成功调用了start()方法后,通过调用 getInput/OutputBuffers()方法检索buffer数组。在这些数组中(非负数)使用buffer的ID-s作为索引,如下面的演示示例中,请注意被系统使用的数组大小和输入和输出buffers数量之间是没有固定关系的,尽管这个数组提供了上限边界。
MediaCodec codec = MediaCodec.createByCodecName(name);
codec.configure(format, …);
codec.start();
ByteBuffer[] inputBuffers = codec.getInputBuffers();
ByteBuffer[] outputBuffers = codec.getOutputBuffers();
for (;;) {
int inputBufferId = codec.dequeueInputBuffer(…);
if (inputBufferId >= 0) {
// fill inputBuffers[inputBufferId] with valid data
…
codec.queueInputBuffer(inputBufferId, …);
}
int outputBufferId = codec.dequeueOutputBuffer(…);
if (outputBufferId >= 0) {
// outputBuffers[outputBufferId] is ready to be processed or rendered.
…
codec.releaseOutputBuffer(outputBufferId, …);
} else if (outputBufferId == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
outputBuffers = codec.getOutputBuffers();
} else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
// Subsequent data will conform to new format.
MediaFormat format = codec.getOutputFormat();
}
}
codec.stop();
codec.release();
End-of-stream Handling
处理End-of-stream
When you reach the end of the input data, you must signal it to the codec by specifying the BUFFER_FLAG_END_OF_STREAM
flag in the call to queueInputBuffer
. You can do this on the last valid input buffer, or by submitting an additional empty input buffer with the end-of-stream flag set. If using an empty buffer, the timestamp will be ignored.
当到达输入数据的结尾,你必须向这个编解码器在调用queueInputBuffer方法中指定BUFFER_FLAG_END_OF_STREAM 来标记输入数据。你可以在最后一个合法的输入buffer上做这些操作,或者提交一个以 end-of-stream 标记的额外的空的输入buffer。如果使用一个空的buffer,它的时间戳将被忽略。
The codec will continue to return output buffers until it eventually signals the end of the output stream by specifying the same end-of-stream flag in the MediaCodec.BufferInfo
set indequeueOutputBuffer
or returned via onOutputBufferAvailable
. This can be set on the last valid output buffer, or on an empty buffer after the last valid output buffer. The timestamp of such empty buffer should be ignored.
编解码器将继续返回输出buffers,直到在这个设置在indequeueOutputBuffer 里的 MediaCodec.BufferInfo 中被同样标记为 end-of-stream 的输出流结束的时候或者通过onOutputBufferAvailable返回。这些可以被设置在最后一个合法的输出buffer上,或者在最后一个合法的buffer后的一个空buffer。那样的空buffer的时间戳将被忽略。
Do not submit additional input buffers after signaling the end of the input stream, unless the codec has been flushed, or stopped and restarted.
不要在输入流被标记为结束后提交额外的输入buffers,除非这个编解码器被flushed,或者stopped 和restarted。
Using an Output Surface
使用输出Surface
The data processing is nearly identical to the ByteBuffer mode when using an output Surface
; however, the output buffers will not be accessible, and are represented as null
values. E.g.getOutputBuffer
/Image(int)
will return null
and getOutputBuffers()
will return an array containing only null
-s.
在使用一个输出Surface时,其数据处理基本上与处理ByteBuffer模式相同。然而,这个输出buffers将不可访问,并且被描述为null值。例如,调用getOutputBuffer/Image(int)将返回null,以及调用getOutputBuffers()将返回一个只包含null-s的数组。
When using an output Surface, you can select whether or not to render each output buffer on the surface. You have three choices:
当使用输出Surface,你可以选择在surface上是否渲染每一个输出buffer。你有三种选择:
-
Do not render the buffer: Call
releaseOutputBuffer(bufferId, false)
. 不要渲染这个buffer:通过调用releaseOutputBuffer(bufferId, false).
-
Render the buffer with the default timestamp: Call
releaseOutputBuffer(bufferId, true)
. 使用默认的时间戳渲染这个buffer:调用eleaseOutputBuffer(bufferId, true).
-
Render the buffer with a specific timestamp: Call
releaseOutputBuffer(bufferId, timestamp)
. 使用指定的时间戳渲染这个buffer:调用 releaseOutputBuffer(bufferId, timestamp).
Since M
, the default timestamp is the presentation timestamp of the buffer (converted to nanoseconds). It was not defined prior to that.
从M API版本开始,默认的时间戳是presentation timestamp这个buffer的时间戳的(转换为纳秒)。在此前的版本中这是没有被定义的。
Also since M
, you can change the output Surface dynamically using setOutputSurface
.
仍然从M 版本API开始,你可以通过使用setOutputSurface动态改变这个输出Surface。
Using an Input Surface
使用输入Surface
When using an input Surface, there are no accessible input buffers, as buffers are automatically passed from the input surface to the codec. Calling dequeueInputBuffer
will throw anIllegalStateException
, and getInputBuffers()
returns a bogus ByteBuffer[]
array that MUST NOT be written into.
当使用输入Surface时,将没有可访问的输入buffers,因为这些buffers将会从输入surface自动地向编解码器传输。调用dequeueInputBuffer时将抛出一个IllegalStateException,调用getInputBuffers()将要返回一个不能写入的假的ByteBUffer[]数组。
Call signalEndOfInputStream()
to signal end-of-stream. The input surface will stop submitting data to the codec immediately after this call.
调用signalEndOfInputStream() 方法标记end-of-stream。调用这个方法后,输入surface将会立即停止向编解码器提交数据。
Seeking & Adaptive Playback Support
Seeking及adaptive playback 放的支持
Video decoders (and in general codecs that consume compressed video data) behave differently regarding seek and format change whether or not they support and are configured for adaptive playback. You can check if a decoder supports adaptive playback via CodecCapabilities.isFeatureSupported(String)
. Adaptive playback support for video decoders is only activated if you configure the codec to decode onto a Surface
.
{视频解码器(通常是消费压缩视频数据的编解码器)关于seek和格式变化的行为是不同的,不管他们是否支持以及被配置为adaptive playback}。你可以通过调用CodecCapabilities.isFeatureSupported(String)方法来检查解码器是否支持adaptive playback 。只有在编解码器被配置在Surface上解码时支持Adaptive playback播放的解码器才被激活。
Stream Boundary and Key Frames
流边界及关键帧
It is important that the input data after start()
or flush()
starts at a suitable stream boundary: the first frame must a key frame. A key frame can be decoded completely on its own (for most codecs this means an I-frame), and no frames that are to be displayed after a key frame refer to frames before the key frame.
在调用start()或flush()方法后输入数据以合适的流边界开始是非常重要的:其第一帧必须是关键帧。一个关键帧能够通过其自身完全解码(针对大多数编解码器它是一个I帧),没有帧能够在关键帧之前或之后显示。
The following table summarizes suitable key frames for various video formats.
下面的表格针对不同的格式总结了合适的关键帧。
For decoders that do not support adaptive playback (including when not decoding onto a Surface)
针对解码器不支持adaptive playback的编解码器(包括不解码到Surface的)。
In order to start decoding data that is not adjacent to previously submitted data (i.e. after a seek) you MUST flush the decoder. Since all output buffers are immediately revoked at the point of the flush, you may want to first signal then wait for the end-of-stream before you call flush
. It is important that the input data after a flush starts at a suitable stream boundary/key frame.
为了开始解码与先前提交的数据(例如,在执行了seek后)不相邻的数据你必须刷新解码器。自从在靠近调用fulush方法的地方立即被取消所有输出buffers,你可能希望在调用flush方法前等待这些buffers首先被标记为end-of-stream。在调用 fush 方法后传入的输入数据开始于一个合适的流边界或关键帧是非常重要的。
Note: the format of the data submitted after a flush must not change; flush()
does not support format discontinuities; for that, a full stop()
- configure(…)
- start()
cycle is necessary.
注意:调用fush方法后传入的数据的格式是不能够改变的;flush()方法不支持不连续的格式;因此,一个完整的stop()-configure(...)-start()的生命循环是必要的。
Also note: if you flush the codec too soon after start()
– generally, before the first output buffer or output format change is received – you will need to resubmit the codec-specific-data to the codec. See the codec-specific-data section for more info.
同时注意:如果你在太靠近调用start()方法后刷新编解码器,通常,在收到第一个输出buffer或输出format变化前你需要向这个编解码器再次提交codec-specific-data。具体查看codec-specific-data部分以获得更多信息。
For decoders that support and are configured for adaptive playback
针对支持并配置为adaptive playback的编解码器
In order to start decoding data that is not adjacent to previously submitted data (i.e. after a seek) it is not necessary to flush the decoder; however, input data after the discontinuity must start at a suitable stream boundary/key frame.
为了开始解码与先前提交的数据(例如,在执行了seek后)不相邻的数据,你不是一定要刷新解码器;然而,在不连续的数据之后传入的数据必须开始于一个合适的流边界或关键帧。
For some video formats - namely H.264, H.265, VP8 and VP9 - it is also possible to change the picture size or configuration mid-stream. To do this you must package the entire new codec-specific configuration data together with the key frame into a single buffer (including any start codes), and submit it as a regular input buffer.
针对一些视频格式-也就是H.264、H.265、VP8和VP9,这些格式也可以修改图片大小或者配置mid-stream。为了做到这些你必须将整个新codec-specific配置数据与关键帧一起打包到一个单独的buffer中(包括所有的开始数据),并将它作为一个正常的输入数据提交。
You will receive an INFO_OUTPUT_FORMAT_CHANGED
return value from dequeueOutputBuffer
or a onOutputFormatChanged
callback just after the picture-size change takes place and before any frames with the new size have been returned.
你可以从picture-size被改变后以及任意具有新大小帧返回之前从dequeueOutputBuffer或一个onOutputFormatChanged回调中得到 INFO_OUTPUT_FORMAT_CHANGED的返回值。
Note: just as the case for codec-specific data, be careful when calling flush()
shortly after you have changed the picture size. If you have not received confirmation of the picture size change, you will need to repeat the request for the new picture size.
注意:在使用codec-specific数据案例中,在你修改图片大小后立即调用fush()方法时需要非常小心。如果你没有接收到图片大小改变的配置信息,你需要重试修改图片大小的请求。
Error handling
错误处理
The factory methods createByCodecName
and createDecoder
/EncoderByType
throw IOException
on failure which you must catch or declare to pass up. MediaCodec methods throwIllegalStateException
when the method is called from a codec state that does not allow it; this is typically due to incorrect application API usage. Methods involving secure buffers may throwMediaCodec.CryptoException
, which has further error information obtainable from getErrorCode()
.
工厂方法createByCodecName以及 createDecoder/EncoderByType将会在操作失败时抛出一个IOException,你必须捕获或声明为上抛这个异常。MediaCodec的方法将会在调用了编解码器不被允许的状态时抛出IllegalStateException异常;这种情况一般是由于API接口的非正常调用引起的。涉及secure buffers的方法可能会抛出一个MediaCodec.CryptoException异常,更多的异常信息你可以从调用getErrorCode()方法获得。
Internal codec errors result in a MediaCodec.CodecException
, which may be due to media content corruption, hardware failure, resource exhaustion, and so forth, even when the application is correctly using the API. The recommended action when receiving a CodecException
can be determined by calling isRecoverable()
and isTransient()
:
编解码器的内部错误是在MediaCodec.CodecException中体现的,即便应用正确使用API也会由于几种原因导致失败,例如:媒体内容“脏数据”、硬件错误、资源不足等等。当接收到一个CodecException时的首选方法可以调用isRecoverable() 和 isTransient()这两个方法进行定义。
-
recoverable errors: If
isRecoverable()
returns true, then callstop()
,configure(…)
, andstart()
to recover. - 可恢复错误:如果 isRecoverable() 方法返回true,然后就可以调用stop(),configure(...),以及start()方法进行恢复。
-
transient errors: If
isTransient()
returns true, then resources are temporarily unavailable and the method may be retried at a later time. - 短暂错误:如果isTransient()方法返回true,短时间内资源可能不可用,在一会后这个方法可能会重试。
-
fatal errors: If both
isRecoverable()
andisTransient()
return false, then theCodecException
is fatal and the codec must be reset or released. - 致命错误:如果isRecoverable() 和 isTransient()方法均返回fase,CodecException错误是致命的,此时就必须reset这个编解码器或调用released方法释放资源。
Both isRecoverable()
and isTransient()
do not return true at the same time.
isRecoverable() 和 isTransient()方法不可能同时都返回true。
Valid API Calls and API History
合法API接口及历史API
This sections summarizes the valid API calls in each state and the API history of the MediaCodec class. For API version numbers, see Build.VERSION_CODES
.
下面的表格总结了MediaCodec中合法的API以及API历史版本。更多的API版本编号详见Build.VERSION_CODES。