华为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}`);
}
});
};
}
相关推荐
skywalk8163几秒前
Deepseek harness的4种模式 配置文件内容
linux·人工智能·ubuntu·deepseek·harness
货拉拉技术2 分钟前
构建稳定的 AI Agent:Harness 工程的核心机制与实践思考
人工智能·后端·数据分析
Herbert_hwt2 分钟前
拒绝“猜谜式”编程:从 Google PTCF 到 AI 编程五要素框架
人工智能
2601_965742228 分钟前
短视频脚本创作方法分享:本地生活类账号的起步思路
大数据·人工智能·算法·ai·新媒体运营·生活
xhy_070712 分钟前
Git 合并冲突怎么解决?用 AI 处理冲突的流程、Prompt 和 4 个易错点
人工智能·git·安全·prompt·ai编程·代码复审
马剑威(威哥爱编程)15 分钟前
【AI全栈后端12-02】Spring Boot 跑通第一个 AI 对话接口:HR 政策问答机器人实战
java·人工智能·spring boot·机器人
果霸大叔16 分钟前
RAG 数据导入与解析全攻略(二):图文与 PDF 解析——OCR、多模态大模型与九种 PDF 工具选型
人工智能
IT枫斗者枫哥18 分钟前
AI返回合法JSON,字段就可信吗?给抽取结果补一道业务校验
java·人工智能·后端
旋生万物19 分钟前
素数螺旋映射 $z_n=n^{1+i}$ 的角分布统计检验与零模型对比
大数据·前端·人工智能·算法·云原生·螺旋生成论·螺旋相位
天天被压力22 分钟前
【别再到处找免费股票数据API了:官方204个接口,32篇一次讲透 #06】Python实时行情总报错?五档盘口+逐笔一次跑通
java·人工智能·python