- 继续解码
*/
fun goOn()
/**
- 停止解码
*/
fun stop()
/**
- 是否正在解码
*/
fun isDecoding(): Boolean
/**
- 是否正在快进
*/
fun isSeeking(): Boolean
/**
- 是否停止解码
*/
fun isStop(): Boolean
/**
- 设置状态监听器
*/
fun setStateListener(l: IDecoderStateListener?)
/**
- 获取视频宽
*/
fun getWidth(): Int
/**
- 获取视频高
*/
fun getHeight(): Int
/**
- 获取视频长度
*/
fun getDuration(): Long
/**
- 获取视频旋转角度
*/
fun getRotationAngle(): Int
/**
- 获取音视频对应的格式参数
*/
fun getMediaFormat(): MediaFormat?
/**
- 获取音视频对应的媒体轨道
*/
fun getTrack(): Int
/**
- 获取解码的文件路径
*/
fun getFilePath(): String
}
定义了解码器的一些基础操作,如暂停/继续/停止解码,获取视频的时长,视频的宽高,解码状态等等
为什么继承Runnable?
这里使用的是同步模式解码,需要不断循环压入和拉取数据,是一个耗时操作,因此,我们将解码器定义为一个Runnable,最后放到线程池中执行。
接着,继承IDecoder,定义基础解码器BaseDecoder。
首先来看下基础参数:
abstract class BaseDecoder: IDecoder {
//-------------线程相关------------------------
/**
- 解码器是否在运行
*/
private var mIsRunning = true
/**
- 线程等待锁
*/
private val mLock = Object()
/**
- 是否可以进入解码
*/
private var mReadyForDecode = false
//---------------解码相关-----------------------
/**
- 音视频解码器
*/
protected var mCodec: MediaCodec? = null
/**
- 音视频数据读取器
*/
protected var mExtractor: IExtractor? = null
/**
- 解码输入缓存区
*/
protected var mInputBuffers: Array? = null
/**
- 解码输出缓存区
*/
protected var mOutputBuffers: Array? = null
/**
- 解码数据信息
*/
private var mBufferInfo = MediaCodec.BufferInfo()
private var mState = DecodeState.STOP
private var mStateListener: IDecoderStateListener? = null
/**
- 流数据是否结束
*/
private var mIsEOS = false
protected var mVideoWidth = 0
protected var mVideoHeight = 0
//省略后面的方法
…
}
-
首先,我们定义了线程相关的资源,用于判断是否持续解码的mIsRunning,挂起线程的mLock等。
-
然后,就是解码相关的资源了,比如MdeiaCodec本身,输入输出缓冲,解码状态等等。
-
其中,有一个解码状态DecodeState和音视频数据读取器IExtractor。
定义解码状态
为了方便记录解码状态,这里使用一个枚举类表示
enum class DecodeState {
/*开始状态/
START,
/*解码中/
DECODING,
/*解码暂停/
PAUSE,
/*正在快进/
SEEKING,
/*解码完成/
FINISH,
/*解码器释放/
STOP
}
定义音视频数据分离器
前面说过,MediaCodec需要我们不断地喂数据给输入缓冲,那么数据从哪里来呢?肯定是音视频文件了,这里的IExtractor就是用来提取音视频文件中数据流。
Android自带有一个音视频数据读取器MediaExtractor,同样为了方便维护和拓展性,我们依然先定一个读取器IExtractor。
interface IExtractor {
/**
- 获取音视频格式参数
*/
fun getFormat(): MediaFormat?
/**
- 读取音视频数据
*/
fun readBuffer(byteBuffer: ByteBuffer): Int
/**
- 获取当前帧时间
*/
fun getCurrentTimestamp(): Long
/**
- Seek到指定位置,并返回实际帧的时间戳
*/
fun seek(pos: Long): Long
fun setStartPos(pos: Long)
/**
- 停止读取数据
*/
fun stop()
}
最重要的一个方法就是readBuffer,用于读取音视频数据流
定义解码流程
前面我们只贴出了解码器的参数部分,接下来,贴出最重要的部分,也就是解码流程部分。
abstract class BaseDecoder: IDecoder {
//省略参数定义部分,见上
…
final override fun run() {
mState = DecodeState.START
mStateListener?.decoderPrepare(this)
//【解码步骤:1. 初始化,并启动解码器】
if (!init()) return
while (mIsRunning) {
if (mState != DecodeState.START &&
mState != DecodeState.DECODING &&
mState != DecodeState.SEEKING) {
waitDecode()
}
if (!mIsRunning ||
mState == DecodeState.STOP) {
mIsRunning = false
break
}
//如果数据没有解码完毕,将数据推入解码器解码
if (!mIsEOS) {
//【解码步骤:2. 将数据压入解码器输入缓冲】
mIsEOS = pushBufferToDecoder()
}
//【解码步骤:3. 将解码好的数据从缓冲区拉取出来】
val index = pullBufferFromDecoder()
if (index >= 0) {
//【解码步骤:4. 渲染】
render(mOutputBuffers!![index], mBufferInfo)
//【解码步骤:5. 释放输出缓冲】
mCodec!!.releaseOutputBuffer(index, true)
if (mState == DecodeState.START) {
mState = DecodeState.PAUSE
}
}
//【解码步骤:6. 判断解码是否完成】
if (mBufferInfo.flags == MediaCodec.BUFFER_FLAG_END_OF_STREAM) {
mState = DecodeState.FINISH
mStateListener?.decoderFinish(this)
}
}
doneDecode()
//【解码步骤:7. 释放解码器】
release()
}
/**
- 解码线程进入等待
*/
private fun waitDecode() {
try {
if (mState == DecodeState.PAUSE) {
mStateListener?.decoderPause(this)
}
synchronized(mLock) {
mLock.wait()
}
} catch (e: Exception) {
e.printStackTrace()
}
}
/**
- 通知解码线程继续运行
*/
protected fun notifyDecode() {
synchronized(mLock) {
mLock.notifyAll()
}
if (mState == DecodeState.DECODING) {
mStateListener?.decoderRunning(this)
}
}
/**
- 渲染
*/
abstract fun render(outputBuffers: ByteBuffer,
bufferInfo: MediaCodec.BufferInfo)
/**
- 结束解码
*/
abstract fun doneDecode()
}
在Runnable的run回调方法中,集成了整个解码流程:
- 【解码步骤:1. 初始化,并启动解码器】
abstract class BaseDecoder: IDecoder {
//省略上面已有代码
…
private fun init(): Boolean {
//1.检查参数是否完整
if (mFilePath.isEmpty() || File(mFilePath).exists()) {
Log.w(TAG, “文件路径为空”)
mStateListener?.decoderError(this, “文件路径为空”)
return false
}
//调用虚函数,检查子类参数是否完整
if (!check()) return false
//2.初始化数据提取器
mExtractor = initExtractor(mFilePath)
if (mExtractor == null ||
mExtractor!!.getFormat() == null) return false
//3.初始化参数
if (!initParams()) return false
//4.初始化渲染器
if (!initRender()) return false
//5.初始化解码器
if (!initCodec()) return false
return true
}
private fun initParams(): Boolean {
try {
val format = mExtractor!!.getFormat()!!
mDuration = format.getLong(MediaFormat.KEY_DURATION) / 1000
if (mEndPos == 0L) mEndPos = mDuration
initSpecParams(mExtractor!!.getFormat()!!)
} catch (e: Exception) {
return false
}
return true
}
private fun initCodec(): Boolean {
try {
//1.根据音视频编码格式初始化解码器
val type = mExtractor!!.getFormat()!!.getString(MediaFormat.KEY_MIME)
mCodec = MediaCodec.createDecoderByType(type)
尾声
一转眼时间真的过的飞快。我们各奔东西,也各自踏上了自己的旅途,但是即使多年不见,也因为这份情谊我们依旧如从前那般“亲密”。不忘初心方得始终。加油吧,程序员们,在我看来35岁,40岁从来不是危机,只要永远不要忘记自己为何踏上征程!
最后需要同款资料的,可以 **私信我点击【学习】**我愿意分享给你!
为了让更多在学习中或者最近要准备面试的朋友们看到这篇文章,希望你们能多多评论,点赞+转发!
再次感谢所有给我提供过题目的朋友们,感谢一路有你!