媒体编解码器MediaCodec

news2024/11/25 20:19:26

目录

1.介绍MediaCodec类

2.创建MediaCodec的方式

3.MediaCodec流程

(1)配置编码参数

(2)创建编码器

(3)创建混合器

(4)开始编码 

4.MediaCodec编码的工作方式

 5.MediaCodec状态周期图

 6.使用缓冲区的异步和同步处理

7.录制纯视频demo


1.介绍MediaCodec类


     

final public class MediaCodec

MediaCodec类可以访问底层媒体编解码框架(Stage或OpenMAX),即编解码组件。通常与MediaExtractor、MediaSync、MediaMuxer、Image、Surface和AudioTrack一起使用。它本身并不是Codec,它通过调用底层编解码组件获得了Codec的能力。

2.创建MediaCodec的方式


创建MediaCodec(按格式创建):

  • MediaCodec createDecoderByType(String type):创建编码器。
  • MediaCodec createEncoderByType(String type):创建解码器。
  • type:数据解析阶段的mimeType,如”video/avc“。

创建MediaCodec(按Codec名字创建):

  • MediaCodec createByCodecName(String name)。
  • OMX.google.h264.decoder:软解码
  • OMX.MTK.VIDEO.DECODER.AVC:硬解码

3.MediaCodec流程


(1)配置编码参数

视频类型的Mediaformat

可以通过如下代码创建视频类型Mediaformat:

MediaFormat videoFormat = MediaFormat.createVideoFormat(videoType, width, height);

videoType常用的有两种:

  • MediaFormat.MIMETYPE_VIDEO_AVC(H.264)
  • MediaFormat.MIMETYPE_VIDEO_HEVC(H.265)

以下配置是必须要指定的,否则会报错。

            MediaFormat format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, width, height);
            //色彩空间
            format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
                    MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);
            //码率
            format.setInteger(MediaFormat.KEY_BIT_RATE,500_000);
            //帧率fps
            format.setInteger(MediaFormat.KEY_FRAME_RATE,20);
            //关键帧间隔
            format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL,2);

音频类型的Mediaformat

可以通过如下代码创建音频类型Mediaformat:

MediaFormat.createAudioFormat(audioType, sampleRate, channelCount);
  • audioType:常用的是MediaFormat.MIMETYPE_AUDIO_AAC;
  • sampleRate:采样率
  • channelCount:声道数

以下配置是必须要指定的,否则会报错。

//音频比特率(码率)
audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, 96000);
(2)创建编码器

配置解码器或者编码器configure函数

public void configure( @Nullable MediaFormat format,

@Nullable Surface surface,
@Nullable MediaCrypto crypto,

@ConfigureFlag int flags) 

函数的参数介绍:

  • MediaFormat format:输入数据的格式(解码器)或输出数据的所需格式(编码器)。传null等同于传递MediaFormat.MediaFormat作为空的MediaFormat。
  • Surface surface:指定Surface,用于解码器输出的渲染。如果编解码器不生成原始视频输出(例如,不是视频解码器)和/或想配置解码器输出ByteBuffer,则传null。
  • MediaCrypto crypto:指定一个crypto对象,用于对媒体数据进行安全解密。对于非安全的编解码器,传null。
  • int flags:当组件是编码器时,flags指定为常量CONFIGURE_FLAG_ENCODE。
     

使用示例:

  •             mediaCodec=MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
                mediaCodec.configure(format,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE);
                mediaCodec.start();
(3)创建混合器

创建混合器MediaMuxer函数

 public MediaMuxer(@NonNull String path, @Format int format)

函数的参数介绍:

  • path:输出文件的路径
  • format:输出的格式

示例代码:

            mMuxer=new MediaMuxer(path,
                    MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4
                    );
            mMuxer.setOrientationHint(degress);
(4)开始编码 

介绍MediaCodec编码用到的四个方法:
1. dequeueInputBuffer

返回用于填充有效数据的输入buffer的索引,如果当前没有可用的buffer,则返回-1。

public final int dequeueInputBuffer(long timeoutUs)

函数的参数介绍:

  • long timeoutUs:等待可用的输入buffer的时间。
  • 如果timeoutUs == 0,则立即返回。
  • 如果timeoutUs < 0,则无限期等待可用的输入buffer。
  • 如果timeoutUs > 0,则等待“timeoutUs”微秒。

2. queueInputBuffer

在指定索引处填充输入buffer后,使用queueInputBuffer将buffer提交给组件。


public native final void queueInputBuffer(
    int index,
    int offset, int size, long presentationTimeUs, int flags)

  • int index:以前调用dequeueInputBuffer(long)返回的输入buffer的索引。
  • int offset:数据开始时输入buffer中的字节偏移量。
  • int size:有效输入数据的字节数。
  • long presentationTimeUs:此buffer的PTS(以微秒为单位)。
  • int flags:一个由BUFFER_FLAG_CODEC_CONFIG和BUFFER_FLAG_END_OF_STREAM标志组成的位掩码。虽然没有被禁止,但是大多数codec并不对输入buffer使用BUFFER_FLAG_KEY_FRAME标志。

3. dequeueOutputBuffer

从MediaCodec获取输出buffer。

    public final int dequeueOutputBuffer(
            @NonNull BufferInfo info, long timeoutUs) 

  • BufferInfo info:输出buffer的metadata。
  • long timeoutUs:含义同dequeueInputBuffer中的timeoutUs参数。
  • 返回值:已成功解码的输出buffer的索引或INFO_*常量之一(INFO_TRY_AGAIN_LATER, INFO_OUTPUT_FORMAT_CHANGED 或 INFO_OUTPUT_BUFFERS_CHANGED)。返回INFO_TRY_AGAIN_LATER而timeoutUs指定为了非负值,表示超时了。返回INFO_OUTPUT_FORMAT_CHANGED表示输出格式已更改,后续数据将遵循新格式。

4. releaseOutputBuffer

使用此方法将输出buffer返回给codec或将其渲染在输出surface。

public void releaseOutputBuffer (int index, boolean render)

  • index:以前调用dequeueOutputBuffer(long)返回的输入buffer的索引。
  • boolean render:如果在配置codec时指定了一个有效的surface,则传递true会将此输出buffer在surface上渲染。一旦不再使用buffer,该surface将把buffer释放回codec。

示例代码:

 public void queueEncode(byte[] buffer){
        if(!isRecording){
            Log.e("xxx","没开始录制");
            return;
        }
        Log.e("xxx","开始录制");
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                     //立即得到有效输入缓冲区
                int index=mediaCodec.dequeueInputBuffer(0);
                if(index>=0){
                    ByteBuffer inputBuffer=mediaCodec.getInputBuffer(index);
                    inputBuffer.clear();
                    inputBuffer.put(buffer,0,buffer.length);
                    //填充数据后再加入队列
                    mediaCodec.queueInputBuffer(index,0,buffer.length,System.nanoTime()/1000,0);
                }
                while (true){
                    //获取输出缓冲区(编码后的数据从缓冲区获取)
                    MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
                    int encoderStatus = mediaCodec.dequeueOutputBuffer(bufferInfo, 10_000);
                    //稍后重试
                    if(encoderStatus== MediaCodec.INFO_TRY_AGAIN_LATER){
                        break;
                    }else if(encoderStatus==MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){
                        //输出格式发生变化 第一次总会调用,所以在这里开启混合器
                        MediaFormat newFormat=mediaCodec.getOutputFormat();
                        videoTrack=mMuxer.addTrack(newFormat);
                        mMuxer.start();
                    }else if(encoderStatus==MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED){
                        //可以忽略
                    }else{
                        //正常则encoderStatus 获取缓冲区下标
                        ByteBuffer encodedData=mediaCodec.getOutputBuffer(encoderStatus);
                        //如果当前的buffer是配置信息,不管它,不用写出去
                        if((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG)!=0){
                            bufferInfo.size=0;
                        }
                        if(bufferInfo.size!=0){
                            //设置从哪里开始读数据(读出来就是编码后的数据)
                            encodedData.position(bufferInfo.offset);
                            //设置能读数据的总长度
                            encodedData.limit(bufferInfo.offset+bufferInfo.size);
                            //写出为MP4
                            mMuxer.writeSampleData(videoTrack,encodedData,bufferInfo);
                        }
                        //释放这个缓冲区,后续可以存放新的编码后的数据
                         mediaCodec.releaseOutputBuffer(encoderStatus,false);
                    }
                }
            }
        });
    }

4.MediaCodec编码的工作方式


简单来说,可以看做是一个生产者-消费者模式,在MediaCodec里面有两个队列,一个是输入队列,一个是输出队列,数据放入输入队列,MediaCodec拿出数据进行编码,把编码完的数据放入输出队列。

 5.MediaCodec状态周期图


在MediaCodec的生命周期内存在3种状态,即Stopped、Executing和Released。

Stopped状态还可处于三个状态:Uninitialized、Configures和Error。

Executing状态概念上的进展通过3个子状态进行:Flushed、Running和end-of-Stream。

MediaCodec状态周期图如下图:

 6.使用缓冲区的异步和同步处理


使用缓冲区的异步处理:

在异步模式下,媒体编解码器通常像这样使用:


 MediaCodec codec = MediaCodec.createByCodecName(name);
 MediaFormat mOutputFormat; // member variable
 codec.setCallback(new MediaCodec.Callback() {
  @Override
  void onInputBufferAvailable(MediaCodec mc, int inputBufferId) {
    ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId);
    // fill inputBuffer with valid data
    …
    codec.queueInputBuffer(inputBufferId, …);
  }
 
  @Override
  void onOutputBufferAvailable(MediaCodec mc, int outputBufferId, …) {
    ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
    MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
    // bufferFormat is equivalent to mOutputFormat
    // outputBuffer is ready to be processed or rendered.
    …
    codec.releaseOutputBuffer(outputBufferId, …);
  }
 
  @Override
  void onOutputFormatChanged(MediaCodec mc, MediaFormat format) {
    // Subsequent data will conform to new format.
    // Can ignore if using getOutputFormat(outputBufferId)
    mOutputFormat = format; // option B
  }
 
  @Override
  void onError(…) {
    …
  }
  @Override
  void onCryptoError(…) {
    …
  }
 });
 codec.configure(format, …);
 mOutputFormat = codec.getOutputFormat(); // option B
 codec.start();
 // wait for processing to complete
 codec.stop();
 codec.release();

 使用缓冲区的同步处理:

媒体编解码器在同步模式下通常这样使用:


 MediaCodec codec = MediaCodec.createByCodecName(name);
 codec.configure(format, …);
 MediaFormat outputFormat = codec.getOutputFormat(); // option B
 codec.start();
 for (;;) {
  int inputBufferId = codec.dequeueInputBuffer(timeoutUs);
  if (inputBufferId >= 0) {
    ByteBuffer inputBuffer = codec.getInputBuffer(…);
    // fill inputBuffer with valid data
    …
    codec.queueInputBuffer(inputBufferId, …);
  }
  int outputBufferId = codec.dequeueOutputBuffer(…);
  if (outputBufferId >= 0) {
    ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
    MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
    // bufferFormat is identical to outputFormat
    // outputBuffer is ready to be processed or rendered.
    …
    codec.releaseOutputBuffer(outputBufferId, …);
  } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
    // Subsequent data will conform to new format.
    // Can ignore if using getOutputFormat(outputBufferId)
    outputFormat = codec.getOutputFormat(); // option B
  }
 }
 codec.stop();
 codec.release();

7.录制纯视频demo

功能介绍:

实现Camera1采集相机数据,设置预览画布TextureView显示采集的数据,然后MediaCode对图像数据编码,最后MediaMuxer把图像数据放入MP4盒子。

运行图:

这是我的项目代码:

(小白一个,代码只是能简单地实现功能)

https://github.com/Bookonpillow/mediacode

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

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

相关文章

整型提升——(巩固提高——字符截取oneNote笔记详解)

文章目录 前言一、整型提升是什么&#xff1f;二、详细图解1.图解展示 总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 整型提升是数据存储的重要题型&#xff0c;也是计算机组成原理的核心知识点。学习c语言进阶的时候,了解内存中数据怎么存&#…

Lua表实现类

--类 Student { name "Holens",age 1,sex true,Say1 function()print(Student.name.."说话了")end,Say2 function(t)print(t.name.."说话了2")end } Student.Say1() print("*************************************")--声明后添加…

Python大数据之pandas快速入门(一)

文章目录 pandas快速入门学习目标1. DataFrame 和 Series 简介2. 加载数据集(csv和tsv)2.1 csv和tsv文件格式简介2.2 加载数据集(tsv和csv) pandas快速入门 学习目标 能够知道 DataFrame 和 Series 数据结构能够加载 csv 和 tsv 数据集能够区分 DataFrame 的行列标签和行列位…

Windows11安装MySQL8.1

安装过程中遇到任何问题均可以参考(这个博客只是单纯升级个版本和简化流程) Windows安装MySQL8教程-CSDN博客 到官网下载mysql8数据库软件 MySQL :: Download MySQL Community Server 下载完后,解压到你需要安装的文件夹 其中的配置文件内容了如下 [mysqld]# 设置3306端口po…

设计模式4、建造者模式 Builder

解释说明&#xff1a;将一个复杂对象的构建与它的表示分离&#xff0c;使得同样的构建过程可以创建不同的表示 UML 结构图&#xff1a; 抽象建造者&#xff08;Builder&#xff09;&#xff1a;这个接口规定要实现复杂对象的那些部分的创建&#xff0c;并不设计具体部件对象的创…

【Spring Cloud】Ribbon 实现负载均衡的原理,策略以及饥饿加载

文章目录 前言一、什么是 Ribbon二、Ribbon 实现负载均衡的原理2.1 负载均衡的流程2.2 Ribbon 实现负载均衡的源码剖析 三、Ribbon 负载均衡策略3.1 负载均衡策略3.2 演示 Ribbon 负载均衡策略的更改 四、Ribbon 的饥饿加载4.1查看 Ribbon 的懒加载4.2 Ribbon 的饥饿加载模式 前…

《The Rise and Potential of Large Language Model Based Agents: A Survey》全文翻译

The Rise and Potential of Large Language Model Based Agents: A Surve - 基于 LLMs 的代理的兴起和潜力&#xff1a;一项调查 论文信息摘要1. 介绍2. 背景2.1 AI 代理的起源2.2 代理研究的技术趋势2.3 为什么大语言模型适合作为代理大脑的主要组件 3. 代理的诞生&#xff1a…

【文档智能】:GeoLayoutLM:一种用于视觉信息提取(VIE)的预训练模型

前言 文章介绍了一种用于视觉信息提取&#xff08;VIE&#xff09;的预训练模型&#xff1a;GeoLayoutLM。GeoLayoutLM通过显式建模几何关系和特殊的预训练任务来提高文本和布局的特征表示。该模型能够提高文档信息抽取的性能。 一、提出背景 当前多模态预训练模型在 SER 任…

c/c++中如何输入pi

标准的 C/C 语言中没有π这个符号及常量&#xff0c;一般在开发过程中是通过开发人员自己定义这个常量的&#xff0c;最常见的方式是使用宏定义&#xff1a; 方法1&#xff1a;#define pi 3.1415926 方法2&#xff1a;使用反三角函数const double pi acos(-1.0);

如何使用记事本制作一个简陋的小网页(2)——表格的建立

前情题要&#xff1a;http://t.csdnimg.cn/bK3H9 1.首先在之前的基础上建立一个新的标签页面。 2.使用<body></body>建立背景颜色 3.表格的填充和使用 如图所示&#xff0c;<table></table>是建立一个表格所需要使用的标签&#xff0c;可以使用width来…

Docker 容器跨主机通信 - Flannel

Author&#xff1a;rab 目录 前言一、架构及环境二、服务部署2.1 Etcd 部署2.2 Flannel 部署2.3 Docker 网络配置 三、容器通信验证及路由分析3.1 通信验证3.2 路由转发分析3.3 数据分发分析 总结 前言 今天是中秋佳节&#xff0c;首先在此祝大家“中秋快乐&#xff0c;阖家团…

java mongodb 并表 group 查询 Bson

对mongodb的使用中&#xff0c;需要将发生额表occr期初表open表&#xff0c;进行union的并表操作后&#xff0c;再进行group&#xff0c;sum&#xff0c;排序&#xff0c;分页操作。 查询了一番后&#xff0c;mongodb4.4版本后&#xff0c;新增了一个管道符&#xff0c;$union…

什么是 DNS 泛洪攻击(DNS 泛洪)

什么是 DNS 泛洪攻击&#xff08;DNS 泛洪&#xff09;&#xff1f; 域名系统 &#xff08;DNS&#xff09; 是用于在网站的机器可读地址&#xff08;例如 191.168.0.1&#xff1a;80&#xff09;和人类可读名称&#xff08;例如 radware.com&#xff09;之间进行解析的目录DN…

【HUAWEI】单臂路由

目录 ​ &#x1f96e;写在前面 &#x1f96e;2.1、拓扑图 &#x1f96e;2.2、操作思路 &#x1f96e;2.3、配置操作 &#x1f363;2.3.1、LSW4配置 &#x1f363;2.3.2、R2配置 &#x1f363;2.3.3、测试网络 &#x1f990;博客主页&#xff1a;大虾好吃吗的博客 &…

​面试官:谈谈 Go 泛型编程

大家好&#xff0c;我是木川 泛型编程是一种编程范式&#xff0c;它允许编写具有参数化类型的代码&#xff0c;从而增加代码的复用性和灵活性。在泛型编程中&#xff0c;你可以编写一段代码&#xff0c;使其适用于不同类型的参数&#xff0c;而不需要为每种类型编写不同的实现。…

js 时差计算 根据时间戳获取相差时间 几时几分几秒

上代码 // 如下&#xff1a;封装方法直接调用console.log("根据时间戳差值计算相差几小时几分几秒"); console.log("时间相差", timestampDifference(1695522383442 ,1695521518845)); 根据测试代码段 可以在浏览器控制台打印&#xff1a; 可以看到 &…

No148.精选前端面试题,享受每天的挑战和学习

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…

No150.精选前端面试题,享受每天的挑战和学习

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…

MacBook Pro 电池电量限制充电怎么设置AlDente Pro for Mac最大充电限制工具

通过充电电量限制工具可以更好的保护MacBook Pro的电池&#xff0c;通过 AlDente Pro 您可以设置电池的最大充电百分比设置为 20&#xff05; 至 100&#xff05;&#xff0c;然后&#xff0c;它将保持在所需的电池百分比&#xff0c;然后再次使用电源适配器进行充电。 AlDent…

led灯什么牌子的质量好?Led护眼台灯排行榜

现在我们很多家长对自己孩子的视力十分关心&#xff0c;生怕自己的孩子是近视、远视、弱视等等。对于父母而言&#xff0c;在孩子读书压力大课业重的关键时期&#xff0c;为孩子选择合适的桌椅&#xff0c;保护灯具从而保护孩子的眼睛是非常重要的事情!那么买给孩子读书做功课的…