嵌入式音訊應用開發介紹

liwen01發表於2023-12-12

liwen01 2023.12.10

前言

音訊是聲音的一種數字化表示方式,它的應用領域非常多,很多領域的應用技術已經很成熟,比如常見的:通訊、娛樂、醫療(超聲)、人機互動等等。就我目前接觸到的消費類嵌入式裝置而言,比較多的應用場景是:

  • 語音對講,
  • 音影片錄影
  • 語音檢測,識別

涉及到的開發技術主要有:

  • 音訊的編碼、解碼
  • 音訊格式封裝、格式轉換
  • 回聲消除
  • 聲音檢測、識別

雖然音訊的應用技術大部分都已經比較成熟了,但是在嵌入式開發中,受限於硬體資源的匱乏,還是會遇到不少的問題。其中涉及到很多的知識和概念,如果不是專業做音影片的同學,估計也容易弄迷糊。

下面內容是將我自己在實際開發工作中接觸到的音訊相關的知識進行了一個簡單整理歸納,僅供參考。

(一)音訊處理流程介紹

(1)理想處理流程

比較理想的音訊應用處理流程,大概入下圖所示:

  1. MIC 將聲音震動訊號轉換為電(數字/模擬)訊號,將其輸入到SOC的AI(音訊輸入模組)
  2. AI模組對輸入的訊號進行轉換(ADC轉換取樣),輸出為PCM格式的音訊資料
  3. 將PCM音訊資料進行壓縮、轉換、封裝成各種格式,比如常見的AAC、MP3等
  4. 將壓縮過的音訊檔案,與影片檔案一起封裝成音影片檔案,比如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結構的定義

typedefstruct {
/* ADTS header fields */
UCHAR mpeg_id;
UCHAR layer;
UCHAR protection_absent;
UCHAR profile;
UCHAR sample_freq_index;
UCHAR private_bit;
UCHAR channel_config;
UCHAR original;
UCHAR home;
UCHAR copyright_id;
UCHAR copyright_start;
USHORT frame_length;
USHORT adts_fullness;
UCHAR num_raw_blocks;
UCHAR num_pce_bits;
} STRUCT_ADTS_BS;

這裡只是把結構頭部的項列出來了,這裡列出來的有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\n");
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$

(四)工程資料下載

如需上面介紹的工程,測試檔案,以及檢視工具,可以在公眾號中回覆 資源 獲取,內容在音影片連線中。

結尾

嵌入式音訊開發涉及到的內容很多,每個功能單獨拉出來都會涉及到很多的知識點。

上面只是簡單的介紹了一下它們的概念,以簡單使用。如有錯誤,歡迎批評指正。

 

---------------------------End---------------------------
如需獲取更多內容
請關注 liwen01 公眾號

相關文章