NDK调用android系统库(基于android frameworks自己编写库)过程记录

  • Post author:
  • Post category:其他


此篇博文记录android NDK调用系统库、第三方库实践记录,由于笔者在android上实现内录音,

此apk是运行在云手机上的后台java程序,直接编译到云手机的镜像中、自动运行。由于android系统

的声卡权限问题,故通过C++基于android系统frameworks编写自己的录音库,此库可以通过NDK调用。

实现内录音功能。



第一部分: 在android源码中、增加自己的so库



1.1 在 external 文件夹中创建 audio_record 文件夹,

此文件夹是库的目录,文件基本结构如下。

robot@ubuntu:~/android_build/external/audio_record$ tree -L 2
|-- Android.bp              // 共享库编译规则
|-- ImxAudioRecord.cpp      // 源文件
|-- ImxAudioRecord.h        // 导出的头文件,供NDK工程调用
|-- DemoAudioRecord.cpp     //> 调试库源文件
|-- backup-android.mk       //> 调试库编译规则文件
|-- jni
`-- libs                    // NDK工程文件的依赖库
    |-- Android.bp          // 此文件是在android源码调用三方库规则
    `-- arm64-v8a -> /home/robot/Server/NDK/app/build/intermediates/cxx/Debug/6m4c2jr7/obj/arm64-v8a
                            // 此 libs 文件夹是 NDK工程依赖so库文件,此处做了软连接;



1.2 创建 Android.bp 编译文件,内容如下:

创建自定义库首先需要创建 Android.bp 编译脚本配置文件。内容如下

cc_library_shared {

    name: "libimxrecord",
    compile_multilib: "both",     //编译32与64库和兼容库
    //compile_multilib: "32",
    //compile_multilib: "64",
    
    include_dirs: ["frameworks/av/include"],
    shared_libs: [
        "liblog",
        "libutils",
        "libcutils",
        "libbinder",
        "libmedia",
        "libaudioutils",
        "libaudiomanager",
        "libaudioclient",
        "libmediametrics",
    ],
    export_include_dirs: [
        "jni",
    ],
    srcs: ["ImxAudioRecord.cpp"],
    cflags: [
        "-D_LIB",
        "-DMULTICORE",
        "-fPIC",

        "-DANDROID",
        "-Werror",
    ],
}



1.3 创建头文件与源文件

#ifndef IMX_AUDIO_RECORD_H_
#define IMX_AUDIO_RECORD_H_

#include<thread>
#include<queue>
#include <functional>

using funCB = std::function<void(void*,int)>;
class ImxAudioRecord {

public:
    ImxAudioRecord();
    ~ImxAudioRecord();

private:

    ImxAudioRecord * pImxAudioRecord = nullptr;
    std::thread mAudioRecordThread;

    bool g_bQuitAudioRecordThread = false;
    int bufferSizeInBytes = 1024*4;
    funCB m_CB;
    
    int Init();
    int start();
    void stop();
    void AudioRecordThread(void);
    std::thread CreateAudioRecordThread(){
        return std::thread(&ImxAudioRecord::AudioRecordThread,this);
    }

public:

    ImxAudioRecord* getInstance();
    void exitInstance();
    
    //> bind function
    void setCB(funCB CB) { m_CB = CB; }
    void runCB(void* b, int l){
        if(m_CB != nullptr)
            m_CB(b,l); 
    }
};
#endif //IMX_AUDIO_RECORD_H_

源文件在文章尾有gitee的链接地址,有需要的朋友可下载。



1.4 在android源码中的Product.mk文件中

添加对此库的依赖,否则编译整个镜像时,编译工具链不会编译此部分内容。

本例是在 @device/fsl/imx8q/ProductConfigCommon.mk 文件中,增加 libimxrecord.so 库依赖,内容如下

## 添加用户编写库文件至 system/lib 文件中。
PRODUCT_PACKAGES += \
    libimxrecord \

## 添加依赖库到 vendor/lib64 和 lib 文件夹
PRODUCT_COPY_FILES += \
    external/audio_record/libs/arm64-v8a/libmylib.so:$(TARGET_COPY_OUT_VENDOR)/lib/libmylib.so \
    external/audio_record/libs/arm64-v8a/libc++_shared.so:$(TARGET_COPY_OUT_VENDOR)/lib/libc++_shared.so \
    external/audio_record/libs/arm64-v8a/libscrcpy.so:$(TARGET_COPY_OUT_VENDOR)/lib/libscrcpy.so \
    external/audio_record/libs/arm64-v8a/libmylib.so:$(TARGET_COPY_OUT_VENDOR)/lib64/libmylib.so \
    external/audio_record/libs/arm64-v8a/libc++_shared.so:$(TARGET_COPY_OUT_VENDOR)/lib64/libc++_shared.so \
    external/audio_record/libs/arm64-v8a/libscrcpy.so:$(TARGET_COPY_OUT_VENDOR)/lib64/libscrcpy.so \

其中 PRODUCT_COPY_FILES 部分内容是 NDK工程依赖的几个so库内容,此配置是让android编译链把此内容拷贝至

系统文件的 vendor/lib 和 lib64 文件夹中,apk运行调用库时、就可以成功加载相关库文件。



1.5 在 android 源码中编译与调试

进入 external/audio_record/ 目录后,使用 mm 对此模块进行编译,生成的so文件内容和路径如下:

robot@ubuntu:~/android_build/out/target/product/mek_8q/obj/SHARED_LIBRARIES/libimxrecord_intermediates$ tree -L 1  
.
|-- ImxAudioRecord.d
|-- ImxAudioRecord.o
|-- LINKED
|-- PACKED
|-- export_includes
|-- import_includes
|-- libimxrecord.so         //> 此库是兼容库
|-- libimxrecord.so.32
|-- libimxrecord.so.64
|-- libimxrecord.so.debug
|-- libimxrecord.so.dynsyms
|-- libimxrecord.so.funcsyms
|-- libimxrecord.so.keep_symbols
|-- libimxrecord.so.mini_debuginfo.xz
|-- libimxrecord.so.toc
`-- link_type

如何调试so库呢,如果每次都把库push到手机中、在用代码调用库,调试有点麻烦,通过在mk文件使用 include $(BUILD_EXECUTABLE) 方式,

生成可执行文件,把可执行文件push到手机中,直接运行可执行文件,验证库逻辑代码运行正确,在使用 Android.bp 配置文件生成库文件。

至此,我就把在android源码中添加自己编写库的过程介绍清晰了,库做好后、我们怎么用呢?



第二部分: NDK自定义的so库

采用NDK制作库是有限制的,android内核很多功能是无法直接使用,所以上面介绍在android

源码中指针库的过程,此部分简单介绍如何使用NDK制作自己库文件。



2.1 创建库文件夹和文件

创建 user_cpp_lib 文件夹和相关内容,库编译后完整文件夹内容如下

robot@ubuntu:~/user_cpp_lib$ tree -L 2
|-- jni
|   |-- Android.mk           //> 库编译规则文件
|   |-- Application.mk       //> 通过变量APP_STL变量指定运libc++_shared
|   |-- MyLib.cpp
|   |-- MyLib.h
|   `-- jniLibs
|-- libs
|   |-- arm64-v8a
|   |-- armeabi-v7a
|   |-- x86
|   `-- x86_64
|-- obj
|   `-- local
`-- readme.md



2.2 创建头文件和源文件

头文件

user_cpp_lib/jni$ cat MyLib.h 
#ifndef TESTNDK_MYLIB_H
#define TESTNDK_MYLIB_H
#include <string>
#include <stdlib.h>
class MyLib {
    public:
    MyLib();
    ~MyLib();

    int add(int a,int b);
};

源文件

user_cpp_lib/jni$ cat MyLib.cpp 
#include "MyLib.h"

MyLib::MyLib()
{}
MyLib::~MyLib()
{}

int MyLib::add(int a,int b)
{
    return a+b;
}



2.3 创建编译规则文件mk

Application.mk 文件夹

user_cpp_lib/jni$ cat Application.mk 
APP_STL:=c++_shared             #指定库版本
APP_PLATFORM := android-16
APP_CPPFLAGS:=-frtti -fexceptions
APP_ABI := all

Android.mk 文件

user_cpp_lib/jni$ cat Android.mk 
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_C_INCLUDES:= \
~/android_build/frameworks/av/include \
~/android_build/development/ndk/platforms/android-21/include
#LOCAL_SHARED_LIBRARIES:= liblog libmedia libutils libcutils
LOCAL_SRC_FILES := \
                   MyLib.cpp
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE    := mylib
LOCAL_CFLAGS += -g 

NDK_APP_DST_DIR := ./jniLibs/$(TARGET_ARCH_ABI)  #生成库路径
include $(BUILD_SHARED_LIBRARY)                  #生成共享库
#include $(BUILD_EXECUTABLE)



2.4 编译自定义库

user_cpp_lib$ export ANDROID_NDK_ROOT=~/Android/Sdk/ndk/21.4.7075529    # 导出NDK工具版本
user_cpp_lib$ ${ANDROID_NDK_ROOT}/ndk-build                             #编译库
[arm64-v8a] Install        : libmylib.so => jniLibs/arm64-v8a/libmylib.so
[arm64-v8a] Install        : libc++_shared.so => libs/arm64-v8a/libc++_shared.so
[x86_64] Install        : libmylib.so => jniLibs/x86_64/libmylib.so
[x86_64] Install        : libc++_shared.so => libs/x86_64/libc++_shared.so
[armeabi-v7a] Install        : libmylib.so => jniLibs/armeabi-v7a/libmylib.so
[armeabi-v7a] Install        : libc++_shared.so => libs/armeabi-v7a/libc++_shared.so
[x86] Install        : libmylib.so => jniLibs/x86/libmylib.so
[x86] Install        : libc++_shared.so => libs/x86/libc++_shared.so

此部分介绍基于NDK编译私有库方法;接下来介绍如何在android studio的NDK工程中,如何使用私有库。



第三部分: 通过 android studio 创建 ndk 工程

使用android studio 创建ndk工程,不是本篇重点,故省略具体过程;假定工程创建完成且可以编译运行,如何在工程中引用私有库呢?



3.1 在CMakeList.txt文件中增加如下内容

cmake_minimum_required(VERSION 3.18.1)
project("scrcpy")
add_library( 
        scrcpy
        SHARED
        native-lib.cpp
        com_metis_scrcpy_NativeHelper.cpp
        com_metis_scrcpy_AAudioRecord.cpp
        )

find_library( 
        log-lib
        log)

add_library(
        mylib        # ndk 编译私有库
        SHARED
        IMPORTED)
set_target_properties(
        mylib
        PROPERTIES IMPORTED_LOCATION
        ${PROJECT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}/libmylib.so
)

add_library(
        imxrecord   # android系统中编译私有库
        SHARED
        IMPORTED)

set_target_properties(
        imxrecord
        PROPERTIES IMPORTED_LOCATION
        ${PROJECT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}/libimxrecord.so #把库文件拷贝至jniLibs/相关文件夹中
)

include_directories(src/main/cpp/include)  # 把两个库的头文件拷贝至include文件夹中。

target_link_libraries( # Specifies the target library.
        scrcpy
        mylib        # 链接
        imxrecord
        ${log-lib})

android系统生成的32和64位版本库文件,分别把对应so文件拷贝到 android studio ndk工程中的jniLibs文件夹下对应arm64-v8a和armeabi-v7a 的文件

夹中,把头文件拷贝到工程的 cpp\include 文件夹下,ndk工程就能够调用你在android源码中编写的so文件。

笔者在实际使用时,使用的兼容库libimxrecord.so,拷贝至arm64-v8a和armeabi-v7a的文件夹。



3.2 在build.gradle中增加相关内容

defaultConfig {
        applicationId "com.metis.scrcpy"
        minSdk 24
        targetSdk 31
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        //> 增加共享库
        externalNativeBuild {
            cmake {
                cppFlags ""
                arguments "-DANDROID_STL=c++_shared"
            }
        }
        //> 指定引用库类型 exclude 'armeabi-v7a','armeabi'
        ndk{
            abiFilters 'arm64-v8a'
        }
    }
    //> 设置库文件
    sourceSets{
        main{
            jniLibs.srcDirs = ['libs']
        }
    }

笔者android studio 是2021.2版本的,因版本不同配置可能有差异,请读者针对具体版本调整。

在 native-lib.cpp程序中包含库文件的头文件后,就可以直接使用啦。

#include <jni.h>
#include <string>
#include "include/MyLib.h"
#include "include/ImxAudioRecord.h"

extern "C" JNIEXPORT jstring JNICALL
Java_com_metis_scrcpy_Server_stringFromJNI(
        JNIEnv* env,
        jclass clazz) {
    //std::string hello = "Hello from C++";
    //return env->NewStringUTF(hello.c_str());
    MyLib myLib;
    int res = myLib.add(10,10);
    ImxAudioRecord mAudioRecord;
    mAudioRecord.getInstance();
    return env->NewStringUTF(std::to_string(res).c_str());
}

extern "C" JNIEXPORT jint JNICALL
Java_com_metis_scrcpy_Server_intFromJNI(JNIEnv *env, jclass clazz) {
    // TODO: implement intFromJNI()
    jint nativeInt = 20;
    return nativeInt;
}

因笔者的apk不能用android界面相关内容,是通过 app_process 方式直接运行的java程序,

需要对android studio生成工程文件做如下修改。



3.3 修改工程的 manifests 文件

<manifest package="com.metis.scrcpy">
</manifest>

仅是一个java包。



3.4 删除 MainActivity.java文件

删除工程自动创建的mainActivity文件,新建 Server.java文件内容如下。

package com.metis.scrcpy;

public final class Server {
    static {
        try {
            System.loadLibrary("scrcpy");
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    public static void main(String... args) throws Exception {
        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                e.printStackTrace();
            }
        });

        System.out.println("main startup ...");
        String ss = NativeHelper.getAudioName();    //> 此处是调用 NDK编译私有库
        System.out.println("hello world: " + ss);
        int id = NativeHelper.getAudioSource();     //> 此处调用是系统环境下的私有库
        System.out.println("AudioSource id: "+id);
    }
    //>
    public static native String stringFromJNI();
}

NativeHelper类是把库调用做了封装,如此修改是把android兼容application工程,修改为java通用包,通过app_process直接启动运行。



第四步: 部署并测试库

步骤如下



4.1 编译android镜像并烧写至目标板

在 external/audio_record 文件夹中引用库、并把库打包至android镜像中;



4.2 编译工程并生成apk包

NDK$ ls app/release/
app-release.apk  output-metadata.json



4.3 app_process 运行apk

通过adb push 文件到 /data/local/tmp 文件夹下,adb shell 进入console后,如下方法运行apk

hikay960pro:/data/local/tmp # 
CLASSPATH=/data/local/tmp/app-release.apk app_process / com.metis.scrcpy.Server



总结与补充说明

各位读者看官朋友们,此工程 Demo 我放到 gitee.com 上,有需求朋友请自行下载参考。

最后麻烦读者朋友们如果本篇对你有帮助,请关注和点赞一下,当然如果有错误和不足的地方也可以拍砖。

补充说明:

把NDK工程中生成的库,软链接至 audio_record/libs/arm64-v8a 中、每次编译android镜像时自动打包库至系统镜像。

robot@ubuntu:~/android_build/external/audio_record/libs$ ls -l
total 8
-rw-rw-r-- 1 robot robot 241 May 24 19:12 Android.bp
lrwxrwxrwx 1 robot robot  92 May 24 18:49 arm64-v8a -> /home/robot/Server/NDK/app/build/intermediates/cxx/Debug/6m4c2jr7/obj/arm64-v8a



版权声明:本文为weixin_38387929原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。