最近在修改asterisk转码和编码协商的问题,发现asterisk的转码策略的选择还是有些问题的(基于1.8.9.3版本)。
——————————————
相关的CLI命令
转码路径的调试命令:
core show channels
core show channel ${CHANNEL}

查看不同编码之间进行转换的时间开销:
core show translation

查看某种编码转换为其它编码的路径:
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,无需转码)

从主叫到被叫与从被叫到主叫的转码路径是不一致的,前者比后者多了一次从 PCMU 到 PCMA 的转换。为什么会出现这种情况呢?

打开log开关、结合CLI命令进行分析,开始看代码。

asterisk对于是否需要转码及转码策略的选择是在ast_channel_make_compatible中做的。该函数又调用ast_channel_make_compatible_helper来设置从主叫到被叫及被叫到主叫的转码策略。

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 = ; /* 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, );
// modify end if (rc < )
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, );
// modify end return rc;
}

ast_channel_make_compatible_helper判断呼叫双方通道的编码是否已经兼容,如果否,就调用ast_set_read_format和ast_set_write_format分别对readformat和writeformat进行设置,并建立转码路径。

/*! \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 *), )) {
return ;
} if (from->readformat == to->writeformat && from->writeformat == to->readformat) {
/* Already compatible! Moving on ... */
ast_log(LOG_NOTICE, "Already compatible!\n");
return ;
} /* 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) == || (dst & AST_FORMAT_AUDIO_MASK) == )
return ; if (ast_translator_best_choice(&dst, &src) < ) { ast_log(LOG_WARNING, "No path to translate from %s to %s\n", from->name, to->name);
return -;
} /* 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) < ) { ast_log(LOG_WARNING, "Unable to set read format on channel %s to %s\n", from->name, ast_getformatname(dst));
return -;
}
if (ast_set_write_format(to, dst) < ) { ast_log(LOG_WARNING, "Unable to set write format on channel %s to %s\n", to->name, ast_getformatname(dst));
return -;
} return ;
}
 

在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(, "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, ) | (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按照设置好的转码

Asterisk1.8 转码策略分析的更多相关文章

  1. MapReduce中TextInputFormat分片和读取分片数据源码级分析

    InputFormat主要用于描述输入数据的格式(我们只分析新API,即org.apache.hadoop.mapreduce.lib.input.InputFormat),提供以下两个功能: (1) ...

  2. LinkedHashMap 源码详细分析(JDK1.8)

    1. 概述 LinkedHashMap 继承自 HashMap,在 HashMap 基础上,通过维护一条双向链表,解决了 HashMap 不能随时保持遍历顺序和插入顺序一致的问题.除此之外,Linke ...

  3. sqlmap Bool型&延时型 检测策略分析

    目录 sqlmap Bool型&延时型 检测策略分析 0x00 预备-queryPage() 0x01 bool型检测策略 判断依据 quick_ratio() 案例 0x02 延时型 判断依 ...

  4. 源码角度分析-newFixedThreadPool线程池导致的内存飙升问题

    前言 使用无界队列的线程池会导致内存飙升吗?面试官经常会问这个问题,本文将基于源码,去分析newFixedThreadPool线程池导致的内存飙升问题,希望能加深大家的理解. (想自学习编程的小伙伴请 ...

  5. 硬核干货:4W字从源码上分析JUC线程池ThreadPoolExecutor的实现原理

    前提 很早之前就打算看一次JUC线程池ThreadPoolExecutor的源码实现,由于近段时间比较忙,一直没有时间整理出源码分析的文章.之前在分析扩展线程池实现可回调的Future时候曾经提到并发 ...

  6. MyBatis源码骨架分析

    源码包分析 MyBatis 源码下载地址:https://github.com/MyBatis/MyBatis-3 MyBatis源码导入过程: 下载MyBatis的源码 检查maven的版本,必须是 ...

  7. MapReduce的ReduceTask任务的运行源码级分析

    MapReduce的MapTask任务的运行源码级分析 这篇文章好不容易恢复了...谢天谢地...这篇文章讲了MapTask的执行流程.咱们这一节讲解ReduceTask的执行流程.ReduceTas ...

  8. Activity源码简要分析总结

    Activity源码简要分析总结 摘自参考书籍,只列一下结论: 1. Activity的顶层View是DecorView,而我们在onCreate()方法中通过setContentView()设置的V ...

  9. MapReduce的MapTask任务的运行源码级分析

    TaskTracker任务初始化及启动task源码级分析 这篇文章中分析了任务的启动,每个task都会使用一个进程占用一个JVM来执行,org.apache.hadoop.mapred.Child方法 ...

随机推荐

  1. Oracle体系结构之控制文件管理

    控制文件作用:记录了数据库的结构和行为,有多少个数据文件,日志文件及其位置名称,状态,维护数据库的一致性,即记录了数据库的启动SCN号和终止SCN号. 控制文件的位置和个数记录在参数文件中,通常控制文 ...

  2. python全栈开发目录

    python全栈开发目录 Linux系列 python基础 前端~HTML~CSS~JavaScript~JQuery~Vue web框架们~Django~Flask~Tornado 数据库们~MyS ...

  3. AT2043 AND Grid 构造

    正解:构造 解题报告: 传送门传送门! 这题psj讲了俩做法,一个是最常见的解法,还一种还不知道484对的QAQ 然后先把psj讲的不知正确性的做法港下QwQ 大概就是说,第一个图,先把底给染完 然后 ...

  4. 如何让dedecms文章点击量增加一定的数值

    用dedecms建站都知道有一个文章点击量这个参数,我们可不可以用这个浏览量做些延伸扩展呢?比如加上一个固定值变成另外一个指标.很多朋友已经想到了,如下图,我们将本文浏览量286设为点击量,加上300 ...

  5. PAT World Cup Betting[非常简单]

    1011 World Cup Betting (20)(20 分) With the 2010 FIFA World Cup running, football fans the world over ...

  6. python接口自动化 -参数关联(一)

    原文地址https://www.cnblogs.com/yoyoketang/p/6886610.html 原文地址https://www.cnblogs.com/yoyoketang/ 原文地址ht ...

  7. testNG入门详解

    TestNG 的注释: @DataProvider @ExpectedExceptions @Factory @Test @Parameters <suite name="Parame ...

  8. C语言typeof详解

    前言:     typeof关键字是C语言中的一个新扩展,这个特性在linux内核中应用非常广泛. 一,说明     typeof的参数可以是两种形式:表达式或类型. 1,表达式的的例子:       ...

  9. OO第三次阶段性总结

    一.规格化设计的历史以及人们重视的原因 发展历史 从20世纪60年代开始,就存在着许多不同的形式规格说明语言和软件开发方法.在形式规格说明领域一些最主要的发展过程列举如下: 1969-1972 C.A ...

  10. jquery 删除table行,该如何解决

    query 删除table行< table >  < tbody >  < tr >  < td > 这行原来就有 </ td >  < ...