@@ -3,59 +3,154 @@ import worker, { MessageEvents } from '@ohos.worker';
33import { BusinessError } from '@kit.BasicServicesKit';
44import { picker } from '@kit.CoreFileKit';
55
6+ import { Permissions } from '@kit.AbilityKit';
7+ import { allAllowed, requestPermissions } from './Permission';
8+ import { audio } from '@kit.AudioKit';
9+
610
711@Entry
812@Component
913struct Index {
1014 @State currentIndex: number = 0;
11- @State resultFromFile : string = '';
15+ @State resultForFile : string = '';
1216 @State progressForFile: number = 0;
1317 @State selectFileBtnEnabled: boolean = false;
14- @State message: string = 'To be implemented';
1518 @State lang: string = 'English';
19+ @State resultForMic: string = '';
20+ @State micStarted: boolean = false;
21+ @State message: string = 'Start recording';
22+ @State micInitDone: boolean = false;
1623 private controller: TabsController = new TabsController();
1724 private workerInstance?: worker.ThreadWorker
1825 private readonly scriptURL: string = 'entry/ets/workers/NonStreamingAsrWithVadWorker.ets'
26+ private mic?: audio.AudioCapturer;
27+ private sampleList: Float32Array[] = []
28+
29+ flatten(samples: Float32Array[]): Float32Array {
30+ let n = 0;
31+ for (let i = 0; i < samples.length; ++i) {
32+ n += samples[i].length;
33+ }
34+
35+ const ans: Float32Array = new Float32Array(n);
36+ let offset: number = 0;
37+ for (let i = 0; i < samples.length; ++i) {
38+ ans.set(samples[i], offset);
39+ offset += samples[i].length;
40+ }
41+
42+ return ans;
43+ }
44+
45+ async initMic() {
46+ const permissions: Permissions[] = ["ohos.permission.MICROPHONE"];
47+ let allowed: boolean = await allAllowed(permissions);
48+ if (!allowed) {
49+ requestPermissions(permissions);
50+ console.log("request to access the microphone");
51+
52+ allowed = await allAllowed(permissions);
53+ if (!allowed) {
54+ console.error('failed to get microphone permission');
55+ this.resultForMic = "Failed to get microphone permission. Please retry";
56+ return;
57+ }
58+ } else {
59+ console.log("allowed to access microphone");
60+ }
1961
20- aboutToAppear(): void {
62+ const audioStreamInfo: audio.AudioStreamInfo = {
63+ samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_16000,
64+ channels: audio.AudioChannel.CHANNEL_1,
65+ sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,
66+ encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW,
67+ };
68+
69+ const audioCapturerInfo: audio.AudioCapturerInfo = {
70+ source: audio.SourceType.SOURCE_TYPE_MIC,
71+ capturerFlags: 0
72+ };
73+
74+ const audioCapturerOptions: audio.AudioCapturerOptions = {
75+ streamInfo: audioStreamInfo,
76+ capturerInfo: audioCapturerInfo
77+
78+ };
79+ audio.createAudioCapturer(audioCapturerOptions, (err, data) => {
80+ if (err) {
81+ console.error(`error code is ${err.code}, error message is ${err.message}`);
82+ this.resultForMic = 'Failed to init microphone';
83+ } else {
84+ console.info(`init mic successfully`);
85+ this.mic = data;
86+ this.mic.on('readData', this.micCallback);
87+
88+ if (this.workerInstance) {
89+ this.workerInstance.postMessage({ msgType: 'init-vad-mic', context: getContext() });
90+ }
91+ }
92+ });
93+ }
94+
95+ async aboutToAppear() {
2196 this.workerInstance = new worker.ThreadWorker(this.scriptURL, {
2297 name: 'NonStreaming ASR worker'
2398 });
2499
25100 this.workerInstance.onmessage = (e: MessageEvents) => {
26101 const msgType = e.data['msgType'] as string;
27- console.log(`received data ${msgType}`);
102+ console.log(`received msg from worker: ${msgType}`);
103+
104+ if (msgType == 'init-vad-mic-done') {
105+ this.micInitDone = true;
106+ }
28107
29108 if (msgType == 'init-non-streaming-asr-done') {
30109 this.selectFileBtnEnabled = true;
110+ this.resultForFile = `Initializing done.\n\nPlease select a wave file of 16kHz in language ${this.lang}`;
31111 }
32112
33113 if (msgType == 'non-streaming-asr-vad-decode-done') {
34- this.resultFromFile = e.data['text'] as string + '\n';
114+ this.resultForFile = e.data['text'] as string + '\n';
35115 }
36116
37117 if (msgType == 'non-streaming-asr-vad-decode-partial') {
38- if (this.resultFromFile == '') {
39- this.resultFromFile = e.data['text'] as string;
118+ if (this.resultForFile == '') {
119+ this.resultForFile = e.data['text'] as string;
40120 } else {
41- this.resultFromFile += '\n\n' + e.data['text'] as string;
121+ this.resultForFile += '\n\n' + e.data['text'] as string;
42122 }
43123 }
44124
45125 if (msgType == 'non-streaming-asr-vad-decode-error') {
46- this.resultFromFile = e.data['text'] as string;
126+ this.resultForFile = e.data['text'] as string;
47127 }
48128
49129 if (msgType == 'non-streaming-asr-vad-decode-progress') {
50130 this.progressForFile = e.data['progress'] as number;
51131
52132 this.selectFileBtnEnabled = this.progressForFile >= 100;
53133 }
134+
135+ if (msgType == 'non-streaming-asr-vad-mic-partial') {
136+ if (this.resultForMic == '') {
137+ this.resultForMic = e.data['text'] as string;
138+ } else {
139+ this.resultForMic += '\n\n' + e.data['text'] as string;
140+ }
141+ }
142+
143+ if (msgType == 'non-streaming-asr-vad-mic-error') {
144+ this.resultForMic = e.data['text'] as string;
145+ }
54146 }
55147
56148 const context = getContext();
149+ this.resultForFile = 'Initializing models';
57150 this.workerInstance.postMessage({ msgType: 'init-vad', context });
58151 this.workerInstance.postMessage({ msgType: 'init-non-streaming-asr', context });
152+
153+ await this.initMic();
59154 }
60155
61156 @Builder
@@ -86,13 +181,13 @@ struct Index {
86181 .lineHeight(41)
87182 .fontWeight(500)
88183
89- Button('Select .wav file ')
184+ Button('Select .wav file (16kHz) ')
90185 .enabled(this.selectFileBtnEnabled)
91186 .fontSize(13)
92187 .width(296)
93188 .height(60)
94189 .onClick(() => {
95- this.resultFromFile = '';
190+ this.resultForFile = '';
96191 this.progressForFile = 0;
97192
98193 const documentSelectOptions = new picker.DocumentSelectOptions();
@@ -103,7 +198,7 @@ struct Index {
103198 console.log(`Result: ${result}`);
104199
105200 if (!result[0]) {
106- this.resultFromFile = 'Please select a file to decode';
201+ this.resultForFile = 'Please select a file to decode';
107202 this.selectFileBtnEnabled = true;
108203 return;
109204 }
@@ -135,7 +230,7 @@ struct Index {
135230 }.width('100%').justifyContent(FlexAlign.Center)
136231 }
137232
138- TextArea({ text: this.resultFromFile }).width('100%').lineSpacing({ value: 10, unit: LengthUnit.VP });
233+ TextArea({ text: this.resultForFile }).width('100%').lineSpacing({ value: 10, unit: LengthUnit.VP });
139234
140235 }
141236 .alignItems(HorizontalAlign.Center)
@@ -144,10 +239,50 @@ struct Index {
144239
145240 TabContent() {
146241 Column() {
147- Text(this.message)
148- .fontSize(50)
149- .fontWeight(FontWeight.Bold);
242+ Button(this.message)
243+ .enabled(this.micInitDone)
244+ .onClick(() => {
245+ console.log('clicked mic button');
246+ this.resultForMic = '';
247+ if (this.mic) {
248+ if (this.micStarted) {
249+ this.mic.stop();
250+ this.message = "Start recording";
251+ this.micStarted = false;
252+ console.log('mic stopped');
253+
254+ const samples = this.flatten(this.sampleList);
255+ let s = 0;
256+ for (let i = 0; i < samples.length; ++i) {
257+ s += samples[i];
258+ }
259+ console.log(`samples ${samples.length}, sum: ${s}`);
260+
261+ if (this.workerInstance) {
262+ console.log('decode mic');
263+ this.workerInstance.postMessage({
264+ msgType: 'non-streaming-asr-vad-mic',
265+ samples,
266+ });
267+ } else {
268+ console.log(`this worker instance is undefined ${this.workerInstance}`);
269+ }
270+ } else {
271+ this.sampleList = [];
272+ this.mic.start();
273+ this.message = "Stop recording";
274+ this.micStarted = true;
275+ console.log('mic started');
276+ }
277+ }
278+ });
279+
280+ Text(`Supported languages: ${this.lang}`)
281+
282+ TextArea({ text: this.resultForMic }).width('100%').lineSpacing({ value: 10, unit: LengthUnit.VP });
150283 }
284+ .alignItems(HorizontalAlign.Center)
285+ .justifyContent(FlexAlign.Start)
151286 }
152287 .tabBar(this.TabBuilder('From mic', 1, $r('app.media.ic_public_input_voice'),
153288 $r('app.media.ic_public_input_voice_default')))
@@ -170,4 +305,14 @@ struct Index {
170305 .width('100%')
171306 .justifyContent(FlexAlign.Start)
172307 }
308+
309+ private micCallback = (buffer: ArrayBuffer) => {
310+ const view: Int16Array = new Int16Array(buffer);
311+
312+ const samplesFloat: Float32Array = new Float32Array(view.length);
313+ for (let i = 0; i < view.length; ++i) {
314+ samplesFloat[i] = view[i] / 32768.0;
315+ }
316+ this.sampleList.push(samplesFloat);
317+ }
173318}
0 commit comments