chore: migrate project into clean repository

This commit is contained in:
yuuux
2026-08-13 16:50:52 +08:00
commit d1d25a09e7
27405 changed files with 9422808 additions and 0 deletions

View File

@@ -0,0 +1,191 @@
#!/bin/bash
OS="$OSTYPE"
# AI2T_LingXinEngine Pre-push Hook
# 在 git push 之前执行 makefile 编译检查
echo "🔍 Running pre-push checks for AI2T_LingXinEngine..."
# 获取脚本所在目录和仓库根目录
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
LOG_FILE="$SCRIPT_DIR/pre-build.log"
# 进入 AI2T_LingXinEngine 路径
REPO_DIR="$( cd "$SCRIPT_DIR/.." && pwd )"
cd "$REPO_DIR"
# ==================== 路径转换函数 ====================
# 将 Windows 路径转换为 WSL 路径
# 输入D:/code/... 或 /d/code/...
# 输出:/mnt/d/code/...
convert_to_wsl_path() {
local win_path="$1"
local wsl_path="$win_path"
# 处理 D:/code/... 格式Git Bash -W 输出)
if [[ "$wsl_path" =~ ^([A-Za-z]):/ ]]; then
local drive_letter="${BASH_REMATCH[1]}"
local drive_lower=$(echo "$drive_letter" | tr '[:upper:]' '[:lower:]')
wsl_path="/mnt/${drive_lower}${wsl_path:2}"
# 处理 /d/code/... 格式Git Bash 默认输出)
elif [[ "$wsl_path" =~ ^/([a-z])/ ]]; then
local drive_letter="${BASH_REMATCH[1]}"
wsl_path="/mnt/${drive_letter}${wsl_path:2}"
fi
# 统一转换反斜杠为正斜杠
wsl_path="${wsl_path//\\//}"
# 返回结果
echo "$wsl_path"
}
# ==================== 编译检查 ====================
echo "🔨 Checking if code compiles..."
# 检查是否存在 makefile
if [ ! -f "makefile" ] && [ ! -f "Makefile" ]; then
echo "⚠️ Warning: No makefile found, skipping build check"
exit 0
fi
run_make_locally() {
echo " 🌐 Environment: $OS (local Unix-like)"
echo " 🧹 Cleaning previous build..."
# /dev/null 是一个特殊的"黑洞"设备文件,写入的所有内容都会被丢弃
make clean >/dev/null 2>&1 || true
# 尝试编译
echo " ⚙️ Compiling..."
# 临时禁用 errexit 以处理管道的退出状态
set +e
make 2>&1 | tee "$LOG_FILE"
# 获取管道中第一个命令make的退出状态
MAKE_EXIT_CODE=${PIPESTATUS[0]}
set -e # 重新启用 errexit
if [ $MAKE_EXIT_CODE -ne 0 ]; then
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "❌ Error: Build failed!"
echo "💡 Please fix the compilation errors before pushing."
echo "💡 Full build log saved to: "$LOG_FILE""
echo ""
echo "To bypass this check (not recommended):"
echo " git push --no-verify"
# 清理编译产物
make clean > /dev/null 2>&1
exit 1
fi
echo " ✅ Build successful"
# 清理编译产物
echo " 🧹 Cleaning build artifacts..."
make clean > /dev/null 2>&1
}
run_make_via_wsl() {
echo " 🌐 Environment: $OS (Windows Git Bash/MSYS, use WSL Ubuntu for build)"
# 检查 wsl 命令是否可用
if ! command -v wsl >/dev/null 2>&1; then
echo "⚠ wsl 未安装或不可用,无法在 WSL 中编译。"
echo "⚠ 请先安装 wsl 再执行 git push 操作"
# echo " 跳过编译检查,允许本次 push如需强制可改为 exit 1。"
exit 1
fi
# 路径转换(使用抽取的函数)
WIN_PWD="$(pwd -W 2>/dev/null || pwd)"
WSL_PWD="$(convert_to_wsl_path "$WIN_PWD")"
echo " 📁 Windows path: $WIN_PWD"
echo " 📁 WSL path : $WSL_PWD"
# 检查 WSL Ubuntu 中是否已安装 gcc使用系统默认版本
echo " 🔍 Checking gcc in WSL Ubuntu..."
if ! wsl bash -lc "command -v gcc >/dev/null 2>&1"; then
echo " ⚠ gcc not found, installing build-essential..."
# 在 WSL 中安装 build-essential包含默认版本的 gcc 和 g++
if wsl bash -lc "sudo apt update && sudo apt install -y build-essential"; then
echo " ✅ build-essential installed successfully !"
else
echo ""
echo "❌ Failed to install build-essential in WSL Ubuntu"
echo " 请手动在 WSL 中执行:"
echo " wsl"
echo " sudo apt update"
echo " sudo apt install -y build-essential"
exit 1
fi
fi
# 获取并显示 Ubuntu 版本和 GCC 版本信息
UBUNTU_VERSION=$(wsl bash -lc "lsb_release -d -s 2>/dev/null || grep PRETTY_NAME /etc/os-release | cut -d'\"' -f2")
GCC_VERSION=$(wsl bash -lc "gcc --version | head -n1")
echo " ✅ Using $GCC_VERSION"
echo " ✅ Ubuntu: $UBUNTU_VERSION"
# 将环境信息写入日志文件头部
WSL_LOG_FILE="$(convert_to_wsl_path "$LOG_FILE")"
wsl bash -lc "echo '========== Build Environment ==========' > '$WSL_LOG_FILE'"
wsl bash -lc "echo 'OS: $UBUNTU_VERSION' >> '$WSL_LOG_FILE'"
wsl bash -lc "echo 'Compiler: $GCC_VERSION' >> '$WSL_LOG_FILE'"
wsl bash -lc "echo 'Build Time: \$(date)' >> '$WSL_LOG_FILE'"
wsl bash -lc "echo '=======================================' >> '$WSL_LOG_FILE'"
wsl bash -lc "echo '' >> '$WSL_LOG_FILE'"
echo " 🧹 WSL: make clean..."
wsl bash -lc "cd '$WSL_PWD' && make clean >/dev/null 2>&1 || true"
echo " ⚙️ WSL: make..."
# CC 和 CXX 指定编译器版本
if ! wsl bash -lc "set -o pipefail; cd '$WSL_PWD' && make 2>&1 | tee -a '$WSL_LOG_FILE'"; then
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "❌ Build failed (via WSL)!"
echo "💡 Please fix the compilation errors before pushing."
echo "💡 Full build log saved to: "$LOG_FILE""
echo ""
echo "To bypass this check (not recommended):"
echo " git push --no-verify"
# 清理编译产物
wsl bash -lc "cd '$WSL_PWD' && make clean >/dev/null 2>&1 || true"
exit 1
fi
echo " ✅ Build succeeded (via WSL)"
# 清理编译产物
echo " 🧹 Cleaning build artifacts..."
wsl bash -lc "cd '$WSL_PWD' && make clean >/dev/null 2>&1 || true"
}
case "$OS" in
linux*|darwin*)
# macOS / 纯 Linux / 直接在 WSL 里跑 git 时,走本地 make
run_make_locally
;;
msys*|mingw*|cygwin*)
# Windows 上的 Git Bash / MSYS / Cygwin通过 WSL 编译
run_make_via_wsl
;;
*)
echo "⚠ Unknown OSTYPE=$OS暂不进行编译检查允许本次 push。"
;;
esac
echo ""
echo "✅ Pre-push checks passed!"
echo "📝 Proceeding with push..."
exit 0

View File

@@ -0,0 +1,64 @@
#!/bin/bash
# Setup script for AI2T_LingXinEngine Git hooks
# 自动配置 Git hooks 并设置执行权限
echo "🔧 Setting up Git hooks for AI2T_LingXinEngine..."
# 获取脚本所在目录(.githooks 目录)
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
# 获取仓库根目录(.githooks 的上级目录)
REPO_DIR="$( cd "$SCRIPT_DIR/.." && pwd )"
echo "📂 Repository directory: $REPO_DIR"
# 切换到仓库根目录
cd "$REPO_DIR"
# ==================== 配置 Git 使用 .githooks 目录 ====================
echo ""
echo "⚙️ Configuring Git to use .githooks directory..."
if git config core.hooksPath .githooks; then
echo " ✅ Git hooks path configured successfully"
else
echo " ❌ Failed to configure Git hooks path"
exit 1
fi
# ==================== 给 hook 文件添加执行权限 ====================
echo ""
echo "🔑 Setting executable permissions for hook files..."
# 检查并设置每个 hook 文件的权限
hook_count=0
for hook_file in .githooks/pre-commit .githooks/commit-msg .githooks/pre-push; do
if [ -f "$hook_file" ]; then
chmod +x "$hook_file"
echo "$(basename $hook_file)"
((hook_count++))
fi
done
# 给 setup.sh 本身也添加执行权限
if [ -f ".githooks/setup.sh" ]; then
chmod +x .githooks/setup.sh
echo " ✓ setup.sh"
fi
# ==================== 完成提示 ====================
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "✅ Git hooks setup complete!"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "📋 Configuration summary:"
echo " • Hooks directory: $(git config core.hooksPath)"
echo " • Active hooks: $hook_count"
echo ""
echo "💡 Usage tips:"
echo " • To skip hooks for a single commit: git commit --no-verify"
echo " • To skip hooks for a single push: git push --no-verify"
echo " • To disable hooks: git config core.hooksPath ''"
echo ""

View File

@@ -0,0 +1,17 @@
*.exe
.vscode
AK_SK.h
CMakeFiles/
CMakeCache.txt
cmake_install.cmake
TargetDirectories.txt
build/
install_manifest.txt
build.ninja
CPackConfig.cmake
CPackSourceConfig.cmake
sdk/jieli_project/Release/*
sdk/jieli_project/Debug/*
*.depend
*.layout
*.log

View File

@@ -0,0 +1,20 @@
#### 非芯片适配场景AI2T_LingXinEngine 下所有代码不可修改
### 目录结构
.
├── inc/ # 外部头文件(供开发者调用)
│ ├── func/ # 可直接调用的功能模块头文件
│ └── system_adapter/ # 功能适配层头文件((需要开发者适配实现))
├── src/ # SDK 源码实现
│ ├── asr/ # ASR 实现
│ ├── inc/ # 内部使用的头文件
│ ├── llm/ # LLM 功能实现(生文、生图)
│ ├── websocket/ # websocket hook
│ ├── schedule/ # 定时任务功能实现
│ ├── tts/ # TTS 实现
│ ├── utils/ # 工具类方法
│ ├── log/ # 日志方法实现
│ └── voice_chat/ # chat 功能核心实现
└── README.md # 项目说明文档

View File

@@ -0,0 +1,56 @@
#ifndef LINGXIN_ASRT_H
#define LINGXIN_ASRT_H
#ifdef __cplusplus
extern "C"
{
#endif
#include <stdbool.h>
#include <stddef.h>
// 定义ASR事件类型的枚举
typedef enum
{
ASR_EVENT_ON_READY, // 当ASR准备就绪时触发
ASR_EVENT_ON_SEND_START, // 当ASR可以接收音频时触发
ASR_EVENT_ON_SEND_RESULT, // 当ASR结果回调时触发
ASR_EVENT_ON_SEND_END, // 当ASR结果回调完成时触发
ASR_EVENT_ON_ERROR, // 当ASR发生错误时触发
ASR_EVENT_ON_DESTROY // 当ASR对象被销毁时触发
} ASREventType;
typedef struct ASRHandler ASRHandler;
typedef struct
{
char *taskId;
char *requestId; // 请求ID
char *instanceId;
} ASRExtraInfo;
typedef void (*ASREventListener)(ASREventType event, const char *data,
size_t dataSize, ASRExtraInfo *extraInfo);
typedef struct
{
const char *appKey;
const char *sn;
const char *appId;
} ASRConfig;
char *asrCreate(ASRHandler **handlerAddress, ASRConfig *config, ASREventListener listener);
bool asrSendStart(ASRHandler *handler, const char *taskId, const char *payload);
int asrSend(ASRHandler *handler, const char *audioData, size_t dataSize);
bool asrSendStop(ASRHandler *handler, const char *taskId);
void asrDestroy(ASRHandler *handler);
#ifdef __cplusplus
}
#endif
#endif // LINGXIN_ASRT_H

View File

@@ -0,0 +1,204 @@
#ifndef __CHAT_API_H__
#define __CHAT_API_H__
#ifdef __cplusplus
extern "C"
{
#endif
#include <stdbool.h>
#include <stddef.h>
typedef char *(*AuthAppIdGetFunc)(void);
typedef char *(*AuthLicenseGetFunc)(void);
typedef char *(*AuthSnGetFunc)(void);
typedef char *(*AuthAppCodeGetFunc)(void);
typedef char *(*DeviceCodeGetFunc)(void);
typedef char *(*ChatBizParameterGetFunc)(void);
typedef char *(*ChatCustomParameterGetFunc)(void);
// 退出完成Code
typedef enum
{
EXIT_REASON_USER_INITIATED, // 0. 主动退出
EXIT_REASON_WEBSOCKET_DISCONNECT, // 1. websocket异常断开可能原因断网
EXIT_REASON_WEBSOCKET_CONNECTION_FAILED, // 2. websocket建联失败
EXIT_REASON_NO_INPUT_TIMEOUT, // 3. 持续无输入超时自动退出
EXIT_REASON_EXCEPTION_TIMEOUT, // 4. 内部异常超时退出
} ExitCode;
// 退出完成事件载荷
typedef struct
{
ExitCode exit_code; // 退出类型
char *reason; // 具体的退出原因
} ExitPayload;
typedef enum
{
CHAT_PHASE_STANDBY, // 0. 待命中
CHAT_PHASE_STARTING, // 1. 启动中
CHAT_PHASE_INPUTING, // 2. 输入中
CHAT_PHASE_THINKING, // 3. 思考中
CHAT_PHASE_OUTPUTING, // 4. 输出中
CHAT_PHASE_INTERRUPTING, // 5. 打断中
CHAT_PHASE_EXITING, // 6. 退出中
} ChatPhaseCode;
// 对话模式阶段变化事件载荷
typedef struct
{
ChatPhaseCode phase_code;
} ChatPhaseChangePayload;
// 对话模式生命周期事件
typedef enum
{
CHAT_LIFE_CYCLE_EVENT_EXIT, // 0. 退出完成
CHAT_LIFE_CYCLE_EVENT_SCHEDULE_EMIT, // 1. 定时任务触发
CHAT_LIFE_CYCLE_EVENT_TEXT_OUT, // 2. 指令+文本
CHAT_LIFE_CYCLE_EVENT_PLAY_END, // 3. 播放完成事件
CHAT_LIFE_CYCLE_EVENT_CHAT_PHASE_CHANGE, // 4. 对话模式阶段变化
CHAT_LIFE_CYCLE_EVENT_ERROR, // 5. 错误事件
} ChatLifeCycleEvent;
typedef void (*ChatLifeCycleEventListener)(ChatLifeCycleEvent event, void *payload);
// 多模态相关方法
// (1) 多模态发送流
typedef struct
{
char *unique_id;
int index;
char *frame;
int content_len;
char *content_type;
bool is_last;
} LingxinSendStreamProps;
typedef bool (*LingxinSendStream)(LingxinSendStreamProps *send_stream_props);
// (2) 多模态发送文本
typedef struct
{
char *content;
} LingxinSendTextProps;
typedef bool (*LingxinSendText)(LingxinSendTextProps *send_text_props);
// (3) 多模态打开内置录音
typedef struct
{
} LingxinStartRecordProps;
typedef bool (*LingxinStartRecord)(LingxinStartRecordProps *start_record_props);
// (4) 多模态关闭内置录音
typedef struct
{
} LingxinStopRecordProps;
typedef bool (*LingxinStopRecord)(LingxinStopRecordProps *stop_record_props);
// (5) 多模态本轮输入结束
typedef struct
{
char *unique_id;
char *content_type;
} LingxinConfirmData;
typedef struct
{
size_t confirm_data_count;
LingxinConfirmData *confirm_data_array;
} LingxinInputEndProps;
typedef bool (*LingxinInputEnd)(LingxinInputEndProps *input_end_props);
// 多模态事件回调
typedef enum
{
LINGXIN_MULTIMODAL_EVENT_INPUT_START = 0,
LINGXIN_MULTIMODAL_EVENT_RECORDER_START = 1,
LINGXIN_MULTIMODAL_EVENT_RECORDER_STOP = 2,
LINGXIN_MULTIMODAL_EVENT_INPUT_INTERRUPT = 3,
LINGXIN_MULTIMODAL_EVENT_STREAM_INPUT_SUCCESS = 4,
} LingxinMultimodalInputEvent;
// 多模态输入开始监听方法参数
typedef struct
{
LingxinSendStream send_stream;
LingxinSendText send_text;
LingxinStartRecord start_record;
LingxinStopRecord stop_record;
LingxinInputEnd input_end;
LingxinMultimodalInputEvent event;
char *event_payload;
} LingxinMultimodalInputListenerProps;
typedef void (*LingxinMultimodalInputListener)(LingxinMultimodalInputListenerProps props);
// 对话模式初始化方法与参数
typedef struct
{
AuthAppIdGetFunc auth_app_id_get_func; // (必填) 获取appId的方法
AuthLicenseGetFunc auth_license_get_func; // (必填) 获取license的方法
AuthSnGetFunc auth_sn_get_func; // (必填) 获取sn的方法
AuthAppCodeGetFunc auth_app_code_get_func; // (必填) 获取appCode的方法也可以是agentCode
DeviceCodeGetFunc device_code_get_func; // 获取设备型号的方法
ChatBizParameterGetFunc chat_biz_parameter_get_func; // 获取业务参数的方法
ChatCustomParameterGetFunc chat_custom_parameter_get_func; // 获取自定义参数的方法
int websocket_check_interval; // 检测websocket连接状态的间隔时间
int websocket_check_timeout; // 检测websocket连接状态的超时时间
ChatLifeCycleEventListener chat_life_cycle_event_listener;
int send_uni_size; // 设置录音单次发送的大小(字节)
int send_cbuf_scale; // 设置录音缓冲区大小对于单次发送大小的倍数
char *welcome_audio_path; // 设置首次唤醒后播放的音频
char *terminate_audio_path; // 设置打断时播放的音频
char *continue_audio_path; // 设置连续对话进入下一轮对话前播放的音频
int is_schedule_task_on; // 是否开启定时任务
int is_log_upload_on; // 是否开启日志
char *props_init_tag; // 标记是否经过灵芯自带的初始化,用户无需关心
char *flash_cache_path; // 设置flash中可用于缓存文件的分区路径分区可用存储空间需要大于600K
} VoiceChatInitProps;
VoiceChatInitProps get_voice_chat_init_default_props();
/**
* 对话模式初始化
* @param init_props 对话模式初始化参数
* @return 0: 初始化成功,-1: 参数有误,-2: 音频格式不支持
*/
int voice_chat_init(VoiceChatInitProps *init_props);
// 对话模式新一轮对话方法与参数
typedef struct
{
bool disable_welcome_audio; // 首次唤醒是否需要开场白
bool disable_vad; // 本轮对话是否启用云端VAD仅首轮禁用废弃
char *task_id; // 本轮对话是否指定task_id
char *task; // 本轮对话的场景 (chat_vad/chat/translate/chat_multimodal)
bool single_round; // 本轮对话是否为仅单轮对话
char *user_input; // 用户输入的文本
LingxinMultimodalInputListener multimodal_input_listener; // 多模输入开始事件监听
char *props_init_tag; // 标记是否经过灵芯自带的初始化,用户无需关心
} StartNewChatProps;
StartNewChatProps get_start_new_chat_default_props();
int start_new_chat(StartNewChatProps *start_props);
// 对话模式主动停止录音方法与参数
typedef struct
{
} StopChatRecordProps; // 用于后续拓展
int stop_chat_record(StopChatRecordProps *stop_record_props);
// 对话模式退出方法与参数
typedef struct
{
bool disable_close_ws_immediately; // 是否立即关闭websockettrue为立即断联false为不立即断联
char *props_init_tag; // 标记是否经过灵芯自带的初始化,用户无需关心
} ExitChatProps;
ExitChatProps get_exit_chat_default_props();
int exit_chat(ExitChatProps *exit_props);
// 加音量
int set_volume(int volume);
#ifdef __cplusplus
}
#endif
#endif // __CHAT_API_H__

View File

@@ -0,0 +1,41 @@
#ifndef LINGXIN_LOG_H
#define LINGXIN_LOG_H
#ifdef __cplusplus
extern "C"
{
#endif
/**
*
* 灵芯log模块
* log 方法支持的最长参数为512字节
* log中自动添加当前文件名和代码行无需手动设置
* log格式[time] [version] [level] [module:line] [node]: log内容
*/
/** log级别 */
#define LINGXIN_DEBUG (1 << 0)
#define LINGXIN_WARN (1 << 1)
#define LINGXIN_ERROR (1 << 2)
/* 下面四种方法仅打印log不缓存 */
#define lingxin_log_debug(format, ...) _lingxin_log_print_internal_(LINGXIN_DEBUG, 0, __FILE__, __LINE__, NULL, format, ##__VA_ARGS__)
#define lingxin_log_warn(format, ...) _lingxin_log_print_internal_(LINGXIN_WARN, 0, __FILE__, __LINE__, NULL, format, ##__VA_ARGS__)
#define lingxin_log_error(format, ...) _lingxin_log_print_internal_(LINGXIN_ERROR, 0, __FILE__, __LINE__, NULL, format, ##__VA_ARGS__)
/**
* log_level参数使用上面的log级别
* 该方法log打印并缓存上传云端
* */
#define lingxin_log_ut(log_level, node_name) _lingxin_log_print_internal_(log_level, 1, __FILE__, __LINE__, node_name, "")
#define lingxin_log_ut_with_args(log_level, node_name, format, ...) _lingxin_log_print_internal_(log_level, 1, __FILE__, __LINE__, node_name, format, ##__VA_ARGS__)
// 内部方法,勿用
void _lingxin_log_print_internal_(int log_level, int is_ut, const char *file_path, int line, const char *node_name, const char *format, ...);
#ifdef __cplusplus
}
#endif
#endif // LINGXIN_LOG_H

View File

@@ -0,0 +1,69 @@
#ifndef LINGXIN_MEMORY_H
#define LINGXIN_MEMORY_H
#ifdef __cplusplus
extern "C"
{
#endif
#include "lingxin_memory.h"
/**
* 申请内存
* @param size 申请内存大小,单位是字节
* * @return 返回指向分配内存的指针,内存已清零;如果分配失败则返回 NULL
*/
#define lingxin_malloc(size) _lingxin_malloc_internal_((size), __FILE__, __LINE__)
/**
* 申请内存并清零申请的内存
* @param num 申请的内存块数量
* @param size 每个内存块的大小,单位是字节
* @return 返回指向分配内存的指针,内存已清零;如果分配失败则返回 NULL
*/
#define lingxin_calloc(num, size) _lingxin_calloc_internal_((num), (size), __FILE__, __LINE__)
/**
* 重新分配内存
*/
#define lingxin_realloc(ptr, size) _lingxin_realloc_internal_((void *)(ptr), (size), __FILE__, __LINE__)
/**
* 释放内存
* @param ptr 要释放的内存指针
*/
#define lingxin_free(ptr) _lingxin_free_internal_((void *)(ptr), __FILE__, __LINE__)
/**
* 申请字符串内存并复制字符串
*/
#define lingxin_strdup(message) _lingxin_strdup_internal_((char *)(message), __FILE__, __LINE__)
/**
* 启用内存统计功能
*/
void lingxin_memory_enable_statistics();
/**
* 打印内存使用情况
*/
void lingxin_memory_print_statistics();
/*
* 销毁内存统计数据
*/
void lingxin_memory_destroy_statistics();
/**
* 私有函数实现,不对外
*/
void *_lingxin_malloc_internal_(int size, const char *file_path, int line);
void *_lingxin_calloc_internal_(int num, int size, const char *file_path, int line);
void *_lingxin_realloc_internal_(void *ptr, int size, const char *file_path, int line);
void _lingxin_free_internal_(void *ptr, const char *file_path, int line);
char *_lingxin_strdup_internal_(char *message, const char *file_path, int line);
#ifdef __cplusplus
}
#endif
#endif // LINGXIN_MEMORY_H

View File

@@ -0,0 +1,25 @@
#ifndef LINGXIN_GENERATE_BY_LLM_H
#define LINGXIN_GENERATE_BY_LLM_H
#ifdef __cplusplus
extern "C"
{
#endif
#include <stdbool.h>
typedef void (*GenerateTextRequestCallback)(char *contents, int finish);
void generateText(const char *appId, const char *sn, const char *appKey, const char *input,
GenerateTextRequestCallback callback);
void generateImage(const char *appId, const char *sn, const char *appKey, const char *requestParams, char **response);
void queryGenerateImageResult(const char *appId, const char *sn,
const char *appKey, const char *requestParams, char **response);
#ifdef __cplusplus
}
#endif
#endif // LINGXIN_GENERATE_BY_LLM_H

View File

@@ -0,0 +1,55 @@
#ifndef LINGXIN_TTS_H
#define LINGXIN_TTS_H
#ifdef __cplusplus
extern "C"
{
#endif
#include <stdbool.h>
#include <stddef.h>
// 定义tts事件类型的枚举
typedef enum
{
TTS_EVENT_ON_READY, // 当tts准备就绪时触发
TTS_EVENT_ON_SEND_START, // 当tts可以接收文本时触发
TTS_EVENT_ON_SEND_RESULT, // 当tts结果回调时触发
TTS_EVENT_ON_SEND_END, // 当tts结果回调完成时触发
TTS_EVENT_ON_ERROR, // 当tts发生错误时触发
TTS_EVENT_ON_DESTROY // 当tts对象被销毁时触发
} TTSEventType;
typedef struct
{
const char *appKey;
const char *sn;
const char *appId;
} TTSConfig;
typedef struct TTSHandler TTSHandler;
typedef struct
{
char *taskId;
char *requestId; // 请求ID
char *instanceId;
} TTSExtraInfo;
typedef void (*TTSEventListener)(TTSEventType event, const char *data,
const size_t len, TTSExtraInfo *extraInfo);
char *ttsCreate(TTSHandler **handlerAddress, TTSConfig *config, const char *payload, TTSEventListener listener);
bool ttsSendStart(TTSHandler *handler, const char *taskId);
int ttsSend(TTSHandler *handler, const char *taskId, const char *text);
bool ttsSendStop(TTSHandler *handler, const char *taskId);
void ttsDestroy(TTSHandler *handler);
#ifdef __cplusplus
}
#endif
#endif // LINGXIN_TTS_H

View File

@@ -0,0 +1,28 @@
#ifndef AUDIO_BUFFER_PLAY_H
#define AUDIO_BUFFER_PLAY_H
#include "download_audio_play_interface.h"
// 功能在方法里面实现模块的初始化逻辑。并且Chat套件内核会多次回调这个方法如果当前模块已经初始化成功可直接回调初始化成功的回调事件。
// 调用时机由chat套件内核发起调用客户实现
void module_bufferPlay_audioInit(PlaybackEventHandler callback, void *user_data);
// buf为mp3数据 rlen为当前数据长度
void module_bufferPlay_data(void *buf, int rlen);
// 功能指Chat套件内核调用流式播放模块告诉他已经没有流式播放数据了并非要立刻停止播放。
// 调用时机由chat套件内核发起调用客户实现
void module_bufferPlay_audioEnd();
// 功能:停止当前的播放逻辑
// 调用时机由chat套件内核发起调用客户实现
void module_bufferPlay_terminate();
// 功能:设置当前播放的音量
// 调用时机由chat套件内核发起调用客户实现
void module_bufferPlay_setVolume(int volume);
#endif // AUDIO_BUFFER_PLAY_H

View File

@@ -0,0 +1,51 @@
#ifndef CHAT_STATE_MACHINE_EVENT_H
#define CHAT_STATE_MACHINE_EVENT_H
// 模块抛给状态机的事件
typedef enum
{
State_Event_Wakeup_Detected = 0, // 唤醒事件
State_Event_Welcome_Play_End = 1, // 播放欢迎语结束事件
State_Event_VoiceChat_TerminateEnd = 3, // voice chat 打断成功事件
State_Event_VoiceChat_AIEnd = 5, // voice chat 结束推送语音流(注:原文注释有误,已修正)
State_Event_Vad_Stop = 6, // vad 停止
State_Event_Vad_Exit = 7, // vad 退出唤醒
State_Event_BufferPlay_AudioInitEnd = 8, // 流式播放初始化结束
State_Event_BufferPlay_PlayEnd = 9, // 流式播放结束
State_Event_BufferPlay_TerminateEnd = 10, // 流式播放打断后暂停
State_Event_BufferPlay_Error = 11, // 流式播放模块出错
State_Event_Upload_InitEnd = 12, // 录音模块初始化成功
State_Event_Upload_CloseEnd = 13, // 录音模块停止录音
State_Event_Upload_TerminateEnd = 14, // 录音模块打断事件
State_Event_VoiceChat_ExitEnd = 15, // voice chat 对话退出成功
State_Event_WillExit = 16, // 用户调用退出
State_Event_NoVoice_Start = 17, // 开启新一轮novoice模式
State_Event_NoVoice_Error = 18, // noVoice下行阶段服务端推送error
State_Event_NoVoice_TerminateEnd = 19,
State_Event_TerminatePrompt_PlayEnd = 20, // 增加打断唤醒提示音
State_Event_ContinuePrompt_PlayEnd = 21, // 增加连续对话提示音
// ==== 仅 task complete 状态内部使用的事件 ====
Event_Inc_TaskComplete_PlayEnd = 22, // 用于表明已经将时机转发给客户
Event_Inc_TaskComplete_End = 25, // 用于表明已经将时机转发给客户
// ==== 仅 下行 状态内部使用的事件 ====
Event_Inc_Download_End = 24, // 用于下行状态结束
} StateEvent;
/******************** 在chat套件内核中已实现客户调用 ********************/
// 模块抛出事件给调用状态机
void state_machine_run_event(StateEvent event);
// 录音模块把录音数据发送给状态机
void state_machine_post_record_data(void *buf, int rlen, int index);
#endif // CHAT_STATE_MACHINE_EVENT_H

View File

@@ -0,0 +1,24 @@
#ifndef LINGXIN_CHIP_INFO_H
#define LINGXIN_CHIP_INFO_H
#ifdef __cplusplus
extern "C"
{
#endif
/**
* 获取设备对应的芯片名称
* 举例: LINGXIN_芯片名称
*/
char *get_lingxin_device_name();
/**
* 获取设备对应的灵芯版本
* 举例0.0.1
*/
char *get_lingxin_device_version();
#ifdef __cplusplus
}
#endif
#endif // LINGXIN_CHIP_INFO_H

View File

@@ -0,0 +1,88 @@
#ifndef LINGXIN_FILE_H
#define LINGXIN_FILE_H
#ifdef __cplusplus
extern "C"
{
#endif
#include <stdbool.h>
/**
* 创建指定大小的文件
*
* @param file_path 文件名称
* @param length 文件大小
*
* @return true:创建成功 false:创建失败
*/
bool lingxin_file_create(const char *file_path, int length);
/**
* 根据指定名称的文件,从指定的起始位置开始,读取一定大小的文件内容到缓冲区中
*
* @param file_path 文件名称
* @param buffer 要写入的缓冲区
* @param offet 需要读取的文件内容偏移位置
* @param length 需要读取的文件内容长度
*
* @return true:读取成功 false:读取失败
*/
bool lingxin_file_read(const char *file_path, char *buffer, int offet, int length);
/**
* 向指定名称的文件中写入一定长度的内容
*
* @param file_path 文件名称
* @param is_append 是否以追加模式写入文件
* @param data 要写入的内容
* @param length 要写入的内容长度
*
* @return true:写入成功 false:写入失败
*/
bool lingxin_file_write(const char *file_path, bool is_append, const char *data, int length);
/**
* 检查指定名称的文件是否存在
*
* @param file_path 文件名称
*
* @return true:存在 false:不存在
*/
bool lingxin_file_exist(const char *file_path);
/**
* 获取指定名称的文件内容长度(文件大小)
*
* @param file_path 文件名称
*
* @return 文件内容长度(文件大小)
*/
int lingxin_file_length(const char *file_path);
/**
* 删除指定名称的文件
*
* @param file_path 文件名称
*
* @return true:删除成功 false:删除失败
*/
bool lingxin_file_delete(const char *file_path);
/**
* 清空指定名称的文件内容
*
* @param file_path 文件名称
*
* @return true:清理成功 false:清理失败
*/
bool lingxin_file_clear(const char *file_path);
#ifdef __cplusplus
}
#endif
#endif // LINGXIN_FILE_H

View File

@@ -0,0 +1,45 @@
// httpclient.h
#ifndef LINGXIN_HTTP_HTTPCLIENT_H
#define LINGXIN_HTTP_HTTPCLIENT_H
#ifdef __cplusplus
extern "C"
{
#endif
#include <stddef.h>
typedef void (*RequestCallback)(void *contents, size_t size, void *userp);
typedef struct
{
const char *signature;
const char *sn;
const char *app_id;
const char *timestamp;
} HttpHeader;
typedef struct
{
const char *protocol; // http or https
const char *host;
const char *path;
int port;
const char *post_data;
HttpHeader *headers;
} HttpConfig;
/**
* http_post 方法适配
*
* @param config http配置
* @param userCallback 回调函数
* @param userData 需要透传的参数在userCallback中会透传给用户
*
* @return: 0: fail 其他: success
*/
int http_post(HttpConfig *config, RequestCallback userCallback, void *userData);
#ifdef __cplusplus
}
#endif
#endif // LINGXIN_HTTP_HTTPCLIENT_H

View File

@@ -0,0 +1,60 @@
#ifndef __LINGXIN_LOCAL_PLAYER_H__
#define __LINGXIN_LOCAL_PLAYER_H__
#ifdef __cplusplus
extern "C" {
#endif
/**
* 本地播放器句柄
*/
typedef void *lingxin_local_player_t;
/**
* 本地播放器回调函数
* @param result 播放结果。0为播放成功-1为播放失败
*/
typedef void (*lingxin_local_player_callback_t)(int result);
/**
* 创建本地播放器
* @return 本地播放器句柄
*/
lingxin_local_player_t lingxin_local_player_create();
/**
* 定义播放参数的结构体
*/
typedef struct {
// 音频路径
char *audio_path;
// 初始音量
int initial_volume;
} lingxin_local_player_play_param_t;
/**
* 播放本地音频
* @param player 本地播放器句柄
* @param param 播放参数结构体的指针
* @param callback 播放回调函数
*/
void lingxin_local_player_play(lingxin_local_player_t player, lingxin_local_player_play_param_t *param, lingxin_local_player_callback_t callback);
/**
* 设置播放的音量
* @param player 本地播放器句柄
* @param volume 音量
*/
void lingxin_local_player_set_volume(lingxin_local_player_t player, int volume);
/**
* 销毁本地播放器
* @param player 本地播放器句柄
*/
void lingxin_local_player_destory(lingxin_local_player_t player);
#ifdef __cplusplus
}
#endif
#endif // __LINGXIN_LOCAL_PLAYER_H__

View File

@@ -0,0 +1,43 @@
#ifndef __LINGXIN_MUTEX_H__
#define __LINGXIN_MUTEX_H__
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
/**
* 互斥锁句柄
*/
typedef void *lingxin_mutex_t;
/**
* 创建互斥锁
*/
lingxin_mutex_t lingxin_mutex_create();
/**
* 互斥锁 Lock
* @param mutex 互斥锁句柄
*/
void lingxin_mutex_lock(lingxin_mutex_t mutex);
/**
* 互斥锁 Unlock
* @param mutex 互斥锁句柄
*/
void lingxin_mutex_unlock(lingxin_mutex_t mutex);
/**
* 销毁互斥锁
* @param mutex 互斥锁句柄
*/
void lingxin_mutex_destroy(lingxin_mutex_t mutex);
#ifdef __cplusplus
}
#endif
#endif /* __LINGXIN_MUTEX_H__ */

View File

@@ -0,0 +1,19 @@
#ifndef __LINGXIN_PRINTF_H__
#define __LINGXIN_PRINTF_H__
#ifdef __cplusplus
extern "C" {
#endif
/**
* 打印方法
* @param message 打印信息
*/
void lingxin_printf(char* message);
#ifdef __cplusplus
}
#endif
#endif /* __LINGXIN_PRINTF_H__ */

View File

@@ -0,0 +1,74 @@
#ifndef __LINGXIN_RECORDER_H__
#define __LINGXIN_RECORDER_H__
#ifdef __cplusplus
extern "C" {
#endif
/**
* 录音器句柄
*/
typedef void *lingxin_recorder_t;
/**
* 录音器开启/关闭回调函数
* @param result 开启/关闭结果。0为开启/关闭成功;-1为开启/关闭失败
*/
typedef void (*lingxin_recorder_callback_t)(int result);
/******************** 由chat套件内核发起调用客户实现 ********************/
/**
* 创建录音
*/
lingxin_recorder_t lingxin_recorder_create();
/**
* 开启录音参数的结构体
*/
typedef struct {
// 录音单次发送的大小
int frame_size;
} lingxin_recorder_open_param_t;
/**
* 开启录音
* @param recorder 录音句柄
* @param param 开启录音参数的结构体的指针
* @param callback 录音开启的回调(参数result: 0-成功/-1-失败)
*/
void lingxin_recorder_open(lingxin_recorder_t recorder, lingxin_recorder_open_param_t *param, lingxin_recorder_callback_t callback);
/**
* 关闭录音
* @param recorder 录音句柄
* @param callback 录音关闭的回调(参数result: 0-成功/-1-失败)
*/
void lingxin_recorder_close(lingxin_recorder_t recorder, lingxin_recorder_callback_t callback);
/**
* 销毁录音
* @param recorder 录音句柄
*/
void lingxin_recorder_destroy(lingxin_recorder_t recorder);
/**
* 获取默认每毫秒的录音大小
* @return 每毫秒的录音大小
*/
int lingxin_recorder_get_size_per_ms();
/******************** 由chat套件内核实现客户调用 ********************/
/**
* 发送录音数据
* @param data 录音数据指针
* @param len 录音数据长度
*/
void lingxin_process_record_data(void *data, int len);
#ifdef __cplusplus
}
#endif
#endif // __LINGXIN_RECORDER_H__

View File

@@ -0,0 +1,50 @@
#ifndef __LINGXIN_SEMAPHORE_H__
#define __LINGXIN_SEMAPHORE_H__
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
/**
* 信号量句柄
*/
typedef void *lingxin_semaphore_t;
/**
* 创建信号量
* @param cnt 信号量初始值
*/
lingxin_semaphore_t lingxin_semaphore_create(uint32_t cnt);
/**
* 等待信号量
* @param sem 信号量句柄
* @param timeout_ms 等待超时时间,单位为毫秒
*/
void lingxin_semaphore_pend(lingxin_semaphore_t sem, uint32_t timeout_ms);
/**
* 发送信号量
* @param sem 信号量句柄
*/
void lingxin_semaphore_post(lingxin_semaphore_t sem);
/**
* 给信号量设值,用于清零信号量
* @param sem 信号量句柄
*/
void lingxin_semaphore_set_value(lingxin_semaphore_t sem, uint32_t cnt);
/**
* 销毁信号量
* @param sem 信号量句柄
*/
void lingxin_semaphore_destroy(lingxin_semaphore_t sem);
#ifdef __cplusplus
}
#endif
#endif /* __LINGXIN_SEMAPHORE_H__ */

View File

@@ -0,0 +1,17 @@
#ifndef __LINGXIN_SYSTEM_H__
#define __LINGXIN_SYSTEM_H__
#ifdef __cplusplus
extern "C" {
#endif
/**
* 系统重启函数(仅在适配测试时需实现)
*/
void lingxin_system_abort();
#ifdef __cplusplus
}
#endif
#endif // __LINGXIN_SYSTEM_H__

View File

@@ -0,0 +1,35 @@
#ifndef LINGXIN_SYSTEM_TIME_H
#define LINGXIN_SYSTEM_TIME_H
#ifdef __cplusplus
extern "C"
{
#endif
typedef struct
{
int mill_sec;
int sec;
int min;
int hour;
int day;
int mon;
int year;
} LINGXIN_TIME;
/**
* 获取当前时间
* @param lingxin_time 灵芯时间结构体
*/
void lingxin_get_current_time(LINGXIN_TIME *lingxin_time);
/**
* 获取秒级时间戳10 位长度的整数
* @return 时间戳
*/
long lingxin_get_timestamp_s();
#ifdef __cplusplus
}
#endif
#endif // LINGXIN_SYSTEM_TIME_H

View File

@@ -0,0 +1,75 @@
#ifndef __LINGXIN_THREAD_H__
#define __LINGXIN_THREAD_H__
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
/**
* 线程名称最大长度
*/
#define LINXIN_THREAD_NAME_MAX_LENGTH 16
/**
* 线程ID
*/
typedef int lingxin_tid_t;
/**
* 定义线程参数的结构体
*/
typedef struct {
// 线程的名称,最大长度由 LINXIN_THREAD_NAME_MAX_LENGTH 定义
char* name;
// 线程的优先级
int priority;
// 线程的栈大小
int stack_size;
} lingxin_thread_param_t;
/**
* 创建线程
* @param thread 指向存储新线程 ID 的变量的指针
* @param param 指向线程参数结构体的指针
* @param start_routine 线程启动时执行的函数指针
* @param args 传递给线程启动函数的参数
* @return 操作结果的状态码(0表示成功-1表示失败)
*/
int lingxin_thread_create(lingxin_tid_t *thread, const lingxin_thread_param_t *param, void *(*start_routine)(void *), void *args);
/**
* 线程销毁模式枚举
*/
typedef enum {
LINGXIN_THREAD_DESTROY_WAIT = 0, // 等待线程结束
LINGXIN_THREAD_DESTROY_DETACH = 1, // 分离线程,线程运行结束时自动销毁
LINGXIN_THREAD_DESTROY_CANCEL = 2, // 立即取消线程
} lingxin_thread_destroy_mode_t;
/**
* 销毁线程
* @param thread 线程 ID
* @param mode 销毁模式
*/
void lingxin_thread_destroy(lingxin_tid_t thread, lingxin_thread_destroy_mode_t mode);
/**
* 获取当前所在线程名称
* @return 线程名称,如果获取失败则返回 NULL
*/
char* lingxin_get_current_thread_name();
/**
* 线程休眠
* @param time 休眠时间,单位为毫秒
*/
void lingxin_thread_sleep(int time);
#ifdef __cplusplus
}
#endif
#endif /* __LINGXIN_THREAD_H__ */

View File

@@ -0,0 +1,60 @@
#ifndef LINGXIN_TIMER_H
#define LINGXIN_TIMER_H
#ifdef __cplusplus
extern "C"
{
#endif
#include <stdint.h>
#define INVALID_TIMER_ID (-1)
// 周期性定时器
/**
* @brief sys_timer定时扫描增加接口创建一个周期性定时器
* @param priv 定时器回调函数func的私有参数
* @param func 超时扫描回调函数
* @param msec 超时时间, 单位:毫秒
* @return 定时器分配的id号, 创建失败时返回INVALID_TIMER_ID
*/
int lingxin_sys_timer_add(void *priv, void (*func)(void *priv), long msec);
/**
* @brief sys_timer定时扫描删除接口
* @param timer_id sys_timer_add分配的id号
* @return 删除结果0-成功,-1-失败)
*/
int lingxin_sys_timer_del(int timer_id);
/**
* @brief sys_timer定时扫描重置接口
* @param timer_id sys_timer_add分配的id号
* @return 重置结果0-成功,-1-失败)
*/
int lingxin_sys_timer_re_run(int timer_id);
// 一次性定时器
/**
* @brief 创建一个一次性定时器
* @param priv 定时器回调函数func的私有参数
* @param func 定时器回调函数
* @param countdown 超时时间, 单位毫秒值可能为0
* @return 定时器分配的id号, 创建失败时返回INVALID_TIMER_ID
*/
int lingxin_one_shot_timer_create(void *priv, void (*func)(void *priv), long countdown);
/**
* @brief 删除指定的一次性定时器
* @param timerId 定时器ID
* @return 删除结果0-成功,-1-失败)
*/
int lingxin_one_shot_timer_delete(int timerId);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,57 @@
#ifndef LINGXIN_WEBSOCKET_H
#define LINGXIN_WEBSOCKET_H
#ifdef __cplusplus
extern "C"
{
#endif
#include <stdbool.h>
#include <stddef.h>
typedef enum
{
ON_WEBSOCKET_CONNECTION_SUCCESS, // 建联成功
ON_WEBSOCKET_CONNECTION_FAIL, // 建联失败
ON_WEBSOCKET_DATA_RECEIVED, // 收到数据
ON_WEBSOCKET_DESTROY, // 销毁完成
ON_WEBSOCKET_ERROR // 收到错误
} WebSocketEventType;
typedef void (*WebSocketEventListener)(WebSocketEventType event, const char *data, size_t data_len, const int isBinary, void *userContext);
typedef struct
{
const char *protocol; // ws or wss
const char *host;
const char *path;
const char *header_signature;
const char *header_sn;
const char *header_app_id;
const char *header_timestamp;
int port;
void *userContext;
WebSocketEventListener listener;
} WebsocketConfig;
typedef struct
{
void *clientHandler;
WebsocketConfig *config;
} WebsocketClient;
WebsocketClient *initWebsocket(WebsocketConfig *config);
bool startWebsocket(WebsocketClient *client);
bool websocketSendText(WebsocketClient *client, const char *message);
int websocketSendBinary(WebsocketClient *client, const char *audioData, size_t dataSize);
void closeWebsocket(WebsocketClient *client);
#ifdef __cplusplus
}
#endif
#endif // LINGXIN_WEBSOCKET_H

52
third_party/AI2T_LingXinEngine/makefile vendored Normal file
View File

@@ -0,0 +1,52 @@
# 默认遇到编译错误不中断
MAKEFLAGS += --keep-going
# 编译器
CC ?= gcc
$(info [🔧 MAKE] Active compiler: $(CC))
$(info [🔧 MAKE] Version info: $(shell command -v $(CC) >/dev/null 2>&1 && $(CC) --version 2>/dev/null | head -1 || echo "❌ NOT FOUND"))
$(info )
# 项目根目录
SRC_DIR := ../AI2T_LingXinEngine
# 查找所有 .c 文件(包括子目录)
SOURCES := $(shell find $(SRC_DIR) -name "*.c")
# 生成对应的 .o 文件路径(保持目录结构)
OBJECTS := $(SOURCES:.c=.o)
# 自动收集所有子目录作为头文件搜索路径
INCLUDE_DIRS := $(shell find $(SRC_DIR) -type d)
INCLUDE_FLAGS := $(addprefix -I, $(INCLUDE_DIRS))
# 编译选项:只编译不链接,指定头文件搜索路径,开启所有警告,调试信息
CFLAGS := -c \
-Wall \
-Werror \
-Wno-error=unused-function \
-Wno-error=unused-variable \
-Wno-error=unused-but-set-variable \
-Wno-error=deprecated-declarations \
-Wextra \
-Wno-unused-parameter \
-Wno-sign-compare \
# -Wenum-conversion -Werror=enum-conversion \
# -Wenum-compare -Werror=enum-compare \
-g
# 默认目标:编译所有 .c 文件为 .o
all: $(OBJECTS)
# 通用编译规则:从 .c 生成 .o
%.o: %.c
@echo "Compiling $< ..."
@echo "Include flags: $(INCLUDE_FLAGS)"
$(CC) $(CFLAGS) $(INCLUDE_FLAGS) -o $@ $<
# 清理生成的 .o 文件
clean:
rm -f $(OBJECTS)
.PHONY: all clean

View File

@@ -0,0 +1,338 @@
#include "asr.h"
#include "cJSON.h"
#include "lingxin_common.h"
#include "lingxin_json_util.h"
#include "lingxin_hook_websocket.h"
#include <stdarg.h>
#include "lingxin_log.h"
#include "lingxin_memory.h"
struct ASRHandler
{
WebsocketClient *websocket;
ASREventListener listener;
ASRExtraInfo *extraInfo;
struct ASRHandler **selfPointer; // 存储双指针的引用
};
static char *getASRLogPre(ASRHandler *handler)
{
char *logInstanceId = NULL;
if (handler && handler->selfPointer && handler->extraInfo)
{
logInstanceId = handler->extraInfo->instanceId;
}
return (!logInstanceId || strlen(logInstanceId) == 0) ? "" : logInstanceId;
}
static void triggerCallback(ASRHandler *handler, ASREventType eventType,
const char *data, const size_t len)
{
if (!handler)
{
lingxin_log_error("triggerCallback handler null");
return;
}
if (!handler->listener)
{
lingxin_log_error( "[%s], triggerCallback listener null", getASRLogPre(handler));
return;
}
handler->listener(eventType, data, len, handler->extraInfo);
}
static void dealEventFromServer(ASRHandler *handler, const char *event,
cJSON *message)
{
if (!event)
{
lingxin_log_error("[%s]", "event is null", getASRLogPre(handler));
return;
}
if (strcmp(event, "task_started") == 0)
{
if (handler->extraInfo)
{
char *reqId = parseRequestId(message);
if (!handler->extraInfo->requestId || strlen(handler->extraInfo->requestId) == 0)
{
handler->extraInfo->requestId = lingxin_strdup(reqId);
}
else if (strlen(reqId) == strlen(handler->extraInfo->requestId))
{
memcpy(handler->extraInfo->requestId, reqId, strlen(reqId));
}
else
{
char *old_requestId = handler->extraInfo->requestId;
handler->extraInfo->requestId = lingxin_strdup(reqId);
lingxin_free(old_requestId);
}
}
triggerCallback(handler, ASR_EVENT_ON_SEND_START, NULL, 0);
}
else if (strcmp(event, "text_result_generated") == 0)
{
//这里用到了cJSON_PrintUnformatted注意要释放
const char *textResult = parsePayloadStr(message);
if (!textResult)
{
lingxin_log_error("[%s], Failed to parse text result", getASRLogPre(handler));
return;
}
const int length = strlen(textResult);
lingxin_log_debug("[%s], dealEventFromServer: %d", getASRLogPre(handler), length);
triggerCallback(handler, ASR_EVENT_ON_SEND_RESULT, textResult, length);
// 释放内存
cJSON_free((char*)textResult);
}
else if (strcmp(event, "task_ended") == 0)
{
triggerCallback(handler, ASR_EVENT_ON_SEND_END, NULL, 0);
}
else if (strcmp(event, "error") == 0)
{
//这里用到了cJSON_PrintUnformatted注意要释放
char *errorInfo = parseErrorInfo(message);
triggerCallback(handler, ASR_EVENT_ON_ERROR, errorInfo, strlen(errorInfo));
cJSON_free(errorInfo);
}
}
static void onMessageReceived(ASRHandler *handler, const char *message)
{
lingxin_log_debug("[%s], asr onMessageReceived, eventStr = %s", getASRLogPre(handler), message);
cJSON *jsonMessage = cJSON_Parse(message);
if (!jsonMessage)
{
const char *error_ptr = cJSON_GetErrorPtr();
if (error_ptr)
{
lingxin_log_error("[%s], Error json: %s", getASRLogPre(handler), message);
}
return;
}
// 解析 event
const char *eventStr = parseEvent(jsonMessage);
dealEventFromServer(handler, eventStr, jsonMessage);
// Free the JSON object
cJSON_Delete(jsonMessage);
}
static void freeASR(ASRHandler **handlerAddress)
{
if (!handlerAddress)
{
lingxin_log_error("freeASR handlerAddress null");
return;
}
ASRHandler *handler = *handlerAddress;
if (!handler)
{
lingxin_log_error("freeASR handler null");
return;
}
lingxin_log_debug("[%s], freeASR begin", getASRLogPre(handler));
if (handler->websocket && handler->websocket->config)
{
free_websocket_config(handler->websocket->config);
handler->websocket->config = NULL;
}
if (handler->extraInfo)
{
if (handler->extraInfo->instanceId)
{
lingxin_free(handler->extraInfo->instanceId);
}
if(handler->extraInfo->requestId) {
lingxin_free(handler->extraInfo->requestId);
}
lingxin_free(handler->extraInfo);
handler->extraInfo = NULL;
}
lingxin_free(handler);
*handlerAddress = NULL;
lingxin_log_debug("freeASR finish");
}
static void onWebSocketEvent(WebSocketEventType event, const char *data,
const size_t len, const int isBinary,
void *userData)
{
ASRHandler *handler = (ASRHandler *)userData;
if (!handler)
{
lingxin_log_error("onWebSocketEvent handler null");
return;
}
switch (event)
{
case ON_WEBSOCKET_CONNECTION_SUCCESS:
triggerCallback(handler, ASR_EVENT_ON_READY, NULL, 0);
break;
case ON_WEBSOCKET_DATA_RECEIVED:
onMessageReceived(handler, data);
break;
case ON_WEBSOCKET_ERROR:
lingxin_log_error("[%s], triggerCallback ON_WEBSOCKET_ERROR", getASRLogPre(handler));
triggerCallback(handler, ASR_EVENT_ON_ERROR, data, len);
break;
case ON_WEBSOCKET_DESTROY:
lingxin_log_debug("[%s], triggerCallback ON_WEBSOCKET_DESTROY", getASRLogPre(handler));
triggerCallback(handler, ASR_EVENT_ON_DESTROY, data, len);
freeASR(handler->selfPointer);
break;
default:
break;
}
}
char *asrCreate(ASRHandler **handlerAddress, ASRConfig *config, ASREventListener listener)
{
lingxin_log_debug("asrCreate begin");
ASRHandler *handler = (ASRHandler *)lingxin_calloc(1, sizeof(ASRHandler));
if (!handler)
{
lingxin_log_error("Failed to allocate memory for ASR handler");
return NULL;
}
WebsocketConfig *websocketConfig =
createWebsocketConfig(handler, config->sn, config->appKey, config->appId,
WEBSOCKET_ASR_PATH, onWebSocketEvent);
if (!websocketConfig)
{
lingxin_log_error("Failed to create WebsocketConfig");
lingxin_free(handler);
return NULL;
}
WebsocketClient *client = hook_websocket_init(websocketConfig);
if (!client)
{
lingxin_log_error("Failed to initialize WebSocket client");
free_websocket_config(websocketConfig);
lingxin_free(handler);
return NULL;
}
handler->websocket = client;
handler->listener = listener;
handler->extraInfo = NULL;
ASRExtraInfo *extraInfo = (ASRExtraInfo *)lingxin_calloc(1, sizeof(ASRExtraInfo));
if (extraInfo)
{
extraInfo->taskId = NULL;
extraInfo->requestId = NULL;
extraInfo->instanceId = generateUUID(16);
handler->extraInfo = extraInfo;
}
hook_websocket_start(handler->websocket);
handler->selfPointer = handlerAddress; // 设置 selfPointer
*handlerAddress = handler; // 返回 handler
lingxin_log_debug("[%s], asrCreate finish", getASRLogPre(handler));
return extraInfo ? extraInfo->instanceId : "";
}
bool asrSendStart(ASRHandler *handler, const char *taskId, const char *payload)
{
lingxin_log_debug("asrSendStart begin");
if (!handler)
{
lingxin_log_error("handler null");
return false;
}
if (!taskId || !payload)
{
lingxin_log_error("[%s], taskId or payload null", getASRLogPre(handler));
return false;
}
if (handler->extraInfo)
{
handler->extraInfo->taskId = (char *)taskId;
}
int length = snprintf(NULL, 0, "{\"header\":{\"action\":\"start_task\",\"task_id\":\"%s\"},\"payload\":%s}", taskId, payload);
if(length <= 0) {
return false;
}
char *message = lingxin_malloc(length + 1);
if (!message)
{
lingxin_log_error("[%s],Failed to allocate memory for message", getASRLogPre(handler));
return false;
}
snprintf(message, length+1,"{\"header\":{\"action\":\"start_task\",\"task_id\":\"%s\"},\"payload\":%s}", taskId, payload);
bool result = hook_websocket_send_text(handler->websocket, message);
lingxin_free(message);
lingxin_log_debug("[%s],asrSendStart result: %s", getASRLogPre(handler), result ? "true" : "false");
return result;
}
int asrSend(ASRHandler *handler, const char *audioData, size_t dataSize)
{
lingxin_log_debug("[%s], asrSend begin", getASRLogPre(handler));
if (!handler)
{
lingxin_log_error("handler null");
return 0;
}
if (!audioData || !dataSize)
{
lingxin_log_error("[%s], audioData or dataSize null", getASRLogPre(handler));
return 0;
}
int result = hook_websocket_send_binary(handler->websocket, audioData, dataSize);
lingxin_log_debug("[%s], asrSend result: %d", getASRLogPre(handler), result);
return result;
}
void asrDestroy(ASRHandler *handler)
{
lingxin_log_debug("[%s], asrDestroy begin", getASRLogPre(handler));
if (!handler || !handler->selfPointer || !*handler->selfPointer)
{
lingxin_log_error("handler or handler->selfPointer null");
return;
}
hook_websocket_close(handler->websocket);
lingxin_log_debug("asrDestroy after");
}
bool asrSendStop(ASRHandler *handler, const char *taskId)
{
lingxin_log_debug("[%s], asrSendStop begin", getASRLogPre(handler));
if (!handler)
{
lingxin_log_error("handler null");
return false;
}
if (!taskId)
{
lingxin_log_error("[%s], taskId null", getASRLogPre(handler));
return false;
}
if (handler->extraInfo)
{
handler->extraInfo->taskId = (char *)taskId;
}
char message[256];
snprintf(message, sizeof(message), "{\"header\":{\"action\":\"end_task\",\"task_id\":\"%s\",\"request_id\":\"%s\"},\"payload\":{}}", taskId, (handler->extraInfo && handler->extraInfo->requestId) ? handler->extraInfo->requestId : "");
bool result = hook_websocket_send_text(handler->websocket, message);
lingxin_log_debug("[%s], asrSendStop result: %s", getASRLogPre(handler), result ? "true" : "false");
return result;
}

View File

@@ -0,0 +1,313 @@
/*
Copyright (c) 2009 Dave Gamble
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#ifndef cJSON__h
#define cJSON__h
#ifdef __cplusplus
extern "C" {
#endif
/* project version */
#define CJSON_VERSION_MAJOR 1
#define CJSON_VERSION_MINOR 4
#define CJSON_VERSION_PATCH 5
#include <stddef.h>
/* cJSON Types: */
#define cJSON_Invalid (0)
#define cJSON_False (1 << 0)
#define cJSON_True (1 << 1)
#define cJSON_NULL (1 << 2)
#define cJSON_Number (1 << 3)
#define cJSON_String (1 << 4)
#define cJSON_Array (1 << 5)
#define cJSON_Object (1 << 6)
#define cJSON_Raw (1 << 7) /* raw json */
#define cJSON_IsReference 256
#define cJSON_StringIsConst 512
/* The cJSON structure: */
typedef struct cJSON {
/* next/prev allow you to walk array/object chains. Alternatively, use
* GetArraySize/GetArrayItem/GetObjectItem */
struct cJSON *next;
struct cJSON *prev;
/* An array or object item will have a child pointer pointing to a chain of
* the items in the array/object. */
struct cJSON *child;
/* The type of the item, as above. */
int type;
/* The item's string, if type==cJSON_String and type == cJSON_Raw */
char *valuestring;
/* The item's number, if type==cJSON_Number */
int valueint;
/* The item's number, if type==cJSON_Number */
double valuedouble;
/* The item's name string, if this item is the child of, or is in the list of
* subitems of an object. */
char *string;
} cJSON;
typedef struct cJSON_Hooks {
void *(*malloc_fn)(size_t sz);
void (*free_fn)(void *ptr);
} cJSON_Hooks;
typedef int cJSON_bool;
#if !defined(__WINDOWS__) && \
(defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32))
#define __WINDOWS__
#endif
#ifdef __WINDOWS__
/* When compiling for windows, we specify a specific calling convention to avoid
issues where we are being called from a project with a different default calling
convention. For windows you have 2 define options:
CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever
dllexport symbols CJSON_EXPORT_SYMBOLS - Define this on library build when you
want to dllexport symbols
For *nix builds that support visibility attribute, you can define similar
behavior by
setting default visibility to hidden by adding
-fvisibility=hidden (for gcc)
or
-xldscope=hidden (for sun cc)
to CFLAGS
then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way
CJSON_EXPORT_SYMBOLS does
*/
#if defined(CJSON_HIDE_SYMBOLS)
#define CJSON_PUBLIC(type) type __stdcall
#elif defined(CJSON_EXPORT_SYMBOLS)
#define CJSON_PUBLIC(type) __declspec(dllexport) type __stdcall
#else
#define CJSON_PUBLIC(type) __declspec(dllimport) type __stdcall
#endif
#else /* !WIN32 */
#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined(__SUNPRO_C)) && \
defined(CJSON_API_VISIBILITY)
#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type
#else
#define CJSON_PUBLIC(type) type
#endif
#endif
/* returns the version of cJSON as a string */
CJSON_PUBLIC(const char *) cJSON_Version(void);
/* Supply malloc, realloc and free functions to cJSON */
CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks *hooks);
/* Supply a block of JSON, and this returns a cJSON object you can interrogate.
* Call cJSON_Delete when finished. */
CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value);
/* Render a cJSON entity to text for transfer/storage. Free the char* when
* finished. */
CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item);
/* Render a cJSON entity to text for transfer/storage without any formatting.
* Free the char* when finished. */
CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item);
/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess
* at the final size. guessing well reduces reallocation. fmt=0 gives
* unformatted, =1 gives formatted */
CJSON_PUBLIC(char *)
cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt);
/* Render a cJSON entity to text using a buffer already allocated in memory with
* given length. Returns 1 on success and 0 on failure. */
/* NOTE: If you are printing numbers, the buffer hat to be 63 bytes bigger then
* the printed JSON (worst case) */
CJSON_PUBLIC(cJSON_bool)
cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length,
const cJSON_bool format);
/* Delete a cJSON entity and all subentities. */
CJSON_PUBLIC(void) cJSON_Delete(cJSON *c);
/* Returns the number of items in an array (or object). */
CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array);
/* Retrieve item number "item" from array "array". Returns NULL if unsuccessful.
*/
CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int item);
/* Get item "string" from object. Case insensitive. */
CJSON_PUBLIC(cJSON *)
cJSON_GetObjectItem(const cJSON *object, const char *string);
CJSON_PUBLIC(cJSON *)
cJSON_GetObjectItemCaseSensitive(const cJSON *object, const char *string);
CJSON_PUBLIC(cJSON_bool)
cJSON_HasObjectItem(const cJSON *object, const char *string);
/* For analysing failed parses. This returns a pointer to the parse error.
* You'll probably need to look a few chars back to make sense of it. Defined
* when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */
CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void);
/* Check item type and return its value */
CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON *const item);
CJSON_PUBLIC(int) cJSON_GetIntValue(const cJSON *const item);
/* These functions check the type of an item */
CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON *const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON *const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON *const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON *const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON *const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON *const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON *const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON *const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON *const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON *const item);
#if 1
/* Duplicate will create a new, identical cJSON item to the one you pass, in new
memory that will need to be released. With recurse!=0, it will duplicate any
children connected to the item. The item->next and ->prev pointers are always
zero on return from Duplicate. */
/* Recursively compare two cJSON items for equality. If either a or b is NULL or
* invalid, they will be considered unequal.
* case_sensitive determines if object keys are treated case sensitive (1) or
* case insensitive (0) */
CJSON_PUBLIC(cJSON_bool)
cJSON_Compare(const cJSON *const a, const cJSON *const b,
const cJSON_bool case_sensitive);
/* malloc/free objects using the malloc/free functions that have been set with
* cJSON_InitHooks */
CJSON_PUBLIC(void *) cJSON_malloc(size_t size);
CJSON_PUBLIC(void) cJSON_free(void *object);
#endif
/* These calls create a cJSON item of the appropriate type. */
CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean);
CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num);
CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string);
/* raw json */
CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw);
CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void);
/* These utilities create an Array of count items. */
CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char **strings, int count);
/* Append item to the specified array/object. */
CJSON_PUBLIC(void) cJSON_AddItemToArray(cJSON *array, cJSON *item);
CJSON_PUBLIC(void)
cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item);
/* Use this when string is definitely const (i.e. a literal, or as good as), and
* will definitely survive the cJSON object. WARNING: When this function was
* used, make sure to always check that (item->type & cJSON_StringIsConst) is
* zero before writing to `item->string` */
CJSON_PUBLIC(void)
cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item);
/* Append reference to item to the specified array/object. Use this when you
* want to add an existing cJSON to a new cJSON, but don't want to corrupt your
* existing cJSON. */
CJSON_PUBLIC(void) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item);
CJSON_PUBLIC(void)
cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item);
/* Remove/Detatch items from Arrays/Objects. */
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which);
CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which);
CJSON_PUBLIC(cJSON *)
cJSON_DetachItemFromObject(cJSON *object, const char *string);
CJSON_PUBLIC(void)
cJSON_DeleteItemFromObject(cJSON *object, const char *string);
/* Update array items. */
CJSON_PUBLIC(void)
cJSON_InsertItemInArray(
cJSON *array, int which,
cJSON *newitem); /* Shifts pre-existing items to the right. */
CJSON_PUBLIC(void)
cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem);
CJSON_PUBLIC(void)
cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem);
/* Duplicate a cJSON item */
CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse);
/* Duplicate will create a new, identical cJSON item to the one you pass, in new
memory that will need to be released. With recurse!=0, it will duplicate any
children connected to the item. The item->next and ->prev pointers are always
zero on return from Duplicate. */
/* ParseWithOpts allows you to require (and check) that the JSON is null
* terminated, and to retrieve the pointer to the final byte parsed. */
/* If you supply a ptr in return_parse_end and parsing fails, then
* return_parse_end will contain a pointer to the error. If not, then
* cJSON_GetErrorPtr() does the job. */
CJSON_PUBLIC(cJSON *)
cJSON_ParseWithOpts(const char *value, const char **return_parse_end,
cJSON_bool require_null_terminated);
CJSON_PUBLIC(void) cJSON_Minify(char *json);
/* Macros for creating things quickly. */
#define cJSON_AddNullToObject(object, name) \
cJSON_AddItemToObject(object, name, cJSON_CreateNull())
#define cJSON_AddTrueToObject(object, name) \
cJSON_AddItemToObject(object, name, cJSON_CreateTrue())
#define cJSON_AddFalseToObject(object, name) \
cJSON_AddItemToObject(object, name, cJSON_CreateFalse())
#define cJSON_AddBoolToObject(object, name, b) \
cJSON_AddItemToObject(object, name, cJSON_CreateBool(b))
#define cJSON_AddNumberToObject(object, name, n) \
cJSON_AddItemToObject(object, name, cJSON_CreateNumber(n))
#define cJSON_AddStringToObject(object, name, s) \
cJSON_AddItemToObject(object, name, cJSON_CreateString(s))
#define cJSON_AddRawToObject(object, name, s) \
cJSON_AddItemToObject(object, name, cJSON_CreateRaw(s))
/* When assigning an integer value, it needs to be propagated to valuedouble
* too. */
#define cJSON_SetIntValue(object, number) \
((object) ? (object)->valueint = (object)->valuedouble = (number) : (number))
/* helper for the cJSON_SetNumberValue macro */
CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number);
#define cJSON_SetNumberValue(object, number) \
((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number))
/* Macro for iterating over an array */
#define cJSON_ArrayForEach(element, array) \
for (element = (array != NULL) ? (array)->child : NULL; element != NULL; \
element = element->next)
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,119 @@
// chat_runtime_context.h
#ifndef CHAT_RUNTIME_CONTEXT_H
#define CHAT_RUNTIME_CONTEXT_H
#include <stdbool.h>
#include "chat_state_machine.h" // 包含 ChatStateMediaType
#include "chat_api.h" // 包含 ExitCode 等定义
// 只声明结构体,不重新定义 enum
typedef struct {
ExitCode exit_code;
bool has_exit_code;
bool is_normal_exit;
bool has_is_normal_exit;
bool need_terminate_prompt;
bool has_need_terminate_prompt;
bool need_continue_prompt;
bool has_need_continue_prompt;
bool is_vad_exit;
bool has_is_vad_exit;
bool input_timeout_audio;
bool has_input_timeout_audio;
// 下行类型
ChatStateMediaType download_type;
bool has_download_type;
char *global_task; // 对于 task使用字符串存储
bool has_global_task;
char *current_task_id;
bool has_current_task_id;
bool single_round;
bool has_single_round;
/**
* 支持二开定制
* */
ChatStateMediaType upload_type;
bool has_upload_type;
bool disable_server_vad; // 本轮对话是否启用云端VAD
bool has_disable_server_vad;
bool disable_welcome_audio; // 本轮对话是否禁用欢迎语
bool has_disable_welcome_audio;
char *current_user_input;
bool has_current_user_input;
char *current_schedule_id;
bool has_current_schedule_id;
} ChatStateRuntimeContext;
// 字段枚举(这个是你自己用的,可以保留)
typedef enum {
CTX_FIELD_EXIT_CODE, // 0
CTX_FIELD_IS_NORMAL_EXIT, // 1
CTX_FIELD_NEED_TERMINATE_PROMPT, // 2
CTX_FIELD_NEED_CONTINUE_PROMPT, // 3
CTX_FIELD_IS_VAD_EXIT, // 4
CTX_FIELD_INPUT_TIMEOUT_AUDIO, // 5
CTX_FIELD_DOWNLOAD_TYPE, // 6
CTX_FIELD_GLOBAL_TASK, // 7
CTX_FIELD_SINGLE_ROUND, // 8
CTX_FIELD_COUNT, // 9
CTX_FIELD_UPLOAD_TYPE, // 10
CTX_FIELD_CURRENT_TASK_ID, // 11
CTX_FIELD_DISABLE_SERVER_VAD, // 12
CTX_FIELD_CURRENT_USER_INPUT, // 13
CTX_FIELD_CURRENT_SCHEDULE_ID, // 14
CTX_FIELD_DISABLE_WELCOME_AUDIO // 15
} ChatContextField;
// 联合体
typedef union {
ExitCode exit_code;
bool boolean;
ChatStateMediaType media_type;
const char *string;
} ContextValue;
// 函数声明
bool init_chat_runtime_context(const ChatStateRuntimeContext *default_config);
void destroy_chat_runtime_context(void);
bool start_new_chat_runtime_context(void);
void end_current_chat_runtime_context(void);
void reset_session_context(void);
const ChatStateRuntimeContext* get_current_context(void);
// 更新临时上下文
bool update_temp_context(ChatContextField field, ContextValue value);
// 更新会话上下文
bool update_sesseion_context(ChatContextField field, ContextValue value);
bool update_current_context(ChatContextField field, ContextValue value);
/**
* 工具函数
*/
// 打印函数
void print_chat_context(const ChatStateRuntimeContext *ctx);
// start_task指令根据端侧的type置换为服务端需要的字符串
char *get_input_type_string();
// start_task指令根据端侧的type置换为服务端需要的字符串
char *get_output_type_string();
#endif // CHAT_RUNTIME_CONTEXT_H

View File

@@ -0,0 +1,123 @@
#ifndef CHAT_STATE_MACHINE_H
#define CHAT_STATE_MACHINE_H
// 添加模块函数依赖
#include "audio_buffer_play.h"
#include "lingxin_local_player_manager.h"
#include "lingxin_protocol_manager.h"
#include "lingxin_time_task_manager.h"
// 添加事件定义
#include "chat_state_machine_event.h"
// 添加对外暴露函数定义
#include "chat_api.h"
#include "lingxin_chat_api_inner.h"
// 状态机状态
typedef enum
{
State_Idle = 0, // 等待唤醒态
State_Welcome = 1, // 欢迎语播放态
State_Upload_Init = 2, // 新一轮对话初始化(开始录音和请求连续对话)
State_Upload_Transfer = 3, // 录音传输态
State_Download_Init = 5, // 流式播放开始态
State_Download_Play = 6, // 流式播放中
State_Download_End = 7, // 下行结束
State_NoVoice_Start = 8, // noVoice循环初始化
State_NoVoice_Terminate = 9, // 打断当前对话开启新一轮noVoice循环
State_Terminate = 10, // 打断状态
State_Exit = 11, // 主动退出
State_Task_Complete = 12, // 任务完成态(单轮任务结束)
} ChatState;
typedef enum
{
Media_Type_Chat = 0, // 默认对话
Media_Type_Multimodal = 2, // 多模态对话
Media_Type_TextOnly = 3, // 纯文本对话
} ChatStateMediaType;
// 携带payload向状态机发送事件
typedef struct {
bool disable_welcome_audio; // 首次唤醒是否需要开场白
bool disable_vad; // 本轮对话是否启用云端VAD
char *task_id; // 本轮ß对话是否指定task_id
char *task; // 本轮对话的场景
char *task_for_once; // 本轮第一次对话的场景
bool single_round; // 本轮对话是否为仅单轮对话
char *user_input; // 用户输入的文本
} WakeupDetectedPayload;
typedef struct {
bool disable_close_ws_immediately; // 是否立即关闭websocket, true 就是立即关闭websocket
} WillExitPayload;
typedef struct {
bool disable_vad; // 本次事件是否为禁用云端VAD的场景
} VadStopPayload;
typedef struct {
void *buf;
int rlen;
} AudioDataPayload;
typedef struct {
char *schedule_task_id; // 当前触发的定时任务的唯一id, 塞入scheduleTaskId中
char *input_mode; // 当前触发的定时任务的输入模式塞入inputMode中
} ScheduleTimerPayload;
typedef struct {
WakeupDetectedPayload *wakeup_detected_payload;
WillExitPayload *will_exit_payload;
VadStopPayload *vad_stop_payload;
AudioDataPayload *audio_data_payload;
ScheduleTimerPayload *schedule_timer_payload;
} StateEventPayload;
/**
* State_Exit退出对话阶段的中间状态。
* 用于跟踪退出流程中各模块的完成情况,
* 录音、流式播放、voice chat 三者均完成后才切换到 State_Idle。
*/
typedef struct
{
bool record_terminated; // 上行录音模块是否已打断完成
bool buffer_play_terminated; // 下行流式播放模块是否已打断完成
bool voice_chat_terminated; // voice chat 打断指令是否已完成TerminateEnd / AIEnd
bool voice_chat_exited; // voice chat 引擎是否已完全销毁ExitEnd
bool is_normal_exit; // 是否为用户主动退出对话模式true=主动退出false=异常断开)
} InnerStateForExit;
// 内部状态集合:用于在状态切换时向目标状态传递预设的内部状态
typedef struct
{
InnerStateForExit *inner_state_for_exit; // 退出中状态的内部状态
} InnerStateCollection;
void state_machine_run_event_with_payload(StateEvent event, StateEventPayload *payload);
// 状态机初始化
void voice_chat_machine_init(bool need_terminate_prompt, bool need_continue_prompt);
// 获取状态机是否为终止状态
bool get_chat_state_terminate();
// 接收SDK的mp3数据
void state_machine_receive_mp3_data(void *buf, int rlen);
// 接收SDK的定时任务数据
void state_machine_receive_schedule_data(void *scheduleStr);
void state_machine_receive_error(ExitCode exit_code);
// 是否正在播放音频
bool is_state_audio_playing();
// 内部策略类调用
void turn_to_with_preset_inner_state(ChatState state, StateEvent event, InnerStateCollection *preset_inner_state);
#endif // CHAT_STATE_MACHINE_H

View File

@@ -0,0 +1,32 @@
#ifndef LINGXIN_BASE64_UTIL_H
#define LINGXIN_BASE64_UTIL_H
#ifdef __cplusplus
extern "C"
{
#endif
#include <stddef.h>
/**
* Base64编码函数
* @param input 输入字符串
* @param input_len 输入字符串长度
* @return 返回编码后的字符串失败返回NULL
* @note 【注意】调用者需要自行使用lingxin_free释放返回的字符串
*/
char* lingxin_base64_encode(const char *input, size_t input_len);
/**
* Base64解码函数
* @param input 输入字符串
* @param input_len 输入字符串长度
* @return 返回解码后的字符串失败返回NULL
* @note 【注意】调用者需要自行使用lingxin_free释放返回的字符串
*/
char *lingxin_base64_decode(const char *input, size_t input_len, size_t *output_len);
#ifdef __cplusplus
}
#endif
#endif // LINGXIN_BASE64_UTIL_H

View File

@@ -0,0 +1,39 @@
#ifndef CBUFFER_H
#define CBUFFER_H
#include <stddef.h>
typedef struct {
int *buffer;
int head;
int tail;
int max;
int full;
size_t item_size; // 每个元素的大小
} LingxinCircularBuffer;
// 初始化循环缓冲区
LingxinCircularBuffer* lingxin_cbuffer_init(int size, size_t item_size);
// 销毁循环缓冲区
void lingxin_cbuffer_free(LingxinCircularBuffer *cb);
// 向循环缓冲区添加一个元素
void lingxin_cbuffer_put(LingxinCircularBuffer *cb, const void *item);
// 从循环缓冲区读取一个元素
int lingxin_cbuffer_get(LingxinCircularBuffer *cb, void *item);
// 检查缓冲区是否为空
int lingxin_cbuffer_empty(LingxinCircularBuffer *cb);
// 检查缓冲区是否已满
int lingxin_cbuffer_full(LingxinCircularBuffer *cb);
// 获取缓冲区中的元素数量
int lingxin_cbuffer_size(LingxinCircularBuffer *cb);
// 重置循环缓冲区
int lingxin_cbuffer_reset(LingxinCircularBuffer *cb);
#endif // CBUFFER_H

View File

@@ -0,0 +1,41 @@
#ifndef __LINGXIN_CHAT_API_INNER_H__
#define __LINGXIN_CHAT_API_INNER_H__
#ifdef __cplusplus
extern "C" {
#endif
#include "chat_api.h"
// #define LINGXIN_TEST
/**
* 内部对外发送事件的方法(直接透传)
*/
void lingxin_emit_chat_event(ChatLifeCycleEvent event, void *payload);
/**
* 报告错误
*/
void lingxin_report_error(char *error);
/**
* 触发多模态输入
*/
int lingxin_emit_multimodal_input_event(LingxinMultimodalInputListenerProps props);
/*
* 内部初始化方法
*/
int inner_voice_chat_init(VoiceChatInitProps *init_props);
/**
* 内部启动新对话方法
*/
int inner_start_new_chat(StartNewChatProps *start_props);
#ifdef __cplusplus
}
#endif
#endif /* __LINGXIN_CHAT_API_INNER_H__ */

View File

@@ -0,0 +1,50 @@
#ifndef LINGXIN_COMMON_COMMON_H
#define LINGXIN_COMMON_COMMON_H
#ifdef __cplusplus
extern "C"
{
#endif
#include "lingxin_http.h"
#include "lingxin_hook_websocket.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define PROTOCOL_HTTP "http"
#define PROTOCOL_WEBSOCKET "ws"
#ifdef ENV_DAILY
#define REQUEST_URL "math-daily.edu-aliyun.com"
#define REQUEST_PORT 80
#else
#define REQUEST_URL "eagent.edu-aliyun.com"
#define REQUEST_PORT 80
#endif
#define WEBSOCKET_CHAT_PATH "gw/ws/open/api/v1/agentChat"
#define WEBSOCKET_ASR_PATH "gw/ws/open/api/v1/asr"
#define WEBSOCKET_TTS_PATH "gw/ws/open/api/v1/tts"
#define LLM_TEXT_PATH "smart/api/v1/llm/compatible-mode/chat"
#define LLM_IMAGE_PATH "gw/d/api/v1/text2image/createImage"
#define LLM_IMAGE_RESULT_PATH "gw/d/api/v1/text2image/getImageCreateResult"
#define LOG_UPLOAD_SWITCH_GET_PATH "gw/d/api/v1/terminal/meta"
#define LINGXIN_SERVER_CONFIG_GET_PATH "gw/d/api/v1/terminal/config/get"
WebsocketConfig *createWebsocketConfig(void *handler, const char *sn, const char *appKey, const char *appId, const char *path, WebSocketEventListener listener);
void free_websocket_config(WebsocketConfig *config);
HttpConfig *createHttpConfig(const char *appId, const char *sn, const char *appKey, const char *host, const char *path, const char *reqBody);
void free_http_config(HttpConfig *config);
char *generateUUID(int length);
void parse_host_path_from_url(const char *url, char **host, char **path);
bool http_post_without_callback(HttpConfig *config, char **response);
void parse_file_name_from_path(char file_name[64], const char *file_path);
#ifdef __cplusplus
}
#endif
#endif // LINGXIN_COMMON_COMMON_H

View File

@@ -0,0 +1,74 @@
#ifndef EVENT_QUEUE_H
#define EVENT_QUEUE_H
#ifdef __cplusplus
extern "C"
{
#endif
#ifdef LINGXI_USE_VOICE_QUEUE
#include <stdbool.h>
#include <stddef.h>
#include <pthread.h>
#include "lingxin_common.h"
extern int EVENT_QUEUE_FINISH_FLAG;
typedef struct
{
int eventType;
const char *data;
size_t dataSize;
} EventChunk;
// 队列节点结构
typedef struct EventQueueNode
{
EventChunk *event;
struct EventQueueNode *next;
} EventQueueNode;
typedef void (*EventQueueCallback)(void *userContext, int event,
const char *data, const size_t len);
// 队列结构
typedef struct
{
EventQueueNode *front;
EventQueueNode *rear;
pthread_mutex_t *mutex;
pthread_cond_t *cond;
bool isDestroyed;
pthread_t notifyThread;
EventQueueCallback callback;
void *userContext;
} EventQueue;
// 状态码
typedef enum
{
QUEUE_SUCCESS,
QUEUE_MEMORY_ERROR,
QUEUE_MUTEX_ERROR,
} EventQueueStatus;
// 初始化队列
EventQueue *eventQueueCreate(void *userContext, EventQueueCallback callback);
// 销毁队列
void eventQueueDestroy(EventQueue *queue);
// 入队操作
EventQueueStatus eventQueueEnqueue(EventQueue *queue, int dataType,
const char *message, size_t messageSize);
// 出队操作
EventChunk *eventQueueDequeue(EventQueue *queue);
#endif // LINGXI_USE_VOICE_QUEUE
#ifdef __cplusplus
}
#endif
#endif // EVENT_QUEUE_H

View File

@@ -0,0 +1,28 @@
#ifndef LINGXIN_HOOK_WEBSOCKET_H
#define LINGXIN_HOOK_WEBSOCKET_H
#ifdef __cplusplus
extern "C"
{
#endif
#include <stdbool.h>
#include <stddef.h>
#include "lingxin_websocket.h"
WebsocketClient *hook_websocket_init(WebsocketConfig *config);
bool hook_websocket_start(WebsocketClient *client);
bool hook_websocket_send_text(WebsocketClient *client, const char *message);
int hook_websocket_send_binary(WebsocketClient *client, const char *audio, size_t size);
void hook_websocket_close(WebsocketClient *client);
bool is_all_websocket_idle();
#ifdef __cplusplus
}
#endif
#endif // LINGXIN_HOOK_WEBSOCKET_H

View File

@@ -0,0 +1,23 @@
#ifndef LINGXIN_UTIL_JSON_UTIL_H
#define LINGXIN_UTIL_JSON_UTIL_H
#ifdef __cplusplus
extern "C"
{
#endif
#include "cJSON.h"
#include "schedule_timer_manager.h"
const char *parsePayloadStr(cJSON *json);
char *parseRequestId(cJSON *json);
const char *parseEvent(cJSON *json);
int isUseLLMStreaming(const char *message);
int isJSON(const char *message);
int parsePoolSize(const char *message);
char *parseErrorInfo(cJSON *json);
int parseScheduleTaskList(const char *jsonStr, ScheduleTaskList *outList);
#ifdef __cplusplus
}
#endif
#endif // LINGXIN_UTIL_JSON_UTIL_H

View File

@@ -0,0 +1,47 @@
#ifndef __LINGXIN_LOCAL_PLAYER_MANAGER_H__
#define __LINGXIN_LOCAL_PLAYER_MANAGER_H__
#ifdef __cplusplus
extern "C" {
#endif
/**
* 设置音量
* @param volume 音量
*/
void module_local_play_set_volume(int volume);
/**
* 设置首次唤醒后播放的音频
* @param audio_path 音频路径
*/
void module_local_play_set_welcome_audio_path(char *audio_path);
/**
* 设置打断时播放的音频
* @param audio_path 音频路径
*/
void module_local_play_set_terminate_audio_path(char *audio_path);
/**
* 设置连续对话进入下一轮对话前播放的音频
* @param audio_path 音频路径
*/
void module_local_play_set_continue_audio_path(char *audio_path);
/**
* 播放首次唤醒后播放的音频
*/
void module_local_play_welcome_audio();
/**
* 播放打断时播放的音频
*/
void module_local_play_terminate_audio();
/**
* 播放连续对话进入下一轮对话前播放的音频
*/
void module_local_play_continue_audio();
#ifdef __cplusplus
}
#endif
#endif /* __LINGXIN_LOCAL_PLAYER_MANAGER_H__ */

View File

@@ -0,0 +1,15 @@
#ifndef __LINGXIN_MULTIMODAL_INPUT_H__
#define __LINGXIN_MULTIMODAL_INPUT_H__
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
#endif /* __LINGXIN_MULTIMODAL_INPUT_H__ */

View File

@@ -0,0 +1,87 @@
#ifndef LINGXIN_PROTOCOL_MANAGER_H
#define LINGXIN_PROTOCOL_MANAGER_H
#include <stdbool.h>
#include "chat_api.h"
typedef enum
{
CHAT_EVENT_ON_AI_READY,
CHAT_EVENT_ON_ERROR,
CHAT_EVENT_ON_VAD_END,
CHAT_EVENT_ON_VAD_EXIT,
CHAT_EVENT_ON_STREAM_DATA,
CHAT_EVENT_ON_SYSTEM_EVENT,
CHAT_EVENT_ON_REQUEST_DATA_END,
} ChatEventType;
typedef void (*ChatEventListener)(ChatEventType event, const char *data, const size_t len);
bool add_protocol_event_listener(ChatEventListener listener);
bool remove_protocol_event_listener(ChatEventListener listener);
typedef struct VoiceChatHandler VoiceChatHandler;
typedef struct
{
char *requestId; // 请求ID
char *instanceId;
} VoiceChatContextInfo;
typedef struct
{
const char *serverPath;
const char *payload;
const char *taskId;
const char *appKey;
const char *appId;
const char *sn;
} VoiceChatConfig;
typedef struct
{
bool server_vad;
bool is_schedule_timer_task;
char *taskId;
char *task;
char *input_mode;
char *output_mode;
char *user_input;
char *scheduleTaskId;
} ChatStartNewParams;
typedef void (*ChatStartNewCallback)();
typedef void (*TerminateCheckCallback)();
typedef void (*ErrorCallback)(char *data);
void setWaitTerminateOrEndSuccess(bool target);
bool voice_chat_start_new(ChatStartNewParams *params);
void voiceChatSendAudio(void *buf, int rlen);
void voiceChatStopSendAudio();
bool isVoiceChatResponding();
bool isVoiceChatInited();
/**
* 打断对话
* @return 0 打断指令没有发出去 1 打断指令已经正常发送出去 2 打断指令或者end_task指令之前已经发送出去过需要等待响应回来
*/
int module_voiceChat_terminate();
// 被退出对话流程
bool module_voiceChat_exit();
typedef struct
{
char *unique_id;
char *frame_type;
} Multimodal_Chat_Confirm_Data;
// 多模态发送流
bool voiceChat_send_request_data_stream(char *unique_id, int index, const char *content, int content_len, char *content_type, char *frame_type, bool is_last);
// 多模态发送文字
bool voiceChat_send_request_data_text(char *content);
// 多模态结束任务
bool voiceChat_send_end_up_task(size_t confirm_data_count, Multimodal_Chat_Confirm_Data confirm_data[]);
#endif // LINGXIN_PROTOCOL_MANAGER_H

View File

@@ -0,0 +1,44 @@
#ifndef __LINGXIN_RECORDER_MANAGER_H__
#define __LINGXIN_RECORDER_MANAGER_H__
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
typedef void (*LingxinRecorderStartCallback)(bool is_success);
typedef void (*LingxinRecorderStopCallback)(bool is_success);
typedef void (*LingxinRecorderDataCallback)(void *buf, int rlen, int index);
/**
* 初始化录音模块
* @param custom_send_uni_size 录音单次发送的大小
* @param custom_send_cbuf_scale 录音缓冲区相对于录音单次发送的大小比例
*/
int module_record_manager_init(int custom_send_uni_size, int custom_send_cbuf_scale);
/**
* 开启录音
* @param start_callback 录音开始回调
*/
int module_record_start(LingxinRecorderStartCallback start_callback);
/**
* 录音可以开始发送
* @param data_callback 录音数据回调
*/
void module_record_start_send(LingxinRecorderDataCallback data_callback);
/**
* 录音结束
* @param wait_send_left 是否等待发送剩余数据
* @param stop_callback 结束回调
*/
void module_record_stop(int wait_send_left, LingxinRecorderStopCallback stop_callback);
#ifdef __cplusplus
}
#endif
#endif /* __LINGXIN_RECORDER_MANAGER_H__ */

View File

@@ -0,0 +1,14 @@
#ifndef LINGXIN_TLS_UTIL_H
#define LINGXIN_TLS_UTIL_H
#ifdef __cplusplus
extern "C"
{
#endif
char *generateSignature(const char *sn, const char *appKey, const char *appId,
const char *timestamp);
#ifdef __cplusplus
}
#endif
#endif // LINGXIN_TLS_UTIL_H

View File

@@ -0,0 +1,22 @@
#ifndef LINGXIN_USER_TRACK_H
#define LINGXIN_USER_TRACK_H
#ifdef __cplusplus
extern "C"
{
#endif
#include <stdbool.h>
bool user_track_init(char* flash_path);
void user_track_record(char* log);
bool is_user_track_init();
void core_node_record(char* node);
#ifdef __cplusplus
}
#endif
#endif // LINGXIN_USER_TRACK_H

View File

@@ -0,0 +1,14 @@
#ifndef LINGXIN_VERSION_H
#define LINGXIN_VERSION_H
#ifdef __cplusplus
extern "C"
{
#endif
#define LINGXIN_VERSION "1.13.0"
#ifdef __cplusplus
}
#endif
#endif // LINGXIN_VERSION_H

View File

@@ -0,0 +1,85 @@
#ifndef LINGXIN_VOICE_CHAT_CONFIG_H
#define LINGXIN_VOICE_CHAT_CONFIG_H
#ifdef __cplusplus
extern "C"
{
#endif
#include "chat_api.h"
/**
* @brief appId是企业在灵芯的唯一标识在灵芯工作台获取https://eagent.edu-aliyun.com/console/device/#/tenant/developer
* @return appId
*/
char* lingxin_auth_appId_get();
/**
* @brief License是灵芯平台颁发的 License
* @return License
*/
char* lingxin_auth_license_get();
/**
* @brief sn是设备在灵芯平台上的唯一 id一般用设备唯一编号
* @return sn
*/
char* lingxin_auth_sn_get();
/**
* @brief appCode是灵芯工作台中创建的app应用的唯一标识
* @return appCode
*/
char* lingxin_auth_appCode_get();
/**
* @brief device_code是设备型号
* @return device_code
*/
char* lingxin_device_code_get();
/**
* @brief 内部注册动态获取appId、license、sn、appCode的函数
* @param auth_app_id_get_func appId获取函数
* @param auth_license_get_func license获取函数
* @param auth_sn_get_func sn获取函数
* @param auth_app_code_get_func appCode获取函数
* @param chat_biz_parameter_get_func 获取业务参数的方法
* @param chat_custom_parameter_get_func 获取自定义参数的方法
* @param websocket_check_interval 检测websocket连接状态的间隔时间
* @param websocket_check_timeout 检测websocket连接状态的超时时间
*/
void module_voice_chat_config_init(
AuthAppIdGetFunc auth_app_id_get_func,
AuthLicenseGetFunc auth_license_get_func,
AuthSnGetFunc auth_sn_get_func,
AuthAppCodeGetFunc auth_app_code_get_func,
DeviceCodeGetFunc device_code_get_func,
ChatBizParameterGetFunc chat_biz_parameter_get_func,
ChatCustomParameterGetFunc chat_custom_parameter_get_func,
int websocket_check_interval,
int websocket_check_timeout
);
int websocket_check_interval_get();
int websocket_check_timeout_get();
char* lingxin_chat_biz_parameter_get();
char* lingxin_chat_custom_parameter_get();
typedef struct {
char* input_format; // 输入音频格式
int input_sample_rate; // 输入音频采样率
char* output_format; // 输出音频格式
int output_sample_rate; // 输出音频采样率
bool enable_schedule_task;// 是否支持定时任务
bool enable_log_upload; // 是否支持日志上传
} LingxinServerConfig;
#ifdef __cplusplus
}
#endif
#endif // LINGXIN_VOICE_CHAT_CONFIG_H

View File

@@ -0,0 +1,59 @@
#ifndef Voice_QUEUE_H
#define Voice_QUEUE_H
#ifdef __cplusplus
extern "C"
{
#endif
#ifdef LINGXI_USE_VOICE_QUEUE
#include <stdbool.h>
#include <pthread.h>
#include "lingxin_common.h"
// 定义队列结构
typedef struct
{
void *buffer; // 数据缓冲区
size_t capacity; // 队列总容量(字节数)
size_t count; // 当前已使用的缓冲区大小
size_t front; // 队列头部索引
size_t rear; // 队列尾部索引
pthread_mutex_t *mutex;
pthread_cond_t *cond;
} VoiceQueue;
typedef struct
{
void *data;
size_t length;
} VoiceChunk;
typedef bool (*ContinueWaitCheck)(void *userContext, size_t space);
// 初始化队列
VoiceQueue *voiceQueueCreate(size_t capacity);
// 销毁队列
void destroyVoiceQueue(VoiceQueue *queue);
// 入队操作
bool voiceEnqueue(VoiceQueue *queue, const char *data, size_t size);
// 出队操作
bool voiceDequeue(VoiceQueue *queue, VoiceChunk *chunk,
ContinueWaitCheck checkFunc, void *userContext);
size_t getRemainSpaceOfVoiceQueue(VoiceQueue *queue);
bool isVoiceQueueSpaceEnough(VoiceQueue *queue);
void clearVoiceQueue(VoiceQueue *queue);
#endif // LINGXI_USE_VOICE_QUEUE
#ifdef __cplusplus
}
#endif
#endif // Voice_QUEUE_H

View File

@@ -0,0 +1,49 @@
#ifndef AI_IOT_SDK_SCHEDULE_CHAT_H
#define AI_IOT_SDK_SCHEDULE_CHAT_H
#ifdef __cplusplus
extern "C"
{
#endif
#include <stdbool.h>
#include <stddef.h>
typedef struct ScheduleChatHandler ScheduleChatHandler;
typedef struct
{
char *taskId;
char *requestId; // 请求ID
char *instanceId;
} ScheduleWsExtraInfo;
typedef struct
{
bool isRecievedSystemEvent; // 是否收到 system_event
bool isWsConnectSuccess; // WebSocket 是否连接成功
bool isDestroying; // 是否正在销毁(防止重复销毁)
int sysEventTimerId; // 定时器 ID
} ScheduleWsState;
typedef void (*SystemEventListener)(ScheduleChatHandler *globalHandler, const char *data);
typedef struct
{
bool showLog;
const char *serverPath;
const char *payload;
const char *taskId;
const char *appKey;
const char *appId;
const char *sn;
} ScheduleChatConfig;
char *startFirstScheduleConnect( ScheduleChatConfig *config, SystemEventListener listener);
void scheduleWsDestroy(ScheduleChatHandler *handler);
#ifdef __cplusplus
}
#endif
#endif // AI_IOT_SDK_SCHEDULE_CHAT_H

View File

@@ -0,0 +1,29 @@
#ifndef SCHEDULE_TIMER_MANAGER_H
#define SCHEDULE_TIMER_MANAGER_H
#ifdef __cplusplus
extern "C"
{
#endif
#include <stdint.h>
#include "chat_state_machine_event.h"
typedef struct
{
int countdown;
char *taskId;
} TaskItem;
typedef struct
{
TaskItem *tasks;
int taskCount;
int advanceConnectTime;
} ScheduleTaskList;
void recieve_schedule_task_error();
void initTimerTaskList(char *scheduleStr);
void module_schedule_init();
void novoice_user_custom_listener(StateEvent event);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,7 @@
#ifndef SCHEDULE_CHAT_ENGINE_H
#define SCHEDULE_CHAT_ENGINE_H
void initScheduleChat();
#endif // SCHEDULE_CHAT_ENGINE_H

View File

@@ -0,0 +1,5 @@
#include "download_audio_play_interface.h"
extern const PlaybackInterface g_audioPlayer;

View File

@@ -0,0 +1,23 @@
#ifndef DOWNLOAD_AUDIO_PLAY_INTERFACE_H
#define DOWNLOAD_AUDIO_PLAY_INTERFACE_H
#include <stdint.h>
#include "chat_state_machine_event.h"
typedef enum {
Lingxin_Download_Audio_InitEnd,
Lingxin_Download_Audio_TerminateEnd,
Lingxin_Download_Audio_PlayEnd
} LingxinDownloadAudioEvent;
typedef void (*PlaybackEventHandler)(LingxinDownloadAudioEvent event, void *user_data);
typedef struct {
void (*init)(PlaybackEventHandler callback, void *user_data);
void (*feedData)(const void *buf, int len);
void (*endOfStream)(void);
void (*terminate)(void);
void (*setVolume)(int volume);
} PlaybackInterface;
#endif // DOWNLOAD_AUDIO_PLAY_INTERFACE_H

View File

@@ -0,0 +1,7 @@
#ifndef LINGXIN_CHAT_UPLOAD_MANAGER_H
#define LINGXIN_CHAT_UPLOAD_MANAGER_H
// chat_api 调用,直接停用 上行模块录音
void lingxin_chat_upload_manager_stop_record();
#endif // LINGXIN_CHAT_UPLOAD_MANAGER_H

View File

@@ -0,0 +1,22 @@
// 单例模式
/**
* websocket控制:由内核调用,适配方只需要按照功能实现即可
* 使用方法:
* 用于控制服务端数据推送,当执行 lock 之后服务端无法再发送webSocket指令回来
* 执行 unlock 之后服务端即可发送webSocket指令
* 使用场景:
* 控制数据在首次初始化流式播放时候由于初始化线程可能稍微比较耗时需要在流式播放模块初始化结束之后才可以让服务端发送mp3数据
*/
// websocket控制删除
void lingxin_websocket_control_del();
// websocket控制创建
void lingxin_websocket_control_create();
// 解锁
void lingxin_unlock_write_websocket_controle();
// 加锁
void lingxin_lock_write_websocket_control();

View File

@@ -0,0 +1,23 @@
#include <stdbool.h>
// 定时器:后台只存在一个定时器,如果已经有,则不允许重复创建
/**
* 创建定时器
* 如果已经存在定时器,则返回 false
* 如果不存在定时器则创建一个10s定时器返回 true
*/
bool init_lingxin_chat_timer(void *priv, void (*func)(void *priv));
/**
* 删除定时器
* 如果定时器存在,则删除定时器,返回 true
* 如果定时器不存在,则返回 false
*/
bool delete_lingxin_chat_timer();
/**
* 重置定时器时间
* 如果定时器存在,则重置定时器时间,返回 true
* 如果定时器不存在,则返回 false
*/
bool reset_lingxin_chat_timer_run();

View File

@@ -0,0 +1,21 @@
#include "download_audio_play_interface.h"
#include "chat_state_machine.h"
#include "chat_state_machine_event.h"
// 初始化播放管理器(必须先调用)
void playback_manager_init(ChatStateMediaType type);
// 流式喂数据(核心接口)
void playback_manager_feed_data(void *buf, int len);
// 通知流结束(不再有数据,播放器可播完缓冲区)
void playback_manager_end_stream(void);
// 立即终止播放(打断)
void playback_manager_terminate(void);
// 设置音量0~100
void playback_manager_set_volume(int volume);

View File

@@ -0,0 +1,20 @@
// state_upload_manager.h
#ifndef STATE_UPLOAD_MANAGER_H
#define STATE_UPLOAD_MANAGER_H
#include "upload_record_interface.h"
#include "chat_state_machine.h"
// #include "ulpload_record_adapter.h"
// 初始化 & 控制
int upload_manager_init(ChatStateMediaType type);
void upload_manager_start(); // 允许开始发送(设置内部标志)
void upload_manager_terminate(void);
#endif // STATE_UPLOAD_MANAGER_H

View File

@@ -0,0 +1,13 @@
// upload_manager_factory.h
#ifndef UPLOAD_MANAGER_FACTORY_H
#define UPLOAD_MANAGER_FACTORY_H
#include "upload_record_interface.h"
#include "chat_state_machine.h"
// 初始化 & 控制
UploadModuleInterface * upload_factory_manager_init(ChatStateMediaType type);
#endif // UPLOAD_MANAGER_FACTORY_H

View File

@@ -0,0 +1,17 @@
#ifndef UPLOAD_AUDIO_RECORD_INTERFACE_H
#define UPLOAD_AUDIO_RECORD_INTERFACE_H
#include <stdint.h>
#include "lingxin_protocol_manager.h"
typedef struct
{
// 真正的上行模块的初始化
int (*upload_init)(); // 录音 init
void (*upload_start)(); // 发送start、接收task_started
void (*upload_terminate)(void); // 立即打断
void (*upload_destory)(void); // 销毁
} UploadModuleInterface;
#endif // UPLOAD_AUDIO_RECORD_INTERFACE_H

View File

@@ -0,0 +1,10 @@
#include <stdio.h>
#include "chat_state_machine.h"
#include "chat_runtime_context.h"
// 切换到任务完成态 current_context仅供只读使用
void turn_to_task_complete(const ChatStateRuntimeContext *current_context, InnerStateCollection *innerPayload);
// 接受到事件
void state_task_complete_receive_event(StateEvent event, StateEventPayload *payload);

View File

@@ -0,0 +1,7 @@
#include "chat_state_machine.h"
#include "chat_runtime_context.h"
void turn_to_download_end(const ChatStateRuntimeContext *current_context, InnerStateCollection *innerPayload);
// 接受到状态机的事件
void state_download_end_receive_event(StateEvent event, StateEventPayload *payload);

View File

@@ -0,0 +1,8 @@
#include "chat_state_machine.h"
#include "chat_runtime_context.h"
void turn_to_download_init(const ChatStateRuntimeContext *current_context, InnerStateCollection *innerPayload);
// 接受到事件
void state_download_init_receive_event(StateEvent event, StateEventPayload *payload);

View File

@@ -0,0 +1,10 @@
#include "chat_state_machine.h"
#include "chat_runtime_context.h"
void turn_to_download_transfer(const ChatStateRuntimeContext *current_context, InnerStateCollection *innerPayload);
// 接受到事件
void state_download_transfer_receive_event(StateEvent event, StateEventPayload *payload);
void state_download_feed_data(void *data, int len);

View File

@@ -0,0 +1,8 @@
#include "chat_state_machine.h"
#include "chat_runtime_context.h"
// 切换到任务完成态
void turn_to_exit(const ChatStateRuntimeContext *current_context, InnerStateCollection *payload, StateEvent event);
// 接受到事件
void state_exit_receive_event(StateEvent event, StateEventPayload *payload);

View File

@@ -0,0 +1,15 @@
#ifndef A0D29965_3A0B_4525_B6E0_63209E080EE9
#define A0D29965_3A0B_4525_B6E0_63209E080EE9
#include "chat_state_machine.h"
#include "chat_runtime_context.h"
#include "upload_record_interface.h"
#include "chat_state_machine_event.h"
void turn_to_upload_init(const ChatStateRuntimeContext *current_context, InnerStateCollection *innerPayload);
// 接受到事件
void state_upload_init_receive_event(StateEvent event, StateEventPayload *payload);
#endif /* A0D29965_3A0B_4525_B6E0_63209E080EE9 */

View File

@@ -0,0 +1,15 @@
#ifndef D4B54680_F5B1_42CF_AC3C_DF659F2E55AB
#define D4B54680_F5B1_42CF_AC3C_DF659F2E55AB
#include "chat_state_machine.h"
#include "chat_runtime_context.h"
#include "state_module_manager/state_upload_manager.h"
#include "upload_record_interface.h"
#include "chat_state_machine_event.h"
void turn_to_upload_transfer(const ChatStateRuntimeContext *current_context, InnerStateCollection *innerPayload);
// 接受到事件
void state_upload_transfer_receive_event(StateEvent event, StateEventPayload *payload);
#endif /* D4B54680_F5B1_42CF_AC3C_DF659F2E55AB */

View File

@@ -0,0 +1,50 @@
#include "cJSON.h"
#include "lingxin_common.h"
#include "lingxin_http.h"
#include "llm_generate.h"
#include "lingxin_log.h"
#include "lingxin_memory.h"
static void callbackFromHttpRequest(void *contents, size_t size, void *userp)
{
char **response = (char **)userp;
// 计算现有数据长度
size_t existingLength = *response ? strlen(*response) : 0;
// 重新分配内存以容纳现有数据和新数据
char *newResponse = (char *)realloc((void *)(intptr_t)*response, existingLength + size + 1);
if (!newResponse)
{
lingxin_log_error("Memory allocation failed");
return;
}
*response = newResponse;
// 拷贝新数据到响应缓冲区
memcpy(*response + existingLength, contents, size);
(*response)[existingLength + size] = '\0'; // 添加字符串结束符
}
void generateImage(const char *appId, const char *sn, const char *appKey, const char *requestParams, char **response)
{
if (!appId || !sn || !appKey || !requestParams)
{
lingxin_log_error("generateImage: Invalid input parameters");
return;
}
HttpConfig *config = createHttpConfig(appId, sn, appKey, REQUEST_URL, LLM_IMAGE_PATH, requestParams);
http_post(config, callbackFromHttpRequest, response);
free_http_config(config);
}
void queryGenerateImageResult(const char *appId, const char *sn, const char *appKey, const char *requestParams, char **response)
{
if (!appId || !sn || !appKey || !requestParams)
{
lingxin_log_error("queryGenerateImageResult: Invalid input parameters");
return;
}
HttpConfig *config = createHttpConfig(appId, sn, appKey, REQUEST_URL, LLM_IMAGE_RESULT_PATH, requestParams);
http_post(config, callbackFromHttpRequest, response);
free_http_config(config);
}

View File

@@ -0,0 +1,95 @@
#include "lingxin_common.h"
#include "lingxin_json_util.h"
#include "lingxin_http.h"
#include "llm_generate.h"
#include "lingxin_log.h"
#include "lingxin_memory.h"
struct LLMCallerStruct
{
int useStreaming;
GenerateTextRequestCallback userCallback;
};
static char *getValidMessage(const char *data, size_t total_size)
{
// 分配足够的内存来存储提取的消息和终止符
char *message = (char *)lingxin_malloc((total_size + 1) * sizeof(char));
if (message == NULL)
{
lingxin_log_error("Memory allocation failed\n");
return NULL;
}
// 复制最多total_size个字符到message中
strncpy(message, data, total_size);
// 确保字符串以空字符终止
message[total_size] = '\0';
return message;
}
static void callbackFromLLMRequest(void *contents, size_t size, void *userp)
{
size_t total_size = size;
// lingxin_log_debug("-------------------------custom_write_callback
// --------------------------\n"); lingxin_log_debug("%.*s\n", (int)total_size,
// contents);
struct LLMCallerStruct *callerData = (struct LLMCallerStruct *)userp;
if (callerData->userCallback == NULL)
{
lingxin_log_debug("use callback null\n");
return;
}
char *message = getValidMessage(contents, total_size);
// lingxin_free(contents);
// 非流式输出
if (callerData->useStreaming == 0)
{
// lingxin_log_debug("----------------------not streaming
// data--------------------------\n"); printWithEscapedNewlines(message);
// lingxin_log_debug("\n");
callerData->userCallback(message, 1);
lingxin_free(message);
return;
}
// lingxin_log_debug("----------------------streaming
// data--------------------------\n"); 流式输出
// printWithEscapedNewlines(message);
// lingxin_log_debug("\n");
// if (strcmp(message, "data:") == 0 || strcmp(message, "\n\n") == 0)
// {
// // lingxin_log_debug("----------------------invali streaming
// data--------------------------\n"); lingxin_free(message); return;
// }
if (strcmp(message, "[DONE]") == 0)
{
// lingxin_log_debug("----------------------streaming data
// finish--------------------------\n");
callerData->userCallback("", 1);
lingxin_free(message);
return;
}
if (isJSON(message))
{
// lingxin_log_debug("----------------------streaming data
// result--------------------------\n");
callerData->userCallback(message, 0);
}
lingxin_free(message);
}
void generateText(const char *appId, const char *sn, const char *appKey, const char *input, GenerateTextRequestCallback callback)
{
if (!appId || !sn || !appKey || !input)
{
lingxin_log_error("generateText: Invalid input parameters");
return;
}
const int useStreaming = isUseLLMStreaming(input);
struct LLMCallerStruct callerData = {.useStreaming = useStreaming,
.userCallback = callback};
HttpConfig *config = createHttpConfig(appId, sn, appKey, REQUEST_URL, LLM_TEXT_PATH, input);
http_post(config, callbackFromLLMRequest, &callerData);
free_http_config(config);
}

View File

@@ -0,0 +1,86 @@
#include "lingxin_log.h"
#include <stdarg.h>
#include <stdio.h>
#include "lingxin_version.h"
#include "lingxin_common.h"
#include <string.h>
#include "lingxin_system_time.h"
#include "lingxin_user_track.h"
#include "lingxin_device_info.h"
#include "lingxin_printf.h"
#include "lingxin_thread.h"
static void _log_internal(char *log_buffer, size_t log_buffer_size, int level, const char *file_path, int line, const char *node, const char *format, va_list args)
{
char moduleName[64] = {0};
parse_file_name_from_path(moduleName, file_path);
char *level_str = "";
if (level == LINGXIN_DEBUG)
{
level_str = "D";
}
else if (level == LINGXIN_ERROR)
{
level_str = "E";
}
else if (level == LINGXIN_WARN)
{
level_str = "W";
}
static char *app_name = NULL;
static char *app_version = NULL;
if (!app_name)
{
char *temp_name = get_lingxin_device_name();
app_name = temp_name ? temp_name : "NULL";
}
if (!app_version)
{
char *temp_version = get_lingxin_device_version();
app_version = temp_version ? temp_version : "NULL";
}
// 格式化时间字符串,[mm-dd hh:mm:ss.milliseconds]
LINGXIN_TIME lingxin_time = {0};
lingxin_get_current_time(&lingxin_time);
char *temp_name = lingxin_get_current_thread_name();
char *thread_name = temp_name ? temp_name : "";
const char *format_template_with_node = "[%02d-%02d %02d:%02d:%02d.%03d] [%s_%s] [%s] [%s:%d] [%s] [%s] ";
const char *format_template_without_node = "[%02d-%02d %02d:%02d:%02d.%03d] [%s_%s] [%s] [%s:%d] [%s] ";
// 格式化完整日志到静态缓冲区
int prefix_len = -1;
if (node)
{
prefix_len = snprintf(log_buffer, log_buffer_size, format_template_with_node, lingxin_time.mon, lingxin_time.day, lingxin_time.hour, lingxin_time.min, lingxin_time.sec, lingxin_time.mill_sec, app_name, app_version, level_str, moduleName, line, thread_name, node);
}
else
{
prefix_len = snprintf(log_buffer, log_buffer_size, format_template_without_node, lingxin_time.mon, lingxin_time.day, lingxin_time.hour, lingxin_time.min, lingxin_time.sec, lingxin_time.mill_sec, app_name, app_version, level_str, moduleName, line, thread_name);
}
if (prefix_len >= 0 && prefix_len < log_buffer_size)
{
// 添加实际日志内容
vsnprintf(log_buffer + prefix_len, log_buffer_size - prefix_len, format, args);
}
// 输出到控制台
lingxin_printf(log_buffer);
}
void _lingxin_log_print_internal_(int level, int is_ut, const char *file_path, int line, const char *node, const char *format, ...)
{
char log_buffer[512];
va_list args;
va_start(args, format);
_log_internal(log_buffer, sizeof(log_buffer), level, file_path, line, node, format, args);
va_end(args);
if (is_user_track_init() && is_ut == 1)
{
user_track_record(log_buffer);
}
}

View File

@@ -0,0 +1,823 @@
#include "lingxin_user_track.h"
#include "cJSON.h"
#include "lingxin_voice_chat_config.h"
#include "lingxin_common.h"
#include "lingxin_file.h"
#include "lingxin_http.h"
#include "lingxin_log.h"
#include "lingxin_thread.h"
#include "lingxin_mutex.h"
#include "chat_state_machine.h"
#include "lingxin_system_time.h"
#include "lingxin_version.h"
#include "lingxin_device_info.h"
#include "lingxin_memory.h"
/**
日志先写入内存,超过上限后缓存到文件,缓存到文件前,从主缓冲区切到备用缓冲区,切换后,清空主缓冲区
内存中保存格式:[4位数字][log内容][4位数字][log内容]...
文件中保存格式:
{
"sn": "xxx",
"log": [
{"content": "log内容" },
{ "content": "log内容"}
]
}
**/
// 核心节点统计文件
#define CORE_NODE_FILE_NAME "cn.lx"
// 日志缓存文件
#define USER_TRACK_FILE_NAME "ut.lx"
#define USER_TRACK_FILE_MAX_LENGTH (500 * 1024) // 本地文件最大500K
#define USER_TRACK_MEMORY_BUFFER_SIZE (40 * 1024) // 缓冲区上限
#define MEMORY_LENGTH_FIELD_WIDTH 4 // 固定4位数字表示每一个内容的长度
#define SWITCH_UPDATE_TIME_INTERVAL 60 // 60秒更新一次日志开关
#define UPLOAD_TIME_INTERVAL 90 // 90秒上传一次日志
// 缓冲区结构
typedef struct
{
lingxin_tid_t thread_id;
lingxin_mutex_t mutex;
lingxin_mutex_t core_node_mutex;
bool running;
// 写日志是频率很高的操作,不能频繁申请释放内存,否则会造成内存碎片化问题,所以用双缓冲方案
char *buffer_a; // 缓冲区A
char *buffer_b; // 缓冲区B
char *recording_buffer; // 记录log的缓冲区可以指向缓冲区A或者缓冲区B
char *will_cache_buffer; // 等待写入文件的缓冲区可以指向缓冲区A或者缓冲区B
int offset_recording_buffer;
int offset_will_cache_buffer;
bool enable_file_cache;
volatile bool can_record;
bool core_node_file_null; // 文件清空是异步操作,这里用一个标志位来表示是否有内容
bool ut_file_null; // 文件清空是异步操作,这里用一个标志位来表示是否有内容
char *file_path_core_node; // 核心节点存储文件路径
char *memory_buffer_core_node; // 核心节点存储内存,文件缓存不可用时激活
int offset_core_node_buffer;
char *file_path_ut; // 日志缓存存储文件路径
long last_upload_time;
long last_switch_update_time;
char *log_upload_path;
char *log_upload_host;
} lingxin_user_track;
static lingxin_user_track *lingxin_ut = NULL;
static int get_ut_file_length(char *file_name)
{
if (!file_name)
{
lingxin_log_error("file name null");
return -1;
}
return lingxin_file_length(file_name);
}
static void clear_file_content(char *file_path)
{
lingxin_file_clear(file_path);
}
static bool parse_upload_result(char *response)
{
if (!response)
{
lingxin_log_error("ut upload result null");
return false;
}
cJSON *result_json = cJSON_Parse(response);
if (!result_json)
{
lingxin_log_error("ut upload result parse error %s", response);
return false;
}
cJSON *success_obj = cJSON_GetObjectItem(result_json, "success");
bool upload_success = false;
if (success_obj && cJSON_IsBool(success_obj))
{
upload_success = cJSON_IsTrue(success_obj);
}
cJSON_Delete(result_json);
return upload_success;
}
static bool do_ut_upload(char *content_to_upload)
{
char *sn = lingxin_auth_sn_get();
char *app_id = lingxin_auth_appId_get();
char *app_key = lingxin_auth_license_get();
HttpConfig *config_upload = createHttpConfig(app_id, sn, app_key, lingxin_ut->log_upload_host, lingxin_ut->log_upload_path, content_to_upload);
if (!config_upload)
{
return false;
}
char *post_result = NULL;
http_post_without_callback(config_upload, &post_result);
free_http_config(config_upload);
if (!post_result)
{
lingxin_log_error("upload result null");
return false;
}
bool upload_success = parse_upload_result(post_result);
lingxin_free(post_result);
if (!upload_success)
{
lingxin_log_error("upload failed");
return false;
}
return true;
}
static int lxut_content_to_json(char *ut_content, int all_ut_content_len, char **jsonString)
{
int cur_parse_pos = 0;
cJSON *root = cJSON_CreateObject();
if (!root)
{
lingxin_log_error("json create fail");
return -1;
}
char *sn = lingxin_auth_sn_get();
cJSON_AddStringToObject(root, "sn", sn ? sn : "");
cJSON *log_array = cJSON_CreateArray();
if (!log_array)
{
cJSON_Delete(root);
return -1;
}
cJSON_AddItemToObject(root, "log", log_array);
while (cur_parse_pos < all_ut_content_len)
{
if (cur_parse_pos + MEMORY_LENGTH_FIELD_WIDTH > all_ut_content_len)
{
lingxin_log_error("Incomplete log data at offset %d", cur_parse_pos);
break;
}
// 读取4位长度字段
char length_str_temp[MEMORY_LENGTH_FIELD_WIDTH + 1];
memcpy(length_str_temp, ut_content + cur_parse_pos, MEMORY_LENGTH_FIELD_WIDTH);
length_str_temp[MEMORY_LENGTH_FIELD_WIDTH] = '\0';
// 解析长度
int length_of_cur_log = atoi(length_str_temp);
if (length_of_cur_log < 0)
{
lingxin_log_error("Invalid length value at offset %d: %s", cur_parse_pos, length_str_temp);
break;
}
// 检查是否有足够的数据
if (cur_parse_pos + MEMORY_LENGTH_FIELD_WIDTH + length_of_cur_log > all_ut_content_len)
{
break;
}
cJSON *content_fragment = cJSON_CreateObject();
if (content_fragment)
{
char *log_content = lingxin_malloc(length_of_cur_log + 1);
if (log_content)
{
memcpy(log_content, ut_content + cur_parse_pos + MEMORY_LENGTH_FIELD_WIDTH, length_of_cur_log);
log_content[length_of_cur_log] = '\0';
cJSON_AddStringToObject(content_fragment, "content", log_content);
cJSON_AddItemToArray(log_array, content_fragment);
lingxin_free(log_content);
}
else
{
cJSON_Delete(content_fragment);
lingxin_log_error("Failed to allocate memory for log content");
}
}
cur_parse_pos += MEMORY_LENGTH_FIELD_WIDTH + length_of_cur_log;
}
*jsonString = cJSON_PrintUnformatted(root);
cJSON_Delete(root);
return cur_parse_pos;
}
static bool upload_file_cache(char *file_name)
{
int total_file_length = get_ut_file_length(file_name);
if (total_file_length <= 0)
{
lingxin_log_error("file length zero");
return false;
}
int single_max_load_size = USER_TRACK_MEMORY_BUFFER_SIZE;
int cur_file_read_pos = 0;
char *content_file_segment = lingxin_calloc(1, single_max_load_size + 1);
if (!content_file_segment)
{
lingxin_log_error("Failed to allocate memory for file content");
return false;
}
bool is_file_clear = false;
while (cur_file_read_pos < total_file_length)
{
int remaining_bytes = total_file_length - cur_file_read_pos;
int actual_read_size = remaining_bytes > single_max_load_size ? single_max_load_size : remaining_bytes;
bool read_result = lingxin_file_read(file_name, content_file_segment, cur_file_read_pos, actual_read_size);
if (!read_result)
{
lingxin_log_error("Failed to read file content");
break;
}
(content_file_segment)[actual_read_size] = '\0';
char *content_file_json = NULL;
// printf("content_file_segment: %s\n", content_file_segment);
int parsed_index = lxut_content_to_json(content_file_segment, strlen(content_file_segment), &content_file_json);
if (parsed_index == -1)
{
break;
}
if (parsed_index == 0 || !content_file_json)
{
is_file_clear = true;
lingxin_log_error("no content has parsed, break");
break;
}
// printf("lxut_content_to_json: %s\n", content_file_json);
bool upload_result = do_ut_upload(content_file_json);
if (upload_result)
{
is_file_clear = true;
}
cJSON_free(content_file_json);
cur_file_read_pos += parsed_index;
}
// 文件内容解析失败,或者任意内容上传成功,清空文件
if (is_file_clear)
{
clear_file_content(file_name);
}
lingxin_free(content_file_segment);
return is_file_clear;
}
static void upload_memory_core_node_buffer()
{
char *content_file_json = NULL;
lxut_content_to_json(lingxin_ut->memory_buffer_core_node, lingxin_ut->offset_core_node_buffer, &content_file_json);
if (content_file_json)
{
if (do_ut_upload(content_file_json))
{
lingxin_ut->offset_core_node_buffer = 0;
}
cJSON_free(content_file_json);
}
}
static void upload_memory_will_cache_buffer()
{
char *content_file_json = NULL;
lxut_content_to_json(lingxin_ut->will_cache_buffer, lingxin_ut->offset_will_cache_buffer, &content_file_json);
if (content_file_json)
{
if (do_ut_upload(content_file_json))
{
lingxin_ut->offset_will_cache_buffer = 0;
}
cJSON_free(content_file_json);
}
}
static void upload_memory_recording_cache_buffer()
{
char *content_file_json = NULL;
lxut_content_to_json(lingxin_ut->recording_buffer, lingxin_ut->offset_recording_buffer, &content_file_json);
if (content_file_json)
{
if (do_ut_upload(content_file_json))
{
lingxin_ut->offset_recording_buffer = 0;
}
cJSON_free(content_file_json);
}
}
static bool parse_upload_switch_and_url(char *response, char **upload_url)
{
if (!response)
{
lingxin_log_error("upload switch result null");
return false;
}
cJSON *result_json = cJSON_Parse(response);
if (!result_json)
{
lingxin_log_error("upload switch result parse error %s", response);
return false;
}
cJSON *success_obj = cJSON_GetObjectItem(result_json, "success");
bool switch_get_success = false;
if (success_obj && cJSON_IsBool(success_obj))
{
switch_get_success = cJSON_IsTrue(success_obj);
}
if (!switch_get_success)
{
lingxin_log_error("success false");
cJSON_Delete(result_json);
return false;
}
cJSON *data_obj = cJSON_GetObjectItem(result_json, "data");
if (!data_obj)
{
lingxin_log_error("upload switch result parse data error %s", response);
cJSON_Delete(result_json);
return false;
}
bool upload_switch = false;
cJSON *enable_upload_obj = cJSON_GetObjectItem(data_obj, "enable_upload");
if (enable_upload_obj && cJSON_IsBool(enable_upload_obj))
{
upload_switch = cJSON_IsTrue(enable_upload_obj);
}
else
{
lingxin_log_error("upload switch result parse switch error %s", response);
}
if (!upload_switch)
{
lingxin_log_error("upload switch is false");
cJSON_Delete(result_json);
return false;
}
cJSON *upload_url_obj = cJSON_GetObjectItem(data_obj, "upload_url");
if (!upload_url_obj)
{
lingxin_log_error("upload switch result parse upload_url error %s", response);
}
if (cJSON_IsString(upload_url_obj) && upload_url_obj->valuestring)
{
*upload_url = lingxin_strdup(upload_url_obj->valuestring);
}
else
{
lingxin_log_error("upload switch result parse upload_url error %s", response);
}
cJSON_Delete(result_json);
return upload_switch;
}
static bool local_file_check(char *file_name)
{
if (!file_name)
{
lingxin_log_debug("file name null");
return false;
}
bool fie_exist_result = lingxin_file_exist(file_name);
if (fie_exist_result)
{
lingxin_log_debug("%s exist", file_name);
return true;
}
bool create_result = lingxin_file_create(file_name, 0);
if (!create_result)
{
lingxin_log_error("file_create func call fail");
return false;
}
return true;
}
static bool memory_buffer_to_file(bool append, char *buffer, int buffer_size, char *file_name)
{
if (!buffer || buffer_size <= 0)
{
return false;
}
return lingxin_file_write(file_name, append, buffer, buffer_size);
}
static bool if_ut_file_full()
{
int file_length = get_ut_file_length(lingxin_ut->file_path_ut);
bool result = file_length >= USER_TRACK_FILE_MAX_LENGTH;
if (result)
{
lingxin_log_error("file length %ld > %d", file_length, USER_TRACK_FILE_MAX_LENGTH);
}
return result;
}
static void update_log_switch()
{
lingxin_ut->last_switch_update_time = lingxin_get_timestamp_s();
char *sn = lingxin_auth_sn_get();
char body[64];
snprintf(body, sizeof(body), "{\"sn\":\"%s\"}", sn);
#ifdef ENV_DAILY
char* req_url = "math-daily.edu-aliyun.com";
#else
char* req_url = "eagent.edu-aliyun.com";
#endif
HttpConfig *config_get_switch = createHttpConfig(lingxin_auth_appId_get(), sn, lingxin_auth_license_get(), req_url, LOG_UPLOAD_SWITCH_GET_PATH, body);
if (!config_get_switch)
{
return;
}
char *post_result = NULL;
http_post_without_callback(config_get_switch, &post_result);
free_http_config(config_get_switch);
if (!post_result)
{
lingxin_log_error("switch get result null");
return;
}
char *url_to_upload = NULL;
parse_upload_switch_and_url(post_result, &url_to_upload);
lingxin_free(post_result);
if (!url_to_upload)
{
return;
}
if(lingxin_ut->log_upload_host) {
lingxin_free(lingxin_ut->log_upload_host);
}
if(lingxin_ut->log_upload_path) {
lingxin_free(lingxin_ut->log_upload_path);
}
parse_host_path_from_url(url_to_upload, &lingxin_ut->log_upload_host, &lingxin_ut->log_upload_path);
if (!lingxin_ut->log_upload_host || !lingxin_ut->log_upload_path)
{
lingxin_log_error("upload switch result parse url error, %s", url_to_upload);
}
lingxin_free(url_to_upload);
}
/**
* 1分钟上传一次日志
* 上传顺序:文件缓存 -> 待缓冲内存缓存 -> 正在记录的内存缓存
*/
static void try_to_upload()
{
long now = lingxin_get_timestamp_s();
// 1分钟上传执行一次
if (now - lingxin_ut->last_upload_time < UPLOAD_TIME_INTERVAL)
{
return;
}
// 更新上传时间
lingxin_ut->last_upload_time = now;
// 上传开关未更新,则不进行上传
if (!lingxin_ut->log_upload_host || !lingxin_ut->log_upload_path)
{
lingxin_log_warn("upload info null");
return;
}
// 先上传核心节点记录
if (!lingxin_ut->core_node_file_null)
{
lingxin_log_debug("upload core node file");
if (upload_file_cache(lingxin_ut->file_path_core_node))
{
lingxin_ut->core_node_file_null = true;
}
}
else if (lingxin_ut->offset_core_node_buffer > 0)
{
upload_memory_core_node_buffer();
}
lingxin_mutex_lock(lingxin_ut->mutex);
lingxin_ut->can_record = false;
lingxin_mutex_unlock(lingxin_ut->mutex);
// 本地缓存文件不为空,则上传
if (!lingxin_ut->ut_file_null)
{
lingxin_log_debug("upload ut file");
bool upload_result = upload_file_cache(lingxin_ut->file_path_ut);
if (upload_result)
{
lingxin_ut->ut_file_null = true;
}
}
// 处理已满的缓冲区
if (lingxin_ut->offset_will_cache_buffer > 0)
{
lingxin_log_debug("upload will cache");
upload_memory_will_cache_buffer();
}
// 处理正在记录的缓冲区
if (lingxin_ut->offset_recording_buffer > 0)
{
lingxin_log_debug("upload recording cache");
upload_memory_recording_cache_buffer();
}
lingxin_mutex_lock(lingxin_ut->mutex);
lingxin_ut->can_record = true;
lingxin_mutex_unlock(lingxin_ut->mutex);
}
static void *ut_thread_routine(void *arg)
{
// if (!local_file_check(lingxin_ut->file_path_core_node))
// {
// lingxin_ut->memory_buffer_core_node = lingxin_calloc(1, USER_TRACK_MEMORY_BUFFER_SIZE);
// }
if (!local_file_check(lingxin_ut->file_path_ut))
{
lingxin_ut->enable_file_cache = false;
}
while (lingxin_ut->running)
{
// 仅当没有流式音频播放时,才执行文件缓存,避免造成播放卡顿
// todo 录音时是否有影响?
if (!is_state_audio_playing())
{
// 文件缓存可用,且有缓冲区已满,有,则将已满的缓冲区数据写入文件
if (lingxin_ut->enable_file_cache && lingxin_ut->offset_will_cache_buffer > 0)
{
bool file_exceed = if_ut_file_full();
// 文件大小超过限制,直接覆盖写
bool result = memory_buffer_to_file(!file_exceed, lingxin_ut->will_cache_buffer, lingxin_ut->offset_will_cache_buffer, lingxin_ut->file_path_ut);
if (result)
{
lingxin_ut->ut_file_null = false;
// 写入成功后清空缓存缓冲区
lingxin_ut->offset_will_cache_buffer = 0;
}
}
}
// websocket空闲则执行上传
if (is_all_websocket_idle())
{
if (lingxin_get_timestamp_s() - lingxin_ut->last_switch_update_time > SWITCH_UPDATE_TIME_INTERVAL)
{
update_log_switch();
}
try_to_upload();
}
lingxin_thread_sleep(100); // 100ms
}
lingxin_log_debug("ut_thread_routine finish");
return NULL;
}
static bool memory_buffer_init()
{
// 分配两个缓冲区
lingxin_ut->buffer_a = lingxin_calloc(1, USER_TRACK_MEMORY_BUFFER_SIZE);
if (!lingxin_ut->buffer_a)
{
return false;
}
lingxin_ut->buffer_b = lingxin_calloc(1, USER_TRACK_MEMORY_BUFFER_SIZE);
if (!lingxin_ut->buffer_b)
{
lingxin_free(lingxin_ut->buffer_a);
return false;
}
lingxin_ut->memory_buffer_core_node = lingxin_calloc(1, USER_TRACK_MEMORY_BUFFER_SIZE / 4);
// 初始化指针指向
lingxin_ut->recording_buffer = lingxin_ut->buffer_a;
lingxin_ut->will_cache_buffer = lingxin_ut->buffer_b;
lingxin_ut->offset_recording_buffer = 0;
lingxin_ut->offset_will_cache_buffer = 0;
return true;
}
static char *append_file_path_name(char *flash_path, char *file_name)
{
if (!flash_path)
{
lingxin_log_error("flash path null");
return NULL;
}
int flash_path_len = strlen(flash_path);
if (flash_path_len == 0)
{
lingxin_log_error("path len zero");
return NULL;
}
int file_name_path = strlen(file_name);
// 确保路径以'/'结尾
bool need_slash = flash_path[flash_path_len - 1] != '/';
int full_path_len = flash_path_len + (need_slash ? 1 : 0) + file_name_path + 1;
char *final_path = (char *)lingxin_malloc(full_path_len);
if (!final_path)
{
lingxin_log_error("failed to allocate memory for file path");
return NULL;
}
if (need_slash)
{
snprintf(final_path, full_path_len, "%s/%s", flash_path, file_name);
}
else
{
snprintf(final_path, full_path_len, "%s%s", flash_path, file_name);
}
return final_path;
}
bool user_track_init(char *flash_path)
{
lingxin_log_debug("begin");
if (lingxin_ut)
{
lingxin_log_error("ut has inited");
return false;
}
lingxin_ut = (lingxin_user_track *)lingxin_calloc(1, sizeof(lingxin_user_track));
if (!lingxin_ut)
{
lingxin_log_error("failed to calloc lingxin ut memory");
return false;
}
lingxin_ut->enable_file_cache = false;
lingxin_ut->file_path_core_node = append_file_path_name(flash_path, CORE_NODE_FILE_NAME);
lingxin_ut->file_path_ut = append_file_path_name(flash_path, USER_TRACK_FILE_NAME);
lingxin_ut->can_record = true;
lingxin_ut->ut_file_null = true;
lingxin_ut->core_node_file_null = true;
lingxin_ut->log_upload_path = NULL;
lingxin_ut->last_upload_time = lingxin_get_timestamp_s();
lingxin_ut->last_switch_update_time = 0;
lingxin_ut->running = true;
lingxin_ut->core_node_mutex = lingxin_mutex_create();
if (!lingxin_ut->core_node_mutex)
{
lingxin_log_error("failed to initialize core_node_mutex");
}
lingxin_ut->mutex = lingxin_mutex_create();
if (!lingxin_ut->mutex)
{
lingxin_log_error("failed to initialize mutex");
lingxin_free(lingxin_ut);
lingxin_ut = NULL;
return false;
}
if (!memory_buffer_init())
{
lingxin_log_error("failed to malloc memory buffer");
lingxin_mutex_destroy(lingxin_ut->core_node_mutex);
lingxin_mutex_destroy(lingxin_ut->mutex);
lingxin_free(lingxin_ut);
lingxin_ut = NULL;
return false;
}
lingxin_thread_param_t thread_param = {.name = "lx_ut_thread", .priority = 9, .stack_size = 4096};
if (lingxin_thread_create(&lingxin_ut->thread_id, &thread_param, ut_thread_routine, NULL) != 0)
{
lingxin_log_error("failed to create ut thread");
lingxin_free(lingxin_ut->buffer_a);
lingxin_free(lingxin_ut->buffer_b);
lingxin_free(lingxin_ut);
lingxin_ut = NULL;
return false;
}
return true;
}
void user_track_record(char *content)
{
if (!content)
{
lingxin_log_error("content null");
return;
}
if (!lingxin_ut)
{
lingxin_log_error("ut not init");
return;
}
int content_len = strlen(content);
int needed_space = MEMORY_LENGTH_FIELD_WIDTH + content_len;
bool buffer_switched = false;
int cached_bytes = 0;
lingxin_mutex_lock(lingxin_ut->mutex);
if (!lingxin_ut->can_record)
{
lingxin_log_debug("reading buffer, can not record");
lingxin_mutex_unlock(lingxin_ut->mutex);
return;
}
// 检查 recording_buffer 是否已满
if ((lingxin_ut->offset_recording_buffer + needed_space) > USER_TRACK_MEMORY_BUFFER_SIZE)
{
// 检查 will_cache_buffer 是否不为空
if (lingxin_ut->offset_will_cache_buffer > 0)
{
// 这个节点需要写入排查文档改成warn
lingxin_log_warn("buffer need change, but will_cache_buffer not empty, data overwritten");
}
// 缓冲区切换 - 交换指针而不是拷贝数据
char *temp_buffer = lingxin_ut->recording_buffer;
int temp_offset = lingxin_ut->offset_recording_buffer;
lingxin_ut->recording_buffer = lingxin_ut->will_cache_buffer;
lingxin_ut->offset_recording_buffer = 0;
lingxin_ut->will_cache_buffer = temp_buffer;
lingxin_ut->offset_will_cache_buffer = temp_offset;
buffer_switched = true;
cached_bytes = temp_offset;
}
// 使用固定宽度存储长度例如4位数字不足补0
if (content_len > 9999)
{
lingxin_log_error("Content too long: %d", content_len);
lingxin_mutex_unlock(lingxin_ut->mutex);
return;
}
// 内存中写入固定4位长度
snprintf(lingxin_ut->recording_buffer + lingxin_ut->offset_recording_buffer, MEMORY_LENGTH_FIELD_WIDTH + 1, "%04d", content_len);
// 内存中写入日志内容
memcpy(lingxin_ut->recording_buffer + lingxin_ut->offset_recording_buffer + MEMORY_LENGTH_FIELD_WIDTH, content, content_len);
lingxin_ut->offset_recording_buffer += needed_space;
lingxin_mutex_unlock(lingxin_ut->mutex);
// 在锁外记录缓冲区切换日志
if (buffer_switched)
{
lingxin_log_debug("Buffer switched, cached %d bytes", cached_bytes);
}
}
bool is_user_track_init()
{
return lingxin_ut != NULL;
}
void core_node_record(char *node)
{
if (!node)
{
lingxin_log_error("core node null");
return;
}
if (!lingxin_ut || !lingxin_ut->core_node_mutex)
{
lingxin_log_error("user track not init");
return;
}
lingxin_mutex_lock(lingxin_ut->core_node_mutex);
static char key_pre[128];
static int key_pre_len = -1;
if (key_pre_len == -1)
{
char *app_name = get_lingxin_device_name();
char *app_version = get_lingxin_device_version();
if (app_name && app_version)
{
snprintf(key_pre, sizeof(key_pre), "app:%s,version:%s,CORE_NODE_", app_name, app_version);
}
else
{
snprintf(key_pre, sizeof(key_pre), "app:NONE,version:NONE,CORE_NODE_");
}
key_pre_len = strlen(key_pre);
}
int node_len = strlen(node);
int content_len = key_pre_len + node_len;
int needed_space = MEMORY_LENGTH_FIELD_WIDTH + content_len;
if (lingxin_ut->memory_buffer_core_node)
{
// 文件缓存不可用时,直接将数据按格式写入内存缓冲区
if ((lingxin_ut->offset_core_node_buffer + needed_space) <= (USER_TRACK_MEMORY_BUFFER_SIZE / 4))
{
// 写入长度字段
snprintf(lingxin_ut->memory_buffer_core_node + lingxin_ut->offset_core_node_buffer, MEMORY_LENGTH_FIELD_WIDTH + 1, "%04d", content_len);
// 写入内容
memcpy(lingxin_ut->memory_buffer_core_node + lingxin_ut->offset_core_node_buffer + MEMORY_LENGTH_FIELD_WIDTH, key_pre, key_pre_len);
memcpy(lingxin_ut->memory_buffer_core_node + lingxin_ut->offset_core_node_buffer + MEMORY_LENGTH_FIELD_WIDTH + key_pre_len, node, node_len);
lingxin_ut->offset_core_node_buffer += needed_space;
}
else
{
lingxin_log_warn("Memory buffer core node is full, data discarded");
}
}
lingxin_mutex_unlock(lingxin_ut->core_node_mutex);
}

View File

@@ -0,0 +1,393 @@
#include "schedule_first_ws.h"
#include "cJSON.h"
#include "lingxin_common.h"
#include "lingxin_json_util.h"
#include "lingxin_hook_websocket.h"
#include "chat_state_machine.h"
#include "lingxin_timer.h"
#include "lingxin_log.h"
#include "lingxin_memory.h"
#include "lingxin_mutex.h"
#ifdef LINGXI_USE_VOICE_QUEUE
#include "lingxin_event_queue.h"
#include "lingxin_voice_queue.h"
#endif // LINGXI_USE_VOICE_QUEUE
struct ScheduleChatHandler
{
ScheduleChatConfig *config;
WebsocketClient *websocket;
SystemEventListener listener;
ScheduleWsExtraInfo *extraInfo;
ScheduleWsState *state;
lingxin_mutex_t state_mutex; // 保护所有状态变量
};
static void dealEventFromServer(ScheduleChatHandler *handler, const char *event,
cJSON *message)
{
lingxin_log_debug("dealEventFromServer: %s", event);
if (!event)
{
lingxin_log_debug("没有event");
return;
}
// 等待打断期间只接收task_terminated和error
if (strcmp(event, "error") == 0)
{
char *errorInfo = parseErrorInfo(message);
lingxin_log_debug("error: %s", errorInfo);
cJSON_free(errorInfo);
}
else if (strcmp(event, "system_event") == 0)
{
lingxin_mutex_lock(handler->state_mutex);
int timer_id = handler->state->sysEventTimerId;
handler->state->sysEventTimerId = INVALID_TIMER_ID;
handler->state->isRecievedSystemEvent = true;
bool wsConnected = handler->state->isWsConnectSuccess;
bool alreadyDestroying = handler->state->isDestroying;
bool shouldDestroy = wsConnected && !alreadyDestroying;
if (shouldDestroy) {
handler->state->isDestroying = true;
}
lingxin_mutex_unlock(handler->state_mutex);
if(timer_id != INVALID_TIMER_ID) {
lingxin_log_debug("delete system_event timeout timer %d", timer_id);
lingxin_one_shot_timer_delete(timer_id);
}
const char *payload = parsePayloadStr(message);
lingxin_log_ut_with_args(LINGXIN_DEBUG, "schedule_manager_recv_system_event", "payload: %s", payload);
handler->listener(handler, payload);
if (shouldDestroy)
{
scheduleWsDestroy(handler);
}
cJSON_free((char *)payload);
}
}
static void onEventMessageReceived(ScheduleChatHandler *handler,
const char *message)
{
lingxin_log_debug(" onEventMessageReceived: %s", message);
cJSON *jsonMessage = cJSON_Parse(message);
if (!jsonMessage)
{
const char *error_ptr = cJSON_GetErrorPtr();
if (error_ptr)
{
lingxin_log_error(" onEventMessageReceived: Error json: %s", message);
}
return;
}
// 解析 event
const char *eventStr = parseEvent(jsonMessage);
dealEventFromServer(handler, eventStr, jsonMessage);
cJSON_Delete(jsonMessage);
}
static void freeScheduleWs(ScheduleChatHandler **handlerAddress)
{
if (!handlerAddress)
{
lingxin_log_error("freeScheduleWs handlerAddress null");
return;
}
ScheduleChatHandler *handler = *handlerAddress;
if (!handler)
{
lingxin_log_error("freeScheduleWs handler null");
return;
}
lingxin_log_debug(" freeScheduleWs begin");
if (handler->state_mutex && handler->state) {
lingxin_mutex_lock(handler->state_mutex);
int timer_id = handler->state->sysEventTimerId;
handler->state->sysEventTimerId = INVALID_TIMER_ID;
lingxin_mutex_unlock(handler->state_mutex);
if (timer_id != INVALID_TIMER_ID) {
lingxin_log_debug("Cleaning up timeout timer in freeScheduleWs");
lingxin_one_shot_timer_delete(timer_id);
}
}
if (handler->websocket && handler->websocket->config)
{
lingxin_free(handler->websocket->config);
}
if (handler->extraInfo)
{
if (handler->extraInfo->instanceId)
{
lingxin_free(handler->extraInfo->instanceId);
handler->extraInfo->instanceId = NULL;
}
lingxin_free(handler->extraInfo);
handler->extraInfo = NULL;
}
if (handler->state)
{
lingxin_free(handler->state);
handler->state = NULL;
}
if (handler->state_mutex)
{
lingxin_mutex_destroy(handler->state_mutex);
handler->state_mutex = NULL;
}
lingxin_free(handler);
*handlerAddress = NULL;
lingxin_log_debug("freeScheduleWs after");
}
static void sysEventTimeoutCallback(void *userData) {
ScheduleChatHandler *handler = (ScheduleChatHandler *)userData;
if (!handler)
{
lingxin_log_error("onWebSocketEvent handler null");
return;
}
lingxin_mutex_lock(handler->state_mutex);
bool wsConnected = handler->state->isWsConnectSuccess;
bool eventReceived = handler->state->isRecievedSystemEvent;
bool alreadyDestroying = handler->state->isDestroying;
bool shouldDestroy = wsConnected && !eventReceived && !alreadyDestroying;
if (shouldDestroy) {
handler->state->isDestroying = true;
}
handler->state->sysEventTimerId = INVALID_TIMER_ID;
lingxin_mutex_unlock(handler->state_mutex);
if(!wsConnected) {
lingxin_log_warn("ws is not connected when timeout");
return;
}
if(eventReceived) {
lingxin_log_debug("already received system event when timeout");
return;
}
if(alreadyDestroying) {
lingxin_log_debug("already destroying when timeout");
return;
}
lingxin_log_warn("Timeout waiting for system_event (10s), destroying websocket");
scheduleWsDestroy(handler);
}
static void onWebSocketEvent(WebSocketEventType event, const char *data,
const size_t len, const int isBinary,
void *userData)
{
ScheduleChatHandler *handler = (ScheduleChatHandler *)userData;
if (!handler)
{
lingxin_log_error("onWebSocketEvent handler null");
return;
}
switch (event)
{
case ON_WEBSOCKET_CONNECTION_SUCCESS:
lingxin_log_debug("ON_WEBSOCKET_CONNECTION_SUCCESS");
lingxin_mutex_lock(handler->state_mutex);
handler->state->isWsConnectSuccess = true;
bool eventReceived = handler->state->isRecievedSystemEvent;
bool alreadyDestroying = handler->state->isDestroying;
bool shouldDestroy = eventReceived && !alreadyDestroying;
if (shouldDestroy) {
handler->state->isDestroying = true;
}
lingxin_mutex_unlock(handler->state_mutex);
if (shouldDestroy)
{
lingxin_log_debug("system_event already received before connection success");
scheduleWsDestroy(handler);
}
else
{
// 创建定时器
int timer_id = lingxin_one_shot_timer_create(handler, sysEventTimeoutCallback, 10 * 1000);
lingxin_mutex_lock(handler->state_mutex);
handler->state->sysEventTimerId = timer_id;
lingxin_mutex_unlock(handler->state_mutex);
if (timer_id == INVALID_TIMER_ID) {
lingxin_log_error("Failed to create timeout timer");
} else {
lingxin_log_debug("Created timeout timer (10s) for system_event, timer_id=%d", timer_id);
}
}
break;
case ON_WEBSOCKET_DATA_RECEIVED:
if (isBinary)
{
lingxin_log_debug("服务端推送音频数据");
}
else
{
onEventMessageReceived(handler, data);
}
break;
case ON_WEBSOCKET_ERROR:
{
char *errorData = (char *)lingxin_malloc(len + 1);
if (!errorData)
{
lingxin_log_error(" Failed to allocate memory for error data");
return;
}
// 复制数据
memcpy(errorData, data, len);
// 添加字符串结束符
errorData[len] = '\0';
lingxin_log_debug("Websocket Error data: %s", errorData);
lingxin_free(errorData);
}
break;
case ON_WEBSOCKET_DESTROY:
lingxin_log_ut(LINGXIN_DEBUG, "schedule_ws_destroy_finished");
lingxin_log_debug("销毁初始化时用于定时任务同步的ws连接");
freeScheduleWs(&handler);
break;
default:
break;
}
}
void freeConfig(ScheduleChatConfig *config)
{
if (config)
{
lingxin_free(config);
config = NULL;
lingxin_log_debug("freeConfig finished");
}
}
char *startFirstScheduleConnect(ScheduleChatConfig *config,
SystemEventListener listener)
{
ScheduleChatHandler *handler = NULL;
WebsocketConfig *websocketConfig = NULL;
ScheduleWsExtraInfo *extraInfo = NULL;
lingxin_log_ut(LINGXIN_DEBUG, "schedule_manager_first_ws_begin");
if (!config || !config->sn || !config->appKey || !config->appId)
{
lingxin_log_ut_with_args(LINGXIN_ERROR, "schedule_manager_first_ws_failed", "config params error!");
goto create_fail;
}
handler =
(ScheduleChatHandler *)lingxin_calloc(1, sizeof(struct ScheduleChatHandler));
if (!handler)
{
lingxin_log_ut_with_args(LINGXIN_ERROR, "schedule_manager_first_ws_failed", "Failed to allocate memory for startFirstScheduleConnect handler!");
goto create_fail;
}
handler->state = (ScheduleWsState *)lingxin_calloc(1, sizeof(ScheduleWsState));
if (!handler->state)
{
lingxin_log_ut_with_args(LINGXIN_ERROR, "schedule_manager_first_ws_failed", "Failed to allocate state!");
goto create_fail;
}
handler->listener = listener;
handler->state->isRecievedSystemEvent = false;
handler->state->isWsConnectSuccess = false;
handler->state->isDestroying = false;
handler->state->sysEventTimerId = INVALID_TIMER_ID;
handler->config = config;
handler->state_mutex = lingxin_mutex_create();
if (!handler->state_mutex)
{
lingxin_log_ut_with_args(LINGXIN_ERROR, "schedule_manager_first_ws_failed", "Failed to create mutex!");
goto create_fail;
}
websocketConfig =
createWebsocketConfig(handler, config->sn, config->appKey, config->appId,
config->serverPath, onWebSocketEvent);
if (!websocketConfig)
{
lingxin_log_ut_with_args(LINGXIN_ERROR, "schedule_manager_first_ws_failed", "Failed to create WebsocketConfig!");
goto create_fail;
}
handler->websocket = hook_websocket_init(websocketConfig);
if (!handler->websocket)
{
lingxin_log_ut_with_args(LINGXIN_ERROR, "schedule_manager_first_ws_failed", "Failed to init websocket!");
goto create_fail;
}
handler->extraInfo = NULL;
extraInfo = (ScheduleWsExtraInfo *)lingxin_calloc(1, sizeof(ScheduleWsExtraInfo));
if (!extraInfo)
{
lingxin_log_ut_with_args(LINGXIN_ERROR, "schedule_manager_first_ws_failed", "Failed to allocate extraInfo");
goto create_fail;
}
extraInfo->taskId = (char *)config->taskId;
extraInfo->requestId = "";
extraInfo->instanceId = generateUUID(16);
handler->extraInfo = extraInfo;
if(hook_websocket_start(handler->websocket) == false) {
lingxin_log_ut_with_args(LINGXIN_ERROR, "schedule_manager_first_ws_failed", "websocket start fail");
goto create_fail;
}
lingxin_log_ut(LINGXIN_DEBUG, "schedule_manager_first_ws_finished");
return extraInfo->instanceId;
create_fail:
if(config)
{
freeConfig(config);
}
if (websocketConfig)
{
free_websocket_config(websocketConfig);
}
if (handler)
{
if(handler->state) {
lingxin_free(handler->state);
handler->state = NULL;
}
if(handler->state_mutex) {
lingxin_mutex_destroy(handler->state_mutex);
handler->state_mutex = NULL;
}
if(handler->extraInfo) {
if(handler->extraInfo->instanceId) {
lingxin_free(handler->extraInfo->instanceId);
handler->extraInfo->instanceId = NULL;
}
lingxin_free(handler->extraInfo);
handler->extraInfo = NULL;
}
lingxin_free(handler);
handler = NULL;
}
return NULL;
}
void scheduleWsDestroy(ScheduleChatHandler *handler)
{
lingxin_log_ut(LINGXIN_DEBUG, "schedule_ws_destroy_begin");
if (!handler)
{
lingxin_log_ut_with_args(LINGXIN_ERROR, "schedule_ws_destroy_failed", "handler is null");
return;
}
hook_websocket_close(handler->websocket);
lingxin_log_debug("scheduleWsDestroy after");
}

View File

@@ -0,0 +1,623 @@
#include "chat_state_machine.h"
#include "lingxin_timer.h"
#include "schedule_ws_manager.h"
#include "schedule_timer_manager.h"
#include "lingxin_log.h"
#include "lingxin_mutex.h"
#include "lingxin_semaphore.h"
#include "lingxin_thread.h"
#include "lingxin_json_util.h"
#include <stdlib.h>
#include <string.h>
#include "lingxin_chat_api_inner.h"
#include "lingxin_memory.h"
typedef struct
{
char *taskId; // 任务唯一ID
int timerId; // 定时器ID由系统分配
long countdown; // 倒计时时间(单位:毫秒或秒)
} TimerTask;
#define MAX_TIMER_TASKS 20
TimerTask timerTasks[MAX_TIMER_TASKS]; // 存储所有定时任务
int taskCount = 0; // 当前任务数量
static bool is_task_emit_error = false; // 定时任务是否触发失败,是否接收到error消息
static int is_schedule_task_on = 0; // 定时任务是否开启
// 适配层代码包装
static void inner_delete_schedule_timer(int timerId);
// 更新任务队列相关
#define MAX_TASK_QUEUE 5
static ScheduleTaskList *task_queue[MAX_TASK_QUEUE];
static int queue_head = 0;
static int queue_tail = 0;
static int queue_size = 0;
static lingxin_mutex_t queue_mutex = NULL;
// 定时器更新线程
static lingxin_semaphore_t timer_update_sem = NULL;
static lingxin_tid_t timer_update_thread_id = 0;
static void *timer_update_thread_entry(void *arg);
static void init_timer_update_thread();
static ScheduleTaskList *dequeue_task();
static bool enqueue_task(ScheduleTaskList *parseResult);
static bool set_timer(TimerTask *timerTask, int advance_connect_time);
static void updateTimerTaskList(TaskItem sync_task_list[], int task_num, int advance_connect_time);
void initTimerTaskList(char *scheduleStr);
static void clearTimerTask();
static void free_parse_result(ScheduleTaskList *parseResult);
// 定时器触发线程
static lingxin_semaphore_t timer_trigger_sem = NULL;
static lingxin_tid_t timer_trigger_thread_id = 0;
static bool is_timer_trigger_ready = true;
static void *timer_trigger_thread_entry();
static void init_timer_trigger_thread();
static void timer_callback(void *priv);
static void timer_trigger_func(void *timer_task);
#define MAX_TRIGGER_QUEUE_SIZE 10
typedef struct
{
TimerTask *timer_tasks; // 存储定时器任务的数组
int task_count; // 任务计数可能与size重复
int head; // 队列头部索引
int tail; // 队列尾部索引
int size; // 当前队列中的元素数量
int capacity; // 队列最大容量
lingxin_mutex_t mutex; // 用于保护队列的互斥锁
} TimerTriggerQueue;
// 定时器触发队列
static TimerTriggerQueue timer_trigger_queue = {0};
static bool init_timer_trigger_queue();
static bool is_timer_task_exists(int timerId, const char *taskId);
static bool enqueue_timer_trigger(TimerTask *timer_task);
static TimerTask *dequeue_timer_trigger();
static void free_timer_trigger_task(TimerTask *task);
static bool is_update_thread_initialized = false;
static bool is_trigger_thread_initialized = false;
static bool is_trigger_queue_initialized = false;
/********************定时任务模块对外暴露时机 ********************/
void novoice_user_custom_listener(StateEvent event)
{
switch (event)
{
case State_Event_NoVoice_TerminateEnd:
{
lingxin_log_ut(LINGXIN_DEBUG, "schedule_task_before_trigger");
// 用户在开启NoVoice循环前注入的自定义action(关闭tts/asr等)
lingxin_emit_chat_event(CHAT_LIFE_CYCLE_EVENT_SCHEDULE_EMIT, NULL);
lingxin_log_ut(LINGXIN_DEBUG, "schedule_task_before_trigger_finish");
break;
}
default:
{
break;
}
}
}
void recieve_schedule_task_error()
{
lingxin_log_ut(LINGXIN_DEBUG, "schedule_task_recv_trigger_error");
is_task_emit_error = true;
}
/********************定时任务模块初始化 ********************/
void module_schedule_init()
{
is_schedule_task_on = 1;
init_timer_update_thread();
init_timer_trigger_thread();
initScheduleChat();
}
void initTimerTaskList(char *scheduleStr)
{
lingxin_log_ut(LINGXIN_DEBUG, "schedule_list_update_begin");
// 0. 同步定时任务须满足的配置
if (!is_schedule_task_on)
{
// 0-1. 端侧定时任务是否开启
lingxin_log_ut_with_args(LINGXIN_ERROR, "schedule_list_update_failed", "schedule task not open");
return;
}
if (!is_update_thread_initialized || !is_trigger_thread_initialized)
{
// 0-2. 定时任务相关线程是否初始化完成
lingxin_log_ut_with_args(LINGXIN_ERROR, "schedule_list_update_failed", "thread init not ready");
return;
}
if (!is_trigger_queue_initialized)
{
// 0-3. 定时任务相关线程是否初始化完成
lingxin_log_ut_with_args(LINGXIN_ERROR, "schedule_list_update_failed", "trigger queue init not ready");
return;
}
// 1. 控制在接收到error后的system_event之后才向状态机发送事件拉起voice循环
if (is_task_emit_error)
{
state_machine_run_event(State_Event_NoVoice_Error);
is_task_emit_error = false;
}
lingxin_log_ut(LINGXIN_DEBUG, "schedule_list_parse_begin");
// 2. 从scheduleStr中解析出定时任务列表
ScheduleTaskList *parseRes = (ScheduleTaskList *)lingxin_calloc(1, sizeof(ScheduleTaskList));
if (!parseRes)
{
lingxin_log_ut_with_args(LINGXIN_ERROR, "schedule_list_parse_failed", "malloc taskList failed");
return;
}
if (parseScheduleTaskList(scheduleStr, parseRes) == 0)
{
// 3-1. 解析成功,将任务添加到处理队列
if (enqueue_task(parseRes))
{
// 4-1. 入队成功,通知更新线程同步定时任务列表
// lingxin_semaphore_set_value(timer_update_sem, 0);
lingxin_semaphore_post(timer_update_sem); // 添加成功后通知处理线程
lingxin_log_debug("after timer_update_sem post");
}
else
{
// 4-2. 入队失败,释放内存
lingxin_log_error("添加任务到队列失败");
free_parse_result(parseRes);
}
}
else
{
// 3-2. 任务解析失败,释放内存
lingxin_free(parseRes);
parseRes = NULL;
}
lingxin_log_debug("initTimerTaskList finish");
}
/********************定时任务模块适配层方法包装 ********************/
static void inner_delete_schedule_timer(int timerId)
{
lingxin_log_ut(LINGXIN_DEBUG, "schedule_task_delete_begin");
// 删除定时器
if (lingxin_one_shot_timer_delete(timerId) == 0)
{
lingxin_log_ut(LINGXIN_DEBUG, "schedule_task_delete_finished");
}
else
{
lingxin_log_ut(LINGXIN_ERROR, "schedule_task_delete_failed");
}
}
/******************定时器更新线程 ********************/
static void init_timer_update_thread() // 定时器更新线程初始化
{
// 初始化同步对象
if (queue_mutex == NULL)
{
queue_mutex = lingxin_mutex_create();
}
if (timer_update_sem == NULL)
{
timer_update_sem = lingxin_semaphore_create(0);
}
// 创建处理线程
lingxin_thread_param_t thread_param = {
.priority = 16,
.stack_size = 2048 * 2,
.name = "task_update",
};
int ret = lingxin_thread_create(&timer_update_thread_id, &thread_param, timer_update_thread_entry, NULL);
if (ret == 0)
{
lingxin_log_debug("任务处理线程创建成功PID: %d", timer_update_thread_id);
is_update_thread_initialized = true; // 标记线程初始化完成
}
else
{
lingxin_log_error("任务处理线程创建失败,错误码: %d", ret);
timer_update_thread_id = 0;
is_update_thread_initialized = false;
}
}
static void free_parse_result(ScheduleTaskList *parseResult)
{
if (!parseResult)
{
return;
}
if (parseResult->tasks)
{
for (int i = 0; i < parseResult->taskCount; i++)
{
if (parseResult->tasks[i].taskId)
{
lingxin_free(parseResult->tasks[i].taskId);
parseResult->tasks[i].taskId = NULL;
}
}
lingxin_free(parseResult->tasks);
parseResult->tasks = NULL;
}
lingxin_free(parseResult);
parseResult = NULL;
}
static void *timer_update_thread_entry(void *arg) // 定时器更新线程入口
{
lingxin_log_debug("定时器设置线程启动");
while (1)
{
lingxin_log_debug("定时器设置线程 before pend");
lingxin_semaphore_pend(timer_update_sem, 0);
lingxin_log_debug("定时器设置线程 after pend");
ScheduleTaskList *parseResult = dequeue_task();
if (parseResult)
{
updateTimerTaskList(parseResult->tasks, parseResult->taskCount, parseResult->advanceConnectTime);
free_parse_result(parseResult);
lingxin_log_debug("定时任务线程单次设置完成");
}
lingxin_log_ut(LINGXIN_DEBUG, "schedule_list_update_finished");
}
lingxin_log_debug("定时器设置线程退出");
return NULL;
}
static ScheduleTaskList *dequeue_task() // 定时任务更新队列出队操作,从队列头部取出任务
{
lingxin_log_debug("dequeue_task start");
ScheduleTaskList *parseRes = NULL;
lingxin_mutex_lock(queue_mutex);
if (queue_size > 0)
{
parseRes = task_queue[queue_head];
queue_head = (queue_head + 1) % MAX_TASK_QUEUE;
queue_size--;
}
lingxin_mutex_unlock(queue_mutex);
lingxin_log_debug("dequeue_task finish");
return parseRes;
}
static bool enqueue_task(ScheduleTaskList *parseResult) // 定时任务更新队列入队操作,添加任务到队列尾部
{
lingxin_log_debug("enqueue_task start");
bool result = false;
if (!parseResult)
{
return false;
}
lingxin_mutex_lock(queue_mutex);
// 如果队列已满,移除最旧的任务并释放其内存
if (queue_size >= MAX_TASK_QUEUE)
{
lingxin_log_debug("full task queue, remove latest task");
free_parse_result(parseResult);
lingxin_mutex_unlock(queue_mutex);
return false;
}
// 添加新任务
task_queue[queue_tail] = parseResult;
queue_tail = (queue_tail + 1) % MAX_TASK_QUEUE;
queue_size++;
result = true;
lingxin_mutex_unlock(queue_mutex);
lingxin_log_debug("enqueue task success, queue size: %d, queue head: %d, queue tail: %d", queue_size, queue_head, queue_tail);
return result;
}
/********************定时器设置逻辑 ********************/
static void clearTimerTask() // 清空当前维护的计时器数组
{
lingxin_log_ut(LINGXIN_DEBUG, "schedule_list_clear_begin");
int cleared_count = 0;
for (int i = 0; i < taskCount && i < MAX_TIMER_TASKS; i++)
{
if (timerTasks[i].timerId != INVALID_TIMER_ID)
{
inner_delete_schedule_timer(timerTasks[i].timerId);
}
// ✅ 释放 taskId 内存
if (timerTasks[i].taskId)
{
lingxin_free(timerTasks[i].taskId);
timerTasks[i].taskId = NULL;
}
// 重置结构体内容
timerTasks[i].timerId = INVALID_TIMER_ID;
timerTasks[i].countdown = 0;
cleared_count++;
}
taskCount = 0;
lingxin_log_ut_with_args(LINGXIN_DEBUG, "schedule_list_clear_finished", "cleared %d tasks, current task count is %d",
cleared_count, taskCount);
}
static bool set_timer(TimerTask *timerTask, int advance_connect_time)
{
lingxin_log_ut(LINGXIN_DEBUG, "schedule_task_set_begin");
timerTask->timerId = lingxin_one_shot_timer_create(timerTask, timer_callback, timerTask->countdown * 1000);
if (timerTask->timerId == INVALID_TIMER_ID)
{
lingxin_log_ut_with_args(LINGXIN_ERROR, "schedule_task_set_failed", "invalid timerId");
// 如果 taskId 分配了内存,但注册失败,应立即释放
if (timerTask->taskId)
{
lingxin_free(timerTask->taskId);
timerTask->taskId = NULL;
}
return false;
}
taskCount++;
lingxin_log_ut_with_args(LINGXIN_DEBUG, "schedule_task_set_success", "taskId is %s, timerId is %d, finished task count is %d", timerTask->taskId, timerTask->timerId, taskCount);
return true;
}
static void updateTimerTaskList(TaskItem sync_task_list[], int task_num, int advance_connect_time)
{
clearTimerTask();
lingxin_log_ut_with_args(LINGXIN_DEBUG, "schedule_list_sync_begin", "current parsed task total count is %d, advanced time is %d", task_num, advance_connect_time);
// 根据传入的任务列表重新设置定时器
for (int i = 0; i < task_num && i < MAX_TIMER_TASKS; i++)
{
lingxin_log_debug("定时任务列表添加任务 %d,任务id为%s倒计时为%d", i, sync_task_list[i].taskId, sync_task_list[i].countdown);
TaskItem *task = &sync_task_list[i];
TimerTask *timer = &timerTasks[i];
timer->taskId = task->taskId ? lingxin_strdup(task->taskId) : NULL;
int countdownSec = task->countdown - advance_connect_time;
timer->countdown = (countdownSec > 0) ? (long)countdownSec : 0;
}
// 基本属性设置完后再设置定时器
for (int i = 0; i < task_num && i < MAX_TIMER_TASKS; i++)
{
TimerTask *timer = &timerTasks[i];
set_timer(timer, advance_connect_time); // 设置软件定时器
}
lingxin_log_ut(LINGXIN_DEBUG, "schedule_list_sync_finished");
}
/********************定时器触发线程 ********************/
static void *timer_trigger_thread_entry()
{
while (1)
{
lingxin_log_debug("timer_trigger_thread_entry start");
lingxin_semaphore_pend(timer_trigger_sem, 0);
lingxin_log_debug("timer_trigger_thread_entry after pend");
if (is_timer_trigger_ready)
{
is_timer_trigger_ready = false;
TimerTask *timer_task = dequeue_timer_trigger();
if (timer_task != NULL)
{
timer_trigger_func(timer_task);
free_timer_trigger_task(timer_task);
}
is_timer_trigger_ready = true;
}
lingxin_log_debug("timer_trigger_thread_entry finish");
}
return NULL;
}
static void init_timer_trigger_thread()
{
// 初始化定时器触发队列
if (init_timer_trigger_queue())
{
is_trigger_queue_initialized = true;
lingxin_log_debug("Timer threads initialized successfully");
}
else
{
lingxin_log_error("Failed to initialize timer trigger queue");
is_trigger_queue_initialized = false;
}
if (timer_trigger_sem == NULL)
{
timer_trigger_sem = lingxin_semaphore_create(0);
}
// 创建定时任务触发线程
lingxin_thread_param_t thread_param = {
.priority = 16,
.stack_size = 4096,
.name = "timer_trigger",
};
int ret = lingxin_thread_create(&timer_trigger_thread_id, &thread_param, timer_trigger_thread_entry, NULL);
if (ret == 0)
{ // 检查线程创建是否成功
lingxin_log_debug("线程timer_trigger创建成功PID: %d", timer_trigger_thread_id);
is_trigger_thread_initialized = true;
}
else
{
lingxin_log_error("Error: 线程timer_trigger创建失败错误码: %d", ret);
timer_trigger_thread_id = 0;
is_trigger_thread_initialized = false;
}
}
/********************定时器触发逻辑 ********************/
static void timer_callback(void *priv)
{
TimerTask *timer_task = (TimerTask *)priv;
if (enqueue_timer_trigger(timer_task))
{
lingxin_semaphore_post(timer_trigger_sem);
lingxin_log_debug("timer_trriger post semaphore");
}
else
{
lingxin_log_error("Failed to enqueue timer trigger task.");
}
}
static void timer_trigger_func(void *timer_task)
{
TimerTask *timer_task_info = (TimerTask *)timer_task;
lingxin_log_ut_with_args(LINGXIN_DEBUG, "schedule_task_callback_begin", "trigger timerId: %d, trigger taskId: %s, countdown: %d", timer_task_info->timerId, timer_task_info->taskId, timer_task_info->countdown);
if (!timer_task_info->taskId)
{
lingxin_log_ut_with_args(LINGXIN_ERROR, "schedule_task_callback_failed", "taskId is null");
lingxin_log_debug("当前定时任务id为空%d", timer_task_info->taskId);
return;
}
if (timer_task_info->timerId == INVALID_TIMER_ID)
{
lingxin_log_ut_with_args(LINGXIN_ERROR, "schedule_task_callback_failed", "timerId is invalid");
lingxin_log_debug("当前定时任务倒计时id为无效timerId\n");
return;
}
// 定时器触发后的处理逻辑
inner_delete_schedule_timer(timer_task_info->timerId);
lingxin_log_ut(LINGXIN_DEBUG, "schedule_task_trigger_success");
ScheduleTimerPayload schedule_trigger_payload = {0};
if (timer_task_info->taskId != NULL)
{
schedule_trigger_payload.schedule_task_id = lingxin_strdup(timer_task_info->taskId);
if (schedule_trigger_payload.schedule_task_id == NULL)
{
lingxin_log_error("scheduleTaskId内存分配失败");
}
}
schedule_trigger_payload.input_mode = "no_voice";
StateEventPayload payload = {
.schedule_timer_payload = &schedule_trigger_payload};
state_machine_run_event_with_payload(State_Event_NoVoice_Start, &payload);
}
// 定时器触发队列相关函数
static bool init_timer_trigger_queue()
{
if (timer_trigger_queue.timer_tasks != NULL)
{
return true; // 已经初始化
}
timer_trigger_queue.timer_tasks = (TimerTask *)lingxin_calloc(MAX_TRIGGER_QUEUE_SIZE, sizeof(TimerTask));
if (timer_trigger_queue.timer_tasks == NULL)
{
lingxin_log_error("Failed to allocate memory for timer trigger queue");
return false;
}
timer_trigger_queue.capacity = MAX_TRIGGER_QUEUE_SIZE;
timer_trigger_queue.task_count = 0;
timer_trigger_queue.head = 0;
timer_trigger_queue.tail = 0;
timer_trigger_queue.size = 0;
timer_trigger_queue.mutex = lingxin_mutex_create();
if (timer_trigger_queue.mutex == NULL)
{
lingxin_log_error("Failed to create mutex for timer trigger queue");
lingxin_free(timer_trigger_queue.timer_tasks);
timer_trigger_queue.timer_tasks = NULL;
return false;
}
return true;
}
static bool is_timer_task_exists(int timerId, const char *taskId)
{
for (int i = 0; i < timer_trigger_queue.size; i++)
{
int index = (timer_trigger_queue.head + i) % timer_trigger_queue.capacity;
if (timer_trigger_queue.timer_tasks[index].timerId == timerId)
{
return true;
}
// 检查 taskId 是否重复(两个都非空且相等)
if (taskId && timer_trigger_queue.timer_tasks[index].taskId &&
strcmp(taskId, timer_trigger_queue.timer_tasks[index].taskId) == 0)
{
return true;
}
}
return false;
}
static bool enqueue_timer_trigger(TimerTask *timer_task)
{
if (!timer_task || timer_task->timerId == INVALID_TIMER_ID || timer_task->taskId == NULL)
{
return false;
}
lingxin_mutex_lock(timer_trigger_queue.mutex);
// 检查是否已存在相同timerId或相同taskId的任务
if (is_timer_task_exists(timer_task->timerId, timer_task->taskId))
{
lingxin_log_debug("Timer task with timerId %d already exists in queue, skipping", timer_task->timerId);
lingxin_mutex_unlock(timer_trigger_queue.mutex);
return false;
}
// 如果队列已满,丢弃最新触发的任务
if (timer_trigger_queue.size >= timer_trigger_queue.capacity)
{
lingxin_log_debug("Timer trigger queue is full, discarding new task to keep queue unchanged");
free_timer_trigger_task(timer_task);// 释放新任务的内存
lingxin_mutex_unlock(timer_trigger_queue.mutex);
return false;
}
// 添加新任务到队尾
TimerTask *queue_task = &timer_trigger_queue.timer_tasks[timer_trigger_queue.tail];
queue_task->timerId = timer_task->timerId;
queue_task->countdown = timer_task->countdown;
if (timer_task->taskId)
{
queue_task->taskId = lingxin_strdup(timer_task->taskId);
}
else
{
queue_task->taskId = NULL;
}
timer_trigger_queue.tail = (timer_trigger_queue.tail + 1) % timer_trigger_queue.capacity;
timer_trigger_queue.size++;
lingxin_mutex_unlock(timer_trigger_queue.mutex);
lingxin_log_debug("Enqueued timer task, timerId: %d, taskId: %s, queue size: %d",
timer_task->timerId, timer_task->taskId ? timer_task->taskId : "NULL", timer_trigger_queue.size);
return true;
}
static TimerTask *dequeue_timer_trigger()
{
lingxin_mutex_lock(timer_trigger_queue.mutex);
if (timer_trigger_queue.size <= 0)
{
lingxin_mutex_unlock(timer_trigger_queue.mutex);
return NULL;
}
TimerTask *task = &timer_trigger_queue.timer_tasks[timer_trigger_queue.head];
timer_trigger_queue.head = (timer_trigger_queue.head + 1) % timer_trigger_queue.capacity;
timer_trigger_queue.size--;
lingxin_mutex_unlock(timer_trigger_queue.mutex);
lingxin_log_debug("dequeued timer task, timerId: %d, taskId: %s, queue size: %d",
task->timerId, task->taskId ? task->taskId : "NULL", timer_trigger_queue.size);
return task;
}
static void free_timer_trigger_task(TimerTask *task)
{
if (task && task->taskId)
{
lingxin_free(task->taskId);
task->taskId = NULL;
}
}

View File

@@ -0,0 +1,75 @@
#include "schedule_first_ws.h"
#include "schedule_ws_manager.h"
#include "chat_state_machine.h"
#include "lingxin_voice_chat_config.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "lingxin_common.h"
#include "lingxin_log.h"
#include "lingxin_memory.h"
extern char *generateUUID(int length);
static ScheduleChatConfig *scheduleConfig = NULL;
static bool isFirstExec = true;
static void getConfig()
{
if (scheduleConfig == NULL)
{
scheduleConfig = (ScheduleChatConfig *)lingxin_calloc(1, sizeof(ScheduleChatConfig));
}
char *appKey = lingxin_auth_license_get();
if (!appKey || strlen(appKey) == 0)
{
lingxin_log_error("license is null, please check lingxin_auth_license_get() implementation");
return;
}
char *sn = lingxin_auth_sn_get();
if (!sn || strlen(sn) == 0)
{
lingxin_log_error("sn is null, please check lingxin_auth_sn_get() implementation");
return;
}
char *appId = lingxin_auth_appId_get();
if (!appId || strlen(appId) == 0)
{
lingxin_log_error("appId is null, please check lingxin_auth_appId_get() implementation");
return;
}
char *agentCode = lingxin_auth_appCode_get();
if (!agentCode || strlen(agentCode) == 0)
{
lingxin_log_error("appCode is null, please check lingxin_auth_appCode_get() implementation");
return;
}
scheduleConfig->serverPath = WEBSOCKET_CHAT_PATH;
scheduleConfig->appKey = appKey;
scheduleConfig->sn = sn;
scheduleConfig->appId = appId;
scheduleConfig->showLog = true;
scheduleConfig->taskId = generateUUID(32);
}
static void systemEventListener(ScheduleChatHandler *globalHandler, const char *data)
{
lingxin_log_debug("-----SCHEDULECHAT_EVENT_ON_SYSTEM_EVENT-----%s", data);
state_machine_receive_schedule_data((void *)data);
}
static void doCreate()
{
getConfig();
startFirstScheduleConnect(scheduleConfig, systemEventListener);
}
void initScheduleChat()
{
if (isFirstExec)
{
isFirstExec = false;
doCreate();
}
}

View File

@@ -0,0 +1,535 @@
#include "tts.h"
#include "lingxin_common.h"
#include "lingxin_json_util.h"
#include "lingxin_hook_websocket.h"
#include "lingxin_log.h"
#include "lingxin_memory.h"
#ifdef LINGXI_USE_VOICE_QUEUE
#include "lingxin_event_queue.h"
#include "lingxin_voice_queue.h"
#endif // LINGXI_USE_VOICE_QUEUE
struct TTSHandler
{
const char *payload;
WebsocketClient *websocket;
TTSEventListener listener;
void *voiceQueue;
bool canRequestNextVoice;
bool isVoiceEnd;
void *notifyQueue;
TTSExtraInfo *extraInfo;
struct TTSHandler **selfPointer; // 存储双指针的引用
};
static char *getReqId(TTSHandler *handler)
{
return (handler && handler->extraInfo && handler->extraInfo->requestId)? handler->extraInfo->requestId : "";
}
static char *getTTSLogPre(TTSHandler *handler)
{
char *logInstanceId = NULL;
if (handler && handler->selfPointer && handler->extraInfo)
{
logInstanceId = handler->extraInfo->instanceId;
}
return (!logInstanceId || strlen(logInstanceId) == 0) ? "" : logInstanceId;
}
static void triggerCallbackOrEnqueue(TTSHandler *handler, TTSEventType eventType, const char *data, const size_t len)
{
if (!handler->listener)
{
lingxin_log_error("[%s], triggerCallback listener null", getTTSLogPre(handler));
return;
}
if (handler->notifyQueue)
{
#ifdef LINGXI_USE_VOICE_QUEUE
eventQueueEnqueue(handler->notifyQueue, eventType, data, len);
#endif // LINGXI_USE_VOICE_QUEUE
return;
}
handler->listener(eventType, data, len, handler->extraInfo);
}
#ifdef LINGXI_USE_VOICE_QUEUE
static char *getTaskId(TTSHandler *handler)
{
return handler->extraInfo ? handler->extraInfo->taskId : "";
}
static void onEventQueueCallback(void *userContext, int event, const char *data,
const size_t len)
{
if (!userContext)
{
lingxin_log_error("onEventQueueCallback params null");
return;
}
TTSHandler *handler = (TTSHandler *)userContext;
if (!handler)
{
lingxin_log_error("onEventQueueCallback handler null");
return;
}
if (!handler->listener)
{
return;
}
handler->listener(event, data, len, handler->extraInfo);
}
static bool requestNextAudioPackets(TTSHandler *handler, size_t space)
{
lingxin_log_debug("[%s], requestNextAudioPackets", getTTSLogPre(handler));
if (!handler || !handler->voiceQueue)
{
lingxin_log_error("[%s], requestNextAudioPackets params null", getTTSLogPre(handler));
return false;
}
char message[256];
snprintf(message, sizeof(message), "{\"header\":{\"action\":\"request_audio_packets\","
"\"task_id\":\"%s\"\"request_id\":\"%s\"},\"payload\":"
"{\"flow_control_parameters\":{\"data_size\":%zu}}}",
getTaskId(handler), getReqId(handler), space);
return hook_websocket_send_text(handler->websocket, message);
}
static void checkNextVoiceRequest(TTSHandler *handler, size_t space)
{
if (handler->canRequestNextVoice)
{
handler->canRequestNextVoice = !requestNextAudioPackets(handler, space);
}
}
static bool continueWaitCheck(void *userContext, size_t space)
{
TTSHandler *handler = (TTSHandler *)userContext;
if (handler->isVoiceEnd)
{
triggerCallbackOrEnqueue(handler, TTS_EVENT_ON_SEND_END, NULL, 0);
return false;
}
checkNextVoiceRequest(handler, space);
return true;
}
static void initFlowControl(TTSHandler *handler)
{
int poolSize = parsePoolSize(handler->payload);
if (!poolSize)
{
lingxin_log_error("[%s], initFlowControl poolSize null", getTTSLogPre(handler));
return;
}
handler->notifyQueue = eventQueueCreate(handler, onEventQueueCallback);
if (!handler->notifyQueue)
{
lingxin_log_error("[%s], handler->notifyQueue poolSize null", getTTSLogPre(handler));
return;
}
handler->voiceQueue = voiceQueueCreate(poolSize);
if (!handler->voiceQueue)
{
lingxin_log_error("[%s], initFlowControl voiceQueueCreate fail", getTTSLogPre(handler));
eventQueueDestroy(handler->notifyQueue);
}
lingxin_log_debug("[%s], initFlowControl voiceQueueCreate after", getTTSLogPre(handler));
}
#endif // LINGXI_USE_VOICE_QUEUE
// 服务端新代码不兼容按需拉取的流控策略,先注释
// bool ttsGetNextFlow(TTSHandler *handler)
// {
// #ifdef LINGXI_USE_VOICE_QUEUE
// lingxin_log_debug("[%s], ttsGetNextFlow begin", getTTSLogPre(handler));
// if (!handler)
// {
// lingxin_log_error("[%s], handler null", getTTSLogPre(handler));
// return false;
// }
// if (!handler->voiceQueue)
// {
// lingxin_log_error("[%s], ttsGetNextFlow params null", getTTSLogPre(handler));
// return false;
// }
// lingxin_log_debug("[%s], getNextFlow: calloc VoiceChunk", getTTSLogPre(handler));
// VoiceChunk *chunk = (VoiceChunk *)lingxin_calloc(1, sizeof(VoiceChunk));
// if (!chunk)
// {
// lingxin_log_error("[%s], getNextFlow: Failed to allocate memory for voice chunk", getTTSLogPre(handler));
// return false;
// }
// lingxin_log_debug("[%s], getNextFlow: begin, %d", getTTSLogPre(handler), handler->isVoiceEnd);
// bool result =
// voiceDequeue(handler->voiceQueue, chunk, continueWaitCheck, handler);
// lingxin_log_debug("[%s], getNextFlow: result length: %d", getTTSLogPre(handler), chunk->length);
// if (result)
// {
// triggerCallbackOrEnqueue(handler, TTS_EVENT_ON_SEND_RESULT, chunk->data,
// chunk->length);
// if (isVoiceQueueSpaceEnough(handler->voiceQueue))
// {
// checkNextVoiceRequest(handler,
// getRemainSpaceOfVoiceQueue(handler->voiceQueue));
// }
// return true;
// }
// lingxin_log_debug("[%s], getNextFlow: finish", getTTSLogPre(handler));
// #endif // LINGXI_USE_VOICE_QUEUE
// return false;
// }
static void dealEventFromServer(TTSHandler *handler, const char *event,
cJSON *message)
{
lingxin_log_debug("[%s], dealEventFromServer: %s", getTTSLogPre(handler), event);
if (!event)
{
return;
}
if (strcmp(event, "task_started") == 0)
{
if (handler->extraInfo)
{
char *reqId = parseRequestId(message);
if (!handler->extraInfo->requestId || strlen(handler->extraInfo->requestId) == 0)
{
handler->extraInfo->requestId = lingxin_strdup(reqId);
}
else if (strlen(reqId) == strlen(handler->extraInfo->requestId))
{
memcpy(handler->extraInfo->requestId, reqId, strlen(reqId));
}
else
{
char *old_requestId = handler->extraInfo->requestId;
handler->extraInfo->requestId = lingxin_strdup(reqId);
lingxin_free(old_requestId);
}
}
triggerCallbackOrEnqueue(handler, TTS_EVENT_ON_SEND_START, NULL, 0);
}
else if (strcmp(event, "audio_packets_responded") == 0)
{
if (handler->voiceQueue)
{
#ifdef LINGXI_USE_VOICE_QUEUE
handler->canRequestNextVoice = true;
#endif // LINGXI_USE_VOICE_QUEUE
}
}
else if (strcmp(event, "task_ended") == 0)
{
if (handler->voiceQueue)
{
#ifdef LINGXI_USE_VOICE_QUEUE
handler->isVoiceEnd = true;
handler->canRequestNextVoice = false;
#endif // LINGXI_USE_VOICE_QUEUE
}
else
{
triggerCallbackOrEnqueue(handler, TTS_EVENT_ON_SEND_END, NULL, 0);
}
}
else if (strcmp(event, "error") == 0)
{
//这里用到了cJSON_PrintUnformatted注意要释放
char *errorInfo = parseErrorInfo(message);
triggerCallbackOrEnqueue(handler, TTS_EVENT_ON_ERROR, errorInfo,
strlen(errorInfo));
cJSON_free(errorInfo);
}
}
static void onEventMessageReceived(TTSHandler *handler, const char *message)
{
cJSON *jsonMessage = cJSON_Parse(message);
if (!jsonMessage)
{
const char *error_ptr = cJSON_GetErrorPtr();
if (error_ptr)
{
lingxin_log_error("[%s], Error json: %s", getTTSLogPre(handler), message);
}
return;
}
// 解析 event
const char *eventStr = parseEvent(jsonMessage);
dealEventFromServer(handler, eventStr, jsonMessage);
// Free the JSON object
cJSON_Delete(jsonMessage);
}
static void freeTTS(TTSHandler **handlerAddress)
{
if (!handlerAddress)
{
lingxin_log_error("freeTTS handlerAddress null");
return;
}
TTSHandler *handler = *handlerAddress;
if (!handler)
{
lingxin_log_error("freeTTS handler null");
return;
}
lingxin_log_debug("[%s], freeTTS begin", getTTSLogPre(handler));
#ifdef LINGXI_USE_VOICE_QUEUE
eventQueueDestroy(handler->notifyQueue);
destroyVoiceQueue(handler->voiceQueue);
#endif // LINGXI_USE_VOICE_QUEUE
if (handler->websocket && handler->websocket->config)
{
free_websocket_config(handler->websocket->config);
handler->websocket->config = NULL;
}
if (handler->extraInfo)
{
if (handler->extraInfo->instanceId)
{
lingxin_free(handler->extraInfo->instanceId);
}
if (handler->extraInfo->requestId)
{
lingxin_free(handler->extraInfo->requestId);
}
lingxin_free(handler->extraInfo);
handler->extraInfo = NULL;
}
lingxin_free(handler);
*handlerAddress = NULL;
lingxin_log_debug("freeTTS after");
}
static void onWebSocketEvent(WebSocketEventType event, const char *data,
const size_t len, const int isBinary,
void *userData)
{
TTSHandler *handler = (TTSHandler *)userData;
if (!handler)
{
lingxin_log_error("onWebSocketEvent handler null");
return;
}
switch (event)
{
case ON_WEBSOCKET_CONNECTION_SUCCESS:
lingxin_log_debug("[%s], TTS CONNECTION_SUCCESS", getTTSLogPre(handler));
triggerCallbackOrEnqueue(handler, TTS_EVENT_ON_READY, NULL, 0);
break;
case ON_WEBSOCKET_DATA_RECEIVED:
lingxin_log_debug("[%s], TTS REVEIVE: %d, %d", getTTSLogPre(handler), len, isBinary);
if (isBinary)
{
if (handler->voiceQueue)
{
#ifdef LINGXI_USE_VOICE_QUEUE
bool result = voiceEnqueue(handler->voiceQueue, data, len);
lingxin_log_debug("[%s], onWebSocketEvent: enqueue result: %d", getTTSLogPre(handler), result);
#endif // LINGXI_USE_VOICE_QUEUE
}
else
{
triggerCallbackOrEnqueue(handler, TTS_EVENT_ON_SEND_RESULT, data, len);
}
}
else
{
onEventMessageReceived(handler, data);
}
break;
case ON_WEBSOCKET_ERROR:
triggerCallbackOrEnqueue(handler, TTS_EVENT_ON_ERROR, data, len);
break;
case ON_WEBSOCKET_DESTROY:
lingxin_log_debug("[%s], TTS_EVENT_ON_DESTROY", getTTSLogPre(handler));
triggerCallbackOrEnqueue(handler, TTS_EVENT_ON_DESTROY, data, len);
freeTTS(handler->selfPointer);
break;
default:
break;
}
}
char *ttsCreate(TTSHandler **handlerAddress, TTSConfig *config, const char *payload, TTSEventListener listener)
{
lingxin_log_debug("ttsCreate begin");
if (!config || !config->sn || !config->appKey || !config->appId)
{
lingxin_log_error("ttsCreate config params error!");
return NULL;
}
TTSHandler *handler = (TTSHandler *)lingxin_calloc(1, sizeof(struct TTSHandler));
if (!handler)
{
lingxin_log_error("Failed to allocate memory for TTS handler");
return NULL;
}
handler->listener = listener;
handler->canRequestNextVoice = false;
handler->isVoiceEnd = false;
handler->voiceQueue = NULL;
handler->notifyQueue = NULL;
WebsocketConfig *websocketConfig =
createWebsocketConfig(handler, config->sn, config->appKey, config->appId,
WEBSOCKET_TTS_PATH, onWebSocketEvent);
if (!websocketConfig)
{
lingxin_log_error("Failed to create WebsocketConfig");
lingxin_free(handler);
return NULL;
}
handler->websocket = hook_websocket_init(websocketConfig);
if (!handler->websocket)
{
lingxin_log_error("Failed to initialize WebSocket client");
free_websocket_config(websocketConfig);
lingxin_free(handler);
return NULL;
}
handler->payload = payload;
#ifdef LINGXI_USE_VOICE_QUEUE
initFlowControl(handler);
#endif // LINGXI_USE_VOICE_QUEUE
handler->extraInfo = NULL;
TTSExtraInfo *extraInfo = (TTSExtraInfo *)lingxin_calloc(1, sizeof(TTSExtraInfo));
if (extraInfo)
{
extraInfo->taskId = NULL;
extraInfo->requestId = NULL;
extraInfo->instanceId = generateUUID(16);
handler->extraInfo = extraInfo;
}
hook_websocket_start(handler->websocket);
handler->selfPointer = handlerAddress; // 设置 selfPointer
*handlerAddress = handler; // 返回 handler
lingxin_log_debug("[%s], ttsCreate finish", getTTSLogPre(handler));
return extraInfo ? extraInfo->instanceId : "";
}
bool ttsSendStart(TTSHandler *handler, const char *taskId)
{
lingxin_log_debug("[%s], ttsSendStart begin", getTTSLogPre(handler));
if (!handler)
{
lingxin_log_error("handler null");
return false;
}
if (!taskId || !handler->payload)
{
lingxin_log_error("[%s], ttsSendStart params null", getTTSLogPre(handler));
return false;
}
if (handler->extraInfo)
{
handler->extraInfo->taskId = (char *)taskId;
}
handler->isVoiceEnd = false;
handler->canRequestNextVoice = false;
char message[256];
snprintf(message, sizeof(message), "{\"header\":{\"action\":\"start_task\",\"task_id\":\"%s\"},\"payload\":%s}",taskId, handler->payload);
bool result = hook_websocket_send_text(handler->websocket, message);
lingxin_log_debug("[%s], ttsSendStart result: %s", getTTSLogPre(handler), result ? "true" : "false");
return result;
}
int ttsSend(TTSHandler *handler, const char *taskId, const char *text)
{
lingxin_log_debug("[%s], ttsSend begin", getTTSLogPre(handler));
if (!handler)
{
lingxin_log_error("[%s], handler null", getTTSLogPre(handler));
return 0;
}
if (!taskId || !text)
{
lingxin_log_error("[%s], ttsSend params null", getTTSLogPre(handler));
return 0;
}
if (handler->extraInfo)
{
handler->extraInfo->taskId = (char *)taskId;
}
int length = snprintf(NULL, 0, "{\"header\":{\"action\":\"send_text\",\"task_id\":\"%s\",\"request_id\":\"%s\"},\"payload\":{\"input\":{\"text\":\"%s\"}}}", taskId, getReqId(handler), text);
if (length <= 0)
{
lingxin_log_error("[%s], text length calc error", getTTSLogPre(handler));
return 0;
}
char *message = lingxin_malloc(length + 1);
snprintf(message, length + 1, "{\"header\":{\"action\":\"send_text\",\"task_id\":\"%s\",\"request_id\":\"%s\"},\"payload\":{\"input\":{\"text\":\"%s\"}}}", taskId, getReqId(handler), text);
int result = hook_websocket_send_text(handler->websocket, message);
lingxin_free(message); // 释放分配的内存
lingxin_log_debug("[%s], ttsSend result: %d", getTTSLogPre(handler), result);
return result;
}
bool ttsSendStop(TTSHandler *handler, const char *taskId)
{
lingxin_log_debug("[%s], ttsSendStop begin", getTTSLogPre(handler));
if (!handler)
{
lingxin_log_error("handler null");
return false;
}
if (!taskId)
{
lingxin_log_error("[%s], ttsSendStop params null", getTTSLogPre(handler));
return false;
}
if (handler->extraInfo)
{
handler->extraInfo->taskId = (char *)taskId;
}
char message[256];
snprintf(message, sizeof(message), "{\"header\":{\"action\":\"end_task\",\"task_id\":\"%s\",\"request_id\":\"%s\"},\"payload\":{}}",taskId, getReqId(handler));
bool result = hook_websocket_send_text(handler->websocket, message);
lingxin_log_debug("[%s], ttsSendStop result: %s", getTTSLogPre(handler), result ? "true" : "false");
return result;
}
void ttsDestroy(TTSHandler *handler)
{
lingxin_log_debug("[%s], ttsDestroy begin", getTTSLogPre(handler));
if (!handler || !handler->selfPointer || !*handler->selfPointer)
{
lingxin_log_error("handler or handler->selfPointer null");
return;
}
hook_websocket_close(handler->websocket);
lingxin_log_debug("ttsDestroy after");
}

View File

@@ -0,0 +1,56 @@
#include "mbedtls/base64.h"
#include "lingxin_base64_utils.h"
#include "lingxin_memory.h"
char *lingxin_base64_encode(const char *input, size_t input_len)
{
// 计算输出缓冲区大小Base64编码后的长度大约是原长度的4/3倍再加上填充和结束符
size_t output_size = (input_len + 2) / 3 * 4 + 1; // +1 for null terminator
unsigned char *output = (unsigned char *)lingxin_malloc(output_size);
if (!output)
{
return NULL;
}
size_t output_len;
int ret = mbedtls_base64_encode(output, output_size, &output_len, (const unsigned char *)input, input_len);
if (ret != 0)
{
lingxin_free(output);
return NULL;
}
// 确保字符串以null结尾
output[output_len] = '\0';
return (char *)output;
}
char *lingxin_base64_decode(const char *input, size_t input_len, size_t *output_len)
{
if (!input)
{
goto exit_decode;
}
// 计算输出缓冲区大小解码后的长度大约是Base64长度的3/4倍
size_t output_size = input_len / 4 * 3 + 1; // +1 for null terminator
unsigned char *output = (unsigned char *)lingxin_malloc(output_size);
if (!output)
{
goto exit_decode;
}
size_t actual_len;
int ret = mbedtls_base64_decode(output, output_size, &actual_len, (const unsigned char *)input, input_len);
if (ret != 0)
{
lingxin_free(output);
goto exit_decode;
}
// 确保字符串以null结尾
output[actual_len] = '\0';
if (output_len)
*output_len = actual_len;
return (char *)output;
exit_decode:
if (output_len)
*output_len = 0;
return NULL;
}

View File

@@ -0,0 +1,106 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "lingxin_cbuffer.h"
#include "lingxin_log.h"
#include "lingxin_memory.h"
// 初始化循环缓冲区
LingxinCircularBuffer* lingxin_cbuffer_init(int size, size_t item_size) {
LingxinCircularBuffer* cb = lingxin_malloc(sizeof(LingxinCircularBuffer));
if (cb == NULL) {
lingxin_log_error("Error: cbuffer结构体内存malloc失败");
return NULL;
}
cb->buffer = lingxin_malloc(size * item_size);
if (cb->buffer == NULL) {
lingxin_log_error("Error: cbuffer->buffer内存malloc失败");
lingxin_free(cb);
return NULL;
}
cb->max = size;
cb->head = 0;
cb->tail = 0;
cb->full = 0;
cb->item_size = item_size;
return cb;
}
// 销毁循环缓冲区
void lingxin_cbuffer_free(LingxinCircularBuffer *cb) {
if (cb == NULL) {
lingxin_log_warn("Warning: cbuffer为NULL无需销毁");
return;
}
if (cb->buffer != NULL) {
lingxin_free(cb->buffer);
}
lingxin_free(cb);
}
// 向循环缓冲区添加一个元素
void lingxin_cbuffer_put(LingxinCircularBuffer *cb, const void *item) {
memcpy((char*)cb->buffer + cb->head * cb->item_size, item, cb->item_size);
if (cb->full) {
cb->tail = (cb->tail + 1) % cb->max;
}
cb->head = (cb->head + 1) % cb->max;
cb->full = (cb->head == cb->tail);
}
// 从循环缓冲区读取一个元素
int lingxin_cbuffer_get(LingxinCircularBuffer *cb, void *item) {
int success = 0;
if (!cb->full && cb->head == cb->tail) {
success = -1; // 缓冲区为空
} else {
memcpy(item, (char*)cb->buffer + cb->tail * cb->item_size, cb->item_size);
cb->full = 0;
cb->tail = (cb->tail + 1) % cb->max;
success = 0;
}
return success;
}
// 检查缓冲区是否为空
int lingxin_cbuffer_empty(LingxinCircularBuffer *cb) {
int empty = (!cb->full && (cb->head == cb->tail));
return empty;
}
// 检查缓冲区是否已满
int lingxin_cbuffer_full(LingxinCircularBuffer *cb) {
int full = cb->full;
return full;
}
// 获取缓冲区中的元素数量
int lingxin_cbuffer_size(LingxinCircularBuffer *cb) {
int size = cb->max;
if (!cb->full) {
if (cb->head >= cb->tail) {
size = cb->head - cb->tail;
} else {
size = cb->max + cb->head - cb->tail;
}
}
return size;
}
// 重置循环缓冲区
int lingxin_cbuffer_reset(LingxinCircularBuffer *cb) {
if (cb == NULL) {
lingxin_log_error("Warning: cbuffer为NULL无法重置");
return -1;
}
cb->head = 0;
cb->tail = 0;
cb->full = 0;
return 0;
}

View File

@@ -0,0 +1,279 @@
#include "lingxin_common.h"
#include "lingxin_hook_websocket.h"
#include "lingxin_tls_utils.h"
#include <stdarg.h>
#include "lingxin_log.h"
#include "lingxin_system_time.h"
#include "lingxin_memory.h"
#ifdef __ANDROID__
#include <android/log.h>
#include <jni.h>
#endif
char *generateUUID(int length)
{
const char charset[] =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
int charset_len = sizeof(charset) - 1;
char *uuid = (char *)lingxin_malloc(length + 1);
// 初始化随机数生成器
srand((unsigned int)lingxin_get_timestamp_s());
for (int i = 0; i < length; ++i)
{
uuid[i] = charset[rand() % charset_len];
}
uuid[length] = '\0'; // 确保字符串以 null 结尾
return uuid;
}
static void long_long_timestamp_to_string(long long n, char *buf)
{
// 时间戳总是正数,且在合理范围内
if (n == 0)
{
strcpy(buf, "0");
return;
}
int i = 0;
char temp[16]; // 时间戳最多13位16字节足够
// 提取各位数字
while (n)
{
temp[i] = '0' + (n % 10);
i++;
n /= 10;
}
temp[i] = '\0';
// 反转字符串
int len = i;
for (int j = 0; j < len; j++)
{
buf[j] = temp[len - 1 - j];
}
buf[len] = '\0';
}
static char *generateTimestampMS()
{
char *timestamp_str = (char *)lingxin_calloc(1, 14);
// 这里1000后面的LL不能少否则可能会类型溢出还有一种写法(long long)lingxin_get_timestamp_s() * 1000
long_long_timestamp_to_string(lingxin_get_timestamp_s() * 1000LL, timestamp_str);
return timestamp_str;
}
WebsocketConfig *createWebsocketConfig(void *handler, const char *sn, const char *appKey, const char *appId, const char *path, WebSocketEventListener listener)
{
WebsocketConfig *config = (WebsocketConfig *)lingxin_malloc(sizeof(WebsocketConfig));
char *timestamp = generateTimestampMS();
config->header_signature = generateSignature(sn, appKey, appId, timestamp);
config->header_sn = lingxin_strdup(sn);
config->header_app_id = lingxin_strdup(appId);
config->header_timestamp = timestamp;
config->protocol = PROTOCOL_WEBSOCKET;
config->host = REQUEST_URL;
config->path = path;
config->port = REQUEST_PORT;
config->listener = listener;
config->userContext = handler;
return config;
}
void free_websocket_config(WebsocketConfig *config)
{
if (!config)
{
return;
}
if (config->header_signature)
{
lingxin_free(config->header_signature);
config->header_signature = NULL;
}
if (config->header_sn)
{
lingxin_free(config->header_sn);
config->header_sn = NULL;
}
if (config->header_app_id)
{
lingxin_free(config->header_app_id);
config->header_app_id = NULL;
}
if (config->header_timestamp)
{
lingxin_free(config->header_timestamp);
config->header_timestamp = NULL;
}
lingxin_free(config);
config = NULL;
}
HttpConfig *createHttpConfig(const char *appId, const char *sn, const char *appKey, const char *host, const char *path, const char *reqBody)
{
HttpConfig *config = (HttpConfig *)lingxin_calloc(1, sizeof(HttpConfig));
if (!config)
{
lingxin_log_error("Failed to allocate memory for HttpConfig");
return NULL;
}
config->headers = (HttpHeader *)lingxin_malloc(sizeof(HttpHeader));
if (!config->headers)
{
lingxin_log_error("Failed to allocate memory for HttpHeader");
lingxin_free(config);
return NULL;
}
char *timestamp = generateTimestampMS();
config->headers->signature = generateSignature(sn, appKey, appId, timestamp);
config->headers->timestamp = timestamp;
config->headers->app_id = lingxin_strdup(appId);
config->headers->sn = lingxin_strdup(sn);
config->host = host;
config->path = path;
config->port = REQUEST_PORT;
config->protocol = PROTOCOL_HTTP;
config->post_data = lingxin_strdup((reqBody && strlen(reqBody)) != 0 ? reqBody : "{}");
return config;
}
void free_http_config(HttpConfig *config)
{
if (!config)
{
return;
}
if (config->headers)
{
if (config->headers->signature)
{
lingxin_free(config->headers->signature);
config->headers->signature = NULL;
}
if (config->headers->app_id)
{
lingxin_free(config->headers->app_id);
config->headers->app_id = NULL;
}
if (config->headers->sn)
{
lingxin_free(config->headers->sn);
config->headers->sn = NULL;
}
if(config->headers->timestamp) {
lingxin_free(config->headers->timestamp);
config->headers->timestamp = NULL;
}
lingxin_free(config->headers);
config->headers = NULL;
}
if (config->post_data)
{
lingxin_free(config->post_data);
config->post_data = NULL;
}
lingxin_free(config);
config = NULL;
}
void parse_host_path_from_url(const char *url, char **host, char **path)
{
if (!url || !host || !path)
{
return;
}
*host = NULL;
*path = NULL;
const char *url_ptr = url;
// 跳过schema部分如果存在
const char *schema_end = strstr(url_ptr, "://");
if (schema_end)
{
url_ptr = schema_end + 3;
}
// 查找路径开始位置
const char *path_start = strchr(url_ptr, '/');
if (path_start)
{
// 提取host部分
int host_len = path_start - url_ptr;
*host = lingxin_malloc(host_len + 1);
if (*host)
{
strncpy(*host, url_ptr, host_len);
(*host)[host_len] = '\0';
}
// 提取path部分
*path = lingxin_strdup(path_start);
}
else
{
// 只有host没有路径
*host = lingxin_strdup(url_ptr);
*path = lingxin_strdup("/");
}
}
static void callback_http_post(void *contents, size_t size, void *userp)
{
char **response = (char **)userp;
// 如果response已经分配过内存先释放
if (*response)
{
lingxin_free(*response);
*response = NULL;
}
*response = (char *)lingxin_malloc(size + 1);
if (!*response)
{
lingxin_log_error("respose malloc failed");
return;
}
// 拷贝新数据到响应缓冲区
memcpy(*response, contents, size);
(*response)[size] = '\0'; // 添加字符串结束符
}
bool http_post_without_callback(HttpConfig *config, char **response)
{
if (!config)
{
lingxin_log_error("config null");
return false;
}
return http_post(config, callback_http_post, response) != 0;
}
void parse_file_name_from_path(char file_name[64], const char *file_path)
{
if (!file_name || !file_path)
{
return;
}
// 提取文件名部分
const char *temp = strrchr(file_path, '/');
if (!temp)
temp = strrchr(file_path, '\\');
temp = temp ? temp + 1 : file_path;
// 去掉扩展名
const char *dot = strrchr(temp, '.');
int len = dot ? (dot - temp) : strlen(temp);
if (len >= 64)
{
len = 63;
}
strncpy(file_name, temp, len);
file_name[len] = '\0';
}

View File

@@ -0,0 +1,173 @@
#ifdef LINGXI_USE_VOICE_QUEUE
#include <stdbool.h>
#include <stddef.h>
#include "lingxin_event_queue.h"
#include "lingxin_log.h"
int EVENT_QUEUE_FINISH_FLAG = 10001;
static void *eventNotifyThread(void *arg)
{
EventQueue *queue = (EventQueue *)arg;
if (!queue)
{
return NULL;
}
// lingxin_log_debug("eventNotifyThread before");
while (!queue->isDestroyed)
{
// lingxin_log_debug("eventNotifyThread");
EventChunk *event = eventQueueDequeue(queue);
if (event && queue->callback)
{
if (queue && queue->callback)
{
queue->callback(queue->userContext, event->eventType, event->data,
event->dataSize);
}
lingxin_free(event); // 释放 eventChunk 内存
lingxin_log_debug("eventNotifyThread callback finish");
}
}
// lingxin_log_debug("eventNotifyThread after");
return NULL;
}
// 初始化队列
EventQueue *eventQueueCreate(void *userContext, EventQueueCallback callback)
{
EventQueue *queue = (EventQueue *)lingxin_calloc(1, sizeof(EventQueue));
if (!queue)
{
return NULL;
}
queue->front = NULL;
queue->rear = NULL;
queue->isDestroyed = false;
queue->callback = callback;
queue->userContext = userContext;
queue->mutex = (pthread_mutex_t *)lingxin_calloc(1, sizeof(pthread_mutex_t));
queue->cond = (pthread_cond_t *)lingxin_calloc(1, sizeof(pthread_cond_t));
pthread_mutex_init(queue->mutex, NULL);
pthread_cond_init(queue->cond, NULL);
pthread_create(&queue->notifyThread, NULL, eventNotifyThread, (void *)queue);
return queue;
}
// 销毁队列
void eventQueueDestroy(EventQueue *queue)
{
if (!queue)
{
return;
}
lingxin_log_debug("eventQueueDestroy begin");
pthread_mutex_lock(queue->mutex);
EventQueueNode *current = queue->front;
while (current)
{
EventQueueNode *next = current->next;
lingxin_free(current);
current = next;
}
queue->isDestroyed = true;
// 唤醒等待的线程
pthread_cond_signal(queue->cond);
pthread_mutex_unlock(queue->mutex);
// 等待通知线程结束
pthread_join(queue->notifyThread, NULL);
pthread_mutex_destroy(queue->mutex);
pthread_cond_destroy(queue->cond);
lingxin_free(queue->mutex);
lingxin_free(queue->cond);
lingxin_free(queue);
lingxin_log_debug("eventQueueDestroy finish");
}
// 入队操作
EventQueueStatus eventQueueEnqueue(EventQueue *queue, int dataType,
const char *message, size_t messageSize)
{
lingxin_log_debug("eventQueueEnqueue begin %d: %d: %s", dataType, messageSize,
!message ? "NULL" : message);
EventQueueNode *new_node =
(EventQueueNode *)lingxin_calloc(1, sizeof(EventQueueNode));
if (!new_node)
{
return QUEUE_MEMORY_ERROR;
}
EventChunk *chunk = (EventChunk *)lingxin_calloc(1, sizeof(EventChunk));
if (!chunk)
{
lingxin_log_debug("event malloc fail");
}
chunk->eventType = dataType;
chunk->data = message;
chunk->dataSize = messageSize;
new_node->event = chunk;
new_node->next = NULL;
int lockResult = pthread_mutex_lock(queue->mutex);
if (lockResult != 0)
{
lingxin_log_error("queueEnqueue: Mutex lock failed with error code %d", lockResult);
lingxin_free(new_node);
return QUEUE_MUTEX_ERROR;
}
if (!queue->rear)
{
queue->front = new_node;
queue->rear = new_node;
}
else
{
queue->rear->next = new_node;
queue->rear = new_node;
}
pthread_cond_signal(queue->cond);
pthread_mutex_unlock(queue->mutex);
lingxin_log_debug("eventQueueEnqueue finish");
return QUEUE_SUCCESS;
}
// 出队操作
EventChunk *eventQueueDequeue(EventQueue *queue)
{
lingxin_log_debug("eventQueueDequeue: begin");
pthread_mutex_lock(queue->mutex);
while (!queue->front && !queue->isDestroyed)
{
lingxin_log_debug("eventQueueDequeue: wait");
pthread_cond_wait(queue->cond, queue->mutex);
lingxin_log_debug("eventQueueDequeue:after wait");
}
if (queue->isDestroyed)
{
pthread_mutex_unlock(queue->mutex);
return NULL;
}
EventQueueNode *front_node = queue->front;
EventChunk *data = front_node->event;
queue->front = front_node->next;
if (!queue->front)
{
queue->rear = NULL;
}
pthread_mutex_unlock(queue->mutex);
lingxin_free(front_node);
lingxin_log_debug("eventQueueDequeue: finish: %d, %zu", data->eventType,
data->dataSize);
return data;
}
#endif // LINGXI_USE_VOICE_QUEUE

View File

@@ -0,0 +1,317 @@
#include "lingxin_json_util.h"
#include "lingxin_common.h"
#include "lingxin_timer.h"
#include "schedule_timer_manager.h"
#include "lingxin_memory.h"
#include "lingxin_log.h"
const char *parsePayloadStr(cJSON *json)
{
if (!json)
{
return "";
}
cJSON *payload = cJSON_GetObjectItemCaseSensitive(json, "payload");
if (!cJSON_IsObject(payload))
{
lingxin_log_error("payload not an object");
return "";
}
return cJSON_PrintUnformatted(payload);
}
const char *parseEvent(cJSON *json)
{
char *event = NULL;
// 获取 header 对象
cJSON *header = cJSON_GetObjectItemCaseSensitive(json, "header");
if (!header)
{
const char *event = NULL;
cJSON *eventJSON = cJSON_GetObjectItemCaseSensitive(json, "action");
if (cJSON_IsString(eventJSON) && eventJSON->valuestring)
{
event = eventJSON->valuestring;
}
return event;
}
if (!cJSON_IsObject(header))
{
lingxin_log_error("Header not found or is not an object");
return event;
}
// 获取 event 字段
cJSON *eventJSON = cJSON_GetObjectItemCaseSensitive(header, "action");
if (cJSON_IsString(eventJSON) && eventJSON->valuestring)
{
// lingxin_log_debug("parseEvent header event found");
event = eventJSON->valuestring;
}
return event;
}
char *parseRequestId(cJSON *json)
{
char *event = "";
// 获取 header 对象
cJSON *header = cJSON_GetObjectItemCaseSensitive(json, "header");
if (!cJSON_IsObject(header))
{
lingxin_log_error("Header not found or is not an object");
return event;
}
// 获取 event 字段
cJSON *eventJSON = cJSON_GetObjectItemCaseSensitive(header, "request_id");
if (cJSON_IsString(eventJSON) && eventJSON->valuestring)
{
event = eventJSON->valuestring;
}
return event;
}
char *parseErrorInfo(cJSON *json)
{
// 获取 header 对象
cJSON *header = cJSON_GetObjectItemCaseSensitive(json, "header");
if (!cJSON_IsObject(header))
{
lingxin_log_error("Header not found or is not an object");
return "";
}
cJSON *errorInfoObj = cJSON_CreateObject();
if (!errorInfoObj)
{
lingxin_log_error("Failed to create errorInfo object");
return "";
}
// 获取 action 字段
cJSON *actionObj = cJSON_GetObjectItemCaseSensitive(header, "action");
if (cJSON_IsString(actionObj) && actionObj->valuestring && errorInfoObj)
{
cJSON_AddStringToObject(errorInfoObj, "action", actionObj->valuestring);
} // 获取 event 字段
cJSON *codeObj = cJSON_GetObjectItemCaseSensitive(header, "code");
if (cJSON_IsString(codeObj) && codeObj->valuestring && errorInfoObj)
{
cJSON_AddStringToObject(errorInfoObj, "code", codeObj->valuestring);
}
// 获取 event 字段
cJSON *msgObj = cJSON_GetObjectItemCaseSensitive(header, "err_msg");
if (cJSON_IsString(msgObj) && msgObj->valuestring && errorInfoObj)
{
cJSON_AddStringToObject(errorInfoObj, "err_msg", msgObj->valuestring);
}
char *result = cJSON_PrintUnformatted(errorInfoObj);
cJSON_Delete(errorInfoObj); // 修复:释放 cJSON 对象
return result ? result : "";
}
char *parseHttpResult(cJSON *json)
{
// 获取 header 对象
cJSON *header = cJSON_GetObjectItemCaseSensitive(json, "header");
if (!cJSON_IsObject(header))
{
lingxin_log_error("Header not found or is not an object");
return "";
}
cJSON *errorInfoObj = cJSON_CreateObject();
if (!errorInfoObj)
{
lingxin_log_error("Failed to create errorInfo object");
return "";
}
// 获取 action 字段
cJSON *actionObj = cJSON_GetObjectItemCaseSensitive(header, "action");
if (cJSON_IsString(actionObj) && actionObj->valuestring && errorInfoObj)
{
cJSON_AddStringToObject(errorInfoObj, "action", actionObj->valuestring);
}
// 获取 code 字段
cJSON *codeObj = cJSON_GetObjectItemCaseSensitive(header, "code");
if (cJSON_IsString(codeObj) && codeObj->valuestring && errorInfoObj)
{
cJSON_AddStringToObject(errorInfoObj, "code", codeObj->valuestring);
}
// 获取 err_msg 字段
cJSON *msgObj = cJSON_GetObjectItemCaseSensitive(header, "err_msg");
if (cJSON_IsString(msgObj) && msgObj->valuestring && errorInfoObj)
{
cJSON_AddStringToObject(errorInfoObj, "err_msg", msgObj->valuestring);
}
char *result = cJSON_PrintUnformatted(errorInfoObj);
cJSON_Delete(errorInfoObj); // 修复:释放 cJSON 对象
return result ? result : "";
}
int isUseLLMStreaming(const char *message)
{
cJSON *jsonMessage = cJSON_Parse(message);
if (!jsonMessage)
{
const char *error_ptr = cJSON_GetErrorPtr();
if (error_ptr)
{
lingxin_log_debug("Error json: %s", message);
}
return 0;
}
cJSON *stream = cJSON_GetObjectItemCaseSensitive(jsonMessage, "stream");
if (cJSON_IsBool(stream))
{
int result = cJSON_IsTrue(stream);
cJSON_Delete(jsonMessage); // 释放解析后的 JSON 对象
return result;
}
cJSON_Delete(jsonMessage);
return 0;
}
int isJSON(const char *message)
{
cJSON *jsonMessage = cJSON_Parse(message);
if (!jsonMessage)
{
return 0;
}
return 1;
}
int parsePoolSize(const char *message)
{
cJSON *payloadObject = cJSON_Parse(message);
if (!payloadObject)
{
lingxin_log_error("appendParamsToStartPayload: Error json: %s", message);
return 0;
}
cJSON *paramObject = cJSON_GetObjectItemCaseSensitive(
payloadObject, "flow_control_parameters");
if (!paramObject)
{
cJSON_Delete(payloadObject);
return 0;
}
cJSON *strategy =
cJSON_GetObjectItemCaseSensitive(paramObject, "flow_control_strategy");
if (!cJSON_IsString(strategy) || !strategy->valuestring ||
strcmp(strategy->valuestring, "dynamic") != 0)
{
cJSON_Delete(payloadObject);
return 0;
}
cJSON *poolSizeObj =
cJSON_GetObjectItemCaseSensitive(paramObject, "buffer_pool_size");
int poolSize = 0;
if (cJSON_IsString(poolSizeObj) && poolSizeObj->valuestring)
{
poolSize = atoi(poolSizeObj->valuestring); // 将字符串转换为整数
}
else if (cJSON_IsNumber(poolSizeObj))
{
poolSize = poolSizeObj->valueint;
}
else
{
lingxin_log_error("Key 'buffer_pool_size' is neither a number nor a string.");
return 0;
}
return poolSize;
}
int parseScheduleTaskList(const char *jsonStr, ScheduleTaskList *outList)
{
if (!jsonStr || !outList)
{
lingxin_log_ut_with_args(LINGXIN_ERROR, "schedule_list_parse_failed", "Invalid input parameters");
return -1;
}
cJSON *root = cJSON_Parse(jsonStr);
if (!root)
{
lingxin_log_ut_with_args(LINGXIN_ERROR, "schedule_list_parse_failed", "Failed to parse JSON string");
return -1;
}
cJSON *dataObj = cJSON_GetObjectItemCaseSensitive(root, "data");
if (!dataObj || !cJSON_IsObject(dataObj))
{
lingxin_log_ut_with_args(LINGXIN_ERROR, "schedule_list_parse_failed", "data not found or is not an object");
cJSON_Delete(root);
return -1;
}
// 检查 type 是否为 "schedule_task_list"
cJSON *typeObj = cJSON_GetObjectItemCaseSensitive(dataObj, "type");
if (!typeObj || !cJSON_IsString(typeObj) || strcmp(typeObj->valuestring, "schedule_task_list") != 0)
{
lingxin_log_ut_with_args(LINGXIN_ERROR, "schedule_list_parse_failed", "type not found or not 'schedule_task_list'");
cJSON_Delete(root);
return -1;
}
// 解析 task_list 数组
cJSON *taskListArray = cJSON_GetObjectItemCaseSensitive(dataObj, "task_list");
if (!taskListArray || !cJSON_IsArray(taskListArray))
{
lingxin_log_ut_with_args(LINGXIN_ERROR, "schedule_list_parse_failed", "task_list not found or is not an array");
cJSON_Delete(root);
return -1;
}
int taskCount = cJSON_GetArraySize(taskListArray);
TaskItem *tasks = NULL;
if (taskCount > 0)
{
tasks = (TaskItem *)lingxin_calloc(taskCount, sizeof(TaskItem));
if (!tasks)
{
lingxin_log_ut_with_args(LINGXIN_ERROR, "schedule_list_parse_failed", "Memory allocation failed for tasks");
cJSON_Delete(root);
return -1;
}
for (int i = 0; i < taskCount; i++)
{
cJSON *taskObj = cJSON_GetArrayItem(taskListArray, i);
if (!taskObj || !cJSON_IsObject(taskObj))
{
lingxin_log_ut_with_args(LINGXIN_ERROR, "schedule_list_parse_failed", "task_list[%d] is not a valid object", i);
continue;
}
cJSON *taskIdObj = cJSON_GetObjectItemCaseSensitive(taskObj, "schedule_task_id");
cJSON *countdownObj = cJSON_GetObjectItemCaseSensitive(taskObj, "trigger_time");
tasks[i].taskId = taskIdObj && cJSON_IsString(taskIdObj) ? lingxin_strdup(taskIdObj->valuestring) : lingxin_strdup("");
tasks[i].countdown = countdownObj && cJSON_IsNumber(countdownObj) ? countdownObj->valueint : 0;
}
}
else
{
tasks = NULL;
}
// 解析 schedule_task_config 对象
cJSON *configObj = cJSON_GetObjectItemCaseSensitive(dataObj, "schedule_task_config");
int advanceConnectTime = 0;
if (configObj && cJSON_IsObject(configObj))
{
cJSON *advanceTimeObj = cJSON_GetObjectItemCaseSensitive(configObj, "advance_connect_time");
if (advanceTimeObj && cJSON_IsNumber(advanceTimeObj))
{
advanceConnectTime = advanceTimeObj->valueint;
}
}
// 填充输出结构体
outList->tasks = tasks;
outList->taskCount = taskCount;
outList->advanceConnectTime = advanceConnectTime;
cJSON_Delete(root);
lingxin_log_ut(LINGXIN_DEBUG, "schedule_list_parse_finished");
return 0;
}

View File

@@ -0,0 +1,488 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include "lingxin_memory.h"
#include "lingxin_log.h"
#include "lingxin_common.h"
#include "lingxin_mutex.h"
// 内存跟踪结构体
typedef struct mem_track
{
void *addr;
int size;
const char *file;
int line;
int freed;
struct mem_track *next; // 链表指针
} mem_track_t;
static lingxin_mutex_t g_mem_tracker_lock = NULL; // 添加全局锁
static mem_track_t *g_mem_tracker_head = NULL; // 链表头指针
static int g_tracker_count = 0;
static int g_total_allocated = 0;
static int g_current_used = 0;
static bool g_memory_statistics_enabled = false;
static int g_file_strings_allocated = 0;
static bool add_node_to_track_list(void *address, int size, char module_name[64], int line)
{
mem_track_t *new_node = (mem_track_t *)malloc(sizeof(mem_track_t));
if (!new_node)
{
return false;
}
// 提前复制文件名,避免在锁内进行内存分配
char *file_copy = strdup(module_name);
if (!file_copy)
{
free(new_node);
return false;
}
new_node->addr = address;
new_node->size = size;
new_node->file = file_copy;
new_node->line = line;
new_node->freed = 0;
if (g_mem_tracker_lock)
{
lingxin_mutex_lock(g_mem_tracker_lock);
}
new_node->next = g_mem_tracker_head;
g_mem_tracker_head = new_node;
g_tracker_count++;
g_total_allocated += new_node->size;
g_current_used += new_node->size;
g_file_strings_allocated += strlen(module_name) + 1;
if (g_mem_tracker_lock)
{
lingxin_mutex_unlock(g_mem_tracker_lock);
}
return true;
}
// 内存分配统计函数实现
void *_lingxin_malloc_internal_(int size, const char *file_path, int line)
{
void *ptr = malloc(size);
if (!ptr)
{
char module_name[64] = {0};
parse_file_name_from_path(module_name, file_path);
lingxin_log_ut_with_args(LINGXIN_WARN, "lingxin_malloc_fail", " %s:%d malloc %p, size: %d", module_name, line, ptr, size);
}
if (g_memory_statistics_enabled && ptr)
{
char module_name[64] = {0};
parse_file_name_from_path(module_name, file_path);
// lingxin_log_debug("malloc(%p, %d) called from %s:%d", ptr, size, module_name, line);
if (!add_node_to_track_list(ptr, size, module_name, line))
{
lingxin_log_ut_with_args(LINGXIN_WARN, "lingxin_malloc_add_node_fail", " %s:%d malloc %p, size: %d", module_name, line, ptr, size);
}
}
return ptr;
}
void *_lingxin_calloc_internal_(int num, int size, const char *file_path, int line)
{
void *ptr = calloc(num, size);
int total_size = num * size;
if (!ptr)
{
char module_name[64] = {0};
parse_file_name_from_path(module_name, file_path);
lingxin_log_ut_with_args(LINGXIN_WARN, "lingxin_calloc_fail", " %s:%d calloc %p, size: %d, num: %d, each size: %d", module_name, line, ptr, total_size, num, size);
}
if (g_memory_statistics_enabled && ptr)
{
char module_name[64] = {0};
parse_file_name_from_path(module_name, file_path);
// lingxin_log_debug("calloc(%p, %d) called from %s:%d", ptr, total_size, module_name, line);
if (!add_node_to_track_list(ptr, total_size, module_name, line))
{
lingxin_log_ut_with_args(LINGXIN_WARN, "lingxin_calloca_dd_node_fail", " %s:%d calloc %p, size: %d, num: %d, each size: %d", module_name, line, ptr, total_size, num, size);
}
}
return ptr;
}
void *_lingxin_realloc_internal_(void *ptr, int size, const char *file_path, int line)
{
// 当 ptr 为 NULL 时realloc 的行为等同于 malloc
void *new_ptr = realloc(ptr, size);
if (g_memory_statistics_enabled)
{
char module_name[64] = {0};
parse_file_name_from_path(module_name, file_path);
if (ptr == NULL)
{
// ptr 为 NULL相当于 malloc 行为
if (new_ptr)
{
if (!add_node_to_track_list(new_ptr, size, module_name, line))
{
lingxin_log_ut_with_args(LINGXIN_WARN, "lingxin_realloc_add_node_fail", " %s:%d realloc(NULL) %p, size: %d (equivalent to malloc)", module_name, line, new_ptr, size);
}
}
}
else
{
// ptr 不为 NULL处理正常的 realloc 逻辑
if (new_ptr)
{
if (g_mem_tracker_lock)
{
lingxin_mutex_lock(g_mem_tracker_lock);
}
// 查找原始内存块信息
mem_track_t *current = g_mem_tracker_head;
mem_track_t *prev = NULL;
bool found = false;
while (current)
{
if (current->addr == ptr && !current->freed)
{
found = true;
// 更新内存跟踪信息
if (new_ptr == ptr)
{
// 原地调整大小
g_current_used = g_current_used - current->size + size;
g_total_allocated = g_total_allocated - current->size + size;
current->size = size;
lingxin_log_ut_with_args(LINGXIN_DEBUG, "lingxin_realloc", " %s:%d realloc %p, resized from %d to %d bytes", module_name, line, new_ptr, ptr, current->size, size);
}
else
{
// 分配了新内存块,需要更新跟踪信息
// 删除旧节点
if (prev)
{
prev->next = current->next;
}
else
{
g_mem_tracker_head = current->next;
}
g_current_used -= current->size;
g_tracker_count--;
if (current->file)
{
g_file_strings_allocated -= strlen(current->file) + 1;
free((void *)current->file);
}
free(current);
// 解锁后添加新节点因为add_node_to_track_list内部有锁
if (g_mem_tracker_lock)
{
lingxin_mutex_unlock(g_mem_tracker_lock);
}
if (!add_node_to_track_list(new_ptr, size, module_name, line))
{
lingxin_log_ut_with_args(LINGXIN_WARN, "lingxin_realloc_add_node_fail", " %s:%d realloc %p -> %p, size: %d", module_name, line, ptr, new_ptr, size);
}
}
break;
}
prev = current;
current = current->next;
}
// 如果没找到原始内存块,创建新的跟踪节点
if (!found && new_ptr != ptr)
{
// 解锁后添加新节点因为add_node_to_track_list内部有锁
if (g_mem_tracker_lock)
{
lingxin_mutex_unlock(g_mem_tracker_lock);
}
if (!add_node_to_track_list(new_ptr, size, module_name, line))
{
lingxin_log_ut_with_args(LINGXIN_WARN, "lingxin_realloc_add_node_fail", " %s:%d realloc %p, size: %d (new tracking entry)", module_name, line, new_ptr, size);
}
}
else
{
// 只有在找到了原始节点且不是新增节点的情况下才解锁
if (found && new_ptr == ptr)
{
if (g_mem_tracker_lock)
{
lingxin_mutex_unlock(g_mem_tracker_lock);
}
}
}
}
else if (size == 0)
{
if (g_mem_tracker_lock)
{
lingxin_mutex_lock(g_mem_tracker_lock);
}
// realloc(ptr, 0) 相当于 free(ptr)
// 查找并删除对应的内存跟踪节点
mem_track_t *current = g_mem_tracker_head;
mem_track_t *prev = NULL;
while (current)
{
if (current->addr == ptr && !current->freed)
{
// 删除节点
if (prev)
{
prev->next = current->next;
}
else
{
g_mem_tracker_head = current->next;
}
g_current_used -= current->size;
g_tracker_count--;
if (current->file)
{
g_file_strings_allocated -= strlen(current->file) + 1;
free((void *)current->file);
}
free(current);
break;
}
prev = current;
current = current->next;
}
if (g_mem_tracker_lock)
{
lingxin_mutex_unlock(g_mem_tracker_lock);
}
lingxin_log_ut_with_args(LINGXIN_DEBUG, "lingxin_realloc", " %s:%d realloc %p with size 0, treated as free",
module_name, line, ptr);
}
else
{
// realloc 失败且 size > 0
lingxin_log_ut_with_args(LINGXIN_ERROR, "lingxin_realloc", " %s:%d realloc failed for ptr %p, size: %d",
module_name, line, ptr, size);
}
}
}
return new_ptr;
}
void _lingxin_free_internal_(void *ptr, const char *file_path, int line)
{
if (ptr)
{
if (g_memory_statistics_enabled)
{
char module_name[64] = {0};
parse_file_name_from_path(module_name, file_path);
if (g_mem_tracker_lock)
{
lingxin_mutex_lock(g_mem_tracker_lock);
}
// 在链表中查找并标记为已释放
bool found = false;
mem_track_t *current = g_mem_tracker_head;
mem_track_t *prev = NULL;
while (current)
{
if (current->addr == ptr && !current->freed)
{
if (prev)
{
prev->next = current->next;
}
else
{
g_mem_tracker_head = current->next;
}
g_current_used -= current->size;
g_tracker_count--;
if (current->file)
{
g_file_strings_allocated -= strlen(current->file) + 1;
free((void *)current->file);
}
free(current);
found = true;
break;
}
prev = current;
current = current->next;
}
if (g_mem_tracker_lock)
{
lingxin_mutex_unlock(g_mem_tracker_lock);
}
// 如果没有找到对应的内存记录,说明这块内存没有被统计过
if (!found)
{
lingxin_log_ut_with_args(LINGXIN_WARN, "lingxin_free", "%s:%d free %p - memory not tracked by statistics", module_name, line, ptr);
}
}
// 真正释放内存
free(ptr);
}
}
char *_lingxin_strdup_internal_(char *message, const char *file_path, int line)
{
if (!message)
{
return NULL;
}
char *copy = strdup(message);
if (!copy)
{
char module_name[64] = {0};
parse_file_name_from_path(module_name, file_path);
lingxin_log_ut_with_args(LINGXIN_WARN, "lingxin_strdup_fail", " %s:%d strdup %p", module_name, line, copy);
}
if (g_memory_statistics_enabled && copy)
{
char module_name[64] = {0};
parse_file_name_from_path(module_name, file_path);
// lingxin_log_debug("calloc(%p, %d) called from %s:%d", copy, strlen(message) + 1, module_name, line);
if (!add_node_to_track_list(copy, strlen(message) + 1, module_name, line))
{
lingxin_log_ut_with_args(LINGXIN_WARN, "lingxin_strdup_add_node_fail", " %s:%d strdup %p", module_name, line, copy);
}
}
return copy;
}
// 打印内存统计信息
void lingxin_memory_print_statistics()
{
if (!g_memory_statistics_enabled)
{
return;
}
if (g_mem_tracker_lock)
{
lingxin_mutex_lock(g_mem_tracker_lock);
}
// 计算链表本身占用的内存大小
size_t list_memory_size = g_tracker_count * sizeof(mem_track_t);
size_t total_tracking_overhead = list_memory_size + g_file_strings_allocated;
lingxin_log_ut_with_args(LINGXIN_DEBUG, "print_lingxin_memory", "=== lingxin Memory Statistics Report ===");
lingxin_log_ut_with_args(LINGXIN_DEBUG, "print_lingxin_memory", "Total allocations: %d", g_tracker_count);
lingxin_log_ut_with_args(LINGXIN_DEBUG, "print_lingxin_memory", "Total allocated: %d bytes", g_total_allocated);
lingxin_log_ut_with_args(LINGXIN_DEBUG, "print_lingxin_memory", "Currently used: %d bytes", g_current_used);
lingxin_log_ut_with_args(LINGXIN_DEBUG, "print_lingxin_memory", "Memory tracking list size: %d bytes", list_memory_size);
lingxin_log_ut_with_args(LINGXIN_DEBUG, "print_lingxin_memory", "File strings memory: %d bytes", g_file_strings_allocated);
lingxin_log_ut_with_args(LINGXIN_DEBUG, "print_lingxin_memory", "Total tracking overhead: %d bytes", total_tracking_overhead);
// 统计未释放的内存
int unfreed_count = 0;
int unfreed_size = 0;
lingxin_log_ut_with_args(LINGXIN_DEBUG, "print_lingxin_memory", "=== Unfreed Memory Blocks ===");
mem_track_t *current = g_mem_tracker_head;
int index = 1;
while (current)
{
if (!current->freed)
{
unfreed_count++;
unfreed_size += current->size;
lingxin_log_ut_with_args(LINGXIN_DEBUG, "print_lingxin_memory", "[%d] Address: %p, Size: %d bytes, Location: %s:%d\n",
index,
current->addr,
current->size,
current->file,
current->line);
}
current = current->next;
index++;
}
lingxin_log_ut_with_args(LINGXIN_DEBUG, "print_lingxin_memory", "Total unfreed blocks: %d", unfreed_count);
lingxin_log_ut_with_args(LINGXIN_DEBUG, "print_lingxin_memory", "Total unfreed memory: %d bytes", unfreed_size);
if (g_mem_tracker_lock)
{
lingxin_mutex_unlock(g_mem_tracker_lock);
}
}
void lingxin_memory_enable_statistics()
{
g_memory_statistics_enabled = true;
if (!g_mem_tracker_lock)
{
g_mem_tracker_lock = lingxin_mutex_create();
}
}
// 释放所有内存跟踪节点
void lingxin_memory_destroy_statistics()
{
if (g_mem_tracker_lock)
{
lingxin_mutex_lock(g_mem_tracker_lock);
}
mem_track_t *current = g_mem_tracker_head;
while (current)
{
mem_track_t *temp = current;
current = current->next;
// 释放文件名内存
if (temp->file)
{
free((void *)temp->file);
}
// 释放节点内存
free(temp);
}
// 重置统计变量
g_mem_tracker_head = NULL;
g_tracker_count = 0;
g_total_allocated = 0;
g_current_used = 0;
g_file_strings_allocated = 0;
g_memory_statistics_enabled = false;
if (g_mem_tracker_lock)
{
lingxin_mutex_unlock(g_mem_tracker_lock);
}
lingxin_mutex_destroy(g_mem_tracker_lock);
}

View File

@@ -0,0 +1,198 @@
#include "lingxin_tls_utils.h"
#include "lingxin_common.h"
#include "mbedtls/base64.h"
#include "mbedtls/md.h"
#include <ctype.h>
#include <stddef.h>
#include "lingxin_log.h"
#include "lingxin_memory.h"
// 实现 HMAC-SHA1 加密逻辑
static char *sdk_hmac_sha1(const char *key, const char *data)
{
unsigned char output[20]; // SHA1 的输出长度为 20 字节
size_t output_len = sizeof(output);
// 执行 HMAC-SHA1 加密
mbedtls_md_context_t ctx;
const mbedtls_md_info_t *md_info;
int ret = 0;
// 初始化 MD 上下文
mbedtls_md_init(&ctx);
// 获取 SHA1 的 MD 信息
md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA1);
if (md_info == NULL)
{
lingxin_log_error("无法获取 SHA1 MD 信息");
mbedtls_md_free(&ctx);
return NULL;
}
// 设置 HMAC 上下文
if ((ret = mbedtls_md_setup(&ctx, md_info, 1)) != 0)
{
lingxin_log_error("HMAC 上下文设置失败,错误码: %d", ret);
mbedtls_md_free(&ctx);
return NULL;
}
// 设置 HMAC 密钥
if ((ret = mbedtls_md_hmac_starts(&ctx, (const unsigned char *)key,
strlen(key))) != 0)
{
lingxin_log_error("设置 HMAC 密钥失败,错误码: %d", ret);
mbedtls_md_free(&ctx);
return NULL;
}
// 输入数据
if ((ret = mbedtls_md_hmac_update(&ctx, (const unsigned char *)data,
strlen(data))) != 0)
{
lingxin_log_error("HMAC 数据更新失败,错误码: %d", ret);
mbedtls_md_free(&ctx);
return NULL;
}
// 生成 HMAC-SHA1 输出
if ((ret = mbedtls_md_hmac_finish(&ctx, output)) != 0)
{
lingxin_log_error("HMAC 计算失败,错误码: %d", ret);
mbedtls_md_free(&ctx);
return NULL;
}
// 清理上下文
mbedtls_md_free(&ctx);
// 使用 mbedtls 的 Base64 编码
char base64_output[64]; // Base64 编码长度取决于输入长度20 字节的输出需要 28
// 字节
size_t base64_len = 0;
ret = mbedtls_base64_encode((unsigned char *)base64_output,
sizeof(base64_output), &base64_len, output,
output_len);
if (ret != 0)
{
lingxin_log_error("Base64 编码失败,错误码: %d", ret);
return NULL;
}
// 分配存储结果字符串的内存
char *base64_result = lingxin_malloc(base64_len + 1);
if (!base64_result)
{
lingxin_log_error("内存分配失败");
return NULL;
}
// 复制 Base64 编码结果
memcpy(base64_result, base64_output, base64_len);
base64_result[base64_len] = '\0'; // 添加字符串终止符
return base64_result;
}
/**
* Helper function to check if a character is unreserved in URL encoding.
* Unreserved characters are alphanumeric or one of "-_.~".
*/
static int isUnreserved(unsigned char c)
{
return (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~');
}
/**
* Helper function to convert a byte to a hex string.
* @param dest Destination buffer to store the hex representation (must have at
* least 3 bytes).
* @param byte The byte to convert.
*/
static void byteToHex(char *dest, unsigned char byte)
{
const char *hex_digits = "0123456789ABCDEF";
dest[0] = '%';
dest[1] = hex_digits[(byte >> 4) & 0xF]; // High nibble
dest[2] = hex_digits[byte & 0xF]; // Low nibble
}
/**
* URL-encodes the given input string.
* @param input The input string to encode.
* @param length The length of the input string. If set to -1, the function will
* use strlen(input).
* @return A newly allocated string containing the URL-encoded result, or NULL
* on error. The caller is responsible for freeing the returned string.
*/
static char *urlEncode(const char *input, int length)
{
if (!input)
{
return NULL;
}
// Allocate the output buffer (worst case: every character is encoded as %XX)
size_t output_size = length * 3 + 1; // +1 for null terminator
char *output = (char *)lingxin_malloc(output_size);
if (!output)
{
return NULL; // Memory allocation failed
}
// Perform URL encoding
char *dest = output;
for (size_t i = 0; i < length; i++)
{
unsigned char c = (unsigned char)input[i];
if (isUnreserved(c))
{
*dest++ = c; // Copy unreserved characters as-is
}
else
{
byteToHex(dest, c); // Encode reserved characters as %XX
dest += 3; // Advance by 3 characters (%XX)
}
}
*dest = '\0'; // Null-terminate the resulting string
return output;
}
char *generateSignature(const char *sn, const char *appKey, const char *appId,
const char *timestamp)
{
bool free_sn = false;
bool free_appId = false;
const char *snEncode = urlEncode(sn, strlen(sn));
if (!snEncode)
{
snEncode = sn;
} else {
free_sn = true;
}
const char *appIdEncode = urlEncode(appId, strlen(appId));
if (!appIdEncode)
{
appIdEncode = appId;
} else {
free_appId = true;
}
char hmacValue[128];
snprintf(hmacValue, sizeof(hmacValue), "app_id=%s&sn=%s&timestamp=%s", appIdEncode, snEncode, timestamp);
char *result = sdk_hmac_sha1(appKey, hmacValue);
if (!result)
{
lingxin_log_debug("Signature generatte fail");
return NULL;
}
if(free_sn) {
lingxin_free(snEncode);
}
if(free_appId) {
lingxin_free(appIdEncode);
}
return result;
}

View File

@@ -0,0 +1,269 @@
#ifdef LINGXI_USE_VOICE_QUEUE
#include <errno.h>
#include <stdbool.h>
#include "lingxin_voice_queue.h"
#include "lingxin_log.h"
#include "lingxin_memory.h"
#define HEADER_SIZE sizeof(size_t)
static bool isSpaceAvailableInner(VoiceQueue *queue)
{
return queue && (queue->capacity - queue->count) >= (queue->capacity * 0.3);
}
bool isVoiceQueueSpaceEnough(VoiceQueue *queue)
{
pthread_mutex_lock(queue->mutex);
bool result = isSpaceAvailableInner(queue);
pthread_mutex_unlock(queue->mutex);
return result;
}
// 初始化队列
VoiceQueue *voiceQueueCreate(size_t capacity)
{
VoiceQueue *queue = (VoiceQueue *)lingxin_calloc(1, sizeof(VoiceQueue));
if (!queue)
{
lingxin_log_error("voicequeue malloc failed");
return NULL;
}
queue->buffer = lingxin_calloc(1, capacity);
if (!queue->buffer)
{
lingxin_log_error("voicequeue buffer malloc failed");
lingxin_free(queue);
return NULL;
}
queue->capacity = capacity;
queue->count = 0;
queue->front = 0;
queue->rear = 0;
queue->mutex = (pthread_mutex_t *)lingxin_malloc(sizeof(pthread_mutex_t));
if (!queue->mutex)
{
lingxin_free(queue->buffer);
lingxin_free(queue);
return NULL;
}
if (pthread_mutex_init(queue->mutex, NULL) != 0)
{
lingxin_free(queue->buffer);
lingxin_free(queue->mutex);
lingxin_free(queue);
return NULL;
}
queue->cond = (pthread_cond_t *)lingxin_malloc(sizeof(pthread_cond_t));
if (!queue->cond)
{
lingxin_free(queue->buffer);
lingxin_free(queue->mutex);
lingxin_free(queue);
return NULL;
}
if (pthread_cond_init(queue->cond, NULL) != 0)
{
lingxin_free(queue->buffer);
lingxin_free(queue->mutex);
lingxin_free(queue->cond);
lingxin_free(queue);
return NULL;
}
return queue;
}
// 销毁队列
void destroyVoiceQueue(VoiceQueue *queue)
{
if (!queue)
{
return;
}
lingxin_log_debug("destroyVoiceQueue: begin");
pthread_mutex_destroy(queue->mutex); // 销毁互斥锁
pthread_cond_destroy(queue->cond);
lingxin_free(queue->mutex);
lingxin_free(queue->cond);
lingxin_free(queue->buffer);
lingxin_free(queue);
lingxin_log_debug("destroyVoiceQueue: finish");
}
// 入队操作
bool voiceEnqueue(VoiceQueue *queue, const char *data, size_t length)
{
if (!queue)
{
return false;
}
lingxin_log_debug("voiceEnqueue: begin: %d", length);
pthread_mutex_lock(queue->mutex);
size_t totalSize = HEADER_SIZE + length;
if (queue->count == queue->capacity ||
totalSize > (queue->capacity - queue->count))
{
pthread_mutex_unlock(queue->mutex);
return false;
}
size_t spaceAtEnd = queue->capacity - queue->rear;
if (spaceAtEnd >= totalSize)
{
memcpy((char *)queue->buffer + queue->rear, &length, HEADER_SIZE);
queue->rear = (queue->rear + HEADER_SIZE) % queue->capacity;
memcpy((char *)queue->buffer + queue->rear, data, length);
queue->rear = (queue->rear + length) % queue->capacity;
}
else
{
if (spaceAtEnd >= HEADER_SIZE)
{
memcpy((char *)queue->buffer + queue->rear, &length, HEADER_SIZE);
queue->rear = (queue->rear + HEADER_SIZE) % queue->capacity;
size_t remainSpace = spaceAtEnd - HEADER_SIZE;
memcpy((char *)queue->buffer + queue->rear, data, remainSpace);
queue->rear = (queue->rear + remainSpace) % queue->capacity;
memcpy(queue->buffer, (char *)data + remainSpace, length - remainSpace);
queue->rear = (length - remainSpace) % queue->capacity;
}
else
{
size_t part1Size = spaceAtEnd;
size_t part2Size = HEADER_SIZE - part1Size;
memcpy((char *)queue->buffer + queue->rear, &length, part1Size);
memcpy(queue->buffer, ((char *)&length) + part1Size, part2Size);
queue->rear = part2Size;
memcpy((char *)queue->buffer + queue->rear, data, length);
queue->rear = (queue->rear + length) % queue->capacity;
}
}
queue->count += totalSize;
pthread_cond_signal(queue->cond);
pthread_mutex_unlock(queue->mutex);
lingxin_log_debug("voiceEnqueue: after");
return true;
}
// 出队操作
bool voiceDequeue(VoiceQueue *queue, VoiceChunk *chunk,
ContinueWaitCheck continueWaitCheckFunc, void *userContext)
{
if (!queue)
{
return false;
}
lingxin_log_debug("voiceDequeue: begin");
pthread_mutex_lock(queue->mutex);
while (queue && queue->count == 0)
{
lingxin_log_debug("voiceDequeue: wait");
if (isSpaceAvailableInner(queue))
{
size_t space = queue->capacity - queue->count;
if (!continueWaitCheckFunc(userContext, space))
{
pthread_mutex_unlock(queue->mutex);
return false;
}
}
// 设置超时时间为当前时间加上500毫秒
struct timespec timeout;
clock_gettime(CLOCK_REALTIME, &timeout);
timeout.tv_nsec += 500 * 1000 * 1000; // 500毫秒
if (timeout.tv_nsec >= 1000 * 1000 * 1000)
{
timeout.tv_nsec -= 1000 * 1000 * 1000;
timeout.tv_sec++;
}
int ret = pthread_cond_timedwait(queue->cond, queue->mutex, &timeout);
if (ret == ETIMEDOUT)
{
lingxin_log_debug("voiceDequeue: timeout");
continue;
}
else if (ret != 0)
{
lingxin_log_error("voiceDequeue: pthread_cond_timedwait failed");
pthread_mutex_unlock(queue->mutex);
return false;
}
lingxin_log_debug("voiceDequeue: after wait");
}
size_t spaceAtEnd = queue->capacity - queue->front;
if (spaceAtEnd >= HEADER_SIZE)
{
memcpy(&chunk->length, (char *)queue->buffer + queue->front, HEADER_SIZE);
queue->front = (queue->front + HEADER_SIZE) % queue->capacity;
}
else
{
size_t part1Size = spaceAtEnd;
size_t part2Size = HEADER_SIZE - part1Size;
memcpy(&chunk->length, (char *)queue->buffer + queue->front, part1Size);
memcpy(((char *)&chunk->length) + part1Size, queue->buffer, part2Size);
queue->front = part2Size;
}
chunk->data = lingxin_malloc(chunk->length);
if (!chunk->data)
{
lingxin_log_error("voiceDequeue: chunk->data fail malloc");
pthread_mutex_unlock(queue->mutex);
return false;
}
spaceAtEnd = queue->capacity - queue->front;
if (spaceAtEnd >= chunk->length)
{
memcpy(chunk->data, (char *)queue->buffer + queue->front, chunk->length);
queue->front = (queue->front + chunk->length) % queue->capacity;
}
else
{
memcpy(chunk->data, (char *)queue->buffer + queue->front, spaceAtEnd);
memcpy((char *)chunk->data + spaceAtEnd, queue->buffer,
chunk->length - spaceAtEnd);
queue->front = (chunk->length - spaceAtEnd) % queue->capacity;
}
queue->count -= (HEADER_SIZE + chunk->length);
pthread_mutex_unlock(queue->mutex);
lingxin_log_debug("voiceDequeue: finish %d", queue->count);
return true;
}
size_t getRemainSpaceOfVoiceQueue(VoiceQueue *queue)
{
if (!queue)
{
return 0;
}
pthread_mutex_lock(queue->mutex);
size_t result = queue->capacity - queue->count;
pthread_mutex_unlock(queue->mutex);
return result;
}
void clearVoiceQueue(VoiceQueue *queue)
{
if (!queue)
{
return;
}
lingxin_log_debug("clearVoiceQueue begin");
pthread_mutex_lock(queue->mutex);
queue->count = 0;
queue->front = 0;
queue->rear = 0;
pthread_mutex_unlock(queue->mutex);
lingxin_log_debug("clearVoiceQueue finish");
}
#endif // LINGXI_USE_VOICE_QUEUE

View File

@@ -0,0 +1,498 @@
#include <stdio.h>
#include <string.h>
#include "lingxin_log.h"
#include "chat_api.h"
#include "lingxin_chat_api_inner.h"
#include "cJSON.h"
// 引入各个模块所需的头文件
#include "lingxin_voice_chat_config.h"
#include "lingxin_file.h"
#include "lingxin_user_track.h"
#include "chat_state_machine.h"
#include "chat_runtime_context.h"
#include "lingxin_recorder_manager.h"
#include "lingxin_local_player_manager.h"
#include "schedule_timer_manager.h"
#include "audio_buffer_play.h"
#include "lingxin_http.h"
#include "lingxin_common.h"
#include "lingxin_mutex.h"
#include "lingxin_thread.h"
#include "lingxin_memory.h"
#include "lingxin_trace.h"
#include "lingxin_chat_upload_manager.h"
#define LINGXIN_PROPS_INIT_TAG "lingxin_props_init_tag"
// 标记是否初始化
static int is_inited = 0;
// 用户注册的对话模式事件回调方法
static ChatLifeCycleEventListener chat_event_listenner = NULL;
// 多模态事件回调方法
static LingxinMultimodalInputListener multimodal_input_listener = NULL;
// 云端配置获取线程的ID
static int lingxin_config_get_thread_id = 0;
static int lingxin_config_get_state = 0; // 0: 未开始; 1: 完成; -1: 超时
static lingxin_mutex_t lingxin_config_get_state_mutex = NULL;
// 云端配置获取线程入口函数
static void* lingxin_config_get_thread_entry(void *arg);
// 解析云端配置
static bool lingxin_parse_server_config(char* response, LingxinServerConfig *server_config);
/**
* 初始化方法
*/
VoiceChatInitProps get_voice_chat_init_default_props()
{
VoiceChatInitProps props = {0};
props.is_schedule_task_on = 1;
props.is_log_upload_on = 1;
props.props_init_tag = LINGXIN_PROPS_INIT_TAG;
return props;
}
int voice_chat_init(VoiceChatInitProps *init_props) {
#ifdef LINGXIN_TEST
lingxin_test_runner_init(init_props);
return -1;
#else
return inner_voice_chat_init(init_props);
#endif
}
int inner_voice_chat_init(VoiceChatInitProps *init_props) {
if (is_inited) {
lingxin_log_ut(LINGXIN_ERROR, "chat_api_voice_chat_init_already_inited");
return 0;
}
lingxin_log_ut(LINGXIN_DEBUG, "chat_api_voice_chat_init_start");
// 检测传参是否合法
if (init_props && strcmp(init_props->props_init_tag, LINGXIN_PROPS_INIT_TAG) != 0)
{
lingxin_log_ut(LINGXIN_ERROR, "chat_api_voice_chat_init_fail_props_illegal");
return -1;
}
// 检测传参是否为空
if (!init_props)
{
lingxin_log_ut(LINGXIN_ERROR, "chat_api_voice_chat_init_fail_props_empty");
return -1;
}
/******************************** voice chat & websocket模块初始化 ********************************/
lingxin_log_ut(LINGXIN_DEBUG, "chat_api_voice_chat_config_init_start");
// 注册动态获取appId、license、sn、appCode的函数以及获取业务参数、自定义参数的方法、websocket检测配置
if (init_props->auth_app_id_get_func && init_props->auth_license_get_func && init_props->auth_sn_get_func && init_props->auth_app_code_get_func)
{
module_voice_chat_config_init(
init_props->auth_app_id_get_func,
init_props->auth_license_get_func,
init_props->auth_sn_get_func,
init_props->auth_app_code_get_func,
init_props->device_code_get_func,
init_props->chat_biz_parameter_get_func,
init_props->chat_custom_parameter_get_func,
init_props->websocket_check_interval,
init_props->websocket_check_timeout);
lingxin_log_ut(LINGXIN_DEBUG, "chat_api_voice_chat_config_init_success");
}
else
{
lingxin_log_ut(LINGXIN_ERROR, "chat_api_voice_chat_init_fail_empty_auth_func");
return -1;
}
/********************************* 云端配置获取与校验 ********************************/
// 调试阶段先绕过云端配置拉取,使用本地保守默认值,避免初始化阶段因线程/网络请求卡死。
LingxinServerConfig server_config = {
.input_format = "pcm",
.output_format = "mp3",
.enable_schedule_task = false,
.enable_log_upload = false,
};
lingxin_log_ut(LINGXIN_DEBUG, "chat_api_lingxin_config_debug_bypass");
/******************************** 校验上下行音频格式 ********************************/
if (server_config.input_format && strcmp(server_config.input_format, "pcm")) {
lingxin_log_ut(LINGXIN_ERROR, "chat_api_input_format_illegal");
return -2;
}
if (server_config.output_format && strcmp(server_config.output_format, "mp3")) {
lingxin_log_ut(LINGXIN_ERROR, "chat_api_output_format_illegal");
return -2;
}
/******************************** 埋点初始化 ********************************/
if (server_config.enable_log_upload && init_props->is_log_upload_on) {
// 检测日志功能必传参数
if (!init_props->flash_cache_path) {
lingxin_log_ut(LINGXIN_ERROR, "chat_api_voice_chat_init_fail_flash_cache_path_empty");
return -1;
}
lingxin_log_ut(LINGXIN_DEBUG, "chat_api_user_track_init_start");
if (user_track_init(init_props->flash_cache_path)) {
lingxin_log_ut(LINGXIN_DEBUG, "chat_api_user_track_init_success");
} else {
lingxin_log_ut(LINGXIN_ERROR, "chat_api_user_track_init_fail");
}
}
/******************************** 状态机初始化 ********************************/
lingxin_log_ut(LINGXIN_DEBUG, "chat_api_state_machine_init_start");
bool enable_terminate_audio = init_props->terminate_audio_path != NULL;
bool enable_continue_audio = init_props->continue_audio_path != NULL;
voice_chat_machine_init(enable_terminate_audio, enable_continue_audio);
lingxin_log_ut(LINGXIN_DEBUG, "chat_api_state_machine_init_success");
/******************************** 录音/本地播放模块初始化 ********************************/
lingxin_log_ut(LINGXIN_DEBUG, "chat_api_record_manager_init_start");
module_record_manager_init(init_props->send_uni_size, init_props->send_cbuf_scale);
lingxin_log_ut(LINGXIN_DEBUG, "chat_api_record_manager_init_success");
lingxin_log_ut(LINGXIN_DEBUG, "chat_api_local_player_manager_init_start");
if (init_props->welcome_audio_path)
{
module_local_play_set_welcome_audio_path(init_props->welcome_audio_path);
}
if (init_props->terminate_audio_path)
{
module_local_play_set_terminate_audio_path(init_props->terminate_audio_path);
}
if (init_props->continue_audio_path)
{
module_local_play_set_continue_audio_path(init_props->continue_audio_path);
}
lingxin_log_ut(LINGXIN_DEBUG, "chat_api_local_player_manager_init_success");
/******************************** 定时任务模块初始化 ********************************/
// 设置是否开启定时任务
if (server_config.enable_schedule_task && init_props->is_schedule_task_on) {
lingxin_log_ut(LINGXIN_DEBUG, "chat_api_schedule_timer_manager_init_start");
module_schedule_init();
lingxin_log_ut(LINGXIN_DEBUG, "chat_api_schedule_timer_manager_init_success");
} else {
lingxin_log_ut(LINGXIN_DEBUG, "chat_api_schedule_timer_manager_no_init");
}
/******************************* 注册chat生命周期监听函数 ******************************/
chat_event_listenner = init_props->chat_life_cycle_event_listener;
// 标记初始化完成
is_inited = 1;
lingxin_log_ut(LINGXIN_DEBUG, "chat_api_voice_chat_init_success");
return 0;
}
static void* lingxin_config_get_thread_entry(void *arg) {
LingxinServerConfig* server_config_ptr = (LingxinServerConfig*)arg;
lingxin_log_debug("lingxin_config_get_thread_entry start, server_config_ptr=%p", server_config_ptr);
// 构造请求参数
char *app_id = lingxin_auth_appId_get();
char *sn = lingxin_auth_sn_get();
char *license = lingxin_auth_license_get();
char *device_code = lingxin_device_code_get();
lingxin_log_debug("lingxin_config_get_thread_entry auth ok, app_id=%s sn=%s device_code=%s",
app_id ? app_id : "",
sn ? sn : "",
device_code ? device_code : "");
char body[128] = "";
if (device_code) {
snprintf(body, sizeof(body), "{\"device_code\":\"%s\"}", device_code);
}
HttpConfig *config = createHttpConfig(app_id, sn, license, REQUEST_URL, LINGXIN_SERVER_CONFIG_GET_PATH, body);
lingxin_log_debug("lingxin_config_get_thread_entry createHttpConfig done, path=%s", LINGXIN_SERVER_CONFIG_GET_PATH);
// 发送请求
char *response = NULL;
bool is_post_success = http_post_without_callback(config, &response);
if (lingxin_config_get_state_mutex && server_config_ptr && !lingxin_config_get_state) {
lingxin_mutex_lock(lingxin_config_get_state_mutex);
if (is_post_success) {
// 解析返回值
lingxin_parse_server_config(response, server_config_ptr);
}
lingxin_config_get_state = 1;
lingxin_mutex_unlock(lingxin_config_get_state_mutex);
}
// 释放内存
lingxin_free(response);
free_http_config(config);
lingxin_thread_destroy(lingxin_config_get_thread_id, LINGXIN_THREAD_DESTROY_WAIT);
return NULL;
}
/**
* 进入对话模式
*/
StartNewChatProps get_start_new_chat_default_props()
{
StartNewChatProps props = {0};
props.props_init_tag = LINGXIN_PROPS_INIT_TAG;
return props;
}
int start_new_chat(StartNewChatProps *start_props) {
#ifdef LINGXIN_TEST
lingxin_test_runner_wakeup();
return -1;
#else
return inner_start_new_chat(start_props);
#endif
}
int inner_start_new_chat(StartNewChatProps *start_props)
{
lingxin_log_ut(LINGXIN_DEBUG, "chat_api_start_new_chat_called");
// 检测是否初始化
if (!is_inited)
{
lingxin_log_ut(LINGXIN_ERROR, "chat_api_start_new_chat_fail_not_inited");
return -2;
}
// 检测传参是否合法
if (start_props && strcmp(start_props->props_init_tag, LINGXIN_PROPS_INIT_TAG) != 0)
{
lingxin_log_ut(LINGXIN_ERROR, "chat_api_start_new_chat_fail_props_illegal");
return -1;
}
// 设置taskid
if (start_props && start_props->task_id) {
update_sesseion_context(CTX_FIELD_CURRENT_TASK_ID, (ContextValue){.string = start_props->task_id});
} else {
update_sesseion_context(CTX_FIELD_CURRENT_TASK_ID, (ContextValue){.string = NULL});
}
// 设置single_round
if (start_props && start_props->single_round) {
update_sesseion_context(CTX_FIELD_SINGLE_ROUND, (ContextValue){.boolean = true});
} else {
update_sesseion_context(CTX_FIELD_SINGLE_ROUND, (ContextValue){.boolean = false});
}
// 设置disable_welcome_audio
if (start_props && start_props->disable_welcome_audio) {
update_sesseion_context(CTX_FIELD_DISABLE_WELCOME_AUDIO, (ContextValue){.boolean = true});
} else {
lingxin_log_debug("inner_start_new_chat CTX_FIELD_DISABLE_WELCOME_AUDIO");
update_sesseion_context(CTX_FIELD_DISABLE_WELCOME_AUDIO, (ContextValue){.boolean = false});
}
// 设置task
if (start_props && start_props->task) {
if (strcmp(start_props->task, "chat_multimodal") == 0) {
multimodal_input_listener = start_props->multimodal_input_listener;
update_sesseion_context(CTX_FIELD_GLOBAL_TASK, (ContextValue){.string = "chat_multimodal"});
update_sesseion_context(CTX_FIELD_DISABLE_SERVER_VAD, (ContextValue){.boolean = true});
} else if (strcmp(start_props->task, "chat_multimodal_vad") == 0) {
multimodal_input_listener = start_props->multimodal_input_listener;
update_sesseion_context(CTX_FIELD_GLOBAL_TASK, (ContextValue){.string = "chat_multimodal"});
update_sesseion_context(CTX_FIELD_DISABLE_SERVER_VAD, (ContextValue){.boolean = false});
} else {
multimodal_input_listener = NULL;
update_sesseion_context(CTX_FIELD_GLOBAL_TASK, (ContextValue){.string = start_props->task});
update_sesseion_context(CTX_FIELD_DISABLE_SERVER_VAD, (ContextValue){.boolean = false});
}
} else if (start_props && start_props->disable_vad) {
multimodal_input_listener = NULL;
update_temp_context(CTX_FIELD_GLOBAL_TASK, (ContextValue){.string = "chat"});
update_sesseion_context(CTX_FIELD_GLOBAL_TASK, (ContextValue){.string = "chat_vad"});
update_sesseion_context(CTX_FIELD_DISABLE_SERVER_VAD, (ContextValue){.boolean = false});
} else {
multimodal_input_listener = NULL;
update_sesseion_context(CTX_FIELD_GLOBAL_TASK, (ContextValue){.string = "chat_vad"});
update_sesseion_context(CTX_FIELD_DISABLE_SERVER_VAD, (ContextValue){.boolean = false});
}
// 临时设置user_input
if (start_props && start_props->user_input) {
update_temp_context(CTX_FIELD_CURRENT_USER_INPUT, (ContextValue){.string = start_props->user_input});
update_temp_context(CTX_FIELD_UPLOAD_TYPE, (ContextValue){.media_type = Media_Type_TextOnly});
}
// 发送事件
lingxin_log_ut(LINGXIN_DEBUG, "chat_api_start_new_chat_send_event");
lingxin_trace_set("chat:before_state_machine_event");
lingxin_log_debug("chat_api_start_new_chat_before_state_machine_run_event");
state_machine_run_event(State_Event_Wakeup_Detected);
lingxin_trace_set("chat:after_state_machine_event");
lingxin_log_debug("chat_api_start_new_chat_after_state_machine_run_event");
return 0;
}
/**
* 用户主动调用停止录音
*/
int stop_chat_record(StopChatRecordProps *stop_record_props)
{
lingxin_log_ut(LINGXIN_DEBUG, "chat_api_stop_chat_record_called");
lingxin_chat_upload_manager_stop_record();
return 0;
}
/**
* 退出对话模式
*/
ExitChatProps get_exit_chat_default_props()
{
ExitChatProps props = {0};
props.props_init_tag = LINGXIN_PROPS_INIT_TAG;
return props;
}
int exit_chat(ExitChatProps *exit_props)
{
lingxin_log_ut(LINGXIN_DEBUG, "chat_api_exit_chat_called");
// 检测传参是否合法
if (exit_props && strcmp(exit_props->props_init_tag, LINGXIN_PROPS_INIT_TAG) != 0)
{
lingxin_log_ut(LINGXIN_ERROR, "chat_api_exit_chat_fail_props_illegal");
return -1;
}
// 构造payload
WillExitPayload will_exit_payload = {0};
if (exit_props)
{
will_exit_payload.disable_close_ws_immediately = exit_props->disable_close_ws_immediately;
}
StateEventPayload payload = {
.will_exit_payload = &will_exit_payload};
// 发送事件
lingxin_log_ut(LINGXIN_DEBUG, "chat_api_exit_chat_send_event");
state_machine_run_event_with_payload(State_Event_WillExit, &payload);
return 0;
}
/**
* 设置音量0-100
*/
int set_volume(int volume)
{
lingxin_log_ut_with_args(LINGXIN_DEBUG, "chat_api_set_volume", "%d", volume);
int real_volume = volume;
if (volume < 0)
{
real_volume = 0;
}
else if (volume > 100)
{
real_volume = 100;
}
module_bufferPlay_setVolume(real_volume);
module_local_play_set_volume(real_volume);
return 0;
}
/**
* 设置对话生命周期监听函数
*/
void lingxin_emit_chat_event(ChatLifeCycleEvent event, void *payload)
{
if (chat_event_listenner)
{
chat_event_listenner(event, payload); // 给对话生命周期监听函数加一层非空校验
}
}
/**
* 触发多模态输入
*/
int lingxin_emit_multimodal_input_event(LingxinMultimodalInputListenerProps props)
{
if (multimodal_input_listener)
{
multimodal_input_listener(props); // 给多模态输入监听函数加一层非空校验
return 0;
} else {
return -1; // 多模态输入监听函数为空,触发失败
}
}
/**
* 报告错误
*/
void lingxin_report_error(char *error_str)
{
cJSON *error_obj = cJSON_Parse(error_str);
if (!error_obj)
{
const char *error_ptr = cJSON_GetErrorPtr();
lingxin_log_error("Error json: %s", error_str);
lingxin_log_error("Error error_ptr: %s", error_ptr);
}
else
{
cJSON *payload = cJSON_GetObjectItemCaseSensitive(error_obj, "payload");
if (payload && cJSON_IsObject(payload))
{
cJSON *notice_type = cJSON_GetObjectItemCaseSensitive(payload, "notice_type");
if (notice_type && cJSON_IsString(notice_type))
{
char *notice_type_str = notice_type->valuestring;
if (strcmp(notice_type_str, "TO_USER") == 0)
{
lingxin_emit_chat_event(CHAT_LIFE_CYCLE_EVENT_ERROR, error_str);
}
else if (strcmp(notice_type_str, "TO_USER_SDK") == 0)
{
lingxin_emit_chat_event(CHAT_LIFE_CYCLE_EVENT_ERROR, error_str);
}
}
}
cJSON_Delete(error_obj);
}
}
bool lingxin_parse_server_config(char* response, LingxinServerConfig *server_config_ptr) {
char *json_str = response;
if (json_str == NULL) {
lingxin_log_error("parse response empty");
goto parse_error;
}
lingxin_log_debug("parse responese: %s", json_str);
cJSON *json_obj = cJSON_Parse(json_str);
if (!json_obj) {
const char* err = cJSON_GetErrorPtr();
lingxin_log_error("parse response error: response=%s, error_ptr=%s", json_str, err);
goto parse_error;
}
cJSON *data = cJSON_GetObjectItemCaseSensitive(json_obj, "data");
if (!data || !cJSON_IsObject(data)) {
lingxin_log_error("parse data error");
cJSON_Delete(json_obj);
goto parse_error;
}
cJSON *input_format = cJSON_GetObjectItemCaseSensitive(data, "input_format");
if (input_format && cJSON_IsString(input_format)) {
server_config_ptr->input_format = lingxin_strdup(input_format->valuestring);
}
cJSON *input_sample_rate = cJSON_GetObjectItemCaseSensitive(data, "input_sample_rate");
if (input_sample_rate && cJSON_IsNumber(input_sample_rate)) {
server_config_ptr->input_sample_rate = input_sample_rate->valueint;
}
cJSON *output_format = cJSON_GetObjectItemCaseSensitive(data, "output_format");
if (output_format && cJSON_IsString(output_format)) {
server_config_ptr->output_format = lingxin_strdup(output_format->valuestring);
}
cJSON *output_sample_rate = cJSON_GetObjectItemCaseSensitive(data, "output_sample_rate");
if (output_sample_rate && cJSON_IsNumber(output_sample_rate)) {
server_config_ptr->output_sample_rate = output_sample_rate->valueint;
}
cJSON *enable_schedule_task = cJSON_GetObjectItemCaseSensitive(data, "enable_schedule_task");
if (enable_schedule_task && cJSON_IsBool(enable_schedule_task)) {
server_config_ptr->enable_schedule_task = cJSON_IsTrue(enable_schedule_task);
}
cJSON *enable_log_upload = cJSON_GetObjectItemCaseSensitive(data, "enable_log_upload");
if (enable_log_upload && cJSON_IsBool(enable_log_upload)) {
server_config_ptr->enable_log_upload = cJSON_IsTrue(enable_log_upload);
}
lingxin_log_debug("parse success");
cJSON_Delete(json_obj);
return true;
parse_error:
return false;
}

View File

@@ -0,0 +1,772 @@
// chat_runtime_context.c
#include "chat_runtime_context.h"
#include <string.h>
#include "lingxin_log.h"
#include "lingxin_mutex.h"
#include "lingxin_memory.h"
// -------------------------------
// 全局上下文指针
// -------------------------------
static ChatStateRuntimeContext *global_config = NULL;
static ChatStateRuntimeContext *session_config = NULL;
static ChatStateRuntimeContext *temp_config = NULL;
static ChatStateRuntimeContext *current_config = NULL;
// 静态函数前置声明(避免 implicit declaration
static const char *exit_code_to_str(ExitCode code);
static const char *media_type_to_str(ChatStateMediaType type);
static const char *bool_to_str(bool b);
#define SAFE_STR(s) ((s) ? (s) : "NULL")
// log keys
#define chat_runtime_context_init_log_key "chat_runtime_context_init"
#define chat_runtime_context_update_log_key "chat_runtime_context_update"
bool set_session_context(const ChatStateRuntimeContext *src);
bool set_temp_session_context(const ChatStateRuntimeContext *src);
static void set_current_upload_type(char *task, char *user_input, char *schedule_task_id)
{
ChatStateRuntimeContext *ctx = current_config;
if (!ctx)
{
lingxin_log_ut_with_args(LINGXIN_WARN, chat_runtime_context_update_log_key, "Current context is NULL");
return;
}
if (schedule_task_id && strlen(schedule_task_id) > 0)
{
ctx->upload_type = Media_Type_TextOnly;
ctx->has_upload_type = true;
lingxin_log_ut_with_args(LINGXIN_DEBUG, chat_runtime_context_update_log_key, "Set upload_type to TextOnly due to schedule_task_id");
return;
}
if (user_input && strlen(user_input) > 0)
{
ctx->upload_type = Media_Type_TextOnly;
ctx->has_upload_type = true;
lingxin_log_ut_with_args(LINGXIN_DEBUG, chat_runtime_context_update_log_key, "Set upload_type to TextOnly due to user_input");
return;
}
if (task && (strcmp(task, "chat_multimodal") == 0))
{
ctx->upload_type = Media_Type_Multimodal;
ctx->has_upload_type = true;
lingxin_log_ut_with_args(LINGXIN_DEBUG, chat_runtime_context_update_log_key, "Set upload_type to Multimodal due to task");
return;
}
// 默认上传类型为 Audio
ctx->upload_type = Media_Type_Chat;
ctx->has_upload_type = true;
lingxin_log_ut_with_args(LINGXIN_DEBUG, chat_runtime_context_update_log_key, "Set upload_type to Chat (Audio) by default");
}
// -------------------------------
// 静态内存池(替代 malloc
// -------------------------------
// 预留 4 个上下文实例空间global, session, temp, current
static ChatStateRuntimeContext s_context_pool[4];
static bool s_context_in_use[4] = {false}; // 标记是否被占用
// 分配一个上下文实例
static ChatStateRuntimeContext *alloc_context(void)
{
for (int i = 0; i < 4; i++)
{
if (!s_context_in_use[i])
{
s_context_in_use[i] = true;
memset(&s_context_pool[i], 0, sizeof(ChatStateRuntimeContext));
return &s_context_pool[i];
}
}
return NULL;
}
static void free_string(ChatStateRuntimeContext *ctx)
{
if (!ctx)
return;
if (ctx->global_task)
{
lingxin_free(ctx->global_task);
ctx->global_task = NULL;
ctx->has_global_task = false;
}
if (ctx->current_task_id)
{
lingxin_free(ctx->current_task_id);
ctx->current_task_id = NULL;
ctx->has_current_task_id = false;
}
if (ctx->current_user_input)
{
lingxin_free(ctx->current_user_input);
ctx->current_user_input = NULL;
ctx->has_current_user_input = false;
}
if (ctx->current_schedule_id)
{
lingxin_free(ctx->current_schedule_id);
ctx->current_schedule_id = NULL;
ctx->has_current_schedule_id = false;
}
}
// 释放上下文(不清除内存,仅标记可用)
static void free_context(ChatStateRuntimeContext *ctx)
{
if (!ctx)
return;
free_string(ctx);
for (int i = 0; i < 4; i++)
{
if (ctx == &s_context_pool[i])
{
s_context_in_use[i] = false;
return;
}
}
}
// 深拷贝上下文(使用 strncpy
static bool copy_context(ChatStateRuntimeContext *dst, const ChatStateRuntimeContext *src)
{
if (!dst || !src)
return false;
// 先清空目标
memset(dst, 0, sizeof(ChatStateRuntimeContext));
// 结构体赋值(包含所有 has_xxx
*dst = *src;
return true;
}
// 字符串赋值互斥锁
static lingxin_mutex_t lingxin_taskid_mutex = NULL;
// -------------------------------
// 模块生命周期
// -------------------------------
bool init_chat_runtime_context(const ChatStateRuntimeContext *default_config)
{
// 初始化互斥锁
if (lingxin_taskid_mutex == NULL)
{
lingxin_taskid_mutex = lingxin_mutex_create();
}
// 清理旧状态
if (session_config)
free_context(session_config);
if (temp_config)
free_context(temp_config);
if (current_config)
free_context(current_config);
// 分配 global_config
global_config = alloc_context();
if (!global_config)
{
lingxin_log_ut_with_args(LINGXIN_WARN, chat_runtime_context_init_log_key, "Failed to allocate global_config");
return false;
}
// 首次初始化session context
ChatStateRuntimeContext context = {0};
set_session_context(&context);
// 拷贝默认配置
if (default_config)
{
if (!copy_context(global_config, default_config))
{
free_context(global_config);
global_config = NULL;
return false;
}
}
return true;
}
void destroy_chat_runtime_context(void)
{
// 仅标记释放,不操作堆
if (session_config)
free_context(session_config);
session_config = NULL;
if (temp_config)
free_context(temp_config);
temp_config = NULL;
if (current_config)
free_context(current_config);
current_config = NULL;
// if (global_config) free_context(global_config); global_config = NULL;
// 初始化session context
ChatStateRuntimeContext context = {0};
set_session_context(&context);
}
// -------------------------------
// session_config: 多轮会话配置
// -------------------------------
bool set_session_context(const ChatStateRuntimeContext *src)
{
if (!src)
return false;
// if (session_config->current_task_id) {
// lingxin_mutex_lock(lingxin_taskid_mutex);
// lingxin_free(session_config->current_task_id);
// session_config->current_task_id = NULL;
// session_config->has_current_task_id = false;
// lingxin_mutex_unlock(lingxin_taskid_mutex);
// }
ChatStateRuntimeContext *new_ctx = alloc_context();
if (!new_ctx || !copy_context(new_ctx, src))
{
if (new_ctx)
free_context(new_ctx);
return false;
}
if (session_config)
{
free_context(session_config);
}
session_config = new_ctx;
return true;
}
void reset_session_context(void)
{
if (session_config)
{
free_context(session_config);
session_config = NULL;
}
}
// -------------------------------
// temp_config: 临时单轮配置
// -------------------------------
bool set_temp_session_context(const ChatStateRuntimeContext *src)
{
if (!src)
return false;
// if (temp_config->current_task_id) {
// lingxin_mutex_lock(lingxin_taskid_mutex);
// lingxin_free(temp_config->current_task_id);
// temp_config->current_task_id = NULL;
// temp_config->has_current_task_id = false;
// lingxin_mutex_unlock(lingxin_taskid_mutex);
// }
ChatStateRuntimeContext *new_ctx = alloc_context();
if (!new_ctx || !copy_context(new_ctx, src))
{
if (new_ctx)
free_context(new_ctx);
return false;
}
if (temp_config)
{
free_context(temp_config);
}
temp_config = new_ctx;
return true;
}
static void clear_temp_session_context(void)
{
if (temp_config)
{
free_context(temp_config);
temp_config = NULL;
}
// 清除临时变量后,重置 temp_context
ChatStateRuntimeContext context = {0};
set_temp_session_context(&context);
}
// -------------------------------
// current_config: 当前生效上下文
// -------------------------------
bool start_new_chat_runtime_context(void)
{
// 释放旧的 current session
if (current_config)
{
free_context(current_config);
}
current_config = alloc_context();
if (!current_config)
{
lingxin_log_ut_with_args(LINGXIN_WARN, chat_runtime_context_init_log_key, "Failed to allocate current_config");
return false;
}
// 优先级temp > session > global
const ChatStateRuntimeContext *sources[] = {temp_config, session_config, global_config};
#define MERGE_FIELD(field) \
do \
{ \
if (!current_config->has_##field && src->has_##field) \
{ \
current_config->field = src->field; \
current_config->has_##field = true; \
} \
} while (0)
#define MERGE_STRING(field) \
do \
{ \
if (!current_config->has_##field && src->has_##field) \
{ \
if (current_config->field) \
{ \
lingxin_free(current_config->field); \
} \
current_config->field = lingxin_strdup(src->field); \
current_config->has_##field = true; \
} \
} while (0)
for (int i = 0; i < 3; i++)
{
const ChatStateRuntimeContext *src = sources[i];
if (!src)
continue;
MERGE_FIELD(exit_code);
MERGE_FIELD(is_normal_exit);
MERGE_FIELD(need_terminate_prompt);
MERGE_FIELD(need_continue_prompt);
MERGE_FIELD(is_vad_exit);
MERGE_FIELD(input_timeout_audio);
MERGE_FIELD(download_type);
MERGE_FIELD(upload_type);
MERGE_FIELD(single_round);
MERGE_FIELD(disable_server_vad);
MERGE_FIELD(disable_welcome_audio);
MERGE_STRING(global_task);
MERGE_STRING(current_task_id);
MERGE_STRING(current_user_input);
MERGE_STRING(current_schedule_id);
}
#undef MERGE_FIELD
#undef MERGE_STRING
// 补充默认值
if (!current_config->has_exit_code)
{
current_config->exit_code = EXIT_REASON_USER_INITIATED;
current_config->has_exit_code = true;
}
if (!current_config->has_is_normal_exit)
{
current_config->is_normal_exit = true;
current_config->has_is_normal_exit = true;
}
if (!current_config->has_download_type)
{
current_config->download_type = Media_Type_Chat;
current_config->has_download_type = true;
}
if (!current_config->has_global_task)
{
const char *default_task = "chat_vad";
if (current_config->global_task)
{
lingxin_free(current_config->global_task);
}
current_config->global_task = lingxin_strdup(default_task);
current_config->has_global_task = true;
}
// 初始化upload_type
if (!current_config->has_upload_type)
{
set_current_upload_type(current_config->global_task, current_config->current_user_input, current_config->current_schedule_id);
}
else
{
lingxin_log_debug("当前upload_type已设置为 %s", media_type_to_str(current_config->upload_type));
}
lingxin_log_debug("初始化upload_type为 %s global_task为 %s", media_type_to_str(current_config->upload_type), SAFE_STR(current_config->global_task));
// 使用完,清除临时变量
clear_temp_session_context();
print_chat_context(current_config);
return true;
}
void end_current_chat_runtime_context(void)
{
if (current_config)
{
free_context(current_config);
current_config = NULL;
}
}
// -------------------------------
// 获取当前上下文(只读)
// -------------------------------
const ChatStateRuntimeContext *get_current_context(void)
{
if (!current_config)
{
lingxin_log_ut_with_args(LINGXIN_WARN, chat_runtime_context_update_log_key, "current_config is null");
return NULL;
}
return current_config;
}
// -------------------------------
// 动态更新当前上下文
// -------------------------------
bool update_runtime_context_data(ChatStateRuntimeContext *ctx, ChatContextField field, ContextValue value)
{
if (!ctx)
{
lingxin_log_ut_with_args(LINGXIN_WARN, chat_runtime_context_update_log_key, "current_config is null, cannot update field %d", field);
return false;
}
switch (field)
{
case CTX_FIELD_EXIT_CODE:
ctx->exit_code = value.exit_code;
ctx->has_exit_code = true;
break;
case CTX_FIELD_IS_NORMAL_EXIT:
ctx->is_normal_exit = value.boolean;
ctx->has_is_normal_exit = true;
break;
case CTX_FIELD_NEED_TERMINATE_PROMPT:
ctx->need_terminate_prompt = value.boolean;
ctx->has_need_terminate_prompt = true;
break;
case CTX_FIELD_NEED_CONTINUE_PROMPT:
ctx->need_continue_prompt = value.boolean;
ctx->has_need_continue_prompt = true;
break;
case CTX_FIELD_IS_VAD_EXIT:
ctx->is_vad_exit = value.boolean;
ctx->has_is_vad_exit = true;
break;
case CTX_FIELD_INPUT_TIMEOUT_AUDIO:
ctx->input_timeout_audio = value.boolean;
ctx->has_input_timeout_audio = true;
break;
case CTX_FIELD_DOWNLOAD_TYPE:
ctx->download_type = value.media_type;
ctx->has_download_type = true;
break;
case CTX_FIELD_UPLOAD_TYPE:
ctx->upload_type = value.media_type;
ctx->has_upload_type = true;
break;
case CTX_FIELD_GLOBAL_TASK:
if (value.string)
{
if (ctx->global_task)
{
lingxin_free(ctx->global_task);
}
ctx->global_task = lingxin_strdup(value.string);
ctx->has_global_task = true;
}
else
{
if (ctx->global_task)
{
lingxin_free(ctx->global_task);
ctx->global_task = NULL;
}
ctx->has_global_task = false;
}
break;
case CTX_FIELD_CURRENT_TASK_ID:
if (value.string)
{
if (ctx->current_task_id)
{
lingxin_free(ctx->current_task_id);
}
ctx->current_task_id = lingxin_strdup(value.string);
ctx->has_current_task_id = true;
}
else
{
if (ctx->global_task)
{
lingxin_free(ctx->global_task);
ctx->global_task = NULL;
}
ctx->has_global_task = false;
}
break;
case CTX_FIELD_CURRENT_USER_INPUT:
if (value.string)
{
if (ctx->current_user_input)
{
lingxin_free(ctx->current_user_input);
}
ctx->current_user_input = lingxin_strdup(value.string);
ctx->has_current_user_input = true;
}
else
{
if (ctx->current_user_input)
{
lingxin_free(ctx->current_user_input);
ctx->current_user_input = NULL;
}
ctx->has_current_user_input = false;
}
break;
case CTX_FIELD_CURRENT_SCHEDULE_ID:
if (value.string)
{
if (ctx->current_schedule_id)
{
lingxin_free(ctx->current_schedule_id);
}
ctx->current_schedule_id = lingxin_strdup(value.string);
ctx->has_current_schedule_id = true;
}
else
{
if (ctx->current_schedule_id)
{
lingxin_free(ctx->current_schedule_id);
ctx->current_schedule_id = NULL;
}
ctx->has_current_schedule_id = false;
}
break;
case CTX_FIELD_SINGLE_ROUND:
ctx->single_round = value.boolean;
ctx->has_single_round = true;
break;
case CTX_FIELD_DISABLE_SERVER_VAD:
ctx->disable_server_vad = value.boolean;
ctx->has_disable_server_vad = true;
break;
case CTX_FIELD_DISABLE_WELCOME_AUDIO:
ctx->disable_welcome_audio = value.boolean;
ctx->has_disable_welcome_audio = true;
break;
default:
lingxin_log_ut_with_args(LINGXIN_WARN, chat_runtime_context_update_log_key, "Invalid field ID: %d", field);
return false;
}
return true;
}
// 更新临时上下文
bool update_temp_context(ChatContextField field, ContextValue value)
{
if (!temp_config)
{
lingxin_log_debug("临时上下文未初始化,自动创建一个新的临时上下文");
ChatStateRuntimeContext ctx = {0};
set_temp_session_context(&ctx);
}
return update_runtime_context_data(temp_config, field, value);
}
// 更新会话上下文
bool update_sesseion_context(ChatContextField field, ContextValue value)
{
if (!session_config)
{
lingxin_log_debug("会话上下文未初始化,自动创建一个新的会话上下文");
ChatStateRuntimeContext ctx = {0};
set_session_context(&ctx);
}
return update_runtime_context_data(session_config, field, value);
}
// 更新当前上下文
bool update_current_context(ChatContextField field, ContextValue value)
{
return update_runtime_context_data(current_config, field, value);
}
/**
* 工具函数
*/
static char *get_string_with_media_type(ChatStateMediaType type)
{
if (type == Media_Type_Chat)
{
return "voice";
}
else if (type == Media_Type_Multimodal)
{
return "no_voice";
}
return "";
}
// start_task指令根据端侧的type置换为服务端需要的字符串
char *get_input_type_string()
{
if (!current_config || !current_config->has_upload_type)
{
return "";
}
else
{
return get_string_with_media_type(current_config->upload_type);
}
}
// start_task指令根据端侧的type置换为服务端需要的字符串
char *get_output_type_string()
{
if (!current_config || !current_config->has_download_type)
{
return "";
}
else
{
return get_string_with_media_type(current_config->download_type);
}
}
// -------------------------------
// 调试打印函数
// -------------------------------
void print_chat_context(const ChatStateRuntimeContext *ctx)
{
if (!ctx)
{
lingxin_log_debug("[ChatContext] NULL context pointer.\n");
return;
}
lingxin_log_debug("[ChatStateRuntimeContext] Current Context Dump:\n");
lingxin_log_debug("------------------------------------------------\n");
#define PRINT_FIELD(fmt, name, value_expr, has_field, comment) \
do \
{ \
if (has_field) \
{ \
lingxin_log_debug("%-30s = " fmt " // %s\n", #name, value_expr, comment); \
} \
else \
{ \
lingxin_log_debug("%-30s = (not set) // %s\n", #name, comment); \
} \
} while (0)
PRINT_FIELD("%s", exit_code, exit_code_to_str(ctx->exit_code), ctx->has_exit_code, "退出时的错误信息");
PRINT_FIELD("%s", is_normal_exit, bool_to_str(ctx->is_normal_exit), ctx->has_is_normal_exit, "是否是主动退出对话模式");
PRINT_FIELD("%s", need_terminate_prompt, bool_to_str(ctx->need_terminate_prompt), ctx->has_need_terminate_prompt, "打断唤醒是否需要播放提示音");
PRINT_FIELD("%s", need_continue_prompt, bool_to_str(ctx->need_continue_prompt), ctx->has_need_continue_prompt, "连续对话前是否需要播放提示音");
PRINT_FIELD("%s", is_vad_exit, bool_to_str(ctx->is_vad_exit), ctx->has_is_vad_exit, "是否是vad退出");
PRINT_FIELD("%s", input_timeout_audio, bool_to_str(ctx->input_timeout_audio), ctx->has_input_timeout_audio, "是否正在播放本地音频");
PRINT_FIELD("%s", download_type, media_type_to_str(ctx->download_type), ctx->has_download_type, "当前播放的类型");
PRINT_FIELD("%s", upload_type, media_type_to_str(ctx->upload_type), ctx->has_upload_type, "当前上传的类型");
PRINT_FIELD("%s", global_task, SAFE_STR(ctx->global_task), ctx->has_global_task, "全局任务描述");
PRINT_FIELD("%s", current_task_id, SAFE_STR(ctx->current_task_id), ctx->has_current_task_id, "全局任务ID描述");
PRINT_FIELD("%s", single_round, bool_to_str(ctx->single_round), ctx->has_single_round, "本轮对话是否为单轮对话");
PRINT_FIELD("%s", disable_server_vad, bool_to_str(ctx->disable_server_vad), ctx->has_disable_server_vad, "本轮对话是否禁用云端VAD");
PRINT_FIELD("%s", current_user_input, SAFE_STR(ctx->current_user_input), ctx->has_current_user_input, "当前用户输入文本");
PRINT_FIELD("%s", current_schedule_id, SAFE_STR(ctx->current_schedule_id), ctx->has_current_schedule_id, "当前定时任务调度任务ID");
PRINT_FIELD("%s", disable_welcome_audio, bool_to_str(ctx->disable_welcome_audio), ctx->has_disable_welcome_audio, "本轮对话是否禁用欢迎语");
#undef PRINT_FIELD
lingxin_log_debug("------------------------------------------------\n");
}
// -------------------------------
// 辅助函数(放在最后)
// -------------------------------
static const char *exit_code_to_str(ExitCode code)
{
switch (code)
{
case EXIT_REASON_USER_INITIATED:
return "EXIT_REASON_USER_INITIATED";
case EXIT_REASON_WEBSOCKET_DISCONNECT:
return "EXIT_REASON_WEBSOCKET_DISCONNECT";
case EXIT_REASON_WEBSOCKET_CONNECTION_FAILED:
return "EXIT_REASON_WEBSOCKET_CONNECTION_FAILED";
case EXIT_REASON_NO_INPUT_TIMEOUT:
return "EXIT_REASON_NO_INPUT_TIMEOUT";
case EXIT_REASON_EXCEPTION_TIMEOUT:
return "EXIT_REASON_EXCEPTION_TIMEOUT";
default:
return "UNKNOWN_EXIT_CODE";
}
}
static const char *media_type_to_str(ChatStateMediaType type)
{
switch (type)
{
case Media_Type_Chat:
return "Media_Type_Chat";
case Media_Type_TextOnly:
return "Media_Type_TextOnly";
case Media_Type_Multimodal:
return "Media_Type_Multimodal";
default:
return "UNKNOWN_MEDIA";
}
}
static const char *bool_to_str(bool b)
{
return b ? "true" : "false";
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,105 @@
#include "lingxin_local_player.h"
#include "lingxin_log.h"
#include "chat_state_machine.h"
static int initial_volume = 80;
static lingxin_local_player_t welcome_audio_player = NULL;
static lingxin_local_player_t terminate_audio_player = NULL;
static lingxin_local_player_t continue_audio_player = NULL;
static char *welcome_audio_path = NULL;
static char *terminate_audio_path = NULL;
static char *continue_audio_path = NULL;
void module_local_play_set_welcome_audio_path(char *audio_path) {
welcome_audio_path = audio_path;
}
void module_local_play_set_terminate_audio_path(char *audio_path) {
terminate_audio_path = audio_path;
}
void module_local_play_set_continue_audio_path(char *audio_path) {
continue_audio_path = audio_path;
}
static void create_player_and_start(lingxin_local_player_t *player, char *audio_path, lingxin_local_player_callback_t callback) {
if (audio_path) {
lingxin_local_player_play_param_t param = {
.audio_path = audio_path,
.initial_volume = initial_volume,
};
lingxin_log_ut(LINGXIN_DEBUG, "local_player_adapter_create");
*player = lingxin_local_player_create();
lingxin_log_ut(LINGXIN_DEBUG, "local_player_adapter_play");
lingxin_local_player_play(*player, &param, callback);
} else {
callback(0); // 如果audio_path为空则直接执行回调函数
}
}
static void destory_player(lingxin_local_player_t *player) {
if (*player) {
lingxin_log_ut(LINGXIN_DEBUG, "local_player_adapter_destory");
lingxin_local_player_destory(*player);
*player = NULL;
}
}
static void play_welcome_audio_callback(int result) {
lingxin_log_ut_with_args(LINGXIN_DEBUG, "local_player_manager_welcome_audio_play_end", "%d", result);
destory_player(&welcome_audio_player);
state_machine_run_event(State_Event_Welcome_Play_End);
}
static void play_terminate_audio_callback(int result) {
lingxin_log_ut_with_args(LINGXIN_DEBUG, "local_player_manager_terminate_audio_play_end", "%d", result);
destory_player(&terminate_audio_player);
state_machine_run_event(State_Event_TerminatePrompt_PlayEnd);
}
static void play_continue_audio_callback(int result) {
lingxin_log_ut_with_args(LINGXIN_DEBUG, "local_player_manager_continue_audio_play_end", "%d", result);
destory_player(&continue_audio_player);
state_machine_run_event(State_Event_ContinuePrompt_PlayEnd);
}
// 播放欢迎语
void module_local_play_welcome_audio() {
if (welcome_audio_player) {
lingxin_log_ut(LINGXIN_WARN, "local_player_manager_welcome_audio_already_start");
return;
}
lingxin_log_ut_with_args(LINGXIN_DEBUG, "local_player_manager_welcome_audio_play_start", "%s", welcome_audio_path);
create_player_and_start(&welcome_audio_player, welcome_audio_path, play_welcome_audio_callback);
}
// 播放打断音频
void module_local_play_terminate_audio() {
if (terminate_audio_player) {
lingxin_log_ut(LINGXIN_WARN, "local_player_manager_terminate_audio_already_start");
return;
}
lingxin_log_ut_with_args(LINGXIN_DEBUG, "local_player_manager_terminate_audio_play_start", "%s", terminate_audio_path);
create_player_and_start(&terminate_audio_player, terminate_audio_path, play_terminate_audio_callback);
}
// 播放连续对话的音频
void module_local_play_continue_audio() {
lingxin_log_ut_with_args(LINGXIN_DEBUG, "local_player_manager_continue_audio_play_start", "%s", continue_audio_path);
create_player_and_start(&continue_audio_player, continue_audio_path, play_continue_audio_callback);
}
// 设置本地播放音频的音量
void module_local_play_set_volume(int volume)
{
lingxin_log_ut_with_args(LINGXIN_DEBUG, "local_player_manager_set_volume", "%d", volume);
initial_volume = volume;
if (welcome_audio_player) {
lingxin_log_ut(LINGXIN_DEBUG, "local_player_adapter_set_volume welcome_audio_player");
lingxin_local_player_set_volume(welcome_audio_player, volume);
}
if (terminate_audio_player) {
lingxin_log_ut(LINGXIN_DEBUG, "local_player_adapter_set_volume terminate_audio_player");
lingxin_local_player_set_volume(terminate_audio_player, volume);
}
if (continue_audio_player) {
lingxin_log_ut(LINGXIN_DEBUG, "local_player_adapter_set_volume continue_audio_player");
lingxin_local_player_set_volume(continue_audio_player, volume);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,527 @@
#include <stdlib.h>
#include "lingxin_cbuffer.h"
#include "lingxin_mutex.h"
#include "lingxin_semaphore.h"
#include "lingxin_thread.h"
#include "lingxin_recorder.h"
#include "chat_state_machine.h"
#include "lingxin_log.h"
#include <stdio.h>
#include "lingxin_system_time.h"
#include "lingxin_memory.h"
#include "upload_record_interface.h"
#include "lingxin_test_runner.h"
#include "lingxin_recorder_manager.h"
#include "lingxin_trace.h"
#define LINGXIN_RECORDER_CALLBACK_LIST_MAX_LENGTH 5
static lingxin_mutex_t send_thread_mutex = NULL;
static lingxin_mutex_t record_close_mutex = NULL;
static lingxin_mutex_t record_close_callback_list_mutex = NULL;
static lingxin_recorder_t *lingxin_recorder = NULL;
static int recorder_ready = 0;
static int server_ready = 0;
static int send_flag = 0;
static LingxinCircularBuffer* send_cbuf = NULL;
static lingxin_semaphore_t send_r_sem = NULL;
static int send_thread_running = 0;
static lingxin_tid_t send_thread_pid = 0;
static int* send_thread_pid_ptr = NULL;
static int send_thread_stop_flag = 0; // 0: 不退出 1: 立刻退出 2: 等待发完再退出
static int send_uni_size = 0;
// Debug/embedded-friendly default: avoid allocating ~128KB buffer during init.
static int send_cbuf_scale = 16;
typedef void (*SendThreadStopCallback)();
static void inner_record_init();
static void record_open_callback(int result);
static void record_close_callback_for_start(int result);
static void record_close_callback_for_stop(int result);
static void record_close_callback_for_stop_wait_send_left(int result);
static void* send_record(void *arg);
static int stop_send_thread(int wait_send);
static int start_send_thread();
static int get_frame_size();
static void set_send_flag();
/****************** 录音模块初始化 ******************/
int module_record_manager_init(int custom_send_uni_size, int custom_send_cbuf_scale) {
lingxin_log_ut_with_args(LINGXIN_DEBUG, "recorder_manager_init", "send_uni_size is %d, send_cbuf_scale is %d", custom_send_uni_size, custom_send_cbuf_scale);
if (!send_thread_mutex) {
send_thread_mutex = lingxin_mutex_create();
}
if (!record_close_mutex) {
record_close_mutex = lingxin_mutex_create();
}
if (!record_close_callback_list_mutex) {
record_close_callback_list_mutex = lingxin_mutex_create();
}
if (custom_send_uni_size) {
send_uni_size = custom_send_uni_size;
}
if (custom_send_cbuf_scale) {
send_cbuf_scale = custom_send_cbuf_scale;
}
send_uni_size = get_frame_size();
lingxin_checkpoint_report_with_int("lingxin_test_send_uni_size_check", send_uni_size);
lingxin_checkpoint_report_with_int("lingxin_test_send_cbuf_scale_check", send_cbuf_scale);
if (!send_cbuf) {
send_cbuf = lingxin_cbuffer_init(send_cbuf_scale, send_uni_size);
if (send_cbuf == NULL) {
lingxin_log_ut(LINGXIN_ERROR, "recorder_manager_init_fail");
return -1;
}
}
lingxin_log_ut(LINGXIN_DEBUG, "recorder_manager_init_success");
return 0;
}
/****************** 录音器开启 ******************/
LingxinRecorderStartCallback temp_start_callback = NULL;
static void run_start_callback(bool is_success) {
if (temp_start_callback) {
temp_start_callback(is_success);
temp_start_callback = NULL;
} else {
lingxin_log_ut(LINGXIN_ERROR, "recorder_manager_run_start_callback_fail");
}
}
int module_record_start(LingxinRecorderStartCallback start_callback) {
lingxin_log_ut(LINGXIN_DEBUG, "recorder_manager_recorder_start_called");
lingxin_log_debug("recorder_manager_recorder_start_before_close");
lingxin_trace_set("rec:start_enter");
temp_start_callback = start_callback;
recorder_ready = 0;
server_ready = 0;
set_send_flag();
// [开启录音-1]关闭可能存在的录音
lingxin_trace_set("rec:before_close_lock");
lingxin_mutex_lock(record_close_mutex);
lingxin_trace_set("rec:after_close_lock");
if (lingxin_recorder == NULL) {
lingxin_trace_set("rec:no_prev_recorder");
record_close_callback_for_start(0);
} else {
lingxin_log_ut(LINGXIN_DEBUG, "recorder_adapter_recorder_close_before_recorder_start");
lingxin_trace_set("rec:close_prev_recorder");
lingxin_recorder_close(lingxin_recorder, record_close_callback_for_start);
}
lingxin_trace_set("rec:start_return");
return 0;
}
static void record_close_callback_for_start(int result) {
lingxin_log_ut(LINGXIN_DEBUG, "recorder_adapter_recorder_close_callback_before_recorder_start");
lingxin_trace_set(result == 0 ? "rec:close_cb_start_ok" : "rec:close_cb_start_fail");
if (result == 0) {
if (lingxin_recorder != NULL) {
lingxin_log_ut(LINGXIN_DEBUG, "record_adapter_recorder_destory_before_recorder_start");
lingxin_recorder_destroy(lingxin_recorder);
lingxin_recorder = NULL;
lingxin_log_ut(LINGXIN_DEBUG, "recorder_manager_destory_recorder_success_before_recorder_start");
}
lingxin_mutex_unlock(record_close_mutex);
// [开启录音-2]退出可能存在的录音发送线程
int res = stop_send_thread(0);
if (res == 0) {
lingxin_log_ut(LINGXIN_DEBUG, "recorder_manager_stop_send_thread_success_before_recorder_start");
// [开启录音-3]初始化新的录音
inner_record_init();
} else {
lingxin_log_ut(LINGXIN_ERROR, "recorder_manager_stop_send_thread_fail_before_recorder_start");
run_start_callback(false);
}
} else {
lingxin_log_ut(LINGXIN_ERROR, "recorder_manager_close_recorder_fail_before_recorder_start");
lingxin_mutex_unlock(record_close_mutex);
run_start_callback(false);
}
}
static void inner_record_init() {
lingxin_log_debug("recorder_manager_inner_record_init_begin");
lingxin_trace_set("chat:recorder_inner_init");
// [开启录音-3.1]开启新的录音发送线程
int res = start_send_thread();
if (res == 0) {
lingxin_log_ut(LINGXIN_DEBUG, "recorder_manager_start_send_thread_success");
// [开启录音-3.2]开启录音
lingxin_mutex_lock(record_close_mutex);
recorder_ready = 1;
set_send_flag();
lingxin_recorder = lingxin_recorder_create();
lingxin_recorder_open_param_t props = {
.frame_size = send_uni_size,
};
lingxin_log_ut(LINGXIN_DEBUG, "recorder_adapter_recorder_open");
lingxin_trace_set("chat:recorder_before_open");
lingxin_log_debug("recorder_manager_before_recorder_open frame_size=%d", send_uni_size);
lingxin_recorder_open(lingxin_recorder, &props, record_open_callback);
} else {
lingxin_log_ut(LINGXIN_ERROR, "recorder_manager_start_send_thread_fail");
run_start_callback(false);
}
}
static void record_open_callback(int result) {
lingxin_log_ut(LINGXIN_DEBUG, "recorder_adapter_recorder_open_callback");
lingxin_trace_set(result == 0 ? "rec:open_cb_ok" : "rec:open_cb_fail");
if (result == 0) {
lingxin_trace_set("chat:recorder_open_ok");
lingxin_log_ut(LINGXIN_DEBUG, "recorder_manager_open_recorder_success");
// [开启录音-4]通知状态机录音模块启动已完成
lingxin_mutex_unlock(record_close_mutex);
run_start_callback(true);
} else {
lingxin_trace_set("chat:recorder_open_fail");
lingxin_log_ut(LINGXIN_ERROR, "recorder_manager_open_recorder_fail");
lingxin_mutex_unlock(record_close_mutex);
run_start_callback(false);
}
}
/****************** 录音开始发送 ******************/
LingxinRecorderDataCallback temp_data_callback = NULL;
void module_record_start_send(LingxinRecorderDataCallback data_callback) {
lingxin_log_ut(LINGXIN_DEBUG, "record_manager_start_send");
temp_data_callback = data_callback;
server_ready = 1;
set_send_flag();
}
/****************** 录音器结束 ******************/
static LingxinRecorderStopCallback temp_stop_callback_list[LINGXIN_RECORDER_CALLBACK_LIST_MAX_LENGTH];
static int temp_stop_callback_list_length = 0;
static void run_stop_callback(bool is_success) {
while (1) {
lingxin_mutex_lock(record_close_callback_list_mutex);
if (!temp_stop_callback_list_length) {
lingxin_log_ut(LINGXIN_ERROR, "recorder_manager_run_stop_callback_fail");
lingxin_mutex_unlock(record_close_callback_list_mutex);
return;
}
LingxinRecorderStopCallback currrent_stop_callback = temp_stop_callback_list[0];
for (int i = 1; i < temp_stop_callback_list_length; i++) {
temp_stop_callback_list[i - 1] = temp_stop_callback_list[i];
}
temp_stop_callback_list[temp_stop_callback_list_length - 1] = NULL;
temp_stop_callback_list_length--;
lingxin_mutex_unlock(record_close_callback_list_mutex);
currrent_stop_callback(is_success);
}
}
void module_record_stop(int wait_send_left, LingxinRecorderStopCallback stop_callback) {
lingxin_log_ut_with_args(LINGXIN_DEBUG, "recorder_manager_stop_called", "wait_send_left = %d", wait_send_left);
if (stop_callback) {
if (temp_stop_callback_list_length >= LINGXIN_RECORDER_CALLBACK_LIST_MAX_LENGTH) {
lingxin_log_ut(LINGXIN_WARN, "recorder_manager_stop_callback_list_full");
} else {
lingxin_mutex_lock(record_close_callback_list_mutex);
temp_stop_callback_list[temp_stop_callback_list_length++] = stop_callback;
lingxin_mutex_unlock(record_close_callback_list_mutex);
}
}
// [正常结束录音-1]关闭可能存在的录音
lingxin_recorder_callback_t callback = wait_send_left
? record_close_callback_for_stop_wait_send_left
: record_close_callback_for_stop;
lingxin_mutex_lock(record_close_mutex);
if (lingxin_recorder == NULL) {
callback(0);
} else {
lingxin_log_ut(LINGXIN_DEBUG, "recorder_adapter_recorder_close_when_stop");
lingxin_recorder_close(lingxin_recorder, callback);
}
}
static void record_close_callback_for_stop(int result) {
lingxin_log_ut(LINGXIN_DEBUG, "recorder_adapter_recorder_close_callback_when_stop");
if (result == 0) {
if (lingxin_recorder != NULL) {
lingxin_log_ut(LINGXIN_DEBUG, "recorder_adapter_recorder_destroy_when_stop");
lingxin_recorder_destroy(lingxin_recorder);
lingxin_recorder = NULL;
lingxin_log_ut(LINGXIN_DEBUG, "recorder_manager_destory_recorder_success_when_stop");
}
lingxin_mutex_unlock(record_close_mutex);
// [正常结束录音-2]退出可能存在的录音发送线程
int res = stop_send_thread(0);
if (res == 0) {
lingxin_log_ut(LINGXIN_DEBUG, "recorder_manager_stop_send_thread_success_when_stop");
// [正常结束录音-3]通知状态机录音模块正常结束已完成
run_stop_callback(true);
} else {
lingxin_log_ut(LINGXIN_ERROR, "recorder_manager_stop_send_thread_fail_when_stop");
run_stop_callback(false);
}
} else {
lingxin_log_ut(LINGXIN_ERROR, "recorder_manager_close_recorder_fail_when_stop");
lingxin_mutex_unlock(record_close_mutex);
run_stop_callback(false);
}
}
static void record_close_callback_for_stop_wait_send_left(int result) {
lingxin_log_ut(LINGXIN_DEBUG, "recorder_adapter_recorder_close_callback_when_stop_wait");
if (result == 0) {
if (lingxin_recorder != NULL) {
lingxin_log_ut(LINGXIN_DEBUG, "recorder_adapter_recorder_destroy_when_stop_wait");
lingxin_recorder_destroy(lingxin_recorder);
lingxin_recorder = NULL;
lingxin_log_ut(LINGXIN_DEBUG, "recorder_manager_destory_recorder_success_when_stop_wait");
}
lingxin_mutex_unlock(record_close_mutex);
// [正常结束录音-2]退出可能存在的录音发送线程,但等待发完当前内容
int res = stop_send_thread(1);
if (res == 0) {
lingxin_log_ut(LINGXIN_DEBUG, "recorder_manager_stop_send_thread_success_when_stop_wait");
// [正常结束录音-3]通知状态机录音模块正常结束已完成
// state_machine_run_event(State_Event_Record_Stop);
} else {
lingxin_log_ut(LINGXIN_ERROR, "recorder_manager_stop_send_thread_fail_when_stop_wait");
run_stop_callback(false);
}
} else {
lingxin_log_ut(LINGXIN_ERROR, "recorder_manager_close_recorder_fail_when_stop_wait");
lingxin_mutex_unlock(record_close_mutex);
run_stop_callback(false);
}
}
/****************** 发送线程(读缓存) ******************/
static int start_send_thread() {
lingxin_log_ut(LINGXIN_DEBUG, "recorder_thread_start_called");
lingxin_mutex_lock(send_thread_mutex);
// 0. 检查发送线程是否已经运行
if (send_thread_running) {
lingxin_log_ut_with_args(LINGXIN_ERROR, "recorder_thread_start_fail", "send thread is already running.");
goto start_send_thread_err;
}
// 1. 清空录音发送缓冲区
if (send_cbuf == NULL) {
lingxin_log_ut_with_args(LINGXIN_ERROR, "recorder_thread_start_fail", "fail to get send buffer.");
goto start_send_thread_err;
}
lingxin_cbuffer_reset(send_cbuf);
// 2. 初始化信号量
lingxin_log_ut(LINGXIN_DEBUG, "recorder_adapter_semaphore_create");
send_r_sem = lingxin_semaphore_create(0);
if (send_r_sem == NULL) {
lingxin_log_ut_with_args(LINGXIN_ERROR, "recorder_thread_start_fail", "fail to initialize send semaphore.");
goto start_send_thread_err;
}
// 3. 重置flag
send_thread_stop_flag = 0;
// 4. 创建录音发送线程
long now_time = lingxin_get_timestamp_s();
char name[16];
snprintf(name, sizeof(name), "s_%ld", now_time);
lingxin_thread_param_t thread_param = {
.priority = 16,
.stack_size = 4096*2,
.name = name,
};
if (!send_thread_pid_ptr) {
send_thread_pid_ptr = (int*)lingxin_malloc(sizeof(int));
}
lingxin_log_ut(LINGXIN_DEBUG, "recorder_adapter_thread_create");
int ret = lingxin_thread_create(send_thread_pid_ptr, &thread_param, send_record, NULL);
if (ret == 0) { // 检查线程创建是否成功
send_thread_pid = *send_thread_pid_ptr;
lingxin_log_ut_with_args(LINGXIN_DEBUG, "recorder_thread_start_success", "pid is %d", send_thread_pid);
send_thread_running = 1;
} else {
lingxin_log_ut_with_args(LINGXIN_ERROR, "recorder_thread_start_fail", "fail to create send thread, ret is %d.", ret);
send_thread_pid = 0;
send_thread_running = 0;
goto start_send_thread_err;
}
lingxin_mutex_unlock(send_thread_mutex);
return 0;
start_send_thread_err:
lingxin_mutex_unlock(send_thread_mutex);
return -1;
}
static int stop_send_thread(int wait_send) {
lingxin_log_ut(LINGXIN_DEBUG, "recorder_thread_stop_called");
lingxin_mutex_lock(send_thread_mutex);
if (send_thread_running) {
lingxin_log_ut_with_args(LINGXIN_DEBUG, "recorder_thread_stop_start", "wait_send is %d", wait_send);
if (wait_send) {
send_thread_stop_flag = 2;
if (send_flag) {
lingxin_semaphore_set_value(send_r_sem, 0);
lingxin_semaphore_post(send_r_sem);
}
goto finish_stop_send_thread;
} else {
send_thread_stop_flag = 1;
lingxin_semaphore_set_value(send_r_sem, 0);
lingxin_semaphore_post(send_r_sem);
int retry = 0;
while (send_thread_running && retry < 500) {
lingxin_thread_sleep(10);
retry++;
}
if (retry >= 500) {
lingxin_log_ut_with_args(LINGXIN_ERROR, "recorder_thread_stop_fail", "stop send thread timeout.");
if (send_thread_pid) {
send_thread_running = 0;
send_thread_pid_ptr = (int*)lingxin_malloc(sizeof(int));
send_thread_pid = 0;
}
} else {
lingxin_log_ut(LINGXIN_DEBUG, "recorder_thread_stop_success");
if (send_thread_pid) {
lingxin_log_ut(LINGXIN_DEBUG, "recorder_adapter_thread_destroy");
lingxin_thread_destroy(send_thread_pid, LINGXIN_THREAD_DESTROY_WAIT);
lingxin_log_ut(LINGXIN_DEBUG, "recorder_adapter_thread_destroy_end");
send_thread_pid = 0;
}
}
lingxin_log_ut(LINGXIN_DEBUG, "recorder_thread_destroy_success");
}
}
if (send_r_sem) {
lingxin_log_ut(LINGXIN_DEBUG, "recorder_adapter_semaphore_destroy");
lingxin_semaphore_destroy(send_r_sem);
send_r_sem = NULL;
}
finish_stop_send_thread:
lingxin_mutex_unlock(send_thread_mutex);
return 0;
}
static void* send_record(void *arg) {
lingxin_log_ut(LINGXIN_DEBUG, "recorder_thread_entry_function_called");
lingxin_tid_t pid;
int ret;
char *buf = lingxin_malloc(send_uni_size);
if (buf == NULL) {
lingxin_log_ut_with_args(LINGXIN_ERROR, "recorder_thread_entry_function_fail", "buf malloc failed");
return NULL;
}
int count = 0;
while(1) {
lingxin_semaphore_pend(send_r_sem, 0);
pid = send_thread_pid;
if (send_thread_stop_flag == 1) {
lingxin_log_ut_with_args(LINGXIN_DEBUG, "recorder_thread_receive_stop_signal", "flag is %d", send_thread_stop_flag);
break;
}
// 读取buffer的大小和写入一致不存在写入数据不到current_frame_size的情况
while(lingxin_cbuffer_size(send_cbuf) >= 1) {
if (send_thread_stop_flag == 1) {
lingxin_log_ut_with_args(LINGXIN_DEBUG, "recorder_thread_receive_stop_signal", "flag is %d", send_thread_stop_flag);
goto send_record_thread_exit;
}
ret = lingxin_cbuffer_get(send_cbuf, buf);
if(ret < 0){
lingxin_log_ut_with_args(LINGXIN_ERROR, "recorder_thread_read_data_error", "ret=%d", ret);
continue;
}
// lingxin_log_debug("recorder_thread_send_data: length is %d", send_uni_size);
if (temp_data_callback) {
temp_data_callback(buf, send_uni_size, ++count);
}
if (pid != send_thread_pid) {
lingxin_log_debug("[send_record]清除游离线程");
if (buf) {
lingxin_free(buf);
buf = NULL;
}
lingxin_log_debug("[send_record]清除游离线程,释放内存");
while (1) {
lingxin_thread_sleep(60000);
}
return NULL;
}
}
if (send_thread_stop_flag == 1 || send_thread_stop_flag == 2) {
lingxin_log_ut_with_args(LINGXIN_DEBUG, "recorder_thread_receive_stop_signal", "flag is %d", send_thread_stop_flag);
break;
}
}
send_record_thread_exit:
if (buf) {
lingxin_free(buf);
buf = NULL;
}
if (send_r_sem) {
lingxin_log_ut(LINGXIN_DEBUG, "recorder_adapter_semaphore_destroy");
lingxin_semaphore_destroy(send_r_sem);
send_r_sem = NULL;
}
pid = send_thread_pid; // 在修改线程运行状态前先保存pid不要放后面的if语句里因为一旦send_thread_running设置为0send_thread_pid就可能会被置为NULL
send_thread_running = 0;
// 如果此时是等待发完当前内容,则需要通知状态机录音模块正常结束已完成
if (send_thread_stop_flag == 2) {
send_thread_pid = 0; // 将send_thread_pid置为NULL后续用pid销毁自己防止外部回调重复新建线程而冲突
lingxin_log_ut_with_args(LINGXIN_DEBUG, "recorder_thread_wait_send_complete", "send_thread_pid=%d, pid=%d", send_thread_pid, pid);
run_stop_callback(true);
if (pid) {
lingxin_log_ut(LINGXIN_DEBUG, "recorder_adapter_thread_destroy_self");
lingxin_thread_destroy(pid, LINGXIN_THREAD_DESTROY_WAIT);
lingxin_log_ut(LINGXIN_DEBUG, "recorder_adapter_thread_destroy_self_end");
}
}
return NULL;
}
/****************** 接收录音数据写入缓存(写缓存) ******************/
void lingxin_process_record_data(void *data, int len) {
if (!send_thread_running) {
lingxin_log_ut_with_args(LINGXIN_ERROR, "recorder_data_process_fail", "send_thread is not running.");
return;
}
if (send_r_sem == NULL) {
lingxin_log_ut_with_args(LINGXIN_ERROR, "recorder_data_process_fail", "send_r_sem is NULL.");
return;
}
if (data == NULL) {
lingxin_log_ut_with_args(LINGXIN_ERROR, "recorder_data_process_fail", "data is NULL.");
return;
}
if (send_cbuf == NULL) {
lingxin_log_ut_with_args(LINGXIN_ERROR, "recorder_data_process_fail", "send_cbuf is NULL.");
return;
}
lingxin_cbuffer_put(send_cbuf, data);
// lingxin_log_debug("recorder_data_process_success: len=%d, send_flag=%d", len, send_flag);
if (send_flag) {
lingxin_semaphore_set_value(send_r_sem, 0);
lingxin_semaphore_post(send_r_sem);
}
}
/****************** 相关参数获取 ******************/
// 获取帧大小
static int get_frame_size() {
if (send_uni_size <= 0) {
return lingxin_recorder_get_size_per_ms() * 20; // 默认20ms写一次
} else {
return send_uni_size;
}
}
static void set_send_flag() {
if (recorder_ready && server_ready) {
send_flag = 1;
if (send_thread_stop_flag == 2) {
// 录音已经停止,需要通知发送线程发送剩余内容
lingxin_semaphore_set_value(send_r_sem, 0);
lingxin_semaphore_post(send_r_sem);
}
} else {
send_flag = 0;
}
}

View File

@@ -0,0 +1,37 @@
// audio_player_adapter.c
#include "download_audio_adapter.h"
#include "audio_buffer_play.h"
#include "download_audio_play_interface.h"
static void audio_init(PlaybackEventHandler callback, void *user_data)
{
// 把回调传给底层播放器
module_bufferPlay_audioInit(callback, user_data);
}
static void audio_feedData(const void *buf, int len)
{
module_bufferPlay_data((void *)buf, len);
}
static void audio_endOfStream(void)
{
module_bufferPlay_audioEnd();
}
static void audio_terminate(void)
{
module_bufferPlay_terminate();
}
static void audio_setVolume(int volume)
{
module_bufferPlay_setVolume(volume);
}
const PlaybackInterface g_audioPlayer = {
.init = audio_init,
.feedData = audio_feedData,
.endOfStream = audio_endOfStream,
.terminate = audio_terminate,
.setVolume = audio_setVolume};

View File

@@ -0,0 +1,64 @@
#include "lingxin_download_stream_control_manager.h"
#include "lingxin_log.h"
#include "lingxin_semaphore.h"
#include <stdio.h>
#include <stdbool.h>
// 使用 lingxin_semaphore_t 类型替代 OS_SEM
static lingxin_semaphore_t w_sem = NULL;
static bool is_sem_initialized = false;
/**
* 信号量控制
* 使用方法:
* 用于控制服务端数据推送,当执行 pend 之后服务端无法再发送webSocket指令回来
* 执行 post 之后服务端即可发送webSocket指令
* 使用场景:
* 控制数据在首次初始化流式播放时候由于初始化线程可能稍微比较耗时需要在流式播放模块初始化结束之后才可以让服务端发送mp3数据
*/
// 信号量删除
void lingxin_websocket_control_del(void)
{
if (is_sem_initialized && w_sem != NULL) {
lingxin_semaphore_destroy(w_sem);
w_sem = NULL;
is_sem_initialized = false;
}
}
// 信号量创建
void lingxin_websocket_control_create(void)
{
if (!is_sem_initialized) {
w_sem = lingxin_semaphore_create(0); // 初始计数为 0
if (w_sem != NULL) {
is_sem_initialized = true;
} else {
lingxin_log_error("Failed to create websocket control semaphore");
}
}
}
void lingxin_unlock_write_websocket_controle(void)
{
lingxin_log_debug("chat内核 zzz: 打开写");
if (w_sem != NULL) {
// 先将信号量值设为 0确保状态干净然后 post 使其变为 1允许通过 pend
lingxin_semaphore_set_value(w_sem, 0);
lingxin_semaphore_post(w_sem);
}
}
void lingxin_lock_write_websocket_control(void)
{
lingxin_log_debug("chat内核 zzz: 阻止写");
if (w_sem != NULL) {
// 永久等待timeout_ms = 0 表示无限等待?注意:需确认你的 os_sem_pend 实现)
// 根据你原来的 os_sem_pend(&w_sem, 0),这里传 0
// 但注意:在你的封装中 timeout_ms / 10所以传 0 就是 0 ticks可能立即返回
// 如果原意是“永久阻塞”,应传一个极大值,比如 UINT32_MAX
// 不过先按原逻辑:传 0
lingxin_semaphore_pend(w_sem, 0);
}
}

View File

@@ -0,0 +1,47 @@
#include "lingxin_timer.h"
#include "lingxin_time_task_manager.h"
#include "lingxin_log.h"
#include "chat_api.h"
static int timer_id = INVALID_TIMER_ID;
bool init_lingxin_chat_timer(void *priv, void (*func)(void *priv)) {
lingxin_log_debug("%s 开始初始化, time_id: %d", __func__, timer_id);
if (timer_id != INVALID_TIMER_ID) {
return false;
}
timer_id = lingxin_sys_timer_add(priv, func, 10000); // 设置10秒后执行定时任务
lingxin_log_debug("%s 定时器初始化, time_id: %d", __func__, timer_id);
return true;
}
bool delete_lingxin_chat_timer() {
if (timer_id != INVALID_TIMER_ID) {
lingxin_log_debug("%s 定时器开始删除timer_id: %d", __func__, timer_id);
lingxin_sys_timer_del(timer_id);
timer_id = INVALID_TIMER_ID;
lingxin_log_debug("%s 定时器删除成功timer_id: %d", __func__, timer_id);
return true;
}
else {
lingxin_log_debug("%s 定时器未初始化无法删除timer_id: %d", __func__, timer_id);
return false;
}
}
bool reset_lingxin_chat_timer_run() {
if (timer_id == INVALID_TIMER_ID) {
lingxin_log_debug("定时器未初始化,无法重置, time_id: %d", timer_id);
return false;
}
lingxin_log_debug("%s重置定时器时间 time_id: %d", __func__, timer_id);
lingxin_sys_timer_re_run(timer_id); // 重置定时器
return true;
}

View File

@@ -0,0 +1,96 @@
#include "state_download_manager.h"
#include "lingxin_log.h"
static const PlaybackInterface *s_player = NULL;
// 回调函数:播放器 → PlaybackManager → 状态机
static void on_playback_event_from_player(LingxinDownloadAudioEvent event, void *user_data)
{
lingxin_log_debug("在download manager 接受到事件 %d", event);
StateEvent eve = 0;
switch (event)
{
case Lingxin_Download_Audio_InitEnd:
{
eve = State_Event_BufferPlay_AudioInitEnd;
break;
}
case Lingxin_Download_Audio_TerminateEnd:
{
eve = State_Event_BufferPlay_TerminateEnd;
break;
}
case Lingxin_Download_Audio_PlayEnd:
{
eve = State_Event_BufferPlay_PlayEnd;
break;
}
default:
break;
}
if (eve == 0)
{
lingxin_log_debug("无效事件,忽略");
return;
}
state_machine_run_event_with_payload(eve, NULL);
}
void playback_manager_init(ChatStateMediaType type)
{
lingxin_log_debug("进入下行状态,初始化播放器");
if (type == Media_Type_Chat)
{
extern const PlaybackInterface g_audioPlayer;
s_player = &g_audioPlayer;
}
else
{
lingxin_log_debug("Video playback not supported yet");
return;
}
s_player->init(on_playback_event_from_player, NULL);
}
void playback_manager_feed_data(void *buf, int len)
{
if (s_player && s_player->feedData)
{
s_player->feedData(buf, len);
}
}
void playback_manager_end_stream(void)
{
if (s_player && s_player->endOfStream)
{
s_player->endOfStream();
}
else
{
state_machine_run_event_with_payload(State_Event_BufferPlay_PlayEnd, NULL);
}
}
void playback_manager_terminate(void)
{
if (s_player && s_player->terminate)
{
s_player->terminate();
}
else
{
state_machine_run_event_with_payload(State_Event_BufferPlay_TerminateEnd, NULL);
}
}
void playback_manager_set_volume(int volume)
{
if (s_player && s_player->setVolume)
{
s_player->setVolume(volume);
}
}

View File

@@ -0,0 +1,180 @@
// recorder_adapter.c
#include "lingxin_recorder_manager.h"
#include "upload_record_interface.h"
#include "chat_state_machine.h"
#include "chat_runtime_context.h"
#include "lingxin_log.h"
#include "lingxin_protocol_manager.h"
#include "lingxin_chat_upload_manager.h"
#include "chat_state_machine_event.h"
#include "lingxin_trace.h"
static void on_record_init(bool success);
static void on_record_stop(bool success);
static void on_record_terminate(bool success);
static void sent_start_callback(void *buf, int rlen, int index)
{
if (buf != NULL && rlen > 0)
{
voiceChatSendAudio(buf, rlen);
}
else
{
lingxin_log_error("chat模块 上行录音发送失败");
}
}
static void on_record_stop_no_event(bool success)
{
voiceChatStopSendAudio();
if (!success)
{
lingxin_log_error("chat模块 上行录音打断失败");
}
}
// 监听器
static void onChatEvent(ChatEventType event, const char *data, const size_t len)
{
lingxin_log_debug("chat模块 上行事件回调 event:%d", event);
switch (event)
{
case CHAT_EVENT_ON_VAD_END:
module_record_stop(0, on_record_stop);
break;
case CHAT_EVENT_ON_VAD_EXIT:
// 通知状态机结束 chat
state_machine_run_event_with_payload(State_Event_Vad_Exit, NULL);
module_record_stop(0, on_record_stop_no_event);
break;
case CHAT_EVENT_ON_AI_READY:
// 调用录音发送的方法
module_record_start_send(sent_start_callback);
break;
default:
break;
}
}
static void on_record_init(bool success)
{
lingxin_log_debug("chat模块 on_record_init success=%d", success ? 1 : 0);
state_machine_run_event_with_payload(State_Event_Upload_InitEnd, NULL);
if (!success)
{
lingxin_log_error("chat模块 上行初始化失败");
}
}
static void on_record_stop(bool success)
{
voiceChatStopSendAudio();
state_machine_run_event_with_payload(State_Event_Upload_CloseEnd, NULL);
if (!success)
{
lingxin_log_error("chat模块 上行停止录音失败");
}
}
static void on_record_terminate(bool success)
{
state_machine_run_event_with_payload(State_Event_Upload_TerminateEnd, NULL);
if (!success)
{
lingxin_log_error("chat模块 上行录音打断失败");
}
}
static void voice_chat_continue()
{
ChatStartNewParams voice_chat_continue_params = {0};
// 初始化参数
voice_chat_continue_params.taskId = "";
voice_chat_continue_params.task = "";
voice_chat_continue_params.input_mode = "";
voice_chat_continue_params.output_mode = "";
voice_chat_continue_params.user_input = "";
voice_chat_continue_params.scheduleTaskId = "";
if (get_current_context() != NULL)
{
if (get_current_context()->has_global_task)
{
voice_chat_continue_params.task = get_current_context()->global_task;
}
if (get_current_context()->has_current_task_id)
{
voice_chat_continue_params.taskId = get_current_context()->current_task_id;
}
if (get_current_context()->has_disable_server_vad)
{
voice_chat_continue_params.server_vad = !(get_current_context()->disable_server_vad);
}
voice_chat_continue_params.output_mode = get_output_type_string();
voice_chat_continue_params.input_mode = get_input_type_string();
print_chat_context(get_current_context());
}
lingxin_log_debug("voice_chat_continue_params.taskId:%s, voice_chat_continue_params.task:%s voice_chat_continue_params.user_input:%s, voice_chat_continue_params.scheduleTaskId:%s", voice_chat_continue_params.taskId, voice_chat_continue_params.task, voice_chat_continue_params.user_input, voice_chat_continue_params.scheduleTaskId);
voice_chat_start_new(&voice_chat_continue_params); // 普通连续对话
}
static int record_init()
{
int ret;
// 添加事件的监听器
add_protocol_event_listener(onChatEvent);
lingxin_trace_set("chat:record_init_start");
lingxin_log_debug("chat模块 record_init start module_record_start");
ret = module_record_start(on_record_init);
if (ret != 0)
{
lingxin_trace_set("chat:record_init_start_fail");
lingxin_log_error("chat模块 record_init module_record_start failed");
on_record_init(false);
return 0;
}
return 1;
}
static void send_start()
{
lingxin_trace_set("chat:send_start_before_continue");
lingxin_log_debug("chat模块 send_start before voice_chat_continue");
voice_chat_continue();
lingxin_trace_set("chat:send_start_after_continue");
lingxin_log_debug("chat模块 send_start after voice_chat_continue");
}
static void recorder_terminate(void)
{
// TODO: 打断时需要阻塞websocket, 移动到外层
extern void freeze_websocket();
freeze_websocket();
module_record_stop(0, on_record_terminate);
}
static void recorder_destory(void)
{
remove_protocol_event_listener(onChatEvent);
}
void lingxin_chat_upload_manager_stop_record()
{
module_record_stop(1, on_record_stop);
}
const UploadModuleInterface g_chatUploadManager = {
.upload_init = record_init,
.upload_start = send_start,
.upload_terminate = recorder_terminate,
.upload_destory = recorder_destory,
};

View File

@@ -0,0 +1,391 @@
#include <string.h>
#include "chat_api.h"
#include "lingxin_chat_api_inner.h"
#include "lingxin_protocol_manager.h"
#include "lingxin_log.h"
#include "lingxin_memory.h"
#include "lingxin_recorder_manager.h"
#include "upload_record_interface.h"
#include "chat_state_machine.h"
#include "chat_runtime_context.h"
static bool multimodal_finish_input(LingxinInputEndProps *input_end_props);
static bool multimodal_send_stream(LingxinSendStreamProps *send_stream_props);
static bool multimodal_send_text(LingxinSendTextProps *send_text_props);
static bool multimodal_start_record_by_user(LingxinStartRecordProps *start_record_props);
static bool multimodal_stop_record_by_user(LingxinStopRecordProps *start_record_props);
static void on_multimodal_chat_event(ChatEventType event, const char *data, const size_t len);
static bool is_recorder_open = false;
static bool is_ai_ready = false;
static bool wait_input_terminate = false;
static bool is_server_vad_enabled()
{
const ChatStateRuntimeContext *ctx = get_current_context();
return ctx && !ctx->disable_server_vad;
}
/**
* 从type中解析出前缀部分 (如从 "audio/wav" 中提取 "audio")
*/
static bool get_prefix_from_type(const char *type, char *prefix_type, size_t prefix_size)
{
if (!type || !prefix_type || prefix_size == 0)
{
return false;
}
char *slash_pos = strchr(type, '/');
if (slash_pos)
{
size_t prefix_len = slash_pos - type;
// 确保有足够的空间存储结果和null终止符
if (prefix_len < prefix_size)
{
strncpy(prefix_type, type, prefix_len);
prefix_type[prefix_len] = '\0';
return true;
}
}
// 如果没有找到斜杠,复制整个字符串
size_t type_len = strlen(type);
if (type_len < prefix_size)
{
strcpy(prefix_type, type);
return true;
}
return false;
}
static bool multimodal_send_stream(LingxinSendStreamProps *send_stream_props)
{
lingxin_log_debug("multimodal_send_stream");
if (!send_stream_props)
{
lingxin_log_ut_with_args(LINGXIN_ERROR, "multimodal_upload_send_stream", "send_text_props is null");
return false;
}
if (wait_input_terminate)
{
lingxin_log_ut_with_args(LINGXIN_ERROR, "multimodal_upload_send_stream", "cannot receive data, %d", wait_input_terminate);
return false;
}
char frame_type[32];
get_prefix_from_type(send_stream_props->content_type, frame_type, sizeof(frame_type));
return voiceChat_send_request_data_stream(send_stream_props->unique_id, send_stream_props->index, send_stream_props->frame,
send_stream_props->content_len, send_stream_props->content_type, frame_type, send_stream_props->is_last);
}
static bool multimodal_send_text(LingxinSendTextProps *send_text_props)
{
lingxin_log_debug("multimodal_send_text");
if (!send_text_props)
{
lingxin_log_ut_with_args(LINGXIN_ERROR, "multimodal_upload_send_text", "send_text_props is null");
return false;
}
if (wait_input_terminate)
{
lingxin_log_ut_with_args(LINGXIN_ERROR, "multimodal_upload_send_stream", "cannot receive data, %d", wait_input_terminate);
return false;
}
return voiceChat_send_request_data_text(send_text_props->content);
}
static void multimodal_recorder_data_callback(void *buf, int rlen, int index)
{
voiceChat_send_request_data_stream("audio_recorder_data", index, buf, rlen, "audio/pcm", "audio", false);
}
/**
* 录音开始回调和收到ai_ready的顺序不能保证所以两个地方分别都调一次
*/
static void try_to_start_send_record()
{
if (!is_recorder_open || !is_ai_ready)
{
lingxin_log_ut_with_args(LINGXIN_ERROR, "try_to_start_send_record", "wait to send %d %d", is_recorder_open, is_ai_ready);
return;
}
module_record_start_send(multimodal_recorder_data_callback);
}
static void multimodal_input_event_callback(LingxinMultimodalInputEvent event, char *event_payload, bool is_server_vad)
{
lingxin_log_ut_with_args(LINGXIN_DEBUG, "multimodal_input_event_callback", "%d, %d", event, is_server_vad);
LingxinMultimodalInputListenerProps props = {
.input_end = multimodal_finish_input,
.send_stream = multimodal_send_stream,
.send_text = multimodal_send_text,
.start_record = is_server_vad ? NULL : multimodal_start_record_by_user,
.stop_record = is_server_vad ? NULL : multimodal_stop_record_by_user,
.event = event,
.event_payload = event_payload};
lingxin_emit_multimodal_input_event(props);
}
static void recorder_start_callback_from_user(bool is_success)
{
lingxin_log_ut_with_args(LINGXIN_DEBUG, "recorder_start_callback_from_user", "%d", is_success);
if (!is_success)
{
lingxin_log_ut_with_args(LINGXIN_ERROR, "multimodal_recorder_start_callback", "start fail");
return;
}
is_recorder_open = true;
try_to_start_send_record();
multimodal_input_event_callback(LINGXIN_MULTIMODAL_EVENT_RECORDER_START, "", false);
}
static bool multimodal_start_record_by_user(LingxinStartRecordProps *start_record_props)
{
lingxin_log_ut(LINGXIN_DEBUG, "multimodal_start_record_by_user");
module_record_start(recorder_start_callback_from_user);
return true;
}
static void recorder_stop_callback_from_user(bool is_success)
{
lingxin_log_ut_with_args(LINGXIN_DEBUG, "recorder_stop_callback_from_user", "%d", is_success);
if (!is_success)
{
lingxin_log_ut_with_args(LINGXIN_ERROR, "multimodal_upload_stop_record", "recorder stop failed");
return;
}
multimodal_input_event_callback(LINGXIN_MULTIMODAL_EVENT_RECORDER_STOP, "", false);
}
static bool multimodal_stop_record_by_user(LingxinStopRecordProps *start_record_props)
{
lingxin_log_ut(LINGXIN_DEBUG, "multimodal_stop_record_by_user");
// 用户手动暂停录音,需要等待剩余录音数据发完
module_record_stop(1, recorder_stop_callback_from_user);
return true;
}
static bool multimodal_finish_input(LingxinInputEndProps *input_end_props)
{
lingxin_log_ut_with_args(LINGXIN_DEBUG, "multimodal_finish_input", "%d", wait_input_terminate);
// 重置start_recorder_send需要的变量
is_recorder_open = false;
is_ai_ready = false;
if (wait_input_terminate)
{
wait_input_terminate = false;
lingxin_log_ut_with_args(LINGXIN_DEBUG, "multimodal_upload_end_input", "State_Event_Upload_TerminateEnd");
state_machine_run_event_with_payload(State_Event_Upload_TerminateEnd, NULL);
return true;
}
// 无confirm data 场景
if (!input_end_props || input_end_props->confirm_data_count <= 0 || !input_end_props->confirm_data_array)
{
// 发送结束任务请求
bool result = voiceChat_send_end_up_task(0, NULL);
// 通知状态机上行流程结束
if (result)
{
lingxin_log_ut_with_args(LINGXIN_DEBUG, "multimodal_upload_end_input", "State_Event_Upload_CloseEnd");
state_machine_run_event_with_payload(State_Event_Upload_CloseEnd, NULL);
}
return result;
}
// 有confirm data 场景
Multimodal_Chat_Confirm_Data *multimodal_confirm_data = lingxin_calloc(1, input_end_props->confirm_data_count * sizeof(Multimodal_Chat_Confirm_Data));
if (!multimodal_confirm_data)
{
lingxin_log_ut_with_args(LINGXIN_ERROR, "multimodal_upload_end_input", "confirm data calloc failed");
return false;
}
for (size_t i = 0; i < input_end_props->confirm_data_count; i++)
{
// 复制ID
multimodal_confirm_data[i].unique_id = input_end_props->confirm_data_array[i].unique_id;
char prefix_type[32]; // 假设前缀最大长度为31字符+NULL
if (!get_prefix_from_type(input_end_props->confirm_data_array[i].content_type, prefix_type, sizeof(prefix_type)))
{
lingxin_log_ut_with_args(LINGXIN_ERROR, "multimodal_upload_end_input", "%d, get_prefix_from_type fail", i);
return false;
}
multimodal_confirm_data[i].frame_type = lingxin_calloc(1, strlen(prefix_type) + 1);
if (!multimodal_confirm_data[i].frame_type)
{
lingxin_log_ut_with_args(LINGXIN_ERROR, "multimodal_upload_end_input", "%d, frame_type calloc fail", i);
return false;
}
strcpy(multimodal_confirm_data[i].frame_type, prefix_type);
}
// 发送结束任务请求
bool result = voiceChat_send_end_up_task(input_end_props->confirm_data_count, multimodal_confirm_data);
// 通知状态机上行流程结束
if (result)
{
lingxin_log_ut_with_args(LINGXIN_DEBUG, "multimodal_upload_end_input", "State_Event_Upload_CloseEnd");
state_machine_run_event_with_payload(State_Event_Upload_CloseEnd, NULL);
}
// 清理临时分配的内存
for (size_t i = 0; i < input_end_props->confirm_data_count; i++)
{
if (multimodal_confirm_data[i].frame_type)
{
lingxin_free((void *)multimodal_confirm_data[i].frame_type);
}
}
lingxin_free(multimodal_confirm_data);
return result;
}
static void recorder_stop_callback_from_vad_end(bool is_success)
{
lingxin_log_ut_with_args(LINGXIN_DEBUG, "recorder_stop_callback_from_vad_end", "%d", is_success);
if (!is_success)
{
lingxin_log_ut_with_args(LINGXIN_ERROR, "multimodal_upload_stop_record", "recorder stop failed");
return;
}
multimodal_input_event_callback(LINGXIN_MULTIMODAL_EVENT_RECORDER_STOP, "", true);
}
static void on_multimodal_chat_event(ChatEventType event, const char *data, const size_t len)
{
switch (event)
{
case CHAT_EVENT_ON_VAD_END:
case CHAT_EVENT_ON_VAD_EXIT:
module_record_stop(0, recorder_stop_callback_from_vad_end);
break;
case CHAT_EVENT_ON_STREAM_DATA:
/* code */
break;
case CHAT_EVENT_ON_AI_READY:
{
is_ai_ready = true;
bool is_server_vad = is_server_vad_enabled();
if (is_server_vad)
{
try_to_start_send_record();
}
else
{
state_machine_run_event_with_payload(State_Event_Upload_InitEnd, NULL);
}
multimodal_input_event_callback(LINGXIN_MULTIMODAL_EVENT_INPUT_START, "", is_server_vad);
break;
}
case CHAT_EVENT_ON_REQUEST_DATA_END:
{
multimodal_input_event_callback(LINGXIN_MULTIMODAL_EVENT_STREAM_INPUT_SUCCESS, (char *)data, is_server_vad_enabled());
break;
}
case CHAT_EVENT_ON_ERROR:
/* code */
break;
default:
break;
}
}
static void recorder_start_callback_from_init_case_vad(bool is_success)
{
lingxin_log_ut_with_args(LINGXIN_DEBUG, "recorder_start_callback_from_init_case_vad", "%d", is_success);
if (!is_success)
{
lingxin_log_ut_with_args(LINGXIN_ERROR, "recorder_start_callback_from_init_case_vad", "start fail");
return;
}
is_recorder_open = true;
state_machine_run_event_with_payload(State_Event_Upload_InitEnd, NULL);
multimodal_input_event_callback(LINGXIN_MULTIMODAL_EVENT_RECORDER_START, "", true);
try_to_start_send_record();
}
static int multimodal_upload_init()
{
lingxin_log_ut(LINGXIN_DEBUG, "multimodal_upload_init");
add_protocol_event_listener(on_multimodal_chat_event);
// 云端vad场景才自动打开录音否则不处理
if (is_server_vad_enabled())
{
module_record_start(recorder_start_callback_from_init_case_vad);
}
return 1;
}
static void multimodal_upload_start()
{
bool is_server_vad = is_server_vad_enabled();
lingxin_log_ut_with_args(LINGXIN_DEBUG, "multimodal_upload_start", "%d", is_server_vad);
const ChatStateRuntimeContext *ctx = get_current_context();
char *final_task = "chat_multimodal";
char *final_taskId = NULL;
if (ctx != NULL)
{
final_task = (char *)ctx->global_task;
final_taskId = (char *)ctx->current_task_id;
}
ChatStartNewParams multimodal_start_params = {
.is_schedule_timer_task = false,
.server_vad = is_server_vad,
.input_mode = "",
.output_mode = "",
.scheduleTaskId = "",
.user_input = "",
.task = final_task,
.taskId = final_taskId};
voice_chat_start_new(&multimodal_start_params);
}
static void recorder_stop_callback_from_terminate(bool is_success)
{
lingxin_log_ut_with_args(LINGXIN_DEBUG, "recorder_stop_callback_from_terminate", "%d", is_success);
if (!is_success)
{
lingxin_log_ut_with_args(LINGXIN_ERROR, "multimodal_upload_terminate_record", "recorder stop failed");
return;
}
if (is_ai_ready)
{
multimodal_input_event_callback(LINGXIN_MULTIMODAL_EVENT_INPUT_INTERRUPT, "", is_server_vad_enabled());
}
else
{
// 没有通知过客户开始输入,则直接结束输入
multimodal_finish_input(NULL);
}
}
static void multimodal_upload_terminate()
{
lingxin_log_ut(LINGXIN_DEBUG, "multimodal_upload_terminate");
wait_input_terminate = true;
module_record_stop(0, recorder_stop_callback_from_terminate);
}
static void multimodal_upload_destroy()
{
lingxin_log_ut(LINGXIN_DEBUG, "multimodal_upload_destroy");
remove_protocol_event_listener(on_multimodal_chat_event);
}
const UploadModuleInterface g_multimodalUploadManager = {
.upload_init = multimodal_upload_init,
.upload_start = multimodal_upload_start,
.upload_terminate = multimodal_upload_terminate,
.upload_destory = multimodal_upload_destroy};

View File

@@ -0,0 +1,67 @@
#include "lingxin_log.h"
#include "state_upload_manager.h"
#include "upload_manager_factory.h"
static const UploadModuleInterface *s_upload = NULL;
static ChatStateMediaType current_upload_type = Media_Type_Chat;
int upload_manager_init(ChatStateMediaType type)
{
lingxin_log_debug("进入上传状态,初始化录音器");
lingxin_trace_set("upload:init_enter");
if (s_upload && s_upload->upload_destory)
{
lingxin_trace_set("upload:destroy_prev");
s_upload->upload_destory();
}
// 通过工厂类方法创建相关类
s_upload = upload_factory_manager_init(type);
lingxin_trace_set(s_upload ? "upload:factory_ok" : "upload:factory_null");
lingxin_log_debug("upload_manager_init type=%d upload=%p", type, s_upload);
if (!s_upload || !s_upload->upload_init)
{
lingxin_log_error("upload_manager_init failed, upload=%p", s_upload);
return 0;
}
current_upload_type = type;
s_upload->upload_init();
lingxin_trace_set("upload:init_ret");
return 1;
}
void upload_manager_start()
{
lingxin_trace_set("upload:start_enter");
lingxin_log_debug("upload_manager_start upload=%p", s_upload);
if (s_upload && s_upload->upload_start)
{
lingxin_log_debug("上传模块 发送 task_start");
lingxin_trace_set("upload:start_call");
s_upload->upload_start();
lingxin_trace_set("upload:start_ret");
}
else
{
lingxin_trace_set("upload:start_no_handler");
lingxin_log_error("upload_manager_start no handler upload=%p", s_upload);
}
}
void upload_manager_terminate(void)
{
if (s_upload && s_upload->upload_terminate)
{
s_upload->upload_terminate();
}
else
{
state_machine_run_event_with_payload(State_Event_Upload_TerminateEnd, NULL);
}
}

View File

@@ -0,0 +1,93 @@
// recorder_adapter.c
#include "chat_state_machine.h"
#include "chat_runtime_context.h"
#include "lingxin_log.h"
#include "upload_record_interface.h"
#include "schedule_timer_manager.h"
static void onChatEvent(ChatEventType event, const char *data, const size_t len);
static void onChatEvent(ChatEventType event, const char *data, const size_t len)
{
switch (event)
{
case CHAT_EVENT_ON_AI_READY:
state_machine_run_event_with_payload(State_Event_Upload_CloseEnd, NULL);
break;
case CHAT_EVENT_ON_ERROR:
// 通知定时任务模块定时任务触发失败
recieve_schedule_task_error();
break;
default:
break;
}
}
static void voice_chat_continue()
{
ChatStartNewParams voice_chat_continue_params = {0};
voice_chat_continue_params.taskId = "";
voice_chat_continue_params.task = "";
voice_chat_continue_params.input_mode = "";
voice_chat_continue_params.output_mode = "";
voice_chat_continue_params.user_input = "";
voice_chat_continue_params.scheduleTaskId = "";
// 三个参数 char* input_mode; char *output_mode;
voice_chat_continue_params.output_mode = "voice";
voice_chat_continue_params.input_mode = "no_voice";
voice_chat_continue_params.task = "chat_vad";
if (get_current_context() != NULL)
{
if (get_current_context()->has_current_schedule_id)
{
voice_chat_continue_params.scheduleTaskId = get_current_context()->current_schedule_id;
voice_chat_continue_params.is_schedule_timer_task = true;
}
if (get_current_context()->has_current_user_input)
{
voice_chat_continue_params.user_input = get_current_context()->current_user_input;
}
if (get_current_context()->has_current_task_id)
{
voice_chat_continue_params.taskId = get_current_context()->current_task_id;
}
print_chat_context(get_current_context());
}
lingxin_log_debug("纯 text 模块任务 voice_chat_continue_params.taskId:%s, voice_chat_continue_params.task:%s voice_chat_continue_params.user_input:%s, voice_chat_continue_params.scheduleTaskId:%s", voice_chat_continue_params.taskId, voice_chat_continue_params.task, voice_chat_continue_params.user_input, voice_chat_continue_params.scheduleTaskId);
voice_chat_start_new(&voice_chat_continue_params);
}
static int schedule_upload_manager_init()
{
// 初始化录音器模块
lingxin_log_debug("定时任务模块 上行初始化成功");
add_protocol_event_listener(onChatEvent);
state_machine_run_event_with_payload(State_Event_Upload_InitEnd, NULL);
return 1;
}
static void send_start()
{
voice_chat_continue();
}
static void schdule_upload_manager_destory(void)
{
remove_protocol_event_listener(onChatEvent);
}
const UploadModuleInterface g_scheduleUploadManager = {
.upload_init = schedule_upload_manager_init,
.upload_start = send_start,
.upload_terminate = NULL,
.upload_destory = schdule_upload_manager_destory};

View File

@@ -0,0 +1,32 @@
// 上行模块的管理类
#include "upload_manager_factory.h"
#include "lingxin_log.h"
UploadModuleInterface *upload_factory_manager_init(ChatStateMediaType type)
{
UploadModuleInterface *upload_manager = NULL;
if (type == Media_Type_Chat)
{
extern const UploadModuleInterface g_chatUploadManager;
upload_manager = (UploadModuleInterface *)&g_chatUploadManager;
}
else if (type == Media_Type_TextOnly)
{
extern const UploadModuleInterface g_scheduleUploadManager;
upload_manager = (UploadModuleInterface *)&g_scheduleUploadManager;
}
else if (type == Media_Type_Multimodal)
{
lingxin_log_debug("多模态上传模块初始化");
extern const UploadModuleInterface g_multimodalUploadManager;
upload_manager = (UploadModuleInterface *)&g_multimodalUploadManager;
}
else
{
lingxin_log_debug("Video recording not supported yet");
return NULL;
}
return upload_manager;
}

View File

@@ -0,0 +1,147 @@
#include <stdio.h>
#include "lingxin_log.h"
#include "chat_state_machine.h"
#include "state_task_complete_stategies.h"
#include "chat_runtime_context.h"
#include "state_download_manager.h"
static void continue_strategy(StateEvent event)
{
end_current_chat_runtime_context();
// 执行继续对话策略
start_new_chat_runtime_context();
turn_to_with_preset_inner_state(State_Upload_Init, event, NULL);
}
static void continue_with_prompt_strategy()
{
// 执行带提示音的继续对话策略
lingxin_log_debug("播放跟进提示音");
module_local_play_continue_audio();
}
static void vad_exit_strategy(bool input_timeout_audio)
{
// 执行vad退出策略
if (input_timeout_audio)
{
playback_manager_end_stream();
}
else
{
// 不播放退出提示音,直接退出
state_task_complete_receive_event(Event_Inc_TaskComplete_PlayEnd, NULL);
}
}
static void single_round_exit_strategy()
{
// 执行单轮对话退出策略
state_task_complete_receive_event(Event_Inc_TaskComplete_End, NULL);
}
// 切换到任务完成态
void turn_to_task_complete(const ChatStateRuntimeContext *current_context, InnerStateCollection *innerPayload)
{
if (!current_context)
{
lingxin_log_debug(" current_context is null");
return;
}
if (current_context->has_is_vad_exit && current_context->is_vad_exit)
{
// 等待 服务端 end 指令
return;
}
if (current_context->single_round)
{
// 执行带提示音的继续对话策略
single_round_exit_strategy();
}
else
{
if (current_context->need_continue_prompt)
{
// 执行带提示音的继续对话策略
continue_with_prompt_strategy();
}
else
{
// 执行继续对话策略
continue_strategy(Event_Inc_TaskComplete_End);
}
}
}
// 接受到事件
void state_task_complete_receive_event(StateEvent event, StateEventPayload *payload)
{
lingxin_log_debug("state_machine_receive_event: %d", event);
// 根据当前状态和事件,决定状态转移
switch (event)
{
case State_Event_VoiceChat_AIEnd:
{
if (get_current_context() != NULL && get_current_context()->is_vad_exit)
{
// 执行退出逻辑策略
vad_exit_strategy(get_current_context()->input_timeout_audio);
}
break;
}
case State_Event_WillExit:
{
if (get_current_context() != NULL && get_current_context()->is_vad_exit && get_current_context()->input_timeout_audio)
{
// 执行退出逻辑策略
playback_manager_terminate();
}
if (payload && payload->will_exit_payload && payload->will_exit_payload->disable_close_ws_immediately)
{
InnerStateForExit inner_state = {true, true, true, true, true};
InnerStateCollection inner_state_collection = {
.inner_state_for_exit = &inner_state,
};
turn_to_with_preset_inner_state(State_Exit, State_Event_WillExit, &inner_state_collection);
}
else
{
InnerStateForExit inner_state = {true, true, true, false, true};
InnerStateCollection inner_state_collection = {
.inner_state_for_exit = &inner_state,
};
turn_to_with_preset_inner_state(State_Exit, State_Event_WillExit, &inner_state_collection);
}
break;
}
case State_Event_ContinuePrompt_PlayEnd:
{
continue_strategy(State_Event_ContinuePrompt_PlayEnd);
break;
}
case State_Event_VoiceChat_ExitEnd:
{
break;
}
case Event_Inc_TaskComplete_End:
{
InnerStateForExit inner_state = {false, true, true, false, true};
InnerStateCollection inner_state_collection = {
.inner_state_for_exit = &inner_state,
};
turn_to_with_preset_inner_state(State_Exit, event, &inner_state_collection);
break;
}
case Event_Inc_TaskComplete_PlayEnd:
{
// vad exit退出, 不需要断开 websocket连接
turn_to_with_preset_inner_state(State_Idle, event, NULL);
break;
}
default:
break;
}
}

View File

@@ -0,0 +1,25 @@
// 下行模块结束状态策略
#include "state_task_download_end_strategies.h"
#include "lingxin_log.h"
void turn_to_download_end(const ChatStateRuntimeContext *current_context, InnerStateCollection *innerPayload)
{
state_download_end_receive_event(Event_Inc_Download_End, NULL);
}
// 接受到事件
void state_download_end_receive_event(StateEvent event, StateEventPayload *payload)
{
switch (event)
{
case Event_Inc_Download_End:
{
lingxin_log_debug("进入下行结束态");
turn_to_with_preset_inner_state(State_Task_Complete, event, NULL);
break;
}
default:
break;
}
}

View File

@@ -0,0 +1,41 @@
// 下行模块初始化策略
#include "state_task_download_init_strategies.h"
#include "lingxin_log.h"
#include "chat_state_machine.h"
#include "state_download_manager.h"
#include "lingxin_download_stream_control_manager.h"
void turn_to_download_init(const ChatStateRuntimeContext *current_context, InnerStateCollection *innerPayload)
{
if (!current_context)
{
lingxin_log_debug(" current_context is null");
return;
}
// 执行下行模块初始化策略
lingxin_log_debug("进入download init状态初始化播放器");
ChatStateMediaType download_type = current_context->download_type;
playback_manager_init(download_type);
}
// 接受到事件
void state_download_init_receive_event(StateEvent event, StateEventPayload *payload)
{
switch (event)
{
case State_Event_BufferPlay_AudioInitEnd:
{
turn_to_with_preset_inner_state(State_Download_Play, event, NULL);
}
break;
case State_Event_VoiceChat_AIEnd:
{
// 纯文本
turn_to_with_preset_inner_state(State_Download_End, event, NULL);
break;
}
default:
break;
}
}

View File

@@ -0,0 +1,45 @@
// 下行模块数据传输状态策略
#include "state_task_download_transfer_strategies.h"
#include "lingxin_log.h"
#include "chat_state_machine.h"
#include "state_download_manager.h"
void turn_to_download_transfer(const ChatStateRuntimeContext *current_context, InnerStateCollection *innerPayload)
{
if (!current_context)
{
lingxin_log_debug(" current_context is null");
return;
}
}
// 接受到事件
void state_download_transfer_receive_event(StateEvent event, StateEventPayload *payload)
{
switch (event)
{
case State_Event_VoiceChat_AIEnd:
{
// 结束播放
playback_manager_end_stream();
break;
}
case State_Event_BufferPlay_PlayEnd:
{
// 结束播放
lingxin_log_debug("下行模块播放结束,进入下行结束态");
turn_to_with_preset_inner_state(State_Download_End, event, NULL);
break;
}
default:
lingxin_log_debug("");
break;
}
}
void state_download_feed_data(void *data, int len)
{
playback_manager_feed_data(data, len);
}

Some files were not shown because too many files have changed in this diff Show More