CMake交叉编译LAME

回顾平时的 C/C++ 程序开发,直接 gcc 编译出可执行文件,这就非常普通的编译过程,也称为本机编译。那么什么是交叉编译呢?交叉编译本质就是在一个平台(如PC、Mac)上生成另外一个平台 (Android、iOS 或者其他嵌入式设备)的可执行代码。本篇将以编译 LAME 这个 Mp3 编码库为例,编译出 Android 平台的可执行代码。

交叉编译的必要性

在一般的嵌入式系统开发中,运行程序的目标平台其存储空间和运算能力都是有限的,尽管现在的 iOS和Android设备拥有越来越强劲的计算能力,但是在这种嵌入式设备中进行本地编译是不太可能的,一则是因为计算能力的问题,还有一个重要的原因就是编译工具以及整个编译过程异常繁琐,所以在这种情况下,直接在ARM平台下进行本机编译几乎是不可能的。而具有更加强劲的计算能力与更大存储空间的PC才是理想的选择,所以大部分的嵌入式开发平台都提供了本身平台交叉编译所需要的交叉工具编译链,通过该交叉工具编译链,开发者就能在 PC上编译出可以运行在ARM平台下的程序了。

交叉编译工具链

无论是自行安装PC上的编译器,还是下载其他平台(Android或者 iOS)的交叉工具编译链,它们都会提供以下几个工具:CC、AS、 AR、LD、NM、GDB。

CC:编译器,对C源文件进行编译处理,生成汇编文件。 AS:将汇编文件生成目标文件(汇编文件使用的是指令助记符,AS将它翻译成机器码)。 AR:打包器,用于库操作,可以通过该工具从一个库中删除或者增加目标代码模块。 LD:链接器,为前面生成的目标代码分配地址空间,将多个目标文件链接成一个库或者是可执行文件。 GDB:调试工具,可以对运行过程中的程序进行代码调试工作。 STRIP:以最终生成的可执行文件或者库文件作为输入,然后消除掉其中的源码。 NM:查看静态库文件中的符号表。 Objdump:查看静态库或者动态库的方法签名。

LAME编译实践

下载好LAME的源码文件, https://jaist.dl.sourceforge.net/project/lame/lame/3.100/lame-3.100.tar.gz

接下来创建Shell脚本文件,指定好编译器里面的各个工具,然后把对应的Configure的命令与选项开关配置好,最后执行该Shell脚本即可,但是在这里不推荐这么做。

其实这个Shell的意图很简单,就是设置各种工具的路径,然后通过./configure生成MakeFile,再执行make就可以得到对应的so库了,但是逐条去设置交叉编译工具链是不是显得过于复杂呢?

现在更好的方式是直接使用AndroidStudio编译,直接把源码包中的include的lame.h,以及libmp3lame中的全部.c和.h文件,以及libmp3lame中的vector文件夹放在cpp下,此时容易出现的问题主要是:

1、util.h中extern ieee754_float32_t fast_log2(ieee754_float32_t x); ,因为Android不支持 ieee754_float32_t这种类型,所以直接用float替换即可:

对应的util.c实现函数也改成这种类型即可。

2、#include<>报错,因为不是系统头文件,所以直接改成#include ""即可,有些是因为相对路径问题。

接下来需要改动CMakeLists,其实就是把全部的c源文件添加进去了:

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.10.2)

# Declares and names the project.

project("lame")

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.
             native-lib

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             native-lib.cpp
        bitstream.c encoder.c fft.c gain_analysis.c
        id3tag.c lame.c mpglib_interface.c newmdct.c
        presets.c psymodel.c quantize.c quantize_pvt.c
        reservoir.c set_get.c tables.c takehiro.c util.c
        vbrquantize.c VbrTag.c version.c
        )

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
              log-lib

              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log )

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
                       native-lib

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )

现在对原本的native-lib.cpp稍作修改,可以尝试使用一下lame的函数

#include <jni.h>
#include <string>
#include "lame.h"

extern "C" JNIEXPORT jstring JNICALL
Java_com_tal_lame_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    // 返回LAME的版本信息
    return env->NewStringUTF(get_lame_version());
}

OK,现在还需要对app的build.gradle进行设置,然后直接在运行

......
android {
    ......
    defaultConfig {
        .......
        externalNativeBuild {
            cmake {
                cppFlags "-frtti -fexceptions"
                cFlags "-DSTDC_HEADERS"
            }
        }
    }
    externalNativeBuild {
        cmake {
            path file('src/main/cpp/CMakeLists.txt')
            version '3.10.2'
        }
    }
    ndkVersion '22.1.7171670'
}

dependencies {
		......
}

可以看到已经生成的SO库,并且也展示出了LAME 的版本,证明是OK的

注意,不过如果是编译出lame库给在其它地方使用,最好是删除我们自己添加的native-lib.cpp,只保留lame原本的代码文件即可。