美文网首页音视频
音视频开发 - 交叉编译

音视频开发 - 交叉编译

作者: 文艺女青年的男人 | 来源:发表于2019-09-29 10:59 被阅读0次

    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添加进来。

    2-1

    在这个过程中可能会遇到一些问题:再执行完上面的命令之后,提示找不到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就好了


    2-2

    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文件夹结构


    2-3

    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();
    

    相关文章

      网友评论

        本文标题:音视频开发 - 交叉编译

        本文链接:https://www.haomeiwen.com/subject/cvfqyctx.html