Compare commits
No commits in common. "63b21fdfedac6818b94221558a264fc3daebc2d9" and "b1577d84182c9884f0c53b939608991b9d774fa2" have entirely different histories.
63b21fdfed
...
b1577d8418
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,9 +1,5 @@
|
||||
tmp/
|
||||
# 顶层 components/ 下默认全部 ignore (第三方 SDK/库), 但 common/ 是项目自己代码要跟踪
|
||||
/components/*
|
||||
!/components/common/
|
||||
# 子项目 (esp-spot 等) 里的 components/ 一律 ignore (历史规则保持)
|
||||
esp-spot/**/components/
|
||||
components/
|
||||
managed_components/
|
||||
build/
|
||||
.vscode/
|
||||
|
||||
@ -1,10 +0,0 @@
|
||||
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()
|
||||
@ -1,50 +0,0 @@
|
||||
// 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 <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#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__ */
|
||||
@ -1,64 +0,0 @@
|
||||
// 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 <stdint.h>
|
||||
|
||||
// 硬件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__ */
|
||||
@ -1,48 +0,0 @@
|
||||
// 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 <stdbool.h>
|
||||
|
||||
#include <cJSON.h>
|
||||
|
||||
#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__
|
||||
@ -1,135 +0,0 @@
|
||||
// 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__
|
||||
@ -1,60 +0,0 @@
|
||||
// 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 <stdio.h>
|
||||
|
||||
#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__ */
|
||||
@ -1,166 +0,0 @@
|
||||
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#ifndef __VOLC_CONV_AI_H__
|
||||
#define __VOLC_CONV_AI_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#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__ */
|
||||
@ -1,16 +0,0 @@
|
||||
// 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__ */
|
||||
@ -1,66 +0,0 @@
|
||||
// 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 <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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__ */
|
||||
@ -1,42 +0,0 @@
|
||||
// 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 <stdbool.h>
|
||||
|
||||
#include <cJSON.h>
|
||||
|
||||
#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__ */
|
||||
@ -1,728 +0,0 @@
|
||||
// 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 <inttypes.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <cJSON.h>
|
||||
|
||||
// For HMAC-SHA256 signature generation
|
||||
#include <mbedtls/md.h>
|
||||
#include <mbedtls/base64.h>
|
||||
#include <mbedtls/aes.h>
|
||||
|
||||
// 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;
|
||||
}
|
||||
@ -1,207 +0,0 @@
|
||||
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "volc_http.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <esp_log.h>
|
||||
#include <esp_http_client.h>
|
||||
#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;
|
||||
}
|
||||
@ -1,208 +0,0 @@
|
||||
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "util/volc_json.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
@ -1,155 +0,0 @@
|
||||
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// 火山引擎平台适配层 - 实现ESP32平台相关的硬件抽象层函数
|
||||
// 高性能版本针对ESP32平台的内存管理和线程调度进行了优化
|
||||
|
||||
#include "volc_platform.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <esp_netif.h>
|
||||
#include <esp_timer.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
|
||||
// 内存分配函数 - 使用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;
|
||||
}
|
||||
@ -1,793 +0,0 @@
|
||||
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <cJSON.h>
|
||||
#include <esp_heap_caps.h>
|
||||
|
||||
// 确保在包含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;
|
||||
// 防御性判空: 火山 RTC SDK 在某些 ICE Agent 失败路径下会用 message=NULL 调用本回调,
|
||||
// 导致 printf("%s", NULL) → strlen(NULL) → LoadProhibited panic → 设备重启
|
||||
// (idle ≥ 10 分钟后服务端 session 超时 / NAT 表过期等场景偶发触发)
|
||||
LOGI("global error %d %s\n", code, message ? message : "(null)");
|
||||
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;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user