Android原生編解碼接口 MediaCodec 之——踩坑

版權聲明:本文爲博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連接和本聲明。
本文連接:https://blog.csdn.net/gb702250823/article/details/81669684
但願咱們尊重每一個人的成果,轉載請標明出處:
https://blog.csdn.net/gb702250823/article/details/81669684
本文出自小口鍋的博客java

關鍵幀
MediaCodec 有兩種方式觸發輸出關鍵幀,一是由配置時設置的 KEY_FRAME_RATE和KEY_I_FRAME_INTERVAL參數自動觸發,二是運行過程當中經過 setParameters 手動觸發輸出關鍵幀。android

自動觸發輸出關鍵幀
在MediaCodec硬編碼中設置I(關鍵幀)時間間隔,在 api 中是這麼設置的api

mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); //關鍵幀間隔時間 單位s
1
自動觸發實際是按照幀數觸發的,例如設置幀率爲 20 fps,關鍵幀間隔爲 1s ,那就會每 20楨輸出一個關鍵幀,一旦實際幀率低於配置幀率,那就會致使關鍵幀間隔時間變長。因爲 MediaCodec 啓動後就不能修改配置幀率/關鍵幀間隔了,因此若是但願改變關鍵幀間隔幀數,就必須重啓編碼器。緩存

手動觸發輸出關鍵幀:ide

if (System.currentTimeMillis() - timeStamp >= 1000) {//1000毫秒後,設置參數
timeStamp = System.currentTimeMillis();
if (Build.VERSION.SDK_INT >= 23) {
Bundle params = new Bundle();
params.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
mMediaCodec.setParameters(params);
}


關鍵幀踩坑
有時候你會發現自動觸發關鍵幀方式失效了ui

經排查發現真正的緣由是在於視頻的輸入源,若是是經過Camera的PreviewCallback的方式來獲取視頻數據再餵給MediaCodec的方式是沒法控制輸出關鍵幀的數量的。
發現當選擇支持顏色格式爲yuv420p的編碼器時,KEY_I_FRAME_INTERVAL 設置無效;
選擇支持yuv420sp的編碼器時,KEY_I_FRAME_INTERVAL 設置有效;編碼

想要控制輸出輸出關鍵幀數量就必須經過調用MediaCodec.createInputSurface()方法獲取輸入Surface,再經過Opengl渲染後餵給MediaCodec才能真正控制關鍵幀的數量。.net

//判斷輸出數據是否爲關鍵幀的方法:
boolean keyFrame = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0;

部分機型MediaCodec.configure直接crash
未設置編碼強制要求的一些配置 會拋出 IllegalStateException
看這個例子stackoverflow線程

若是初始化MediaFormat視頻流的預設寬高高於當前手機支持的解碼最大分辨率那麼在調用MediaCodec.configure的時候就會crash。把MediaFormat.createVideoFormat時候的寬高設置小一點就ok了,那麼就會有另一個問題,就是若是我設置1080*720的後,視頻流來了一個1920*1080的會不會有影響?若是當前設備的最大分辨率高於這個值,就算預設值不同,也仍是能夠正常解碼並顯示1920*1080的畫面。那麼若是低於這個值呢?兩種狀況 綠屏/MediaCodec.dequeueInputBuffer的值一直拋IllegalStateExceptioncode

如何獲取當前手機支持的解碼最大分辨率
每一個手機下都有這樣一個文件,/system/etc/media_codecs.xml (your path)。這是一個xml文件,能夠直接看到MediaCodecs–>Decoders節點下的各個視頻格式的支持狀況,以華爲榮耀7x Android 8.0 爲例


獲取解碼視頻的寬和高

//得到音視頻的配置器MediaFormat
private static MediaFormat getFormat(String path,boolean isVideo) {
try {
MediaExtractor mediaExtractor = new MediaExtractor();
mediaExtractor.setDataSource(path);
int trackCount = mediaExtractor.getTrackCount();
for (int i = 0; i < trackCount; i++) {
MediaFormat trackFormat = mediaExtractor.getTrackFormat(i);
if (trackFormat.getString(MediaFormat.KEY_MIME).startsWith(isVideo ? "video/" :"audio/")) {
return mediaExtractor.getTrackFormat(i);
}
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
//單獨獲取寬高
MediaFormat newFormat = getFormat(path,true);
int videoWidth = newFormat.getInteger(MediaFormat.KEY_WIDTH);
int videoHeight = newFormat.getInteger(MediaFormat.KEY_HEIGHT);
//結合編碼時獲取寬高
MediaFormat newFormat = mMediaCodec.getOutputFormat();
int videoWidth = newFormat.getInteger(MediaFormat.KEY_WIDTH);
int videoHeight = newFormat.getInteger(MediaFormat.KEY_HEIGHT);

部分機型MediaCodec.dequeueOutputBuffer一直報IllegalStateException
部分機型會一直卡在MediaCodec.INFO_TRY_AGAIN_LATER中,有的緣由也是由於這個
該機型硬解碼最大配置分辨率低於當前視頻流的分辨率

部分機型輸出的數據過短,或者爲0
取出 output buffer 後,要手動設置 position 和 limit(api19如下必須設置),有些設備的編碼器不會設置這兩個值,致使沒法正確取出數據;取出 input buffer 後,要手動調用 clear。參見 bigflake FAQ #11

mMediaCodec.createInputSurface()建立失敗或者取出的數據不理想
雖然 mMediaCodec.createInputSurface() 從 API 18 就已經引入,但用在某些 API 18 的機型上會致使編碼器輸出數據量特別小,畫面是黑屏,因此 Surface 輸入模式從 API 19 啓用。
mMediaCodec.createInputSurface() 必須在mediaformat.configure(..)以後,在mediacodec.start() 以前調用。

關於BufferInfo中的presentationTimeUs設置
若是不正確設置presentationTimeUs,有的設備的編碼器會丟掉輸入楨,或者輸出圖像質量不好,參見bigflake FAQ #8;
MediaCodec 使用的是微秒,大多數java 使用毫秒和納秒,單位要處理好

若是採用surface輸入,想要丟幀要如何操做
??(什麼時候開始編碼條件) 知足該條件後開始編碼。這就作到可控了。

if (mBufferInfo.size != 0 && ??(什麼時候開始編碼條件)) {
outputBuffer.position(mBufferInfo.offset);
outputBuffer.limit(mBufferInfo.offset + mBufferInfo.size);
// mMuxer.writeSampleData(mTrackIndex, encodedData, bufferInfo);
} else {
outputBuffer.clear();
}
// 處理結束,釋放輸出緩存區資源
mMediaCodec.releaseOutputBuffer(outputBufferIndex,false);

輸出數據和寫入數據爲MP4要在同一線程中,不然會出現偶發性的花屏,馬賽克等
如下作法會出現花屏,馬賽克

if (mBufferInfo.size != 0) {
outputBuffer.position(mBufferInfo.offset);
outputBuffer.limit(mBufferInfo.offset + mBufferInfo.size);
//這邊線程就懶得寫成阻塞等待數據做用了,懶。知道這個狀況就OK了
new Thread(new Runnable() {
@Override
public void run() {
// mMuxer.writeSampleData(mTrackIndex, encodedData, bufferInfo);
}
}).start();
}

關於 bitrate_mode 配置問題
MediaCodec中的bitrate mode有個坑,好比我在設置以前想確認下CBR是否支持,那麼會調用isBitrateModeSupported()判斷,這樣會有問題。

// MediaCodecInfo.java
public boolean isBitrateModeSupported(int mode) {
for (Feature feat: bitrates) {
if (mode == feat.mValue) {
return (mBitControl & (1 << mode)) != 0;
}
}
return false;
}

mode是否支持從bitrates判斷

private static final Feature[] bitrates = new Feature[] {
new Feature("VBR", BITRATE_MODE_VBR, true),
new Feature("CBR", BITRATE_MODE_CBR, false),
new Feature("CQ", BITRATE_MODE_CQ, false)
};

framework竟然把它寫死了!而不是從hardware或者xml中獲取,而xml是寫着支持的。
vendor/rockchip/common/vpu/etc/media_codecs.xml

若是你寫代碼比較嚴謹,先用isBitrateModeSupported()判斷CBR是否支持,那麼就悲劇了。
另外,在默認狀況下,若是上層沒有主動設置bitrate_mode的話,返回的是VBR。 也就是默認採用VBR 關於VBR CQ CBR區別,可查看Android原生編解碼接口 MediaCodec 之——徹底解析中的流控。

關於 level、profile設置
因爲視頻編碼後顯示的數據質量偏低,因此須要調整質量。這個時候須要在這個設置level、profile

Profile是對視頻壓縮特性的描述(CABAC呀、顏色採樣數等等)。
Level是對視頻自己特性的描述(碼率、分辨率、fps)。
簡單來講,Profile越高,就說明採用了越高級的壓縮特性。
Level越高,視頻的碼率、分辨率、fps越高

// 不支持設置Profile和Level,而應該採用默認設置mediaFormat.setInteger(MediaFormat.KEY_PROFILE, MediaCodecInfo.CodecProfileLevel.AVCProfileHigh); mediaFormat.setInteger("level", MediaCodecInfo.CodecProfileLevel.AVCLevel41); // Level 4.1關於設置這兩個參數,我發現某些設備上,設置了無效,仍是默認值,經排查 是由於在android7.0如下,android 內部寫死了參數,編碼出來的只能是Baseline,除非系統改過這個BUG,否者設置無效,甚至會致使configure參數失敗。————————————————版權聲明:本文爲CSDN博主「小口鍋」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連接及本聲明。原文連接:https://blog.csdn.net/gb702250823/article/details/81669684

相關文章
相關標籤/搜索