微信小程序

本文介绍如何使用阿里云智能语音服务提供的微信小程序SDK,包括SDK的安装方法及SDK代码示例。

前提条件

在使用SDK前,请先阅读接口说明,详情请参见接口说明

下载安装

  1. 下载并安装SDK。

    通过Github下载对应SDK代码,或直接下载alibabacloud-nls-wx-sdk-master.zip

  2. 导入SDK。

    您可将下载好的代码放入工程合适目录下,然后根据目录位置通过require进行导入。

获取Token

getToken

获取Token并以AKID(AccessKey ID)和AKKEY(AccessKey Secret)为key缓存对应Token,如果缓存的Token过期则自动刷新并获取。缓存机制请参见微信小程序文档的数据缓存部分。

  • 参数说明:无。

  • 返回值:String类型的Token。

getTokenInner

直接获取Token,不带任何缓存机制,适用于用户自定义缓存方式。

  • 参数说明:无。

  • 返回值:String类型的Token。

重要

频繁调用该接口会被服务端拒绝访问。

关键接口和参数描述

实现语音合成的功能,围绕SpeechSynthesizer类进行,一般按照如下步骤编写代码(步骤2和步骤3顺序可互换):

  1. 创建SpeechSynthesizer实例,此时会传入语音合成服务地址和认证信息。

  2. 设置语音合成的发音人、采样率、音频格式等属性:创建属性对象或者修改SpeechSynthesizer实例的defaultStartParams方法返回的默认属性对象。

  3. 补充SpeechSynthesizer实例的回调函数on的细节(观察者模式——当连接成功建立、合成语音、发生异常等时,服务器会通过回调on函数通知客户端)。

  4. 调用SpeechSynthesizer实例的start函数开始语音合成。

1. SpeechSynthesizer

SpeechSynthesizer类用于语音合成。

SpeechSynthesizer的构造函数参数说明如下:

参数

类型

参数说明

config

Object

连接配置对象。

config参数说明:

参数

类型

参数说明

url

String

语音合成服务地址。默认值为wss://nls-gateway.cn-shanghai.aliyuncs.com/ws/v1。

您需要配置符合自己实际情况的地址,参见服务地址进行获取。

token

String

访问Token,详情可参见获取Token概述

appkey

String

对应项目Appkey。

示例:

let tts = new SpeechSynthesizer({
    url: app.globalData.URL,
    appkey:app.globalData.APPKEY,
    token:this.data.token
})

2. 设置语音合成的发音人、采样率、音频格式等属性

可以基于SpeechSynthesizer实例的defaultStartParams方法返回的默认属性对象进行设置,也可以自定义一个属性对象。该属性对象在调用SpeechSynthesizer实例的start函数时需要传入。

参数说明:

参数

类型

是否必需

说明

text

String

待合成文本,文本内容必须采用UTF-8编码,长度不超过300个字符(英文字母之间需要添加空格)。

说明

调用某音色的多情感内容,需要在text中加上ssml-emotion标签,具体请参见<emotion>

voice

String

发音人,默认是xiaoyun

format

String

音频编码格式,支持.pcm、.wav和.mp3格式。默认值:pcm

sample_rate

Integer

音频采样率,默认值:16000 Hz。

volume

Integer

音量,取值范围:0~100。默认值:50。

speech_rate

Integer

语速,取值范围:-500~500,默认值:0。

[-500, 0, 500] 对应的语速倍速区间为 [0.5, 1.0, 2.0]。

  • -500表示默认语速的0.5倍速。

  • 0表示默认语速的1倍速。1倍速是指模型默认输出的合成语速,语速会依据每一个发音人略有不同,大概每秒钟4个字左右。

  • 500表示默认语速的2倍速。

计算方法如下:

  • 0.8倍速(1-1/0.8)/0.002 = -125

  • 1.2倍速(1-1/1.2)/0.001 = 166

说明

  • 小于1倍速时,使用0.002系数。

  • 大于1倍速时,使用0.001系数。

实际算法结果取近似值。

pitch_rate

Integer

语调,取值范围:-500~500,默认值:0。

enable_subtitle

Boolean

开启字级别时间戳。更多使用方法,请参见语音合成时间戳功能介绍

基于defaultStartParams返回的默认属性进行设置

defaultStartParams函数返回一个对象:

{
  voice: voice, // voice的值为调用函数时传入的参数
  format: "wav",
  sample_rate: 16000,
  volume: 50,
  speech_rate: 0,
  pitch_rate: 0,
  enable_subtitle: false
}

可对该对象重新设置,例如:

let param = tts.defaultStartParams();
// 待合成文本
param.text = "举头望明月,低头思故乡";
// 发音人
param.voice = "aixia";
// 语调,范围是-500~500,可选,默认是0
// param.pitch_rate = 100;
// 语速,范围是-500~500,默认是0
// param.speech_rate = 100;
// 设置返回音频的编码格式
// param.format = "wav";
// 设置返回音频的采样率
// param.sample_rate = 16000;
// 是否开启字级别时间戳
// param.enable_subtitle = true;

自定义属性对象

let param = {};
// 待合成文本
param.text = line;
// 发音人
param.voice = "aixia";
// 语调,范围是-500~500,可选,默认是0
// param.pitch_rate = 100;
// 语速,范围是-500~500,默认是0
// param.speech_rate = 100;
// 设置返回音频的编码格式
// param.format = "wav";
// 设置返回音频的采样率
// param.sample_rate = 16000;
// 是否开启字级别时间戳
// param.enable_subtitle = true;

3. 回调函数on

on函数源码如下:

on(which, handler)

语音合成任务开启后,服务端会回调该函数,将语音合成过程中的一些信息返回给客户端。

参数

类型

参数说明

which

String

事件名称。

handler

Function

回调函数。

其中,which参数对应的事件如下:

事件名称

事件说明

回调函数参数个数

回调函数参数说明

meta

字幕回调。

1

String类型,字幕信息。

data

合成音频回调。

1

Buffer类型,合成音频数据。

completed

语音合成完成。

1

String类型,完成信息。

closed

连接关闭。

0

无。

failed

错误。

1

String类型,错误信息。

示例:

let tts = new SpeechSynthesizer({
    url: app.globalData.URL,
    appkey:app.globalData.APPKEY,
    token:this.data.token
})

tts.on("meta", (msg)=>{
  console.log("Client recv metainfo:", msg)
})

tts.on("data", (msg)=>{
  console.log(`recv size: ${msg.length}`)
  console.log(dumpFile.write(msg, "binary"))
})

tts.on("completed", (msg)=>{
  console.log("Client recv completed:", msg)
})

tts.on("closed", () => {
  console.log("Client recv closed")
})

tts.on("failed", (msg)=>{
  console.log("Client recv failed:", msg)
})

4. 任务开启:start

start函数如下,根据传入的param的信息,开始语音合成,服务器会通过调用回调函数on返回语音合成的结果等信息。

async start(param)

参数说明:

参数

类型

参数说明

param

Object

语音合成参数。

返回值: Promise对象,当错误发生后携带异常信息。

代码示例

以下代码示例仅供参考,代码中使用微信小程序自带录音功能,实际使用时,需要考虑微信小程序的限制,以及前端页面设计和具体业务功能。

// pages/tts/tts.js
const app = getApp()

const AKID = "Your AKID"
const AKKEY = "Your AKKEY"
const SpeechSynthesizer = require("../../utils/tts")
const formatTime = require("../../utils/util").formatTime
const sleep = require("../../utils/util").sleep
const getToken = require("../../utils/token").getToken
const fs = wx.getFileSystemManager()
Page({

    /**
     * 页面的初始数据
     */
    data: {
        ttsStart: false, 
        ttsText: ""
    },

    /**
     * 生命周期函数--监听页面加载
     */
    onLoad: async function (options) {
        try {
            this.data.token = await getToken(AKID, AKKEY)
        } catch (e) {
            console.log("error on get token:", JSON.stringify(e))
            return
        }

        let tts = new SpeechSynthesizer({
            url: app.globalData.URL,
            appkey:app.globalData.APPKEY,
            token:this.data.token
        })
        
        tts.on("meta", (msg)=>{
            console.log("Client recv metainfo:", msg)
        })
    
        tts.on("data", (msg)=>{
            console.log(`recv size: ${msg.byteLength}`)
            //console.log(dumpFile.write(msg, "binary"))
            if (this.data.saveFile) {
                try {
                  fs.appendFileSync(
                      this.data.saveFile,
                      msg,
                      "binary"
                  )
                  console.log(`append ${msg.byteLength}`)
                } catch (e) {
                  console.error(e)
                }
            } else {
                console.log("save file empty")
            }
        })
    
        tts.on("completed", async (msg)=>{
            console.log("Client recv completed:", msg)
            await sleep(500)
            fs.close({
                fd : this.data.saveFd,
                success: (res)=> {
                    let ctx = wx.createInnerAudioContext()
                    ctx.autoplay = true
                    ctx.src = this.data.saveFile
                    ctx.onPlay(() => {
                        console.log('start playing..')
                    })
                    ctx.onError((res) => {
                        console.log(res.errMsg)
                        console.log(res.errCode)
                        fs.unlink({
                            filePath: this.data.saveFile,
                            success: (res)=>{
                                console.log(`remove ${this.data.saveFile} done`)
                                this.data.saveFile = null
                                this.data.saveFd = null
                            },
                            failed: (res)=>{
                                console.log("remove failed:" + res.errMsg)
                            }
                        })
                    })
                    ctx.onEnded((res)=>{
                        console.log("play done...")
                        fs.unlink({
                            filePath: this.data.saveFile,
                            success: (res)=>{
                                console.log(`remove ${this.data.saveFile} done`)
                                this.data.saveFile = null
                                this.data.saveFd = null
                            },
                            failed: (res)=>{
                                console.log("remove failed:" + res.errMsg)
                            }
                        })
                    })
                },
                fail : (res)=>{
                    console.log("saved file error:" + res.errMsg)
                }
            })
        })
    
        tts.on("closed", () => {
            console.log("Client recv closed")
        })
    
        tts.on("failed", (msg)=>{
            console.log("Client recv failed:", msg)
        })

        this.data.tts = tts
    },

    /**
     * 生命周期函数--监听页面初次渲染完成
     */
    onReady: function () {

    },

    /**
     * 生命周期函数--监听页面显示
     */
    onShow: function () {

    },

    /**
     * 生命周期函数--监听页面隐藏
     */
    onHide: function () {

    },

    /**
     * 生命周期函数--监听页面卸载
     */
    onUnload: function () {

    },

    /**
     * 页面相关事件处理函数--监听用户下拉动作
     */
    onPullDownRefresh: function () {

    },

    /**
     * 页面上拉触底事件的处理函数
     */
    onReachBottom: function () {

    },

    /**
     * 用户点击右上角分享
     */
    onShareAppMessage: function () {

    },
    textInput: function(e) {
        this.setData({
            ttsText:e.detail.value
        })
    },
    onTtsStart: function() {
        if (!this.data.ttsText || !this.data.tts) {
            console.log("text empty")
            wx.showToast({
                title: "文本为空",
                icon: "error",
                duration: 1000,
                mask: true
              })
            return
        }
        if (this.data.ttsStart) {
            wx.showToast({
                title: "正在合成请稍候",
                icon: "error",
                duration: 1000,
                mask: true
              })
            return
        } else {
            this.data.ttsStart = true
        }
        console.log("try to synthesis:" + this.data.ttsText)
        let save = formatTime(new Date()) + ".wav"
        let savePath = wx.env.USER_DATA_PATH + "/" + save
        console.log(`save to ${savePath}`)
        fs.open({
            filePath:savePath,
            flag : "a+",
            success: async (res)=> {
                console.log(`open ${savePath} done`)
                this.data.saveFd = res.fd
                this.data.saveFile = savePath
                let param = this.data.tts.defaultStartParams()
                param.text = this.data.ttsText
                param.voice = "aixia"
                try {
                    await this.data.tts.start(param)
                    console.log("tts done")
                    this.data.ttsStart = false
                } catch(e) {
                    console.log("tts start error:" + e)
                }
            },
            fail: (res)=> {
                console.log(`open ${savePath} failed: ${res.errMsg}`)
            }
        })
    }
})