MP3是一种广泛使用的音频压缩格式,以其高效的压缩算法和广泛的兼容性而闻名,是最为普及的音频格式之一,几乎所有的音频播放设备、移动设备、计算机和音频软件都支持 MP3 播放。这使得 MP3 成为事实上的标准格式,比起压缩性能兼容性才更是MP3 市场占有率的保障。
但是MP3是版权编码,一般手机厂商不包含MP3硬件编码器,只有MP3硬件解码器,市面上最常用的开源MP3软编码器是Lame,本文我们以Lame 为例,从跨平台编译到接入应用全流程基于Lame 事件MP3 软编码器。
编译Lame
一般三方开源的C/C++库有三种编译方式:
- cmake
- make
- configure
不同构建脚本要配置不同变量,lame 是基于Configure 构建脚本的,通过./Configure -h
查看配置参数。OpenHarmony 提供了一套交叉编译框架lycium,根据模版配置三方库信息后执行构建脚本即可。tpc_c_cplusplus 项目thirdparty中已经包含了 lame模块,我们直接编译即可。
进入lycium目录下执行:./build.sh lame
即可开始编译,编译完成后可以在lycium目录下看到user/lame
目录,里面已经编译好对应的动态库。
在macos arm版本电脑中编译报错,在tpc_c_cplusplus/thirdparty/lame/lame-3.100/armeabi-v7a-build/build.log
查看日志后:
1 error generated.
1 error generated.
1 error generated.
../../mpglib/dct64_i386.c:34:10: fatal error: 'config.h' file not found
#include <config.h>
^~~~~~~~~~
make[2]: *** [tabinit.lo] Error 1
make[2]: *** Waiting for unfinished jobs....
make[2]: *** [common.lo] Error 1
make[2]: *** [interface.lo] Error 1
make[2]: *** [decode_i386.lo] Error 1
make[2]: *** [layer1.lo] Error 1
1 error generated.
1 error generated.
make[2]: *** [layer2.lo] Error 1
make[2]: *** [dct64_i386.lo] Error 1
1 error generated.
make[2]: *** [layer3.lo] Error 1
make[1]: *** [all-recursive] Error 1
** [tabinit.lo] Error 1
make[2]: *** Waiting for unfinished jobs....
make[2]: *** [common.lo] Error 1
make[2]: *** [interface.lo] Error 1
make[2]: *** [decode_i386.lo] Error 1
make[2]: *** [layer1.lo] Error 1
1 error generated.
1 error generated.
make[2]: *** [layer2.lo] Error 1
make[2]: *** [dct64_i386.lo] Error 1
1 error generated.
make[2]: *** [layer3.lo] Error 1
make[1]: *** [all-recursive] Error 1
make: *** [all] Error 2
"build.log" 310L, 21047Bmake: *** [all] Error 2
生成config.h
头文件失败,lame是基于Configure的,依赖的一些工具版本不匹配导致,更换Intel电脑后编译成功:
接入HarmonyOS 工程
接下来将编译好的lame动态库以预编译的方式接入到我们的工程中。
首先创建native c++工程:
在cpp目录下新建third_party/lame文件夹,将编译好的so和导出的头文件拷贝到该路径下:
接下来修改CMakeLists.txt,导入预编译好的lame动态库,添加链接:
add_library(lame SHARED IMPORTED)
set_target_properties(lame
PROPERTIES
IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/third_party/lame/libs/${OHOS_ARCH}/libmp3lame.so)
add_library(audio_engine SHARED napi_init.cpp)
target_link_libraries(audio_engine PUBLIC libace_napi.z.so lame)
要使用lame的头文件,还需要配置头文件搜索路径:
include_directories(${NATIVERENDER_ROOT_PATH}
${NATIVERENDER_ROOT_PATH}/include
${NATIVERENDER_ROOT_PATH}/third_party/lame/include
)
接入到工程后就可以使用了。
录制MP3 音频文件
编码库接入完成后需要在C++侧封装接口供TS侧调用,这里我们提供三个最基础的接口:
- 创建编码器
- 对数据进行编码
- 关闭编码器
创建编码器
lame创建编码器后需要设置编码参数:
- 输入音频采样率
- 输入音频声道数
- 输出采样率
- 输出码率
- 输出质量
定义initLame方法,提供五个参数:
size_t argc = 5;
napi_value args[5] = {nullptr};
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
int inSamplerate;
napi_get_value_int32(env, args[0], &inSamplerate);
int inChannel;
napi_get_value_int32(env, args[1], &inChannel);
int outSamplerate;
napi_get_value_int32(env, args[2], &outSamplerate);
int outBitrate;
napi_get_value_int32(env, args[3], &outBitrate);
int quality;
napi_get_value_int32(env, args[4], &quality);
接下来配置编码器:
lame = lame_init();
lame_set_in_samplerate(lame, inSamplerate);
lame_set_num_channels(lame, inChannel);//输入流的声道
lame_set_out_samplerate(lame, outSamplerate);
lame_set_brate(lame, outBitrate);
lame_set_quality(lame, quality);
lame_init_params(lame);
编码音频数据
lame编码函数原型如下:
/*
* input pcm data, output (maybe) mp3 frames. * This routine handles all buffering, resampling and filtering for you. * * return code number of bytes output in mp3buf. Can be 0 * -1: mp3buf was too small * -2: malloc() problem * -3: lame_init_params() not called * -4: psycho acoustic problems * * The required mp3buf_size can be computed from num_samples, * samplerate and encoding rate, but here is a worst case estimate: * * mp3buf_size in bytes = 1.25*num_samples + 7200 * * I think a tighter bound could be: (mt, March 2000) * MPEG1: * num_samples*(bitrate/8)/samplerate + 4*1152*(bitrate/8)/samplerate + 512 * MPEG2: * num_samples*(bitrate/8)/samplerate + 4*576*(bitrate/8)/samplerate + 256 * * but test first if you use that! * * set mp3buf_size = 0 and LAME will not check if mp3buf_size is * large enough. * * NOTE: * if gfp->num_channels=2, but gfp->mode = 3 (mono), the L & R channels * will be averaged into the L channel before encoding only the L channel * This will overwrite the data in buffer_l[] and buffer_r[]. **/
int CDECL lame_encode_buffer (
lame_global_flags* gfp, /* global context handle */
const short int buffer_l [], /* PCM data for left channel */
const short int buffer_r [], /* PCM data for right channel */
const int nsamples, /* number of samples per channel */
unsigned char* mp3buf, /* pointer to encoded MP3 stream */
const int mp3buf_size ); /* number of valid octets in this
stream */
需要传入左声道,右声道数据,以及每个声道采样个数、输出编码后数据buffer、输出编码后数据buffer大小。
NAPI 接口需要传入三个buffer:
static napi_value NAPI_Global_encodeLame(napi_env env, napi_callback_info info)
{
size_t argc = 4;
napi_value args[4] = {nullptr};
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
napi_typedarray_type type;// 数据类型
napi_value left_input_buffer;
size_t byte_offset;//数据偏移
size_t length;//数据字节大小
napi_get_typedarray_info(env, args[0], &type, &length, NULL, &left_input_buffer, &byte_offset);
void* leftBuffer;
size_t leftLength;
napi_get_arraybuffer_info(env, left_input_buffer, &leftBuffer, &leftLength);
napi_value right_input_buffer;
napi_get_typedarray_info(env, args[1], &type, &length, NULL, &right_input_buffer, &byte_offset);
void* rightBuffer;
size_t rightLength;
napi_get_arraybuffer_info(env, right_input_buffer, &rightBuffer, &rightLength);
int samples;
napi_get_value_int32(env, args[2], &samples);
napi_value mp3_output_buffer;
napi_get_typedarray_info(env, args[3], &type, &length, NULL, &mp3_output_buffer, &byte_offset);
void* mp3Buffer;
size_t mp3Length;
napi_get_arraybuffer_info(env, mp3_output_buffer, &mp3Buffer, &mp3Length);
int result = lame_encode_buffer(lame, (short int*)leftBuffer, (short int*)rightBuffer,
samples, (unsigned char*)mp3Buffer, mp3Length);
napi_value result_value;
napi_create_int32(env, result,&result_value);
return result_value;
}
主要用到napi_get_typedarray_info和napi_get_arraybuffer_info方法:
- Native C++ 侧接受传入的 ArkTS Array,通过 napi_get_typedarray_info 将获取到的数据传入数组 typedarray 生成 input_buffer ,然后通过 napi_get_arraybuffer_info 获取数组数据。
- ArkTS 侧 接收 Native C++ 侧返回的 Array,通过 napi_create_arraybuffer 创建一个 arraybuffer 数组,根据创建的 arraybuffer 通过 napi_create_typedarray 创建一个 typedarray 并将 arraybuffer 存入 output_array,然后给 arraybuffer 赋值,最后返回 output_array。
关闭编码器
调用lame_close 关闭编码器,关闭前需要lame_encode_flush 方法将编码器中缓存的数据获取出来保证数据的完整性:
static napi_value NAPI_Global_flushLame(napi_env env, napi_callback_info info)
{
size_t argc = 1;
napi_value args[1] = {nullptr};
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
napi_typedarray_type type;// 数据类型
napi_value output_buffer;
size_t byte_offset;//数据偏移
size_t length;//数据字节大小
napi_get_typedarray_info(env, args[0], &type, &length, NULL, &output_buffer, &byte_offset);
void* outBuffer;
size_t outLength;
napi_get_arraybuffer_info(env, output_buffer, &outBuffer, &outLength);
int result = lame_encode_flush(lame, (unsigned char *)outBuffer, outLength);
napi_value result_value;
napi_create_int32(env, result,&result_value);
return result_value;
}
遇到问题
- 链接mp3lame库后调用native 方法报错:
Cannot read property encodeLame of undefined
,原因是编译出的mp3lame.so包含版本,直接去掉版本号拷贝到工程出现该问题,保留版本号的最高位后问题解决; - 线程问题,编码是耗时操作,需要在独立线程中处理,在C++侧创建线程和缓存来交互。
总结
Android、iOS手机由于版权问题没有直接提供MP3 硬件解码器,在录制MP3 时都是采用Lame三方库。本文介绍了HarmonyOS 下实现MP3 软件编码的全流程:从三方库编译到集成进工程封装调用,虽然HarmonyOS 提供了MP3 的硬件编码方式,但是本文以Lame 为例提供了集成三方C++ 库的最佳实践。
来源:HarmonyOS 技术领航者公众号每天分享一点鸿蒙知识,与您一同成长。同时它还是一个 HarmonyOS 开发助手,可以回答鸿蒙开发过程中遇到的各种问题。
版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。