PJSIP实现通话过程中MIC静音功能


需求:客户希望把打电话模式修改为PTT模式,按住按键才发送MIC的拾音数据。

实现思路:

1、彻底禁用MIC,这可以通过MIC的关闭命令来实现,比方tinymix;

但是会有下面的日志输出,表示一直没有MIC数据;

11:30:27.157   Master/sound  Underflow, buf_cnt=0, will generate 1 frame
11:30:27.177   Master/sound  Underflow, buf_cnt=0, will generate 1 frame
11:30:27.198   Master/sound  Underflow, buf_cnt=0, will generate 1 frame

2、修改PJSIP,实现MIC静音功能。静音的效果无非是发送静音包和彻底禁用MIC.

思路一:默认电话接通后关闭MIC通路,按住才打开MIC通路,有几种实现方式:

参考python的一段代码:

呱牛笔记

配置rxlevel的音量为-128

pjsua_aud.c
/* Value must be from -128 to +127 */
/*
 * Adjust the signal level to be transmitted from the bridge to the
 * specified port by making it louder or quieter.
 */
PJ_DEF(pj_status_t) pjsua_conf_adjust_tx_level(pjsua_conf_port_id slot,
					       float level)
{
    return pjmedia_conf_adjust_tx_level(pjsua_var.mconf, slot,
					(int)((level-1) * 128));
}

/*
 * Adjust the signal level to be received from the specified port (to
 * the bridge) by making it louder or quieter.
 */
PJ_DEF(pj_status_t) pjsua_conf_adjust_rx_level(pjsua_conf_port_id slot,
					       float level)
{
    return pjmedia_conf_adjust_rx_level(pjsua_var.mconf, slot,
					(int)((level-1) * 128));
}


思路2:关闭:MIC到网络的数据流通路。

pjsua_conf_disconnect( pjsua_conf_port_id source,

   pjsua_conf_port_id sink)

source是0,sink是谁呢?就是下面的call_conf_slot 


static void on_call_audio_state(pjsua_call_info *ci, unsigned mi,

                                pj_bool_t *has_error)

call_conf_slot = ci->media[mi].stream.aud.conf_slot;

static void on_call_audio_state(pjsua_call_info *ci, unsigned mi,
                                pj_bool_t *has_error)	
                                /* Otherwise connect to sound device */
	if (connect_sound) {
	    pjsua_conf_connect(call_conf_slot, 0);
	    if (!disconnect_mic)
		pjsua_conf_connect(0, call_conf_slot);

	    /* Automatically record conversation, if desired */
	    if (app_config.auto_rec && app_config.rec_port != PJSUA_INVALID_ID)
	    {
		pjsua_conf_connect(call_conf_slot, app_config.rec_port);
		pjsua_conf_connect(0, app_config.rec_port);
	    }
	}
    }


思路三:利用conference的tx_flag和rx_flag。

PJ_DEF(pj_status_t) pjmedia_conf_configure_port( pjmedia_conf *conf,
                                                  unsigned slot,
                                                  pjmedia_port_op tx,
                                                  pjmedia_port_op rx)
{
    struct conf_port *conf_port;

    /* Check arguments */
    PJ_ASSERT_RETURN(conf && slot<conf->max_ports, PJ_EINVAL);

    pj_mutex_lock(conf->mutex);

    /* Port must be valid. */
    conf_port = conf->ports[slot];
    if (conf_port == NULL) {
        pj_mutex_unlock(conf->mutex);
        return PJ_EINVAL;
    }

    conf_port = conf->ports[slot];

    if (tx != PJMEDIA_PORT_NO_CHANGE)
        conf_port->tx_setting = tx;

    if (rx != PJMEDIA_PORT_NO_CHANGE)
        conf_port->rx_setting = rx;

    pj_mutex_unlock(conf->mutex);

    return PJ_SUCCESS;
}

在pjsua_aud.c中添加一个下面的方法:

PJ_DEF(pj_status_t) pjsua_conf_mute_trx(pjsua_conf_port_id slot, pjmedia_port_op tx_flag, pjmedia_port_op rx_flag) 
{   
PJ_ASSERT_RETURN(slot >= 0, PJ_EINVAL);   

return pjmedia_conf_configure_port(pjsua_var.mconf, slot, tx_flag, rx_flag);

}


然后在pjsip_app.c中封装下面的方法:

void mute_mic() {
	pjsua_conf_mute_trx(0, PJMEDIA_PORT_ENABLE, PJMEDIA_PORT_MUTE);
	//pjsua_conf_adjust_rx_level(0, 0);


}
void unmute_mic() {
	pjsua_conf_mute_trx(0, PJMEDIA_PORT_ENABLE, PJMEDIA_PORT_ENABLE);
	//pjsua_conf_adjust_rx_level(0, 1);
}


最后实现,使用的是MUTE的方法,但是修改了MUTE的处理逻辑,conference.c中的put_frame方法:

static pj_status_t put_frame(pjmedia_port *this_port, 
                             pjmedia_frame *frame)
{
    pjmedia_conf *conf = (pjmedia_conf*) this_port->port_data.pdata;
    struct conf_port *port = conf->ports[this_port->port_data.ldata];
    pj_status_t status;

    /* Check for correct size. */
    PJ_ASSERT_RETURN( frame->size == conf->samples_per_frame *
                                     conf->bits_per_sample / 8,
                      PJMEDIA_ENCSAMPLESPFRAME);

    /* Check existance of delay_buf instance */
    PJ_ASSERT_RETURN( port->delay_buf, PJ_EBUG );

    /* Skip if this port is muted/disabled. */
    if (port->rx_setting != PJMEDIA_PORT_ENABLE) {
        
	if (PJMEDIA_PORT_MUTE == port->rx_setting ){
	      //如果是MUTE,将frame bufer的数据写0,表示为静音。
		memset(frame->buf, 0x00, frame->size);
	}else{
           return PJ_SUCCESS;
	}
    }
	

    /* Skip if no port is listening to the microphone */
    if (port->listener_cnt == 0) {
        return PJ_SUCCESS;
    }

    status = pjmedia_delay_buf_put(port->delay_buf, (pj_int16_t*)frame->buf);

    return status;
}

要不,会一直出现没有mic时的日志输出:

11:30:27.157   Master/sound  Underflow, buf_cnt=0, will generate 1 frame
11:30:27.177   Master/sound  Underflow, buf_cnt=0, will generate 1 frame
11:30:27.198   Master/sound  Underflow, buf_cnt=0, will generate 1 frame



audio部分的代码一直没有细看,主要是pjsip对音频的处理一直都没有什么问题,逻辑层次也很清晰。但是也一直有几个问题,理解不是很深刻,就是pjsip的conference 混音机制,还有source到sink的逻辑通路。看这个代码,可以从音频设备反着来看,也可以顺着呼叫的逻辑顺着来看,然后对齐,整个代码逻辑就理顺了。借改这个问题的机会,捋了捋,确实是清晰了不少。


声音的数据流驱动,原来以为是会议的clock_tick,其实不是,声音数据流的驱动,依靠的是音频声卡播放的回调方法,在回调方法中,完成收包,和从声卡缓存数据的网络发包。


录音的数据需要抛给网络的stream,从网络stream回来的数据,需要扔给播放器去播放,也就是两条路:

录音   -> delay_buffer ->网络tx

网络rx ->jitterbuffer-> 播放 

依靠音频卡的play_cb驱动。


声卡一端的数据,录音回调到conference的put_frame,然后放到了port->delay_buf

//sound_port.c

static pj_status_t rec_cb(void *user_data, pjmedia_frame *frame)

//conference.c

pjmedia_port_put_frame(port, frame);

{    

    pjmedia_conf *conf = (pjmedia_conf*) this_port->port_data.pdata;

    struct conf_port *port = conf->ports[this_port->port_data.ldata];

}


发送,则依赖的是声卡的play_cb回调方法。

呱牛笔记


-------------------广告线---------------
项目、合作,欢迎勾搭,邮箱:promall@qq.com


本文为呱牛笔记原创文章,转载无需和我联系,但请注明来自呱牛笔记 ,it3q.com

请先登录后发表评论
  • 最新评论
  • 总共0条评论