1. 程式人生 > >pjsip學習筆記1 -- 音訊裝置抽象

pjsip學習筆記1 -- 音訊裝置抽象


目前最關心聲音的採集與回放,所以第一篇從PJSIP的PJMedia音效卡驅動流層開始研究:

API的使用參考: http://www.pjsip.org/docs/latest/pjmedia/docs/html/group__audio__device__api.htm

資料流:               https://trac.pjsip.org/repos/wiki/media-flow

有問題,看這裡: https://trac.pjsip.org/repos/wiki/FAQ


注意pjsip原始碼裡面有兩個audiodev.c檔案, 但路徑不同,如下:
pjmedia/src/pjmedia/audiodev.c             -----提供對聲音裝置本身的操作封裝,重點在對具體裝置的操作或管理    
pjmedia/src/pjmedia-audiodev/audiodev.c        -----提供與聲音裝置子系統初始化和裝置工廠的註冊管理操作

一開始沒注意, 看程式碼看糊塗了, 裝置子系統工廠管理操作包括:
    pjmedia_aud_register_factory
    pjmedia_aud_unregister_factory

    pjmedia_aud_subsys_init        
    pjmedia_aud_subsys_shutdown

pisip在pjmedia_aud_subsys_init()中採取了硬編碼的方式進行了一些聲音裝置的靜態註冊,pjmedia_aud_register_factory並未在例子程式碼中被使用
例如alsa裝置工廠註冊程式碼:aud_subsys->drv[aud_subsys->drv_cnt].create = &pjmedia_alsa_factory;
                      然後:pjmedia_aud_driver_init(aud_subsys->drv_cnt) 中使用該create,進一步填充裝置資訊(裝置名,該裝置的工廠操作介面初始化)

1) 應用層通過以下函式遍歷註冊的聲音裝置,根據裝置名,獲得裝置在系統中的唯一ID
     pjmedia_aud_dev_lookup( const char *drv_name, const char *dev_name, pjmedia_aud_dev_index *id)

2) 應用層通過呼叫工廠介面建立/銷燬一個裝置例項, 這些函式是對pjmedia_aud_dev_factory_op介面的封裝
    pjmedia_aud_stream_create
    pjmedia_aud_stream_destory

3) 應用層通過以下函式呼叫裝置操作介面,進行裝置控制(啟動、停止、配置讀寫), 這些函式是對pjmedia_aud_stream_op介面呼叫的封裝
    pjmedia_aud_stream_start
    pjmedia_aud_stream_stop
    pjmedia_aud_stream_get_cap
    pjmedia_aud_stream_set_cap
    pjmedia_aud_stream_get_cap
      
***pjsip庫提供了一個auddemo.c程式, 演示了使用pjmedia_aud_subsys_init註冊所有聲音裝置後,使用控制檯命令對音效卡進行操作的過程

***下面講述以下alsa音效卡的alsa_stream裝置實現

    1) pjmedia_aud_stream_create()函式呼叫工廠介面函式create_stream建立一個alsa_stream裝置例項
     介面函式原型: pj_status_t (*create_stream)(pjmedia_aud_dev_factory *f,
                 const pjmedia_aud_param *param,
                 pjmedia_aud_rec_cb rec_cb,
                 pjmedia_aud_play_cb play_cb,
                 void *user_data,
                 pjmedia_aud_stream **p_aud_strm);


    2) alsa音效卡裝置會為PCM回放和抓取功能各自建立一個執行緒,執行緒函式入口引數就是驅動對應的stream(例如alsa_stream)
    3)  應用層在呼叫pjmedia_aud_stream_create()建立alsa_stream時指定回撥函式, user_data作為回撥引數,通常會指向一個pjmedia_port例項,表明PCM資料的來源或去向
    4)  抓取執行緒中通過ca_cb回撥(工廠介面函式create_stream提供),把抓取的pcm幀資料提供個app層        ==》對於MT7628來說,每次讀取固定幀長的資料, 大小是個常數
    5)  回訪執行緒中通過pb_cb回撥(工廠介面函式create_stream提供),從app層獲得滿足音效卡設定的pcm幀資料  ==》對於MT7628來說,每次寫入固定幀長的資料, 大小是個常數

****所以,對於MT7628板子來說,剛好會遇到下面提到的問題:

Potential Problem:

Ideally, rec_cb() and play_cb() should be called one after another, interchangeably, by the sound device. But unfortunately this is not always the case; in many low-end sound cards, it is quite common to have several consecutive rec_cb() callbacks called and then followed by several consecutive play_cb()

calls. To accomodate this behavior, the internal sound device queue buffer in the conference bridge is made large enough to store eight audio frames, and this is controlled by RX_BUF_COUNT macro in conference.c. It is possible that a very very bad sound device may issue more than eight consecutive rec_cb()
/play_cb() calls, which in this case it would be necessary to enlarge the RX_BUF_COUNT number.


*** 以下是pjsip中對alsa音效卡的封裝, 根據pjsip音效卡抽象層的要求,

struct alsa_stream
{
    pjmedia_aud_stream     base;=========》struct pjmedia_aud_stream{                                   
                                struct {
                                /** Driver index */
                                unsigned drv_idx; //該裝置在所有聲音裝置陣列列表中的陣列索引
                                } sys;

                                pjmedia_aud_stream_op *op;========》aud_stream操作介面函式族
                            };

    /* Common */
    pj_pool_t        *pool;
    struct alsa_factory *af;======>aud_stream工廠物件( struct alsa_factory)
                        {
                            pjmedia_aud_dev_factory     base; =======》struct pjmedia_aud_dev_factory
                                                    {
                                                        /** Internal data to be initialized by audio subsystem. */
                                                        struct {
                                                        /** Driver index */
                                                        unsigned drv_idx;
                                                        } sys;

                                                        /** Operations */
                                                        pjmedia_aud_dev_factory_op *op; ==========>aud_stream工廠操作函式族
                                                    };
                            pj_pool_factory        *pf;
                            pj_pool_t            *pool;
                            pj_pool_t            *base_pool;

                            unsigned             dev_cnt;
                            pjmedia_aud_dev_info     devs[MAX_DEVICES];
                            char                         pb_mixer_name[MAX_MIX_NAME_LEN];
                        };

    void        *user_data;      //給回撥函式pb_cb和ca_cb使用的私有資料,指向一個pjmedia_port例項
    pjmedia_aud_param     param;
    int                  rec_id;
    int                  quit;

    /* Playback 執行緒*/
    snd_pcm_t            *pb_pcm;
    snd_pcm_uframes_t    pb_frames;
    pjmedia_aud_play_cb  pb_cb;
    unsigned             pb_buf_size;
    char                *pb_buf;
    pj_thread_t        *pb_thread;

    /* Capture 執行緒*/
    snd_pcm_t            *ca_pcm;
    snd_pcm_uframes_t    ca_frames;
    pjmedia_aud_rec_cb   ca_cb;
    unsigned             ca_buf_size;
    char                *ca_buf;
    pj_thread_t        *ca_thread;
};