零、前言

前面两篇文章只介绍了如何使用项目,也说了项目依赖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.

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。

    1. 获取oh4.1源码,执行prebuilts,获取编译工具链
    2. 配置native路径,参考OpenHarmony 4.1Release交叉编译fio进行I/O性能测试_会飞的企鹅12138-Laval社区 ,这篇文章配置了oh4.1 SDK路径
    3. 获取源码https://github.com/k2-fsa/sherpa-onnx.git ,进入sherpa-onnx文件夹
    4. 配置路径export OHOS_SDK_NATIVE_DIR=/Users/oh4.1/prebuilts/ohos-sdk/linux/11/native/
    5. 执行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的目录结构

    img

三、修改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包

  1. 拉取sherpa-onnx/harmony-os/SherpaOnnxTts at master · k2-fsa/sherpa-onnx 项目

  2. 使用DevEco4.1打开,同样需要修改配置文件的sdk,runtimeOS等

  3. 将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"
      }
    }
    
  4. 修改依赖,源项目基于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);
    
  5. 修改至不报错,参考最上面的文章,下载神经网络模型到rawfile文件夹

  6. 点击run安装到设备。

至此,已经能打出完整hap包,但是会在oh4.1闪退,oh5.0正常

六、排查闪退

报错日志

img

har源码位置

img

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

Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐