本文章已授權(quán)微信公眾號(hào)郭霖(guolin_blog)轉(zhuǎn)載片效。
本文主要講解的是音頻基礎(chǔ)概念红伦、交叉編譯原理和實(shí)踐(LAME的交叉編譯),是基于Android平臺(tái)淀衣,示例代碼如下所示:
另外昙读,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 v1、HE-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ò)如下步驟:
得到這段程序的源代碼氓扛,它可以是自己編寫的源代碼,也可以是從第三方開(kāi)源網(wǎng)站上下載的源代碼论笔。
在PC上編譯鏈接這些源代碼生成可執(zhí)行文件采郎。
在終端(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文件夾脓杉,然后找到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行,如圖所示:
解決辦法:
extern float fast_log2(float x);
fft.c
在47行约计,如圖所示:
解決辦法:刪除該行代碼诀拭。
set_get.h
在24行,如圖所示:
解決辦法:
#include "lame.h"
其他錯(cuò)誤
如圖所示:
解決辦法:修改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:譚嘉俊