日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

使用MediaCodeC将图片集编码为视频

發布時間:2025/3/20 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 使用MediaCodeC将图片集编码为视频 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

原文地址 原創文章,轉載請聯系作者

綠生鶯啼春正濃,釵頭青杏小,綠成叢。 玉船風動酒鱗紅。歌聲咽,相見幾時重?

提要

這是MediaCodeC系列的第三章,主題是如何使用MediaCodeC將圖片集編碼為視頻文件。在Android多媒體的處理上,MediaCodeC是一套非常有用的API。此次實驗中,所使用的圖片集正是MediaCodeC硬解碼視頻,并將視頻幀存儲為圖片文件文章中,對視頻解碼出來的圖片文件集,總共332張圖片幀。
若是對MediaCodeC視頻解碼感興趣的話,也可以瀏覽之前的文章:MediaCodeC解碼視頻指定幀,迅捷、精確

核心流程

MediaCodeC的常規工作流程是:拿到可用輸入隊列,填充數據;拿到可用輸出隊列,取出數據,如此往復直至結束。在一般情況下,填充和取出兩個動作并不是即時的,也就是說并不是壓入一幀數據,就能拿出一幀數據。當然,除了編碼的視頻每一幀都是關鍵幀的情況下。
一般情況下,輸入和輸出都使用buffer的代碼寫法如下:

for (;;) {//拿到可用InputBuffer的idint inputBufferId = codec.dequeueInputBuffer(timeoutUs);if (inputBufferId >= 0) {ByteBuffer inputBuffer = codec.getInputBuffer(…);// inputBuffer 填充數據codec.queueInputBuffer(inputBufferId, …);}// 查詢是否有可用的OutputBufferint outputBufferId = codec.dequeueOutputBuffer(…); 復制代碼

本篇文章的編碼核心流程,和以上代碼相差不多。只是將輸入Buffer替換成了Surface,使用Surface代替InputBuffer來實現數據的填充。

為什么使用Surface

在MediaCodeC官方文檔里有一段關于Data Type的描述:

CodeC接受三種類型的數據,壓縮數據(compressed data)、原始音頻數據(raw audio data)以及原始視頻數據(raw video data)。這三種數據都能被加工為ByteBuffer。但是對于原始視頻數據,應該使用Surface去提升CodeC的性能。

在本次項目中,使用的是MediaCodeCcreateInputSurface函數創造出Surface,搭配OpenGL實現Surface數據輸入。
這里我畫了一張簡單的工作流程圖:

整體流程上其實和普通的MediaCodeC工作流程差不多,只不過是將輸入源由Buffer換成了Surface。

知識點

在代碼中,MediaCodeC只負責數據的傳輸,而生成MP4文件主要靠的類是MediaMuxer。整體上,項目涉及到的主要API有:

  • MediaCodeC,圖片編碼為幀數據
  • MediaMuxer,幀數據編碼為Mp4文件
  • OpenGL,負責將圖片繪制到Surface

接下來,我將會按照流程工作順序,詳解各個步驟:

流程詳解

在詳解流程前,有一點要注意的是,工作流程中所有環節都必須處在同一線程。

配置

首先,啟動子線程。配置MediaCodeC:

var codec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC) // mediaFormat配置顏色格式、比特率、幀率、關鍵幀間隔 // 顏色格式默認為MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface var mediaFomat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, size.width, size.height).apply {setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat)setInteger(MediaFormat.KEY_BIT_RATE, bitRate)setInteger(MediaFormat.KEY_FRAME_RATE, frameRate)setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, iFrameInterval)} codec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE) var inputSurface = codec.createInputSurface() codec.start() 復制代碼

將編碼器配置好之后,接下來配置OpenGL的EGL環境以及GPU Program。由于OpenGL涉及到比較多的知識,在這里便不再贅述。視頻編碼項目中,為方便使用,我將OpenGL環境搭建以及GPU program搭建封裝在了GLEncodeCore類中,感興趣的可以看一下。
EGL環境在初始化時,可以選擇兩種和設備連接的方式,一種是eglCreatePbufferSurface;另一種是eglCreateWindowSurface,創建一個可實際顯示的windowSurface,需要傳一個Surface參數,毫無疑問選擇這個函數。

var encodeCore = GLEncodeCore(...) encodeCore.buildEGLSurface(inputSurface)fun buildEGLSurface(surface: Surface) {// 構建EGL環境eglEnv.setUpEnv().buildWindowSurface(surface)// GPU program構建encodeProgram.build() } 復制代碼

圖片數據傳入,并開始編碼

在各種API配置好之后,開啟一個循環,將File文件讀取的Bitmap傳入編碼。

val videoEncoder = VideoEncoder(640, 480, 1800000, 24) videoEncoder.start(Environment.getExternalStorageDirectory().path+ "/encodeyazi640${videoEncoder.bitRate}.mp4") val file = File(圖片集文件夾地址) file.listFiles().forEachIndexed { index, it ->BitmapFactory.decodeFile(it.path)?.apply {videoEncoder.drainFrame(this, index)} } videoEncoder.drainEnd() 復制代碼

在提要里面也提到了,編碼項目使用的圖片集是之前MediaCodeC硬解碼視頻,并將視頻幀存儲為圖片文件中的視頻文件解碼出來的,332張圖片。
循環代碼中,我們逐次將圖片Bitmap傳入drainFrame(...)函數,用于編碼。當所有幀編碼完成后,使用drainEnd函數通知編碼器編碼完成。

視頻幀編碼

接著我們再來看drameFrame(...)函數中的具體實現。

/**** @b : draw bitmap to texture** @presentTime: frame current time* */fun drainFrame(b: Bitmap, presentTime: Long) {encodeCore.drainFrame(b, presentTime)drainCoder(false)}fun drainFrame(b: Bitmap, index: Int) {drainFrame(b, index * mediaFormat.perFrameTime * 1000)}fun drainCoder(...){偽代碼:MediaCodeC拿到輸出隊列數據,使用MediaMuxer編碼為Mp4文件} 復制代碼

首先使用OpenGL將Bitmap繪制紋理上,將數據傳輸到Surface上,并且需要將這個Bitmap所代表的時間戳傳入。在傳入數據后使用drainCoder函數,從MediaCodeC讀取輸出數據,使用MediaMuxer編碼為Mp4視頻文件。drainCoder函數具體實現如下:

loopOut@ while (true) {// 獲取可用的輸出緩存隊列val outputBufferId = dequeueOutputBuffer(bufferInfo, defTimeOut)Log.d("handleOutputBuffer", "output buffer id : $outputBufferId ")if (outputBufferId == MediaCodec.INFO_TRY_AGAIN_LATER) {if (needEnd) {// 輸出無響應break@loopOut}} else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {// 輸出數據格式改變,在這里啟動mediaMuxer} else if (outputBufferId >= 0) {// 拿到相應的輸出數據if (bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) {break@loopOut}}} 復制代碼

就像之前提到過的,并不是壓入一幀數據就能即時得到一幀數據。在使用OpenGL將Bitmap繪制到紋理上,并傳到Surface之后。要想得到輸出數據,必須在一個無限循環的代碼中,去拿MediaCodeC輸出數據。
也就是在這里的代碼中,當輸出數據格式改變時,為MediaMuxer加上視頻軌,并啟動。

trackIndex = mediaMuxer!!.addTrack(codec.outputFormat)mediaMuxer!!.start() 復制代碼

整體上的工作流程就是以上這些代碼了,傳入一幀數據到Surface-->MediaCodeC循環拿輸出數據--> MediaMuxer寫入Mp4視頻文件。
當然,后兩步的概念已經相對比較清晰,只有第一步的實現是一個難點,也是當時比較困擾我的一點。接下來我們將會詳解,如何將一個Bitmap通過OpenGL把數據傳輸到Surface上。

Bitmap --> Surface

項目中,將Bitmap數據傳輸到Surface上,主要靠這一段代碼:

fun drainFrame(b: Bitmap, presentTime: Long) {encodeProgram.renderBitmap(b)// 給渲染的這一幀設置一個時間戳eglEnv.setPresentationTime(presentTime)eglEnv.swapBuffers() } 復制代碼

其中encodeProgram是顯卡繪制程序,它內部會生成一個紋理,然后將Bitmap繪制到紋理上。此時這個紋理就代表了這張圖片,再將紋理繪制到窗口上。
之后,使用EGL的swapBuffer提交當前渲染結果,在提交之前,使用setPresentationTime提交當前幀代表的時間戳。

更加具體的代碼實現,都在我的Github項目中。GLEncodeCore以及EncodeProgram GPU Program還有EGL 環境構建

結語

此處有項目地址,點擊傳送

與50位技術專家面對面20年技術見證,附贈技術全景圖

總結

以上是生活随笔為你收集整理的使用MediaCodeC将图片集编码为视频的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。