Committed by
GitHub
Add speaker identification demo for HarmonyOS (#1608)
正在显示
47 个修改的文件
包含
1048 行增加
和
5 行删除
| @@ -13,5 +13,8 @@ | @@ -13,5 +13,8 @@ | ||
| 13 | - [./SherpaOnnxStreamingAsr](./SherpaOnnxStreamingAsr) It shows how to use | 13 | - [./SherpaOnnxStreamingAsr](./SherpaOnnxStreamingAsr) It shows how to use |
| 14 | streaming ASR models for real-time on-device speech recognition. | 14 | streaming ASR models for real-time on-device speech recognition. |
| 15 | 15 | ||
| 16 | +- [./SherpaOnnxSpeakerIdentification](./SherpaOnnxSpeakerIdentification) It shows how to use | ||
| 17 | + speaker embedding models for on-device speaker identification. | ||
| 18 | + | ||
| 16 | - [./SherpaOnnxTts](./SherpaOnnxTts) It shows how to run on-device text-to-speech. | 19 | - [./SherpaOnnxTts](./SherpaOnnxTts) It shows how to run on-device text-to-speech. |
| 17 | Please see the doc at <https://k2-fsa.github.io/sherpa/onnx/harmony-os/tts.html> | 20 | Please see the doc at <https://k2-fsa.github.io/sherpa/onnx/harmony-os/tts.html> |
| @@ -764,7 +764,7 @@ static Napi::Array SpeakerEmbeddingManagerGetAllSpeakersWrapper( | @@ -764,7 +764,7 @@ static Napi::Array SpeakerEmbeddingManagerGetAllSpeakersWrapper( | ||
| 764 | 764 | ||
| 765 | int32_t num_speakers = SherpaOnnxSpeakerEmbeddingManagerNumSpeakers(manager); | 765 | int32_t num_speakers = SherpaOnnxSpeakerEmbeddingManagerNumSpeakers(manager); |
| 766 | if (num_speakers == 0) { | 766 | if (num_speakers == 0) { |
| 767 | - return {}; | 767 | + return Napi::Array::New(env, num_speakers); |
| 768 | } | 768 | } |
| 769 | 769 | ||
| 770 | const char *const *all_speaker_names = | 770 | const char *const *all_speaker_names = |
2.7 KB
| 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 | +{ | ||
| 2 | + "apiType": "stageMode", | ||
| 3 | + "buildOption": { | ||
| 4 | + "sourceOption": { | ||
| 5 | + "workers": [ | ||
| 6 | + './src/main/ets/workers/SpeakerIdentificationWorker.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 | +# 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 |
harmony-os/SherpaOnnxSpeakerIdentification/entry/src/main/ets/entryability/EntryAbility.ets
0 → 100644
| 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 worker, { MessageEvents } from '@ohos.worker'; | ||
| 2 | +import { audio } from '@kit.AudioKit'; | ||
| 3 | +import { allAllowed, requestPermissions } from './Permission'; | ||
| 4 | +import { Permissions } from '@kit.AbilityKit'; | ||
| 5 | +import { picker } from '@kit.CoreFileKit'; | ||
| 6 | +import fs from '@ohos.file.fs'; | ||
| 7 | + | ||
| 8 | + | ||
| 9 | + | ||
| 10 | +function flatten(samples: Float32Array[]): Float32Array { | ||
| 11 | + let n = 0; | ||
| 12 | + for (let i = 0; i < samples.length; ++i) { | ||
| 13 | + n += samples[i].length; | ||
| 14 | + } | ||
| 15 | + | ||
| 16 | + const ans: Float32Array = new Float32Array(n); | ||
| 17 | + let offset: number = 0; | ||
| 18 | + for (let i = 0; i < samples.length; ++i) { | ||
| 19 | + ans.set(samples[i], offset); | ||
| 20 | + offset += samples[i].length; | ||
| 21 | + } | ||
| 22 | + | ||
| 23 | + return ans; | ||
| 24 | +} | ||
| 25 | + | ||
| 26 | +function savePcmToWav(filename: string, samples: Int16Array, sampleRate: number) { | ||
| 27 | + const fp = fs.openSync(filename, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE); | ||
| 28 | + | ||
| 29 | + const header = new ArrayBuffer(44); | ||
| 30 | + const view = new DataView(header); | ||
| 31 | + | ||
| 32 | + // http://soundfile.sapp.org/doc/WaveFormat/ | ||
| 33 | + // F F I R | ||
| 34 | + view.setUint32(0, 0x46464952, true); // chunkID | ||
| 35 | + view.setUint32(4, 36 + samples.length * 2, true); // chunkSize // E V A W | ||
| 36 | + view.setUint32(8, 0x45564157, true); // format // // t m f | ||
| 37 | + view.setUint32(12, 0x20746d66, true); // subchunk1ID | ||
| 38 | + view.setUint32(16, 16, true); // subchunk1Size, 16 for PCM | ||
| 39 | + view.setUint32(20, 1, true); // audioFormat, 1 for PCM | ||
| 40 | + view.setUint16(22, 1, true); // numChannels: 1 channel | ||
| 41 | + view.setUint32(24, sampleRate, true); // sampleRate | ||
| 42 | + view.setUint32(28, sampleRate * 2, true); // byteRate | ||
| 43 | + view.setUint16(32, 2, true); // blockAlign | ||
| 44 | + view.setUint16(34, 16, true); // bitsPerSample | ||
| 45 | + view.setUint32(36, 0x61746164, true); // Subchunk2ID | ||
| 46 | + view.setUint32(40, samples.length * 2, true); // subchunk2Size | ||
| 47 | + | ||
| 48 | + fs.writeSync(fp.fd, new Uint8Array(header).buffer, { length: header.byteLength }); | ||
| 49 | + fs.writeSync(fp.fd, samples.buffer, { length: samples.buffer.byteLength }); | ||
| 50 | + | ||
| 51 | + fs.closeSync(fp.fd); | ||
| 52 | +} | ||
| 53 | + | ||
| 54 | +function toInt16Samples(samples: Float32Array): Int16Array { | ||
| 55 | + const int16Samples = new Int16Array(samples.length); | ||
| 56 | + for (let i = 0; i < samples.length; ++i) { | ||
| 57 | + let s = samples[i] * 32767; | ||
| 58 | + s = s > 32767 ? 32767 : s; | ||
| 59 | + s = s < -32768 ? -32768 : s; | ||
| 60 | + int16Samples[i] = s; | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + return int16Samples; | ||
| 64 | +} | ||
| 65 | + | ||
| 66 | +@Entry | ||
| 67 | +@Component | ||
| 68 | +struct Index { | ||
| 69 | + @State title: string = 'Next-gen Kaldi: Speaker Identification'; | ||
| 70 | + @State titleFontSize: number = 18; | ||
| 71 | + private controller: TabsController = new TabsController(); | ||
| 72 | + | ||
| 73 | + @State currentIndex: number = 0; | ||
| 74 | + | ||
| 75 | + @State message: string = 'Hello World'; | ||
| 76 | + | ||
| 77 | + private workerInstance?: worker.ThreadWorker | ||
| 78 | + private readonly scriptURL: string = 'entry/ets/workers/SpeakerIdentificationWorker.ets' | ||
| 79 | + | ||
| 80 | + @State allSpeakerNames: string[] = []; | ||
| 81 | + private inputSpeakerName: string = ''; | ||
| 82 | + | ||
| 83 | + @State btnSaveAudioEnabled: boolean = false; | ||
| 84 | + @State btnAddEnabled: boolean = false; | ||
| 85 | + | ||
| 86 | + private sampleRate: number = 16000; | ||
| 87 | + private sampleList: Float32Array[] = [] | ||
| 88 | + private mic?: audio.AudioCapturer; | ||
| 89 | + | ||
| 90 | + @State infoHome: string = ''; | ||
| 91 | + @State infoAdd: string = ''; | ||
| 92 | + | ||
| 93 | + @State micBtnCaption: string = 'Start recording'; | ||
| 94 | + @State micStarted: boolean = false; | ||
| 95 | + | ||
| 96 | + async initMic() { | ||
| 97 | + const permissions: Permissions[] = ["ohos.permission.MICROPHONE"]; | ||
| 98 | + let allowed: boolean = await allAllowed(permissions); | ||
| 99 | + if (!allowed) { | ||
| 100 | + console.log("request to access the microphone"); | ||
| 101 | + const status: boolean = await requestPermissions(permissions); | ||
| 102 | + | ||
| 103 | + if (!status) { | ||
| 104 | + console.error('access to microphone is denied') | ||
| 105 | + this.infoHome = "Failed to get microphone permission. Please retry"; | ||
| 106 | + this.infoAdd = this.infoHome; | ||
| 107 | + return; | ||
| 108 | + } | ||
| 109 | + | ||
| 110 | + allowed = await allAllowed(permissions); | ||
| 111 | + if (!allowed) { | ||
| 112 | + console.error('failed to get microphone permission'); | ||
| 113 | + this.infoHome = "Failed to get microphone permission. Please retry"; | ||
| 114 | + this.infoAdd = this.infoHome; | ||
| 115 | + return; | ||
| 116 | + } | ||
| 117 | + } else { | ||
| 118 | + console.log("allowed to access microphone"); | ||
| 119 | + } | ||
| 120 | + | ||
| 121 | + const audioStreamInfo: audio.AudioStreamInfo = { | ||
| 122 | + samplingRate: this.sampleRate, | ||
| 123 | + channels: audio.AudioChannel.CHANNEL_1, | ||
| 124 | + sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, | ||
| 125 | + encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW, | ||
| 126 | + }; | ||
| 127 | + | ||
| 128 | + const audioCapturerInfo: audio.AudioCapturerInfo = { | ||
| 129 | + source: audio.SourceType.SOURCE_TYPE_MIC, capturerFlags: 0 | ||
| 130 | + }; | ||
| 131 | + | ||
| 132 | + const audioCapturerOptions: audio.AudioCapturerOptions = { | ||
| 133 | + streamInfo: audioStreamInfo, capturerInfo: audioCapturerInfo | ||
| 134 | + | ||
| 135 | + }; | ||
| 136 | + audio.createAudioCapturer(audioCapturerOptions, (err, data) => { | ||
| 137 | + if (err) { | ||
| 138 | + console.error(`error code is ${err.code}, error message is ${err.message}`); | ||
| 139 | + this.infoHome = 'Failed to init microphone'; | ||
| 140 | + this.infoAdd = this.infoHome; | ||
| 141 | + } else { | ||
| 142 | + console.info(`init mic successfully`); | ||
| 143 | + this.mic = data; | ||
| 144 | + this.mic.on('readData', this.micCallback); | ||
| 145 | + } | ||
| 146 | + }); | ||
| 147 | + } | ||
| 148 | + | ||
| 149 | + async aboutToAppear() { | ||
| 150 | + this.workerInstance = new worker.ThreadWorker(this.scriptURL, { | ||
| 151 | + name: 'Speaker identification worker' | ||
| 152 | + }); | ||
| 153 | + | ||
| 154 | + this.workerInstance.onmessage = (e: MessageEvents) => { | ||
| 155 | + const msgType = e.data['msgType'] as string; | ||
| 156 | + console.log(`received msg from worker: ${msgType}`); | ||
| 157 | + | ||
| 158 | + if (msgType == 'manager-all-speaker-names') { | ||
| 159 | + this.allSpeakerNames = e.data['allSpeakers'] as string[]; | ||
| 160 | + } | ||
| 161 | + }; | ||
| 162 | + | ||
| 163 | + this.workerInstance.postMessage({ msgType: 'init-extractor', context: getContext()}); | ||
| 164 | + | ||
| 165 | + await this.initMic(); | ||
| 166 | + } | ||
| 167 | + | ||
| 168 | + @Builder | ||
| 169 | + TabBuilder(title: string, targetIndex: number, selectedImg: Resource, normalImg: Resource) { | ||
| 170 | + Column() { | ||
| 171 | + Image(this.currentIndex == targetIndex ? selectedImg : normalImg).size({ width: 25, height: 25 }) | ||
| 172 | + Text(title).fontColor(this.currentIndex == targetIndex ? '#28bff1' : '#8a8a8a') | ||
| 173 | + }.width('100%').height(50).justifyContent(FlexAlign.Center).onClick(() => { | ||
| 174 | + this.currentIndex = targetIndex; | ||
| 175 | + this.controller.changeIndex(this.currentIndex); | ||
| 176 | + }) | ||
| 177 | + } | ||
| 178 | + | ||
| 179 | + build() { | ||
| 180 | + Column() { | ||
| 181 | + Tabs({ barPosition: BarPosition.End, controller: this.controller }) { | ||
| 182 | + TabContent() { | ||
| 183 | + Column({ space: 10 }) { | ||
| 184 | + Button('Home') | ||
| 185 | + } | ||
| 186 | + }.tabBar(this.TabBuilder('Home', 0, $r('app.media.icon_home'), $r('app.media.icon_home'))) | ||
| 187 | + | ||
| 188 | + TabContent() { | ||
| 189 | + Column({ space: 10 }) { | ||
| 190 | + Text(this.title).fontSize(this.titleFontSize).fontWeight(FontWeight.Bold); | ||
| 191 | + | ||
| 192 | + if (this.allSpeakerNames.length == 0) { | ||
| 193 | + Text('Please add speakers first') | ||
| 194 | + } else { | ||
| 195 | + List({ space: 10, initialIndex: 0 }) { | ||
| 196 | + ForEach(this.allSpeakerNames, (item: string, index: number) => { | ||
| 197 | + ListItem() { | ||
| 198 | + Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center }) { | ||
| 199 | + Text(item) | ||
| 200 | + .width('100%') | ||
| 201 | + .height(80) | ||
| 202 | + .fontSize(20) | ||
| 203 | + .textAlign(TextAlign.Center) | ||
| 204 | + .borderRadius(10) | ||
| 205 | + .flexShrink(1) | ||
| 206 | + | ||
| 207 | + Button('Delete') | ||
| 208 | + .width('30%') | ||
| 209 | + .height(40) | ||
| 210 | + .onClick(() => { | ||
| 211 | + if (index != undefined) { | ||
| 212 | + const name = this.allSpeakerNames[index]; | ||
| 213 | + console.log(`Deleting speaker ${name}`); | ||
| 214 | + if (this.workerInstance) { | ||
| 215 | + this.workerInstance.postMessage({ | ||
| 216 | + msgType: 'manager-delete-speaker', | ||
| 217 | + name: name | ||
| 218 | + }); | ||
| 219 | + } | ||
| 220 | + } | ||
| 221 | + }).stateEffect(true) | ||
| 222 | + | ||
| 223 | + Text('') | ||
| 224 | + .width('15%') | ||
| 225 | + .height(80) | ||
| 226 | + } | ||
| 227 | + } | ||
| 228 | + }, (item: string) => item) | ||
| 229 | + } | ||
| 230 | + } | ||
| 231 | + } | ||
| 232 | + }.tabBar(this.TabBuilder('View', 1, $r('app.media.icon_view'), $r('app.media.icon_view'))) | ||
| 233 | + | ||
| 234 | + TabContent() { | ||
| 235 | + Column({ space: 10 }) { | ||
| 236 | + Text(this.title).fontSize(this.titleFontSize).fontWeight(FontWeight.Bold); | ||
| 237 | + | ||
| 238 | + Row({space: 10}) { | ||
| 239 | + Text('Speaker name') | ||
| 240 | + TextInput({placeholder: 'Input speaker name'}) | ||
| 241 | + .onChange((value: string)=>{ | ||
| 242 | + this.inputSpeakerName = value.trim(); | ||
| 243 | + }); | ||
| 244 | + }.width('100%') | ||
| 245 | + | ||
| 246 | + Row({space: 10}) { | ||
| 247 | + Button(this.micBtnCaption) | ||
| 248 | + .onClick(()=> { | ||
| 249 | + if (this.mic) { | ||
| 250 | + if (this.micStarted) { | ||
| 251 | + this.micStarted = false; | ||
| 252 | + this.micBtnCaption = 'Start recording'; | ||
| 253 | + this.mic.stop(); | ||
| 254 | + this.infoAdd = ''; | ||
| 255 | + if (this.sampleList.length > 0) { | ||
| 256 | + this.btnAddEnabled = true; | ||
| 257 | + this.btnSaveAudioEnabled = true; | ||
| 258 | + } | ||
| 259 | + } else { | ||
| 260 | + this.micStarted = true; | ||
| 261 | + this.micBtnCaption = 'Stop recording'; | ||
| 262 | + this.sampleList = []; | ||
| 263 | + this.mic.start(); | ||
| 264 | + this.infoAdd = ''; | ||
| 265 | + | ||
| 266 | + this.btnAddEnabled = false; | ||
| 267 | + this.btnSaveAudioEnabled = false; | ||
| 268 | + } | ||
| 269 | + } | ||
| 270 | + | ||
| 271 | + }) | ||
| 272 | + | ||
| 273 | + Button('Add') | ||
| 274 | + .enabled(this.btnAddEnabled) | ||
| 275 | + .onClick(()=>{ | ||
| 276 | + if (this.inputSpeakerName.trim() == '') { | ||
| 277 | + this.infoAdd += 'Please input a speaker name first'; | ||
| 278 | + return; | ||
| 279 | + } | ||
| 280 | + | ||
| 281 | + const samples = flatten(this.sampleList); | ||
| 282 | + console.log(`number of samples: ${samples.length}, ${samples.length / this.sampleRate}`); | ||
| 283 | + }) | ||
| 284 | + | ||
| 285 | + Button('Save audio') | ||
| 286 | + .enabled(this.btnSaveAudioEnabled) | ||
| 287 | + .onClick(()=>{ | ||
| 288 | + if (this.sampleList.length == 0) { | ||
| 289 | + this.btnSaveAudioEnabled = false; | ||
| 290 | + return; | ||
| 291 | + } | ||
| 292 | + | ||
| 293 | + const samples = flatten(this.sampleList); | ||
| 294 | + | ||
| 295 | + if (samples.length == 0) { | ||
| 296 | + this.btnSaveAudioEnabled = false; | ||
| 297 | + return; | ||
| 298 | + } | ||
| 299 | + | ||
| 300 | + let uri: string = ''; | ||
| 301 | + | ||
| 302 | + | ||
| 303 | + const audioOptions = new picker.AudioSaveOptions(); // audioOptions.newFileNames = ['o.wav']; | ||
| 304 | + | ||
| 305 | + const audioViewPicker = new picker.AudioViewPicker(); | ||
| 306 | + | ||
| 307 | + audioViewPicker.save(audioOptions).then((audioSelectResult: Array<string>) => { | ||
| 308 | + uri = audioSelectResult[0]; | ||
| 309 | + savePcmToWav(uri, toInt16Samples(samples), this.sampleRate); | ||
| 310 | + console.log(`Saved to ${uri}`); | ||
| 311 | + this.infoAdd += `\nSaved to ${uri}`; | ||
| 312 | + }); | ||
| 313 | + }) | ||
| 314 | + } | ||
| 315 | + TextArea({text: this.infoAdd}) | ||
| 316 | + .height('100%') | ||
| 317 | + .width('100%') | ||
| 318 | + .focusable(false) | ||
| 319 | + } | ||
| 320 | + }.tabBar(this.TabBuilder('Add', 2, $r('app.media.icon_add'), $r('app.media.icon_add'))) | ||
| 321 | + | ||
| 322 | + TabContent() { | ||
| 323 | + Column({ space: 10 }) { | ||
| 324 | + Text(this.title).fontSize(this.titleFontSize).fontWeight(FontWeight.Bold); | ||
| 325 | + TextArea({ | ||
| 326 | + text: ` | ||
| 327 | +Everyting is open-sourced. | ||
| 328 | + | ||
| 329 | +It runs locally, without accessing the network | ||
| 330 | + | ||
| 331 | +See also https://github.com/k2-fsa/sherpa-onnx | ||
| 332 | + | ||
| 333 | +新一代 Kaldi QQ 和微信交流群: 请看 | ||
| 334 | + | ||
| 335 | +https://k2-fsa.github.io/sherpa/social-groups.html | ||
| 336 | + | ||
| 337 | +微信公众号: 新一代 Kaldi | ||
| 338 | + ` | ||
| 339 | + }).width('100%').height('100%').focusable(false) | ||
| 340 | + } | ||
| 341 | + }.tabBar(this.TabBuilder('Help', 3, $r('app.media.icon_info'), $r('app.media.icon_info'))) | ||
| 342 | + | ||
| 343 | + }.scrollable(false) | ||
| 344 | + }.width('100%') | ||
| 345 | + } | ||
| 346 | + | ||
| 347 | + private micCallback = (buffer: ArrayBuffer) => { | ||
| 348 | + const view: Int16Array = new Int16Array(buffer); | ||
| 349 | + | ||
| 350 | + const samplesFloat: Float32Array = new Float32Array(view.length); | ||
| 351 | + for (let i = 0; i < view.length; ++i) { | ||
| 352 | + samplesFloat[i] = view[i] / 32768.0; | ||
| 353 | + } | ||
| 354 | + | ||
| 355 | + this.sampleList.push(samplesFloat); | ||
| 356 | + } | ||
| 357 | +} |
| 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, { ThreadWorkerGlobalScope, MessageEvents, ErrorEvent } from '@ohos.worker'; | ||
| 2 | +import { | ||
| 3 | + readWaveFromBinary, | ||
| 4 | + Samples, | ||
| 5 | + SpeakerEmbeddingExtractor, | ||
| 6 | + SpeakerEmbeddingExtractorConfig, | ||
| 7 | + SpeakerEmbeddingManager | ||
| 8 | +} from 'sherpa_onnx'; | ||
| 9 | +import { fileIo } from '@kit.CoreFileKit'; | ||
| 10 | + | ||
| 11 | +const workerPort: ThreadWorkerGlobalScope = worker.workerPort; | ||
| 12 | + | ||
| 13 | +let extractor: SpeakerEmbeddingExtractor; | ||
| 14 | +let manager: SpeakerEmbeddingManager; | ||
| 15 | + | ||
| 16 | +function readWaveFromRawfile(filename: string, context: Context): Samples { | ||
| 17 | + const data: Uint8Array = context.resourceManager.getRawFileContentSync(filename); | ||
| 18 | + return readWaveFromBinary(data) as Samples; | ||
| 19 | +} | ||
| 20 | + | ||
| 21 | +function initExtractor(context: Context): SpeakerEmbeddingExtractor { | ||
| 22 | + const config = new SpeakerEmbeddingExtractorConfig(); | ||
| 23 | + config.model = '3dspeaker_speech_eres2net_base_sv_zh-cn_3dspeaker_16k.onnx'; | ||
| 24 | + config.numThreads = 2; | ||
| 25 | + config.debug = true; | ||
| 26 | + | ||
| 27 | + return new SpeakerEmbeddingExtractor(config, context.resourceManager); | ||
| 28 | +} | ||
| 29 | + | ||
| 30 | +function extractEmbedding(samples: Samples): Float32Array { | ||
| 31 | + const stream = extractor.createStream(); | ||
| 32 | + stream.acceptWaveform(samples); | ||
| 33 | + return extractor.compute(stream); | ||
| 34 | +} | ||
| 35 | + | ||
| 36 | +/** | ||
| 37 | + * Defines the event handler to be called when the worker thread receives a message sent by the host thread. | ||
| 38 | + * The event handler is executed in the worker thread. | ||
| 39 | + * | ||
| 40 | + * @param e message data | ||
| 41 | + */ | ||
| 42 | +workerPort.onmessage = (e: MessageEvents) => { | ||
| 43 | + const msgType = e.data['msgType'] as string; | ||
| 44 | + | ||
| 45 | + console.log(`from the main thread, msg-type: ${msgType}`); | ||
| 46 | + | ||
| 47 | + if (msgType == 'init-extractor' && !extractor) { | ||
| 48 | + const context: Context = e.data['context'] as Context; | ||
| 49 | + extractor = initExtractor(context); | ||
| 50 | + manager = new SpeakerEmbeddingManager(extractor.dim); | ||
| 51 | + | ||
| 52 | + const filename1 = 'sr-data/enroll/fangjun-sr-1.wav'; | ||
| 53 | + const samples1 = readWaveFromRawfile(filename1, context); | ||
| 54 | + console.log(`sample rate: ${samples1.sampleRate}`); | ||
| 55 | + let ok = manager.add({ name: 'fangjun0', v: extractEmbedding(samples1) }); | ||
| 56 | + ok = manager.add({ name: 'fangjun1', v: extractEmbedding(samples1) }); | ||
| 57 | + /* | ||
| 58 | + ok = manager.add({ name: 'fangjun2', v: extractEmbedding(samples1) }); | ||
| 59 | + ok = manager.add({ name: 'fangjun3', v: extractEmbedding(samples1) }); | ||
| 60 | + ok = manager.add({ name: 'fangjun4', v: extractEmbedding(samples1) }); | ||
| 61 | + ok = manager.add({ name: 'fangjun5', v: extractEmbedding(samples1) }); | ||
| 62 | + ok = manager.add({ name: 'fangjun6', v: extractEmbedding(samples1) }); | ||
| 63 | + ok = manager.add({ name: 'fangjun7', v: extractEmbedding(samples1) }); | ||
| 64 | + ok = manager.add({ name: 'fangjun8', v: extractEmbedding(samples1) }); | ||
| 65 | + ok = manager.add({ name: 'fangjun9', v: extractEmbedding(samples1) }); | ||
| 66 | + ok = manager.add({ name: 'fangjun10', v: extractEmbedding(samples1) }); | ||
| 67 | + */ | ||
| 68 | + | ||
| 69 | + if (ok) { | ||
| 70 | + console.log(`Added fangjun`); | ||
| 71 | + let n = manager.getNumSpeakers(); | ||
| 72 | + console.log(`number of speakers: ${n}`); | ||
| 73 | + console.log(`speaker names: ${manager.getAllSpeakerNames().join('\n')}`); | ||
| 74 | + } | ||
| 75 | + | ||
| 76 | + workerPort.postMessage({ | ||
| 77 | + msgType: 'manager-all-speaker-names', allSpeakers: manager.getAllSpeakerNames(), | ||
| 78 | + }); | ||
| 79 | + } | ||
| 80 | + | ||
| 81 | + if (msgType == 'manager-delete-speaker') { | ||
| 82 | + const name = e.data['name'] as string; | ||
| 83 | + const ok = manager.remove(name); | ||
| 84 | + if (ok) { | ||
| 85 | + console.log(`Removed ${name}.`); | ||
| 86 | + | ||
| 87 | + console.log(`Number of speakers: ${manager.getNumSpeakers()}`); | ||
| 88 | + console.log(`Number of speakers2: ${manager.getAllSpeakerNames().length}`); | ||
| 89 | + console.log(JSON.stringify(manager.getAllSpeakerNames())); | ||
| 90 | + workerPort.postMessage({ | ||
| 91 | + msgType: 'manager-all-speaker-names', allSpeakers: manager.getAllSpeakerNames(), | ||
| 92 | + }); | ||
| 93 | + } | ||
| 94 | + } | ||
| 95 | +} | ||
| 96 | + | ||
| 97 | +/** | ||
| 98 | + * Defines the event handler to be called when the worker receives a message that cannot be deserialized. | ||
| 99 | + * The event handler is executed in the worker thread. | ||
| 100 | + * | ||
| 101 | + * @param e message data | ||
| 102 | + */ | ||
| 103 | +workerPort.onmessageerror = (e: MessageEvents) => { | ||
| 104 | +} | ||
| 105 | + | ||
| 106 | +/** | ||
| 107 | + * Defines the event handler to be called when an exception occurs during worker execution. | ||
| 108 | + * The event handler is executed in the worker thread. | ||
| 109 | + * | ||
| 110 | + * @param e error message | ||
| 111 | + */ | ||
| 112 | +workerPort.onerror = (e: ErrorEvent) => { | ||
| 113 | +} |
| 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 | +} |
harmony-os/SherpaOnnxSpeakerIdentification/entry/src/main/resources/base/element/string.json
0 → 100644
| 1 | +{ | ||
| 2 | + "string": [ | ||
| 3 | + { | ||
| 4 | + "name": "module_desc", | ||
| 5 | + "value": "On-device speaker identification with Next-gen Kaldi" | ||
| 6 | + }, | ||
| 7 | + { | ||
| 8 | + "name": "EntryAbility_desc", | ||
| 9 | + "value": "On-device speaker identification with Next-gen Kaldi" | ||
| 10 | + }, | ||
| 11 | + { | ||
| 12 | + "name": "EntryAbility_label", | ||
| 13 | + "value": "Speaker identification" | ||
| 14 | + }, | ||
| 15 | + { | ||
| 16 | + "name": "mic_reason", | ||
| 17 | + "value": "access the microphone for on-device speaker identification with Next-gen Kaldi" | ||
| 18 | + } | ||
| 19 | + ] | ||
| 20 | +} |
harmony-os/SherpaOnnxSpeakerIdentification/entry/src/main/resources/base/media/background.png
0 → 100644
56.0 KB
harmony-os/SherpaOnnxSpeakerIdentification/entry/src/main/resources/base/media/foreground.png
0 → 100644
12.1 KB
harmony-os/SherpaOnnxSpeakerIdentification/entry/src/main/resources/base/media/icon_add.svg
0 → 100644
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24" version="1.1"> | ||
| 3 | + <!-- Generator: Sketch 63.1 (92452) - https://sketch.com --> | ||
| 4 | + <title>Public/ic_public_list_add_light</title> | ||
| 5 | + <desc>Created with Sketch.</desc> | ||
| 6 | + <g id="_Public/ic_public_list_add_light" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> | ||
| 7 | + <path d="M12,1 C18.0751322,1 23,5.92486775 23,12 C23,18.0751322 18.0751322,23 12,23 C5.92486775,23 1,18.0751322 1,12 C1,5.92486775 5.92486775,1 12,1 Z" id="_椭圆形" fill="#0A59F7"/> | ||
| 8 | + <path d="M12.75,17.25 C12.75,17.6642136 12.4142136,18 12,18 C11.5857864,18 11.25,17.6642136 11.25,17.25 L11.25,12.749 L6.75,12.75 C6.33578644,12.75 6,12.4142136 6,12 C6,11.5857864 6.33578644,11.25 6.75,11.25 L11.25,11.249 L11.25,6.75 C11.25,6.33578644 11.5857864,6 12,6 C12.4142136,6 12.75,6.33578644 12.75,6.75 L12.75,17.25 Z M13.75,11.25 L17.25,11.25 C17.6642136,11.25 18,11.5857864 18,12 C18,12.4142136 17.6642136,12.75 17.25,12.75 L13.75,12.75 L13.75,11.25 Z" id="_形状" fill="#FFFFFF"/> | ||
| 9 | + </g> | ||
| 10 | +</svg> |
harmony-os/SherpaOnnxSpeakerIdentification/entry/src/main/resources/base/media/icon_home.svg
0 → 100644
| 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> |
harmony-os/SherpaOnnxSpeakerIdentification/entry/src/main/resources/base/media/icon_info.svg
0 → 100644
| 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> |
harmony-os/SherpaOnnxSpeakerIdentification/entry/src/main/resources/base/media/icon_view.svg
0 → 100644
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24" version="1.1"> | ||
| 3 | + <title>Public/ic_public_view_list_filled</title> | ||
| 4 | + <defs> | ||
| 5 | + <path d="M3.75,17.25 C4.44035594,17.25 5,17.8096441 5,18.5 C5,19.1903559 4.44035594,19.75 3.75,19.75 C3.05964406,19.75 2.5,19.1903559 2.5,18.5 C2.5,17.8096441 3.05964406,17.25 3.75,17.25 Z M21,17.5 C21.5522847,17.5 22,17.9477153 22,18.5 C22,19.0522847 21.5522847,19.5 21,19.5 L8,19.5 C7.44771525,19.5 7,19.0522847 7,18.5 C7,17.9477153 7.44771525,17.5 8,17.5 L21,17.5 Z M3.75,10.75 C4.44035594,10.75 5,11.3096441 5,12 C5,12.6903559 4.44035594,13.25 3.75,13.25 C3.05964406,13.25 2.5,12.6903559 2.5,12 C2.5,11.3096441 3.05964406,10.75 3.75,10.75 Z M21,11 C21.5522847,11 22,11.4477153 22,12 C22,12.5522847 21.5522847,13 21,13 L8,13 C7.44771525,13 7,12.5522847 7,12 C7,11.4477153 7.44771525,11 8,11 L21,11 Z M3.75,4.25 C4.44035594,4.25 5,4.80964406 5,5.5 C5,6.19035594 4.44035594,6.75 3.75,6.75 C3.05964406,6.75 2.5,6.19035594 2.5,5.5 C2.5,4.80964406 3.05964406,4.25 3.75,4.25 Z M21,4.5 C21.5522847,4.5 22,4.94771525 22,5.5 C22,6.05228475 21.5522847,6.5 21,6.5 L8,6.5 C7.44771525,6.5 7,6.05228475 7,5.5 C7,4.94771525 7.44771525,4.5 8,4.5 L21,4.5 Z" id="_path-1"/> | ||
| 6 | + </defs> | ||
| 7 | + <g id="_Public/ic_public_view_list_filled" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> | ||
| 8 | + <mask id="_mask-2" fill="white"> | ||
| 9 | + <use xlink:href="#_path-1"/> | ||
| 10 | + </mask> | ||
| 11 | + <use id="_形状结合" fill="#000000" fill-rule="nonzero" xlink:href="#_path-1"/> | ||
| 12 | + </g> | ||
| 13 | +</svg> |
harmony-os/SherpaOnnxSpeakerIdentification/entry/src/main/resources/base/media/startIcon.png
0 → 100644
19.6 KB
harmony-os/SherpaOnnxSpeakerIdentification/entry/src/main/resources/en_US/element/string.json
0 → 100644
| 1 | +{ | ||
| 2 | + "string": [ | ||
| 3 | + { | ||
| 4 | + "name": "module_desc", | ||
| 5 | + "value": "On-device speaker identification with Next-gen Kaldi" | ||
| 6 | + }, | ||
| 7 | + { | ||
| 8 | + "name": "EntryAbility_desc", | ||
| 9 | + "value": "On-device speaker identification with Next-gen Kaldi" | ||
| 10 | + }, | ||
| 11 | + { | ||
| 12 | + "name": "EntryAbility_label", | ||
| 13 | + "value": "Speaker identification" | ||
| 14 | + }, | ||
| 15 | + { | ||
| 16 | + "name": "mic_reason", | ||
| 17 | + "value": "access the microphone for on-device speaker identification with Next-gen Kaldi" | ||
| 18 | + } | ||
| 19 | + ] | ||
| 20 | +} |
harmony-os/SherpaOnnxSpeakerIdentification/entry/src/main/resources/zh_CN/element/string.json
0 → 100644
| 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 { 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 | +{ | ||
| 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 | +} |
| @@ -14,7 +14,7 @@ | @@ -14,7 +14,7 @@ | ||
| 14 | }, | 14 | }, |
| 15 | { | 15 | { |
| 16 | "name": "mic_reason", | 16 | "name": "mic_reason", |
| 17 | - "value": "access the microhone for on-device real-time speech recognition with Next-gen Kaldi" | 17 | + "value": "access the microphone for on-device real-time speech recognition with Next-gen Kaldi" |
| 18 | } | 18 | } |
| 19 | ] | 19 | ] |
| 20 | } | 20 | } |
| @@ -14,7 +14,7 @@ | @@ -14,7 +14,7 @@ | ||
| 14 | }, | 14 | }, |
| 15 | { | 15 | { |
| 16 | "name": "mic_reason", | 16 | "name": "mic_reason", |
| 17 | - "value": "access the microhone for on-device real-time speech recognition with Next-gen Kaldi" | 17 | + "value": "access the microphone for on-device real-time speech recognition with Next-gen Kaldi" |
| 18 | } | 18 | } |
| 19 | ] | 19 | ] |
| 20 | } | 20 | } |
| @@ -14,7 +14,7 @@ | @@ -14,7 +14,7 @@ | ||
| 14 | }, | 14 | }, |
| 15 | { | 15 | { |
| 16 | "name": "mic_reason", | 16 | "name": "mic_reason", |
| 17 | - "value": "access the microhone for on-device speech recognition with Next-gen Kaldi" | 17 | + "value": "access the microphone for on-device speech recognition with Next-gen Kaldi" |
| 18 | } | 18 | } |
| 19 | ] | 19 | ] |
| 20 | } | 20 | } |
| @@ -14,7 +14,7 @@ | @@ -14,7 +14,7 @@ | ||
| 14 | }, | 14 | }, |
| 15 | { | 15 | { |
| 16 | "name": "mic_reason", | 16 | "name": "mic_reason", |
| 17 | - "value": "access the microhone for on-device speech recognition with Next-gen Kaldi" | 17 | + "value": "access the microphone for on-device speech recognition with Next-gen Kaldi" |
| 18 | } | 18 | } |
| 19 | ] | 19 | ] |
| 20 | } | 20 | } |
-
请 注册 或 登录 后发表评论