小程序--语音合成tts 对接多平台(讯飞,思必驰,百度)

4,142 阅读3分钟

小程序功能特点

  1. 文本转语音
  2. 多平台多发音人可选
  3. 可调语速
  4. 可提供音频下载
  5. 良心产品无广告😆

小程序码

已对接在线语音识别服务

  1. 思必驰dui平台 (超过40个免费可选发音人)
  2. 讯飞开放平台 (5个免费可选发音人)
  3. 百度语音(4个免费发音人可选)

小程序截图

服务端主要代码

class TTSController extends Controller {
  async tts () {
    let params = this.ctx.query
    let result = null
    // 根据plat参数来调用不同的接口
    if (params.plat === 'xf') {
      result = await this.ctx.service.xftts.getTts(params)
    } else if (params.plat === 'baidu') {
      result = await this.ctx.service.baidutts.getTts(params)
    } else {
      result = await this.ctx.service.aispeechtts.getTts(params)
    }
    // 设置response的类型,这样客户端接收到的就是一个文件流
    this.ctx.response.type = 'audio/mpeg'
    this.ctx.body = result
  }
}

小程序客户端template代码(使用的mpvue)

<template>
  <div class="container">
    <div class="preview">
      <textarea :class="textAreaFocus? 'focus' : ''" 
      auto-height @focus="bindTextAreaFocus" 
      @blur="bindTextAreaBlur" placeholder="请输入文本" 
      v-model="text"  maxlength="256"/>
    </div>
    <div class="setting">
      <picker @change="bindPlatChange" v-model="platIndex" range-key="name" :range="platArr">
        <div class="item">
          <div class="label">选择平台</div>
          <div class="value voice">
            {{platArr[platIndex].name}}
          </div>
        </div>
      </picker>
      <picker @change="bindPickerChange" v-model="index" range-key="name" :range="array">
        <div class="item">
          <div class="label">选择发音人</div>
          <div class="value voice">
            {{array[index].name}}
          </div>
        </div>
      </picker>
      <div class="item speed">
        <div class="label">调节语速</div>
        <div class="value">
          <slider @change="onSpeedChange" :value="speedObj.default" :step='speedObj.step' activeColor="#6F8FFF" :min="speedObj.min" :max="speedObj.max" show-value />
        </div>
      </div>
    </div>
    <div style="height: 140rpx;">
      <div class="btn-group">
        <div class="item"><button @click="audioPlay" type="main">播放合成语音</button> </div>
        <div class="item"> <button @click="audioDownload" type="submain">复制链接下载</button> </div>
      </div>
    </div>
    <div class="desc">
      说明:tts是英文 text to speech的缩写,即文本转语音技术
      <contact-button 
        type="default-light"
        session-from="weapp">联系客服
      </contact-button>
    </div>
  </div>
</template>

script 代码

<script>
import voiceIdArray from './voiceIdArray'

export default {

  data () {
    return {
      array: voiceIdArray.aispeech,
      platArr: [{id: 'xf', name: '科大讯飞'}, {id: 'aispeech', name: '思必驰'}, {id: 'baidu', name: '百度'}],
      platIndex: 1,
      index: 26,
      text: `改革春风吹满地,吹满地,春风吹满地。\n中国人民真争气,真争气,人民真争气。\n这个世界太疯狂,耗子都给猫当伴娘。\n齐德隆,齐东强。\n齐德隆的咚得隆咚锵。`,
      voiceId: 'lili1f_diantai',
      speed: 1,
      textAreaFocus: false,
      audioCtx: null,
      ttsServer: 'https://tts.server.com',
      audioSrc: '',
      downloadUrl: '',
      xfSpeedObj: {
        min: 0,
        max: 100,
        default: 50,
        step: 1
      },
      aispeechSpeedObj: {
        min: 0.7,
        max: 2,
        default: 1,
        step: 0.1
      },
      baiduSpeedObj: {
        min: 0,
        max: 9,
        default: 5,
        step: 1
      },
      speedObj: {}
    }
  },
  watch: {
    platIndex (newVal, oldVal) {
      if (newVal === 2) {
        this.array = voiceIdArray.baidu
        this.index = 0
        this.speedObj = this.baiduSpeedObj
      }
      if (newVal === 1) {
        this.array = voiceIdArray.aispeech
        this.index = 26
        this.speedObj = this.aispeechSpeedObj
      }
      if (newVal === 0) {
        this.array = voiceIdArray.xf
        this.index = 0
        this.speedObj = this.xfSpeedObj
      }
    }
  },
  onShareAppMessage () {
    return {
      title: '文本转语音服务,多发音人可选'
    }
  },
  methods: {
    onSpeedChange (e) {
      this.speedObj.default = e.target.value
    },
    bindPlatChange (e) {
      this.platIndex = e.target.value * 1
    },
    bindPickerChange (e) {
      this.index = e.target.value
    },
    getAudioSrc () {
      if (this.text === '') {
        return false
      }
      const speed = this.speedObj.default
      const voiceId = this.array[this.index].id
      const plat = this.platArr[this.platIndex].id
      return encodeURI(`${this.ttsServer}/tts?plat=${plat}&voiceId=${voiceId}&speed=${speed}&text=${this.text}`)
    },
    getDownloadUrl () {
      const plat = this.platArr[this.platIndex].id
      const voiceId = this.array[this.index].id
      wx.showLoading({
        title: '加载中'
      })
      wx.request({
        url: 'https://tts.server.com/getdownloadurl',
        data: {
          plat: plat,
          voiceId: voiceId,
          speed: this.speedObj.default,
          text: this.text
        },
        header: {
          'content-type': 'application/json' // 默认值
        },
        success (res) {
          wx.hideLoading()
          wx.setClipboardData({
            data: res.data.short_url,
            success (res) {
              wx.showToast({
                title: '链接已复制请用浏览器下载(ios端无法下载)',
                icon: 'none',
                duration: 3000
              })
            }
          })
        }
      })
    },
    audioPlay () {
      this.audioCtx.src = this.getAudioSrc()
      if (!this.audioCtx.src) {
        wx.showToast({
          title: '请先输入文本',
          icon: 'none',
          duration: 2000
        })
        return false
      }
      wx.showLoading({
        title: '加载中'
      })
      this.audioCtx.play()
    },
    audioDownload () {
      this.getDownloadUrl()
    },
    bindTextAreaBlur (e) {
      this.textAreaFocus = false
      this.text = e.target.value
    },
    bindTextAreaFocus () {
      this.textAreaFocus = true
    }
  },

  created () {
    this.speedObj = this.aispeechSpeedObj
  },
  mounted () {
    this.audioCtx = wx.createInnerAudioContext()
    this.audioCtx.onEnded((res) => {
      wx.hideLoading()
    })
    this.audioCtx.onPlay((res) => {
      wx.hideLoading()
    })
    wx.showShareMenu({
      withShareTicket: true
    })
  }
}
</script>

接口对接过程中,百度的是最方便的因为有sdk可以直接使用,讯飞的最麻烦需要自己做参数加密,思必驰dui的虽然没提供SDK但是文档写的比较详细对接过程也很方便快速。

目前无法解决的就是,小程序内无法直接下载的问题,只能提供链接,然后用户自己打开浏览器进行下载(iPhone似乎无解)。