跨平台TTS eSpeak Windows开发

原文链接:http://cool.worm.blog.163.com/blog/static/6433900620097535713944/

      估计我又要长篇大论一番了,这个问题折磨了十多天,最后终于有了起色,算是安慰了。

      eSpeak是最为流行的开源跨平台的文本转语音程序,我早在星际译王StarDict中就已经接触到,但并没有详细介绍应用它,这些天一直用Qt编写程序,当然在功能需求上eSpeak当然成了最好的选择!Pass掉了Microsoft Speech SDK,我怎么有些感觉郁闷……不过我比较喜欢研究一些未接触过的东东,结果碰了一脸灰!等我慢慢喷来呀~

      从哪开始说那……

  

先去网站看看吧!    http://espeak.sourceforge.net/
这里我提及两个版本的下载,稳定版和测试版,它们的下载地址如下:

http://espeak.sourceforge.net/download.html
http://espeak.sourceforge.net/test/latest.html

上面页面的版本是espeak-1.40.02和espeak-1.40.23,在Windows下有专门的安装程序,安装后便可测试文本转语音功能,但不适合开发,没有特定的动态链接库和头文件,庆幸的是开源软件,当然不用说,下载espeak-1.40.02-source.zip和espeakedit-1.40.02.zip了(当然你也可以下载espeak-1.40.23.zip和speakedit-1.40.23.zip,两个版本我都编译过,1.40.23比较不错,它去掉了一些无用的编译文件,呵呵!)这里我用espeak-1.40.02进行的开发。

通过阅读文档发现需要另一个开源软件的支持,PortAudio库:免费开源的跨平台音频播放库,支持Windows, Macintosh, Unix, SGI and BeOS等平台,如下是在Windows + VC 6.0 环境下编译(这部分转的呀);

一、 准备
     1、PortAudio开源库 :本人用的是 portaudio_v19
          官方主页:
http://www.portaudio.com/

     2、依赖ASIO库:用于异步处理 这个自己在网上搜索下吧,我也是找了一会下载页面没记~

     3、依赖DirectX库:用于驱动声卡

二、步骤
      1、将ASIO库 拷贝到 portaudio\src\hostapi\asio\目录下 文件夹改名为ASIOSDK。
      2、安装DirectX库。
      3、用VC6.0 打开portaudio\build\msvc\portaudio.dsw (要是VS2005打开portaudio.sln)
      4、可选:

           官方说明:http://www.portaudio.com/trac/wiki/TutorialDir/Compile/Windows(参照设置)
                           
http://www.portaudio.com/trac/wiki/TutorialDir/Compile/WindowsASIOMSVC(参照检查文件)

           注:Finally, open the "pa_win_hostapis.c" file. Add the following:

               #define PA_NO_WMME
               #define PA_NO_DS

几句话,郁闷了我半天,总是在测试程序时也通不过,总得到后面错误的结果。在Win32环境中是需要WMME和DS的。这两个define语句是不能加的,小小的惩罚,看文章要认真点哦。

编译生成的动态链接库portaudio_x86.dll、portaudio_x86.lib和portaudio.h是我们在eSpeak中可能要用到的哦!

解压espeak-1.40.02-source.zip,espeak-1.40.02-source\platforms\windows\目录下包含了windows_cmd、windows_dll、windows_sapi和espeakedit工程目录,里边都含有VC工程项目文件。 
windows_cmd是生成espeak.exe命令行程序。 
windows_dll是生成espeak_lib.dll动态链接库(本人主要想使用这个,通过函数调用实现文本转语音功能)。 
windows_sapi是通过SAPI实现的动态链接库(需要Microsoft Speech SDK的支持,哈哈!)。 
espeakedit生成espeakedit.exe(这里需要wxWidgets的支持和下载的espeakedit-1.40.02.zip)。
貌似可以编译运行了,那就开始吧!

先从windows_cmd开始,阅读目录下的!ReadMe.txt,espeak-1.40.02-source\src下的文件全部拷贝到,espeak-1.40.02-source\platforms\windows\windows_cmd\src,不包括speech.h、stdint.h,编译被磕倒……

此程序运行后报错误:Cannot open include file: 'unistd.h': No such file or directoryc

搜索原因,解释如下:Linux下开发的C程序都需要头文件unistd.h,但VC中没有个头文件, 所以用VC编译总是报错。把下面的内容保存为unistd.h,可以解决这个问题。

/** This file is part of the Mingw32 package. 
* unistd.h maps    (roughly) to io.h 
*/ 

#ifndef _UNISTD_H 
#define _UNISTD_H 

#include <io.h> 
#include <process.h> 

#endif /* _UNISTD_H */

其实这样问题并不能解决,我已经安装Dev-Cpp,里面含有MingW32,我也包含C:\Dev-Cpp\include中的头文件,结果来时屡试不爽!

在无意中,合计试试另一个Linux工程转入Windows程序的工具Cygwin:

安装Cygwin比较容易,在http://www.cygwin.com/您需要下载setup.exe,此程序根据您选择的软件包,再从互联网下载所有组件。安装比较容易这里可以在安装中添加一个国内最快的镜像进行下载。

跨平台TTS eSpeak Windows开发 - vic.MINg - vic.MINg的博客

配置一下环境变量将C:/Cygwin/bin加到%PATH%当中,您就可以直接在cmd.exe里面使用Linux命令了,比如less, cat, wc , wget

安装完毕,将C:\Cygwin\usr\include加到工程中,继续编译代码。

啊!对了呀portaudio_x86.dll、portaudio_x86.lib和portaudio.h这个问题忘讲了在espeak-1.40.02-source\platforms\windows\windows_cmd目录下可以看见PAStaticWMME.lib在espeak-1.40.02-source\platforms\windows\windows_cmd\src目录中有portaudio.h头文件,这里说明在工程中已经包含了默认的PortAudio静态链接库,你可以改变它加入新的portaudio_x86.dll、portaudio_x86.lib和portaudio.h,替换掉以前的PortAudio库。

如果没有意外的话这样编译就通过了,哈哈!编译Release版本,在工程目录下生成espeak.exe,将其拷贝到espeak-1.40.02-source目录下,进行测试:

在cmd模式下运行命令:

C:\Documents and Settings\Administrator>cd C:\espeak-1.40.02-source
C:\espeak-1.40.02-source>espeak "hello"
Can't read data file: '\espeak-data\phontab'
Failed to load espeak-data

表慌,看下提示说找不到文件,我们在还是看看代码吧,main()函数在espeak-1.40.02-source\platforms\windows\windows_cmd\src\speak.cpp中调用了设置路径init_path(argv[0],data_path); 
可以找到static void init_path(char *argv0, char *path_specified)函数,观看一遍可以看出如果data_path为NULL传入的话
Windows会从注册表中到到路径,当然我们并非安装当然找不到路径了……这样只需要传入路径即可!

C:\Documents and Settings\Administrator>cd C:\espeak-1.40.02-source
C:\espeak-1.40.02-source>espeak --path="." -v en "hello"

霸道的听到声音了吧~来庆祝一下!

 

编译windows_dll工程,这是文章的重点,因为我们要使用它编译出来的动态链接库来进行编程开发。同样将espeak-1.40.02-source\src下的文件拷贝到espeak-1.40.02-source\platforms\windows\windows_dll\src下不覆盖speak_lib.h、speech.h、StdAfx.h、stdint.h文件。进行编译,提示找不到tr_english.cpp文件(在espeak-1.40.23下就不会出现这种情况了,而且speak_lib.h添加对espeakCHARS_16BIT的支持),这个文件已经被废弃了,值要添加一个空文件并且在中添加#include "StdAfx.h"即可。

编译通过,生成了espeak_lib.dll、espeak_lib.lib,这样迫不及待的写了一个测试程序想看看劳动成果。但结果会让人大失所望。在网上几乎没有关于eSpeak在Windows平台下编译开发的文章及例子,偶尔看见一个英文论坛中有些问题,也是无法止痒……前方的路还真黑呀!

一步一步来吧,先说例子,先建立一个mfc对话框的例子,在上添加一个按钮,目的就是点击按钮时调用函数实现TTS功能。首先要在把所需的文件拷贝到工程目录下espeak_lib.dll、espeak_lib.lib、speak_lib.h和espeak-data目录下的所有文件。按照网上Linux下的例子编写代表:

#include "speak_lib.h" 
#pragma comment(lib, "espeak_lib.lib")

void CTTSDlg::OnButton() 
{
    
// TODO: Add your control notification handler code here
    char en_word[] = "Hello World";
    espeak_Initialize(AUDIO_OUTPUT_PLAYBACK, 0, ".", 0);
    // "."是当前目录路径,不能使NULL。上边已经讲解原因了
    espeak_SetVoiceByName("en");
    espeak_Synth(en_word, strlen(en_word)+1, 0, POS_CHARACTER, 0, espeakCHARS_UTF8, NULL ,NULL);
    espeak_Terminate();
}

跟本没声……咋办,调试运行吧,断点跟踪发现espeak_Synth()函数的返回值总是EE_INTERNAL_ERROR。通过动态链接库的调试发现一个synchronous_mode得东东,阅读espeak-1.40.02-source\platforms\windows\windows_dll\!ReadMe.txt发现一句话

This provides the API which is defined in speak_lib.h, using the AUDIO_OUTPUT_SYNCHRONOUS mode only.

顿时有些觉悟,根据"speak_lib.h"中的文档,说是使用 AUDIO_OUTPUT_SYNCHRONOUS模式要设置回调函数。根据需要修改了代码如下:

#include "speak_lib.h" 
#pragma comment(lib, "espeak_lib.lib")

static int SynthCallback(short *wav, int numsamples, espeak_EVENT *events)
{
    
// 你可以根据源码程序里编写这部分代码实现生成语音文件功能,这里忽略掉了
    return(0);
}

void CTTSDlg::OnButton() 
{
    
// TODO: Add your control notification handler code here
    char en_word[] = "Hello World";
    espeak_Initialize(AUDIO_OUTPUT_SYNCHRONOUS, 0, ".", 0);
    
// "."是当前目录路径,不能使NULL。上边已经讲解原因了
    espeak_SetSynthCallback(SynthCallback);   // 设置回调函数
    espeak_SetVoiceByName("en");
    espeak_Synth(en_word, 0, 0, POS_CHARACTER, 0, espeakCHARS_UTF8, NULL ,NULL);
    espeak_Synchronize();
    espeak_Terminate();
}

还是没声……,调试运行,这是各个返回值完全正常,这样查看代码发现windows_dll工程中根本没入没有引入PortAudio库,这样怎么会有声音,为了方便拷贝windows_cmd中的PAStaticWMME.lib到windows_dll里。在espeak-1.40.02-source\platforms\windows\windows_dll\src\speech.h 中会发现 #define USE_PORTAUDIO 被注释掉了,把注释释放。

在"Project"菜单中的"Settings…”,"Link"选项卡中"Object/library modules:"中添加 PAStaticWMME.lib winmm.lib 。
屏蔽库冲突,在"Project"菜单中的"Settings…”,"Link"选项卡中"Category:"中选择Input,"Ignore libraries:"中添加 libc.lib。

这样加入了PortAudio库,从新编译,生成espeak_lib.dll、espeak_lib.lib,复制到测试工程中。运行测试,未免还会让人失望……因为"using the AUDIO_OUTPUT_SYNCHRONOUS mode only"为我们带来的误区。

    if(my_mode == AUDIO_OUTPUT_SYNCH_PLAYBACK)
    {
        for(;;)
        {
#ifdef PLATFORM_WINDOWS
             Sleep(300);   // 0.3s
#else
#ifdef USE_NANOSLEEP
              struct timespec period;
              struct timespec remaining;
              period.tv_sec = 0;
              period.tv_nsec = 300000000;  // 0.3 sec
              nanosleep(&period,&remaining);
#else
              sleep(1);
#endif
#endif
              if(SynthOnTimer() != 0)
                  break;
        }
        return(EE_OK);
   }

这个我也是被折磨了好久好久,根据调试动态链接库中上边的代码和对比windows_cmd工程,发现在初始化的时候应该使用AUDIO_OUTPUT_SYNCH_PLAYBACK参数,很有道理,很有道理!

#include "speak_lib.h" 
#pragma comment(lib, "espeak_lib.lib")

static int SynthCallback(short *wav, int numsamples, espeak_EVENT *events)
{
    // 你可以根据源码程序里编写这部分代码实现生成语音文件功能,这里忽略掉了
    return(0);
}

void CTTSDlg::OnButton() 
{
    // TODO: Add your control notification handler code here
    char en_word[] = "Hello World";
    espeak_InitializeAUDIO_OUTPUT_SYNCH_PLAYBACK, 0, ".", 0);
    // "."是当前目录路径,不能使NULL。上边已经讲解原因了
    espeak_SetSynthCallback(SynthCallback);   // 设置回调函数
    espeak_SetVoiceByName("en");
    espeak_Synth(en_word, 0, 0, POS_CHARACTER, 0, espeakCHARS_UTF8, NULL ,NULL);
    espeak_Synchronize();
    espeak_Terminate();
}

运行测试吧,这回你一定有惊喜。说的是英文!想要说中文的话还用进行一些修改,以为中文使用的是wchar_t

#include "speak_lib.h" 
#pragma comment(lib, "espeak_lib.lib")

static int SynthCallback(short *wav, int numsamples, espeak_EVENT *events)
{
    // 你可以根据源码程序里编写这部分代码实现生成语音文件功能,这里忽略掉了
    return(0);
}

void CTTSDlg::OnButton() 
{
    // TODO: Add your control notification handler code here
    wchar_t zh_word[] = "你好沈阳";
    espeak_InitializeAUDIO_OUTPUT_SYNCH_PLAYBACK, 0, ".", 0);
    // "."是当前目录路径,不能使NULL。上边已经讲解原因了
    espeak_SetSynthCallback(SynthCallback);   // 设置回调函数
    espeak_SetVoiceByName("zh");
    espeak_Synth(zh_word, 0, 0, POS_CHARACTER, 0, espeakCHARS_WCHAR, NULL ,NULL);
    espeak_Synchronize();
    espeak_Terminate();
}

打字木乃了……休息休息一下。

 

还有两个工程要编译,其实其他的两个对我已经无关紧要了,并不需要他们,都到这步了,我这里简单说下吧!

编译windows_sapi工程时,要使用到SAPI所以需要安装Microsoft Speech SDK,在工程中包含相关的头文件和库文件即可。这里要注意的是包含(#include)C:\PROGRAM FILES\MICROSOFT SPEECH SDK 5.1\IDL中的文件。

在espeak-1.40.02中会发现有个错误:

windows_sapi\TtsEngObj.cpp(425) : error C2039: 'WritePhonemes' : is not a member of 'CTTSEngObj' 

这事因为头文件中没定义int WritePhonemes(SPPHONEID *phons, wchar_t *pW)函数,在TtsEngObj.h中加上即可。

编译espeakedit工程,需要下载espeakedit-1.40.02.zip,并且有wxWidgets的环境,你可以参看《VC++6.0 下搭建 wxWidgets 开发环境》文章。

其他的按照上述两例进行就可以了!

 

累死,这个问题困扰了两个星期,超无助……我想学的东东好多,OpenGL、OpenCV、DirectX、ATL都是出入牛毛。没办法工作需要呀!最近学习了一点游戏开发,本来感觉极为无用的SDK,居然在游戏开发中崭露头角,颇感兴趣!最起码了解了“小飞机”游戏的原理。不写了,挺不住了,写了一天,写文章是为了为自己的开发道路留有脚印,如果你偶然看见这篇文章,有什么问题可以给我留言,我们共同进步!毕竟关于eSpeak的开发文章寥寥无几!收工!

posted on 2011-02-02 17:50  hicjiajia  阅读(6324)  评论(1编辑  收藏  举报