音視頻開(kāi)發(fā)之旅——音頻基礎(chǔ)概念池摧、交叉編譯原理和實(shí)踐(LAME的交叉編譯)(Android)

本文章已授權(quán)微信公眾號(hào)郭霖(guolin_blog)轉(zhuǎn)載片效。

本文主要講解的是音頻基礎(chǔ)概念红伦、交叉編譯原理和實(shí)踐(LAME的交叉編譯),是基于Android平臺(tái)淀衣,示例代碼如下所示:

AndroidAudioDemo

另外昙读,iOS平臺(tái)也有相關(guān)的文章,如下所示:

音視頻開(kāi)發(fā)之旅——音頻基礎(chǔ)概念膨桥、交叉編譯原理和實(shí)踐(LAME的交叉編譯)(iOS)

音頻基礎(chǔ)概念

在進(jìn)行音頻開(kāi)發(fā)的之前蛮浑,了解聲學(xué)的基礎(chǔ)還是很有必要的唠叛。

聲音的物理性質(zhì)

在初中物理的時(shí)候?qū)W過(guò),聲音是由三要素組成:音調(diào)沮稚、響度音色艺沼。

音調(diào)

聲音的高低叫做音調(diào)。物體振動(dòng)得越快蕴掏,發(fā)出聲音的音調(diào)就越高障般;物體振動(dòng)得越慢,發(fā)出的音調(diào)越低盛杰。頻率過(guò)零率挽荡,指信號(hào)的符號(hào)變化的比率)決定了音調(diào),頻率越高即供,波長(zhǎng)越短定拟,聲音更容易繞過(guò)障礙物,也就是能量衰減越小募狂,反之得到相反的結(jié)論。

響度

聲音的強(qiáng)弱叫做響度角雷。我們可以一般用分貝(dB)來(lái)描述響度祸穷,分貝越大,聲音響度越大勺三,反之得到相反的結(jié)論雷滚。

音色

聲音的品質(zhì)叫做音色,它反映了每個(gè)物體發(fā)出的聲音特有的品質(zhì)吗坚。例如在同樣的音調(diào)和響度下祈远,吉他和鋼琴的聲音聽(tīng)起來(lái)是不同的,也就是音色是不同的商源。波的形狀決定聲音的音色车份,吉他和鋼琴音色不同就是因?yàn)樗鼈兘橘|(zhì)產(chǎn)生的波形不同。

業(yè)界來(lái)說(shuō)牡彻,人耳能夠聽(tīng)到頻率范圍大約為20Hz~20kHz扫沼,對(duì)3kHz~4kHz頻率范圍內(nèi)的聲音比較敏感,對(duì)于較低或者較高頻率的聲音庄吼,人耳的敏感度會(huì)減弱缎除;在分貝較低時(shí),聽(tīng)覺(jué)的頻率特性會(huì)很不均勻总寻,反之就會(huì)較為均勻器罐。一個(gè)頻率范圍較寬的音樂(lè),最佳的分貝范圍為80dB~90dB渐行,超過(guò)90dB就會(huì)損害人耳轰坊,105dB是人耳的極限铸董。

聲音在不同的介質(zhì)傳播的速度也會(huì)不一樣,在空氣中的傳播速度為340m/s衰倦,不過(guò)在真空是無(wú)法傳播的袒炉。

有時(shí)候我們?cè)诳諘绲牡胤交蛘吒呱酱蠛暗臅r(shí)候,會(huì)聽(tīng)到回聲(echo)樊零,產(chǎn)生回聲的原因是聲音在傳播的過(guò)程中遇到障礙物后反彈回來(lái)后再次讓我們聽(tīng)到我磁,但是如果這兩種聲音傳回到我們耳朵的時(shí)差小于80毫秒的話,我們就無(wú)法分辨這兩種聲音驻襟。

音頻數(shù)字化

將聲音模擬信號(hào)轉(zhuǎn)換為數(shù)字信號(hào)的過(guò)程稱之為音頻數(shù)字化夺艰,這里需要經(jīng)過(guò)三個(gè)步驟:采樣量化編碼沉衣。

采樣

首先對(duì)模擬信號(hào)進(jìn)行采樣郁副,采樣是指在時(shí)間軸(橫軸)對(duì)信號(hào)進(jìn)行數(shù)字化,根據(jù)奎斯特定理(采樣定理豌习,我們要按比聲音最高音頻高兩倍以上的頻率對(duì)聲音進(jìn)行采樣存谎,這個(gè)過(guò)程也稱為AD轉(zhuǎn)換。上面提過(guò)的人耳能夠聽(tīng)到的頻率為20Hz~20kHz肥隆,所以一般采樣頻率為44.1kHz既荚,也就是說(shuō)1秒會(huì)采樣44100次

量化

上面提到的栋艳,具體每個(gè)采樣需要怎樣處理呢恰聘?這就需要量化,量化是指在幅度軸(縱軸)上對(duì)信號(hào)進(jìn)行數(shù)字化吸占,要注意的是晴叨,和上面提到的采樣形成平面直角坐標(biāo)系,舉個(gè)例子:用16bit的二進(jìn)制信號(hào)表示這個(gè)聲音的一個(gè)采樣矾屯,16bit等于一個(gè)short兼蕊,表示范圍為[-32768, 32767],也就是說(shuō)有65536個(gè)可能取值件蚕,所以在幅度上分為65536層遍略。

編碼

最后一步就是要將采樣的數(shù)據(jù)進(jìn)行存儲(chǔ),也就是是需要進(jìn)行編碼骤坐,編碼就是按照一定的格式記錄采樣和量化后的數(shù)據(jù)數(shù)據(jù)绪杏,例如:順序存儲(chǔ)壓縮存儲(chǔ)等等。常用的格式為音頻的裸數(shù)據(jù)格式纽绍,也就是脈沖編碼調(diào)制(Pulse Code Modulation蕾久,簡(jiǎn)稱PCM)。描述一段PCM的數(shù)據(jù)需要這幾個(gè)概念:采樣率(sampleRate)拌夏、量化格式(sampleFormat僧著,也稱為位深度)聲道數(shù)(channel)履因。比特率用于衡量音頻數(shù)據(jù)單位時(shí)間內(nèi)的容量大小,也就是一秒時(shí)間內(nèi)的比特?cái)?shù)目盹愚,我們以常見(jiàn)的CD格式和DVD-Audio格式為例子:

CD格式的采樣率為44100Hz栅迄,量化格式為16bit(2byte),聲道數(shù)為2皆怕,那么它的比特率為:

44100 * 16 * 2 = 1411200bps

轉(zhuǎn)換可得1411200bps / 1024 = 1378.125Kibps

DVD-Audio格式的采樣率為96000Hz毅舆,量化格式為24bit(3byte),聲道數(shù)為6.那么它的比特率為:

96000 * 24 * 6 = 13824000bps

轉(zhuǎn)換可得13824000bps / 1024 = 13500Kibps愈腾,再轉(zhuǎn)換可得13500Kibps / 1024 ≈ 13.18Mibps

一般來(lái)說(shuō)一首歌曲的時(shí)間大概在4分鐘左右憋活,那我們算下CD格式和DVD-Audio格式會(huì)占用多大的存儲(chǔ)空間,如下所示:

CD格式:1411200bps * 4 * 60 = 338688000b虱黄,轉(zhuǎn)換可得338688000b / 8 / 1024 / 1024 ≈ 40.37MiB

DVD-Audio格式:13824000bps * 4 * 60 = 3317760000b悦即,轉(zhuǎn)換可得3317760000b / 8 / 1024 / 1024 = 395.51MiB

由數(shù)據(jù)可得,DVD-Audio格式一秒時(shí)間內(nèi)的比特?cái)?shù)目大于CD格式橱乱,因此它的音質(zhì)會(huì)更好辜梳,當(dāng)然所占的儲(chǔ)存空間也會(huì)相應(yīng)得大。

壓縮編碼

由上面可以看到一首歌如果僅僅是已CD格式去存儲(chǔ)的已經(jīng)占用了40.37MiB泳叠,如果只是存儲(chǔ)在存儲(chǔ)設(shè)備上(例如:硬盤或者光盤)那還可以接受作瞄,但是如果在網(wǎng)絡(luò)上實(shí)時(shí)在線傳輸?shù)脑挘@樣的大小實(shí)在是太大了析二,所以我們需要對(duì)其進(jìn)行壓縮編碼粉洼,壓縮編碼里有個(gè)指標(biāo)叫做壓縮比节预,壓縮比是小于1叶摄,壓縮比越小(越接近0)安拟,丟失的信息就越多蛤吓,反之得出相反的結(jié)論。壓縮算法有兩種:無(wú)損壓縮有損壓縮糠赦。無(wú)損壓縮是指解壓后的數(shù)據(jù)能夠復(fù)原会傲;有損壓縮是指解壓后的數(shù)據(jù)不能夠復(fù)原,壓縮導(dǎo)致的丟失得越多拙泽,還原的失真就越大淌山。

有如下常用的壓縮編碼格式:

WAV編碼

WAV(Waveform Audio File Format)是微軟專門為Windows開(kāi)發(fā)的一種編碼格式,它會(huì)在PCM數(shù)據(jù)格式的前面加上44字節(jié)顾瞻,分別用來(lái)描述該P(yáng)CM數(shù)據(jù)的采樣率泼疑、聲道數(shù)、量化格式荷荤。

優(yōu)點(diǎn):音質(zhì)非常好退渗,有大量軟件支持移稳。

缺點(diǎn):占用的存儲(chǔ)空間較大。

適用場(chǎng)合:多媒體開(kāi)發(fā)的中間文件会油、音樂(lè)和音效素材个粱。

MP3編碼

MP3(MPEG-1或者M(jìn)PEG-2 Audio Layer III)是一種有損壓縮的編碼格式,它通過(guò)舍棄PCM數(shù)據(jù)人類聽(tīng)覺(jué)不重要的部分翻翩,已達(dá)到壓縮成較小文件的目的都许,對(duì)于大多數(shù)用戶來(lái)說(shuō),它的音質(zhì)和不壓縮的音頻沒(méi)有明顯的下降体斩。我們常用LAME編碼MP3文件梭稚,下面會(huì)講解到。

優(yōu)點(diǎn):音質(zhì)在高碼率(≥128Kbit/s)表現(xiàn)不錯(cuò)絮吵,同時(shí)壓縮比也比較高弧烤;有大量硬件和軟件支持,兼容性不錯(cuò)蹬敲。

適用場(chǎng)合:高碼率(≥128Kbit/s)的音頻暇昂。并且需要比較好的兼容性。

AAC編碼

AAC(Advanced Audio Coding伴嗡,高級(jí)音頻編碼)是一種高壓縮比的編碼格式急波,由于采用多聲道和使用低復(fù)雜性的描述方式,使其比幾乎所有的傳統(tǒng)編碼方式在同規(guī)格的情況下更勝一籌瘪校。目前衍生出LC-AAC澄暮、HE-AAC v1HE-AAC v2三種主要的編碼格式阱扬。LC-AAC是比較傳統(tǒng)的AAC泣懊,主要編碼中高碼率(≥80Kbit/s)的音頻;HE-AAC v1是高效AAC麻惶,是對(duì)AAC的擴(kuò)展馍刮,它使用頻段復(fù)制(SBR)提高頻域的壓縮效率,適用于中低碼率(≤80Kbit/s)窃蹋;HE-AAC v2結(jié)合使用了頻段復(fù)制(SBR)參數(shù)立體聲(PS)提高立體聲信號(hào)的壓縮效率卡啰,進(jìn)一步降低了對(duì)碼率的需要(接近于50%),主要編碼低碼率(≤48Kbit/s)的音質(zhì)警没。大部分編碼器都設(shè)置為≤48Kbit/s自動(dòng)啟用PS匈辱,>48Kbit/s就關(guān)閉PS,箱單與HE-AAC v1杀迹。

優(yōu)點(diǎn):音質(zhì)在中低碼率(<128Kbit/s)表現(xiàn)優(yōu)異亡脸,多用于視頻中音頻軌的編碼。

適用場(chǎng)合:中低碼率(<128Kbit/s)的音頻,多用于視頻中音頻軌的編碼梗掰。

Ogg編碼

Ogg在各種碼率下都有優(yōu)秀的表現(xiàn)嵌言,尤其在中低碼率的場(chǎng)景表現(xiàn)不錯(cuò),同時(shí)它不收到軟件專利的限制及穗,完全免費(fèi)摧茴。Ogg有著非常出的的算法,可以用更小的碼率編碼出更好的音質(zhì)埂陆,舉個(gè)例子:128Kbit/s的Ogg音質(zhì)甚至比192Kbit甚至更高的MP3還要好苛白。

優(yōu)點(diǎn):可以用更小的碼率編碼出更好的音質(zhì),在各種碼率下都變現(xiàn)優(yōu)異焚虱。

缺點(diǎn):目前兼容性不夠好购裙,流媒體特性不支持。

適用場(chǎng)合:語(yǔ)音聊天的音頻消息鹃栽。

Android平臺(tái)增加C和C++支持

Android提供了一種編譯框架躏率,叫做JNI(Java Native Interface),用于允許運(yùn)行于JVM的Java或者Kotlin代碼去調(diào)用本地代碼(C民鼓、C++薇芝、匯編語(yǔ)言)。大概步驟為將相關(guān)的C/C++代碼放在項(xiàng)目模塊的cpp目錄下丰嘉,在構(gòu)建項(xiàng)目的時(shí)候夯到,Gradle會(huì)將這些代碼和應(yīng)用的代碼一起打包到原生庫(kù),然后Java或者Kotlin代碼就可以通過(guò)JNI去調(diào)用原生庫(kù)中的函數(shù)饮亏。什么時(shí)候需要用到JNI呢耍贾?有以下幾種情況:

  • 應(yīng)用程序需要一些平臺(tái)的特性支持,但是Java層沒(méi)有提供相應(yīng)的API支持路幸,例如:OpenSL ES的使用)

  • 調(diào)用一些已經(jīng)存在并且已是成熟方案的C/C++庫(kù)荐开,例如:使用LAME編碼MP3文件、使用FFmpeg處理音頻或者視頻劝赔、使用OpenGL ES處理視頻特效誓焦。

  • 應(yīng)用程序?qū)Σ糠诌壿嫷倪\(yùn)行速度有較高的要求胆敞,那么這部分就可以用C/C++實(shí)現(xiàn)着帽,再通過(guò)JNI向Java層提供訪問(wèn)接口。

我們需要以下組件:

  • Android原生開(kāi)發(fā)套件(NDK):這是一套可以讓開(kāi)發(fā)者使用C/C++的工具移层。

  • ndk-build腳本或者CMake:用于構(gòu)建原生庫(kù)仍翰。

  • LLDB:Android Studio用于調(diào)試原生代碼的程序,默認(rèn)情況下观话,它會(huì)隨同Android Studio的安裝而安裝予借。

這里講解下ndk-build腳本CMake的區(qū)別,在講解之前,我們要了解下下面的內(nèi)容:

GNU灵迫、GCC秦叛、gcc、g++

  • GNU:它是一個(gè)完全自由的操作系統(tǒng)瀑粥,起源于GNU計(jì)劃挣跋。

  • GCC:GNU Compiler Collection(GNU編譯器套件)的縮寫,它是一組GNU操作系統(tǒng)中的編譯器集合狞换,可以用于編譯C避咆、C++、Java修噪、Go等語(yǔ)言查库。

  • gcc:GCC中的GNU C Compiler(C編譯器)。

  • g++:GCC中的GNU C++ Compiler(C++編譯器)黄琼。

對(duì)于.c文件和.cpp文件樊销,gcc會(huì)分別當(dāng)作c文件和cpp文件編譯,而g++會(huì)統(tǒng)一當(dāng)作cpp文件編譯脏款。

編譯C/C++的四個(gè)步驟

接下來(lái)我們要了解一下使用gcc(GNU Compiler Collection现柠,GNU編譯器套件)生成可執(zhí)行二進(jìn)制文件的大概過(guò)程:

預(yù)處理(Preprocess)

預(yù)處理(Preprocess):預(yù)處理會(huì)處理一些編譯前的準(zhǔn)備工作,把一些#define的宏定義完成文本替換弛矛,然后將#include里的文件復(fù)制到.cpp文件够吩,如果.h文件里還有.h文件,那么就會(huì)遞歸展開(kāi)丈氓,要注意的是周循,在這一步中,代碼注釋會(huì)被忽略万俗。通過(guò)g++ -E命令將.c文件預(yù)處理為.i文件湾笛,它是文本文件。

編譯(Compile)

編譯(Compile):編譯是把代碼轉(zhuǎn)換成匯編代碼闰歪,同時(shí)檢查詞法規(guī)則和語(yǔ)法規(guī)則嚎研,如果沒(méi)有出現(xiàn)語(yǔ)法錯(cuò)誤,那么不管邏輯是否錯(cuò)誤都不會(huì)報(bào)錯(cuò)库倘。通過(guò)g++ -S命令將.i文件轉(zhuǎn)換為.s文件临扮,它是文本文件。

匯編(Assemble)

匯編(Assemble):匯編是把匯編代碼(.s文件)轉(zhuǎn)換為機(jī)器碼教翩。通過(guò)g++ -c命令將.s文件轉(zhuǎn)換為.o文件(目標(biāo)文件)杆勇,它是二進(jìn)制格式。

鏈接(Link)

C/C++代碼經(jīng)過(guò)匯編后生成的.o文件(目標(biāo)文件)饱亿,它是二進(jìn)制文件蚜退,但是它不是最終可執(zhí)行的闰靴,需要和系統(tǒng)組件(例如:標(biāo)準(zhǔn)庫(kù)、動(dòng)態(tài)鏈接庫(kù))鏈接起來(lái)才能得到可執(zhí)行的二進(jìn)制文件(Executable File)钻注,完成這個(gè)過(guò)程的組件叫做鏈接器(Linker)蚂且。鏈接分為靜態(tài)鏈接動(dòng)態(tài)鏈接,生成的文件叫做靜態(tài)庫(kù)動(dòng)態(tài)庫(kù)幅恋。

靜態(tài)庫(kù)

靜態(tài)庫(kù)在Linux下為.a文件膘掰,在Windows下為.lib文件。之所以稱之為靜態(tài)庫(kù)佳遣,是因?yàn)樵阪溄与A段會(huì)將.o文件和引用到的庫(kù)一起鏈接打包到可執(zhí)行文件中识埋,它有如下特點(diǎn):

  • 會(huì)在編譯時(shí)期完成靜態(tài)庫(kù)對(duì)函數(shù)庫(kù)的鏈接。

  • 程序在運(yùn)行的時(shí)候與函數(shù)無(wú)關(guān)零渐,方便移植窒舟。

  • 會(huì)浪費(fèi)一定的空間和資源,因?yàn)樗心繕?biāo)文件和涉及到的函數(shù)庫(kù)被鏈接合成一個(gè)可執(zhí)行文件诵盼。

動(dòng)態(tài)庫(kù)

動(dòng)態(tài)庫(kù)在Linux下為.so文件惠豺,在Windows下為.dll文件。動(dòng)態(tài)庫(kù)在程序編譯時(shí)不會(huì)被鏈接到目標(biāo)文件风宁,而是在程序運(yùn)行時(shí)才被載入洁墙,它有如下特點(diǎn):

  • 把對(duì)一些庫(kù)函數(shù)的鏈接載入推遲到程序運(yùn)行的時(shí)期。

  • 不同的應(yīng)用程序如果調(diào)用相同的庫(kù)戒财,那么在內(nèi)存中只需要有一份該共享庫(kù)的實(shí)例热监,可以實(shí)現(xiàn)進(jìn)程之間的資源共享,節(jié)省了空間饮寞。

  • 由于動(dòng)態(tài)庫(kù)是在程序運(yùn)行的時(shí)候才載入孝扛,因此解決了靜態(tài)庫(kù)對(duì)程序的更新、部署和發(fā)布帶來(lái)的麻煩,只需要更新動(dòng)態(tài)庫(kù)就可以了,即增量更新政鼠。

  • 開(kāi)發(fā)者可以在程序代碼中控制鏈接載入,即顯示調(diào)用斯辰。

Make

Make其實(shí)是一個(gè)批量處理的工具,它是通過(guò)調(diào)用Makefile文件中開(kāi)發(fā)者指定的命令來(lái)進(jìn)行編譯鏈接,例如調(diào)用gcc或者其他編譯器的命令。上面提的ndk-build腳本就是使用NDK基于Make來(lái)構(gòu)建項(xiàng)目咨油。

CMake

如果是簡(jiǎn)單的工程Makefile文件手寫起來(lái)還是比較輕松的,但是如果復(fù)雜的工程手寫起來(lái)就比較麻煩了类缤,換平臺(tái)還要重新修改臼勉,所以就有上面提到的CMake邻吭。CMake是一個(gè)開(kāi)源的跨平臺(tái)自動(dòng)化建構(gòu)系統(tǒng)餐弱,它可以根據(jù)CMakeList.txt文件自動(dòng)生成Makefile文件來(lái)給上面的Make工具使用。它也是目前Android Studio編譯NDK默認(rèn)構(gòu)建工具,當(dāng)然也可以使用上面提到的ndk-build腳本膏蚓,官方也是支持的瓢谢。

本機(jī)編譯

我們要在PC上運(yùn)行一個(gè)二進(jìn)制的程序(要注意的是,是以源碼的方式進(jìn)行編譯驮瞧,而不是以包管理器的方式去安裝)會(huì)經(jīng)過(guò)如下步驟:

  1. 得到這段程序的源代碼氓扛,它可以是自己編寫的源代碼,也可以是從第三方開(kāi)源網(wǎng)站上下載的源代碼论笔。

  2. 在PC上編譯鏈接這些源代碼生成可執(zhí)行文件采郎。

  3. 在終端(Terminal)下執(zhí)行該可執(zhí)行文件。

總結(jié)就是使用本機(jī)器的編譯器和鏈接器狂魔,將源代碼編譯鏈接成一個(gè)可以在本機(jī)器運(yùn)行的程序蒜埋,這個(gè)編譯過(guò)程叫做本機(jī)編譯,它是正常的編譯過(guò)程最楷。

交叉編譯

了解完本機(jī)編譯后整份,交叉編譯就好理解了,它就是一個(gè)平臺(tái)(例如:PC)上生成另外一個(gè)平臺(tái)(例如:Android籽孙、iOS烈评、其他嵌入式設(shè)備)可執(zhí)行的程序。這里的編譯機(jī)器是PC犯建,所以編譯器是安裝在PC上讲冠,并且運(yùn)行在PC上的,而這個(gè)編譯器叫做交叉工具編譯鏈适瓦。那其實(shí)為啥需要交叉編譯呢沟启?因?yàn)檫\(yùn)行程序的目標(biāo)平臺(tái)運(yùn)算能力和存儲(chǔ)能力都是有限的,盡管現(xiàn)在iOS和Android設(shè)備的性能越來(lái)越強(qiáng)勁犹菇,但是和PC還是有一定的距離德迹,而且ARM平臺(tái)下的編譯工具和整個(gè)編譯過(guò)程異常繁瑣,所以PC是最佳選擇揭芍。目前大部分的嵌入式開(kāi)發(fā)平臺(tái)都提供本身平臺(tái)交叉編譯所需要運(yùn)行在PC上的交叉工具編譯鏈胳搞。

在所有的編譯器中,包括自行安裝在PC上的編譯器和嵌入式平臺(tái)的交叉工具編譯鏈称杨,都包含以下這幾個(gè)工具:

  • CC:編譯器肌毅,作用是對(duì)C或者C++源文件編譯成匯編文件。

  • AS:將匯編文件翻譯成機(jī)器碼姑原,生成目標(biāo)文件悬而,匯編文件使用的是指令助記符。

  • AR:打包器锭汛,它可以從一個(gè)庫(kù)增加或者刪除目標(biāo)代碼模塊笨奠。

  • LD:鏈接器袭蝗,作用是為前面生成的目標(biāo)代碼分配地址空間,將多個(gè)目標(biāo)文件鏈接成一個(gè)庫(kù)或者可執(zhí)行文件般婆。

  • GDB:調(diào)試工具到腥,它可以對(duì)正在運(yùn)行的程序進(jìn)行代碼調(diào)試。

  • STRIP:消除最終生成的庫(kù)文件或者可執(zhí)行文件其中的源碼蔚袍。

  • NM:查看靜態(tài)庫(kù)文件中符號(hào)表乡范。

  • Objdump:查看靜態(tài)庫(kù)或者動(dòng)態(tài)庫(kù)的方法簽名。

在Android的NDK提供的交叉工具編譯鏈就在開(kāi)發(fā)者用到的ndk下的prebuilt/darwin-x86_64/bin路徑中啤咽,它提供了上面這些工具晋辆。我的路徑如下所示:

/Users/tanjiajun/Library/Android/sdk/ndk/26.1.10909125/prebuilt/darwin-x86_64/bin

LAME的交叉編譯

我們了解完交叉編譯后,以LAME庫(kù)為例進(jìn)行實(shí)踐宇整。

先介紹一下LAME庫(kù)栈拖,它是目前最優(yōu)秀也是最常用的MP3編碼引擎。當(dāng)碼率達(dá)到320Kbit/s以上的時(shí)候没陡,LAME編碼出來(lái)的音頻質(zhì)量幾乎可以和CD音質(zhì)媲美涩哟,并且還能保證其文件體積非常小,因此如果要在移動(dòng)端編碼MP3文件盼玄,使用LAME是唯一選擇贴彼。

下面來(lái)講解下,在Android平臺(tái)下如何交叉編譯LAME庫(kù)埃儿,并且打印LAME版本器仗。

Android Studio準(zhǔn)備工作

首先我們的Android Studio要下載好NDK,然后新建一個(gè)Android項(xiàng)目童番,右鍵模塊點(diǎn)擊“Add C++ to Module”精钮,上面也提及過(guò),Android Studio編譯NDK的默認(rèn)構(gòu)建工具是CMake剃斧,所以我們會(huì)看到添加完畢后會(huì)有相關(guān)的代碼和文件生成轨香,例如生成了cpp文件夾和里面相關(guān)的cpp文件和CMakeList.txt文件。

下載LAME庫(kù)并解壓幼东,復(fù)制到項(xiàng)目中

然后在SourceForge下載最新版本的LAME庫(kù)臂容,目前為3.100,點(diǎn)擊下面文本即可下載:

lame-3.100.tar.gz

下載完成后根蟹,解壓文件得到lame-3.100文件夾脓杉,然后找到libmp3lame文件夾,把里面的.c和.h文件全部復(fù)制到Android Studio項(xiàng)目生成的cpp文件夾下简逮,我這邊會(huì)新建一個(gè)lame的文件夾來(lái)存放這些文件球散,這樣看起來(lái)目錄會(huì)整潔點(diǎn),我這邊寫了個(gè)的Python腳本用于把文件夾里的.c和.h文件復(fù)制到指定的目錄散庶,同時(shí)還會(huì)打印復(fù)制后的絕對(duì)路徑蕉堰。我已經(jīng)把它push到示例代碼中凌净,文件名為CopySpecifiedFiles.py,代碼如下所示:

import os
import shutil

oldDir = '/Users/tanjiajun/lame-3.100/libmp3lame'
newDir = '/Users/tanjiajun/StudioProjects/AndroidAudioDemo/app/src/main/cpp/lame'
cExtension = '.c'
hExtension = '.h'

if not os.path.exists(newDir):
    os.makedirs(newDir)
for file in os.listdir(oldDir):
    extension = os.path.splitext(file)[-1]
    if extension == cExtension or extension == hExtension:
        shutil.copy(os.path.join(oldDir, file), newDir)
        print(os.path.join(newDir, file))

然后再找到上面提到的lame-3.100文件夾下的include文件夾嘁灯,把里面的lame.h文件同樣復(fù)制到Android Studio的lame文件夾泻蚊。

編譯項(xiàng)目

接下來(lái)躲舌,我們需要修改CMakeList.txt文件丑婿,利用上面腳本打印的絕對(duì)路徑,修改后代碼如下所示:

cmake_minimum_required(VERSION 3.22.1)

project("audiodemo")

add_library(
        ${CMAKE_PROJECT_NAME}
        SHARED
        audiodemo.cpp
        lame/reservoir.c
        lame/mpglib_interface.c
        lame/machine.h
        lame/fft.h
        lame/set_get.c
        lame/quantize_pvt.h
        lame/psymodel.h
        lame/newmdct.c
        lame/id3tag.h
        lame/lame-analysis.h
        lame/id3tag.c
        lame/reservoir.h
        lame/lameerror.h
        lame/set_get.h
        lame/quantize.c
        lame/fft.c
        lame/l3side.h
        lame/newmdct.h
        lame/quantize.h
        lame/gain_analysis.c
        lame/encoder.c
        lame/lame.c
        lame/bitstream.c
        lame/quantize_pvt.c
        lame/presets.c
        lame/bitstream.h
        lame/encoder.h
        lame/gain_analysis.h
        lame/lame_global_flags.h
        lame/psymodel.c
        lame/lame.h
        lame/tables.c
        lame/tables.h
        lame/takehiro.c
        lame/util.c
        lame/util.h
        lame/vbrquantize.c
        lame/vbrquantize.h
        lame/VbrTag.c
        lame/VbrTag.h
        lame/version.c
        lame/version.h
)

target_link_libraries(
        ${CMAKE_PROJECT_NAME}
        android
        log
)

然后我們make project没卸,build完后以下文件會(huì)報(bào)錯(cuò)羹奉,這里列出解決的辦法:

util.h

在570行,如圖所示:

util_error.png

解決辦法:

extern float fast_log2(float x);

fft.c

在47行约计,如圖所示:

fft_error.png

解決辦法:刪除該行代碼诀拭。

set_get.h

在24行,如圖所示:

set_get_error.png

解決辦法:

#include "lame.h"

其他錯(cuò)誤

如圖所示:

lame_error.png

解決辦法:修改app模塊中的build.gradle.kts文件煤蚌,在android函數(shù)中增加如下代碼:

android {
    ...

    defaultConfig {
        ...
        externalNativeBuild {
            cmake {
                cFlags("-DSTDC_HEADERS")
            }
        }
    }

    ...
}

修改后耕挨,再次make project后編譯成功,CMake會(huì)幫我們自動(dòng)生成libaudiodemo.so文件尉桩,過(guò)程上面也提過(guò)了筒占,這里就不再贅述。

打印LAME庫(kù)的版本

新建MainActivity蜘犁,這里我使用Compose寫界面翰苫,并且在AndroidManifest.xml添加相關(guān)的代碼,AndroidManifest.xml的代碼我就不貼了这橙,詳細(xì)可查看該demo奏窑。調(diào)用getLameVersion函數(shù)就能獲取當(dāng)前LAME版本,要注意的是屈扎,需要調(diào)用System.loadLibrary("audiodemo")把生成的動(dòng)態(tài)庫(kù)加載進(jìn)來(lái)埃唯。代碼如下所示:

/**
 * Created by TanJiaJun on 2023/12/13.
 */
class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ContentView()
        }
    }

    @Composable
    private fun ContentView() {
        ConstraintLayout(modifier = Modifier.fillMaxSize()) {
            val (topBox, lameVersionText) = createRefs()
            Box(
                modifier = Modifier
                    .fillMaxWidth()
                    .height(45.dp)
                    .background(Purple80)
                    .constrainAs(topBox) {
                        start.linkTo(parent.start)
                        top.linkTo(parent.top)
                        end.linkTo(parent.end)
                    }
            ) {
                Text(
                    modifier = Modifier.align(Alignment.Center),
                    text = packageManager.getApplicationLabel(applicationInfo).toString(),
                    fontSize = 18.sp,
                    color = White
                )
            }
            Text(
                modifier = Modifier.constrainAs(lameVersionText) {
                    start.linkTo(parent.start)
                    top.linkTo(topBox.bottom)
                    end.linkTo(parent.end)
                    bottom.linkTo(parent.bottom)
                },
                text = getLameVersion(),
                fontSize = 16.sp,
                color = Black
            )
        }
    }

    /**
     * 獲取當(dāng)前LAME版本
     *
     * @return 當(dāng)前LAME版本
     */
    private external fun getLameVersion(): String

    private companion object {
        init {
            System.loadLibrary("androidaudiodemo")
        }
    }

}

運(yùn)行后,我們就可以看到界面有個(gè)居中的3.100文本鹰晨,這就是目前編譯的LAME版本筑凫,代表我們編譯LAME庫(kù)成功。

我的GitHub:TanJiaJunBeyond

Android通用框架:Android通用框架

我的掘金:譚嘉俊

我的簡(jiǎn)書:譚嘉俊

我的CSDN:譚嘉俊

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末并村,一起剝皮案震驚了整個(gè)濱河市巍实,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌哩牍,老刑警劉巖棚潦,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異膝昆,居然都是意外死亡丸边,警方通過(guò)查閱死者的電腦和手機(jī)叠必,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)妹窖,“玉大人纬朝,你說(shuō)我怎么就攤上這事〗竞簦” “怎么了共苛?”我有些...
    開(kāi)封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)蜓萄。 經(jīng)常有香客問(wèn)我隅茎,道長(zhǎng),這世上最難降的妖魔是什么嫉沽? 我笑而不...
    開(kāi)封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任辟犀,我火速辦了婚禮,結(jié)果婚禮上绸硕,老公的妹妹穿的比我還像新娘堂竟。我一直安慰自己,他們只是感情好玻佩,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布出嘹。 她就那樣靜靜地躺著,像睡著了一般夺蛇。 火紅的嫁衣襯著肌膚如雪疚漆。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天刁赦,我揣著相機(jī)與錄音娶聘,去河邊找鬼。 笑死甚脉,一個(gè)胖子當(dāng)著我的面吹牛丸升,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播牺氨,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼狡耻,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了猴凹?” 一聲冷哼從身側(cè)響起夷狰,我...
    開(kāi)封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎郊霎,沒(méi)想到半個(gè)月后沼头,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年进倍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了土至。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡猾昆,死狀恐怖陶因,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情垂蜗,我是刑警寧澤楷扬,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站么抗,受9級(jí)特大地震影響毅否,放射性物質(zhì)發(fā)生泄漏亚铁。R本人自食惡果不足惜蝇刀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望徘溢。 院中可真熱鬧吞琐,春花似錦、人聲如沸然爆。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)曾雕。三九已至奴烙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間剖张,已是汗流浹背切诀。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留搔弄,地道東北人幅虑。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像顾犹,于是被迫代替她去往敵國(guó)和親倒庵。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容