音视频开发进阶指南(第六章)-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();


目录
相关文章
|
16天前
|
搜索推荐 前端开发 API
探索安卓开发中的自定义视图:打造个性化用户界面
在安卓应用开发的广阔天地中,自定义视图是一块神奇的画布,让开发者能够突破标准控件的限制,绘制出独一无二的用户界面。本文将带你走进自定义视图的世界,从基础概念到实战技巧,逐步揭示如何在安卓平台上创建和运用自定义视图来提升用户体验。无论你是初学者还是有一定经验的开发者,这篇文章都将为你打开新的视野,让你的应用在众多同质化产品中脱颖而出。
40 19
|
16天前
|
JSON Java API
探索安卓开发:打造你的首个天气应用
在这篇技术指南中,我们将一起潜入安卓开发的海洋,学习如何从零开始构建一个简单的天气应用。通过这个实践项目,你将掌握安卓开发的核心概念、界面设计、网络编程以及数据解析等技能。无论你是初学者还是有一定基础的开发者,这篇文章都将为你提供一个清晰的路线图和实用的代码示例,帮助你在安卓开发的道路上迈出坚实的一步。让我们一起开始这段旅程,打造属于你自己的第一个安卓应用吧!
41 14
|
19天前
|
Java Linux 数据库
探索安卓开发:打造你的第一款应用
在数字时代的浪潮中,每个人都有机会成为创意的实现者。本文将带你走进安卓开发的奇妙世界,通过浅显易懂的语言和实际代码示例,引导你从零开始构建自己的第一款安卓应用。无论你是编程新手还是希望拓展技术的开发者,这篇文章都将为你打开一扇门,让你的创意和技术一起飞扬。
|
17天前
|
XML 存储 Java
探索安卓开发之旅:从新手到专家
在数字时代,掌握安卓应用开发技能是进入IT行业的关键。本文将引导读者从零基础开始,逐步深入安卓开发的世界,通过实际案例和代码示例,展示如何构建自己的第一个安卓应用。我们将探讨基本概念、开发工具设置、用户界面设计、数据处理以及发布应用的全过程。无论你是编程新手还是有一定基础的开发者,这篇文章都将为你提供宝贵的知识和技能,帮助你在安卓开发的道路上迈出坚实的步伐。
30 5
|
16天前
|
开发框架 Android开发 iOS开发
安卓与iOS开发中的跨平台策略:一次编码,多平台部署
在移动应用开发的广阔天地中,安卓和iOS两大阵营各占一方。随着技术的发展,跨平台开发框架应运而生,它们承诺着“一次编码,到处运行”的便捷。本文将深入探讨跨平台开发的现状、挑战以及未来趋势,同时通过代码示例揭示跨平台工具的实际运用。
|
17天前
|
XML 搜索推荐 前端开发
安卓开发中的自定义视图:打造个性化UI组件
在安卓应用开发中,自定义视图是一种强大的工具,它允许开发者创造独一无二的用户界面元素,从而提升应用的外观和用户体验。本文将通过一个简单的自定义视图示例,引导你了解如何在安卓项目中实现自定义组件,并探讨其背后的技术原理。我们将从基础的View类讲起,逐步深入到绘图、事件处理以及性能优化等方面。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的见解和技巧。
|
17天前
|
搜索推荐 前端开发 测试技术
打造个性化安卓应用:从设计到开发的全面指南
在这个数字时代,拥有一个定制的移动应用不仅是一种趋势,更是个人或企业品牌的重要延伸。本文将引导你通过一系列简单易懂的步骤,从构思你的应用理念开始,直至实现一个功能齐全的安卓应用。无论你是编程新手还是希望拓展技能的开发者,这篇文章都将为你提供必要的工具和知识,帮助你将创意转化为现实。
|
17天前
|
Java Android开发 开发者
探索安卓开发:构建你的第一个“Hello World”应用
在安卓开发的浩瀚海洋中,每个新手都渴望扬帆起航。本文将作为你的指南针,引领你通过创建一个简单的“Hello World”应用,迈出安卓开发的第一步。我们将一起搭建开发环境、了解基本概念,并编写第一行代码。就像印度圣雄甘地所说:“你必须成为你希望在世界上看到的改变。”让我们一起开始这段旅程,成为我们想要见到的开发者吧!
24 0
|
20天前
|
存储 监控 Java
探索安卓开发:从基础到进阶的旅程
在这个数字时代,移动应用已成为我们日常生活的一部分。对于开发者来说,掌握安卓开发不仅是技能的提升,更是通往创新世界的钥匙。本文将带你了解安卓开发的核心概念,从搭建开发环境到实现复杂功能,逐步深入安卓开发的奥秘。无论你是初学者还是有经验的开发者,这篇文章都将为你提供新的见解和技巧,帮助你在安卓开发的道路上更进一步。
19 0
|
编解码 区块链 Android开发
Android MediaCodec 硬编码 H264 文件
在 Android 4.1 版本提供了 MediaCodec 接口来访问设备的编解码器,不同于 FFmpeg 的软件编解码,它采用的是硬件编解码能力,因此在速度上会比软解更具有优势,但是由于 Android 的碎片化问题,机型众多,版本各异,导致 MediaCodec 在机型兼容性上需要花精力去适配,并且编解码流程不可控,全交由厂商的底层硬件去实现,最终得到的视频质量不一定很理想。
867 0
Android MediaCodec 硬编码 H264 文件