Fangjun Kuang
Committed by GitHub

Add on-device real-time ASR demo for HarmonyOS (#1606)

正在显示 55 个修改的文件 包含 1341 行增加4 行删除
@@ -10,5 +10,8 @@ @@ -10,5 +10,8 @@
10 VAD + Non-streaming ASR for speech recognition. 10 VAD + Non-streaming ASR for speech recognition.
11 Please see the doc at <https://k2-fsa.github.io/sherpa/onnx/harmony-os/vad-asr.html> 11 Please see the doc at <https://k2-fsa.github.io/sherpa/onnx/harmony-os/vad-asr.html>
12 12
  13 +- [./SherpaOnnxStreamingAsr](./SherpaOnnxStreamingAsr) It shows how to use
  14 + streaming ASR models for real-time on-device speech recognition.
  15 +
13 - [./SherpaOnnxTts](./SherpaOnnxTts) It shows how to run on-device text-to-speech. 16 - [./SherpaOnnxTts](./SherpaOnnxTts) It shows how to run on-device text-to-speech.
14 Please see the doc at <https://k2-fsa.github.io/sherpa/onnx/harmony-os/tts.html> 17 Please see the doc at <https://k2-fsa.github.io/sherpa/onnx/harmony-os/tts.html>
  1 +/node_modules
  2 +/oh_modules
  3 +/local.properties
  4 +/.idea
  5 +**/build
  6 +/.hvigor
  7 +.cxx
  8 +/.clangd
  9 +/.clang-format
  10 +/.clang-tidy
  11 +**/.test
  12 +/.appanalyzer
  1 +{
  2 + "app": {
  3 + "bundleName": "com.k2fsa.sherpa.onnx.streaming.asr",
  4 + "vendor": "example",
  5 + "versionCode": 1000000,
  6 + "versionName": "1.0.0",
  7 + "icon": "$media:app_icon",
  8 + "label": "$string:app_name"
  9 + }
  10 +}
  1 +{
  2 + "string": [
  3 + {
  4 + "name": "app_name",
  5 + "value": "SherpaOnnxStreamingAsr"
  6 + }
  7 + ]
  8 +}
  1 +{
  2 + "app": {
  3 + "signingConfigs": [],
  4 + "products": [
  5 + {
  6 + "name": "default",
  7 + "signingConfig": "default",
  8 + "compatibleSdkVersion": "4.0.0(10)",
  9 + "runtimeOS": "HarmonyOS",
  10 + "buildOption": {
  11 + "strictMode": {
  12 + "caseSensitiveCheck": true,
  13 + }
  14 + }
  15 + }
  16 + ],
  17 + "buildModeSet": [
  18 + {
  19 + "name": "debug",
  20 + },
  21 + {
  22 + "name": "release"
  23 + }
  24 + ]
  25 + },
  26 + "modules": [
  27 + {
  28 + "name": "entry",
  29 + "srcPath": "./entry",
  30 + "targets": [
  31 + {
  32 + "name": "default",
  33 + "applyToProducts": [
  34 + "default"
  35 + ]
  36 + }
  37 + ]
  38 + }
  39 + ]
  40 +}
  1 +{
  2 + "files": [
  3 + "**/*.ets"
  4 + ],
  5 + "ignore": [
  6 + "**/src/ohosTest/**/*",
  7 + "**/src/test/**/*",
  8 + "**/src/mock/**/*",
  9 + "**/node_modules/**/*",
  10 + "**/oh_modules/**/*",
  11 + "**/build/**/*",
  12 + "**/.preview/**/*"
  13 + ],
  14 + "ruleSet": [
  15 + "plugin:@performance/recommended",
  16 + "plugin:@typescript-eslint/recommended"
  17 + ],
  18 + "rules": {
  19 + }
  20 +}
  1 +/node_modules
  2 +/oh_modules
  3 +/.preview
  4 +/build
  5 +/.cxx
  6 +/.test
  1 +{
  2 + "apiType": "stageMode",
  3 + "buildOption": {
  4 + "sourceOption": {
  5 + "workers": [
  6 + './src/main/ets/workers/StreamingAsrWorker.ets'
  7 + ]
  8 + }
  9 + },
  10 + "buildOptionSet": [
  11 + {
  12 + "name": "release",
  13 + "arkOptions": {
  14 + "obfuscation": {
  15 + "ruleOptions": {
  16 + "enable": false,
  17 + "files": [
  18 + "./obfuscation-rules.txt"
  19 + ]
  20 + }
  21 + }
  22 + }
  23 + },
  24 + ],
  25 + "targets": [
  26 + {
  27 + "name": "default"
  28 + },
  29 + {
  30 + "name": "ohosTest",
  31 + }
  32 + ]
  33 +}
  1 +import { hapTasks } from '@ohos/hvigor-ohos-plugin';
  2 +
  3 +export default {
  4 + system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
  5 + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */
  6 +}
  1 +# Define project specific obfuscation rules here.
  2 +# You can include the obfuscation configuration files in the current module's build-profile.json5.
  3 +#
  4 +# For more details, see
  5 +# https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5
  6 +
  7 +# Obfuscation options:
  8 +# -disable-obfuscation: disable all obfuscations
  9 +# -enable-property-obfuscation: obfuscate the property names
  10 +# -enable-toplevel-obfuscation: obfuscate the names in the global scope
  11 +# -compact: remove unnecessary blank spaces and all line feeds
  12 +# -remove-log: remove all console.* statements
  13 +# -print-namecache: print the name cache that contains the mapping from the old names to new names
  14 +# -apply-namecache: reuse the given cache file
  15 +
  16 +# Keep options:
  17 +# -keep-property-name: specifies property names that you want to keep
  18 +# -keep-global-name: specifies names that you want to keep in the global scope
  19 +
  20 +-enable-property-obfuscation
  21 +-enable-toplevel-obfuscation
  22 +-enable-filename-obfuscation
  23 +-enable-export-obfuscation
  1 +{
  2 + "meta": {
  3 + "stableOrder": true
  4 + },
  5 + "lockfileVersion": 3,
  6 + "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.",
  7 + "specifiers": {
  8 + "libsherpa_onnx.so@../oh_modules/.ohpm/sherpa_onnx@1.10.33/oh_modules/sherpa_onnx/src/main/cpp/types/libsherpa_onnx": "libsherpa_onnx.so@../oh_modules/.ohpm/sherpa_onnx@1.10.33/oh_modules/sherpa_onnx/src/main/cpp/types/libsherpa_onnx",
  9 + "sherpa_onnx@1.10.33": "sherpa_onnx@1.10.33"
  10 + },
  11 + "packages": {
  12 + "libsherpa_onnx.so@../oh_modules/.ohpm/sherpa_onnx@1.10.33/oh_modules/sherpa_onnx/src/main/cpp/types/libsherpa_onnx": {
  13 + "name": "libsherpa_onnx.so",
  14 + "version": "1.0.0",
  15 + "resolved": "../oh_modules/.ohpm/sherpa_onnx@1.10.33/oh_modules/sherpa_onnx/src/main/cpp/types/libsherpa_onnx",
  16 + "registryType": "local"
  17 + },
  18 + "sherpa_onnx@1.10.33": {
  19 + "name": "sherpa_onnx",
  20 + "version": "1.10.33",
  21 + "integrity": "sha512-cmZ8zwOMx4qmDvOjF1/PL6/suBgReanSf5XdQTuMWWZ6qN74rynODHrt4C+Qz754MTXg0q/phAKeVjGA4rHHSA==",
  22 + "resolved": "https://ohpm.openharmony.cn/ohpm/sherpa_onnx/-/sherpa_onnx-1.10.33.har",
  23 + "registryType": "ohpm",
  24 + "dependencies": {
  25 + "libsherpa_onnx.so": "file:./src/main/cpp/types/libsherpa_onnx"
  26 + }
  27 + }
  28 + }
  29 +}
  1 +{
  2 + "name": "entry",
  3 + "version": "1.0.0",
  4 + "description": "Please describe the basic information.",
  5 + "main": "",
  6 + "author": "",
  7 + "license": "",
  8 + "dependencies": {
  9 + "sherpa_onnx": "1.10.33",
  10 + }
  11 +}
  12 +
  1 +import AbilityConstant from '@ohos.app.ability.AbilityConstant';
  2 +import hilog from '@ohos.hilog';
  3 +import UIAbility from '@ohos.app.ability.UIAbility';
  4 +import Want from '@ohos.app.ability.Want';
  5 +import window from '@ohos.window';
  6 +
  7 +export default class EntryAbility extends UIAbility {
  8 + onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  9 + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
  10 + }
  11 +
  12 + onDestroy(): void {
  13 + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
  14 + }
  15 +
  16 + onWindowStageCreate(windowStage: window.WindowStage): void {
  17 + // Main window is created, set main page for this ability
  18 + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
  19 +
  20 + windowStage.loadContent('pages/Index', (err) => {
  21 + if (err.code) {
  22 + hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
  23 + return;
  24 + }
  25 + hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.');
  26 + });
  27 + }
  28 +
  29 + onWindowStageDestroy(): void {
  30 + // Main window is destroyed, release UI related resources
  31 + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
  32 + }
  33 +
  34 + onForeground(): void {
  35 + // Ability has brought to foreground
  36 + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
  37 + }
  38 +
  39 + onBackground(): void {
  40 + // Ability has back to background
  41 + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
  42 + }
  43 +}
  1 +import hilog from '@ohos.hilog';
  2 +import BackupExtensionAbility, { BundleVersion } from '@ohos.application.BackupExtensionAbility';
  3 +
  4 +export default class EntryBackupAbility extends BackupExtensionAbility {
  5 + async onBackup() {
  6 + hilog.info(0x0000, 'testTag', 'onBackup ok');
  7 + }
  8 +
  9 + async onRestore(bundleVersion: BundleVersion) {
  10 + hilog.info(0x0000, 'testTag', 'onRestore ok %{public}s', JSON.stringify(bundleVersion));
  11 + }
  12 +}
  1 +import { LengthUnit } from '@kit.ArkUI';
  2 +import worker, { MessageEvents } from '@ohos.worker';
  3 +import { BusinessError } from '@kit.BasicServicesKit';
  4 +import { picker } from '@kit.CoreFileKit';
  5 +import systemTime from '@ohos.systemTime';
  6 +import { Permissions } from '@kit.AbilityKit';
  7 +import { allAllowed, requestPermissions } from './Permission';
  8 +import { audio } from '@kit.AudioKit';
  9 +import fs from '@ohos.file.fs';
  10 +
  11 +
  12 +function savePcmToWav(filename: string, samples: Int16Array, sampleRate: number) {
  13 + const fp = fs.openSync(filename, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
  14 +
  15 + const header = new ArrayBuffer(44);
  16 + const view = new DataView(header);
  17 +
  18 + // http://soundfile.sapp.org/doc/WaveFormat/
  19 + // F F I R
  20 + view.setUint32(0, 0x46464952, true); // chunkID
  21 + view.setUint32(4, 36 + samples.length * 2, true); // chunkSize // E V A W
  22 + view.setUint32(8, 0x45564157, true); // format // // t m f
  23 + view.setUint32(12, 0x20746d66, true); // subchunk1ID
  24 + view.setUint32(16, 16, true); // subchunk1Size, 16 for PCM
  25 + view.setUint32(20, 1, true); // audioFormat, 1 for PCM
  26 + view.setUint16(22, 1, true); // numChannels: 1 channel
  27 + view.setUint32(24, sampleRate, true); // sampleRate
  28 + view.setUint32(28, sampleRate * 2, true); // byteRate
  29 + view.setUint16(32, 2, true); // blockAlign
  30 + view.setUint16(34, 16, true); // bitsPerSample
  31 + view.setUint32(36, 0x61746164, true); // Subchunk2ID
  32 + view.setUint32(40, samples.length * 2, true); // subchunk2Size
  33 +
  34 + fs.writeSync(fp.fd, new Uint8Array(header).buffer, { length: header.byteLength });
  35 + fs.writeSync(fp.fd, samples.buffer, { length: samples.buffer.byteLength });
  36 +
  37 + fs.closeSync(fp.fd);
  38 +}
  39 +
  40 +function toInt16Samples(samples: Float32Array): Int16Array {
  41 + const int16Samples = new Int16Array(samples.length);
  42 + for (let i = 0; i < samples.length; ++i) {
  43 + let s = samples[i] * 32767;
  44 + s = s > 32767 ? 32767 : s;
  45 + s = s < -32768 ? -32768 : s;
  46 + int16Samples[i] = s;
  47 + }
  48 +
  49 + return int16Samples;
  50 +}
  51 +
  52 +
  53 +@Entry
  54 +@Component
  55 +struct Index {
  56 + @State title: string = 'Next-gen Kaldi: Real-time speech recognition';
  57 + @State titleFontSize: number = 15;
  58 + @State currentIndex: number = 0;
  59 + @State lang: string = 'English';
  60 + @State resultForFile: string = ''
  61 + @State resultForMic: string = ''
  62 + @State selectFileBtnEnabled: boolean = false;
  63 + @State micBtnCaption: string = 'Start';
  64 + @State micStarted: boolean = false;
  65 + @State micAllowed: boolean = false;
  66 + @State micBtnEnabled: boolean = false;
  67 + @State micSaveBtnCaption: string = 'Save recorded audio';
  68 + @State micSaveBtnEnabled: boolean = false;
  69 + @State info: string = '';
  70 + @State micInfo: string = '';
  71 + @State micInitDone: boolean = false;
  72 + private resultListForMic: string[] = [];
  73 + private controller: TabsController = new TabsController();
  74 + private workerInstance?: worker.ThreadWorker
  75 + private readonly scriptURL: string = 'entry/ets/workers/StreamingAsrWorker.ets'
  76 + private startTime: number = 0;
  77 + private stopTime: number = 0;
  78 + private sampleRate: number = 48000;
  79 + private sampleList: Float32Array[] = []
  80 + private mic?: audio.AudioCapturer;
  81 +
  82 + flatten(samples: Float32Array[]): Float32Array {
  83 + let n = 0;
  84 + for (let i = 0; i < samples.length; ++i) {
  85 + n += samples[i].length;
  86 + }
  87 +
  88 + const ans: Float32Array = new Float32Array(n);
  89 + let offset: number = 0;
  90 + for (let i = 0; i < samples.length; ++i) {
  91 + ans.set(samples[i], offset);
  92 + offset += samples[i].length;
  93 + }
  94 +
  95 + return ans;
  96 + }
  97 +
  98 + async initMic() {
  99 + const permissions: Permissions[] = ["ohos.permission.MICROPHONE"];
  100 + let allowed: boolean = await allAllowed(permissions);
  101 + if (!allowed) {
  102 + console.log("request to access the microphone");
  103 + const status: boolean = await requestPermissions(permissions);
  104 +
  105 + if (!status) {
  106 + console.error('access to microphone is denied')
  107 + this.resultForMic = "Failed to get microphone permission. Please retry";
  108 + return;
  109 + }
  110 +
  111 + allowed = await allAllowed(permissions);
  112 + if (!allowed) {
  113 + console.error('failed to get microphone permission');
  114 + this.resultForMic = "Failed to get microphone permission. Please retry";
  115 + return;
  116 + }
  117 + this.micAllowed = true;
  118 + } else {
  119 + console.log("allowed to access microphone");
  120 + this.micAllowed = true;
  121 + }
  122 +
  123 + const audioStreamInfo: audio.AudioStreamInfo = {
  124 + samplingRate: this.sampleRate,
  125 + channels: audio.AudioChannel.CHANNEL_1,
  126 + sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,
  127 + encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW,
  128 + };
  129 +
  130 + const audioCapturerInfo: audio.AudioCapturerInfo = {
  131 + source: audio.SourceType.SOURCE_TYPE_MIC, capturerFlags: 0
  132 + };
  133 +
  134 + const audioCapturerOptions: audio.AudioCapturerOptions = {
  135 + streamInfo: audioStreamInfo, capturerInfo: audioCapturerInfo
  136 +
  137 + };
  138 + audio.createAudioCapturer(audioCapturerOptions, (err, data) => {
  139 + if (err) {
  140 + console.error(`error code is ${err.code}, error message is ${err.message}`);
  141 + this.resultForMic = 'Failed to init microphone';
  142 + } else {
  143 + console.info(`init mic successfully`);
  144 + this.mic = data;
  145 + this.mic.on('readData', this.micCallback);
  146 + }
  147 + });
  148 + }
  149 +
  150 + async aboutToAppear() {
  151 + this.workerInstance = new worker.ThreadWorker(this.scriptURL, {
  152 + name: 'Streaming ASR worker'
  153 + });
  154 +
  155 + this.workerInstance.onmessage = (e: MessageEvents) => {
  156 + const msgType = e.data['msgType'] as string;
  157 + console.log(`received msg from worker: ${msgType}`);
  158 +
  159 + if (msgType == 'init-streaming-asr-done') {
  160 + this.selectFileBtnEnabled = true;
  161 + this.micBtnEnabled = true;
  162 + this.info = `Initializing done.\n\nPlease select a wave file of 16kHz in language ${this.lang}`;
  163 + this.micInfo = `Initializing done.\n\nPlease click Start and speak`;
  164 + }
  165 +
  166 + if (msgType == 'streaming-asr-decode-file-done') {
  167 + const text = e.data['text'] as string;
  168 + this.resultForFile = text;
  169 + this.selectFileBtnEnabled = true;
  170 +
  171 + systemTime.getRealTime((err, data) => {
  172 + if (err) {
  173 + console.log('Failed to get stop time');
  174 + } else {
  175 + this.stopTime = data;
  176 +
  177 + const audioDuration = e.data['duration'] as number;
  178 + const elapsedSeconds = (this.stopTime - this.startTime) / 1000;
  179 + const RTF = elapsedSeconds / audioDuration;
  180 + this.info = `Audio duration: ${audioDuration.toFixed(2)} s
  181 +Elapsed: ${elapsedSeconds.toFixed(2)} s
  182 +RTF = ${elapsedSeconds.toFixed(2)}/${audioDuration.toFixed(2)} = ${RTF.toFixed(3)}
  183 +`;
  184 + }
  185 + });
  186 + }
  187 +
  188 + if (msgType == 'streaming-asr-decode-mic-result') {
  189 + const text = e.data['text'] as string;
  190 + if (text.trim() == '') {
  191 + return;
  192 + }
  193 +
  194 + const isEndpoint = e.data['isEndpoint'] as boolean;
  195 +
  196 + let s = '';
  197 + let i = 0;
  198 + for (; i < this.resultListForMic.length; ++i) {
  199 + s += `${i}: ${this.resultListForMic[i]}\n`
  200 + }
  201 +
  202 + s += `${i}: ${text}`;
  203 + this.resultForMic = s;
  204 +
  205 + if (isEndpoint) {
  206 + this.resultListForMic.push(text);
  207 + }
  208 + }
  209 + };
  210 +
  211 + const context = getContext();
  212 + this.workerInstance.postMessage({ msgType: 'init-streaming-asr', context });
  213 + this.info = 'Initializing ASR model.\nPlease wait';
  214 + this.micInfo = 'Initializing ASR model.\nPlease wait';
  215 +
  216 + await this.initMic();
  217 + }
  218 +
  219 + @Builder
  220 + TabBuilder(title: string, targetIndex: number, selectedImg: Resource, normalImg: Resource) {
  221 + Column() {
  222 + Image(this.currentIndex == targetIndex ? selectedImg : normalImg).size({ width: 25, height: 25 })
  223 + Text(title).fontColor(this.currentIndex == targetIndex ? '#28bff1' : '#8a8a8a')
  224 + }.width('100%').height(50).justifyContent(FlexAlign.Center).onClick(() => {
  225 + this.currentIndex = targetIndex;
  226 + this.controller.changeIndex(this.currentIndex);
  227 + })
  228 + }
  229 +
  230 + build() {
  231 + Column() {
  232 + Tabs({ barPosition: BarPosition.End, controller: this.controller }) {
  233 + TabContent() {
  234 + Column({ space: 10 }) {
  235 + Text(this.title).fontSize(this.titleFontSize).fontWeight(FontWeight.Bold);
  236 + Button('Select .wav file (16kHz) ')
  237 + .enabled(this.selectFileBtnEnabled)
  238 + .fontSize(13)
  239 + .width(296)
  240 + .height(60)
  241 + .onClick(() => {
  242 + this.resultForFile = '';
  243 + this.info = '';
  244 + this.selectFileBtnEnabled = false;
  245 +
  246 + const documentSelectOptions = new picker.DocumentSelectOptions();
  247 + documentSelectOptions.maxSelectNumber = 1;
  248 + documentSelectOptions.fileSuffixFilters = ['.wav'];
  249 + const documentViewPicker = new picker.DocumentViewPicker();
  250 +
  251 + documentViewPicker.select(documentSelectOptions).then((result: Array<string>) => {
  252 + console.log(`select file result: ${result}`);
  253 +
  254 + if (!result[0]) {
  255 + this.resultForFile = 'Please select a file to decode';
  256 + this.selectFileBtnEnabled = true;
  257 + return;
  258 + }
  259 +
  260 + if (this.workerInstance) {
  261 + systemTime.getRealTime((err, data) => {
  262 + if (err) {
  263 + console.log('Failed to get start time');
  264 + } else {
  265 + this.startTime = data;
  266 + }
  267 + });
  268 +
  269 + this.workerInstance.postMessage({
  270 + msgType: 'streaming-asr-decode-file', filename: result[0],
  271 + });
  272 + this.info = `Decoding ${result[0]} ... ...`;
  273 + } else {
  274 + console.log(`this worker instance is undefined ${this.workerInstance}`);
  275 + }
  276 +
  277 + }).catch((err: BusinessError) => {
  278 + console.error(`Failed to select file, code is ${err.code}, message is ${err.message}`);
  279 + this.selectFileBtnEnabled = true;
  280 + })
  281 + })
  282 +
  283 + Text(`Supported languages: ${this.lang}`);
  284 + if (this.info != '') {
  285 + TextArea({ text: this.info }).focusable(false);
  286 + }
  287 + TextArea({ text: this.resultForFile })
  288 + .width('100%')
  289 + .lineSpacing({ value: 10, unit: LengthUnit.VP })
  290 + .height('100%');
  291 + }
  292 + }.tabBar(this.TabBuilder('From file', 0, $r('app.media.icon_doc'), $r('app.media.icon_doc')))
  293 +
  294 + TabContent() {
  295 + Column({ space: 10 }) {
  296 + Text(this.title).fontSize(this.titleFontSize).fontWeight(FontWeight.Bold);
  297 + Button(this.micBtnCaption)
  298 + .enabled(this.micBtnEnabled)
  299 + .fontSize(13)
  300 + .width(296)
  301 + .height(60)
  302 + .onClick(() => {
  303 + this.micInfo = '';
  304 + if (this.mic) {
  305 + if (this.micStarted) {
  306 + this.micStarted = false;
  307 + this.micBtnCaption = 'Start';
  308 + this.mic.stop();
  309 + this.micSaveBtnEnabled = true;
  310 +
  311 + if (this.workerInstance) {
  312 + this.workerInstance.postMessage({
  313 + msgType: 'streaming-asr-decode-mic-stop'
  314 + });
  315 + }
  316 + } else {
  317 + this.micStarted = true;
  318 + this.micSaveBtnEnabled = false;
  319 + this.micBtnCaption = 'Stop';
  320 + this.resultForMic = '';
  321 + this.resultListForMic = [];
  322 +
  323 + if (this.workerInstance) {
  324 + this.workerInstance.postMessage({
  325 + msgType: 'streaming-asr-decode-mic-start'
  326 + });
  327 + }
  328 +
  329 + this.sampleList = [];
  330 + this.mic.start();
  331 + }
  332 + }
  333 + });
  334 + Button(this.micSaveBtnCaption)
  335 + .enabled(this.micSaveBtnEnabled)
  336 + .fontSize(13)
  337 + .width(296)
  338 + .height(60)
  339 + .onClick(() => {
  340 + if (this.sampleList.length == 0) {
  341 + this.micSaveBtnEnabled = false;
  342 + return;
  343 + }
  344 +
  345 + const samples = this.flatten(this.sampleList);
  346 +
  347 + if (samples.length == 0) {
  348 + this.micSaveBtnEnabled = false;
  349 + return;
  350 + }
  351 +
  352 +
  353 + let uri: string = '';
  354 +
  355 +
  356 + const audioOptions = new picker.AudioSaveOptions(); // audioOptions.newFileNames = ['o.wav'];
  357 +
  358 + const audioViewPicker = new picker.AudioViewPicker();
  359 +
  360 + audioViewPicker.save(audioOptions).then((audioSelectResult: Array<string>) => {
  361 + uri = audioSelectResult[0];
  362 + savePcmToWav(uri, toInt16Samples(samples), this.sampleRate);
  363 + console.log(`Saved to ${uri}`);
  364 + this.micInfo += `\nSaved to ${uri}`;
  365 + });
  366 +
  367 + })
  368 +
  369 +
  370 + Text(`Supported languages: ${this.lang}`)
  371 +
  372 + if (this.micInfo != '') {
  373 + TextArea({ text: this.micInfo })
  374 + .focusable(false);
  375 + }
  376 +
  377 + TextArea({ text: this.resultForMic })
  378 + .width('100%')
  379 + .lineSpacing({ value: 10, unit: LengthUnit.VP })
  380 + .width('100%')
  381 + .height('100%');
  382 + }
  383 + }.tabBar(this.TabBuilder('From mic', 1, $r('app.media.icon_mic'), $r('app.media.icon_mic')))
  384 +
  385 +
  386 + TabContent() {
  387 + Column({ space: 10 }) {
  388 + Text(this.title).fontSize(this.titleFontSize).fontWeight(FontWeight.Bold);
  389 + TextArea({
  390 + text: `
  391 +Everyting is open-sourced.
  392 +
  393 +It runs locally, without accessing the network
  394 +
  395 +See also https://github.com/k2-fsa/sherpa-onnx
  396 +
  397 +新一代 Kaldi QQ 和微信交流群: 请看
  398 +
  399 +https://k2-fsa.github.io/sherpa/social-groups.html
  400 +
  401 +微信公众号: 新一代 Kaldi
  402 + `
  403 + }).width('100%').height('100%').focusable(false)
  404 + }.justifyContent(FlexAlign.Start)
  405 + }.tabBar(this.TabBuilder('Help', 2, $r('app.media.info'), $r('app.media.info')))
  406 + }.scrollable(false)
  407 + }.width('100%')
  408 + }
  409 +
  410 + private micCallback = (buffer: ArrayBuffer) => {
  411 + const view: Int16Array = new Int16Array(buffer);
  412 +
  413 + const samplesFloat: Float32Array = new Float32Array(view.length);
  414 + for (let i = 0; i < view.length; ++i) {
  415 + samplesFloat[i] = view[i] / 32768.0;
  416 + }
  417 +
  418 + this.sampleList.push(samplesFloat);
  419 +
  420 + if (this.workerInstance) {
  421 + this.workerInstance.postMessage({
  422 + msgType: 'streaming-asr-decode-mic-samples',
  423 + samples: samplesFloat,
  424 + sampleRate: this.sampleRate,
  425 + })
  426 + }
  427 + }
  428 +}
  1 +// This file is modified from
  2 +// https://gitee.com/ukSir/hmchat2/blob/master/entry/src/main/ets/utils/permissionMananger.ets
  3 +import { abilityAccessCtrl, bundleManager, common, Permissions } from '@kit.AbilityKit';
  4 +
  5 +export function allAllowed(permissions: Permissions[]): boolean {
  6 + if (permissions.length == 0) {
  7 + return false;
  8 + }
  9 +
  10 + const mgr: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
  11 +
  12 + const bundleInfo = bundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
  13 +
  14 + let tokenID: number = bundleInfo.appInfo.accessTokenId;
  15 +
  16 + return permissions.every(permission => abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED ==
  17 + mgr.checkAccessTokenSync(tokenID, permission));
  18 +}
  19 +
  20 +export async function requestPermissions(permissions: Permissions[]): Promise<boolean> {
  21 + const mgr: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
  22 + const context: Context = getContext() as common.UIAbilityContext;
  23 +
  24 + const result = await mgr.requestPermissionsFromUser(context, permissions);
  25 + return result.authResults.length > 0 && result.authResults.every(authResults => authResults == 0);
  26 +}
  1 +import worker, { ErrorEvent, MessageEvents, ThreadWorkerGlobalScope } from '@ohos.worker';
  2 +import {
  3 + OnlineModelConfig,
  4 + OnlineRecognizer,
  5 + OnlineRecognizerConfig,
  6 + OnlineStream,
  7 + readWaveFromBinary,
  8 + Samples
  9 +} from 'sherpa_onnx';
  10 +import { fileIo } from '@kit.CoreFileKit';
  11 +
  12 +const workerPort: ThreadWorkerGlobalScope = worker.workerPort;
  13 +
  14 +
  15 +let recognizer: OnlineRecognizer;
  16 +let micStream: OnlineStream;
  17 +
  18 +function getModelConfig(type: number): OnlineModelConfig {
  19 + const modelConfig = new OnlineModelConfig();
  20 + switch (type) {
  21 + case 0: {
  22 + const modelDir = 'sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20';
  23 + modelConfig.transducer.encoder = `${modelDir}/encoder-epoch-99-avg-1.onnx`;
  24 + modelConfig.transducer.decoder = `${modelDir}/decoder-epoch-99-avg-1.onnx`;
  25 + modelConfig.transducer.joiner = `${modelDir}/joiner-epoch-99-avg-1.onnx`;
  26 + modelConfig.tokens = `${modelDir}/tokens.txt`;
  27 + modelConfig.modelType = 'zipformer';
  28 + break;
  29 + }
  30 +
  31 + case 1: {
  32 + const modelDir = 'sherpa-onnx-lstm-zh-2023-02-20';
  33 + modelConfig.transducer.encoder = `${modelDir}/encoder-epoch-11-avg-1.onnx`;
  34 + modelConfig.transducer.decoder = `${modelDir}/decoder-epoch-11-avg-1.onnx`;
  35 + modelConfig.transducer.joiner = `${modelDir}/joiner-epoch-11-avg-1.onnx`;
  36 + modelConfig.tokens = `${modelDir}/tokens.txt`;
  37 + modelConfig.modelType = 'lstm';
  38 + break;
  39 + }
  40 +
  41 + case 2: {
  42 + const modelDir = 'sherpa-onnx-lstm-en-2023-02-17';
  43 + modelConfig.transducer.encoder = `${modelDir}/encoder-epoch-99-avg-1.onnx`;
  44 + modelConfig.transducer.decoder = `${modelDir}/decoder-epoch-99-avg-1.onnx`;
  45 + modelConfig.transducer.joiner = `${modelDir}/joiner-epoch-99-avg-1.onnx`;
  46 + modelConfig.tokens = `${modelDir}/tokens.txt`;
  47 + modelConfig.modelType = 'lstm';
  48 + break;
  49 + }
  50 +
  51 + case 3: {
  52 + const modelDir = 'icefall-asr-zipformer-streaming-wenetspeech-20230615';
  53 + modelConfig.transducer.encoder = `${modelDir}/exp/encoder-epoch-12-avg-4-chunk-16-left-128.int8.onnx`;
  54 + modelConfig.transducer.decoder = `${modelDir}/exp/decoder-epoch-12-avg-4-chunk-16-left-128.onnx`;
  55 + modelConfig.transducer.joiner = `${modelDir}/exp/joiner-epoch-12-avg-4-chunk-16-left-128.onnx`;
  56 + modelConfig.tokens = `${modelDir}/data/lang_char/tokens.txt`;
  57 + modelConfig.modelType = 'zipformer2';
  58 + break;
  59 + }
  60 +
  61 + case 4: {
  62 + const modelDir = 'icefall-asr-zipformer-streaming-wenetspeech-20230615';
  63 + modelConfig.transducer.encoder = `${modelDir}/exp/encoder-epoch-12-avg-4-chunk-16-left-128.onnx`;
  64 + modelConfig.transducer.decoder = `${modelDir}/exp/decoder-epoch-12-avg-4-chunk-16-left-128.onnx`;
  65 + modelConfig.transducer.joiner = `${modelDir}/exp/joiner-epoch-12-avg-4-chunk-16-left-128.onnx`;
  66 + modelConfig.tokens = `${modelDir}/data/lang_char/tokens.txt`;
  67 + modelConfig.modelType = 'zipformer2';
  68 + break;
  69 + }
  70 +
  71 + case 5: {
  72 + const modelDir = 'sherpa-onnx-streaming-paraformer-bilingual-zh-en';
  73 + modelConfig.paraformer.encoder = `${modelDir}/encoder.int8.onnx`;
  74 + modelConfig.paraformer.decoder = `${modelDir}/decoder.int8.onnx`;
  75 + modelConfig.tokens = `${modelDir}/tokens.txt`;
  76 + modelConfig.modelType = 'paraformer';
  77 + break;
  78 + }
  79 +
  80 + case 6: {
  81 + const modelDir = 'sherpa-onnx-streaming-zipformer-en-2023-06-26';
  82 + modelConfig.transducer.encoder = `${modelDir}/encoder-epoch-99-avg-1-chunk-16-left-128.int8.onnx`;
  83 + modelConfig.transducer.decoder = `${modelDir}/decoder-epoch-99-avg-1-chunk-16-left-128.onnx`;
  84 + modelConfig.transducer.joiner = `${modelDir}/joiner-epoch-99-avg-1-chunk-16-left-128.onnx`;
  85 + modelConfig.tokens = `${modelDir}/tokens.txt`;
  86 + modelConfig.modelType = 'zipformer2';
  87 + break;
  88 + }
  89 +
  90 + case 7: {
  91 + const modelDir = 'sherpa-onnx-streaming-zipformer-fr-2023-04-14';
  92 + modelConfig.transducer.encoder = `${modelDir}/encoder-epoch-29-avg-9-with-averaged-model.int8.onnx`;
  93 + modelConfig.transducer.decoder = `${modelDir}/decoder-epoch-29-avg-9-with-averaged-model.onnx`;
  94 + modelConfig.transducer.joiner = `${modelDir}/joiner-epoch-29-avg-9-with-averaged-model.onnx`;
  95 + modelConfig.tokens = `${modelDir}/tokens.txt`;
  96 + modelConfig.modelType = 'zipformer';
  97 + break;
  98 + }
  99 +
  100 + case 8: {
  101 + const modelDir = 'sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20';
  102 + modelConfig.transducer.encoder = `${modelDir}/encoder-epoch-99-avg-1.int8.onnx`;
  103 + modelConfig.transducer.decoder = `${modelDir}/decoder-epoch-99-avg-1.onnx`;
  104 + modelConfig.transducer.joiner = `${modelDir}/joiner-epoch-99-avg-1.int8.onnx`;
  105 + modelConfig.tokens = `${modelDir}/tokens.txt`;
  106 + modelConfig.modelType = 'zipformer';
  107 + break;
  108 + }
  109 +
  110 + case 9: {
  111 + const modelDir = 'sherpa-onnx-streaming-zipformer-zh-14M-2023-02-23'
  112 + modelConfig.transducer.encoder = `${modelDir}/encoder-epoch-99-avg-1.int8.onnx`;
  113 + modelConfig.transducer.decoder = `${modelDir}/decoder-epoch-99-avg-1.onnx`;
  114 + modelConfig.transducer.joiner = `${modelDir}/joiner-epoch-99-avg-1.int8.onnx`;
  115 + modelConfig.tokens = `${modelDir}/tokens.txt`;
  116 + modelConfig.modelType = 'zipformer';
  117 + break;
  118 + }
  119 +
  120 + case 10: {
  121 + const modelDir = 'sherpa-onnx-streaming-zipformer-en-20M-2023-02-17';
  122 + modelConfig.transducer.encoder = `${modelDir}/encoder-epoch-99-avg-1.int8.onnx`;
  123 + modelConfig.transducer.decoder = `${modelDir}/decoder-epoch-99-avg-1.onnx`;
  124 + modelConfig.transducer.joiner = `${modelDir}/joiner-epoch-99-avg-1.int8.onnx`;
  125 + modelConfig.tokens = `${modelDir}/tokens.txt`;
  126 + modelConfig.modelType = 'zipformer';
  127 + break;
  128 + }
  129 +
  130 + case 14: {
  131 + const modelDir = 'sherpa-onnx-streaming-zipformer-korean-2024-06-16';
  132 + modelConfig.transducer.encoder = `${modelDir}/encoder-epoch-99-avg-1.int8.onnx`;
  133 + modelConfig.transducer.decoder = `${modelDir}/decoder-epoch-99-avg-1.onnx`;
  134 + modelConfig.transducer.joiner = `${modelDir}/joiner-epoch-99-avg-1.int8.onnx`;
  135 + modelConfig.tokens = `${modelDir}/tokens.txt`;
  136 + modelConfig.modelType = 'zipformer';
  137 + break;
  138 + }
  139 + default: {
  140 + console.log(`Please specify a supported type. Given type ${type}`);
  141 + }
  142 + }
  143 + return modelConfig;
  144 +}
  145 +
  146 +function initStreamingAsr(context: Context): OnlineRecognizer {
  147 + let type: number;
  148 +
  149 + /*
  150 +
  151 +If you use type = 8, then you should have the following directory structure in the rawfile directory
  152 +
  153 +(py38) fangjuns-MacBook-Pro:rawfile fangjun$ pwd
  154 +/Users/fangjun/open-source/sherpa-onnx/harmony-os/SherpaOnnxStreamingAsr/entry/src/main/resources/rawfile
  155 +(py38) fangjuns-MacBook-Pro:rawfile fangjun$ ls
  156 +sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20
  157 +(py38) fangjuns-MacBook-Pro:rawfile fangjun$ tree .
  158 +.
  159 +└── sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20
  160 + ├── decoder-epoch-99-avg-1.onnx
  161 + ├── encoder-epoch-99-avg-1.int8.onnx
  162 + ├── joiner-epoch-99-avg-1.int8.onnx
  163 + └── tokens.txt
  164 +
  165 +1 directory, 4 files
  166 +
  167 +You can download model files from
  168 +https://github.com/k2-fsa/sherpa-onnx/releases/tag/asr-models
  169 +
  170 +Note that please delete files that are not used. Otherwise, you APP will be very large
  171 +due to containing unused large files.
  172 +
  173 + */
  174 + type = 8;
  175 +
  176 + const config: OnlineRecognizerConfig = new OnlineRecognizerConfig();
  177 + config.modelConfig = getModelConfig(type);
  178 + config.modelConfig.debug = true;
  179 + config.modelConfig.numThreads = 2;
  180 + config.enableEndpoint = true;
  181 +
  182 + return new OnlineRecognizer(config, context.resourceManager);
  183 +}
  184 +
  185 +interface DecodeFileResult {
  186 + text: string;
  187 + duration: number;
  188 +}
  189 +
  190 +function decodeFile(filename: string): DecodeFileResult {
  191 + const fp = fileIo.openSync(filename);
  192 + const stat = fileIo.statSync(fp.fd);
  193 + const arrayBuffer = new ArrayBuffer(stat.size);
  194 + fileIo.readSync(fp.fd, arrayBuffer);
  195 + const data: Uint8Array = new Uint8Array(arrayBuffer);
  196 + const wave: Samples = readWaveFromBinary(data) as Samples;
  197 + console.log(`Sample rate: ${wave.sampleRate}`);
  198 +
  199 + const stream = recognizer.createStream();
  200 + stream.acceptWaveform(wave);
  201 + const tailPadding = new Float32Array(0.5 * wave.sampleRate);
  202 + tailPadding.fill(0);
  203 +
  204 + stream.acceptWaveform({ samples: tailPadding, sampleRate: wave.sampleRate });
  205 +
  206 + while (recognizer.isReady(stream)) {
  207 + recognizer.decode(stream);
  208 + }
  209 +
  210 + const audioDuration = wave.samples.length / wave.sampleRate;
  211 +
  212 + return { text: recognizer.getResult(stream).text, duration: audioDuration };
  213 +}
  214 +
  215 +/**
  216 + * Defines the event handler to be called when the worker thread receives a message sent by the host thread.
  217 + * The event handler is executed in the worker thread.
  218 + *
  219 + * @param e message data
  220 + */
  221 +workerPort.onmessage = (e: MessageEvents) => {
  222 + const msgType = e.data['msgType'] as string;
  223 +
  224 + if (msgType != 'streaming-asr-decode-mic-samples') {
  225 + console.log(`from the main thread, msg-type: ${msgType}`);
  226 + }
  227 +
  228 + if (msgType == 'init-streaming-asr' && !recognizer) {
  229 + console.log('initializing streaming ASR...');
  230 + const context = e.data['context'] as Context;
  231 + recognizer = initStreamingAsr(context);
  232 + console.log('streaming ASR is initialized. ');
  233 + workerPort.postMessage({ 'msgType': 'init-streaming-asr-done' });
  234 + }
  235 +
  236 + if (msgType == 'streaming-asr-decode-file') {
  237 + const filename = e.data['filename'] as string;
  238 + console.log(`decoding ${filename}`);
  239 + const result = decodeFile(filename);
  240 + workerPort.postMessage({
  241 + 'msgType': 'streaming-asr-decode-file-done', text: result.text, duration: result.duration
  242 + });
  243 + }
  244 +
  245 + if (msgType == 'streaming-asr-decode-mic-start') {
  246 + micStream = recognizer.createStream();
  247 + }
  248 +
  249 + if (msgType == 'streaming-asr-decode-mic-stop') { // nothing to do
  250 + }
  251 +
  252 + if (msgType == 'streaming-asr-decode-mic-samples') {
  253 + const samples = e.data['samples'] as Float32Array;
  254 + const sampleRate = e.data['sampleRate'] as number;
  255 +
  256 + micStream.acceptWaveform({ samples, sampleRate });
  257 + while (recognizer.isReady(micStream)) {
  258 + recognizer.decode(micStream);
  259 +
  260 + let isEndpoint = false;
  261 + let text = recognizer.getResult(micStream).text;
  262 +
  263 + if (recognizer.isEndpoint(micStream)) {
  264 + isEndpoint = true;
  265 + recognizer.reset(micStream);
  266 + }
  267 +
  268 + if (text.trim() != '') {
  269 + workerPort.postMessage({
  270 + 'msgType': 'streaming-asr-decode-mic-result', text: text, isEndpoint: isEndpoint,
  271 + });
  272 + }
  273 + }
  274 + }
  275 +
  276 +}
  277 +
  278 +/**
  279 + * Defines the event handler to be called when the worker receives a message that cannot be deserialized.
  280 + * The event handler is executed in the worker thread.
  281 + *
  282 + * @param e message data
  283 + */
  284 +workerPort.onmessageerror = (e: MessageEvents) => {
  285 +}
  286 +
  287 +/**
  288 + * Defines the event handler to be called when an exception occurs during worker execution.
  289 + * The event handler is executed in the worker thread.
  290 + *
  291 + * @param e error message
  292 + */
  293 +workerPort.onerror = (e: ErrorEvent) => {
  294 +}
  1 +{
  2 + "module": {
  3 + "name": "entry",
  4 + "type": "entry",
  5 + "description": "$string:module_desc",
  6 + "mainElement": "EntryAbility",
  7 + "deviceTypes": [
  8 + "phone",
  9 + "tablet",
  10 + "2in1"
  11 + ],
  12 + "deliveryWithInstall": true,
  13 + "installationFree": false,
  14 + "pages": "$profile:main_pages",
  15 + "abilities": [
  16 + {
  17 + "name": "EntryAbility",
  18 + "srcEntry": "./ets/entryability/EntryAbility.ets",
  19 + "description": "$string:EntryAbility_desc",
  20 + "icon": "$media:layered_image",
  21 + "label": "$string:EntryAbility_label",
  22 + "startWindowIcon": "$media:startIcon",
  23 + "startWindowBackground": "$color:start_window_background",
  24 + "exported": true,
  25 + "skills": [
  26 + {
  27 + "entities": [
  28 + "entity.system.home"
  29 + ],
  30 + "actions": [
  31 + "action.system.home"
  32 + ]
  33 + }
  34 + ]
  35 + }
  36 + ],
  37 + "extensionAbilities": [
  38 + {
  39 + "name": "EntryBackupAbility",
  40 + "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets",
  41 + "type": "backup",
  42 + "exported": false,
  43 + "metadata": [
  44 + {
  45 + "name": "ohos.extension.backup",
  46 + "resource": "$profile:backup_config"
  47 + }
  48 + ],
  49 + }
  50 + ],
  51 + "requestPermissions": [
  52 + {
  53 + "name": "ohos.permission.MICROPHONE",
  54 + "reason": "$string:mic_reason",
  55 + "usedScene": {
  56 + "abilities": [
  57 + "EntryAbility",
  58 + ],
  59 + "when": "inuse",
  60 + }
  61 + }
  62 + ]
  63 + }
  64 +}
  1 +{
  2 + "color": [
  3 + {
  4 + "name": "start_window_background",
  5 + "value": "#FFFFFF"
  6 + }
  7 + ]
  8 +}
  1 +{
  2 + "string": [
  3 + {
  4 + "name": "module_desc",
  5 + "value": "On-device real-time speech recognition with Next-gen Kaldi"
  6 + },
  7 + {
  8 + "name": "EntryAbility_desc",
  9 + "value": "On-device real-time speech recognition with Next-gen Kaldi"
  10 + },
  11 + {
  12 + "name": "EntryAbility_label",
  13 + "value": "Real-time ASR"
  14 + },
  15 + {
  16 + "name": "mic_reason",
  17 + "value": "access the microhone for on-device real-time speech recognition with Next-gen Kaldi"
  18 + }
  19 + ]
  20 +}
  1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 -960 960 960"><path d="m480-840 440 330-48 64-72-54v380H160v-380l-72 54-48-64zM294-478q0 53 57 113t129 125q72-65 129-125t57-113q0-44-30-73t-72-29q-26 0-47.5 10.5T480-542q-15-17-37.5-27.5T396-580q-42 0-72 29t-30 73m426 278v-360L480-740 240-560v360zm0 0H240z"/></svg>
  1 +<?xml version="1.0" standalone="no"?>
  2 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><rect width="24" height="24" opacity="0"></rect><g><path d="M6.22 1.01Q5.35 1.01 4.61 1.45Q3.86 1.9 3.42 2.64Q2.98 3.38 2.98 4.25L2.98 19.75Q2.98 20.64 3.42 21.38Q3.86 22.13 4.61 22.56Q5.35 22.99 6.22 22.99L17.76 22.99Q18.65 22.99 19.39 22.56Q20.14 22.13 20.58 21.38Q21.02 20.64 21.02 19.75L21.02 7.25L14.76 1.01L6.22 1.01ZM15.48 7.25Q15.17 7.25 14.95 7.02Q14.74 6.79 14.74 6.48L14.74 3.1L18.89 7.25L15.48 7.25ZM6.22 21.5Q5.5 21.5 4.98 20.99Q4.46 20.47 4.46 19.75L4.46 4.25Q4.46 3.53 4.98 3.01Q5.5 2.5 6.22 2.5L13.22 2.5L13.22 6.48Q13.22 7.1 13.52 7.62Q13.82 8.14 14.34 8.44Q14.86 8.74 15.48 8.74L19.51 8.74L19.51 19.75Q19.51 20.47 19 20.99Q18.48 21.5 17.76 21.5L6.22 21.5Z" fill="rgba(0,0,0,0.9019607843137255)"></path></g></svg>
  1 +<?xml version="1.0" standalone="no"?>
  2 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><rect width="24" height="24" opacity="0"></rect><g><path d="M12 3.46Q13.06 3.46 13.78 4.18Q14.5 4.9 14.5 5.95L14.5 11.21Q14.5 12.24 13.78 12.97Q13.06 13.7 12 13.7Q10.97 13.7 10.24 12.97Q9.5 12.24 9.5 11.21L9.5 5.95Q9.5 4.9 10.24 4.18Q10.97 3.46 12 3.46ZM12 1.94Q10.92 1.94 10 2.48Q9.07 3.02 8.53 3.95Q7.99 4.87 7.99 5.95L7.99 11.21Q7.99 12.29 8.53 13.21Q9.07 14.14 10 14.68Q10.92 15.22 12 15.22Q13.08 15.22 14 14.68Q14.93 14.14 15.47 13.21Q16.01 12.29 16.01 11.21L16.01 5.95Q16.01 4.87 15.47 3.95Q14.93 3.02 14 2.48Q13.08 1.94 12 1.94ZM19.51 11.23Q19.51 10.92 19.28 10.69Q19.06 10.46 18.74 10.46Q18.43 10.46 18.22 10.69Q18 10.92 18 11.23Q18 12.84 17.2 14.22Q16.39 15.6 15.01 16.4Q13.63 17.21 12 17.21Q10.37 17.21 8.99 16.4Q7.61 15.6 6.8 14.22Q6 12.84 6 11.23Q6 10.92 5.78 10.69Q5.57 10.46 5.26 10.46Q4.94 10.46 4.73 10.69Q4.51 10.92 4.51 11.23Q4.51 13.13 5.4 14.76Q6.29 16.39 7.84 17.44Q9.38 18.48 11.26 18.67L11.26 21.29Q11.26 21.6 11.47 21.82Q11.69 22.03 12 22.03Q12.31 22.03 12.53 21.82Q12.74 21.6 12.74 21.29L12.74 18.67Q14.62 18.48 16.16 17.44Q17.71 16.39 18.61 14.76Q19.51 13.13 19.51 11.23Z" fill="rgba(0,0,0,0.9019607843137255)"></path></g></svg>
  1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 -960 960 960"><path d="M440-280h80v-240h-80zm40-320q17 0 28.5-11.5T520-640t-11.5-28.5T480-680t-28.5 11.5T440-640t11.5 28.5T480-600m0 520q-83 0-156-31.5T197-197t-85.5-127T80-480t31.5-156T197-763t127-85.5T480-880t156 31.5T763-763t85.5 127T880-480t-31.5 156T763-197t-127 85.5T480-80m0-80q134 0 227-93t93-227-93-227-227-93-227 93-93 227 93 227 227 93m0-320"/></svg>
  1 +{
  2 + "layered-image":
  3 + {
  4 + "background" : "$media:background",
  5 + "foreground" : "$media:foreground"
  6 + }
  7 +}
  1 +{
  2 + "string": [
  3 + {
  4 + "name": "module_desc",
  5 + "value": "On-device real-time speech recognition with Next-gen Kaldi"
  6 + },
  7 + {
  8 + "name": "EntryAbility_desc",
  9 + "value": "On-device real-time speech recognition with Next-gen Kaldi"
  10 + },
  11 + {
  12 + "name": "EntryAbility_label",
  13 + "value": "Real-time ASR"
  14 + },
  15 + {
  16 + "name": "mic_reason",
  17 + "value": "access the microhone for on-device real-time speech recognition with Next-gen Kaldi"
  18 + }
  19 + ]
  20 +}
  1 +{
  2 + "string": [
  3 + {
  4 + "name": "module_desc",
  5 + "value": "新一代Kaldi: 本地实时语音识别"
  6 + },
  7 + {
  8 + "name": "EntryAbility_desc",
  9 + "value": "新一代Kaldi: 本地实时语音识别"
  10 + },
  11 + {
  12 + "name": "EntryAbility_label",
  13 + "value": "实时语音识别"
  14 + },
  15 + {
  16 + "name": "mic_reason",
  17 + "value": "使用新一代Kaldi, 访问麦克风进行本地实时语音识别 (不需要联网)"
  18 + }
  19 + ]
  20 +}
  1 +import hilog from '@ohos.hilog';
  2 +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
  3 +
  4 +export default function abilityTest() {
  5 + describe('ActsAbilityTest', () => {
  6 + // Defines a test suite. Two parameters are supported: test suite name and test suite function.
  7 + beforeAll(() => {
  8 + // Presets an action, which is performed only once before all test cases of the test suite start.
  9 + // This API supports only one parameter: preset action function.
  10 + })
  11 + beforeEach(() => {
  12 + // Presets an action, which is performed before each unit test case starts.
  13 + // The number of execution times is the same as the number of test cases defined by **it**.
  14 + // This API supports only one parameter: preset action function.
  15 + })
  16 + afterEach(() => {
  17 + // Presets a clear action, which is performed after each unit test case ends.
  18 + // The number of execution times is the same as the number of test cases defined by **it**.
  19 + // This API supports only one parameter: clear action function.
  20 + })
  21 + afterAll(() => {
  22 + // Presets a clear action, which is performed after all test cases of the test suite end.
  23 + // This API supports only one parameter: clear action function.
  24 + })
  25 + it('assertContain', 0, () => {
  26 + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function.
  27 + hilog.info(0x0000, 'testTag', '%{public}s', 'it begin');
  28 + let a = 'abc';
  29 + let b = 'b';
  30 + // Defines a variety of assertion methods, which are used to declare expected boolean conditions.
  31 + expect(a).assertContain(b);
  32 + expect(a).assertEqual(a);
  33 + })
  34 + })
  35 +}
  1 +import abilityTest from './Ability.test';
  2 +
  3 +export default function testsuite() {
  4 + abilityTest();
  5 +}
  1 +{
  2 + "module": {
  3 + "name": "entry_test",
  4 + "type": "feature",
  5 + "deviceTypes": [
  6 + "phone",
  7 + "tablet",
  8 + "2in1"
  9 + ],
  10 + "deliveryWithInstall": true,
  11 + "installationFree": false
  12 + }
  13 +}
  1 +import localUnitTest from './LocalUnit.test';
  2 +
  3 +export default function testsuite() {
  4 + localUnitTest();
  5 +}
  1 +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
  2 +
  3 +export default function localUnitTest() {
  4 + describe('localUnitTest', () => {
  5 + // Defines a test suite. Two parameters are supported: test suite name and test suite function.
  6 + beforeAll(() => {
  7 + // Presets an action, which is performed only once before all test cases of the test suite start.
  8 + // This API supports only one parameter: preset action function.
  9 + });
  10 + beforeEach(() => {
  11 + // Presets an action, which is performed before each unit test case starts.
  12 + // The number of execution times is the same as the number of test cases defined by **it**.
  13 + // This API supports only one parameter: preset action function.
  14 + });
  15 + afterEach(() => {
  16 + // Presets a clear action, which is performed after each unit test case ends.
  17 + // The number of execution times is the same as the number of test cases defined by **it**.
  18 + // This API supports only one parameter: clear action function.
  19 + });
  20 + afterAll(() => {
  21 + // Presets a clear action, which is performed after all test cases of the test suite end.
  22 + // This API supports only one parameter: clear action function.
  23 + });
  24 + it('assertContain', 0, () => {
  25 + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function.
  26 + let a = 'abc';
  27 + let b = 'b';
  28 + // Defines a variety of assertion methods, which are used to declare expected boolean conditions.
  29 + expect(a).assertContain(b);
  30 + expect(a).assertEqual(a);
  31 + });
  32 + });
  33 +}
  1 +{
  2 + "modelVersion": "5.0.0",
  3 + "dependencies": {
  4 + },
  5 + "execution": {
  6 + // "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | false ]. Default: "normal" */
  7 + // "daemon": true, /* Enable daemon compilation. Value: [ true | false ]. Default: true */
  8 + // "incremental": true, /* Enable incremental compilation. Value: [ true | false ]. Default: true */
  9 + // "parallel": true, /* Enable parallel compilation. Value: [ true | false ]. Default: true */
  10 + // "typeCheck": false, /* Enable typeCheck. Value: [ true | false ]. Default: false */
  11 + },
  12 + "logging": {
  13 + // "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */
  14 + },
  15 + "debugging": {
  16 + // "stacktrace": false /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */
  17 + },
  18 + "nodeOptions": {
  19 + // "maxOldSpaceSize": 8192 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process. Default: 8192*/
  20 + // "exposeGC": true /* Enable to trigger garbage collection explicitly. Default: true*/
  21 + }
  22 +}
  1 +import { appTasks } from '@ohos/hvigor-ohos-plugin';
  2 +
  3 +export default {
  4 + system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
  5 + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */
  6 +}
  1 +{
  2 + "meta": {
  3 + "stableOrder": true
  4 + },
  5 + "lockfileVersion": 3,
  6 + "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.",
  7 + "specifiers": {
  8 + "@ohos/hypium@1.0.19": "@ohos/hypium@1.0.19"
  9 + },
  10 + "packages": {
  11 + "@ohos/hypium@1.0.19": {
  12 + "name": "@ohos/hypium",
  13 + "version": "1.0.19",
  14 + "integrity": "sha512-cEjDgLFCm3cWZDeRXk7agBUkPqjWxUo6AQeiu0gEkb3J8ESqlduQLSIXeo3cCsm8U/asL7iKjF85ZyOuufAGSQ==",
  15 + "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/hypium/-/hypium-1.0.19.har",
  16 + "registryType": "ohpm"
  17 + }
  18 + }
  19 +}
  1 +{
  2 + "modelVersion": "5.0.0",
  3 + "description": "Please describe the basic information.",
  4 + "dependencies": {
  5 + },
  6 + "devDependencies": {
  7 + "@ohos/hypium": "1.0.19"
  8 + }
  9 +}
@@ -229,7 +229,7 @@ struct Index { @@ -229,7 +229,7 @@ struct Index {
229 .lineSpacing({ value: 10, unit: LengthUnit.VP }) 229 .lineSpacing({ value: 10, unit: LengthUnit.VP })
230 .height('100%'); 230 .height('100%');
231 }.alignItems(HorizontalAlign.Center).justifyContent(FlexAlign.Start) 231 }.alignItems(HorizontalAlign.Center).justifyContent(FlexAlign.Start)
232 - }.tabBar(this.TabBuilder('From file', 0, $r('app.media.icon_doc'), $r('app.media.icon_doc_default'))) 232 + }.tabBar(this.TabBuilder('From file', 0, $r('app.media.icon_doc'), $r('app.media.icon_doc')))
233 233
234 TabContent() { 234 TabContent() {
235 Column({ space: 10 }) { 235 Column({ space: 10 }) {
@@ -278,8 +278,8 @@ struct Index { @@ -278,8 +278,8 @@ struct Index {
278 .height('100%'); 278 .height('100%');
279 }.alignItems(HorizontalAlign.Center).justifyContent(FlexAlign.Start) 279 }.alignItems(HorizontalAlign.Center).justifyContent(FlexAlign.Start)
280 } 280 }
281 - .tabBar(this.TabBuilder('From mic', 1, $r('app.media.ic_public_input_voice'),  
282 - $r('app.media.ic_public_input_voice_default'))) 281 + .tabBar(this.TabBuilder('From mic', 1, $r('app.media.icon_mic'),
  282 + $r('app.media.icon_mic')))
283 283
284 TabContent() { 284 TabContent() {
285 Column({ space: 10 }) { 285 Column({ space: 10 }) {
@@ -300,7 +300,7 @@ https://k2-fsa.github.io/sherpa/social-groups.html @@ -300,7 +300,7 @@ https://k2-fsa.github.io/sherpa/social-groups.html
300 ` 300 `
301 }).width('100%').height('100%').focusable(false) 301 }).width('100%').height('100%').focusable(false)
302 }.justifyContent(FlexAlign.Start) 302 }.justifyContent(FlexAlign.Start)
303 - }.tabBar(this.TabBuilder('Help', 2, $r('app.media.info_circle'), $r('app.media.info_circle_default'))) 303 + }.tabBar(this.TabBuilder('Help', 2, $r('app.media.info'), $r('app.media.info')))
304 304
305 }.scrollable(false) 305 }.scrollable(false)
306 }.width('100%').justifyContent(FlexAlign.Start) 306 }.width('100%').justifyContent(FlexAlign.Start)
  1 +<?xml version="1.0" standalone="no"?>
  2 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><rect width="24" height="24" opacity="0"></rect><g><path d="M6.22 1.01Q5.35 1.01 4.61 1.45Q3.86 1.9 3.42 2.64Q2.98 3.38 2.98 4.25L2.98 19.75Q2.98 20.64 3.42 21.38Q3.86 22.13 4.61 22.56Q5.35 22.99 6.22 22.99L17.76 22.99Q18.65 22.99 19.39 22.56Q20.14 22.13 20.58 21.38Q21.02 20.64 21.02 19.75L21.02 7.25L14.76 1.01L6.22 1.01ZM15.48 7.25Q15.17 7.25 14.95 7.02Q14.74 6.79 14.74 6.48L14.74 3.1L18.89 7.25L15.48 7.25ZM6.22 21.5Q5.5 21.5 4.98 20.99Q4.46 20.47 4.46 19.75L4.46 4.25Q4.46 3.53 4.98 3.01Q5.5 2.5 6.22 2.5L13.22 2.5L13.22 6.48Q13.22 7.1 13.52 7.62Q13.82 8.14 14.34 8.44Q14.86 8.74 15.48 8.74L19.51 8.74L19.51 19.75Q19.51 20.47 19 20.99Q18.48 21.5 17.76 21.5L6.22 21.5Z" fill="rgba(0,0,0,0.9019607843137255)"></path></g></svg>
  1 +<?xml version="1.0" standalone="no"?>
  2 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><rect width="24" height="24" opacity="0"></rect><g><path d="M12 3.46Q13.06 3.46 13.78 4.18Q14.5 4.9 14.5 5.95L14.5 11.21Q14.5 12.24 13.78 12.97Q13.06 13.7 12 13.7Q10.97 13.7 10.24 12.97Q9.5 12.24 9.5 11.21L9.5 5.95Q9.5 4.9 10.24 4.18Q10.97 3.46 12 3.46ZM12 1.94Q10.92 1.94 10 2.48Q9.07 3.02 8.53 3.95Q7.99 4.87 7.99 5.95L7.99 11.21Q7.99 12.29 8.53 13.21Q9.07 14.14 10 14.68Q10.92 15.22 12 15.22Q13.08 15.22 14 14.68Q14.93 14.14 15.47 13.21Q16.01 12.29 16.01 11.21L16.01 5.95Q16.01 4.87 15.47 3.95Q14.93 3.02 14 2.48Q13.08 1.94 12 1.94ZM19.51 11.23Q19.51 10.92 19.28 10.69Q19.06 10.46 18.74 10.46Q18.43 10.46 18.22 10.69Q18 10.92 18 11.23Q18 12.84 17.2 14.22Q16.39 15.6 15.01 16.4Q13.63 17.21 12 17.21Q10.37 17.21 8.99 16.4Q7.61 15.6 6.8 14.22Q6 12.84 6 11.23Q6 10.92 5.78 10.69Q5.57 10.46 5.26 10.46Q4.94 10.46 4.73 10.69Q4.51 10.92 4.51 11.23Q4.51 13.13 5.4 14.76Q6.29 16.39 7.84 17.44Q9.38 18.48 11.26 18.67L11.26 21.29Q11.26 21.6 11.47 21.82Q11.69 22.03 12 22.03Q12.31 22.03 12.53 21.82Q12.74 21.6 12.74 21.29L12.74 18.67Q14.62 18.48 16.16 17.44Q17.71 16.39 18.61 14.76Q19.51 13.13 19.51 11.23Z" fill="rgba(0,0,0,0.9019607843137255)"></path></g></svg>
  1 +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 -960 960 960"><path d="M440-280h80v-240h-80zm40-320q17 0 28.5-11.5T520-640t-11.5-28.5T480-680t-28.5 11.5T440-640t11.5 28.5T480-600m0 520q-83 0-156-31.5T197-197t-85.5-127T80-480t31.5-156T197-763t127-85.5T480-880t156 31.5T763-763t85.5 127T880-480t-31.5 156T763-197t-127 85.5T480-80m0-80q134 0 227-93t93-227-93-227-227-93-227 93-93 227 93 227 227 93m0-320"/></svg>
@@ -118,15 +118,24 @@ void OnlineZipformerTransducerModel::InitEncoder(void *model_data, @@ -118,15 +118,24 @@ void OnlineZipformerTransducerModel::InitEncoder(void *model_data,
118 for (auto i : v) { 118 for (auto i : v) {
119 os << i << " "; 119 os << i << " ";
120 } 120 }
  121 +#if __OHOS__
  122 + SHERPA_ONNX_LOGE("%{public}s\n", os.str().c_str());
  123 +#else
121 SHERPA_ONNX_LOGE("%s\n", os.str().c_str()); 124 SHERPA_ONNX_LOGE("%s\n", os.str().c_str());
  125 +#endif
122 }; 126 };
123 print(encoder_dims_, "encoder_dims"); 127 print(encoder_dims_, "encoder_dims");
124 print(attention_dims_, "attention_dims"); 128 print(attention_dims_, "attention_dims");
125 print(num_encoder_layers_, "num_encoder_layers"); 129 print(num_encoder_layers_, "num_encoder_layers");
126 print(cnn_module_kernels_, "cnn_module_kernels"); 130 print(cnn_module_kernels_, "cnn_module_kernels");
127 print(left_context_len_, "left_context_len"); 131 print(left_context_len_, "left_context_len");
  132 +#if __OHOS__
  133 + SHERPA_ONNX_LOGE("T: %{public}d", T_);
  134 + SHERPA_ONNX_LOGE("decode_chunk_len_: %{public}d", decode_chunk_len_);
  135 +#else
128 SHERPA_ONNX_LOGE("T: %d", T_); 136 SHERPA_ONNX_LOGE("T: %d", T_);
129 SHERPA_ONNX_LOGE("decode_chunk_len_: %d", decode_chunk_len_); 137 SHERPA_ONNX_LOGE("decode_chunk_len_: %d", decode_chunk_len_);
  138 +#endif
130 } 139 }
131 } 140 }
132 141
@@ -147,7 +156,11 @@ void OnlineZipformerTransducerModel::InitDecoder(void *model_data, @@ -147,7 +156,11 @@ void OnlineZipformerTransducerModel::InitDecoder(void *model_data,
147 std::ostringstream os; 156 std::ostringstream os;
148 os << "---decoder---\n"; 157 os << "---decoder---\n";
149 PrintModelMetadata(os, meta_data); 158 PrintModelMetadata(os, meta_data);
  159 +#if __OHOS__
  160 + SHERPA_ONNX_LOGE("%{public}s", os.str().c_str());
  161 +#else
150 SHERPA_ONNX_LOGE("%s", os.str().c_str()); 162 SHERPA_ONNX_LOGE("%s", os.str().c_str());
  163 +#endif
151 } 164 }
152 165
153 Ort::AllocatorWithDefaultOptions allocator; // used in the macro below 166 Ort::AllocatorWithDefaultOptions allocator; // used in the macro below
@@ -172,7 +185,11 @@ void OnlineZipformerTransducerModel::InitJoiner(void *model_data, @@ -172,7 +185,11 @@ void OnlineZipformerTransducerModel::InitJoiner(void *model_data,
172 std::ostringstream os; 185 std::ostringstream os;
173 os << "---joiner---\n"; 186 os << "---joiner---\n";
174 PrintModelMetadata(os, meta_data); 187 PrintModelMetadata(os, meta_data);
  188 +#if __OHOS__
  189 + SHERPA_ONNX_LOGE("%{public}s", os.str().c_str());
  190 +#else
175 SHERPA_ONNX_LOGE("%s", os.str().c_str()); 191 SHERPA_ONNX_LOGE("%s", os.str().c_str());
  192 +#endif
176 } 193 }
177 } 194 }
178 195
@@ -126,7 +126,11 @@ void OnlineZipformer2TransducerModel::InitEncoder(void *model_data, @@ -126,7 +126,11 @@ void OnlineZipformer2TransducerModel::InitEncoder(void *model_data,
126 for (auto i : v) { 126 for (auto i : v) {
127 os << i << " "; 127 os << i << " ";
128 } 128 }
  129 +#if __OHOS__
  130 + SHERPA_ONNX_LOGE("%{public}s\n", os.str().c_str());
  131 +#else
129 SHERPA_ONNX_LOGE("%s\n", os.str().c_str()); 132 SHERPA_ONNX_LOGE("%s\n", os.str().c_str());
  133 +#endif
130 }; 134 };
131 print(encoder_dims_, "encoder_dims"); 135 print(encoder_dims_, "encoder_dims");
132 print(query_head_dims_, "query_head_dims"); 136 print(query_head_dims_, "query_head_dims");
@@ -135,8 +139,14 @@ void OnlineZipformer2TransducerModel::InitEncoder(void *model_data, @@ -135,8 +139,14 @@ void OnlineZipformer2TransducerModel::InitEncoder(void *model_data,
135 print(num_encoder_layers_, "num_encoder_layers"); 139 print(num_encoder_layers_, "num_encoder_layers");
136 print(cnn_module_kernels_, "cnn_module_kernels"); 140 print(cnn_module_kernels_, "cnn_module_kernels");
137 print(left_context_len_, "left_context_len"); 141 print(left_context_len_, "left_context_len");
  142 +
  143 +#if __OHOS__
  144 + SHERPA_ONNX_LOGE("T: %{public}d", T_);
  145 + SHERPA_ONNX_LOGE("decode_chunk_len_: %{public}d", decode_chunk_len_);
  146 +#else
138 SHERPA_ONNX_LOGE("T: %d", T_); 147 SHERPA_ONNX_LOGE("T: %d", T_);
139 SHERPA_ONNX_LOGE("decode_chunk_len_: %d", decode_chunk_len_); 148 SHERPA_ONNX_LOGE("decode_chunk_len_: %d", decode_chunk_len_);
  149 +#endif
140 } 150 }
141 } 151 }
142 152