pjsip学习笔记3

在学习笔记2中
1)我们知道了声卡抽象层audiodev.c的上层是pmedia_snd_port层
2)pmedia_snd_port层提供pmedia_port抽象接口所需要的函数指针: get_frame,put_frame,on_destroy,get_clock_src
3)pmedia_snd_port层还提供了与pmedia_snd_port层自己的上一层pjmedia对象的音频数据交换接口: on_play_frame和on_rec_frame
4)pmedia_snd_port层还提供了相应的API来帮助上层应用创建和销毁pmedia_snd_port对象
    pjmedia_snd_port_create_rec
    pjmedia_snd_port_create_player
    pjmedia_snd_port_create               ---老式的API
    pjmedia_snd_port_create2          ---推荐使用的新式API
    pjmedia_snd_port_destroy
    pjmedia_snd_port_get_snd_stream

    pjmedia_snd_port_get_ec_tail          ---回声消除反射相关

    pjmedia_snd_port_set_ec              ---配置回声消除     关于回声消除,推荐参考: https://blog.csdn.net/yuanchunsi/article/details/52315108

                                                   --- 顺便抄两个概念过来备忘:   delay时间就是speaker播出时间到mic录到时间的差(这个delay肯定越小越好,保证在100ms以内)
                              --- tail length最好是房间内反射时间的1/3, 反射时延为与环境相关(例如300ms),tail length的设定是一个关键
    pjmedia_snd_port_get_clock_src
    pjmedia_snd_port_connect         --连接到其他pjmedia_port, 当play_cb时,需要使用pjmedia_port_get_frame从这个port(例如file_read_port或会议桥)获取数据
    pjmedia_snd_port_get_port
    pjmedia_snd_port_disconnect
    

搜索源码, 查找pjmedia_snd_port被创建的地方:
          在会议桥代码中(conference.c)会创建pjmedia_snd_port:
          pjmedia_conf_create->create_sound_port->pjmedia_snd_port_create


      在pjusa层, 有几个地方会创建pmedia_snd_port:
       1) pjsua_call_make_call->pjsua_set_snd_dev->pjsua_set_snd_dev2->open_snd_dev->pjmedia_snd_port_create2
     2) pjsua_conf_connect->pjsua_set_snd_dev->pjsua_set_snd_dev2->open_snd_dev->pjmedia_snd_port_create2
     3) pjsua_snd_get_setting->pjsua_set_snd_dev->pjsua_set_snd_dev2->open_snd_dev->pjmedia_snd_port_create2
      

一)   关于会议桥-这里有篇文章:https://blog.csdn.net/lincaig/article/details/79633771
      会议桥的定义:  struct pjmedia_conf
            {
                unsigned          options;    /**< Bitmask options.            */
                unsigned          max_ports;    /**< Maximum ports.            */
                unsigned          port_cnt;    /**< Current number of ports.        */
                unsigned          connect_cnt;    /**< Total number of connections    */
                pjmedia_snd_port     *snd_dev_port;    /**< Sound device port.            */
                pjmedia_port          *master_port;    /**< Port zero's port.            */
                char              master_name_buf[80]; /**< Port0 name buffer.        */
                pj_mutex_t         *mutex;    /**< Conference mutex.            */
                struct conf_port    **ports;    /**< Array of ports.            */
                unsigned          clock_rate;    /**< Sampling rate.            */
                unsigned          channel_count;/**< Number of channels (1=mono).   */
                unsigned          samples_per_frame;    /**< Samples per frame.        */
                unsigned          bits_per_sample;    /**< Bits per sample.        */
            };       

       可以看到,会议桥也实现了一个叫做master_port的pjmedia_port抽象接口
       同时:    会议桥还包含了一个snd_dev_port
       另外:    会议桥还包含了若干个conf_port, 我们看看conf_port结构体定义:struct conf_port
                                            {
                                                pj_str_t         name;        /**< Port name.                */
                                                pjmedia_port    *port;        /**< get_frame() and put_frame()    */
                                                  
                                                 ...
                                            }
        每个conf_port都包含了一个pjmedia_port接口对象  


       *** 为什么说会议桥也实现(而不是说包含)了一个叫做master_port的pjmedia_port抽象接口, 我们看看pjmedia_conf_create的代码片段就明白了    

        conf->master_port = PJ_POOL_ZALLOC_T(pool, pjmedia_port);  //master_port由会议桥代码分配并初始化
        PJ_ASSERT_RETURN(conf->master_port, PJ_ENOMEM);
        
        pjmedia_port_info_init(&conf->master_port->info, &name, SIGNATURE,
                   clock_rate, channel_count, bits_per_sample,  samples_per_frame);

        conf->master_port->port_data.pdata = conf;
        conf->master_port->port_data.ldata = 0;

        conf->master_port->get_frame = &get_frame;        //get_frame由会议桥代码实现
        conf->master_port->put_frame = &put_frame;        //put_frame由会议桥代码实现
        conf->master_port->on_destroy = &destroy_port;

        /* Create port zero for sound device. */
        status = create_sound_port(pool, conf);        

             ....

        //注意下面这句, 这句的作用是让snd_dev_port->port = master_port
       pjmedia_snd_port_connect( conf->snd_dev_port, conf->master_port);

            //也就是说, 当会议桥创建后, 通过snd_dev_port, 建立起声卡设备与会议桥的联系
        //alsa封装层线程中回调snd_dev_port提供的play_cb和rec_cb
        //play_cb和rec_cb中再调用pjmedia_port接口的get_frame和put_frame实现了声卡数据与会议桥之间的数据传输   

         *** 会议桥的conf->ports[0]与master_port的关系:

            会议桥的conf->ports[0]是一个passive port(被动port), 代码:
            create_sound_port()->create_pasv_port()->pjmedia_delay_buf_create();//创建了一个被分配了delay_buf缓冲区的conf_port

            根据create_sound_port()代码, 会议桥也可以不去创建一个pjmedia_snd_port(当options标志包含PJMEDIA_CONF_NO_DEVICE时), 这时会议桥应该是纯粹的混音器了 。

             ---我们注意到pjmedia_conf_create的代码中
            conf->master_port->port_data.pdata = conf;
            conf->master_port->port_data.ldata = 0;  
             ---再看master_port的put_frame实现代码中
            pjmedia_conf *conf = (pjmedia_conf*) this_port->port_data.pdata;
            struct conf_port *port = conf->ports[this_port->port_data.ldata];
             这足以证明conf->ports[0]就是为master_port保留的
             ---我们注意到create_sound_port()代码create_pasv_port()的入口参数中, pjmedia_port指针参数为空

        ***会议桥中的passive port
           会议桥的conf->ports[0]是一个passive_port, 被master_port使用
           会议桥还可以通过pjmedia_conf_add_passive_port()添加更多的passive_port类型的pjmedia实例, 代码如下:
            port = PJ_POOL_ZALLOC_T(pool, pjmedia_port);
            PJ_ASSERT_RETURN(port, PJ_ENOMEM);
            
            pjmedia_port_info_init(&port->info, name, SIGNATURE_PORT,
                       clock_rate, channel_count, bits_per_sample,
                       samples_per_frame);

            port->port_data.pdata = conf;     //所有passive port类型的pjmedia_port的port_data都会指向对应的会议桥实例。
            port->port_data.ldata = index;    //以及它自己在议桥实例的conf->ports数组成员中的数组索引

            port->get_frame = &get_frame_pasv;
            port->put_frame = &put_frame;
            port->on_destroy = &destroy_port_pasv;

           是不是有点眼熟, 这个master_port的创建很像, 区别在于get_frame和on_destroy的实现函数是不同的
           从声卡的角度看来,这些passive_port不提供下行的音频数据,get_frame是个空函数, 它通过put_frame获得上行的音频数据。

           总结: 会议桥中的passive类型的port都是在会议桥内部创建的, 不过搜索了下libpjsip源码, 没有发现使用pjmedia_conf_add_passive_port的例子。
         conf->ports[0]->port = NULL, 对应会议桥conf的master_port

    
       ***会议桥中外部pjmedia_port
        pjmedia_conf_add_port, 这个函数的目的是: Add stream port to the conference bridge
            从pjmedia_conf_add_port函数的实现来看,入口参数strm_port必须是已经存在的pjmedia_port实例
            源码提供了一个通过会议桥混音的例子confbench.c

             pjmedia_conf_remove_port则从会议桥中移除一个port

       **** 会议桥结构体中的三个成员
        unsigned          max_ports;    /**< Maximum ports.            */
        unsigned          port_cnt;    /**< Current number of ports.        */
        unsigned          connect_cnt;    /**< Total number of connections    */
            
          1)max_ports 是在会议桥创建时就确定了, 它决定了ports[] 数组的大小, 其中ports[]分配给master_port, 因此给外部使用的还剩下max_ports-1个
          2)port_cnt  是ports[] 数组的有效conf_port的数量,matser_port占一个
                       pjmedia_conf_add_passive_port()和pjmedia_conf_add_port()都会导致数量增加
                       pjmedia_conf_remove_port()都会导致数量减少
          3)connect_cnt    pjmedia_conf_connect_port()调用(可能)会导致connect_cnt++
                            pjmedia_conf_disconnect_port 和 pjmedia_conf_remove_port调用(可能)会导致connect_cnt--

        》》会议桥外部port之间是可以通过pjmedia_conf_connect_port()实现相互连接的
               pjmedia_conf_connect_port入口参数指定了源端口和目标端口在会议桥ports[]数组中的索引
                    每一个源端口(会议端口conf_port)可以由1个或以上的监听端口(会议端口conf_port) ---conf_port[] ->listener_cnt
                    每一个监听端口(会议端口conf_port)可以由1个或以上的源端口(会议端口conf_port) ---conf_port[] ->transmitter_cnt
            当pjmedia_conf_connect_port被调用时,如果连接不存在,则connect_cnt数量增加
            同时:
               源端口(会议端口conf_port)的listener_cnt++, 同时listener_slots[]数组中添加监听端口的在conf->ports[]中的索引值(代码叫sink_slot)
               监听端口(会议端口conf_port)的transmitter_cnt++
    
      **** 会议桥端口结构体conf_port中的三个成员
        unsigned         listener_cnt;    //conf_port作为源端口时, listener_slots[]数组的有效元素数量
        SLOT_TYPE        *listener_slots;  //listener_slots数组大小被设置为会议桥的max_ports, 确保了不会发生访问越界
        unsigned         transmitter_cnt; //conf_port作为监听端口时, 记录所监听的源端口的数量
                                //transmitter_cnt>1时, 会议桥将进行混音, 混音结果放在监听端口的mix_buf[]中

              混音的过程可以参考: https://blog.csdn.net/lincaig/article/details/79633771
              如何实现混音时,各源端口的数据不缺失, 这个应该就是stream对象的jitter缓冲的功劳了
              另外get_frame的调用应该与时间待播放数据的时间标签进行同步, 防止提前太多时间(超过jitter缓冲时间), 混音时可能造成某路数据缺失

猜你喜欢

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