回顾一下sphinx4的整体架构:

092200435.gif


从上面我们看到,应用程序的输入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 的处理流程:

094310811.jpg


上面的那些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获取最终的数据。