Android 音视频学习之《MediaCodec》

news2024/11/17 3:52:49

一、介绍以及编解码流程

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)
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/139265.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

060-MySQL数据库综合应用(实现登录及注册功能源代码)

【上一讲】059-MySQL数据库综合应用(实现登录及注册功能)_CSDN专家-赖老师(软件之家)的博客-CSDN博客 本文章讲解JAVA数据库技术与MySQL数据库结合使用,利用DAO技术对数据库操作进行封装,达到高内聚低耦合,具体技术如下: 1.综合利用JAVA数据库技术,掌握Connection,St…

操作系统第四次实验-基本分页存储管理(python代码实现)

一、实验目的&#xff1a; 目的&#xff1a;熟悉并掌握基本分页存储管理的思想及其实现方法&#xff0c;熟悉并掌握基本分页存储管理的分配和回收方式。 任务&#xff1a;模拟实现基本分页存储管理方式下内存空间的分配和回收。 二、实验内容&#xff1a; 1、实验内容 内存空间…

一道有意思的图论题

今天写这道题的过程就一直在摆&#xff0c;主要是写不太出来&#xff0c;之前想到动态规划去了&#xff0c;然后又开始深搜&#xff0c;在出口那块放动态规划 题&#xff1a; D. Ela and the Wiring Wizard 点我 但是cf让我明白了一个道理&#xff0c;任何一道我写不出来的题代…

点菜方案数

题目描述 不过uim的口袋里只剩 M元(M < 10000)&#xff0c;来到了一家低端餐馆前。 餐馆虽低端&#xff0c;但是菜品种类不少&#xff0c;有 N 种(N < 100)&#xff0c;第i种卖元(ai < 1000)。由于是很低端的餐馆所以每种菜只有一份。 uim奉行“不把钱吃光不罢休”&a…

作用域与作用域链

javascript拥有一套设计良好的规则来存储变量&#xff0c;并且之后可以方便的找到这些变量&#xff0c;这套规则叫做作用域。 内部原理 内部原理分成编译、执行、查询、嵌套和异常五部分。今天来简单说一下查询。 var a2;这行代码中&#xff0c;在引擎执行的第一步操作中&am…

【C++ Primer】阅读笔记(3):decltype

目录 简介decltype基础decltype需要注意的地方1.decltype处理顶层const、引用与auto不同2.如果decltype使用的表达式不是一个变量,则decltype返回表达式结果对应的类型。3.如果表达式的内容是解引用操作,则decltype会得到引用类型。4.对于decltype所用的表达式来说,如果变量…

Spring AOP统一功能处理

⭐️前言⭐️ 这篇文章主要介绍AOP&#xff08;Aspect Oriented Programming&#xff09;——面向切面编程的思想&#xff0c;它是对某一类事情的集中处理&#xff0c;也是对OOP&#xff08;Object Oriented Programming&#xff09;面向对象编程的补充和完善。 &#x1f349;…

【编程语言选择】我们学C++将来能做什么?

首先贴上C嘎嘎祖师爷的镇楼帅照&#x1f606; 凝视目录 什么是C C的使用广泛度 C的具体工作领域有什么 什么是C 简单说 C是基于C语言而产生的&#xff0c;它既可以进行C语言的过程化程序设计&#xff0c;又可以进行以抽象数据类型为特点的基于对象的程序设计&#xff0c;还可…

【区块链 | 前端】前端开发人员入门区块链的最佳实践

前端开发人员入门区块链的最佳实践 一. 建立信仰 从技术入门一个行业通常是漫无目的&#xff0c;个人认为正确的入行区块链的方式是去了解他的背景&#xff0c;是去建立自己信仰的&#xff0c;尤其身处一个刚起步就被扼杀的行业&#xff0c;我们每个人都是领头人&#xff0c;我…

06栈和队列

开始系统学习算法啦&#xff01;为后面力扣和蓝桥杯的刷题做准备&#xff01;这个专栏将记录自己学习算法是的笔记&#xff0c;包括概念&#xff0c;算法运行过程&#xff0c;以及代码实现&#xff0c;希望能给大家带来帮助&#xff0c;感兴趣的小伙伴欢迎评论区留言或者私信博…

[NOIP 2003] 栈(三种方法:DP、数论、搜索)

[NOIP2003 普及组] 栈 题目背景 栈是计算机中经典的数据结构&#xff0c;简单的说&#xff0c;栈就是限制在一端进行插入删除操作的线性表。 栈有两种最重要的操作&#xff0c;即 pop&#xff08;从栈顶弹出一个元素&#xff09;和 push&#xff08;将一个元素进栈&#xff…

5 个必须尝试的无代码应用

无代码软件让任何人无需了解编程语言即可构建产品、网站和应用程序。Editor XWix 发布了Editor X&#xff0c;这是一款无需编写任何 CSS &#xff08;层叠样式表&#xff09;或 HTML&#xff08;超文本标记语言&#xff09;代码的新型拖放式网站构建器&#xff0c;为设计师和机…

如何把可观测需求落地为业务大盘?

2022 年 9 月 28 日&#xff0c;阿里云用户组&#xff08;AUG&#xff09;第 11 期活动在深圳举办。活动现场&#xff0c;阿里云技术专家李加贝向参会企业代表分享了如何把可观测需求落地为业务大盘的议题。本文根据现场分享内容整理而成。 为什么需要 Grafana&#xff1f; 演…

智能合约Smart Contract技术详解

文章目录合约编写基本介绍构造方法ipfsmint提现白名单合约前端部署验证合约代码前端和合约交互准备工作获取已经mint了的数量mint合约编写 建议读者先了解下solidity&#xff0c;这里推荐CryptoZombies&#xff0c;还是比较详细的。 ok当你大概知道自己在做什么之后&#xff0…

【概率论】期末复习笔记:假设检验

假设检验目录一、假设检验的基本概念1. 假设检验的基本原理2. 两类错误3. 假设检验的一般步骤4. ppp值二、正态总体参数的假设检验σ2已知&#xff0c;检验μ与μ0的关系\color{dodgerblue}\sigma^2\text{已知&#xff0c;检验}\mu\text{与}\mu_0\text{的关系}σ2已知&#xff…

上海华清远见

解析设备树节点信息实例1获取属性数值实例2获取u32类型的值将获取到的u32类型的值存放在array数组中

什么是许可式邮件营销?

邮件是很多企业日常中用到的信息传播工具&#xff0c;并且它还具备了成本低、长期性等优点&#xff0c;所以很多企业选择使用邮件作为载体进行营销推广。而在进行邮件营销的时候&#xff0c;不同的方式和技巧也会影响到最终的营销效果。为了达到较好的营销效果&#xff0c;很多…

Seata应用

下载seata-server 下载地址&#xff1a;Tags seata/seata GitHub 配置Seata-server 第一步&#xff1a;配置seata-server数据源 E:\seata-server-1.4.2\seata\seata-server-1.4.2\conf\file.conf 第二步&#xff1a;创建seata数据库 create database seata 第三步&#xf…

单元测试-SpringBoot Test和Mock

单元测试-SpringBoot Test和Mock “单元测试” “junit&#xff0c;mock&#xff0c;桩” 1. 什么是单元测试 定义&#xff1a;是指对软件中的最小可测试单元进行检查和验证。 Java里单元指一个方法。单元测试是在软件开发过程中要进行的最低级别的测试活动&#xff0c;软件的…

玻纤效应对skew的影响(三)

玻纤效应对skew的影响&#xff08;一&#xff09;玻纤效应对skew的影响&#xff08;二&#xff09;对内skew对32Gbps NRZ和64Gbps PAM-4的影响这一篇中&#xff0c;玻纤效应造成的对内skew将会加入到32Gbps NRZ和64Gbps PAM-4 SerDes全链路分析中。PCIe 5.0代表32Gbps NRZ&…