pjsip学习笔记7

     研究pjmedia_stream模块的时候, 我们讲到了编解码器, pjmedia_codec_encode()内部调用编解码器的抽象层接口实现PCM数据的编解码。

我们先看看编解码器实例是如何生成的, 参考函数pjmedia_stream_create():
    stream->codec_mgr = pjmedia_endpt_get_codec_mgr(endpt);
    pjmedia_codec_mgr_alloc_codec( stream->codec_mgr, &info->fmt, &stream->codec);

前一行应该是获得一个编码器管理器stream->codec_mgr
后一行应该是根据编码器信息参数,从码器管理器中生成或获得与之对应的编码器实例stream->codec
那么这个endpt是个什么东西呢?
从函数名字pjmedia_endpt_get_codec_mgr字面上看,编码器管理器codec_mgr应该是endpt的一个内部对象才是.
我们看看endpt对象的结构体定义:
        struct pjmedia_endpt{
           
            pj_pool_t         *pool;         /** Pool. */
            
            pj_pool_factory     *pf;            /** Pool factory. */
           
            pjmedia_codec_mgr      codec_mgr;     /** Codec manager. */
           
            pj_ioqueue_t      *ioqueue;             /** IOqueue instance. */
            
            pj_bool_t          own_ioqueue;    /** Do we own the ioqueue? */
          
            unsigned          thread_cnt;          /** Number of threads. */
           
            pj_thread_t         *thread[MAX_THREADS];     /** IOqueue polling thread, if any. */
            
            pj_bool_t          quit_flag;        /** To signal polling thread to quit. */
           
            pj_bool_t          has_telephone_event;     /** Is telephone-event enable */
           
            exit_cb          exit_cb_list;             /** List of exit callback. */
        };

果然,pjmedia_endpt中包含了一个Codec manager实例

我们还看到了在研究pjmedia_stream模块时,提到的ioqueue对象

我们据从这个pjmedia_endpt入手,来研究一下pjsip 编码器框架

一) pjsip 编码器框架pjmedia_endpt:  
   http://www.pjsip.org/docs/latest-2/pjmedia/docs/html/group__PJMEDIA__CODEC.htm有如下的信息:

    Application can instruct ​the Media Endpoint to instantiate an internal IOQueue and start one or some worker thread(s) to poll this IOQueue. This probably is the recommended strategy so that polling to media sockets is done by separate thread (and this is the default settings in ​PJSUA-API).
    Alternatively, application can use a single IOQueue for both SIP and media sockets (well basically for all network sockets in the application), and poll the whole thing from a single thread, possibly the main thread. To use this, application will specify the IOQueue instance to be used when creating the media endpoint and disable worker thread.
     This strategy will probably work best on a small-footprint devices to reduce number of threads in the system.

     大意应该是pjmedia_endpt实例内部有个IOQueue队列,并会启动一个工作线程对IOQueue进行轮询(poll),也就是对media sockets使用该独立线程进行轮询, 嵌入式系统中为了节省线程资源, 可以与SIP信令共享一个IOQueue队列(结构成员own_ioqueue, 应该就是用来标识媒体层是否与sip信令层共享ioqueue队列的吧?)。

    看看pjmedia_endpt_create()函数的实现代码:
    1) pjmedia_aud_subsys_init(pf);                        //初始化声音设备子系统
    2) pjmedia_endpt_create2(pf, ioqueue, worker_cnt, p_endpt);    //创建pjmedia_endpt模块实例
    

    再看pjmedia_endpt_create2()函数的实现代码, 摘录关键代码如下:
        。。。
       endpt->ioqueue = ioqueue;
       endpt->thread_cnt = worker_cnt;
        。。。
       pjmedia_codec_mgr_init(&endpt->codec_mgr, endpt->pf);        //初始化编码器管理器
        。。。
          if (endpt->ioqueue == NULL) {                        //这个关于使用外部ioqueue的描述与上面摘录的英文描述是一致的
            endpt->own_ioqueue = PJ_TRUE;
            status = pj_ioqueue_create( endpt->pool, PJ_IOQUEUE_MAX_HANDLES,&endpt->ioqueue);
            if (status != PJ_SUCCESS)
                    goto on_error;

        if (worker_cnt == 0) {
                PJ_LOG(4,(THIS_FILE, "Warning: no worker thread is created in media endpoint for internal ioqueue"));
        }
           }
   
       for (i=0; i<worker_cnt; ++i) {                        //建立工作线程池,用于ioqueue的轮询
        status = pj_thread_create( endpt->pool, "media", &worker_proc,  endpt, 0, 0, &endpt->thread[i]);
        if (status != PJ_SUCCESS)
            goto on_error;
       }     

    可见,  pjmedia_endpt主要作用如下:
    1) 初始化声音设备子系统  
    2) 初始化编解码器管理器  
    3) 创建ioqueue轮询工作线程  
     

二) pjsip 编解码器管理器pjmedia_codec_mgr:  
    根据前面研究声音设备是的经验,pjmedia_codec_mgr应该是一个用于注册编码器工厂的模块
    编解码器应该同声音设备一样, 通过工厂接口进行实例创建, 通过编解码器抽象接口执行编解码操作, 下面来证实一下。

    在pjmedia/codec.c中我们果然找到了函数:
    pjmedia_codec_mgr_register_factory()
    pjmedia_codec_mgr_unregister_factory()

    与声音设备的工厂接口是硬编码静态注册不同, 编解码器在初始化函数中通过调用pjmedia_codec_mgr_register_factory()进行动态注册
    以g711编码器的注册为例:
            PJ_DEF(pj_status_t) pjmedia_codec_g711_init(pjmedia_endpt *endpt)
            {
                pjmedia_codec_mgr *codec_mgr;
                pj_status_t status;

                if (g711_factory.endpt != NULL) {
                /* Already initialized. */
                return PJ_SUCCESS;
                }

                /* Init factory */
                g711_factory.base.op = &g711_factory_op;
                g711_factory.base.factory_data = NULL;
                g711_factory.endpt = endpt;

                pj_list_init(&g711_factory.codec_list);

                /* Create pool */
                g711_factory.pool = pjmedia_endpt_create_pool(endpt, "g711", 4000, 4000);
                if (!g711_factory.pool)
                return PJ_ENOMEM;

                /* Create mutex. */
                status = pj_mutex_create_simple(g711_factory.pool, "g611", &g711_factory.mutex);
                if (status != PJ_SUCCESS)
                goto on_error;

                /* Get the codec manager. */
                codec_mgr = pjmedia_endpt_get_codec_mgr(endpt);
                if (!codec_mgr) {
                return PJ_EINVALIDOP;
                }

                /* Register codec factory to endpoint. */
                status = pjmedia_codec_mgr_register_factory(codec_mgr, &g711_factory.base); /////在这里注册g711编码器
                if (status != PJ_SUCCESS)
                return status;


                return PJ_SUCCESS;

            on_error:
                if (g711_factory.mutex) {
                pj_mutex_destroy(g711_factory.mutex);
                g711_factory.mutex = NULL;
                }
                if (g711_factory.pool) {
                pj_pool_release(g711_factory.pool);
                g711_factory.pool = NULL;
                }
                return status;
            }

   
    看看编码器的定义
        struct pjmedia_codec
        {
            /** Entries to put this codec instance in codec factory's list. */
            PJ_DECL_LIST_MEMBER(struct pjmedia_codec);

            /** Codec's private data. */
            void            *codec_data;

            /** Codec factory where this codec was allocated. */
            pjmedia_codec_factory   *factory;

            /** Operations to codec. */
            pjmedia_codec_op        *op;
        };
            
    G711编码器的编解码操作接口实现:
        static pjmedia_codec_op g711_op =
        {
            &g711_init,
            &g711_open,
            &g711_close,
            &g711_modify,
            &g711_parse,
            &g711_encode,
            &g711_decode,
        #if !PLC_DISABLED
            &g711_recover
        #else
            NULL
        #endif
        };

    G711编码器的实例工厂操作接口实现:
        static pjmedia_codec_factory_op g711_factory_op =
        {
            &g711_test_alloc,
            &g711_default_attr,
            &g711_enum_codecs,
            &g711_alloc_codec,
            &g711_dealloc_codec,
            &pjmedia_codec_g711_deinit
        };

    
    应用初始化时调用,pjmedia_codec_g711_init(pjmedia_endpt *endpt), 调用pjmedia_codec_mgr_register_factory把G711编码器的实例工厂操作接口告诉编码器管理器
在需要使用编码器的地方,就通过编码器类型锁定对应的编码器工厂, 就可以通过工厂接口alloc_codec获得编码器实例。

       具体的编解码操作实现, 则通过编码器实例调用编解码器统一抽象层接口函数族来完成,include/pjmedia/codec.h中, 使用下列函数对抽象层接口函数进行了封装, 这里只列出其中两个:
             pjmedia_codec_encode(pjmedia_codec *codec, const struct pjmedia_frame *input,unsigned out_size, struct pjmedia_frame *output)
        pjmedia_codec_decode(pjmedia_codec *codec,  const struct pjmedia_frame *input,unsigned out_size, struct pjmedia_frame *output )

猜你喜欢

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