前言

语音合成(Text To Speech,TTS)可以实现网页文章的阅读。通过它,我们可以不需要看文字,就可以听新闻、听小说。

目前国内的主流的语音合成解决方案无非是以下几种:

  1. 科大讯飞
  2. Google
  3. 云服务(阿里云、腾讯云……)
  4. 百度api

上述的方案,音质不错,语音包选择多。但是都有一定的免费配额或者语言的限制(如科大讯飞的试用语音包),没有完全免费的方案。

开源免费的方案的效果比较差,而且需要利用自己的服务器资源,需要考虑额外的成本。

那有没有什么免费可以使用的方案呢?还是有的!

这里就要搬出来 Web API 里面的 SpeechSynthesis。这个接口是浏览器原生支持的语音合成。

基本上现在主流的现代浏览器都支持这个接口。具体的浏览器支持情况可以查阅上面链接里的文档。

1、SpeechSynthesis技术简介

SpeechSynthesis是HTML5的一个新特性,基于SpeechSynthesis可以实现在客户浏览器端进行动态文本的语音合成播放。

在HTML5中和Web Speech相关的API实际上有两类,一类是“语音识别(Speech Recognition)”,另外一个就是“语音合成(Speech Synthesis)”,实际上指的分别是“语音转文字”,和“文字变语音”。

文章介绍的就是“语音合成-文字变语音”。为什么称为“合成”呢?比方说你Siri发音“你好,世界!” 实际上是把“你”、“好”、“世”、“界”这4个字的读音给合并在一起,因此,称为“语音合成”。

技术参考api-MDN

mdn英文文档:MDN Web Docs

 mdn中文文档:Web 开发者指南 | MDN

1.1 SpeechSynthesis 的技术类方法

1.2 SpeechSyntehesisUtteranc属性

这个类主要用于控制合成声音的属性配置,比如主要内容,语音模板,语速等等,通过这个核心类控制。它的属性信息如下:

序号

参数

解释

1

text

要合成的文字内容,字符串

2

lang

使用的语言,字符串, 例如:"zh-cn"

3

voiceURI

指定希望使用的声音和服务,字符串。

4

volume

声音的音量,区间范围是0到1,默认是1

5

rate

语速,数值,默认值是1,范围是0.1到10,表示语速的倍数,例如2表示正常语速的两倍。

6

pitch

表示说话的音高,数值,范围从0(最小)到2(最大)。默认值为1

1.3  SpeechSyntehesisUtteranc核心方法

如下表所示:

序号

方法名

说明

1

onstart

语音合成开始时候的回调。

2

onpause

语音合成暂停时候的回调

3

onresume

语音合成重新开始时候的回调

4

onend

语音合成结束时候的回调

5

onmark

Fired when the spoken utterance reaches a named SSML "mark" tag.

1.4 speechSynthesis对象

 是实际调用SpeechSynthesisUtterance对象进行合成播报的。

他的属性和方法如下两个表格描述。

序号

名称

描述

1

paused

当SpeechSynthesis 处于暂停状态时, Boolean (en-US) 值返回 true

2

pending

当语音播放队列到目前为止保持没有说完的语音时, Boolean (en-US) 值返回 true 。

3

speaking

当语音谈话正在进行的时候,即使SpeechSynthesis处于暂停状态, Boolean (en-US) 返回 true 。

1.5 语音包

简单来说,SpeechSynthesis 的语音包来自两大地方——操作系统自带和浏览器自带。

所以我们才发现 window.speechSynthesis.getVoices() 拿到的语音包存在一部分是 Apple 提供的,一部分是 Google 提供的。 

对于语音包的一些技术解释,具体的文档可以查看 MDN。

以 macOS 下的 Google Chrome 为例,
通过 window.speechSynthesis.getVoices() 可以获取全部语音包,下面是一部分的输出:

[{
  "voiceURI": "Ting-Ting",
  "name": "Ting-Ting",
  "lang": "zh-CN",
  "localService": true,
  "default": true
}, {
  "voiceURI": "Google 普通话(中国大陆)",
  "name": "Google 普通话(中国大陆)",
  "lang": "zh-CN",
  "localService": false,
  "default": false
}]
上述数组是 SpeechSynthesisVoice 的实例数组,SpeechSynthesisVoice 的参数如下:

voiceURI:语音合成服务的URL
name:语音包名称
lang:语言(符合BCP 47规范)
localService:是本地语言包(离线)还是远程语言包(网络)
default:是否为默认的语音包

通过观察,可以发现
Google Chrome 在 macOS 下可以使用的语音包有macOS 自带的本地语音包和谷歌提供的在线语音包。
如苹果的「Ting-Ting」和谷歌的「Google 普通话(中国大陆)」。

在构建 SpeechSynthesisUtterance 的时候,传入支持的语音包的 lang 和 voice (对应 SpeechSynthesisVoice 的 name)就可以选择语音包了。

通过这个接口,我们可以给使用 Google Chrome 的浏览器用户提供来自谷歌的语音合成功能。

探索 SpeechSynthesis 的原理下面,
我们通过 Chromium 的源代码探索 Google Chrome 的 SpeechSynthesis 是怎么实现的。

Chromium 里面的 SpeechSynthesis 类都是在 speech 模块 中实现的。
其中,该模块包含 语音识别(speechRecognizer)和 语音合成 (SpeechSynthesis)两大功能的实现。

看语音合成的头文件就大概知道有哪些功能:

speech_synthesis_impl.h: SpeechSynthesis 实现的后端,分派任务给 TTS Conreoller
tts_controller_impl.h: 语音合成的控制器,集成了获取语音包、设置引擎、播放、暂停等功能
tts_platform_impl.h: 管理平台相关的语音包,具体的实现在
语音包怎么来的?



我们可以先看 tts_controller_impl 里面的 GetVoices 实现:

void TtsControllerImpl::GetVoices
(BrowserContext* browser_context,std::vector<VoiceData>* out_voices) {
  TtsPlatform* tts_platform = GetTtsPlatform(); // 获取操作系统相关的 TTS
  if (tts_platform) { // 当前平台存在 TTS
    // Ensure we have all built-in voices loaded. This is a no-op if already
    // loaded.
    tts_platform->LoadBuiltInTtsEngine(browser_context); // 加载本地的 TTS
    if (tts_platform->PlatformImplAvailable())
      tts_platform->GetVoices(out_voices);
  }

  if (browser_context) {
    TtsControllerDelegate* delegate = GetTtsControllerDelegate();
    if (delegate && delegate->GetTtsEngineDelegate()) // 浏览器自带的 TTS
      delegate->GetTtsEngineDelegate()->GetVoices(browser_context, out_voices);
  }
}


简单来说,SpeechSynthesis 的语音包来自两大地方——操作系统自带和浏览器自带。所以我们才发现 window.speechSynthesis.getVoices() 拿到的语音包存在一部分是 Apple 提供的,一部分是 Google 提供的。

这个技术是最常用在谷歌浏览器,H5的语音合成:Speech Synthesis ,基本上兼容大部分的主流浏览器,但是完全不兼容IE,需要设置IE需要写一些兼容的语句。Microsoft Edge浏览器的支持较好,支持的语言包比较丰富。

2、项目界面截图

3、封装class类方法(实例化调用)

// 语音播报的函数
export default class SpeakVoice {
  constructor(vm, config) {
    let that = this
    that._vm = vm
    that.config = {
      text: '春江潮水连海平,海上明月共潮生。滟滟随波千万里,何处春江无月明!',
      volume: 1, // 声音音量:1,范围从0到1
      rate: 1,   // 设置语速:1,范围从0到100
      labelData:{
        name:''
      },
      ...config
    }
    that.synth = window.speechSynthesis // 启用文本
    that.instance = new SpeechSynthesisUtterance()
    that.instance.lang = "zh-CN"; // 使用的语言:中文
    that.status = '初始化'
    that.isload = false;
    that.initVoice();
  }
  // 初始化
  initVoice(){
    let that = this
    if(that.isload){
      return false
    }else{
      that.isload = true
      that.instance.text = that.config.text; // 文字内容: 测试内容
      that.instance.volume = that.config.volume;
      that.instance.rate = that.config.rate;

      that.instance.onstart = e => {
        that.status = '开始播放'
      }
      that.instance.onend = e => {
        that.status = '结束播放'
      }
      that.instance.onpause = e => {
        this.status = "暂停播放"
      }

    }
    /*let speech = that.getSpeechVoices();
    speech.then((voices) => {
      voices = voices.filter(item => (item.lang.indexOf('zh-') > -1 && item.localService));
      if (voices.length === 0) {
        console.error('没有可用的中文语音!');  //中文包可以调试多种语言包切换
      }else {
        //实例化播报内容
        that.instance.lang = "zh-CN"; // 使用的语言:中文
        that.instance.text = '测试内容'; // 文字内容: 测试内容
        that.instance.volume = 1
        that.instance.rate = 1
        that.instance.voice = voices[0]
        that.synth.speak(that.instance); // 播放
      }
    });*/
  }
  // 语音开始
  handleSpeak() {
    this.synth.speak(this.instance); // 播放
  }
  // 语音队列重播
  handleReply() {
    this.handleCancel();
    this.handleSpeak()
  }
  // 语音队列删除 , 删除队列中所有的语音.如果正在播放,则直接停止
  handleCancel() {
    this.synth.cancel(this.instance);
  }
  // 语音暂停, 暂停语音该次语音播放
  handleStop() {
    this.synth.pause(this.instance);
  }
  // 恢复暂停的语音
  handleResume(){
    this.synth.resume(this.instance) //恢复暂停的语音
  }



  //获取语言包数据, 这个接口需要一点时间,改成异步的
  getSpeechVoices() {
    let that = this
    return new Promise(function (resolve, reject) {
        let id;
        id = setInterval(() => {
          if (that.synth.getVoices().length !== 0) {
            resolve(that.synth.getVoices());
            clearInterval(id);
          }
        }, 10);
      }
    )
  }

  destory () {
    this.handleCancel()
  }
}


4、实例化父组件

<i title="播放" class="el-icon-microphone" @click="actMicroFun"></i>


import SpeakVoice from './speak-voice.js'


actMicroFun () {
  let speakVoice = new SpeakVoice();
  speakVoice.handleReply();
}

5、关于语音api解析

属性设置
SpeechSynthesisUtterance.lang 获取并设置话语的语言
SpeechSynthesisUtterance.pitch 获取并设置话语的音调(值越大越尖锐,越低越低沉)
SpeechSynthesisUtterance.rate 获取并设置说话的速度(值越大语速越快,越小语速越慢)
SpeechSynthesisUtterance.text 获取并设置说话时的文本
SpeechSynthesisUtterance.voice 获取并设置说话的声音
SpeechSynthesisUtterance.volume 获取并设置说话的音量


函数设置
speechSynthesis.speak() 将对应的实例添加到语音队列中
speechSynthesis.cancel() 删除队列中所有的语音.如果正在播放,则直接停止
speechSynthesis.pause() 暂停语音
speechSynthesis.resume() 恢复暂停的语音
speechSynthesis.getVoices() 获取支持的语言数组.

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐