pjsip学习笔记2

在学习笔记1中
1)我们知道了声卡抽象层audiodev.c封装了各种声卡的操作,通过pjmedia_aud_stream_op接口函数族实现对声卡设备的操作
2)对于alsa声卡来说,alsa_dev.c对操作系统提供的alsa API进行了封装, 定义了pjmedia_aud_stream_op接口函数族供声卡抽象层audiodev.c调用
3)对于alsa声卡来说,当声卡抽象层audiodev.c调用start_steam启动声卡时,会创建一个PCM的抓取线程和一个PCM回放线程, 线程中分别调用回调ca_cb和pb_cb
4)回调函数是声卡抽象层audiodev.c的“上层”提供的, 在pjmedia体系中, 这个“上层”是叫做pjmedia_snd_port
5)pjmedia_snd_port是pjmedia_port抽象层的一个派生(sound_port.c), 专门用于与声卡抽象层audiodev.c建立绑定的
6)pjmedia_snd_port通过调用pjmedia_aud_stream_create,调用声卡抽象层audiodev的opnstream函数,把回调接口告诉alsa封装层的alsa_dev.c,并把自己作为回调参数userdata

---------------------------下面来研究一下这个pjmedia_port抽象层
参考:https://blog.csdn.net/gregcheng/article/details/5800564

pjmedia_port定义如下(来自源码pjmedia/port.h):
可以看到, 结构体中甚至对接口函数的调用者都作出了具体的规定,实际上, 这些调用入口就是pjmedia_por的抽象层port.c所提供接口函数
也就是说, pjmedia不希望应用层直接越过pjmedia_por的抽象层port.c去操作pjmedia_port的具体实现,而希望使用port.c所提供的统一接口去操作

typedef struct pjmedia_port
{
    pjmedia_port_info     info;            /**< Port information.  */

    /** Port data can be used by the port creator to attach arbitrary
    *  value to be associated with the port.
    */
    struct port_data {
    void        *pdata;            /**< Pointer data.        */
    long         ldata;            /**< Long data.        */
    } port_data;

    /**
    * Get clock source.
    * This should only be called by #pjmedia_port_get_clock_src().
    */
    pjmedia_clock_src* (*get_clock_src)(struct pjmedia_port *this_port,    pjmedia_dir dir);

    /**
    * Sink interface.
    * This should only be called by #pjmedia_port_put_frame().
    */
    pj_status_t (*put_frame)(struct pjmedia_port *this_port,  pjmedia_frame *frame);

    /**
    * Source interface.
    * This should only be called by #pjmedia_port_get_frame().
    */
    pj_status_t (*get_frame)(struct pjmedia_port *this_port,  pjmedia_frame *frame);

    /**
    * Called to destroy this port.
    */
    pj_status_t (*on_destroy)(struct pjmedia_port *this_port);

} pjmedia_port;


pjmedia系统中, 存在着各种各样的port, 分别用于与不同的媒体格式打交道, 例如:
sound_port.c        专门与声卡抽象层打交到
wave_player.c        专门处理文件媒体流播放
wave_wav_writer.c        专门处理文件媒体流录制
wav_playlist.c        专门处理文件列表媒体流播放

还有一些是用于媒体流数据转换的
resample_port.c         采样频率转换
echo_port.c            回声消除处理


下面重点关注一下与VOIP声卡接口的pjmedia_snd_port

一) pjmedia_snd_port (sound_port.c)
     关于 pjmedia_snd_port在voip媒体流中的位置,可以参考: https://trac.pjsip.org/repos/wiki/media-flow#IncomingRTPRTCPPackets
     图中,pjmedia_snd_port位于声卡抽象层和会议桥之间, pjmedia_snd_port本身还挂载了一个回声消除模块, 先看看pjmedia_snd_port定义:
    struct pjmedia_snd_port
    {
        int             rec_id;
        int             play_id;
        pj_uint32_t         aud_caps;            //
        pjmedia_aud_param     aud_param;
        pjmedia_aud_stream    *aud_stream;
        pjmedia_dir         dir;
        pjmedia_port    *port;                //pjmedia_port 统一接口

        pjmedia_clock_src    cap_clocksrc,
                         play_clocksrc;

        /*PCM采样相关, 这些参数最终会送到alsa声卡的驱动里面去*/
        unsigned         clock_rate;
        unsigned         channel_count;
        unsigned         samples_per_frame;
        unsigned         bits_per_sample;
        unsigned         options;

        unsigned         prm_ec_options; //回声消除选项

        /* 回声消除相关*/
        pjmedia_echo_state    *ec_state;
        unsigned         ec_options;
        unsigned         ec_tail_len;
        pj_bool_t         ec_suspended;
        unsigned         ec_suspend_count;
        unsigned         ec_suspend_limit;

        /* audio frame preview callbacks  
              这个实际上是提供给 pjmedia_snd_port的上层的接口
         也就是声卡抽象层audiodev.c的更上层的接口, 例如会议桥
         参考图: https://trac.pjsip.org/repos/wiki/media-flow#IncomingRTPRTCPPackets
        */
        void        *user_data;                //一般指向pjmedia_snd_port的上层对象
        pjmedia_aud_play_cb  on_play_frame;         //jmedia_snd_port的上层定义的播放函数   
        pjmedia_aud_rec_cb   on_rec_frame;        //jmedia_snd_port的上层定义的本地声音处理函数
    };

在sound_port.c中, 定义了alsa声卡封装层alsa_dev.c所需要的回调函数play_cb和rec_cb
回调函数play_cb和rec_cb被调用时, pjmedia_snd_port由提供了两个回调函数on_play_frame/on_rec_frame对帧数据进行预处理
play_cb调用pjmedia_port_get_frame获得数据,进行回声消除处理后,数据交给on_play_frame进一步处理,也就是数据由pjmedia_snd_port的上层对象提供
rec_cb把数据进行回声消除处理后,调用pjmedia_port_put_frame把数据on_rec_frame进一步处理, 也就是交给pjmedia_snd_port的上层对象来处理
//

2) pjmedia_snd_port的回声消除功能
   libsip提供的回声消除的例子aectest.c
    使用pjmedia_wav_player_port_create建立"两个"wav文件播放port
    使用pjmedia_wav_writer_port_create建立"一个"wav录制port
   pjmedia_echo_create2建立回声消除上下文
    第一个文件获取数据,送入回声消除port:  pjmedia_echo_playback(ec, (short*)play_frame.buf);
    第二个文件获取数据,并合并回声消除数据:  pjmedia_echo_capture(ec, (short*)rec_frame.buf, 0);
    获得的数据位于rec_frame.buf, 使用pjmedia_port_put_frame(wav_out, &rec_frame); 写入新的wav文件

    在pjmedia_snd_port中, 根据pjmedia初始参数: pjmedia_snd_port_create2->start_sound_device->pjmedia_snd_port_set_ec将启动软件ACE功能
    回调函数play_cb中进行消声处理(消声数据输出)后再执行on_play_frame进行放音
    回调函数rec_cb中本地发出的声音由pjmedia_echo_capture进行消声数据输入

    
3)    pjmedia_clock_src    cap_clocksrc和play_clocksrc;

    struct pjmedia_snd_port结构体中包含两个时钟源: cap_clocksrc和play_clocksrc
      回调函数play_cb每调用一次, 使用frame中的时间标签对play_clocksrc时钟源的时间标签进行一次更新,并记录本次更新时间
      回调函数cap_cb每调用一次,  使用frame中的时间标签对cap_clocksrc时钟源的时间标签进行一次更新,并记录本次更新时间
     这个时钟源有什么功能?音画同步用的?会议桥用?以后再研究吧!


补充: 在研究会议桥时, 发现会议桥创建时调用

             pjmedia_snd_port_connect( conf->snd_dev_port, conf->master_port);

     实现了对pjmedia_send_port中的pjmedia_port 接口对象赋值, 于是又看了看pjmedia_send_port代码,发现port成员并没有在pjmedia_send_port中被实现, 它就是一个接口指针。jmedia_send_port对象必须使用pjmedia_snd_port_connect函数给自己关联一个pjmedia_port对象实例,才能够打通声卡到应用层的声音数据通道。

    因此,pjmedia_send_port中只是“包含“了一个pjmedia_port对象, 但并没“实现“这个pjmedia_port对象



 

猜你喜欢

转载自blog.csdn.net/twd_1991/article/details/80567323