需求:客户希望把打电话模式修改为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
