FreeSWITCH添加自定義endpoint之媒體交互

来源:https://www.cnblogs.com/MikeZhang/archive/2023/08/06/fsAddEndpoint20230806.html
-Advertisement-
Play Games

操作系統 :CentOS 7.6_x64 FreeSWITCH版本 :1.10.9 之前寫過FreeSWITCH添加自定義endpoint的文章: https://www.cnblogs.com/MikeZhang/p/fsAddEndpoint20230528.html 今天記錄下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):

函數調用鏈:

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

 函數調用鏈:

uuid_bridge_function => switch_ivr_uuid_bridge

2、bridge機制

註冊回調函數:

 狀態機裡面進行回調, 當channel進入CS_EXCHANGE_MEDIA狀態後,回調 audio_bridge_on_exchange_media 函數,觸發audio_bridge_thread線程。

三、媒體交互流程 

1、註冊編解碼類型

通過 switch_core_codec_add_implementation 註冊編解碼。

添加PCMA編碼:

 添加opus編碼:

 

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 

 音頻數據會轉成L16編碼(raw格式),然後再編碼成目標編碼,示意圖如下:

 具體可參考各個編碼的 encode 和 decode 代碼(添加編碼時的註釋也可參考下):

四、自定義endpoint集成媒體交互示例 

1、產生舒適噪音

產生舒適噪音,避免沒有rtp導致的掛機。

1)需要設置 SFF_CNG 標誌;
具體可參考 loopback 模塊: 

 2)需要設置通道變數 bridge_generate_comfort_noise 為 true:

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

或者在orginate字元串中設置。

3)audio_bridge_thread函數裡面有舒適噪音處理相關邏輯;

 

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函數實現:

完整代碼可從如下渠道獲取:

關註微信公眾號(聊聊博文,文末可掃碼)後回覆 20230806 獲取。

4、運行效果

1)編譯及安裝

 2)呼叫效果

測試命令:

originate user/1000 &bridge(ctest/1001)

運行效果:

這裡的raw文件採用之前文章裡面的示例(test1.raw),如何生成請參考:

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

endpoint模塊集成媒體交互功能的編譯及運行效果視頻:

關註微信公眾號(聊聊博文,文末可掃碼)後回覆 2023080601 獲取。

五、資源下載

本文涉及源碼和文件,可從如下途徑獲取:

關註微信公眾號(聊聊博文,文末可掃碼)後回覆 20230806 獲取。

 

  微信公眾號:

  • E-Mail : [email protected]
  • 轉載請註明出處,謝謝!
    您的分享是我們最大的動力!

    -Advertisement-
    Play Games
    更多相關文章
    • 一.前言 從上個世紀到現在,工程師們在優化伺服器性能的過程中,提出了各種不同的io模型,比如非阻塞io,io復用,信號驅動式io,非同步io。具體io模型在不同平臺上的實現也不一樣,比如io復用在bsd上可以由kqueue實現,在solaris系統上可以由/dev/poll實現。為了實現系統的可移植性 ...
    • 分庫分表是大型互聯網應用經常採用的一種數據層優化方案,常見的分庫分表中間件如 sharding-jdbc、mycat 都已經比較成熟,基本上可以應對我們一般的分庫分表需求。 做過分庫分表的同學應該知道,在給業務系統做分庫分表改造過程中,難的不是如何使用這些組件進行分庫分表,而是如何將非分庫分表的系... ...
    • 因歷史遺留原因,接手的項目沒有代碼提醒/格式化,包括 eslint、pretttier,也沒有 commit 提交校驗,如 husky、commitlint、stylelint,與其期待自己或者同事的代碼寫得完美無缺,不如通過一些工具來進行規範和約束。 ### eslint eslint 是一個代碼 ...
    • # 引言 這幾天幫朋友忙,用了一周時間,高仿了一個釘釘審批流。這個東西會有不少朋友有類似需求,就分享出來,希望能有所幫助。為了方便朋友的使用,設計製作的時候,儘量做到節點配置可定製,減少集成成本。如果您的項目有審批流需求,這個項目可以直接拿過去使用。React初學者也可以把本項目當做研讀案例,學習並 ...
    • ![](https://img2023.cnblogs.com/blog/3076680/202308/3076680-20230804112759115-773698620.png) # 1. 配置服務 ## 1.1. 配置服務本身就是分散式資料庫 ### 1.1.1. 像ZooKeeper和et ...
    • 隨著硬體技術的飛速發展,多核處理器已經成為計算設備的標配,這使得開發人員需要掌握併發編程的知識和技巧,以充分發揮多核處理器的潛力。然而併發編程並非易事,它涉及到許多複雜的概念和原理。為了更好地理解併發編程的內在機制,需要深入研究記憶體模型及其在併發編程中的應用。本文將主要以 Java 記憶體模型來探討並 ...
    • 最近看到一個冷門的資料庫管理工具:**slashbase**。 這個工具是開源免費的,由於開源不久,目前才900+的Star,但用下來還是非常不錯的,DD覺得這款工具還是非常有潛力的,所以給大家推薦一下。 ## 主要特性 [**slashbase**](https://blog.didispace. ...
    • # 最全的零基礎Flask教程 ## 1 Flask介紹 ### 1.1 為什麼要使用Flask Django和Flask是Python使用最多的兩個框架 ![image-20230802071519906](https://img2023.cnblogs.com/blog/2602103/2023 ...
    一周排行
      -Advertisement-
      Play Games
    • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
    • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
    • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
    • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
    • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
    • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
    • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
    • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
    • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
    • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...