一、介绍以及编解码流程
MediaCodec 类可用于访问低级媒体编解码器,即编码器/解码器组件。它是 Android 低级多媒体支持基础结构的一部分(通常与MediaExtractor、MediaSync、MediaMuxer、MediaCrypto、 MediaDrm、Image、Surface和一起使用AudioTrack。)
由上图可以知道,大概的流程就是:
- 1、Client 通过dequeueInputBuffer 申请一个空的buffers缓冲区
- 2、通过queueInputBuffer 将数据填充到缓冲区
- 3、传给编码器或者解码器(Codec)
- 4、通过dequeueOutputBuffer 将编解码后的数据下发到输出缓冲区
- 5、Client 接收并消耗掉输出缓冲区的数据后,将其释放到编解码器中
二、生命周期
由上图可以知道,生命周期大概会分为3类:
-
Stoped(停止):
当 使用MediaCodec.createEncoderByType 创建编解码类型时,此时是 Uninitialized 状态
当使用encoderCodec.configure()接口时,编解码器会进入configure状态。 -
Executing(执行):
-
encoderCodec.start() 调用后,将使编解码器进入Executing 状态!
-
Flushed:在调用start()方法后MediaCodec立即进入Flushed子状态,此时MediaCodec会拥有所有的缓存。可以在Executing状态的任何时候通过调用flush()方法返回到Flushed子状态。
-
Running:一旦第一个输入缓存(input buffer)被移出队列,MediaCodec就转入Running子状态,这种状态占据了MediaCodec的大部分生命周期。通过调用stop()方法转移到Uninitialized状态。
-
End-of-Stream:将一个带有end-of-stream标记的输入buffer入队列时,MediaCodec将转入End-of-Stream子状态。在这种状态下,MediaCodec不再接收之后的输入buffer,但它仍然产生输出buffer直到end-of-stream标记输出。
-
Released(释放):
-
当使用完MediaCodec后,必须调用release()方法释放其资源。调用 release()方法进入最终的Released状态。
三、API 接口
//创建解码器(type为mime或name)
public static MediaCodec createDecoderByType(String type)
//创建编码器(type为mime或name)
public static MediaCodec createEncoderByType(String type)
//配置解码器和编码器(flag为0表示解码器,1表示编码器)
public void configure(MediaFormat format, Surface surface, MediaCrypto crypto, int flags)
//启动编码器或解码器
public final void start()
//获取输入队列的一个空闲索引(timeoutUs:最多等待时间,-1表示一直等待,单位:微秒us)
public final int dequeueInputBuffer(long timeoutUs)
//获取输入队列的一个空闲缓存区(index:dequeueInputBuffer方法的返回值)
public ByteBuffer getInputBuffer(int index)
//提醒解码器或编码器处理数据(index:dequeueInputBuffer方法的返回值)
public final void queueInputBuffer(int index, int offset, int size, long presentationTimeUs, int flags)
//创建BufferInfo类,用于存储解码或编码后的缓存数据的格式信息
public final static class BufferInfo{}
//获取输出队列的一个缓存区的索引,并将格式信息保存在info中(timeoutUs:最多等待时间,-1表示一直等待,单位:微秒us)
public final int dequeueOutputBuffer(BufferInfo info, long timeoutUs)
//获取输出队列的一个缓存区(index:dequeueOutputBuffer方法的返回值)
public ByteBuffer getOutputBuffer(int index)
//清除index指向的缓存区中的数据
public final void releaseOutputBuffer(int index, boolean render)
//结束解码或编码会话
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
/**
* 编码基类
*/
abstract class BaseEncoder(var width: Int, var height: Int) {
companion object {
const val encoder_delay_time = 1L //延时时间*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.创建Mediacodec(两种创建方式) 创建 MediaCodec,此时是 Uninitialized 状态
encoderCodec = MediaCodec.createEncoderByType(getEncoderType())
//2.配置configure Configured 状态
setConfigure(encoderCodec)
//3启动编码 进入excuting 状态
encoderCodec.start()
mMediaMuxer = MediaMuxer(path, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
startEncoder()
}
var mStartMuxer = false
/**
* 开始循环编码
*/
@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
//输入缓冲区
val inputBuffers = encoderCodec.inputBuffers
// try {//1、获取缓存区空buffer
val dequeueInputBuffer =
encoderCodec.dequeueInputBuffer(-1)
if (dequeueInputBuffer >= 0) {
val inputBuffer = inputBuffers[dequeueInputBuffer]
inputBuffer.clear()
//塞入一帧数据buffer
inputBuffer.put(bufferArray)
//塞入队列进行编码
encoderCodec.queueInputBuffer(
dequeueInputBuffer,
0,
bufferArray.size,
computePresentationTime(generateIndex),
0
)
generateIndex += 1
}
//获取输出数据缓存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
}
// 获取有效数据
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
// 将数据写入复用器以生成文件
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
}
/**
* 编码结束,是否资源
*/
fun release() {
try {
job?.cancel()
isEncoderRunning = true
encoderCodec.stop()
encoderCodec.release()
if (mStartMuxer) {
mMediaMuxer.stop()
}
mMediaMuxer.release()
} catch (e: Exception) {
e.printStackTrace()
}
}
/**
* 获取编码类型 h264(video/avc)/h265(video/hevc)
*/
abstract fun getEncoderType(): String
/**
* 创建编码配置
*/
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的整倍数
val createVideoFormat = MediaFormat.createVideoFormat(getEncoderType(), width, height)
//设置比特率,如果超过android 10 需要设置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
)
// 指定关键帧时间间隔
createVideoFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1)
//设置码率控制
// createVideoFormat.setInteger(
// MediaFormat.KEY_BITRATE_MODE,
// MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR
// )
val createInputSurface = encoderCodec.createInputSurface()
encoderCodec.configure(createVideoFormat,createInputSurface,null,MediaCodec.CONFIGURE_FLAG_ENCODE)
}
}