FreeSWITCH添加自定义endpoint之媒体交互

这篇具有很好参考价值的文章主要介绍了FreeSWITCH添加自定义endpoint之媒体交互。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

操作系统 :CentOS 7.6_x64
FreeSWITCH版本 :1.10.9
 
之前写过FreeSWITCH添加自定义endpoint的文章:
https://www.cnblogs.com/MikeZhang/p/fsAddEndpoint20230528.html
今天记录下endpoint媒体交互的过程并提供示例代码及相关资源下载,本文涉及示例代码和资源可从如下渠道获取:
关注微信公众号(聊聊博文,文末可扫码)后回复 20230806 获取。

一、originate流程 

1、originate命令的使用

originate用于发起呼叫,命令使用的基础模板:

originate ALEG BLEG

在fs_cli控制台使用的完整语法如下:

originate <call url> <exten>|&<application_name>(<app_args>) [<dialplan>][&lt;context>] [<cid_name>][&lt;cid_num>] [<timeout_sec>]
其中,
originate 为命令关键字,为必选字段,用于定义ALEG的呼叫信息,也就是通常说的呼叫字符串,可以通过通道变量定义很多参数;
|&<application_name>(<app_args>)  为必选字段,用于指定BLEG的分机号码或者用于创建BLEG的app(比如echo、bridge等);
[][<context>]  可选参数,该参数用于指定dialplan的context,默认值:xml default ;
[<timeout_sec>] 可选参数,该参数用于指定originate超时,默认值:60 ;
 
这里以分机进行示例呼叫:
originate user/1000 9196 xml default 'user1' 13012345678 
更多使用方法可参考我之前写的文章:
https://www.cnblogs.com/MikeZhang/p/originate20230402.html

2、originate功能入口函数

入口函数为originate_function,在 mod_commands_load 中绑定:

SWITCH_ADD_API(commands_api_interface, "originate", "Originate a call", originate_function, ORIGINATE_SYNTAX);
具体实现如下:
#define ORIGINATE_SYNTAX "<call url> <exten>|&<application_name>(<app_args>) [<dialplan>] [<context>] [<cid_name>] [<cid_num>] [<timeout_sec>]"
SWITCH_STANDARD_API(originate_function)
{
    switch_channel_t *caller_channel;
    switch_core_session_t *caller_session = NULL;
    char *mycmd = NULL, *argv[10] = { 0 };
    int i = 0, x, argc = 0;
    char *aleg, *exten, *dp, *context, *cid_name, *cid_num;
    uint32_t timeout = 60;
    switch_call_cause_t cause = SWITCH_CAUSE_NORMAL_CLEARING;
    switch_status_t status = SWITCH_STATUS_SUCCESS;

    if (zstr(cmd)) {
        stream->write_function(stream, "-USAGE: %s\n", ORIGINATE_SYNTAX);
        return SWITCH_STATUS_SUCCESS;
    }

    /* log warning if part of ongoing session, as we'll block the session */
    if (session){
        switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_NOTICE, "Originate can take 60 seconds to complete, and blocks the existing session. Do not confuse with a lockup.\n");
    }

    mycmd = strdup(cmd);
    switch_assert(mycmd);
    argc = switch_separate_string(mycmd, ' ', argv, (sizeof(argv) / sizeof(argv[0])));

    if (argc < 2 || argc > 7) {
        stream->write_function(stream, "-USAGE: %s\n", ORIGINATE_SYNTAX);
        goto done;
    }

    for (x = 0; x < argc && argv[x]; x++) {
        if (!strcasecmp(argv[x], "undef")) {
            argv[x] = NULL;
        }
    }

    aleg = argv[i++];
    exten = argv[i++];
    dp = argv[i++];
    context = argv[i++];
    cid_name = argv[i++];
    cid_num = argv[i++];

    switch_assert(exten);

    if (!dp) {
        dp = "XML";
    }

    if (!context) {
        context = "default";
    }

    if (argv[6]) {
        timeout = atoi(argv[6]);
    }

    if (switch_ivr_originate(NULL, &caller_session, &cause, aleg, timeout, NULL, cid_name, cid_num, NULL, NULL, SOF_NONE, NULL, NULL) != SWITCH_STATUS_SUCCESS
        || !caller_session) {
            stream->write_function(stream, "-ERR %s\n", switch_channel_cause2str(cause));
        goto done;
    }

    caller_channel = switch_core_session_get_channel(caller_session);

    if (*exten == '&' && *(exten + 1)) {
        switch_caller_extension_t *extension = NULL;
        char *app_name = switch_core_session_strdup(caller_session, (exten + 1));
        char *arg = NULL, *e;

        if ((e = strchr(app_name, ')'))) {
            *e = '\0';
        }

        if ((arg = strchr(app_name, '('))) {
            *arg++ = '\0';
        }

        if ((extension = switch_caller_extension_new(caller_session, app_name, arg)) == 0) {
            switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_CRIT, "Memory Error!\n");
            abort();
        }
        switch_caller_extension_add_application(caller_session, extension, app_name, arg);
        switch_channel_set_caller_extension(caller_channel, extension);
        switch_channel_set_state(caller_channel, CS_EXECUTE);
    } else {
        switch_ivr_session_transfer(caller_session, exten, dp, context);
    }

    stream->write_function(stream, "+OK %s\n", switch_core_session_get_uuid(caller_session));

    switch_core_session_rwunlock(caller_session);

  done:
    switch_safe_free(mycmd);
    return status;
}
调用流程如下:
originate_function 
    => switch_ivr_originate 
        => switch_core_session_outgoing_channel 
            => endpoint_interface->io_routines->outgoing_channel
        => switch_core_session_thread_launch     

3、switch_ivr_originate函数

该函数用于发起具体的呼叫。

switch_ivr_originate函数定义:

SWITCH_DECLARE(switch_status_t) switch_ivr_originate(
    switch_core_session_t *session,
    switch_core_session_t **bleg,
    switch_call_cause_t *cause,
    const char *bridgeto,
    uint32_t timelimit_sec,
    const switch_state_handler_table_t *table,
    const char *cid_name_override,
    const char *cid_num_override,
    switch_caller_profile_t *caller_profile_override,
    switch_event_t *ovars, switch_originate_flag_t flags,
    switch_call_cause_t *cancel_cause,
    switch_dial_handle_t *dh)
参数解释:
session : 发起originate的channel,即 caller_channel , aleg
bleg : originate所在的leg,会在该函数赋值
cause : 失败原因,会在该函数赋值
bridgeto : bleg的呼叫字符串,只读
timelimit_sec :originate超时时间
table : bleg的状态机回调函数
cid_name_override : origination_caller_id_name,用于设置主叫名称
cid_num_override : origination_caller_id_number,用于设置主叫号码
caller_profile_override :主叫的profile
ovars : originate导出的通道变量(从aleg)
flags : originate flag 参数,一般为 SOF_NONE
cancel_cause :originate取消原因
dh : dial handle,功能类似呼叫字符串,可以设置多条leg同时originate
如果outgoing_channel执行成功,会发送SWITCH_EVENT_CHANNEL_OUTGOING事件;并且该channel会设置上CF_ORIGINATING标识位。
if (switch_event_create(&event, SWITCH_EVENT_CHANNEL_OUTGOING) == SWITCH_STATUS_SUCCESS) {
    switch_channel_event_set_data(peer_channel, event);
    switch_event_fire(&event);
}
使用 switch_core_session_thread_launch 启动线程创建session :
if (!switch_core_session_running(oglobals.originate_status[i].peer_session)) {
    if (oglobals.originate_status[i].per_channel_delay_start) {
        switch_channel_set_flag(oglobals.originate_status[i].peer_channel, CF_BLOCK_STATE);
    }
    switch_core_session_thread_launch(oglobals.originate_status[i].peer_session);
}

二、bridge流程 

1、流程入口

bridge app入口(mod_dptools.c):

FreeSWITCH添加自定义endpoint之媒体交互

函数调用链:

audio_bridge_function 
    => switch_ivr_signal_bridge
        => switch_ivr_multi_threaded_bridge 
            => audio_bridge_thread
uuid_bridge api入口(mod_commands.c):

FreeSWITCH添加自定义endpoint之媒体交互

 函数调用链:

uuid_bridge_function => switch_ivr_uuid_bridge

2、bridge机制

注册回调函数:

FreeSWITCH添加自定义endpoint之媒体交互

 状态机里面进行回调, 当channel进入CS_EXCHANGE_MEDIA状态后,回调 audio_bridge_on_exchange_media 函数,触发audio_bridge_thread线程。

三、媒体交互流程 

1、注册编解码类型

通过 switch_core_codec_add_implementation 注册编解码。

添加PCMA编码:

FreeSWITCH添加自定义endpoint之媒体交互

 添加opus编码:

FreeSWITCH添加自定义endpoint之媒体交互 

2、RTP数据交互及转码

函数调用链:

audio_bridge_on_exchange_media => audio_bridge_thread

收发音频数据:

audio_bridge_thread 
    => switch_core_session_read_frame
         => need_codec
         => switch_core_codec_decode (调用implement的encode进行转码操作,比如 switch_g711a_decode)
     => session->endpoint_interface->io_routines->read_frame 即: sofia_read_frame
         => switch_core_media_read_frame
        => switch_rtp_zerocopy_read_frame
            => rtp_common_read
            => read_rtp_packet
                  => switch_socket_recvfrom


audio_bridge_thread 
    => switch_core_session_write_frame
         => switch_core_session_start_audio_write_thread (ptime不一致时启动线程,有500长度的队列)
          => switch_core_codec_encode (调用implement的encode进行转码操作,比如 switch_g711u_encode)
     => perform_write
        => session->endpoint_interface->io_routines->write_frame 比如: sofia_write_frame
        => switch_core_media_write_frame
            => switch_rtp_write_frame
            => rtp_common_write 
                => switch_socket_sendto 

FreeSWITCH添加自定义endpoint之媒体交互

 音频数据会转成L16编码(raw格式),然后再编码成目标编码,示意图如下:

FreeSWITCH添加自定义endpoint之媒体交互

 具体可参考各个编码的 encode 和 decode 代码(添加编码时的注释也可参考下):

FreeSWITCH添加自定义endpoint之媒体交互FreeSWITCH添加自定义endpoint之媒体交互

四、自定义endpoint集成媒体交互示例 

1、产生舒适噪音

产生舒适噪音,避免没有rtp导致的挂机。

1)需要设置 SFF_CNG 标志;
具体可参考 loopback 模块: 

FreeSWITCH添加自定义endpoint之媒体交互

 2)需要设置通道变量 bridge_generate_comfort_noise 为 true:

switch_channel_set_variable(chan_a,"bridge_generate_comfort_noise","true");

或者在orginate字符串中设置。

3)audio_bridge_thread函数里面有舒适噪音处理相关逻辑;

FreeSWITCH添加自定义endpoint之媒体交互

 FreeSWITCH添加自定义endpoint之媒体交互

2、ptime保持一致

需要注意下编码的ptime值,当ptime不一致会触发freeswitch的缓存机制,进而导致运行过程中内存增加。

具体原理可从如下渠道获取:

关注微信公众号(聊聊博文,文末可扫码)后回复 20230806 获取。

3、示例代码

这里基于之前写的FreeSWITCH添加自定义endpoint的文章:

https://www.cnblogs.com/MikeZhang/p/fsAddEndpoint20230528.html

以 C 代码为示例,简单实现endpoint收发媒体功能,注意事项如下:
1)设置endpoint编码信息,这里使用L16编码,ptime为20ms;
2)桥接 sip 侧的leg,实现媒体互通;
3)这里用音频文件模拟 endpoint 发送媒体操作,通过 read_frame 函数发送给对端;
4)接收到sip侧的rtp数据(write_frame函数),可写入文件、通过socket发出去或直接丢弃(这里直接丢弃了);
5)不要轻易修改状态机;
6)需要注意数据的初始化和资源回收;
需要对channel进行answer,这里在ctest_on_consume_media函数实现:

FreeSWITCH添加自定义endpoint之媒体交互

完整代码可从如下渠道获取:

关注微信公众号(聊聊博文,文末可扫码)后回复 20230806 获取。

4、运行效果

1)编译及安装

FreeSWITCH添加自定义endpoint之媒体交互

 2)呼叫效果

测试命令:

originate user/1000 &bridge(ctest/1001)

运行效果:

FreeSWITCH添加自定义endpoint之媒体交互

这里的raw文件采用之前文章里面的示例(test1.raw),如何生成请参考:

https://www.cnblogs.com/MikeZhang/p/pcm20232330.html

endpoint模块集成媒体交互功能的编译及运行效果视频:

关注微信公众号(聊聊博文,文末可扫码)后回复 2023080601 获取。

五、资源下载

本文涉及源码和文件,可从如下途径获取:

关注微信公众号(聊聊博文,文末可扫码)后回复 20230806 获取。

FreeSWITCH添加自定义endpoint之媒体交互

 文章来源地址https://www.toymoban.com/news/detail-629062.html

 

到了这里,关于FreeSWITCH添加自定义endpoint之媒体交互的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • freeswitch 1.10.10-dev录音早期媒体卡通道的bug分析

    最近编译了fs 1.10.10-dev也就是 master版本(2023年7月6日) 给几个客户升级了一下,发现非常不稳定(每天都有几个通道卡在early状态),最近才有空来分析原因。 之前跑的是1.10.8 release 版本,从来没出现过这个问题,我把 1.10.8的代码和1.10.10-dev的代码整体对比了一下,整体改变不

    2024年02月14日
    浏览(21)
  • FreeSWITCH添加iLBC编码及转码

    操作系统 :CentOS 7.6_x64 FreeSWITCH版本 :1.10.9 从第三方库里下载指定版本: 如果下载过慢,可从如下途径获取: 关注微信公众号(聊聊博文,文末可扫码)后回复 20230416 获取。 编译及安装步骤如下: 安装成功:  如果遇到如下错误: libilbc目录下会自动生成libtool文件,将系统

    2023年04月16日
    浏览(30)
  • FreeSWITCH添加g729编码及pcap音频提取

    操作系统 : debian 11 (bullseye,docker)、Windows10_x64 FreeSWITCH版本 :1.10.9 Docker版本:23.0.6 Python 版本  :  3.9.2   日常工作中,有时候会遇到g729编码的相关内容,但FreeSWITCH默认是不支持g729编码转码的,今天记录下使用开源的 bcg729 进行g729转码的过程(本文仅作技术研究,商业使用

    2024年02月16日
    浏览(27)
  • 自定义Python版本ESL库访问FreeSWITCH

    环境:CentOS 7.6_x64 Python版本:3.9.12 FreeSWITCH版本 :1.10.9 ESL库是FreeSWITCH对外提供的接口,使用起来很方便,但该库是基于C语言实现的,Python使用该库的话需要使用源码进行编译。 如果使用系统自带的Python版本进行编译,过程会比较流畅,就不描述了。这里记录下使用自定义

    2023年04月25日
    浏览(27)
  • k8s自定义Endpoint实现内部pod访问外部应用

    endpoint除了可以暴露pod的IP和端口还可以代理到外部的ip和端口 使用场景 公司业务还还没有完成上云, 一部分云原生的,一部分是实体的 业务上云期间逐步实现上云,保证各个模块之间的解耦性 比如使用云数据库或者实体数据库服务器啥的,因为像数据库实现容器化的话在

    2024年01月25日
    浏览(41)
  • 无人直播系统:探索未来的交互式媒体体验

        随着科技的不断进步,无人直播系统作为一种新兴的媒体形式,正逐渐引起人们的关注和兴趣。它通过结合无人机、传感器技术和实时数据传输,实现了全新的交互式媒体体验。本文将深入探讨无人直播系统的定义、原理、技术应用以及对未来的影响。     一、无人直播

    2024年02月12日
    浏览(34)
  • 多模态交互:利用多媒体元素提高用户满意度

    作者:禅与计算机程序设计艺术 在智能手机、平板电脑等新型移动终端上,用户通过不同类型的输入方式(触摸屏、触控笔、键盘)进行交互,包括语音、文本、手势、动作、图像、视频等多种形式。不同类型的输入方式都可以为应用提供丰富的内容和服务,如信息搜索、购

    2024年02月13日
    浏览(43)
  • 威联通+Plex添加阿里网盘资源至媒体库的方法

    分三步: 挂载阿里网盘 将挂载的网盘挂载到本地,否则在Plex中找不到文件目录 Plex添加挂载到本地的文件目录 照文档操作: https://github.com/iranee/qnap-aliyunpan-webdav,这一步是在nas上起了一个阿里云盘的服务给后面使用。 更新使用Alist,支持多种网盘,查看官方文档,注意:

    2024年02月06日
    浏览(41)
  • PR剪辑视频做自媒体添加字幕快速方式(简单好用的pr视频字幕模板)

    如何选择合适的字幕添加进短视频呢?首先要先确定增加的视频风格,简约、商务、科技感、炫酷;再确定用途,注释、标记、语音翻译、引用、介绍;最后在相应的模板中挑选几个尝试,悬着一个最切合主题的使用,这里有几个视频字幕合集,可供参考。 16个科技未来数码

    2024年02月04日
    浏览(41)
  • Flutter中为控件添加交互

    stateful widget 是动态的. 用户可以和其交互 (例如输入一个表单、 或者移动一个slider滑块),或者可以随时间改变 (也许是数据改变导致的UI更新). Checkbox, Radio, Slider, InkWell, Form, and TextField 都是 stateful widgets, 他们都是 StatefulWidget的子类。 2.创建一个有状态的widget 要创建一个自定义

    2024年04月15日
    浏览(24)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包