freeswitch mrcp 源码分析--数据包的发送

本文详细分析了Freeswitch中MRCP语音识别启动后的数据包发送流程,从recog_channel_start函数开始,涵盖创建mrcp_message_t结构体、设置参数、填充body、发送消息等步骤。核心是mrcp_application_message_send函数,该函数通过消息任务分发机制,最终由mrcp_client_agent_messsage_send完成数据通过socket的实际发送。

在MRCP 开始语音识别后会调用recog_channel_start()函数

static switch_status_t recog_channel_start(speech_channel_t *schannel)
{
    switch_status_t status = SWITCH_STATUS_SUCCESS;
    switch_hash_index_t *egk;
    mrcp_message_t *mrcp_message;
    mrcp_recog_header_t *recog_header;
    mrcp_generic_header_t *generic_header;
    recognizer_data_t *r;
    char *start_input_timers;
    const char *mime_type;
    char *key = NULL;
    switch_size_t len;
    grammar_t *grammar = NULL;
    switch_size_t grammar_uri_count = 0;
    switch_size_t grammar_uri_list_len = 0;
    char *grammar_uri_list = NULL;
    int warned = 0;

    switch_mutex_lock(schannel->mutex);
    if (schannel->state != SPEECH_CHANNEL_READY) {
        status = SWITCH_STATUS_FALSE;
        goto done;
    }

    if (schannel->data == NULL) {
        status = SWITCH_STATUS_FALSE;
        goto done;
    }
    r = (recognizer_data_t *) schannel->data;
    r->result = NULL;
    if (r->result_headers) {
        switch_event_destroy(&r->result_headers);
    }
    r->start_of_input = 0;

    /* input timers are started by default unless the start-input-timers=false param is set */
    start_input_timers = (char *) switch_core_hash_find(schannel->params, "start-input-timers");
    r->timers_started = zstr(start_input_timers) || strcasecmp(start_input_timers, "false");

    /* count enabled grammars */
    for (egk = switch_core_hash_first(r->enabled_grammars); egk; egk = switch_core_hash_next(&egk)) {
        // NOTE: This postponed type check is necessary to allow a non-URI-list grammar to execute alone
        if (grammar_uri_count == 1 && grammar->type != GRAMMAR_TYPE_URI)
            goto no_grammar_alone;
        ++grammar_uri_count;
        switch_core_hash_this(egk, (void *) &key, NULL, (void *) &grammar);
        if (grammar->type != GRAMMAR_TYPE_URI && grammar_uri_count != 1) {
              no_grammar_alone:
            switch_log_printf(SWITCH_CHANNEL_UUID_LOG(schannel->session_uuid), SWITCH_LOG_ERROR, "(%s) Grammar '%s' can only be used alone (not a URI list)\n", schannel->name, key);
            status = SWITCH_STATUS_FALSE;
            switch_safe_free(egk);
            goto done;
        }
        len = strlen(grammar->data);
        if (!len)
            continue;
        grammar_uri_list_len += len;
        if (grammar->data[len - 1] != '\n')
            grammar_uri_list_len += 2;
    }

    switch (grammar_uri_count) {
    case 0:
        switch_log_printf(SWITCH_CHANNEL_UUID_LOG(schannel->session_uuid), SWITCH_LOG_ERROR, "(%s) No grammar specified\n", schannel->name);
        status = SWITCH_STATUS_FALSE;
        goto done;
    case 1:
        /* grammar should already be the unique grammar */
        break;
    default:
        /* get the enabled grammars list */
        grammar_uri_list = switch_core_alloc(schannel->memory_pool, grammar_uri_list_len + 1);
        grammar_uri_list_len = 0;
        for (egk = switch_core_hash_first(r->enabled_grammars); egk; egk = switch_core_hash_next(&egk)) {
            switch_core_hash_this(egk, (void *) &key, NULL, (void *) &grammar);
            len = strlen(grammar->data);
            if (!len)
                continue;
            memcpy(&(grammar_uri_list[grammar_uri_list_len]), grammar->data, len);
            grammar_uri_list_len += len;
            if (grammar_uri_list[grammar_uri_list_len - 1] != '\n')
            {
                grammar_uri_list_len += 2;
                grammar_uri_list[grammar_uri_list_len - 2] = '\r';
                grammar_uri_list[grammar_uri_list_len - 1] = '\n';
            }
        }
        grammar_uri_list[grammar_uri_list_len++] = '\0';
        grammar = NULL;
    }

    /* create MRCP message */
    mrcp_message = mrcp_application_message_create(schannel->unimrcp_session, schannel->unimrcp_channel, RECOGNIZER_RECOGNIZE);
    if (mrcp_message == NULL) {
        status = SWITCH_STATUS_FALSE;
        goto done;
    }

    /* allocate generic header */
    generic_header = (mrcp_generic_header_t *) mrcp_generic_header_prepare(mrcp_message);
    if (generic_header == NULL) {
        status = SWITCH_STATUS_FALSE;
        goto done;
    }

    /* set Content</
<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]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值