isEncoding = true //開始編碼
mMediaCodec.start() //構(gòu)建連接器。
mWorkerThread = HandlerThread(“WorkerThread-Encoder”)
mWorkerThread.start()
mHandler = Handler(mWorkerThread.looper)
}
注意,我們并不在此處就開啟Muxer,我們會在子線程中接受數(shù)據(jù)的時候的某個狀態(tài)開始進(jìn)行混合。
mCameraDevice.setPreviewCallback { data, camera ->
if (::mHandler.isInitialized) {
mHandler.post {
//把橫版視頻分辨率:1920 * 1080 轉(zhuǎn)換成豎版: 1080 * 1920
val verticalData = ImageFormatUtils.rotateYUV420Degree90(data, mPreviewSize.height, mPreviewSize.width)
onFrameAvailable(verticalData)
}
}
}
我在查詢Camera支持的分辨率的時候,發(fā)現(xiàn)所有的分辨率都是橫版的分辨率,即:1920*1080版本的,但是我們MediaCodec最初設(shè)定的分辨率是豎版的,這里也是一個坑。
onFrameAvailable()方法中,我們不斷地插入一個byte數(shù)組,這個數(shù)組中是相機(jī)實時傳來的預(yù)覽畫面,我們對這個畫面進(jìn)行編碼即可。編碼完成后,將編碼出來的畫面接入到Muxer中:
private fun onFrameAvailable(_data: ByteArray?) {
if (!isEncoding) {
return;
}
//(可選NV21->I420),然后送入解碼器
val data: ByteArray = _data!!
var index = 0
try {
index = mMediaCodec.dequeueInputBuffer(0)
} catch (e: Exception) {
e.printStackTrace()
return
}
if (index >= 0) {
val inputBuffer = mMediaCodec.getInputBuffer(index)
inputBuffer!!.clear()
inputBuffer.put(data, 0, data.size)
mMediaCodec.queueInputBuffer(
index,
0,
data.size,
System.nanoTime() / 1000,
0)
}
while (true) {
val bufferInfo = MediaCodec.BufferInfo()
val encoderStatus = mMediaCodec.dequeueOutputBuffer(bufferInfo, 10_000)
if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
break//稍后再試
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
//輸出的格式發(fā)生了改變,此處開啟混合器
val newFormat = mMediaCodec.outputFormat
mVideoTrack = mMuxer!!.addTrack(newFormat)
mMuxer!!.start()
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
//
} else {
//正常編碼則獲得緩沖區(qū)下標(biāo)
val encodedDat = mMediaCodec.getOutputBuffer(encoderStatus)
if ((bufferInfo.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
bufferInfo.size = 0
}
if (bufferInfo.size != 0) {
//設(shè)置從XX地方開始讀取數(shù)據(jù)
encodedDat!!.position(bufferInfo.offset)
//設(shè)置讀數(shù)據(jù)總長度
encodedDat.limit(bufferInfo.offset + bufferInfo.size)
//寫出MP4
if (!isEncoding) {
return
}
mMuxer!!.writeSampleData(mVideoTrack, encodedDat, bufferInfo)
}
//釋放緩沖區(qū)
mMediaCodec.releaseOutputBuffer(encoderStatus, false)
}
}
}
這個方法是在子線程中執(zhí)行的。
七. 生成文件
private fun pauseRecord() {
+send//顯示發(fā)送按鈕
record.isRunning = false
Timer.cancel()//取消計時
showBackOrCancel()
if (isEncoding) {
stopEncoder()
}
}
private fun stopEncoder() {
isEncoding = false
Toast(this.obbDir.absolutePath + “\下”)
try {
mMuxer?.stop()
mMuxer?.release()
//停止
mMediaCodec.stop()
mMediaCodec.release()
} catch (e: Exception) {
e.printStackTrace()
}
}
這樣一來,我們在存儲目錄中的Android/obb/包名/
下就有生成的文件了。
八. 總結(jié)
總體來說還是挺簡陋的,比如沒有根據(jù)具體的設(shè)備動態(tài)地去判斷錄制的尺寸、錄制的色彩格式選擇等等,相機(jī)相關(guān)的功能閃光燈、對焦也未加入。
MediaCodec本身是編解碼器,和FFmpeg不同,它會優(yōu)先進(jìn)行硬件解碼,效率高,功耗低,但是缺點就是,兼容性、可擴(kuò)展性相對于軟件解碼來說會更低。有一部分的播放軟件,將硬解還是軟解的選擇權(quán)交給了用戶,這樣既可以兼顧到擴(kuò)展性,又可以兼顧到功耗。
最終實現(xiàn)的效果(沒對焦):
附. 一些相關(guān)的方法:
1. 橫屏Nv21->豎屏Nv21的排列:
public byte[] rotateYUV420Degree90(byte[] data, int imageWidth, int imageHeight) {
byte[] yuv = new byte[imageWidth * imageHeight * 3 / 2];
// Rotate the Y luma
int i = 0;
for (int x = 0; x < imageWidth; x++) {
for (int y = imageHeight - 1; y >= 0; y–) {
yuv[i] = data[y * imageWidth + x];
i++;
}
}
// Rotate the U and V color components
i = imageWidth * imageHeight * 3 / 2 - 1;
for (int x = imageWidth - 1; x > 0; x = x - 2) {
for (int y = 0; y < imageHeight / 2; y++) {
yuv[i] = data[(imageWidth * imageHeight) + (y * imageWidth) + (x - 1)];
i–;
yuv[i] = data[(imageWidth * imageHeight) + (y * imageWidth) + x];
i–;
}
}
return yuv;
}
2. 查詢設(shè)備支持的色彩格式
public static int getSupportColorFormat() {
int numCodecs = MediaCodecList.getCodecCount();
MediaCodecInfo codecInfo = null;
for (int i = 0; i < numCodecs && codecInfo == null; i++) {
MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
if (!info.isEncoder()) {
continue;
}
String[] types = info.getSupportedTypes();
boolean found = false;
自我介紹一下,小編13年上海交大畢業(yè),曾經(jīng)在小公司待過,也去過華為、OPPO等大廠,18年進(jìn)入阿里一直到現(xiàn)在。
深知大多數(shù)Android工程師,想要提升技能,往往是自己摸索成長或者是報班學(xué)習(xí),但對于培訓(xùn)機(jī)構(gòu)動則幾千的學(xué)費,著實壓力不小。自己不成體系的自學(xué)效果低效又漫長,而且極易碰到天花板技術(shù)停滯不前!
因此收集整理了一份《2024年Android移動開發(fā)全套學(xué)習(xí)資料》,初衷也很簡單,就是希望能夠幫助到想自學(xué)提升又不知道該從何學(xué)起的朋友,同時減輕大家的負(fù)擔(dān)。
既有適合小白學(xué)習(xí)的零基礎(chǔ)資料,也有適合3年以上經(jīng)驗的小伙伴深入學(xué)習(xí)提升的進(jìn)階課程,基本涵蓋了95%以上Android開發(fā)知識點,真正體系化!
由于文件比較大,這里只是將部分目錄大綱截圖出來,每個節(jié)點里面都包含大廠面經(jīng)、學(xué)習(xí)筆記、源碼講義、實戰(zhàn)項目、講解視頻,并且后續(xù)會持續(xù)更新
如果你覺得這些內(nèi)容對你有幫助,可以添加V:vip204888 備注Android獲?。ㄙY料價值較高,非無償)
最后
在這里我和身邊一些朋友特意整理了一份快速進(jìn)階為Android高級工程師的系統(tǒng)且全面的學(xué)習(xí)資料。涵蓋了Android初級——Android高級架構(gòu)師進(jìn)階必備的一些學(xué)習(xí)技能。
附上:我們之前因為秋招收集的二十套一二線互聯(lián)網(wǎng)公司Android面試真題(含BAT、小米、華為、美團(tuán)、滴滴)和我自己整理Android復(fù)習(xí)筆記(包含Android基礎(chǔ)知識點、Android擴(kuò)展知識點、Android源碼解析、設(shè)計模式匯總、Gradle知識點、常見算法題匯總。)
id高級架構(gòu)師進(jìn)階必備的一些學(xué)習(xí)技能。**
附上:我們之前因為秋招收集的二十套一二線互聯(lián)網(wǎng)公司Android面試真題(含BAT、小米、華為、美團(tuán)、滴滴)和我自己整理Android復(fù)習(xí)筆記(包含Android基礎(chǔ)知識點、Android擴(kuò)展知識點、Android源碼解析、設(shè)計模式匯總、Gradle知識點、常見算法題匯總。)
[外鏈圖片轉(zhuǎn)存中…(img-7kBRVeTn-1711545185388)]文章來源:http://www.zghlxwxcb.cn/news/detail-853252.html
本文已被CODING開源項目:《Android學(xué)習(xí)筆記總結(jié)+移動架構(gòu)視頻+大廠面試真題+項目實戰(zhàn)源碼》收錄文章來源地址http://www.zghlxwxcb.cn/news/detail-853252.html
到了這里,關(guān)于Android 音視頻入門 (四)- 記錄一次MediaCodec ,天吶的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!