本文首要解说的是音频根底概念、穿插编译原理和实践(LAME的穿插编译),是依据iOS渠道,示例代码如下所示:
别的,Android渠道也有相关的文章,如下所示:
音视频开发之旅——音频根底概念、穿插编译原理和实践(LAME的穿插编译)(Android)
音频根底概念
在进行音频开发的之前,了解声学的根底还是很有必要的。
声响的物理性质
在初中物理的时分学过,声响是由三要素组成:腔调、响度和音色。
腔调
声响的高低叫做腔调。物体振动得越快,宣布声响的腔调就越高;物体振动得越慢,宣布的腔调越低。频率(过零率,指信号的符号变化的比率)决议了腔调,频率越高,波长越短,声响更容易绕过障碍物,也便是能量衰减越小,反之得到相反的定论。
响度
声响的强弱叫做响度。咱们能够一般用分贝(dB)来描绘响度,分贝越大,声响响度越大,反之得到相反的定论。
音色
声响的质量叫做音色,它反映了每个物体宣布的声响特有的质量。例如在相同的腔谐和响度下,吉他和钢琴的声响听起来是不同的,也便是音色是不同的。波的形状决议声响的音色,吉他和钢琴音色不同便是由于它们介质发生的波形不同。
业界来说,人耳能够听到频率规模大约为20Hz~20kHz,对3kHz~4kHz频率规模内的声响比较灵敏,对于较低或许较高频率的声响,人耳的灵敏度会减弱;在分贝较低时,听觉的频率特性会很不均匀,反之就会较为均匀。一个频率规模较宽的音乐,最佳的分贝规模为80dB~90dB,超过90dB就会损害人耳,105dB是人耳的极限。
声响在不同的介质传达的速度也会不相同,在空气中的传达速度为340m/s,不过在真空是无法传达的。
有时分咱们在空旷的地方或许高山大喊的时分,会听到回声(echo),发生回声的原因是声响在传达的进程中遇到障碍物后反弹回来后再次让咱们听到,可是假如这两种声响传回到咱们耳朵的时差小于80毫秒的话,咱们就无法分辨这两种声响。
音频数字化
将声响模拟信号转化为数字信号的进程称之为音频数字化,这儿需求经过三个进程:采样、量化和编码。
采样
首要对模拟信号进行采样,采样是指在时间轴(横轴)对信号进行数字化,依据奎斯特定理(采样定理,咱们要按比声响最高音频高两倍以上的频率对声响进行采样,这个进程也称为AD转化。上面提过的人耳能够听到的频率为20Hz~20kHz,所以一般采样频率为44.1kHz,也便是说1秒会采样44100次。
量化
上面说到的,具体每个采样需求怎样处理呢?这就需求量化,量化是指在幅度轴(纵轴)上对信号进行数字化,要注意的是,和上面说到的采样形成平面直角坐标系,举个比如:用16bit的二进制信号表示这个声响的一个采样,16bit等于一个short,表示规模为[-32768, 32767],也便是说有65536个或许取值,所以在幅度上分为65536层。
编码
终究一步便是要将采样的数据进行存储,也便是是需求进行编码,编码便是依照必定的格局记录采样和量化后的数据数据,例如:次序存储和紧缩存储等等。常用的格局为音频的裸数据格局,也便是脉冲编码调制(Pulse Code Modulation,简称PCM)。描绘一段PCM的数据需求这几个概念:采样率(sampleRate)、量化格局(sampleFormat,也称为位深度)和声道数(channel)。比特率用于衡量音频数据单位时间内的容量巨细,也便是一秒时间内的比特数目,咱们以常见的CD格局和DVD-Audio格局为比如:
CD格局的采样率为44100Hz,量化格局为16bit(2byte),声道数为2,那么它的比特率为:
44100 * 16 * 2 = 1411200bps
转化可得1411200bps / 1024 = 1378.125Kibps
DVD-Audio格局的采样率为96000Hz,量化格局为24bit(3byte),声道数为6.那么它的比特率为:
96000 * 24 * 6 = 13824000bps
转化可得13824000bps / 1024 = 13500Kibps,再转化可得13500Kibps / 1024 ≈ 13.18Mibps
一般来说一首歌曲的时间大概在4分钟左右,那咱们算下CD格局和DVD-Audio格局会占用多大的存储空间,如下所示:
CD格局:1411200bps * 4 * 60 = 338688000b,转化可得338688000b / 8 / 1024 / 1024 ≈ 40.37MiB
DVD-Audio格局:13824000bps * 4 * 60 = 3317760000b,转化可得3317760000b / 8 / 1024 / 1024 = 395.51MiB
由数据可得,DVD-Audio格局一秒时间内的比特数目大于CD格局,因而它的音质会更好,当然所占的储存空间也会相应得大。
紧缩编码
由上面能够看到一首歌假如仅仅是已CD格局去存储的现已占用了40.37MiB,假如仅仅存储在存储设备上(例如:硬盘或许光盘)那还能够承受,可是假如在网络上实时在线传输的话,这样的巨细实在是太大了,所以咱们需求对其进行紧缩编码,紧缩编码里有个指标叫做紧缩比,紧缩比是小于1,紧缩比越小(越接近0),丢掉的信息就越多,反之得出相反的定论。紧缩算法有两种:无损紧缩和有损紧缩。无损紧缩是指解压后的数据能够恢复;有损紧缩是指解压后的数据不能够恢复,紧缩导致的丢掉得越多,还原的失真就越大。
有如下常用的紧缩编码格局:
WAV编码
WAV(Waveform Audio File Format)是微软专门为Windows开发的一种编码格局,它会在PCM数据格局的前面加上44字节,分别用来描绘该PCM数据的采样率、声道数、量化格局。
长处:音质非常好,有许多软件支撑。
缺陷:占用的存储空间较大。
适用场合:多媒体开发的中心文件、音乐和音效素材。
MP3编码
MP3(MPEG-1或许MPEG-2 Audio Layer III)是一种有损紧缩的编码格局,它经过放弃PCM数据人类听觉不重要的部分,已达到紧缩成较小文件的意图,对于大多数用户来说,它的音质和不紧缩的音频没有显着的下降。咱们常用LAME编码MP3文件,下面会解说到。
长处:音质在**高码率(≥128Kbit/s)**体现不错,一起紧缩比也比较高;有许多硬件和软件支撑,兼容性不错。
适用场合:高码率(≥128Kbit/s)的音频。而且需求比较好的兼容性。
AAC编码
AAC(Advanced Audio Coding,高档音频编码)是一种高紧缩比的编码格局,由于选用多声道和运用低复杂性的描绘方法,使其比简直一切的传统编码方法在同规格的情况下更胜一筹。现在衍生出LC-AAC、HE-AAC v1、HE-AAC v2三种首要的编码格局。LC-AAC是比较传统的AAC,首要编码中高码率(≥80Kbit/s)的音频;HE-AAC v1是高效AAC,是对AAC的扩展,它运用频段仿制(SBR)进步频域的紧缩功率,适用于中低码率(≤80Kbit/s);HE-AAC v2结合运用了**频段仿制(SBR)和参数立体声(PS)进步立体声信号的紧缩功率,进一步降低了对码率的需求(接近于50%),首要编码低码率(≤48Kbit/s)**的音质。大部分编码器都设置为≤48Kbit/s主动启用PS,>48Kbit/s就封闭PS,箱单与HE-AAC v1。
长处:音质在**中低码率(<128Kbit/s)**体现优异,多用于视频中音频轨的编码。
适用场合:中低码率(<128Kbit/s)的音频,多用于视频中音频轨的编码。
Ogg编码
Ogg在各种码率下都有优异的体现,尤其在中低码率的场景体现不错,一起它不收到软件专利的限制,彻底免费。Ogg有着非常出的的算法,能够用更小的码率编码出更好的音质,举个比如:128Kbit/s的Ogg音质乃至比192Kbit乃至更高的MP3还要好。
长处:能够用更小的码率编码出更好的音质,在各种码率下都变现优异。
缺陷:现在兼容性不够好,流媒体特性不支撑。
适用场合:语音聊天的音频消息。
iOS渠道添加C和C++支撑
比较于Android,iOS对C和C++支撑简略许多。假如咱们运用Objective-C开发iOS,由于Objective-C语法支撑混编,所以咱们只需求把引证C++的Objective-C类的后缀名改为.mm(Objective-C类的正常后缀名是.m),就能够和C++一同编译。下面介绍一些根底概念:
GNU、GCC、gcc、g++
-
GNU:它是一个彻底自由的操作体系,起源于GNU计划。
-
GCC:GNU Compiler Collection(GNU编译器套件)的缩写,它是一组GNU操作体系中的编译器调集,能够用于编译C、C++、Java、Go等语言。
-
gcc:GCC中的GNU C Compiler(C编译器)。
-
g++:GCC中的GNU C++ Compiler(C++编译器)。
对于.c文件和.cpp文件,gcc会分别当作c文件和cpp文件编译,而g++会一致当作cpp文件编译。
编译C/C++的四个进程
接下来咱们要了解一下运用gcc(GNU Compiler Collection,GNU编译器套件)生成可履行二进制文件的大概进程:
预处理(Preprocess)
预处理(Preprocess):预处理会处理一些编译前的准备工作,把一些#define的宏定义完成文本替换,然后将#include里的文件仿制到.cpp文件,假如.h文件里还有.h文件,那么就会递归展开,要注意的是,在这一步中,代码注释会被疏忽。经过g++ -E指令将.c文件预处理为.i文件,它是文本文件。
编译(Compile)
编译(Compile):编译是把代码转化成汇编代码,一起查看词法规则和语法规则,假如没有出现语法错误,那么不论逻辑是否错误都不会报错。经过g++ -S指令将.i文件转化为.s文件,它是文本文件。
汇编(Assemble)
汇编(Assemble):汇编是把汇编代码(.s文件)转化为机器码。经过g++ -c指令将.s文件转化为.o文件(方针文件),它是二进制格局。
链接(Link)
C/C++代码经过汇编后生成的.o文件(方针文件),它是二进制文件,可是它不是终究可履行的,需求和体系组件(例如:规范库、动态链接库)链接起来才能得到可履行的二进制文件(Executable File),完成这个进程的组件叫做链接器(Linker)。链接分为静态链接和动态链接,生成的文件叫做静态库和动态库。
静态库
静态库在Linux下为.a文件,在Windows下为.lib文件。之所以称之为静态库,是由于在链接阶段会将.o文件和引证到的库一同链接打包到可履行文件中,它有如下特色:
-
会在编译时期完成静态库对函数库的链接。
-
程序在运转的时分与函数无关,方便移植。
-
会浪费必定的空间和资源,由于一切方针文件和涉及到的函数库被链接合成一个可履行文件。
动态库
动态库在Linux下为.so文件,在Windows下为.dll文件。动态库在程序编译时不会被链接到方针文件,而是在程序运转时才被载入,它有如下特色:
-
把对一些库函数的链接载入推迟到程序运转的时期。
-
不同的应用程序假如调用相同的库,那么在内存中只需求有一份该同享库的实例,能够实现进程之间的资源同享,节省了空间。
-
由于动态库是在程序运转的时分才载入,因而处理了静态库对程序的更新、部署和发布带来的费事,只需求更新动态库就能够了,即增量更新。
-
开发者能够在程序代码中操控链接载入,即显示调用。
Make
Make其实是一个批量处理的东西,它是经过调用makefile文件中开发者指定的指令来进行编译链接,例如调用gcc或许其他编译器的指令。
本机编译
咱们要在PC上运转一个二进制的程序(要注意的是,是以源码的方法进行编译,而不是以包管理器的方法去装置)会经过如下进程:
-
得到这段程序的源代码,它能够是自己编写的源代码,也能够是从第三方开源网站上下载的源代码。
-
在PC上编译链接这些源代码生成可履行文件。
-
在终端(Terminal)下履行该可履行文件。
总结便是运用本机器的编译器和链接器,将源代码编译链接成一个能够在本机器运转的程序,这个编译进程叫做本机编译,它是正常的编译进程。
穿插编译
了解完本机编译后,穿插编译就好理解了,它便是一个渠道(例如:PC)上生成别的一个渠道(例如:Android、iOS、其他嵌入式设备)可履行的程序。这儿的编译机器是PC,所以编译器是装置在PC上,而且运转在PC上的,而这个编译器叫做穿插东西编译链。那其实为啥需求穿插编译呢?由于运转程序的方针渠道运算才能和存储才能都是有限的,虽然现在iOS和Android设备的性能越来越微弱,可是和PC还是有必定的间隔,而且ARM渠道下的编译东西和整个编译进程反常繁琐,所以PC是最佳挑选。现在大部分的嵌入式开发渠道都供给本身渠道穿插编译所需求运转在PC上的穿插东西编译链。
在一切的编译器中,包括自行装置在PC上的编译器和嵌入式渠道的穿插东西编译链,都包括以下这几个东西:
-
CC:编译器,作用是对C或许C++源文件编译成汇编文件。
-
AS:将汇编文件翻译成机器码,生成方针文件,汇编文件运用的是指令助记符。
-
AR:打包器,它能够从一个库添加或许删去方针代码模块。
-
LD:链接器,作用是为前面生成的方针代码分配地址空间,将多个方针文件链接成一个库或许可履行文件。
-
GDB:调试东西,它能够对正在运转的程序进行代码调试。
-
STRIP:消除终究生成的库文件或许可履行文件其间的源码。
-
NM:查看静态库文件中符号表。
-
Objdump:查看静态库或许动态库的方法签名。
由于在装置iOS的开发环境Xcode的时分,配套的编译器现已装置好了,所以咱们就不需求再独自下载穿插东西编译链。
LAME的穿插编译
咱们了解完穿插编译后,以LAME库为例进行实践。
先介绍一下LAME库,它是现在最优异也是最常用的MP3编码引擎。当码率达到320Kbit/s以上的时分,LAME编码出来的音频质量简直能够和CD音质媲美,而且还能保证其文件体积非常小,因而假如要在移动端编码MP3文件,运用LAME是唯一挑选。
下面来解说下,在iOS渠道下如何穿插编译LAME库,而且打印LAME版别。
下载LAME库并解压
然后在SourceForge下载最新版别的LAME库,现在为3.100,点击下面文本即可下载:
下载完成后,解压文件得到lame-3.100文件夹。
编写编译脚本
由于苹果在Xcode 14 Release Notes中声明构建iOS项目不再支撑armv7、armv7s和i386指令集。
Building iOS projects with deployment targets for the armv7, armv7s, and i386 architectures is no longer supported. (92831716)
所以咱们只需求编译arm64和x86_64指令集下的版别,其间x86_64的版别用于在模拟器上运转。下面来编写相关的脚本,相关的代码现已push到示例代码中,代码如下所示:
arm64指令集下的编译脚本
ios_lame_build_arm64.sh
./configure
--disable-shared
--disable-frontend
--host=arm-apple-darwin
--prefix="/Users/tanjiajun/lame-3.100/output/arm64"
CC="xcrun -sdk iphoneos clang -arch arm64"
CFLAGS="-arch arm64 -miphoneos-version-min=11.0"
LDFLAGS="-arch arm64 -miphoneos-version-min=11.0"
make clean
make -j8
make install
x86_64指令集下的编译脚本
ios_lame_build_x86_64.sh
./configure
--disable-shared
--disable-frontend
--host=x86_64-apple-darwin
--prefix="/Users/tanjiajun/lame-3.100/output/x86_64"
CC="xcrun -sdk iphonesimulator clang -arch x86_64"
CFLAGS="-arch x86_64 -mios-simulator-version-min=11.0"
LDFLAGS="-arch x86_64 -mios-simulator-version-min=11.0"
make clean
make -j8
make install
下面解说下这些指令和选项的含义:
configure是契合GNU规范的软件包发布所必备的指令,所以这儿经过configure的方法生成Makefile文件,然后运用make和make install编译和装置整个库。
-
make-clean:铲除上次make指令发生的.o文件和可履行文件。
-
make -j8:依据Makefile文件履行编译四个进程,也便是预处理、编译、汇编、链接,终究生成可履行文件。make -j能够带一个参数,用于进行并行编译,j8是指让make一起履行最多八个指令。
-
make install:将编译成功的可履行文件装置到体系目录中,一般为/usr/local/bin目录。
咱们能够运用configure -h指令查看configure的帮助文档,一起了解LAME的可选装备项。
-
–disable-shared:GNU规范中用于封闭动态链接库,一般是在编译出指令行东西的时分,期望指令行东西能够独自运用而不需求动态链接库的装备。
-
–disable-frontend:不编译出LAME的可履行文件。
-
–host:指定终究生成的库要运转的渠道,arm64指令集指定的是arm-apple-darwin,x86_64指令集指定的是x86_64-apple-darwin。
-
–prefix:指定终究生成的库要放在哪个目录下,一般来说,这个是GNU大部分库的规范装备。
-
CC:指定穿插东西编译链的途径,在这儿便是指定gcc的途径。
-
CFLAGS:指定编译时所带的参数。-arch用于指定终究生成的库运转的方针渠道,要注意的是,这个选项只在Darwin渠道有用,也便是只在Mac电脑有用,假如不是该渠道能够运用-march。别的,由于苹果在Xcode 14 Release Notes中声明弃用bitcode,在这之前是需求添加-fembed-bitcode选项,它用于打开bitcode,现在不需求了。一起指定了编译出来这个库所支撑的最低iOS版别为11.0。
-
LDFLAGS:指定链接进程中的参数。参数含义和上面所述的CFLAGS相同,这儿就不再赘述。
bitcode(苹果已弃用)
以前是把一切的指令集的源码编译好,然后悉数打包到一个APP,开启bitcode后,开发者提交APP到App Store的时分,Xcode会将程序编译为一个中心体现形式,只需求上传这个中心件(Intermediate Representation),而不是终究的可履行文件,从而削减二进制包的巨细;在用户下载APP之前,App Store会依据用户设备的指令集主动编译中心件,然后发生设备所需求的可履行文件供给给用户下载装置;以后假如有新指令集的CPU,就能够继续从这份bitcode编译出这个CPU上的可履行文件供给给用户下载装置。
Starting with Xcode 14, bitcode is no longer required for watchOS and tvOS applications, and the App Store no longer accepts bitcode submissions from Xcode 14.
Xcode no longer builds bitcode by default and generates a warning message if a project explicitly enables bitcode: “Building with bitcode is deprecated. Please update your project and/or target settings to disable bitcode.” The capability to build with bitcode will be removed in a future Xcode release. IPAs that contain bitcode will have the bitcode stripped before being submitted to the App Store. Debug symbols can only be downloaded from App Store Connect / TestFlight for existing bitcode submissions and are no longer available for submissions made with Xcode 14. (86118779)
脚本编写完成后,存储到lame-3.100文件夹下,然后经过下面这两条指令修正这两个脚本的文件权限为可履行权限,指令如下所示:
chmod 777 /Users/tanjiajun/lame-3.100/ios_lame_build_arm64.sh
chmod 777 /Users/tanjiajun/lame-3.100/ios_lame_build_x86_64.sh
履行结束后运转脚本,指令如下所示:
/Users/tanjiajun/lame-3.100/ios_lame_build_arm64.sh
/Users/tanjiajun/lame-3.100/ios_lame_build_x86_64.sh
履行结束后,能够在lame-3.100文件夹中的output文件下看到两个文件夹分别为arm64和x86_64,这两个文件夹下会看到include、lib和share文件夹,由于在装备的时分裁剪了可履行文件,所以不会发生bin文件夹,include文件夹为编译进程中所需求引证的lame.h头文件,lib文件夹为链接进程中所需求链接的libmp3lame.a静态库文件。
至此咱们编译出arm64和x86_64这两个指令集下的头文件和静态库文件。
兼并静态库
咱们需求将上面两个指令集下的静态库文件兼并到一个libmp3lame.a静态库文件中,能够经过如下指令实现:
lipo -create /Users/tanjiajun/lame-3.100/output/arm64/lib/libmp3lame.a /Users/tanjiajun/lame-3.100/output/x86_64/lib/libmp3lame.a -output libmp3lame.a
履行结束后就能够在output文件夹下看到libmp3lame.a静态库文件,然后咱们在该文件夹下履行如下指令:
file libmp3lame.a
履行结束后,假如看到以下信息,说明编译成功:
libmp3lame.a: Mach-O universal binary with 2 architectures: [x86_64:current ar archive random library] [arm64:current ar archive random library]
libmp3lame.a (for architecture x86_64): current ar archive random library
libmp3lame.a (for architecture arm64): current ar archive random library
Xcode准备工作
咱们在Xcode新建一个SwiftUI的项目,履行以下进程:
-
新建一个lame文件夹,导入上面生成的lame.h头文件和兼并后的libmp3lame.a静态库文件。
-
由于咱们需求用Swift调用C++文件,所以要创立桥接文件iOSAudioDemo-Bridging-Header.h,而且在工程中Build Settings中的Objective-C Bridging Header设置文件名为iOSAudioDemo-Bridging-Header.h。
引进lame.h头文件,代码如下所示:
//
// iOSAudioDemo-Bridging-Header.h
// iOSAudioDemo
//
// Created by 谭嘉俊 on 2024/3/7.
//
#ifndef iOSAudioDemo_Bridging_Header_h
#define iOSAudioDemo_Bridging_Header_h
#import "iOSAudioDemo/lame/lame.h"
#endif /* iOSAudioDemo_Bridging_Header_h */
打印LAME库的版别
咱们用SwiftUI写界面,代码如下所示:
//
// iOSAudioDemoApp.swift
// iOSAudioDemo
//
// Created by 谭嘉俊 on 2024/3/5.
//
import SwiftUI
@main
struct iOSAudioDemoApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
调用get_lame_version函数就能获取当时LAME版别,ContentView代码如下所示:
//
// ContentView.swift
// iOSAudioDemo
//
// Created by 谭嘉俊 on 2024/3/5.
//
import SwiftUI
struct ContentView: View {
// 调用get_lame_version函数就能获取当时LAME版别
let lameVersion: String = String(cString: get_lame_version())
var body: some View {
NavigationView {
Text(lameVersion)
.navigationTitle("iOSAudioDemo")
}
}
}
#Preview {
ContentView()
}
运转后,咱们就能够看到界面有个居中的3.100文本,这便是现在编译的LAME版别,代表咱们编译LAME库成功。
我的GitHub:TanJiaJunBeyond
Android通用框架:Android通用框架
我的:谭嘉俊
我的简书:谭嘉俊
我的CSDN:谭嘉俊