个人理解
freeswitch中呼叫是基于一个具体的实体的,那就是channel。
channel和leg是一一对应的,单腿呼叫那就是只有一个channel,双腿呼叫就是两个channel。
甚至于3个腿及3个腿以上的会议呼叫。
但是最基础的单位还是channel。
对应到具体的应用上来,手机呼入到freeswitch,就会有一个呼入的channel。
freeswitch呼出到手机,就会有一个呼出的channel。
所以,channel中天然的,就需要绑定一些比如主被叫号码、呼叫方向、编码等等信息,来指代这通呼叫。
同时,channel也有自己的状态,代表着进入freeswitch之后,走过的各个阶段:从刚建立呼叫、寻找拨号规则、确认执行的命令、接通、挂断、销毁等等。
现在就来总结下freeswitch使用的基本数据结构。
Session与channel的关系
这里引用一下: 链接
Session 与 Channel
对每一次呼叫,FreeSWITCH 都会启动一个 Session(会话,它包含SIP会话,SIP会在每对UAC-UAS之间生成一个 SIP Session),用于控制整个呼叫,它会一直持续到通话结束。其中,每个 Session 都控制着一个 Channel(信道),Channel 是一对 UA 间通信的实体,相当于 FreeSWITCH 的一条腿(leg),每个 Channel 都有一个唯一的 UUID。另外,Channel 上可以绑定一些呼叫参数,称为 Channel Variable(信道变量)。Channel 中可能包含媒体(音频或视频流),也可能不包含。通话时,FreeSWITCH 的作用是将两个 Channel(a-leg 和 b-leg,通常先创建的或占主动的叫 a-leg)桥接(bridge)到一起,使双方可以通话。 通话中,媒体(音频或视频)数据流在 RTP 包中传送(不同于 SIP, RTP是另外的协议)。一般来说,Channel是双向的,因此,媒体流会有发送(Send/Write)和接收(Receive/Read)两个方向。
这里说的Session,对应的是代码中的switch_core_session结构体,该结构体里面就是包含了switch_channel_t。
简陋的UML图
基本数据结构
会话:switch_core_session
文件:switch_core_pvt.h
该文件属于内核私有数据,外部模块无法直接访问其数据内容,只能通过switch_core_session.c提供的函数来访问session内部对象。
struct switch_core_session {
switch_memory_pool_t *pool;
switch_thread_t *thread;
switch_thread_id_t thread_id;
switch_endpoint_interface_t *endpoint_interface;
switch_size_t id;
switch_session_flag_t flags;
switch_channel_t *channel;
switch_io_event_hooks_t event_hooks;
switch_codec_t *read_codec;
switch_codec_t *real_read_codec;
switch_codec_t *write_codec;
switch_codec_t *real_write_codec;
switch_codec_t *video_read_codec;
switch_codec_t *video_write_codec;
...
};
通道: switch_channel
文件: switch_channel.c
struct switch_channel {
char *name;
switch_call_direction_t direction;
switch_call_direction_t logical_direction;
switch_queue_t *dtmf_queue;
switch_queue_t *dtmf_log_queue;
switch_mutex_t*dtmf_mutex;
switch_mutex_t *flag_mutex;
switch_mutex_t *state_mutex;
switch_mutex_t *thread_mutex;
switch_mutex_t *profile_mutex;
switch_core_session_t *session;
switch_channel_state_t state;
switch_channel_state_t running_state;
switch_channel_callstate_t callstate;
uint32_t flags[CF_FLAG_MAX];
uint32_t caps[CC_FLAG_MAX];
uint8_t state_flags[CF_FLAG_MAX];
uint32_t private_flags;
switch_caller_profile_t *caller_profile;
const switch_state_handler_table_t *state_handlers[SWITCH_MAX_STATE_HANDLERS];
int state_handler_index;
switch_event_t *variables;
switch_event_t *scope_variables;
switch_hash_t *private_hash;
switch_hash_t *app_flag_hash;
switch_call_cause_t hangup_cause;
int vi;
int event_count;
int profile_index;
opaque_channel_flag_t opaque_flags;
switch_originator_type_t last_profile_type;
switch_caller_extension_t *queued_extension;
switch_event_t *app_list;
switch_event_t *api_list;
switch_event_t *var_list;
switch_hold_record_t *hold_record;
switch_device_node_t *device_node;
char *device_id;
};
通道状态:switch_channel_state_t
文件:switch_types.h
typedef enum {
CS_NEW, //channel刚建立
CS_INIT, //channel完成初始化
CS_ROUTING, //channel处于寻找合适的拨号规则中
CS_SOFT_EXECUTE, //channel已可以接收来自esl等第三方发来的控制指令(该状态会被其他app重置)
CS_EXECUTE, //channel找到拨号规则后,处于执行中
CS_EXCHANGE_MEDIA, //channel和其他channel进行媒体交互(该状态会被其他app重置)
CS_PARK, //channel处于媒体接收中,等待进一步的指令
CS_CONSUME_MEDIA, //channel处于媒体消费中(该状态会被其他app重置)
CS_HIBERNATE, //channel处于睡眠状态
CS_RESET, //channel处于重置状态
CS_HANGUP, //channel被标记为hangup,等待真正结束
CS_REPORTING, //channel准备收集呼叫数据
CS_DESTROY, //channel准备销毁,退出状态机
CS_NONE
} switch_channel_state_t;
呼叫状态:switch_channel_callstate_t
文件:
typedef enum {
CCS_DOWN,
CCS_DIALING,
CCS_RINGING,
CCS_EARLY,
CCS_ACTIVE,
CCS_HELD,
CCS_RING_WAIT,
CCS_HANGUP,
CCS_UNHOLD
} switch_channel_callstate_t;
主叫上下文:switch_caller_profile
从呼入的场景来看, caller profile主要是保存主叫的相关信息,在拨号方案中都可以用着这里变量。
文件:switch_caller.h
struct switch_caller_profile {
/*! The Call's User Name */
const char *username;
/*! The name of the dialplan */
const char *dialplan;
/*! Caller ID Name */
const char *caller_id_name;
/*! Caller ID Number */
const char *caller_id_number;
/*! Original Caller ID Name */
const char *orig_caller_id_name;
/*! Original Caller ID Number */
const char *orig_caller_id_number;
/*! Callee ID Name */
const char *callee_id_name;
/*! Callee ID Number */
const char *callee_id_number;
uint8_t caller_ton;
uint8_t caller_numplan;
/*! Caller Network Address (when applicable) */
const char *network_addr;
/*! ANI (when applicable) */
const char *ani;
uint8_t ani_ton;
uint8_t ani_numplan;
/*! ANI II (when applicable) */
const char *aniii;
/*! RDNIS */
const char *rdnis;
uint8_t rdnis_ton;
uint8_t rdnis_numplan;
/*! Destination Number */
char *destination_number;
uint8_t destination_number_ton;
uint8_t destination_number_numplan;
/*! channel type */
const char *source;
/*! channel name */
char *chan_name;
/*! unique id */
char *uuid;
/*! context */
const char *context;
/*! profile index */
const char *profile_index;
/*! flags */
switch_caller_profile_flag_t flags;
struct switch_caller_profile *originator_caller_profile;
struct switch_caller_profile *originatee_caller_profile;
struct switch_caller_profile *origination_caller_profile;
struct switch_caller_profile *hunt_caller_profile;
struct switch_channel_timetable *times;
struct switch_channel_timetable *old_times;
struct switch_caller_extension *caller_extension;
switch_memory_pool_t *pool;
struct switch_caller_profile *next;
switch_call_direction_t direction;
switch_call_direction_t logical_direction;
profile_node_t *soft;
char *uuid_str;
char *clone_of;
char *transfer_source;
};
基本流程
简化流程
- originate命令实例
- originate sofia/internal/18xxxx@xx:xx &record_session(/test.wav)
- originate sofia/internal/18xxxx@xx:xx calloutbot XML default
- 收到命令后,效验参数
- 调用switch_ivr_originate发起呼叫,如果呼叫成功的话,将session赋给switch_core_session_t *caller_session
- 如果命令中含有&,处理如下:
- 基于当前session,新建一个extension
- 将&后面的app和app参数,保存到extension中
- 将extension设置到channel中
- 将channel状态设置为CS_EXECUTE
- 命令中不含有&,处理如下:
- 调用switch_ivr_session_transfer,直接将session转到命令参数中指定的extension上。
新建extension,并增加app
如果是解析xml文件生成的extension,则里面保存的是解析完成后所有的action列表。
此处originate不需要走拨号规则,所以直接跳过一步,直接创建extension,然后往里面存app
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);
extension数据结构
文件:switch_caller.h
struct switch_caller_extension {
/*! The name of the extension */
char *extension_name;
/*! The number of the extension */
char *extension_number;
/*! Pointer to the current application for this extension */
switch_caller_application_t *current_application;
/*! Pointer to the last application for this extension */
switch_caller_application_t *last_application;
/*! Pointer to the entire stack of applications for this extension */
switch_caller_application_t *applications;
struct switch_caller_profile *children;
struct switch_caller_extension *next;
};