
OpenHarmony适配TTS\ASR遇到的问题(如何编译神经网络运行时)
零、前言 前面两篇文章只介绍了如何使用项目,也说了项目依赖sherpa_onnx.har,但并没有说明har是如何来的如何适配的。本篇文章主要解决这个问题。 前面两篇文章: 基于OpenHarmony5.0.0\4.1的开发者手机TTS实现方案_会飞的企鹅12138-Laval社区 基于OpenHarmony5.0.0\4.1的开发者手机ASR实现方案_会
零、前言
前面两篇文章只介绍了如何使用项目,也说了项目依赖sherpa_onnx.har,但并没有说明har是如何来的如何适配的。本篇文章主要解决这个问题。
前面两篇文章:
基于OpenHarmony5.0.0\4.1的开发者手机TTS实现方案_会飞的企鹅12138-Laval社区
基于OpenHarmony5.0.0\4.1的开发者手机ASR实现方案_会飞的企鹅12138-Laval社区
获取方式一:
其实原作者已经将sherpa_onnx.har上架到了ohpm三方中心仓,可以直接在项目中导入使用https://ohpm.openharmony.cn/#/cn/detail/sherpa\_onnx ,但是仅支持OH5.0。所以有了获取方式二。
获取方式二:
下载源码自行编译,har包项目地址https://github.com/k2-fsa/sherpa-onnx/tree/master/harmony-os/SherpaOnnxHar 。如何编译出har包是本篇文章接下来讨论的内容。
一、前期准备
开发环境:DevEco Studio 5.0.0 Release(构建版本:5.0.3.910)
SDK版本:4.1.9.2 Release full sdk
har包项目地址:https://github.com/k2-fsa/sherpa-onnx/tree/master/harmony-os/SherpaOnnxHar
- 目标是构建出产物har包,不是直接把har运行到设备,所以此处没有限制设备,也不需要设备。
- 为什么不选择低版本DevEco进行兼容?原har包项目基于DevEco5.0构建,涉及编译so等,降级不太方便故维持原版本,但是compileSdkVersion和compatibleSdkVersion选择的11,继续在api层面进行低版本兼容。
二、根据README修改文件
sherpa_onnx
└───src
└───main
└───cpp
├───include
│ └───sherpa-onnx
│ └───c-api
│ c-api.h
│ README.md
│
└───libs
.gitignore
README.md
可以看到项目有两个遗留README.md,分别对应以下两个问题
2.1、填充c-api.h
README.md内容如下
# Node
[./c-api.h](./c-api.h) is a symbolic link to
https://github.com/k2-fsa/sherpa-onnx/blob/master/sherpa-onnx/c-api/c-api.h
If you are using Windows, then you need to manually replace this file with
https://github.com/k2-fsa/sherpa-onnx/blob/master/sherpa-onnx/c-api/c-api.h
since Windows does not support symbolic links.
- 如果是windows,打开连接https://github.com/k2-fsa/sherpa-onnx/blob/master/sherpa-onnx/c-api/c-api.h ,复制全文,粘贴到c-api.h。
2.2、获取libonnxruntime.so和libsherpa-onnx-c-api.so
README.md内容如下
# Introduction
You need to get the following four `.so` files using
- [build-ohos-arm64-v8a.sh](https://github.com/k2-fsa/sherpa-onnx/blob/master/build-ohos-arm64-v8a.sh)
- [build-ohos-x86-64.sh](https://github.com/k2-fsa/sherpa-onnx/blob/master/build-ohos-x86-64.sh)
.
├── README.md
├── arm64-v8a
│ ├── libonnxruntime.so
│ └── libsherpa-onnx-c-api.so
└── x86_64
├── libonnxruntime.so
└── libsherpa-onnx-c-api.so
获取方式官方文档:https://k2-fsa.github.io/sherpa/onnx/harmony-os/how-to-build-har.html
项目自带的或者三方库下载的都是5.0编译工具链的,直接放到4.1上运行会出现异常闪退等,所以需要针对4.1release做针对性适配。
首先是使用4.1的编译工具链编译libonnxruntime.so和libsherpa-onnx-c-api.so两个so。
- 获取oh4.1源码,执行prebuilts,获取编译工具链
- 配置native路径,参考OpenHarmony 4.1Release交叉编译fio进行I/O性能测试_会飞的企鹅12138-Laval社区 ,这篇文章配置了oh4.1 SDK路径
- 获取源码https://github.com/k2-fsa/sherpa-onnx.git ,进入sherpa-onnx文件夹
- 配置路径export OHOS_SDK_NATIVE_DIR=/Users/oh4.1/prebuilts/ohos-sdk/linux/11/native/
- 执行build-ohos-arm64-v8a.sh(需要多少位的就编多少位的)
cd /Users/fangjun/open-source git clone https://github.com/k2-fsa/sherpa-onnx cd sherpa-onnx export OHOS_SDK_NATIVE_DIR=/Users/oh4.1/prebuilts/ohos-sdk/linux/11/native/ ./build-ohos-arm64-v8a.sh ./build-ohos-armeabi-v7a.sh ./build-ohos-x86-64.sh
编译完成后可以在如目录看到so文件,需要把libonnxruntime.so和libsherpa-onnx-c-api.so下载移动到Har项目的lib,形成如上README的目录结构
三、修改build-profile.json5
我们的目标是兼容4.1,所以需要配置sdk版本为11,runtimeOS也需要设置为OpenHarmony
{ "app": { "signingConfigs": [], "products": [ { "name": "default", "signingConfig": "default", "compileSdkVersion": 11, "compatibleSdkVersion": 11, "runtimeOS": "OpenHarmony", "buildOption": { "strictMode": { "caseSensitiveCheck": true, } } } ], "buildModeSet": [ { "name": "debug", }, { "name": "release" } ] }, "modules": [ { "name": "entry", "srcPath": "./entry", "targets": [ { "name": "default", "applyToProducts": [ "default" ] } ] }, { "name": "sherpa_onnx", "srcPath": "./sherpa_onnx", } ] }
四、开始尝试编译
4.1、网络报错
git config --global http.sslVerify false
4.2、utils.cc报错
utils.cc报错,由于OH_ResourceManager_IsRawDir
这个函数API11不存在,我们先直接使用bool is_dir = true;
替换掉。确实会影响函数功能,但是后面我们会在JS层重新实现,避免调用这个函数。
function copyRawFileDirToSandbox(context: Context, srcDir: string) {
let mgr = context.resourceManager;
let allFiles: string[] = mgr.getRawFileListSync(srcDir);
console.log('allFiles', allFiles);
allFiles = allFiles.map((src) => srcDir + '/' + src);
console.log('allFiles', allFiles);
allFiles = [...allFiles, ...mgr.getRawFileListSync("vits-melo-tts-zh_en/dict/pos_dict")
.map((src) => srcDir + '/pos_dict/' + src)];
console.log('allFiles', allFiles);
allFiles = allFiles.filter((src) => src !== "vits-melo-tts-zh_en/dict/pos_dict");
console.log('allFiles', allFiles);
for (const src of allFiles) {
const parts: string[] = src.split('/');
if (parts.length != 1) {
mkdir(context, parts.slice(0, -1));
}
copyRawFileToSandbox(context, src, src);
}
}
后面忘了什么原因,直接将utils.cc清空了,CMakeLists.txt也修改不再使用utils.cc
add_library(sherpa_onnx SHARED
audio-tagging.cc
keyword-spotting.cc
non-streaming-asr.cc
non-streaming-speaker-diarization.cc
non-streaming-tts.cc
punctuation.cc
sherpa-onnx-node-addon-api.cc
speaker-identification.cc
spoken-language-identification.cc
streaming-asr.cc
vad.cc
wave-reader.cc
wave-writer.cc
)
至此,har编译不报错,得到一个支持api 11的har包。
五、导入TTS项目使用Har包
拉取sherpa-onnx/harmony-os/SherpaOnnxTts at master · k2-fsa/sherpa-onnx 项目
使用DevEco4.1打开,同样需要修改配置文件的sdk,runtimeOS等
将har包放在entry/src/sherpa_onnx.har,在oh-package.json5配置依赖
{ "name": "entry", "version": "1.0.0", "description": "Please describe the basic information.", "main": "", "author": "", "license": "", "dependencies": { "sherpa_onnx": "file:./src/sherpa_onnx.har" } }
修改依赖,源项目基于HarmonyOS,很多导包OH使用名称不一致
import { audio } from '@kit.AudioKit'; import { fileIo as fs } from '@kit.CoreFileKit'; import { buffer } from '@kit.ArkTS';//去掉,用法无意义 fs.writeSync(fp.fd, buffer.from(uint8Array).buffer)
import audio from '@ohos.multimedia.audio'; import fs, { ReadOptions } from '@ohos.file.fs'; fs.writeSync(fp.fd, arrayBuffer);
修改至不报错,参考最上面的文章,下载神经网络模型到rawfile文件夹
点击run安装到设备。
至此,已经能打出完整hap包,但是会在oh4.1闪退,oh5.0正常
六、排查闪退
报错日志
har源码位置
6.1、打桩,定位到哪一行
出当时的代码
static SherpaOnnxOfflineTtsVitsModelConfig GetOfflineTtsVitsModelConfig(Napi::Object obj) {
SherpaOnnxOfflineTtsVitsModelConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("vits") || !obj.Get("vits").IsObject()) {
return c;
}
Napi::Object o = obj.Get("vits").As<Napi::Object>();
OH_LOG_INFO(LOG_APP, " 2222222222");
Napi::Array keys = o.GetPropertyNames();
OH_LOG_INFO(LOG_APP, "属性名: 2222222222,3");
// 遍历对象的所有属性
for (uint32_t i = 0; i < keys.Length(); i++) {
OH_LOG_INFO(LOG_APP, "public,4");
// 获取属性名
Napi::Value keyValue = keys.Get(i);
OH_LOG_INFO(LOG_APP, "5");
std::string key = keyValue.As<Napi::String>().Utf8Value();
OH_LOG_INFO(LOG_APP, "6");
// 获取属性值
Napi::Value value = obj.Get(key);
// 打印属性名和属性值
if (value.IsNumber()) {
OH_LOG_INFO(LOG_APP, "属性名: %{public}s, 属性值: %{public}f", key.c_str(), value.As<Napi::Number>().DoubleValue());
} else if (value.IsString()) {
OH_LOG_INFO(LOG_APP, "属性名: %{public}s, 属性值: %{public}s", key.c_str(), value.As<Napi::String>().Utf8Value().c_str());
} else if (value.IsBoolean()) {
const char* boolStr = value.As<Napi::Boolean>().Value() ? "true" : "false";
OH_LOG_INFO(LOG_APP, "属性名: %{public}s, 属性值: %{public}s", key.c_str(), boolStr);
} else {
OH_LOG_INFO(LOG_APP, "属性名: %{public}s, 属性值: 其他类型", key.c_str());
}
}
OH_LOG_INFO(LOG_APP, "属性名: 2222222222,7");
SHERPA_ONNX_ASSIGN_ATTR_STR(model, model);
SHERPA_ONNX_ASSIGN_ATTR_STR(lexicon, lexicon);
SHERPA_ONNX_ASSIGN_ATTR_STR(tokens, tokens);
SHERPA_ONNX_ASSIGN_ATTR_STR(data_dir, dataDir);
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(noise_scale, noiseScale);
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(noise_scale_w, noiseScaleW);
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(length_scale, lengthScale);
SHERPA_ONNX_ASSIGN_ATTR_STR(dict_dir, dictDir);
return c;
}
6.2、找到中断位置下一步怎么办?
定位到是 .Utf8Value()这个方法会报错,导致应用端抛出异常。.Utf8Value()实现方法如下:
#define SHERPA_ONNX_ASSIGN_ATTR_STR(c_name, js_name) \
do { \
if (o.Has(#js_name) && o.Get(#js_name).IsString()) { \
Napi::String _str = o.Get(#js_name).As<Napi::String>(); \
std::string s = _str.Utf8Value(); \
char *p = new char[s.size() + 1]; \
std::copy(s.begin(), s.end(), p); \
p[s.size()] = 0; \
\
c.c_name = p; \
} else if (o.Has(#js_name) && o.Get(#js_name).IsTypedArray()) { \
Napi::Uint8Array _array = o.Get(#js_name).As<Napi::Uint8Array>(); \
char *p = new char[_array.ElementLength() + 1]; \
std::copy(_array.Data(), _array.Data() + _array.ElementLength(), p); \
p[_array.ElementLength()] = '\0'; \
\
c.c_name = p; \
} \
} while (0)
inline std::string String::Utf8Value() const {
size_t length;
napi_status status =
napi_get_value_string_utf8(_env, _value, nullptr, 0, &length);
NAPI_THROW_IF_FAILED(_env, status, "");
std::string value;
value.reserve(length + 1);
value.resize(length);
status = napi_get_value_string_utf8(
_env, _value, &value[0], value.capacity(), nullptr);
NAPI_THROW_IF_FAILED(_env, status, "");
return value;
}
此时使用的napi_get_value_string_utf8来自作者导入的node-addon-api。
include(FetchContent)
FetchContent_Declare(node_addon_api
GIT_REPOSITORY "https://github.com/nodejs/node-addon-api.git"
GIT_TAG c679f6f4c9dc6bf9fc0d99cbe5982bd24a5e2c7b
PATCH_COMMAND git checkout . && git apply --ignore-whitespace "${CMAKE_CURRENT_LIST_DIR}/my-patch.diff"
)
6.2.1、直接降低版本
无效
OH5.0描述
OpenHarmony Node-API是基于Node.js 8.x LTS的Node-API规范扩展开发的机制,为开发者提供了ArkTS/JS与C/C++模块之间的交互能力。它提供了一组稳定的、跨平台的API,可以在不同的操作系统上使用。
OH4.1描述
OpenHarmony Node-API是基于Node.js 12.x LTS的Node-API规范扩展开发的机制,为开发者提供了ArkTS/JS与C/C++模块之间的交互能力。它提供了一组稳定的、跨平台的API,可以在不同的操作系统上使用。
OH4.1Node版本太低,强行降低node-addon-api版本太多会导致har包项目编译不过,但是版本但凡高一点,oh4.1上应用就会闪退。
6.2.2、替换成Native的napi_get_value_string_utf8
修改后是这样,确保napi_get_value_string_utf8来自js_native_api.h
#define SHERPA_ONNX_ASSIGN_ATTR_STR(env, c_name, js_name) \
do { \
if (o.Has(#js_name)) { \
Napi::Value _value = o.Get(#js_name); \
if (_value.IsString()) { \
size_t strLen = 0; \
napi_status status = napi_get_value_string_utf8(env, _value, nullptr, 0, &strLen); \
if (status == napi_ok) { \
char *p = new char[strLen + 1]; \
status = napi_get_value_string_utf8(env, _value, p, strLen + 1, &strLen); \
if (status == napi_ok) { \
p[strLen] = 0; \
c.c_name = p; \
} else { \
delete[] p; \
} \
} \
} else if (_value.IsTypedArray()) { \
Napi::Uint8Array _array = _value.As<Napi::Uint8Array>(); \
char *p = new char[_array.ElementLength() + 1]; \
std::copy(_array.Data(), _array.Data() + _array.ElementLength(), p); \
p[_array.ElementLength()] = '\0'; \
c.c_name = p; \
} \
} \
} while (0)
static SherpaOnnxOfflineTtsVitsModelConfig GetOfflineTtsVitsModelConfig(Napi::Env env, Napi::Object obj) {
SherpaOnnxOfflineTtsVitsModelConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("vits") || !obj.Get("vits").IsObject()) {
return c;
}
Napi::Object o = obj.Get("vits").As<Napi::Object>();
SHERPA_ONNX_ASSIGN_ATTR_STR(env, model, model);
SHERPA_ONNX_ASSIGN_ATTR_STR(env, lexicon, lexicon);
SHERPA_ONNX_ASSIGN_ATTR_STR(env, tokens, tokens);
SHERPA_ONNX_ASSIGN_ATTR_STR(env, data_dir, dataDir);
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(env, noise_scale, noiseScale);
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(env, noise_scale_w, noiseScaleW);
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(env, length_scale, lengthScale);
SHERPA_ONNX_ASSIGN_ATTR_STR(env, dict_dir, dictDir);
return c;
}
项目涉及到的应用与Native传参的地方都要修改。
至此,问题解决,TTS在开发者手机5.0/4.1均可用(ASR一样修改)
七、参考文档:
https://k2-fsa.github.io/sherpa/onnx/harmony-os/how-to-build-har.html
https://ohpm.openharmony.cn/#/cn/detail/sherpa\_onnx
https://docs.openharmony.cn/pages/v4.1/zh-cn/application-dev/napi/napi-introduction.md
https://docs.openharmony.cn/pages/v5.0/zh-cn/application-dev/napi/napi-introduction.md
更多推荐
所有评论(0)