【FreeSwitch开发实践】C语言中使用ESL连接FreeSwitch

作     者:小小马车夫
所属专栏:【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

文章目录

  • 系列文章目录
  • 前言
  • 1、libesl库编译安装
  • 2、在Makefile中引入libesl
  • 3、ESL连接FreeSwitch
  • 4、ESL事件订阅
  • 5、事件监听
  • 6、完整的例子
  • 总结


前言

之前在【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 
#include 
#include 
#include 
#include 

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。

如果觉得有些帮助或觉得文章还不错,请关注一下博主,你的关注是我持续写作的动力。另外,如果有什么问题,可以在评论区留言,或者私信博主,博主看到后会第一时间回复。

你可能感兴趣的:(FreeSwitch开发实践,ESL,libesl,连接,FreeSwitch,C语言)