網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
一、介紹以及編解碼流程
MediaCodec 類(lèi)可用于訪問(wèn)低級(jí)媒體編解碼器,即編碼器/解碼器組件。它是 Android 低級(jí)多媒體支持基礎(chǔ)結(jié)構(gòu)的一部分(通常與MediaExtractor、MediaSync、MediaMuxer、MediaCrypto、 MediaDrm、Image、Surface和一起使用AudioTrack。)
由上圖可以知道,大概的流程就是:
1、Client 通過(guò)dequeueInputBuffer 申請(qǐng)一個(gè)空的buffers緩沖區(qū)
2、通過(guò)queueInputBuffer 將數(shù)據(jù)填充到緩沖區(qū)
3、傳給編碼器或者解碼器(Codec)
4、通過(guò)dequeueOutputBuffer 將編解碼后的數(shù)據(jù)下發(fā)到輸出緩沖區(qū)
5、Client 接收并消耗掉輸出緩沖區(qū)的數(shù)據(jù)后,將其釋放到編解碼器中
二、生命周期
由上圖可以知道,生命周期大概會(huì)分為3類(lèi):
Stoped(停止):
當(dāng) 使用MediaCodec.createEncoderByType 創(chuàng)建編解碼類(lèi)型時(shí),此時(shí)是 Uninitialized 狀態(tài)
當(dāng)使用encoderCodec.configure()接口時(shí),編解碼器會(huì)進(jìn)入configure狀態(tài)。
Executing(執(zhí)行):
encoderCodec.start() 調(diào)用后,將使編解碼器進(jìn)入Executing 狀態(tài)!
Flushed:在調(diào)用start()方法后MediaCodec立即進(jìn)入Flushed子狀態(tài),此時(shí)MediaCodec會(huì)擁有所有的緩存。可以在Executing狀態(tài)的任何時(shí)候通過(guò)調(diào)用flush()方法返回到Flushed子狀態(tài)。
Running:一旦第一個(gè)輸入緩存(input buffer)被移出隊(duì)列,MediaCodec就轉(zhuǎn)入Running子狀態(tài),這種狀態(tài)占據(jù)了MediaCodec的大部分生命周期。通過(guò)調(diào)用stop()方法轉(zhuǎn)移到Uninitialized狀態(tài)。
End-of-Stream:將一個(gè)帶有end-of-stream標(biāo)記的輸入buffer入隊(duì)列時(shí),MediaCodec將轉(zhuǎn)入End-of-Stream子狀態(tài)。在這種狀態(tài)下,MediaCodec不再接收之后的輸入buffer,但它仍然產(chǎn)生輸出buffer直到end-of-stream標(biāo)記輸出。
Released(釋放):
當(dāng)使用完MediaCodec后,必須調(diào)用release()方法釋放其資源。調(diào)用 release()方法進(jìn)入最終的Released狀態(tài)。
三、API 接口
//創(chuàng)建解碼器(type為mime或name)
public static MediaCodec createDecoderByType(String type)
//創(chuàng)建編碼器(type為mime或name)
public static MediaCodec createEncoderByType(String type)
//配置解碼器和編碼器(flag為0表示解碼器,1表示編碼器)
public void configure(MediaFormat format, Surface surface, MediaCrypto crypto, int flags)
//啟動(dòng)編碼器或解碼器
public final void start()
//獲取輸入隊(duì)列的一個(gè)空閑索引(timeoutUs:最多等待時(shí)間,-1表示一直等待,單位:微秒us)
public final int dequeueInputBuffer(long timeoutUs)
//獲取輸入隊(duì)列的一個(gè)空閑緩存區(qū)(index:dequeueInputBuffer方法的返回值)
public ByteBuffer getInputBuffer(int index)
//提醒解碼器或編碼器處理數(shù)據(jù)(index:dequeueInputBuffer方法的返回值)
public final void queueInputBuffer(int index, int offset, int size, long presentationTimeUs, int flags)
//創(chuàng)建BufferInfo類(lèi),用于存儲(chǔ)解碼或編碼后的緩存數(shù)據(jù)的格式信息
public final static class BufferInfo{}
//獲取輸出隊(duì)列的一個(gè)緩存區(qū)的索引,并將格式信息保存在info中(timeoutUs:最多等待時(shí)間,-1表示一直等待,單位:微秒us)
public final int dequeueOutputBuffer(BufferInfo info, long timeoutUs)
//獲取輸出隊(duì)列的一個(gè)緩存區(qū)(index:dequeueOutputBuffer方法的返回值)
public ByteBuffer getOutputBuffer(int index)
//清除index指向的緩存區(qū)中的數(shù)據(jù)
public final void releaseOutputBuffer(int index, boolean render)
//結(jié)束解碼或編碼會(huì)話
public final void stop()
//釋放資源
public final void release()
五、封裝(kotlin)
這里封裝的主要是同步模式:
package com.kt.ktmvvm.jetpack.mediacodec
import android.media.MediaCodec
import android.media.MediaMuxer
import android.os.Environment
import android.util.Log
import kotlinx.coroutines.*
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
import java.util.concurrent.ArrayBlockingQueue
/**
* 編碼基類(lèi)
*/
abstract class BaseEncoder(var width: Int, var height: Int) {
companion object {
const val encoder_delay_time = 1L //延時(shí)時(shí)間*1000
val TAG: String = BaseEncoder::class.java.simpleName
}
private var isEncoderRunning: Boolean = false//是否正在編碼
private lateinit var encoderCodec: MediaCodec
private var job: Job? = null
private var yuvQueue: ArrayBlockingQueue<ByteArray>? = null
private var maxQueueSize = 10
private lateinit var mMediaMuxer: MediaMuxer
private val path =
Environment.getExternalStorageDirectory().absolutePath.toString() + "/" + "test.mp4"
init {
createFile()
initEncoder()
}
private fun createFile() {
val file = File(path)
if (file.exists()) {
file.delete()
}
}
fun putQueue(byteArray: ByteArray) {
yuvQueue?.let {
if (yuvQueue?.size!! >= maxQueueSize) {
yuvQueue?.poll()
}
yuvQueue?.add(byteArray)
}
}
/**
* 初始化編解碼
*/
private fun initEncoder() {
//1.創(chuàng)建Mediacodec(兩種創(chuàng)建方式) 創(chuàng)建 MediaCodec,此時(shí)是 Uninitialized 狀態(tài)
encoderCodec = MediaCodec.createEncoderByType(getEncoderType())
//2.配置configure Configured 狀態(tài)
setConfigure(encoderCodec)
//3啟動(dòng)編碼 進(jìn)入excuting 狀態(tài)
encoderCodec.start()
mMediaMuxer = MediaMuxer(path, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
startEncoder()
}
var mStartMuxer = false
/**
* 開(kāi)始循環(huán)編碼
*/
@OptIn(DelicateCoroutinesApi::class)
fun startEncoder() {
var pts = 0L
var mTrackIndex = 0
job = GlobalScope.launch(Dispatchers.IO) {
var bufferArray: ByteArray
isEncoderRunning = false
if (yuvQueue == null) {
yuvQueue = ArrayBlockingQueue<ByteArray>(maxQueueSize)
}
var generateIndex: Long = 0
while (isActive && !isEncoderRunning) {
Log.e(TAG, "THE yuvQueue size =${yuvQueue?.size}")
if (yuvQueue?.size!! > 0) {
bufferArray = yuvQueue?.poll() as ByteArray
//輸入緩沖區(qū)
val inputBuffers = encoderCodec.inputBuffers
// try {//1、獲取緩存區(qū)空buffer
val dequeueInputBuffer =
encoderCodec.dequeueInputBuffer(-1)
if (dequeueInputBuffer >= 0) {
val inputBuffer = inputBuffers[dequeueInputBuffer]
inputBuffer.clear()
//塞入一幀數(shù)據(jù)buffer
inputBuffer.put(bufferArray)
//塞入隊(duì)列進(jìn)行編碼
encoderCodec.queueInputBuffer(
dequeueInputBuffer,
0,
bufferArray.size,
computePresentationTime(generateIndex),
0
)
generateIndex += 1
}
//獲取輸出數(shù)據(jù)緩存buffer
val bufferInfo: MediaCodec.BufferInfo = MediaCodec.BufferInfo()
var outputBufferId =
encoderCodec.dequeueOutputBuffer(bufferInfo, 1000)
Log.e(TAG, "THE dequeueOutputBuffer =${outputBufferId}")
if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
// 添加視頻軌道
mTrackIndex = mMediaMuxer.addTrack(encoderCodec.outputFormat)
mMediaMuxer.start()
mStartMuxer = true
} else {
while (outputBufferId >= 0) {
if (!mStartMuxer) {
Log.i(TAG, "MediaMuxer not start")
continue
}
// 獲取有效數(shù)據(jù)
val outputBuffer =
encoderCodec.getOutputBuffer(outputBufferId) ?: continue
outputBuffer.position(bufferInfo.offset)
outputBuffer.limit(bufferInfo.offset + bufferInfo.size)
if (pts == 0L) {
pts = bufferInfo.presentationTimeUs
}
bufferInfo.presentationTimeUs = bufferInfo.presentationTimeUs - pts
// 將數(shù)據(jù)寫(xiě)入復(fù)用器以生成文件
mMediaMuxer.writeSampleData(mTrackIndex, outputBuffer, bufferInfo)
Log.d(
TAG,
"pts = ${bufferInfo.presentationTimeUs / 1000000.0f} s ,${pts / 1000} ms"
)
encoderCodec.releaseOutputBuffer(outputBufferId, false)
outputBufferId = encoderCodec.dequeueOutputBuffer(bufferInfo, 1000)
}
}
}
}
}
}
open fun computePresentationTime(frameIndex: Long): Long {
return 132 + frameIndex * 1000000 / 30
}
/**
* 編碼結(jié)束,是否資源
*/
fun release() {
try {
job?.cancel()
isEncoderRunning = true
encoderCodec.stop()
encoderCodec.release()
if (mStartMuxer) {
mMediaMuxer.stop()
}
mMediaMuxer.release()
} catch (e: Exception) {
e.printStackTrace()
}
}
/**
* 獲取編碼類(lèi)型 h264(video/avc)/h265(video/hevc)
*/
abstract fun getEncoderType(): String
/**
* 創(chuàng)建編碼配置
*/
abstract fun setConfigure(encoderCodec: MediaCodec)
}
繼承BaseEncorder
package com.kt.ktmvvm.jetpack.mediacodec
import android.media.MediaCodec
import android.media.MediaCodecInfo
import android.media.MediaFormat
import android.os.Build
import android.util.Log
import com.kt.ktmvvm.inner.EncoderType
class VideoEncoder(width: Int, height: Int) : BaseEncoder(width, height) {
override fun getEncoderType(): String {
return EncoderType.H264
}
override fun setConfigure(encoderCodec: MediaCodec) {
Log.i(TAG,"the width="+width+"height="+height)
//寬高必須是16的整倍數(shù)
val createVideoFormat = MediaFormat.createVideoFormat(getEncoderType(), width, height)
//設(shè)置比特率,如果超過(guò)android 10 需要設(shè)置max-bitrate
createVideoFormat.setInteger(MediaFormat.KEY_BIT_RATE, width*height*3)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// createVideoFormat.setInteger(MediaFormat.keybit)
}
// 指定幀率
createVideoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30)
// 指定編碼器顏色格式
createVideoFormat.setInteger(
MediaFormat.KEY_COLOR_FORMAT,
MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface
)
// 指定關(guān)鍵幀時(shí)間間隔
createVideoFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1)
//設(shè)置碼率控制
// createVideoFormat.setInteger(
// MediaFormat.KEY_BITRATE_MODE,
// MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR
// )
val createInputSurface = encoderCodec.createInputSurface()
encoderCodec.configure(createVideoFormat,createInputSurface,null,MediaCodec.CONFIGURE_FLAG_ENCODE)
}
}
原文鏈接:https://blog.csdn.net/Android_LeeJiaLun/article/details/128533191
相關(guān)推薦
- 2022-10-11 Data truncation: Data too long for column ‘context
- 2022-08-15 Android開(kāi)發(fā)gradle拉取依賴的加速配置_Android
- 2021-12-13 linux系統(tǒng)AutoFs自動(dòng)掛載服務(wù)安裝配置_Linux
- 2022-04-14 如何解決error: failed to push some refs to ‘xxx(遠(yuǎn)程庫(kù))‘
- 2022-02-22 Uncaught RangeError: Maximum call stack size excee
- 2022-06-14 C語(yǔ)言?分析逆序字符串與字符串的逆序輸出有什么區(qū)別_C 語(yǔ)言
- 2022-08-18 利用Redis實(shí)現(xiàn)訂單30分鐘自動(dòng)取消_Redis
- 2022-10-22 Python基礎(chǔ)Lists和tuple實(shí)例詳解_python
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過(guò)濾器
- Spring Security概述快速入門(mén)
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支