【Android 音视频开发打怪升级:音视频硬解码篇

二、解码流程

MediaCodec有两种工作模式,分别为异步模式和同步模式,这里我们使用同步模式,异步模式可以参考官网例子

根据官方的数据流图和状态图,画出一个最基础的解码流程如下:

解码流程图

经过初始化和配置以后,进入循环解码流程,不断的输入数据,然后获取解码完数据,最后渲染出来,直到所有数据解码完成(End of Stream)。

三、开始解码

根据上面的流程图,可以发现,无论音频还是视频,解码流程基本是一致的,不同的地方只在于【配置】、【渲染】两个部分。

定义解码器

因此,我们将整个解码流程抽象为一个解码基类:BaseDecoder,为了规范代码和更好的拓展性,我们先定义一个解码器:IDecoder,继承Runnable。

interface IDecoder: Runnable {

/**

  • 暂停解码
    */
    fun pause()

/**

  • 继续解码
    */
    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

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
img

最后

写到这里也结束了,在文章最后放上一个小小的福利,以下为小编自己在学习过程中整理出的一个学习思路及方向,从事互联网开发,最主要的是要学好技术,而学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯,更加需要准确的学习方向达到有效的学习效果。
由于内容较多就只放上一个大概的大纲,需要更及详细的学习思维导图的点击这里>Android IOC架构设计免费获取。
群内还有免费的高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术资料,并且还有技术大牛一起讨论交流解决问题。

6293677)]

最后

写到这里也结束了,在文章最后放上一个小小的福利,以下为小编自己在学习过程中整理出的一个学习思路及方向,从事互联网开发,最主要的是要学好技术,而学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯,更加需要准确的学习方向达到有效的学习效果。
由于内容较多就只放上一个大概的大纲,需要更及详细的学习思维导图的点击这里>Android IOC架构设计免费获取。
群内还有免费的高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术资料,并且还有技术大牛一起讨论交流解决问题。

image

  • 21
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的主角打怪升级的代码示例,使用Python语言实现: ```python import random player_level = 1 player_exp = 0 player_max_exp = 10 player_hp = 100 player_max_hp = 100 player_attack = 10 monster_level = 1 monster_hp = 50 monster_max_hp = 50 monster_attack = 5 print("你遇到了一个等级为", monster_level, "的怪物,准备战斗!") while player_hp > 0 and monster_hp > 0: # 玩家击怪物 damage = random.randint(player_attack - 2, player_attack + 2) monster_hp -= damage print("你攻击了怪物,造成了", damage, "点伤害。怪物剩余血量:", monster_hp) # 怪物攻击玩家 damage = random.randint(monster_attack - 2, monster_attack + 2) player_hp -= damage print("怪物攻击了你,造成了", damage, "点伤害。你剩余血量:", player_hp) # 判断战斗结果 if player_hp <= 0: print("你被怪物击败了,游戏结束。") break if monster_hp <= 0: print("你击败了怪物,获得了", monster_level * 10, "点经验值。") player_exp += monster_level * 10 if player_exp >= player_max_exp: player_level += 1 player_max_exp *= 2 player_hp = player_max_hp player_attack += 5 print("恭喜你升级了!当前等级为", player_level, ",最大生命值增加到", player_max_hp, ",攻击力增加到", player_attack, "。") # 生成新的怪物 monster_level += 1 monster_max_hp *= 2 monster_hp = monster_max_hp monster_attack += 5 print("你遇到了一个等级为", monster_level, "的怪物,准备战斗!") ``` 在这个代码中,玩家和怪物都有等级、生命值、攻击力等属性。玩家和怪物每次攻击都会随机产生一定的伤害值,直到一方的生命值降为0为止。如果玩家胜利,就会获得一定的经验值,并有可能升级升级后,玩家的属性会得到提升,同时会生成一个等级更高的新怪物。如果玩家失败,则游戏结束。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值