在开始分析之前,先对编码协商中可能涉及的asterisk数据结构和变量作些说明。
ast_channel:定义一个通用的通道数据结构
struct ast_channel { const struct ast_channel_tech *tech; /*!< Technology (point to channel driver) */ void *tech_pvt; /*!< Private data used by the technology driver */ ... }
其中tech和tech_pvt两个成员是与通道具体使用的技术相关的,tech是与一种通道技术(如SIP)对应的驱动的数据结构,tech的类型为 ast_channel_tech的结构体,通道驱动定义了通道类型、描述,基本的呼叫相关函数指针(call,hangup,answer,transfer,bridge,early-bridge),帧的读写函数指针,DTMF、文本、图像、HTML、视频的发送,通道状态指示函数指针(indicate)等,对通道的操作主要是在这里定义的,而tech_pvt则定义了具体通道技术的数据信息,如sip_pvt。这些都是依赖于通道所使用的技术的。而ast_channel中其他的成员则可认为是各种通道都具有的通用的数据信息。
保存默认编码偏好的两个全局变量sip_cfg.capability和default_prefs
sip_cfg.capability:
sip_cfg是保存sip.conf的general段配置的全局结构体。sip_cfg.capability则是sip general配置的asterisk支持的编码。在reload_config函数中对sip_cfg.capability进行初始化。首先,把 DEFAULT_CAPABILITY这个宏(定义在sip.h中)中的5种编码(ulaw | testlaw | alaw | gsm | h.263)加到sip_cfg.capability中,然后解析(ast_parse_allow_disallow函数)sip.conf中的general段中的disallow和allow两项配置,先剔除disallow中的编码,再将allow中的编码加到sip_cfg.capability中。
default_prefs:
default_prefs是保存默认音频编码偏好的全局结构体。
对default_prefs的初始化也是在reload_config中进行的。解析(ast_parse_allow_disallow函数)sip.conf中的general段中的disallow和allow两项配置,先剔除disallow中的编码,然后将allow中的音频编码加到default_prefs中。default_prefs中不包括视频编码,因为asterisk不能对视频编码进行转码,只得使用所提供(offer)的视频编码。
sip_pvt结构体p中关于编码的成员的说明
p->peercapability: 即user/peer对应的终端上支持的编码。
p->capability: 即user/peer对应的编码配置。初始化为sip.conf [general]中allow选项配置的编码,在check_peer_ok函数中重新赋值为对应user/peer的编码。
p->prefcodec: 只用于呼出呼叫(outbound call),由呼入通道以参数传递进来。
p->jointcapability: 对于呼入通道来说,指的是user/peer和终端都具有的编码。对于呼出通道来说,在发出invite还未收到带sdp的响应之前,指的是p->capability中能够与呼入通道传递的nativeformats(即p->prefcodec)进行互相转码的编码;收到终端带sdp的响应后,在处理sdp时,赋值为sdp中携带的编码。
p->prefs: 即user/peer对应的音频编码配置。在sip_alloc函数中被初始化为default_prefs这个全局结构体的值,在check_peer_ok函数中重新赋值为对应user/peer的编码中的音频编码。
呼入呼叫(Inbound Call)协商:
在load或reload chan_sip模块时,会调用build_peer函数从sip.conf或users.conf中读取配置,并把每一个帐号的配置保存到一个sip_peer的结构体中,在build_peer中调用set_peer_defaults来初始化这个结构体的成员(比如sippeer->capability = sip_cfg.capability; sippeer->prefs = default_prefs;),然后解析sip.conf(或users.conf),如果该帐号对应的context中有allow选项的话,就覆盖sippeer的capability和prefs成员的初始值,将allow中的所有编码赋值给sippeer->capability,将allow中的音频编码保持原顺序赋值给sippeer->prefs。
p->prefs、p->capability、呼入通道的nativeformats的初始化:
asterisk接收到sip请求消息时,调用handle_request_do->find_call->sip_alloc,在sip_alloc中为sip_pvt结构体p的capability和prefs成员初始化:p->capability = sip_cfg.capability;p->prefs = default_prefs。
接着handle_request_do->handle_incoming->handle_request_invite->check_user_full->check_peer_ok,在check_peer_ok中将找到的sip_peer的编码成员分别赋值给p->capability,p->jointcapability,p->prefs,重新对p->capability、p->prefs进行初始化,对p->jointcapability进行初始化。
p->capability = peer->capability; p->prefs = peer->prefs; p->jointcapability = peer->capability;
然后handle_request_do->handle_incoming->handle_request_invite->sip_new,在sip_new中创建sip通道结构体tmp,并为tmp的nativeformats成员赋值:首先从tmp通道对应的sip_pvt结构体成员i->prefs(这里的i实际上就是sip_alloc中分配的sip_pvt结构体p作为参数传进来)的编码中按顺序从最前面开始选出(ast_codec_choose)属于sip_pvt结构体capability的一种编码作为nativeformats的值,如果没找到,就调用ast_best_codec来选择一种编码,该函数内部定义了一个名为prefs的音频编码数组,按顺序遍历该数组,直到找到一个属于sip_pvt结构体capability并且为音频的编码。然后或上视频和文本编码能力,赋值给tmp->nativeformats。
/* Select our native format based on codec preference until we receive something from another device to the contrary. */ if (i->jointcapability) { /* The joint capabilities of us and peer */ what = i->jointcapability; video = i->jointcapability & AST_FORMAT_VIDEO_MASK; text = i->jointcapability & AST_FORMAT_TEXT_MASK; } else if (i->capability) { /* Our configured capability for this peer */ what = i->capability; video = i->capability & AST_FORMAT_VIDEO_MASK; text = i->capability & AST_FORMAT_TEXT_MASK; } else { what = sip_cfg.capability; /* Global codec support */ video = sip_cfg.capability & AST_FORMAT_VIDEO_MASK; text = sip_cfg.capability & AST_FORMAT_TEXT_MASK; } /* Set the native formats for audio and merge in video */ tmp->nativeformats = ast_codec_choose(&i->prefs, what, 1) | video | text;
在handle_request_invite中调用process_sdp(注:process_sdp并不处理outbound call中的invite,process_sdp会在有incoming的invite和200OK时被调用),将invite中sdp携带的编码逐个解析出来,将这些编码对应的code相或并赋值给p->peercapability,将p->capability & p->peercapability的结果赋值给p->jointcapability.
/* Scan media stream (m=) specific parameters loop */ while (!ast_strlen_zero(nextm)) { int audio = FALSE; int video = FALSE; int image = FALSE; int text = FALSE; char protocol[5] = {0,}; int x; numberofports = 1; len = -1; start = next; m = nextm; iterator = next; nextm = get_sdp_iterate(&next, req, "m"); /* Search for audio media definition */ /* 处理SDP中的m(音频媒体属性,如: m: audio 13422 RTP/AVP 0 3 101) */ if ((sscanf(m, "audio %30u/%30u RTP/%4s %n", &x, &numberofports, protocol, &len) == 3 && len > 0 && x) || (sscanf(m, "audio %30u RTP/%4s %n", &x, protocol, &len) == 2 && len > 0 && x)) { if (!strcmp(protocol, "SAVP")) { secure_audio = 1; } else if (strcmp(protocol, "AVP")) { ast_log(LOG_WARNING, "unknown SDP media protocol in offer: %s ", protocol); continue; } if (p->offered_media[SDP_AUDIO].order_offered) { ast_log(LOG_WARNING, "Multiple audio streams are not supported "); return -3; } audio = TRUE; p->offered_media[SDP_AUDIO].order_offered = ++numberofmediastreams; portno = x; /* Scan through the RTP payload types specified in a "m=" line: */ codecs = m + len; ast_copy_string(p->offered_media[SDP_AUDIO].codecs, codecs, sizeof(p->offered_media[SDP_AUDIO].codecs)); for (; !ast_strlen_zero(codecs); codecs = ast_skip_blanks(codecs + len)) { if (sscanf(codecs, "%30u%n", &codec, &len) != 1) { ast_log(LOG_WARNING, "Error in codec string '%s' ", codecs); return -1; } if (debug) ast_verbose("Found RTP audio format %d ", codec); ast_rtp_codecs_payloads_set_m_type(&newaudiortp, NULL, codec); } /* Search for video media definition */ /* 处理SDP中的m(视频媒体属性,如: m: video 12036 RTP/AVP 34 98 99 ) */ } else if ((sscanf(m, "video %30u/%30u RTP/%4s %n", &x, &numberofports, protocol, &len) == 3 && len > 0 && x) || (sscanf(m, "video %30u RTP/%4s %n", &x, protocol, &len) == 2 && len >= 0 && x)) { if (!strcmp(protocol, "SAVP")) { secure_video = 1; } else if (strcmp(protocol, "AVP")) { ast_log(LOG_WARNING, "unknown SDP media protocol in offer: %s ", protocol); continue; } if (p->offered_media[SDP_VIDEO].order_offered) { ast_log(LOG_WARNING, "Multiple video streams are not supported "); return -3; } video = TRUE; p->novideo = FALSE; p->offered_media[SDP_VIDEO].order_offered = ++numberofmediastreams; vportno = x; /* Scan through the RTP payload types specified in a "m=" line: */ codecs = m + len; ast_copy_string(p->offered_media[SDP_VIDEO].codecs, codecs, sizeof(p->offered_media[SDP_VIDEO].codecs)); for (; !ast_strlen_zero(codecs); codecs = ast_skip_blanks(codecs + len)) { if (sscanf(codecs, "%30u%n", &codec, &len) != 1) { ast_log(LOG_WARNING, "Error in codec string '%s' ", codecs); return -1; } if (debug) ast_verbose("Found RTP video format %d ", codec); ast_rtp_codecs_payloads_set_m_type(&newvideortp, NULL, codec); } /* Search for text media definition */ } /* ...... */ /* Media stream specific parameters */ while ((type = get_sdp_line(&iterator, next - 1, req, &value)) != '