前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >PJSIP实现通话过程中MIC静音功能

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

作者头像
呱牛笔记
发布2024-06-21 13:49:36
930
发布2024-06-21 13:49:36
举报
文章被收录于专栏:呱牛笔记呱牛笔记

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

实现思路:

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

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

代码语言:javascript
复制
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

代码语言:javascript
复制
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;

代码语言:javascript
复制
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。

代码语言:javascript
复制
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中添加一个下面的方法:

代码语言:javascript
复制
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中封装下面的方法:

代码语言:javascript
复制
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方法:

代码语言:javascript
复制
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时的日志输出:

代码语言:javascript
复制
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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-06-20 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档