最近在修改asterisk轉碼和編碼協商的問題,發現asterisk的轉碼策略的選擇仍是有些問題的(基於1.8.9.3版本)。
——————————————
相關的CLI命令
轉碼路徑的調試命令:
core show channels
core show channel ${CHANNEL}數據結構
查看不一樣編碼之間進行轉換的時間開銷:
core show translationapp
查看某種編碼轉換爲其它編碼的路徑:
core show translation paths {codec}
eg: core show translation paths ulaw函數
ast_channel中與轉碼相關的數據成員:
ast_channel->nativeformats
ast_channel->writeformat
ast_channel->readformat
ast_channel->rawwriteformat
ast_channel->rawreadformat
ast_channel->writetrans
ast_channel->readtrans
——————————————
如下是測試用的case:
phone A: PCMU phone B:GSM
user A:PCMU,GSM user B:PCMA,GSM測試
對於該用例asterisk的轉碼路徑是這樣的。
1.phone A => phone B
channel A讀轉碼(PCMU => PCMA)
channel B寫轉碼(PCMA => SLINEAR => GSM)
2.phone B => phone A
channel B讀轉碼(GSM => SLINEAR => PCMU)
channel A寫轉碼(PCMU,無需轉碼)ui
從主叫到被叫與從被叫到主叫的轉碼路徑是不一致的,前者比後者多了一次從 PCMU 到 PCMA 的轉換。爲何會出現這種狀況呢?this
打開log開關、結合CLI命令進行分析,開始看代碼。編碼
asterisk對因而否須要轉碼及轉碼策略的選擇是在ast_channel_make_compatible中作的。該函數又調用ast_channel_make_compatible_helper來設置從主叫到被叫及被叫到主叫的轉碼策略。spa
int ast_channel_make_compatible(struct ast_channel *chan, struct ast_channel *peer) { /* Some callers do not check return code, and we must try to set all call legs correctly */ int rc = 0; /* Set up translation from the chan to the peer */ // modify //rc = ast_channel_make_compatible_helper(chan, peer); rc = ast_channel_make_compatible_helper(chan, peer, 0); // modify end if (rc < 0) return rc; /* Set up translation from the peer to the chan */ // modify //rc = ast_channel_make_compatible_helper(peer, chan); rc = ast_channel_make_compatible_helper(peer, chan, 1); // modify end return rc; }
ast_channel_make_compatible_helper判斷呼叫雙方通道的編碼是否已經兼容,若是否,就調用ast_set_read_format和ast_set_write_format分別對readformat和writeformat進行設置,並創建轉碼路徑。debug
/*! \brief Set up translation from one channel to another */ /* modify : Add a 'bool' argument to judge which is a caller channel and which a callee channel. if 'bool' is true, then 'from' is a caller channel, 'to' is a callee channel. otherwise, 'to' is a caller channel, 'from' is a callee channel. */ static int ast_channel_make_compatible_helper(struct ast_channel *from, struct ast_channel *to, int bool) { format_t src, dst; //int use_slin; /* See if the channel driver can natively make these two channels compatible */ if (from->tech->bridge && from->tech->bridge == to->tech->bridge && !ast_channel_setoption(from, AST_OPTION_MAKE_COMPATIBLE, to, sizeof(struct ast_channel *), 0)) { return 0; } if (from->readformat == to->writeformat && from->writeformat == to->readformat) { /* Already compatible! Moving on ... */ ast_log(LOG_NOTICE, "Already compatible!\n"); return 0; } /* Set up translation from the 'from' channel to the 'to' channel */ src = from->nativeformats; dst = to->nativeformats; /* If there's no audio in this call, don't bother with trying to find a translation path */ if ((src & AST_FORMAT_AUDIO_MASK) == 0 || (dst & AST_FORMAT_AUDIO_MASK) == 0) return 0; if (ast_translator_best_choice(&dst, &src) < 0) { ast_log(LOG_WARNING, "No path to translate from %s to %s\n", from->name, to->name); return -1; } /* if the best path is not 'pass through', then * transcoding is needed; if desired, force transcode path * to use SLINEAR between channels, but only if there is * no direct conversion available. If generic PLC is * desired, then transcoding via SLINEAR is a requirement */ // modify : comment these /* use_slin = (src == AST_FORMAT_SLINEAR || dst == AST_FORMAT_SLINEAR); if ((src != dst) && (ast_opt_generic_plc || ast_opt_transcode_via_slin) && (ast_translate_path_steps(dst, src) != 1 || use_slin)){ ast_log(LOG_NOTICE, "dst is AST_FORMAT_SLINEAR!\n"); dst = AST_FORMAT_SLINEAR; } */ // modify end // add /* we only build translation path and do translations in the callee channel. to achieve this goal, we set readformat and writeformat of the caller channel and the callee channel both to nativeformat of the caller channel,so the caller channel won't execute read-transcode and write-transcode. */ if(bool){ dst = src; } // add end if (ast_set_read_format(from, dst) < 0) { ast_log(LOG_WARNING, "Unable to set read format on channel %s to %s\n", from->name, ast_getformatname(dst)); return -1; } if (ast_set_write_format(to, dst) < 0) { ast_log(LOG_WARNING, "Unable to set write format on channel %s to %s\n", to->name, ast_getformatname(dst)); return -1; } return 0; }
在ast_set_read_format中,先調用ast_translator_best_choice(&fmt,&native)從native(即對應通道的nativeformates)和fmt(要設置的編碼集合)中分別選擇最優的一種編碼,並將選擇出的編碼從新賦值給native和fmt,這裏是傳地址的,經過指針修改。而後把native的值賦值給rawformat(通道的rawreadformat),將fmt的值賦值給format(通道的readformat)。最後,若是format與native值不相同的話,就調用ast_translator_build_path(*format, *rawformat)來創建轉碼路徑的鏈表。ast_set_write_format與ast_set_read_format同理,只不過fmt是賦值給通道的writeformat,native是賦值給通道的rawwriteformat。指針
收到200 OK後,會調用process_sdp(file:channels/chan_sip.c)來解析SDP,在處理被叫終端的SDP時,被叫通道的nativeformats可能會改變,此時須要從新設置被叫通道的讀寫轉碼路徑,對於writetranscode: 從channel->writeformat到channel->nativeformats進行轉換,對於readtranscode: 從channel->nativeformats到channel->readformat進行轉換。被叫通道的writeformat和readformat在以前調用ast_channel_make_compatible時已經被設置過了。
if (!(p->owner->nativeformats & p->jointcapability) && (p->jointcapability & AST_FORMAT_AUDIO_MASK)) { if (debug) { char s1[SIPBUFSIZE], s2[SIPBUFSIZE]; ast_debug(1, "Oooh, we need to change our audio formats since our peer supports only %s and not %s\n", ast_getformatname_multiple(s1, SIPBUFSIZE, p->jointcapability), ast_getformatname_multiple(s2, SIPBUFSIZE, p->owner->nativeformats)); } p->owner->nativeformats = ast_codec_choose(&p->prefs, p->jointcapability, 1) | (p->capability & vpeercapability) | (p->capability & tpeercapability); ast_set_read_format(p->owner, p->owner->readformat); ast_set_write_format(p->owner, p->owner->writeformat); }
再回到咱們的問題上來。通過分析,出現該問題的緣由是在收到被叫的200 OK以前channelB->nativeformats的音頻編碼是Ulaw,而在收到200 OK後,channelB->nativeformats的音頻編碼是G729,而在通道橋接時,並無檢查channelB->nativeformats是否發生變化,沒有去更改channelB->writeformat(例子中爲ulaw)和channelA->readformat(例子中爲ulaw),使主叫仍按照以前選擇的路徑去進行轉碼,從而致使了沒必要要的轉碼步驟。
另外,在asterisk關於轉碼的實現中,主叫和被叫通道都分別有 WriteTranscode和ReadTranscode(能夠在通話時經過core show channel sip/{EXTEN}查看),這樣每一路通話最多可能會用到四個轉碼資源(ast_trans_pvt)。針對這些問題,我修改後的作法是:只在被叫通道一側作轉碼,主叫、被叫通道的readformat與writeformat設置爲主叫通道的nativeformats(主叫的nativeformats是不會改變的),這樣主叫通道不須要分配轉碼資源,最多隻佔用兩個轉碼資源。而且若是在處理完被叫終端回覆的SDP後被叫通道的nativeformats改變了,不須要對主叫和被叫的readformat和writeformat從新設置,只須要從新設置被叫通道的讀寫轉碼路徑便可(具體修改見以上代碼中modify和add部分)。
ast_channel_make_compatible這個函數只是檢查呼叫中的兩個channel的數據結構的一些編碼成員並設置兩個channel之間的編碼路徑。實際的轉碼是橋接時(如:ast_generic_bridge)在ast_read或ast_write中進行的,在讀寫幀以前調用ast_translate按照設置好的轉碼