最近在修改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安装部署之linux(redhat/centos)快速安装oracle 11g rac

    安装oracle 11gR2 RAC 一.网络规划及安装虚拟主机 主机名 主机版本 Ip rac1.localdomain Redhat 6.5 RAC节点1 192.168.100.11 rac2. ...

  2. python数据结构之树(二叉树的遍历)

    树是数据结构中非常重要的一种,主要的用途是用来提高查找效率,对于要重复查找的情况效果更佳,如二叉排序树.FP-树. 本篇学习笔记来自:二叉树及其七种遍历方式.python遍历与非遍历方式实现二叉树 介 ...

  3. B-tree indexes

    High Performance MySQL, Third Edition by Baron Schwartz, Peter Zaitsev, and Vadim Tkachenko http://d ...

  4. JS继承封装

    <script> var extend = function (subClass, superClass) { //1.继承类的中间类 var Tmp = function() {}; / ...

  5. HTML5 桌面提示

    <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title&g ...

  6. 洛谷P2634 聪聪可可 [国家集训队] 点分治/dp

    正解:点分治/dp 解题报告: 传送门! 这题有两个做法,都是我不擅长的就都说下好了QAQ 首先这题一看到就会想到点分治? 也确实可以用点分治,那就直接用点分治鸭 每次求出到当前根距离余数为0,1,2 ...

  7. Rufus 制作 USB 启动盘简单教程

    制作 Windows 10 启动盘 U盘 / USB 安装盘图文教程  http://rufus.akeo.ie/downloads/rufus-2.2p.exe 1.将U盘连接到电脑,以管理员身份运 ...

  8. zkSNARK 零知识验证

    参考文献 ZCash7篇,有社区翻译版,但还是推荐看原汁原味的     https://z.cash/blog/snark-explain.html Vitalik3篇,小天才作者我就不介绍了,这三篇 ...

  9. storm并发机制,通信机制,任务提交

    一.storm的并发 (1)Workers(JVMs):在一个物理节点上可以运行一个或多个独立的JVM进程.一个Topology可以包含一个或多个worker(并行的跑在不同的物理机上),所以work ...

  10. 【java】System.out重定向到文件,并重定向会console上

    重定向到文件: System.setOut(new PrintStream(new File("data\\train.1.scale"))); 重定向回console: //把输 ...