最近看很多人研究语音接口,于是我也来一发。

很早之前想做一个路由器说话的项目,我的路由器是一台X86架构的小型主机,带有音频输出接口,在装系统的时候驱动都装的很全,插上耳机即可放出音乐。

命令行下播放音乐是用mplayer

mplayer filename.mp3

即可。

接下来要做一个文字转语音的接口,text to speech,简称TTS。网上有很多,python也很容易安装,但是只有英文的。
中文语音处理做的不错的应该是科大讯飞了,科大讯飞也有自己的开放平台,上去注册一个帐号,下载一个开发工具。
工具里有appkey,貌似每个开发者下载的开发包都不一样。

打开Example文件夹下面的ttsdemo,make一下,然后有个可执行文件。执行可执行文件,会生成一个pcm文件,使用mplayer即可播放这个文件,发出声音。

下面把ttsdemo.c这个文件改一下:
改成不生成文件,而是直接从标准输出中输出音频流。注意apppkey要填写自己的。


#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#include "include/qtts.h"
#include "include/msp_cmn.h"
#include "include/msp_errors.h"


typedef int SR_DWORD;
typedef short int SR_WORD ;

//音频头部格式
struct wave_pcm_hdr
{
        char            riff[4];                        // = "RIFF"
        SR_DWORD        size_8;                         // = FileSize - 8
        char            wave[4];                        // = "WAVE"
        char            fmt[4];                         // = "fmt "
        SR_DWORD        dwFmtSize;                      // = 下一个结构体的大小 : 16

        SR_WORD         format_tag;              // = PCM : 1
        SR_WORD         channels;                       // = 通道数 : 1
        SR_DWORD        samples_per_sec;        // = 采样率 : 8000 | 6000 | 11025 | 16000
        SR_DWORD        avg_bytes_per_sec;      // = 每秒字节数 : dwSamplesPerSec * wBitsPerSample / 8
        SR_WORD         block_align;            // = 每采样点字节数 : wBitsPerSample / 8
        SR_WORD         bits_per_sample;         // = 量化比特数: 8 | 16

        char            data[4];                        // = "data";
        SR_DWORD        data_size;                // = 纯数据长度 : FileSize - 44
} ;

//默认音频头部数据
struct wave_pcm_hdr default_pcmwavhdr =
{
        { 'R', 'I', 'F', 'F' },
        0,
        {'W', 'A', 'V', 'E'},
        {'f', 'm', 't', ' '},
        16,
        1,
        1,
        16000,
        32000,
        2,
        16,
        {'d', 'a', 't', 'a'},
        0
};

int text_to_speech(const char* src_text ,const char* params)
{
        struct wave_pcm_hdr pcmwavhdr = default_pcmwavhdr;
        const char* sess_id = NULL;
        int ret = 0;
        unsigned int text_len = 0;
        char* audio_data;
        unsigned int audio_len = 0;
        int synth_status = 1;
        FILE* fp = NULL;

        printf("begin to synth...\n");
        if (NULL == src_text)
        {
                printf("params is null!\n");
                return -1;
        }
        text_len = (unsigned int)strlen(src_text);
        fp = stdout;
        if (NULL == fp)
        {
                printf("open stdout error\n");
                return -1;
        }
        sess_id = QTTSSessionBegin(params, &ret);
        if ( ret != MSP_SUCCESS )
        {
                printf("QTTSSessionBegin: qtts begin session failed Error code %d.\n",ret);
                return ret;
        }

        ret = QTTSTextPut(sess_id, src_text, text_len, NULL );
        if ( ret != MSP_SUCCESS )
        {
                printf("QTTSTextPut: qtts put text failed Error code %d.\n",ret);
                QTTSSessionEnd(sess_id, "TextPutError");
                return ret;
        }
        fwrite(&pcmwavhdr, sizeof(pcmwavhdr) ,1, fp);
        while (1)
        {
                const void *data = QTTSAudioGet(sess_id, &audio_len, &synth_status, &ret);
                if (NULL != data)
                {
                   fwrite(data, audio_len, 1, fp);
                   pcmwavhdr.data_size += audio_len;//修正pcm数据的大小
                }
                if (synth_status == 2 || ret != 0)
                break;
        }

        //修正pcm文件头数据的大小
        pcmwavhdr.size_8 += pcmwavhdr.data_size + 36;

        //将修正过的数据写回文件头部
        fseek(fp, 4, 0);
        fwrite(&pcmwavhdr.size_8,sizeof(pcmwavhdr.size_8), 1, fp);
        fseek(fp, 40, 0);
        fwrite(&pcmwavhdr.data_size,sizeof(pcmwavhdr.data_size), 1, fp);
        fclose(fp);

        ret = QTTSSessionEnd(sess_id, NULL);
        if ( ret != MSP_SUCCESS )
        {
        printf("QTTSSessionEnd: qtts end failed Error code %d.\n",ret);
        }
        return ret;
}

int main(int argc, char* argv[])
{
        ///APPID请勿随意改动
        const char* login_configs = " appid = xxxxxxxxx, work_dir =   .  ";
        const char* text  = argv[1];
        const char* param = "aue = speex-wb;3, vcn=xiaoyan,  spd = 50, vol = 50, tte = utf8";
        int ret = 0;
        char key = 0;

        //用户登录
        ret = MSPLogin(NULL, NULL, login_configs);
        if ( ret != MSP_SUCCESS )
        {
                printf("MSPLogin failed , Error code %d.\n",ret);
        }
        //音频合成
        ret = text_to_speech(text,param);
        if ( ret != MSP_SUCCESS )
        {
                printf("text_to_speech: failed , Error code %d.\n",ret);
        }
        //退出登录
        MSPLogout();
        return 0;
}

编译一下,改个名叫做say_stdout。say_stdout程序会把命令行参数转换为音频流,从标准输出中输出,接下来是把音频流播放出来。

mplayer具有播放音频流的功能
写一个shell程序,命名为say

#/bin/sh
say_stdout $1 | mplayer -cache 8192 - > /dev/null 2>&1

接下来使用say命令就能让路由器说话了。

say "我会讲话了!"

嘿嘿,还可以调节语速和音量参数。

路由器能说话之后,我让他来个正点报时的功能。
在crontab中增加一行

0 * * * * say "现在时刻`date +%I`点整" | mplayer -cache 8192 - > /dev/null 2>&1

完工!
参看http://open.voicecloud.cn/

======================================

忘了说了,会说话只让他正点报时肯定不爽,于是我又开发了一个自定义内容的说话接口,是一个python 写的 cgi程序。
因为cgi程序运行时不是root用户所以不能发出声音,具体原因我也不清楚,至少是不能通过cgi直接来发音的。
于是我做了一个客户端和一个服务器端,客户端是cgi程序,以www用户身份执行,服务器端是开机启动,并且一直在后台运行,两者之间通过unix的套接字通信。
cgi端(客户端)

#!/usr/bin/python
# coding:utf8
print "Content-type: text/html"
print
import cgi,socket,sys
try:
    form = cgi.FieldStorage()
    content = form.getvalue('saywhat')
except:
    content = ''

if content:
    content = content.replace(' ','_')
    content = content.replace('\t','_')
    content = content.replace('\r','_')
    content = content.replace('\n','_')
    try:
        sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        sock.connect("/tmp/say.sock")
        sock.send(content)
        print sock.recv(1024)
        sock.close()
    except:
        print "Error",sys.exc_type

print "<html>"
print "<head><title>路由器说话</title><meta charset='UTF-8'></head>"
print "<body>"
print "said:", content
print "<form method='post' action=''>"
print "    <input type='text' name='saywhat' />"
print "    <input type='submit' value='SAY' />"
print "</form>"
print "</body>"
print "</html>"

服务器端

#!/usr/bin/python
import socket,os,commands,stat

def main():
    sockfile="/tmp/say.sock"
    sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
    if os.path.exists(sockfile):
        os.unlink(sockfile)
    sock.bind(sockfile)
    sock.listen(5)
    os.chmod(sockfile,stat.S_IRWXU|stat.S_IRWXG|stat.S_IRWXO)
    while True:
        connection,address = sock.accept()
        content = connection.recv(1024)
        if content:
            print 'say "%s"' % content
            (rc,rs) = commands.getstatusoutput('say ' + content)
            connection.send(rs)
        connection.close()

if __name__ == "__main__":
    main()

目前即可通过网页来命令路由器讲话了。

Leave a reply