feat: expand companion platform, device controls, and memoir workflows
ci / verify (push) Canceled after 0s
ci / verify (push) Canceled after 0s
Add customer and operator login, BLE provisioning, device fleet controls, call memory, memoir generation, and refreshed family and maintenance portals. Include API contracts, migrations, firmware sources, tests, assets, and deployment documentation.
This commit is contained in:
@@ -36,7 +36,7 @@ endif()
|
||||
idf_component_register(
|
||||
SRCS ${VOLC_SRCS}
|
||||
INCLUDE_DIRS ${VOLC_INCS}
|
||||
REQUIRES mbedtls json lwip esp_netif
|
||||
REQUIRES mbedtls json lwip esp_netif esp_audio_codec
|
||||
)
|
||||
|
||||
if(CONFIG_VOLC_RTC_MODE)
|
||||
|
||||
+16
-9
@@ -98,7 +98,7 @@ int volc_device_register(volc_iot_info_t* info, char** output)
|
||||
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);
|
||||
LOGD("device registration request prepared");
|
||||
|
||||
char* response = volc_http_post(url, json_str, strlen(json_str));
|
||||
if (response == NULL) {
|
||||
@@ -140,8 +140,8 @@ int volc_device_register(volc_iot_info_t* info, char** output)
|
||||
err_out_label:
|
||||
HAL_SAFE_FREE(payload);
|
||||
HAL_SAFE_FREE(response);
|
||||
HAL_SAFE_FREE(root);
|
||||
HAL_SAFE_FREE(response_json);
|
||||
cJSON_Delete(root);
|
||||
cJSON_Delete(response_json);
|
||||
HAL_SAFE_FREE(signature);
|
||||
HAL_SAFE_FREE(json_str);
|
||||
return ret;
|
||||
@@ -170,17 +170,19 @@ int volc_get_rtc_config(volc_iot_info_t* info, int audio_codec, const char* bot_
|
||||
if(params){
|
||||
cJSON* params_config = cJSON_Parse(params);
|
||||
|
||||
cJSON* agent_config = cJSON_CreateObject();
|
||||
cJSON* agent_config = NULL;
|
||||
volc_json_read_object(params_config,"AgentConfig",&agent_config);
|
||||
cJSON_AddItemToObject(root, "agent_config", agent_config);
|
||||
if (agent_config) cJSON_AddItemToObject(root, "agent_config", agent_config);
|
||||
|
||||
cJSON* config = cJSON_CreateObject();
|
||||
cJSON* config = NULL;
|
||||
volc_json_read_object(params_config,"Config",&config);
|
||||
cJSON_AddItemToObject(root, "config", config);
|
||||
// Omit Config when using the elder's already-configured bot defaults.
|
||||
if (config) cJSON_AddItemToObject(root, "config", config);
|
||||
cJSON_Delete(params_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);
|
||||
LOGI("RTC configuration request prepared");
|
||||
char* response = volc_http_post(url, json_str, strlen(json_str));
|
||||
if (response == NULL) {
|
||||
LOGE("Failed to get response from server");
|
||||
@@ -193,7 +195,12 @@ int volc_get_rtc_config(volc_iot_info_t* info, int audio_codec, const char* bot_
|
||||
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);
|
||||
char *provider_code = NULL;
|
||||
volc_json_read_string(response_json, "ResponseMetadata.Error.Code", &provider_code);
|
||||
LOGE("RTC config rejected code=%s fields room=%d user=%d token=%d task=%d", provider_code ? provider_code : "NO_ERROR_CODE",
|
||||
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);
|
||||
HAL_SAFE_FREE(provider_code);
|
||||
ret = -1;
|
||||
goto err_out_label;
|
||||
}
|
||||
|
||||
+1
@@ -26,6 +26,7 @@ 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);
|
||||
const char *volc_rtc_user_id(volc_rtc_t rtc,bool remote);
|
||||
|
||||
int volc_rtc_send(volc_rtc_t rtc, const void* data, int size, volc_data_info_t* data_info);
|
||||
|
||||
|
||||
+573
-36
@@ -6,6 +6,30 @@
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdatomic.h>
|
||||
#ifdef ESP_PLATFORM
|
||||
#include "esp_opus_enc.h"
|
||||
#include "esp_opus_dec.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_timer.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/idf_additions.h"
|
||||
|
||||
/* Own complete transport packets: SDK callback memory is borrowed. Storage and
|
||||
* the decoder task stack use PSRAM; only small RTOS control objects use DRAM. */
|
||||
#define RTC_RX_PACKET_MAX 4096
|
||||
#define RTC_RX_PACKET_COUNT 48
|
||||
#define RTC_RX_ROOM_MAX 256
|
||||
typedef struct {
|
||||
uint32_t generation;
|
||||
uint32_t arrival_ms;
|
||||
size_t size;
|
||||
uint8_t data[RTC_RX_PACKET_MAX];
|
||||
} rtc_rx_packet_t;
|
||||
#endif
|
||||
|
||||
#include "volc_osal.h"
|
||||
#include "util/volc_list.h"
|
||||
@@ -18,12 +42,17 @@
|
||||
#define MAGIC_OFFSET 8
|
||||
const char* interrupt_str = "{\"Command\":\"interrupt\"}";
|
||||
|
||||
// Optional platform control hook. Audio remains on the RTC transport. Invoke
|
||||
// before joining/publishing so the cloud recorder can capture the opening turn.
|
||||
extern int volc_platform_recording_prepare(const char *app, const char *room, const char *user) __attribute__((weak));
|
||||
|
||||
typedef struct {
|
||||
bool b_pipeline_started;
|
||||
bool b_user_joined;
|
||||
bool b_channel_joined;
|
||||
bool b_first_keyframe_received;
|
||||
bool b_fini;
|
||||
atomic_bool b_fini;
|
||||
atomic_bool closing;
|
||||
bool b_audio_publish;
|
||||
bool b_video_publish;
|
||||
bool b_audio_subscribe;
|
||||
@@ -40,8 +69,46 @@ typedef struct {
|
||||
volc_data_cb data_callback;
|
||||
byte_rtc_engine_t rtc;
|
||||
byte_rtc_event_handler_t event_handler;
|
||||
uint8_t pcm_frame[640]; // 20 ms, 16 kHz, mono PCM16
|
||||
size_t pcm_used;
|
||||
uint64_t rx_report_ms, rx_previous_ms;
|
||||
unsigned rx_frames, rx_pcm_bytes, rx_gap_ms, rx_decode_ms, rx_errors;
|
||||
#ifdef ESP_PLATFORM
|
||||
void *opus_encoder;
|
||||
void *opus_decoder;
|
||||
uint8_t *opus_packet;
|
||||
int opus_packet_size;
|
||||
uint8_t decoded_pcm[3840]; // Maximum 120 ms at 16 kHz, mono PCM16.
|
||||
rtc_rx_packet_t *rx_packets;
|
||||
QueueHandle_t rx_free, rx_pending;
|
||||
SemaphoreHandle_t rx_work_lock, rx_worker_stopped;
|
||||
TaskHandle_t rx_worker;
|
||||
atomic_bool rx_accepting, rx_shutdown;
|
||||
atomic_uint rx_generation, rx_producers;
|
||||
atomic_uint rx_callbacks, rx_callback_us, rx_callback_max_us;
|
||||
atomic_uint rx_free_max_us, rx_copy_max_us, rx_send_max_us, rx_post_max_us;
|
||||
atomic_uint rx_overflow, rx_oversized, rx_stale, rx_peak;
|
||||
char rx_room[RTC_RX_ROOM_MAX];
|
||||
uint32_t rx_decoder_generation;
|
||||
unsigned rx_applied_generation;
|
||||
unsigned rx_decode_total_us;
|
||||
#endif
|
||||
} rtc_impl_t;
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
static int __rtc_rx_init(rtc_impl_t *rtc);
|
||||
static int __rtc_rx_start(rtc_impl_t *rtc, const char *room);
|
||||
static void __rtc_rx_stop(rtc_impl_t *rtc);
|
||||
static void __rtc_rx_destroy(rtc_impl_t *rtc);
|
||||
#endif
|
||||
|
||||
static void __clear_room_info(rtc_impl_t *rtc) {
|
||||
HAL_SAFE_FREE(rtc->info.rtc_opt.p_channel_name);
|
||||
HAL_SAFE_FREE(rtc->info.rtc_opt.p_uid);
|
||||
HAL_SAFE_FREE(rtc->info.rtc_opt.p_token);
|
||||
HAL_SAFE_FREE(rtc->info.task_id);
|
||||
}
|
||||
|
||||
static bool __is_first_keyframe_not_received(rtc_impl_t* rtc, int is_key_frame)
|
||||
{
|
||||
return (!rtc->b_first_keyframe_received && !is_key_frame);
|
||||
@@ -80,7 +147,7 @@ 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) {
|
||||
if (!rtc || !option || !option->p_channel_name || !option->p_uid || rtc->b_pipeline_started) {
|
||||
LOGE("rtc or option is NULL");
|
||||
return -1;
|
||||
}
|
||||
@@ -89,43 +156,74 @@ static int __rtc_start(rtc_impl_t* rtc, volc_rtc_option_t* option)
|
||||
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);
|
||||
#ifndef ESP_PLATFORM
|
||||
rtc->rx_report_ms=rtc->rx_previous_ms=0;
|
||||
rtc->rx_frames=rtc->rx_pcm_bytes=rtc->rx_gap_ms=rtc->rx_decode_ms=rtc->rx_errors=0;
|
||||
#endif
|
||||
rtc->p_channel_name = strdup(option->p_channel_name);
|
||||
rtc->p_user_id = strdup(option->p_uid);
|
||||
if (!rtc->p_channel_name || !rtc->p_user_id) {
|
||||
HAL_SAFE_FREE(rtc->p_channel_name);
|
||||
HAL_SAFE_FREE(rtc->p_user_id);
|
||||
return -1;
|
||||
}
|
||||
#ifdef ESP_PLATFORM
|
||||
if (rtc->opus_encoder && __rtc_rx_start(rtc, option->p_channel_name) != 0) {
|
||||
HAL_SAFE_FREE(rtc->p_channel_name);
|
||||
HAL_SAFE_FREE(rtc->p_user_id);
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
rtc->b_pipeline_started = true;
|
||||
LOGI("Joining RTC channel: vpub=%d vsub=%d apub=%d asub=%d", (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);
|
||||
#ifdef ESP_PLATFORM
|
||||
__rtc_rx_stop(rtc);
|
||||
#endif
|
||||
rtc->b_pipeline_started = rtc->b_channel_joined = rtc->b_user_joined = false;
|
||||
HAL_SAFE_FREE(rtc->p_channel_name);
|
||||
HAL_SAFE_FREE(rtc->p_user_id);
|
||||
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)
|
||||
static int __rtc_stop(rtc_impl_t* rtc)
|
||||
{
|
||||
if (!rtc) {
|
||||
LOGE("rtc instance is NULL");
|
||||
return;
|
||||
return -1;
|
||||
}
|
||||
#ifdef ESP_PLATFORM
|
||||
/* Close delivery even when leave_room subsequently fails. */
|
||||
__rtc_rx_stop(rtc);
|
||||
#endif
|
||||
if (!rtc->b_pipeline_started) {
|
||||
LOGI("pipeline not started");
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ret = byte_rtc_leave_room(rtc->rtc, rtc->p_channel_name);
|
||||
if (ret != 0) {
|
||||
LOGE("Failed to leave room: %d", ret);
|
||||
return;
|
||||
return ret;
|
||||
}
|
||||
rtc->b_pipeline_started = false;
|
||||
rtc->pcm_used = 0;
|
||||
rtc->b_channel_joined = false;
|
||||
rtc->b_user_joined = false;
|
||||
HAL_SAFE_FREE(rtc->p_channel_name);
|
||||
HAL_SAFE_FREE(rtc->p_user_id);
|
||||
HAL_SAFE_FREE(rtc->p_remote_user_id);
|
||||
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!atomic_load(&rtc->closing) && rtc->data_callback) {
|
||||
rtc->data_callback(rtc->context, data, data_len, info);
|
||||
}
|
||||
}
|
||||
@@ -137,7 +235,7 @@ static void _register_message_router(rtc_impl_t* rtc, volc_msg_cb callback)
|
||||
|
||||
static void _send_message_2_user(rtc_impl_t* rtc, volc_msg_t* msg)
|
||||
{
|
||||
if (rtc->message_callback) {
|
||||
if (!atomic_load(&rtc->closing) && rtc->message_callback) {
|
||||
rtc->message_callback(rtc->context, msg);
|
||||
}
|
||||
}
|
||||
@@ -164,7 +262,7 @@ static int _on_conversion_status_message_parsed(uint8_t* message, rtc_impl_t* rt
|
||||
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);
|
||||
LOGW("RTC error detail withheld");
|
||||
if(strcmp(error_reason, "preParamCheck:AI license duration quota exceeded") == 0) {
|
||||
msg.code = VOLC_MSG_QUOTA_EXCEEDED;
|
||||
msg.data.conv_status = 0;
|
||||
@@ -177,8 +275,52 @@ static int _on_conversion_status_message_parsed(uint8_t* message, rtc_impl_t* rt
|
||||
return c;
|
||||
}
|
||||
|
||||
/* RTC_SELF_PRIORITY_BEGIN: the SDK must not yield to its priority-5 decoder
|
||||
* while publishing a packet. Apply a base-priority floor in the SDK's own
|
||||
* callbacks, without borrowing task handles or lowering a higher base. */
|
||||
#ifdef ESP_PLATFORM
|
||||
#define RTC_TASK_PRIORITY_FLOOR 6
|
||||
static atomic_bool diagnostic_rtc_seen;
|
||||
static atomic_uint diagnostic_rtc_effective_priority, diagnostic_rtc_base, diagnostic_rtc_observed_ms;
|
||||
static atomic_int diagnostic_rtc_core;
|
||||
|
||||
void volc_rtc_diagnostic_snapshot(void)
|
||||
{
|
||||
bool seen = atomic_load(&diagnostic_rtc_seen);
|
||||
unsigned base = atomic_load(&diagnostic_rtc_base);
|
||||
LOGI("RTC_TASK_SNAPSHOT seen=%d priority=%u base=%u core=%d floor=%u observed_age_ms=%u floor_met=%d",
|
||||
seen, atomic_load(&diagnostic_rtc_effective_priority), base, atomic_load(&diagnostic_rtc_core), RTC_TASK_PRIORITY_FLOOR,
|
||||
seen ? (unsigned)((uint32_t)(esp_timer_get_time()/1000) - atomic_load(&diagnostic_rtc_observed_ms)) : 0,
|
||||
seen && base >= RTC_TASK_PRIORITY_FLOOR);
|
||||
}
|
||||
|
||||
static void __rtc_diagnostic_priority_apply(void)
|
||||
{
|
||||
if (strcmp(pcTaskGetName(NULL), "VolcRTCMain")) return;
|
||||
TaskStatus_t status;
|
||||
vTaskGetInfo(NULL, &status, pdFALSE, eInvalid);
|
||||
UBaseType_t before = uxTaskPriorityGet(NULL);
|
||||
UBaseType_t desired = status.uxBasePriority < RTC_TASK_PRIORITY_FLOOR ? RTC_TASK_PRIORITY_FLOOR : status.uxBasePriority;
|
||||
if (status.uxBasePriority != desired) vTaskPrioritySet(NULL, desired);
|
||||
if (!atomic_load(&diagnostic_rtc_seen) || status.uxBasePriority != desired) {
|
||||
LOGI("RTC_PRIORITY_FLOOR before=%u base_before=%u after=%u base_after=%u floor=%u core=%d",
|
||||
(unsigned)before, (unsigned)status.uxBasePriority, (unsigned)uxTaskPriorityGet(NULL), (unsigned)desired, RTC_TASK_PRIORITY_FLOOR,
|
||||
(int)xTaskGetCoreID(NULL));
|
||||
}
|
||||
atomic_store(&diagnostic_rtc_effective_priority, uxTaskPriorityGet(NULL));
|
||||
atomic_store(&diagnostic_rtc_base, desired);
|
||||
atomic_store(&diagnostic_rtc_core, (int)xTaskGetCoreID(NULL));
|
||||
atomic_store(&diagnostic_rtc_observed_ms, (uint32_t)(esp_timer_get_time()/1000));
|
||||
atomic_store(&diagnostic_rtc_seen, true);
|
||||
}
|
||||
#else
|
||||
static void __rtc_diagnostic_priority_apply(void) {}
|
||||
#endif
|
||||
/* RTC_SELF_PRIORITY_END */
|
||||
|
||||
static void _on_join_channel_success(byte_rtc_engine_t engine, const char* channel, int elapsed_ms, bool rejoin)
|
||||
{
|
||||
__rtc_diagnostic_priority_apply();
|
||||
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);
|
||||
@@ -196,8 +338,9 @@ static void _on_user_joined(byte_rtc_engine_t engine, const char* channel, const
|
||||
volc_msg_t msg = {0};
|
||||
LOGI("remote user joined %s:%s elapsed %d ms\n", channel, user_name, elapsed_ms);
|
||||
|
||||
rtc->b_user_joined = true;
|
||||
HAL_SAFE_FREE(rtc->p_remote_user_id);
|
||||
rtc->p_remote_user_id = strdup(user_name);
|
||||
rtc->b_user_joined = rtc->p_remote_user_id != NULL;
|
||||
|
||||
msg.code = VOLC_MSG_USER_JOINED;
|
||||
_send_message_2_user(rtc, &msg);
|
||||
@@ -205,6 +348,7 @@ static void _on_user_joined(byte_rtc_engine_t engine, const char* channel, const
|
||||
|
||||
static void _on_user_offline(byte_rtc_engine_t engine, const char* channel, const char* user_name, int reason)
|
||||
{
|
||||
__rtc_diagnostic_priority_apply();
|
||||
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);
|
||||
@@ -224,17 +368,296 @@ static void _on_user_mute_video(byte_rtc_engine_t engine, const char* channel, c
|
||||
LOGD("remote user mute video %s:%s %d\n", channel, user_name, muted);
|
||||
};
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
/* RTC_RX_HOST_BEGIN: host tests extract these production functions verbatim. */
|
||||
static void __rtc_rx_max(atomic_uint *value, unsigned candidate)
|
||||
{
|
||||
unsigned old = atomic_load(value);
|
||||
while (old < candidate && !atomic_compare_exchange_weak(value, &old, candidate)) {}
|
||||
}
|
||||
|
||||
static bool __rtc_rx_current(rtc_impl_t *rtc, unsigned generation)
|
||||
{
|
||||
return atomic_load(&rtc->rx_accepting) && generation == atomic_load(&rtc->rx_generation);
|
||||
}
|
||||
|
||||
/* No decoder, allocator, logging, or waiting on the SDK's synchronous thread. */
|
||||
static void __rtc_rx_enqueue(rtc_impl_t *rtc, const char *room, const void *data, size_t size)
|
||||
{
|
||||
uint64_t started = esp_timer_get_time();
|
||||
atomic_fetch_add(&rtc->rx_producers, 1);
|
||||
unsigned generation = atomic_load(&rtc->rx_generation);
|
||||
if (!atomic_load(&rtc->rx_accepting) || !room || strcmp(room, rtc->rx_room)) {
|
||||
atomic_fetch_add(&rtc->rx_stale, 1);
|
||||
goto done;
|
||||
}
|
||||
if (!data || !size || size > RTC_RX_PACKET_MAX) {
|
||||
atomic_fetch_add(&rtc->rx_oversized, 1);
|
||||
goto done;
|
||||
}
|
||||
unsigned slot;
|
||||
uint64_t stage = esp_timer_get_time();
|
||||
BaseType_t acquired = xQueueReceive(rtc->rx_free, &slot, 0);
|
||||
__rtc_rx_max(&rtc->rx_free_max_us, (unsigned)(esp_timer_get_time() - stage));
|
||||
if (acquired != pdTRUE) {
|
||||
atomic_fetch_add(&rtc->rx_overflow, 1);
|
||||
goto done;
|
||||
}
|
||||
stage = esp_timer_get_time();
|
||||
rtc_rx_packet_t *packet = &rtc->rx_packets[slot];
|
||||
packet->generation = generation;
|
||||
packet->arrival_ms = (uint32_t)(started / 1000);
|
||||
packet->size = size;
|
||||
memcpy(packet->data, data, size);
|
||||
__rtc_rx_max(&rtc->rx_copy_max_us, (unsigned)(esp_timer_get_time() - stage));
|
||||
if (!__rtc_rx_current(rtc, generation)) {
|
||||
atomic_fetch_add(&rtc->rx_stale, 1);
|
||||
xQueueSend(rtc->rx_free, &slot, 0);
|
||||
} else {
|
||||
stage = esp_timer_get_time();
|
||||
BaseType_t sent = xQueueSend(rtc->rx_pending, &slot, 0);
|
||||
__rtc_rx_max(&rtc->rx_send_max_us, (unsigned)(esp_timer_get_time() - stage));
|
||||
stage = esp_timer_get_time();
|
||||
if (sent != pdTRUE) {
|
||||
atomic_fetch_add(&rtc->rx_overflow, 1);
|
||||
xQueueSend(rtc->rx_free, &slot, 0);
|
||||
} else {
|
||||
__rtc_rx_max(&rtc->rx_peak, (unsigned)uxQueueMessagesWaiting(rtc->rx_pending));
|
||||
}
|
||||
__rtc_rx_max(&rtc->rx_post_max_us, (unsigned)(esp_timer_get_time() - stage));
|
||||
}
|
||||
done: ;
|
||||
unsigned elapsed = (unsigned)(esp_timer_get_time() - started);
|
||||
atomic_fetch_add(&rtc->rx_callbacks, 1);
|
||||
atomic_fetch_add(&rtc->rx_callback_us, elapsed);
|
||||
__rtc_rx_max(&rtc->rx_callback_max_us, elapsed);
|
||||
atomic_fetch_sub(&rtc->rx_producers, 1);
|
||||
}
|
||||
|
||||
/* The worker serializes the service's PCM discard with its own delivery. A
|
||||
* generation check alone has a check/emit race if the worker is preempted. */
|
||||
static void __rtc_rx_interrupt(rtc_impl_t *rtc)
|
||||
{
|
||||
atomic_fetch_add(&rtc->rx_generation, 1);
|
||||
}
|
||||
|
||||
/* Only the worker calls this while holding rx_work_lock. */
|
||||
static void __rtc_rx_apply_interrupt(rtc_impl_t *rtc)
|
||||
{
|
||||
unsigned generation = atomic_load(&rtc->rx_generation);
|
||||
if (rtc->rx_applied_generation == generation) return;
|
||||
rtc->rx_applied_generation = generation;
|
||||
if (atomic_load(&rtc->rx_accepting)) {
|
||||
volc_msg_t msg = {.code=VOLC_MSG_CONV_STATUS};
|
||||
msg.data.conv_status = VOLC_CONV_STATUS_INTERRUPTED;
|
||||
_send_message_2_user(rtc, &msg);
|
||||
}
|
||||
}
|
||||
|
||||
/* A transport callback may contain more than one encoded frame. The decoder
|
||||
* reports consumed input separately from decoded PCM; do not discard its tail. */
|
||||
static int __rtc_decode_opus(rtc_impl_t *rtc, const void *data, size_t length, volc_data_info_t *info, unsigned generation)
|
||||
{
|
||||
esp_audio_dec_in_raw_t raw = {.buffer=(uint8_t *)data, .len=length};
|
||||
int total=0;
|
||||
while(raw.len && __rtc_rx_current(rtc, generation)) {
|
||||
esp_audio_dec_out_frame_t frame={.buffer=rtc->decoded_pcm,.len=sizeof(rtc->decoded_pcm)};
|
||||
esp_audio_dec_info_t decoded_info={0};
|
||||
raw.consumed=0;
|
||||
int ret=esp_opus_dec_decode(rtc->opus_decoder,&raw,&frame,&decoded_info);
|
||||
if(ret!=ESP_AUDIO_ERR_OK || !raw.consumed || raw.consumed>raw.len || frame.decoded_size>sizeof(rtc->decoded_pcm)) {
|
||||
rtc->rx_errors++;
|
||||
LOGE("RTC Opus decode failed code=%d remaining=%u consumed=%u",ret,(unsigned)raw.len,(unsigned)raw.consumed);
|
||||
return total;
|
||||
}
|
||||
if(frame.decoded_size && __rtc_rx_current(rtc, generation)) {
|
||||
info->info.audio.data_type=VOLC_AUDIO_DATA_TYPE_PCM;
|
||||
__send_data_2_user(rtc,frame.buffer,frame.decoded_size,info);
|
||||
total+=frame.decoded_size;
|
||||
}
|
||||
raw.buffer+=raw.consumed;
|
||||
raw.len-=raw.consumed;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
static int __rtc_rx_decoder_reset(rtc_impl_t *rtc, unsigned generation)
|
||||
{
|
||||
if (rtc->opus_decoder) esp_opus_dec_close(rtc->opus_decoder);
|
||||
rtc->opus_decoder = NULL;
|
||||
esp_opus_dec_cfg_t config = ESP_OPUS_DEC_CONFIG_DEFAULT();
|
||||
config.sample_rate = 16000;
|
||||
config.channel = 1;
|
||||
config.frame_duration = ESP_OPUS_DEC_FRAME_DURATION_120_MS;
|
||||
if (esp_opus_dec_open(&config, sizeof(config), &rtc->opus_decoder) != ESP_AUDIO_ERR_OK) {
|
||||
rtc->rx_errors++;
|
||||
return -1;
|
||||
}
|
||||
rtc->rx_decoder_generation = generation;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __rtc_rx_report(rtc_impl_t *rtc)
|
||||
{
|
||||
uint64_t now = volc_osal_get_time_ms();
|
||||
if (!rtc->rx_report_ms) rtc->rx_report_ms = now;
|
||||
if (now - rtc->rx_report_ms < 1000) return;
|
||||
LOGI("DOWNLINK span_ms=%u frames=%u pcm_ms=%u max_gap_ms=%u max_decode_ms=%u errors=%u",
|
||||
(unsigned)(now-rtc->rx_report_ms), rtc->rx_frames, rtc->rx_pcm_bytes/32,
|
||||
rtc->rx_gap_ms, rtc->rx_decode_ms, rtc->rx_errors);
|
||||
LOGI("RTC_RX callbacks=%u callback_total_us=%u callback_max_us=%u decode_total_us=%u queued=%u peak=%u overflow=%u oversized=%u stale=%u",
|
||||
atomic_exchange(&rtc->rx_callbacks,0), atomic_exchange(&rtc->rx_callback_us,0),
|
||||
atomic_exchange(&rtc->rx_callback_max_us,0), rtc->rx_decode_total_us,
|
||||
(unsigned)uxQueueMessagesWaiting(rtc->rx_pending), atomic_exchange(&rtc->rx_peak,0),
|
||||
atomic_exchange(&rtc->rx_overflow,0), atomic_exchange(&rtc->rx_oversized,0), atomic_exchange(&rtc->rx_stale,0));
|
||||
/* Wall time includes preemption after a queue wakes another task. These are
|
||||
* per-stage maxima, not CPU costs or additive parts of the same callback. */
|
||||
LOGI("RTC_RX_STAGE free_max_us=%u copy_max_us=%u send_max_us=%u post_max_us=%u worker_priority=%u worker_core=%d",
|
||||
atomic_exchange(&rtc->rx_free_max_us,0), atomic_exchange(&rtc->rx_copy_max_us,0),
|
||||
atomic_exchange(&rtc->rx_send_max_us,0), atomic_exchange(&rtc->rx_post_max_us,0),
|
||||
(unsigned)uxTaskPriorityGet(NULL), (int)xTaskGetCoreID(NULL));
|
||||
rtc->rx_report_ms = now;
|
||||
rtc->rx_frames=rtc->rx_pcm_bytes=rtc->rx_gap_ms=rtc->rx_decode_ms=rtc->rx_errors=rtc->rx_decode_total_us=0;
|
||||
}
|
||||
|
||||
static void __rtc_rx_process(rtc_impl_t *rtc, unsigned slot)
|
||||
{
|
||||
rtc_rx_packet_t *packet = &rtc->rx_packets[slot];
|
||||
xSemaphoreTake(rtc->rx_work_lock, portMAX_DELAY);
|
||||
__rtc_rx_apply_interrupt(rtc);
|
||||
if (__rtc_rx_current(rtc, packet->generation)) {
|
||||
unsigned gap = rtc->rx_previous_ms ? (uint32_t)(packet->arrival_ms - (uint32_t)rtc->rx_previous_ms) : 0;
|
||||
rtc->rx_previous_ms = packet->arrival_ms;
|
||||
if (gap > rtc->rx_gap_ms) rtc->rx_gap_ms = gap;
|
||||
rtc->rx_frames++;
|
||||
uint64_t started = esp_timer_get_time();
|
||||
if ((rtc->opus_decoder && rtc->rx_decoder_generation == packet->generation) ||
|
||||
__rtc_rx_decoder_reset(rtc, packet->generation) == 0) {
|
||||
volc_data_info_t info = {.type=VOLC_DATA_TYPE_AUDIO};
|
||||
rtc->rx_pcm_bytes += __rtc_decode_opus(rtc, packet->data, packet->size, &info, packet->generation);
|
||||
}
|
||||
unsigned elapsed = (unsigned)(esp_timer_get_time()-started);
|
||||
rtc->rx_decode_total_us += elapsed;
|
||||
if (elapsed/1000 > rtc->rx_decode_ms) rtc->rx_decode_ms = elapsed/1000;
|
||||
} else {
|
||||
atomic_fetch_add(&rtc->rx_stale, 1);
|
||||
}
|
||||
__rtc_rx_report(rtc);
|
||||
xSemaphoreGive(rtc->rx_work_lock);
|
||||
xQueueSend(rtc->rx_free, &slot, 0);
|
||||
}
|
||||
|
||||
static void __rtc_rx_task(void *arg)
|
||||
{
|
||||
rtc_impl_t *rtc = arg;
|
||||
while (!atomic_load(&rtc->rx_shutdown)) {
|
||||
xSemaphoreTake(rtc->rx_work_lock, portMAX_DELAY);
|
||||
__rtc_rx_apply_interrupt(rtc);
|
||||
xSemaphoreGive(rtc->rx_work_lock);
|
||||
unsigned slot;
|
||||
if (xQueueReceive(rtc->rx_pending, &slot, pdMS_TO_TICKS(20)) == pdTRUE) {
|
||||
__rtc_rx_process(rtc, slot);
|
||||
}
|
||||
}
|
||||
xSemaphoreGive(rtc->rx_worker_stopped);
|
||||
/* Control deletes this task with its original WithCaps handle. Self-delete
|
||||
* would allocate an IDF cleanup task, unsafe when internal RAM is scarce. */
|
||||
for (;;) vTaskSuspend(NULL);
|
||||
}
|
||||
|
||||
static int __rtc_rx_init(rtc_impl_t *rtc)
|
||||
{
|
||||
rtc->rx_packets = heap_caps_calloc(RTC_RX_PACKET_COUNT, sizeof(*rtc->rx_packets), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
||||
rtc->rx_free = xQueueCreate(RTC_RX_PACKET_COUNT, sizeof(unsigned));
|
||||
rtc->rx_pending = xQueueCreate(RTC_RX_PACKET_COUNT, sizeof(unsigned));
|
||||
rtc->rx_work_lock = xSemaphoreCreateMutex();
|
||||
rtc->rx_worker_stopped = xSemaphoreCreateBinary();
|
||||
if (!rtc->rx_packets || !rtc->rx_free || !rtc->rx_pending || !rtc->rx_work_lock || !rtc->rx_worker_stopped) return -1;
|
||||
for (unsigned i=0; i<RTC_RX_PACKET_COUNT; i++) xQueueSend(rtc->rx_free, &i, 0);
|
||||
if (xTaskCreatePinnedToCoreWithCaps(__rtc_rx_task, "rtc_opus_rx", 16*1024, rtc, 5, &rtc->rx_worker,
|
||||
tskNO_AFFINITY, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT) != pdPASS) {
|
||||
rtc->rx_worker = NULL;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __rtc_rx_stop(rtc_impl_t *rtc)
|
||||
{
|
||||
atomic_store(&rtc->rx_accepting, false);
|
||||
__rtc_rx_interrupt(rtc);
|
||||
while (atomic_load(&rtc->rx_producers)) vTaskDelay(1);
|
||||
if (!rtc->rx_work_lock) return;
|
||||
/* Wait for a decoder/user callback already in progress. Future work checks
|
||||
* accepting/generation under this lock, so nothing is emitted after return. */
|
||||
xSemaphoreTake(rtc->rx_work_lock, portMAX_DELAY);
|
||||
unsigned slot;
|
||||
if (rtc->rx_pending && rtc->rx_free) {
|
||||
while (xQueueReceive(rtc->rx_pending, &slot, 0) == pdTRUE) xQueueSend(rtc->rx_free, &slot, 0);
|
||||
}
|
||||
rtc->rx_applied_generation = atomic_load(&rtc->rx_generation);
|
||||
xSemaphoreGive(rtc->rx_work_lock);
|
||||
}
|
||||
|
||||
static int __rtc_rx_start(rtc_impl_t *rtc, const char *room)
|
||||
{
|
||||
if (!room || strlen(room) >= sizeof(rtc->rx_room) || !rtc->rx_worker) return -1;
|
||||
__rtc_rx_stop(rtc);
|
||||
xSemaphoreTake(rtc->rx_work_lock, portMAX_DELAY);
|
||||
memcpy(rtc->rx_room, room, strlen(room)+1);
|
||||
rtc->rx_report_ms=rtc->rx_previous_ms=0;
|
||||
rtc->rx_frames=rtc->rx_pcm_bytes=rtc->rx_gap_ms=rtc->rx_decode_ms=rtc->rx_errors=rtc->rx_decode_total_us=0;
|
||||
rtc->rx_applied_generation = atomic_load(&rtc->rx_generation);
|
||||
atomic_store(&rtc->rx_accepting, true);
|
||||
xSemaphoreGive(rtc->rx_work_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __rtc_rx_destroy(rtc_impl_t *rtc)
|
||||
{
|
||||
__rtc_rx_stop(rtc);
|
||||
atomic_store(&rtc->rx_shutdown, true);
|
||||
if (rtc->rx_worker) {
|
||||
xSemaphoreTake(rtc->rx_worker_stopped, portMAX_DELAY);
|
||||
vTaskDeleteWithCaps(rtc->rx_worker);
|
||||
rtc->rx_worker = NULL;
|
||||
}
|
||||
if (rtc->rx_free) vQueueDelete(rtc->rx_free);
|
||||
if (rtc->rx_pending) vQueueDelete(rtc->rx_pending);
|
||||
if (rtc->rx_work_lock) vSemaphoreDelete(rtc->rx_work_lock);
|
||||
if (rtc->rx_worker_stopped) vSemaphoreDelete(rtc->rx_worker_stopped);
|
||||
heap_caps_free(rtc->rx_packets);
|
||||
rtc->rx_packets = NULL;
|
||||
rtc->rx_free = rtc->rx_pending = NULL;
|
||||
rtc->rx_work_lock = rtc->rx_worker_stopped = NULL;
|
||||
}
|
||||
/* RTC_RX_HOST_END */
|
||||
#endif
|
||||
|
||||
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)
|
||||
{
|
||||
__rtc_diagnostic_priority_apply();
|
||||
volc_data_info_t info = {0};
|
||||
rtc_impl_t* rtc = (rtc_impl_t*)byte_rtc_get_user_data(engine);
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
if (rtc && data_type == AUDIO_DATA_TYPE_OPUS) {
|
||||
__rtc_rx_enqueue(rtc, channel, data_ptr, data_len);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
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;
|
||||
uint64_t now=volc_osal_get_time_ms();
|
||||
if(!rtc->rx_report_ms)rtc->rx_report_ms=now;
|
||||
unsigned gap=rtc->rx_previous_ms?(unsigned)(now-rtc->rx_previous_ms):0;
|
||||
if(gap>rtc->rx_gap_ms)rtc->rx_gap_ms=gap;
|
||||
rtc->rx_previous_ms=now;rtc->rx_frames++;
|
||||
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);
|
||||
@@ -268,7 +691,7 @@ static void _on_channel_error(byte_rtc_engine_t engine, const char* channel, int
|
||||
{
|
||||
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 : "");
|
||||
LOGE("RTC channel error code=%d", code);
|
||||
msg_data.code = code;
|
||||
msg_data.data.msg = (char *)msg;
|
||||
_send_message_2_user(rtc, &msg_data);
|
||||
@@ -281,7 +704,7 @@ static void _on_global_error(byte_rtc_engine_t engine, int code, const char* mes
|
||||
rtc->b_channel_joined = false;
|
||||
|
||||
rtc->b_first_keyframe_received = false;
|
||||
LOGI("global error %d %s\n", code, message ? message : "");
|
||||
LOGI("RTC global error code=%d", code);
|
||||
msg_data.code = VOLC_MSG_DISCONNECTED;
|
||||
_send_message_2_user(rtc, &msg_data);
|
||||
};
|
||||
@@ -309,7 +732,8 @@ static void _on_target_bitrate_changed(byte_rtc_engine_t engine, const char* cha
|
||||
|
||||
static void _on_token_privilege_will_expire(byte_rtc_engine_t engine, const char* token)
|
||||
{
|
||||
LOGI("\ntoken privilege will expire %s", token ? token : "");
|
||||
(void)token;
|
||||
LOGI("RTC token privilege will expire");
|
||||
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;
|
||||
@@ -319,6 +743,7 @@ static void _on_token_privilege_will_expire(byte_rtc_engine_t engine, const char
|
||||
|
||||
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)
|
||||
{
|
||||
__rtc_diagnostic_priority_apply();
|
||||
|
||||
int ret = 0;
|
||||
volc_msg_t msg = { 0 };
|
||||
@@ -331,22 +756,47 @@ static void _on_message_received(byte_rtc_engine_t engine, const char* channel_n
|
||||
LOGE("pipeline not started or channel not joined or user not joined");
|
||||
return;
|
||||
}
|
||||
#ifdef ESP_PLATFORM
|
||||
/* rx_room is only changed after in-flight SDK readers have left. */
|
||||
bool rx_guard = false;
|
||||
if (rtc->opus_encoder) {
|
||||
atomic_fetch_add(&rtc->rx_producers, 1);
|
||||
bool current_room = atomic_load(&rtc->rx_accepting) && channel_name && !strcmp(channel_name, rtc->rx_room);
|
||||
if (!current_room) {
|
||||
atomic_fetch_sub(&rtc->rx_producers, 1);
|
||||
return;
|
||||
}
|
||||
rx_guard = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (_is_target_message(message, MAGIC_CONV)) {
|
||||
ret = _on_conversion_status_message_parsed((uint8_t *)message + MAGIC_OFFSET,rtc);
|
||||
if(ret != -1){
|
||||
#ifdef ESP_PLATFORM
|
||||
if (ret == VOLC_CONV_STATUS_INTERRUPTED && rtc->opus_encoder) {
|
||||
__rtc_rx_interrupt(rtc);
|
||||
goto message_done;
|
||||
}
|
||||
#endif
|
||||
msg.code = VOLC_MSG_CONV_STATUS;
|
||||
msg.data.conv_status = ret;
|
||||
_send_message_2_user(rtc, &msg);
|
||||
}
|
||||
return;
|
||||
goto message_done;
|
||||
}
|
||||
__send_data_2_user(rtc, message, size, &info);
|
||||
message_done:
|
||||
#ifdef ESP_PLATFORM
|
||||
if (rx_guard) atomic_fetch_sub(&rtc->rx_producers, 1);
|
||||
#endif
|
||||
return;
|
||||
};
|
||||
|
||||
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 : "");
|
||||
__rtc_diagnostic_priority_apply();
|
||||
LOGD("MessageSendResult id=%" PRId64 " error=%d", msgid, error);
|
||||
};
|
||||
|
||||
static void _on_license_will_expire(byte_rtc_engine_t engine, int daysleft)
|
||||
@@ -361,7 +811,7 @@ static void _on_license_will_expire(byte_rtc_engine_t engine, int daysleft)
|
||||
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;
|
||||
atomic_store(&rtc->b_fini, true);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -444,6 +894,7 @@ static int __rtc_init(rtc_impl_t* engine, cJSON* p_config)
|
||||
}
|
||||
|
||||
engine->rtc = byte_rtc_create(engine->p_appid, &rtc_event_handler);
|
||||
if (!engine->rtc) return -1;
|
||||
byte_rtc_set_user_data(engine->rtc, (void*) engine);
|
||||
byte_rtc_set_log_level(engine->rtc, log_level);
|
||||
ret = volc_json_read_object(p_config, "params", &p_params);
|
||||
@@ -455,16 +906,20 @@ static int __rtc_init(rtc_impl_t* engine, cJSON* p_config)
|
||||
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);
|
||||
int param_ret = byte_rtc_set_params(engine->rtc, param_str);
|
||||
LOGI("volc_rtc_create: set param[%d] result=%d", i, param_ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cJSON_Delete(p_params);
|
||||
}
|
||||
byte_rtc_init(engine->rtc);
|
||||
byte_rtc_set_audio_codec(engine->rtc, __volc_to_rtc_audio_codec(engine->audio_codec));
|
||||
ret = byte_rtc_init(engine->rtc);
|
||||
LOGI("RTC init result=%d", ret);
|
||||
if (ret != 0) return ret;
|
||||
ret = byte_rtc_set_audio_codec(engine->rtc, __volc_to_rtc_audio_codec(engine->audio_codec));
|
||||
LOGI("RTC codec=%d result=%d", engine->audio_codec, ret);
|
||||
if (ret != 0) return ret;
|
||||
byte_rtc_set_video_codec(engine->rtc, video_codec - 1); // -1 for default codec
|
||||
return 0;
|
||||
}
|
||||
@@ -490,11 +945,28 @@ volc_rtc_t volc_rtc_create(const char* appid, void* context, cJSON* p_config, vo
|
||||
goto err_out_label;
|
||||
}
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
if (rtc->audio_codec == VOLC_AUDIO_CODEC_TYPE_OPUS) {
|
||||
esp_opus_enc_config_t enc = ESP_OPUS_ENC_CONFIG_DEFAULT();
|
||||
enc.sample_rate=16000; enc.channel=1; enc.bitrate=24000;
|
||||
enc.complexity=0; enc.enable_vbr=true;
|
||||
int input_size=0;
|
||||
if (esp_opus_enc_open(&enc,sizeof(enc),&rtc->opus_encoder)!=ESP_AUDIO_ERR_OK ||
|
||||
esp_opus_enc_get_frame_size(rtc->opus_encoder,&input_size,&rtc->opus_packet_size)!=ESP_AUDIO_ERR_OK ||
|
||||
input_size != sizeof(rtc->pcm_frame) || rtc->opus_packet_size <= 0 ||
|
||||
!(rtc->opus_packet=volc_osal_malloc(rtc->opus_packet_size)) || __rtc_rx_init(rtc) != 0) {
|
||||
LOGE("RTC Opus codec initialization failed");
|
||||
volc_rtc_destroy(rtc);
|
||||
return NULL;
|
||||
}
|
||||
LOGI("RTC Opus ready: 16000 Hz mono, 20 ms uplink");
|
||||
}
|
||||
#endif
|
||||
|
||||
LOGD("rtc create success");
|
||||
return (volc_rtc_t)rtc;
|
||||
err_out_label:
|
||||
HAL_SAFE_FREE(rtc->p_appid);
|
||||
HAL_SAFE_FREE(rtc);
|
||||
volc_rtc_destroy(rtc);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -505,13 +977,31 @@ void volc_rtc_destroy(volc_rtc_t handle)
|
||||
LOGE("rtc instance is NULL");
|
||||
return;
|
||||
}
|
||||
atomic_store(&rtc->closing, true);
|
||||
__rtc_stop(rtc);
|
||||
|
||||
byte_rtc_fini(rtc->rtc);
|
||||
while (!rtc->b_fini) {
|
||||
usleep(1000 * 10);
|
||||
if (rtc->rtc) {
|
||||
int fini_result = byte_rtc_fini(rtc->rtc);
|
||||
if (fini_result != 0) {
|
||||
/* The SDK only documents destroy as safe after fini_notify. Keep
|
||||
* callback context alive rather than free memory it might access. */
|
||||
LOGE("RTC cleanup deferred: fini failed code=%d", fini_result);
|
||||
return;
|
||||
}
|
||||
while (!atomic_load(&rtc->b_fini)) {
|
||||
usleep(1000 * 10);
|
||||
}
|
||||
}
|
||||
byte_rtc_destroy(rtc->rtc);
|
||||
if (rtc->rtc) byte_rtc_destroy(rtc->rtc);
|
||||
__clear_room_info(rtc);
|
||||
HAL_SAFE_FREE(rtc->p_appid);
|
||||
HAL_SAFE_FREE(rtc->p_user_id);
|
||||
#ifdef ESP_PLATFORM
|
||||
__rtc_rx_destroy(rtc);
|
||||
if (rtc->opus_encoder) esp_opus_enc_close(rtc->opus_encoder);
|
||||
if (rtc->opus_decoder) esp_opus_dec_close(rtc->opus_decoder);
|
||||
HAL_SAFE_FREE(rtc->opus_packet);
|
||||
#endif
|
||||
HAL_SAFE_FREE(rtc->p_channel_name);
|
||||
HAL_SAFE_FREE(rtc->p_remote_user_id);
|
||||
HAL_SAFE_FREE(rtc->p_token);
|
||||
@@ -527,12 +1017,23 @@ int volc_rtc_start(volc_rtc_t rtc, const char* bot_id, volc_iot_info_t* iot_info
|
||||
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)) {
|
||||
cJSON *request=cJSON_Parse(params);
|
||||
const char *task_id=cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(request,"PlatformTaskID"));
|
||||
if(!task_id||strlen(task_id)!=36){cJSON_Delete(request);return -1;}
|
||||
// Never reuse a previous room/token if the next provider request fails.
|
||||
__clear_room_info(rtc_impl);
|
||||
int config_result=volc_get_rtc_config(iot_info, __volc_to_rtc_audio_codec(rtc_impl->audio_codec), bot_id, task_id, &rtc_impl->info, params);
|
||||
cJSON_Delete(request);
|
||||
if (config_result) {
|
||||
__clear_room_info(rtc_impl);
|
||||
LOGE("get rtc config failed");
|
||||
return -1;
|
||||
}
|
||||
opt = &rtc_impl->info.rtc_opt;
|
||||
if (volc_platform_recording_prepare) {
|
||||
int recording_result=volc_platform_recording_prepare(rtc_impl->p_appid,opt->p_channel_name,opt->p_uid);
|
||||
if (recording_result) LOGW("Cloud conversation recording not ready (%d)",recording_result);
|
||||
}
|
||||
return __rtc_start(rtc_impl, opt);
|
||||
}
|
||||
|
||||
@@ -542,7 +1043,38 @@ int volc_rtc_stop(volc_rtc_t handle) {
|
||||
LOGE("rtc instance is NULL");
|
||||
return -1;
|
||||
}
|
||||
__rtc_stop(rtc);
|
||||
return __rtc_stop(rtc);
|
||||
}
|
||||
|
||||
const char *volc_rtc_user_id(volc_rtc_t handle,bool remote) {
|
||||
rtc_impl_t *rtc=handle;if(!rtc)return NULL;return remote?rtc->p_remote_user_id:rtc->p_user_id;
|
||||
}
|
||||
|
||||
static int __rtc_send_pcm(rtc_impl_t *rtc, const uint8_t *data, size_t size) {
|
||||
audio_frame_info_t info = {.data_type = AUDIO_DATA_TYPE_PCM};
|
||||
while (size) {
|
||||
size_t take = sizeof(rtc->pcm_frame) - rtc->pcm_used;
|
||||
if (take > size) take = size;
|
||||
memcpy(rtc->pcm_frame + rtc->pcm_used, data, take);
|
||||
rtc->pcm_used += take; data += take; size -= take;
|
||||
if (rtc->pcm_used == sizeof(rtc->pcm_frame)) {
|
||||
int ret;
|
||||
#ifdef ESP_PLATFORM
|
||||
if (rtc->opus_encoder) {
|
||||
esp_audio_enc_in_frame_t in = {.buffer=rtc->pcm_frame,.len=sizeof(rtc->pcm_frame)};
|
||||
esp_audio_enc_out_frame_t out = {.buffer=rtc->opus_packet,.len=rtc->opus_packet_size};
|
||||
ret=esp_opus_enc_process(rtc->opus_encoder,&in,&out);
|
||||
if(ret==ESP_AUDIO_ERR_OK){
|
||||
info.data_type=AUDIO_DATA_TYPE_OPUS;
|
||||
ret=byte_rtc_send_audio_data(rtc->rtc,rtc->p_channel_name,out.buffer,out.encoded_bytes,&info);
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
ret = byte_rtc_send_audio_data(rtc->rtc,rtc->p_channel_name,rtc->pcm_frame,sizeof(rtc->pcm_frame),&info);
|
||||
rtc->pcm_used = 0;
|
||||
if (ret != 0) return ret;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -550,14 +1082,19 @@ int volc_rtc_send(volc_rtc_t handle, const void* data, int size, volc_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) {
|
||||
if (NULL == rtc || NULL == data || NULL == data_info || size <= 0) {
|
||||
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: {
|
||||
// Join is asynchronous. Do not enqueue media into the vendor
|
||||
// transport until both room and agent are actually present.
|
||||
if (!rtc->b_pipeline_started || !rtc->b_channel_joined || !rtc->b_user_joined) return 0;
|
||||
if (data_info->info.audio.data_type == VOLC_AUDIO_DATA_TYPE_PCM && size > 0)
|
||||
return __rtc_send_pcm(rtc,data,(size_t)size);
|
||||
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);
|
||||
return byte_rtc_send_audio_data(rtc->rtc, rtc->p_channel_name, data, size, &audio_info);
|
||||
break;
|
||||
}
|
||||
case VOLC_DATA_TYPE_VIDEO: {
|
||||
|
||||
+8
-8
@@ -125,16 +125,16 @@ static void __ws_recv_data(ws_impl_t* ws, const char* data, int data_len)
|
||||
|
||||
p_json = cJSON_ParseWithLength(data, data_len);
|
||||
if (!p_json) {
|
||||
LOGE("Failed to parse json, data_len: %d, data: %s", data_len, data);
|
||||
LOGE("Failed to parse json, data_len: %d", data_len);
|
||||
return;
|
||||
}
|
||||
// LOGI("json: %s", data);
|
||||
// Protocol payloads contain private text and must not be logged.
|
||||
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);
|
||||
LOGE("delta is empty");
|
||||
goto err_out_label;
|
||||
}
|
||||
if (NULL == ws || !ws->b_pipeline_started) {
|
||||
@@ -182,11 +182,11 @@ static void __ws_recv_data(ws_impl_t* ws, const char* data, int data_len)
|
||||
ws->conv_status = VOLC_CONV_STATUS_INTERRUPTED;
|
||||
msg.data.conv_status = VOLC_CONV_STATUS_INTERRUPTED;
|
||||
}
|
||||
LOGI("data: %s", data);
|
||||
LOGI("conversation status updated");
|
||||
__send_message_2_user(ws, &msg);
|
||||
} else {
|
||||
if (strcmp(p_type, "session.created") == 0) {
|
||||
LOGI("%s", data);
|
||||
LOGI("provider session created");
|
||||
} 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)) {
|
||||
@@ -471,7 +471,7 @@ static int __ws_input_audio_buffer_commit(ws_impl_t* ws) {
|
||||
root = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(root, "type", "input_audio_buffer.commit");
|
||||
char* json = cJSON_PrintUnformatted(root);
|
||||
LOGD("json: %s", json);
|
||||
LOGD("WebSocket message prepared");
|
||||
cJSON_Delete(root);
|
||||
ret = volc_ws_client_send_text(ws->client, json, strlen(json), 1000);
|
||||
if (ret >= 0) {
|
||||
@@ -500,7 +500,7 @@ static int __ws_response_create(ws_impl_t* ws) {
|
||||
cJSON_AddItemToObject(response_obj, "modalities", modalities_array);
|
||||
// 生成 JSON 字符串
|
||||
const char* json = cJSON_PrintUnformatted(root);
|
||||
LOGD("json: %s", json);
|
||||
LOGD("WebSocket message prepared");
|
||||
// 释放内存
|
||||
cJSON_Delete(root);
|
||||
ret = volc_ws_client_send_text(ws->client, json, strlen(json), 1000);
|
||||
@@ -555,7 +555,7 @@ static void __ws_parse_params(const char* params, ws_params_t* out_params) {
|
||||
}
|
||||
p_json = cJSON_Parse(params);
|
||||
if (!p_json) {
|
||||
LOGW("Failed to parse json params: %s", params);
|
||||
LOGW("Failed to parse json params");
|
||||
return;
|
||||
}
|
||||
volc_json_read_int(p_json, "audio.codec", (int*)&out_params->audio_codec_type);
|
||||
|
||||
+7
-7
@@ -416,7 +416,7 @@ static int ws_read(volc_ws_client_t* client, char* buffer, int len, int timeout_
|
||||
transport_ws_t* ws = client->ws_transport;
|
||||
if (ws->frame_state.bytes_remaining <= 0) {
|
||||
if ((rlen = ws_read_header(client, buffer, len, timeout_ms)) < 0) {
|
||||
LOGE("Error reading header %d, %s\r\n", rlen, buffer);
|
||||
LOGE("Error reading header, length=%d", rlen);
|
||||
ws->frame_state.bytes_remaining = 0;
|
||||
return rlen;
|
||||
}
|
||||
@@ -835,7 +835,7 @@ static int ws_connect(volc_ws_client_t* client, const char* host, int port, int
|
||||
"Sec-WebSocket-Key: %s\r\n"
|
||||
"Sec-WebSocket-Version: 13\r\n",
|
||||
ws->path, host, host, user_agent_ptr, client_key);
|
||||
LOGD("http request: %s", ws->buffer);
|
||||
LOGD("WebSocket handshake prepared");
|
||||
|
||||
if (len <= 0 || len >= WS_BUFFER_SIZE) {
|
||||
LOGE("Error in request generation, desired request len: %d, buffer size: %d\r\n", len, WS_BUFFER_SIZE);
|
||||
@@ -871,7 +871,7 @@ static int ws_connect(volc_ws_client_t* client, const char* host, int port, int
|
||||
r, len, WS_BUFFER_SIZE);
|
||||
return -1;
|
||||
}
|
||||
LOGD("ws->buffer:\r\n%s", ws->buffer);
|
||||
LOGD("WebSocket handshake sent");
|
||||
if (ws_tcp_write(client, ws->buffer, len, timeout_ms) < 0) {
|
||||
LOGE("Write FAIL");
|
||||
}
|
||||
@@ -879,12 +879,12 @@ static int ws_connect(volc_ws_client_t* client, const char* host, int port, int
|
||||
int header_len = 0;
|
||||
do {
|
||||
if ((len = ws_tcp_read(client, ws->buffer + header_len, WS_BUFFER_SIZE - header_len, timeout_ms)) <= 0) {
|
||||
LOGE("read FAIL for upgrade header:%s\r\n", ws->buffer);
|
||||
LOGE("Failed to read upgrade header");
|
||||
return -1;
|
||||
}
|
||||
header_len += len;
|
||||
ws->buffer[header_len] = '\0';
|
||||
LOGD("Read header chunk %d, current header size: %d, header: %s\r\n", len, header_len, ws->buffer);
|
||||
LOGD("Read header chunk %d, current header size: %d", len, header_len);
|
||||
} while (NULL == strstr(ws->buffer, "\r\n\r\n") && header_len < WS_BUFFER_SIZE);
|
||||
|
||||
char* server_key = get_http_header(ws->buffer, "Sec-WebSocket-Accept:");
|
||||
@@ -1117,7 +1117,7 @@ static int __parse_url(volc_ws_client_t* client, char* url) {
|
||||
return -1;
|
||||
}
|
||||
url_end = url + strlen(url);
|
||||
LOGD("url: %s", url);
|
||||
LOGD("WebSocket URL configured");
|
||||
|
||||
// 1. parse scheme
|
||||
filed_end = strstr(pos, "://");
|
||||
@@ -1209,7 +1209,7 @@ static int __parse_url(volc_ws_client_t* client, char* url) {
|
||||
}
|
||||
}
|
||||
|
||||
LOGD("scheme: %s, host: %s, port: %d, path: %s", client->scheme, client->host, (int)client->port, client->path);
|
||||
LOGD("WebSocket endpoint: %s://%s:%d", client->scheme, client->host, (int)client->port);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <mbedtls/sha256.h>
|
||||
#include <mbedtls/md5.h>
|
||||
#include <mbedtls/base64.h>
|
||||
#include <mbedtls/platform_util.h>
|
||||
|
||||
#include "volc_osal.h"
|
||||
#include "util/volc_base64.h"
|
||||
@@ -53,63 +54,32 @@ static int __aes_cbc(bool encrypt, const unsigned char* key, unsigned char* iv,
|
||||
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);
|
||||
int result=encrypt?mbedtls_aes_setkey_enc(&aes_ctx,key,key_bits):mbedtls_aes_setkey_dec(&aes_ctx, key, key_bits);
|
||||
if(!result)result=mbedtls_aes_crypt_cbc(&aes_ctx, mode, ilen, iv, (const unsigned char *)input, output);
|
||||
mbedtls_aes_free(&aes_ctx);
|
||||
|
||||
return 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
#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;
|
||||
size_t key_len=partial_secret?16:24,decoded_len=0;
|
||||
if(!secret||strlen(secret)<key_len||!input||!input[0]||strlen(input)>8192)return NULL;
|
||||
size_t capacity=strlen(input)+1;
|
||||
uint8_t *decoded=volc_osal_calloc(capacity,1),*output=volc_osal_calloc(capacity,1);
|
||||
if(!decoded||!output){HAL_SAFE_FREE(decoded);HAL_SAFE_FREE(output);return NULL;}
|
||||
unsigned char key[24]={0},iv[16];memcpy(key,secret,key_len);memcpy(iv,secret,16);
|
||||
bool valid=mbedtls_base64_decode(decoded,capacity,&decoded_len,(const unsigned char*)input,strlen(input))==0&&decoded_len>0&&decoded_len%16==0;
|
||||
if(valid)valid=__aes_cbc(false,key,iv,decoded,decoded_len,key_len*8,output)==0;
|
||||
if(valid){size_t padding=output[decoded_len-1];valid=padding>=1&&padding<=16&&padding<=decoded_len;
|
||||
if(valid){for(size_t i=decoded_len-padding;i<decoded_len;i++)if(output[i]!=padding)valid=false;
|
||||
if(valid){size_t length=decoded_len-padding;valid=length>=16&&memchr(output,0,length)==NULL;output[length]=0;}}
|
||||
}
|
||||
|
||||
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;
|
||||
mbedtls_platform_zeroize(key,sizeof(key));mbedtls_platform_zeroize(iv,sizeof(iv));
|
||||
mbedtls_platform_zeroize(decoded,capacity);HAL_SAFE_FREE(decoded);
|
||||
if(!valid){mbedtls_platform_zeroize(output,capacity);HAL_SAFE_FREE(output);return NULL;}
|
||||
return (char*)output;
|
||||
}
|
||||
|
||||
void volc_sha256_hmac(const unsigned char* key, int keylen, const unsigned char* input, int ilen, unsigned char* output, int* plen) {
|
||||
|
||||
@@ -30,7 +30,7 @@ char* volc_http_post(const char* uri, const char* post_data, int data_len)
|
||||
}
|
||||
|
||||
webclient_response(session, (void**) &buffer, &res_len);
|
||||
LOGD("url: %s, request: %s, response: %s", uri, post_data, buffer);
|
||||
LOGD("HTTP exchange completed, status: %d", resp_status);
|
||||
err_out_label:
|
||||
if (session) {
|
||||
webclient_close(session);
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
#if defined(ENABLE_RTC_MODE)
|
||||
#include "VolcEngineRTCLite.h"
|
||||
#include "volc_rtc.h"
|
||||
#elif defined(ENABLE_WS_MODE)
|
||||
#endif
|
||||
#if defined(ENABLE_WS_MODE)
|
||||
#include "volc_ws.h"
|
||||
#endif
|
||||
|
||||
@@ -195,6 +196,7 @@ static int __config_iot_parse(cJSON* iot_config, volc_engine_impl_t* engine) {
|
||||
* Product Secret is deliberately not accepted by the firmware path.
|
||||
*/
|
||||
ret |= volc_json_read_string(iot_config, "device_secret", &engine->info.device_secret);
|
||||
(void)volc_json_read_string(iot_config,"rtc_app_id",&engine->info.rtc_app_id);
|
||||
|
||||
return ret == 0 ? 0 : -1;
|
||||
}
|
||||
@@ -263,7 +265,9 @@ int volc_create(volc_engine_t* handle, const char* config_json, volc_event_handl
|
||||
cJSON* rtc_cfg = cJSON_GetObjectItem(config, "rtc");
|
||||
if (rtc_cfg) {
|
||||
#if defined(ENABLE_RTC_MODE)
|
||||
if(!engine->info.rtc_app_id||!engine->info.rtc_app_id[0]){ret=VOLC_ERR_FAILED;goto err_out_label;}
|
||||
engine->rtc = volc_rtc_create(engine->info.rtc_app_id, engine, rtc_cfg, __realtime_user_event_router, __realtime_data_router);
|
||||
if(!engine->rtc){ret=VOLC_ERR_FAILED;goto err_out_label;}
|
||||
#else
|
||||
LOGW("RTC mode is not enabled");
|
||||
#endif
|
||||
@@ -365,6 +369,14 @@ int volc_start(volc_engine_t handle, volc_opt_t* opt) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
const char *volc_current_user_id(volc_engine_t handle,bool remote) {
|
||||
#if defined(ENABLE_RTC_MODE)
|
||||
volc_engine_impl_t *engine=handle;return engine?volc_rtc_user_id(engine->rtc,remote):NULL;
|
||||
#else
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
int volc_stop(volc_engine_t handle) {
|
||||
int ret = 0;
|
||||
volc_engine_impl_t* engine = (volc_engine_impl_t*)handle;
|
||||
|
||||
Vendored
+2
-1
@@ -19,6 +19,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#if defined(MBEDTLS_USER_CONFIG_FILE)
|
||||
#include MBEDTLS_USER_CONFIG_FILE
|
||||
@@ -162,7 +163,7 @@ int mbedtls_client_connect(MbedTLSSession *session)
|
||||
ret = mbedtls_net_connect(&session->server_fd, session->host, session->port,
|
||||
MBEDTLS_NET_PROTO_TCP);
|
||||
if (ret != 0) {
|
||||
LOGE("mbedtls_net_connect error, return -0x%x", -ret);
|
||||
LOGE("mbedtls_net_connect error, return -0x%x errno=%d", -ret, errno);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user