这一篇介绍eSpeak1.06源码的编译调试及基本框架。
上篇详细说明了eSpeak的调试环境的搭建,调试则和一般的gdb调试命令行调试区别不大,只不过是有了vim的代码界面和vimgdb提供的gdb命令的快捷键。为了调试肯定要有已经编译好的speak可执行文件,当然前面已经说过增加-g参数,这里直接make即可,而后#vim speak.cpp进入编辑界面,因为运行过run macros/gdb_mappings.vim了,所以直接按空格键进入调试命令行,file speak加载可执行文件,会看到vimgdb的模式状态、gdb的版本信息,再在命令行下按个回车会看到加载speak成功的提示:
17 (gdb) file speak
18 Reading symbols from /opt/tts/speak-1.06-source/src/speak...done.
而如果之前在speak的makefile中没有修改,则会提示没有调试信息。 Ctrl+B在speak.cpp中添加断点,Shift+R键运行,Ctrl+P(命令行中p命令)输出变量值等;一般调试会有两个问题:
一是如何运行时带参数,即这里想要保存为WAV文件,运行时需要-w参数和保存的WAV文件名。这时可以在命令行下通过r命令加参数来执行,即:r -w test就是运行speak -w test,当然前提是你已经加载了speak。
二是调试运行中,需要和终端交互即输入输出,参考【1】中方法,通过tty /dev/pts/1命令来重定向标准输入输出其中的1数字可以在新开的终端通过tty命令查看。但是据说在Ubuntu中(我的是debian)会出现 “warning: GDB: Failed to set controlling terminal: 不允许的操作”提示,据说是个bug,没有解决,但是它在这里并不影响我们的输入,因此,只要在需要输入的时候,输入即可,比如这里输入“hello world”,回车之后虽然重定向的终端提示没有此命令,而后你再输入一遍,终端上没有回显,但其实已经输入,这时如果关闭该终端,而speak调试端没有其他断点,会显示“Program exited normally”,看到speak所在目录下的名为test的WAV文件。(此处如果不关闭终端会一直等待输入是因为speak程序收不到结束符。)
下面看看speak的运行流程如图所示:
在参数选择后(这里主要说保存问WAV文件),执行如下几个函数:
initialise();LoadVoice(voicename,0);SetSpeed(speed,amp);
OpenWaveFile(wavefile);
synth->SpeakNextClause(f_text,0);
for(;;)
{
if(WavegenFile() != 0)
break; // finished, wavegen command queue is empty
if(synth->Generate(phoneme_list,1)==0)
synth->SpeakNextClause(NULL,0);
}
CloseWaveFile();
在初始化过程中,主要是WavegenInit(22050,0);VoiceInit();LoadPhData();完成初始输入源的设置、音调等声音属性设置和加载因素表载入声音数据等操作,而后创建Synthesize类对象synth,它的部分函数如下:
int Generate(PHONEME_LIST *phoneme_list, int resume);
void MakeWave2(PHONEME_LIST *p, int n_ph);
int OnTimer(void);
void SpeakNextClause(FILE *f_text, int stop);
private:
void DoAmplitude(int amp);
void DoPitch(unsigned char *env, int pitch1, int pitch2);
void DoPause(int length);
void DoSample(PHONEME_TAB *ph1, PHONEME_TAB *ph2, int which, int length);
void DoSpect(PHONEME_TAB *this_ph, PHONEME_TAB *this_ph, int which, int length_mod, int stress, int modulation);
void DoMarker(int type, int index);
void EndPitch(int voice_break);
void SmoothSpect(void);
void StartSyllable(void);
能够看到其完成的合成操作。而最开始的文本输入和处理则是由Translator类来完成的,看看其部分函数:
char *TranslateClause(FILE *f_text, char *buf, int *tone);
int LoadDictionary(const char *name);
virtual void CalcLengths();
virtual void CalcPitches(int clause_tone);
protected:
int TranslateWord(char *word, int next_pause, int wflags);
int TranslateWord2(char *word, int wflags, int pre_pause, int next_pause, int source_ix);
void TranslateLetter(char letter, char *phonemes);
void GetTranslatedPhonemeString(char *phon_out, int n_phon_out);
void MakePhonemeList(int post_pause);
char GetC(void);
主要分析这两个类,大部分所要的功能已经足够了。就列到这里了,其余的根据自己的需要深入吧,我只能给开这样一个头。
参考
vi/vim使用进阶: vimgdb调试时的常见问题及解决 http://easwy.com/blog/archives/advanced-vim-skills-vim-gdb-vimgdb-faq/