🏠 作 者:小小马车夫
🍅 所属专栏:【FreeSwitch开发实践】
🥝 专栏介绍:主要介绍博主在实际项目中使用FreeSwitch开发外呼类项目的一些经验心得,主要涉及FreeSwitch的基本安装编译、基本配置、ESL、WSS、录音、自定义模块、media bug、语音播放、MRCP及对接AI机器人等内容。内容在持续更新中,如果感兴趣可以对专栏进行订阅~
🍒 对自己说的话:间歇性的努力和蒙混过日子,都是对之前努力的清零。
系列文章目录
【FreeSwitch开发实践】centos7下编译安装freeswitch及常见编译问题的解决
【FreeSwitch开发实践】freeswitch配置wss
【FreeSwitch开发实践】freeswitch配置wss证书问题 Encrypted Alert/Certification Unknown
【FreeSwitch开发实践】ESL简介
【FreeSwitch开发实践】ESL配置
【FreeSwitch开发实践】在nodejs中用ESL连接FreeSwitch
【FreeSwitch开发实践】死锁问题解决Over Session Limit 1000/Locked, Waiting on external entities
前言
之前在【FreeSwitch开发实践】在nodejs中用ESL连接FreeSwitch一文介绍了在NodeJS
下使用ESL
连接FreeSwitch
, 本文则对在C语言下使用ESL连接FreeSwitch作了一个系统介绍。和NodeJS下使用ESL需要安装modesl模块一样,C语言下使用ESL也需要libesl
库.
(本文代码示例下载地址)
1、libesl库编译安装
libesl
库在FreeSwitch中是自带的,所以编译FreeSwitch
的时候,实际已经安装了libesl,这里单独对FreeSwitch下编译libesl简要说明下:
#进入FreeSwitch下libesl源码目录
/data/freeswitch/libs/esl
make
make install
运行完编译命令后,libesl实际已经安装到了FreeSwitch的安装目录下:
/usr/local/freeswitch/lib/
2、在Makefile中引入libesl
上一节说明了,libesl的编译安装过程以及编译后库的生成目录,这里对在
Makefile
中引入libesl
,介绍下。
Makefile如下:
TOP_PATH := $(shell pwd)
INCLUDE := -I/data/freeswitch/libs/esl/src/include
LIBS_PATH := -L/usr/local/freeswitch/lib
LIBS := -lesl -lpthread
CC := gcc
TARGET := esl_test
SRCDIRS := ./
CFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.c))
CFILENDIR := $(CFILES)
COBJS := $(patsubst %, %, $(CFILENDIR:.c=.o))
.PHONY: clean
$(TARGET):$(COBJS)
$(CC) $^ $(INCLUDE) $(LIBS_PATH) $(LIBS) -o $(TOP_PATH)/$@
$(COBJS) : %.o : %.c
$(CC) -c $< $(INCLUDE) $(LIBS_PATH) $(LIBS) -o $@
clean:
@rm *.o
@rm $(TOP_PATH)/$(TARGET)
其中,
INCLUDE
是FreeSwitch中esl头文件
的目录,LIBS_PATH
是libesl的最终生成目录,LIBS
为引入的库,-lesl
则为libesl.a
库的引入。
3、ESL连接FreeSwitch
先看一下用
C语言
版本的ESL
连接FreeSwitch
esl_handle_t handle = {{0}};
esl_global_set_default_logger(ESL_LOG_LEVEL_DEBUG);
memset(&handle, 0, sizeof(handle));
if (esl_connect_timeout(&handle, "10.0.8.10", 8021, "", "ClueCon", 3000)) {
esl_global_set_default_logger(7);
esl_log(ESL_LOG_ERROR, "Error Connecting [%s]\n", handle.err);
return -1;
}
连接FreeSwitch用的接口是
esl_connect_timeout
, 需要的参数分别是·ip
、端口
、密码
和超时时间,基本和NodeJS中类似。
4、ESL事件订阅
esl_events(&handle, ESL_EVENT_TYPE_PLAIN, "CHANNEL_ANSWER");
esl_events(&handle, ESL_EVENT_TYPE_PLAIN, "CHANNEL_HANGUP_COMPLETE");
事件订阅用到的
esl_events
接口, 第3个参数是事件名称
, 第2个参数是事件体的类型,包括plain/xml/json
,定义如下:
typedef enum {
ESL_EVENT_TYPE_PLAIN,
ESL_EVENT_TYPE_XML,
ESL_EVENT_TYPE_JSON
} esl_event_type_t;
5、事件监听
esl_recv_event_timed(handle, 10, 1, NULL);
完整的定义如下:
/*!
\brief Poll the handle's socket until an event is received, a connection error occurs or ms expires
\param handle Handle to poll
\param ms Maximum time to poll
\param check_q If set to 1, will check the handle queue (handle->race_event) and return the last event from it
\param[out] save_event If this is not NULL, will return the event received
*/
ESL_DECLARE(esl_status_t) esl_recv_event_timed(esl_handle_t *handle, uint32_t ms, int check_q, esl_event_t **save_event);
6、完整的例子
下面是完整的例子,包括
ESL连接
,发送命令
和事件订阅
、事件监听
。
#include <stdio.h>
#include <stdlib.h>
#include <esl.h>
#include <signal.h>
#include <getopt.h>
static void _sleep_ns(int secs, long nsecs) {
#ifndef WIN32
if (nsecs > 999999999) {
secs += nsecs/1000000000;
nsecs = nsecs % 1000000000;
}
{
struct timespec ts = { secs, nsecs };
nanosleep(&ts, NULL);
}
#else
Sleep(secs*1000 + nsecs/1000000);
#endif
}
static void sleep_ns(long nsecs) { _sleep_ns(0, nsecs); }
static void sleep_ms(int msecs) { sleep_ns(msecs*1000000); }
static void sleep_s(int secs) { _sleep_ns(secs, 0); }
static void *msg_thread_run(esl_thread_t *me, void *obj);
static esl_mutex_t *MUTEX = NULL;
static int thread_running = 0, thread_up = 0, check_up = 0;
int main()
{
char cmd_str[2048] = "";
esl_handle_t handle = {{0}};
esl_global_set_default_logger(ESL_LOG_LEVEL_DEBUG);
memset(&handle, 0, sizeof(handle));
//ESL连接FreeSwitch
if (esl_connect_timeout(&handle, "10.0.8.10", 8021, "", "ClueCon", 3000)) {
esl_global_set_default_logger(7);
esl_log(ESL_LOG_ERROR, "Error Connecting [%s]\n", handle.err);
return -1;
}
esl_mutex_create(&MUTEX);
//启动ESL事件临听线程
if (esl_thread_create_detached(msg_thread_run, &handle) != ESL_SUCCESS) {
esl_log(ESL_LOG_ERROR, "Error starting thread!\n");
esl_disconnect(&handle);
return 0;
}
//ESL事件订阅
esl_events(&handle, ESL_EVENT_TYPE_PLAIN, "CHANNEL_ANSWER");
esl_events(&handle, ESL_EVENT_TYPE_PLAIN, "CHANNEL_HANGUP_COMPLETE");
//发送api version命令
snprintf(cmd_str, sizeof(cmd_str), "api version\n\n");
esl_send_recv(&handle, cmd_str);
//接收api version返回
if (handle.last_sr_event && handle.last_sr_event->body) {
esl_log(ESL_LOG_INFO, "%s\n", handle.last_sr_event->body);
}
//拨打软电话命令api originate user/1000 &echo
snprintf(cmd_str, sizeof(cmd_str), "api originate user/1000 &echo\n\n");
esl_send_recv(&handle, cmd_str);
if (handle.last_sr_event && handle.last_sr_event->body) {
esl_log(ESL_LOG_INFO, "%s\n", handle.last_sr_event->body);
}
//等待,防止主线程退出
while(handle.connected)
{
sleep_ms(10);
}
//断开ESL连接
esl_disconnect(&handle);
//等待ESL事件监听线程退出
do {
esl_mutex_lock(MUTEX);
check_up = thread_up;
esl_mutex_unlock(MUTEX);
sleep_ms(10);
} while (check_up > 0);
esl_mutex_destroy(&MUTEX);
}
//事件监听线程
static void *msg_thread_run(esl_thread_t *me, void *obj)
{
esl_handle_t *handle = (esl_handle_t *) obj;
thread_running = 1;
esl_mutex_lock(MUTEX);
thread_up = 1;
esl_mutex_unlock(MUTEX);
while(thread_running && handle->connected) {
int aok = 1;
esl_status_t status;
esl_mutex_lock(MUTEX);
//等待事件到来,只有esl_events注册的事件才能监听到
status = esl_recv_event_timed(handle, 10, 1, NULL);
esl_mutex_unlock(MUTEX);
if (status == ESL_BREAK) {
sleep_ms(1);
} else if (status == ESL_FAIL) {
esl_log(ESL_LOG_WARNING, "Disconnected.\n");
thread_running = 0;
} else if (status == ESL_SUCCESS) {
//事件到来,打印事件体
esl_log(ESL_LOG_INFO, "coming event_body:%xs\n", handle->last_event);
if (handle->last_event->body) {
esl_log(ESL_LOG_INFO, "event_body:%s\n", handle->last_event->body);
}
}
}
esl_mutex_lock(MUTEX);
thread_up = 0;
esl_mutex_unlock(MUTEX);
thread_running = 0;
esl_log(ESL_LOG_DEBUG, "Thread Done\n");
}
本例子是博主亲自实验过的,基于
fs_cli
实现,如果读者有兴趣可以详细看一下fs_cli
源码,应该会有更多收获。
输出:
[root@VM-8-10-centos esl_test]# ./esl_test
[DEBUG] esl.c:1303 esl_recv_event() RECV HEADER [Content-Type] = [auth/request]
[DEBUG] esl.c:1467 esl_recv_event() RECV MESSAGE
Event-Name: SOCKET_DATA
Content-Type: auth/request
[DEBUG] esl.c:1495 esl_send() SEND
auth ClueCon
[DEBUG] esl.c:1303 esl_recv_event() RECV HEADER [Content-Type] = [command/reply]
[DEBUG] esl.c:1303 esl_recv_event() RECV HEADER [Reply-Text] = [+OK accepted]
[DEBUG] esl.c:1467 esl_recv_event() RECV MESSAGE
Event-Name: SOCKET_DATA
Content-Type: command/reply
Reply-Text: +OK accepted
[DEBUG] esl.c:1495 esl_send() SEND
event plain CHANNEL_ANSWER
[DEBUG] esl.c:1303 esl_recv_event() RECV HEADER [Content-Type] = [command/reply]
[DEBUG] esl.c:1303 esl_recv_event() RECV HEADER [Reply-Text] = [+OK event listener enabled plain]
[DEBUG] esl.c:1495 esl_send() SEND
event plain CHANNEL_HANGUP_COMPLETE
[DEBUG] esl.c:1303 esl_recv_event() RECV HEADER [Content-Type] = [command/reply]
[DEBUG] esl.c:1303 esl_recv_event() RECV HEADER [Reply-Text] = [+OK event listener enabled plain]
[DEBUG] esl.c:1495 esl_send() SEND
api version
[DEBUG] esl.c:1303 esl_recv_event() RECV HEADER [Content-Type] = [api/response]
[DEBUG] esl.c:1303 esl_recv_event() RECV HEADER [Content-Length] = [113]
[INFO] test.c:59 main() FreeSWITCH Version 1.10.7-release+git~20211024T163933Z~883d2cb662~64bit (git 883d2cb 2021-10-24 16:39:33Z 64bit)
[DEBUG] esl.c:1495 esl_send() SEND
api originate user/1000 &echo
answer事件
[INFO] test.c:108 msg_thread_run() coming event_body:a40008c0s
[INFO] test.c:110 msg_thread_run() event_body:Event-Name: CHANNEL_ANSWER
Core-UUID: 8f363510-c0cf-4aa4-bbfc-577cc6ff543b
FreeSWITCH-Hostname: VM-8-10-centos
FreeSWITCH-Switchname: VM-8-10-centos
FreeSWITCH-IPv4: 10.0.8.10
FreeSWITCH-IPv6: fe80%3A%3A5054%3Aff%3Afe58%3A28ea
Event-Date-Local: 2022-07-17%2022%3A34%3A37
Event-Date-GMT: Sun,%2017%20Jul%202022%2014%3A34%3A37%20GMT
Event-Date-Timestamp: 1658068477404961
Event-Calling-File: switch_channel.c
Event-Calling-Function: switch_channel_perform_mark_answered
Event-Calling-Line-Number: 3884
Event-Sequence: 646
Channel-State: CS_CONSUME_MEDIA
Channel-Call-State: RINGING
Channel-State-Number: 7
Channel-Name: sofia/internal/1000%4061.149.73.246%3A3387
Unique-ID: ba515d00-58f1-4433-af24-f0cafeac7e4e
Call-Direction: outbound
Presence-Call-Direction: outbound
Channel-HIT-Dialplan: false
Channel-Presence-ID: 1000%4010.0.8.10
Channel-Call-UUID: ba515d00-58f1-4433-af24-f0cafeac7e4e
Answer-State: answered
Channel-Read-Codec-Name: PCMU
Channel-Read-Codec-Rate: 8000
Channel-Read-Codec-Bit-Rate: 64000
Channel-Write-Codec-Name: PCMU
Channel-Write-Codec-Rate: 8000
Channel-Write-Codec-Bit-Rate: 64000
Caller-Direction: outbound
Caller-Logical-Direction: outbound
Caller-Caller-ID-Number: 0000000000
Caller-Orig-Caller-ID-Number: 0000000000
Caller-Callee-ID-Name: Outbound%20Call
Caller-Callee-ID-Number: 1000
Caller-Network-Addr: 61.149.73.246
Caller-ANI: 0000000000
Caller-Destination-Number: 1000
Caller-Unique-ID: ba515d00-58f1-4433-af24-f0cafeac7e4e
Caller-Source: src/switch_ivr_originate.c
Caller-Context: default
Caller-Channel-Name: sofia/internal/1000%4061.149.73.246%3A3387
Caller-Profile-Index: 1
Caller-Profile-Created-Time: 1658068471904968
Caller-Channel-Created-Time: 1658068471904968
Caller-Channel-Answered-Time: 1658068477404961
Caller-Channel-Progress-Time: 1658068471984961
Caller-Channel-Progress-Media-Time: 0
Caller-Channel-Hangup-Time: 0
Caller-Channel-Transfer-Time: 0
Caller-Channel-Resurrect-Time: 0
Caller-Channel-Bridged-Time: 0
Caller-Channel-Last-Hold: 0
Caller-Channel-Hold-Accum: 0
Caller-Screen-Bit: true
Caller-Privacy-Hide-Name: false
Caller-Privacy-Hide-Number: false
variable_direction: outbound
variable_is_outbound: true
hangup事件
[INFO] test.c:108 msg_thread_run() coming event_body:8400df60s
[INFO] test.c:110 msg_thread_run() event_body:Event-Name: CHANNEL_HANGUP_COMPLETE
Core-UUID: f7005cb2-45e7-4643-ae7e-d10d45a7b4d1
FreeSWITCH-Hostname: VM-8-10-centos
FreeSWITCH-Switchname: VM-8-10-centos
FreeSWITCH-IPv4: 10.0.8.10
FreeSWITCH-IPv6: fe80%3A%3A5054%3Aff%3Afe58%3A28ea
Event-Date-Local: 2022-07-17%2022%3A32%3A42
Event-Date-GMT: Sun,%2017%20Jul%202022%2014%3A32%3A42%20GMT
Event-Date-Timestamp: 1658068362698622
Event-Calling-File: switch_core_state_machine.c
Event-Calling-Function: switch_core_session_reporting_state
Event-Calling-Line-Number: 943
Event-Sequence: 846950
Hangup-Cause: NORMAL_CLEARING
Channel-State: CS_REPORTING
Channel-Call-State: HANGUP
Channel-State-Number: 11
Channel-Name: sofia/external/1001%4010.0.8.10
Unique-ID: 44c3b4f0-7091-49ea-8f61-8a368648609a
Call-Direction: inbound
Presence-Call-Direction: inbound
Channel-HIT-Dialplan: true
Channel-Call-UUID: 44c3b4f0-7091-49ea-8f61-8a368648609a
Answer-State: hangup
Hangup-Cause: NORMAL_CLEARING
Caller-Direction: inbound
Caller-Logical-Direction: inbound
Caller-Username: 1001
Caller-Dialplan: XML
Caller-Caller-ID-Name: 1001
Caller-Caller-ID-Number: 1001
Caller-Orig-Caller-ID-Name: 1001
Caller-Orig-Caller-ID-Number: 1001
Caller-Network-Addr: 20.52.185.66
Caller-ANI: 1001
Caller-Destination-Number: 2985720103
总结
以上就是今天的内容,详细的介绍了C语言使用ESL连接FreeSwitch。
如果觉得有些帮助或觉得文章还不错,请关注一下博主,你的关注是我持续写作的动力。另外,如果有什么问题,可以在评论区留言,或者私信博主,博主看到后会第一时间回复。