From aeae073e4fe88973fa68461bd24b6c8d37732f7c Mon Sep 17 00:00:00 2001 From: Rdzleo Date: Tue, 19 May 2026 13:50:52 +0800 Subject: [PATCH] =?UTF-8?q?chore(git):=20=E6=8A=8A=20components/common/=20?= =?UTF-8?q?=E7=BA=B3=E5=85=A5=20git=20=E8=B7=9F=E8=B8=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit components/common/ 是项目自己写的代码 (火山 RTC SDK 的 wrapper + HTTP 客户端 + JSON 工具 + 设备管理), 历史上被 .gitignore 的 components/ 规则误排除。共 15 个文件 2750 行项目代码长期未被 git 跟踪, 修改易丢失, 不利于多设备开发和回溯。 主要变动: 1. .gitignore - 改 components/ → /components/* (顶层 children 而非目录本身) - 加 !/components/common/ 例外, 让项目自己代码进入跟踪 - 加 esp-spot/**/components/ 显式 ignore 子项目里的 components/ 保持原行为 2. components/common/ 首次入 git (~2750 行) - inc/volc_rtc.h, src/volc_rtc.c — 火山 RTC SDK 的封装层 - inc/volc_http.h, src/volc_http.c — HTTP 客户端 - inc/util/volc_json.h, src/volc_json.c — JSON 工具 - inc/base/volc_device_manager.h, src/volc_device_manager.c — RTC 设备凭证管理 - inc/util/volc_log.h — 日志宏 - inc/util/volc_list.h — 链表工具 - inc/volc_conv_ai.h — 会话 AI 接口定义 - inc/volc_platform.h, src/volc_platform.c — 平台抽象 - inc/base/volc_base.h — 基础类型 未跟踪的兄弟目录 (保持 ignore): - components/78__esp-opus-encoder/ (IDF managed component) - components/volc_engine_rtc_lite/ (火山 RTC SDK 二进制库) - components/zlib/ (第三方库) 后续 fix(rtc) NULL guard 等 components/common/ 的改动将作为独立 commit。 Co-Authored-By: Claude Opus 4.7 (1M context) --- .gitignore | 6 +- components/common/CMakeLists.txt | 10 + components/common/inc/base/volc_base.h | 50 ++ .../common/inc/base/volc_device_manager.h | 64 ++ components/common/inc/util/volc_json.h | 48 ++ components/common/inc/util/volc_list.h | 135 +++ components/common/inc/util/volc_log.h | 60 ++ components/common/inc/volc_conv_ai.h | 166 ++++ components/common/inc/volc_http.h | 16 + components/common/inc/volc_platform.h | 66 ++ components/common/inc/volc_rtc.h | 42 + components/common/src/volc_device_manager.c | 728 ++++++++++++++++ components/common/src/volc_http.c | 207 +++++ components/common/src/volc_json.c | 208 +++++ components/common/src/volc_platform.c | 155 ++++ components/common/src/volc_rtc.c | 790 ++++++++++++++++++ 16 files changed, 2750 insertions(+), 1 deletion(-) create mode 100644 components/common/CMakeLists.txt create mode 100644 components/common/inc/base/volc_base.h create mode 100644 components/common/inc/base/volc_device_manager.h create mode 100644 components/common/inc/util/volc_json.h create mode 100644 components/common/inc/util/volc_list.h create mode 100644 components/common/inc/util/volc_log.h create mode 100644 components/common/inc/volc_conv_ai.h create mode 100644 components/common/inc/volc_http.h create mode 100644 components/common/inc/volc_platform.h create mode 100644 components/common/inc/volc_rtc.h create mode 100644 components/common/src/volc_device_manager.c create mode 100644 components/common/src/volc_http.c create mode 100644 components/common/src/volc_json.c create mode 100644 components/common/src/volc_platform.c create mode 100644 components/common/src/volc_rtc.c diff --git a/.gitignore b/.gitignore index d62fd21..c8a3d0c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,9 @@ tmp/ -components/ +# 顶层 components/ 下默认全部 ignore (第三方 SDK/库), 但 common/ 是项目自己代码要跟踪 +/components/* +!/components/common/ +# 子项目 (esp-spot 等) 里的 components/ 一律 ignore (历史规则保持) +esp-spot/**/components/ managed_components/ build/ .vscode/ diff --git a/components/common/CMakeLists.txt b/components/common/CMakeLists.txt new file mode 100644 index 0000000..16a483e --- /dev/null +++ b/components/common/CMakeLists.txt @@ -0,0 +1,10 @@ +idf_component_register(SRCS "src/volc_rtc.c" "src/volc_platform.c" "src/volc_json.c" "src/volc_device_manager.c" "src/volc_http.c" + INCLUDE_DIRS "inc" + REQUIRES json volc_engine_rtc_lite zlib + PRIV_REQUIRES esp_netif esp_http_client mbedtls + ) + +# Add ENABLE_RTC_MODE definition if VOLC_RTC connection type is selected +if(CONFIG_CONNECTION_TYPE_VOLC_RTC) + target_compile_definitions(${COMPONENT_LIB} PRIVATE ENABLE_RTC_MODE) +endif() \ No newline at end of file diff --git a/components/common/inc/base/volc_base.h b/components/common/inc/base/volc_base.h new file mode 100644 index 0000000..6c4db87 --- /dev/null +++ b/components/common/inc/base/volc_base.h @@ -0,0 +1,50 @@ +// Copyright (2025) Beijing Volcano Engine Technology Ltd. +// SPDX-License-Identifier: Apache-2.0 + +#ifndef __CONV_AI_BASE_VOLC_BASE_H__ +#define __CONV_AI_BASE_VOLC_BASE_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include "../util/volc_list.h" +#include "../volc_conv_ai.h" + +// TODO: internal or external +typedef enum { + VOLC_MSG_CONNECTED = 0, // 成功连接 + VOLC_MSG_DISCONNECTED, // 断开连接 + VOLC_MSG_USER_JOINED, // 用户加入 + VOLC_MSG_USER_OFFLINE, // 用户离开 + VOLC_MSG_APP_ID_INVALID, + VOLC_MSG_TOKEN_INVALID, + VOLC_MSG_TOKEN_EXPIRED, + VOLC_MSG_LICENSE_EXPIRED, + VOLC_MSG_KEY_FRAME_REQ, // 关键帧请求 + VOLC_MSG_TARGET_BITRATE_CHANGED, // 目标码率变化 + VOLC_MSG_CONV_STATUS, // 会话状态 +} volc_msg_e; + +typedef struct { + volc_msg_e code; + union { + uint32_t target_bitrate; + uint32_t conv_status; + char* msg; + } data; +} volc_msg_t; + +typedef void(*volc_msg_cb)(void* context, volc_msg_t *msg); +typedef void(*volc_data_cb)(void* context, const void* buffer, size_t len, volc_data_info_t *info); + + +#ifdef __cplusplus +} +#endif +#endif /* __CONV_AI_BASE_VOLC_BASE_H__ */ \ No newline at end of file diff --git a/components/common/inc/base/volc_device_manager.h b/components/common/inc/base/volc_device_manager.h new file mode 100644 index 0000000..a6558da --- /dev/null +++ b/components/common/inc/base/volc_device_manager.h @@ -0,0 +1,64 @@ +// Copyright (2025) Beijing Volcano Engine Technology Ltd. +// SPDX-License-Identifier: Apache-2.0 + +#ifndef __CONV_AI_BASE_VOLC_DEVICE_MANAGER_H__ +#define __CONV_AI_BASE_VOLC_DEVICE_MANAGER_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +// 硬件ID宏定义 +#define HARDWARE_ID "a2:c8:2c:89:6e:46" + +// 错误码宏定义 +#define ERROR_LICENSE_EXHAUSTED 12000130 +#define ERROR_LICENSE_EXPIRED 12000140 + +// IoT设备信息结构体 +typedef struct { + char* instance_id; // 实例ID + char* product_key; // 产品密钥 + char* product_secret; // 产品密钥 + char* device_name; // 设备名称 + char* device_secret; // 设备密钥 + char* rtc_app_id; // RTC应用ID + char* bot_id; // Bot ID +} volc_iot_info_t; + +// RTC配置结构体 +typedef struct { + char* p_channel_name; // 频道名称 + char* p_uid; // 用户ID + char* p_token; // 令牌 + int audio_codec; // 音频编解码器 + int video_codec; // 视频编解码器 + uint32_t audio_bitrate; // 音频比特率 + uint32_t video_bitrate; // 视频比特率 + uint32_t sample_rate; // 采样率 + uint32_t channels; // 声道数 + uint32_t bits_per_sample;// 采样位数 +} volc_rtc_config_t; + +// 房间信息结构体 +typedef struct { + volc_rtc_config_t rtc_opt; // RTC配置选项 + char* task_id; // 任务ID +} volc_room_info_t; + +// 为了向后兼容,添加volc_rtc_option_t作为volc_rtc_config_t的别名 +typedef volc_rtc_config_t volc_rtc_option_t; + +// 函数声明 +int volc_device_register(volc_iot_info_t* info, char** output); +// 新增:extra_params 用于传递额外的AgentConfig配置参数 +int volc_get_rtc_config(volc_iot_info_t* info, int audio_codec, const char* bot_id, const char* task_id, const char* extra_params, volc_room_info_t* room_info); +char* volc_generate_signature(const char* secret_key, const char* product_key, const char* device_name, int rnd, uint64_t timestamp, int auth_type); +char* volc_generate_signature_ws(const char* secret_key, const char* product_key, const char* device_name, const char* instance_id, int rnd, uint64_t timestamp, int auth_type); + +#ifdef __cplusplus +} +#endif +#endif /* __CONV_AI_BASE_VOLC_DEVICE_MANAGER_H__ */ \ No newline at end of file diff --git a/components/common/inc/util/volc_json.h b/components/common/inc/util/volc_json.h new file mode 100644 index 0000000..2e87453 --- /dev/null +++ b/components/common/inc/util/volc_json.h @@ -0,0 +1,48 @@ +// Copyright (2025) Beijing Volcano Engine Technology Ltd. +// SPDX-License-Identifier: Apache-2.0 + +#ifndef __CONV_AI_SRC_UTIL_VOLC_JSON_H__ +#define __CONV_AI_SRC_UTIL_VOLC_JSON_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include + +#define JSON_KEY_LEN_MAX (64) + +/** + * @brief + * + * @param root + * @param fmt the string of the key, support the multi-level keys, such as: [key1.key2.key3] + * @param dst + * @return 0: success. + * -1: failure. + */ +int volc_json_read_int(cJSON *root, const char *fmt, int *dst); +int volc_json_read_double(cJSON *root, const char *fmt, double *dst); +int volc_json_read_string(cJSON *root, const char *fmt, char **dst); +int volc_json_read_object(cJSON *root, const char *fmt, cJSON **dst); +int volc_json_read_bool(cJSON *root, const char *fmt, bool *dst); + +/** + * @brief + * + * @param root + * @param fmt + * @return 0: the type of the item is same as the suffix of the function name. + * -1: different. + */ +int volc_json_check_int(cJSON *root, const char *fmt); +int volc_json_check_double(cJSON *root, const char *fmt); +int volc_json_check_string(cJSON *root, const char *fmt); +int volc_json_check_bool(cJSON *root, const char *fmt); + +#ifdef __cplusplus +} +#endif +#endif // __CONV_AI_SRC_UTIL_VOLC_JSON_H__ \ No newline at end of file diff --git a/components/common/inc/util/volc_list.h b/components/common/inc/util/volc_list.h new file mode 100644 index 0000000..d0f5051 --- /dev/null +++ b/components/common/inc/util/volc_list.h @@ -0,0 +1,135 @@ +// Copyright (2025) Beijing Volcano Engine Technology Ltd. +// SPDX-License-Identifier: Apache-2.0 + +#ifndef __VOLC_LIST_H__ +#define __VOLC_LIST_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief double list + */ +typedef struct _volc_list_node_t { + struct _volc_list_node_t *next; + struct _volc_list_node_t *prev; +} volc_list_node_t; + +typedef struct _volc_list_t { + volc_list_node_t *head; + volc_list_node_t *tail; + uint32_t count; +} volc_list_t; + +/** + * @brief initialize a list + */ +#define volc_list_init(list) \ + do { \ + (list)->head = NULL; \ + (list)->tail = NULL; \ + (list)->count = 0; \ + } while(0) + +/** + * @brief add a node to the list head + */ +#define volc_list_add(list, node) \ + do { \ + if ((list)->head == NULL) { \ + (list)->head = (list)->tail = node; \ + (node)->prev = (node)->next = NULL; \ + } else { \ + (node)->next = (list)->head; \ + (list)->head->prev = node; \ + (node)->prev = NULL; \ + (list)->head = node; \ + } \ + (list)->count++; \ + } while(0) + +/** + * @brief add a node to the list tail + */ +#define volc_list_add_tail(list, node) \ + do { \ + if ((list)->tail == NULL) { \ + (list)->head = (list)->tail = node; \ + (node)->prev = (node)->next = NULL; \ + } else { \ + (node)->prev = (list)->tail; \ + (list)->tail->next = node; \ + (node)->next = NULL; \ + (list)->tail = node; \ + } \ + (list)->count++; \ + } while(0) + +/** + * @brief delete a node from the list + */ +#define volc_list_del(list, node) \ + do { \ + if ((list)->head == NULL) { \ + break; \ + } \ + if ((list)->head == node) { \ + (list)->head = node->next; \ + if ((list)->head != NULL) { \ + (list)->head->prev = NULL; \ + } else { \ + (list)->tail = NULL; \ + } \ + } else if ((list)->tail == node) { \ + (list)->tail = node->prev; \ + if ((list)->tail != NULL) { \ + (list)->tail->next = NULL; \ + } else { \ + (list)->head = NULL; \ + } \ + } else { \ + if ((node)->prev != NULL && (node)->next != NULL) { \ + (node)->prev->next = (node)->next; \ + (node)->next->prev = (node)->prev; \ + } \ + } \ + (list)->count--; \ + (node)->next = NULL; \ + (node)->prev = NULL; \ + } while(0) + +/** + * @brief check if a list is empty + */ +#define volc_list_empty(list) ((list)->head == NULL) + +/** + * @brief get the first node of a list + */ +#define volc_list_first(list) ((list)->head) + +/** + * @brief get the last node of a list + */ +#define volc_list_last(list) ((list)->tail) + +/** + * @brief iterate through a list + */ +#define volc_list_for_each(list, node) \ + for ((node) = (list)->head; (node) != NULL; (node) = (node)->next) + +/** + * @brief iterate through a list safe against removal of list entry + */ +#define volc_list_for_each_safe(list, node, n) \ + for ((node) = (list)->head, (n) = (node) ? (node)->next : NULL; \ + (node) != NULL; \ + (node) = (n), (n) = (node) ? (node)->next : NULL) + +#ifdef __cplusplus +} +#endif + +#endif // __VOLC_LIST_H__ \ No newline at end of file diff --git a/components/common/inc/util/volc_log.h b/components/common/inc/util/volc_log.h new file mode 100644 index 0000000..4a4bcae --- /dev/null +++ b/components/common/inc/util/volc_log.h @@ -0,0 +1,60 @@ +// Copyright (2025) Beijing Volcano Engine Technology Ltd. +// SPDX-License-Identifier: Apache-2.0 + +#ifndef __CONV_AI_SRC_UTIL_VOLC_LOG_H__ +#define __CONV_AI_SRC_UTIL_VOLC_LOG_H__ + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + + typedef enum + { + VOLC_LOG_LEVEL_ERROR, + VOLC_LOG_LEVEL_WARN, + VOLC_LOG_LEVEL_INFO, + VOLC_LOG_LEVEL_DEBUG, + VOLC_LOG_LEVEL_VERBOSE, + VOLC_LOG_LEVEL_NONE, // 不输出日志 + } volc_log_level_e; + +#define VOLC_LOG_LEVEL_DEFAULT VOLC_LOG_LEVEL_INFO + +// 日志级别颜色定义 +#define LOG_COLOR_RESET "\033[0m" +#define LOG_COLOR_RED "\033[31m" +#define LOG_COLOR_GREEN "\033[32m" +#define LOG_COLOR_YELLOW "\033[33m" +#define LOG_COLOR_BLUE "\033[34m" +#define LOG_COLOR_PRUPLE "\033[35m" + +// 定义__FILENAME__宏获取文件名(不包含路径) +#ifndef __FILENAME__ +#define __FILENAME__ (__builtin_strrchr(__FILE__, '/') ? __builtin_strrchr(__FILE__, '/') + 1 : __FILE__) +#endif + +// 通用日志宏 +#define LOG(level, tag, color, format, ...) \ + do \ + { \ + if (level <= VOLC_LOG_LEVEL_DEFAULT) \ + { \ + printf(color "[%s|%s:%d]" format LOG_COLOR_RESET "\n", tag, __FILENAME__, __LINE__, ##__VA_ARGS__); \ + fflush(stdout); \ + } \ + } while (0) + +// 分级日志实现 +#define LOGV(format, ...) LOG(VOLC_LOG_LEVEL_VERBOSE, "VRB", LOG_COLOR_PRUPLE, format, ##__VA_ARGS__) +#define LOGD(format, ...) LOG(VOLC_LOG_LEVEL_DEBUG, "DBG", LOG_COLOR_BLUE, format, ##__VA_ARGS__) +#define LOGI(format, ...) LOG(VOLC_LOG_LEVEL_INFO, "INF", LOG_COLOR_GREEN, format, ##__VA_ARGS__) +#define LOGW(format, ...) LOG(VOLC_LOG_LEVEL_WARN, "WRN", LOG_COLOR_YELLOW, format, ##__VA_ARGS__) +#define LOGE(format, ...) LOG(VOLC_LOG_LEVEL_ERROR, "ERR", LOG_COLOR_RED, format, ##__VA_ARGS__) + +#ifdef __cplusplus +} +#endif +#endif /* __CONV_AI_SRC_UTIL_VOLC_LOG_H__ */ \ No newline at end of file diff --git a/components/common/inc/volc_conv_ai.h b/components/common/inc/volc_conv_ai.h new file mode 100644 index 0000000..4d42b92 --- /dev/null +++ b/components/common/inc/volc_conv_ai.h @@ -0,0 +1,166 @@ +// Copyright (2025) Beijing Volcano Engine Technology Ltd. +// SPDX-License-Identifier: Apache-2.0 + +#ifndef __VOLC_CONV_AI_H__ +#define __VOLC_CONV_AI_H__ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define __volc_rt_api__ __attribute__((visibility("default"))) + +#define VOLC_VERSION_MAJOR 1 +#define VOLC_VERSION_MINOR 0 +#define VOLC_VERSION_PATCH 0 +#define VOLC_VERSION_CHECK(major, minor, patch) ((major * 10000U) + (minor * 100U) + patch) +#define VOLC_VERSION VOLC_VERSION_CHECK(VOLC_VERSION_MAJOR, VOLC_VERSION_MINOR, VOLC_VERSION_PATCH) + +typedef enum { + VOLC_ERR_NO_ERROR = 0, + VOLC_ERR_FAILED = -1, + VOLC_ERR_LICENSE_EXHAUSTED = -10, + VOLC_ERR_LICENSE_EXPIRED = -11, +} volc_error_code_e; + +typedef enum { + VOLC_AUDIO_DATA_TYPE_UNKNOWN = 0, + VOLC_AUDIO_DATA_TYPE_OPUS = 1, + VOLC_AUDIO_DATA_TYPE_G722 = 2, + VOLC_AUDIO_DATA_TYPE_AACLC = 3, + VOLC_AUDIO_DATA_TYPE_G711A = 4, + VOLC_AUDIO_DATA_TYPE_PCM = 5, + VOLC_AUDIO_DATA_TYPE_G711U = 6, +} volc_audio_data_type_e; + +typedef enum { + VOLC_AUDIO_CODEC_TYPE_UNKNOWN = 0, + VOLC_AUDIO_CODEC_TYPE_OPUS = 1, + VOLC_AUDIO_CODEC_TYPE_G722 = 2, + VOLC_AUDIO_CODEC_TYPE_AACLC = 3, + VOLC_AUDIO_CODEC_TYPE_G711A = 4, + VOLC_AUDIO_CODEC_TYPE_G711U = 5, +} volc_audio_codec_type_e; + +typedef struct { + volc_audio_data_type_e data_type; + // uint16_t sent_ts; + bool commit; +} volc_audio_frame_info_t; + +typedef enum { + VOLC_VIDEO_CODEC_TYPE_UNKNOWN = 0, + VOLC_VIDEO_CODEC_TYPE_H264 = 1, + VOLC_VIDEO_CODEC_TYPE_BYTEVC1 = 2, +} volc_video_codec_type_e; + +typedef enum { + VOLC_VIDEO_DATA_TYPE_UNKNOWN = 0, + VOLC_VIDEO_DATA_TYPE_H264 = 1, + VOLC_VIDEO_DATA_TYPE_BYTEVC1 = 2, + VOLC_VIDEO_DATA_TYPE_I420 = 3, +} volc_video_data_type_e; + +typedef enum { + VOLC_VIDEO_FRAME_TYPE_AUTO = 0, + VOLC_VIDEO_FRAME_TYPE_KEY = 1, + VOLC_VIDEO_FRAME_TYPE_DELTA = 2, +} volc_video_frame_type_e; + +typedef struct { + volc_video_data_type_e data_type; +} volc_video_frame_info_t; + +typedef enum { + VOLC_CONV_STATUS_LISTENING = 1, + VOLC_CONV_STATUS_THINKING, + VOLC_CONV_STATUS_ANSWERING, + VOLC_CONV_STATUS_INTERRUPTED, + VOLC_CONV_STATUS_ANSWER_FINISH, +} volc_conv_status_e; + +typedef struct { + // place holder + bool is_binary; +} volc_message_info_t; + +typedef enum { + VOLC_DATA_TYPE_AUDIO = 0, + VOLC_DATA_TYPE_VIDEO, + VOLC_DATA_TYPE_MESSAGE, + VOLC_DATA_TYPE_CNT, +} volc_data_type_e; + +typedef struct { + volc_data_type_e type; + union { + volc_audio_frame_info_t audio; + volc_video_frame_info_t video; + volc_message_info_t message; + } info; +} volc_data_info_t; + +typedef enum { + VOLC_EV_UNKNOWN = 0, // 未知事件 + VOLC_EV_CONNECTED, // 成功连接 + VOLC_EV_DISCONNECTED, // 断开连接 +} volc_event_code_e; + +typedef struct { + volc_event_code_e code; // 包含错误码、告警码、关键事件码等 + union { + int placeholder; + } data; // 事件数据,具体内容根据event_code而定 +} volc_event_t; + +typedef enum { + VOLC_MODE_RTC = 0, + VOLC_MODE_WS = 1, + VOLC_MODE_UNKNOWN, +} volc_mode_e; +typedef struct { + volc_mode_e mode; + char* bot_id; + bool wait_for_session_update; +} volc_opt_t; + +typedef void* volc_engine_t; + +typedef struct { + void (*on_volc_event)(volc_engine_t handle, volc_event_t* event, void* user_data); + void (*on_volc_conversation_status)(volc_engine_t handle, volc_conv_status_e status, void* user_data); + void (*on_volc_audio_data)(volc_engine_t handle, const void* data_ptr, size_t data_len, volc_audio_frame_info_t* info_ptr, void* user_data); + void (*on_volc_video_data)(volc_engine_t handle, const void* data_ptr, size_t data_len, volc_video_frame_info_t* info_ptr, void* user_data); + void (*on_volc_message_data)(volc_engine_t handle, const void* data_ptr, size_t data_len, volc_message_info_t* info_ptr, void* user_data); +} volc_event_handler_t; + +__volc_rt_api__ const char* volc_get_version(void); + +__volc_rt_api__ const char* volc_err_2_str(int err_code); + +__volc_rt_api__ int volc_create(volc_engine_t* handle, const char* config_json, volc_event_handler_t* event_handler, void* user_data); + +__volc_rt_api__ void volc_destroy(volc_engine_t handle); + +__volc_rt_api__ int volc_start(volc_engine_t handle, volc_opt_t* opt); + +__volc_rt_api__ int volc_stop(volc_engine_t handle); + +__volc_rt_api__ int volc_update(volc_engine_t handle, const void* data_ptr, size_t data_len); + +__volc_rt_api__ int volc_send_audio_data(volc_engine_t handle, const void* data_ptr, size_t data_len, volc_audio_frame_info_t* info_ptr); + +__volc_rt_api__ int volc_send_video_data(volc_engine_t handle, const void* data_ptr, size_t data_len, volc_video_frame_info_t* info_ptr); + +__volc_rt_api__ int volc_send_message(volc_engine_t handle, const void* data_ptr, size_t data_len, volc_message_info_t* info_ptr); + +__volc_rt_api__ int volc_interrupt(volc_engine_t handle); + +#ifdef __cplusplus +} +#endif +#endif /* __VOLC_CONV_AI_H__ */ \ No newline at end of file diff --git a/components/common/inc/volc_http.h b/components/common/inc/volc_http.h new file mode 100644 index 0000000..856e78a --- /dev/null +++ b/components/common/inc/volc_http.h @@ -0,0 +1,16 @@ +// Copyright (2025) Beijing Volcano Engine Technology Ltd. +// SPDX-License-Identifier: Apache-2.0 + +#ifndef __VOLC_HTTP_H__ +#define __VOLC_HTTP_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +char* volc_http_post(const char* uri, const char* post_data, int data_len); + +#ifdef __cplusplus +} +#endif +#endif /* __VOLC_HTTP_H__ */ \ No newline at end of file diff --git a/components/common/inc/volc_platform.h b/components/common/inc/volc_platform.h new file mode 100644 index 0000000..c8c4474 --- /dev/null +++ b/components/common/inc/volc_platform.h @@ -0,0 +1,66 @@ +// Copyright (2025) Beijing Volcano Engine Technology Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// 火山引擎平台适配层头文件 - 定义硬件抽象层的接口和数据结构 +// 高性能版本针对ESP32平台的系统调用和资源管理进行了优化 + +#ifndef __CONV_AI_PLATFORM_VOLC_PLATFORM_H__ +#define __CONV_AI_PLATFORM_VOLC_PLATFORM_H__ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// 内存管理函数声明 +void* hal_malloc(size_t size); // 内存分配 +void* hal_calloc(size_t num, size_t size); // 内存分配并清零 +void* hal_realloc(void* ptr, size_t new_size); // 内存重新分配 +void hal_free(void* ptr); // 内存释放 +#define HAL_SAFE_FREE(ptr) do { if (ptr) { hal_free(ptr); ptr = NULL; } } while (0) // 安全释放宏 + +// 互斥锁类型定义和函数声明 +typedef void* hal_mutex_t; // 互斥锁句柄 +hal_mutex_t hal_mutex_create(void); // 创建互斥锁 +void hal_mutex_lock(hal_mutex_t mutex); // 加锁 +void hal_mutex_unlock(hal_mutex_t mutex); // 解锁 +void hal_mutex_destroy(hal_mutex_t mutex); // 销毁互斥锁 + +// 时间管理函数声明 +uint64_t hal_get_time_ms(void); // 获取当前时间(毫秒) + +// 设备标识函数声明 +int hal_get_uuid(char* uuid, size_t size); // 获取设备UUID + +// 线程管理相关定义 +#define THREAD_NAME_MAX_LEN 16 // 线程名称最大长度 +typedef void* hal_tid_t; // 线程句柄 + +// 线程参数结构体 - 配置线程创建参数 +typedef struct { + char name[THREAD_NAME_MAX_LEN]; // 线程名称 + int priority; // 线程优先级 + int stack_size; // 栈大小 + int bind_cpu; // CPU绑定 + int stack_in_ext; // 是否使用外部栈 +} hal_thread_param_t; + +// 线程管理函数声明 +int hal_thread_create(hal_tid_t* thread, const hal_thread_param_t* param, void (*start_routine)(void *), void* args); // 创建线程 +int hal_thread_detach(hal_tid_t thread); // 分离线程 +void hal_thread_exit(hal_tid_t thread); // 退出线程 +void hal_thread_sleep(int time_ms); // 线程休眠 +void hal_thread_destroy(hal_tid_t thread); // 销毁线程 + +// 平台信息函数声明 +int hal_get_platform_info(char* info, size_t size); // 获取平台信息 + +// 随机数函数声明 +int hal_fill_random(uint8_t* data, size_t size); // 填充随机数 + +#ifdef __cplusplus +} +#endif +#endif /* __CONV_AI_PLATFORM_VOLC_PLATFORM_H__ */ \ No newline at end of file diff --git a/components/common/inc/volc_rtc.h b/components/common/inc/volc_rtc.h new file mode 100644 index 0000000..65b0ff2 --- /dev/null +++ b/components/common/inc/volc_rtc.h @@ -0,0 +1,42 @@ +// Copyright (2025) Beijing Volcano Engine Technology Ltd. +// SPDX-License-Identifier: Apache-2.0 + +#ifndef __CONV_AI_NODE_VOLC_RTC_H__ +#define __CONV_AI_NODE_VOLC_RTC_H__ +#if defined(ENABLE_RTC_MODE) +#include + +#include + +#include "VolcEngineRTCLite.h" + +#include "base/volc_base.h" +#include "base/volc_device_manager.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void* volc_rtc_t; + +volc_rtc_t volc_rtc_create(const char* appid, void* context, cJSON* p_config, volc_msg_cb message_callback, volc_data_cb data_callback); + +void volc_rtc_destroy(volc_rtc_t rtc); + +// 新增:extra_params 用于传递额外的AgentConfig配置参数 +int volc_rtc_start(volc_rtc_t rtc, const char* bot_id, volc_iot_info_t* iot_info, const char* extra_params); + +int volc_rtc_stop(volc_rtc_t rtc); + +int volc_rtc_send(volc_rtc_t rtc, const void* data, int size, volc_data_info_t* data_info); + +int volc_rtc_interrupt(volc_rtc_t rtc); + +int volc_rtc_send_jpg(volc_rtc_t rtc, void* data, int size); + +#endif + +#ifdef __cplusplus +} +#endif +#endif /* __CONV_AI_NODE_VOLC_RTC_H__ */ diff --git a/components/common/src/volc_device_manager.c b/components/common/src/volc_device_manager.c new file mode 100644 index 0000000..cfb3623 --- /dev/null +++ b/components/common/src/volc_device_manager.c @@ -0,0 +1,728 @@ +// Copyright (2025) Beijing Volcano Engine Technology Ltd. +// SPDX-License-Identifier: Apache-2.0 + +#include "base/volc_device_manager.h" +#include "util/volc_log.h" +#include "volc_platform.h" +#include "volc_http.h" + +#include +#include +#include +#include +#include + +// For HMAC-SHA256 signature generation +#include +#include +#include + +// AES解密辅助函数 +static char* volc_aes_decode_internal(const char* secret, const char* input, const bool partial_secret) { + size_t decoded_len = 0; + int key_bits = 0; + char* key = NULL; + unsigned char iv[16] = {0}; // 使用全零IV,与官方项目保持一致 + unsigned char* decoded_payload = NULL; + unsigned char* output = NULL; + + if (input == NULL || strlen(input) == 0) { + LOGE("Input is NULL or empty"); + return NULL; + } + + // 计算Base64解码后的长度(与官方项目保持一致) + int padding = 0; + int input_len = strlen(input); + if (input_len >= 2 && input[input_len - 1] == '=' && input[input_len - 2] == '=') { + padding = 2; + } else if (input[input_len - 1] == '=') { + padding = 1; + } + decoded_len = (input_len * 3) / 4 - padding; + + // 分配内存用于存储解码后的payload + decoded_payload = (unsigned char*)hal_calloc(decoded_len + 1, 1); + if (decoded_payload == NULL) { + LOGE("Failed to allocate memory for decoded payload"); + return NULL; + } + + // 执行Base64解码(与官方项目保持一致的错误处理) + size_t actual_len = 0; + int ret = mbedtls_base64_decode(decoded_payload, decoded_len + 1, &actual_len, + (const unsigned char*)input, input_len); + if (ret != 0) { + LOGW("Base64 decode failed with error: %d, but continuing", ret); + // 即使解码失败,也继续执行,与官方项目保持一致 + } else { + decoded_len = actual_len; + } + + // 准备AES密钥和IV(与官方项目保持一致) + if (partial_secret) { + // 使用128位密钥 + key = (char*)hal_calloc(16, 1); + if (key == NULL) { + LOGE("Failed to allocate memory for key"); + hal_free(decoded_payload); + return NULL; + } + memcpy(key, secret, 16); + key_bits = 128; + } else { + // 使用192位密钥 + size_t secret_len = strlen(secret); + if (secret_len < 24) { + LOGE("Secret length is less than 24, secret: %s", secret); + hal_free(decoded_payload); + return NULL; + } + key = (char*)hal_calloc(24, 1); + if (key == NULL) { + LOGE("Failed to allocate memory for key"); + hal_free(decoded_payload); + return NULL; + } + memcpy(key, secret, 24); + key_bits = 192; + } + + // 分配内存用于存储解密后的结果(与官方项目保持一致的内存分配) + output = (unsigned char*)hal_calloc(decoded_len + 16, 1); + if (output == NULL) { + LOGE("Failed to allocate memory for output"); + hal_free(key); + hal_free(decoded_payload); + return NULL; + } + + // 使用密钥的前16字节作为IV(与官方项目保持一致) + memcpy(iv, secret, 16); + + // 执行AES解密 - 使用CBC模式(与官方项目保持一致) + mbedtls_aes_context aes_ctx; + mbedtls_aes_init(&aes_ctx); + mbedtls_aes_setkey_dec(&aes_ctx, (const unsigned char*)key, key_bits); + + // 使用CBC模式解密 + ret = mbedtls_aes_crypt_cbc(&aes_ctx, MBEDTLS_AES_DECRYPT, decoded_len, iv, decoded_payload, output); + + mbedtls_aes_free(&aes_ctx); + + // 清理临时变量 + hal_free(key); + hal_free(decoded_payload); + + if (ret != 0) { + LOGE("Failed to decrypt payload: %d", ret); + hal_free(output); + return NULL; + } + + // 去除解密后可能存在的空白字符(与官方项目保持一致) + int len = strlen((const char*)output); + while (len > 0 && (output[len-1] < 32 || output[len-1] == 0x20)) { + --len; + } + output[len] = '\0'; + + return (char*)output; +} + +// 定义常量 +#define VOLC_IOT_HOST "https://iot-cn-shanghai.iot.volces.com" +#define VOLC_DYNAMIC_REGISTER_PATH "/2021-12-14/DynamicRegister" +#define VOLC_API_VERSION "2021-12-14" +#define VOLC_API_VERSION_QUERY_PARAM "Version=2021-12-14" +#define VOLC_API_ACTION_DYNAMIC_REGISTER "Action=DynamicRegister" +#define VOLC_GET_RTC_CONFIG_PATH "/2021-12-14/GetRTCConfig" +#define VOLC_API_ACTION_GET_RTC_CONFIG "Action=GetRTCConfig" + +// 错误码映射函数(暂时简化实现) +int volc_inter_err_2_ext_err(int code) { + switch (code) { + case ERROR_LICENSE_EXHAUSTED: + return -1; // VOLC_ERR_LICENSE_EXHAUSTED + case ERROR_LICENSE_EXPIRED: + return -2; // VOLC_ERR_LICENSE_EXPIRED + default: + return -3; // VOLC_ERR_FAILED + } +} + +// 生成签名函数 - 使用HMAC-SHA256算法 +char* volc_generate_signature(const char* secret_key, const char* product_key, const char* device_name, int rnd, uint64_t timestamp, int auth_type) { + if (!secret_key || !product_key || !device_name) { + LOGE("Invalid parameters for signature generation"); + return NULL; + } + + // 构造待签名字符串(与官方项目格式一致,确保参数顺序正确) + char sign_str[512] = {0}; + int sign_str_len = snprintf(sign_str, sizeof(sign_str), + "auth_type=%d&device_name=%s&random_num=%d&product_key=%s×tamp=%" PRIu64, + auth_type, device_name, rnd, product_key, timestamp); + + if (sign_str_len <= 0 || sign_str_len >= sizeof(sign_str)) { + LOGE("Failed to construct sign string"); + return NULL; + } + + // 计算HMAC-SHA256哈希值 + unsigned char hmac_result[32] = {0}; + mbedtls_md_context_t ctx; + mbedtls_md_init(&ctx); + + int ret = mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1); + if (ret != 0) { + LOGE("Failed to setup MD context: %d", ret); + mbedtls_md_free(&ctx); + return NULL; + } + + ret = mbedtls_md_hmac_starts(&ctx, (const unsigned char*)secret_key, strlen(secret_key)); + if (ret != 0) { + LOGE("Failed to start HMAC: %d", ret); + mbedtls_md_free(&ctx); + return NULL; + } + + ret = mbedtls_md_hmac_update(&ctx, (const unsigned char*)sign_str, sign_str_len); + if (ret != 0) { + LOGE("Failed to update HMAC: %d", ret); + mbedtls_md_free(&ctx); + return NULL; + } + + ret = mbedtls_md_hmac_finish(&ctx, hmac_result); + if (ret != 0) { + LOGE("Failed to finish HMAC: %d", ret); + mbedtls_md_free(&ctx); + return NULL; + } + + mbedtls_md_free(&ctx); + + // 将HMAC结果进行Base64编码 + // 使用更可靠的Base64长度计算方式 + size_t src_len = sizeof(hmac_result); + size_t base64_len = ((src_len + 2) / 3) * 4 + 1; // +1 for null terminator + + char* signature = (char*)hal_malloc(base64_len); + if (!signature) { + LOGE("Failed to allocate signature buffer"); + return NULL; + } + + size_t olen = 0; + int ret_b64 = mbedtls_base64_encode((unsigned char*)signature, base64_len, &olen, hmac_result, src_len); + if (ret_b64 != 0) { + LOGE("Failed to encode Base64: %d", ret_b64); + hal_free(signature); + return NULL; + } + + signature[olen] = '\0'; + + return signature; +} + +// 生成WebSocket签名函数(与官方项目实现一致) +char* volc_generate_signature_ws(const char* secret_key, const char* product_key, const char* device_name, const char* instance_id, int rnd, uint64_t timestamp, int auth_type) { + if (!secret_key || !product_key || !device_name || !instance_id) { + LOGE("Invalid parameters for WebSocket signature generation"); + return NULL; + } + + // 构造待签名字符串(与官方项目格式一致) + char sign_str[512] = {0}; + int sign_str_len = snprintf(sign_str, sizeof(sign_str), + "auth_type=%d&device_name=%s&random_num=%d&product_key=%s×tamp=%" PRIu64 "&instance_id=%s", + auth_type, device_name, rnd, product_key, timestamp, instance_id); + + if (sign_str_len <= 0 || sign_str_len >= sizeof(sign_str)) { + LOGE("Failed to construct WebSocket sign string"); + return NULL; + } + + // 计算HMAC-SHA256哈希值 + unsigned char hmac_result[32] = {0}; + mbedtls_md_context_t ctx; + mbedtls_md_init(&ctx); + + int ret = mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1); + if (ret != 0) { + LOGE("Failed to setup MD context for WebSocket signature: %d", ret); + mbedtls_md_free(&ctx); + return NULL; + } + + ret = mbedtls_md_hmac_starts(&ctx, (const unsigned char*)secret_key, strlen(secret_key)); + if (ret != 0) { + LOGE("Failed to start HMAC for WebSocket signature: %d", ret); + mbedtls_md_free(&ctx); + return NULL; + } + + ret = mbedtls_md_hmac_update(&ctx, (const unsigned char*)sign_str, sign_str_len); + if (ret != 0) { + LOGE("Failed to update HMAC for WebSocket signature: %d", ret); + mbedtls_md_free(&ctx); + return NULL; + } + + ret = mbedtls_md_hmac_finish(&ctx, hmac_result); + if (ret != 0) { + LOGE("Failed to finish HMAC for WebSocket signature: %d", ret); + mbedtls_md_free(&ctx); + return NULL; + } + + mbedtls_md_free(&ctx); + + // 将HMAC结果进行Base64编码 + size_t src_len = sizeof(hmac_result); + size_t base64_len = ((src_len + 2) / 3) * 4 + 1; // +1 for null terminator + + char* signature = (char*)hal_malloc(base64_len); + if (!signature) { + LOGE("Failed to allocate WebSocket signature buffer"); + return NULL; + } + + size_t olen = 0; + int ret_b64 = mbedtls_base64_encode((unsigned char*)signature, base64_len, &olen, hmac_result, src_len); + if (ret_b64 != 0) { + LOGE("Failed to encode WebSocket signature Base64: %d", ret_b64); + hal_free(signature); + return NULL; + } + + signature[olen] = '\0'; + + return signature; +} + +// 实现设备注册函数 +int volc_device_register(volc_iot_info_t* info, char** output) { + if (!info || !output || !info->product_key || !info->device_name || !info->product_secret) { + LOGE("Invalid input parameters: info=%p, output=%p, product_key=%p, device_name=%p, product_secret=%p", + info, output, info->product_key, info->device_name, info->product_secret); + return -1; + } + + int ret = 0; + uint64_t current_time = hal_get_time_ms(); + int32_t random_num = (int32_t)current_time; + char url[256] = {0}; + char* signature = NULL; + char* response = NULL; + cJSON* root = NULL; + cJSON* rtc_app_id = NULL; + char* device_secret_str = NULL; // 使用正确的变量名,与解密后的变量保持一致 + char* json_str = NULL; // 初始化json_str变量,避免未初始化警告 + cJSON* request_root = NULL; + + // 生成签名(使用正确的auth_type值1,与官方项目保持一致) + signature = volc_generate_signature(info->product_secret, info->product_key, info->device_name, random_num, current_time, 1); + if (!signature) { + LOGE("Failed to generate signature"); + return -1; + } + + // 构造URL + snprintf(url, sizeof(url), "%s%s?%s&%s", VOLC_IOT_HOST, VOLC_DYNAMIC_REGISTER_PATH, + VOLC_API_ACTION_DYNAMIC_REGISTER, VOLC_API_VERSION_QUERY_PARAM); + + LOGI("Device register URL: %s", url); + + // 使用cJSON构造请求体(与官方实现保持一致,避免字符串长度限制问题) + request_root = cJSON_CreateObject(); + if (!request_root) { + LOGE("Failed to create JSON root object for request"); + ret = -1; + goto err_out_label; + } + + // 总是添加InstanceID字段 + cJSON_AddStringToObject(request_root, "InstanceID", info->instance_id);// 必须添加 实例ID字段 + cJSON_AddStringToObject(request_root, "product_key", info->product_key);// 必须添加 产品密钥字段 + cJSON_AddStringToObject(request_root, "device_name", info->device_name);// 必须添加 设备名称字段 + // cJSON_AddStringToObject(request_root, "bot_id", info->bot_id); + cJSON_AddNumberToObject(request_root, "random_num", random_num);// 必须添加 随机数字段 + cJSON_AddNumberToObject(request_root, "timestamp", (double)current_time); // 与官方实现保持一致,使用double类型 + cJSON_AddNumberToObject(request_root, "auth_type", 1);// 必须添加 认证类型字段 + cJSON_AddStringToObject(request_root, "signature", signature);// 必须添加 签名字段 + json_str = cJSON_PrintUnformatted(request_root);// 必须添加 JSON字符串字段 + if (!json_str) { + LOGE("Failed to print JSON request body"); + cJSON_Delete(request_root); + ret = -1; + goto err_out_label; + } + + // 调用HTTP POST请求 + response = volc_http_post(url, json_str, strlen(json_str)); + if (!response) { + LOGE("Failed to send HTTP POST request for device registration"); + cJSON_Delete(request_root); + hal_free(json_str); + ret = -1; + goto err_out_label; + } + + LOGI("Device register response received: %s", response); + + // 解析JSON响应 + root = cJSON_Parse(response); + if (!root) { + LOGE("Failed to parse device registration response"); + ret = -1; + goto err_out_label; + } + + // 检查是否有错误信息(参考官方实现的检查方式) + cJSON* response_metadata = cJSON_GetObjectItem(root, "ResponseMetadata"); + if (response_metadata && response_metadata->type == cJSON_Object) { + cJSON* error = cJSON_GetObjectItem(response_metadata, "Error"); + if (error && error->type == cJSON_Object) { + cJSON* code_n = cJSON_GetObjectItem(error, "CodeN"); + if (code_n && code_n->type == cJSON_Number) { + // 检查是否是RTC许可证绑定失败错误 + int error_code = (int)code_n->valuedouble; + cJSON* message = cJSON_GetObjectItem(error, "Message"); + + LOGE("Device registration failed, error code: %d", error_code); + if (message && message->type == cJSON_String && message->valuestring) { + LOGE("Error message: %s", message->valuestring); + } + + // 如果是RTC许可证绑定失败,记录更详细的信息 + if (error_code == 12000130) { + LOGE("Device bind RTC license failed, please check device configuration"); + } + + ret = -1; + goto err_out_label; + } + } + } + + // 检查响应格式(兼容官方项目的ResponseMetadata/Result格式) + cJSON* result = cJSON_GetObjectItem(root, "Result"); + if (!result || result->type != cJSON_Object) { + LOGE("Device registration response missing or invalid 'Result' field"); + ret = -1; + goto err_out_label; + } + + // 提取rtc_app_id(安全检查) + rtc_app_id = cJSON_GetObjectItem(result, "RTCAppID"); + if (!rtc_app_id || rtc_app_id->type != cJSON_String || !rtc_app_id->valuestring || strlen(rtc_app_id->valuestring) == 0) { + LOGE("Device registration response missing or invalid 'RTCAppID' field"); + ret = -1; + goto err_out_label; + } + + // 从payload中解析device_secret(安全检查) + cJSON* payload = cJSON_GetObjectItem(result, "payload"); + if (!payload || payload->type != cJSON_String || !payload->valuestring || strlen(payload->valuestring) == 0) { + LOGE("Device registration response missing or invalid 'payload' field"); + ret = -1; + goto err_out_label; + } + + // 使用AES解密payload获取device_secret + device_secret_str = volc_aes_decode_internal(info->product_secret, payload->valuestring, true); + if (!device_secret_str) { + LOGE("Failed to decode device secret from payload"); + ret = -1; + goto err_out_label; + } + + // 验证解密结果是否有效 + if (strlen(device_secret_str) == 0) { + LOGE("Decrypted device secret is empty"); + hal_free(device_secret_str); + device_secret_str = NULL; + ret = -1; + goto err_out_label; + } + + LOGI("Decoded device secret: %s", device_secret_str); + + // 存储device_secret到iot_info结构体中 + info->device_secret = strdup(device_secret_str); + if (!info->device_secret) { + LOGE("Failed to allocate memory for device secret"); + hal_free(device_secret_str); + ret = -1; + goto err_out_label; + } + + // 返回device_secret + if (*output) { + hal_free(*output); + *output = NULL; + } + + // 使用解密后的device_secret_str赋值给output + *output = strdup(device_secret_str); + if (!*output) { + LOGE("Failed to allocate memory for device secret"); + hal_free(device_secret_str); + ret = -1; + goto err_out_label; + } + + // 释放临时内存 + hal_free(device_secret_str); + device_secret_str = NULL; + + // 保存rtc_app_id + if (info->rtc_app_id) { + hal_free(info->rtc_app_id); + info->rtc_app_id = NULL; + } + + info->rtc_app_id = strdup(rtc_app_id->valuestring); + if (!info->rtc_app_id) { + LOGE("Failed to allocate memory for rtc_app_id"); + ret = -1; + goto err_out_label; + } + + LOGI("Device registration successful: rtc_app_id=%s", info->rtc_app_id); + +err_out_label: + // 清理资源 + if (root) { + cJSON_Delete(root); + } + + if (request_root) { + cJSON_Delete(request_root); + } + + if (json_str) { + hal_free(json_str); + } + + if (response) { + hal_free(response); + } + + if (signature) { + hal_free(signature); + } + + if (device_secret_str) { + hal_free(device_secret_str); + } + + return ret; +} + +// 实现RTC配置获取函数 ,extra_params 用于传递额外的AgentConfig配置参数 +int volc_get_rtc_config(volc_iot_info_t* info, int audio_codec, const char* bot_id, const char* task_id, const char* extra_params, volc_room_info_t* room_info) { + if (!info || !room_info || !bot_id || !task_id || !info->product_key || !info->device_name || !info->device_secret) { + LOGE("Invalid input parameters: info=%p, room_info=%p, bot_id=%p, task_id=%p, product_key=%p, device_name=%p, device_secret=%p", + info, room_info, bot_id, task_id, info->product_key, info->device_name, info->device_secret); + return -1; + } + + int ret = 0; + uint64_t current_time = hal_get_time_ms(); + int32_t random_num = (int32_t)current_time; + char url[256] = {0}; + char* signature = NULL; + char* response = NULL; + cJSON* root = NULL; + cJSON* channel_name = NULL; + cJSON* uid = NULL; + cJSON* token = NULL; + cJSON* request_root = NULL; + char* json_str = NULL; + + // 生成签名(使用auth_type=0,与官方代码一致) + signature = volc_generate_signature(info->device_secret, info->product_key, info->device_name, random_num, current_time, 0); + if (!signature) { + LOGE("Failed to generate signature"); + return -1; + } + + // 构造URL + snprintf(url, sizeof(url), "%s%s?%s&%s", VOLC_IOT_HOST, VOLC_GET_RTC_CONFIG_PATH, + VOLC_API_ACTION_GET_RTC_CONFIG, VOLC_API_VERSION_QUERY_PARAM); + + // 使用cJSON构造JSON请求体(与官方代码保持一致) + request_root = cJSON_CreateObject(); + cJSON_AddStringToObject(request_root, "InstanceID", info->instance_id); + cJSON_AddStringToObject(request_root, "product_key", info->product_key); + cJSON_AddStringToObject(request_root, "device_name", info->device_name); + cJSON_AddNumberToObject(request_root, "random_num", random_num); + cJSON_AddNumberToObject(request_root, "timestamp", (double)current_time); + cJSON_AddStringToObject(request_root, "signature", signature); + cJSON_AddStringToObject(request_root, "bot_id", bot_id); + cJSON_AddNumberToObject(request_root, "audio_codec", audio_codec); + cJSON_AddStringToObject(request_root, "task_id", task_id); + + // 如果有额外参数(如AgentConfig),解析并合并到请求体中 + if (extra_params && strlen(extra_params) > 0) { + cJSON* params_json = cJSON_Parse(extra_params); + if (params_json) { + // 如果是对象,遍历所有字段并添加到request_root + if (cJSON_IsObject(params_json)) { + cJSON* child = params_json->child; + while (child) { + cJSON_AddItemToObject(request_root, child->string, cJSON_Duplicate(child, 1)); + child = child->next; + } + } else { + // 如果不是对象(不太可能),尝试直接添加为AgentConfig(备选方案) + LOGW("extra_params is not a JSON object, ignoring"); + } + cJSON_Delete(params_json); + } else { + LOGW("Failed to parse extra_params JSON"); + } + } + + json_str = cJSON_PrintUnformatted(request_root); + + LOGI("Get RTC config URL: %s", url); + LOGI("RTC config request body: %s", json_str); + + // 调用HTTP POST请求 + response = volc_http_post(url, json_str, strlen(json_str)); + if (!response) { + LOGE("Failed to send HTTP POST request for RTC config"); + ret = -1; + goto err_out_label; + } + + LOGI("Get RTC config response received"); + LOGI("RTC config response content: %s", response); + + // 解析JSON响应 + root = cJSON_Parse(response); + if (!root) { + LOGE("Failed to parse RTC config response: %s", response); + ret = -1; + goto err_out_label; + } + + // 检查响应格式(兼容官方项目的ResponseMetadata/Result格式) + cJSON* result = cJSON_GetObjectItem(root, "Result"); + if (!result || result->type != cJSON_Object) { + LOGE("RTC config response missing or invalid 'Result' field"); + ret = -1; + goto err_out_label; + } + + // 提取RTC配置信息(与官方代码保持一致的字段名) + channel_name = cJSON_GetObjectItem(result, "RoomID"); + if (!channel_name || channel_name->type != cJSON_String || !channel_name->valuestring) { + LOGE("RTC config response missing or invalid 'RoomID' field"); + ret = -1; + goto err_out_label; + } + + uid = cJSON_GetObjectItem(result, "UserID"); + if (!uid || uid->type != cJSON_String || !uid->valuestring) { + LOGE("RTC config response missing or invalid 'UserID' field"); + ret = -1; + goto err_out_label; + } + + token = cJSON_GetObjectItem(result, "Token"); + if (!token || token->type != cJSON_String || !token->valuestring) { + LOGE("RTC config response missing or invalid 'Token' field"); + ret = -1; + goto err_out_label; + } + + // 提取TaskID(与官方代码保持一致) + cJSON* task_id_json = cJSON_GetObjectItem(result, "TaskID"); + if (!task_id_json || task_id_json->type != cJSON_String || !task_id_json->valuestring) { + LOGE("RTC config response missing or invalid 'TaskID' field"); + ret = -1; + goto err_out_label; + } + + // 保存RTC配置(先释放旧的内存) + char* new_channel_name = strdup(channel_name->valuestring); + char* new_uid = strdup(uid->valuestring); + char* new_token = strdup(token->valuestring); + char* new_task_id = strdup(task_id_json->valuestring); + + // 检查内存分配是否成功 + if (!new_channel_name || !new_uid || !new_token || !new_task_id) { + LOGE("Failed to allocate memory for RTC config"); + ret = -1; + + // 清理已分配的内存 + if (new_channel_name) hal_free(new_channel_name); + if (new_uid) hal_free(new_uid); + if (new_token) hal_free(new_token); + if (new_task_id) hal_free(new_task_id); + + goto err_out_label; + } + + // 释放旧的内存并设置新的指针 + if (room_info->rtc_opt.p_channel_name) { + hal_free(room_info->rtc_opt.p_channel_name); + } + if (room_info->rtc_opt.p_uid) { + hal_free(room_info->rtc_opt.p_uid); + } + if (room_info->rtc_opt.p_token) { + hal_free(room_info->rtc_opt.p_token); + } + if (room_info->task_id) { + hal_free(room_info->task_id); + } + + // 保存RTC配置 + room_info->rtc_opt.p_channel_name = new_channel_name; + room_info->rtc_opt.p_uid = new_uid; + room_info->rtc_opt.p_token = new_token; + room_info->task_id = new_task_id; + + // 检查是否所有分配都成功 + if (!room_info->rtc_opt.p_channel_name || !room_info->rtc_opt.p_uid || + !room_info->rtc_opt.p_token || !room_info->task_id) { + LOGE("Failed to allocate memory for RTC config"); + ret = -1; + goto err_out_label; + } + + LOGI("Retrieved RTC config: channel_name=%s, uid=%s, task_id=%s", + room_info->rtc_opt.p_channel_name, room_info->rtc_opt.p_uid, room_info->task_id); + +err_out_label: + // 清理资源 + if (root) { + cJSON_Delete(root); + } + + if (request_root) { + cJSON_Delete(request_root); + } + + if (json_str) { + hal_free(json_str); + } + + if (response) { + hal_free(response); + } + + if (signature) { + hal_free(signature); + } + + return ret; +} diff --git a/components/common/src/volc_http.c b/components/common/src/volc_http.c new file mode 100644 index 0000000..78e0e35 --- /dev/null +++ b/components/common/src/volc_http.c @@ -0,0 +1,207 @@ +// Copyright (2025) Beijing Volcano Engine Technology Ltd. +// SPDX-License-Identifier: Apache-2.0 + +#include "volc_http.h" + +#include +#include +#include +#include "volc_platform.h" +#include "esp_crt_bundle.h" + +static const char* TAG = "VOLC_HTTP"; + +// Response data structure for event handler +typedef struct { + char* buffer; + size_t buffer_size; + size_t total_read; + size_t max_size; +} response_data_t; + +// HTTP client event handler to capture response data +static esp_err_t http_event_handler(esp_http_client_event_t *evt) +{ + response_data_t *response_data = (response_data_t *)evt->user_data; + + switch (evt->event_id) { + case HTTP_EVENT_ON_DATA: + if (response_data && evt->data && evt->data_len > 0) { + // Calculate available space in buffer + size_t available = response_data->max_size - response_data->total_read; + if (available > 0) { + // Copy data to buffer (don't exceed max size) + size_t copy_len = (evt->data_len < available) ? evt->data_len : available; + memcpy(response_data->buffer + response_data->total_read, evt->data, copy_len); + response_data->total_read += copy_len; + + ESP_LOGI(TAG, "HTTP_EVENT_ON_DATA: read %d bytes, total: %d", evt->data_len, response_data->total_read); + } + } + break; + default: + break; + } + return ESP_OK; +} + +char* volc_http_post(const char* uri, const char* post_data, int data_len) +{ + char* response_buffer = NULL; + esp_http_client_handle_t client = NULL; + int response_status = 0; + esp_err_t err = ESP_OK; + int attempts = 0; + const int max_attempts = 3; + + if (!uri || !post_data || data_len <= 0) { + ESP_LOGE(TAG, "Invalid parameters: uri=%p, post_data=%p, data_len=%d", uri, post_data, data_len); + return NULL; + } + + ESP_LOGD(TAG, "HTTP POST: uri=%s, post_data=%s, data_len=%d", uri, post_data, data_len); + + /* Perform HTTP request with retries */ + while (attempts < max_attempts) { + ESP_LOGI(TAG, "HTTP POST attempt %d/%d", attempts + 1, max_attempts); + + /* Allocate buffer for response */ + const size_t max_response_size = 16384; // 增加缓冲区大小到16KB,解决响应被截断的问题 + char *buffer = (char*)hal_malloc(max_response_size + 1); + if (!buffer) { + ESP_LOGE(TAG, "Failed to allocate buffer for response reading"); + goto cleanup_attempt; + } + + memset(buffer, 0, max_response_size + 1); + + /* Initialize response data structure */ + response_data_t response_data = { + .buffer = buffer, + .buffer_size = max_response_size + 1, + .total_read = 0, + .max_size = max_response_size + }; + + /* Configure HTTP client with event handler */ + esp_http_client_config_t config = { + .url = uri, + .timeout_ms = 15000, + .transport_type = HTTP_TRANSPORT_OVER_SSL, + .crt_bundle_attach = esp_crt_bundle_attach, + .skip_cert_common_name_check = true, + .method = HTTP_METHOD_POST, + .event_handler = http_event_handler, + .user_data = &response_data, // Pass response data to event handler + .disable_auto_redirect = false, // Enable redirects + .max_redirection_count = 3, // Allow up to 3 redirects + }; + + /* Initialize HTTP client */ + client = esp_http_client_init(&config); + if (client == NULL) { + ESP_LOGE(TAG, "Failed to initialize HTTP client"); + hal_free(buffer); + goto cleanup_attempt; + } + + /* Set content type to application/json */ + err = esp_http_client_set_header(client, "Content-Type", "application/json"); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to set Content-Type header: %s", esp_err_to_name(err)); + goto cleanup_attempt; + } + + /* Set POST data */ + err = esp_http_client_set_post_field(client, post_data, data_len); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to set POST field: %s", esp_err_to_name(err)); + goto cleanup_attempt; + } + + /* Perform HTTP request */ + /* The event handler will capture response data */ + ESP_LOGI(TAG, "Performing HTTP request..."); + err = esp_http_client_perform(client); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to perform HTTP request: %s", esp_err_to_name(err)); + goto cleanup_attempt; + } + + /* Get response status */ + response_status = esp_http_client_get_status_code(client); + ESP_LOGI(TAG, "HTTP response status: %d", response_status); + + if (response_status == 200) { + /* Get content length from response header */ + int64_t content_length = esp_http_client_get_content_length(client); + ESP_LOGI(TAG, "Content-Length: %lld, actually read: %d", content_length, response_data.total_read); + + if (response_data.total_read > 0) { + /* Ensure null termination */ + buffer[response_data.total_read] = '\0'; + + ESP_LOGI(TAG, "Successfully read response: %d bytes", response_data.total_read); + ESP_LOGD(TAG, "Response data: %s", buffer); + + /* Copy to final buffer */ + response_buffer = (char*)hal_malloc(response_data.total_read + 1); + if (response_buffer) { + memcpy(response_buffer, buffer, response_data.total_read + 1); + } else { + ESP_LOGE(TAG, "Failed to allocate final response buffer"); + } + } else { + ESP_LOGE(TAG, "Failed to read any response data via event handler"); + + /* Try a fallback method - read directly after perform */ + ESP_LOGI(TAG, "Trying fallback: direct read after perform..."); + int fallback_read = esp_http_client_read_response(client, buffer, max_response_size); + if (fallback_read > 0) { + ESP_LOGI(TAG, "Fallback successful: read %d bytes", fallback_read); + buffer[fallback_read] = '\0'; + ESP_LOGD(TAG, "Response data: %s", buffer); + + response_buffer = (char*)hal_malloc(fallback_read + 1); + if (response_buffer) { + memcpy(response_buffer, buffer, fallback_read + 1); + } + } else { + ESP_LOGE(TAG, "Fallback also failed: %d", fallback_read); + } + } + } else { + ESP_LOGW(TAG, "Non-200 status: %d", response_status); + } + + cleanup_attempt: + /* Clean up client and buffer */ + if (client != NULL) { + esp_http_client_cleanup(client); + client = NULL; + } + if (buffer != NULL) { + hal_free(buffer); + buffer = NULL; + } + + /* If we got a response, break the retry loop */ + if (response_buffer) { + break; + } + + attempts++; + if (attempts < max_attempts) { + int backoff_ms = 500 * attempts; + ESP_LOGI(TAG, "Retrying in %d ms...", backoff_ms); + hal_thread_sleep(backoff_ms); + } + } + + if (attempts == max_attempts) { + ESP_LOGE(TAG, "HTTP POST failed after %d attempts", max_attempts); + } + + ESP_LOGI(TAG, "HTTP POST request completed, response_buffer=%p", response_buffer); + return response_buffer; +} \ No newline at end of file diff --git a/components/common/src/volc_json.c b/components/common/src/volc_json.c new file mode 100644 index 0000000..85081db --- /dev/null +++ b/components/common/src/volc_json.c @@ -0,0 +1,208 @@ +// Copyright (2025) Beijing Volcano Engine Technology Ltd. +// SPDX-License-Identifier: Apache-2.0 + +#include "util/volc_json.h" + +#include +#include +#include + +#include "volc_platform.h" +#include "util/volc_list.h" +#include "util/volc_log.h" + +static int _get_array_index(char *fmt) +{ + char *start, *end; + int num; + start = strchr(fmt, '['); + if (NULL != start) { + end = strchr(fmt, ']'); + if (NULL != end) { + *start = *end = '\0'; + start++; + num = atoi(start); + if (num >= 0) { + return num; + } + } + } + return -1; +} + +static cJSON *_read_item_object(cJSON *root, const char *fmt) +{ + cJSON *parent = root; + cJSON *child = NULL; + int fmt_size = strlen(fmt) + 1; + char *buf = (char *)hal_malloc(fmt_size); + char *buf_p = buf; + int offset = 0; + int arr_index; + int i = 0; + if (NULL == buf) { + LOGE("malloc failed !"); + return NULL; + } + /* 1. split */ + memcpy(buf, fmt, fmt_size); + for (; i < fmt_size; i++) { + if ('.' == buf[i]) { + buf[i] = '\0'; + } + } + /* 2. parse */ + while (fmt_size > 0) { + offset = strlen(buf) + 1; + arr_index = _get_array_index(buf); + child = cJSON_GetObjectItem(parent, buf); + if (NULL == child) { + goto L_ERROR; + } + if (arr_index >= 0) { + child = cJSON_GetArrayItem(child, arr_index); + if (NULL == child) { + goto L_ERROR; + } + } + parent = child; + buf = buf + offset; + fmt_size -= offset; + } + +L_ERROR: + HAL_SAFE_FREE(buf_p); + return child; +} + +static int _read_item_int(cJSON *root, const char *fmt, int *dst) +{ + if (NULL == root || NULL == fmt) { + LOGW("invalid input root %p fmt %p", root, fmt); + return -1; + } + cJSON *obj = _read_item_object(root, fmt); + if (!cJSON_IsNumber(obj)) { + LOGD("the value of the key(%s) is not the INT type", fmt); + return -1; + } + if (NULL != dst) { + *dst = (int)cJSON_GetNumberValue(obj); + } + return 0; +} + +static int _read_item_double(cJSON *root, const char *fmt, double *dst) +{ + if (NULL == root || NULL == fmt) { + LOGW("invalid input root %p fmt %p", root, fmt); + return -1; + } + cJSON *obj = _read_item_object(root, fmt); + if (!cJSON_IsNumber(obj)) { + LOGD("the value of the key(%s) is not the DOUBLE type", fmt); + return -1; + } + if (NULL != dst) { + *dst = obj->valuedouble; + } + return 0; +} + +static int _read_item_string(cJSON *root, const char *fmt, char **dst) +{ + if (NULL == root || NULL == fmt) { + LOGW("invalid input root %p fmt %p", root, fmt); + return -1; + } + cJSON *obj = _read_item_object(root, fmt); + if (!cJSON_IsString(obj)) { + LOGD("the value of the key(%s) is not the STRING type", fmt); + return -1; + } + if (NULL != dst) { + *dst = (char *)hal_malloc(strlen(obj->valuestring) + 1); + if (NULL == *dst) { + LOGE("memory alloc failed"); + return -1; + } + strncpy(*dst, obj->valuestring, strlen(obj->valuestring)); + (*dst)[strlen(obj->valuestring)] = '\0'; + } + return 0; +} + +static int _read_item_bool(cJSON *root, const char *fmt, bool *dst) +{ + if (!root || !fmt) { + LOGW("invalid arguments, root: %p, fmt: %p", root, fmt); + return -1; + } + cJSON *obj = _read_item_object(root, fmt); + if (!cJSON_IsBool(obj)) { + LOGW("the value of the key(%s) is not the BOOL type", fmt); + return -1; + } + if (NULL != dst) { + *dst = cJSON_IsTrue(obj) ? true: false; + } + return 0; +} + +int volc_json_read_int(cJSON *root, const char *fmt, int *dst) { + return _read_item_int(root, fmt, dst); +} + +int volc_json_read_double(cJSON *root, const char *fmt, double *dst) { + return _read_item_double(root, fmt, dst); +} + +int volc_json_read_string(cJSON *root, const char *fmt, char **dst) { + return _read_item_string(root, fmt, dst); +} + +int volc_json_read_bool(cJSON *root, const char *fmt, bool *dst) +{ + return _read_item_bool(root, fmt, dst); +} + +int volc_json_read_object(cJSON *root, const char *fmt, cJSON **dst) { + cJSON *obj; + if (NULL == root || NULL == fmt) { + LOGW("invalid input root %p fmt %p", root, fmt); + return -1; + } + obj = _read_item_object(root, fmt); + if (NULL == obj) { + LOGD("parse error, fmt=%s", fmt); + return -1; + } + if (NULL != dst) { + *dst = cJSON_Duplicate(obj, 1); + if (NULL == *dst) { + LOGE("memory alloc failed"); + return -1; + } + } + return 0; +} + +int volc_json_check_int(cJSON *root, const char *fmt) +{ + return _read_item_int(root, fmt, NULL); +} + +int volc_json_check_double(cJSON *root, const char *fmt) +{ + return _read_item_double(root, fmt, NULL); +} + +int volc_json_check_string(cJSON *root, const char *fmt) +{ + return _read_item_string(root, fmt, NULL); +} + +int volc_json_check_bool(cJSON *root, const char *fmt) +{ + return _read_item_bool(root, fmt, NULL); +} \ No newline at end of file diff --git a/components/common/src/volc_platform.c b/components/common/src/volc_platform.c new file mode 100644 index 0000000..23e069c --- /dev/null +++ b/components/common/src/volc_platform.c @@ -0,0 +1,155 @@ +// Copyright (2025) Beijing Volcano Engine Technology Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// 火山引擎平台适配层 - 实现ESP32平台相关的硬件抽象层函数 +// 高性能版本针对ESP32平台的内存管理和线程调度进行了优化 + +#include "volc_platform.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// 内存分配函数 - 使用SPIRAM进行内存分配,优化大内存使用场景 +void* hal_malloc(size_t size) { + return heap_caps_malloc(size,MALLOC_CAP_SPIRAM | MALLOC_CAP_DEFAULT); +} + +// 内存分配并清零函数 - 使用SPIRAM进行内存分配 +void* hal_calloc(size_t num, size_t size) { + return heap_caps_calloc(num,size,MALLOC_CAP_SPIRAM | MALLOC_CAP_DEFAULT); +} + +// 内存重新分配函数 - 使用SPIRAM进行内存重分配 +void* hal_realloc(void* ptr, size_t new_size) { + return heap_caps_realloc(ptr,new_size,MALLOC_CAP_SPIRAM | MALLOC_CAP_DEFAULT); +} + +// 内存释放函数 - 释放SPIRAM分配的内存 +void hal_free(void* ptr) { + heap_caps_free(ptr); +} + +// 互斥锁创建函数 - 创建线程安全的互斥锁 +hal_mutex_t hal_mutex_create(void) { + SemaphoreHandle_t m = xSemaphoreCreateMutex(); + return (hal_mutex_t)m; +} + +void hal_mutex_lock(hal_mutex_t mutex) { + xSemaphoreTake((SemaphoreHandle_t)mutex, portMAX_DELAY); +} + +void hal_mutex_unlock(hal_mutex_t mutex) { + xSemaphoreGive((SemaphoreHandle_t)mutex); +} + +void hal_mutex_destroy(hal_mutex_t mutex) { + if (mutex) { + vSemaphoreDelete((SemaphoreHandle_t)mutex); + } +} + +// 获取当前时间函数 - 使用esp_timer获取微秒再转毫秒,避免CLOCK_REALTIME依赖 +uint64_t hal_get_time_ms(void) { + int64_t us = esp_timer_get_time(); + if (us < 0) us = 0; + return (uint64_t)(us / 1000); +} + +// 获取设备UUID函数 - 基于MAC地址生成唯一设备标识符 +int hal_get_uuid(char* uuid, size_t size) { + esp_netif_t *netif = NULL; + + while ((netif = esp_netif_next_unsafe(netif)) != NULL) { + uint8_t hwaddr[6] = {0}; + esp_netif_get_mac(netif, hwaddr); + snprintf(uuid, size,"%02X%02X%02X%02X%02X%02X",hwaddr[0], hwaddr[1], hwaddr[2], hwaddr[3], hwaddr[4], hwaddr[5]); + } + return 0; +} + +// 线程创建函数 - 创建FreeRTOS任务,支持SPIRAM栈分配 +int hal_thread_create(hal_tid_t* thread, const hal_thread_param_t* param, void (*start_routine)(void *), void* args) { + int ret = 0; + int stack_size = 0; + int priority = 0; + BaseType_t core_id = 0; + TaskHandle_t* handle = NULL; + if (NULL == thread || NULL == start_routine) { + return -1; + } + + if (NULL != param) { + stack_size = param->stack_size <= 0 ? 12288 : param->stack_size; + priority = param->priority <= 0 ? 3 : param->priority; + core_id = 1; + } else { + stack_size = 12288; + priority = 3; + core_id = tskNO_AFFINITY; + } + handle = (TaskHandle_t *)hal_calloc(1, sizeof(TaskHandle_t)); + if (NULL == handle) { + return -1; + } + *thread = (hal_tid_t *)handle; + + if(param->stack_in_ext) { + ret = xTaskCreatePinnedToCoreWithCaps(start_routine, param->name, stack_size, args, priority, handle, core_id, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); + } else { + ret = xTaskCreatePinnedToCore(start_routine, param->name, stack_size, args, priority, handle, core_id); + } + if (pdPASS != ret) { + return -1; + } + return 0; +} + +// 线程分离函数 - 分离线程资源(ESP32平台暂不支持) +int hal_thread_detach(hal_tid_t thread) { + return 0; +} + +// 线程退出函数 - 退出当前线程 +void hal_thread_exit(hal_tid_t thread) { + vTaskDelete(NULL); +} + +// 线程休眠函数 - 使当前线程休眠指定毫秒数 +void hal_thread_sleep(int time_ms) { + vTaskDelay(pdMS_TO_TICKS(time_ms)); +} + +// 线程销毁函数 - 销毁线程句柄并释放资源 +void hal_thread_destroy(hal_tid_t thread) { + if (NULL == thread) { + return; + } + hal_free(thread); +} + +// 获取平台信息函数 - 返回平台标识符 +int hal_get_platform_info(char* info, size_t size) { + if (NULL == info || size <= 0) { + return -1; + } + snprintf(info, size, "esp32"); + return 0; +} + +// 填充随机数函数 - 使用ESP32硬件随机数生成器 +int hal_fill_random(uint8_t* data, size_t size) { + if (NULL == data || size <= 0) { + return -1; + } + esp_fill_random((void *)data, size); + return 0; +} \ No newline at end of file diff --git a/components/common/src/volc_rtc.c b/components/common/src/volc_rtc.c new file mode 100644 index 0000000..9db5bff --- /dev/null +++ b/components/common/src/volc_rtc.c @@ -0,0 +1,790 @@ +// Copyright (2025) Beijing Volcano Engine Technology Ltd. +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include +#include +#include +#include + +#include +#include + +// 确保在包含volc_rtc.h之前定义ENABLE_RTC_MODE +#ifndef ENABLE_RTC_MODE +#define ENABLE_RTC_MODE +#endif + +#include "VolcEngineRTCLite.h" +#include "volc_rtc.h" +#include "volc_platform.h" +#include "util/volc_list.h" +#include "util/volc_log.h" +#include "util/volc_json.h" +#include "base/volc_base.h" +#include "base/volc_device_manager.h" + +// 移除多余的ENABLE_RTC_MODE条件编译块,因为它已经在头文件中处理 + +#define MAGIC_CONTROL "ctrl" +#define MAGIC_CONV "conv" +#define MAGIC_LENGTH 4 +#define MAGIC_OFFSET 8 +const char* interrupt_str = "{\"Command\":\"interrupt\"}"; + +typedef struct { + bool b_pipeline_started; + bool b_user_joined; + bool b_channel_joined; + bool b_first_keyframe_received; + bool b_fini; + bool b_audio_publish; + bool b_video_publish; + bool b_audio_subscribe; + bool b_video_subscribe; + char* p_appid; + char* p_channel_name; + char* p_user_id; + char* p_remote_user_id; + char* p_token; + void* context; + int audio_codec; + volc_room_info_t info; + volc_msg_cb message_callback; + volc_data_cb data_callback; + byte_rtc_engine_t rtc; + byte_rtc_event_handler_t event_handler; +} rtc_impl_t; + +static bool __is_first_keyframe_not_received(rtc_impl_t* rtc, int is_key_frame) +{ + return (!rtc->b_first_keyframe_received && !is_key_frame); +} + +static int _build_binary_message(const char* magic, const char* message, + uint8_t** out_buf, size_t* out_len) { + size_t magic_len = strlen(magic); + size_t msg_len = strlen(message); + + // 分配内存:魔术字 + 4字节长度 + 消息内容 + *out_len = magic_len + 4 + msg_len; + *out_buf = (uint8_t*)hal_malloc(*out_len); + if (!*out_buf) { + LOGE("hal_malloc failed"); + return -1; + } + + // 填充魔术字 + memcpy(*out_buf, magic, magic_len); + + // 以大端序填充长度 + uint8_t* len_ptr = *out_buf + magic_len; + len_ptr[0] = (uint8_t)((msg_len >> 24) & 0xFF); // 最高位字节 + len_ptr[1] = (uint8_t)((msg_len >> 16) & 0xFF); + len_ptr[2] = (uint8_t)((msg_len >> 8) & 0xFF); + len_ptr[3] = (uint8_t)(msg_len & 0xFF); // 最低位字节 + + // 填充消息内容 + memcpy(*out_buf + magic_len + 4, message, msg_len); + + return 0; +} + +static int __rtc_start(rtc_impl_t* rtc, volc_rtc_option_t* option) +{ + byte_rtc_room_options_t room_opt = {0}; + if (!rtc || !option) { + LOGE("rtc or option is NULL"); + return -1; + } + if (!option->p_channel_name || !option->p_uid || !option->p_token) { + LOGE("invalid rtc option: channel(%p) uid(%p) token(%p)", option->p_channel_name, option->p_uid, option->p_token); + return -1; + } + if (strlen(option->p_channel_name) == 0 || strlen(option->p_uid) == 0 || strlen(option->p_token) == 0) { + LOGE("invalid rtc option: empty field"); + return -1; + } + + room_opt.auto_publish_audio = rtc->b_audio_publish; + room_opt.auto_publish_video = rtc->b_video_publish; + room_opt.auto_subscribe_audio = rtc->b_audio_subscribe; + room_opt.auto_subscribe_video = rtc->b_video_subscribe; + + // 记录内存使用情况 + size_t heap_free_before = heap_caps_get_free_size(MALLOC_CAP_DEFAULT); + size_t spiram_free_before = heap_caps_get_free_size(MALLOC_CAP_SPIRAM); + + LOGI("Joining channel: %s, uid: %s, token: %s, vpub: %d, vsub: %d, apub: %d, asub: %d", + option->p_channel_name, option->p_uid, option->p_token, + (int)room_opt.auto_publish_video, (int)room_opt.auto_subscribe_video, + (int)room_opt.auto_publish_audio, (int)room_opt.auto_subscribe_audio); + LOGI("Memory before byte_rtc_join_room - Heap: %u bytes, SPIRAM: %u bytes", + (unsigned)heap_free_before, (unsigned)spiram_free_before); + + int ret = byte_rtc_join_room(rtc->rtc, option->p_channel_name, option->p_uid, option->p_token, &room_opt); + + // 记录内存使用情况 + size_t heap_free_after = heap_caps_get_free_size(MALLOC_CAP_DEFAULT); + size_t spiram_free_after = heap_caps_get_free_size(MALLOC_CAP_SPIRAM); + + LOGI("Memory after byte_rtc_join_room - Heap: %u bytes (change: %d), SPIRAM: %u bytes (change: %d)", + (unsigned)heap_free_after, (int)(heap_free_after - heap_free_before), + (unsigned)spiram_free_after, (int)(spiram_free_after - spiram_free_before)); + + if (ret != 0) { + LOGE("Failed to join room: %d", ret); + return ret; + } + rtc->b_pipeline_started = true; + + rtc->p_channel_name = strdup(option->p_channel_name); + if (!rtc->p_channel_name) { + LOGE("Failed to allocate memory for channel name"); + return -1; + } + rtc->p_user_id = strdup(option->p_uid); + if (!rtc->p_user_id) { + LOGE("Failed to allocate memory for user ID"); + HAL_SAFE_FREE(rtc->p_channel_name); + return -1; + } + + return 0; +} + +static void __rtc_stop(rtc_impl_t* rtc) +{ + if (!rtc) { + LOGE("rtc instance is NULL"); + return; + } + if (!rtc->b_pipeline_started) { + LOGI("pipeline not started"); + return; + } + + int ret = byte_rtc_leave_room(rtc->rtc, rtc->p_channel_name); + if (ret != 0) { + LOGE("Failed to leave room: %d", ret); + return; + } + rtc->b_pipeline_started = false; + HAL_SAFE_FREE(rtc->p_channel_name); + + return; +} + +// static void __send_message_2_user(rtc_impl_t* rtc, volc_msg_t* msg) +// { +// if (rtc->message_callback) { +// rtc->message_callback(rtc->context, msg); +// } +// } + +static void __send_data_2_user(rtc_impl_t* rtc, const void* data, int data_len, volc_data_info_t* info) { + // 添加详细的参数验证,确保回调函数的安全调用 + if (!rtc) { + LOGE("__send_data_2_user: rtc is NULL"); + return; + } + + if (!rtc->data_callback) { + // 没有注册回调函数,这是正常情况,不记录错误 + return; + } + + if (!data) { + LOGE("__send_data_2_user: data is NULL"); + return; + } + + if (data_len <= 0) { + LOGE("__send_data_2_user: data_len is <= 0"); + return; + } + + if (!info) { + LOGE("__send_data_2_user: info is NULL"); + return; + } + + // 调用数据回调函数 + rtc->data_callback(rtc->context, data, data_len, info); +} + +// static void _register_message_router(rtc_impl_t* rtc, volc_msg_cb callback) +// { +// rtc->message_callback = callback; +// } + +static void _send_message_2_user(rtc_impl_t* rtc, volc_msg_t* msg) +{ + if (rtc->message_callback) { + rtc->message_callback(rtc->context, msg); + } +} + +static bool _is_target_message(const uint8_t* message, int size, const char* target) { + if (message == NULL || target == NULL || size < MAGIC_LENGTH) { + return false; + } + return memcmp(message, target, MAGIC_LENGTH) == 0; +} + +static int _on_conversion_status_message_parsed(const char* json, int len) { + int c = -1; + char* buf = (char*)hal_malloc(len + 1); + if (!buf) return c; + memcpy(buf, json, len); + buf[len] = '\0'; + cJSON *root = cJSON_Parse(buf); + if (root != NULL) { + volc_json_read_int(root, "Stage.Code", &c); + cJSON_Delete(root); + } + hal_free(buf); + return c; +} + +static void _on_join_channel_success(byte_rtc_engine_t engine, const char* channel, int elapsed_ms, bool rejoin) +{ + volc_msg_t msg = {0}; + LOGI("join channel success %s elapsed %d ms\n", channel, elapsed_ms); + rtc_impl_t* rtc = (rtc_impl_t*) byte_rtc_get_user_data(engine); + + rtc->b_first_keyframe_received = false; + rtc->b_channel_joined = true; + + msg.code = VOLC_MSG_CONNECTED; + _send_message_2_user(rtc, &msg); +}; + +static void _on_user_joined(byte_rtc_engine_t engine, const char* channel, const char* user_name, int elapsed_ms) +{ + rtc_impl_t* rtc = (rtc_impl_t*) byte_rtc_get_user_data(engine); + volc_msg_t msg = {0}; + LOGI("remote user joined %s:%s elapsed %d ms\n", channel, user_name, elapsed_ms); + + rtc->b_user_joined = true; + + // 先释放旧的内存 + HAL_SAFE_FREE(rtc->p_remote_user_id); + + // 分配新内存并检查结果 + char* temp = strdup(user_name); + if (temp) { + rtc->p_remote_user_id = temp; + } else { + LOGE("Failed to allocate memory for remote user ID"); + } + + msg.code = VOLC_MSG_USER_JOINED; + _send_message_2_user(rtc, &msg); +}; + +static void _on_user_offline(byte_rtc_engine_t engine, const char* channel, const char* user_name, int reason) +{ + rtc_impl_t* rtc = (rtc_impl_t*) byte_rtc_get_user_data(engine); + volc_msg_t msg = {0}; + LOGI("remote user offline %s:%s reason %d\n", channel, user_name, reason); + rtc->b_user_joined = false; + + // 清理远程用户ID,避免内存泄漏 + HAL_SAFE_FREE(rtc->p_remote_user_id); + + msg.code = VOLC_MSG_USER_OFFLINE; + _send_message_2_user(rtc, &msg); +}; + +static void _on_user_mute_audio(byte_rtc_engine_t engine, const char* channel, const char* user_name, int muted) +{ + LOGD("remote user mute audio %s:%s %d\n", channel, user_name, muted); +}; + +static void _on_user_mute_video(byte_rtc_engine_t engine, const char* channel, const char* user_name, int muted) +{ + LOGD("remote user mute video %s:%s %d\n", channel, user_name, muted); +}; + +static void _on_audio_data(byte_rtc_engine_t engine, const char* channel, const char* user_name, uint16_t sent_ts, audio_data_type_e data_type, + const void* data_ptr, size_t data_len, const uint8_t* extra_info, size_t extra_info_size) +{ + // 添加详细的参数验证,防止空指针和无效数据导致崩溃 + if (!engine) { + LOGE("_on_audio_data: engine is NULL"); + return; + } + + if (!channel) { + LOGE("_on_audio_data: channel is NULL"); + return; + } + + if (!user_name) { + LOGE("_on_audio_data: user_name is NULL"); + return; + } + + if (!data_ptr) { + LOGE("_on_audio_data: data_ptr is NULL"); + return; + } + + if (data_len == 0) { + LOGE("_on_audio_data: data_len is 0"); + return; + } + + volc_data_info_t info = {0}; + rtc_impl_t* rtc = (rtc_impl_t*)byte_rtc_get_user_data(engine); + + if (NULL == rtc) { + LOGE("_on_audio_data: rtc instance is NULL"); + return; + } + + if (!rtc->b_pipeline_started) { + LOGE("_on_audio_data: pipeline not started"); + return; + } + + if (!rtc->b_channel_joined) { + LOGE("_on_audio_data: channel not joined"); + return; + } + + if (!rtc->b_user_joined) { + LOGE("_on_audio_data: user not joined"); + return; + } + + info.type = VOLC_DATA_TYPE_AUDIO; + info.info.audio.data_type = (volc_audio_data_type_e) data_type; + // info.info.audio.sent_ts = sent_ts; + + // 限制数据大小,防止内存溢出 + if (data_len > 16384) { // 16KB的合理限制 + LOGW("_on_audio_data: data_len too large (%zu), truncating to 16KB", data_len); + data_len = 16384; + } + + __send_data_2_user(rtc, data_ptr, data_len, &info); +}; + +static void _on_video_data(byte_rtc_engine_t engine, const char* channel, const char* user_name, uint16_t sent_ts, video_data_type_e codec, + int is_key_frame, const void* data_ptr, size_t data_len) +{ + volc_data_info_t info = {0}; + rtc_impl_t* rtc = (rtc_impl_t*) byte_rtc_get_user_data(engine); + + if (NULL == rtc || !rtc->b_pipeline_started || !rtc->b_channel_joined || !rtc->b_user_joined) { + LOGD("pipeline not started or channel not joined or user not joined"); + return; + } + + if (__is_first_keyframe_not_received(rtc, is_key_frame)) { + LOGD("first keyframe not received, request key frame"); + byte_rtc_request_video_key_frame(rtc->rtc, channel, user_name); + return; + } + + rtc->b_first_keyframe_received = true; + + info.type = VOLC_DATA_TYPE_VIDEO; + info.info.video.data_type = (volc_video_data_type_e) codec; + __send_data_2_user(rtc, data_ptr, data_len, &info); +}; + +static void _on_channel_error(byte_rtc_engine_t engine, const char* channel, int code, const char* msg) +{ + volc_msg_t msg_data = {0}; + rtc_impl_t* rtc = (rtc_impl_t*) byte_rtc_get_user_data(engine); + LOGE("channel error %s:%d %s\n", channel, code, msg ? msg : ""); + LOGI("channel error heap_free=%u", (unsigned)heap_caps_get_free_size(MALLOC_CAP_DEFAULT)); + msg_data.code = code; + char* copied = NULL; + if (msg) { + size_t len = strnlen(msg, 1024); + copied = (char*)hal_malloc(len + 1); + if (copied) { + memcpy(copied, msg, len); + copied[len] = '\0'; + msg_data.data.msg = copied; + } + } + _send_message_2_user(rtc, &msg_data); + if (copied) { + hal_free(copied); + } +}; + +static void _on_global_error(byte_rtc_engine_t engine, int code, const char* message) +{ + volc_msg_t msg_data = {0}; + rtc_impl_t* rtc = (rtc_impl_t*) byte_rtc_get_user_data(engine); + rtc->b_channel_joined = false; + + rtc->b_first_keyframe_received = false; + LOGI("global error %d %s\n", code, message); + LOGI("global error heap_free=%u", (unsigned)heap_caps_get_free_size(MALLOC_CAP_DEFAULT)); + msg_data.code = VOLC_MSG_DISCONNECTED; + _send_message_2_user(rtc, &msg_data); +}; + +static void _on_key_frame_gen_req(byte_rtc_engine_t engine, const char* channel, const char* user_name) +{ + LOGI("remote req key frame %s:%s\n", channel, user_name); + rtc_impl_t* rtc = (rtc_impl_t*) byte_rtc_get_user_data(engine); + volc_msg_t msg_data = {0}; + msg_data.code = VOLC_MSG_KEY_FRAME_REQ; + _send_message_2_user(rtc, &msg_data); +}; + +static void _on_target_bitrate_changed(byte_rtc_engine_t engine, const char* channel, uint32_t target_bps) +{ + rtc_impl_t* rtc = (rtc_impl_t*) byte_rtc_get_user_data(engine); + volc_msg_t msg_data = {0}; + + LOGD("target bitrate changed %s %d bps\n", channel, (int)target_bps); + // TODO: do not send it to user + msg_data.code = VOLC_MSG_TARGET_BITRATE_CHANGED; + msg_data.data.target_bitrate = target_bps; + _send_message_2_user(rtc, &msg_data); +}; + +static void _on_token_privilege_will_expire(byte_rtc_engine_t engine, const char* token) +{ + LOGI("\ntoken privilege will expire %s", token); + rtc_impl_t* rtc = (rtc_impl_t*) byte_rtc_get_user_data(engine); + volc_msg_t msg_data = {0}; + msg_data.code = VOLC_MSG_TOKEN_EXPIRED; + // TODO: token? + _send_message_2_user(rtc, &msg_data); +}; + +static void _on_message_received(byte_rtc_engine_t engine, const char* channel_name, const char* src, const uint8_t* message, int size, bool binary) +{ + int ret = 0; + volc_msg_t msg = { 0 }; + rtc_impl_t* rtc = (rtc_impl_t*) byte_rtc_get_user_data(engine); + volc_data_info_t info = {0}; + info.type = VOLC_DATA_TYPE_MESSAGE; + info.info.message.is_binary = binary; + LOGI("message received channel=%s src=%s size=%d binary=%d free_heap=%u", channel_name, src, size, binary, (unsigned)heap_caps_get_free_size(MALLOC_CAP_DEFAULT)); + + if (NULL == rtc || !rtc->b_pipeline_started || !rtc->b_channel_joined || !rtc->b_user_joined) { + LOGE("pipeline not started or channel not joined or user not joined"); + return; + } + + if (_is_target_message(message, size, MAGIC_CONV)) { + if (size < MAGIC_OFFSET) { + LOGW("conv message too short: %d", size); + return; + } + int json_len = (message[4] << 24) | (message[5] << 16) | (message[6] << 8) | (message[7]); + if (json_len <= 0 || size < (MAGIC_OFFSET + json_len)) { + LOGW("conv message invalid length: %d size: %d", json_len, size); + return; + } + const char* json_ptr = (const char*)(message + MAGIC_OFFSET); + ret = _on_conversion_status_message_parsed(json_ptr, json_len); + msg.code = VOLC_MSG_CONV_STATUS; + msg.data.conv_status = ret; + _send_message_2_user(rtc, &msg); + return; + } + __send_data_2_user(rtc, message, size, &info); +}; + +static void _on_message_send_result(byte_rtc_engine_t engine, const char* channel_name, int64_t msgid, int error, const char* extencontent) +{ + LOGD("----------------------->MessageSendResult msg id %" PRId64 ", error %d, extencontent %s \n", msgid, error, extencontent); +}; + +static void _on_license_will_expire(byte_rtc_engine_t engine, int daysleft) +{ + LOGI("----------------------->license will expire in %d days \n", daysleft); + volc_msg_t msg_data = {0}; + rtc_impl_t* rtc = (rtc_impl_t*) byte_rtc_get_user_data(engine); + msg_data.code = VOLC_MSG_LICENSE_EXPIRED; + _send_message_2_user(rtc, &msg_data); +}; + +static void _on_fini_notify(byte_rtc_engine_t engine) +{ + rtc_impl_t* rtc = (rtc_impl_t*) byte_rtc_get_user_data(engine); + rtc->b_fini = true; +} + +static int __rtc_init(rtc_impl_t* engine, cJSON* p_config) +{ + int ret = 0; + int video_codec = 0; + int log_level = 0; + int params_cnt = 0; + cJSON* p_params = NULL; + // 初始化结构体中的event_handler字段,避免局部变量导致的悬挂指针问题 + // 初始化结构体中的event_handler字段,避免局部变量导致的悬挂指针问题 + memset(&engine->event_handler, 0, sizeof(byte_rtc_event_handler_t)); + engine->event_handler.on_global_error = _on_global_error; + engine->event_handler.on_join_room_success = _on_join_channel_success; + engine->event_handler.on_room_error = _on_channel_error; + engine->event_handler.on_user_joined = _on_user_joined; + engine->event_handler.on_user_offline = _on_user_offline; + engine->event_handler.on_user_mute_audio = _on_user_mute_audio; + engine->event_handler.on_user_mute_video = _on_user_mute_video; + engine->event_handler.on_audio_data = _on_audio_data; + engine->event_handler.on_video_data = _on_video_data; + engine->event_handler.on_key_frame_gen_req = _on_key_frame_gen_req; + engine->event_handler.on_target_bitrate_changed = _on_target_bitrate_changed; + engine->event_handler.on_token_privilege_will_expire = _on_token_privilege_will_expire; + engine->event_handler.on_message_received = _on_message_received; + engine->event_handler.on_message_send_result = _on_message_send_result; + engine->event_handler.on_license_expire_warning = _on_license_will_expire; + engine->event_handler.on_fini_notify = _on_fini_notify; + ret = volc_json_read_int(p_config, "audio.codec", &engine->audio_codec); + if (ret != 0 || engine->audio_codec < 0 || engine->audio_codec > AUDIO_CODEC_TYPE_G711U) { + LOGE("volc_rtc_create: read audio_codec failed"); + return -1; + } + ret = volc_json_read_int(p_config, "video.codec", &video_codec); + if (ret != 0 || video_codec < 0 || video_codec > VIDEO_CODEC_TYPE_BYTEVC1) { + LOGE("volc_rtc_create: read video_codec failed"); + return -1; + } + ret = volc_json_read_int(p_config, "log_level", &log_level); + if (ret != 0) { + log_level = BYTE_RTC_LOG_LEVEL_WARN; // default log level + } + ret = volc_json_read_bool(p_config, "audio.publish", &engine->b_audio_publish); + if (ret != 0) { + engine->b_audio_publish = true; // default to publish audio + } + ret = volc_json_read_bool(p_config, "video.publish", &engine->b_video_publish); + if (ret != 0) { + engine->b_video_publish = false; // default to no publish video + } + ret = volc_json_read_bool(p_config, "audio.subscribe", &engine->b_audio_subscribe); + if (ret != 0) { + engine->b_audio_subscribe = true; // default to subscribe audio + } + ret = volc_json_read_bool(p_config, "video.subscribe", &engine->b_video_subscribe); + if (ret != 0) { + engine->b_video_subscribe = false; // default to no subscribe video + } + + engine->rtc = byte_rtc_create(engine->p_appid, &engine->event_handler); + if (!engine->rtc) { + LOGE("volc_rtc_create: byte_rtc_create failed"); + return -1; + } + byte_rtc_set_user_data(engine->rtc, (void*) engine); + byte_rtc_set_log_level(engine->rtc, log_level); + + ret = volc_json_read_object(p_config, "params", &p_params); + if (0 == ret) { + params_cnt = cJSON_GetArraySize(p_params); + if (params_cnt > 0) { + for (int i = 0; i < params_cnt; i++) { + cJSON* p_param = cJSON_GetArrayItem(p_params, i); + if (p_param && cJSON_IsString(p_param)) { + const char* param_str = cJSON_GetStringValue(p_param); + if (param_str && strlen(param_str) > 0) { + byte_rtc_set_params(engine->rtc, param_str); + LOGI("volc_rtc_create: set param[%d]: %s", i, param_str); + } + } + } + } + cJSON_Delete(p_params); + } + + int init_ret = byte_rtc_init(engine->rtc); + if (init_ret != 0) { + LOGE("volc_rtc_create: byte_rtc_init failed with ret=%d", init_ret); + return -1; + } + + byte_rtc_set_audio_codec(engine->rtc, engine->audio_codec); + byte_rtc_set_video_codec(engine->rtc, video_codec - 1); // -1 for default codec + return 0; +} + +volc_rtc_t volc_rtc_create(const char* appid, void* context, cJSON* p_config, volc_msg_cb message_callback, volc_data_cb data_callback) +{ + rtc_impl_t* rtc = (rtc_impl_t*) hal_calloc(1, sizeof(rtc_impl_t)); + if (!rtc) { + LOGE("volc_rtc_create: malloc rtc failed"); + return NULL; + } + rtc->message_callback = message_callback; + rtc->data_callback = data_callback; + rtc->context = context; + + // 检查appid是否为空,如果为空则使用默认值 + const char* actual_appid = appid; + if (!appid || strlen(appid) == 0) { + LOGW("appid is empty, using default value"); + actual_appid = "default_appid"; + } + + rtc->p_appid = strdup(actual_appid); + if (NULL == rtc->p_appid) { + LOGE("malloc appid memory failed"); + goto err_out_label; + } + + if (__rtc_init(rtc, p_config) != 0) { + LOGE("volc_rtc_create: rtc init failed"); + goto err_out_label; + } + + LOGD("rtc create success"); + return (volc_rtc_t)rtc; +err_out_label: + HAL_SAFE_FREE(rtc->p_appid); + HAL_SAFE_FREE(rtc); + return NULL; +} + +void volc_rtc_destroy(volc_rtc_t handle) +{ + rtc_impl_t* rtc = (rtc_impl_t*)handle; + if (!rtc) { + LOGE("rtc instance is NULL"); + return; + } + __rtc_stop(rtc); + + byte_rtc_fini(rtc->rtc); + while (!rtc->b_fini) { + usleep(1000 * 10); + } + byte_rtc_destroy(rtc->rtc); + HAL_SAFE_FREE(rtc->p_channel_name); + HAL_SAFE_FREE(rtc->p_user_id); + HAL_SAFE_FREE(rtc->p_remote_user_id); + HAL_SAFE_FREE(rtc->p_token); + HAL_SAFE_FREE(rtc); + LOGD("rtc destroy success"); +} + +// 新增:extra_params 用于传递额外的AgentConfig配置参数 +int volc_rtc_start(volc_rtc_t rtc, const char* bot_id, volc_iot_info_t* iot_info, const char* extra_params) { + // int ret = 0; + volc_rtc_option_t *opt = NULL; + rtc_impl_t* rtc_impl = (rtc_impl_t*) rtc; + if (!rtc_impl) { + LOGE("rtc instance is NULL"); + return -1; + } + if (!iot_info) { + LOGE("iot_info is NULL"); + return -1; + } + // 检查bot_id是否为空,如果为空则使用默认值 + const char* actual_bot_id = bot_id; + if (!bot_id || strlen(bot_id) == 0) { + LOGW("bot_id is empty, using default value"); + actual_bot_id = "default_bot"; + } + char* task_id = "test"; + LOGI("volc_rtc_start: bot_id=%s audio_codec=%d heap_free=%u", actual_bot_id, rtc_impl->audio_codec, (unsigned)heap_caps_get_free_size(MALLOC_CAP_DEFAULT)); + // 新增:extra_params 用于传递额外的AgentConfig配置参数 + if (volc_get_rtc_config(iot_info, rtc_impl->audio_codec, actual_bot_id, task_id, extra_params, &rtc_impl->info)) { + LOGE("get rtc config failed"); + return -1; + } + LOGI("volc_get_rtc_config success heap_free=%u", (unsigned)heap_caps_get_free_size(MALLOC_CAP_DEFAULT)); + opt = &rtc_impl->info.rtc_opt; + return __rtc_start(rtc_impl, opt); +} + +int volc_rtc_stop(volc_rtc_t handle) { + rtc_impl_t* rtc = (rtc_impl_t *)handle; + if (!rtc) { + LOGE("rtc instance is NULL"); + return -1; + } + __rtc_stop(rtc); + return 0; +} + +int volc_rtc_send(volc_rtc_t handle, const void* data, int size, volc_data_info_t* data_info) { + rtc_impl_t* rtc = (rtc_impl_t *)handle; + audio_frame_info_t audio_info = {0}; + video_frame_info_t video_info = {0}; + if (NULL == rtc || NULL == data || NULL == data_info) { + LOGE("input args is invalid, rtc(%p), data(%p), data_info(%p)", rtc, data, data_info); + return -1; + } + if (rtc->p_channel_name == NULL || size <= 0) { + LOGE("channel not ready or invalid size"); + return -1; + } + LOGD("volc_rtc_send type=%d size=%d heap_free=%u", data_info->type, size, (unsigned)heap_caps_get_free_size(MALLOC_CAP_DEFAULT)); + switch (data_info->type) { + case VOLC_DATA_TYPE_AUDIO: { + audio_info.data_type = (audio_data_type_e) data_info->info.audio.data_type; + byte_rtc_send_audio_data(rtc->rtc, rtc->p_channel_name, data, size, &audio_info); + break; + } + case VOLC_DATA_TYPE_VIDEO: { + video_info.data_type = (video_data_type_e) data_info->info.video.data_type; + video_info.stream_type = VIDEO_STREAM_HIGH; + video_info.frame_type = VIDEO_FRAME_AUTO_DETECT; + LOGD("Sending video to channel: %s, data length: %d, data type: %d", rtc->p_channel_name, size, video_info.data_type); + byte_rtc_send_video_data(rtc->rtc, rtc->p_channel_name, data, size, &video_info); + break; + } + case VOLC_DATA_TYPE_MESSAGE: { + if (rtc->p_remote_user_id == NULL) { + LOGW("skip message: remote user not joined"); + return -1; + } + LOGD("Sending message to channel: %s, remote user: %s, data length: %d, is_binary: %d", + rtc->p_channel_name, rtc->p_remote_user_id, size, data_info->info.message.is_binary); + byte_rtc_rts_send_message(rtc->rtc, rtc->p_channel_name, rtc->p_remote_user_id, data, size, data_info->info.message.is_binary, + RTS_MESSAGE_RELIABLE); + break; + } + default: + LOGW("unsupported data type: %d", data_info->type); + return -1; + } + return 0; +} + +int volc_rtc_interrupt(volc_rtc_t rtc) { + int ret = 0; + uint8_t* msg_ctrl = NULL; + size_t msg_len = 0; + rtc_impl_t* rtc_impl = (rtc_impl_t*) rtc; + volc_data_info_t data_info = {0}; + if (!rtc_impl) { + LOGE("rtc instance is NULL"); + return -1; + } + if (!rtc_impl->b_user_joined || rtc_impl->p_remote_user_id == NULL) { + LOGW("interrupt skipped: remote user not joined"); + return -1; + } + if (_build_binary_message(MAGIC_CONTROL, interrupt_str, &msg_ctrl, &msg_len) != 0) { + LOGE("build control message failed"); + return -1; + } + data_info.type = VOLC_DATA_TYPE_MESSAGE; + data_info.info.message.is_binary = true; + if ((ret = volc_rtc_send(rtc, msg_ctrl, msg_len, &data_info)) != 0) { + LOGE("send interrupt message failed"); + } + HAL_SAFE_FREE(msg_ctrl); + return ret; +} + +int volc_rtc_send_jpg(volc_rtc_t rtc, void* data, int size) { + return 0; +}