Asterisk1.8 转码策略分析

最近在修改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 = 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进行设置,并建立转码路径。

/*! \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按照设置好的转码

时间: 2024-07-29 06:10:43

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

JobTracker启动流程源码级分析

org.apache.hadoop.mapred.JobTracker类是个独立的进程,有自己的main函数.JobTracker是在网络环境中提交及运行MR任务的核心位置. main方法主要代码有两句: 1 //创建jobTracker对象 2 JobTracker tracker = startTracker(new JobConf()); 3 //启动各个服务,包括JT内部一些重要的服务或者线程 4 tracker.offerService(); 一.startTracker(new Jo

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

InputFormat主要用于描述输入数据的格式(我们只分析新API,即org.apache.hadoop.mapreduce.lib.input.InputFormat),提供以下两个功能: (1)数据切分:按照某个策略将输入数据切分成若干个split,以便确定MapTask个数以及对应的split: (2)为Mapper提供输入数据:读取给定的split的数据,解析成一个个的key/value对,供mapper使用. InputFormat有两个比较重要的方法:(1)List<InputSp

多版本软件构建策略分析

原文:多版本软件构建策略分析 主要分析存在多个版本特性时的软件构建策略.多个版本特性在有些情况下仅仅对应于软件的本地化,复杂的情况就是不同版本中模块的业务逻辑.呈现策略都不相 同.这不仅在产品开发过程中增加成本,更多的成本将在维护阶段体现出来.因此,选择一个合适的构建策略对降低开发与维护成本都是非常重要的. 一.传统软件构建策略 不同的版本采用不同的代码,通过派生或直接使用不同的代码实现.每个版本都会对应到一份的这个版本相关的代码.在代码发布成产品时,我们还需要一个build过程,将源码打包发布

Java7/8 中 HashMap 和 ConcurrentHashMap源码对比分析

网上关于 HashMap 和 ConcurrentHashMap 的文章确实不少,不过缺斤少两的文章比较多,所以才想自己也写一篇,把细节说清楚说透,尤其像 Java8 中的 ConcurrentHashMap,大部分文章都说不清楚.终归是希望能降低大家学习的成本,不希望大家到处找各种不是很靠谱的文章,看完一篇又一篇,可是还是模模糊糊. 阅读建议:四节基本上可以进行独立阅读,建议初学者可按照 Java7 HashMap -> Java7 ConcurrentHashMap -> Java8 Ha

又是正版!Win下ffmpeg源码调试分析二(Step into ffmpeg from Opencv for bugs in debug mode with MSVC)

最近工作忙一直没时间写,但是看看网络上这方面的资源确实少,很多都是linux的(我更爱unix,哈哈),而且很多是直接引入上一篇文章的编译结果来做的.对于使用opencv但是又老是被ffmpeg库坑害的朋友们,可能又爱又恨,毕竟用它处理和分析视频是第一选择,不仅是因为俩者配合使用方便,而且ffmpeg几乎囊括了我所知道的所有解编码器,但是正是因为这个导致了一些bug很难定位,所以有必要考虑一下如何快速定位你的ffmpeg bug. sorry,废话多了.首先给个思路: 1.使opencv 的hi

uboot移植——uboot源码目录分析

uboot移植(一)--uboot源码目录分析 本文分析的uboot是九鼎官方提供的,是对应s5pv210开发板x210bv3的uboot 一:uboot的概念及移植的原理. uboot就是在内核运行前的一段小程序,用来初始化硬件设备,建立内存空间映射图.从而将系统的软硬件带到合适的状态,主要功能就是为了启动内核,它将内核从flash中拷贝到ddr中,然后跳转到内核入口中,交由内核控制权,uboot严重依赖硬件,因此一个通用的uboot不太可能. 移植原理:uboot中有很多平行代码,各自属于各

netty 源码简单分析一

周末简单看了下netty5的源码,只看懂了个大概,记录下成果,方便下次再看的时候回忆. 上服务端代码: public void run() throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.grou

MapReduce job在JobTracker初始化源码级分析

mapreduce job提交流程源码级分析(三)中已经说明用户最终调用JobTracker.submitJob方法来向JobTracker提交作业.而这个方法的核心提交方法是JobTracker.addJob(JobID jobId, JobInProgress job)方法,这个addJob方法会把Job提交到调度器(默认是JobQueueTaskScheduler)的监听器JobQueueJobInProgressListener和EagerTaskInitializationListen

OpenStack_Swift源码分析——创建Ring及添加设备源码详细分析

1 创建Ring 代码详细分析 在OpenStack_Swift--Ring组织架构中我们详细分析了Ring的具体工作过程,下面就Ring中增加设备,删除设备,已经重新平衡的实现过程作详细的介绍. 首先看RingBuilder类 def __init__(self, part_power, replicas, min_part_hours): #why 最大 2**32 if part_power > 32: raise ValueError("part_power must be at