jpsip学习笔记5

在学习笔记4中,我们研究了pjmedia_stream对象

pjmedia_stream对象的功能和接口在这张图上很清楚了

                                 https://trac.pjsip.org/repos/wiki/media-flow#IncomingRTPRTCPPackets

下面学习下传输层对象


一) 传输层对象的定义
    struct pjmedia_transport
    {
        /** Transport name (for logging purpose). */
        char             name[PJ_MAX_OBJ_NAME];

        /** Transport type. */
        pjmedia_transport_type   type;

        /** Transport's "virtual" function table. */
        pjmedia_transport_op    *op;

        /** Application/user data */
        void            *user_data;
    };

    注意到: "virtual" function, 很明显, pjmedia_transport必须实现标准接口函数族
              pjmedia_transport 就像声音设备抽象层 pjmedia_aud_stream或者pjmedia_port一样, 是一个抽象层

        在pjsip源代码的pjmedia核心代码, 我们找到了以下pjmedia_transport的实现:
        transport_adapter_sample.c
        transport_ice.c
        transport_loop.c
        transport_srtp_dtls.c          注意这个派生“类”均把pjmedia_transport base成员作为第一个成员
        transport_srtp_sdes.c          目的是通过对pjmedia_transport*指针强制转换,可以获得“派生”对象的指针
        transport_srtp.c           例如: transport_udp* tp_udp = (transport_udp*)pbase;
        transport_udp.c                这个转换通常是用在pjmedia_transport_op函数族的实现代码中


        以transport_udp为例, 派生对象的定义如下, 机构体内不出所料,包含了网络传输所需要的网络地址以及套接字相关的定义:
        struct transport_udp
        {
            pjmedia_transport    base;        /**< Base transport.            */

            pj_pool_t           *pool;        /**< Memory pool            */
            unsigned        options;    /**< Transport options.            */
            unsigned        media_options;    /**< Transport media options.        */
            void           *user_data;    /**< Only valid when attached        */
            //pj_bool_t        attached;    /**< Has attachment?            */
            pj_sockaddr        rem_rtp_addr;    /**< Remote RTP address            */
            unsigned        rem_rtp_cnt;    /**< How many pkt from this addr.   */
            pj_sockaddr        rem_rtcp_addr;    /**< Remote RTCP address        */
            int            addr_len;    /**< Length of addresses.        */
            void  (*rtp_cb)(    void*,        /**< To report incoming RTP.        */
                    void*,
                    pj_ssize_t);
            void  (*rtcp_cb)(    void*,        /**< To report incoming RTCP.        */
                    void*,
                    pj_ssize_t);

            unsigned        tx_drop_pct;    /**< Percent of tx pkts to drop.    */
            unsigned        rx_drop_pct;    /**< Percent of rx pkts to drop.    */

            pj_sock_t          rtp_sock;    /**< RTP socket                */
            pj_sockaddr        rtp_addr_name;    /**< Published RTP address.        */
            pj_ioqueue_key_t   *rtp_key;    /**< RTP socket key in ioqueue        */
            pj_ioqueue_op_key_t    rtp_read_op;    /**< Pending read operation        */
            unsigned        rtp_write_op_id;/**< Next write_op to use        */
            pending_write        rtp_pending_write[MAX_PENDING];  /**< Pending write */
            pj_sockaddr        rtp_src_addr;    /**< Actual packet src addr.        */
            unsigned        rtp_src_cnt;    /**< How many pkt from this addr.   */
            int            rtp_addrlen;    /**< Address length.            */
            char        rtp_pkt[RTP_LEN];/**< Incoming RTP packet buffer    */

            pj_sock_t        rtcp_sock;    /**< RTCP socket            */
            pj_sockaddr        rtcp_addr_name;    /**< Published RTCP address.        */
            pj_sockaddr        rtcp_src_addr;    /**< Actual source RTCP address.    */
            unsigned        rtcp_src_cnt;    /**< How many pkt from this addr.   */
            int            rtcp_addr_len;    /**< Length of RTCP src address.    */

            pj_ioqueue_key_t   *rtcp_key;    /**< RTCP socket key in ioqueue        */
            pj_ioqueue_op_key_t rtcp_read_op;    /**< Pending read operation        */
            pj_ioqueue_op_key_t rtcp_write_op;    /**< Pending write operation        */
            char        rtcp_pkt[RTCP_LEN];/**< Incoming RTCP packet buffer */
        };
       
二) transport_udp 传输层代码分析:
     类比声卡抽象层, transport_udp的pjmedia_transport抽象接口实现一定包含了网络套接子的启动和释放代码, 网络数据收发代码  
     我们来看看 transport_udp中pjmedia_transport_udp_create3()函数中有关对pjmedia_transport抽象接口函数的实现代码
     1) 建立RTCP和RTP数据收发套接子, 然后调用pjmedia_transport_udp_attach进一步初始化(以下步骤发生在pjmedia_transport_udp_attach中)
     2) 使用pjmedia_endpt_get_ioqueue(endpt)获得一个ioqueue , 供pj_ioqueue_poll()进行套接子列表轮询用
   3) jmedia_transport抽象接口函数族实现如下:
    static pjmedia_transport_op transport_udp_op = {
                                    &transport_get_info,
                                    &transport_attach,
                                    &transport_detach,
                                    &transport_send_rtp,
                                    &transport_send_rtcp,
                                    &transport_send_rtcp2,
                                    &transport_media_create,
                                    &transport_encode_sdp,
                                    &transport_media_start,
                                    &transport_media_stop,
                                    &transport_simulate_lost,
                                    &transport_destroy
                                };
     4)修正下RTP/RTCP套接子所使用的地址(对方需要一个确定的地址, 而不像本地端口绑定可以使用0.0.0.0这个地址)
     5)使用pj_ioqueue_register_sock函数把RTP/RTCP套接子注册到到轮询对列ioqueue中, 同时注册了相应的回调接口,并返回一个唯一的pj_ioqueue_key_t作为回调函数入口参数识别socket数据来源
     6)在pj_ioqueue中,套接子回调接口实际上是一个函数族, 定义如下:
        typedef struct pj_ioqueue_callback
        {
            void (*on_read_complete)(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key,     pj_ssize_t bytes_read);

            void (*on_write_complete)(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key,    pj_ssize_t bytes_sent);

            void (*on_accept_complete)(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key,   pj_sock_t sock, pj_status_t status);

            void (*on_connect_complete)(pj_ioqueue_key_t *key,     pj_status_t status);

        } pj_ioqueue_callback;

         我们果然在函数入口参数中其中找到了2)中提到的pj_ioqueue_key_t
         在transport_udp, 函数族中,只实现了on_read_complete, 也就是针对RTP的on_rx_rtp() 和 针对RTCP的on_rx_rtcp()

        *** 至于套接子数据的发送, 则由jmedia_transport抽象接口函数的以下三个函数实现
        send_rtp,
        send_rtcp,
        send_rtcp2,

    为了不破坏封装,pjmedia建议使用pjmedia_transport_send_xxx()系列函数,实现对各种接口在发送数据的形式上实现统一

        在笔记4中,我们曾经提到过:
              pjmedia_transport层发送的数据, 则是由pjmedia_stream主动调用以下两个函数实现的
              pjmedia_transport_send_rtp   调用是这样滴: put_frame->put_frame_imp->pjmedia_transport_send_rtp

 至此,我们基本上算搞清楚了声音是如何从alsa声卡到会议桥再到传输层的整个过程。

       遗留问题:pj_ioqueue_poll()进行套接子列表轮询是在哪里调用的呢?在编码器框架中会讲到该问题

 https://trac.pjsip.org/repos/wiki/media-flow#IncomingRTPRTCPPackets中由提到:

The flow of incoming RTP packets are like this:

  • an internal worker thread in the Media Endpoint polls the IOQueue where all media transports are registered to.
  • when an incoming packet arrives, the poll function will trigger on_rx_rtp() callback of the UDP media transport to be called. This callback was previously registered by the UDP media transport to the ioqueue.
  • the on_rx_rtp() callback reports the incoming RTP packet to the media stream port. The media stream was attached to the UDP media transport during session initialization by application.
  • the media stream unpacks the RTP packet using its internal RTP session, update RX statistics, de-frame the payload according to the codec being used (there can be multiple audio frames in a single RTP packet), and put the frames into the jitter buffer.
  • the processing of incoming packets stops here, as the frames in the jitter buffer will be picked up by the main flow (a call to pjmedia_port_get_frame() to the media stream.



猜你喜欢

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