1.交叉编译原理
先来看一下,如果要在PC上运行一个二进制程序(以源码的方式进行编译,不要以包管理工具的方式来安装),
需要怎样做。首先,要有这个二进制程序的源代码(有可能是直接下载的,也有可能是自己编写的代码),然后
在PC上进行编译链接生成可执行文件,最后在Terminal下面去执行该可执行文件。上述流程中包含了几个角色,
首先是要有源代码,然后是要知道最终运行该二进制程序的机器是哪一个(其实就是本机器),当然,其中最重
要的就是编译器和链接器了,对于C或者C++程序来讲,就是使用gcc和g++,而该编译器是需要预先安装在机器
上的。分析了这么多角色,总结成一句话就是:使用本机器的编译器,将源代码编译链接成为
一个可以在本机器。这就是正常的编译过程,也称为Native Compilation,中文译作本机编
译。
交叉编译
就是在一个平台(如PC)上生成另外一个平台(Android、iOS或者其他嵌
入式设备)的可执行代码。相较于正常编译,下面来看一下交叉编译的相应角色。首先,最终
程序运行的设备就是Android或者iOS设备,源代码就是从第三方开源网站上下载的源代码,编
译机器就是我们的PC,而编译器也必须要安装到该PC上。但是这里对编译器是有特殊需求
的,最终程序运行的系统必须要提供可运行在PC上的编译器,而该编译器就是大家常说的交
叉工具编译链。
编译器的组成:
它们都会提供以下几个工具:CC、AS、AR、LD、NM、GDB。
那么,这几个工具到底是做什么用的呢?下面就来逐一解释一下。
CC:编译器,对C源文件进行编译处理,生成汇编文件。
AS:将汇编文件生成目标文件(汇编文件使用的是指令助记符,AS将它翻译成机器码)。
AR:打包器,用于库操作,可以通过该工具从一个库中删除或者增加目标代码模块。
LD:链接器,为前面生成的目标代码分配地址空间,将多个目标文件链接成一个库或者是可执行文件。
GDB:调试工具,可以对运行过程中的程序进行代码调试工作。
STRIP:以最终生成的可执行文件或者库文件作为输入,然后消除掉其中的源码。
NM:查看静态库文件中的符号表。
Objdump:查看静态库或者动态库的方法签名。
ARM平台
苹果的iOS系统架构都基于ARM平台,但是随着时间的推移,平台也在不断地演进下面就依据iOS设备发布的时间线来逐个看一下。
armv6:iPhone、iPhone 2、iPhone 3G
armv7:iPhone 4、iPhone 4S
armv7s:iPhone 5、iPhone 5S
arm64:iPhone 5S、iPhone 6(P)、iPhone 6S(P)、iPhone7(P)机器对指令集的支持是向下兼容的,因此armv7的指令集是可以运行在iPhone 5S中的,只是效率没那么高而已。
讨论一下iOS项目文件中的一项配置,即Build Settings里面的Architectures选项。
Architectures指的是该App支持的指令集,一般情况下,在Xcode中新建一个项目,其默认的
Architectures选项值是Standard architectures(armv7、arm64),表示该App仅支持armv7和
arm64的指令集;
Valid architectures选项指即将编译的指令集,一般设置为armv7、armv7s、arm64,表示一般会编译这三个指令集;
Build ActiveArchitecture Only选项表示是否只编译当前适用的指令集,一般情况下在Debug的时候设置为YES,以便可以更加快速、高效地调试程序,而在Release的情况下设置为NO,以便App在各个机器上都能够以最高效率运行,因为Valid architectures选择的对应指令集是armv7、armv7s和arm64,在Release下会为各个指令集编译对应的代码,因此最后的ipa体积基本上翻了3倍。
2、LAME交叉编译(MP3编码引擎)
LAME是目前非常优秀的一种MP3编码引擎,在业界,转码成MP3格式的音频文件时,最常用
的编码器就是LAME库。当达到320Kbit/s以上时,LAME编码出来的音频质量几乎可以和CD的
音质相媲美,并且还能保证整个音频文件的体积非常小,因此若要在移动端平台上编码MP3文
件,使用LAME便成为唯一的选择。
在iOS上进行交叉编译时,其实在安装iOS的开发环境Xcode时,配套的编译器就已经安装好
了。开发iOS平台下的App就是这么方便,不需要再单独下载交叉工具编译链,接下来直接去
SourceForge下载最新的LAME版本,访问链接如下:
1、https://sourceforge.net/projects/lame/files/lame/3.99/
2、然后去下载外国大神的编译lame的shell脚本:https://github.com/kewlbear/lame-ios-build
3.将lame源码解压到一个文件夹里面,文件夹命名为lame
4.修改shell脚本(底下按需修改指令集)
5.在桌面生成一个文件夹X,将shell脚本和lame文件夹拖入此文件夹中
6.打开终端,输入指令
(1)cd 到文件夹X
(2)chmod 777 build-lame.sh
(3)sudo -s //提升到root权限,好像不用提升权限也可以,省掉(3)(4)
(4)输入系统密码
(5)./build-lame.sh
开始编译,编译完成之后。生成fat-lame目录和thin-lame目录,分别存放合并所有指令集的静态库,以及各指令集的静态库.
根据所需,copy lame.h和libmp3lame.a文件到project里,就可以正常使用了,如果发现Build Phases 的Compile Sources是否有lame.h,否则将.h添加进来。
在这个过程中可能会遇到一些问题:再执行完上面的命令之后,提示找不到xcodebuild
xcode-select: error: tool 'xcodebuild' requires Xcode, but active developer directory '/Library/Developer/CommandLineTools' is a command line tools instance
需要更新一下xcode的路径
解决方法:在终端输入命令
xcode-select -switch 新的xcode路径
例如:
$ sudo xcode-select --switch /Applications/Xcode\ 5.1.app/Contents/Developer/
3、FDK_AAC交叉编译(ACC音频编码)
FDK_AAC是用来编码和解码AAC格式音频文件的开源库,Android系统编码和解码AAC所用
的就是这个库。开发者Fraunhofer IIS是AAC音频规范的核心制定者(MP3时代Fraunhofer IIS
也是MP3规范的制定者)。AAC有很多种Profile,而FDK_AAC几乎支持大部分的Profile,并
且支持CBR和VBR这两种模式,在同等码率下FDK_AAC比NeroAAC以及faac和voaac的音
都要好一些。
1、下载地址:
https://sourceforge.net/p/opencore-amr/fdk-aac/ci/v0.1.4/tree/
2、下载完成后将其放到桌面一个新建的文件夹fdk-aac下,FDK_AAC的配置选项中要求比LAME多
配置一项AS参数,并且需要安装gas-preprocessor,首先进入下方链接:
https://github.com/applexiaohao/gas-preprocessor
下载gas-preprocessor.pl,然后复制到/usr/local/bin/目录下,修改/usr/local/bin/gas-preprocessor.pl的文件权限为可执行权限:
chmod 777 /usr/local/bin/gas-preprocessor.pl
这样gas-preprocessor.pl就安装成功了,
3、脚本下载路径:https://github.com/kewlbear/fdk-aac-build-script-for-iOS,将下载的build-fdk-aac.sh文件放到文件夹fdk-aac下,
4、终端执行:brew install automake libtool .
5、修改build-fdk-aac.sh 文件里:SOURCE="fdk-aac名称"和ARCHS="arm64 x86_64 i386 armv7 armv7s",打开文本编辑修改就行
6、终端cd 到文件夹fdk-aac,再执行./build-fdk-aac.sh就好了
7、再cd 到 fdk-aac-ios文件夹下的lib文件夹 执行:file libfdk-aac.a
出现:
libfdk-aac.a: Mach-O universal binary with 5 architectures: [i386:current ar archive] [arm64]
libfdk-aac.a (for architecture i386): current ar archive
libfdk-aac.a (for architecture armv7): current ar archive
libfdk-aac.a (for architecture armv7s): current ar archive
libfdk-aac.a (for architecture x86_64): current ar archive
libfdk-aac.a (for architecture arm64): current ar archive
就好了
4、X264交叉编译(视频编码)
X264是一个开源的H.264/MPEG-4 AVC视频编码函数库,是最好的有损视频编码器之一。一
般的输入是视频帧的YUV表示,输出是编码之后的H264的数据包,并且支持CBR、VBR模
式,可以在编码的过程中直接改变码率的设置,这在直播的场景中是非常实用的(直播场景下
利用该特点可以做码率自适应)。
1、获取X264源码,还是放到一个x264的文件夹中
http://www.videolan.org/developers/x264.html
2、下载脚本,放到x264文件夹中
https://github.com/tangyi1234/x264-iOS-build-script
3、由于已经下载了gas-preprocessor,所以不需要执行相应的下载步骤
4、打开终端进入cd到集成脚本目录,执行./build-x264.sh,从lib中查看.a文件的支持架构
libx264.a (for architecture armv7): current ar archive random library
libx264.a (for architecture armv7s): current ar archive random library
libx264.a (for architecture arm64): current ar archive random library
执行完之后的x264文件夹结构
5、脚本中可能涉及到的几个重要的字段
上面提到的三个脚本其实内部分为了很多部分,包括单独架构的.a文件生成、合并不同编译器架构的.a文件,其中在生成.a文件的时候用到了几个关键字段:
./configure \
--disable-shared \
--disable-frontend \
--host=arm-apple-darwin \
--prefix="./thin/armv7" \
CC="xcrun -sdk iphoneos clang -arch armv7" \
CFLAGS="-arch armv7 -fembed-bitcode -miphoneos-version-min=7.0" \
LDFLAGS="-arch armv7 -fembed-bitcode -miphoneos-version-min=7.0"
make clean
make -j8
make instal
configure是符合GNU标准的软件包发布所必备的命令,所以这里是通过configure的方式来生成Makefile文件,然后使用make和make install编译和安装整个库。可使用configure-h命令来查看一下configure的帮助文档,了解LAME的可选配置项,具体如下。
--prefix:指定将编译好的库放到哪个目录下,这是GNU大部分库的标准配置。
--host:指定最终库要运行的平台。
CC:指定交叉工具编译链的路径,其实这里就是指定gcc的路径。
CFLAGS:指定编译时所带的参数。Shell脚本中指定-march是armv7平台,代表编译的库运行的目标平台是armv7平台;另外Shell脚本中也指定了打开bitcode选项,这使得使用编译出来的这个库的工程,可以将enable-bitcode选项设置为YES,如果没有打开该选项,那么其在Xcode中只能设置为NO,而这对于最终App的运行性能会有一定的影响。Shell脚本中同时也指定了编译出来的这个库所支持的最低iOS版本是7.0,如果不配置该参数的话,则默认是iOS 9.0版本,而所使用的编译出来的这个库的工程,若所支持的最低iOS版本不是9.0的话,Xcode就会给出警告。
LDFLAGS:指定链接过程中的参数,同样也要带上bitcode的选项以及开发者期望App支持的
最低iOS版本的选项参数。
--disable-shared:通常是GNU标准中关闭动态链接库的选项,一般是在编译出命令行工具的时候,期望命令行工具可以单独使用而不需要动态链接库的配置。
--disable-frontend:不编译出LAME的可执行文件。
bitcode:表明当开发者提交应用(App)到App Store上的时候,Xcode会将程序编译为一个中间表现形式(bitcode)。App Store会将该bitcode中间表现形式的代码进行编译优化,链接为64位或者32位的程序。如果程序中用到了第三方静态库,则必须在编译第三方静态库的时候也开启bitcode,否则在Xcode的Build Setting中必须要关闭bitcode,这对于App来讲可能会造成性能的降低。
6、利用LAME进行pcm转MP3
1、创建Xcode项目WKLameDemo,并引入编译好的lame.h和libmp3lame.a
2、首先新建两个文件:mp3_encoder.h和mp3_encoder.cpp,当前的两个文件是创建c++文件时
将.hpp改为.h,先看一下头文件应该如何编写:
头文件其实就是用于定义该类对外提供的接口。这里提供的是一个Init接口,输入的是一个
PCM FilePath和一个MP3FilePath,会判定输入文件是否存在、初始化LAME以及初始化输出
文件的资源,返回值是该函数是否成功初始化了所有的相关资源,成功则返回true,否则返回
false。此外,还要再提供一个encode方法,负责读取PCM数据,并且调用LAME进行编码,
然后将编码之后的数据写入文件。最后再对外提供一个销毁资源的接口destroy方法,用于关
闭所有的资源。
#include "lame.h"
class Mp3Encoder {
private:
FILE* pcmFile;
FILE* mp3File;
lame_t lameClient;
public:
Mp3Encoder();
~Mp3Encoder();
int Init(const char* pcmFilePath, const char *mp3FilePath, int sampleRate, int channels, int bitRate);
void Encode();
void Destory();
};
接下来实现文件中进行具体实现:
#include "mp3_encoder.h"
//以读的方式”rb“打开pcm文件,以写的方式”wb“打开mp3文件,初始化Lame
int Mp3Encoder::Init(const char* pcmFilePath, const char *mp3FilePath, int sampleRate, int channels, int bitRate) {
int ret = -1;
pcmFile = fopen(pcmFilePath, "rb");
if(pcmFile) {
mp3File = fopen(mp3FilePath, "wb");
if(mp3File) {
lameClient = lame_init();
lame_set_in_samplerate(lameClient, sampleRate);
lame_set_out_samplerate(lameClient, sampleRate);
lame_set_num_channels(lameClient, channels);
lame_set_brate(lameClient, bitRate / 1000);
lame_init_params(lameClient);
ret = 0;
}
}
return ret;
}
//encode编码,主体是一个循环,每次都会读取一段bufferSize
void Mp3Encoder::Encode(){
//每次都会读取一段bufferSize大小
int bufferSize = 1024*256;
short* buffer = new short[bufferSize/2];
short* leftBuffer = new short[bufferSize/4];
short* rightBuffer = new short[bufferSize/4];
unsigned char* mp3_buffer = new unsigned char[bufferSize];
size_t readBufferSize = 0;
while ((readBufferSize = fread(buffer, 2, bufferSize/2, pcmFile)) > 0){
//将buffer的左右声道()分开
for (int i = 0; readBufferSize; i++) {
if (i%2==0) {
leftBuffer[i/2] = buffer[i];
}else{
rightBuffer[i/2] = buffer[i];
}
}
//送入到lame编码器
size_t wroteSize = lame_encode_buffer(lameClient, (short int *)leftBuffer, (short int *)rightBuffer, (int)(readBufferSize/2), mp3_buffer, bufferSize);
fwrite(mp3_buffer, 1, wroteSize, mp3File);
}
delete [] buffer;
delete [] leftBuffer;
delete [] rightBuffer;
delete [] mp3_buffer;
}
//销毁中间变量
void Mp3Encoder::Destory(){
if (pcmFile) {
fclose(pcmFile);
}
if (mp3File) {
fclose(mp3File);
lame_close(lameClient);
}
}
3、在ViewController中开始将pcm编码成mp3
ViewController要改成.mm支持c++,将vocal.pcm文件放入沙河,并开始执行encode任务,最终可以在电脑上导出真机中的沙河mp3文件进行播放。
[[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"vocal.pcm"];
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"vocal.mp3"];
Mp3Encoder* encoder = new Mp3Encoder;
encoder->Encode();