从零开始写一个FreeSWITCH模块

创建一个模块

我们创建一个mod_book的模块

创建一个mod_book.c文件,代码如下

c 复制代码
// mod_book.c

#include <switch.h>

SWITCH_MODULE_LOAD_FUNCTION(mod_book_load);  // 模块加载函数的声明
SWITCH_MODULE_DEFINITION(mod_book, mod_book_load, NULL, NULL);  // 模块的定义

// 模块加载函数的定义
SWITCH_MODULE_LOAD_FUNCTION(mod_book_load) {
  *module_interface = switch_loadable_module_create_module_interface(pool, modname);
  return SWITCH_STATUS_SUCCESS;
}

创建一个Makefile用于编译

makefile 复制代码
# FreeSWITCH源码目录
BASE=/root/sources/freeswitch
include $(BASE)/build/modmake.rules

编译

执行make install进行编译安装

sh 复制代码
➜  mod_book make install
Compiling /root/code/mod_book/mod_book.c...
libtool: compile:  gcc -I/usr/include/uuid -I/root/sources/freeswitch/src/include -I/root/sources/freeswitch/src/include -I/root/sources/freeswitch/libs/libteletone/src -fPIC -ffast-math -Werror -Wno-unused-result -Wno-misleading-indentation -fvisibility=hidden -DSWITCH_API_VISIBILITY=1 -DCJSON_API_VISIBILITY=1 -DHAVE_VISIBILITY=1 -g -ggdb -DHAVE_OPENSSL -g -O2 -Wall -std=c99 -pedantic -Wdeclaration-after-statement -D_GNU_SOURCE -DHAVE_CONFIG_H -c /root/code/mod_book/mod_book.c  -fPIC -DPIC -o .libs/mod_book.o
Creating mod_book.la...
installing mod_book.la
libtool: warning: relinking 'mod_book.la'

在FreeSWITCH中加载模块

执行load mod_book加载模块

sh 复制代码
freeswitch@debianh61> load mod_book
2025-04-06 08:21:21.994470 99.73% [INFO] mod_enum.c:884 ENUM Reloaded
2025-04-06 08:21:21.994470 99.73% [CONSOLE] switch_loadable_module.c:1772 Successfully Loaded [mod_book]

+OK Reloading XML
+OK

2025-04-06 08:21:21.994470 99.73% [INFO] switch_time.c:1436 Timezone reloaded 597 definitions

添加一个Dialplan

c 复制代码
#include <stdlib.h>
#include <switch.h>

SWITCH_MODULE_LOAD_FUNCTION(mod_book_load);  // 模块加载函数的声明
SWITCH_MODULE_DEFINITION(mod_book, mod_book_load, NULL, NULL);  // 模块的定义


// dialplan的回调函数
SWITCH_STANDARD_DIALPLAN(book_dialplan_hunt) {
  switch_caller_extension_t *extension = NULL;  // 创建一个分机,用于dialplan查找时候返回该分机
  switch_channel_t *channel = switch_core_session_get_channel(session);  // 获取通话的channel
  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);

  // 创建一个分机,因为查找dialplan的结果是返回一个extension
  extension = switch_caller_extension_new(session, "book_extension_name", "book_extension_number");

  if (!extension) abort();

  // 往分机里面添加一个application,用于后续EXECUTE执行阶段执行该app
  switch_caller_extension_add_application(session, extension, "log", "INFO Hey, I'm in the book");

  return extension;
}

// 模块加载函数的定义
SWITCH_MODULE_LOAD_FUNCTION(mod_book_load) {
  switch_dialplan_interface_t *dp_interface;  // 定义一个dialplan

  *module_interface = switch_loadable_module_create_module_interface(pool, modname);

  // 向核心注册dialplan,并设置一个回调函数
  SWITCH_ADD_DIALPLAN(dp_interface, "book", book_dialplan_hunt);

  return SWITCH_STATUS_SUCCESS;
}

重新编译安装模块

sh 复制代码
make install

重新加载模块

使用reload mod_book重新加载模块

sh 复制代码
freeswitch@debianh61> reload mod_book 
2025-04-06 08:55:17.714472 99.77% [NOTICE] switch_loadable_module.c:1197 Deleting Dialplan 'book'
2025-04-06 08:55:17.714472 99.77% [CONSOLE] switch_loadable_module.c:2357 mod_book has no shutdown routine
2025-04-06 08:55:17.714472 99.77% [CONSOLE] switch_loadable_module.c:2374 mod_book unloaded.
2025-04-06 08:55:17.714472 99.77% [INFO] mod_enum.c:884 ENUM Reloaded
2025-04-06 08:55:17.714472 99.77% [INFO] switch_time.c:1436 Timezone reloaded 597 definitions
2025-04-06 08:55:17.714472 99.77% [CONSOLE] switch_loadable_module.c:1772 Successfully Loaded [mod_book]

+OK Reloading XML
+OK module unloaded
+OK module loaded

2025-04-06 08:55:17.714472 99.77% [NOTICE] switch_loadable_module.c:273 Adding Dialplan 'book'

测试

我们发起一个呼叫originate user/1000 999 book, 该呼叫走book这个dialplan

sh 复制代码
freeswitch@debianh61> originate user/1000 999 book
2025-04-06 08:56:32.154504 99.97% [NOTICE] switch_channel.c:1142 New Channel sofia/internal/[email protected]:55000 [1906c61d-5afc-40d2-91d8-696cc52d2700]
2025-04-06 08:56:32.154504 99.97% [NOTICE] switch_ivr_originate.c:3059 Cannot create outgoing channel of type [error] cause: [USER_NOT_REGISTERED]
2025-04-06 08:56:32.154504 99.97% [INFO] sofia_glue.c:1659 sofia/internal/[email protected]:55000 sending invite call-id: (null)
2025-04-06 08:56:32.394500 99.97% [NOTICE] sofia.c:7604 Ring-Ready sofia/internal/[email protected]:55000!
2025-04-06 08:56:33.594500 99.93% [NOTICE] sofia.c:8681 Channel [sofia/internal/[email protected]:55000] has been answered

+OK 1906c61d-5afc-40d2-91d8-696cc52d2700

2025-04-06 08:56:33.614471 99.93% [NOTICE] switch_ivr.c:2303 Transfer sofia/internal/[email protected]:55000 to book[999@default]
2025-04-06 08:56:33.614471 99.93% [INFO] mod_book.c:16 Processing  <0000000000>->999 in context default
2025-04-06 08:56:33.614471 99.93% [INFO] switch_channel.c:3289 sofia/internal/[email protected]:55000 Flipping CID from "" <0000000000> to "Outbound Call" <1000>
EXECUTE [depth=0] sofia/internal/[email protected]:55000 log(INFO Hey, I'm in the book)
2025-04-06 08:56:33.614471 99.93% [INFO] mod_dptools.c:1865 Hey, I'm in the book
2025-04-06 08:56:33.614471 99.93% [NOTICE] switch_core_state_machine.c:382 sofia/internal/[email protected]:55000 has executed the last dialplan instruction, hanging up.
2025-04-06 08:56:33.614471 99.93% [NOTICE] switch_core_state_machine.c:384 Hangup sofia/internal/[email protected]:55000 [CS_EXECUTE] [NORMAL_CLEARING]
freeswitch@debianh61> 2025-04-06 08:56:33.614471 99.93% [NOTICE] switch_core_session.c:1762 Session 4 (sofia/internal/[email protected]:55000) Ended
2025-04-06 08:56:33.614471 99.93% [NOTICE] switch_core_session.c:1766 Close Channel sofia/internal/[email protected]:55000 [CS_DESTROY]

增加一个APP

c 复制代码
#include <stdlib.h>
#include <switch.h>

SWITCH_MODULE_LOAD_FUNCTION(mod_book_load);  // 模块加载函数的声明
SWITCH_MODULE_DEFINITION(mod_book, mod_book_load, NULL, NULL);  // 模块的定义
SWITCH_STANDARD_APP(book_function);  // APP回调函数声明

switch_application_interface_t *app_interface;  // 声明一个app_interface


// APP回调函数
SWITCH_STANDARD_APP(book_function) {
  const char* name;

  if(zstr(data)) {  // data为传给该函数的参数
    name = "No Name";
  } else {
    name = data;
  }

  switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "I'm a book, My name is: %s\n", name);
}

// dialplan的回调函数
SWITCH_STANDARD_DIALPLAN(book_dialplan_hunt) {
  switch_caller_extension_t *extension = NULL;  // 创建一个分机,用于dialplan查找时候返回该分机
  switch_channel_t *channel = switch_core_session_get_channel(session);  // 获取通话的channel
  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);

  // 创建一个分机,因为查找dialplan的结果是返回一个extension
  extension = switch_caller_extension_new(session, "book_extension_name", "book_extension_number");

  if (!extension) abort();

  // 往分机里面添加一个application,用于后续EXECUTE执行阶段执行该app
  // 这里使用我们自定义的APP
  switch_caller_extension_add_application(session, extension, "book", "Use my book application");

  return extension;
}

// 模块加载函数的定义
SWITCH_MODULE_LOAD_FUNCTION(mod_book_load) {
  switch_dialplan_interface_t *dp_interface;  // 定义一个dialplan

  *module_interface = switch_loadable_module_create_module_interface(pool, modname);

  // 向核心注册dialplan,并设置一个回调函数
  SWITCH_ADD_DIALPLAN(dp_interface, "book", book_dialplan_hunt);

  // 向核心注册APP,回调函数为book_function
  SWITCH_ADD_APP(app_interface, "book", "book example short descript", "book example long descript.......", book_function, "[name]", SAF_SUPPORT_NOMEDIA);

  return SWITCH_STATUS_SUCCESS;
}

重新编译安装模块

sh 复制代码
make install

重新加载模块

使用reload mod_book重新加载模块

sh 复制代码
freeswitch@debianh61> reload mod_book
2025-04-06 09:29:44.054472 99.83% [NOTICE] switch_loadable_module.c:1197 Deleting Dialplan 'book'
2025-04-06 09:29:44.054472 99.83% [CONSOLE] switch_loadable_module.c:2357 mod_book has no shutdown routine
2025-04-06 09:29:44.054472 99.83% [CONSOLE] switch_loadable_module.c:2374 mod_book unloaded.
2025-04-06 09:29:44.054472 99.83% [INFO] mod_enum.c:884 ENUM Reloaded
2025-04-06 09:29:44.054472 99.83% [INFO] switch_time.c:1436 Timezone reloaded 597 definitions
2025-04-06 09:29:44.054472 99.83% [CONSOLE] switch_loadable_module.c:1772 Successfully Loaded [mod_book]

+OK Reloading XML
+OK module unloaded
+OK module loaded

2025-04-06 09:29:44.054472 99.83% [NOTICE] switch_loadable_module.c:273 Adding Dialplan 'book'
2025-04-06 09:29:44.054472 99.83% [NOTICE] switch_loadable_module.c:329 Adding Application 'book'

测试

执行originate user/1000 999 book发起一个呼叫

sh 复制代码
freeswitch@debianh61> originate user/1000 999 book
2025-04-06 09:31:36.214501 99.77% [NOTICE] switch_channel.c:1142 New Channel sofia/internal/[email protected]:55000 [4a0a0226-4e53-41fc-b0bd-88ccbba2d707]
2025-04-06 09:31:36.214501 99.77% [NOTICE] switch_ivr_originate.c:3059 Cannot create outgoing channel of type [error] cause: [USER_NOT_REGISTERED]
2025-04-06 09:31:36.214501 99.77% [INFO] sofia_glue.c:1659 sofia/internal/[email protected]:55000 sending invite call-id: (null)
2025-04-06 09:31:36.494486 99.77% [NOTICE] sofia.c:7604 Ring-Ready sofia/internal/[email protected]:55000!
2025-04-06 09:31:40.174504 99.77% [NOTICE] sofia.c:8681 Channel [sofia/internal/[email protected]:55000] has been answered

+OK 4a0a0226-4e53-41fc-b0bd-88ccbba2d707

2025-04-06 09:31:40.194503 99.77% [NOTICE] switch_ivr.c:2303 Transfer sofia/internal/[email protected]:55000 to book[999@default]
2025-04-06 09:31:40.194503 99.77% [INFO] mod_book.c:32 Processing  <0000000000>->999 in context default
2025-04-06 09:31:40.194503 99.77% [INFO] switch_channel.c:3289 sofia/internal/[email protected]:55000 Flipping CID from "" <0000000000> to "Outbound Call" <1000>
EXECUTE [depth=0] sofia/internal/[email protected]:55000 book(Use my book application)
2025-04-06 09:31:40.194503 99.77% [INFO] mod_book.c:21 I'm a book, My name is: Use my book application
2025-04-06 09:31:40.194503 99.77% [NOTICE] switch_core_state_machine.c:382 sofia/internal/[email protected]:55000 has executed the last dialplan instruction, hanging up.
2025-04-06 09:31:40.194503 99.77% [NOTICE] switch_core_state_machine.c:384 Hangup sofia/internal/[email protected]:55000 [CS_EXECUTE] [NORMAL_CLEARING]

增加一个API

c 复制代码
#include <stdlib.h>
#include <switch.h>

SWITCH_MODULE_LOAD_FUNCTION(mod_book_load);  // 模块加载函数的声明
SWITCH_MODULE_DEFINITION(mod_book, mod_book_load, NULL, NULL);  // 模块的定义
SWITCH_STANDARD_APP(book_function);  // APP回调函数声明

switch_application_interface_t *app_interface;  // 声明一个app_interface
switch_api_interface_t *api_interface;  // 声明一个api_interface

// APP回调函数
SWITCH_STANDARD_APP(book_function) {
  const char* name;

  if(zstr(data)) {  // data为传给该函数的参数
    name = "No Name";
  } else {
    name = data;
  }

  switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "I'm a book, My name is: %s\n", name);
}

// dialplan的回调函数
SWITCH_STANDARD_DIALPLAN(book_dialplan_hunt) {
  switch_caller_extension_t *extension = NULL;  // 创建一个分机,用于dialplan查找时候返回该分机
  switch_channel_t *channel = switch_core_session_get_channel(session);  // 获取通话的channel
  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);

  // 创建一个分机,因为查找dialplan的结果是返回一个extension
  extension = switch_caller_extension_new(session, "book_extension_name", "book_extension_number");

  if (!extension) abort();

  // 往分机里面添加一个application,用于后续EXECUTE执行阶段执行该app
  switch_caller_extension_add_application(session, extension, "book", "Use my book application");

  return extension;
}

// API回调函数
SWITCH_STANDARD_API(book_api_function) {
  const char* name;

  if (zstr(cmd)) {  // 从命令行读取
    name = "api no name";
  } else {
    name = cmd;
  }

  stream->write_function(stream, "I'm book api function, my name is: %s\n", name);  // 输出

  return SWITCH_STATUS_SUCCESS;
}

// 模块加载函数的定义
SWITCH_MODULE_LOAD_FUNCTION(mod_book_load) {
  switch_dialplan_interface_t *dp_interface;  // 定义一个dialplan

  *module_interface = switch_loadable_module_create_module_interface(pool, modname);

  // 向核心注册dialplan,并设置一个回调函数
  SWITCH_ADD_DIALPLAN(dp_interface, "book", book_dialplan_hunt);

  // 向核心注册APP,回调函数为book_function
  SWITCH_ADD_APP(app_interface, "book", "book example short descript", "book example long descript.......", book_function, "[name]", SAF_SUPPORT_NOMEDIA);

  // 向核心注册API,回调函数为book_api_function
  SWITCH_ADD_API(api_interface, "book", "book example description", book_api_function, "[name]");

  return SWITCH_STATUS_SUCCESS;
}

重新编译安装模块

sh 复制代码
make install

重新加载模块

使用reload mod_book重新加载模块

sh 复制代码
freeswitch@debianh61> reload mod_book 
2025-04-06 09:41:15.674474 99.67% [NOTICE] switch_loadable_module.c:1197 Deleting Dialplan 'book'
2025-04-06 09:41:15.674474 99.67% [NOTICE] switch_loadable_module.c:1230 Deleting Application 'book'
2025-04-06 09:41:15.674474 99.67% [CONSOLE] switch_loadable_module.c:2357 mod_book has no shutdown routine
2025-04-06 09:41:15.674474 99.67% [CONSOLE] switch_loadable_module.c:2374 mod_book unloaded.
2025-04-06 09:41:15.674474 99.67% [INFO] mod_enum.c:884 ENUM Reloaded
2025-04-06 09:41:15.674474 99.67% [INFO] switch_time.c:1436 Timezone reloaded 597 definitions
2025-04-06 09:41:15.674474 99.67% [CONSOLE] switch_loadable_module.c:1772 Successfully Loaded [mod_book]

+OK Reloading XML
+OK module unloaded
+OK module loaded

2025-04-06 09:41:15.674474 99.67% [NOTICE] switch_loadable_module.c:273 Adding Dialplan 'book'
2025-04-06 09:41:15.674474 99.67% [NOTICE] switch_loadable_module.c:329 Adding Application 'book'
2025-04-06 09:41:15.674474 99.67% [NOTICE] switch_loadable_module.c:389 Adding API Function 'book'

测试

我们在命令行输入bookbook test666test777进行测试

sh 复制代码
freeswitch@debianh61> book 

I'm book api function, my name is: api no name


freeswitch@debianh61> book test666test777

I'm book api function, my name is: test666test777

示例程序宏定义参考

SWITCH_MODULE_LOAD_FUNCTION

c 复制代码
#define SWITCH_MODULE_LOAD_ARGS (switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool)

#define SWITCH_MODULE_LOAD_FUNCTION(name) switch_status_t name SWITCH_MODULE_LOAD_ARGS

SWITCH_ADD_APP

c 复制代码
#define SWITCH_ADD_APP(app_int, int_name, short_descript, long_descript, funcptr, syntax_string, app_flags) \
	for (;;) { \
	app_int = (switch_application_interface_t *)switch_loadable_module_create_interface(*module_interface, SWITCH_APPLICATION_INTERFACE); \
	app_int->interface_name = int_name; \
	app_int->application_function = funcptr; \
	app_int->short_desc = short_descript; \
	app_int->long_desc = long_descript; \
	app_int->syntax = syntax_string; \
	app_int->flags = app_flags; \
	break; \
	}

SWITCH_ADD_API

c 复制代码
#define SWITCH_ADD_API(api_int, int_name, descript, funcptr, syntax_string) \
	for (;;) { \
	api_int = (switch_api_interface_t *)switch_loadable_module_create_interface(*module_interface, SWITCH_API_INTERFACE); \
	api_int->interface_name = int_name; \
	api_int->desc = descript; \
	api_int->function = funcptr; \
	api_int->syntax = syntax_string; \
	break; \
	}

SWITCH_ADD_DIALPLAN

c 复制代码
#define SWITCH_ADD_DIALPLAN(dp_int, int_name, funcptr) \
	for (;;) { \
	dp_int = (switch_dialplan_interface_t *)switch_loadable_module_create_interface(*module_interface, SWITCH_DIALPLAN_INTERFACE); \
	dp_int->hunt_function = funcptr; \
	dp_int->interface_name = int_name; \
	break; \
	}

SWITCH_STANDARD_APP

c 复制代码
#define SWITCH_STANDARD_APP(name) static void name (switch_core_session_t *session, const char *data)

SWITCH_STANDARD_API

c 复制代码
#define SWITCH_STANDARD_API(name) static switch_status_t name (_In_opt_z_ const char *cmd, _In_opt_ switch_core_session_t *session, _In_ switch_stream_handle_t *stream)

SWITCH_MODULE_DEFINITION

c 复制代码
#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_STANDARD_DIALPLAN

c 复制代码
#define SWITCH_STANDARD_DIALPLAN(name) static switch_caller_extension_t *name (switch_core_session_t *session, void *arg, switch_caller_profile_t *caller_profile)

参考资料

FreeSWITCH权威指南

相关推荐
慕容莞青6 小时前
MATLAB语言的进程管理
开发语言·后端·golang
陈明勇6 小时前
用 Go 语言轻松构建 MCP 客户端与服务器
后端·go·mcp
麻芝汤圆8 小时前
MapReduce 的广泛应用:从数据处理到智能决策
java·开发语言·前端·hadoop·后端·servlet·mapreduce
努力的搬砖人.8 小时前
java如何实现一个秒杀系统(原理)
java·经验分享·后端·面试
怒放吧德德8 小时前
实际应用:使用Nginx实现代理与服务治理
后端·nginx
6<78 小时前
【go】空接口
开发语言·后端·golang
Asthenia04129 小时前
BCrypt vs MD5:加盐在登录流程和数据库泄露中的作用
后端
追逐时光者9 小时前
由 MCP 官方推出的 C# SDK,使 .NET 应用程序、服务和库能够快速实现与 MCP 客户端和服务器交互!
后端·.net·mcp
AskHarries9 小时前
如何获取oracle cloud永久免费的vps(4C/24G)?
后端
烛阴10 小时前
Express入门必学三件套:路由、中间件、模板引擎全解析
javascript·后端·express