最近做了一個android項目用到編解碼功能。大概需求是:通過攝像頭拍攝一段視頻,然後抽幀,生成一個短視頻,以及倒序視頻,剛開始直接用 H.264 編碼格式,沒有使用MP4容器封裝。做了這些功能後,反而覺得使用MP4格式更加相容各機型,減少BUG出現。舉個明顯例子:在Android硬編的時候,常常會 ...
最近做了一個android項目用到編解碼功能。大概需求是:通過攝像頭拍攝一段視頻,然後抽幀,生成一個短視頻,以及倒序視頻,剛開始直接用 H.264 編碼格式,沒有使用MP4容器封裝。做了這些功能後,反而覺得使用MP4格式更加相容各機型,減少BUG出現。舉個明顯例子:在Android硬編的時候,常常會用到 MediaCodec和MediaExtractor 相結合。但是,如果你用的 H.264 裸視頻文件,MediaExtractor 的 setSource 函數會報異常,它在某些機型(如魅族Note2,系統是5.1)無法解析該視頻文件。 得到大概的需求後,最初我們使用FFmpeg來做視頻編解碼,所謂軟體編解碼。由於在處理的過程中速率太慢,且需要在解碼後快速展示,所以該方案無法達到我們的預想效果(一個FFmpeg視頻解碼,並保存為jpeg例子:https://github.com/xiaoxiaoqingyi/ffmpeg-android-video-decoder)。但其也有一些優點,比如在相容方面,顏色轉換方面都做得很好,畢竟不是硬體編解碼(國內這麼多機型,你懂的),其次FFmpeg能輸出指定幀,而Android硬解(MediaCodec)不能輸出指定幀,需要輸入好幾幀到解碼器,才能解碼出一幀。目前我還是沒有找到輸入一幀解出一幀的方案,哪位大神知道的,可以指導指導。 在軟體編解碼不太適合的情況下,就只能考慮用硬體編解碼了(MediaCodec)。在前些日子,我參加了騰訊2017LIVE 直播開發者大會,瞭解到,現在的直播已經大部分使用硬體來編解碼了。剛說了,有些機型不能使用MediaExtractor來解析 h.264文件,為了相容大部分的機型,需要自己來解析,通過分析h.264文件的每一個位元組區分每一幀位置且是什麼類型幀。實現該需求,首先在從攝像頭獲取的數據,如果使用 Camera,一般設置為NV21格式, 但有些人使用Camera2,設置的格式是IMAGE。不管是哪種格式,最終都需要轉換成yuv420sp或yuv420p(註意:在轉碼時候,最好使用jni,用C/C++來轉格式,效率會高很多倍),才能供MediaCodec編碼,然後保存h.264文件。在創建MediaCodec實例化的時候,除了設置必須參數外,也要註意一些地方,比如,選擇哪種編碼器,一般情況會選擇如下:
MediaCodec.createEncoderByType("video/avc");這看上去其實沒什麼問題,大概原理就是獲取最優的Encoder,獲取Android系統中編碼器註冊表最前的一個,一般都是硬體解碼(MediaCodec也能調用軟體編解碼)。這樣創建編碼器其實不太靠譜,雖然官網也是這麼推薦,但是在國內眾多的Android機型中,有些手機就會出問題,有的編碼出現藍屏,有些直接就閃退了。有個國外的例子,大概的意思就是先獲取 "video/avc" 類型的編碼器,然後通過 try catch 一個個試驗,如果沒問題,就選用這個編碼器。源碼:https://github.com/ldm520/android_mediacodec_rtsp_h264 還有一個問題就是在設置 I 幀間隔的時候,有些手機不起作用,如下設置:
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, interval);針對這種情況,需要使用另外一種設置I幀的方式,強制設置:
Bundle params = new Bundle(); params.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0); mMediaCodec.setParameters(params);在編解碼時,當把所有的數據都輸入編解碼器的時候,要記得輸入結束符,編解碼器才會輸出所有的幀。 還有一個抽幀問題,如果使用MediaCodec來抽幀,生成一個新的視頻。是否可以直接把H.264文件里的幀去掉就行了?這樣不行的,通常會出現花屏。這需要重新把h.264文件輸入到解碼器,然後獲取到自己想要的幀,再輸入到一個新的編碼器中,生成你想要的H.264文件。在這裡還有一個格式問題,並不是從解碼器解碼出來的數據,就能直接使用編碼器來編碼,有部分手機可以,有些會出現藍屏,甚至閃退的情況。這時候需要統一解碼器處理的格式。如果你使用這種形式獲取:
mMediaCodec.getOutputBuffer()出來的格式各種各樣,你很難去相容。google已經推出了一種新的格式:
mMediaCodec.getOutputImage(outIndex)得出的是一個Image 對象,該對象可以保存為 JPEG格式圖片,也可以轉換成NV21(參考:http://www.cnblogs.com/welhzh/p/6079631.html),像上面拍攝部分,轉換成yuv422格式,再輸入到編碼器編碼。這樣不管什麼機型都可以相容了(我試用10多部不同廠商手機),雖然繞了很多彎路。 在使用MediaCodec還是遇到比較多的問題,畢竟官網都說它是一個輕量的編解碼器封裝。該總結適合使用過MediaCodec或有一定的解碼編碼經驗的童鞋們。如果你還沒瞭解過 MediaCodec,可以參考官網: https://developer.android.google.cn/reference/android/media/MediaCodec.html 或者中文翻譯版:http://blog.csdn.net/yssjz960427031/article/details/70050142 在使用MediaCodec的時候還遇到很多問題,這裡沒有一一列舉出來, 歡迎有遇到同樣問題或類似問題的童鞋留言討論!