回顾一下sphinx4的整体架构:
从上面我们看到,应用程序的输入Input(一般是录音数据),首先经过前端(FrontEnd)处理。前端处理有一序列的步骤,最后会得到声音对应的特征值,也就是所谓的Feature。然后将得到的feature传给解码器Decoder中的Scorer模块进行处理。今天我们从整体上分析一下FrontEnd的处理流程。
我们还是以HelloWorld这个为例,先来看一下它的配置文件:
<component name="threadedScorer"
type="edu.cmu.sphinx.decoder.scorer.ThreadedAcousticScorer">
<propertyname="frontend"value="${frontend}"/>
</component>
threadedScorer有FrontEnd这个属性,在看一下FrontEnd的配置:
<component name="frontEnd" type="edu.cmu.sphinx.frontend.FrontEnd">
<propertylist name="pipeline">
<item>microphone </item>
<item>preemphasizer </item>
<item>windower </item>
<item>fft </item>
<item>melFilterBank </item>
<item>dct </item>
<item>liveCMN </item>
<item>featureExtraction </item>
</propertylist>
</component>
从配置文件可以看到,FrontEnd有一个属性列表,列表中的每一个item都是前段处理的一个模块。熟悉设计模式的应该知道,这里显然用到了责任链设计模式。首先从microphone开始,录音数据处理完后再沿着这条管道线交给下一个处理,最后到达featureExtraction,进行最后的特征提取,提取完特征再传递给Scorer处理。
下面这个是 FrontEnd 的处理流程:
上面的那些item对应的java类,实际上都是一个DataProcessor,即它们都继承了BaseDataProcessor这个类,DataProcessor有一个方法getData(),可以得到DataProcessor处理完后的数据。
我们再来看一下FrontEnd的相关定义。在FrontEnd定义了一个DataProcessor的List,用来存放上面配置文件中的那些item对应的实例对象,还定义了两个DataProcessor,first表示第一个DataProcessor,last表示下一个DataProcessor:
private List<DataProcessor> frontEndList;
private DataProcessor first;
private DataProcessor last;
而得到这些示例对象是通过它的newProperties回调方法,有关解析和属性相关的内容前面已经介绍过了。
@Override
public void newProperties(PropertySheet ps) throws PropertyException {
super.newProperties(ps);
frontEndList = ps.getComponentList(PROP_PIPELINE, DataProcessor.class);
init();
}
获取完属性列表后,接着调用了init方法:
private void init() {
last = null;
for (DataProcessor dp : frontEndList) {
assert dp != null;
if (last != null)
dp.setPredecessor(last);
if (first == null) {
first = dp;
}
last = dp;
}
initialize();
}
这个方法的作用就是遍历整个DataProcessor List,按顺序依次设置下一个DataProcessor,这样整个List就串起来了,接着又调用了initialize方法:
@Override
public void initialize() {
super.initialize();
for (DataProcessor dp : frontEndList) {
dp.initialize();
}
}
initialize这个是接口DataProcessor定义的一个抽象方法,每个子类必须实现自己的版本。这个方法遍历了所有的DataProcessor,然后调用它们自己的initialize,完成自己的初始化工作。
FontEnd还有一个 getData方法,用来获取整个流程处理完后最终的数据:
@Override
public Data getData() throws DataProcessingException {
Data data = last.getData();
// fire the signal listeners if its a signal
if (data instanceof Signal) {
fireSignalListeners((Signal) data);
}
return data;
}
以上就是FrontEnd的大致的处理流程,本质上就是使用了责任链设计模式,将所有需要对语音进行处理的DataProcessor串成一个链条,然后沿着这个链条逐一处理,处理完后通过getData获取最终的数据。