音视频开发进阶指南(第六章)-Android MediaCodec编码为AAC

简介: 笔记

MediaCodec简介


MediaCodec是Android提供的硬件编解码器,它可以利用设备的硬件来完成编解码,从而大大提高编解码的效率,还可以降低电量的使用。

MediaCodec通常与MediaExtractor、MediaMuxer、AudioTrack结合使用,能够编解码诸如H.264、H.265、AAC、3gp等常见的音视频格式。广义而言,MediaCodec的工作原理就是处理输入数据以产生输出数据。具体来说,MediaCodec在编解码的过程中使用了一组输入/输出缓存区来同步或异步处理数据:

首先,客户端向输入缓存区写入要编解码的数据并将其提交给编解码器,待编解码器处理完毕后转存到编码器的输出缓存区,同时收回客户端对输入缓存区的所有权;

然后,客户端从输出缓存区读取编码好的数据进行处理,待处理完毕后编解码器收回客户端对输出缓存区的所有权。

不断重复整个过程,直至编码器停止工作或者异常退出。

工作原理图如下:

1.png

image.png

MediaCodec的工作状态

在整个编解码过程中,MediaCodec的使用会经历配置、启动、数据处理、停止、释放几个过程。相应的状态可归纳为停止(Stopped)执行(Executing)、以及释放(Released)三个状态,而Stopped状态又可细分为未初始化(Uninitialized)、配置(Configured)、异常( Error),Executing状态也可细分为读写数据(Flushed)、运行(Running)流结束(End-of-Stream)。MediaCodec整个状态结构图如下:

2.png

image.png


从上图可知,当MediaCodec被创建后会进入未初始化状态,待设置好配置信息并调用start()启动后,MediaCodec会进入运行状态,并且可进行数据读写操作。如果在这个过程中出现了错误,MediaCodec会进入Stopped状态,可以使用reset方法来重置编解码器,否则MediaCodec所持有的资源最终会被释放。当然,如果MediaCodec正常使用完毕,我们也可以向编解码器发送EOS指令,同时调用stop和release方法终止编解码器的使用。


MediaCodec API 说明


主要API说明:

getInputBuffers:获取需要编码数据的输入流队列,返回的是一个ByteBuffer数组

queueInputBuffer:在指定索引处填充一定范围的输入缓冲区后,将其提交给组件

dequeueInputBuffer:返回要填充有效数据的输入缓冲区的索引

getOutputBuffers:获取编解码之后的数据输出流队列,返回的是一个ByteBuffer数组

dequeueOutputBuffer:使输出缓冲区出队列,最多阻塞"timeoutUs"微秒。

releaseOutputBuffer:处理完成,释放ByteBuffer数据


MediaCodec的基本使用


  1. 创建并配置一个 MediaCodec 对象
  2. 如果输入缓冲区就绪,读取一个输入块,并复制到输入缓冲区中
  3. 如果输出缓冲区就绪,复制输出缓冲区的数据
  4. 循环2.3步直到完成
  5. 释放 MediaCodec 对象


MediaCodec初始化

MediaCodec主要提供了createEncoderByType(String type)createDecoderByType(String type)两个方法来创建编解码器,它们均需要传入一个MIME类型多媒体格式。常见的MIME类型多媒体格式如下:

● "video/x-vnd.on2.vp8" - VP8 video (i.e. video in .webm)

● "video/x-vnd.on2.vp9" - VP9 video (i.e. video in .webm)

● "video/avc" - H.264/AVC video

● "video/mp4v-es" - MPEG4 video

● "video/3gpp" - H.263 video

● "audio/3gpp" - AMR narrowband audio

● "audio/amr-wb" - AMR wideband audio

● "audio/mpeg" - MPEG1/2 audio layer III

● "audio/mp4a-latm" - AAC audio (note, this is raw AAC packets, not packaged in LATM!)

● "audio/vorbis" - vorbis audio

● "audio/g711-alaw" - G.711 alaw audio

● "audio/g711-mlaw" - G.711 ulaw audio

MediaCodec还提供了一个createByCodecName (String name)方法,支持使用组件的具体名称来创建编解码器。但是该方法使用起来有些麻烦,且官方是建议最好是配合MediaCodecList使用,因为MediaCodecList记录了所有可用的编解码器。


MediaCodec配置和启动


编解码器配置使用的是MediaCodec的configure方法,该方法首先对MediaFormat存储的数据map进行提取,然后调用本地方法native-configure实现对编解码器的配置工作。

在配置时,configure(format, surface, crypto, flags)方法需要传入format、surface、crypto、flags参数。

  • format为MediaFormat的实例,它使用"key-value"键值对的形式存储多媒体数据格式信息;
  • surface用于指明解码器的数据源来自于该surface;
  • crypto用于指定一个MediaCrypto对象,以便对媒体数据进行安全解密;
  • flags指明配置的是编码器(CONFIGURE_FLAG_ENCODE)。

其中MediaFormat必须配置以下几项,否则运行configure出错:采样率,比特率,通道个数。

下面是编码为AAC的配置

MediaFormat mediaFormat = MediaFormat.createAudioFormat(MINE_TYPE, SAMPLE_RATE, CHANNEL_CONFIG_IN);
MediaFormat mediaFormat = MediaFormat.createAudioFormat(MINE_TYPE, SAMPLE_RATE, CHANNEL_CONFIG_IN);
//指定比特率
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 128 * 1024);
//指定采样率
mediaFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, SAMPLE_RATE);
//指定通道个数
mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, CHANNEL_CONFIG_IN);
//指定PROFILE
mediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectERLC);
//指定缓冲区最大长度
mediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 10 * 1024);
//应用配置
mMediaCodecEncoder.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);

当编解码器配置完毕后,就可以调用MediaCodec的start()方法,该方法会调用低层native_start()方法来启动编码器,并调用低层方法ByteBuffer[] getBuffers(input)来开辟一系列输入、输出缓存区。

可以看一下start方法的源码,开辟了一块输入和一块输出缓存区:

public final void start() {
    native_start();
    synchronized(mBufferLock) {
        cacheBuffers(true /* input */);
        cacheBuffers(false /* input */);
    }
}

MediaCodec数据处理


MediaCodec支持两种模式编解码器,即同步synchronous、异步asynchronous

  • 同步模式是指编解码器数据的输入和输出是同步的,编解码器只有处理输出完毕才会再次接收输入数据;
  • 异步编解码器数据的输入和输出是异步的,编解码器不会等待输出数据处理完毕才再次接收输入数据。
    我们主要介绍下同步编解码,因为这种方式我们用得比较多。我们知道当编解码器被启动后,每个编解码器都会拥有一组输入和输出缓存区,但是这些缓存区暂时无法被使用,只有通过MediaCodec的dequeueInputBufferdequeueOutputBuffer方法获取输入输出缓存区授权,通过返回的ID来操作这些缓存区。
    示例如下:

ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers(); //所有的输入缓冲区
 ByteBuffer[]outputBuffers = mediaCodec.getOutputBuffers();//所有的输出缓冲区
//注:一次编码,只使用一个缓冲区,所以需要获取缓冲区的索引
//获取可用的输入缓冲区索引
int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1);
if (inputBufferIndex >= 0) {
    //写原始数据到输入缓冲区
    ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
    inputBuffer.clear();
    inputBuffer.put(data);
    mediaCodec.queueInputBuffer(inputBufferIndex, 0, len, System.nanoTime(), 0);
}
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
//获取可用输出缓冲区的索引
int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
while (outputBufferIndex >= 0) { //循环读取完输出缓冲区的数据
    ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
    if (outputAACDelegate != null) {
        int outPacketSize = bufferInfo.size + 7;// 7为ADTS头部的大小
        outputBuffer.position(bufferInfo.offset);
        outputBuffer.limit(bufferInfo.offset + bufferInfo.size);
        byte[] outData = new byte[outPacketSize];
        addADTStoPacket(outData, outPacketSize);//添加ADTS 代码后面会贴上
        outputBuffer.get(outData, 7, bufferInfo.size);//将编码得到的AAC数据 取出到byte[]中 偏移量offset=7
        outputBuffer.position(bufferInfo.offset);
        outputAACDelegate.outputAACPacket(outData); //写入到文件
    }
    mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
    outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
}

释放资源


mediaCodec.stop();
mediaCodec.release();


目录
相关文章
|
4月前
|
移动开发 前端开发 Android开发
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
522 12
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
4月前
|
移动开发 JavaScript 应用服务中间件
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
469 5
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
4月前
|
移动开发 Rust JavaScript
【01】首页建立-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【01】首页建立-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
890 4
【01】首页建立-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
5月前
|
开发工具 Android开发
X Android SDK file not found: adb.安卓开发常见问题-Android SDK 缺少 `adb`(Android Debug Bridge)-优雅草卓伊凡
X Android SDK file not found: adb.安卓开发常见问题-Android SDK 缺少 `adb`(Android Debug Bridge)-优雅草卓伊凡
672 11
X Android SDK file not found: adb.安卓开发常见问题-Android SDK 缺少 `adb`(Android Debug Bridge)-优雅草卓伊凡
|
4月前
|
移动开发 Android开发
【03】建立隐私关于等相关页面和内容-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【03】建立隐私关于等相关页面和内容-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
254 0
|
5月前
|
Java 开发工具 Maven
【01】完整的安卓二次商业实战-详细的初级步骤同步项目和gradle配置以及开发思路-优雅草伊凡
【01】完整的安卓二次商业实战-详细的初级步骤同步项目和gradle配置以及开发思路-优雅草伊凡
416 6
|
7月前
|
安全 数据库 Android开发
在Android开发中实现两个Intent跳转及数据交换的方法
总结上述内容,在Android开发中,Intent不仅是活动跳转的桥梁,也是两个活动之间进行数据交换的媒介。运用Intent传递数据时需注意数据类型、传输大小限制以及安全性问题的处理,以确保应用的健壯性和安全性。
496 11
|
11月前
|
JavaScript Linux 网络安全
Termux安卓终端美化与开发实战:从下载到插件优化,小白也能玩转Linux
Termux是一款安卓平台上的开源终端模拟器,支持apt包管理、SSH连接及Python/Node.js/C++开发环境搭建,被誉为“手机上的Linux系统”。其特点包括零ROOT权限、跨平台开发和强大扩展性。本文详细介绍其安装准备、基础与高级环境配置、必备插件推荐、常见问题解决方法以及延伸学习资源,帮助用户充分利用Termux进行开发与学习。适用于Android 7+设备,原创内容转载请注明来源。
3033 77
|
7月前
|
移动开发 Java 编译器
Kotlin与Jetpack Compose:Android开发生态的演进与架构思考
本文从资深Android工程师视角深入分析Kotlin与Jetpack Compose在Android系统中的技术定位。Kotlin通过空安全、协程等特性解决了Java在移动开发中的痛点,成为Android官方首选语言。Jetpack Compose则引入声明式UI范式,通过重组机制实现高效UI更新。两者结合不仅提升开发效率,更为跨平台战略和现代架构模式提供技术基础,代表了Android开发生态的根本性演进。
319 0
|
8月前
|
安全 Java Android开发
为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡
为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡
384 0
为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡

热门文章

最新文章