操作系統 :CentOS 7.6_x64 FreeSWITCH版本 :1.10.9 日常開發過程中會遇到需要擴展FreeSWITCH對接其它系統的情況,這裡記錄下編寫FreeSWITCH自定義endpoint的過程。 一、模塊定義函數 使用FreeSWITCH自帶的框架來定義模塊函數,函數指針及參數 ...
操作系統 :CentOS 7.6_x64 FreeSWITCH版本 :1.10.9 日常開發過程中會遇到需要擴展FreeSWITCH對接其它系統的情況,這裡記錄下編寫FreeSWITCH自定義endpoint的過程。
一、模塊定義函數
使用FreeSWITCH自帶的框架來定義模塊函數,函數指針及參數列表定義如下(src/include/switch_types.h)#define SWITCH_MODULE_LOAD_ARGS (switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool) #define SWITCH_MODULE_RUNTIME_ARGS (void) #define SWITCH_MODULE_SHUTDOWN_ARGS (void) typedef switch_status_t (*switch_module_load_t) SWITCH_MODULE_LOAD_ARGS; typedef switch_status_t (*switch_module_runtime_t) SWITCH_MODULE_RUNTIME_ARGS; typedef switch_status_t (*switch_module_shutdown_t) SWITCH_MODULE_SHUTDOWN_ARGS; #define SWITCH_MODULE_LOAD_FUNCTION(name) switch_status_t name SWITCH_MODULE_LOAD_ARGS #define SWITCH_MODULE_RUNTIME_FUNCTION(name) switch_status_t name SWITCH_MODULE_RUNTIME_ARGS #define SWITCH_MODULE_SHUTDOWN_FUNCTION(name) switch_status_t name SWITCH_MODULE_SHUTDOWN_ARGS
1、模塊載入
SWITCH_MODULE_LOAD_FUNCTION 模塊載入函數,負責系統啟動時或運行時載入模塊,可以進行配置讀取及資源初始化。2、模塊卸載
SWITCH_MODULE_SHUTDOWN_FUNCTION 模塊卸載函數,負載模塊卸載及相關資源回收。3、模塊運行時
SWITCH_MODULE_RUNTIME_FUNCTION 模塊運行時函數,可以啟動線程處理請求,監聽socket等。4、模塊定義
SWITCH_MODULE_DEFINITION 相關代碼:typedef struct switch_loadable_module_function_table { int switch_api_version; switch_module_load_t load; switch_module_shutdown_t shutdown; switch_module_runtime_t runtime; switch_module_flag_t flags; } switch_loadable_module_function_table_t; #define SWITCH_MODULE_DEFINITION_EX(name, load, shutdown, runtime, flags) \ static const char modname[] = #name ; \ SWITCH_MOD_DECLARE_DATA switch_loadable_module_function_table_t name##_module_interface = { \ SWITCH_API_VERSION, \ load, \ shutdown, \ runtime, \ flags \ } #define SWITCH_MODULE_DEFINITION(name, load, shutdown, runtime) \ SWITCH_MODULE_DEFINITION_EX(name, load, shutdown, runtime, SMODF_NONE)
二、模塊載入流程
FreeSWITCH使用 switch_loadable_module_load_module 或 switch_loadable_module_load_module_ex 進行模塊載入,具體實現邏輯可以在 switch_loadable_module.c 中查看,這裡做下簡單介紹。1、模塊載入函數
通過 switch_loadable_module_load_module 函數載入模塊,函數調用鏈如下:switch_loadable_module_load_module => switch_loadable_module_load_module_ex => switch_loadable_module_load_file => switch_loadable_module_process => switch_core_launch_thread => switch_loadable_module_exec通過 switch_dso_data_sym 根據定義的 XXX_module_interface 從動態庫裡面獲取回調函數指針,使用 switch_loadable_module_function_table_t 數據結構進行回調函數綁定。 switch_dso_data_sym 函數實現如下(src/switch_dso.c):
void *switch_dso_data_sym(switch_dso_lib_t lib, const char *sym, char **err) { void *addr = dlsym(lib, sym); if (!addr) { char *err_str = NULL; dlerror(); if (!(addr = dlsym(lib, sym))) { err_str = (char *)dlerror(); } if (err_str) { *err = strdup(err_str); } } return addr; }switch_loadable_module_exec函數:
static void *SWITCH_THREAD_FUNC switch_loadable_module_exec(switch_thread_t *thread, void *obj) { switch_status_t status = SWITCH_STATUS_SUCCESS; switch_core_thread_session_t *ts = obj; switch_loadable_module_t *module = ts->objs[0]; int restarts; switch_assert(thread != NULL); switch_assert(module != NULL); for (restarts = 0; status != SWITCH_STATUS_TERM && !module->shutting_down; restarts++) { status = module->switch_module_runtime(); } switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "Thread ended for %s\n", module->module_interface->module_name); if (ts->pool) { switch_memory_pool_t *pool = ts->pool; switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Destroying Pool for %s\n", module->module_interface->module_name); switch_core_destroy_memory_pool(&pool); } switch_thread_exit(thread, 0); return NULL; }switch_loadable_module_exec 函數為獨立線程中運行,模塊運行時通過 module->switch_module_runtime() 觸發。
2、FreeSWITCH啟動時載入模塊
1)整體結構函數調用鏈如下:
main => switch_core_init_and_modload => switch_core_init => switch_loadable_module_init => switch_loadable_module_load_modulemain函數在switch.c中實現。 2)載入順序 先載入系統核心模塊:
switch_loadable_module_load_module_ex("", "CORE_SOFTTIMER_MODULE", SWITCH_FALSE, SWITCH_FALSE, &err, SWITCH_LOADABLE_MODULE_TYPE_COMMON, event_hash); switch_loadable_module_load_module_ex("", "CORE_PCM_MODULE", SWITCH_FALSE, SWITCH_FALSE, &err, SWITCH_LOADABLE_MODULE_TYPE_COMMON, event_hash); switch_loadable_module_load_module_ex("", "CORE_SPEEX_MODULE", SWITCH_FALSE, SWITCH_FALSE, &err, SWITCH_LOADABLE_MODULE_TYPE_COMMON, event_hash);使用 switch_xml_open_cfg 函數(src/switch_xml.c中定義)先後載入以下文件中定義的模塊: pre_load_modules.conf modules.conf post_load_modules.conf 具體格式參考 conf/autoload_configs/modules.conf.xml 3)xml載入過程 函數調用鏈如下:
main => switch_core_init_and_modload => switch_core_init => switch_xml_init => switch_xml_open_root => XML_OPEN_ROOT_FUNCTION其中 SWITCH_GLOBAL_filenames 變數定義如下(main => switch_core_set_globals):
if (!SWITCH_GLOBAL_filenames.conf_name && (SWITCH_GLOBAL_filenames.conf_name = (char *) malloc(BUFSIZE))) { switch_snprintf(SWITCH_GLOBAL_filenames.conf_name, BUFSIZE, "%s", "freeswitch.xml"); }XML_OPEN_ROOT_FUNCTION實現如下(src/switch_xml.c):
static switch_xml_open_root_function_t XML_OPEN_ROOT_FUNCTION = (switch_xml_open_root_function_t)__switch_xml_open_root; SWITCH_DECLARE_NONSTD(switch_xml_t) __switch_xml_open_root(uint8_t reload, const char **err, void *user_data) { char path_buf[1024]; uint8_t errcnt = 0; switch_xml_t new_main, r = NULL; if (MAIN_XML_ROOT) { if (!reload) { r = switch_xml_root(); goto done; } } switch_snprintf(path_buf, sizeof(path_buf), "%s%s%s", SWITCH_GLOBAL_dirs.conf_dir, SWITCH_PATH_SEPARATOR, SWITCH_GLOBAL_filenames.conf_name); if ((new_main = switch_xml_parse_file(path_buf))) { *err = switch_xml_error(new_main); switch_copy_string(not_so_threadsafe_error_buffer, *err, sizeof(not_so_threadsafe_error_buffer)); *err = not_so_threadsafe_error_buffer; if (!zstr(*err)) { switch_xml_free(new_main); new_main = NULL; errcnt++; } else { *err = "Success"; switch_xml_set_root(new_main); } } else { *err = "Cannot Open log directory or XML Root!"; errcnt++; } if (errcnt == 0) { r = switch_xml_root(); } done: return r; }freeswitch.xml 為xml文件的總入口,配置的有載入各個模塊的數據:
<section name="configuration" description="Various Configuration"> <X-PRE-PROCESS cmd="include" data="autoload_configs/*.xml"/> </section>
3、控制台動態載入
在fs_cli中可以使用load及reload載入模塊,具體流程如下:fs_cli => load ... => SWITCH_STANDARD_API(load_function) => switch_loadable_module_load_module
fs_cli => reload ... => SWITCH_STANDARD_API(reload_function) => switch_loadable_module_unload_module
=> switch_loadable_module_load_module
三、關鍵數據結構
1、switch_loadable_module_t
作用:用於定義模塊信息。 結構體定義:struct switch_loadable_module { char *key; char *filename; int perm; switch_loadable_module_interface_t *module_interface; switch_dso_lib_t lib; switch_module_load_t switch_module_load; switch_module_runtime_t switch_module_runtime; switch_module_shutdown_t switch_module_shutdown; switch_memory_pool_t *pool; switch_status_t status; switch_thread_t *thread; switch_bool_t shutting_down; switch_loadable_module_type_t type; }; typedef struct switch_loadable_module switch_loadable_module_t;欄位解釋: key =》 模塊文件名稱 filename => 模塊文件路徑(動態庫路徑) perm =》 定義模塊是否允許被卸載 module_interface =》 模塊介面(由switch_module_load函數賦值) lib =》 動態庫句柄(dlopen函數返回) switch_module_load =》 模塊載入函數 switch_module_runtime =》 模塊運行時函數 switch_module_shutdown =》 模塊關閉(卸載)函數 pool =》 模塊記憶體池 status =》 switch_module_shutdown 函數的返回值 shutting_down => 模塊是否關閉
2、switch_loadable_module_interface
作用: 模塊介面(入口) 結構體定義:struct switch_loadable_module_interface { /*! the name of the module */ const char *module_name; /*! the table of endpoints the module has implemented */ switch_endpoint_interface_t *endpoint_interface; /*! the table of timers the module has implemented */ switch_timer_interface_t *timer_interface; /*! the table of dialplans the module has implemented */ switch_dialplan_interface_t *dialplan_interface; /*! the table of codecs the module has implemented */ switch_codec_interface_t *codec_interface; /*! the table of applications the module has implemented */ switch_application_interface_t *application_interface; /*! the table of chat applications the module has implemented */ switch_chat_application_interface_t *chat_application_interface; /*! the table of api functions the module has implemented */ switch_api_interface_t *api_interface; /*! the table of json api functions the module has implemented */ switch_json_api_interface_t *json_api_interface; /*! the table of file formats the module has implemented */ switch_file_interface_t *file_interface; /*! the table of speech interfaces the module has implemented */ switch_speech_interface_t *speech_interface; /*! the table of directory interfaces the module has implemented */ switch_directory_interface_t *directory_interface; /*! the table of chat interfaces the module has implemented */ switch_chat_interface_t *chat_interface; /*! the table of say interfaces the module has implemented */ switch_say_interface_t *say_interface; /*! the table of asr interfaces the module has implemented */ switch_asr_interface_t *asr_interface; /*! the table of management interfaces the module has implemented */ switch_management_interface_t *management_interface; /*! the table of limit interfaces the module has implemented */ switch_limit_interface_t *limit_interface; /*! the table of database interfaces the module has implemented */ switch_database_interface_t *database_interface; switch_thread_rwlock_t *rwlock; int refs; switch_memory_pool_t *pool; }; typedef struct switch_loadable_module_interface switch_loadable_module_interface_t;欄位解釋: module_name => 模塊的名稱 endpoint_interface => 模塊endpoint的具體實現 timer_interface => 模塊timer的具體實現 dialplan_interface => 模塊dialplan的具體實現 codec_interface => 模塊編解碼的具體實現 application_interface => 模塊提供的app工具的具體實現 chat_application_interface => 模塊提供的文本聊天app工具的具體實現 api_interface => 模塊提供的api具體實現 json_api_interface => 模塊提供的json格式api的具體實現 file_interface => 模塊支持的文件格式的具體實現(比如mp4、mkv等文件格式) speech_interface => 模塊使用的speech介面實現 directory_interface => 模塊使用的directory介面實現 chat_interface => 模塊使用的chat介面實現 say_interface => 模塊使用的say介面實現 asr_interface => 模塊使用的asr介面實現 management_interface => 模塊使用的管理介面實現 limit_interface => 模塊使用的limit介面實現 database_interface => 模塊使用的limit介面實現 rwlock => 模塊使用的鎖 refs => 模塊鎖的計數器 pool =》 模塊記憶體池 使用 switch_loadable_module_create_module_interface 來創建 switch_loadable_module_interface_t 實例。
SWITCH_DECLARE(switch_loadable_module_interface_t *) switch_loadable_module_create_module_interface(switch_memory_pool_t *pool, const char *name) { switch_loadable_module_interface_t *mod; mod = switch_core_alloc(pool, sizeof(switch_loadable_module_interface_t)); switch_assert(mod != NULL); mod->pool = pool; mod->module_name = switch_core_strdup(mod->pool, name); switch_thread_rwlock_create(&mod->rwlock, mod->pool); return mod; }使用 switch_loadable_module_create_interface 來創建模塊裡面的子介面,示例如下:
*module_interface = switch_loadable_module_create_module_interface(pool, modname); rtc_endpoint_interface = switch_loadable_module_create_interface(*module_interface, SWITCH_ENDPOINT_INTERFACE); rtc_endpoint_interface->interface_name = "rtc"; rtc_endpoint_interface->io_routines = &rtc_io_routines; rtc_endpoint_interface->state_handler = &rtc_event_handlers; rtc_endpoint_interface->recover_callback = rtc_recover_callback;具體實現如下:
SWITCH_DECLARE(void *) switch_loadable_module_create_interface(switch_loadable_module_interface_t *mod, switch_module_interface_name_t iname) { switch (iname) { case SWITCH_ENDPOINT_INTERFACE: ALLOC_INTERFACE(endpoint) case SWITCH_TIMER_INTERFACE: ALLOC_INTERFACE(timer) case SWITCH_DIALPLAN_INTERFACE: ALLOC_INTERFACE(dialplan) case SWITCH_CODEC_INTERFACE: ALLOC_INTERFACE(codec) case SWITCH_APPLICATION_INTERFACE: ALLOC_INTERFACE(application) case SWITCH_CHAT_APPLICATION_INTERFACE: ALLOC_INTERFACE(chat_application) case SWITCH_API_INTERFACE: ALLOC_INTERFACE(api) case SWITCH_JSON_API_INTERFACE: ALLOC_INTERFACE(json_api) case SWITCH_FILE_INTERFACE: ALLOC_INTERFACE(file) case SWITCH_SPEECH_INTERFACE: ALLOC_INTERFACE(speech) case SWITCH_DIRECTORY_INTERFACE: ALLOC_INTERFACE(directory) case SWITCH_CHAT_INTERFACE: ALLOC_INTERFACE(chat) case SWITCH_SAY_INTERFACE: ALLOC_INTERFACE(say) case SWITCH_ASR_INTERFACE: ALLOC_INTERFACE(asr) case SWITCH_MANAGEMENT_INTERFACE: ALLOC_INTERFACE(management) case SWITCH_LIMIT_INTERFACE: ALLOC_INTERFACE(limit) case SWITCH_DATABASE_INTERFACE: ALLOC_INTERFACE(database) default: switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Invalid Module Type!\n"); return NULL; } }
3、switch_endpoint_interface_t
作用:endpoint的入口 結構體定義:struct switch_endpoint_interface { /*! the interface's name */ const char *interface_name; /*! channel abstraction methods */ switch_io_routines_t *io_routines; /*! state machine methods */ switch_state_handler_table_t *state_handler; /*! private information */ void *private_info; switch_thread_rwlock_t *rwlock; int refs; switch_mutex_t *reflock; /* parent */ switch_loadable_module_interface_t *parent; /* to facilitate linking */ struct switch_endpoint_interface *next; switch_core_recover_callback_t recover_callback; }; typedef struct switch_endpoint_interface switch_endpoint_interface_t;欄位解釋: interface_name => endpoint名稱,比如:"rtc" io_routines => endpoint對應的io操作回調函數 state_handler => endpoint對應的事件處理回調函數 private_info => endpoint私有參數配置(比如編碼格式、採樣率等) rwlock => endpoint鎖 refs => endpoint鎖的引用次數 reflock => endpoint引用鎖 parent => endpoint所屬模塊 next => next指針 recover_callback => endpoint對應的recover回調函數
4、switch_io_routines
作用:存儲io操作的回調函數 結構體定義:struct switch_io_routines { /*! creates an outgoing session from given session, caller profile */ switch_io_outgoing_channel_t outgoing_channel; /*! read a frame from a session */ switch_io_read_frame_t read_frame; /*! write a frame to a session */ switch_io_write_frame_t write_frame; /*! send a kill signal to the session's channel */ switch_io_kill_channel_t kill_channel; /*! send a string of DTMF digits to a session's channel */ switch_io_send_dtmf_t send_dtmf; /*! receive a message from another session */ switch_io_receive_message_t receive_message; /*! queue a message for another session */ switch_io_receive_event_t receive_event; /*! change a sessions channel state */ switch_io_state_change_t state_change; /*! read a video frame from a session */ switch_io_read_video_frame_t read_video_frame; /*! write a video frame to a session */ switch_io_write_video_frame_t write_video_frame; /*! read a video frame from a session */ switch_io_read_text_frame_t read_text_frame; /*! write a video frame to a session */ switch_io_write_text_frame_t write_text_frame; /*! change a sessions channel run state */ switch_io_state_run_t state_run; /*! get sessions jitterbuffer */ switch_io_get_jb_t get_jb; void *padding[10]; }; typedef struct switch_io_routines switch_io_routines_t;欄位解釋: outgoing_channel => 創建外呼channel的回調函數 read_frame => 讀session音頻數據的回調函數 write_frame => 寫session音頻數據的回調函數 kill_channel => kill信號處理函數,用於處理channel接收的kill信號 send_dtmf => send dtmf操作的回調函數,用於處理channel接收的DTMF字元串 receive_message => 處理channel消息的回調函數,用於處理其它channel發來的消息 receive_event => 發送channel消息的回調函數,用於向目標session發送自定義事件(比如rtc session、rtmp session等) state_change => channel狀態修改的回調函數 read_video_frame => 讀session視頻數據的回調函數 write_video_frame => 寫session視頻數據的回調函數 read_text_frame => 讀session文本數據的回調函數 write_text_frame => 寫session文本數據的回調函數 state_run => 改變session的運行狀態,目前沒見到有endpoint使用過 get_jb => 獲取session的jitter_buffer
5、switch_state_handler_table_t
作用:用於存儲狀態機的回調函數。 定義如下:struct switch_state_handler_table { /*! executed when the state changes to init */ switch_state_handler_t on_init; /*! executed when the state changes to routing */ switch_state_handler_t on_routing; /*! executed when the state changes to execute */ switch_state_handler_t on_execute; /*! executed when the state changes to hangup */ switch_state_handler_t on_hangup; /*! executed when the state changes to exchange_media */ switch_state_handler_t on_exchange_media; /*! executed when the state changes to soft_execute */ switch_state_handler_t on_soft_execute; /*! executed when the state changes to consume_media */ switch_state_handler_t on_consume_media; /*! executed when the state changes to hibernate */ switch_state_handler_t on_hibernate; /*! executed when the state changes to reset */ switch_state_handler_t on_reset; /*! executed when the state changes to park */ switch_state_handler_t on_park; /*! executed when the state changes to reporting */ switch_state_handler_t on_reporting; /*! executed when the state changes to destroy */ switch_state_handler_t on_destroy; int flags; void *padding[10]; }; typedef struct switch_state_handler_table switch_state_handler_table_t;參數解釋: on_init => channel進入 CS_INIT 狀態的回調函數 on_routing => channel進入 CS_ROUTING 狀態的回調函數 on_execute => channel進入 CS_EXECUTE 狀態的回調函數,用於執行操作 on_hangup => channel進入 CS_HANGUP 狀態的回調函數 on_exchange_media => channel進入 CS_EXCHANGE_MEDIA 狀態的回調函數 on_soft_execute => channel進入 CS_SOFT_EXECUTE 狀態的回調函數,用於從其它channel接收或發送數據 on_consume_media => channel進入 CS_CONSUME_MEDIA 狀態的回調函數, on_hibernate => channel進入 CS_HIBERNATE 狀態的回調函數,sleep操作 on_reset => channel進入 CS_RESET 狀態的回調函數 on_park => channel進入 CS_PARK 狀態的回調函數 on_reporting => channel進入 CS_REPORTING 狀態的回調函數 on_destroy => channel進入 CS_DESTROY 狀態的回調函數 switch_core_state_machine.c中使用 STATE_MACRO 觸發,部分觸發代碼如下:
case CS_ROUTING: /* Look for a dialplan and find something to do */ STATE_MACRO(routing, "ROUTING"); break; case CS_RESET: /* Reset */ STATE_MACRO(reset, "RESET"); break; /* These other states are intended for prolonged durations so we do not signal lock for them */ case CS_EXECUTE: /* Execute an Operation */ STATE_MACRO(execute, "EXECUTE"); break; case CS_EXCHANGE_MEDIA: /* loop all data back to source */ STATE_MACRO(exchange_media, "EXCHANGE_MEDIA"); break; case CS_SOFT_EXECUTE: /* send/recieve data to/from another channel */ STATE_MACRO(soft_execute, "SOFT_EXECUTE"); break; case CS_PARK: /* wait in limbo */ STATE_MACRO(park, "PARK"); break; case CS_CONSUME_MEDIA: /* wait in limbo */ STATE_MACRO(consume_media, "CONSUME_MEDIA"); break; case CS_HIBERNATE: /* sleep */ STATE_MACRO(hibernate, "HIBERNATE"); break;
四、模塊編寫示例
1、編寫c風格的endpoint模塊
仿照mod_rtc模塊編寫,核心文件只有兩個: mod_rtc.c Makefile.am 1)複製mod_artc目錄 cp mod_rtc mod_ctest -r2)修改文件名
mv mod_rtc.c mod_ctest.c 3)修改文件內容,將rtc關鍵字替換成ctest4)修改編譯選項
文件: freeswitch-1.10.9.-release/configure.ac 仿照rtc模塊,添加ctest模塊內容: src/mod/endpoints/mod_ctest/Makefile5)開啟模塊編譯 文件:freeswitch-1.10.9.-release/modules.conf 仿照rtc模塊,添加ctest模塊編譯: endpoints/mod_ctest
6)生成Makefile ./rebootstrap.sh && ./configure
7)安裝模塊
在 freeswitch-1.10.9.-release 根目錄(或mod_ctest目錄)執行如下指令: make && make install8)載入模塊
文件:conf/autoload_configs/modules.conf.xml 添加如下內容: 9)模塊測試 控制台載入測試: reload mod_ctestc風格endpoint模塊編譯及運行效果視頻:
關註微信公眾號(聊聊博文,文末可掃碼)後回覆 2023052801 獲取。
2、編寫c++風格的endpoint模塊
仿照mod_h323模塊編寫,目錄結構、編譯等參考c風格endpoint模塊編寫部分,關鍵點描述可以從如下渠道獲取: 關註微信公眾號(聊聊博文,文末可掃碼)後回覆 20230528 獲取。 載入效果如下:c++風格endpoint模塊編譯及運行效果視頻:
關註微信公眾號(聊聊博文,文末可掃碼)後回覆 2023052802 獲取。
五、資源下載
本文涉及源碼和文件,可以從如下途徑獲取:
關註微信公眾號(聊聊博文,文末可掃碼)後回覆 20230528 獲取。
微信公眾號: