前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android MediaCodec 使用说明

Android MediaCodec 使用说明

作者头像
字节流动
发布2021-03-16 15:35:33
1.6K0
发布2021-03-16 15:35:33
举报
文章被收录于专栏:字节流动字节流动

最近公司要求提供一个支持 Android 硬件转码的底层库,所以自己从头去看了 MediaCodec 相关的知识,费了老大的劲终于完成了。

目前的硬件转码使用 MediaCodec 进行解码和编码,然后使用 FFmpeg 进行文件封装(为了支持文件分块)。

这篇文章主要介绍一些 MediaCodec 的基础知识和使用方式,后面会写如何利用 FFmpeg 封装 MediaCodec 编码后的数据以及 FFmpeg 分块封装的文章。

MediaCodec 可以用来获得安卓底层的多媒体编码,可以用来编码和解码,它是安卓 low-level 多媒体基础框架的重要组成部分。

MediaCodec 的作用是处理输入的数据生成输出数据。首先生成一个输入数据缓冲区,将数据填入缓冲区提供给 codec,codec 会采用异步的方式处理这些输入的数据,然后将填满输出缓冲区提供给消费者,消费者消费完后将缓冲区返还给 codec。

接收的数据

MediaCodec 接受三种数据格式:压缩数据,原始音频数据和原始视频数据。

这三种数据都可以使用 ByteBuffer 作为载体传输给 MediaCodec 来处理。但是当使用原始视频数据时,最好采用 Surface 作为输入源来替代 ByteBuffer,这样效率更高,因为 Surface 使用的更底层的视频数据,不会映射或复制到 ByteBuffer 缓冲区。

压缩数据

压缩数据可以作为解码器的输入数据或者编码器的输出数据,需要指定数据格式,这样 codec 才能知道如何处理这些压缩数据。

对于视频数据而言,通常是一帧数据;音频数据,一般是单个处理单元。

原始音频数据

原始音频数据即编码器的输入数据,解码器的输出数据。包含整个 PCM 音频数据帧,这是通道顺序中每个通道的一个样本。每个采样都是以本地字节顺序的 16 位有符号整数。

原始视频数据

原始视频数据也是编码器的输入数据,解码器的输出数据。即yuv数据,MediaCodec主要支持的格式为:

  • native raw video format : COLOR_FormatSurface,用来处理 Surface 模式的数据输入输出
  • flexible YUV buffers : 例如 COLOR_FormatYUV420Flexible
  • specific formats: 支持ByteBuffer模式,有一些厂家会定制

使用流程

编解码器处理输入数据并产生输出数据,MediaCodec 使用输入输出缓存,异步处理数据。

  • 请求一个空的输入 input buffer
  • 填入数据、并将其交给 MediaCodec
  • MediaCodec 处理数据后,将处理后的数据放在一个空的 output buffer
  • 获取填充数据了的 output buffer,得到其中的数据,然后将其返还给 MediaCodec

首先了解下 MediaCodec 中的生命周期

同步状态

MediaCodec 大体上分为三种状态:Stopped、Executing 和 Released。

创建 MediaCodec

首先是如何创建 MediaCodec,在知道 MimeType 的情况下,可以通过 createDecoderByType, createEncoderByType, createByCodecName 方法来获取实例。

如果不知道 MimeType,可以使用 MediaCodecList.findDecoderForFormat、 MediaCodecList.findEncoderForFormat 来获取。

创建成功之后,MediaCodec 进入 Uninitialized 状态。

Configuration

在创建好 MediaCodec 之后,需要对其进行设置,这样 MediaCodec 的状态就可以由 uninitialized 变成 configured

代码语言:javascript
复制
public void configure(
            @Nullable MediaFormat format,
            @Nullable Surface surface, @Nullable MediaCrypto crypto,
            @ConfigureFlag int flags) {
        configure(format, surface, crypto, null, flags);
}
public void configure(
            @Nullable MediaFormat format, @Nullable Surface surface,
            @ConfigureFlag int flags, @Nullable MediaDescrambler descrambler) {
        configure(format, surface, null,
                descrambler != null ? descrambler.getBinder() : null, flags);
}
代码语言:javascript
复制

这里最重要的参数是 MediaFormat, 如果某些参数没有设置的话,会导致 MediaCodec 抛出 IllegalStateException.

Video 所必须的 Format Setting

Encoder

Decoder

KEY_MIME

✔️

✔️

KEY_BIT_RATE

✔️

KEY_WIDTH

✔️

✔️

KEY_HEIGHT

✔️

✔️

KEY_COLOR_FORMAT

✔️

KYE_FRAME_RATE

✔️

KEY_I_FRAME_INTERVAL

✔️

Audio 所必须的 Format Setting

Encoder

Decoder

KEY_MIME

✔️

✔️

KEY_BIT_RATE

✔️

KEY_CHANNEL_COUNT

✔️

✔️

KEY_SAMPLE_RATE

✔️

✔️

输入数据与获取编解码后的数据

从 5.0 开始,首选方法是在调用 configure 方法之前通过设置回调来异步处理数据。所以这里就直接介绍异步模式下如何输入需要编解码的数据,以及如何获取编解码后的数据。

异步模式

异步状态

官方示例代码:

代码语言:javascript
复制
MediaCodec codec = MediaCodec.createByCodecName(name);
MediaFormat mOutputFormat; // member variable
// 设置回调方法
codec.setCallback(new MediaCodec.Callback() {
   /**
    * mediacodec 存在可用输入缓冲
    */
   @Override
   void onInputBufferAvailable(MediaCodec mc, int inputBufferId) {
     ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId);
     // 可通过 MediaExtractor 读取 video 或 audio 数据,然后填充数据到缓冲区
     …
     codec.queueInputBuffer(inputBufferId, …);
   }

   /**
    * 输出缓冲填充完数据后
    */
   @Override
   void onOutputBufferAvailable(MediaCodec mc, int outputBufferId, …) {
     // 获取输出缓冲(其中包含编解码后数据)
     ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
     MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); 
     // 处理编解码后的数据
     …
     // 返还输出缓冲给 codec
     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; 
   }

    /**
     * 发生错误
     */
   @Override
   void onError(…) {
     …
   }
 });
 codec.configure(format, …);
 mOutputFormat = codec.getOutputFormat(); 
 codec.start();
 // wait for processing to complete
 codec.stop();
 codec.release();

看一个几个重要的方法

代码语言:javascript
复制
ByteBuffer getInputBuffer(int index)

该方法返回一个已清空、可写入的 input 缓冲区,通过调用 ByteBuffer.put(data) 方法将 data 中的数据放到缓冲区,然后调用

代码语言:javascript
复制
/**
 * @param index              - 缓冲区索引
 * @param offset             - 缓冲区提交数据的起始位置
 * @param size               - 提交的数据长度
 * @param presentationTimeUs - 时间戳
 * @param flags              - BUFFER_FLAG_CODEC_CONFIG:配置信息;
 *                             BUFFER_FLAG_END_OF_STREAM:结束标志;
 *                             BUFFER_FLAG_KEY_FRAME:关键帧
 */
void queueInputBuffer(int index, int offset, int size, long presentationTimeUs, int flags)

就可以将缓冲区返回给 codec。

代码语言:javascript
复制
ByteBuffer getOutputBuffer(int index)
代码语言:javascript
复制

该方法返回一个 output 缓冲区,包含解码或编码后的数据。

代码语言:javascript
复制
void releaseOutputBuffer(int index, boolean render)
void releaseOutputBuffer(int index, long renderTimeStampNs)

这两个方法都会释放 index 所指向的缓冲区。

处理完需要编/解码的数据之后,调用 stop & release 方法释放 MediaCodec。

-- END --

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-03-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 字节流动 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 接收的数据
    • 压缩数据
      • 原始音频数据
        • 原始视频数据
        • 使用流程
          • 创建 MediaCodec
            • Configuration
              • 输入数据与获取编解码后的数据
                • 异步模式
            相关产品与服务
            文件存储
            文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档