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