• 【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


    前言

    之前在【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
    
    • 1
    • 2
    • 3
    • 4

    运行完编译命令后,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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    其中, 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;
     }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    连接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");
    
    • 1
    • 2

    事件订阅用到的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;
    
    • 1
    • 2
    • 3
    • 4
    • 5

    5、事件监听

    esl_recv_event_timed(handle, 10, 1, NULL);
    
    • 1

    完整的定义如下:

    /*!
        \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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    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");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126

    本例子是博主亲自实验过的,基于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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    总结

    以上就是今天的内容,详细的介绍了C语言使用ESL连接FreeSwitch。

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

  • 相关阅读:
    leetcode每天5题-Day31
    A-古代汉语知识点整理大全
    Unity调用C++ dll的那些坑
    java常见集合
    Python学习------Day05
    企业架构LNMP学习笔记46
    代码随想录算法训练营刷题复习4 :单调栈
    阿里二面:SpringCloud 有几种服务调用方式?
    spark性能调优 | 内存优化
    SpringCloud-MQ消息队列
  • 原文地址:https://blog.csdn.net/xxm524/article/details/125840597