原创文章,未经作者允许不得转载
山黛远,月波长
暮云秋影蘸潇湘
醉魂应逐凌波梦,分付西风此夜凉
https://github.com/JadynAi/MediaLearn/blob/feature-0.9/mediakit
在Android开发方面,音视频占据了不小领域。对于想往这方面了解的小伙伴们,往往不知道从何处下手开始学习。
我接触音视频开发有一段日子,作为自己学习的回顾和补充,也一直在记录一些音视频开发的博客。
对往期博客有兴趣的朋友们可以先了解一二。
MediaCodeC硬编码将图片集编码为视频Mp4文件MediaCodeC编码视频
MediaCodeC将视频完整解码,并存储为图片文件。使用两种不同的方式,硬编码解码视频
MediaCodeC解码视频指定帧硬编码解码指定帧
概述
最近再次回顾所学,觉得还有许多不足。遂决定写几篇小总结,以Android平台录制视频项目为例,整理自己的音视频开发知识。如果有小伙伴想学习音视频开发,但又不知道从何着手,可以模仿博主做一个相关的Demo来学习。
本文的项目地址入口在Camera2Record入口界面,业务功能实现在Camera2Recorder。
在这个项目中,我会尽可能地将音视频涉及到的功能模块化,减少耦合性。让这些零散的功能尽可能的适应更多的业务场景。
API
项目中视频方面采用的技术逻辑为:
使用Camera2API,配合MediaCodeC + Surface + OpenGL将原始帧数据编码为H264码流
音频方面采用技术逻辑为:
AudioRecord录音,MediaCodeC将PCM数据编码为AAC数据
音视频编码使用的是MediaMuxer
,将视频帧数据和音频帧数据封装为MP4文件。整体而言涉及到的API有:
- MediaCodeC
- AudioRecord
- MediaMuxer
- OpenGL(不用详细了解)
架构设计【注1】
作为一个简单的音视频录制应用,并没有什么花哨的功能(暂时没有,以后会慢慢追加)。整体业务逻辑就是直截了当的录制视频 ——>
产出视频
。业务再细分的话,主要有三个部分:一是画面,即视频部分;二是声音,即音频部分;三是混合器,即将视频和音频混合,并生成视频文件。
将业务略作区分后,我们由结果向前反推,既然要生成MP4文件,那么需要提供一些什么数据呢?所以我们根据输出——即混合器部分,梳理各个模块的详细功能。
视频封装
在混合器
模块,使用了Android提供的MediaMuxer
作为视频封装输出工具。MediaMuxer
支持三种输出格式,分别为MP4、Webm和3GP文件,本次项目的混合器输出自然选择的是MP4文件。
MP4是MPEG-4的官方容器格式定义的广义文件扩展名,可以流媒体化并支持众多多媒体的内容:多音轨、视频流、字幕、图片、可变帧率、码率【注2】。
在制作MP4文件时,应该优先选用MPEG-4标准下的视频/音频格式,一般来说,对于MP4容器的封装,相对而言比较常见的有两种编码方式:
- H264视频编码,AAC音频编码
- Xvid视频编码,MP4音频编码
视频编码算法
在本项目中,博主采用的视频编码算法为H264。H264作为压缩率最高的视频压缩格式,与其他编码格式相比,同等画面质量,体积最小。它有两个名称,一个是沿用ITU_T组织的H.26x名称——H.264
;另一个是MPEG-4AVC,AVC即为高级视频编码,而MP4格式则是H264编码制定使用的标准封装格式【注3】。
音频编码算法
博主采用的音频编码算法为AAC。AAC可以同时支持48个音轨,15个低频音轨,相比MP3,AAC可以在体积缩小30%的前提下提供更好的音质【注4】。
AAC最初是基于MPEG-2的音频编码技术,后来MPEG_4标准出台,AAC重新集成了其他技术,变更为现在的MPEG-4 AAC标准。一般而言,目前常用的AAC编码指代的就是MPEG-4 AAC。
MPEG-4 AAC有六种子规格:
- MPEG-4 AAC LC 低复杂度规格(Low Complexity)—现在的手机比较常见的MP4文件中的音频部份就包括了该规格音频文件
- MPEG-4 AAC Main 主规格 注:包含了除增益控制之外的全部功能,其音质最好
- MPEG-4 AAC SSR 可变采样率规格(Scaleable SampleRate)
- MPEG-4 AAC LTP 长时期预测规格(Long TermPredicition)
- MPEG-4 AAC LD 低延迟规格(Low Delay)
- MPEG-4 AAC HE高效率规格(HighEfficiency)—这种规格用于低码率编码,有NeroACC 编码器支持
目前最流行的就是LC和HE了。需要注意的是MPEG-4 AAC LC这种规格为“低复杂度规格”,一般应用于中等码率。而中等码率,一般指96kbps~192kbps,所以如果使用了LC编码,请将码率控制在这个范围内会比较好一点。
工作流程
将业务逻辑梳理清楚之后,那么各个模块更具体的功能就清晰了很多。这里有一个大致的工作流程图以作参考:
先从视频模块开始,VideoRecorder
运行在一个独立的工作线程,使用OpenGL+Surface+MediaCodeC
对接Camera2,接受相机回调画面并编码为H264码流。这个类对外回调可用的视频帧数据VideoPacket
对象。这个数据类型是工程中自行定义的对象,封装了这一帧视频的数据——ByteArray类型
,以及这一帧数据携带的信息——BufferInfo:主要是这一帧的时间戳以及其他
。
接下来是音频模块,考虑到录音模块或许日后有机会复用,所以将录音模块单独分离出来。AudioRecorder
在开始录制后不停运行,对外回调PCM原始数据——ByteArray类型。
AudioRecord类可以对外提供两种类型,ShortArray和ByteArray,因为视频对外的数据类型为ByteArray,所以这里也选择了ByteArray。这一段PCM数据会被添加到一个外部的链表中,而AudioEncoder
音频编码模块,也持有PCM数据链表。在开始录制后,AudioEncoder
不断循环地从PCM链表中提取数据,编码为AAC格式的原始帧数据。这里的AAC原始数据,指的是没有添加ADTS头信息的数据。
与此同时,视频模块输出的视频帧数据和音频模块输出的AAC音频帧数据,会被提交到Mux
模块中,在这个模块中,持有两个视频帧数据和音频帧数据的链表。Mux
模块会不断循环地从这两个链表中提取数据,使用MediaMuxer
将帧数据封装到各自的轨上,最终输出MP4文件。
音频录制及编码
音频模块分为录音以及编码两个小模块,分别运行在两个独立的工作线程。录音模块不用多提,完全是基于AudioRecord的二次封装,这里是代码地址AudioRecorder。
这里主要说一下音频编码模块AudioEncoder,音频录制模块在运行后拿到可用PCM数据并回调到外部,封装到一个线程安全的链表中。而AudioEncoder
则会不停地从链表中提取数据,再使用MediaCodeC将PCM数据编码为AAC格式的音频帧数据。由于MediaMuxer
封装AAC音频轨,并不需要ADTS头信息,所以AudioEncoder
得到的AAC原始帧数据也无须再作二次处理了。
1 | var presentationTimeUs = 0L |
这里的工作流程是这样的:只有PCM链表中有数据,MediaCodeC就会将这些数据填入到可用的输入队列中。每一段PCM的数据长度并不一定是一帧音频数据所对应的长度,所以工程要做的是,不停地想编码器输入数据,而编码器也需要不停地往外输出数据,直至将编码器内部的输入数据编码完毕。
还有一个需要注意的点,就是MediaCodec当输入数据全部填充完毕时,需要发送一个==BUFFER_FLAG_END_OF_STREAM==标示,用来标示数据输入END。如果没有发送这个标示的话,那么编码完后的音频数据会丢失掉最后一小段时间的音频。
除此之外,还有一个很重要的点,就是AAC编码的时间戳计算问题,相关部分的知识请阅读博主之前的博客解决AAC编码时间戳问题
未完待续
由于篇幅有限,这篇文章只分享了音频的编码,在下一篇文章里博主会分享视频的录制和编码~~
以上
相关文章
Camera2录制视频(二):MediaCodeC+OpenGL视频编码
注
- 1、本文的架构设计部分参考了《音视频开发进阶指南》—— 实现一款视频录制应用章节
- 2、参考资料Mp4编码全介绍
- 3、参考资料音视频封装格式、编码格式知识
- 4、参考资料AAC音频编码格式介绍