文本转语音模块SNR9816TTS——结合语言模型进行对话

本文基于的工作:ESP32实现的文本对话——基于文心一言
模块的资料:https://pan.baidu.com/s/1SYjj6a_ruBu_QVHq4mydgg?from=init&_at_=1705638174409#list/path=%2F
提取码:0000

  • 我们将使用 SNR9816TTS 模块将大语言模型输出的文本转为语音后进行播放。

🔥模块介绍

  • SNR9816TTS是基于语音合成算法开发的一款高流畅度,高自然度的优美人声语音合成模块。该模块方案基于新一代的神经网络算法,纯中文版选取了优质的女声发音人(默认)和男声发音人(指令切换),中英文版只支持女声发音人,以满足各种应用场景的合成播报。模块以中文为主,支持数字、英文(纯中文版:单个字母发音、中英文版:英文单词发音),文本编码支持GB2312。优异的合成效果(可懂度、清晰度、自然度、表现力、节奏/停顿、语速、语调、音质、音色、理解程度)方面有显著提升。
  • 搭配对应的上位机测试工具(TTS TOOL),模块可以通过串口(UART)接收合成的文本,直接合成输出语音播报。
  • 资料链接也提供了arduino、STM32、52单片机的例程,我们将使用arduino程序做小幅改动搭载在ESP32上进行使用。

模块外观

  • 引脚定义如下:
引脚号 引脚名称 类型 IO电压 上电默认状态 功能定义
1 DAC O - - DAC通道音频输出
2 BAUD I 3.3V 低电平 设置UART通信波特率,通电前设置一次
默认低电平(引脚悬空):115200bps
高电平(3.3V):9600bps
3 SPKP O - - 喇叭输出
4 SPKP O - - 喇叭输出
5 GND P - - 电源地(推荐电源接入)
6 5V P - - 5V电源(推荐电源输接入)
7 5V P - - 5V电源(不推荐电源接入)
8 3V3 P - - 3.3V电源输出,电流不超过10mA
9 TICP - - - ICP烧录接口
10 RICP - - - ICP烧录接口
11 GND P - - 电源地(不推荐电源接入)
12 RX I 3.3V 高电平 UART接收器数据输入,
模块通信控制接口
13 TX O 3.3V 高电平 UART发送器数据输出,
模块通信控制接口

🔥接线图

  • 接线很简单,就是将ESP32和TTS模块的串口互联,并注意一定要 共地 ,然后TTS外接喇叭,同时分别为两者提供电源。
  • ESP32这里用的是DEVKITV1版本,其他的版本可以随情况变化引脚。
    接线图

🔥通信

  • 该模块设有多个命令,包括合成语音、暂停合成、终止合成、继续合成,还有调节音量、语速、音调,可以查询模块工作状态,甚至添加预设好的提示音、铃声等。这里只用语音合成和查询工作状态两个语句。其它的可以参考链接内有的PDF文档。

  • 语音合成:

    帧头(1byte) 数据长度(2byte) 命令字(1byte) 编码参数(1byte) 文本/GB2312编码(N byte)
    0xFD 0x–(高8位),0x–(低8位)
    长度=命令字+编码参数+文本
    0x01 0x01 文本对应的GB2312编码
  • 通信成功返回 0x41

  • 查询状态:

    发送数据 说明
    0xFD 0x00 0x01 0x21 每次发送文本前都应该查询工作状态,必须处于空闲状态,才可以发送新的合成文本
  • 返回 0x4E 表示忙,0x4F 表示空闲,其他情况说明通信异常

🔥代码

承接上一次的代码,已经完成了与大模型对话的功能(需要在确保上期已实现的基础上继续测试),我们只需要实现对模块的控制功能即可。

  • 将接下来的三个文件放在同一个文件夹,将ino文件烧录ESP32。

TTS.cpp

  • 该文件完成了对TTS模块的使用,设置了一个TTS类,对串口进行初始化,设置了语音合成和状态查询两个函数,语音合成会先进行一次状态查询,总共用到了两个串口,Serial和Serial2,前者是我们输入问题的串口,使用Arduino平台的串口窗口即可,后者是ESP32和TTS模块通信的串口(要接线的)。
#include "TTS.h"

TTSModule::TTSModule() {
}

void TTSModule::init() {
  Serial2.begin(115200, SERIAL_8N1);
  if (!Serial2) {
    Serial.println("Invalid EspSoftwareSerial pin configuration, check config"); 
    while (1) {
      delay (1000);
    }
  } 
  Serial.begin(115200);
  speak("语音模块就绪");
}

void TTSModule::speak(String data) {
  while(workstate() > 0) {
    if (workstate() == 1) Serial.println("tts busy");
    if (workstate() == 2) Serial.println("tts wrong");
  }
  Serial.println("Speaking: " + data);
  String utf8_str = data;
  String gb2312_str = GB.get(utf8_str);
  unsigned char head[gb2312_str.length()+6];
  unsigned char gb2312_data[gb2312_str.length() + 1];
  memset(gb2312_data, 0, sizeof(gb2312_data));
  strncpy((char*)gb2312_data, gb2312_str.c_str(), gb2312_str.length());
  head[0] = 0xFD;
  unsigned int dat_len = gb2312_str.length() + 3;
  head[1] = dat_len >> 8;
  head[2] = dat_len;
  head[3] = 0x01;
  head[4] = 0x01;
  for (int i = 0; i < gb2312_str.length(); i++) {
    head[i + 5] = gb2312_data[i];
  }
  head[gb2312_str.length() + 5] = head[0];
  for (int i = 1; i < gb2312_str.length() + 5; i++) {
    head[gb2312_str.length() + 5] ^= head[i];
  }
  for (int j = 0; j < gb2312_str.length() + 6; j++) {
    Serial2.write(head[j]);
  }
}

int TTSModule::workstate() {
  unsigned char head[4];
  head[0] = 0xFD;
  head[1] = 0;
  head[2] = 1;
  head[3] = 0x21;
  for (int j = 0; j < 4; j++) {
    Serial2.write(head[j]);
  }
  while (Serial2.available() < 1) {
    delay(150);
  }
  byte response = Serial2.read();
  if (response == 0x4E) {
    return 1;
  } else if (response == 0x4F) {
    return 0;
  } else {
    return 2;
  }
}

TTS.h

  • TTS.cpp文件的头文件
#ifndef TTS_MODULE_H
#define TTS_MODULE_H

#include "UTF8ToGB2312.h"

class TTSModule {
public:
  TTSModule();
  void init();
  void speak(String data);

private:
  int workstate();
  char snr9816tts_say_sentence(unsigned char *sentence, unsigned int len);
};

#endif

ino

  • 在上期的代码基础上,在setup调用了TTS初始化,在loop中收到语言模型答案时调用speak函数,注意把access token 和 wifi 进行更改。
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include "TTS.h"

TTSModule ttsModule;
// 1. Replace with your network credentials
const char* ssid = "wifi名称";
const char* password = "wifi密码";

String inputText = "你好";
// 2. Replace with your access_token
String apiUrl = "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions?access_token=xxxxx";

String answer;
String getGPTAnswer(String inputText) {
  HTTPClient http;
  http.begin(apiUrl);
  http.setTimeout(30000);     //超过时间,回答超时(ms)
  http.addHeader("Content-Type", "application/json");
  // http.addHeader("access_token", String(access_token));
  String payload = "{\"messages\":[{\"role\": \"user\",\"content\": \"" + inputText + "\"}],\"disable_search\": false,\"enable_citation\": false}";
  int httpResponseCode = http.POST(payload);
  if (httpResponseCode == 200) {
    String response = http.getString();
    http.end();
    Serial.println(response);

    // Parse JSON response
    DynamicJsonDocument jsonDoc(2048);
    deserializeJson(jsonDoc, response);
    String outputText = jsonDoc["result"];
    return outputText;
    // Serial.println(outputText);
  } else {
    http.end();
    Serial.printf("Error %i \n", httpResponseCode);
    return "<error>";
  }
}

void setup() {
  ttsModule.init();
  // Initialize Serial
  Serial.begin(115200);

  // Connect to Wi-Fi network
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.print("Connecting to WiFi ..");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print('.');
    delay(1000);
  }
  Serial.println(WiFi.localIP());
  answer = getGPTAnswer(inputText);
  Serial.println("Answer: " + answer);
  Serial.println("Enter a prompt:");

}

void loop() {
  // do nothing
  if (Serial.available()) {
    inputText = Serial.readStringUntil('\n');
    // inputText.trim();
    Serial.println("\n Input:"+inputText);

    answer = getGPTAnswer(inputText);
    Serial.println("Answer: " + answer);
    Serial.println("Enter a prompt:");
    ttsModule.speak(answer);
  }
}

🔥测试

上位机软件

  • 先使用模块官方提供的上位机软件进行调试(在链接有),其实就是一个定制化的串口调试器,可以看到有之前提到的所有功能,在这里都可以方便调试。
  • 首先接线只需将ESP32暂时换成USB转串口的模块即可,还是 TX/RX/GND 三根线就行了。然后插上电脑,选择对应 COM 口并打开。
  • 先点击工作状态查询,下方收发窗口中显示的发送和接收的信息都符合通信协议。
  • 然后测试语音,这里我输入12,语音播报“十二”而不是“一二”,因为该模块能够实现对于数字的口语化。观察收发窗口可以看到返回了 0x41,说明通信正常。
    上位机调试
  • 后面我也调试了音量、提示音等其他功能也是能正常运作,在此不多展示,说明模块是正常工作的,接下来只需验证代码。

正式调试

  • 按接线图接线后,烧录ESP32代码,上电,即可在Arduino的串口界面进行验证。
  • 先通电时发出“语音模块就绪”(视频没展示),说明TTS模块可以工作,然后在输入框输入问题,得到了对应的语音结果,说明调试成功。
    正式调试结果
  • 注意,复杂问题通常要等待一段时间才能回复。
  • Copyrights © 2023-2025 LegendLeo Chen
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信