回调函数的面向对象改造

熟悉C编程的朋友对回调函数一定不陌生,它一般用于一个模块将自己的阶段性输出传递给另一个模块,由另一个模块进行更细致的处理。

整个处理过程,就像一条河流,从源头出发,到了某个点分出若干支流,支流又分出若干支流,每一段都会被人所用


在面向对象系统里,我们怎么对这个基于回调函数的处理过程进行描述?

我想用一个概念来描述,叫信息流的上下游,信息流上的每个节点都有一个上游,若干个下游,上游可以向下游发送消息,消息的内容就是上游生产的数据

而面向对象系统中,A向B发送消息,其实就是调用B的方法,同时消息内容作为方法的参数传递给了B

这样我们就把回调函数转换成了方法调用

用代码举例

语音识别引擎ifly生产文本,然后通知ROS节点publisher来消费该文本,所以ifly是上游,pub是下游

class IFlyRecognizer{
private:
    char *recogSessionId;
    int audStat;
    int epStat;
    int recStat;
    bool lastSeg;
    std::string recogTxt;
    SpeechPublisher &publisher;
public:
    void stop();//开始为退出识别循环做准备
    int audioProc(const void *inputBuffer);
    IFlyRecognizer(SpeechPublisher &pub);
    ~IFlyRecognizer();
};
注意看,上游ifly的构造函数只有一个入参,那就是下游pub的引用,这就是OO中的组合模式compsition,ifly生产string类型的消息,pub消费string类型的消息,以此实现回调函数的效果

注册回调的地方,就是发生组合compsition的地方

IFlyRecognizer::IFlyRecognizer(SpeechPublisher &p):recogSessionId(NULL),audStat(MSP_AUDIO_SAMPLE_CONTINUE),epStat(MSP_EP_LOOKING_FOR_SPEECH),recStat(MSP_REC_STATUS_SUCCESS),lastSeg(false),recogTxt(""),publisher(p)
{
}

发生消息调用的地方

int IFlyRecognizer::audioProc( const void *inputBuffer)
{
    if (MSP_REC_STATUS_COMPLETE == recStat)
    {
        publisher.publishRecogTxt(recogTxt);
        recogTxt = "";
    }
}

回调函数绕不过去的情况怎么办

PortAudio是一个音频库,可以实现录音、播放等功能,简称PA,该库初始化时需要传入一个固定格式的回调函数,所以必须要定义一个回调函数,但我们可以将该函数做成一个wrapper,具体见下:

我将录音相关API封装成一个类。PA是上游,ifly是下游,PA生产音频数据,ifly消费音频数据,所以PA的构造函数也要传入一个引用参数——下游节点ifly

class PortAudioStream{
public:
    PortAudioStream(IFlyRecognizer &r);
    ~PortAudioStream();
private:
    IFlyRecognizer &recognizer;
    static int record_Callback( const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *userData );
};

不过因为PA规定了回调函数接口的格式,所以需要一个wrapper函数record_Callback来牵线搭桥,这里选择将record_Callback做成静态成员函数,是为了避免this指针扩展造成的类型不匹配错误

不过,静态成员函数无法访问非静态成员,所以构造函数传入的ifly对象不能访问,幸好PA有void指针类型userData参数,可以用该参数来传递ifly对象

PortAudioStream::PortAudioStream(IFlyRecognizer &r):recognizer(r)
{
    PaStreamParameters  inputParameters;
    PaError ret = paNoError;
    PaStream *stream = NULL;
    ret = Pa_OpenStream(
              &stream,
              &inputParameters,
              NULL,                  /* &outputParameters, */
              (double)AudioConfig::SAMPLE_RATE,
              AudioConfig::FRAMES_PER_BUF,
              paClipOff,      /* we won't output out of range samples so don't bother clipping them */
              record_Callback,
              (void *)&recognizer); //将recog对象地址传给静态类方法
}

发送消息(调用回调函数)是这样进行的

int PortAudioStream::record_Callback( const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *userData )
{
    int ret; 
    IFlyRecognizer *recog = (IFlyRecognizer *)userData;
    ret = recog->audioProc(inputBuffer);
}

总结

1、在main函数里先实例化最下游对象,然后依次用下游对象作为初始化参数来构造上游,直到信息流的源头

2、信息流源头生产出数据后,给注册好的下游发送消息,实现数据处理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值