kaldi中SHELL调用C++程序过程源码分析

引入

kaldi真正的核心源码,都是C++写成的,这个结论可以从如下两点得以确认:

(1)在kaldi的源码kaldi/src目录下,能看到很多扩展名为.cc的源程序,这是linux下C++源码;

(2)在源码中,比如kaldi\src\featbin\compute-mfcc-feats.cc,可以看到static_caststd::string这样的代码,这是C++特有的语法。

但是我们是怎么使用kaldi的呢,是直接调用这些C++程序?

有过kaldi使用经验的人都知道,kaldi的使用,并不是直接写C++/python程序来调用C++源码/动态链接库。而是通过写SHELL程序,来使用kaldi提供的各项功能(比如提取音频的MFCC特征)。

那问题就来了,我们写的SHELL程序,是怎么调用这些C++程序的呢? 下面我们就以kaldi中的经典入门例子kaldi/egs/yesno/s5为例,分析其中的关键源码,从而回答这个问题。

yesno例程源码分析

首先,我们进入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;

其中cmdrun.pl,这是perl程序。在kaldi/egs/yesno/s5/utils/run.pl打开这个文件,可以看到其用法描述如下:

@ARGV < 2 && die "usage: run.pl log-file command-line arguments...";

可以看到,command-line就是具体的执行命令。所以,extract-segmentscompute-mfcc-featscopy-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来调用这个可执行文件。

你可能感兴趣的:(Machine,Learning,源码分析)