Fangjun Kuang
Committed by GitHub

Add speaker diarization demo for HarmonyOS (#1610)

正在显示 45 个修改的文件 包含 1074 行增加17 行删除
@@ -282,6 +282,170 @@ static Napi::Array OfflineSpeakerDiarizationProcessWrapper( @@ -282,6 +282,170 @@ static Napi::Array OfflineSpeakerDiarizationProcessWrapper(
282 return ans; 282 return ans;
283 } 283 }
284 284
  285 +struct SpeakerDiarizationCallbackData {
  286 + int32_t num_processed_chunks;
  287 + int32_t num_total_chunks;
  288 +};
  289 +
  290 +// see
  291 +// https://github.com/nodejs/node-addon-examples/blob/main/src/6-threadsafe-function/typed_threadsafe_function/node-addon-api/clock.cc
  292 +static void InvokeJsCallback(Napi::Env env, Napi::Function callback,
  293 + Napi::Reference<Napi::Value> *context,
  294 + SpeakerDiarizationCallbackData *data) {
  295 + if (env != nullptr) {
  296 + if (callback != nullptr) {
  297 + Napi::Number num_processed_chunks =
  298 + Napi::Number::New(env, data->num_processed_chunks);
  299 + Napi::Number num_total_chunks =
  300 + Napi::Number::New(env, data->num_total_chunks);
  301 +
  302 + callback.Call(context->Value(), {num_processed_chunks, num_total_chunks});
  303 + }
  304 + }
  305 + delete data;
  306 +}
  307 +
  308 +using TSFN = Napi::TypedThreadSafeFunction<Napi::Reference<Napi::Value>,
  309 + SpeakerDiarizationCallbackData,
  310 + InvokeJsCallback>;
  311 +
  312 +class SpeakerDiarizationProcessWorker : public Napi::AsyncWorker {
  313 + public:
  314 + SpeakerDiarizationProcessWorker(const Napi::Env &env, TSFN tsfn,
  315 + const SherpaOnnxOfflineSpeakerDiarization *sd,
  316 + std::vector<float> samples)
  317 + : tsfn_(tsfn),
  318 + Napi::AsyncWorker{env, "SpeakerDiarizationProcessAsyncWorker"},
  319 + deferred_(env),
  320 + sd_(sd),
  321 + samples_(std::move(samples)) {}
  322 +
  323 + Napi::Promise Promise() { return deferred_.Promise(); }
  324 +
  325 + protected:
  326 + void Execute() override {
  327 + auto callback = [](int32_t num_processed_chunks, int32_t num_total_chunks,
  328 + void *arg) -> int32_t {
  329 + auto _this = reinterpret_cast<SpeakerDiarizationProcessWorker *>(arg);
  330 +
  331 + auto data = new SpeakerDiarizationCallbackData;
  332 + data->num_processed_chunks = num_processed_chunks;
  333 + data->num_total_chunks = num_total_chunks;
  334 +
  335 + _this->tsfn_.NonBlockingCall(data);
  336 +
  337 + return 0;
  338 + };
  339 +
  340 + r_ = SherpaOnnxOfflineSpeakerDiarizationProcessWithCallback(
  341 + sd_, samples_.data(), samples_.size(), callback, this);
  342 +
  343 + tsfn_.Release();
  344 + }
  345 +
  346 + void OnOK() override {
  347 + Napi::Env env = deferred_.Env();
  348 +
  349 + int32_t num_segments =
  350 + SherpaOnnxOfflineSpeakerDiarizationResultGetNumSegments(r_);
  351 +
  352 + const SherpaOnnxOfflineSpeakerDiarizationSegment *segments =
  353 + SherpaOnnxOfflineSpeakerDiarizationResultSortByStartTime(r_);
  354 +
  355 + Napi::Array ans = Napi::Array::New(env, num_segments);
  356 +
  357 + for (int32_t i = 0; i != num_segments; ++i) {
  358 + Napi::Object obj = Napi::Object::New(env);
  359 +
  360 + obj.Set(Napi::String::New(env, "start"), segments[i].start);
  361 + obj.Set(Napi::String::New(env, "end"), segments[i].end);
  362 + obj.Set(Napi::String::New(env, "speaker"), segments[i].speaker);
  363 +
  364 + ans.Set(i, obj);
  365 + }
  366 +
  367 + SherpaOnnxOfflineSpeakerDiarizationDestroySegment(segments);
  368 + SherpaOnnxOfflineSpeakerDiarizationDestroyResult(r_);
  369 +
  370 + deferred_.Resolve(ans);
  371 + }
  372 +
  373 + private:
  374 + TSFN tsfn_;
  375 + Napi::Promise::Deferred deferred_;
  376 + const SherpaOnnxOfflineSpeakerDiarization *sd_;
  377 + std::vector<float> samples_;
  378 + const SherpaOnnxOfflineSpeakerDiarizationResult *r_;
  379 +};
  380 +
  381 +static Napi::Object OfflineSpeakerDiarizationProcessAsyncWrapper(
  382 + const Napi::CallbackInfo &info) {
  383 + Napi::Env env = info.Env();
  384 +
  385 + if (info.Length() != 3) {
  386 + std::ostringstream os;
  387 + os << "Expect only 3 arguments. Given: " << info.Length();
  388 +
  389 + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
  390 +
  391 + return {};
  392 + }
  393 +
  394 + if (!info[0].IsExternal()) {
  395 + Napi::TypeError::New(
  396 + env, "Argument 0 should be an offline speaker diarization pointer.")
  397 + .ThrowAsJavaScriptException();
  398 +
  399 + return {};
  400 + }
  401 +
  402 + const SherpaOnnxOfflineSpeakerDiarization *sd =
  403 + info[0].As<Napi::External<SherpaOnnxOfflineSpeakerDiarization>>().Data();
  404 +
  405 + if (!info[1].IsTypedArray()) {
  406 + Napi::TypeError::New(env, "Argument 1 should be a typed array")
  407 + .ThrowAsJavaScriptException();
  408 +
  409 + return {};
  410 + }
  411 +
  412 + if (!info[2].IsFunction()) {
  413 + Napi::TypeError::New(env, "Argument 2 should be a function")
  414 + .ThrowAsJavaScriptException();
  415 +
  416 + return {};
  417 + }
  418 +
  419 + Napi::Function cb = info[2].As<Napi::Function>();
  420 +
  421 + auto context =
  422 + new Napi::Reference<Napi::Value>(Napi::Persistent(info.This()));
  423 +
  424 + TSFN tsfn = TSFN::New(
  425 + env,
  426 + cb, // JavaScript function called asynchronously
  427 + "SpeakerDiarizationProcessAsyncFunc", // Name
  428 + 0, // Unlimited queue
  429 + 1, // Only one thread will use this initially
  430 + context,
  431 + [](Napi::Env, void *, Napi::Reference<Napi::Value> *ctx) { delete ctx; });
  432 +
  433 + Napi::Float32Array samples = info[1].As<Napi::Float32Array>();
  434 +
  435 +#if __OHOS__
  436 + int32_t num_samples = samples.ElementLength() / sizeof(float);
  437 +#else
  438 + int32_t num_samples = samples.ElementLength();
  439 +#endif
  440 + std::vector<float> v(num_samples);
  441 + std::copy(samples.Data(), samples.Data() + num_samples, v.begin());
  442 +
  443 + SpeakerDiarizationProcessWorker *worker =
  444 + new SpeakerDiarizationProcessWorker(env, tsfn, sd, v);
  445 + worker->Queue();
  446 + return worker->Promise();
  447 +}
  448 +
285 static void OfflineSpeakerDiarizationSetConfigWrapper( 449 static void OfflineSpeakerDiarizationSetConfigWrapper(
286 const Napi::CallbackInfo &info) { 450 const Napi::CallbackInfo &info) {
287 Napi::Env env = info.Env(); 451 Napi::Env env = info.Env();
@@ -313,7 +477,7 @@ static void OfflineSpeakerDiarizationSetConfigWrapper( @@ -313,7 +477,7 @@ static void OfflineSpeakerDiarizationSetConfigWrapper(
313 return; 477 return;
314 } 478 }
315 479
316 - Napi::Object o = info[0].As<Napi::Object>(); 480 + Napi::Object o = info[1].As<Napi::Object>();
317 481
318 SherpaOnnxOfflineSpeakerDiarizationConfig c; 482 SherpaOnnxOfflineSpeakerDiarizationConfig c;
319 memset(&c, 0, sizeof(c)); 483 memset(&c, 0, sizeof(c));
@@ -335,6 +499,10 @@ void InitNonStreamingSpeakerDiarization(Napi::Env env, Napi::Object exports) { @@ -335,6 +499,10 @@ void InitNonStreamingSpeakerDiarization(Napi::Env env, Napi::Object exports) {
335 Napi::Function::New(env, OfflineSpeakerDiarizationProcessWrapper)); 499 Napi::Function::New(env, OfflineSpeakerDiarizationProcessWrapper));
336 500
337 exports.Set( 501 exports.Set(
  502 + Napi::String::New(env, "offlineSpeakerDiarizationProcessAsync"),
  503 + Napi::Function::New(env, OfflineSpeakerDiarizationProcessAsyncWrapper));
  504 +
  505 + exports.Set(
338 Napi::String::New(env, "offlineSpeakerDiarizationSetConfig"), 506 Napi::String::New(env, "offlineSpeakerDiarizationSetConfig"),
339 Napi::Function::New(env, OfflineSpeakerDiarizationSetConfigWrapper)); 507 Napi::Function::New(env, OfflineSpeakerDiarizationSetConfigWrapper));
340 } 508 }
@@ -344,9 +344,9 @@ struct TtsCallbackData { @@ -344,9 +344,9 @@ struct TtsCallbackData {
344 344
345 // see 345 // see
346 // https://github.com/nodejs/node-addon-examples/blob/main/src/6-threadsafe-function/typed_threadsafe_function/node-addon-api/clock.cc 346 // https://github.com/nodejs/node-addon-examples/blob/main/src/6-threadsafe-function/typed_threadsafe_function/node-addon-api/clock.cc
347 -void InvokeJsCallback(Napi::Env env, Napi::Function callback,  
348 - Napi::Reference<Napi::Value> *context,  
349 - TtsCallbackData *data) { 347 +static void InvokeJsCallback(Napi::Env env, Napi::Function callback,
  348 + Napi::Reference<Napi::Value> *context,
  349 + TtsCallbackData *data) {
350 if (env != nullptr) { 350 if (env != nullptr) {
351 if (callback != nullptr) { 351 if (callback != nullptr) {
352 Napi::ArrayBuffer arrayBuffer = 352 Napi::ArrayBuffer arrayBuffer =
@@ -580,7 +580,6 @@ static Napi::Object OfflineTtsGenerateAsyncWrapper( @@ -580,7 +580,6 @@ static Napi::Object OfflineTtsGenerateAsyncWrapper(
580 context, 580 context,
581 [](Napi::Env, void *, Napi::Reference<Napi::Value> *ctx) { delete ctx; }); 581 [](Napi::Env, void *, Napi::Reference<Napi::Value> *ctx) { delete ctx; });
582 582
583 - const SherpaOnnxGeneratedAudio *audio;  
584 TtsGenerateWorker *worker = new TtsGenerateWorker( 583 TtsGenerateWorker *worker = new TtsGenerateWorker(
585 env, tsfn, tts, text, speed, sid, enable_external_buffer); 584 env, tsfn, tts, text, speed, sid, enable_external_buffer);
586 worker->Queue(); 585 worker->Queue();
@@ -65,5 +65,6 @@ export const speakerEmbeddingManagerGetAllSpeakers: (handle: object) => Array<st @@ -65,5 +65,6 @@ export const speakerEmbeddingManagerGetAllSpeakers: (handle: object) => Array<st
65 65
66 export const createOfflineSpeakerDiarization: (config: object, mgr?: object) => object; 66 export const createOfflineSpeakerDiarization: (config: object, mgr?: object) => object;
67 export const getOfflineSpeakerDiarizationSampleRate: (handle: object) => number; 67 export const getOfflineSpeakerDiarizationSampleRate: (handle: object) => number;
68 -export const offlineSpeakerDiarizationProcess: (handle: object, samples: Float32Array) => object; 68 +export const offlineSpeakerDiarizationProcess: (handle: object, input: object) => object;
  69 +export const offlineSpeakerDiarizationProcessAsync: (handle: object, input: object, callback: object) => object;
69 export const offlineSpeakerDiarizationSetConfig: (handle: object, config: object) => void; 70 export const offlineSpeakerDiarizationSetConfig: (handle: object, config: object) => void;
@@ -2,6 +2,7 @@ import { @@ -2,6 +2,7 @@ import {
2 createOfflineSpeakerDiarization, 2 createOfflineSpeakerDiarization,
3 getOfflineSpeakerDiarizationSampleRate, 3 getOfflineSpeakerDiarizationSampleRate,
4 offlineSpeakerDiarizationProcess, 4 offlineSpeakerDiarizationProcess,
  5 + offlineSpeakerDiarizationProcessAsync,
5 offlineSpeakerDiarizationSetConfig, 6 offlineSpeakerDiarizationSetConfig,
6 } from 'libsherpa_onnx.so'; 7 } from 'libsherpa_onnx.so';
7 8
@@ -32,8 +33,12 @@ export class OfflineSpeakerDiarizationConfig { @@ -32,8 +33,12 @@ export class OfflineSpeakerDiarizationConfig {
32 } 33 }
33 34
34 export class OfflineSpeakerDiarizationSegment { 35 export class OfflineSpeakerDiarizationSegment {
35 - public start: number = 0; // in secondspublic end: number = 0; // in secondspublic speaker: number =  
36 - 0; // ID of the speaker; count from 0 36 + // in seconds
  37 + public start: number = 0;
  38 + // in seconds
  39 + public end: number = 0;
  40 + // ID of the speaker; count from 0
  41 + public speaker: number = 0;
37 } 42 }
38 43
39 export class OfflineSpeakerDiarization { 44 export class OfflineSpeakerDiarization {
@@ -62,8 +67,14 @@ export class OfflineSpeakerDiarization { @@ -62,8 +67,14 @@ export class OfflineSpeakerDiarization {
62 * "speaker": an_integer, 67 * "speaker": an_integer,
63 * } 68 * }
64 */ 69 */
65 - process(samples: Float32Array): OfflineSpeakerDiarizationSegment {  
66 - return offlineSpeakerDiarizationProcess(this.handle, samples) as OfflineSpeakerDiarizationSegment; 70 + process(samples: Float32Array): OfflineSpeakerDiarizationSegment[] {
  71 + return offlineSpeakerDiarizationProcess(this.handle, samples) as OfflineSpeakerDiarizationSegment[];
  72 + }
  73 +
  74 + processAsync(samples: Float32Array, callback: (numProcessedChunks: number,
  75 + numTotalChunks: number) => void): Promise<OfflineSpeakerDiarizationSegment[]> {
  76 + return offlineSpeakerDiarizationProcessAsync(this.handle, samples,
  77 + callback) as Promise<OfflineSpeakerDiarizationSegment[]>;
67 } 78 }
68 79
69 setConfig(config: OfflineSpeakerDiarizationConfig) { 80 setConfig(config: OfflineSpeakerDiarizationConfig) {
  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.speaker.diarization",
  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": "SherpaOnnxSpeakerDiarization"
  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/SpeakerDiarizationWorker.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 + "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, promptAction } from '@kit.ArkUI';
  2 +import worker, { MessageEvents } from '@ohos.worker';
  3 +import { BusinessError, pasteboard } from '@kit.BasicServicesKit';
  4 +import { picker } from '@kit.CoreFileKit';
  5 +
  6 +
  7 +@Entry
  8 +@Component
  9 +struct Index {
  10 + @State title: string = 'Next-gen Kaldi: Speaker Diarization';
  11 + @State titleFontSize: number = 15;
  12 + @State currentIndex: number = 0;
  13 + @State resultForFile: string = '';
  14 + @State resultForMic: string = '';
  15 + @State progressForFile: number = 0;
  16 + @State selectFileBtnEnabled: boolean = false;
  17 + @State copyBtnForFileEnabled: boolean = false;
  18 + private controller: TabsController = new TabsController();
  19 + private workerInstance?: worker.ThreadWorker
  20 + private readonly scriptURL: string = 'entry/ets/workers/SpeakerDiarizationWorker.ets'
  21 + private numSpeakers: string = '-1';
  22 +
  23 + @Builder
  24 + TabBuilder(title: string, targetIndex: number, selectedImg: Resource, normalImg: Resource) {
  25 + Column() {
  26 + Image(this.currentIndex == targetIndex ? selectedImg : normalImg).size({ width: 25, height: 25 })
  27 + Text(title).fontColor(this.currentIndex == targetIndex ? '#28bff1' : '#8a8a8a')
  28 + }.width('100%').height(50).justifyContent(FlexAlign.Center).onClick(() => {
  29 + this.currentIndex = targetIndex;
  30 + this.controller.changeIndex(this.currentIndex);
  31 + })
  32 + }
  33 +
  34 + aboutToAppear(): void {
  35 + this.workerInstance = new worker.ThreadWorker(this.scriptURL, {
  36 + name: 'Streaming ASR worker'
  37 + });
  38 +
  39 + this.workerInstance.onmessage = (e: MessageEvents) => {
  40 + const msgType = e.data['msgType'] as string;
  41 +
  42 + if (msgType != 'speaker-diarization-file-progress') {
  43 + console.log(`received msg from worker: ${msgType}`);
  44 + }
  45 +
  46 + if (msgType == 'init-speaker-diarization-done') {
  47 + console.log('Speaker diarization initialized successfully');
  48 +
  49 + this.resultForFile = 'Initialization finished.\nPlease select a .wav file.';
  50 + this.resultForMic = 'Initialization finished.\nPlease click the button Start recording.';
  51 +
  52 + this.selectFileBtnEnabled = true;
  53 + }
  54 +
  55 + if (msgType == 'speaker-diarization-file-progress') {
  56 + this.progressForFile = e.data['progress'] as number;
  57 + }
  58 +
  59 + if (msgType == 'speaker-diarization-file-done') {
  60 + const result = e.data['result'] as string;
  61 + this.resultForFile = result;
  62 +
  63 + this.selectFileBtnEnabled = true;
  64 + this.copyBtnForFileEnabled = true;
  65 + }
  66 + };
  67 +
  68 + const context = getContext();
  69 + this.workerInstance.postMessage({ msgType: 'init-speaker-diarization', context });
  70 + console.log('initializing');
  71 + this.resultForFile = 'Initializing models. Please wait';
  72 + this.resultForMic = this.resultForFile;
  73 + }
  74 +
  75 + build() {
  76 + Column() {
  77 + Tabs({ barPosition: BarPosition.End, controller: this.controller }) {
  78 + TabContent() {
  79 + Column({ space: 10 }) {
  80 + Text(this.title).fontSize(this.titleFontSize).fontWeight(FontWeight.Bold);
  81 + Row({ space: 10 }) {
  82 + Text(`Number of speakers`).width('60%')
  83 +
  84 + TextInput({ text: this.numSpeakers }).onChange((text) => {
  85 + this.numSpeakers = text.trim();
  86 + }).width('20%')
  87 + }.justifyContent(FlexAlign.Center)
  88 +
  89 + Row({ space: 10 }) {
  90 + Button('Select .wav file (16kHz) ').enabled(this.selectFileBtnEnabled).onClick(() => {
  91 + this.resultForFile = '';
  92 + this.progressForFile = 0;
  93 + this.copyBtnForFileEnabled = false;
  94 +
  95 + let numSpeakers = parseInt(this.numSpeakers);
  96 + if (numSpeakers.toString() != this.numSpeakers) {
  97 + this.resultForFile =
  98 + 'Please input a valid value for the number of speakers in the .wav file you are going to select';
  99 + return;
  100 + }
  101 +
  102 + if (numSpeakers < 1) {
  103 + this.resultForFile =
  104 + 'Please input a positive value for the number of speakers in the .wav file you are going to select';
  105 + return;
  106 + }
  107 +
  108 + this.selectFileBtnEnabled = false;
  109 +
  110 + const documentSelectOptions = new picker.DocumentSelectOptions();
  111 + documentSelectOptions.maxSelectNumber = 1;
  112 + documentSelectOptions.fileSuffixFilters = ['.wav'];
  113 + const documentViewPicker = new picker.DocumentViewPicker();
  114 +
  115 + documentViewPicker.select(documentSelectOptions).then((result: Array<string>) => {
  116 + console.log(`select file result: ${result}`);
  117 +
  118 + if (!result[0]) {
  119 + this.resultForFile = 'Please select a file to decode';
  120 + this.selectFileBtnEnabled = true;
  121 + return;
  122 + }
  123 +
  124 + if (this.workerInstance) {
  125 + this.workerInstance.postMessage({
  126 + msgType: 'speaker-diarization-file', filename: result[0], numSpeakers,
  127 + });
  128 + this.resultForFile = `Decoding ${result[0]} ... ...`;
  129 + } else {
  130 + console.log(`this worker instance is undefined ${this.workerInstance}`);
  131 + }
  132 + }).catch((err: BusinessError) => {
  133 + console.error(`Failed to select file, code is ${err.code}, message is ${err.message}`);
  134 + this.selectFileBtnEnabled = true;
  135 + })
  136 + })
  137 + Button('Copy results')
  138 + .enabled(this.copyBtnForFileEnabled)
  139 + .onClick(() => { // See https://developer.huawei.com/consumer/cn/doc/harmonyos-faqs/faqs-arkui-308-V5
  140 + const pasteboardData = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, this.resultForFile);
  141 + const systemPasteboard = pasteboard.getSystemPasteboard();
  142 + systemPasteboard.setData(pasteboardData);
  143 + systemPasteboard.getData().then((data) => {
  144 + if (data) {
  145 + promptAction.showToast({ message: 'Result copied.' });
  146 + } else {
  147 + promptAction.showToast({ message: 'Failed to copy' });
  148 + }
  149 + })
  150 + })
  151 + }
  152 +
  153 + if (this.progressForFile > 0) {
  154 + Row() {
  155 + Progress({ value: 0, total: 100, type: ProgressType.Capsule })
  156 + .width('80%')
  157 + .height(20)
  158 + .value(this.progressForFile);
  159 +
  160 + Text(`${this.progressForFile.toFixed(2)}%`).width('15%')
  161 + }.width('100%').justifyContent(FlexAlign.Center)
  162 + }
  163 +
  164 + TextArea({ text: this.resultForFile })
  165 + .lineSpacing({ value: 10, unit: LengthUnit.VP })
  166 + .width('100%')
  167 + .height('100%')
  168 + }
  169 + }.tabBar(this.TabBuilder('From file', 0, $r('app.media.icon_doc'), $r('app.media.icon_doc')))
  170 +
  171 + TabContent() {
  172 + Column({ space: 10 }) {
  173 + Text(this.title).fontSize(this.titleFontSize).fontWeight(FontWeight.Bold);
  174 + TextArea({
  175 + text: `
  176 +Everyting is open-sourced.
  177 +
  178 +It runs locally, without accessing the network
  179 +
  180 +See also https://github.com/k2-fsa/sherpa-onnx
  181 +
  182 +新一代 Kaldi QQ 和微信交流群: 请看
  183 +
  184 +https://k2-fsa.github.io/sherpa/social-groups.html
  185 +
  186 +微信公众号: 新一代 Kaldi
  187 + `
  188 + }).width('100%').height('100%').focusable(false)
  189 + }.justifyContent(FlexAlign.Start)
  190 + }.tabBar(this.TabBuilder('Help', 1, $r('app.media.info'), $r('app.media.info')))
  191 + }.scrollable(false)
  192 + }
  193 + }
  194 +}
  1 +import worker, { ErrorEvent, MessageEvents, ThreadWorkerGlobalScope } from '@ohos.worker';
  2 +import {
  3 + OfflineSpeakerDiarization,
  4 + OfflineSpeakerDiarizationConfig,
  5 + OfflineSpeakerDiarizationSegment,
  6 + readWaveFromBinary,
  7 + Samples
  8 +} from 'sherpa_onnx';
  9 +import { fileIo } from '@kit.CoreFileKit';
  10 +
  11 +const workerPort: ThreadWorkerGlobalScope = worker.workerPort;
  12 +
  13 +let sd: OfflineSpeakerDiarization;
  14 +let useAsync: boolean = true;
  15 +
  16 +function readWave(filename: string): Samples {
  17 + const fp = fileIo.openSync(filename);
  18 + const stat = fileIo.statSync(fp.fd);
  19 + const arrayBuffer = new ArrayBuffer(stat.size);
  20 + fileIo.readSync(fp.fd, arrayBuffer);
  21 + const data: Uint8Array = new Uint8Array(arrayBuffer);
  22 + return readWaveFromBinary(data) as Samples;
  23 +}
  24 +
  25 +function initOfflineSpeakerDiarization(context: Context): OfflineSpeakerDiarization {
  26 + const config: OfflineSpeakerDiarizationConfig = new OfflineSpeakerDiarizationConfig();
  27 +
  28 + // Please refer to https://github.com/k2-fsa/sherpa-onnx/releases/tag/speaker-segmentation-models
  29 + // to download models.
  30 + // Make sure you have placed it inside the directory
  31 + // harmony-os/SherpaOnnxSpeakerDiarization/entry/src/main/resources/rawfile
  32 + //
  33 + // Also, please delete unused files to reduce the size of the app
  34 + config.segmentation.pyannote.model = 'sherpa-onnx-pyannote-segmentation-3-0/model.int8.onnx';
  35 + config.segmentation.numThreads = 2;
  36 + config.segmentation.debug = true;
  37 +
  38 + // Please refer to https://github.com/k2-fsa/sherpa-onnx/releases/tag/speaker-recongition-models
  39 + // to download models.
  40 + // Make sure you have placed it inside the directory
  41 + // harmony-os/SherpaOnnxSpeakerDiarization/entry/src/main/resources/rawfile
  42 + config.embedding.model = '3dspeaker_speech_eres2net_base_sv_zh-cn_3dspeaker_16k.onnx';
  43 + config.embedding.numThreads = 2;
  44 + config.embedding.debug = true;
  45 +
  46 + config.minDurationOn = 0.2;
  47 + config.minDurationOff = 0.5;
  48 + return new OfflineSpeakerDiarization(config, context.resourceManager);
  49 +
  50 + // For the above two models files, you should have the following directory structure
  51 + /*
  52 + (py38) fangjuns-MacBook-Pro:rawfile fangjun$ pwd
  53 + /Users/fangjun/open-source/sherpa-onnx/harmony-os/SherpaOnnxSpeakerDiarization/entry/src/main/resources/rawfile
  54 + (py38) fangjuns-MacBook-Pro:rawfile fangjun$ ls -lh
  55 + total 77336
  56 + -rw-r--r-- 1 fangjun staff 38M Dec 10 16:28 3dspeaker_speech_eres2net_base_sv_zh-cn_3dspeaker_16k.onnx
  57 + drwxr-xr-x 3 fangjun staff 96B Dec 10 19:36 sherpa-onnx-pyannote-segmentation-3-0
  58 + (py38) fangjuns-MacBook-Pro:rawfile fangjun$ tree .
  59 + .
  60 + ├── 3dspeaker_speech_eres2net_base_sv_zh-cn_3dspeaker_16k.onnx
  61 + └── sherpa-onnx-pyannote-segmentation-3-0
  62 + └── model.int8.onnx
  63 +
  64 + 1 directory, 2 files
  65 +
  66 + (Note that we have kept only model.int8.onnx and removed all other files
  67 + from sherpa-onnx-pyannote-segmentation-3-0
  68 + )
  69 + */
  70 +}
  71 +
  72 +/**
  73 + * Defines the event handler to be called when the worker thread receives a message sent by the host thread.
  74 + * The event handler is executed in the worker thread.
  75 + *
  76 + * @param e message data
  77 + */
  78 +workerPort.onmessage = (e: MessageEvents) => {
  79 + const msgType = e.data['msgType'] as string;
  80 +
  81 + console.log(`from the main thread, msg-type: ${msgType}`);
  82 + if (msgType == 'init-speaker-diarization' && !sd) {
  83 + const context: Context = e.data['context'] as Context;
  84 + sd = initOfflineSpeakerDiarization(context);
  85 + workerPort.postMessage({ msgType: 'init-speaker-diarization-done' });
  86 + console.log('Init sd done');
  87 + }
  88 +
  89 + if (msgType == 'speaker-diarization-file') {
  90 + const filename = e.data['filename'] as string;
  91 + const numSpeakers = e.data['numSpeakers'] as number;
  92 + const wave = readWave(filename);
  93 + let result = '';
  94 + if (wave == undefined || wave == null) {
  95 + result = `Failed to read ${filename}`;
  96 +
  97 + workerPort.postMessage({
  98 + msgType: 'speaker-diarization-file-done', result
  99 + });
  100 + return;
  101 + }
  102 +
  103 + if (wave.sampleRate != sd.sampleRate) {
  104 + result = `Expected sample rate: ${sd.sampleRate}`;
  105 + result += '\n';
  106 + result += `Sample rate in file ${filename} is ${wave.sampleRate}`;
  107 +
  108 + workerPort.postMessage({
  109 + msgType: 'speaker-diarization-file-done', result
  110 + });
  111 +
  112 + return;
  113 + }
  114 +
  115 + const duration = wave.samples.length / wave.sampleRate;
  116 + console.log(`Processing ${filename} of ${duration} seconds`);
  117 +
  118 + // You can remove this if statement if you want
  119 + if (duration < 0.3) {
  120 + result = `${filename} has only ${duration} seconds. Please use a longer file`;
  121 +
  122 + workerPort.postMessage({
  123 + msgType: 'speaker-diarization-file-done', result
  124 + });
  125 + return;
  126 + }
  127 + sd.config.clustering.numClusters = numSpeakers;
  128 + sd.setConfig(sd.config);
  129 +
  130 + if (useAsync) {
  131 + sd.processAsync(wave.samples, (numProcessedChunks: number, numTotalChunks: number) => {
  132 + const progress = numProcessedChunks / numTotalChunks * 100;
  133 + workerPort.postMessage({
  134 + msgType: 'speaker-diarization-file-progress', progress
  135 + });
  136 + }).then((r: OfflineSpeakerDiarizationSegment[]) => {
  137 + console.log(`r is ${r.length}, ${r}`);
  138 +
  139 + for (const s of r) {
  140 + const start: string = s.start.toFixed(3);
  141 + const end: string = s.end.toFixed(3);
  142 + result += `${start}\t--\t${end}\tspeaker_${s.speaker}\n`;
  143 + console.log(`result: ${result}`);
  144 + }
  145 +
  146 + if (r.length == 0) {
  147 + result = 'The result is empty';
  148 + }
  149 +
  150 + workerPort.postMessage({
  151 + msgType: 'speaker-diarization-file-done', result
  152 + });
  153 + });
  154 + } else {
  155 + const r: OfflineSpeakerDiarizationSegment[] = sd.process(wave.samples)
  156 + console.log(`r is ${r.length}, ${r}`);
  157 + for (const s of r) {
  158 + const start: string = s.start.toFixed(3);
  159 + const end: string = s.end.toFixed(3);
  160 + result += `${start}\t--\t${end}\tspeaker_${s.speaker}\n`;
  161 + console.log(`result: ${result}`);
  162 + }
  163 +
  164 + if (r.length == 0) {
  165 + result = 'The result is empty';
  166 + }
  167 +
  168 + workerPort.postMessage({
  169 + msgType: 'speaker-diarization-file-done', result
  170 + });
  171 + }
  172 + }
  173 +} /**
  174 + * Defines the event handler to be called when the worker receives a message that cannot be deserialized.
  175 + * The event handler is executed in the worker thread.
  176 + *
  177 + * @param e message data
  178 + */
  179 +workerPort.onmessageerror = (e: MessageEvents) => {
  180 +}
  181 +
  182 +/**
  183 + * Defines the event handler to be called when an exception occurs during worker execution.
  184 + * The event handler is executed in the worker thread.
  185 + *
  186 + * @param e error message
  187 + */
  188 +workerPort.onerror = (e: ErrorEvent) => {
  189 +}
  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 + }
  52 +}
  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 speaker diarization with Next-gen Kaldi"
  6 + },
  7 + {
  8 + "name": "EntryAbility_desc",
  9 + "value": "On-device speaker diarization with Next-gen Kaldi"
  10 + },
  11 + {
  12 + "name": "EntryAbility_label",
  13 + "value": "Speaker diarization"
  14 + }
  15 + ]
  16 +}
  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 speaker diarization with Next-gen Kaldi"
  6 + },
  7 + {
  8 + "name": "EntryAbility_desc",
  9 + "value": "On-device speaker diarization with Next-gen Kaldi"
  10 + },
  11 + {
  12 + "name": "EntryAbility_label",
  13 + "value": "Speaker diarization"
  14 + }
  15 + ]
  16 +}
  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 +}
  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 +}
@@ -2053,11 +2053,6 @@ SherpaOnnxCreateOfflineSpeakerDiarizationOHOS( @@ -2053,11 +2053,6 @@ SherpaOnnxCreateOfflineSpeakerDiarizationOHOS(
2053 2053
2054 auto sd_config = GetOfflineSpeakerDiarizationConfig(config); 2054 auto sd_config = GetOfflineSpeakerDiarizationConfig(config);
2055 2055
2056 - if (!sd_config.Validate()) {  
2057 - SHERPA_ONNX_LOGE("Errors in config");  
2058 - return nullptr;  
2059 - }  
2060 -  
2061 SherpaOnnxOfflineSpeakerDiarization *sd = 2056 SherpaOnnxOfflineSpeakerDiarization *sd =
2062 new SherpaOnnxOfflineSpeakerDiarization; 2057 new SherpaOnnxOfflineSpeakerDiarization;
2063 2058
@@ -1512,10 +1512,10 @@ SHERPA_ONNX_API void SherpaOnnxOfflineSpeakerDiarizationDestroySegment( @@ -1512,10 +1512,10 @@ SHERPA_ONNX_API void SherpaOnnxOfflineSpeakerDiarizationDestroySegment(
1512 const SherpaOnnxOfflineSpeakerDiarizationSegment *s); 1512 const SherpaOnnxOfflineSpeakerDiarizationSegment *s);
1513 1513
1514 typedef int32_t (*SherpaOnnxOfflineSpeakerDiarizationProgressCallback)( 1514 typedef int32_t (*SherpaOnnxOfflineSpeakerDiarizationProgressCallback)(
1515 - int32_t num_processed_chunk, int32_t num_total_chunks, void *arg); 1515 + int32_t num_processed_chunks, int32_t num_total_chunks, void *arg);
1516 1516
1517 typedef int32_t (*SherpaOnnxOfflineSpeakerDiarizationProgressCallbackNoArg)( 1517 typedef int32_t (*SherpaOnnxOfflineSpeakerDiarizationProgressCallbackNoArg)(
1518 - int32_t num_processed_chunk, int32_t num_total_chunks); 1518 + int32_t num_processed_chunks, int32_t num_total_chunks);
1519 1519
1520 // The user has to invoke SherpaOnnxOfflineSpeakerDiarizationDestroyResult() 1520 // The user has to invoke SherpaOnnxOfflineSpeakerDiarizationDestroyResult()
1521 // to free the returned pointer to avoid memory leak. 1521 // to free the returned pointer to avoid memory leak.