鸿蒙上使用NDK编译C/C++库 lame

666.png

之前在做公司的鸿蒙版App开发时,有一个录音文件转mp3格式功能。参考iOS和安卓的实现,鸿蒙这里也需要使用到一个三方的c++库lame,但是鸿蒙上同样是不能直接使用c++库,需要将c++库编译成so库,才能供ArkTS使用。接下来我们直接开始吧

1. 新建NDK编译项目

打开DevEco Studio,新建一个Native C++模版项目,如下图图中标记2位置


1280X1280.PNG

2. Native C++ 项目结构

项目创建完成后,先来了解一下项目的大致结构


1280X1280 (1).PNG

上图红框中entry目录是我们主要操作和关注的地方

1. src:代码、资源文件、配置文件存放的目录
2. main:主要是代码和资源文件存放的目录
3. cpp:这里存放C++相关文件:

CMakeLists.txt 是CMake的配置文件;
napi_init.cpp 是C++源文件(其中使用了napi),这里我们还可以添加c的源码文件,比如lame库;
types/xxx 目录下存放的将要编译的so文件的配置,其中index.d.ts是声明so库的Api,供ArkTs调用,oh-package.json5是so库的信息配置文件


a9cfffff-1376-4d99-8a44-bdb9424a68df.png

c027cf3c-684f-4d9a-bac8-18f480d56d0c.png

bea75775-c33f-4ef4-b357-b30738d7fb87.png

83c09215-d30f-4aa7-9163-d5e2373e128b.png
4. ets:存放的是我们的ArkTs源码文件,其中entryability目录下的EntryAbility.ets 就是我们应用程序的入口文件,管理着应用程序的生命周期;page是目录下的index.ets是项目默认帮我们创建的一个页面文件(UI)。
c1fbff94-b191-4513-846d-71b0e0db0562.png

f689d93f-44e2-4702-be2d-72ba9c60a0b4.png

3. 编译三方c库lame

1. 添加c库源码

在编译之前,先把lame的源码添加进来,如下图


c24cc4b1-7212-4598-97f2-1a7833ff6d41.png
2. 修改CMakeLists文件
# the minimum version of CMake.
cmake_minimum_required(VERSION 3.4.1)
project(NDKCompileCppDem)

## NATIVERENDER_ROOT_PATH 为cmakelists.txt文件所在的目录
set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})

if(DEFINED PACKAGE_FIND_FILE)
    include(${PACKAGE_FIND_FILE})
endif()

## 头文件
include_directories(${NATIVERENDER_ROOT_PATH}
                    ${NATIVERENDER_ROOT_PATH}/include)

## 当前../lame目录的所有.c .cpp源文件
AUX_SOURCE_DIRECTORY(${NATIVERENDER_ROOT_PATH}/lame SRC_LIST)

## 生成动态共享库
add_library(
        # 设置编译成so库的名称
        entry
        # 生成动态库或共享库,此处如果SHARED改为STATIC,其含义是生成静态库
        SHARED
        # c/c++ 源码文件
        napi_init.cpp
        ${SRC_LIST}
        )
target_link_libraries(entry PUBLIC libace_napi.z.so)
3. 编译so库
  1. 点击菜单栏Build->Rebuild Project


    dae1bd06-b432-49b7-9c4e-45cd6968853c.png
  2. 等待编译完成


    c46344dd-0cde-46f7-97b3-85f34792f05e.png
  3. 查看entry/build/default/intermediates/cmake/default/obj/arm64-v8a/ 目录下libentry.so文件是否存在,存在则说明编译so文件成功4.


    afe4a670-e1f7-494d-9159-21fdd522327f.png
4. 测试libentry.so中lame接口是否可用

在napi_init.cpp文件中编写代码,使用lame中的c函数const char* CDECL get_lame_version ( void ); 获取lame的版本号

eda96ee3-e8ce-4d81-b3ed-4b1b36ed3cb6.png

30f63f91-5d61-4c0a-9a77-d53ea75c921f.png

napi_init.cp整体代码如下:

#include "napi/native_api.h"
#include "lame/lame.h"

static napi_value GetLameVersion(napi_env env, napi_callback_info info) 
{
    const char *version = get_lame_version();
    napi_value v;
    napi_create_string_utf8(env, version, NAPI_AUTO_LENGTH, &v);
    return v;
}

static napi_value ConvertPcm2Mp3(napi_env env, napi_callback_info info) 
{
    size_t requireArgc = 5;
    size_t argc = 5;
    napi_value args[5] = {nullptr};

    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    napi_valuetype valuetype0;
    napi_typeof(env, args[0], &valuetype0);

    napi_valuetype valuetype1;
    napi_typeof(env, args[1], &valuetype1);

    napi_valuetype valuetype2;
    napi_typeof(env, args[2], &valuetype2);

    napi_valuetype valuetype3;
    napi_typeof(env, args[3], &valuetype3);

    napi_valuetype valuetype4;
    napi_typeof(env, args[4], &valuetype4);
    
    /// /data/storage/el2/base/haps/entry/files/test.wav
    char pcmFilePath[64] = {0};
    size_t r0 = 0;
    napi_get_value_string_utf8(env, args[0], pcmFilePath, sizeof(pcmFilePath), &r0);
    
    printf("c pcmFilePath = %s", pcmFilePath);

    char mp3FilePath[64] = {0};
    size_t r1;
    napi_get_value_string_utf8(env, args[1], mp3FilePath, sizeof(mp3FilePath), &r1);

    int64_t sampleRate;
    napi_get_value_int64(env, args[2], &sampleRate);

    int64_t channels;
    napi_get_value_int64(env, args[3], &channels);

    int64_t bitRate;
    napi_get_value_int64(env, args[4], &bitRate);
    
    bool ret = false;
    FILE *pcmFile = fopen(pcmFilePath, "rb");
    if (pcmFile) {
        FILE *mp3File = fopen(mp3FilePath, "wb");
        if (mp3File) {
            lame_t lameClient = lame_init();
            // in 采样率
            lame_set_in_samplerate(lameClient, sampleRate);
            // out 采样率
            lame_set_out_samplerate(lameClient, sampleRate);
            lame_set_num_channels(lameClient, channels);
            lame_set_brate(lameClient, bitRate / 1000);
            lame_init_params(lameClient);

            /// 初始化完成
            /// 开始转码
            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) {
                for (int i = 0; i < readBufferSize; i++) {
                    if (i % 2 == 0) {
                        leftBuffer[i / 2] = buffer[i];
                    } else {
                        rightBuffer[i / 2] = buffer[i];
                    }
                }
                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;

            if (pcmFile) {
                fclose(pcmFile);
            }
            if (mp3File) {
                fclose(mp3File);
            }
            if (lameClient) {
                lame_close(lameClient);
            }
            ret = true;
        } else {
            fclose(pcmFile);
        }
    }
    napi_value r;
    napi_get_boolean(env, ret, &r);
    return r;
}


static napi_value Add(napi_env env, napi_callback_info info)
{
    size_t requireArgc = 2;
    size_t argc = 2;
    napi_value args[2] = {nullptr};

    napi_get_cb_info(env, info, &argc, args , nullptr, nullptr);

    napi_valuetype valuetype0;
    napi_typeof(env, args[0], &valuetype0);

    napi_valuetype valuetype1;
    napi_typeof(env, args[1], &valuetype1);

    double value0;
    napi_get_value_double(env, args[0], &value0);

    double value1;
    napi_get_value_double(env, args[1], &value1);

    napi_value sum;
    napi_create_double(env, value0 + value1, &sum);

    return sum;

}

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
    napi_property_descriptor desc[] = {
        { "add", nullptr, Add, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "getLameVersion", nullptr, GetLameVersion, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "convertPcm2Mp3", nullptr, ConvertPcm2Mp3, nullptr, nullptr, nullptr, napi_default, nullptr }
    };
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
}
EXTERN_C_END


static napi_module demoModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "entry",
    .nm_priv = ((void*)0),
    .reserved = { 0 },
};

extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
{
    napi_module_register(&demoModule);
}

在types/libentry/index.d.ts文件中声明getLameVersion接口,供ArkTs调用


73965209-28ed-4f11-931b-f8dce6d1723d.png

代码如下:

export const add: (a: number, b: number) => number;
/// 获取lame的版本
export const getLameVersion: () => string;
/// 转换pcm格式文件到mp3格式
export const convertPcm2Mp3: (pcmFilePath: string, mp3FilePath: string, sampleRate: number, channel: number, bitRate: number) => boolean;

执行 Rebuild Project 再次编译一下so文件(\color{red}{注意:这里一定要再次Build,否则napi_init中新写的代码不会打到libentry.so库中}
成功后我们在ets/pages/Index.ets文件写代码测试一下libentry.so文件中的getLameVersion接口是否可用

b96ad56f-f1c4-450d-82f2-7f4f4b3c9be0.png

Index.ets代码如下:

import { hilog } from '@kit.PerformanceAnalysisKit';
import testNapi from 'libentry.so';

@Entry
@Component
struct Index {
  @State message: string = 'Hello World';

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(30)
          .fontWeight(FontWeight.Bold)
          .onClick(() => {
            hilog.info(0x0000, 'testTag', 'Test NAPI 2 + 3 = %{public}d', testNapi.add(2, 3));
          })
        Button('获取lame版本号').onClick(() => {
          /// 点击事件处理
          /// 测试getLameVersion接口
          let lame_version: string = testNapi.getLameVersion()
          console.debug(`lame_version = ${lame_version}`)
          /// 将获取到的version显示在Text文本中
          this.message = "lame_version = \n" + lame_version
        })
      }
      .width('100%')
    }
    .height('100%')
  }
}

如果不出意外的话,这里应该顺利拿到测试结果:lame_version = 3.100


600beeca-5b2d-4a8e-b160-79813e2fc490.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容