个人理解
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;};
