需求:客户希望把打电话模式修改为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回调方法。
本文为呱牛笔记原创文章,转载无需和我联系,但请注明来自呱牛笔记 ,it3q.com