前段時間項目需要,接觸到android 4.0以后新添加的mediacodec類,並用mediacodec類實現了一個無縫播放視頻的播放器,並用這個播放器簡單的實現了dash協議。
接觸到這么多,感覺GOOGLE給的文檔寫的不夠詳細,網絡上也沒有一個標准的例子,為了避免后來人走一些不必要的彎路,我在這里打算先用中文翻譯一下codec的文檔,然后再把自己在實現播放器時候的一些注意事項和問題列出來。
首先這篇文章是給半新手看的,你得有一定的安卓基礎,但是由於作者本人的水平有限,也不是什么高手,所以有的地方會說的不清楚,還請包涵。
給技術牛人推薦一個 mediacodec 例子很多的網站:http://bigflake.com/mediacodec/
感覺像是官方人員編寫的,里面有各種關於mediacodec的例子,還有4.3最新的muxer,都是以CTS的形式寫的,本人沒有接觸過,但是參考代碼還是很好的,畢竟實現方法都差不多。
MediaCodec|文檔翻譯
classoverView
mediacodec類可以用來調用系統底層的編碼/解碼軟件。
mediacodec一般是這么用的:
MediaCodec codec = MediaCodec.createDecoderByType(type); codec.configure(format, ...); codec.start(); ByteBuffer[] inputBuffers = codec.getInputBuffers(); ByteBuffer[] outputBuffers = codec.getOutputBuffers(); for (;;) { int inputBufferIndex = codec.dequeueInputBuffer(timeoutUs); if (inputBufferIndex >= 0) { // fill inputBuffers[inputBufferIndex] with valid data ... codec.queueInputBuffer(inputBufferIndex, ...); } int outputBufferIndex = codec.dequeueOutputBuffer(timeoutUs); if (outputBufferIndex >= 0) { // outputBuffer is ready to be processed or rendered. ... codec.releaseOutputBuffer(outputBufferIndex, ...); } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { outputBuffers = codec.getOutputBuffers(); } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { // Subsequent data will conform to new format. MediaFormat format = codec.getOutputFormat(); ... } } codec.stop(); codec.release(); codec = null;
//等會再逐一介紹上述代碼每一句的作用
每一個Codec類都擁有input buffer和outputbuffer,這些buffer是通過API提供的函數按索引調用(Index),這些buffer的格式是bytebuffer,實際上inputbuffer和outputbuffer都是bytebuffer的數組,用getInputBuffer和getOutputBuffer可以得到bytebuffer的調用。在成功調用Start()函數后,對象(原文是Client,在這里我理解為MediaCodec的實例化對象)並沒有“擁有”input和output buffer,相應的,可以通過調用dequeueInputBuffer(long)
和dequeueOutputBuffer(MediaCodec.BufferInfo, long)把buffer的所有權從codec編解碼器交換給對象。MediaCodec對象沒有必要立刻把buffer傳遞給解碼器或者釋放到surface,上面的代碼僅僅是一個簡單的例子。
當MediaCodec對象有一個可用的input buffer時候,可以調用 queueInputBuffer(int, int, int, long, int)函數把buffer傳遞給解碼器,當然,buffer里需要有數據。
解碼器會相應的調用 dequeueOutputBuffer(MediaCodec.BufferInfo, long)返回一個output buffer,如果繼續調用
releaseOutputBuffer(int, boolean)
,這個output buffer將被返回到解碼器中,如果再 configure(MediaFormat, Surface, MediaCrypto, int)
的時候傳入了一個videoSurface(一般是SurfaceView).那么這個這個output buffer的內容將會顯示在surface上。
不管是用於解碼器的input buffer還是編碼器的output buffer ,其中都包含了編碼好的多媒體數據.對於視頻格式的文件來說,buffer里包含的是一個短暫的時間片(比如一幀),對於音頻格式的文件,可能是一個多幀的聲音片段。不管哪種情況,buffer里的數據並不是任意字節邊界的二進制數據,buffer里存儲的不是數據流,而是一個元單元(access units)流。
很多媒體格式還需要多媒體文件頭數據(這些數據一般是由一些包含set up data的buffer組成),或者編碼器需要的特定數據。因此,最初傳遞給解碼器的buffer必須是帶有BUFFER_FLAG_CODEC_CONFIG下標的編碼特定數據,一般這個下標是由queueInputBuffer(int, int, int, long, int)生成的。編碼器需要的數據,包括媒體格式信息,都通過調用 configure(MediaFormat, Surface, MediaCrypto, int)
(in ByteBuffer entries with keys "csd-0", "csd-1", 括號里這句不知道怎么翻譯),自動傳遞給了codec類,不需要對象自己把格式信息傳給codec(也就是configure會自動傳遞帶有BUFFER_FLAG_CODEC_CONFIG的buffer和媒體格式信息給編碼器)。在輸入數據的末尾,對象會通過queueInputBuffer(int, int, int, long, int)函數發送一個帶有BUFFER_FLAG_END_OF_STREAM
下標的信號。為了解碼與之前數據無關的數據(例如執行一個seek),解碼器需要調用flush()。在調用flush()后,對象里的inputbuffer或者outputbuffer都會清空,也就是說對象不再有任何buffer,但是數據的媒體格式信息不會改變,如果需要改變媒體格式信息,依次調用stop,configure,start.
Summary
Nested Classes | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
class | MediaCodec.BufferInfo | .包含每一個buffer的元數據信息,例如偏差,在相關解碼器中有效的數據大小 | |||||||||
class | MediaCodec.CryptoException | Thrown when a crypto error occurs while queueing a secure input buffer. (沒用到,不翻譯 | |||||||||
class | MediaCodec.CryptoInfo | Metadata describing the structure of a (at least partially) encrypted input sample. (同上 |
Constants | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
int | BUFFER_FLAG_CODEC_CONFIG | 這個下標表明該buffer是解碼需要信息,不是多媒體的有效數據 | |||||||||
int | BUFFER_FLAG_END_OF_STREAM | 表明一個文件的末尾 | |||||||||
int | BUFFER_FLAG_SYNC_FRAME | This indicates that the buffer marked as such contains the data for a sync frame. | |||||||||
int | CONFIGURE_FLAG_ENCODE | 如果codec類被用於編碼器,傳遞這個flag | |||||||||
int | CRYPTO_MODE_AES_CTR | ||||||||||
int | CRYPTO_MODE_UNENCRYPTED | ||||||||||
int | INFO_OUTPUT_BUFFERS_CHANGED | output buffer有改動,對象必須重新調用getoutputbuffer()來獲取改動后的buffer引用。 | |||||||||
int | INFO_OUTPUT_FORMAT_CHANGED | output的格式改變,接下來的有效數據輸出將遵循新的格式 | |||||||||
int | INFO_TRY_AGAIN_LATER | 如果調用dequeueOutputBuffer(MediaCodec.BufferInfo, long)返回一個non-negative timeout flag , 標明執行函數超時,將不會繼續等待。 |
|||||||||
String | PARAMETER_KEY_REQUEST_SYNC_FRAME | Request that the encoder produce a sync frame "soon". | |||||||||
String | PARAMETER_KEY_SUSPEND | Temporarily suspend/resume encoding of input data. | |||||||||
String | PARAMETER_KEY_VIDEO_BITRATE | Change a video encoder's target bitrate on the fly. | |||||||||
int | VIDEO_SCALING_MODE_SCALE_TO_FIT | The content is scaled to the surface dimensions | |||||||||
int | VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING | The content is scaled, maintaining its aspect ratio, the whole surface area is used, content may be cropped |
Public Methods | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
void | configure(MediaFormat format, Surface surface, MediaCrypto crypto, int flags)
配置一個組件
|
||||||||||
static MediaCodec | createByCodecName(String name)
如果你知道組件的具體名字,可以用這個函數創建那個組件(具體參照,系統注冊的組件機制)
|
||||||||||
static MediaCodec | createDecoderByType(String type)
通過多媒體格式名創建一個可用的解碼器
|
||||||||||
static MediaCodec | createEncoderByType(String type)
同上,創建的是一個編碼器
|
||||||||||
final Surface | createInputSurface()
Requests a Surface to use as the input to an encoder, in place of input buffers.(請求一個surface用於編碼器的輸入,而不是常用的inputbuffer輸入)
|
||||||||||
final int | dequeueInputBuffer(long timeoutUs)
(返回一個inputbuffer的索引用來填充數據,返回-1表示暫無可用buffer)
|
||||||||||
final int | dequeueOutputBuffer(MediaCodec.BufferInfo info, long timeoutUs)
排一個輸出buffer,如果等待
timeoutUs時間還沒響應則跳過,返回TRY_AGAIN_LATER
|
||||||||||
final void | flush()
flush組件的input和output接口, 之前調用
dequeueInputBuffer(long) 和
dequeueOutputBuffer(MediaCodec.BufferInfo, long) 排好的buffer都變成不可用。
|
||||||||||
MediaCodecInfo | getCodecInfo()
獲得編碼器的信息。
|
||||||||||
ByteBuffer[] | getInputBuffers()
Call this after start() returns.
|
||||||||||
final String | getName()
獲得組件的名字。
|
||||||||||
ByteBuffer[] | getOutputBuffers()
Call this after start() returns and whenever dequeueOutputBuffer signals an output buffer change by returning
INFO_OUTPUT_BUFFERS_CHANGED
|
||||||||||
final MediaFormat | getOutputFormat()
在dequeueOutputBuffer 返回
INFO_OUTPUT_FORMAT_CHANGED信息后調用,可以查看當前媒體格式信息。
|
||||||||||
final void | queueInputBuffer(int index, int offset, int size, long presentationTimeUs, int flags)
在給指定Index的inputbuffer[]填充數據后,調用這個函數把數據傳給解碼器
|
||||||||||
final void | queueSecureInputBuffer(int index, int offset, MediaCodec.CryptoInfo info, long presentationTimeUs, int flags)
和
queueInputBuffer(int, int, int, long, int) 相信,但是傳入的 buffer 是加密了的
|
||||||||||
final void | release()
在你調用這個函數的時候,確保釋放了所以不需要用的組件,而不是依賴gc為你做這些事
|
||||||||||
final void | releaseOutputBuffer(int index, boolean render)
如果你對outputbuffer的處理完后,調用這個函數把buffer重新返回給codec類。
|
||||||||||
final void | setParameters(Bundle params)
為組件實例設置一些可選參數。
|
||||||||||
final void | setVideoScalingMode(int mode)
如果在
configure(MediaFormat, Surface, MediaCrypto, int)傳入了surface,可以通過這個函數來設置顯示模式。
|
||||||||||
final void | signalEndOfInputStream()
表明輸入流的結束。
|
||||||||||
final void | start()
如果成功配置組件,調用start來開啟
|
||||||||||
final void | stop()
停止編碼/解碼, 可以通過調用start()來重啟。
|
Public Methods
public void configure (MediaFormat format, Surface surface, MediaCrypto crypto, int flags)
配置一個組件。
Parameters
format | 如果為解碼器,此處表示輸入數據的格式;如果為編碼器,此處表示輸出數據的格式。 |
---|---|
surface | 指定一個surface,可用作decode的輸出渲染。 |
crypto | 如果需要給媒體數據加密,此處指定一個crypto類. |
flags | 如果正在配置的對象是用作編碼器,此處加上CONFIGURE_FLAG_ENCODE 標簽。 |
public static MediaCodec createByCodecName (String name)
如果你知道你想實例化的組件確切名字, 用這個方法來初始化它,謹慎使用這個函數,最好配合從 MediaCodecList獲得的信息來使用。
Parameters
name | 需要實例化的組件的名字. |
---|
public static MediaCodec createDecoderByType (String type)
用給定的媒體格式來創建一個解碼器.下面是部分媒體格式和他們所對應的key的列表:
- "video/x-vnd.on2.vp8" - VP8 video (i.e. video in .webm)
- "video/x-vnd.on2.vp9" - VP9 video (i.e. video in .webm)
- "video/avc" - H.264/AVC video
- "video/mp4v-es" - MPEG4 video
- "video/3gpp" - H.263 video
- "audio/3gpp" - AMR narrowband audio
- "audio/amr-wb" - AMR wideband audio
- "audio/mpeg" - MPEG1/2 audio layer III
- "audio/mp4a-latm" - AAC audio (note, this is raw AAC packets, not packaged in LATM!)
- "audio/vorbis" - vorbis audio
- "audio/g711-alaw" - G.711 alaw audio
- "audio/g711-mlaw" - G.711 ulaw audio
Parameters
type | 輸入數據的多媒體格式. |
---|
public static MediaCodec createEncoderByType (String type)
按給定的媒體類型創建一個編碼器。
Parameters
type | 所需要的輸出媒體格式. |
---|
public final Surface createInputSurface ()
請求一個surface作為解碼器的輸入(替代掉inputbuffer?).這個函數需要在 configure(MediaFormat, Surface, MediaCrypto, int)
之后 start()之前被調用。
程序應該自己調用release去釋放這個 surface而不是等gc 去做。
public final int dequeueInputBuffer (long timeoutUs)
返回一個可用來填充有效數據的inputbuffer的索引(Index),如果返回-1表示暫無可用的buffer. 如果 timeoutUs == 0,該方法會立刻返回值, 如果 timeoutUs < 0 則一直等待,如果 timeoutUs > 0則等待對應時間.
Parameters
timeoutUs | 單位為微秒, 負數表示無窮大. |
---|
public final int dequeueOutputBuffer (MediaCodec.BufferInfo info, long timeoutUs)
排一個output buffer, 等待“timeoutUs"后若無有效值則阻塞,單位為 microseconds. 返回成功解碼的outputbuffer的索引 或者 一個 INFO_* constants 常量.
Parameters
info | buffer的一些信息 |
---|---|
timeoutUs | 單位為微秒, 負數表示無窮大. |
public final void flush ()
flush,重洗組件的input和output接口, 之前調用 dequeueInputBuffer(long)
和dequeueOutputBuffer(MediaCodec.BufferInfo, long)
排好的buffer都變成不可用。
public MediaCodecInfo getCodecInfo ()
獲得codec的信息.如果codec是 createDecoderByType 或createEncoderByType創建的, 事先並不知道用的什么組件,那么 調用這個函數不會返回CodecInfo。
public ByteBuffer[] getOutputBuffers ()
Call this after start() returns and whenever dequeueOutputBuffer signals an output buffer change by returning INFO_OUTPUT_BUFFERS_CHANGED
這句話不好翻譯,大意是在start()調用,或者在dequeueOutputBuffer 返回 INFO_OUTPUT_BUFFERS_CHANGED信息后調用。
public final MediaFormat getOutputFormat ()
如果 dequeueOutputBuffer 返回INFO_OUTPUT_FORMAT_CHANGED ,調用這個函數。
public final void queueInputBuffer (int index, int offset, int size, long presentationTimeUs, int flags)
再給指定索引的inputbuffer填充完數據后,把它交給編碼器. 很多解碼器需要媒體文件的文件頭,例如 vorbis audio中的編碼表 ,AVC video中的PPS/SPS,. MediaExtractor
類提供了codec所需要的多媒體格式信息 ... 這些buffer應該加上 BUFFER_FLAG_CODEC_CONFIG標簽
. 如果這是最后一個輸入數據 (接下來沒有其他的數據輸入,除非馬上要調用flush()) 則應該加上 BUFFER_FLAG_END_OF_STREAM標簽
.
Parameters
index | 前面由 調用 dequeueInputBuffer(long)返回的index |
---|---|
offset | The byte offset into the input buffer at which the data starts. 可以理解為有效數據開始的偏差,一般為0 |
size | 輸入的有效數據的大小 |
presentationTimeUs | 這個buffer被渲染的時間(一般由extractor.getsampleTime獲得) |
flags | 根據需要從 BUFFER_FLAG_SYNC_FRAME , BUFFER_FLAG_CODEC_CONFIG , BUFFER_FLAG_END_OF_STREAM選一個,或者0~ |
Throws
MediaCodec.CryptoException | 如果在 configure(MediaFormat, Surface, MediaCrypto, int)被加密則拋出異常 |
---|
public final void queueSecureInputBuffer (int index, int offset, MediaCodec.CryptoInfo info, long presentationTimeUs, int flags)
與 queueInputBuffer(int, int, int, long, int)相似,但是
傳入的是一個被加密的buffer。
Parameters
index | The index of a client-owned input buffer previously returned in a call to dequeueInputBuffer(long) . |
---|---|
offset | The byte offset into the input buffer at which the data starts. |
info | Metadata required to facilitate decryption, the object can be reused immediately after this call returns. |
presentationTimeUs | The time at which this buffer should be rendered. |
flags | A bitmask of flags BUFFER_FLAG_SYNC_FRAME , BUFFER_FLAG_CODEC_CONFIG or BUFFER_FLAG_END_OF_STREAM . |
Throws
MediaCodec.CryptoException | if an error occurs while attempting to decrypt the buffer. An error code associated with the exception helps identify the reason for the failure. |
---|
public final void releaseOutputBuffer (int index, boolean render)
如果你處理完這個buffer, 調用這個函數把buffer重新返回給codec. 如果你在之前configure()的時候為組件配置了一個surface,那么codec類會在那個surface上顯示這個buffer
Parameters
index | 由codec對象擁有的,由 dequeueOutputBuffer(MediaCodec.BufferInfo, long)返回的索引Index. |
---|---|
render | 如果有可用的顯示surface,傳遞為true可表示顯示。 |
public final void setParameters (Bundle params)
可以給組件加上一些附加參數(具體參數文檔沒寫,估計是仍在測試階段)
public final void setVideoScalingMode (int mode)
如果在 configure(MediaFormat, Surface, MediaCrypto, int)傳入了surface,可以通過這個函數來設置顯示模式。
public final void signalEndOfInputStream ()
標志輸入流的結束。 作用於傳遞 BUFFER_FLAG_END_OF_STREAM
下標相同. 但是這個函數只用在把createInputSurface()返回的surface作為輸入,且codec是編碼器的情況。
public final void start ()
在成功配置組件后, 調用start()函數。相應的可以通過函數 操作inputbuffer和outputbuffer。