华为HarmonyOS实现实时文本转语音

场景介绍

  • 将一段不超过10000字符的文本合成为语音并进行播报。
  • 场景举例:手机在无网状态下,系统应用无障碍(屏幕朗读)接入文本转语音能力,为视障人士提供播报能力。

开发步骤

  1. 在使用文本转语音时,将实现文本转语音相关的类添加至工程。

    TypeScript 复制代码
    import { textToSpeech } from '@kit.CoreSpeechKit';
    import { BusinessError } from '@kit.BasicServicesKit';
  2. 调用createEngine接口,创建textToSpeechEngine实例。

    createEngine接口提供了两种调用形式,当前以其中一种作为示例,其他方式可参考API参考

    TypeScript 复制代码
    let ttsEngine: textToSpeech.TextToSpeechEngine;
    
    // 设置创建引擎参数
    let extraParam: Record<string, Object> = {"style": 'interaction-broadcast', "locate": 'CN', "name": 'EngineName'};
    let initParamsInfo: textToSpeech.CreateEngineParams = {
    language: 'zh-CN',
    person: 0,
    online: 1,
    extraParams: extraParam
    };
    
    // 调用createEngine方法
    textToSpeech.createEngine(initParamsInfo, (err: BusinessError, textToSpeechEngine: textToSpeech.TextToSpeechEngine) => {
    if (!err) {
    console.info('Succeeded in creating engine');
    // 接收创建引擎的实例
    ttsEngine = textToSpeechEngine;
    } else {
    // 创建引擎失败时返回错误码1003400005,可能原因:引擎不存在、资源不存在、创建引擎超时
    console.error(`Failed to create engine. Code: ${err.code}, message: ${err.message}.`);
    }
    });
  3. 得到TextToSpeechEngine实例对象后,实例化SpeakParams对象、SpeakListener对象,并传入待合成及播报的文本originalText,调用speak接口进行播报。

    TypeScript 复制代码
    // 设置speak的回调信息
    let speakListener: textToSpeech.SpeakListener = {
    // 开始播报回调
    onStart(requestId: string, response: textToSpeech.StartResponse) {
    console.info(`onStart, requestId: ${requestId} response: ${JSON.stringify(response)}`);
    },
    // 合成完成及播报完成回调
    onComplete(requestId: string, response: textToSpeech.CompleteResponse) {
    console.info(`onComplete, requestId: ${requestId} response: ${JSON.stringify(response)}`);
    },
    // 停止播报回调
    onStop(requestId: string, response: textToSpeech.StopResponse) {
    console.info(`onStop, requestId: ${requestId} response: ${JSON.stringify(response)}`);
    },
    // 返回音频流
    onData(requestId: string, audio: ArrayBuffer, response: textToSpeech.SynthesisResponse) {
    console.info(`onData, requestId: ${requestId} sequence: ${JSON.stringify(response)} audio: ${JSON.stringify(audio)}`);
    },
    // 错误回调
    onError(requestId: string, errorCode: number, errorMessage: string) {
    console.error(`onError, requestId: ${requestId} errorCode: ${errorCode} errorMessage: ${errorMessage}`);
    }
    };
    // 设置回调
    ttsEngine.setListener(speakListener);
    let originalText: string = '你好,鸿蒙';
    // 设置播报相关参数
    let extraParam: Record<string, Object> = {"queueMode": 0, "speed": 1, "volume": 2, "pitch": 1, "languageContext": 'zh-CN',
    "audioType": "pcm", "soundChannel": 3, "playType": 1 };
    let speakParams: textToSpeech.SpeakParams = {
    requestId: '123456', // requestId在同一实例内仅能用一次,请勿重复设置
    extraParams: extraParam
    };
    // 调用播报方法
    ttsEngine.speak(originalText, speakParams);
  4. (可选)当需要停止合成及播报时,可调用stop接口。

    TypeScript 复制代码
    ttsEngine.stop();
  5. (可选)当需要查询文本转语音服务是否处于忙碌状态时,可调用isBusy接口。

    TypeScript 复制代码
    ttsEngine.isBusy();
  6. (可选)当需要查询支持的语种音色信息时,可调用listVoices接口。

    listVoices接口提供了两种调用形式,当前以其中一种作为示例,其他方式可参考API参考

    TypeScript 复制代码
    // 在组件中声明并初始化字符串voiceInfo
    @State voiceInfo: string = "";
    
    // 设置查询相关参数
    let voicesQuery: textToSpeech.VoiceQuery = {
    requestId: '12345678', // requestId在同一实例内仅能用一次,请勿重复设置
    online: 1
    };
    // 调用listVoices方法,以callback返回
    ttsEngine.listVoices(voicesQuery, (err: BusinessError, voiceInfo: textToSpeech.VoiceInfo[]) => {
    if (!err) {
    // 接收目前支持的语种音色等信息
    this.voiceInfo = JSON.stringify(voiceInfo);
    console.info(`Succeeded in listing voices, voiceInfo is ${this.voiceInfo}`);
    } else {
    console.error(`Failed to list voices. Code: ${err.code}, message: ${err.message}`);
    }
    });

开发实例

点击按钮,播报一段文本。

TypeScript 复制代码
import { textToSpeech } from '@kit.CoreSpeechKit';
import { BusinessError } from '@kit.BasicServicesKit';

let ttsEngine: textToSpeech.TextToSpeechEngine;
@Entry
@Component
struct Index {
@State createCount: number = 0;
@State result: boolean = false;
@State voiceInfo: string = "";
@State text: string = "";
@State textContent: string = "";
@State utteranceId: string = "123456";
@State originalText: string = "\n\t\t古人学问无遗力,少壮工夫老始成;\n\t\t" +
"纸上得来终觉浅,绝知此事要躬行。\n\t\t";
@State illegalText: string = "";

build() {
Column() {
Scroll() {
Column() {
TextArea({ placeholder: 'Please enter tts original text', text: `${this.originalText}` })
.margin(20)
.focusable(false)
.border({ width: 5, color: 0x317AE7, radius: 10, style: BorderStyle.Dotted })
.onChange((value: string) => {
this.originalText = value;
console.info(`original text: ${this.originalText}`);
})

Button() {
Text("CreateEngineByCallback")
.fontColor(Color.White)
.fontSize(20)
}
.type(ButtonType.Capsule)
.backgroundColor("#0x317AE7")
.width("80%")
.height(50)
.margin(10)
.onClick(() => {
this.createCount++;
console.info(`CreateTtsEngine:createCount:${this.createCount}`);
this.createByCallback();
})

Button() {
Text("speak")
.fontColor(Color.White)
.fontSize(20)
}
.type(ButtonType.Capsule)
.backgroundColor("#0x317AE7")
.width("80%")
.height(50)
.margin(10)
.onClick(() => {
this.createCount++;
this.speak();
})

Button() {
Text("listVoicesCallback")
.fontColor(Color.White)
.fontSize(20)
}
.type(ButtonType.Capsule)
.backgroundColor("#0x317AE7")
.width("80%")
.height(50)
.margin(10)
.onClick(() => {
this.listVoicesCallback();
})

Button() {
Text("stop")
.fontColor(Color.White)
.fontSize(20)
}
.type(ButtonType.Capsule)
.backgroundColor("#0x317AE7")
.width("80%")
.height(50)
.margin(10)
.onClick(() => {
// 停止播报
console.info("Stop button clicked.");
ttsEngine.stop();
})

Button() {
Text("isBusy")
.fontColor(Color.White)
.fontSize(20)
}
.type(ButtonType.Capsule)
.backgroundColor("#0x317AE7")
.width("80%")
.height(50)
.margin(10)
.onClick(() => {
// 查询播报状态
let isBusy = ttsEngine.isBusy();
console.info(`isBusy: ${isBusy}`);
})

Button() {
Text("shutdown")
.fontColor(Color.White)
.fontSize(20)
}
.type(ButtonType.Capsule)
.backgroundColor("#0x317AA7")
.width("80%")
.height(50)
.margin(10)
.onClick(() => {
// 释放引擎
ttsEngine.shutdown();
})
}
.layoutWeight(1)
}
.width('100%')
.height('100%')
}
}

// 创建引擎,通过callback形式返回
// 当引擎不存在、引擎资源不存在、初始化超时,返回错误码1003400005,引擎创建失败
private createByCallback() {
// 设置创建引擎参数
let extraParam: Record<string, Object> = {"style": 'interaction-broadcast', "locate": 'CN', "name": 'EngineName'};
let initParamsInfo: textToSpeech.CreateEngineParams = {
language: 'zh-CN',
person: 0,
online: 1,
extraParams: extraParam
};

// 调用createEngine方法
textToSpeech.createEngine(initParamsInfo, (err: BusinessError, textToSpeechEngine: textToSpeech.TextToSpeechEngine) => {
if (!err) {
console.info('Succeeded in creating engine.');
// 接收创建引擎的实例
ttsEngine = textToSpeechEngine;
} else {
// 创建引擎失败时返回错误码1003400005,可能原因:引擎不存在、资源不存在、创建引擎超时
console.error(`Failed to create engine. Code: ${err.code}, message: ${err.message}.`);
}
});
};

// 调用speak播报方法
// 未初始化引擎时调用speak方法,返回错误码1003400007,合成及播报失败
private speak() {
let speakListener: textToSpeech.SpeakListener = {
// 开始播报回调
onStart(requestId: string, response: textToSpeech.StartResponse) {
console.info(`onStart, requestId: ${requestId} response: ${JSON.stringify(response)}`);
},
// 完成播报回调
onComplete(requestId: string, response: textToSpeech.CompleteResponse) {
console.info(`onComplete, requestId: ${requestId} response: ${JSON.stringify(response)}`);
},
// 停止播报完成回调,调用stop方法并完成时会触发此回调
onStop(requestId: string, response: textToSpeech.StopResponse) {
console.info(`onStop, requestId: ${requestId} response: ${JSON.stringify(response)}`);
},
// 返回音频流
onData(requestId: string, audio: ArrayBuffer, response: textToSpeech.SynthesisResponse) {
console.info(`onData, requestId: ${requestId} sequence: ${JSON.stringify(response)} audio: ${JSON.stringify(audio)}`);
},
// 错误回调,播报过程发生错误时触发此回调
// 未创建引擎时调用speak方法时返回错误码1003400007,合成及播报失败
// 连续调用两次speak,第二次speak会返回错误码1003400006,服务正忙碌
onError(requestId: string, errorCode: number, errorMessage: string) {
console.error(`onError, requestId: ${requestId} errorCode: ${errorCode} errorMessage: ${errorMessage}`);
}
};
// 设置回调
ttsEngine.setListener(speakListener);
// 设置播报相关参数
let extraParam: Record<string, Object> = {"queueMode": 0, "speed": 1, "volume": 2, "pitch": 1, "languageContext": 'zh-CN', "audioType": "pcm", "soundChannel": 3, "playType":1}
let speakParams: textToSpeech.SpeakParams = {
requestId: '123456-a', // requestId在同一实例内仅能用一次,请勿重复设置
extraParams: extraParam
};
// 调用speak播报方法
ttsEngine.speak(this.originalText, speakParams);
};

// 查询语种音色信息,以callback形式返回
private listVoicesCallback() {
// 设置查询相关参数
let voicesQuery: textToSpeech.VoiceQuery = {
requestId: '123456-b', // requestId在同一实例内仅能用一次,请勿重复设置
online: 1
};

// 调用listVoices方法,以callback返回语种音色查询结果
ttsEngine.listVoices(voicesQuery, (err: BusinessError, voiceInfo: textToSpeech.VoiceInfo[]) => {
if (!err) {
// 接收目前支持的语种音色等信息
this.voiceInfo = JSON.stringify(voiceInfo);
console.info(`Succeeded in listing voices, voiceInfo is ${voiceInfo}`);
} else {
console.error(`Failed to list voices. Code: ${err.code}, message: ${err.message}`);
}
});
};
}
相关推荐
江畔柳前堤5 小时前
roLabelImg 详细安装教程
开发语言·人工智能·后端·云原生
阿里云大数据AI技术5 小时前
分链路差异化设计的DSP准实时数仓|钛动科技基于阿里云实时计算 Flink 版 + DLF Paimon + EMR Serverless StarRocks 的实践
人工智能·flink
陕西企来客5 小时前
2026年7月AI智能搜索曝光趋势研判
大数据·人工智能·机器学习·ai智能搜索曝光
咱入行浅5 小时前
汽车之家联合HarmonyOS SDK,深度构建鸿蒙生态体系
华为·汽车·harmonyos
阿里云大数据AI技术6 小时前
从算力到智能体,面向 Agentic AI 的基础设施演进
人工智能·agent
hangyuekejiGEO6 小时前
GEO技术服务选型指南
大数据·人工智能·python
阿里云大数据AI技术6 小时前
EMR Serverless Spark AI Function 的双维降本实践
人工智能·sql·spark
维基框架7 小时前
GitHub源码处理提速 一趟扫描反而更慢
人工智能·github
冬奇Lab7 小时前
代码库知识库系列(05):向量检索 vs 知识图谱——加了调用图并没有变更好
人工智能
AKAMAI7 小时前
你的源服务器可能是你做出的最昂贵决定
运维·人工智能·云计算