kaldi真正的核心源码,都是C++写成的,这个结论可以从如下两点得以确认:
(1)在kaldi的源码kaldi/src
目录下,能看到很多扩展名为.cc
的源程序,这是linux下C++源码;
(2)在源码中,比如kaldi\src\featbin\compute-mfcc-feats.cc
,可以看到static_cast
和std::string
这样的代码,这是C++特有的语法。
但是我们是怎么使用kaldi的呢,是直接调用这些C++程序?
有过kaldi使用经验的人都知道,kaldi的使用,并不是直接写C++/python程序来调用C++源码/动态链接库。而是通过写SHELL程序,来使用kaldi提供的各项功能(比如提取音频的MFCC特征)。
那问题就来了,我们写的SHELL程序,是怎么调用这些C++程序的呢? 下面我们就以kaldi中的经典入门例子kaldi/egs/yesno/s5
为例,分析其中的关键源码,从而回答这个问题。
首先,我们进入kaldi/egs/yesno/s5
目录,运行其中的run.sh
,看一下例程运行的结果。
run.sh
就是例程的“主程序”,打开它,能看到程序执行的主要过程:下载数据,提取特征,训练,解码。其中提取了MFCC特征,提取MFCC的过程的SHELL代码如下。下面代码中的参数--nj 1
,说明只用一个CPU核来做计算。
# Feature extraction
for x in train_yesno test_yesno; do
steps/make_mfcc.sh --nj 1 data/$x exp/make_mfcc/$x mfcc
steps/compute_cmvn_stats.sh data/$x exp/make_mfcc/$x mfcc
utils/fix_data_dir.sh data/$x
done
可以看到,提取mfcc特征,是调用了steps/make_mfcc.sh
。
继续看文件steps/make_mfcc.sh
,其中大部分代码都在做文件目录配置,参数配置,但能看到如下代码段:
$cmd JOB=1:$nj $logdir/make_mfcc_${name}.JOB.log \
extract-segments scp,p:$scp $logdir/segments.JOB ark:- \| \
compute-mfcc-feats $vtln_opts --verbose=2 --config=$mfcc_config ark:- ark:- \| \
copy-feats --compress=$compress $write_num_frames_opt ark:- \
ark,scp:$mfccdir/raw_mfcc_$name.JOB.ark,$mfccdir/raw_mfcc_$name.JOB.scp \
|| exit 1;
其中cmd
为run.pl
,这是perl程序。在kaldi/egs/yesno/s5/utils/run.pl
打开这个文件,可以看到其用法描述如下:
@ARGV < 2 && die "usage: run.pl log-file command-line arguments...";
可以看到,command-line就是具体的执行命令。所以,extract-segments
,compute-mfcc-feats
和copy-feats
就是该SHELL/perl调用的最终命令。
以compute-mfcc-feats
为例进行分析。首先,可以肯定这不是linux自带的命令。用如下CMD搜索一下看它在什么位置,并查看其属性:
kaldi# find . -name compute-mfcc-feats
./src/featbin/compute-mfcc-feats
kaldi# ll -h src/featbin/compute-mfcc-feats
-rwxr-xr-x 1 root root 2.3M Apr 29 21:39 src/featbin/compute-mfcc-feats*
可以看到compute-mfcc-feats
就是可执行文件。如果这个文件是用kaldi的C++源码编译出来的,那它应该在Makefile里有描述。用如下命令搜出内容中含有compute-mfcc-feats
的所有文件:
kaldi# grep -rnw . -e "compute-mfcc-feats"
./src/featbin/.depend.mk:7225:compute-mfcc-feats.o: compute-mfcc-feats.cc /usr/include/stdc-predef.h \
./src/featbin/Makefile:10: compute-fbank-feats compute-kaldi-pitch-feats compute-mfcc-feats \
./src/featbin/compute-mfcc-feats.cc:1:// featbin/compute-mfcc-feats.cc
./src/featbin/compute-mfcc-feats.cc:31: "Usage: compute-mfcc-feats [options...] \n";
可见,在src/featbin/Makefile
中,确实写到了compute-mfcc-feats
。打开这个Makefile文件,可以看到,上文command-line中的三个命令,都是由这个Makefile描述来生成的:
BINFILES =
compute-mfcc-feats \
extract-segments \
copy-feats \
在这个Makefile所在的目录下,还能找到compute-mfcc-feats.cc
,这就是抽取MFCC特征的C++代码。可以看到compute-mfcc-feats
命令的参数解析过程,都是在改C++代码中实现的。
以kaldi中提取MFCC特征的功能为例,通过分析该过程源码(SHELL/perl/c++),发现了kaldi中SHELL调用C++源码的过程:C++编译为可执行文件,SHELL中调用这个可执行文件。
如果你想改动kaldi的某些过程(比如MFCC提取过程),需要修改其C++源码,而不是SHELL。
如果你想为kaldi添加新的功能,需要写好C++和makefile,生成可执行文件,再通过SHELL来调用这个可执行文件。