@@ -0,0 +1,51 @@
|
||||
// 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_QUOTA_EXCEEDED, // 资源耗尽
|
||||
} 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__ */
|
||||
+211
@@ -0,0 +1,211 @@
|
||||
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "volc_device_manager.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "cJSON.h"
|
||||
#include "volc_osal.h"
|
||||
#include "util/volc_http.h"
|
||||
#include "util/volc_json.h"
|
||||
#include "util/volc_base64.h"
|
||||
#include "util/volc_auth.h"
|
||||
#include "util/volc_list.h"
|
||||
#include "util/volc_log.h"
|
||||
|
||||
#include "volc_conv_ai.h"
|
||||
|
||||
#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"
|
||||
|
||||
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 input_str[256] = {0};
|
||||
uint8_t hmac_result[32] = {0};
|
||||
int hmac_result_len = sizeof(hmac_result);
|
||||
int base64_encoded_len = 0;
|
||||
size_t olen = 0;
|
||||
|
||||
snprintf(input_str, sizeof(input_str), "auth_type=%d&device_name=%s&random_num=%d&product_key=%s×tamp=%" PRIu64, auth_type, device_name, rnd, product_key, timestamp);
|
||||
volc_sha256_hmac((const unsigned char*)secret_key, strlen(secret_key), (const unsigned char*)input_str, strlen(input_str), hmac_result, &hmac_result_len);
|
||||
base64_encoded_len = volc_base64_encoded_length(hmac_result_len);
|
||||
unsigned char* base64_encoded = (unsigned char*)volc_osal_malloc(base64_encoded_len);
|
||||
if (!base64_encoded) {
|
||||
LOGE("Failed to allocate memory for base64 encoded string");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
volc_base64_encode(base64_encoded, base64_encoded_len, &olen, (const unsigned char*)hmac_result, sizeof(hmac_result));
|
||||
return (char*)base64_encoded;
|
||||
}
|
||||
|
||||
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) {
|
||||
char input_str[256] = {0};
|
||||
uint8_t hmac_result[32] = {0};
|
||||
int hmac_result_len = sizeof(hmac_result);
|
||||
int base64_encoded_len = 0;
|
||||
size_t olen = 0;
|
||||
|
||||
snprintf(input_str, sizeof(input_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);
|
||||
volc_sha256_hmac((const unsigned char*)secret_key, strlen(secret_key), (const unsigned char*)input_str, strlen(input_str), hmac_result, &hmac_result_len);
|
||||
base64_encoded_len = volc_base64_encoded_length(hmac_result_len);
|
||||
unsigned char* base64_encoded = (unsigned char*)volc_osal_malloc(base64_encoded_len);
|
||||
if (!base64_encoded) {
|
||||
LOGE("Failed to allocate memory for base64 encoded string");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
volc_base64_encode(base64_encoded, base64_encoded_len, &olen, (const unsigned char*)hmac_result, sizeof(hmac_result));
|
||||
return (char*)base64_encoded;
|
||||
}
|
||||
|
||||
volc_error_code_e volc_inter_err_2_ext_err(int code) {
|
||||
switch (code) {
|
||||
case ERROR_LICENSE_EXHAUSTED:
|
||||
return VOLC_ERR_LICENSE_EXHAUSTED;
|
||||
case ERROR_LICENSE_EXPIRED:
|
||||
return VOLC_ERR_LICENSE_EXPIRED;
|
||||
default:
|
||||
return VOLC_ERR_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int volc_device_register(volc_iot_info_t* info, char** output)
|
||||
{
|
||||
int ret = 0;
|
||||
uint64_t current_time = volc_osal_get_time_ms();
|
||||
int32_t random_num = (int32_t)current_time;
|
||||
char url[256] = {0};
|
||||
char* payload = NULL;
|
||||
char* signature = volc_generate_signature(info->product_secret, info->product_key, info->device_name, random_num, current_time, 1);
|
||||
cJSON* response_json = NULL;
|
||||
cJSON* root = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(root, "InstanceID", info->instance_id);
|
||||
cJSON_AddStringToObject(root, "product_key", info->product_key);
|
||||
cJSON_AddStringToObject(root, "device_name", info->device_name);
|
||||
cJSON_AddNumberToObject(root, "random_num", random_num);
|
||||
cJSON_AddNumberToObject(root, "timestamp", (double)current_time);
|
||||
cJSON_AddNumberToObject(root, "auth_type", 1);
|
||||
cJSON_AddStringToObject(root, "signature", signature);
|
||||
char* json_str = cJSON_PrintUnformatted(root);
|
||||
|
||||
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);
|
||||
LOGD("url: %s, body: %s", url, json_str);
|
||||
|
||||
char* response = volc_http_post(url, json_str, strlen(json_str));
|
||||
if (response == NULL) {
|
||||
LOGE("Failed to get response from server");
|
||||
ret = -1;
|
||||
goto err_out_label;
|
||||
}
|
||||
|
||||
response_json = cJSON_Parse(response);
|
||||
if (response_json == NULL) {
|
||||
LOGE("Failed to parse response JSON");
|
||||
ret = -1;
|
||||
goto err_out_label;
|
||||
}
|
||||
int code = 0;
|
||||
ret = volc_json_read_int(response_json, "ResponseMetadata.Error.CodeN", &code);
|
||||
if (0 == ret) {
|
||||
ret = volc_inter_err_2_ext_err(code);
|
||||
LOGE("register device failed, ret: %d, code: %d", ret, code);
|
||||
goto err_out_label;
|
||||
}
|
||||
ret = volc_json_read_string(response_json, "Result.payload", &payload);
|
||||
if (ret != 0) {
|
||||
LOGE("Failed to read payload from response JSON");
|
||||
ret = -1;
|
||||
goto err_out_label;
|
||||
}
|
||||
ret = volc_json_read_string(response_json, "Result.RTCAppID", &info->rtc_app_id);
|
||||
if (ret != 0) {
|
||||
LOGE("Failed to read rtc app id from response JSON");
|
||||
ret = -1;
|
||||
goto err_out_label;
|
||||
}
|
||||
LOGD("rtc app id: %s", info->rtc_app_id);
|
||||
|
||||
// TODO: device secret, should be freed by caller
|
||||
*output = volc_aes_decode(info->product_secret, payload, true);
|
||||
|
||||
err_out_label:
|
||||
HAL_SAFE_FREE(payload);
|
||||
HAL_SAFE_FREE(response);
|
||||
HAL_SAFE_FREE(root);
|
||||
HAL_SAFE_FREE(response_json);
|
||||
HAL_SAFE_FREE(signature);
|
||||
HAL_SAFE_FREE(json_str);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define VOLC_GET_RTC_CONFIG_PATH "/2021-12-14/GetRTCConfig"
|
||||
#define VOLC_API_ACTION_GET_RTC_CONFIG "Action=GetRTCConfig"
|
||||
int volc_get_rtc_config(volc_iot_info_t* info, int audio_codec, const char* bot_id, const char* task_id, volc_room_info_t* room_info, const char* params) {
|
||||
int ret = 0;
|
||||
uint64_t current_time = volc_osal_get_time_ms();
|
||||
int32_t random_num = (int32_t)current_time;
|
||||
char url[256] = {0};
|
||||
char* signature = volc_generate_signature(info->device_secret, info->product_key, info->device_name, random_num, current_time, 0);
|
||||
cJSON* response_json = NULL;
|
||||
cJSON* root = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(root, "InstanceID", info->instance_id);
|
||||
cJSON_AddStringToObject(root, "product_key", info->product_key);
|
||||
cJSON_AddStringToObject(root, "device_name", info->device_name);
|
||||
cJSON_AddNumberToObject(root, "random_num", random_num);
|
||||
cJSON_AddNumberToObject(root, "timestamp", (double)current_time);
|
||||
cJSON_AddStringToObject(root, "signature", signature);
|
||||
cJSON_AddStringToObject(root, "bot_id", bot_id);
|
||||
cJSON_AddNumberToObject(root, "audio_codec", audio_codec);
|
||||
cJSON_AddStringToObject(root, "task_id", task_id);
|
||||
|
||||
if(params){
|
||||
cJSON* params_config = cJSON_Parse(params);
|
||||
|
||||
cJSON* agent_config = cJSON_CreateObject();
|
||||
volc_json_read_object(params_config,"AgentConfig",&agent_config);
|
||||
cJSON_AddItemToObject(root, "agent_config", agent_config);
|
||||
|
||||
cJSON* config = cJSON_CreateObject();
|
||||
volc_json_read_object(params_config,"Config",&config);
|
||||
cJSON_AddItemToObject(root, "config", config);
|
||||
}
|
||||
char* json_str = cJSON_PrintUnformatted(root);
|
||||
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);
|
||||
LOGI("url: %s, body: %s", url, json_str);
|
||||
char* response = volc_http_post(url, json_str, strlen(json_str));
|
||||
if (response == NULL) {
|
||||
LOGE("Failed to get response from server");
|
||||
ret = -1;
|
||||
goto err_out_label;
|
||||
}
|
||||
response_json = cJSON_Parse(response);
|
||||
volc_json_read_string(response_json, "Result.RoomID", &room_info->rtc_opt.p_channel_name);
|
||||
volc_json_read_string(response_json, "Result.UserID", &room_info->rtc_opt.p_uid);
|
||||
volc_json_read_string(response_json, "Result.Token", &room_info->rtc_opt.p_token);
|
||||
volc_json_read_string(response_json, "Result.TaskID", &room_info->task_id);
|
||||
if (room_info->rtc_opt.p_channel_name == NULL || room_info->rtc_opt.p_uid == NULL || room_info->rtc_opt.p_token == NULL || room_info->task_id == NULL) {
|
||||
LOGE("Failed to get RTC config from server: %s", response);
|
||||
ret = -1;
|
||||
goto err_out_label;
|
||||
}
|
||||
err_out_label:
|
||||
if (root) {
|
||||
cJSON_Delete(root);
|
||||
}
|
||||
if (response_json) {
|
||||
cJSON_Delete(response_json);
|
||||
}
|
||||
HAL_SAFE_FREE(signature);
|
||||
HAL_SAFE_FREE(json_str);
|
||||
HAL_SAFE_FREE(response);
|
||||
return ret;
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#ifndef __CONV_AI_SRC_VOLC_DEVICE_MANAGER__
|
||||
#define __CONV_AI_SRC_VOLC_DEVICE_MANAGER__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define HARDWARE_ID "a2:c8:2c:89:6e:46"
|
||||
|
||||
#define ERROR_LICENSE_EXHAUSTED 12000130
|
||||
#define ERROR_LICENSE_EXPIRED 12000140
|
||||
|
||||
typedef struct {
|
||||
char* instance_id;
|
||||
char* product_key;
|
||||
char* product_secret;
|
||||
char* device_name;
|
||||
char* device_secret;
|
||||
char* rtc_app_id;
|
||||
} volc_iot_info_t;
|
||||
|
||||
typedef struct {
|
||||
char *p_channel_name;
|
||||
char *p_uid;
|
||||
char *p_token;
|
||||
} volc_rtc_option_t;
|
||||
|
||||
typedef struct {
|
||||
volc_rtc_option_t rtc_opt;
|
||||
char* task_id; // Task ID for the room
|
||||
} volc_room_info_t;
|
||||
|
||||
int volc_device_register(volc_iot_info_t* info, char** output);
|
||||
int volc_get_rtc_config(volc_iot_info_t* info, int audio_codec, const char* bot_id, const char* task_id, volc_room_info_t* room_info, const char* params);
|
||||
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_SRC_VOLC_DEVICE_MANAGER__ */
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
// 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);
|
||||
|
||||
int volc_rtc_start(volc_rtc_t rtc, const char* bot_id, volc_iot_info_t* iot_info, const char* 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);
|
||||
|
||||
int volc_rtc_send_text_to_agent(volc_rtc_t rtc, const char* text, volc_agent_type_e type, int interrupt_mode);
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* __CONV_AI_NODE_VOLC_RTC_H__ */
|
||||
+643
@@ -0,0 +1,643 @@
|
||||
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "volc_rtc.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "volc_osal.h"
|
||||
#include "util/volc_list.h"
|
||||
#include "util/volc_log.h"
|
||||
#include "util/volc_json.h"
|
||||
|
||||
#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*)volc_osal_malloc(*out_len);
|
||||
if (!*out_buf) {
|
||||
LOGE("volc_osal_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)
|
||||
{
|
||||
volc_opt_t* p_opt = NULL;
|
||||
byte_rtc_room_options_t room_opt = {0};
|
||||
if (!rtc || !option) {
|
||||
LOGE("rtc or option is NULL");
|
||||
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;
|
||||
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);
|
||||
int ret = byte_rtc_join_room(rtc->rtc, option->p_channel_name, option->p_uid, option->p_token, &room_opt);
|
||||
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);
|
||||
rtc->p_user_id = strdup(option->p_uid);
|
||||
|
||||
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_data_2_user(rtc_impl_t* rtc, const void* data, int data_len, volc_data_info_t* info) {
|
||||
if (rtc->data_callback) {
|
||||
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, const char* target) {
|
||||
if (message == NULL || target == NULL) {
|
||||
return false;
|
||||
}
|
||||
// Check if the first 4 bytes match the magic number for "subv"
|
||||
if (*(const uint32_t*)message != *(const uint32_t*)target) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int _on_conversion_status_message_parsed(uint8_t* message, rtc_impl_t* rtc) {
|
||||
int c = -1;
|
||||
char* error_reason = NULL;
|
||||
volc_msg_t msg = { 0 };
|
||||
cJSON *root = cJSON_Parse((const char*)message);
|
||||
if (root == NULL) {
|
||||
return c;
|
||||
}
|
||||
volc_json_read_int(root, "Stage.Code", &c);
|
||||
volc_json_read_string(root, "ErrorInfo.Reason", &error_reason);
|
||||
if(error_reason != NULL) {
|
||||
LOGW("ErrorInfo.Reason %s",error_reason);
|
||||
if(strcmp(error_reason, "preParamCheck:AI license duration quota exceeded") == 0) {
|
||||
msg.code = VOLC_MSG_QUOTA_EXCEEDED;
|
||||
msg.data.conv_status = 0;
|
||||
_send_message_2_user(rtc, &msg);
|
||||
}
|
||||
HAL_SAFE_FREE(error_reason);
|
||||
return -1;
|
||||
}
|
||||
if(root != NULL) cJSON_Delete(root);
|
||||
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;
|
||||
rtc->p_remote_user_id = strdup(user_name);
|
||||
|
||||
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;
|
||||
|
||||
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)
|
||||
{
|
||||
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) {
|
||||
LOGE("pipeline not started or channel not joined or 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;
|
||||
__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 : "");
|
||||
msg_data.code = code;
|
||||
msg_data.data.msg = (char *)msg;
|
||||
_send_message_2_user(rtc, &msg_data);
|
||||
};
|
||||
|
||||
static void _on_global_error(byte_rtc_engine_t engine, int code, const char* message)
|
||||
{
|
||||
volc_msg_t msg_data = {0};
|
||||
rtc_impl_t* rtc = (rtc_impl_t*) byte_rtc_get_user_data(engine);
|
||||
rtc->b_channel_joined = false;
|
||||
|
||||
rtc->b_first_keyframe_received = false;
|
||||
LOGI("global error %d %s\n", code, message ? message : "");
|
||||
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 ? 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;
|
||||
|
||||
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, MAGIC_CONV)) {
|
||||
ret = _on_conversion_status_message_parsed((uint8_t *)message + MAGIC_OFFSET,rtc);
|
||||
if(ret != -1){
|
||||
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 ? 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;
|
||||
}
|
||||
|
||||
/*
|
||||
VOLC_AUDIO_CODEC_TYPE_UNKNOWN = 0,
|
||||
VOLC_AUDIO_CODEC_TYPE_OPUS = 1,
|
||||
VOLC_AUDIO_CODEC_TYPE_G722 = 2,
|
||||
VOLC_AUDIO_CODEC_TYPE_G711A = 3,
|
||||
VOLC_AUDIO_CODEC_TYPE_PCM = 4,
|
||||
VOLC_AUDIO_CODEC_TYPE_G711U = 5,
|
||||
*/
|
||||
static int __volc_to_rtc_audio_codec(int volc_codec)
|
||||
{
|
||||
switch (volc_codec) {
|
||||
case VOLC_AUDIO_CODEC_TYPE_OPUS:
|
||||
return AUDIO_CODEC_TYPE_OPUS;
|
||||
case VOLC_AUDIO_CODEC_TYPE_G722:
|
||||
return AUDIO_CODEC_TYPE_G722;
|
||||
case VOLC_AUDIO_CODEC_TYPE_G711A:
|
||||
return AUDIO_CODEC_TYPE_G711A;
|
||||
case VOLC_AUDIO_CODEC_TYPE_G711U:
|
||||
return AUDIO_CODEC_TYPE_G711U;
|
||||
default:
|
||||
LOGW("volc audio codec %d not supported, use opus instead", volc_codec);
|
||||
return AUDIO_CODEC_TYPE_OPUS;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
byte_rtc_event_handler_t rtc_event_handler = {.on_global_error = _on_global_error,
|
||||
.on_join_room_success = _on_join_channel_success,
|
||||
.on_room_error = _on_channel_error,
|
||||
.on_user_joined = _on_user_joined,
|
||||
.on_user_offline = _on_user_offline,
|
||||
.on_user_mute_audio = _on_user_mute_audio,
|
||||
.on_user_mute_video = _on_user_mute_video,
|
||||
.on_audio_data = _on_audio_data,
|
||||
.on_video_data = _on_video_data,
|
||||
.on_key_frame_gen_req = _on_key_frame_gen_req,
|
||||
.on_target_bitrate_changed = _on_target_bitrate_changed,
|
||||
.on_token_privilege_will_expire = _on_token_privilege_will_expire,
|
||||
.on_message_received = _on_message_received,
|
||||
.on_message_send_result = _on_message_send_result,
|
||||
.on_license_expire_warning = _on_license_will_expire,
|
||||
.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_DATA_TYPE_MJPEG) {
|
||||
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, &rtc_event_handler);
|
||||
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);
|
||||
}
|
||||
byte_rtc_init(engine->rtc);
|
||||
byte_rtc_set_audio_codec(engine->rtc, __volc_to_rtc_audio_codec(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*) volc_osal_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;
|
||||
rtc->p_appid = strdup(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_remote_user_id);
|
||||
HAL_SAFE_FREE(rtc->p_token);
|
||||
HAL_SAFE_FREE(rtc);
|
||||
LOGD("rtc destroy success");
|
||||
}
|
||||
|
||||
int volc_rtc_start(volc_rtc_t rtc, const char* bot_id, volc_iot_info_t* iot_info, const char* 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;
|
||||
}
|
||||
char* task_id = "test";
|
||||
if (volc_get_rtc_config(iot_info, __volc_to_rtc_audio_codec(rtc_impl->audio_codec), bot_id, task_id, &rtc_impl->info, params)) {
|
||||
LOGE("get rtc config failed");
|
||||
return -1;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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_info.data_type == VIDEO_DATA_TYPE_MJPEG) ? VIDEO_FRAME_KEY : 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: {
|
||||
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 (_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_text_to_agent(volc_rtc_t rtc, const char* text, volc_agent_type_e type, int interrupt_mode){
|
||||
int ret = 0;
|
||||
cJSON * pJsn = cJSON_CreateObject();
|
||||
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(type == VOLC_AGENT_TYPE_TTS){
|
||||
cJSON_AddStringToObject(pJsn,"Command","ExternalTextToSpeech");
|
||||
} else if(type == VOLC_AGENT_TYPE_LLM){
|
||||
cJSON_AddStringToObject(pJsn,"Command","ExternalTextToLLM");
|
||||
}
|
||||
cJSON_AddStringToObject(pJsn,"Message",text);
|
||||
cJSON_AddNumberToObject(pJsn,"InterruptMode",interrupt_mode);
|
||||
char* text_str = cJSON_Print(pJsn);
|
||||
if (_build_binary_message(MAGIC_CONTROL, text_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);
|
||||
cJSON_Delete(pJsn);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int volc_rtc_send_jpg(volc_rtc_t rtc, void* data, int size) {
|
||||
return 0;
|
||||
}
|
||||
+1194
File diff suppressed because it is too large
Load Diff
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+35
@@ -0,0 +1,35 @@
|
||||
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#ifndef __CONV_AI_NODE_VOLC_WS_H__
|
||||
#define __CONV_AI_NODE_VOLC_WS_H__
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "cJSON.h"
|
||||
|
||||
#include "base/volc_base.h"
|
||||
#include "base/volc_device_manager.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef void* volc_ws_t;
|
||||
|
||||
volc_ws_t volc_ws_create(void* context, cJSON* p_config, volc_msg_cb message_callback, volc_data_cb data_callback);
|
||||
|
||||
void volc_ws_destroy(volc_ws_t ws);
|
||||
|
||||
int volc_ws_start(volc_ws_t ws, const char* bot_id, volc_iot_info_t* iot_info, const char* params);
|
||||
|
||||
int volc_ws_send(volc_ws_t ws, const void* data, int size, volc_data_info_t* data_info);
|
||||
|
||||
int volc_ws_stop(volc_ws_t ws);
|
||||
|
||||
int volc_ws_interrupt(volc_ws_t ws);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* __CONV_AI_NODE_VOLC_WS_H__ */
|
||||
+688
@@ -0,0 +1,688 @@
|
||||
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "volc_ws.h"
|
||||
|
||||
#include "volc_osal.h"
|
||||
#include "base/volc_base.h"
|
||||
#include "util/volc_list.h"
|
||||
#include "util/volc_log.h"
|
||||
#include "util/volc_json.h"
|
||||
#include "util/volc_base64.h"
|
||||
#include "websocket.h"
|
||||
|
||||
#define WS_AIGC_URI "wss://ai-gateway.vei.volces.com"
|
||||
#define WS_AIGC_PATH "/v1/realtime"
|
||||
|
||||
const char* ws_interrupt_str = "{\"type\": \"response.cancel\"}";
|
||||
|
||||
typedef struct {
|
||||
uint8_t opcode;
|
||||
uint8_t* buffer;
|
||||
int size;
|
||||
int capacity;
|
||||
int in_progress;
|
||||
} ws_assembler_t;
|
||||
|
||||
typedef struct ws_params {
|
||||
volc_audio_codec_type_e audio_codec_type;
|
||||
char* session_update_json;
|
||||
} ws_params_t;
|
||||
|
||||
typedef struct {
|
||||
bool b_pipeline_started;
|
||||
bool b_connected;
|
||||
bool b_interrupted;
|
||||
char* p_data_buf;
|
||||
char* p_bot_id;
|
||||
char* p_last_response_id;
|
||||
char headers[1024];
|
||||
char uri[256];
|
||||
char event_id[64];
|
||||
int event_id_index;
|
||||
size_t data_buf_size;
|
||||
void* context;
|
||||
volatile volc_conv_status_e conv_status;
|
||||
volc_msg_cb message_callback;
|
||||
volc_data_cb data_callback;
|
||||
char hardware_id[32];
|
||||
ws_params_t params;
|
||||
ws_assembler_t assembler;
|
||||
volc_ws_client_t* client;
|
||||
} ws_impl_t;
|
||||
|
||||
static int __ws_init(ws_impl_t* ws, cJSON* p_config)
|
||||
{
|
||||
int ret = volc_json_read_int(p_config, "audio.codec", (int*)&ws->params.audio_codec_type);
|
||||
if (ret != 0) {
|
||||
ws->params.audio_codec_type = VOLC_AUDIO_CODEC_TYPE_PCM;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool __ws_wait_for_session_update(ws_impl_t* ws) {
|
||||
(void)ws;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int __build_ws_message(const char* msg, uint8_t** out_buf, size_t* out_len) {
|
||||
size_t msg_len = strlen(msg) + 1;
|
||||
*out_len = msg_len - 1;
|
||||
*out_buf = (uint8_t*)volc_osal_malloc(msg_len);
|
||||
if (!*out_buf) {
|
||||
LOGE("volc_osal_malloc failed");
|
||||
return -1;
|
||||
}
|
||||
memcpy(*out_buf, msg, msg_len);
|
||||
(*out_buf)[msg_len - 1] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __send_message_2_user(ws_impl_t* ws, volc_msg_t* msg)
|
||||
{
|
||||
if (ws->message_callback) {
|
||||
ws->message_callback(ws->context, msg);
|
||||
}
|
||||
}
|
||||
|
||||
static void __send_data_2_user(ws_impl_t* ws, const char* data, int data_len, volc_data_info_t* info) {
|
||||
if (ws->data_callback) {
|
||||
ws->data_callback(ws->context, (const void*)data, data_len, info);
|
||||
}
|
||||
}
|
||||
|
||||
static bool __ws_drop_for_interrupted(ws_impl_t* ws, const char* p_response_id) {
|
||||
if (ws->b_interrupted && p_response_id) {
|
||||
ws->b_interrupted = false;
|
||||
if (ws->p_last_response_id) {
|
||||
volc_osal_free(ws->p_last_response_id);
|
||||
ws->p_last_response_id = NULL;
|
||||
}
|
||||
ws->p_last_response_id = strdup(p_response_id);
|
||||
LOGI("drop for interrupted, response_id: %s", p_response_id);
|
||||
}
|
||||
if (ws->p_last_response_id && p_response_id && strcmp(ws->p_last_response_id, p_response_id) == 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void __ws_recv_data(ws_impl_t* ws, const char* data, int data_len)
|
||||
{
|
||||
cJSON* p_json = NULL;
|
||||
char* p_type = NULL;
|
||||
char* p_delta = NULL;
|
||||
char* p_status = NULL;
|
||||
void* p_data = NULL;
|
||||
char* p_response_id = NULL;
|
||||
size_t len = 0;
|
||||
volc_data_info_t info = { 0 };
|
||||
volc_msg_t msg = { 0 };
|
||||
|
||||
if (NULL == data || 0 == data_len) {
|
||||
return;
|
||||
}
|
||||
|
||||
p_json = cJSON_ParseWithLength(data, data_len);
|
||||
if (!p_json) {
|
||||
LOGE("Failed to parse json, data_len: %d, data: %s", data_len, data);
|
||||
return;
|
||||
}
|
||||
// LOGI("json: %s", data);
|
||||
volc_json_read_string(p_json, "type", &p_type);
|
||||
volc_json_read_string(p_json, "delta", &p_delta);
|
||||
volc_json_read_string(p_json, "response.status", &p_status);
|
||||
if (strcmp(p_type, "response.audio.delta") == 0 && p_delta) {
|
||||
if (strlen(p_delta) == 0) {
|
||||
LOGE("delta is empty, data: %s", data);
|
||||
goto err_out_label;
|
||||
}
|
||||
if (NULL == ws || !ws->b_pipeline_started) {
|
||||
LOGD("pipeline not started");
|
||||
goto err_out_label;
|
||||
}
|
||||
volc_json_read_string(p_json, "response_id", &p_response_id);
|
||||
if (__ws_drop_for_interrupted(ws, p_response_id)) {
|
||||
goto err_out_label;
|
||||
}
|
||||
len = volc_base64_decoded_length((const uint8_t*)p_delta, strlen(p_delta));
|
||||
p_data = volc_osal_malloc(len + 1);
|
||||
if (NULL == p_data) {
|
||||
LOGE("Failed to alloc memory");
|
||||
goto err_out_label;
|
||||
}
|
||||
if (ws->conv_status == VOLC_CONV_STATUS_THINKING) {
|
||||
ws->conv_status = VOLC_CONV_STATUS_ANSWERING;
|
||||
msg.code = VOLC_MSG_CONV_STATUS;
|
||||
msg.data.conv_status = ws->conv_status;
|
||||
__send_message_2_user(ws, &msg);
|
||||
}
|
||||
volc_base64_decode((unsigned char *)p_data, len, &len, (const unsigned char *)p_delta, strlen(p_delta));
|
||||
info.type = VOLC_DATA_TYPE_AUDIO;
|
||||
info.info.audio.data_type = VOLC_AUDIO_DATA_TYPE_PCM;
|
||||
// info.info.audio.sent_ts = volc_get_time(); // TODO
|
||||
__send_data_2_user(ws, p_data, len, &info);
|
||||
HAL_SAFE_FREE(p_data);
|
||||
} else if (strcmp(p_type, "input_audio_buffer.speech_started") == 0) {
|
||||
ws->conv_status = VOLC_CONV_STATUS_LISTENING;
|
||||
msg.code = VOLC_MSG_CONV_STATUS;
|
||||
msg.data.conv_status = VOLC_CONV_STATUS_LISTENING;
|
||||
__send_message_2_user(ws, &msg);
|
||||
} else if (strcmp(p_type, "input_audio_buffer.speech_stopped") == 0) {
|
||||
ws->conv_status = VOLC_CONV_STATUS_THINKING;
|
||||
msg.code = VOLC_MSG_CONV_STATUS;
|
||||
msg.data.conv_status = VOLC_CONV_STATUS_THINKING;
|
||||
__send_message_2_user(ws, &msg);
|
||||
} else if (strcmp(p_type, "response.done") == 0 && p_status) {
|
||||
msg.code = VOLC_MSG_CONV_STATUS;
|
||||
if (strcmp(p_status, "completed") == 0) {
|
||||
ws->conv_status = VOLC_CONV_STATUS_ANSWER_FINISH;
|
||||
msg.data.conv_status = VOLC_CONV_STATUS_ANSWER_FINISH;
|
||||
} else if (strcmp(p_status, "cancelled") == 0) {
|
||||
ws->conv_status = VOLC_CONV_STATUS_INTERRUPTED;
|
||||
msg.data.conv_status = VOLC_CONV_STATUS_INTERRUPTED;
|
||||
}
|
||||
LOGI("data: %s", data);
|
||||
__send_message_2_user(ws, &msg);
|
||||
} else {
|
||||
if (strcmp(p_type, "session.created") == 0) {
|
||||
LOGI("%s", data);
|
||||
} else if (strcmp(p_type, "response.audio_transcript.delta") == 0 || strcmp(p_type, "response.audio_transcript.done") == 0 || strcmp(p_type, "response.audio.done") == 0) {
|
||||
volc_json_read_string(p_json, "response_id", &p_response_id);
|
||||
if (__ws_drop_for_interrupted(ws, p_response_id)) {
|
||||
goto err_out_label;
|
||||
}
|
||||
}
|
||||
info.type = VOLC_DATA_TYPE_MESSAGE;
|
||||
info.info.message.is_binary = false;
|
||||
__send_data_2_user(ws, data, data_len, &info);
|
||||
}
|
||||
err_out_label:
|
||||
HAL_SAFE_FREE(p_type);
|
||||
HAL_SAFE_FREE(p_delta);
|
||||
HAL_SAFE_FREE(p_status);
|
||||
HAL_SAFE_FREE(p_response_id);
|
||||
cJSON_Delete(p_json);
|
||||
}
|
||||
|
||||
static void __ws_assembler_free(ws_assembler_t* a) {
|
||||
HAL_SAFE_FREE(a->buffer);
|
||||
a->size = 0;
|
||||
a->capacity = 0;
|
||||
a->in_progress = 0;
|
||||
}
|
||||
|
||||
static int __ws_generate_event_id(ws_impl_t* ws) {
|
||||
if (!ws) {
|
||||
LOGE("ws instance is NULL");
|
||||
return -1;
|
||||
}
|
||||
memset(ws->event_id, 0, sizeof(ws->event_id));
|
||||
snprintf(ws->event_id, sizeof(ws->event_id), "event_id_%llu", ws->event_id_index++);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char* __ws_audio_codec_to_string(ws_impl_t* ws) {
|
||||
switch (ws->params.audio_codec_type) {
|
||||
case VOLC_AUDIO_CODEC_TYPE_OPUS:
|
||||
return "opus16";
|
||||
case VOLC_AUDIO_CODEC_TYPE_G722:
|
||||
return "g722";
|
||||
case VOLC_AUDIO_CODEC_TYPE_G711A:
|
||||
return "g711_alaw";
|
||||
case VOLC_AUDIO_CODEC_TYPE_G711U:
|
||||
return "g711u_ulaw";
|
||||
default:
|
||||
return "pcm16";
|
||||
}
|
||||
}
|
||||
|
||||
static char* __ws_generate_session_update(ws_impl_t* ws) {
|
||||
char* json = NULL;
|
||||
cJSON* root = NULL;
|
||||
cJSON* session = NULL;
|
||||
if (!ws) {
|
||||
LOGE("ws instance is NULL");
|
||||
return NULL;
|
||||
}
|
||||
if (ws->params.session_update_json != NULL) {
|
||||
return strdup(ws->params.session_update_json);
|
||||
}
|
||||
__ws_generate_event_id(ws);
|
||||
root = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(root, "event_id", ws->event_id);
|
||||
cJSON_AddStringToObject(root, "type", "session.update");
|
||||
session = cJSON_CreateObject();
|
||||
cJSON_AddItemToObject(root, "session", session);
|
||||
cJSON_AddStringToObject(session, "object", "realtime.session");
|
||||
cJSON_AddStringToObject(session, "model", "");
|
||||
cJSON_AddStringToObject(session, "input_audio_format", __ws_audio_codec_to_string(ws));
|
||||
json = cJSON_PrintUnformatted(root);
|
||||
cJSON_Delete(root);
|
||||
return json;
|
||||
}
|
||||
|
||||
static void __ws_append_data(ws_impl_t* ws, volc_ws_event_data_t* data) {
|
||||
int new_capacity = 0;
|
||||
uint8_t* new_buffer = NULL;
|
||||
if (data->op_code == VOLC_WS_OPCODES_TEXT || data->op_code == VOLC_WS_OPCODES_BINARY) {
|
||||
ws->assembler.in_progress = 1;
|
||||
ws->assembler.opcode = data->op_code;
|
||||
} else if (data->op_code == VOLC_WS_OPCODES_CONT) {
|
||||
if (!ws->assembler.in_progress) {
|
||||
LOGW("append data, but assembler not in progress");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
LOGD("append data, skip opcode: %d", data->op_code);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ws->assembler.in_progress) {
|
||||
new_capacity = ws->assembler.size + data->data_len + 1;
|
||||
if (new_capacity > ws->assembler.capacity) {
|
||||
LOGI("append data, new_capacity: %d", new_capacity);
|
||||
new_capacity = new_capacity < 1024 ? 1024 : new_capacity * 2;
|
||||
new_buffer = volc_osal_realloc(ws->assembler.buffer, new_capacity);
|
||||
if (new_buffer == NULL) {
|
||||
LOGE("Failed to alloc memory");
|
||||
__ws_assembler_free(&ws->assembler);
|
||||
return;
|
||||
}
|
||||
ws->assembler.buffer = new_buffer;
|
||||
ws->assembler.capacity = new_capacity;
|
||||
}
|
||||
memcpy(ws->assembler.buffer + ws->assembler.size, data->data_ptr, data->data_len);
|
||||
ws->assembler.size += data->data_len;
|
||||
|
||||
if (data->fin && (data->payload_len == data->payload_offset + data->data_len)) {
|
||||
LOGD("append data, fin, len: %d", ws->assembler.size);
|
||||
ws->assembler.buffer[ws->assembler.size] = 0;
|
||||
__ws_recv_data(ws, (const char*)ws->assembler.buffer, ws->assembler.size);
|
||||
memset(ws->assembler.buffer, 0, ws->assembler.capacity);
|
||||
ws->assembler.size = 0;
|
||||
ws->assembler.in_progress = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void __ws_stop(ws_impl_t* ws);
|
||||
static void __ws_event_handler(void* context, int32_t event_id, void* event_data) {
|
||||
ws_impl_t* ws = (ws_impl_t*)context;
|
||||
volc_msg_t msg = { 0 };
|
||||
volc_ws_event_data_t *data = (volc_ws_event_data_t *)event_data;
|
||||
if (NULL == ws) {
|
||||
LOGW("ws is NULL");
|
||||
return;
|
||||
}
|
||||
switch (event_id) {
|
||||
case VOLC_WS_EVENT_CONNECTED:
|
||||
ws->b_connected = true;
|
||||
msg.code = VOLC_MSG_CONNECTED;
|
||||
__send_message_2_user(ws, &msg);
|
||||
break;
|
||||
case VOLC_WS_EVENT_DISCONNECTED:
|
||||
ws->b_connected = false;
|
||||
msg.code = VOLC_MSG_DISCONNECTED;
|
||||
__send_message_2_user(ws, &msg);
|
||||
break;
|
||||
case VOLC_WS_EVENT_DATA:
|
||||
__ws_append_data(ws, data);
|
||||
break;
|
||||
case VOLC_WS_EVENT_CLOSED:
|
||||
LOGW("receive close event");
|
||||
ws->b_connected = false;
|
||||
msg.code = VOLC_MSG_DISCONNECTED;
|
||||
__send_message_2_user(ws, &msg);
|
||||
/* The owner joins this worker; never join from its own callback. */
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int __ws_send_message(ws_impl_t* ws, const void* data_ptr, size_t data_len) {
|
||||
if (!ws || !data_ptr || !data_len) {
|
||||
LOGE("ws or data or info is NULL");
|
||||
return -1;
|
||||
}
|
||||
return volc_ws_client_send_text(ws->client, (const char*)data_ptr, data_len, 1000);
|
||||
}
|
||||
|
||||
static int __ws_start(ws_impl_t* ws, volc_iot_info_t* iot_info)
|
||||
{
|
||||
/* The standard-protocol signature requires Unix seconds, not milliseconds. */
|
||||
uint64_t current_time = volc_osal_get_time_ms() / 1000;
|
||||
bool b_wait_for_session_update = __ws_wait_for_session_update(ws);
|
||||
int random_num = (int)current_time;//volc_get_random_num();
|
||||
char time_str[32] = { 0 };
|
||||
char num_str[32] = { 0 };
|
||||
char platform_info[16] = { 0 };
|
||||
char user_agent[64] = { 0 };
|
||||
if (!ws || !iot_info) {
|
||||
LOGE("ws or option is NULL");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* This instance is reused across conversations; readiness is per socket. */
|
||||
__ws_stop(ws);
|
||||
ws->b_connected = false;
|
||||
ws->b_interrupted = false;
|
||||
ws->conv_status = VOLC_CONV_STATUS_ANSWER_FINISH;
|
||||
HAL_SAFE_FREE(ws->p_last_response_id);
|
||||
__ws_assembler_free(&ws->assembler);
|
||||
volc_osal_get_platform_info(platform_info, sizeof(platform_info));
|
||||
snprintf(user_agent, sizeof(user_agent), "%s(%s)", volc_get_version(), platform_info);
|
||||
snprintf(time_str, sizeof(time_str), "%llu", current_time);
|
||||
snprintf(num_str, sizeof(num_str), "%d", random_num);
|
||||
char* signature = volc_generate_signature_ws(iot_info->device_secret, iot_info->product_key, iot_info->device_name, iot_info->instance_id, random_num, current_time, 1);
|
||||
volc_ws_config_t ws_cfg = { 0 };
|
||||
snprintf(ws->uri, sizeof(ws->uri), "%s%s?bot=%s&wait_for_session_update=%s", WS_AIGC_URI, WS_AIGC_PATH, ws->p_bot_id, b_wait_for_session_update ? "true" : "false");
|
||||
ws_cfg.uri = ws->uri;
|
||||
ws_cfg.user_agent = user_agent;
|
||||
snprintf(ws->headers, sizeof(ws->headers),
|
||||
"X-Signature: %s\r\n"
|
||||
"X-Auth-Type: 1\r\n"
|
||||
"X-Device-Name: %s\r\n"
|
||||
"X-Product-Key: %s\r\n"
|
||||
"X-Random-Num: %s\r\n"
|
||||
"X-Timestamp: %s\r\n"
|
||||
"X-Hardware-Id: %s\r\n"
|
||||
"X-Instance-Id: %s\r\n", signature, iot_info->device_name, iot_info->product_key, num_str, time_str, ws->hardware_id, iot_info->instance_id);
|
||||
ws_cfg.headers = ws->headers;
|
||||
LOGI("WS URL: %s", ws_cfg.uri);
|
||||
ws_cfg.user_context = ws;
|
||||
ws_cfg.buffer_size = 1024 * 5;
|
||||
ws_cfg.ws_event_handler = __ws_event_handler;
|
||||
ws->client = volc_ws_client_init(&ws_cfg);
|
||||
if(volc_ws_client_start(ws->client)) {
|
||||
LOGE("Failed to start websocket client");
|
||||
__ws_stop(ws);
|
||||
return -1;
|
||||
}
|
||||
if (b_wait_for_session_update) {
|
||||
uint64_t connect_deadline = volc_osal_get_time_ms() + 15000;
|
||||
while (!ws->b_connected) {
|
||||
if (ws->client->exit || volc_osal_get_time_ms() >= connect_deadline) {
|
||||
LOGE("WebSocket connection did not become ready");
|
||||
__ws_stop(ws);
|
||||
return -1;
|
||||
}
|
||||
volc_osal_thread_sleep(10);
|
||||
}
|
||||
char* session_update = __ws_generate_session_update(ws);
|
||||
int sent = session_update ? __ws_send_message(ws, session_update, strlen(session_update)) : -1;
|
||||
bool sent_complete = session_update && sent == (int)strlen(session_update);
|
||||
HAL_SAFE_FREE(session_update);
|
||||
if (!sent_complete) {
|
||||
LOGE("Initial session.update failed; audio remains disabled");
|
||||
__ws_stop(ws);
|
||||
return -1;
|
||||
}
|
||||
LOGI("Initial session.update sent after WebSocket connected");
|
||||
}
|
||||
ws->b_pipeline_started = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __ws_stop(ws_impl_t* ws)
|
||||
{
|
||||
if (!ws) {
|
||||
LOGE("ws instance is NULL");
|
||||
return;
|
||||
}
|
||||
ws->b_connected = false;
|
||||
ws->b_pipeline_started = false;
|
||||
if (ws->client) volc_ws_client_destroy(ws->client);
|
||||
ws->client = NULL;
|
||||
ws->b_pipeline_started = false;
|
||||
}
|
||||
|
||||
static int __ws_input_audio_buffer_append(ws_impl_t* ws, const char* data_ptr, size_t data_len) {
|
||||
int ret = 0;
|
||||
cJSON* root = NULL;
|
||||
if (!ws || !data_ptr || data_len == 0) {
|
||||
LOGE("ws or data or data_len is NULL");
|
||||
return -1;
|
||||
}
|
||||
|
||||
root = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(root, "type", "input_audio_buffer.append");
|
||||
cJSON_AddStringToObject(root, "audio", data_ptr);
|
||||
char* json = cJSON_PrintUnformatted(root);
|
||||
cJSON_Delete(root);
|
||||
ret = volc_ws_client_send_text(ws->client, json, strlen(json), 1000);
|
||||
if (ret >= 0) {
|
||||
ret = 0;
|
||||
} else {
|
||||
LOGW("failed to send audio buffer");
|
||||
}
|
||||
HAL_SAFE_FREE(json);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __ws_input_audio_buffer_commit(ws_impl_t* ws) {
|
||||
int ret = 0;
|
||||
cJSON* root = NULL;
|
||||
if (!ws) {
|
||||
LOGE("ws instance is NULL");
|
||||
return -1;
|
||||
}
|
||||
root = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(root, "type", "input_audio_buffer.commit");
|
||||
char* json = cJSON_PrintUnformatted(root);
|
||||
LOGD("json: %s", json);
|
||||
cJSON_Delete(root);
|
||||
ret = volc_ws_client_send_text(ws->client, json, strlen(json), 1000);
|
||||
if (ret >= 0) {
|
||||
ret = 0;
|
||||
}
|
||||
HAL_SAFE_FREE(json);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __ws_response_create(ws_impl_t* ws) {
|
||||
int ret = 0;
|
||||
cJSON* root = NULL;
|
||||
if (!ws) {
|
||||
LOGE("ws instance is NULL");
|
||||
return -1;
|
||||
}
|
||||
root = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(root, "type", "response.create");
|
||||
// 创建 "response" 对象
|
||||
cJSON* response_obj = cJSON_CreateObject();
|
||||
cJSON_AddItemToObject(root, "response", response_obj);
|
||||
// 添加 “modalities” 字段
|
||||
cJSON* modalities_array = cJSON_CreateArray();
|
||||
cJSON_AddItemToArray(modalities_array, cJSON_CreateString("text"));
|
||||
cJSON_AddItemToArray(modalities_array, cJSON_CreateString("audio"));
|
||||
cJSON_AddItemToObject(response_obj, "modalities", modalities_array);
|
||||
// 生成 JSON 字符串
|
||||
const char* json = cJSON_PrintUnformatted(root);
|
||||
LOGD("json: %s", json);
|
||||
// 释放内存
|
||||
cJSON_Delete(root);
|
||||
ret = volc_ws_client_send_text(ws->client, json, strlen(json), 1000);
|
||||
HAL_SAFE_FREE(json);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __ws_send_audio(ws_impl_t* ws, const void* data_ptr, size_t data_len, bool commit) {
|
||||
int ret = 0;
|
||||
size_t len = 0;
|
||||
if (!ws || !data_ptr || !data_len) {
|
||||
LOGE("ws or data or info is NULL");
|
||||
return -1;
|
||||
}
|
||||
len = volc_base64_encoded_length(data_len);
|
||||
if (ws->data_buf_size < len) {
|
||||
HAL_SAFE_FREE(ws->p_data_buf);
|
||||
ws->p_data_buf = (char*)volc_osal_malloc(len);
|
||||
if (!ws->p_data_buf) {
|
||||
LOGE("failed to malloc data buf");
|
||||
ws->data_buf_size = 0;
|
||||
return -1;
|
||||
}
|
||||
ws->data_buf_size = len;
|
||||
}
|
||||
volc_base64_encode((unsigned char*) ws->p_data_buf, len, &len, (const unsigned char*) data_ptr, data_len);
|
||||
if (ret != 0) {
|
||||
LOGE("failed to encode audio");
|
||||
return -1;
|
||||
}
|
||||
ret = __ws_input_audio_buffer_append(ws, ws->p_data_buf, len);
|
||||
if (ret != 0) {
|
||||
LOGE("failed to append audio buffer");
|
||||
return -1;
|
||||
}
|
||||
if (commit) {
|
||||
ret = __ws_input_audio_buffer_commit(ws);
|
||||
if (ret != 0) {
|
||||
LOGE("failed to commit audio buffer");
|
||||
return -1;
|
||||
}
|
||||
ret = __ws_response_create(ws);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __ws_parse_params(const char* params, ws_params_t* out_params) {
|
||||
cJSON* p_json = NULL;
|
||||
if (!params || !out_params) {
|
||||
LOGW("params or out_params is NULL");
|
||||
return;
|
||||
}
|
||||
p_json = cJSON_Parse(params);
|
||||
if (!p_json) {
|
||||
LOGW("Failed to parse json params: %s", params);
|
||||
return;
|
||||
}
|
||||
volc_json_read_int(p_json, "audio.codec", (int*)&out_params->audio_codec_type);
|
||||
cJSON* session_update = cJSON_GetObjectItem(p_json, "session_update");
|
||||
if (cJSON_IsObject(session_update)) {
|
||||
HAL_SAFE_FREE(out_params->session_update_json);
|
||||
out_params->session_update_json = cJSON_PrintUnformatted(session_update);
|
||||
}
|
||||
cJSON_Delete(p_json);
|
||||
}
|
||||
|
||||
volc_ws_t volc_ws_create(void* context, cJSON* p_config, volc_msg_cb message_callback, volc_data_cb data_callback) {
|
||||
ws_impl_t* ws = (ws_impl_t*)volc_osal_calloc(1, sizeof(ws_impl_t));
|
||||
if (!ws) {
|
||||
LOGE("volc_ws_create: malloc ws failed");
|
||||
return NULL;
|
||||
}
|
||||
ws->message_callback = message_callback;
|
||||
ws->data_callback = data_callback;
|
||||
ws->context = context;
|
||||
ws->conv_status = VOLC_CONV_STATUS_ANSWER_FINISH;
|
||||
ws->params.audio_codec_type = VOLC_AUDIO_CODEC_TYPE_PCM;
|
||||
|
||||
volc_osal_get_uuid(ws->hardware_id, sizeof(ws->hardware_id));
|
||||
|
||||
if (__ws_init(ws, p_config) != 0) {
|
||||
HAL_SAFE_FREE(ws);
|
||||
LOGE("volc_ws_create: ws init failed");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
LOGI("ws create success, hardware id: %s", ws->hardware_id);
|
||||
return (volc_ws_t) ws;
|
||||
}
|
||||
|
||||
void volc_ws_destroy(volc_ws_t ws) {
|
||||
ws_impl_t* ws_impl = (ws_impl_t*) ws;
|
||||
if (!ws_impl) {
|
||||
LOGE("ws instance is NULL");
|
||||
return;
|
||||
}
|
||||
|
||||
__ws_stop(ws_impl);
|
||||
HAL_SAFE_FREE(ws_impl->p_data_buf);
|
||||
HAL_SAFE_FREE(ws_impl->p_bot_id);
|
||||
HAL_SAFE_FREE(ws_impl->p_last_response_id);
|
||||
HAL_SAFE_FREE(ws_impl->params.session_update_json);
|
||||
HAL_SAFE_FREE(ws_impl);
|
||||
}
|
||||
|
||||
int volc_ws_start(volc_ws_t ws, const char* bot_id, volc_iot_info_t* iot_info, const char* params) {
|
||||
ws_impl_t* ws_impl = (ws_impl_t*) ws;
|
||||
if (!ws_impl) {
|
||||
LOGE("ws instance is NULL");
|
||||
return -1;
|
||||
}
|
||||
HAL_SAFE_FREE(ws_impl->p_bot_id);
|
||||
ws_impl->p_bot_id = strdup(bot_id);
|
||||
__ws_parse_params(params, &ws_impl->params);
|
||||
return __ws_start(ws_impl, iot_info);
|
||||
}
|
||||
|
||||
int volc_ws_send(volc_ws_t ws, const void* data, int size, volc_data_info_t* data_info) {
|
||||
ws_impl_t* ws_impl = (ws_impl_t*) ws;
|
||||
if (!ws_impl) {
|
||||
LOGE("ws instance is NULL");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!ws_impl->b_pipeline_started || !ws_impl->b_connected) {
|
||||
LOGD("pipeline started[%d], connected[%d], cannot process data", (int) ws_impl->b_pipeline_started, (int) ws_impl->b_connected);
|
||||
return -1;
|
||||
}
|
||||
|
||||
switch (data_info->type) {
|
||||
case VOLC_DATA_TYPE_AUDIO: {
|
||||
return __ws_send_audio(ws_impl, data, size, data_info->info.audio.commit);
|
||||
}
|
||||
case VOLC_DATA_TYPE_VIDEO: {
|
||||
LOGW("video data not supported");
|
||||
break;
|
||||
}
|
||||
case VOLC_DATA_TYPE_MESSAGE: {
|
||||
return __ws_send_message(ws, data, size);
|
||||
}
|
||||
default:
|
||||
LOGW("unsupported data type: %d", data_info->type);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int volc_ws_stop(volc_ws_t ws) {
|
||||
ws_impl_t* ws_impl = (ws_impl_t*) ws;
|
||||
if (!ws_impl) {
|
||||
LOGE("ws instance is NULL");
|
||||
return -1;
|
||||
}
|
||||
__ws_stop(ws_impl);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int volc_ws_interrupt(volc_ws_t ws) {
|
||||
int ret = 0;
|
||||
uint8_t* msg = NULL;
|
||||
size_t msg_len = 0;
|
||||
ws_impl_t* ws_impl = (ws_impl_t*) ws;
|
||||
if (!ws_impl) {
|
||||
LOGE("ws instance is NULL");
|
||||
return -1;
|
||||
}
|
||||
if (ws_impl->conv_status != VOLC_CONV_STATUS_LISTENING && ws_impl->conv_status != VOLC_CONV_STATUS_THINKING &&
|
||||
ws_impl->conv_status != VOLC_CONV_STATUS_ANSWERING) {
|
||||
LOGW("interrupt failed, conv status is not listening or thinking");
|
||||
return -1;
|
||||
}
|
||||
if (__build_ws_message(ws_interrupt_str, &msg, &msg_len) != 0) {
|
||||
LOGE("build control message failed");
|
||||
return -1;
|
||||
}
|
||||
if (ws_impl->conv_status == VOLC_CONV_STATUS_ANSWERING) {
|
||||
ws_impl->b_interrupted = true;
|
||||
}
|
||||
if ((__ws_send_message(ws_impl, msg, msg_len)) <= 0) {
|
||||
LOGE("send control message failed");
|
||||
ret = -1;
|
||||
}
|
||||
HAL_SAFE_FREE(msg);
|
||||
return ret;
|
||||
}
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
+1562
File diff suppressed because it is too large
Load Diff
+152
@@ -0,0 +1,152 @@
|
||||
#ifndef __WEBSOCKET_H__
|
||||
#define __WEBSOCKET_H__
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "volc_osal.h"
|
||||
#include "tls_client.h"
|
||||
#define CONFIG_WEBSOCKET_TLS
|
||||
|
||||
typedef enum {
|
||||
VOLC_WS_STATE_ERROR = -1,
|
||||
VOLC_WS_STATE_UNKNOW = 0,
|
||||
VOLC_WS_STATE_INIT,
|
||||
VOLC_WS_STATE_CONNECTED,
|
||||
VOLC_WS_STATE_WAIT_TIMEOUT,
|
||||
VOLC_WS_STATE_CLOSING,
|
||||
} volc_ws_state_e;
|
||||
|
||||
typedef enum {
|
||||
VOLC_WS_OPCODES_CONT = 0x00, // 附加数据帧
|
||||
VOLC_WS_OPCODES_TEXT = 0x01, // 文本数据帧
|
||||
VOLC_WS_OPCODES_BINARY = 0x02, // 二进制数据帧
|
||||
VOLC_WS_OPCODES_CLOSE = 0x08, // 连接关闭
|
||||
VOLC_WS_OPCODES_PING = 0x09, // 心跳
|
||||
VOLC_WS_OPCODES_PONG = 0x0a,
|
||||
VOLC_WS_OPCODES_FIN = 0x80,
|
||||
VOLC_WS_OPCODES_NONE = 0x100,
|
||||
} volc_ws_opcode_e;
|
||||
|
||||
typedef enum {
|
||||
VOLC_WS_EVENT_ANY = -1,
|
||||
VOLC_WS_EVENT_ERROR = 0,
|
||||
VOLC_WS_EVENT_CONNECTED,
|
||||
VOLC_WS_EVENT_DISCONNECTED,
|
||||
VOLC_WS_EVENT_DATA,
|
||||
VOLC_WS_EVENT_CLOSED,
|
||||
VOLC_WS_EVENT_MAX
|
||||
} volc_ws_event_id_e;
|
||||
|
||||
typedef struct {
|
||||
uint8_t opcode;
|
||||
bool fin;
|
||||
char mask_key[4];
|
||||
int payload_len;
|
||||
int bytes_remaining;
|
||||
bool header_received;
|
||||
} volc_ws_frame_state_t;
|
||||
|
||||
typedef struct {
|
||||
char* path;
|
||||
char* buffer;
|
||||
char* sub_protocol;
|
||||
char* user_agent;
|
||||
char* headers;
|
||||
bool propagate_control_frames;
|
||||
volc_ws_frame_state_t frame_state;
|
||||
} transport_ws_t;
|
||||
|
||||
typedef struct {
|
||||
uint64_t last_print_ms;
|
||||
int sent;
|
||||
int received;
|
||||
} ws_stats_t;
|
||||
|
||||
typedef void (*volc_ws_event_handler_t)(void* user_context, int32_t event_id, void* event_data);
|
||||
|
||||
typedef struct {
|
||||
char* host;
|
||||
char* path;
|
||||
char* scheme;
|
||||
char* signature;
|
||||
int port;
|
||||
volc_ws_state_e state;
|
||||
// uint64_t keepalive_tick_ms;
|
||||
uint64_t reconnect_tick_ms;
|
||||
uint64_t ping_tick_ms;
|
||||
uint64_t pingpong_tick_ms;
|
||||
int auto_reconnect;
|
||||
volatile bool run;
|
||||
volatile bool exit;
|
||||
bool wait_for_pong_resp;
|
||||
char* rx_buffer;
|
||||
char* tx_buffer;
|
||||
int buffer_size;
|
||||
int rx_retry;
|
||||
bool last_fin;
|
||||
volc_ws_opcode_e last_opcode;
|
||||
int payload_len;
|
||||
int payload_offset;
|
||||
transport_ws_t* ws_transport;
|
||||
int sockfd;
|
||||
int is_tls;
|
||||
#if defined(CONFIG_WEBSOCKET_TLS)
|
||||
MbedTLSSession* ssl;
|
||||
#endif
|
||||
void* user_context;
|
||||
volc_ws_event_handler_t ws_event_handler;
|
||||
volc_osal_mutex_t mutex;
|
||||
volc_osal_mutex_t send_mutex;
|
||||
volc_osal_tid_t tid;
|
||||
ws_stats_t stats;
|
||||
} volc_ws_client_t;
|
||||
|
||||
typedef struct {
|
||||
const char* uri;
|
||||
// bool disable_auto_reconnect;
|
||||
int rx_retry;
|
||||
void* user_context;
|
||||
int buffer_size;
|
||||
const char* subprotocol;
|
||||
const char* user_agent;
|
||||
const char* headers;
|
||||
// bool disable_pingpong_discon;
|
||||
volc_ws_event_handler_t ws_event_handler;
|
||||
} volc_ws_config_t;
|
||||
|
||||
/**
|
||||
* @brief Websocket event data
|
||||
*/
|
||||
typedef struct {
|
||||
char* data_ptr;
|
||||
int data_len;
|
||||
bool fin;
|
||||
uint8_t op_code;
|
||||
int payload_len;
|
||||
int payload_offset;
|
||||
} volc_ws_event_data_t;
|
||||
|
||||
volc_ws_client_t* volc_ws_client_init(const volc_ws_config_t* input);
|
||||
int volc_ws_client_destroy(volc_ws_client_t* client);
|
||||
#if defined(PLATFORM_MACOS)
|
||||
void* volc_ws_client_task(void* thread_param);
|
||||
#else
|
||||
void volc_ws_client_task(void* thread_param);
|
||||
#endif
|
||||
int volc_ws_client_start(volc_ws_client_t* client);
|
||||
int volc_ws_client_stop(volc_ws_client_t* client);
|
||||
|
||||
int volc_ws_client_send_text(volc_ws_client_t* client, const char* data, int len, int timeout);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* __WEBSOCKET_H__ */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
Copyright (c) 2009-2017 Dave Gamble and cJSON contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef cJSON__h
|
||||
#define cJSON__h
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32))
|
||||
#define __WINDOWS__
|
||||
#endif
|
||||
|
||||
#ifdef __WINDOWS__
|
||||
|
||||
/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 3 define options:
|
||||
|
||||
CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols
|
||||
CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default)
|
||||
CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol
|
||||
|
||||
For *nix builds that support visibility attribute, you can define similar behavior by
|
||||
|
||||
setting default visibility to hidden by adding
|
||||
-fvisibility=hidden (for gcc)
|
||||
or
|
||||
-xldscope=hidden (for sun cc)
|
||||
to CFLAGS
|
||||
|
||||
then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does
|
||||
|
||||
*/
|
||||
|
||||
#define CJSON_CDECL __cdecl
|
||||
#define CJSON_STDCALL __stdcall
|
||||
|
||||
/* export symbols by default, this is necessary for copy pasting the C and header file */
|
||||
#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS)
|
||||
#define CJSON_EXPORT_SYMBOLS
|
||||
#endif
|
||||
|
||||
#if defined(CJSON_HIDE_SYMBOLS)
|
||||
#define CJSON_PUBLIC(type) type CJSON_STDCALL
|
||||
#elif defined(CJSON_EXPORT_SYMBOLS)
|
||||
#define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL
|
||||
#elif defined(CJSON_IMPORT_SYMBOLS)
|
||||
#define CJSON_PUBLIC(type) __declspec(dllimport) type CJSON_STDCALL
|
||||
#endif
|
||||
#else /* !__WINDOWS__ */
|
||||
#define CJSON_CDECL
|
||||
#define CJSON_STDCALL
|
||||
|
||||
#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY)
|
||||
#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type
|
||||
#else
|
||||
#define CJSON_PUBLIC(type) type
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* project version */
|
||||
#define CJSON_VERSION_MAJOR 1
|
||||
#define CJSON_VERSION_MINOR 7
|
||||
#define CJSON_VERSION_PATCH 13
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
/* cJSON Types: */
|
||||
#define cJSON_Invalid (0)
|
||||
#define cJSON_False (1 << 0)
|
||||
#define cJSON_True (1 << 1)
|
||||
#define cJSON_NULL (1 << 2)
|
||||
#define cJSON_Number (1 << 3)
|
||||
#define cJSON_String (1 << 4)
|
||||
#define cJSON_Array (1 << 5)
|
||||
#define cJSON_Object (1 << 6)
|
||||
#define cJSON_Raw (1 << 7) /* raw json */
|
||||
|
||||
#define cJSON_IsReference 256
|
||||
#define cJSON_StringIsConst 512
|
||||
|
||||
/* The cJSON structure: */
|
||||
typedef struct cJSON
|
||||
{
|
||||
/* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
|
||||
struct cJSON *next;
|
||||
struct cJSON *prev;
|
||||
/* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
|
||||
struct cJSON *child;
|
||||
|
||||
/* The type of the item, as above. */
|
||||
int type;
|
||||
|
||||
/* The item's string, if type==cJSON_String and type == cJSON_Raw */
|
||||
char *valuestring;
|
||||
/* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */
|
||||
int valueint;
|
||||
/* The item's number, if type==cJSON_Number */
|
||||
double valuedouble;
|
||||
|
||||
/* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
|
||||
char *string;
|
||||
} cJSON;
|
||||
|
||||
typedef struct cJSON_Hooks
|
||||
{
|
||||
/* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */
|
||||
void *(CJSON_CDECL *malloc_fn)(size_t sz);
|
||||
void (CJSON_CDECL *free_fn)(void *ptr);
|
||||
} cJSON_Hooks;
|
||||
|
||||
typedef int cJSON_bool;
|
||||
|
||||
/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them.
|
||||
* This is to prevent stack overflows. */
|
||||
#ifndef CJSON_NESTING_LIMIT
|
||||
#define CJSON_NESTING_LIMIT 1000
|
||||
#endif
|
||||
|
||||
/* returns the version of cJSON as a string */
|
||||
CJSON_PUBLIC(const char*) cJSON_Version(void);
|
||||
|
||||
/* Supply malloc, realloc and free functions to cJSON */
|
||||
CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks);
|
||||
|
||||
/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */
|
||||
/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length);
|
||||
/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */
|
||||
/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated);
|
||||
|
||||
/* Render a cJSON entity to text for transfer/storage. */
|
||||
CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item);
|
||||
/* Render a cJSON entity to text for transfer/storage without any formatting. */
|
||||
CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item);
|
||||
/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */
|
||||
CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt);
|
||||
/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */
|
||||
/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format);
|
||||
/* Delete a cJSON entity and all subentities. */
|
||||
CJSON_PUBLIC(void) cJSON_Delete(cJSON *item);
|
||||
|
||||
/* Returns the number of items in an array (or object). */
|
||||
CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array);
|
||||
/* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index);
|
||||
/* Get item "string" from object. Case insensitive. */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string);
|
||||
/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */
|
||||
CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void);
|
||||
|
||||
/* Check item type and return its value */
|
||||
CJSON_PUBLIC(char *) cJSON_GetStringValue(cJSON *item);
|
||||
CJSON_PUBLIC(double) cJSON_GetNumberValue(cJSON *item);
|
||||
|
||||
/* These functions check the type of an item */
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item);
|
||||
|
||||
/* These calls create a cJSON item of the appropriate type. */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string);
|
||||
/* raw json */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void);
|
||||
|
||||
/* Create a string where valuestring references a string so
|
||||
* it will not be freed by cJSON_Delete */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string);
|
||||
/* Create an object/array that only references it's elements so
|
||||
* they will not be freed by cJSON_Delete */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child);
|
||||
|
||||
/* These utilities create an Array of count items.
|
||||
* The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count);
|
||||
|
||||
/* Append item to the specified array/object. */
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item);
|
||||
/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object.
|
||||
* WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before
|
||||
* writing to `item->string` */
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item);
|
||||
/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item);
|
||||
|
||||
/* Remove/Detach items from Arrays/Objects. */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which);
|
||||
CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string);
|
||||
CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string);
|
||||
CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string);
|
||||
|
||||
/* Update array items. */
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem);
|
||||
|
||||
/* Duplicate a cJSON item */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse);
|
||||
/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will
|
||||
* need to be released. With recurse!=0, it will duplicate any children connected to the item.
|
||||
* The item->next and ->prev pointers are always zero on return from Duplicate. */
|
||||
/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal.
|
||||
* case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive);
|
||||
|
||||
/* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings.
|
||||
* The input pointer json cannot point to a read-only address area, such as a string constant,
|
||||
* but should point to a readable and writable adress area. */
|
||||
CJSON_PUBLIC(void) cJSON_Minify(char *json);
|
||||
|
||||
/* Helper functions for creating and adding items to an object at the same time.
|
||||
* They return the added item or NULL on failure. */
|
||||
CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name);
|
||||
CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name);
|
||||
CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name);
|
||||
CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean);
|
||||
CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number);
|
||||
CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string);
|
||||
CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw);
|
||||
CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name);
|
||||
CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name);
|
||||
|
||||
/* When assigning an integer value, it needs to be propagated to valuedouble too. */
|
||||
#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number))
|
||||
/* helper for the cJSON_SetNumberValue macro */
|
||||
CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number);
|
||||
#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number))
|
||||
/* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */
|
||||
CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring);
|
||||
|
||||
/* Macro for iterating over an array or object */
|
||||
#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next)
|
||||
|
||||
/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */
|
||||
CJSON_PUBLIC(void *) cJSON_malloc(size_t size);
|
||||
CJSON_PUBLIC(void) cJSON_free(void *object);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,118 @@
|
||||
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "util/volc_auth.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <mbedtls/ssl.h>
|
||||
#include <mbedtls/entropy.h>
|
||||
#include <mbedtls/ctr_drbg.h>
|
||||
#include <mbedtls/error.h>
|
||||
#include <mbedtls/sha256.h>
|
||||
#include <mbedtls/md5.h>
|
||||
#include <mbedtls/base64.h>
|
||||
|
||||
#include "volc_osal.h"
|
||||
#include "util/volc_base64.h"
|
||||
#include "util/volc_list.h"
|
||||
#include "util/volc_log.h"
|
||||
|
||||
typedef bool(predicate_func)(unsigned char c);
|
||||
|
||||
static bool _url_trim_pred(unsigned char ch) {
|
||||
if (ch < 32) {
|
||||
return true;
|
||||
}
|
||||
switch (ch) {
|
||||
case 0x20: /* ' ' - space */
|
||||
case 0x09: /* '\t' - horizontal tab */
|
||||
case 0x0A: /* '\n' - line feed */
|
||||
case 0x0B: /* '\v' - vertical tab */
|
||||
case 0x0C: /* '\f' - form feed */
|
||||
case 0x0D: /* '\r' - carriage return */
|
||||
case '\a': /* '\r' - carriage return */
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
static int _right_trim_pred(unsigned char* str, predicate_func* pred) {
|
||||
int len = strlen((const char*)str);
|
||||
|
||||
while(len > 0 && pred(str[len - 1])) {
|
||||
--len;
|
||||
}
|
||||
|
||||
str[len] = '\0';
|
||||
return len;
|
||||
}
|
||||
|
||||
static int __aes_cbc(bool encrypt, const unsigned char* key, unsigned char* iv, const unsigned char* input, uint32_t ilen, int key_bits, unsigned char* output) {
|
||||
int mode = encrypt ? MBEDTLS_AES_ENCRYPT : MBEDTLS_AES_DECRYPT;
|
||||
mbedtls_aes_context aes_ctx;
|
||||
mbedtls_aes_init(&aes_ctx);
|
||||
mbedtls_aes_setkey_dec(&aes_ctx, key, key_bits);
|
||||
mbedtls_aes_crypt_cbc(&aes_ctx, mode, ilen, iv, (const unsigned char *)input, output);
|
||||
mbedtls_aes_free(&aes_ctx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define AES_KEY_SIZE 16 // AES key size in bytes
|
||||
#define AES_KEY_SIZE_24 24
|
||||
char* volc_aes_decode(const char* secret, const char* input, const bool partial_secret) {
|
||||
size_t decoded_len = 0;
|
||||
size_t secret_len = 0;
|
||||
int key_bits = 0;
|
||||
char* key = NULL;
|
||||
unsigned char iv[16] = {0};
|
||||
unsigned char* decoded_payload = NULL;
|
||||
if (input == NULL || strlen(input) == 0) {
|
||||
LOGE("Input is NULL or empty");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (partial_secret) {
|
||||
// If partial secret is used, we only use the first 8 bytes of the key and iv
|
||||
key = (char *)volc_osal_calloc(AES_KEY_SIZE, 1);
|
||||
memcpy(key, secret, AES_KEY_SIZE);
|
||||
key_bits = 128; // AES-128
|
||||
} else {
|
||||
secret_len = strlen(secret);
|
||||
if (secret_len < AES_KEY_SIZE_24) {
|
||||
LOGE("Secret length is less than 24, secret: %s", secret);
|
||||
return NULL;
|
||||
}
|
||||
key = (char *)volc_osal_calloc(AES_KEY_SIZE_24, 1); // +1 for null terminator
|
||||
memcpy(key, secret, AES_KEY_SIZE_24);
|
||||
key_bits = 192; // AES-192
|
||||
}
|
||||
|
||||
decoded_len = volc_base64_decoded_length((const uint8_t*)input, strlen(input));
|
||||
decoded_payload = (unsigned char *)volc_osal_calloc(decoded_len + 1, 1);
|
||||
if (!decoded_payload) {
|
||||
LOGE("Failed to allocate memory for decoded payload");
|
||||
return NULL;
|
||||
}
|
||||
volc_base64_decode((unsigned char *)decoded_payload, decoded_len, &decoded_len, (const unsigned char *)input, strlen(input));
|
||||
LOGV("decoded payload: %s, len: %zu", decoded_payload, decoded_len);
|
||||
|
||||
uint8_t *output = volc_osal_calloc(decoded_len + 16, 1); // +1 for null terminator
|
||||
|
||||
memcpy(iv, secret, AES_KEY_SIZE); // Use the same secret for IV
|
||||
__aes_cbc(false, (const unsigned char *)key, (unsigned char *)iv, (const unsigned char *)decoded_payload, decoded_len, key_bits, output);
|
||||
|
||||
_right_trim_pred(output, _url_trim_pred);
|
||||
|
||||
LOGV("trimmed decrypted payload: %s, len: %d", output, (int)strlen((const char*)output));
|
||||
HAL_SAFE_FREE(decoded_payload);
|
||||
HAL_SAFE_FREE(key);
|
||||
return (char *)output;
|
||||
}
|
||||
|
||||
void volc_sha256_hmac(const unsigned char* key, int keylen, const unsigned char* input, int ilen, unsigned char* output, int* plen) {
|
||||
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), key, keylen, input, ilen, output);
|
||||
*(plen) = mbedtls_md_get_size(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256));
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#ifndef __CONV_AI_SRC_UTIL_VOLC_AUTH_H__
|
||||
#define __CONV_AI_SRC_UTIL_VOLC_AUTH_H__
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
char* volc_aes_decode(const char* secret, const char* input, const bool partial_secret);
|
||||
void volc_sha256_hmac(const unsigned char* key, int keylen, const unsigned char* input, int ilen, unsigned char* output, int* plen);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* __CONV_AI_SRC_UTIL_VOLC_AUTH_H__ */
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "util/volc_base64.h"
|
||||
|
||||
#include <mbedtls/base64.h>
|
||||
|
||||
int volc_base64_encoded_length(int len) {
|
||||
return (len + 2) / 3 * 4 + 1; // +1 for null terminator
|
||||
}
|
||||
|
||||
int volc_base64_decoded_length(const uint8_t* to_decode, int len) {
|
||||
int padding = 0;
|
||||
if (len >= 2 && to_decode[len - 1] == '=' && to_decode[len - 2] == '=') { /*last two chars are = */
|
||||
padding = 2;
|
||||
} else if (to_decode[len - 1] == '=') { /*last char is = */
|
||||
padding = 1;
|
||||
}
|
||||
return (len * 3) / 4 - padding; // Calculate the decoded length
|
||||
}
|
||||
|
||||
void volc_base64_encode(unsigned char *dst, size_t dlen, size_t* olen, const unsigned char *src, size_t slen) {
|
||||
int ret = mbedtls_base64_encode(dst, dlen, olen, src, slen);
|
||||
if (ret != 0) {
|
||||
*olen = 0; // If encoding fails, set output length to 0
|
||||
}
|
||||
}
|
||||
|
||||
void volc_base64_decode(unsigned char *dst, size_t dlen, size_t* olen, const unsigned char *src, size_t slen) {
|
||||
int ret = mbedtls_base64_decode(dst, dlen, olen, src, slen);
|
||||
if (ret != 0) {
|
||||
*olen = 0; // If decoding fails, set output length to 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#ifndef __CONV_AI_SRC_UTIL_VOLC_BASE64_H__
|
||||
#define __CONV_AI_SRC_UTIL_VOLC_BASE64_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
int volc_base64_encoded_length(int len);
|
||||
int volc_base64_decoded_length(const uint8_t* to_decode, int len);
|
||||
|
||||
void volc_base64_encode(unsigned char *dst, size_t dlen, size_t* olen, const unsigned char *src, size_t slen);
|
||||
|
||||
void volc_base64_decode(unsigned char *dst, size_t dlen, size_t* olen, const unsigned char *src, size_t slen);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* __CONV_AI_SRC_UTIL_VOLC_BASE64_H__ */
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "volc_http.h"
|
||||
|
||||
#include "webclient.h"
|
||||
#include "tls_certificate.h"
|
||||
#include "util/volc_list.h"
|
||||
#include "util/volc_log.h"
|
||||
|
||||
char* volc_http_post(const char* uri, const char* post_data, int data_len)
|
||||
{
|
||||
struct webclient_session* session = NULL;
|
||||
char* buffer = NULL;
|
||||
int resp_status;
|
||||
size_t res_len = 0;
|
||||
|
||||
/* create webclient session and set header response size */
|
||||
session = webclient_session_create(2048, GLOBAL_ROOT_CERT, GLOBAL_ROOT_CERT_LEN);
|
||||
if (session == NULL) {
|
||||
goto err_out_label;
|
||||
}
|
||||
|
||||
webclient_header_fields_add(session, "Content-Type: application/json\r\n");
|
||||
webclient_header_fields_add(session, "Content-Length: %d\r\n", strlen(post_data));
|
||||
|
||||
/* send POST request by default header */
|
||||
if ((resp_status = webclient_post(session, uri, post_data, data_len)) != 200) {
|
||||
LOGE("webclient POST request failed, response(%d) error.\n", resp_status);
|
||||
}
|
||||
|
||||
webclient_response(session, (void**) &buffer, &res_len);
|
||||
LOGD("url: %s, request: %s, response: %s", uri, post_data, buffer);
|
||||
err_out_label:
|
||||
if (session) {
|
||||
webclient_close(session);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#ifndef __CONV_AI_SRC_UTIL_VOLC_HTTP_H__
|
||||
#define __CONV_AI_SRC_UTIL_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 /* __CONV_AI_SRC_UTIL_VOLC_HTTP_H__ */
|
||||
@@ -0,0 +1,208 @@
|
||||
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "volc_json.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "volc_osal.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 *)volc_osal_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 *)volc_osal_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);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#ifndef __CONV_AI_SRC_UTIL_VOLC_JSON_H__
|
||||
#define __CONV_AI_SRC_UTIL_VOLC_JSON_H__
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <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__
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#ifndef __CONV_AI_SRC_UTIL_VOLC_LIST_H__
|
||||
#define __CONV_AI_SRC_UTIL_VOLC_LIST_H__
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
typedef struct volc_list_head_t
|
||||
{
|
||||
struct volc_list_head_t *next, *prev;
|
||||
} volc_list_head_t;
|
||||
|
||||
#define __LIST_ADD(entry, before, after) \
|
||||
{ \
|
||||
volc_list_head_t *new_ = (entry), *prev = (before), *next = (after); \
|
||||
(next)->prev = (new_); \
|
||||
(new_)->next = (next); \
|
||||
(new_)->prev = (prev); \
|
||||
(prev)->next = (new_); \
|
||||
}
|
||||
#define volc_list_init(entry) \
|
||||
do \
|
||||
{ \
|
||||
(entry)->next = (entry); \
|
||||
(entry)->prev = (entry); \
|
||||
} while (0)
|
||||
#define volc_list_add(entry, base) \
|
||||
do \
|
||||
{ \
|
||||
__LIST_ADD((entry), (base), (base)->next); \
|
||||
} while (0)
|
||||
#define volc_list_add_after(entry, base) \
|
||||
do \
|
||||
{ \
|
||||
__LIST_ADD((entry), (base), (base)->next); \
|
||||
} while (0)
|
||||
#define volc_list_add_before(entry, base) \
|
||||
do \
|
||||
{ \
|
||||
__LIST_ADD((entry), (base)->prev, (base)); \
|
||||
} while (0)
|
||||
#define volc_list_add_head(entry, head) volc_list_add_after(entry, head)
|
||||
#define volc_list_add_tail(entry, head) volc_list_add_before(entry, head)
|
||||
#define volc_list_del(entry) \
|
||||
do \
|
||||
{ \
|
||||
(entry)->prev->next = (entry)->next; \
|
||||
(entry)->next->prev = (entry)->prev; \
|
||||
(entry)->next = (entry)->prev = (entry); \
|
||||
} while (0)
|
||||
#define volc_list_empty(head) ((head)->next == (head))
|
||||
#define volc_list_get_head(head) (volc_list_empty(head) ? (volc_list_head_t *)NULL : (head)->next)
|
||||
#define volc_list_get_tail(head) (volc_list_empty(head) ? (volc_list_head_t *)NULL : (head)->prev)
|
||||
#define volc_list_is_head(entry, head) ((entry)->prev == head)
|
||||
#define volc_list_is_tail(entry, head) ((entry)->next == head)
|
||||
#define volc_list_entry(ptr, type, member) ((type *)((char *)(ptr) - (unsigned long)(&((type *)0)->member)))
|
||||
#define volc_list_for_each(h, head) for (h = (head)->next; h != (head); h = h->next)
|
||||
#define volc_list_for_each_safe(h, n, head) for (h = (head)->next, n = h->next; h != (head); h = n, n = h->next)
|
||||
#define volc_list_for_each_entry(p, head, type, member) \
|
||||
for (p = volc_list_entry((head)->next, type, member); &p->member != (head); \
|
||||
p = volc_list_entry(p->member.next, type, member))
|
||||
#define volc_list_for_each_entry_safe(p, t, head, type, member) \
|
||||
for (p = volc_list_entry((head)->next, type, member), t = volc_list_entry(p->member.next, type, member); \
|
||||
&p->member != (head); p = t, t = volc_list_entry(t->member.next, type, member))
|
||||
#define volc_list_get_head_entry(head, type, member) \
|
||||
(volc_list_empty(head) ? (type *)NULL : volc_list_entry(((head)->next), type, member))
|
||||
#define volc_list_get_tail_entry(head, type, member) \
|
||||
(volc_list_empty(head) ? (type *)NULL : volc_list_entry(((head)->prev), type, member))
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* #define __CONV_AI_SRC_UTIL_VOLC_LIST_H__ */
|
||||
@@ -0,0 +1,58 @@
|
||||
// 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"
|
||||
|
||||
#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__ */
|
||||
@@ -0,0 +1,604 @@
|
||||
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "volc_conv_ai.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
#include "volc_osal.h"
|
||||
#include "util/volc_json.h"
|
||||
#include "util/volc_log.h"
|
||||
#include "base/volc_device_manager.h"
|
||||
#include "base/volc_base.h"
|
||||
|
||||
#if defined(ENABLE_RTC_MODE)
|
||||
#include "VolcEngineRTCLite.h"
|
||||
#include "volc_rtc.h"
|
||||
#elif defined(ENABLE_WS_MODE)
|
||||
#include "volc_ws.h"
|
||||
#endif
|
||||
|
||||
#include "cJSON.h"
|
||||
|
||||
#define MAGIC_CONTROL "ctrl"
|
||||
#define MAGIC_CONV "conv"
|
||||
#define MAGIC_LENGTH 4
|
||||
#define MAGIC_OFFSET 8
|
||||
|
||||
typedef enum {
|
||||
VOLC_RT_STATE_NONE = 0, // Initial state
|
||||
VOLC_RT_STATE_CREATED, // engine created
|
||||
VOLC_RT_STATE_STARTED, // engine started
|
||||
// VOLC_RT_STATE_STARTED, // Joined a channel
|
||||
VOLC_RT_STATE_UNJOINED, // Connection lost
|
||||
VOLC_RT_STATE_STOPPED, // engine stopped
|
||||
VOLC_RT_STATE_DESTROYED, // engine destroyed
|
||||
VOLC_RT_STATE_ERROR // Error state
|
||||
} volc_rt_state_e;
|
||||
|
||||
typedef struct {
|
||||
int bytes;
|
||||
uint64_t last_time_ms;
|
||||
} media_stats_t;
|
||||
|
||||
typedef struct {
|
||||
volatile size_t status;
|
||||
bool b_key_frame_request;
|
||||
int target_kbps;
|
||||
void* user_data;
|
||||
|
||||
volc_mode_e mode;
|
||||
#if defined(ENABLE_WS_MODE)
|
||||
volc_ws_t ws;
|
||||
#endif
|
||||
#if defined(ENABLE_RTC_MODE)
|
||||
volc_rtc_t rtc;
|
||||
#endif
|
||||
|
||||
media_stats_t audio_recv_stats;
|
||||
media_stats_t audio_send_stats;
|
||||
volc_iot_info_t info;
|
||||
// volc_room_info_t room_info;
|
||||
volc_event_handler_t event_handler;
|
||||
} volc_engine_impl_t;
|
||||
|
||||
static void _iot_info_free(volc_iot_info_t* info) {
|
||||
if (info) {
|
||||
HAL_SAFE_FREE(info->device_name);
|
||||
HAL_SAFE_FREE(info->device_secret);
|
||||
HAL_SAFE_FREE(info->instance_id);
|
||||
HAL_SAFE_FREE(info->product_key);
|
||||
HAL_SAFE_FREE(info->product_secret);
|
||||
}
|
||||
}
|
||||
|
||||
static void __realtime_conv_status_to_user(volc_engine_impl_t* impl, volc_conv_status_e status) {
|
||||
if (impl->event_handler.on_volc_conversation_status) {
|
||||
impl->event_handler.on_volc_conversation_status(impl, status, impl->user_data);
|
||||
}
|
||||
}
|
||||
|
||||
static void __realtime_event_2_user_event(void* context, volc_msg_t* msg) {
|
||||
volc_event_t event = { 0 };
|
||||
if (context == NULL || msg == NULL) {
|
||||
LOGE("context or message is NULL");
|
||||
return;
|
||||
}
|
||||
|
||||
volc_engine_impl_t* impl = (volc_engine_impl_t*)context;
|
||||
switch (msg->code) {
|
||||
case VOLC_MSG_CONNECTED:
|
||||
event.code = VOLC_EV_CONNECTED;
|
||||
break;
|
||||
case VOLC_MSG_DISCONNECTED:
|
||||
event.code = VOLC_EV_DISCONNECTED;
|
||||
break;
|
||||
case VOLC_MSG_USER_JOINED:
|
||||
case VOLC_MSG_USER_OFFLINE:
|
||||
break;
|
||||
case VOLC_MSG_TOKEN_EXPIRED:
|
||||
break;
|
||||
case VOLC_MSG_KEY_FRAME_REQ:
|
||||
impl->b_key_frame_request = true;
|
||||
return;
|
||||
case VOLC_MSG_TARGET_BITRATE_CHANGED:
|
||||
impl->target_kbps = msg->data.target_bitrate / 1000;
|
||||
return;
|
||||
case VOLC_MSG_CONV_STATUS:
|
||||
__realtime_conv_status_to_user(impl, msg->data.conv_status);
|
||||
return;
|
||||
case VOLC_MSG_QUOTA_EXCEEDED:
|
||||
event.code = VOLC_EV_QUOTA_EXCEEDED;
|
||||
break;
|
||||
default:
|
||||
LOGW("Unknown message type: %d", msg->code);
|
||||
return; // Ignore unknown messages
|
||||
}
|
||||
|
||||
if (impl->event_handler.on_volc_event) {
|
||||
impl->event_handler.on_volc_event(impl, &event, impl->user_data);
|
||||
}
|
||||
}
|
||||
|
||||
static void __realtime_user_event_router(void* context, volc_msg_t* msg) {
|
||||
__realtime_event_2_user_event(context, msg);
|
||||
}
|
||||
|
||||
static void __record_media_bytes(media_stats_t* stats, size_t len, const char* tag) {
|
||||
#if defined(ENABLE_WS_MODE)
|
||||
uint64_t now_ms = 0;
|
||||
stats->bytes += len;
|
||||
now_ms = volc_osal_get_time_ms();
|
||||
if (stats->last_time_ms == 0) {
|
||||
stats->last_time_ms = now_ms;
|
||||
}
|
||||
if (now_ms - stats->last_time_ms >= 2000) {
|
||||
LOGI("%s bps: %d", tag, (int)(stats->bytes * 8 / (now_ms - stats->last_time_ms)));
|
||||
stats->last_time_ms = now_ms;
|
||||
stats->bytes = 0;
|
||||
}
|
||||
#else
|
||||
(void)stats;
|
||||
(void)len;
|
||||
(void)tag;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void __realtime_audio_router(volc_engine_impl_t* impl, const void* data, size_t len, volc_data_info_t* info) {
|
||||
if (impl->event_handler.on_volc_audio_data) {
|
||||
impl->event_handler.on_volc_audio_data(impl, data, len, &info->info.audio, impl->user_data);
|
||||
}
|
||||
__record_media_bytes(&impl->audio_recv_stats, len, "audio recv");
|
||||
}
|
||||
|
||||
static void __realtime_video_router(volc_engine_impl_t* impl, const void* data, size_t len, volc_data_info_t* info) {
|
||||
if (impl->event_handler.on_volc_video_data) {
|
||||
impl->event_handler.on_volc_video_data(impl, data, len, &info->info.video, impl->user_data);
|
||||
}
|
||||
}
|
||||
|
||||
static void __realtime_message_router(volc_engine_impl_t* impl, const void* data, size_t len, volc_data_info_t* info) {
|
||||
int ret = 0;
|
||||
if (impl->event_handler.on_volc_message_data) {
|
||||
impl->event_handler.on_volc_message_data(impl, data, len, &info->info.message, impl->user_data);
|
||||
}
|
||||
}
|
||||
|
||||
static void __realtime_data_router(void* context, const void* data, size_t len, volc_data_info_t* info) {
|
||||
volc_engine_impl_t* impl = (volc_engine_impl_t*)context;
|
||||
if (info) {
|
||||
switch(info->type) {
|
||||
case VOLC_DATA_TYPE_AUDIO:
|
||||
__realtime_audio_router(impl, data, len, info);
|
||||
break;
|
||||
case VOLC_DATA_TYPE_VIDEO:
|
||||
__realtime_video_router(impl, data, len, info);
|
||||
break;
|
||||
case VOLC_DATA_TYPE_MESSAGE:
|
||||
__realtime_message_router(impl, data, len, info);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int __config_iot_parse(cJSON* iot_config, volc_engine_impl_t* engine) {
|
||||
int ret = 0;
|
||||
|
||||
ret |= volc_json_read_string(iot_config, "instance_id", &engine->info.instance_id);
|
||||
ret |= volc_json_read_string(iot_config, "product_key", &engine->info.product_key);
|
||||
ret |= volc_json_read_string(iot_config, "device_name", &engine->info.device_name);
|
||||
|
||||
/*
|
||||
* Production devices receive a per-device Device Secret from our SaaS.
|
||||
* Product Secret is deliberately not accepted by the firmware path.
|
||||
*/
|
||||
ret |= volc_json_read_string(iot_config, "device_secret", &engine->info.device_secret);
|
||||
|
||||
return ret == 0 ? 0 : -1;
|
||||
}
|
||||
|
||||
const char* volc_get_version(void) {
|
||||
static char version[16] = {0};
|
||||
snprintf(version, sizeof(version), "%d.%d.%d", VOLC_VERSION_MAJOR, VOLC_VERSION_MINOR, VOLC_VERSION_PATCH);
|
||||
return version;
|
||||
}
|
||||
|
||||
const char* volc_err_2_str(int err_code) {
|
||||
switch(err_code) {
|
||||
case VOLC_ERR_NO_ERROR:
|
||||
return "Success";
|
||||
case VOLC_ERR_FAILED:
|
||||
return "Failed";
|
||||
case VOLC_ERR_LICENSE_EXHAUSTED:
|
||||
return "License exhausted";
|
||||
case VOLC_ERR_LICENSE_EXPIRED:
|
||||
return "License expired";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return "Unknown error";
|
||||
}
|
||||
|
||||
int volc_create(volc_engine_t* handle, const char* config_json, volc_event_handler_t* event_handler, void* user_data) {
|
||||
int ret = 0;
|
||||
volc_engine_impl_t* engine = NULL;
|
||||
cJSON* config = NULL;
|
||||
if (config_json == NULL || event_handler == NULL || handle == NULL) {
|
||||
LOGE("Configuration JSON is NULL");
|
||||
return VOLC_ERR_FAILED;
|
||||
}
|
||||
|
||||
engine = (volc_engine_impl_t*)volc_osal_malloc(sizeof(volc_engine_impl_t));
|
||||
if (engine == NULL) {
|
||||
LOGE("Failed to allocate memory for engine");
|
||||
return VOLC_ERR_FAILED;
|
||||
}
|
||||
memset(engine, 0, sizeof(volc_engine_impl_t));
|
||||
|
||||
engine->event_handler = *event_handler;
|
||||
engine->user_data = user_data;
|
||||
engine->mode = VOLC_MODE_UNKNOWN;
|
||||
|
||||
config = cJSON_Parse(config_json);
|
||||
|
||||
cJSON* iot_config = cJSON_GetObjectItem(config, "iot");
|
||||
if (iot_config) {
|
||||
ret = __config_iot_parse(iot_config, engine);
|
||||
if (ret != 0) {
|
||||
LOGE("Failed to parse IoT configuration");
|
||||
goto err_out_label;
|
||||
}
|
||||
if (engine->info.device_secret == NULL || strlen(engine->info.device_secret) < 16) {
|
||||
LOGE("Device Secret is missing or invalid");
|
||||
ret = VOLC_ERR_FAILED;
|
||||
goto err_out_label;
|
||||
}
|
||||
} else {
|
||||
LOGE("IoT configuration is NULL");
|
||||
goto err_out_label;
|
||||
}
|
||||
|
||||
cJSON* rtc_cfg = cJSON_GetObjectItem(config, "rtc");
|
||||
if (rtc_cfg) {
|
||||
#if defined(ENABLE_RTC_MODE)
|
||||
engine->rtc = volc_rtc_create(engine->info.rtc_app_id, engine, rtc_cfg, __realtime_user_event_router, __realtime_data_router);
|
||||
#else
|
||||
LOGW("RTC mode is not enabled");
|
||||
#endif
|
||||
} else {
|
||||
LOGI("RTC configuration is NULL");
|
||||
}
|
||||
|
||||
#if defined(ENABLE_WS_MODE)
|
||||
cJSON* ws_cfg = cJSON_GetObjectItem(config, "ws");
|
||||
engine->ws = volc_ws_create(engine, ws_cfg, __realtime_user_event_router, __realtime_data_router);
|
||||
#else
|
||||
LOGW("WS mode is not enabled");
|
||||
#endif
|
||||
|
||||
engine->status = VOLC_RT_STATE_CREATED;
|
||||
*handle = (volc_engine_t)engine;
|
||||
cJSON_Delete(config);
|
||||
LOGI("Engine created successfully at: %llu ms", volc_osal_get_time_ms());
|
||||
return 0;
|
||||
err_out_label:
|
||||
_iot_info_free(&engine->info);
|
||||
HAL_SAFE_FREE(engine);
|
||||
cJSON_Delete(config);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void volc_destroy(volc_engine_t handle) {
|
||||
volc_engine_impl_t* engine = (volc_engine_impl_t *)handle;
|
||||
if (NULL == engine) {
|
||||
return;
|
||||
}
|
||||
// if (engine->status == VOLC_RT_STATE_STARTED) {
|
||||
// volc_stop(handle);
|
||||
// }
|
||||
switch(engine->mode) {
|
||||
case VOLC_MODE_WS:
|
||||
#if defined(ENABLE_WS_MODE)
|
||||
volc_ws_destroy(engine->ws);
|
||||
#else
|
||||
LOGW("WS mode is not enabled");
|
||||
#endif
|
||||
break;
|
||||
case VOLC_MODE_RTC:
|
||||
#if defined(ENABLE_RTC_MODE)
|
||||
volc_rtc_destroy(engine->rtc);
|
||||
#else
|
||||
LOGW("RTC mode is not enabled");
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
_iot_info_free(&engine->info);
|
||||
HAL_SAFE_FREE(engine);
|
||||
}
|
||||
|
||||
int volc_start(volc_engine_t handle, volc_opt_t* opt) {
|
||||
int ret = 0;
|
||||
volc_engine_impl_t* engine = (volc_engine_impl_t*)handle;
|
||||
if (engine == NULL || opt == NULL) {
|
||||
LOGE("engine handle(%p) or bot id(%p) is NULL", handle, opt);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (NULL == opt->bot_id || strlen(opt->bot_id) <= 0) {
|
||||
LOGE("bot id is invalid");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (engine->status != VOLC_RT_STATE_CREATED && engine->status != VOLC_RT_STATE_STOPPED) {
|
||||
LOGE("engine is not in CREATED state");
|
||||
return -1;
|
||||
}
|
||||
|
||||
engine->mode = opt->mode;
|
||||
if (opt->mode == VOLC_MODE_WS) {
|
||||
#if defined(ENABLE_WS_MODE)
|
||||
ret = volc_ws_start(engine->ws, opt->bot_id, &engine->info, opt->params);
|
||||
#else
|
||||
LOGE("WS mode is not enabled");
|
||||
ret = -1;
|
||||
#endif
|
||||
} else if (opt->mode == VOLC_MODE_RTC) {
|
||||
#if defined(ENABLE_RTC_MODE)
|
||||
ret = volc_rtc_start(engine->rtc, opt->bot_id, &engine->info, opt->params);
|
||||
#else
|
||||
LOGE("RTC mode is not enabled");
|
||||
ret = -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (ret != 0) {
|
||||
engine->status = VOLC_RT_STATE_STOPPED;
|
||||
LOGE("engine start failed: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
engine->status = VOLC_RT_STATE_STARTED;
|
||||
LOGI("engine started successfully");
|
||||
return ret;
|
||||
}
|
||||
|
||||
int volc_stop(volc_engine_t handle) {
|
||||
int ret = 0;
|
||||
volc_engine_impl_t* engine = (volc_engine_impl_t*)handle;
|
||||
if (engine == NULL) {
|
||||
LOGE("engine handle is NULL");
|
||||
return -1;
|
||||
}
|
||||
if (engine->status != VOLC_RT_STATE_STARTED) {
|
||||
LOGW("engine is not in STARTED state");
|
||||
return -1;
|
||||
}
|
||||
|
||||
switch (engine->mode)
|
||||
{
|
||||
case VOLC_MODE_WS:
|
||||
#if defined(ENABLE_WS_MODE)
|
||||
ret = volc_ws_stop(engine->ws);
|
||||
#else
|
||||
LOGE("WS mode is not enabled");
|
||||
ret = -1;
|
||||
#endif
|
||||
break;
|
||||
case VOLC_MODE_RTC:
|
||||
#if defined(ENABLE_RTC_MODE)
|
||||
ret = volc_rtc_stop(engine->rtc);
|
||||
#else
|
||||
LOGE("RTC mode is not enabled");
|
||||
ret = -1;
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
engine->status = VOLC_RT_STATE_STOPPED;
|
||||
LOGI("engine stopped successfully");
|
||||
return ret;
|
||||
}
|
||||
|
||||
int volc_update(volc_engine_t handle, const void* data_ptr, size_t data_len) {
|
||||
int ret = 0;
|
||||
volc_message_info_t info = { 0 };
|
||||
volc_engine_impl_t* engine = (volc_engine_impl_t*)handle;
|
||||
if (engine == NULL) {
|
||||
LOGE("engine handle is NULL");
|
||||
return -1;
|
||||
}
|
||||
if (engine->status != VOLC_RT_STATE_STARTED) {
|
||||
LOGE("engine is not in STARTED state");
|
||||
return -1;
|
||||
}
|
||||
|
||||
info.is_binary = true;
|
||||
return volc_send_message(handle, data_ptr, data_len, &info);
|
||||
}
|
||||
|
||||
int volc_send_audio_data(volc_engine_t handle, const void* data_ptr, size_t data_len, volc_audio_frame_info_t* info_ptr) {
|
||||
int ret = 0;
|
||||
volc_data_info_t info = { 0 };
|
||||
volc_engine_impl_t* engine = (volc_engine_impl_t*)handle;
|
||||
if (engine == NULL || data_ptr == NULL || data_len == 0) {
|
||||
LOGE("engine %p, data_ptr %p, or data_len %zu is NULL or zero", engine, data_ptr, data_len);
|
||||
return -1;
|
||||
}
|
||||
if (engine->status != VOLC_RT_STATE_STARTED) {
|
||||
LOGE("engine is not in STARTED state");
|
||||
return -1;
|
||||
}
|
||||
|
||||
info.type = VOLC_DATA_TYPE_AUDIO;
|
||||
info.info.audio.data_type = info_ptr->data_type;
|
||||
info.info.audio.commit = info_ptr->commit;
|
||||
switch (engine->mode) {
|
||||
case VOLC_MODE_RTC:
|
||||
#if defined(ENABLE_RTC_MODE)
|
||||
ret = volc_rtc_send(engine->rtc, data_ptr, data_len, &info);
|
||||
#else
|
||||
LOGE("RTC mode is not enabled");
|
||||
ret = -1;
|
||||
#endif
|
||||
break;
|
||||
case VOLC_MODE_WS:
|
||||
#if defined(ENABLE_WS_MODE)
|
||||
ret = volc_ws_send(engine->ws, data_ptr, data_len, &info);
|
||||
#else
|
||||
LOGE("WS mode is not enabled");
|
||||
ret = -1;
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
__record_media_bytes(&engine->audio_send_stats, data_len, "audio send");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int volc_send_video_data(volc_engine_t handle, const void* data_ptr, size_t data_len, volc_video_frame_info_t* info_ptr) {
|
||||
int ret = 0;
|
||||
volc_data_info_t info = { 0 };
|
||||
volc_engine_impl_t* engine = (volc_engine_impl_t*)handle;
|
||||
if (engine == NULL || data_ptr == NULL || data_len == 0) {
|
||||
LOGE("engine %p, data_ptr %p, or data_len %zu is NULL or zero", engine, data_ptr, data_len);
|
||||
return -1;
|
||||
}
|
||||
if (engine->status != VOLC_RT_STATE_STARTED) {
|
||||
LOGE("engine is not in STARTED state");
|
||||
return -1;
|
||||
}
|
||||
|
||||
info.type = VOLC_DATA_TYPE_VIDEO;
|
||||
info.info.video.data_type = info_ptr->data_type;
|
||||
switch (engine->mode) {
|
||||
case VOLC_MODE_RTC:
|
||||
#if defined(ENABLE_RTC_MODE)
|
||||
ret = volc_rtc_send(engine->rtc, data_ptr, data_len, &info);
|
||||
#else
|
||||
LOGE("RTC mode is not enabled");
|
||||
ret = -1;
|
||||
#endif
|
||||
break;
|
||||
case VOLC_MODE_WS:
|
||||
#if defined(ENABLE_WS_MODE)
|
||||
ret = volc_ws_send(engine->ws, data_ptr, data_len, &info);
|
||||
#else
|
||||
LOGE("WS mode is not enabled");
|
||||
ret = -1;
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int volc_send_message(volc_engine_t handle, const void* data_ptr, size_t data_len, volc_message_info_t* info_ptr) {
|
||||
int ret = 0;
|
||||
volc_data_info_t info = { 0 };
|
||||
if (handle == NULL || data_ptr == NULL || data_len == 0) {
|
||||
LOGE("engine handle %p, data_ptr %p, or data_len %zu is NULL or zero", handle, data_ptr, data_len);
|
||||
return -1;
|
||||
}
|
||||
|
||||
volc_engine_impl_t* engine = (volc_engine_impl_t*)handle;
|
||||
if (engine->status != VOLC_RT_STATE_STARTED) {
|
||||
LOGE("engine is not in started state");
|
||||
return -1;
|
||||
}
|
||||
|
||||
info.type = VOLC_DATA_TYPE_MESSAGE;
|
||||
info.info.message.is_binary = info_ptr ? info_ptr->is_binary : false;
|
||||
switch (engine->mode) {
|
||||
case VOLC_MODE_RTC:
|
||||
#if defined(ENABLE_RTC_MODE)
|
||||
ret = volc_rtc_send(engine->rtc, data_ptr, data_len, &info);
|
||||
#else
|
||||
LOGE("RTC mode is not enabled");
|
||||
ret = -1;
|
||||
#endif
|
||||
break;
|
||||
case VOLC_MODE_WS:
|
||||
#if defined(ENABLE_WS_MODE)
|
||||
ret = volc_ws_send(engine->ws, data_ptr, data_len, &info);
|
||||
#else
|
||||
LOGE("WS mode is not enabled");
|
||||
ret = -1;
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int volc_send_text_to_agent(volc_engine_t handle, const char* text, volc_agent_type_e type, int interrupt_mode) {
|
||||
int ret = 0;
|
||||
volc_engine_impl_t* engine = (volc_engine_impl_t*)handle;
|
||||
if (handle == NULL) {
|
||||
LOGE("engine handle is NULL");
|
||||
return -1;
|
||||
}
|
||||
LOGD("interrupt: %d", engine->mode);
|
||||
switch (engine->mode) {
|
||||
case VOLC_MODE_RTC:
|
||||
#if defined(ENABLE_RTC_MODE)
|
||||
ret = volc_rtc_send_text_to_agent(engine->rtc,text,type,interrupt_mode);
|
||||
#else
|
||||
LOGE("RTC mode is not enabled");
|
||||
ret = -1;
|
||||
#endif
|
||||
break;
|
||||
case VOLC_MODE_WS:
|
||||
#if defined(ENABLE_WS_MODE)
|
||||
ret = -2;
|
||||
LOGE("WS mode is not support send_text");
|
||||
#else
|
||||
LOGE("WS mode is not enabled");
|
||||
ret = -1;
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int volc_interrupt(volc_engine_t handle) {
|
||||
int ret = 0;
|
||||
volc_engine_impl_t* engine = (volc_engine_impl_t*)handle;
|
||||
if (handle == NULL) {
|
||||
LOGE("engine handle is NULL");
|
||||
return -1;
|
||||
}
|
||||
LOGI("interrupt: %d, at: %" PRIu64"ms", engine->mode, volc_osal_get_time_ms());
|
||||
switch (engine->mode) {
|
||||
case VOLC_MODE_RTC:
|
||||
#if defined(ENABLE_RTC_MODE)
|
||||
ret = volc_rtc_interrupt(engine->rtc);
|
||||
#else
|
||||
LOGE("RTC mode is not enabled");
|
||||
ret = -1;
|
||||
#endif
|
||||
break;
|
||||
case VOLC_MODE_WS:
|
||||
#if defined(ENABLE_WS_MODE)
|
||||
ret = volc_ws_interrupt(engine->ws);
|
||||
#else
|
||||
LOGE("WS mode is not enabled");
|
||||
ret = -1;
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
Reference in New Issue
Block a user