
之前在做公司的鸿蒙版App开发时,有一个录音文件转mp3格式功能。参考iOS和安卓的实现,鸿蒙这里也需要使用到一个三方的c++库lame,但是鸿蒙上同样是不能直接使用c++库,需要将c++库编译成so库,才能供ArkTS使用。接下来我们直接开始吧
1. 新建NDK编译项目
打开DevEco Studio,新建一个Native C++模版项目,如下图图中标记2位置
2. Native C++ 项目结构
项目创建完成后,先来了解一下项目的大致结构
上图红框中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库的信息配置文件




4. ets:存放的是我们的ArkTs源码文件,其中entryability目录下的EntryAbility.ets 就是我们应用程序的入口文件,管理着应用程序的生命周期;page是目录下的index.ets是项目默认帮我们创建的一个页面文件(UI)。


3. 编译三方c库lame
1. 添加c库源码
在编译之前,先把lame的源码添加进来,如下图

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库
-
点击菜单栏Build->Rebuild Project
dae1bd06-b432-49b7-9c4e-45cd6968853c.png -
等待编译完成
c46344dd-0cde-46f7-97b3-85f34792f05e.png -
查看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的版本号


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调用

代码如下:
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文件()
成功后我们在ets/pages/Index.ets文件写代码测试一下libentry.so文件中的getLameVersion接口是否可用

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



