使用MediaCodec實現H264編碼「第四章,Android音視訊編碼那點破事」
本章僅對部分程式碼進行講解,以幫助讀者更好的理解章節內容。
本系列文章涉及的專案HardwareVideoCodec已經開源到Github,支援軟編和硬編。使用它你可以很容易的實現任何解析度的視訊編碼,無需關心攝像頭預覽大小。一切都如此簡單。目前已迭代多個穩定版本,歡迎查閱學習和使用,如有BUG或建議,歡迎Issue。
說到Android的視訊硬編碼,很多新人首先會想到MediaRecorder,這可以說是Android早期版本視訊硬編碼的唯一選擇。這個類的使用很簡單,只需要給定一個Surface(輸入)和一個File(輸出),它就給你生成一個標準的mp4檔案。
但越是簡單的東西便意味著越難以控制,MediaRecorder的缺點很明顯。相信很多人在接觸到斷點視訊錄製
首先,MediaRecorder並沒有斷點錄製的API,當然你可以使用一些“小技巧”,每次錄製的時候,都把MediaRecorder stop掉,然後再次初始化,這樣就會生成一系列的視訊,最後把它們拼接起來。然而問題在於,每次初始化MediaRecorder都需要消耗很長時間,這意味著,當用戶快速點選錄製按鈕的時候可能會出現問題。對於這個問題,你可以等到MediaRecorder初始化完成才讓使用者點選開始錄製,但是這樣往往會因為等待時間過長,導致使用者體驗極差。
這種情況下,一個可控的視訊編碼器是必須的。雖然在Android 4.4以前我們沒得選擇,但是在Android 4.4之後,我們有了
MediaCodec
廢話不多說,我們直接步入正題。要想正確的使用MediaCodec,我們首先得先了解它的工作流程,關於這個,強烈大家去看一下Android文件。呃呃,相信在這個快速開發為王道的環境,沒幾個人會去看,所以還是在這裡簡單介紹一下。
MediaCodec工作流程
- 首先,通過MediaCodec的工廠方法
createEncoderByType
或createByCodecName
建立例項,這時候MediaCodec處於Uninitialized
狀態。- 接下來,呼叫configure(MediaFormat format, Surface surface, MediaCrypto crypto, int flags)設定編碼器引數,這時候MediaCodec處於
Configured
狀態。- 正確設定各種引數之後,呼叫start方法,讓MediaCodec開始編碼,這時候MediaCodec處於
Running
狀態。- 最後順序呼叫
signalEndOfInputStream
、stop
和release
來結束編碼。
流程很簡單,相信大家都能看懂。難點在於running狀態,也就是上圖右側綠色部分的流程。
當MediaCodec處於Running狀態時,內部會持有兩個緩衝區佇列,一個輸入緩衝區,一個輸出緩衝區。當我們向輸入緩衝區輸入資料後,MediaCodec會從中取出資料,送到硬體進行編碼,編碼結束後送到緩衝區,這是一個非同步過程,這時候我們可以從輸出緩衝區取出編碼後的資料。這個過程在更高版本有更好的API,新版MediaCodec可以通過回撥返回編碼後的資料。由於我們可以控制什麼時候給編碼器輸入資料,所以可以隨時暫停或者開始編碼。
理論講的差不多了,接下來我們看一下具體實現。
//初始化一個編碼器配置MediaFormat
fun createVideoFormat(parameter: Parameter, ignoreDevice: Boolean = false): MediaFormat? {
val codecInfo = getCodecInfo(parameter.video.mime, true)
if (!ignoreDevice && null == codecInfo) {//Unsupport codec type
return null
}
val mediaFormat = MediaFormat()
//使用H264編碼
mediaFormat.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_AVC)
//設定視訊寬度
mediaFormat.setInteger(MediaFormat.KEY_WIDTH, width)
//設定視訊高度
mediaFormat.setInteger(MediaFormat.KEY_HEIGHT, height)
//設定視訊輸入顏色格式,這裡選擇使用Surface作為輸入,可以忽略顏色格式的問題,並且不需要直接操作輸入緩衝區。
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface)
//設定視訊位元速率,這裡計算公式選擇一箇中等位元速率,把3改為更大的值可以開啟更高位元速率,通常不建議超過5
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, width * height * fps * 3)
//設定視訊fps
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, fps)
//設定視訊關鍵幀間隔,這裡設定兩秒一個關鍵幀
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
/**
* 可選配置,設定位元速率模式
* BITRATE_MODE_VBR:恆定質量
* BITRATE_MODE_VBR:可變位元速率
* BITRATE_MODE_CBR:恆定位元速率
*/
mediaFormat.setInteger(MediaFormat.KEY_BITRATE_MODE, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR)
/**
* 可選配置,設定H264 Profile
* 需要做相容性檢查
*/
mediaFormat.setInteger(MediaFormat.KEY_PROFILE, MediaCodecInfo.CodecProfileLevel.AVCProfileHigh)
/**
* 可選配置,設定H264 Level
* 需要做相容性檢查
*/
mediaFormat.setInteger(MediaFormat.KEY_LEVEL, MediaCodecInfo.CodecProfileLevel.HEVCHighTierLevel31)
}
return mediaFormat
}
//初始化並配置編碼器
private fun initCodec() {
val format = CodecHelper.createVideoFormat(parameter)
debug_v("create codec: ${format.getString(MediaFormat.KEY_MIME)}")
try {
codec = MediaCodec.createEncoderByType(format.getString(MediaFormat.KEY_MIME))
/**
* 配置編碼器
*/
codec!!.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
/**
* 由於我們使用Surface作為輸入,所以不需要直接操作輸入緩衝區,只需要把MediaCodec生成的Surface繫結到OpenGL即可,所以這裡使用了一個紋理封裝CodecTextureWrapper,請參考前幾章的CameraTextureWrapper和ScreenTextureWrapper,或者直接檢視文章末尾給出的原始碼。
*/
codecWrapper = CodecTextureWrapper(codec!!.createInputSurface(), textureId, eglContext)
codecWrapper?.egl?.makeCurrent()
codec!!.start()
} catch (e: Exception) {
debug_e("Can not create codec")
} finally {
if (null == codec)
debug_e("Can not create codec")
}
}
/**
* 從編碼器迴圈取出編碼資料,通過OpenGL來控制資料輸入,省去了直接控制輸入緩衝區的步驟,所以這裡直接操控輸出緩衝區即可
*/
private fun dequeue(): Boolean {
try {
/**
* 從輸出緩衝區取出一個Buffer,返回一個狀態
* 這是一個同步操作,所以我們需要給定最大等待時間WAIT_TIME,一般設定為10000ms
*/
val flag = codec!!.dequeueOutputBuffer(mBufferInfo, WAIT_TIME)
when (flag) {
MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED -> {//輸出緩衝區改變,通常忽略
debug_v("INFO_OUTPUT_BUFFERS_CHANGED")
}
MediaCodec.INFO_TRY_AGAIN_LATER -> {//等待超時,需要再次等待,通常忽略
// debug_v("INFO_TRY_AGAIN_LATER")
return false
}
/**
* 輸出格式改變,很重要
* 這裡必須把outputFormat設定給MediaMuxer,而不能不能用inputFormat代替,它們時不一樣的,不然無法正確生成mp4檔案
*/
MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> {
debug_v("INFO_OUTPUT_FORMAT_CHANGED")
//這裡通過回撥把outputFormat送出去
onSampleListener?.onFormatChanged(codec!!.outputFormat)
}
else -> {
if (flag < 0) [email protected] false//如果小於零,則跳過
val data = codec!!.outputBuffers[flag]//否則代表便阿門成功,可以從輸出緩衝區佇列取出資料
if (null != data) {
val endOfStream = mBufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM
if (endOfStream == 0) {//如果沒有收到BUFFER_FLAG_END_OF_STREAM訊號,則代表輸出資料時有效的
mBufferInfo.presentationTimeUs = pTimer.presentationTimeUs
//這裡把編碼後的資料通過回撥送出去
onSampleListener?.onSample(mBufferInfo, data)
}
//緩衝區使用完後必須把它還給MediaCodec,以便再次使用,至此一個流程結束,再次迴圈
codec!!.releaseOutputBuffer(flag, false)
// if (endOfStream == MediaCodec.BUFFER_FLAG_END_OF_STREAM) {
// return true
// }
return true
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
return false
}
//編碼結束後,停止編碼器
private fun stop(){
while (dequeue()) {//取出編碼器輸出緩衝區中剩餘的幀資料
}
debug_e("Video encoder stop")
//編碼結束,傳送結束訊號,讓surface不在提供資料
codec!!.signalEndOfInputStream()
codec!!.stop()
codec!!.release()
}
以上就是本章關於MediaCodec的全部學習內容,如果有疑問或者錯誤,歡迎在評論區留言。
本章知識點:
- MediaCodec的工作流程。
- MediaCodec的使用。
相關推薦
使用MediaCodec實現H264編碼「第四章,Android音視訊編碼那點破事」
本章僅對部分程式碼進行講解,以幫助讀者更好的理解章節內容。 本系列文章涉及的專案HardwareVideoCodec已經開源到Github,支援軟編和硬編。使用它你可以很容易的實現任何解析度的視訊編碼,無需關心攝像頭預覽大小。一切都如此簡單。目前已迭代多個穩定版本,歡迎
使用java實現面向對象第四章
變量 面向接口 public 其他 多態 void () 具體實現 實例 第四章 接口 一、接口 1.接口可以看作是一種特殊的“抽象類”。 2.接口有比抽象類更好的特性 3.可以被多繼承 4.設計和實現完全分離 5
Linux內核設計與實現 總結筆記(第四章)進程調度
什麽 原則 好的 nic 調度系統 相交 中間 使用 就是 進程調度 調度程序負責決定將哪個進程投入運行,何時運行以及運行多長時間。 調度程序沒有太復雜的原理,最大限度地利用處理器時間的原則是,只要有可以執行的進程,那麽就總會有進程正在執行。 多任務 多任務系統可以劃分
【MySQL】《高性能MySQL》學習筆記,第四章,Schema與數據類型優化
MySQL優化 MySQL表設計 MySQL開發規範 MySQL數據類型 【MySQL】《高性能MySQL》學習筆記,第四章,Schema與數據類型優化 良好的邏輯設計和物理設計是高性能的基石,應該根據系統將要執行的查詢語句來設計schema。 反範式的設計可以加快某些類型的查詢,單同時可能使
《數學之美》讀書記錄【思維導圖記錄】:第四章,談談中文分詞
post IT .cn splay top style title mage blog 《數學之美》讀書記錄【思維導圖記錄】:第四章,談談中文分詞
淺談RPA--第四章,RPA是否需要BA
首先,為大家普及一下,何為BA,為啥企業內部要存在BA. Business Analytics vs Business analysis,首先博主在這裡介紹的是Business analysis而非Business Analytics。好奇的盆友會問,二者究竟有啥子區別呀?
大牛請進,Android音視訊聊天各種實現方式問題
最近專案需要,要完成一款區域網通訊軟體,主要實現Android端的文字、語音、視訊聊天功能,並且實現伺服器,伺服器端能實現客戶端上下線監控以及資源推送等簡單功能,但是伺服器端現在還沒人做,想找找有沒有現成的,減少工作量。 這兩天查了一些資料看主流
第四章 字符編碼
2個 明顯 更多 post pychar 16進制 滿足 就是 直接 1、存取文件不亂碼的法則:用什麽編碼存的,就要用什麽編碼讀 2、 decode encode bytes------------->unicode----
Taglib 原理和實現:第四章 迴圈的Tag
1。問題:在request裡的 People 物件,有個屬性叫 men ,men 是一個Collection ,有許多個man 。現在,把 collection裡的man的名字都顯示出來 顯然,這是一個巢狀T
《機器學習》 周志華學習筆記第四章 決策樹(課後習題)python 實現
一、基本內容 1.基本流程 決策樹的生成過程是一個遞迴過程,有三種情形會導致遞迴返回 (1)當前節點包含的yangben全屬於同一類別,無需劃分; (2)當前屬性集為空,或是所有yangben在所有屬性上的取值相同,無法劃分; (3)當前結點包含的yangben集合為空,不能
深入淺出學Vue開發:第四章、Vue的生命週期及原始碼實現
歡迎大家訪問我的個人網站 - Sunday俱樂部 通過上面兩章的學習,我們已經學會了Vue的所有基礎語法,包括: 1、{{Mustache}} 語法 2、v-if、v-else、v-else-if、v-show 3、v-for 4、v-bind 5、v-mo
2018/12/01 一個64位作業系統的實現 第四章 匯入kernel.bin(2)
在做程式4-1的實驗的時候, 我刪除了之前的虛擬軟盤和boot.bin、loader.bin、kernel.bin等二進位制檔案, 從頭開始新建虛擬軟盤等等, 試驗成功後. 我嘗試的將原來的kernel.bin 檔案刪除後, 將程式4-2中的kernel.bin檔案複製到bochs-2.6.9資料夾中,
2018/12/01 一個64位作業系統的實現 第四章 匯入kernel.bin(5)
參照之前的部落格, 我直接將程式4-5中生成的kernel.bin程式碼複製到bochs-2.6.9資料夾中, 使用部落格中的描述將kernel.bin載入虛擬軟盤的命令, 執行後, 得到成功的結果: 之後又按照書本上的要求將程式4-目錄下的main.c檔案中的 i = 1/0; 修
2018/12/01 一個64位操作系統的實現 第四章 內存管理(1)
文件的 ade png oot mage make 源文件 href http 本來打算刪掉源文件的kernel.bin文件, 然後直接用程序4-6中make生成的kernel.bin替代, 然而不行, 可能是我操作錯誤, 我直接將boot.img boot.bin loa
2018/12/01 一個64位操作系統的實現 第四章 導入kernel.bin(2)
64位操作系統 bin文件 color 文件刪除 mage inf 操作系統 技術分享 http 在做程序4-1的實驗的時候, 我刪除了之前的虛擬軟盤和boot.bin、loader.bin、kernel.bin等二進制文件, 從頭開始新建虛擬軟盤等等, 試驗成功後.
第四章 Runtime應用:實現NSCoding的自動歸檔和自動解檔
用runtime提供的函式遍歷Model自身所有屬性,並對屬性進行encode和decode操作。 通常系統自帶的資料型別,如:字典NSDictionary,陣列NSArray,字串NSString,
人工智慧:python 實現 第十章,NLP 第四天 A Bag Of Words
使用用詞袋(a bag of words)模型提取頻繁項文字分析的主要目標之一是將文字轉化為數值形式。以便使用機器進行學習。我們考慮下,數以百萬計的單詞文件,為了去分析這些文件,我們需要提取文字 並且將其轉化為數值符號。機器學習演算法需要處理數值的資料,以便他們能夠分析資料並
第四章 資料庫設計與實現
資料庫結構模型 概念資料模型(Concept Data Model,CDM)是一種面向使用者的系統資料模型,它用來描述現實世界的系統概念化資料結構。使資料庫設計人員在系統設計的初始階段,擺脫計算機系統及DBMS的具體技術問題,集中精力分析業務資料以及資料之間的聯絡等,描述系統的資料物件及其組成關
【第四章】 資源 之 4.2 內建Resource實現 ——跟我學spring3
4.2 內建Resource實現 4.2.1 ByteArrayResource ByteArrayResource代表byte[]陣列資源,對於“getInputStream”操作將返回一個ByteArrayInputStream。 首先讓我們看下使用ByteArrayResource如
微信開發(從申請微信到註冊上線的一整套流程)第四章 實現天氣預報功能
這一章裡,我們來實現微信上的天氣預報功能,我們使用方倍工作室的天氣預報介面,其介面為 http://apix.sinaapp.com/weather/ 這個介面的引數appkey為公眾號原始id,引數city為城市名 例如,查詢深圳的天氣預報時,將city值做urlenc