注: 上面的tpport,是我为了帮助理解对应关系,为app_config增加的成员变量, 源码中并无这个成员。
未拨打电话时,如下:
Port #00[24KHz/20ms/1] Master/sound transmitting to:
Port #01[24KHz/20ms/1] ringback transmitting to:
Port #02[24KHz/20ms/1] ring transmitting to:
拨打电话,并成功接通后如下:
Port #00[24KHz/20ms/1] default transmitting to: #3
Port #01[24KHz/20ms/1] ringback transmitting to:
Port #02[24KHz/20ms/1] ring transmitting to:
Port #03[ 8KHz/20ms/1] sip:[email protected]:5062 transmitting to: #0
可见:在默认配置条件下,pjsua程序启动后,只存在3个有效的会议桥端口, 通过跟踪调试app_init()函数也可以证实这一点;我们的个性化的铃声音效,可以通过添加wave文件的player连接到player.slot来实现。
pjsua程序提供了会议桥端口的connect/disconnect命令, 可以实现会议桥的个端口的媒体数据的流动方向,我们可以使用这两个命令来加深对会议桥工作方式的理解。
顺便写一句备忘: 关于彩铃,也就是早期媒体,可以看看这篇文章; 另外关于PRACK,相关的编准RFC5626,在SIP账户的配置参数中 use_rfc5626,与这个有关系,180响应携带SDP的原因应该与早期媒体由关系。
pjsua还为我们分别演示了,在通话建立后,通过DTMF音频或INFO消息向对方发送按键音效的方法, 具体分别参考ui_send_dtmf_2833()函数和ui_send_dtmf_info()函数
pjsua_call_send_request(current_call, &SIP_INFO, &msg_data_);被用来发送一个dialog内的SIP INFO信息
pjsua_call_dial_dtmf(current_call, &digits); 被用来发送一个dialog内的DTMF/RTP包
===== 下面重点分析一下DTMF音频的收发
RF2833规定的DTMF字符表:
/* RFC 2833 digit */
static const char digitmap[17] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '#', 'A', 'B', 'C', 'D', 'R'};
使用DTMF事件event->event做索引, 可以查询对端发送的数字是哪一个。
1)DTMF接收端的handle_incoming_dtmf():
在handle_incoming_dtmf代码中: pjmedia_rtp_dtmf_event *event = (pjmedia_rtp_dtmf_event*) payload;
说明DTMF/RRT负载包净负载是一个结构化的数据,共4个字节,负载类型101(---通过抓包工具验证)
解析出的DTMF数据,如果注册了回调函数, 则使用回调函数处理接收的事件, 否则数据包保存在stream->rx_dtmf_buf[]缓冲区里面
*** pjsua程序中,该回调函数在pjsua_aud_channel_update()函数中被设置, 指向函数dtmf_callback()
该函数使用应用层pjsua_var.ua_cfg.cb.on_dtmf_digit回调函数继续向应用层回调,我们来看看应用层回调的设置:
app_config.cfg.cb.on_dtmf_digit = &call_on_dtmf_callback; 这个回调函数只是提供了一个打印log的操作。
***为什么dtmf_event的RTP负载类型是101? 在SDP解析函数get_audio_codec_info_param()中,由这么一句:
si->rx_event_pt = pj_strtoul(&r.pt);
2)DTMF发送端
pjsua_call_dial_dtmf(current_call, &digits);被用来发送一个dialog内的DTMF音频媒体
我们来看看pjsua_call_dial_dtmf的内部实现的关键调用:
pjmedia_stream_dial_dtmf(call->media[call->audio_idx].strm.a.stream, digits);
对于每个pj_mediastream存在一个DTMF发送缓冲区,pjmedia_stream_dial_dtmf用来填充这个缓冲区:
stream->tx_dtmf_buf[stream->tx_dtmf_count+i].event = pt;
stream->tx_dtmf_buf[stream->tx_dtmf_count+i].duration = 0;
stream->tx_dtmf_buf[stream->tx_dtmf_count+i].ebit_cnt = 0;
这个缓冲区的数据将通过如下的途径被使用:
put_frame()->put_frame_imp()->create_dtmf_payload()->pjmedia_rtp_encode_rtp()
对于DTMF:
create_dtmf_payload函数每次构建一个4字节的event/RTP数据包的payload数据
tx_dtmf_buf中的每个event会被重复发送多次(12次),
在12次重复发送过程中, RTP时间标签不会发生变化,但RTP***或连续递增
并且,每次发送的event数据中duration会增加固定增量,该增量使用宏PJMEDIA_PIA_SPF计算, 计算原理暂时还没高明白
当发送下一个DTMF字符时, 下一个DTMF字符时间标签的增量等于,上一个DTMF字符的最后一个event数据序列中的duration -- 尚未仔细验证,大方向是这样的
负载长度永远为4(负载是一个结构体, 结构体长度必须是4, 程序中存在如下代码:pj_assert(sizeof(pjmedia_rtp_dtmf_event) == 4))
负载的赋值如下:
event->event = (pj_uint8_t)digit->event; //按键字符表digitmap[]数组索引
event->e_vol = 10;
event->duration = pj_htons((pj_uint16_t)digit->duration);//这个值像时间标签一样是单向递增的
3)关于DTMF的频率成分
=== 关于电话音效, 主要在on_call_state()回调中
回铃音: ringback_start() ->pjsua_conf_connect(app_config.ringback_slot, 0); //0是master_port所在端口
ring_stop () ->pjsua_conf_disconnect(app_config.ringback_slot, 0); //0是master_port所在端口 -----注意是ring_stop,不是ringback_stop
震铃音: ring_start() ->pjsua_conf_connect(app_config.ring_slot, 0); //0是master_port所在端口
ring_stop () ->pjsua_conf_disconnect(app_config.ring_slot, 0); //0是master_port所在端口
忙音(busy 486): 未实现
传统话机的铃音标准GB3380:http://ishare.iask.sina.com.cn/f/13367325.html
本机震铃音,给个下载网址:http://sc.chinaz.com/tag_yinxiao/DianHuaLingSheng.html