原文:https://developer.android.com/reference/android/media/MediaCodec.html
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 with MediaExtractor, MediaSync, MediaMuxer, MediaCrypto, MediaDrm, Image, Surface, and AudioTrack.)
MediaCodec 類(lèi)可以處理低層級(jí)的多媒體編解碼器拥坛,如:encoder/decoder。它是Android低層級(jí)的多媒體支持基礎(chǔ)的一部分(通常和MediaExtractor, MediaSync, MediaMuxer, MediaCrypto, MediaDrm, Image, Surface, and 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.
總的來(lái)說(shuō),codec 處理輸入的數(shù)據(jù),生成輸出數(shù)據(jù)。異步處理數(shù)據(jù)陶缺,并且使用一系列的輸入輸出緩沖(buffer). 在一個(gè)簡(jiǎn)化的層次, 你請(qǐng)求(或者接收)一個(gè)空的輸入buffer, 填充數(shù)據(jù)后把它交給codec進(jìn)行處理。codec用完數(shù)據(jù)后把buffer轉(zhuǎn)換成它的里面的眾多空的輸出buffer中的一個(gè)恩掷。最后,你請(qǐng)求(或者接收)一個(gè)充滿數(shù)據(jù)的輸出buffer供嚎,提取出它里面的數(shù)據(jù)黄娘,然后把它釋放回codec。
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).
數(shù)據(jù)類(lèi)型
Codec對(duì)三種數(shù)據(jù)進(jìn)行處理:壓縮數(shù)據(jù)克滴,音頻原始數(shù)據(jù)和視頻原始數(shù)據(jù)逼争。三種數(shù)據(jù)都能夠用ByteBuffers進(jìn)行處理,但是為了提高codec處理能力劝赔,在處理視頻原始數(shù)據(jù)時(shí)誓焦,你應(yīng)該使用Surface。Surface使用native的視頻buffer,不需要映射或都復(fù)制到ByteBuffers;因此杂伟,效率更高移层。通常使用Surface時(shí),你無(wú)法接獲取原始視頻數(shù)據(jù)赫粥,但是你可以使用ImageReader類(lèi)來(lái)獲取未加密的解碼后的(原始)視頻幀观话。即使這樣也比使用ByteBuffers的效率更高,因?yàn)橐恍﹏ative buffers可以被映射到ByteBuffers. 當(dāng)使用ByteBuffer模式時(shí)越平,你可以用Image频蛔, getInput/OutputImage(int)獲取到原始視頻幀。
Compressed 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(解碼器使用)和輸出buffers(編碼器使用)包含根據(jù)格式壓縮的數(shù)據(jù)秦叛。如果是視頻晦溪,那么就是壓縮后的一幀。如果是音頻挣跋,通常是一個(gè)access單位(一個(gè)編碼過(guò)的音頻片段三圆,一般來(lái)說(shuō)包含幾毫秒的音頻),但也可能幾個(gè)access單位避咆。不論是哪種情況嫌术,buffers 不會(huì)在隨意的byte邊界開(kāi)始或結(jié)束,而是以frame/acess為單位來(lái)劃分起始和結(jié)束位置牌借。
Raw Audio 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
原始音頻buffers包含所有PCM音頻數(shù)據(jù)幀度气。PCM音頻數(shù)據(jù)是一個(gè)按通道的順序?qū)λ幸纛l通道的采樣結(jié)果。每個(gè)采樣是一個(gè)16位的帶符號(hào)的整數(shù)膨报。
short[] getSamplesForChannel(MediaCodec codec, int bufferId, int channelIx) {
ByteBuffer outputBuffer = codec.getOutputBuffer(bufferId);
MediaFormat format = codec.getOutputFormat(bufferId);
ShortBuffer samples = outputBuffer.order(ByteOrder.nativeOrder()).asShortBuffer();
int numChannels = formet.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
if (channelIx < 0 || channelIx >= numChannels) {
return null;
}
short[] res = new short[samples.remaining() / numChannels];
for (int i = 0; i < res.length; ++i) {
res[i] = samples.get(i * numChannels + channelIx);
}
return res;
}
Raw Video Buffers
In ByteBuffer mode video buffers are laid out according to their color format. You can get the supported color formats as an array from getCodecInfo().getCapabilitiesForType(…).colorFormats. Video codecs may support three kinds of color formats:
原始視頻buffers
在ByteBuffer模式下磷籍,視頻buffers根據(jù)它們的顏色格式進(jìn)行布置的。調(diào)用getCodecInfo().getCapabilitiesForType(…).colorFormats可以得到一個(gè)包含所有支持的顏色格式的數(shù)組现柠。視頻codecs可能支持三種顏色格式:
****native raw video format:**** This is marked by COLOR_FormatSurface and it can be used with an input or output Surface.
****flexible YUV buffers (such as COLOR_FormatYUV420Flexible):**** These can be used with an input/output Surface, as well as in ByteBuffer mode, by using 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 use getInput/OutputImage(int).
All video codecs support flexible YUV 4:2:0 buffers since LOLLIPOP_MR1.****native原始視頻格式**** 由COLOR_FormatSurface進(jìn)行標(biāo)記院领,可以在輸入或者輸出的Surface中進(jìn)行使用。
****可變的YUV buffers(例如COLOR_FormatYUV420Flexible) :****這些buffers可以在輸入/輸出Surface中使用够吩,同樣在ByteBuffer模式下通過(guò)getInput/OutputImage(int)也可以使用.
****其他比然,特定的格式:****這些格式通常只在ByteBuffer模式下支持。一些顏色格式是廠商定制的周循。其他的格式在MediaCodecInfo.CodecCapabilities中進(jìn)行定義强法。對(duì)于和可變的格式相同的顏色格式,你仍然可以使用getInput/OutputImage(int).從LOLLIPOP_MR1(API Level 22)開(kāi)始所有的視頻codecs支持可變的YUV 4:2:0 buffers湾笛。
Accessing Raw Video ByteBuffers on Older Devices
Prior to LOLLIPOP and Image support, you need to use the KEY_STRIDE and KEY_SLICE_HEIGHT output format values to understand the layout of the raw output buffers.
在LOLLIPOP之前的設(shè)備上獲取原始視頻ByteBuffers
在LOLLIPOP之前的或不支持Image的系統(tǒng)版本中饮怯,你需要用KEY_STRIDE和KEY_SLICE_HEIGHT輸出格式值來(lái)理解原始輸出buffers的布置。
Note that on some devices the slice-height is advertised as 0. This could mean either that the slice-height is the same as the frame height, or that the slice-height is the frame height aligned to some value (usually a power of 2). Unfortunately, there is no standard and simple way to tell the actual slice height in this case. Furthermore, the vertical stride of the U plane in planar formats is also not specified or defined, though usually it is half of the slice height.
注意在一些設(shè)備上嚎研,slice-height值是0蓖墅。這是說(shuō),要么 slice-height和幀高度一致,要么是幀高度與某個(gè)值(通常是2的指數(shù))對(duì)齊之后的值论矾。不幸的是教翩,在這種情況下,沒(méi)有一個(gè)標(biāo)準(zhǔn)簡(jiǎn)單的方法來(lái)告實(shí)際的slice height贪壳。Furthermore, the vertical stride of the U plane in planar formats is also not specified or defined, though usually it is half of the slice height.(不知道這句什么意思)
The KEY_WIDTH and KEY_HEIGHT keys specify the size of the video frames; however, for most encodings the video (picture) only occupies a portion of the video frame. This is represented by the 'crop rectangle'.
KEY_WIDTH和KEY_HEIGHT指定了視頻的幀寬高饱亿;但是,對(duì)于大多數(shù)的encodings寥袭,視頻(圖片)只占了視頻幀的一部分。這部分就是'crop rectangle'.
You need to use the following keys to get the crop rectangle of raw output images from the output format. If these keys are not present, the video occupies the entire video frame.The crop rectangle is understood in the context of the output frame before applying any rotation.
獲取輸出格式的原始輸出圖片的crop rectangle关霸,需要用到以下的keys传黄。如果沒(méi)有提供這些keys,視頻占據(jù)全部的視頻幀队寇。在旋轉(zhuǎn)之前膘掰,crop rectangle在輸出幀的上下文中進(jìn)行‘理解’。
Format Key | Type | Description |
---|---|---|
"crop-left" | Integer | The left-coordinate (x) of the crop rectangle |
"crop-top" | Integer | The top-coordinate (y) of the crop rectangle |
"crop-right" | Integer | The right-coordinate (x) MINUS 1 of the crop rectangle |
"crop-bottom" | Integer | The bottom-coordinate (y) MINUS 1 of the crop rectangle |
The right and bottom coordinates can be understood as the coordinates of the right-most valid column/bottom-most valid row of the cropped output image.
右邊和底部的坐標(biāo)可以理解為裁剪后的圖片最右面的列和最下邊的行的坐標(biāo)佳遣。
The size of the video frame (before rotation) can be calculated as such:
旋轉(zhuǎn)前的視頻幀的大小可以通過(guò)以下方式進(jìn)行計(jì)算:
MediaFormat format = decoder.getOutputFormat(…);
int width = format.getInteger(MediaFormat.KEY_WIDTH);
if (format.containsKey("crop-left") && format.containsKey("crop-right")) {
width = format.getInteger("crop-right") + 1 - format.getInteger("crop-left");
}
int height = format.getInteger(MediaFormat.KEY_HEIGHT);
if (format.containsKey("crop-top") && format.containsKey("crop-bottom")) {
height = format.getInteger("crop-bottom") + 1 - format.getInteger("crop-top");
}
Also note that the meaning of BufferInfo.offset was not consistent across devices. On some devices the offset pointed to the top-left pixel of the crop rectangle, while on most devices it pointed to the top-left pixel of the entire frame.
另外需要注意的是识埋,BufferInfo.offset 代表的意義在不同的設(shè)備上并不一樣。在一些設(shè)備上零渐,offset指crop rectangle最左上方的像素值窒舟,而在另一些設(shè)備上是指整個(gè)幀的最左上方的像素值。
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.
狀態(tài)
在codec的生命周期中诵盼,邏輯上只處于三種狀態(tài)中的一種:停止惠豺,執(zhí)行和釋放。停止?fàn)顟B(tài)包含三種狀態(tài):Uninitialized, Configured和Error风宁;執(zhí)行狀態(tài)也含三種狀態(tài):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 call start() to move it to the Executing state. In this state you can process data through the buffer queue manipulation described above.
當(dāng)你用其中一個(gè)工廠方法創(chuàng)建一個(gè)codec時(shí)洁墙,codec處于Uninitialized狀態(tài)。首先戒财,你需要用configure(…)去配置它热监,使它進(jìn)入Configured狀態(tài),然后調(diào)用start()方法使codec進(jìn)入Executing狀態(tài)饮寞。在Executing狀態(tài)孝扛,你可以操作buffer隊(duì)列進(jìn)行數(shù)據(jù)處理。
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().
執(zhí)行狀態(tài)含三種狀態(tài):Flushed, Running和End-of-Stream.調(diào)用start()方法后幽崩,code立即處于Flushed sub-state, 持有所有buffers疗琉。當(dāng)?shù)谝粋€(gè)輸入buffer被dequeued,codec進(jìn)入Running sub-state, 這個(gè)state會(huì)占據(jù)大部分生命周期歉铝。當(dāng)你用一個(gè)end-of-stream標(biāo)志queue一個(gè)輸入buffer時(shí)盈简,codec進(jìn)入End-of-Stream狀態(tài)。在這個(gè)狀態(tài),codec不再接收輸入buffers柠贤,但是仍然在生成輸出buffers香浩,直到輸出至end-of-stream。在Executing狀態(tài) 臼勉,任何時(shí)候你都可以調(diào)用flush()返回Flushed sub-state.
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().
調(diào)用stop()方法使codec返回Uninitialized狀態(tài),然后它可以重新被配置邻吭。使用完codec,必須調(diào)用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. Call reset() 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.
極少數(shù)的情況下宴霸,codec會(huì)因?yàn)殄e(cuò)誤而進(jìn)入Error狀態(tài) 囱晴。這種況情下,queue操作會(huì)返回一個(gè)非法值瓢谢,或者有時(shí)候拋出異常畸写。調(diào)用reset()方法重置codec。在任何狀態(tài)下都可以調(diào)用reset()方法使codec回到Uninitialized狀態(tài)氓扛。
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).
創(chuàng)建
使用MediaCodecList來(lái)創(chuàng)建一個(gè)特定MediaFormat格式的MediaCodec枯芬。當(dāng)解碼一個(gè)文件或者流的時(shí)候,你可以用MediaExtractor.getTrackFormat來(lái)獲取相應(yīng)的格式采郎。插入任何指定的特性千所,使用MediaFormat.setFeatureEnabled,然后調(diào)用MediaCodecList.findDecoderForFormat來(lái)獲取可以處理這種多媒體格式的codec蒜埋。最后淫痰,使用createByCodecName(String)來(lái)創(chuàng)建codec.
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.
注意:在LOLLIPOP, MediaCodecList.findDecoder/EncoderForFormat中的format不包含frame rate. 使用format.setString(MediaFormat.KEY_FRAME_RATE, null)來(lái)清空已經(jīng)format存在的frame rate.
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)來(lái)創(chuàng)建針對(duì)特定MIME類(lèi)型的codec。然后整份,這種codec不能用來(lái)插入特性黑界,然后可能不能處理特定的多媒體格式。
Creating secure decoders
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.
創(chuàng)建一個(gè)加密的解碼器
在KITKAT_WATCH(API Level 20)和之前皂林,加密的codecs可能不在MediaCodecList里朗鸠,但是仍然是可用的。在正常的codec名字后面添加".secure", 加密的codec只可以通過(guò)名字來(lái)初始化础倍。createByCodecName(String)會(huì)拋出異常烛占,如果codec在系統(tǒng)中不存在。
From LOLLIPOP onwards, you should use the FEATURE_SecurePlayback feature in the media format to create a secure decoder.
LOLLIPOP之后沟启,你應(yīng)該用通過(guò)media format 的FEATURE_SecurePlayback來(lái)創(chuàng)建一個(gè)加密的解碼器忆家。
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 (see MediaCrypto). Finally, since some codecs can operate in multiple modes, you must specify whether you want it to work as a decoder or an encoder.
初始化
創(chuàng)建codec之后,如果你想異步處理數(shù)據(jù)德迹,你可以設(shè)置一個(gè)callback芽卿。然后,使用特定的media format來(lái)配置codec胳搞。這時(shí)你可以為video生產(chǎn)者(生生原始視頻數(shù)據(jù)的codec, 例如:視頻解碼器)指定輸出Surface卸例。在這時(shí)也可以為加密的codec設(shè)置解密參數(shù)(查看:MediaCrypto).最后称杨,因?yàn)橐恍ヽodec能在幾種模式下工作,你必須指定它是做為解碼器還是編碼器筷转。
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.
從LOLLIPOP開(kāi)始姑原,在Configured狀態(tài),你可以查詢輸入輸出格式呜舒。你可以使用這個(gè)功能來(lái)驗(yàn)證配置結(jié)果锭汛,例如:顏色格式,在還沒(méi)有starting codec之前袭蝗。
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).
如果你想用一個(gè)codec(比如一個(gè)視頻編碼器)來(lái)處理原始的視頻輸入buffer唤殴,在配置之后,調(diào)用createInputSurface() 來(lái)為輸入數(shù)據(jù)創(chuàng)建一個(gè)目的地Surface到腥《涫牛或者,調(diào)用setInputSurface(Surface)來(lái)設(shè)置一個(gè)之前已經(jīng)創(chuàng)建好的輸入surface.
Codec-specific Data
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.
指定編碼的數(shù)據(jù)
一些格式左电,如AAC音頻和MPEG4, H.264, H.265視頻格式需要在實(shí)際數(shù)據(jù)之前添加一些含設(shè)置數(shù)據(jù)或者指定編碼的數(shù)據(jù)的buffers做為前綴廉侧。在處理這種壓縮格式時(shí)页响,這些數(shù)據(jù)必須在start()之后篓足、在任何幀數(shù)據(jù)之前提交給codec。在queueInputBuffer時(shí)闰蚕,這種數(shù)據(jù)必須用BUFFER_FLAG_CODEC_CONFIG標(biāo)志栈拖。
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. In case of H.264 AVC, you can also concatenate all codec-specific data and submit it as a single 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".
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.
注意:如果codec馬上或者start之后就flushed,在任何輸出buffer或者輸出格式變化被返回之前没陡,指定編碼的數(shù)據(jù)可能在flush的過(guò)程中丟失涩哟。在flush之后掖疮,你必須重新用BUFFER_FLAG_CODEC_CONFIG標(biāo)記的buffers提交這些數(shù)據(jù)來(lái)確保正常的codec操作堪遂。
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.
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.
數(shù)據(jù)處理
每個(gè)codec維護(hù)著一批輸入和輸出buffers,在API請(qǐng)求中窍育, 這些buffer可以通過(guò)buffer-ID來(lái)指向埃儿。在成功調(diào)用start()方法后器仗,客戶端既沒(méi)有‘擁有’輸出也沒(méi)有‘擁有’輸入buffers。在同步模式中童番,調(diào)用dequeueInput/OutputBuffer(…)從codec中獲取輸入或者輸出buffer精钮。在異步模式中,通過(guò)MediaCodec.Callback.onInput/OutputBufferAvailable(…) 回調(diào)剃斧,你會(huì)自動(dòng)收到可用的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時(shí),填入數(shù)據(jù)后用queueInputBuffer交給codec幼东, 或者臂容,如果是加密的科雳,用queueSecureInputBuffer。不要同時(shí)提交多個(gè)有同要的時(shí)間戳的輸入buffers(除非是指定codec的data).
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.
在異步模式下策橘,通過(guò)onOutputBufferAvailable 回調(diào)codec會(huì)返回一個(gè)只讀的輸出buffer炸渡,或者在同步模式下,響應(yīng)dequeuOutputBuffer丽已。輸出buffer被處理后蚌堵,調(diào)用releaseOutputBuffer來(lái)釋放buffer給codec。
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.
當(dāng)你沒(méi)有被要求馬上提交或者釋放buffers給codec沛婴, 持有輸入或者輸出buffers可能使codec停止工作吼畏,最終結(jié)果可能與設(shè)備有關(guān)。特別地嘁灯,codec可能取消生成輸出buffers直到所有未完成的buffers被釋放/提交泻蚊。所以,盡可能地少持有buffers.
Depending on the API version, you can process data in three ways:
根據(jù)不同的API版本丑婿,你可以用以下三種方式來(lái)處理數(shù)據(jù)性雄。
Asynchronous Processing using 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.
異步處理中的buffers使用
從LOLLIPOP開(kāi)始,較好的方式是在配置之前設(shè)置回調(diào)羹奉,異步處理數(shù)據(jù)秒旋。異步模式稍微改變了狀態(tài)的變換,因?yàn)槟惚仨氃趂lush()之后調(diào)用start()诀拭,使codec處于Running 狀態(tài)迁筛,接收輸入buffers.同樣, codec初始化開(kāi)始之后會(huì)直接進(jìn)入Running狀態(tài)耕挨, 并通過(guò)回調(diào)傳遞可用的輸入buffers.
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
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().
同步處理Buffers的使用
從LOLLIPOP開(kāi)始,你應(yīng)該使用getInput/OutputBuffer(int)和getInput/OutputImage(int)來(lái)獲取輸入和輸出buffers筒占,即使是在同步模式下使用codec.這樣做贪庙,framework會(huì)有一些優(yōu)化,比如在處理動(dòng)態(tài)內(nèi)容翰苫。你調(diào)用getInput/OutputBuffers()時(shí)就不會(huì)有優(yōu)化止邮。
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.
注意:不要在同時(shí)使用buffers和buffer arrays的方法。特別是在start()之后馬上調(diào)用getInput/OutputBuffers革骨,或者是dequeued一個(gè)ID為INFO_OUTPUT_FORMAT_CHANGED的輸出buffer.
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)
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 using getInput/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.
同步模式下Buffer Arrays的使用(deprecated)
在KITKAT_WATCH(API Level 20)和之前,輸入和輸出buffers都是用ByteBuffer[] 表示的良哲。成功調(diào)用start()之后盛卡,通過(guò)getInput/OutputBuffers()來(lái)獲取Buffer數(shù)組。像下面的例子筑凫,采用buffer IDs來(lái)做為數(shù)組索引滑沧。注意并村,數(shù)組長(zhǎng)度和系統(tǒng)中使用的輸入和輸入buffers數(shù)量并沒(méi)有內(nèi)在關(guān)聯(lián),雖然數(shù)組有一個(gè)上限滓技。
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
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.
結(jié)束stream
當(dāng)輸入數(shù)據(jù)結(jié)束時(shí)哩牍,你必須通知codec, 在調(diào)用queueInputBuffer時(shí)標(biāo)志BUFFER_FLAG_END_OF_STREAM.你可以在最后一個(gè)輸入buffer進(jìn)行標(biāo)志,或者增加一個(gè)設(shè)置了end-of-stream標(biāo)志的額外的空buffer令漂。如果使用空buffer膝昆,時(shí)間戳?xí)缓雎浴?/p>
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 in dequeueOutputBuffer 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.
Codec會(huì)持續(xù)返回輸入buffers直到最后在dequeueOutputBuffer 或者onOutputBufferAvailable 返回的MediaCodec.BufferInfo中標(biāo)記同樣的end-of-stream來(lái)說(shuō)明輸出流結(jié)束〉兀可能在最后一個(gè)輸出buffer中標(biāo)記或者在最后一個(gè)buffer后面添加一個(gè)空的buffer進(jìn)記標(biāo)記荚孵。空的buffer上面的時(shí)間戳?xí)缓雎浴?/p>
Do not submit additional input buffers after signaling the end of the input stream, unless the codec has been flushed, or stopped and restarted.
標(biāo)記輸入流結(jié)束后纬朝,不要再繼續(xù)添加輸入buffers收叶,除非codec已經(jīng)被flushed,停止或者重啟了共苛。
Using an Output 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.
使用一個(gè)輸出Surface
使用一個(gè)輸出Surface的數(shù)據(jù)處理與ByteBuffer模式下的數(shù)據(jù)處理幾乎是一樣的判没;但是,在Surface模式下隅茎,不能獲取輸出buffers(值都為null).例如:getOutputBuffer/Image(int) 會(huì)返回null, getOutputBuffers()會(huì)返回一個(gè)只包含null的數(shù)組澄峰。
When using an output Surface, you can select whether or not to render each output buffer on the surface. You have three choices:
當(dāng)使用輸出Surface時(shí),你可以選擇是否在surface上繪制每個(gè)輸出buffer.你有三個(gè)選擇:
Do not render the buffer: Call releaseOutputBuffer(bufferId, false).
不繪制buffer:調(diào)用releaseOutputBuffer(bufferId, false).
Render the buffer with the default timestamp: Call releaseOutputBuffer(bufferId, true).
繪制有缺省時(shí)間戳的buffer:調(diào)用releaseOutputBuffer(bufferId, true)
- Render the buffer with a specific timestamp: Call releaseOutputBuffer(bufferId, timestamp).
- 繪制指定時(shí)間戳的buffer: 調(diào)用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 Level23)開(kāi)始患膛,缺省時(shí)間戳是presentation時(shí)間戳(轉(zhuǎn)換成nanoseconds)摊阀。在M之前是沒(méi)有定義的耻蛇。
Also since M, you can change the output Surface dynamically using setOutputSurface.
同樣踪蹬,從M開(kāi)始,你可以使用setOutputSurface動(dòng)態(tài)地改變輸出Surface.
Transformations When Rendering onto Surface
If the codec is configured into Surface mode, any crop rectangle, rotation and video scaling mode will be automatically applied with one exception:
在Surface繪制時(shí)進(jìn)行轉(zhuǎn)換
如果Codec設(shè)置為Surface模式臣咖,任何矩形的剪裁跃捣,旋轉(zhuǎn)和video縮放模式會(huì)自動(dòng)地進(jìn)行,但有一個(gè)例外:
Prior to the M release, software decoders may not have applied the rotation when being rendered onto a Surface. Unfortunately, there is no standard and simple way to identify software decoders, or if they apply the rotation other than by trying it out.
在M之前夺蛇,在Surface繪制時(shí)疚漆,軟解碼可能不會(huì)進(jìn)行旋轉(zhuǎn)變換。不幸的是刁赦,沒(méi)有標(biāo)準(zhǔn)而簡(jiǎn)單的方法來(lái)對(duì)軟解碼進(jìn)行判斷是否進(jìn)行了旋轉(zhuǎn)變換娶聘。
There are also some caveats.
下面是一些警告:
Note that the pixel aspect ratio is not considered when displaying the output onto the Surface. This means that if you are using VIDEO_SCALING_MODE_SCALE_TO_FIT mode, you must position the output Surface so that it has the proper final display aspect ratio. Conversely, you can only use VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING mode for content with square pixels (pixel aspect ratio or 1:1).
注意,當(dāng)在Surface上繪制輸出時(shí)甚脉,像素的寬高比是不被考慮的丸升。這意味著,如果你正在使用VIDEO_SCALING_MODE_SCALE_TO_FIT模式牺氨,你必須確保輸入Surface最終有一個(gè)合適的寬高比狡耻。相反地墩剖,當(dāng)顯示的內(nèi)容是正方形的,你只能使用VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING模式夷狰。
Note also that as of N release, VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING mode may not work correctly for videos rotated by 90 or 270 degrees.
注意岭皂,當(dāng)N(API Level 24)發(fā)布 ,videos旋轉(zhuǎn)了90或者270度時(shí)沼头,VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING模式可能不能正常工作爷绘。
When setting the video scaling mode, note that it must be reset after each time the output buffers change. Since the INFO_OUTPUT_BUFFERS_CHANGED event is deprecated, you can do this after each time the output format changes.
每當(dāng)輸出buffers改變時(shí),必須重新設(shè)置video縮放模式进倍。因?yàn)镮NFO_OUTPUT_BUFFERS_CHANGED事件已經(jīng)deprecated揉阎,每次輸出格式改變時(shí),你可以進(jìn)行重置背捌。
Using an Input 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 an IllegalStateException, and getInputBuffers() returns a bogus ByteBuffer[] array that MUST NOT be written into.
使用一個(gè)輸入Surface
當(dāng)使用一個(gè)輸入Surface時(shí)毙籽,不能獲取輸入buffers,因?yàn)樗鼈儽蛔詣?dòng)從輸入Surface傳遞給了Codec毡庆。調(diào)用dequeueInputBuffer會(huì)拋出IllegalStateException坑赡,調(diào)用getInputBuffers()會(huì)返回一個(gè)假的ByteBuffer[]數(shù)組,而且這個(gè)數(shù)組不能進(jìn)行賦值么抗。
Call signalEndOfInputStream() to signal end-of-stream. The input surface will stop submitting data to the codec immediately after this call.
調(diào)用signalEndOfInputStream()來(lái)通知結(jié)束毅否。調(diào)用這個(gè)方法之后,輸入Surface就馬上停止提交數(shù)據(jù)給Codec了蝇刀。
Seeking & Adaptive Playback Support
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.
快進(jìn)和適應(yīng)播放的支持
視頻解碼器(或者一般的消耗壓縮視頻數(shù)據(jù)的codecs)在快進(jìn)和格式變化上表現(xiàn)不一致螟加,不論它們是否支持,并且被配置進(jìn)行適應(yīng)性播放吞琐。你可以通過(guò)CodecCapabilities.isFeatureSupported(String)來(lái)檢查一個(gè)解碼器是否支持適應(yīng)性播放捆探。只有你設(shè)置codec解碼到Surface上,視頻解碼器的適應(yīng)性播放才會(huì)被激活站粟。
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.
Stream邊界和關(guān)鍵幀
start()和flush()之后黍图,輸入數(shù)據(jù)從一個(gè)合適的stream邊界開(kāi)始是非常重要的:第一個(gè)幀必須是關(guān)鍵幀。一個(gè)關(guān)鍵幀可以被完全的解碼(對(duì)于大多數(shù)的codecs來(lái)說(shuō)是一個(gè)I-frame), 而且關(guān)鍵幀之后顯示的幀不能指向關(guān)鍵幀之前的幀奴烙。
The following table summarizes suitable key frames for various video formats.
下表的列表概括了不同的視頻格式的合適的關(guān)鍵幀助被。
For decoders that do not support adaptive playback (including when not decoding onto a 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.
不支持適應(yīng)性播放的(包括沒(méi)有解碼到Surface的)解碼器
為了解碼和之前提交的數(shù)據(jù)(比如,快進(jìn)后)不相臨的數(shù)據(jù)切诀,你必須flsuh解碼器揩环。因?yàn)樗械妮敵鯾uffers在flush時(shí)立刻被廢除了,所以在你調(diào)用flush前幅虑,你可能需要先通知丰滑,然后等待end-of-stream 。flush之后翘单,輸入數(shù)據(jù)從一個(gè)合適的stream邊界/關(guān)鍵幀開(kāi)始是非常重要的吨枉。
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.
注意:flush之后提交的數(shù)據(jù)不能更改格式蹦渣;flush()不支持格式的不連續(xù)性;對(duì)于不連續(xù)的格式貌亭,需要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.
還要注意的是:當(dāng)你在start()之后馬上進(jìn)行flush--一般來(lái)說(shuō),在第一個(gè)輸出buffer或者輸出格式變換通知收到以前--你必須重新提交批定編碼的數(shù)據(jù)給codec圃庭。更多信息锄奢,請(qǐng)查看codec-specific-data這一節(jié)。
For decoders that support and are configured for 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.
支持并配置適應(yīng)性播放的解碼器
開(kāi)始解碼一個(gè)和上一次提交的數(shù)據(jù)不相鄰的數(shù)據(jù)(比如剧腻,快進(jìn))拘央,沒(méi)有必要flush解碼器。然而书在,非連續(xù)性的輸入數(shù)據(jù)必須從一個(gè)合適的stream邊界/關(guān)鍵幀開(kāi)始灰伟。
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.
對(duì)于一些視頻格式,比如:H.264, H.265, VP8 and VP9儒旬,是可能改變圖片大小或者mid-stream的結(jié)構(gòu)栏账。要實(shí)現(xiàn)這一步,你必須對(duì)整個(gè)新的特定編碼的配置數(shù)據(jù)和關(guān)鍵幀進(jìn)行打包成一個(gè)新buffer(包括任何start codes),然作為一個(gè)普通的輸入buffer進(jìn)行提交栈源。
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.
在圖片大小改變后挡爵,并且在任何新的大小的幀被返回之前,dequeueOutputBuffer或者onOutputFormatChanged回調(diào)馬上就會(huì)收到一個(gè)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.
注意:對(duì)于指定codec的數(shù)據(jù)茶鹃,改變圖片大小后馬上調(diào)用flush()要非常小心。如果沒(méi)有收到圖片大小改變的確認(rèn)艰亮,你需要重新提交新的圖片大小請(qǐng)求闭翩。
Error handling
The factory methods createByCodecName and createDecoder/EncoderByType throw IOException on failure which you must catch or declare to pass up. MediaCodec methods throw IllegalStateException 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 throw MediaCodec.CryptoException, which has further error information obtainable from getErrorCode().
應(yīng)對(duì)錯(cuò)誤的情況
工廠方法createByCodecName和createDecoder/EncoderByType 在遇錯(cuò)的時(shí)候會(huì)拋出IOException,你必須進(jìn)行catch或者聲明異常垃杖。當(dāng)codec在一個(gè)不允許的狀態(tài)被調(diào)用時(shí)會(huì)拋出IllegalStateException男杈;這是典型的API錯(cuò)誤引起的丈屹。與加密的buffers有關(guān)的方法可能會(huì)拋出MediaCodec.CryptoException调俘,使用getErrorCode()方法可以獲取進(jìn)一步的錯(cuò)誤信息。
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():
內(nèi)部的codec錯(cuò)誤會(huì)拋出MediaCodec.CodecException旺垒,可能是因多媒體文件損壞彩库,硬件錯(cuò)誤,沒(méi)有資源等先蒋,即使是正確地使用了API也是如此骇钦。遇到CodecException時(shí),調(diào)用isRecoverable()和isTransient()可以決定合適的方法竞漾。
recoverable errors: If isRecoverable() returns true, then call stop(), configure(…), and start() to recover.
能恢復(fù)的錯(cuò)誤: 如果isRecoverable() 返回true, 那么調(diào)用stop(),configure(…), 和start() 來(lái)恢復(fù)眯搭。
transient errors: If isTransient() returns true, then resources are temporarily unavailable and the method may be retried at a later time.
短暫的錯(cuò)誤:如果isTransient()返回true,那么暫時(shí)資源不可用窥翩,可以晚點(diǎn)再試這個(gè)方法。
fatal errors: If both isRecoverable() and isTransient() return false, then the CodecException is fatal and the codec must be reset or released.
致命的錯(cuò)誤:如果isRecoverable()和isTransient()都返回false鳞仙,那么CodecException是致命的寇蚊,codec必須被reset或者released.
Both isRecoverable() and isTransient() do not return true at the same time.
isRecoverable()和isTransient()不可能同時(shí)返回true;
Valid API Calls and API History
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.
正確的API請(qǐng)求和API歷史記錄
這小節(jié)概括了在每個(gè)狀態(tài)下正確的API請(qǐng)求,以及MediaCodec類(lèi)的API歷史棍好。查看API版本號(hào)仗岸,請(qǐng)看Build.VERSION_CODES。