一、背景说明

文本转语音(TTS)是无障碍朗读、新闻听书、语音导航类应用的基础能力。HarmonyOS 7 开发套件 26.0.1 Beta1(11 月 10 日面向开发者发布)的 Core Speech Kit 又新增了播报暂停/继续接口,语音播报的播放控制能力进一步补齐。本文基于 Core Speech Kit 的 textToSpeech 模块,完整实现"创建引擎 → 合成播报 → 监听回调 → 停止控制"全流程,代码可在 API 11 及以上(含 26.0.x)工程中运行。

二、技术原理

Core Speech Kit 将不超过 10000 字符的中英文文本(简体、繁体、数字、英文)合成为语音并按选定音色播报,核心概念有四个:

  1. 引擎(TextToSpeechEngine):由 textToSpeech.createEngine 创建,参数指定语言(zh-CN)、音色(person)与在线/离线模式(online);引擎创建成功后全局复用,不必每次播报重建;
  2. 播报参数(SpeakParams):包含 requestId 与 extraParams(语速 speed、音量 volume、音调 pitch、语言上下文、音频类型等),requestId 在同一实例内只能使用一次;
  3. 回调监听(SpeakListener):onStart / onComplete / onStop / onData(返回 PCM 音频流)/ onError 五个回调覆盖播报全生命周期,必须先 setListener 再 speak;
  4. 播放控制:stop 同步停止合成与播报;播报文本支持 [pN] 标记插入毫秒级静音停顿,支持设置汉字发音、数字播报、单词读法策略。

数据流为:createEngine(一次性)→ setListener → speak(text, SpeakParams) → 引擎边合成边播报,onData 持续吐流、onComplete 收尾。

三、实现步骤

无需申请任何 ohos.permission 权限(引擎按 online 参数决定是否联网,首次使用可能需要设备下载音色资源)。工程要求 Stage 模型、API 4.1.0(11) 及以上。

新建 src/main/ets/tts/TtsPlayer.ets(完整代码,引擎管理 + 播报 + 回调封装):

import { textToSpeech } from '@kit.CoreSpeechKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

const TAG: string = 'TtsPlayer';

export class TtsPlayer {
  private engine: textToSpeech.TextToSpeechEngine | null = null;
  private reqSeq: number = 0;

  // 创建引擎(全局只创建一次)
  async init(): Promise<void> {
    if (this.engine) { return; }
    const initParamsInfo: textToSpeech.CreateEngineParams = {
      language: 'zh-CN',
      person: 0,          // 0 为默认音色
      online: 1           // 1 在线合成;0 离线
    };
    try {
      this.engine = await textToSpeech.createEngine(initParamsInfo);
      this.setListener();
      hilog.info(TAG, 'engine created');
    } catch (err) {
      const e: BusinessError = err as BusinessError;
      hilog.error(TAG, 'createEngine failed: %{public}d %{public}s', e.code, e.message);
      throw e;
    }
  }

  private setListener(): void {
    const listener: textToSpeech.SpeakListener = {
      onStart: (requestId: string, response: textToSpeech.StartResponse) => {
        hilog.info(TAG, 'onStart %{public}s', requestId);
      },
      onComplete: (requestId: string, response: textToSpeech.CompleteResponse) => {
        hilog.info(TAG, 'onComplete %{public}s', requestId);
      },
      onStop: (requestId: string, response: textToSpeech.StopResponse) => {
        hilog.info(TAG, 'onStop %{public}s', requestId);
      },
      onData: (requestId: string, audio: ArrayBuffer, response: textToSpeech.SynthesisResponse) => {
        // 需要保存音频文件或自绘波形时在这里消费 PCM 流
      },
      onError: (requestId: string, errorCode: number, errorMessage: string) => {
        hilog.error(TAG, 'onError %{public}s %{public}d %{public}s', requestId, errorCode, errorMessage);
      }
    };
    this.engine?.setListener(listener);
  }

  // 播报文本;文本支持 [p500] 形式的静音停顿标记
  speak(text: string): void {
    if (!this.engine) { throw new Error('engine not initialized'); }
    if (text.length > 10000) {
      throw new Error('text too long, max 10000 chars');
    }
    this.reqSeq += 1;   // requestId 在同一实例内不可重复
    const extraParam: Record<string, Object> = {
      'queueMode': 0,   // 0 排队播报
      'speed': 1,       // 语速 0.5~2
      'volume': 2,      // 音量 0~2
      'pitch': 1,       // 音调
      'languageContext': 'zh-CN',
      'audioType': 'pcm',
      'soundChannel': 3,
      'playType': 1
    };
    const speakParams: textToSpeech.SpeakParams = {
      requestId: `tts-${this.reqSeq}`,
      extraParams: extraParam
    };
    try {
      this.engine.speak(text, speakParams);
    } catch (err) {
      const e: BusinessError = err as BusinessError;
      hilog.error(TAG, 'speak failed: %{public}d %{public}s', e.code, e.message);
    }
  }

  // 停止合成与播报
  stop(): void {
    try { this.engine?.stop(); } catch (err) { }
  }
}
四、应用场景示例

在 Index.ets 中实现一个简易听书控制器:

import { TtsPlayer } from '../tts/TtsPlayer';

@Entry
@Component
struct Index {
  private tts: TtsPlayer = new TtsPlayer();
  @State text: string = '欢迎使用鸿蒙系统[p500]今天是不错的天气。';

  build() {
    Column({ space: 16 }) {
      TextArea({ text: this.text }).height(120)
      Row({ space: 12 }) {
        Button('播报')
          .onClick(async () => {
            await this.tts.init();
            this.tts.speak(this.text);
          })
        Button('停止')
          .onClick(() => { this.tts.stop(); })
      }
    }
    .width('100%').height('100%').justifyContent(FlexAlign.Center)
  }
}

运行后点「播报」,设备即以默认音色朗读文本,并在"鸿蒙系统"后停顿 500ms。

五、技术要点
  1. requestId 一次性:同一引擎实例内 requestId 不可重复使用,重复会报错,示例中用自增序号保证唯一;
  2. 先 setListener 再 speak:不设置监听就播报,合成与播报回调都收不到,错误也只能靠异常感知;
  3. 文本长度上限 10000 字符(不含首尾空白),超限抛 1002300001,长文章需分段播报并按 onComplete 串联下一段;
  4. 引擎复用:createEngine 开销较大且已初始化时会直接返回实例,应用级单例持有即可,不要在每次播报时重建;
  5. 停顿与读法策略:[p500] 插入 500ms 静音;数字、多音字的读法可通过播报策略配置,中文数字场景(如念电话号码)务必显式设置,否则默认按数值读;
  6. 异常包裹:speak 可能同步抛参数校验异常(401),try/catch 与 onError 回调两条路径都要处理。
六、扩展建议

若需要把合成结果保存为音频文件而非即时播放,可在 onData 回调中累积 PCM 流,写入沙箱文件:

onData: (requestId: string, audio: ArrayBuffer, response: textToSpeech.SynthesisResponse) => {
  buffers.push(new Uint8Array(audio.slice(0)));
}
// onComplete 后将 buffers 合并写入 fileIo 生成的 .pcm/.wav 文件

26.0.1 Beta1 新增的暂停/继续播报接口可用于听书类应用的临时打断场景(如来电提示后恢复播报),升级到该版本后可直接调用对应接口实现秒级恢复,无需记录播放进度重新合成。

结语

本文用约 110 行 ArkTS 完成了文本转语音从引擎创建、回调监听、策略化播报到停止控制的完整闭环,核心是 createEngine → setListener → speak 三步链路加 requestId 与文本分段控制。TTS 接入成本低、无权限门槛,适合快速为阅读、导航、客服类应用增加语音输出能力。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐