We use essential cookies for the website to function, as well as analytics cookies for analyzing and creating statistics of the website performance. To agree to the use of analytics cookies, click "Accept All". You can manage your preferences at any time by clicking "Cookie Settings" on the footer. More Information.

Only Essential Cookies
Accept All
GuidesAICore Speech KitText-to-Speech

Text-to-Speech

Core Speech Kit can synthesize Chinese and English text (simplified Chinese, Traditional Chinese, digits, and English) with a maximum of 10,000 characters into speech and output the speech using a selected timbre.

You can set playback policies related to words, digits, mute pause, and Chinese character pronunciation.

Use Cases

A system app with the text-to-speech (TTS) function integrated can offer voice narration capabilities for users with visual impairments or in scenarios where reading is not convenient, even when the device (mobile phone, tablet, and others) is disconnected from the Internet.

Constraints

Expand
AI Capability Constraint
Text-to-speech

- Supported languages: Chinese and English (Simplified Chinese, Traditional Chinese, and English within a Chinese context).

- Supported timbre types: Ling Xiaoshan female voice, Laura female voice for English (US), and Ling Feizhe male voice.

- Text length: no more than 10,000 characters

Development Procedure

  1. Add the TTS-related classes to the project.

    Collapse
    Word wrap
    Dark theme
    Copy code
    1. import { textToSpeech } from '@kit.CoreSpeechKit';
    2. import { BusinessError } from '@kit.BasicServicesKit';
  2. Call the createEngine API to create a TextToSpeechEngine instance.

    The createEngine API can be called in two ways. The following uses one way as an example. For details about the other way, please refer to the API Reference.

    Collapse
    Word wrap
    Dark theme
    Copy code
    1. let ttsEngine: textToSpeech.TextToSpeechEngine;
    2. // Set the parameters for creating an engine.
    3. let extraParam: Record<string, Object> = {'style': 'interaction-broadcast', 'locate': 'CN', 'name': 'EngineName'};
    4. let initParamsInfo: textToSpeech.CreateEngineParams = {
    5. language: 'zh-CN',
    6. person: 0,
    7. online: 1,
    8. extraParams: extraParam
    9. };
    10. // Call the createEngine method.
    11. textToSpeech.createEngine(initParamsInfo, (err: BusinessError,
    12. textToSpeechEngine: textToSpeech.TextToSpeechEngine) => {
    13. if (!err) {
    14. console.info('Succeeded in creating engine');
    15. // Receive the instance for engine creation.
    16. ttsEngine = textToSpeechEngine;
    17. } else {
    18. console.error(`Failed to create engine. Code: ${err.code}, message: ${err.message}.`);
    19. }
    20. });
  3. After obtaining the TextToSpeechEngine instance, instantiate the SpeakParams and SpeakListener objects, pass the text to be synthesized and played (originalText), and call the speak API to play the text.

    Collapse
    Word wrap
    Dark theme
    Copy code
    1. // Define the callback methods for speakListener.
    2. let speakListener: textToSpeech.SpeakListener = {
    3. // Callback triggered upon initialization of playback.
    4. onStart(requestId: string, response: textToSpeech.StartResponse) {
    5. console.info(`onStart, requestId: ${requestId} response: ${JSON.stringify(response)}`);
    6. },
    7. // Callback triggered upon completion of speech synthesis or playback.
    8. onComplete(requestId: string, response: textToSpeech.CompleteResponse) {
    9. console.info(`onComplete, requestId: ${requestId} response: ${JSON.stringify(response)}`);
    10. },
    11. // Callback triggered when the playback stops.
    12. onStop(requestId: string, response: textToSpeech.StopResponse) {
    13. console.info(`onStop, requestId: ${requestId} response: ${JSON.stringify(response)}`);
    14. },
    15. // Callback triggered when the audio stream is returned.
    16. onData(requestId: string, audio: ArrayBuffer, response: textToSpeech.SynthesisResponse) {
    17. console.info(`onData, requestId: ${requestId} sequence: ${JSON.stringify(response)} audio: ${JSON.stringify(audio)}`);
    18. },
    19. // Callback triggered when an error occurs.
    20. onError(requestId: string, errorCode: number, errorMessage: string) {
    21. console.error(`onError, requestId: ${requestId} errorCode: ${errorCode} errorMessage: ${errorMessage}`);
    22. }
    23. };
    24. // Set callback methods.
    25. ttsEngine.setListener(speakListener);
    26. // Set the content to play back.
    27. let originalText: string = 'Hello HarmonyOS';
    28. // Set playback parameters.
    29. let extraParam: Record<string, Object> = {
    30. 'queueMode': 0,
    31. 'speed': 1,
    32. 'volume': 2,
    33. 'pitch': 1,
    34. 'languageContext': 'zh-CN',
    35. 'audioType': 'pcm',
    36. 'soundChannel': 3,
    37. 'playType': 1
    38. };
    39. let speakParams: textToSpeech.SpeakParams = {
    40. requestId: '123456', // requestId can be used only once in the same instance. Do not set it repeatedly.
    41. extraParams: extraParam
    42. };
    43. // Call the playback method.
    44. // You can modify speakParams to set playback policies.
    45. try {
    46. ttsEngine.speak(originalText, speakParams);
    47. } catch (err) {
    48. console.error(`error code: ${err.code}, message: ${err.message}.`);
    49. }
  4. (Optional) To stop synthesis and playback, call stop.

    Collapse
    Word wrap
    Dark theme
    Copy code
    1. ttsEngine.stop();
  5. (Optional) To check whether the TTS service is busy, call isBusy.

    Collapse
    Word wrap
    Dark theme
    Copy code
    1. ttsEngine.isBusy();
  6. (Optional) To query the timbre information of supported languages, call listVoices.

    The listVoices API can be called in two ways. The following uses one way as an example. For details about the other way, please refer to the API Reference.

    Collapse
    Word wrap
    Dark theme
    Copy code
    1. // Declare and initialize the string voiceInfo in the component.
    2. @State voiceInfo: string = '';
    3. // Set query parameters.
    4. let voicesQuery: textToSpeech.VoiceQuery = {
    5. requestId: '12345678', // requestId can be used only once in the same instance. Do not set it repeatedly.
    6. online: 1
    7. };
    8. // Call listVoices. The result will be returned through a callback.
    9. ttsEngine.listVoices(voicesQuery, (err: BusinessError, voiceInfo: textToSpeech.VoiceInfo[]) => {
    10. if (!err) {
    11. // Receive information such as the supported languages and timbres.
    12. this.voiceInfo = JSON.stringify(voiceInfo);
    13. console.info(`Succeeded in listing voices, voiceInfo is ${this.voiceInfo}`);
    14. } else {
    15. console.error(`Failed to list voices. Code: ${err.code}, message: ${err.message}`);
    16. }
    17. });

Setting a playback policy

In different scenarios, playback policies automatically selected by the model may not meet the actual requirement. This section describes how to proactively set playback policies.

NOTE

Valid values are provided in the relevant sections below. If the input value is outside its range, the playback may not work as expected, and an incorrect playback result will be generated.

Setting a word playback mode

Text format: [hN] (N=0/1/2)

The values of N are described as follows.

Expand
Value Description
0 Intelligently determines the word playback mode. The default value is 0.
1 Plays the content in letter-by-letter mode.
2 Plays the content as a word.

Text example:

Collapse
Word wrap
Dark theme
Copy code
  1. 'hello[h1] world'

The word "hello" is pronounced as a word, and the word "world" and subsequent words are pronounced letter by letter.

Setting a digit playback policy

Format: [nN] (N=0/1/2)

The values of N are described as follows.

Expand
Value Description
0 Intelligently determines the digit playback policy. The default value is 0.
1 Plays the content in digit-by-digit mode.
2 Plays the content as a number. Numbers exceeding 18 digits are not supported and will be read out digit by digit automatically.

Text example:

Collapse
Word wrap
Dark theme
Copy code
  1. '[n2]123[n1]456[n0]'

In the example, 123 is played as a number, 456 is played digit by digit, and how the digits in the following text are played is automatically determined.

Inserting a mute pause

Format: [pN]

Description: N is an unsigned integer in the unit of ms.

Text example:

Collapse
Word wrap
Dark theme
Copy code
  1. 'Hello[p500]Celia'

When this sentence is played, a 500 ms mute pause is inserted after "Hello".

Specifying pronunciation of Chinese characters

The tone of a Chinese character is represented by a digit from 1 to 5, indicating five tones: level tone, rising tone, falling-rising tone, falling tone, and light tone.

Format: [=MN]

Description: M indicates pinyin, and N indicates tone.

The values of N are described as follows.

Expand
Value Description
1 Level tone
2 Yang Ping
3 Falling-rising tone
4 Falling tone
5 Light tone

Text example:

Collapse
Word wrap
Dark theme
Copy code
  1. 'Text to be played[=**zhuo2**]'

The text will be played in "zhuó" (rising tone).

Development example

Implement a button that supports text-to-speech conversion and playback.

Index.ets

Collapse
Word wrap
Dark theme
Copy code
  1. import { textToSpeech } from '@kit.CoreSpeechKit';
  2. import { BusinessError } from '@kit.BasicServicesKit';
  3. import { PromptAction, UIContext } from '@kit.ArkUI';
  4. import { TreeMap } from '@kit.ArkTS';
  5. import { fileIo } from '@kit.CoreFileKit';
  6. import PcmPlayer from './PcmPlayer';
  7. import { audio } from '@kit.AudioKit';
  8. import { Context } from '@kit.AbilityKit';
  9. const TAG: string = 'TtsDemo';
  10. let ttsEngine: textToSpeech.TextToSpeechEngine;
  11. let bufferLength: number = 0;
  12. let engineCreated: boolean = false;
  13. // Define a function to concatenate ArrayBuffers.
  14. function concatenateArrayBuffers(buffers: ArrayBuffer[]): ArrayBuffer {
  15. const totalLength = buffers.reduce((sum, buffer) => sum + buffer.byteLength, 0);
  16. const concatenatedBuffer = new ArrayBuffer(totalLength);
  17. let offset = 0;
  18. for (const buffer of buffers) {
  19. const uint8Array = new Uint8Array(buffer);
  20. new Uint8Array(concatenatedBuffer, offset, uint8Array.length).set(uint8Array);
  21. offset += uint8Array.length;
  22. }
  23. return concatenatedBuffer;
  24. }
  25. @Entry
  26. @Component
  27. struct Index {
  28. @State createCount: number = 0;
  29. // Set the content to play back.
  30. @State originalText: string = '\n\t\t *Text to be converted into speech* \n\t\t' + '*Text to be converted into speech* \n\t\t';
  31. @State uiContext: UIContext = this.getUIContext();
  32. @State promptAction: PromptAction = this.uiContext.getPromptAction();
  33. private pcmData: TreeMap<number, Uint8Array> = new TreeMap();
  34. private pcmPlayer: PcmPlayer = new PcmPlayer();
  35. build() {
  36. Column() {
  37. Scroll() {
  38. Column() {
  39. TextArea({ placeholder: 'Please enter tts original text', text: `${this.originalText}` })
  40. .margin(20)
  41. .focusable(false)
  42. .border({ width: 5, color: 0x317AE7, radius: 10, style: BorderStyle.Dotted })
  43. .onChange((value: string) => {
  44. this.originalText = value;
  45. console.info(TAG, 'original text: ' + this.originalText);
  46. })
  47. Button() {
  48. Text('CreateEngine')
  49. .fontColor(Color.White)
  50. .fontSize(20)
  51. }
  52. .type(ButtonType.Capsule)
  53. .backgroundColor('#0x317AE7')
  54. .width('80%')
  55. .height(50)
  56. .margin(10)
  57. .onClick(() => {
  58. engineCreated = true;
  59. this.createCount++;
  60. console.info(`createByCallback: createCount:${this.createCount}`);
  61. this.createByCallback();
  62. try {
  63. this.promptAction.showToast({
  64. message: 'CreateEngine success!',
  65. duration: 2000
  66. });
  67. } catch (error) {
  68. let message = (error as BusinessError).message;
  69. let code = (error as BusinessError).code;
  70. console.error(`showToast args error code is ${code}, message is ${message}`);
  71. }
  72. })
  73. Button() {
  74. Text('speak')
  75. .fontColor(Color.White)
  76. .fontSize(20)
  77. }
  78. .type(ButtonType.Capsule)
  79. .backgroundColor('#0x317AE7')
  80. .width('80%')
  81. .height(50)
  82. .margin(10)
  83. .onClick(() => {
  84. if (engineCreated) {
  85. try {
  86. this.speak();
  87. try {
  88. this.promptAction.showToast({
  89. message: 'start speaking',
  90. duration: 2000
  91. });
  92. } catch (error) {
  93. let message = (error as BusinessError).message;
  94. let code = (error as BusinessError).code;
  95. console.error(`showToast args error code is ${code}, message is ${message}`);
  96. }
  97. } catch (err) {
  98. try {
  99. this.promptAction.showToast({
  100. message: 'start speaking failed',
  101. duration: 2000
  102. });
  103. } catch (error) {
  104. let message = (error as BusinessError).message;
  105. let code = (error as BusinessError).code;
  106. console.error(`showToast args error code is ${code}, message is ${message}`);
  107. }
  108. }
  109. } else {
  110. try {
  111. this.promptAction.showToast({
  112. message: 'The engine has not been created',
  113. duration: 2000
  114. });
  115. } catch (error) {
  116. let message = (error as BusinessError).message;
  117. let code = (error as BusinessError).code;
  118. console.error(`showToast args error code is ${code}, message is ${message}`);
  119. }
  120. }
  121. })
  122. Button() {
  123. Text('speakOnData')
  124. .fontColor(Color.White)
  125. .fontSize(20)
  126. }
  127. .type(ButtonType.Capsule)
  128. .backgroundColor('#0x317AE7')
  129. .width('80%')
  130. .height(50)
  131. .margin(10)
  132. .onClick(() => {
  133. if (engineCreated) {
  134. try {
  135. void this.speakOnData();
  136. try {
  137. this.promptAction.showToast({
  138. message: 'start speakOnData',
  139. duration: 2000
  140. });
  141. } catch (error) {
  142. let message = (error as BusinessError).message;
  143. let code = (error as BusinessError).code;
  144. console.error(`showToast args error code is ${code}, message is ${message}`);
  145. }
  146. } catch (err) {
  147. try {
  148. this.promptAction.showToast({
  149. message: 'start speakOnData failed',
  150. duration: 2000
  151. });
  152. } catch (error) {
  153. let message = (error as BusinessError).message;
  154. let code = (error as BusinessError).code;
  155. console.error(`showToast args error code is ${code}, message is ${message}`);
  156. }
  157. }
  158. } else {
  159. try {
  160. this.promptAction.showToast({
  161. message: 'The engine has not been created',
  162. duration: 2000
  163. });
  164. } catch (error) {
  165. let message = (error as BusinessError).message;
  166. let code = (error as BusinessError).code;
  167. console.error(`showToast args error code is ${code}, message is ${message}`);
  168. }
  169. }
  170. })
  171. Button() {
  172. Text('stop')
  173. .fontColor(Color.White)
  174. .fontSize(20)
  175. }
  176. .type(ButtonType.Capsule)
  177. .backgroundColor('#0x317AE7')
  178. .width('80%')
  179. .height(50)
  180. .margin(10)
  181. .onClick(() => {
  182. try {
  183. let isBusy: boolean = ttsEngine.isBusy();
  184. let isPlaying: boolean = this.pcmPlayer.isPlaying();
  185. if (isBusy) {
  186. ttsEngine.stop();
  187. }
  188. if (isPlaying) {
  189. this.pcmPlayer.stop();
  190. }
  191. try {
  192. this.promptAction.showToast({
  193. message: 'stop!',
  194. duration: 2000
  195. });
  196. } catch (error) {
  197. let message = (error as BusinessError).message;
  198. let code = (error as BusinessError).code;
  199. console.error(`showToast args error code is ${code}, message is ${message}`);
  200. }
  201. } catch (err) {
  202. try {
  203. this.promptAction.showToast({
  204. message: 'stop failed',
  205. duration: 2000
  206. });
  207. } catch (error) {
  208. let message = (error as BusinessError).message;
  209. let code = (error as BusinessError).code;
  210. console.error(`showToast args error code is ${code}, message is ${message}`);
  211. }
  212. }
  213. })
  214. Button() {
  215. Text('isBusy')
  216. .fontColor(Color.White)
  217. .fontSize(20)
  218. }
  219. .type(ButtonType.Capsule)
  220. .backgroundColor('#0x317AE7')
  221. .width('80%')
  222. .height(50)
  223. .margin(10)
  224. .onClick(() => {
  225. try {
  226. let isBusy: boolean = ttsEngine.isBusy();
  227. let isPlaying: boolean = this.pcmPlayer.isPlaying();
  228. console.info('isBusy :' + isBusy);
  229. console.info('isPlaying :' + isPlaying);
  230. try {
  231. this.promptAction.showToast({
  232. message: 'speak isBusy :' + isBusy + '\nspeakOnData isBusy :' + isPlaying,
  233. duration: 2000
  234. });
  235. } catch (error) {
  236. let message = (error as BusinessError).message;
  237. let code = (error as BusinessError).code;
  238. console.error(`showToast args error code is ${code}, message is ${message}`);
  239. }
  240. } catch (err) {
  241. try {
  242. this.promptAction.showToast({
  243. message: 'isBusy failed',
  244. duration: 2000
  245. });
  246. } catch (error) {
  247. let message = (error as BusinessError).message;
  248. let code = (error as BusinessError).code;
  249. console.error(`showToast args error code is ${code}, message is ${message}`);
  250. }
  251. }
  252. })
  253. Button() {
  254. Text('shutdown')
  255. .fontColor(Color.White)
  256. .fontSize(20)
  257. }
  258. .type(ButtonType.Capsule)
  259. .backgroundColor('#0x317AA7')
  260. .width('80%')
  261. .height(50)
  262. .margin(10)
  263. .onClick(() => {
  264. try {
  265. this.pcmPlayer.release();
  266. ttsEngine.shutdown();
  267. engineCreated = false;
  268. try {
  269. this.promptAction.showToast({
  270. message: 'shutdown success!',
  271. duration: 2000
  272. });
  273. } catch (error) {
  274. let message = (error as BusinessError).message;
  275. let code = (error as BusinessError).code;
  276. console.error(`showToast args error code is ${code}, message is ${message}`);
  277. }
  278. } catch (err) {
  279. try {
  280. this.promptAction.showToast({
  281. message: 'shutdown failed',
  282. duration: 2000
  283. });
  284. } catch (error) {
  285. let message = (error as BusinessError).message;
  286. let code = (error as BusinessError).code;
  287. console.error(`showToast args error code is ${code}, message is ${message}`);
  288. }
  289. }
  290. })
  291. }
  292. .layoutWeight(1)
  293. }
  294. .width('100%')
  295. .height('100%')
  296. }
  297. }
  298. // Create an engine. The result will be returned through a callback.
  299. // Error code 1002300005 indicating engine creation failure is returned if the engine does not exist, engine resources do not exist, or the initialization times out.
  300. private createByCallback() {
  301. // Set the parameters for creating an engine.
  302. let extraParam: Record<string, Object> = { 'style': 'interaction-broadcast', 'locate': 'CN', 'name': 'EngineName' };
  303. let initParamsInfo: textToSpeech.CreateEngineParams = {
  304. language: 'zh-CN',
  305. person: 0,
  306. online: 1,
  307. extraParams: extraParam
  308. };
  309. try {
  310. // Call the createEngine method.
  311. textToSpeech.createEngine(initParamsInfo, (err: BusinessError,
  312. textToSpeechEngine: textToSpeech.TextToSpeechEngine) => {
  313. if (!err) {
  314. console.info('createEngine is success');
  315. // Receive the instance for engine creation.
  316. ttsEngine = textToSpeechEngine;
  317. } else {
  318. console.error(`error code: ${err.code}, message: ${err.message}.`);
  319. }
  320. });
  321. } catch (error) {
  322. let message = (error as BusinessError).message;
  323. let code = (error as BusinessError).code;
  324. console.error(`createEngine failed, error code: ${code}, message: ${message}.`);
  325. }
  326. }
  327. // Call the speak method.
  328. private speak() {
  329. let speakListener: textToSpeech.SpeakListener = {
  330. // Callback triggered upon initialization of playback.
  331. onStart(requestId: string, response: textToSpeech.StartResponse) {
  332. console.info(`onStart, requestId: ${requestId} response: ${JSON.stringify(response)}`);
  333. },
  334. // Callback triggered when playback is complete.
  335. onComplete(requestId: string, response: textToSpeech.CompleteResponse) {
  336. console.info(`onComplete, requestId: ${requestId} response: ${JSON.stringify(response)}`);
  337. },
  338. // Callback triggered when playback is stopped, that is, after stop is called and successfully executed.
  339. onStop(requestId: string, response: textToSpeech.StopResponse) {
  340. console.info(`onStop, requestId: ${requestId} response: ${JSON.stringify(response)}`);
  341. },
  342. // Callback triggered when the audio stream is returned.
  343. onData(requestId: string, audio: ArrayBuffer, response: textToSpeech.SynthesisResponse) {
  344. console.info(`onData, requestId: ${requestId} sequence: ${JSON.stringify(response)} audio: ${JSON.stringify(audio)}`);
  345. },
  346. // Callback triggered when an error occurs during the playback.
  347. onError(requestId: string, errorCode: number, errorMessage: string) {
  348. if (errorCode === 1002300007) {
  349. engineCreated = false;
  350. }
  351. console.error(`onError, requestId: ${requestId} errorCode: ${errorCode} errorMessage: ${errorMessage}`);
  352. }
  353. };
  354. // Set callback methods.
  355. ttsEngine.setListener(speakListener);
  356. // Set playback parameters.
  357. let extraParam: Record<string, Object> = {
  358. 'queueMode': 0,
  359. 'speed': 1,
  360. 'volume': 2,
  361. 'pitch': 1,
  362. 'languageContext': 'zh-CN',
  363. 'audioType': 'pcm',
  364. 'soundChannel': 3,
  365. 'playType':1
  366. };
  367. let speakParams: textToSpeech.SpeakParams = {
  368. requestId: '123456' + Date.now(), // requestId can be used only once in the same instance. Do not set it repeatedly.
  369. extraParams: extraParam
  370. };
  371. // Call the speak method.
  372. try {
  373. ttsEngine.speak(this.originalText, speakParams);
  374. } catch (err) {
  375. console.error(TAG, `error code: ${err.code}, message: ${err.message}.`);
  376. }
  377. }
  378. private onStart = (utteranceId: string, response: textToSpeech.StartResponse) => {
  379. bufferLength = 0;
  380. // Initialize audio data mapping.
  381. console.info(TAG, `onStart | utteranceId: ${ utteranceId }, response: ${JSON.stringify(response)}`);
  382. }
  383. private onData = (utteranceId: string, audio: ArrayBuffer, response: textToSpeech.SynthesisResponse) => {
  384. // Convert ArrayBuffer to Uint8Array.
  385. let uint8Array: Uint8Array = new Uint8Array(audio);
  386. this.pcmData.set(response.sequence, uint8Array);
  387. bufferLength += 1;
  388. let str = '';
  389. // Alternatively, print each element using a loop.
  390. for (let i = 0; i < uint8Array.length; i++) {
  391. str = str + (',' + uint8Array[i]);
  392. }
  393. console.info(TAG, `onData | utteranceId: ${utteranceId}, sequence: ${JSON.stringify(response.sequence)}, length: ${uint8Array.length}, audio: ${JSON.stringify(str)}`);
  394. }
  395. private onComplete = (utteranceId: string, response: textToSpeech.CompleteResponse) => {
  396. let buffers: ArrayBuffer[] = new Array();
  397. console.info(TAG, `pcmData len: ${this.pcmData.length}`);
  398. // Traverse the map and add each ArrayBuffer to the array.
  399. try {
  400. this.pcmData?.forEach((value: Uint8Array) => {
  401. buffers.push(value.buffer.slice(0));
  402. })
  403. } catch (forEachErr) {
  404. console.error(TAG, 'pcmData forEach failed:', forEachErr);
  405. }
  406. console.info(TAG, `buffers len: ${buffers.length}`);
  407. // Concatenate all ArrayBuffers in sequence.
  408. let audioData = concatenateArrayBuffers(buffers);
  409. console.info(TAG, `audioData len: ${audioData.byteLength}`);
  410. let context = this.uiContext.getHostContext() as Context;
  411. let path = context.filesDir;
  412. let filePath: string = `${path}/my.pcm`;
  413. fileIo.createStream(filePath, 'w+')
  414. .then(os => os.write(audioData).catch((e: BusinessError) => {
  415. throw new Error(`Write failed: ${e}`)
  416. }))
  417. .then((): Promise<void> => {
  418. try {
  419. this.pcmPlayer.file = fileIo.openSync(filePath, fileIo.OpenMode.READ_ONLY);
  420. return this.pcmPlayer.prepare(audio.AudioSamplingRate.SAMPLE_RATE_16000);
  421. } catch (e) {
  422. throw new Error(`Open failed: ${e}`)
  423. }
  424. })
  425. .then(() => this.pcmPlayer.play(audioData))
  426. .catch((err: BusinessError) => console.error(TAG, `Error: ${err}`));
  427. console.info(TAG, `onComplete | utteranceId: ${utteranceId}, response: ${JSON.stringify(response)}`);
  428. }
  429. // Call the speakOnData method.
  430. // Error code 1002300007 indicating synthesis and playback failure is returned if the speak method is called before engine initialization.
  431. private speakOnData() {
  432. // Set playback parameters.
  433. let extraParam: Record<string, Object> = {
  434. 'queueMode': 0,
  435. 'speed': 1.2,
  436. 'volume': 2,
  437. 'pitch': 1,
  438. 'languageContext': 'zh-CN',
  439. 'audioType': 'pcm',
  440. 'soundChannel': 1,
  441. 'playType':0
  442. };
  443. let speakParams: textToSpeech.SpeakParams = {
  444. requestId: '1234567' + Date.now(),
  445. extraParams: extraParam
  446. };
  447. try {
  448. // Create a callback object.
  449. let speakListener: textToSpeech.SpeakListener = {
  450. // Callback when the recognition is successful.
  451. onStart: this.onStart,
  452. // Callback upon recognition completion.
  453. onComplete: this.onComplete,
  454. // Callback triggered when the playback stops.
  455. onStop(utteranceId: string, response: textToSpeech.StopResponse) {
  456. console.info('speakListener onStop: ' + ' utteranceId: ' + utteranceId + ' response: ' + JSON.stringify(response));
  457. },
  458. // Callback triggered when the audio stream is returned.
  459. onData: this.onData,
  460. // Callback triggered when an error occurs.
  461. onError(utteranceId: string, errorCode: number, errorMessage: string) {
  462. if (errorCode === 1002300007) {
  463. engineCreated = false;
  464. }
  465. console.error('speakListener onError: ' + ' utteranceId: ' + utteranceId + ' errorCode: ' + errorCode + ' errorMessage: ' + errorMessage);
  466. }
  467. };
  468. // Set callback methods.
  469. ttsEngine.setListener(speakListener);
  470. try {
  471. console.info(`speakListener before speak`);
  472. // Call the speak method.
  473. for (let i = 0; i < 1; i++) {
  474. ttsEngine?.speak(this.originalText, speakParams);
  475. }
  476. console.info(`speakListener after speak`)
  477. } catch (error) {
  478. let message = (error as BusinessError).message;
  479. let code = (error as BusinessError).code;
  480. console.error(`speakListener speak failed, error code: ${code}, message: ${message}.`);
  481. }
  482. } catch (error) {
  483. let message = (error as BusinessError).message;
  484. let code = (error as BusinessError).code;
  485. console.error(`speakListener setListener failed, error code: ${code}, message: ${message}.`);
  486. }
  487. }
  488. }

PcmPlayer.ets

Collapse
Word wrap
Dark theme
Copy code
  1. import { audio } from '@kit.AudioKit';
  2. import { fileIo } from '@kit.CoreFileKit';
  3. const TAG = 'PCM_audio';
  4. class Options {
  5. offset?: number;
  6. length?: number;
  7. }
  8. export default class PcmPlayer {
  9. public file: fileIo.File | undefined;
  10. private writeDataCallback = (buffer: ArrayBuffer) => {
  11. let options: Options = {
  12. offset: this.bufferSize,
  13. length: buffer.byteLength
  14. };
  15. try {
  16. fileIo.readSync(this.file?.fd, buffer, options);
  17. this.bufferSize += buffer.byteLength;
  18. if (this.audioDataSize < this.bufferSize) {
  19. this.renderModel?.off('writeData');
  20. void this.stop()
  21. }
  22. console.info(TAG, 'reading file success');
  23. // The system determines that the buffer is valid and plays the data normally.
  24. return audio.AudioDataCallbackResult.VALID;
  25. } catch (error) {
  26. console.error(TAG, `Reading file failed, error code: ${error.code}, message: ${error.message}.`);
  27. // The system determines that the buffer is invalid and does not play the data.
  28. return audio.AudioDataCallbackResult.INVALID;
  29. }
  30. };
  31. /**
  32. * Cache size
  33. */
  34. private bufferSize: number = 0;
  35. /**
  36. * Total audio data size.
  37. */
  38. private audioDataSize: number = 0;
  39. /**
  40. *Player
  41. */
  42. private renderModel: audio.AudioRenderer | null = null;
  43. /**
  44. * Reading status
  45. */
  46. private audioStreamInfo: audio.AudioStreamInfo = {
  47. samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_16000, // Sampling rate.
  48. channels: audio.AudioChannel.CHANNEL_1, // Channel.
  49. sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // Sampling format.
  50. encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // Encoding format.
  51. };
  52. private audioRendererInfo: audio.AudioRendererInfo = {
  53. usage: audio.StreamUsage.STREAM_USAGE_ACCESSIBILITY, // Audio stream usage type.
  54. rendererFlags: 0 // AudioRenderer flag.
  55. };
  56. private audioRendererOptions: audio.AudioRendererOptions = {
  57. streamInfo: this.audioStreamInfo,
  58. rendererInfo: this.audioRendererInfo
  59. };
  60. public async prepare(sampleRate: number) {
  61. this.audioRendererOptions.streamInfo.samplingRate = sampleRate;
  62. this.audioRendererOptions.rendererInfo.usage = audio.StreamUsage.STREAM_USAGE_MUSIC;
  63. if (this.renderModel != null) {
  64. await this.renderModel.release();
  65. }
  66. let renderModel = await audio.createAudioRenderer(this.audioRendererOptions);
  67. if (!renderModel) {
  68. console.error(TAG, `failed to create audio renderer`);
  69. }
  70. console.info(TAG, 'creating AudioRenderer success');
  71. this.renderModel = renderModel;
  72. this.bufferSize = await this.renderModel.getBufferSize();
  73. }
  74. public async play(data: ArrayBuffer): Promise<number> {
  75. this.audioDataSize = data.byteLength
  76. if (this.renderModel != null) {
  77. try {
  78. this.renderModel.on('writeData', this.writeDataCallback);
  79. } catch (err) {
  80. console.error(`error code: ${err.code}, message: ${err.message}.`);
  81. }
  82. // Start rendering.
  83. await this.renderModel.start();
  84. console.info(TAG, 'start AudioRenderer success');
  85. }
  86. return -1;
  87. }
  88. public async stop() {
  89. console.info(TAG, 'Renderer begin stop');
  90. if (this.renderModel == null) {
  91. return;
  92. }
  93. // Rendering can be stopped only when the AudioRenderer is in the running or paused state.
  94. if (this.renderModel.state !== audio.AudioState.STATE_RUNNING
  95. && this.renderModel.state !== audio.AudioState.STATE_PAUSED) {
  96. console.error(TAG, 'Renderer is not running or paused');
  97. return;
  98. }
  99. await this.renderModel.stop(); // Stop rendering.
  100. console.info(TAG, 'Renderer stopped');
  101. }
  102. public async release() {
  103. // The AudioRenderer can be released only when it is not in the released state.
  104. if (this.renderModel != null) {
  105. if (this.renderModel.state === audio.AudioState.STATE_RELEASED) {
  106. console.error(TAG, 'Renderer already released');
  107. return;
  108. }
  109. await this.renderModel.release(); // Release the instance.
  110. this.renderModel = null;
  111. console.info(TAG, 'Renderer released');
  112. }
  113. }
  114. /**
  115. * Check the current rendering status.
  116. *
  117. * @returns **true** if the rendering is running; **false** otherwise.
  118. */
  119. public isPlaying() {
  120. if (this.renderModel != null) {
  121. console.info(TAG, 'player.state:' + this.renderModel.state);
  122. return this.renderModel.state == audio.AudioState.STATE_RUNNING;
  123. } else {
  124. return false;
  125. }
  126. }
  127. /**
  128. * Obtain the current rendering status.
  129. */
  130. public getRenderState(): number {
  131. if (this.renderModel != null) {
  132. console.info(TAG, 'player.state:' + this.renderModel.state);
  133. return this.renderModel.state;
  134. } else {
  135. return audio.AudioState.STATE_INVALID;
  136. }
  137. }
  138. /**
  139. * Obtain the minimum buffer size required by the audio renderer.
  140. */
  141. public getBufferSize(): number {
  142. return this.bufferSize;
  143. }
  144. }
Search in Guides
Enter a keyword.