FreeSWITCH内核开发

FreeSWITCH内核开发

1  环境

FreeSWITCH : FreeSWITCH-1.10.11-dev

OS: debian bullseye

gcc:10.2.1

gdb:10.1.99

2 FreeSWITCH启动流程

2.1 main函数的主要工作

FreeSWITCH在main函数中除了初始化异常处理程序,解析软交换启动参数(比如:-nc -nonat 参数和 -conf -mod -log -run  -db -scripts -htdocs -base  -storage -temp -cache -recordings -grammar -certs -sounds等工作目录)之外,其核心就是调用switch_core_init_and_modload()函数初始化FreeSWITCH内核以及加载外围模块。

switch_core_init_and_modload()函数调用完毕,也就是FreeSWITCH内核和外围模块启动完毕后,main函数会调用switch_core_runtime_loop()函数进入无限循环。对于后台启动的实例,它基本上什么都没做。对于从前台启动的系统,switch_core_runtime_loop()会调用switch_console_loop()函数以启动一个控制台接收用户的键盘输入并打印系统运行信息,比如:命令行输出和日志等。

当实例退出switch_core_runtime_loop()函数后,调用switch_core_destroy()函数清理系统资源。

2.2 switch_console_loop函数主要工作

switch_console_loop()函数分两种情况启动,一种是使用跨平台库libedit库接收用户按键并在控制台上打印信息,一种是直接启动while循环接收用户按键以及在控制台上打印信息。不通的是使用libedit库时,函数内部启动了console_thread()线程,在这里检测用户命令合法性,并将命令存入历史记录,以备将来再执行该命令时可以使用键盘上的箭头按键翻看历史命令。同样,使用非libedit库的switch_console_loop()函数中也实现了相同的功能。

二者最终都是调用switch_console_process()函数,在switch_console_process函数内部执行switch_console_execute()函数执行命令行解析,并最终调用switch_api_execute()函数,执行命令输入,并将执行结果存放到istream中,最后会被取出并打印到控制台上。

2.3 switch_api_execute函数主要工作

通过switch_loadable_module_get_api_interface()查找外面模块中实现的api接口。并执行api->function回调函数。外围模块加载参考后续章节。

2.4 switch_core_init_and_modload函数主要工作

  • 调用switch_core_init()函数初始FreeSWITCH内核。
  • 调用switch_load_network_lists()函数初始化FreeSWITCH工作网络。
  • 调用switch_loadable_module_init()函数外围模块。

2.4.1 switch_core_init函数主要工作

初始FreeSWITCH工作目录,初始化软交换机状态机,加载软交换核心配置文件switch.conf等。

2.4.2 switch_loadable_module_init函数主要工作

调用switch_xml_open_cfg和switch_loadable_module_load_module_ex函数分别加载pre_load_modules.conf,modules.conf,post_load_modules.conf三个配置文件中定义的模块。优先加载pre_load_modules.conf配置的模块,然后加载modules.conf配置的模块,最后加载post_load_modules.conf配置的模块。这些文件存放在conf/autoload_configs目录下。

其中switch_loadable_module_load_module_ex函数启动在软交换模块中SWITCH_MODULE_LOAD_FUNCTION、SWITCH_MODULE_RUNTIME_FUNCTION定义的load和runtime函数。 

2.5 switch_core_destroy函数主要工作

当实例退出switch_core_runtime_loop()函数后,main()函数调用switch_core_destroy()完成内核资源释放、外围模块卸载(调用switch_loadable_module_shutdown函数)等资源清理操作。

3 模块结构

3.1 模块定义函数

FreeSWITCH已经定义好模块的框架,开发者只需根据模块的框架结构实现自身业务即可。其中,预定义的模块函数,函数指针及参数列表定义在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
#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)
  • SWITCH_MODULE_LOAD_FUNCTION:模块加载函数,负责系统启动时或运行时加载模块,可以进行配置读取及资源初始化。
  • SWITCH_MODULE_SHUTDOWN_FUNCTION:模块卸载函数,负载模块卸载及相关资源回收。
  • SWITCH_MODULE_RUNTIME_FUNCTION:模块运行时函数,可以启动线程处理请求,监听socket等。
  • SWITCH_MODULE_DEFINITION:模块定义函数,告知系统核心加载模块时调用load函数进行初始化,启动新线程执行runtime,卸载时调用shutdown函数清理资源。

3.2 模块加载流程

通过 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_loadable_module_load_file()函数中通过switch_dso_data_sym()函数根据定义的 XXX_module_interface 从动态库里面获取回调函数指针,使用 switch_loadable_module_function_table_t 数据结构进行回调函数绑定。switch_loadable_module_exec 函数为独立线程中运行,模块运行时通过 module->switch_module_runtime触发。

所以整个模块加载过程如下:

main 
    => switch_core_init_and_modload 
        => switch_core_init
        => switch_loadable_module_init => switch_loadable_module_load_module

在switch_loadable_module_init()函数中首先加载系统核心模块:

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 ()函数依次加载下列文件中定义的模块:

  • pre_load_modules.conf
  • modules.conf
  • post_load_modules.conf

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实现如下:

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; }

SWITCH_GLOBAL_filenames.conf_name即为freeswitch.xml文件,这个文件是FreeSWITCH中xml文件的总入口。并在freeswitch.xml文件中配置有加载各个模块的参数:

<section name="configuration" description="Various Configuration">
    <X-PRE-PROCESS cmd="include" data="autoload_configs/*.xml"/>
</section>

 控制台动态加载流程,在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

3.3 关键数据结构

 3.3.1 switch_loadable_module_t

作用:用于定义模块信息。

结构体定义:

struct switch_loadable_module {
    char *key;                                                    //模块文件名
    char *filename;                                               //模块文件路径(动态库路径)
    int perm;                                                     //定义模块是否允许被卸载
    switch_loadable_module_interface_t *module_interface;         //模块接口(由switch_module_load函数赋值)
    switch_dso_lib_t lib;                                         //动态库句柄(dlopen函数返回)
    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_module_shutdown 函数的返回值
    switch_thread_t *thread;
    switch_bool_t shutting_down;                                  //模块是否关闭
    switch_loadable_module_type_t type;
};
typedef struct switch_loadable_module switch_loadable_module_t;

 3.3.2 switch_loadable_module_interface_t 

作用:模块接口(入口)。

结构体定义:

/*! \brief The abstraction of a loadable module 模块接口(入口) */
    struct switch_loadable_module_interface {
    /*! the name of the module 模块的名称 */
    const char *module_name;
    /*! the table of endpoints the module has implemented 模块endpoint的具体实现 */
    switch_endpoint_interface_t *endpoint_interface;
    /*! the table of timers the module has implemented  模块timer的具体实现 */
    switch_timer_interface_t *timer_interface;
    /*! the table of dialplans the module has implemented 模块dialplan的具体实现 */
    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 模块提供的app工具的具体实现 */
    switch_application_interface_t *application_interface;
    /*! the table of chat applications the module has implemented 模块提供的文本聊天app工具的具体实现 */
    switch_chat_application_interface_t *chat_application_interface;
    /*! the table of api functions the module has implemented 模块提供的api具体实现 */
    switch_api_interface_t *api_interface;
    /*! the table of json api functions the module has implemented 模块提供的json格式api的具体实现 */
    switch_json_api_interface_t *json_api_interface;
    /*! the table of file formats the module has implemented 模块支持的文件格式的具体实现(比如mp4、mkv等文件格式) */
    switch_file_interface_t *file_interface;
    /*! the table of speech interfaces the module has implemented  模块使用的speech接口实现 */
    switch_speech_interface_t *speech_interface;
    /*! the table of directory interfaces the module has implemented  模块使用的directory接口实现 */
    switch_directory_interface_t *directory_interface;
    /*! the table of chat interfaces the module has implemented 模块使用的chat接口实现 */
    switch_chat_interface_t *chat_interface;
    /*! the table of say interfaces the module has implemented 模块使用的say接口实现 */
    switch_say_interface_t *say_interface;
    /*! the table of asr interfaces the module has implemented  模块使用的asr接口实现 */
    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 模块使用的limit接口实现 */
    switch_limit_interface_t *limit_interface;
    /*! the table of database interfaces the module has implemented  模块使用的limit接口实现*/
    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;

使用 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.3.3 switch_endpoint_interface_t

作用:Endpoints入口。

结构体定义:

struct switch_endpoint_interface {
    /*! the interface's name */                   // endpoint名称,比如:"rtc"
    const char *interface_name; 
    /*! channel abstraction methods */             // endpoint对应的io操作回调函数
    switch_io_routines_t *io_routines; 
    /*! state machine methods */                   // endpoint对应的事件处理回调函数
    switch_state_handler_table_t *state_handler;
    /*! private information */                    //endpoint私有参数配置(比如编码格式、采样率等)
    void *private_info;
    switch_thread_rwlock_t *rwlock;               //endpoint锁
    int refs;                                     //endpoint锁的引用次数
    switch_mutex_t *reflock;                      //endpoint引用锁
    /* parent */
    switch_loadable_module_interface_t *parent;   //endpoint所属模块
    /* to facilitate linking */
    struct switch_endpoint_interface *next;        //next指针
    switch_core_recover_callback_t recover_callback; // endpoint对应的recover回调函数
};
typedef struct switch_endpoint_interface switch_endpoint_interface_t;

 3.3.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;         //创建外呼channel的回调函数
    /*! read a frame from a session */
    switch_io_read_frame_t read_frame;                    //读session音频数据的回调函数
    /*! write a frame to a session */
    switch_io_write_frame_t write_frame;                // 写session音频数据的回调函数
    /*! send a kill signal to the session's channel */
    switch_io_kill_channel_t kill_channel;                // kill信号处理函数,用于处理channel接收的kill信号
    /*! send a string of DTMF digits to a session's channel */
    switch_io_send_dtmf_t send_dtmf;                    //send dtmf操作的回调函数,用于处理channel接收的DTMF字符串
    /*! receive a message from another session */
    switch_io_receive_message_t receive_message;        // 处理channel消息的回调函数,用于处理其它channel发来的消息
    /*! queue a message for another session */
    switch_io_receive_event_t receive_event;            //发送channel消息的回调函数,用于向目标session发送自定义事件(比如rtc session、rtmp session等
    /*! change a sessions channel state */
    switch_io_state_change_t state_change;                //channel状态修改的回调函数
    /*! read a video frame from a session */
    switch_io_read_video_frame_t read_video_frame;        //读session视频数据的回调函数
    /*! write a video frame to a session */
    switch_io_write_video_frame_t write_video_frame;    //写session视频数据的回调函数
    /*! read a video frame from a session */
    switch_io_read_text_frame_t read_text_frame;        //读session文本数据的回调函数
    /*! write a video frame to a session */
    switch_io_write_text_frame_t write_text_frame;        //写session文本数据的回调函数
    /*! change a sessions channel run state */
    switch_io_state_run_t state_run;                    // 改变session的运行状态,保留
    /*! get sessions jitterbuffer */
    switch_io_get_jb_t get_jb;                            //获取session的jitter_buffer
    void *padding[10];
};

typedef struct switch_io_routines switch_io_routines_t;

3.3.5 switch_state_handler_table_t

作用:存储状态机的回调函数

结构体定义:

struct switch_state_handler_table {
    /*! executed when the state changes to init //channel进入 CS_INIT 状态的回调函数 */
    switch_state_handler_t on_init;
    /*! executed when the state changes to routing  //  channel进入 CS_ROUTING 状态的回调函数*/
    switch_state_handler_t on_routing;
    /*! executed when the state changes to execute // channel进入 CS_EXECUTE 状态的回调函数,用于执行操作*/
    switch_state_handler_t on_execute;
    /*! executed when the state changes to hangup  channel进入 CS_HANGUP 状态的回调函数//  */
    switch_state_handler_t on_hangup;
    /*! executed when the state changes to exchange_media // channel进入 CS_EXCHANGE_MEDIA 状态的回调函数*/
    switch_state_handler_t on_exchange_media;
    /*! executed when the state changes to soft_execute  // channel进入 CS_SOFT_EXECUTE 状态的回调函数,用于从其它channel接收或发送数据*/
    switch_state_handler_t on_soft_execute;
    /*! executed when the state changes to consume_media // channel进入 CS_CONSUME_MEDIA 状态的回调函数*/
    switch_state_handler_t on_consume_media;
    /*! executed when the state changes to hibernate //channel进入 CS_HIBERNATE 状态的回调函数,sleep操作*/
    switch_state_handler_t on_hibernate;
    /*! executed when the state changes to reset //channel进入 CS_RESET 状态的回调函数 */
    switch_state_handler_t on_reset;
    /*! executed when the state changes to park  //channel进入 CS_PARK 状态的回调函数 */
    switch_state_handler_t on_park;
    /*! executed when the state changes to reporting  //channel进入 CS_REPORTING 状态的回调函数*/
    switch_state_handler_t on_reporting;
    /*! executed when the state changes to destroy  //channel进入 CS_DESTROY 状态的回调函数*/
    switch_state_handler_t on_destroy;
    int flags;
    void *padding[10];
};

typedef struct switch_state_handler_table switch_state_handler_table_t;

switch_core_state_machine.c文件switch_core_session_run()函数中使用 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;

 3.4 模块开发实例

3.4.1 实现一个api

以模块mod_bsoft为例。实现一个hello_bsoft api接口

在src/mod/endpoints目录下创建mod_bsoft目录。

把模块名加入到modules.conf中,make时根据此文件选择编译哪些模块,并生成相应的makefile文件。

在configure.ac中加入mod_bsoft的Makefile配置:

  在mod_bsoft目录中创建mod_bsoft.c文件。代码如下: 

/*************************************************************************
  > File Name: mod_bsoft.c
  > Author: zhongqf
  > Mail: zhongqf.cn@gmail.com
  > Created Time: 2023-11-22 15:42:03
 ************************************************************************/

#include<switch.h>
SWITCH_MODULE_LOAD_FUNCTION(mod_bsoft_load);
SWITCH_MODULE_RUNTIME_FUNCTION(mod_bsoft_runtime);
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_bsoft_shutdown);
SWITCH_MODULE_DEFINITION(mod_bsoft,mod_bsoft_load,mod_bsoft_shutdown,mod_bsoft_runtime);

SWITCH_STANDARD_API(hello_bsoft)
{
    switch_log_printf(SWITCH_CHANNEL_LOG,SWITCH_LOG_NOTICE,"hello bsoft!\n");
    return SWITCH_STATUS_SUCCESS;
}

SWITCH_MODULE_LOAD_FUNCTION(mod_bsoft_load)
{
    switch_api_interface_t *api_interface;
    *module_interface = switch_loadable_module_create_module_interface(pool,modname);
    SWITCH_ADD_API(api_interface,"hello_bsoft","hello bosft API",hello_bsoft,"syntax");
    return SWITCH_STATUS_SUCCESS;
}
SWITCH_MODULE_RUNTIME_FUNCTION(mod_bsoft_runtime)
{
    int i=0;
    while(i<=10)
    {
        switch_log_printf(SWITCH_CHANNEL_LOG,SWITCH_LOG_NOTICE,"hello from bsoft!\n");
        switch_yield(100000);
        i++;
    }
    return SWITCH_STATUS_TERM;
}
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_bsoft_shutdown)
{
    switch_log_printf(SWITCH_CHANNEL_LOG,SWITCH_LOG_NOTICE,"mod_bsoft shutdown!\n");
    return SWITCH_STATUS_SUCCESS;

}

编写makefile.am文件: 

include $(top_srcdir)/build/modmake.rulesam
MODNAME=mod_bsoft

mod_LTLIBRARIES = mod_bsoft.la
mod_bsoft_la_SOURCES  = mod_bsoft.c
mod_bsoft_la_CFLAGS   = $(AM_CFLAGS)
mod_bsoft_la_LIBADD   = $(switch_builddir)/libfreeswitch.la
mod_bsoft_la_LDFLAGS  = -avoid-version -module -no-undefined -shared

执行freeswitch源码目录下执行

./bootstrap.sh && ./configure --prefix=/opt/freeswitch_install/

可以看到mod_bsoft目录下产生了一个Makefile文件。此时可以执行在此目录下编译单独编译模块,并部署到安装目录下。

make && make install

也可以在freeswitch元目录下执行:

make mod_bsoft && make mod_bsoft-install

在freeswitch安装目录下可以看到mod_bsoft模块相关文件:

在控制台中加载mod_bsoft模块,可以看到模块可以正常加载,并打印runtime中的日志。

 可以调用模块中定义的hello_bsoft接口

 可以正常卸载:

 3.4.2 实现一个Dialplan

 在SWITCH_MODULE_LOAD_FUNCTION中新增一个Dialplan变量声明:

switch_dialplan_interface_t *dp_interface;

在*module_interface之后,向Freeswitch核心注册一个新的Dialplan,并设置一个回调函数:

SWITCH_ADD_DIALPLAN(dp_interface,"bsoft_dialplan",bsoft_dialplan_hunt);

实现这个回调函数:

SWITCH_STANDARD_DIALPLAN(bsoft_dialplan_hunt)
{
    switch_caller_extension_t * extension = NULL;
    switch_channel_t *channel = switch_core_session_get_channel(session);
    if(!caller_profile)
    {
        caller_profile =switch_channel_get_caller_profile(channel);
    }
    switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session),SWITCH_LOG_INFO,
            "Processing %s <%s>->%s in context %s\n",
            caller_profile->caller_id_name,
            caller_profile->caller_id_number,
            caller_profile->destination_number,
            caller_profile->context
            );

    extension = switch_caller_extension_new(session,"bsoft_dialplan","bsoft_dialplan");

    if(!extension) abort();

    switch_caller_extension_add_application(session,extension,"log","Info , bsoft_dialplan");

    return extension;
}

在控制台执行:

freeswitch@debian> originate user/1001 9999 bsoft_dialplan

 其中9999为Extension,bsoft_dialplan为Dialplan,Context没设置,默认为default。

3.4.3 实现一个application

  在SWITCH_MODULE_LOAD_FUNCTION中新增一个Application变量声明

switch_application_interface_t *app_interface;

在*module_interface之后,向Freeswitch核心注册一个新的Application,并设置一个回调函数:

 SWITCH_ADD_APP(app_interface,"bsoft_app","bsoft_app demo","bsoft_app Demo",bsoft_app_fun,"[name]",SAF_SUPPORT_NOMEDIA);

实现这个函数:

SWITCH_STANDARD_APP(bsoft_app_fun)
{

}

通过控制台执行show modules mod_bsoft命令可以看到刚刚实现的api 、dialplan和application:

3.4.4 事件订阅

在mod_bsoft_load()函数中添加 switch_event_bind()。

 switch_event_bind("mod_bsoft", SWITCH_EVENT_ALL, SWITCH_EVENT_SUBCLASS_ANY, on_channel_progress, NULL); 

并实现事件侦听函数:

static void  on_channel_progress(switch_event_t *event) 
{
     switch_log_printf(SWITCH_CHANNEL_LOG,SWITCH_LOG_INFO,"event id:%d owner:%s event name:%s\n",
   event->event_id,event->owner,event->subclass_name); }

 

 4 调试

4.1 设置日志级别

mod_sofia模块接口设置sofia sip协议栈的日志级别,0关闭调试日志,9最高包括函数调用退出流程的日志打印。all会影响所有模块的日志级别。

sofia loglevel <all|default|tport|iptsec|nea|nta|nth_client|nth_server|nua|soa|sresolv|stun> [0-9]

开启sofia最高级别日志:

fsctl loglevel 6
sofia loglevel all 9

关闭sofia日志

sofia loglevel all 0

4.2 堆栈打印

使用系统库函数打印堆栈来查看调用流程。以通过堆栈来查看channel初始化流程为例。在FreeSWITCH呼叫业务中,我们最常见的日志就是呼叫通道的启动信息,日志如下:

[NOTICE] switch_channel.c:1142 New Channel sofia/internal/1001@192.168.1.2 [c30832f7-7af7-4517-9ea9-310f6c4da6a4]

这个日志表示一个新的会话channel初始完成。定位到src/switch_channel.c文件switch_channel_set_name()函数。在New Channel 信息打印之后执行print_stack()函数。

实现print_stack()函数:

#include <execinfo.h>
static void print_stack()
{
        void *stacktrace[99];
        char **symbols;
        int size,i;
        size = backtrace(stacktrace,99);
        symbols = backtrace_symbols(stacktrace,size);
        if(!symbols)
        {
                return;
        }
        for(i=0;i<size;i++)
        {
                switch_log_printf(SWITCH_CHANNEL_LOG,SWITCH_LOG_NOTICE,"STACK:%s\n",symbols[i]);
        }
        free(symbols);
}

重新编译和安装FreeSWITCH。启动FreeSWITCH后发起INVITE呼叫。

switch_channel.c:1161 New Channel sofia/internal/1001@192.168.1.2 [1f1d7158-c050-463a-893b-ce7be864d7dd]
switch_channel.c:1140 STACK:/opt/freeswitch_install/lib/libfreeswitch.so.1(+0x6268f) [0x7fdd7190468f]
switch_channel.c:1140 STACK:/opt/freeswitch_install/lib/libfreeswitch.so.1(switch_channel_set_name+0x176) [0x7fdd7190c096]
switch_channel.c:1140 STACK:./lib/freeswitch/mod/mod_sofia.so(+0x551a4) [0x7fdd6de7b1a4]
switch_channel.c:1140 STACK:./lib/freeswitch/mod/mod_sofia.so(+0x53ed2) [0x7fdd6de79ed2]
switch_channel.c:1140 STACK:/home/freeswitch-1.10.10-dev/lib/libsofia-sip-ua.so.0(+0xc7e1b) [0x7fdd710d1e1b]
switch_channel.c:1140 STACK:/home/freeswitch-1.10.10-dev/lib/libsofia-sip-ua.so.0(+0x135313) [0x7fdd7113f313]
switch_channel.c:1140 STACK:/home/freeswitch-1.10.10-dev/lib/libsofia-sip-ua.so.0(su_base_port_getmsgs+0x73) [0x7fdd7113f09d]
switch_channel.c:1140 STACK:/home/freeswitch-1.10.10-dev/lib/libsofia-sip-ua.so.0(su_base_port_step+0x165) [0x7fdd7113f655]
switch_channel.c:1140 STACK:/home/freeswitch-1.10.10-dev/lib/libsofia-sip-ua.so.0(+0x131734) [0x7fdd7113b734]
switch_channel.c:1140 STACK:/home/freeswitch-1.10.10-dev/lib/libsofia-sip-ua.so.0(su_root_step+0x6e) [0x7fdd7113c87b]
switch_channel.c:1140 STACK:./lib/freeswitch/mod/mod_sofia.so(+0x3dcb3) [0x7fdd6de63cb3]
switch_channel.c:1140 STACK:/lib/x86_64-linux-gnu/libpthread.so.0(+0x7ea7) [0x7fdd7186cea7]
switch_channel.c:1140 STACK:/lib/x86_64-linux-gnu/libc.so.6(clone+0x3f) [0x7fdd7178ca2f]

通过“addr2line”工具,使用模块名和偏移地址,查找函数名:

addr2line 0x6268f -e /opt/freeswitch_install/lib/libfreeswitch.so.1 -f
print_stack
/opt/freeswitch/src/switch_channel.c:1133

addr2line 0x551a4 -e ./lib/freeswitch/mod/mod_sofia.so -f
sofia_glue_set_name
/opt/freeswitch/src/mod/endpoints/mod_sofia/sofia_glue.c:70

addr2line 0x53ed2 -e ./lib/freeswitch/mod/mod_sofia.so -f
set_call_id
/opt/freeswitch/src/mod/endpoints/mod_sofia/sofia.c:2385

addr2line 0xc7e1b -e /home/freeswitch-1.10.10-dev/lib/libsofia-sip-ua.so.0 -f
nua_application_event
nua_stack.c:?

addr2line 0x135313 -e /home/freeswitch-1.10.10-dev/lib/libsofia-sip-ua.so.0 -f
su_base_port_execute_msgs
su_base_port.c:?

addr2line 0x131734 -e /home/freeswitch-1.10.10-dev/lib/libsofia-sip-ua.so.0 -f
su_port_step
su_root.c:?

addr2line 0x3dcb3 -e ./lib/freeswitch/mod/mod_sofia.so -f
sofia_profile_thread_run
/opt/freeswitch/src/mod/endpoints/mod_sofia/sofia.c:3472

addr2line 0x7ea7 -e /lib/x86_64-linux-gnu/libpthread.so.0 -f
start_thread
./nptl/pthread_create.c:478 (discriminator 6)

可以看出Invite初始channel流程如下:

start_thread()
=>sofia_profile_thread_run()//mod_sofia模块,启动profile端口监听
    =>su_root_step()
        =>su_port_step()
            =>su_base_port_step() //sofia_sip库,端口收到消息。
                =>su_base_port_getmsgs()
                    =>su_base_port_execute_msgs() //sofia_sip库,分发消息
                        =>nua_application_event()
                            =>set_call_id()
                                =>sofia_glue_set_name() //mod_sofia模块,设置channel名称。
                                    =>switch_channel_set_name() //freeswitch核心,设置channel名称。
                                        =>print_stack()

5 呼叫流程

在fs_cli命令发一路外呼,注意字符串写的是sofia/192.168.1.2/1001,直接指定sofia的具体profile,而不是user/1001,因为后者是个虚拟的逻辑概念。

originate sofia/192.168.1.2/1001 &bridge(sofia/192.168.1.2/1002)

fs_cli是典型的ESL客户端,所以接收端是mod_event_socketd的监听线程listener_run。

listener_run()
    =>parse_command()
        =>api_exec()
            =>switch_api_execute()
                =>switch_loadable_module_get_api_interface()
                    =>originate_function() //mod_dpcommand
                        =>switch_ivr_originate()
                            =>switch_core_session_outgoing_channel()
                                =>endpoint_interface->io_routines->outgoing_channel()
                                    =>sofia_outgoing_channel() //mod_sofia
                                        =>switch_core_session_request_uuid() 
                                            =>switch_channel_init()

 最终在mod_sofia模块中创建Session对象,并调用switch_channel_init()将状态机设置为CS_INIT。

状态机驱动之后,调用mod_sofia模块的状态回调函数sofia_on_init()函数中的sofia_glue_do_invite()函数发送INVITE消息。然后,核心状态函数switch_core_standard_on_init()驱动状态迁移到CS_ROUTING状态。这时拨号方案执行列表就是我们最开始的命令:

&bridge(sofia/192.168.1.2/1002)

  originate模块添加的状态回调函数originate_on_routing被调用时,驱动状态机迁移到CS_CONSUME_MEDIA状态。这时originate 挂起,等待被叫方的响应。

 sofia协议栈收到消息时,抛出事件,并调用sofia_event_callback回调函数。对于应答消息18X和2XX,调用链差不多:

sofia_event_callback()
    =>sofia_queue_message()
        =>sofia_process_dispatch_event()
            =>our_sofia_event_callback
                =>sofia_handle_sip_i_state()

最后18X调用switch_channel_mark_pre_answered()函数,而2XX调用switch_channel_mark_answered()函数。 被叫应答后,之前被挂起的originate激活,根据返回的结果,驱动状态机继续迁移,对于200OK,也就是answer,置状态为CS_EXECUTE。 接下来的迁移就取决于后续的处理了,本例是,执行完bridge后执行1002呼叫流程。

6 mod_sofia模块上报自定义头域

FreeSWITCH event事件一般都是底层定义好的,这些信息已经很完备,可以满足日常开发需求。但是在某些特殊需求下,需要进行额外的处理。比如,FreeSWITCH注册时间中,就没有X-自定义头域信息。

在定制化的SIP交换过程中,FreeSWITCH支持自定义头域,头域格式要满足“X-***”的模式。而当我们订阅“sofia::register”事件,在事件中是无法获取自定义头域信息的。

修改方式如下,在src/mod/endpionts/mod_sofia/sofia_reg.c文件中找到sofia_reg_handle_register_token()函数,并在函数中找到MY_EVENT_REGISTER "sofia::register" 事件的创建和上报流程,添加如下代码:

 从上面的代码中,我们可以看到“sofia::register”事件中包含的所有头域内容。sofia sip协议栈会解析所有的头域,并把非标准的头域都放到“sip->sip_unknown”中。增的代码中,我们把所有unknown的头域都放到register上报事件中去。

重新加载mod_sofia模块后,可以看到注册事件中看到"X-"开头的自定义头域消息。

 

7 Endpoints媒体交互

endpoints通讯的目的就是双方都向对方推送己方的媒体数据(包括音频数据、视频数据)。Endpoints媒体的交互在被叫发送SIP/2.0 200 OK之后便开启RTP数据包推送。其中音频和视频数据是分开进行RTP打包的。而且音频RTP包和视频RTP包分别使用各自通讯端口。比如:192.168.1.2上音频RTP数据包收发端口为19132,视频RTP数据包收发端口为16610。如下图所示:

 7.1 originate 流程

7.1.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

7.1.2 originate功能入口函数

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

SWITCH_ADD_API(commands_api_interface, "originate", "Originate a call", originate_function, ORIGINATE_SYNTAX);

具体实现如下:

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

7.1.3 switch_ivr_originate函数

该函数用于发起具体的呼叫。switch_ivr_originate函数定义:

SWITCH_DECLARE(switch_status_t) switch_ivr_originate(
    switch_core_session_t *session,                          //发起originate的channel,即 caller_channel , aleg
    switch_core_session_t **bleg,                            //originate所在的leg,会在该函数赋值
    switch_call_cause_t *cause,                                //失败原因,会在该函数赋值
    const char *bridgeto,                                    //bleg的呼叫字符串,只读
    uint32_t timelimit_sec,                                    //originate超时时间
    const switch_state_handler_table_t *table,                //bleg的状态机回调函数
    const char *cid_name_override,                            //origination_caller_id_name,用于设置主叫名称
    const char *cid_num_override,                            //origination_caller_id_number,用于设置主叫号码
    switch_caller_profile_t *caller_profile_override,        //主叫的profile
    switch_event_t *ovars, switch_originate_flag_t flags,    //originate导出的通道变量(从aleg)
    switch_call_cause_t *cancel_cause,                        // originate取消原因
    switch_dial_handle_t *dh)                                // dial handle,功能类似呼叫字符串,可以设置多条leg同时originate

如果outgoing_channel执行成功,会在函数switch_core_session_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);
}

7.2 bridge流程

7.2.1 流程入口

bridge app入口(mod_dptools.c):

 函数调用链:

audio_bridge_function 
    => switch_ivr_signal_bridge
or  => switch_ivr_multi_threaded_bridge 

uuid_bridge api入口(mod_commands.c):

 函数调用链:

uuid_bridge_function => switch_ivr_uuid_bridge

7.2.2 bridge机制

注册回调函数:

static const switch_state_handler_table_t audio_bridge_peer_state_handlers = {
    /*.on_init */ NULL,
    /*.on_routing */ audio_bridge_on_routing,
    /*.on_execute */ NULL,
    /*.on_hangup */ NULL,
    /*.on_exchange_media */ audio_bridge_on_exchange_media,
    /*.on_soft_execute */ NULL,
    /*.on_consume_media */ audio_bridge_on_consume_media,
};

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

7.3 媒体交互流程

7.3.1 注册编码类型

在函数mod_spandsp_codecs_load()中调用switch_core_codec_add_implementation()函数注册音频编解码。

例如添加PCMU编码

switch_core_codec_add_implementation(pool, codec_interface, SWITCH_CODEC_TYPE_AUDIO,    /* enumeration defining the type of the codec */
                                             0,    /* the IANA code number */
                                             "PCMU",    /* the IANA code name */
                                             NULL,    /* default fmtp to send (can be overridden by the init function) */
                                             8000,    /* samples transferred per second */
                                             8000,    /* actual samples transferred per second */
                                             64000,    /* bits transferred per second */
                                             mpf * count,    /* number of microseconds per frame */
                                             spf * count,    /* number of samples per frame */
                                             bpf * count,    /* number of bytes per frame decompressed */
                                             ebpf * count,    /* number of bytes per frame compressed */
                                             1,    /* number of channels represented */
                                             spf * count,    /* number of frames per network packet */
                                             switch_g711u_init,    /* function to initialize a codec handle using this implementation */
                                             switch_g711u_encode,    /* function to encode raw data into encoded data */
                                             switch_g711u_decode,    /* function to decode encoded data into raw data */
                                             switch_g711u_destroy);    /* deinitalize a codec handle using this implementation */

7.3.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
              =>switch_socket_sendto
         =>rtp_common_write

 8 FreeSWITCH内核与Sofia模块协同工作流程分析

FreeSWITCH 调用SIP协议栈实现SIP协议交换层级如下:

 8.1 FreeSWITCH Core

8.1.1 模块加载过程

freeswitch主程序初始化时会从modules.conf.xml文件中读取配置,如果配置中如下内容生效,FreeSWITCH启动时则执行加载sofia模块操作。

<load module="mod_sofia"/>

具体过程如下:

main()
    =>switch_core_init_and_modload()
        =>switch_loadable_module_init()
            =>switch_loadable_module_load_module_ex()// 读取xml文件并加载模块            

8.1.2 状态机

8.1.2.1 状态机初始化

在如下函数中实现状态机初始化

 switch_core_session_run()
8.1.2.2  改变状态机状态

通过调用switch_channel_set_state来实现状态机的状态改变。

8.1.2.3 处理状态变化

当状态发生变化后,通过switch_channel_set_running_state函数来改变running_state,并执行相关的回调来通知其状态已经发生改变:

endpoint_interface->io_routines->state_run()

主叫变化

  • CS_NEW

switch_core_session_run初始状态为CS_NEW

  • CS_INIT

sofia_handle_sip_i_state

case nua_callstate_received (收到invite请求) 修改状态机的状态 :CS_NEW ==> CS_INIT switch_channel_set_state(channel, CS_INIT);

switch_core_session_run

状态机处理状态变化 STATE_MACRO(init, "INIT"); on_init 即 : sofia_on_init

switch_core_standard_on_init

  • CS_ROUTING

sofia_on_init

修改状态机的状态 : CS_INIT == > CS_ROUTING
switch_channel_set_state(channel, CS_ROUTING);

switch_core_session_run
状态机处理状态变化 STATE_MACRO(routing, "ROUTING"); on_routing 即 : sofia_on_routing
switch_core_standard_on_routing

  • CS_EXECUTE

witch_core_standard_on_routing 修改状态机的状态 : CS_ROUTING == > CS_EXECUTE switch_channel_set_state(session->channel, CS_EXECUTE);

switch_core_session_run
状态机处理状态变化 STATE_MACRO(execute, "EXECUTE"); on_execute 即 : sofia_on_execute
switch_core_standard_on_execute

  • CS_HANGUP

sofia_handle_sip_i_bye switch_channel_hangup : 即 switch_channel_perform_hangup 修改状态机的状态 channel->state = CS_HANGUP;

 状态机处理状态变化
    switch_core_session_hangup_state            
        STATE_MACRO(hangup, "HANGUP");

被叫变化

  • CS_NEW

switch_core_session_run初始状态为CS_NEW

  • CS_INIT

sofia_outgoing_channel 修改状态机的状态 :CS_NEW ==> CS_INIT switch_channel_set_state(nchannel, CS_INIT);

状态机处理逻辑参考主叫。

  • CS_ROUTING

sofia_on_init
修改状态机的状态 : CS_INIT == > CS_ROUTING switch_channel_set_state(channel, CS_ROUTING);

状态机处理逻辑参考主叫。

  • CS_CONSUME_MEDIA

originate_on_routing 修改状态机的状态:CS_ROUTING -> CS_CONSUME_MEDIA switch_channel_set_state(channel, CS_CONSUME_MEDIA);

switch_core_session_run
状态机处理状态变化 STATE_MACRO(consume_media, "CONSUME_MEDIA"); on_ consume_media 即 : switch_core_standard_on_consume_media

  • CS_EXCHANGE_MEDIA

switch_core_session_run
状态机处理状态变化 STATE_MACRO(exchange_media, "EXCHANGE_MEDIA"); on_exchange_media 即 : sofia_on_exchange_media
switch_core_standard_on_exchange_media

  • CS_HANGUP

audio_bridge_on_exchange_media
switch_channel_hangup : 即 switch_channel_perform_hangup 修改状态机的状态 channel->state = CS_HANGUP;

    状态机处理状态变化
    switch_core_session_hangup_state

        STATE_MACRO(hangup, "HANGUP");

 8.2 Sofia应用层

模块结构:

 8.2.1 模块加载过程

启动事件处理线程池 :

SWITCH_MODULE_LOAD_FUNCTION(mod_sofia_load)
    => sofia_msg_thread_start(0);
        => sofia_msg_thread_run
            => sofia_process_dispatch_event
                => our_sofia_event_callback : 处理消息

启动服务器监听 :

SWITCH_MODULE_LOAD_FUNCTION(mod_sofia_load)
    => config_sofia(0, NULL)
        => launch_sofia_profile_thread
            => sofia_profile_thread_run                
                => nua_create
                    => su_home_new
                    => nua_stack_init                    
                        => nta_agent_create
                            => nta_agent_add_tport                            
                                => tport_tbind
                                    => tport_bind_server
                                        => tport_listen : 监听客户端发来的数据

8.2.2 数据库表格

freeswitch core 相关:

  • aliases
  • calls
  • channels
  • complete
  • interfaces
  • nat
  • recovery
  • registrations
  • tasks

sofia相关:

  • sip_authentication
  • sip_dialogs
  • sip_presence
  • sip_registrations
  • sip_shared_appearance_dialogs
  • sip_shared_appearance_subscriptions
  • sip_subscriptions

limit相关:

  • db_data
  • group_data
  • limit_data

fifo相关:

  • fifo_bridge
  • fifo_callers
  • fifo_outbound

语音信箱相关:

  • voicemail_msgs
  • voicemail_prefs

8.2.3 呼叫流程涉及内容

  • 收到A的nua_i_invite,返回407,具体如下:
sofia_handle_sip_i_invite => sofia_reg_handle_register => sofia_reg_auth_challenge => 407
  •  收到A的nua_i_invite,返回180,具体如下:
sofia_handle_sip_i_invite => sofia_reg_handle_register => sofia_reg_parse_auth

处理nua_i_state消息
sofia.c : sofia_handle_sip_i_state

mod_dialplan_xml.c : dialplan_hunt (ring_ready)
解析拨号方案,执行lua脚本,设置通道变量。

发送180事件

涉及数据表:
sip_registrations、ip_dialogs

  • 发送invite给B分机,具体如下:

处理nua_r_invite消息
sofia_handle_sip_r_invite => originate_on_routing
涉及数据表:
sip_dialogs

  • B应答,双方通话,具体如下
sofia_receive_message : SWITCH_MESSAGE_INDICATE_ANSWER               
    => sofia_answer_channel
        => sofia_glue_tech_choose_port
        => sofia_glue_set_local_sdp
        => sofia_glue_activate_rtp

8.2.4 编码协商过程

  • 先匹配freeswitch默认的codec,从加载的模块中查找支持类型的具体码率信息
sofia_handle_sip_i_invite
=> sofia_glue_tech_prepare_codecs
=> switch_loadable_module_get_codecs_sorted
  • 协商发给被叫的sdp
sofia_glue_do_invite
=> sofia_glue_tech_prepare_codecs
=> switch_loadable_module_get_codecs_sorted
    ocodec + codec_string     
  • 产生M头,并发送包含sdp的invite
sofia_glue_do_invite
=> sofia_glue_set_local_sdp
=> generate_m
=> nua_invite
  • 协商200 OK的sdp
sofia_answer_channel
=> sofia_glue_tech_prepare_codecs
=> switch_loadable_module_get_codecs_sorted

8.2.5 媒体交互

  • RTP数据交互
audio_bridge_on_exchange_media
=> audio_bridge_thread

收发音频数据

=> switch_core_session_read_frame
=> session->endpoint_interface->io_routines->read_frame 即: sofia_read_frame

=> switch_core_session_write_frame
=> perform_write
=> session->endpoint_interface->io_routines->write_frame 即: sofia_write_frame

收发视频数据

1、启动线程

=> launch_video
=> video_bridge_thread

2、收发数据

=> switch_core_session_read_video_frame
=>session->endpoint_interface->io_routines->read_video_frame
即:sofia_read_video_frame

=> switch_core_session_write_video_frame
=> session->endpoint_interface->io_routines->write_video_frame
即:sofia_write_video_frame
  • 终止RTP交互
audio_bridge_thread
=> switch_core_session_kill_channel(session_b, SWITCH_SIG_BREAK);
即 :switch_core_session_perform_kill_channel
=> session->endpoint_interface->io_routines->kill_channel
即 : sofia_kill_channel
=> switch_rtp_break

8.3 sofia协议栈

8.3.1 协议栈结构图

NUA : 基本的SIP用户代理的功能,其功能包括呼叫管理,消息和事件检索。

NTA : sofia SIP事务API(NTA)提供了简单的接口的SIP事务,传输和信息处理。

Tport : 该模块包含一个由SIP,RTSP 和HTTP协议使用的通用传输接口,这是一个协议栈和传输协议实现之间的抽象层。

8.3.2 信令流程

 场景描述: A 呼叫 B ,B接通后通话一段时间后,挂断电话。

  • A 发送 INVITE 请求
tport.c : tport_recv_data
nta.c : agent_recv_request
  • FS回应100给A分机
nua_server.c : nua_stack_process_request
nua_server.c : SR_STATUS1(sr, SIP_100_TRYING)
nta.c : nta_incoming_treply
nta.c : incoming_reply
tport.c: tport_tsend
  • FS发送认证请求给A分机 
nua_application_event

处理nua_i_invite 消息

sofia.c : sofia_process_dispatch_event
sofia.c : our_sofia_event_callback
sofia.c : sofia_handle_sip_i_invite
sofia.c : sofia_reg_handle_register
sofia_reg.c : sofia_reg_auth_challenge

回复407

nua.c : nua_respond
nua_stack.c : nua_signal
nua_stack.c : nua_stack_signal
nua_server.c : nua_stack_respond
nua_server.c : nua_server_respond

发送407给A分机

nua_invite_server_respond
nua_base_server_respond
nta_incoming_mreply
incoming_reply
tport_tsend
tport_resolve
tport_by_addrinfo
tport_prepare_and_send
tport_send_msg
tport_vsend
sent 407 Proxy Authentication Required for INVITE (1)

call state(A) : init -> received
call state(A) : received -> terminated
  • A分机发送ACK
nta.c : agent_recv_message
nta.c : agent_recv_request
  • A分机重新发送 INVITE 请求,附加认证信息
tport.c : tport_recv_data
nta.c : agent_recv_request
  • FS回应100给A分机
nua_server.c : nua_stack_process_request
nua_server.c : SR_STATUS1(sr, SIP_100_TRYING)
nta.c : nta_incoming_treply
nta.c : incoming_reply
tport.c: tport_tsend
  • FS发送 INVITE 给 B分机
switch_ivr_originate.c : switch_ivr_originate

sofia_on_init
sofia_glue_do_invite
nua_invite : nua_r_invite 消息
nua_signal

sofia.c : sofia_process_dispatch_event
sofia.c : our_sofia_event_callback
sofia.c : sofia_handle_sip_r_invite

sofia_on_init
sofia_on_routing
switch_ivr_originate.c :originate_on_routing

nua_stack_signal
nua_stack_invite

nta_leg_tcreate


执行发送invite请求
nta.c : outgoing_send
tport_tsend

call state(B) : init -> calling
  • B分机回应180给FS
tport_recv_event
agent_recv_response

处理 nua_r_invite 消息
sofia.c : sofia_process_dispatch_event
sofia.c : our_sofia_event_callback
sofia.c : sofia_handle_sip_r_invite

call state(B) : calling -> proceeding
  • FS发送180给A分机
nua_session.c : signal_call_state_change
nua_stack_tevent : nua_i_state
nua_application_event

处理nua_i_invite 消息

sofia.c : sofia_process_dispatch_event
sofia.c : our_sofia_event_callback
sofia.c : sofia_handle_sip_i_invite
sofia_reg.c : sofia_reg_handle_register
sofia_reg.c : sofia_reg_parse_auth

处理nua_i_state消息
sofia.c : sofia_handle_sip_i_state


mod_dialplan_xml.c : dialplan_hunt (ring_ready)
解析拨号方案,执行lua脚本,设置通道变量。

发送180
switch_channel_mark_ring_ready

nua_invite_server_respond
nua_base_server_respond
nta_incoming_mreply
incoming_reply
tport_tsend
tport_resolve
tport_by_addrinfo
tport_prepare_and_send
tport_send_msg
tport_vsend

call state(A) : init -> received
  • B发送200 ok
tport_recv_event
agent_recv_response

处理 nua_r_invite 消息

sofia.c : sofia_process_dispatch_event
sofia.c : our_sofia_event_callback
sofia.c : sofia_handle_sip_r_invite

call state(B) : proceeding -> completing
  • FS发送ACK给B
nua_ack
nua_signal : nua_r_ack
outgoing_send
tport_tsend
call state(A) : received
-> early call state(B) : completing -> ready
  • FS发送200 OK给A
sofia_glue_tech_set_codec
switch_rtp_create
sofia_glue_negotiate_sdp
sofia_glue_activate_rtp

switch_channel_perform_answer : SWITCH_MESSAGE_INDICATE_ANSWER
switch_channel_perform_mark_answered

auto_record.lua : 执行录音操作B
auto_record.lua : 执行录音操作A

sofia_receive_message (SWITCH_MESSAGE_INDICATE_ANSWER)

发送200 OK
nua_respond : 200
nua_signal : nua_r_respond
nua_stack_signal
nua_stack_respond
nua_server_respond
nta_incoming_treply
nta_incoming_mreply
incoming_reply
tport_tsend

call state(A) : early -> completed
  • A回应ACK
nta.c : agent_recv_message
nta.c : agent_recv_request
call state(A) : completed -> ready
  • 通话一段时间后,A主动发送BYE
tport_recv_event
agent_recv_message
agent_recv_request
sofia_on_hangup
  • FS发送BYE给B
nua_bye : nua_r_bye
nua_stack_signal
outgoing_send
tport_tsend
  • FS发送200 OK给A
nta : sent 200 OK for BYE
tport_tsend

call state(A) : ready -> terminating
call state(A) : terminated

B发送200 OK给FS,回应挂断请求

nta: received 200 OK for BYE
状态变化:
call state(B) : ready -> terminating
call state(B) : terminating -> terminated

 

posted @ 2023-11-21 17:44  钟齐峰  阅读(750)  评论(2编辑  收藏  举报