freeswitch mrcp 源码分析--event事件产生

event事件的构建主要在speech_thread()函数里面。

static void *SWITCH_THREAD_FUNC speech_thread(switch_thread_t *thread, void *obj)
{
    struct speech_thread_handle *sth = (struct speech_thread_handle *) obj;
    switch_channel_t *channel = switch_core_session_get_channel(sth->session);
    switch_asr_flag_t flags = SWITCH_ASR_FLAG_NONE;
    switch_status_t status;
    switch_event_t *event;

    switch_thread_cond_create(&sth->cond, sth->pool);
    switch_mutex_init(&sth->mutex, SWITCH_MUTEX_NESTED, sth->pool);

    if (switch_core_session_read_lock(sth->session) != SWITCH_STATUS_SUCCESS) {
        sth->ready = 0;
        return NULL;
    }

    switch_mutex_lock(sth->mutex);

    sth->ready = 1;

    while (switch_channel_up_nosig(channel) && !switch_test_flag(sth->ah, SWITCH_ASR_FLAG_CLOSED)) {
        char *xmlstr = NULL;
        switch_event_t *headers = NULL;

        switch_thread_cond_wait(sth->cond, sth->mutex);

        if (switch_channel_down_nosig(channel) || switch_test_flag(sth->ah, SWITCH_ASR_FLAG_CLOSED)) {
            break;
        }

        if (switch_core_asr_check_results(sth->ah, &flags) == SWITCH_STATUS_SUCCESS) {

            status = switch_core_asr_get_results(sth->ah, &xmlstr, &flags);

            if (status != SWITCH_STATUS_SUCCESS && status != SWITCH_STATUS_BREAK) {
                goto done;
            } else if (status == SWITCH_STATUS_SUCCESS) {
                /* Try to fetch extra information for this result, the return value doesn't really matter here - it's just optional data. */
                switch_core_asr_get_result_headers(sth->ah, &headers, &flags);
            }

            if (status == SWITCH_STATUS_SUCCESS && switch_true(switch_channel_get_variable(channel, "asr_intercept_dtmf"))) {
                const char *p;

                if ((p = switch_stristr("<input>", xmlstr))) {
                    p += 7;
                }

                while (p && *p) {
                    char c;

                    if (*p == '<') {
                        break;
                    }

                    if (!strncasecmp(p, "pound", 5)) {
                        c = '#';
                        p += 5;
                    } else if (!strncasecmp(p, "hash", 4)) {
                        c = '#';
                        p += 4;
                    } else if (!strncasecmp(p, "star", 4)) {
                        c = '*';
                        p += 4;
                    } else if (!strncasecmp(p, "asterisk", 8)) {
                        c = '*';
                        p += 8;
                    } else {
                        c = *p;
                        p++;
                    }

                    if (is_dtmf(c)) {
                        switch_dtmf_t dtmf = {0};
                        dtmf.digit = c;
                        dtmf.duration = switch_core_default_dtmf_duration(0);
                        dtmf.source = SWITCH_DTMF_INBAND_AUDIO;
                        switch_log_printf(SWITCH_CHANNEL_CHANNEL_LOG(channel), SWITCH_LOG_DEBUG, "Queue speech detected dtmf %c\n", c);
                        switch_channel_queue_dtmf(channel, &dtmf);
                    }

                }
                switch_ivr_resume_detect_speech(sth->session);
            }

            if (switch_event_create(&event, SWITCH_EVENT_DETECTED_SPEECH) == SWITCH_STATUS_SUCCESS) {
                if (status == SWITCH_STATUS_SUCCESS) {
                    switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Speech-Type", "detected-speech");

                    if (headers) {
                        switch_event_merge(event, headers);
                    }

                    switch_event_add_body(event, "%s", xmlstr);
                } else {
                    switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Speech-Type", "begin-speaking");
                }

                if (switch_test_flag(sth->ah, SWITCH_ASR_FLAG_FIRE_EVENTS)) {
                    switch_event_t *dup;

                    if (switch_event_dup(&dup, event) == SWITCH_STATUS_SUCCESS) {
                        switch_channel_event_set_data(channel, dup);
                        switch_event_fire(&dup);
                    }

                }

                if (switch_core_session_queue_event(sth->session, &event) != SWITCH_STATUS_SUCCESS) {
                    switch_log_printf(SWITCH_CHANNEL_CHANNEL_LOG(channel), SWITCH_LOG_ERROR, "Event queue failed!\n");
                    switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "delivery-failure", "true");
                    switch_event_fire(&event);
                }
            }

            switch_safe_free(xmlstr);

            if (headers) {
                switch_event_destroy(&headers);
            }
        }
    }
  done:

    if (switch_event_create(&event, SWITCH_EVENT_DETECTED_SPEECH) == SWITCH_STATUS_SUCCESS) {
        switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Speech-Type", "closed");
        if (switch_test_flag(sth->ah, SWITCH_ASR_FLAG_FIRE_EVENTS)) {
            switch_event_t *dup;

            if (switch_event_dup(&dup, event) == SWITCH_STATUS_SUCCESS) {
                switch_channel_event_set_data(channel, dup);
                switch_event_fire(&dup);
            }

        }

        if (switch_core_session_queue_event(sth->session, &event) != SWITCH_STATUS_SUCCESS) {
            switch_log_printf(SWITCH_CHANNEL_CHANNEL_LOG(channel), SWITCH_LOG_ERROR, "Event queue failed!\n");
            switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "delivery-failure", "true");
            switch_event_fire(&event);
        }
    }

    switch_mutex_unlock(sth->mutex);
    switch_core_session_rwunlock(sth->session);

    return NULL;
}

可以看到里面是一个while循环,会阻塞在sth->cond这个信号了里面。在对应的session读取完一帧数据后会产生sth->cond这个信号量。这个信号量最后是在speech_callback里面产生的。详细的产生过程这里就不进行介绍。不过本人通过gdb调试将他的调用栈打了出来,可以自己分析一下。

#0  speech_callback (bug=0x7f7840125f68, user_data=0x7f7840135ce8, type=<optimized out>) at src/switch_ivr_async.c:4549
#1  0x00007f788a3bb9e7 in switch_core_session_read_frame (session=session@entry=0x7f7840051608, frame=frame@entry=0x7f7883fae068, flags=flags@entry=0, stream_id=stream_id@entry=0) at src/switch_core_io.c:742
#2  0x00007f788a427361 in audio_bridge_thread (obj=obj@entry=0x7f784012c8a0, thread=0x0) at src/switch_ivr_bridge.c:686
#3  0x00007f788a4294d5 in switch_ivr_multi_threaded_bridge (session=0x7f7840051608, peer_session=0x7f784008d258, input_callback=<optimized out>, session_data=<optimized out>, peer_session_data=0x7f784002df40)
    at src/switch_ivr_bridge.c:1616
#4  0x00007f788a48c001 in bridge (session_a=..., session_b=...) at src/switch_cpp.cpp:1420
#5  0x00007f783ee89461 in _wrap_bridge (L=0x7f7840003b40) at mod_lua_wrap.cpp:7791
#6  0x00007f783ec4b328 in luaD_precall () from /lib64/liblua-5.1.so
#7  0x00007f783ec55e7f in luaV_execute () from /lib64/liblua-5.1.so
#8  0x00007f783ec4b73d in luaD_call () from /lib64/liblua-5.1.so
#9  0x00007f783ec4aa6e in luaD_rawrunprotected () from /lib64/liblua-5.1.so
#10 0x00007f783ec4b8ca in luaD_pcall () from /lib64/liblua-5.1.so
#11 0x00007f783ec4744d in lua_pcall () from /lib64/liblua-5.1.so
#12 0x00007f783ee765e2 in docall (L=0x7f7840003b40, narg=0, nresults=0, perror=0, fatal=1) at mod_lua.cpp:92
#13 0x00007f783ee76bdd in lua_parse_and_execute (L=L@entry=0x7f7840003b40, input_code=<optimized out>, session=session@entry=0x0) at mod_lua.cpp:195
#14 0x00007f783ee76c77 in lua_thread_run (thread=<optimized out>, obj=0x7f7840004c40) at mod_lua.cpp:222
#15 0x00007f788a668210 in dummy_worker (opaque=0x7f7840004f48) at threadproc/unix/thread.c:151
#16 0x00007f7888479dc5 in start_thread () from /lib64/libpthre

回到speech_thread函数
进程被唤醒后会执行switch_core_asr_check_results()这个函数,去检查是否有识别结果数据。

SWITCH_DECLARE(switch_status_t) switch_core_asr_check_results(switch_asr_handle_t *ah, switch_asr_flag_t *flags)
{
    switch_assert(ah != NULL);

    return ah->asr_interface->asr_check_results(ah, flags);
}

asr_interface是个switch_asr_interface_t结构体指针,也是在recog_load()里面赋值的。

    asr_interface->interface_name = MOD_UNIMRCP;
    asr_interface->asr_open = recog_asr_open;
    asr_interface->asr_load_grammar = recog_asr_load_grammar;
    asr_interface->asr_unload_grammar = recog_asr_unload_grammar;
    asr_interface->asr_enable_grammar = recog_asr_enable_grammar;
    asr_interface->asr_disable_grammar = recog_asr_disable_grammar;
    asr_interface->asr_disable_all_grammars = recog_asr_disable_all_grammars;
    asr_interface->asr_close = recog_asr_close;
    asr_interface->asr_feed = recog_asr_feed;
    asr_interface->asr_feed_dtmf = recog_asr_feed_dtmf;
    asr_interface->asr_resume = recog_asr_resume;
    asr_interface->asr_pause = recog_asr_pause;
    asr_interface->asr_check_results = recog_asr_check_results;
    asr_interface->asr_get_results = recog_asr_get_results;
    asr_interface->asr_get_result_headers = recog_asr_get_result_headers;
    asr_interface->asr_start_input_timers = recog_asr_start_input_timers;
    asr_interface->asr_text_param = recog_asr_text_param;
    asr_interface->asr_numeric_param = recog_asr_numeric_param;
    asr_interface->asr_float_param = recog_asr_float_param;

所以其实调用的是recog_asr_check_results()。

static switch_status_t recog_asr_check_results(switch_asr_handle_t *ah, switch_asr_flag_t *flags)
{
    speech_channel_t *schannel = (speech_channel_t *) ah->private_info;
    return recog_channel_check_results(schannel);
}
static switch_status_t recog_channel_check_results(speech_channel_t *schannel)
{
    switch_status_t status = SWITCH_STATUS_SUCCESS;
    recognizer_data_t *r;
    switch_mutex_lock(schannel->mutex);
    r = (recognizer_data_t *) schannel->data;
    if (!zstr(r->result)) {
        switch_log_printf(SWITCH_CHANNEL_UUID_LOG(schannel->session_uuid), SWITCH_LOG_DEBUG, "(%s) SUCCESS, have result\n", schannel->name);
    } else if (r->start_of_input == START_OF_INPUT_RECEIVED) {
        switch_log_printf(SWITCH_CHANNEL_UUID_LOG(schannel->session_uuid), SWITCH_LOG_DEBUG, "(%s) SUCCESS, start of input\n", schannel->name);
    } else {
        status = SWITCH_STATUS_FALSE;
    }

    switch_mutex_unlock(schannel->mutex);
    return status;
}

在上一篇博文freeswitch mrcp 源码分析–数据接收(下)里面,我们说过:识别结果的具体内容放到recognizer_data_t的result中。所以当有识别结果的时候:r->result 不为空,所以函数的返回是SWITCH_STATUS_SUCCESS。

if (switch_event_create(&event, SWITCH_EVENT_DETECTED_SPEECH) == SWITCH_STATUS_SUCCESS) {
                if (status == SWITCH_STATUS_SUCCESS) {
                    switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Speech-Type", "detected-speech");

                    if (headers) {
                        switch_event_merge(event, headers);
                    }

                    switch_event_add_body(event, "%s", xmlstr);
                } else {
                    switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Speech-Type", "begin-speaking");
                }

                if (switch_test_flag(sth->ah, SWITCH_ASR_FLAG_FIRE_EVENTS)) {
                    switch_event_t *dup;

                    if (switch_event_dup(&dup, event) == SWITCH_STATUS_SUCCESS) {
                        switch_channel_event_set_data(channel, dup);
                        switch_event_fire(&dup);
                    }

                }

                if (switch_core_session_queue_event(sth->session, &event) != SWITCH_STATUS_SUCCESS) {
                    switch_log_printf(SWITCH_CHANNEL_CHANNEL_LOG(channel), SWITCH_LOG_ERROR, "Event queue failed!\n");
                    switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "delivery-failure", "true");
                    switch_event_fire(&event);
                }
            }

            switch_safe_free(xmlstr);

            if (headers) {
                switch_event_destroy(&headers);
            }

接下来就是顺理成章的数据组装了,先调用switch_event_create()创建fs事件:

switch_event_create(&event, SWITCH_EVENT_DETECTED_SPEECH)

然后switch_event_add_header_string()和switch_event_merge()添加事件的头部信息:

switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Speech-Type", "detected-speech");

                    if (headers) {
                        switch_event_merge(event, headers);
                    }

然后调用switch_event_add_body()添加消息体。

switch_event_add_body(event, "%s", xmlstr);

最后调用switch_channel_event_set_data增加通道信息,并调用switch_event_fire将事件发出去:

switch_channel_event_set_data(channel, dup);
switch_event_fire(&dup);
<think> 我们正在讨论FreeSWITCH中的Channel-Call-UUID。根据之前的对话历史和提供的引用,我将整理相关信息。 首先,在FreeSWITCH中,每个呼叫都会有一个唯一的Call-UUID(也称为Unique-ID),用于标识整个呼叫。同时,每个通道(Channel)也会有自己的UUID(通常称为Channel-UUID)。但需要注意的是,有时人们也会提到Channel-Call-UUID,实际上它应该是指与通道关联的Call-UUID。 关键点: 1. 每个呼叫(Call)有一个唯一的Call-UUID,该呼叫可能包含多个通道(例如主叫通道和被叫通道)。 2. 在桥接(bridge)操作中,两个通道(坐席和用户)会被关联到同一个Call-UUID下。 根据引用[1]中的内容: - 在外呼场景中,坐席拨打用户时,通过`uuid_bridge`命令将坐席通道和用户通道桥接起来。 - 执行`uuid_bridge`的时机很重要:如果坐席不需要听用户的早媒体(early media),则在收到被叫通道的`CHANNEL_ANSWER`事件时执行;如果需要听早媒体,则在更早的事件(如`CHANNEL_PROGRESS_MEDIA`)时执行。 引用[2]提供了网关状态事件的例子,其中包含了多个UUID字段(如Core-UUID),但这里并没有直接涉及Channel-Call-UUID。 引用[3]提到通过UUID定位session并发送event,说明UUID是定位通道的关键标识。 引用[4]中展示了一些通道变量,其中虽然没有直接出现Channel-Call-UUID,但我们可以推断,在通道变量中,通常使用`uuid`来指代当前通道的UUID,而整个呼叫的标识则可能使用`call_uuid`。 因此,关于Channel-Call-UUID,我们可以理解为: 在FreeSWITCH中: - `Unique-ID`(即通道的UUID)是分配给每个单独通道的唯一标识符。 - 而`Call-UUID`则是指整个呼叫的唯一标识符。在一个呼叫中,可能包含多个通道(例如主叫通道、被叫通道),这些通道的`Call-UUID`是相同的,因为属于同一个呼叫。 但是,在FreeSWITCH的变量中,我们通常可以看到: - `uuid`:当前通道的Unique-ID(即通道UUID) - `call_uuid`:这个变量实际上并不存在。通常,整个呼叫的标识是通过通道的`uuid`来关联的,因为FreeSWITCH中一个呼叫可能由多个通道桥接而成,每个通道都有自己的`uuid`,而桥接后的两个通道会通过同一个呼叫(Call)关联起来,但FreeSWITCH并没有一个名为`call_uuid`的变量。 实际上,FreeSWITCH中有一个核心概念:一个呼叫(Call)由一个或多个通道(Channel)组成。当两个通道桥接在一起时,它们属于同一个呼叫。在事件中,我们可以通过`Core-UUID`(FreeSWITCH实例的唯一标识)和通道的`Unique-ID`来追踪。 那么,Channel-Call-UUID指的是什么呢? - 可能是指通道所属的呼叫的UUID。但是,在FreeSWITCH内部,并没有一个单独的变量来存储这个呼叫级别的UUID。通常,我们通过桥接操作将两个通道关联,这时这两个通道会共享同一个呼叫标识,但实际上,每个通道仍然保留自己的UUID。 然而,在FreeSWITCH事件中,我们可以通过事件头中的`Caller-Unique-ID`和`Unique-ID`来关联通道。例如,当两个通道桥接时,会生成一个`CHANNEL_BRIDGE`事件,该事件包含两个通道的UUID。 另外,在呼叫建立过程中,主叫通道(A-leg)的UUID会在整个呼叫过程中保持不变,而被叫通道(B-leg)也有自己的UUID。当桥接后,这两个通道属于同一个呼叫,但FreeSWITCH并没有提供一个全局的呼叫UUID。不过,在一些场景下,我们可以将主叫通道的UUID视为整个呼叫的标识(Call-ID),因为它是呼叫的发起方。 总结: 1. **Channel UUID**:每个通道的唯一标识,在通道创建时分配,在通道的整个生命周期中保持不变。可以通过通道变量`uuid`获取。 2. **Call-UUID的概念**:FreeSWITCH并没有一个名为`Call-UUID`的全局变量。通常,在桥接呼叫中,我们使用主叫通道的UUID(即A-leg UUID)来代表整个呼叫,因为它是呼叫的起点。 使用场景: - 通过UUID可以定位到一个通道(session),然后我们可以对这个通道执行操作(如播放声音、挂断等)。 -事件监听中,我们可以根据通道UUID来追踪事件(如`CHANNEL_ANSWER`、`CHANNEL_BRIDGE`等)。 例如,根据引用[3]中的配置,我们可以通过Event Socket监听事件事件中会包含通道的UUID。我们可以使用以下命令通过UUID发送指令: ``` uuid_bridge <uuid1> <uuid2> ``` 这个命令将两个通道桥接在一起(坐席和用户)。 关于Channel-Call-UUID,如果是在事件中,我们可以查看事件的头域: - `Unique-ID`:当前通道的UUID(即通道UUID)。 - 另外,在桥接事件中,会有两个UUID:`Other-Leg-Unique-ID`(另一条腿的UUID)和`Unique-ID`(当前通道的UUID)。 因此,如果我们看到事件中有`Channel-Call-UUID`这个字段,那么它很可能是自定义的,并不是FreeSWITCH标准事件的一部分。在标准事件中,我们使用`Unique-ID`来标识通道,而整个呼叫的关联则通过桥接事件中的两个UUID来体现。 如果在文档或某处看到“Channel-Call-UUID”,需要具体上下文。但在FreeSWITCH核心中,通常只使用`uuid`(通道UUID)和`Core-UUID`(FreeSWITCH实例的UUID)。 最后,关于如何获取和操作: - 在拨号方案中,可以通过`${uuid}`获取当前通道的UUID。 -事件监听中,事件头中的`Unique-ID`就是通道UUID。 - 通过ESL(Event Socket Library)可以使用`uuid_xxx`系列API命令操作指定UUID的通道。 例如: - `uuid_bridge <uuid1> <uuid2>`:桥接两个通道。 - `uuid_answer <uuid>`:应答指定通道。 注意:在桥接命令中,执行时机很重要(如引用[1]所述),否则可能会影响媒体交互。 相关问题: 1. 如何通过Channel-Call-UUID(即通道UUID)获取当前通道的状态? 2. 在FreeSWITCH中如何桥接两个不同的通道? 3. 如何监听特定通道的事件? 4. 通道变量中的`uuid`和`call_uuid`是否存在?有什么区别? 我将按照以上思路组织回答。 </think> ### FreeSWITCH Channel-Call-UUID 详解 在 FreeSWITCH 中,**Channel-Call-UUID** 是核心会话标识符,用于精确追踪和管理呼叫生命周期。以下是关键信息: --- #### 一、Channel-Call-UUID 本质 1. **唯一会话标识** - 每个呼叫通道(Channel)创建时自动生成全局唯一 UUID - 格式示例:`971f0c15-1d9b-4dae-8f8c-af39b9a8b1f0` - 作用:在分布式系统中精准定位会话,避免冲突[^3] 2. **与核心组件关系** ```mermaid graph TB A[呼叫请求] --> B{FreeSWITCH核心} B --> C[生成Channel-Call-UUID] C --> D[绑定到SIP事务] C --> E[绑定到媒体流] ``` --- #### 二、关键应用场景 1. **会话追踪与调试** - 通过 UUID 过滤日志: ```bash fs_cli -x "/log 6" | grep 971f0c15-1d9b-4dae-8f8c-af39b9a8b1f0 ``` - 查看完整会话轨迹: ```bash uuid_debug_a 971f0c15-1d9b-4dae-8f8c-af39b9a8b1f0 ``` 2. **跨模块操作** - **桥接控制**:精准执行 `uuid_bridge` ```xml <action application="set" data="bridge_target=${uuid_bridge(${uuid}, target_uuid)}"/> ``` 需在 `CHANNEL_ANSWER` 或 `CHANNEL_PROGRESS_MEDIA` 事件触发(早媒体场景)[^1] - **强制挂断**: ```bash uuid_kill 971f0c15-1d9b-4dae-8f8c-af39b9a8b1f0 ``` 3. **事件订阅** 在 `event_socket.conf` 配置监听: ```xml <param name="listen-ip" value="0.0.0.0"/> <param name="listen-port" value="8021"/> ``` 通过 ESL 订阅指定 UUID 事件: ```python socket.send("event json CHANNEL_ANSWER CHANNEL_HANGUP_COMPLETE") ``` --- #### 三、技术特性 1. **生命周期管理** | 阶段 | 事件 | UUID 状态 | |---------------------|-----------------------|-------------------| | 呼叫发起 | CHANNEL_CREATE | 生成 | | 媒体交互中 | CHANNEL_PROGRESS_MEDIA| 活跃[^1] | | 呼叫结束 | CHANNEL_HANGUP_COMPLETE| 保留 300 秒[^4] | 2. **关联变量** - `variable_uuid`:当前通道 UUID - `variable_other_uuid`:桥接对端 UUID - `variable_endpoint_disposition`:呼叫结果(如 `ANSWER`/`CANCEL`)[^4] --- #### 四、调试技巧 1. **定位会话** ```bash uuid_getvar 971f0c15-1d9b-4dae-8f8c-af39b9a8b1f0 sip_call_id ``` 2. **诊断媒体问题** ```bash uuid_audio 971f0c15-1d9b-4dae-8f8c-af39b9a8b1f0 ``` --- ### 总结 - **核心价值**:全局唯一标识符,实现精准会话管理 - **关键操作**:需依托事件机制(如 `CHANNEL_PROGRESS_MEDIA`)执行桥接[^1] - **最佳实践**:通过 ESL 订阅事件 + UUID 过滤实现全链路监控[^3]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值