最近公司方面有个业务需求,因为我们做的是智慧社区项目,有一块涉及到车辆异常出库报警,比如车辆被盗是。
这时候如何能快速的通知到安保人员呢?原来想法是给安保人员配一台PAD,或者在手机上安装个APP,但这样也有很大的弊端。而安保人员人人必备对讲机,所以考虑能不能和对讲机联动。
自然就想到了TTS(TextToSpeech),之前和小i、科大讯飞、华声捷通都用过合作,对这块业务还算比较熟悉,本来打算在树莓派上直接内置一个TTS应用就行了,但这三家都没有免费的SDK,收费的还挺贵,所以这个想法基本就放弃了,但就在最后一刻,发现科大讯飞竟然有TTS芯片!!!
XFS5152CE,科大讯飞中英文语音合成芯片,淘宝搜了一下,竟然发现了做好的模块,一个是科大讯飞自己做的(98元),还有一个是基于SYN6288芯片做的(57元),于是都买回来测试了一下,SYN6288跟XFS5152CE一比简直就是扔货,所以这里就不说关于SYN6288的开发了,如果有时间的话,可以单独做一篇。
废话到此为止,干货在下面!
首先需要准备几样东西
一对对讲机、一条手咪线、一块TTS模块,另外,我还开了一块底板,一会做介绍。
先说TTS模块的对接方式
根据XFS5152CE的开发文档,我们用到RXD、TXD、GND、V3.3、RDY四个引脚,当TTS有语音输出的时候RDY会输出高电平,这块一会我们用来控制手台的信道占用(就是我们手动按下呼叫按钮),严重说一下,这里用的电压是3.3V的,因为后面我们控制继电器的时候用的是5V的,这里千万表混了,要不然98块钱就会化作一缕青烟……
先用TTS模块做测试,科大讯飞也提供测试程序了,我们只要把模块用串口线接到电脑上(TTL电平),用他们的软件测试就行了,还没有做下一步和对讲机联动的,先用个耳机试试声音。
当然那我们这里肯定还是要写程序的。
而且仍然用的是JAVA和RXTX做,如果不清楚树莓派上怎么调试的,出门左转,有一篇专门介绍树莓派3配置串口的文章。
直接上代码
package
com.marssoft.jyphon;
import
gnu.io.CommPortIdentifier;
import
gnu.io.PortInUseException;
import
gnu.io.SerialPort;
import
gnu.io.SerialPortEvent;
import
gnu.io.SerialPortEventListener;
import
gnu.io.UnsupportedCommOperationException;
import
java.io.IOException;
import
java.io.InputStream;
import
java.io.OutputStream;
import
java.util.Enumeration;
import
java.util.TooManyListenersException;
/**
* 科大讯飞XFS5152CE芯片语音合成测试程序。
*
@author
Mars.CN
*
*/
public
class
TTS {
public
static
final
byte
STATE_IDLE
=0X4F;
//空闲状态
public
static
final
byte
STATE_USE
=0X4E;
//占用中状态
public
static
final
byte
STATE_INITED
= 0X4A;
//初始化成功
public
static
final
byte
STATE_SUCCESS
= 0X41;
//正确的命令帧
public
static
final
byte
STATE_ERROR
= 0X45;
//错误的命令帧
public
static
final
byte
STATE_NULL
= 0X00;
//设备空
public
static
final
byte
COMMAND_QUERY_STATE
= 0X21;
//查询状态命令
public
static
final
byte
COMMAND_POWER_MODE_SAVING
=(
byte
)0X88;
//设置为省点模式
public
static
final
byte
COMMAND_POWER_MODE_USEING
=(
byte
)0XFF;
//设置为唤醒模式
public
static
final
byte
COMMAND_TTS
=0X01;
//语音合成命令
public
static
final
byte
COMMAND_STOP
=0X02;
//停止语音合成
public
static
final
byte
COMMAND_SUSPEND
=0X03;
//暂停合成
public
static
final
byte
COMMAND_RECOVERY
=0X04;
//回复合成
private
SerialPort
port
=
null
;
private
InputStream
in
=
null
;
private
OutputStream
out
=
null
;
private
TTS() {}
/**
* 初始化串口。
*
@param
name
*/
private
TTS(String name) {
//获得本地所有串口列表,这里其实只能获得ttyS开头的串口
Enumeration portList = CommPortIdentifier.getPortIdentifiers();
while
(portList.hasMoreElements()){
//获得串口的标识符
CommPortIdentifier portId = portList.nextElement();
//通过标识符得到串口名字,并判断这个名字是不是我们需要的那个串口
if
(portId.getName().equals(
"/dev/"
+name)){
SerialPort p=
null
;
try
{
//如果确实是我们需要的串口,则打开这个串口
//open(串口占用进程名称,串口等待超时时间)
p = (SerialPort) portId.open(
"TTSTest"
, 2000);
//给串口一个数据到达侦听(触发器)
p.addEventListener(
new
EventListener());
//把数据到达通知打开
p.notifyOnDataAvailable(
true
);
//设置串口的波特率,参数依次是(波特率,数据位,停止位,校验位)
p.setSerialPortParams(9600,SerialPort.
DATABITS_8
, SerialPort.
STOPBITS_1
,SerialPort.
PARITY_NONE
);
//获得输入输出流,方便操作。
out
= p.getOutputStream();
in
= p.getInputStream();
port
=p;
}
catch
(PortInUseException e) {
e.printStackTrace();
}
catch
(TooManyListenersException e) {
e.printStackTrace();
}
catch
(UnsupportedCommOperationException e) {
e.printStackTrace();
}
catch
(IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 打开串口
*
*
@param
port
*/
public
static
final
TTS open(String name) {
TTS serial =
new
TTS(name);
if
(serial.
port
!=
null
){
return
serial;
}
return
null
;
}
/**
* 发送数据。
*
@param
data
*/
public
void
send(
byte
[] data){
try
{
out
.write(data);
out
.flush();
}
catch
(IOException e) {
e.printStackTrace();
}
}
/**
* 播放信息
*
@param
msg
*/
public
byte
play(String msg){
if
(
port
!=
null
){
try
{
//把文字内容转换成GB2312编码的二进制数据(我记得硬件里用的都是GBK,如果有问题的话大家用GBK试试看)
byte
[] mdata = msg.getBytes(
"GB2312"
);
//根据语音合成指令,协议头三个字节,分别是帧头0XFD,数据长度高字节,数据长度低字节,语音合成命令0X01,编码类型0X00(GB2312),后面是数据
byte
[] datas =
new
byte
[5+mdata.
length
];
datas[0]=(
byte
)0xFD;
datas[1]=(
byte
)((datas.
length
-3)/256);
datas[2]=(
byte
)((datas.
length
-3)%256);
datas[3]=
COMMAND_TTS
;
System.arraycopy(mdata, 0, datas, 5, mdata.
length
);
// System.out.println(HexFormat.format(datas));
//发送开始合成
out
.write(datas);
return
STATE_SUCCESS
;
}
catch
(IOException e) {
e.printStackTrace();
return
STATE_ERROR
;
}
}
return
STATE_NULL
;
}
/**
* 关闭串口。
*/
public
void
close(){
//当然,这里可以做一下事件侦听,再给close加个参数,这样在串口异常报错的时候能能捕获到了。
port
.close();
port
=
null
;
out
=
null
;
in
=
null
;
}
private
class
EventListener
implements
SerialPortEventListener{
@Override
public
void
serialEvent(SerialPortEvent event) {
switch
(event.getEventType()) {
//这些属性应该跟串口特性有关,我还没搞清楚,暂时不解释
case
SerialPortEvent.
BI
:
case
SerialPortEvent.
OE
:
case
SerialPortEvent.
FE
:
case
SerialPortEvent.
PE
:
case
SerialPortEvent.
CD
:
case
SerialPortEvent.
CTS
:
case
SerialPortEvent.
DSR
:
case
SerialPortEvent.
RI
:
case
SerialPortEvent.
OUTPUT_BUFFER_EMPTY
:
break
;
case
SerialPortEvent.
DATA_AVAILABLE
:
//获取到串口返回信息
int
d = 0;
do
{
try
{
//读取一个串口返回之,并判断这个返回值是什么状态。
d =
in
.read();
switch
(d){
case
STATE_INITED
:
System.
out
.println(
"初始化成功"
);
break
;
case
STATE_SUCCESS
:
System.
out
.println(
"命令正确"
);
break
;
case
STATE_ERROR
:
System.
out
.println(
"命令错误"
);
break
;
case
STATE_IDLE
:
System.
out
.println(
"设备空闲"
);
break
;
case
STATE_USE
:
System.
out
.println(
"语音合成中"
);
break
;
}
}
catch
(IOException e){
return
;
}
}
while
(d != -1);
System.
out
.println(
"退出关闭"
);
close();
//这里一定要用close()方法关闭串口,释放资源
break
;
default
:
break
;
}
}
}
/**
* 测试语音
*
@param
args
*/
public
static
void
main(String[] args) {
TTS tts = TTS.open(
"S45"
);
if
(tts!=
null
){
//发送测试语音
tts.play(
"sound 101 你好世界 Hello world"
);
}
else
{
System.
out
.println(
"串口初始化失败!"
);
}
}
}
芯片的开发指南
http://www.iflytek.com/upload/contents/2014/07/53be5e3ec4047.pdf
具体其他语音合成的方式,大家可以去参考一下开发文档,包括一些提示音的设置。
现在语音已经可以正常合成了,现在我们看如何做成一个和对讲机联动的成品。
和对讲机联动,用到了RDY引脚,当语音播放的时候,RDY引脚处于高电平,这时候我们给这个引脚加一个继电器,高电平是继电器导通,这样就把手咪线正负极导通了,也就相当于我们手动按下了对讲键,手咪线有四根,按我这个版本,黄色的是PPT(按键),红色的是MIC+,绿色的是负极,蓝色的是耳机线,直接按照这样接线就行了。
为此,我做了一块底板,上面有个继电器,再把讯飞的TTS模块焊上去,左边留下串口接口,右边留下手咪线的接口。
需要注意的是,对讲机的驱动用5V,讯飞TTS模块驱动用3.3V,正好树莓派就提供者两种电压输出,所以在树莓派一段,留的是RX、TX、GND、Vcc5V、Vcc3.3V五个口,依次对应GPIO14(8)、GPIO15(10)、GND(6)、5V(4)、3V(1),不明白的可以度娘一个树莓派3的GPIO图看看。
注意接线的时候,TX对应树莓派的RX,RX对应树莓派的TX