• 跨平台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的开发文章寥寥无几!收工!

  • 相关阅读:
    Ext.form.TextField组件
    provider: SQL Network Interfaces, error: 26 Error Locating Server/Instance Specified解决办法
    SQL Server访问远程数据库和Linked Server
    C#程序实现动态调用DLL的研究
    把DLL文件打包进EXE的技巧
    C#读取媒体信息
    C# 集合类
    Server Application Unavailable 【Failed to execute request because the AppDomain could not be created.】的解决办法
    比较全的字符串验证类
    如何用VS2005制作Web安装程序
  • 原文地址:https://www.cnblogs.com/hicjiajia/p/1948882.html
Copyright © 2020-2023  润新知