Android音视频开发只硬件解码组件MediaCodec讲解
作者:梦想改变生活 发布时间:2023-08-20 10:33:20
一、介绍以及编解码流程
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)
}
}
来源:https://blog.csdn.net/Android_LeeJiaLun/article/details/128533191
猜你喜欢
- Java doGet, doPost方法和文件上传index.html<!DOCTYPE html><html lang=
- 专业的Android app开发人员会关注一些成熟的项目管理技术,以成功构建Android app,并让这个app在Google Play
- 项目初始流程:首先说一下pom.xml文件的依赖: <dependencies><!-- junit 测试 -->
- maven scope provided和runtime例子maven常用的scope有compile,provided,runtime,t
- 在使用手机时,蓝牙通信给我们带来很多方便。那么在Android手机中怎样进行蓝牙开发呢?本文以实例的方式讲解Android蓝牙开发的知识。&
- 单例类保证一个类全局仅有一个实例,并提供一个全局访问点,由于只能生成一个实例,因此我们必须把构造函数设为私有函数以禁止他人创建实例。实现1:
- 简介本文主要讲解如何用java Selenium 控制鼠标在浏览器上的操作方法。主要列举的代码示例,无图显示。可以自己上代码执行操作看效果。
- Android 自定义返回按钮的实例详解程序中我们有时候想让放回按钮按照自己的需求调整页面而不是单纯的按照系统返回上一级,这个问题很简单,重
- CoordinatorLayout 实现了多种Material Design中提到的滚动效果。目前这个框架提供了几种不用写动画代码就能工作的
- Redis模糊匹配批量删除操作,使用RedisTemplate操作: public void deleteByPrex(String pre
- 制作透明窗体办法有好几种,各有优缺点. 我们先来看看C#本身提供的办法 1:通过设置窗体的 TransparencyKey实现 例:窗体中
- 工具:jdk1.8win10spring5.01.准备工作:下载Spring开发应用的插件,api1.spring插件包:springsou
- 要说,这也是一个很简单的功能,没必要开一篇博客这么大动干戈。 对于一张知道全路径的照片,如果其路径包含后缀名的话,要取得后缀名,只需要一行代
- 效果图白话分析:多线程:肯定是多个线程咯断点:线程停止下载的位置续传:线程从停止下载的位置上继续下载,直到完成任务为止。核心分析:断点:当前
- 目前常用的ORM框架有 Mybatis(batis)、MybatisPlus,Hibernate、Jpa等几个框架,今天就简单介绍一下搭建M
- 工厂方法模式,往往是设计模式初学者入门的模式,的确,有人称之为最为典型最具启发效果的模式。android中用到了太多的工厂类,其中有用工厂方
- 各位亲们可以尝试以下代码:注:这里我就只有一个html标签对来说明问题了,首部之类的东西,自己添加。<html> &n
- 先给大家展示下效果图,如果大家感觉不错,请参考使用方法,效果图如下所示:使用方法:录音工具类:AudioRecoderUtils.java,
- 微信公众平台对信息做了比较清晰的分类,最基本的包括请求(Request)和响应(Response)两大类信息,这两类信息有分为文字、语音、图
- 上篇给大家介绍了Spring Boot启动过程完全解析(一),大家可以点击参考下该说refreshContext(context)了,首先是