会议桥
自定义媒体端口
在《pjSIP注册呼叫流程简介》中介绍了pjSIP注册与呼叫的基本流程,本节对自定义媒体流与端口做下介绍。
pjSIP中通过会议桥(Conference)把媒体流(Stream)与抽象音频设备端口(Sound Device Port)连接起来(并负责各路媒体的混流);他们之间数据传递都是通过pjmedia_port接口来实现的。若要收发自定义的媒体数据,只需连接会议桥,从中接收或发送媒体数据即可。
typedef struct pjmedia_port
{
pj_status_t (*put_frame)(struct pjmedia_port *this_port,
pjmedia_frame *frame);
pj_status_t (*get_frame)(struct pjmedia_port *this_port,
pjmedia_frame *frame);
} pjmedia_port;
两个接口根据需要定义(put与get是从会议桥视角看),若要从会议桥中接收媒体则实现put接口,若要向会议桥中发送媒体则实现get接口;若要收发媒体,则两个都要定义。
Conference内部有一个默认master_port(索引为0)的端口,用作与音频设备间的连接接口(被动被sound device调用):
音频设备采集的PCM数据通过put_frame传递给会议桥;
音频设备通过get_frame从会议桥获取PCM数据(各输入流mix后);
只要端口满足pjmedia_port接口定义,就可以连接(pjsua_conf_connect)到会议桥。
获取媒体
获取媒体端口,只需定义put_frame
即可,当会议桥中有媒体数据时,会调用此接口把媒体发送给此端口。
static pj_status_t listenPutFrame(pjmedia_port *pPort, pjmedia_frame *pFrame){
// get the frame, can be deal iter_swap
// g_getFrammeCallback(pFrame);
return PJ_SUCCESS;
}
static pj_status_t createListenPort(pjmedia_port **ppPort){
auto locPort = (pjmedia_port*)pj_pool_zalloc(g_pool, sizeof(pjmedia_port);
// fill port-info
pj_str_t name = pj_str("pj.listen");
pjmedia_port_info_init(&locPort->info,
&name,
PJMEDIA_SIG_CLASS_PORT_AUD("X", "L");
16000, // Sample-rate
1, // channels
16, // Bit per sample
16000* 20/ 1000 * 1); // samples per frame: sampleRate*MillSecPerPacket*Channel/1000
locPort->put_frame = &listenPutFrame;
// set port info, if need
locPort->port_data.pdata = pj_pool_alloca(g_pool, sizeof(YOU_DATA)); // this is user-data
*ppPort = locPort;
return PJ_SUCCESS;
}
在listenPutFrame的参数pFrame中即为获取到的媒体数据,根据需要处理即可。
发送媒体
发送媒体端口,只需定义get_frame
即可,会议桥会定时调用此接口来获取数据。
static pj_status_t speakGetFrame(pjmedia_port *pPort, pjmedia_frame *pMedia){
// fill fram to pMedia
// pMedia->type = PJMEDIA_FRAME_TYPE_AUDIO;
// Set size & buf
// pMedia->timestamp.u64 = 0; // the pjsip auto-deal the stamp
return PJ_SUCCESS;
}
static pj_status_t createSpeakPort(pjmedia_port **ppPort){
auto locPort = (pjmedia_port*)pj_pool_zalloc(g_pool, sizeof(pjmedia_port);
// fill port-info
pj_str_t name = pj_str("pj.speak");
pjmedia_port_info_init(&locPort->info,
&name,
PJMEDIA_SIG_CLASS_PORT_AUD("X", "S");
16000, // Sample-rate
1, // channels
16, // Bit per sample
16000* 20/ 1000 * 1); // samples per frame: sampleRate*MillSecPerPacket*Channel/1000
locPort->get_frame = &speakGetFrame;
// set port info, if need
locPort->port_data.pdata = pj_pool_alloca(g_pool, sizeof(YOU_DATA)); // this is user-data
*ppPort = locPort;
return PJ_SUCCESS;
}
创建与连接端口
通过上面定义的接口即可创建相应的端口,然后把创建的端口连接到会议桥中,即完成了媒体端口的创建工作。
pjmedia_port *g_listenMedia = NULL;
pjsua_conf_port_id g_listenConf = -1;
pjmedia_port *g_speakMedia = NULL;
pjsua_conf_port_id g_speakConf = -1;
void ToStartMedia(pjsua_call_info &callInfo){
auto confPort = callInfo.conf_slot;
// listen port
auto nStatus = createListenPort(&g_listenMedia);
if (!PjRetsuccess(nStatus)){
...
}
nStatus = pjsua_conf_add_port(g_pool, g_listenMedia, &g_listenConf);
if (!PjRetsuccess(nStatus)){
...
}
nStatus = pjsua_conf_connect(confPort, g_listenConf); // conf->listen
if (!PjRetsuccess(nStatus)){
...
}
// speak port
nStatus = createSpeakPort(&g_speakMedia);
if (!PjRetsuccess(nStatus)){
...
}
nStatus = pjsua_conf_add_port(g_pool, g_speakMedia, &g_speakConf);
if (!PjRetsuccess(nStatus)){
...
}
nStatus = pjsua_conf_connect(g_speakConf, confPort); // speak->conf
if (!PjRetsuccess(nStatus)){
...
}
// ...
}
此接口一般在媒体事件OnMediaState
中,在媒体状态变为可用PJSUA_CALL_MEDIA_ACTIVE
时调用。
端口停止
当通话完成时(OnCallState
事件中,通话状态变为PJSIP_INV_STATE_DISCONNECTED
),需要断开端口连接,并销毁端口。
// pjsua_conf_disconnect
// pjsua_conf_remove_port
void ToStopMedia(pjsua_call_info &callInfo){
auto confPort = callInfo.conf_slot;
if(g_listenConf != -1){
pjsua_conf_disconnect(confPort, g_listenConf);
pjsua_conf_remove_port(g_listenConf);
g_listenConf = -1;
}
// ... speak
// destroy
pj_thread_sleep(20); // wait fo media clear
if(g_listenMedia != NULL){
pjmedia_port_destroy(g_listenMedia);
g_listenMedia = NULL;
}
// ... speak
}
辅助接口
端口创建等使用的内存,都通过内存池获取,所以需要提前创建内存池,并在完成时销毁。
// media
pj_caching_pool g_cachingPool;
pj_pool_t *g_pool = NULL;
void InitMedia(){
pj_caching_pool_init(&g_cachingPool, &pj_pool_factory_default_policy, 0);
g_pool = pj_pool_create(&g_cachingPool.factory,
"pjTest",
4000,
4000,
NULL);
}
void ClearMedia(){
pj_pool_safe_release(&g_pool);
pj_caching_pool_destroy(&g_cachingPool);
}