前言
音频是声音的一种数字化表示方式,它的应用领域非常多,很多领域的应用技术已经很成熟,比如常见的:通信、娱乐、医疗(超声)、人机交互等等。就我目前接触到的消费类嵌入式设备而言,比较多的应用场景是:
语音对讲,
音视频录像
语音检测,识别
涉及到的开发技术主要有:
音频的编码、解码
音频格式封装、格式转换
回声消除
声音检测、识别
虽然音频的应用技术大部分都已经比较成熟了,但是在嵌入式开发中,受限于硬件资源的匮乏,还是会遇到不少的问题。其中涉及到很多的知识和概念,如果不是专业做音视频的同学,估计也容易弄迷糊。
下面内容是将我自己在实际开发工作中接触到的音频相关的知识进行了一个简单整理归纳,仅供参考。
(一)音频处理流程介绍
(1)理想处理流程
比较理想的音频应用处理流程,大概入下图所示:
MIC 将声音震动信号转换为电(数字/模拟)信号,将其输入到SOC的AI(音频输入模块)
AI模块对输入的信号进行转换(ADC转换采样),输出为PCM格式的音频数据
将PCM音频数据进行压缩、转换、封装成各种格式,比如常见的AAC、MP3等
将压缩过的音频文件,与视频文件一起封装成音视频文件,比如MP4文件
(2)实际处理流程
在嵌入式应用中,考虑到系统资源限制、应用场景的不同,实际使用会比较的复杂,主要的受限是:既要支持本地音频存储、又要支持网络传输。
PCM是原始音频数据,一般嵌入式芯片的音频编码是可以将PCM数据编码成G711、G726等格式,但基本上不会支持AAC编码,主要可能是涉及到版权问题。君正和海思系列的SOC都不能直接支持AAC编码。
但是从编码压缩比例来看,ACC编码的压缩比例是比G711、G726的要高的,也就是说在相同条件下,AAC编码可以存储时间更加长的音频信息。另外,很多视频封装库,对AAC的支持都是比较友好。
基于上面这些情况,就会导致在同一个体统中,可能会存在几种格式的音频格式数据。比如下图:
上图中,主要的应用场景,一个是音频网络传输,一个是音频本地存储。
路线1:
将AI模块采集到的PCM直接通过网络传输给IOT平台
这种方式耗费资源少,但是占用网络带宽大
适用于没有音频编码模块的SOC
路线2:
将PCM格式数据,编码成G711、G726等格式之后再通过网络传输出去
耗费资源少,网络带宽占用的也少,是最优的一个选项
适用于带音频编码的SOC
路线3:
将PCM格式数据,通过软件编码的方式编码成AAC格式,然后再封装成MP4、AVI等格式
这种方式会占用CPU资源,运行内存RAM,以及Flash空间(AAC编码库比较大)
适用于一定需要AAC编码的场景
路线4:
出现这种使用方式的主要原因是,SOC同一时间只支持一个音频格式输出,比如如果要输出PCM格式,就不能再编码输出G711、G726等格式
将编码输出的G711、G726格式,进行软件解码成PCM格式,在通过软件压缩成AAC格式,最后才封装成mp4格式
这种方式适用于一定要使用AAC格式,但是SOC又不能同时输出两种类型音频格式的场景
耗费的各种资源都是最多的
(二)音频格式转换
(1)PCM 与 G711A、G711U
PCM:
设备通过MIC采集音频信号,MIC分为两大类,数字MIC和模拟MIC,数字MIC输出的是已经转换过的数字信号,但消费类设备中比较常用的是模拟MIC。
PCM数据是将模拟MIC输入的模拟音频信号通过ADC转换为数字信号的二进制序列,它没有文件头也没有结束标志,是一种未压缩的数据格式。
PCM文件可以通过Audacity Beta (Unicode) 以文件->导入->裸数据 的方式打开,可以进行播放,剪辑,查看等操作
主要的参数有:声道,采样频率,采样位数
下图打开的是一个:2声道,48KHz 采样频率,16位深度的PCM文件
G711A与G711U
G711 分为a-law和u-law,通过查表的方式将16位的PCM数据压缩成8位
G711 它的压缩率为1:2,1个1M 的PCM文件转换为G711格式后只有0.5M
G711 中的u-law 即g711u,主要使用在北美和日本
G711 中的a-law 即g711a,主要使用在欧洲及其它地区
如果要直接播放G711 文件音频,在Linux系统中可以直接使用 ffplay 命令来播放
ffplay -i test.pcm -f s16le -ac 2 -ar 48000 ffplay -i test.g711a -f alaw -ac 2 -ar 48000 ffplay -i test.g711u -f mulaw -ac 2 -ar 48000
-ac: 音频通道数 -ar:音频采样率 -f:文件格式
G711与PCM之间的转换先对来说是比较简单的,上面我是将一个 48K 16bit 2通道PCM 与G711 格式相互转换的简单工程
(三) AAC格式与编码
AAC 相比于G711 要复杂很多,AAC它有很多的版本,编码器也有很多种,使用比较多的是FAAC(Freeware Advanced Audio Coder),因为它是免费的。
(1)AAC的各种格式
AAC的文件格式有:
ADIF(Audio Data Interchange Format) 只有在文件开头的位置才有音频的头部信息
ADTS(Audio Data Transport Stream) 主要特点是每一帧都带有头部信息
文件格式是指主要以文件类型来保存的音频数据
AAC的流格式:
流格式主要是指用于流媒体传输的格式,主要有:
AAC_RAW是指未经过封装AAC裸数据
AAC_ADTS与文件格式中的ADTS格式相同
AAC_LATM (Low-Overhead Audio Transport Multiplex)AAC音频的一种传输协议。
比较常用的是ADTS格式,因为它在音频数据文件存储和流传输中都可以使用
(2)ATDS格式介绍
我们看fdk-aac中对ADTS结构的定义
这里只是把结构头部的项列出来了,这里列出来的有15项,整个结构头的长度有17个字节。
实际ADTS头结构有两种长度,包含CRC校验的是9个字节的长度,没有CRC校验的是7个字节,每项的作用与实际长度可以看wiki上的一个定义:https://wiki.multimedia.cx/index.php/ADTS
我们使用Elecard Stream Analyzer 工具打开一个ADTS格式的AAC文件进行查看会更加的清晰:
标签1随意点的第四帧,它的偏移地址是0x54a
标签2处是ADTS 的同步字Syncword,12位,0xFFF
右上的方框,是ADTS各项参数的解析
标签3处是单前帧(第4帧)的长度,403
标签4是下一帧的偏移地址0x6dd,正好是上一帧的偏移地址+上一帧的长度 = 0x54a + 403 = 0x6dd
如果是需要自己手动解析AAC的ADTS格式文件,也可以通过上面方式进行解析,先找到帧头标签,再逐项的解析各个参数,最后在根据帧长度跳转到下一帧进行数据解析。
(3)AAC格式编码
主要的AAC编码器有:FhG、Nero AAC、QuickTime/iTunes、FAAC、DivX AAC,在嵌入式中比较常用的是FAAC。
基于FAAC的编码工具和库,比较常用的有:
FFMPEG:它可以集成多种编码器
fdk-aac:同时集成了faac编解码
faac:aac 编码库
faad:aac 解码库
上面介绍的几种AAC封装库,都可以在github上下载到源码:
https://github.com/mstorsjo/fdk-aac https://github.com/knik0/faac https://github.com/knik0/faad2
(4) fdk-aac移植
github 上下载源码https://github.com/mstorsjo/fdk-aac
可以通过tag选择不同版本进行下载,tag中的一般都是比较稳定的发布版本
如果要将fdk-aac移植到君正的T31设备上,可以按下面命令进行交叉编译:
mkdir _install_uclibc ./autogen.sh CFLAGS+=-muclibc LDFLAGS+=-muclibc CPPFLAGS+=-muclibc CXXFLAGS+=-muclibc ./configure –prefix=$PWD/_install_uclibc –host=mips-linux-gnu make -j4 make install
交叉编译的文件放置在_install_uclibc文件夹下,可以通过下面命令确定编译使用的编译工具链:file libfdk-aac.so.2.0.2
biao@ubuntu:~/test/fdk-aac-master/_install_uclibc/lib$ file libfdk-aac.so.2.0.2 libfdk-aac.so.2.0.2: ELF 32-bit LSB shared object, MIPS, MIPS32 rel2 version 1 (SYSV), dynamically linked, not stripped
如果要直接在PC上编译测试,可以使用下面命令:
mkdir _install_linux_x86 ./autogen.sh ./configure –prefix=$PWD/_install_linux_x86 make -j4 make install
(5) fdk-aac应用
这里简单介绍如何使用fdk-aac将PCM文件编码成AAC格式文件,然后再通过fdk-aac将AAC解码成PCM格式数据。
fdk-aac源码下有个test-encode-decode.c文件,它是以wav格式的文件为基础的一个demo,如果PCM和AAC数据是以wav的格式存储的,可以直接参考官方demo。
我这里使用的是上面有介绍的PCM裸流进行编码和解码。
(a) PCM编码成AAC
因为我们使用的是PCM裸流,从文件中是无法读取出流的任何信息,所以PCM流的信息是需要我们自己填写的:
int aot, afterburner, eld_sbr, vbr, bitrate, adts, sample_rate, channels,mode; /**参数设置**/ aot = 2; /**Audio object type 2 MPEG-4 AAC Low Complexity.**/ afterburner = 0; /**是否启用分析合成算法,可提高编码质量,但是会耗资源**/ eld_sbr = 0 ; /**Spectral Band Replication 频谱显示**/ vbr = 0; /**可变码率配置**/ bitrate = 48000; /**编码码率**/ adts = 1; /**是否可传输**/ sample_rate = 48000; /**采样率**/ channels = 2; /**通道**/
通过aacEncoder_SetParam(encoder, AACENC_TRANSMUX, 2)可以设定需要编码成的AAC格式,它支持的格式有:
– 0: raw access units – 1: ADIF bitstream format – 2: ADTS bitstream format – 6: Audio Mux Elements (LATM) withmuxConfigPresent = 1 – 7: Audio Mux Elements (LATM) withmuxConfigPresent = 0, out of band StreamMuxConfig – 10: Audio Sync Stream (LOAS) */
(b) AAC解码成PCM
我们这里介绍将ADTS格式编码的AAC文件解压成PCM
要解码AAC文件,首先需要能够检测到AAC文件中音频帧的位置及长度,所以我们首先需要解析AAC 的ADTS头信息,头结构定义如下:
typedefstruct adts_fixed_header { unsigned short syncword:12; unsignedchar id: 1; unsignedchar layer:2; unsignedchar protection_absent: 1; unsignedchar profile: 2; unsignedchar sampling_frequency_index: 4; unsignedchar private_bit: 1; unsignedchar channel_configuration:3; unsignedchar original_copy:1; unsignedchar home: 1; } adts_fixed_header; // length : 28 bits typedefstruct adts_variable_header { unsignedchar copyright_identification_bit:1; unsignedchar copyright_identification_start:1; unsigned short aac_frame_length:13; unsigned short adts_buffer_fullness:11; unsignedchar number_of_raw_data_blocks_in_frame:2; } adts_variable_header; // length : 28 bits
解析方法如下:
memset(&fixed_header, 0, sizeof(adts_fixed_header)); memset(&variable_header, 0, sizeof(adts_variable_header)); get_fixed_header(headerBuff, &fixed_header); get_variable_header(headerBuff, &variable_header);
解码的时候,还需要注意需要使用aacDecoder_ConfigRaw 配置PCM的信息,demo 是通过info.confBuf来获取,这个值是在编码的时候才会有,所以这个值需要根据实际参数来设置:
unsignedchar conf[] = {0x11, 0x90}; //AAL-LC 48kHz 2 channle unsignedchar* conf_array[1] = { conf }; unsignedint length = 2; if (AAC_DEC_OK != aacDecoder_ConfigRaw(decoder, conf_array, &length)) { printf(“error: aac config fail “); exit(1); }
完整工程文件如下:
biao@ubuntu:~/test/faac/fdk-aac-x86$ tree . ├── 48000_16bits_2ch.pcm ├── adts.c ├── adts.h ├── decode_48000_16bits_2ch.pcm ├── include │ └── fdk-aac │ ├── aacdecoder_lib.h │ ├── aacenc_lib.h │ ├── FDK_audio.h │ ├── genericStds.h │ ├── machine_type.h │ └── syslib_channelMapDescr.h ├── lib │ ├── libfdk-aac.a │ ├── libfdk-aac.la │ ├── libfdk-aac.so -> libfdk-aac.so.2.0.2 │ ├── libfdk-aac.so.2 -> libfdk-aac.so.2.0.2 │ ├── libfdk-aac.so.2.0.2 │ └── pkgconfig │ └── fdk-aac.pc ├── Makefile ├── out.aac ├── out_ADIF.aac ├── out_adts.aac ├── out_RAW.aac └── test_faac.c 4 directories, 22 files biao@ubuntu:~/test/faac/fdk-aac-x86$
结尾
嵌入式音频开发涉及到的内容很多,每个功能单独拉出来都会涉及到很多的知识点。