当前位置: 首页 > 工具软件 > PJSIP > 使用案例 >

pjSIP收发自定义媒体

裴理
2023-12-01

会议桥

自定义媒体端口

 

 

在《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);
}

 

 类似资料: