// Copyright (2025) Beijing Volcano Engine Technology Ltd. // SPDX-License-Identifier: Apache-2.0 #include #include #include #include #include #include #include #include // 确保在包含volc_rtc.h之前定义ENABLE_RTC_MODE #ifndef ENABLE_RTC_MODE #define ENABLE_RTC_MODE #endif #include "VolcEngineRTCLite.h" #include "volc_rtc.h" #include "volc_platform.h" #include "util/volc_list.h" #include "util/volc_log.h" #include "util/volc_json.h" #include "base/volc_base.h" #include "base/volc_device_manager.h" // 移除多余的ENABLE_RTC_MODE条件编译块,因为它已经在头文件中处理 #define MAGIC_CONTROL "ctrl" #define MAGIC_CONV "conv" #define MAGIC_LENGTH 4 #define MAGIC_OFFSET 8 const char* interrupt_str = "{\"Command\":\"interrupt\"}"; typedef struct { bool b_pipeline_started; bool b_user_joined; bool b_channel_joined; bool b_first_keyframe_received; bool b_fini; bool b_audio_publish; bool b_video_publish; bool b_audio_subscribe; bool b_video_subscribe; char* p_appid; char* p_channel_name; char* p_user_id; char* p_remote_user_id; char* p_token; void* context; int audio_codec; volc_room_info_t info; volc_msg_cb message_callback; volc_data_cb data_callback; byte_rtc_engine_t rtc; byte_rtc_event_handler_t event_handler; } rtc_impl_t; static bool __is_first_keyframe_not_received(rtc_impl_t* rtc, int is_key_frame) { return (!rtc->b_first_keyframe_received && !is_key_frame); } static int _build_binary_message(const char* magic, const char* message, uint8_t** out_buf, size_t* out_len) { size_t magic_len = strlen(magic); size_t msg_len = strlen(message); // 分配内存:魔术字 + 4字节长度 + 消息内容 *out_len = magic_len + 4 + msg_len; *out_buf = (uint8_t*)hal_malloc(*out_len); if (!*out_buf) { LOGE("hal_malloc failed"); return -1; } // 填充魔术字 memcpy(*out_buf, magic, magic_len); // 以大端序填充长度 uint8_t* len_ptr = *out_buf + magic_len; len_ptr[0] = (uint8_t)((msg_len >> 24) & 0xFF); // 最高位字节 len_ptr[1] = (uint8_t)((msg_len >> 16) & 0xFF); len_ptr[2] = (uint8_t)((msg_len >> 8) & 0xFF); len_ptr[3] = (uint8_t)(msg_len & 0xFF); // 最低位字节 // 填充消息内容 memcpy(*out_buf + magic_len + 4, message, msg_len); return 0; } static int __rtc_start(rtc_impl_t* rtc, volc_rtc_option_t* option) { byte_rtc_room_options_t room_opt = {0}; if (!rtc || !option) { LOGE("rtc or option is NULL"); return -1; } if (!option->p_channel_name || !option->p_uid || !option->p_token) { LOGE("invalid rtc option: channel(%p) uid(%p) token(%p)", option->p_channel_name, option->p_uid, option->p_token); return -1; } if (strlen(option->p_channel_name) == 0 || strlen(option->p_uid) == 0 || strlen(option->p_token) == 0) { LOGE("invalid rtc option: empty field"); return -1; } room_opt.auto_publish_audio = rtc->b_audio_publish; room_opt.auto_publish_video = rtc->b_video_publish; room_opt.auto_subscribe_audio = rtc->b_audio_subscribe; room_opt.auto_subscribe_video = rtc->b_video_subscribe; // 记录内存使用情况 size_t heap_free_before = heap_caps_get_free_size(MALLOC_CAP_DEFAULT); size_t spiram_free_before = heap_caps_get_free_size(MALLOC_CAP_SPIRAM); LOGI("Joining channel: %s, uid: %s, token: %s, vpub: %d, vsub: %d, apub: %d, asub: %d", option->p_channel_name, option->p_uid, option->p_token, (int)room_opt.auto_publish_video, (int)room_opt.auto_subscribe_video, (int)room_opt.auto_publish_audio, (int)room_opt.auto_subscribe_audio); LOGI("Memory before byte_rtc_join_room - Heap: %u bytes, SPIRAM: %u bytes", (unsigned)heap_free_before, (unsigned)spiram_free_before); int ret = byte_rtc_join_room(rtc->rtc, option->p_channel_name, option->p_uid, option->p_token, &room_opt); // 记录内存使用情况 size_t heap_free_after = heap_caps_get_free_size(MALLOC_CAP_DEFAULT); size_t spiram_free_after = heap_caps_get_free_size(MALLOC_CAP_SPIRAM); LOGI("Memory after byte_rtc_join_room - Heap: %u bytes (change: %d), SPIRAM: %u bytes (change: %d)", (unsigned)heap_free_after, (int)(heap_free_after - heap_free_before), (unsigned)spiram_free_after, (int)(spiram_free_after - spiram_free_before)); if (ret != 0) { LOGE("Failed to join room: %d", ret); return ret; } rtc->b_pipeline_started = true; rtc->p_channel_name = strdup(option->p_channel_name); if (!rtc->p_channel_name) { LOGE("Failed to allocate memory for channel name"); return -1; } rtc->p_user_id = strdup(option->p_uid); if (!rtc->p_user_id) { LOGE("Failed to allocate memory for user ID"); HAL_SAFE_FREE(rtc->p_channel_name); return -1; } return 0; } static void __rtc_stop(rtc_impl_t* rtc) { if (!rtc) { LOGE("rtc instance is NULL"); return; } if (!rtc->b_pipeline_started) { LOGI("pipeline not started"); return; } int ret = byte_rtc_leave_room(rtc->rtc, rtc->p_channel_name); if (ret != 0) { LOGE("Failed to leave room: %d", ret); return; } rtc->b_pipeline_started = false; HAL_SAFE_FREE(rtc->p_channel_name); return; } // static void __send_message_2_user(rtc_impl_t* rtc, volc_msg_t* msg) // { // if (rtc->message_callback) { // rtc->message_callback(rtc->context, msg); // } // } static void __send_data_2_user(rtc_impl_t* rtc, const void* data, int data_len, volc_data_info_t* info) { // 添加详细的参数验证,确保回调函数的安全调用 if (!rtc) { LOGE("__send_data_2_user: rtc is NULL"); return; } if (!rtc->data_callback) { // 没有注册回调函数,这是正常情况,不记录错误 return; } if (!data) { LOGE("__send_data_2_user: data is NULL"); return; } if (data_len <= 0) { LOGE("__send_data_2_user: data_len is <= 0"); return; } if (!info) { LOGE("__send_data_2_user: info is NULL"); return; } // 调用数据回调函数 rtc->data_callback(rtc->context, data, data_len, info); } // static void _register_message_router(rtc_impl_t* rtc, volc_msg_cb callback) // { // rtc->message_callback = callback; // } static void _send_message_2_user(rtc_impl_t* rtc, volc_msg_t* msg) { if (rtc->message_callback) { rtc->message_callback(rtc->context, msg); } } static bool _is_target_message(const uint8_t* message, int size, const char* target) { if (message == NULL || target == NULL || size < MAGIC_LENGTH) { return false; } return memcmp(message, target, MAGIC_LENGTH) == 0; } static int _on_conversion_status_message_parsed(const char* json, int len) { int c = -1; char* buf = (char*)hal_malloc(len + 1); if (!buf) return c; memcpy(buf, json, len); buf[len] = '\0'; cJSON *root = cJSON_Parse(buf); if (root != NULL) { volc_json_read_int(root, "Stage.Code", &c); cJSON_Delete(root); } hal_free(buf); return c; } static void _on_join_channel_success(byte_rtc_engine_t engine, const char* channel, int elapsed_ms, bool rejoin) { volc_msg_t msg = {0}; LOGI("join channel success %s elapsed %d ms\n", channel, elapsed_ms); rtc_impl_t* rtc = (rtc_impl_t*) byte_rtc_get_user_data(engine); rtc->b_first_keyframe_received = false; rtc->b_channel_joined = true; msg.code = VOLC_MSG_CONNECTED; _send_message_2_user(rtc, &msg); }; static void _on_user_joined(byte_rtc_engine_t engine, const char* channel, const char* user_name, int elapsed_ms) { rtc_impl_t* rtc = (rtc_impl_t*) byte_rtc_get_user_data(engine); volc_msg_t msg = {0}; LOGI("remote user joined %s:%s elapsed %d ms\n", channel, user_name, elapsed_ms); rtc->b_user_joined = true; // 先释放旧的内存 HAL_SAFE_FREE(rtc->p_remote_user_id); // 分配新内存并检查结果 char* temp = strdup(user_name); if (temp) { rtc->p_remote_user_id = temp; } else { LOGE("Failed to allocate memory for remote user ID"); } msg.code = VOLC_MSG_USER_JOINED; _send_message_2_user(rtc, &msg); }; static void _on_user_offline(byte_rtc_engine_t engine, const char* channel, const char* user_name, int reason) { rtc_impl_t* rtc = (rtc_impl_t*) byte_rtc_get_user_data(engine); volc_msg_t msg = {0}; LOGI("remote user offline %s:%s reason %d\n", channel, user_name, reason); rtc->b_user_joined = false; // 清理远程用户ID,避免内存泄漏 HAL_SAFE_FREE(rtc->p_remote_user_id); msg.code = VOLC_MSG_USER_OFFLINE; _send_message_2_user(rtc, &msg); }; static void _on_user_mute_audio(byte_rtc_engine_t engine, const char* channel, const char* user_name, int muted) { LOGD("remote user mute audio %s:%s %d\n", channel, user_name, muted); }; static void _on_user_mute_video(byte_rtc_engine_t engine, const char* channel, const char* user_name, int muted) { LOGD("remote user mute video %s:%s %d\n", channel, user_name, muted); }; static void _on_audio_data(byte_rtc_engine_t engine, const char* channel, const char* user_name, uint16_t sent_ts, audio_data_type_e data_type, const void* data_ptr, size_t data_len, const uint8_t* extra_info, size_t extra_info_size) { // 添加详细的参数验证,防止空指针和无效数据导致崩溃 if (!engine) { LOGE("_on_audio_data: engine is NULL"); return; } if (!channel) { LOGE("_on_audio_data: channel is NULL"); return; } if (!user_name) { LOGE("_on_audio_data: user_name is NULL"); return; } if (!data_ptr) { LOGE("_on_audio_data: data_ptr is NULL"); return; } if (data_len == 0) { LOGE("_on_audio_data: data_len is 0"); return; } volc_data_info_t info = {0}; rtc_impl_t* rtc = (rtc_impl_t*)byte_rtc_get_user_data(engine); if (NULL == rtc) { LOGE("_on_audio_data: rtc instance is NULL"); return; } if (!rtc->b_pipeline_started) { LOGE("_on_audio_data: pipeline not started"); return; } if (!rtc->b_channel_joined) { LOGE("_on_audio_data: channel not joined"); return; } if (!rtc->b_user_joined) { LOGE("_on_audio_data: user not joined"); return; } info.type = VOLC_DATA_TYPE_AUDIO; info.info.audio.data_type = (volc_audio_data_type_e) data_type; // info.info.audio.sent_ts = sent_ts; // 限制数据大小,防止内存溢出 if (data_len > 16384) { // 16KB的合理限制 LOGW("_on_audio_data: data_len too large (%zu), truncating to 16KB", data_len); data_len = 16384; } __send_data_2_user(rtc, data_ptr, data_len, &info); }; static void _on_video_data(byte_rtc_engine_t engine, const char* channel, const char* user_name, uint16_t sent_ts, video_data_type_e codec, int is_key_frame, const void* data_ptr, size_t data_len) { volc_data_info_t info = {0}; rtc_impl_t* rtc = (rtc_impl_t*) byte_rtc_get_user_data(engine); if (NULL == rtc || !rtc->b_pipeline_started || !rtc->b_channel_joined || !rtc->b_user_joined) { LOGD("pipeline not started or channel not joined or user not joined"); return; } if (__is_first_keyframe_not_received(rtc, is_key_frame)) { LOGD("first keyframe not received, request key frame"); byte_rtc_request_video_key_frame(rtc->rtc, channel, user_name); return; } rtc->b_first_keyframe_received = true; info.type = VOLC_DATA_TYPE_VIDEO; info.info.video.data_type = (volc_video_data_type_e) codec; __send_data_2_user(rtc, data_ptr, data_len, &info); }; static void _on_channel_error(byte_rtc_engine_t engine, const char* channel, int code, const char* msg) { volc_msg_t msg_data = {0}; rtc_impl_t* rtc = (rtc_impl_t*) byte_rtc_get_user_data(engine); LOGE("channel error %s:%d %s\n", channel, code, msg ? msg : ""); LOGI("channel error heap_free=%u", (unsigned)heap_caps_get_free_size(MALLOC_CAP_DEFAULT)); msg_data.code = code; char* copied = NULL; if (msg) { size_t len = strnlen(msg, 1024); copied = (char*)hal_malloc(len + 1); if (copied) { memcpy(copied, msg, len); copied[len] = '\0'; msg_data.data.msg = copied; } } _send_message_2_user(rtc, &msg_data); if (copied) { hal_free(copied); } }; static void _on_global_error(byte_rtc_engine_t engine, int code, const char* message) { volc_msg_t msg_data = {0}; rtc_impl_t* rtc = (rtc_impl_t*) byte_rtc_get_user_data(engine); rtc->b_channel_joined = false; rtc->b_first_keyframe_received = false; LOGI("global error %d %s\n", code, message); LOGI("global error heap_free=%u", (unsigned)heap_caps_get_free_size(MALLOC_CAP_DEFAULT)); msg_data.code = VOLC_MSG_DISCONNECTED; _send_message_2_user(rtc, &msg_data); }; static void _on_key_frame_gen_req(byte_rtc_engine_t engine, const char* channel, const char* user_name) { LOGI("remote req key frame %s:%s\n", channel, user_name); rtc_impl_t* rtc = (rtc_impl_t*) byte_rtc_get_user_data(engine); volc_msg_t msg_data = {0}; msg_data.code = VOLC_MSG_KEY_FRAME_REQ; _send_message_2_user(rtc, &msg_data); }; static void _on_target_bitrate_changed(byte_rtc_engine_t engine, const char* channel, uint32_t target_bps) { rtc_impl_t* rtc = (rtc_impl_t*) byte_rtc_get_user_data(engine); volc_msg_t msg_data = {0}; LOGD("target bitrate changed %s %d bps\n", channel, (int)target_bps); // TODO: do not send it to user msg_data.code = VOLC_MSG_TARGET_BITRATE_CHANGED; msg_data.data.target_bitrate = target_bps; _send_message_2_user(rtc, &msg_data); }; static void _on_token_privilege_will_expire(byte_rtc_engine_t engine, const char* token) { LOGI("\ntoken privilege will expire %s", token); rtc_impl_t* rtc = (rtc_impl_t*) byte_rtc_get_user_data(engine); volc_msg_t msg_data = {0}; msg_data.code = VOLC_MSG_TOKEN_EXPIRED; // TODO: token? _send_message_2_user(rtc, &msg_data); }; static void _on_message_received(byte_rtc_engine_t engine, const char* channel_name, const char* src, const uint8_t* message, int size, bool binary) { int ret = 0; volc_msg_t msg = { 0 }; rtc_impl_t* rtc = (rtc_impl_t*) byte_rtc_get_user_data(engine); volc_data_info_t info = {0}; info.type = VOLC_DATA_TYPE_MESSAGE; info.info.message.is_binary = binary; LOGI("message received channel=%s src=%s size=%d binary=%d free_heap=%u", channel_name, src, size, binary, (unsigned)heap_caps_get_free_size(MALLOC_CAP_DEFAULT)); if (NULL == rtc || !rtc->b_pipeline_started || !rtc->b_channel_joined || !rtc->b_user_joined) { LOGE("pipeline not started or channel not joined or user not joined"); return; } if (_is_target_message(message, size, MAGIC_CONV)) { if (size < MAGIC_OFFSET) { LOGW("conv message too short: %d", size); return; } int json_len = (message[4] << 24) | (message[5] << 16) | (message[6] << 8) | (message[7]); if (json_len <= 0 || size < (MAGIC_OFFSET + json_len)) { LOGW("conv message invalid length: %d size: %d", json_len, size); return; } const char* json_ptr = (const char*)(message + MAGIC_OFFSET); ret = _on_conversion_status_message_parsed(json_ptr, json_len); msg.code = VOLC_MSG_CONV_STATUS; msg.data.conv_status = ret; _send_message_2_user(rtc, &msg); return; } __send_data_2_user(rtc, message, size, &info); }; static void _on_message_send_result(byte_rtc_engine_t engine, const char* channel_name, int64_t msgid, int error, const char* extencontent) { LOGD("----------------------->MessageSendResult msg id %" PRId64 ", error %d, extencontent %s \n", msgid, error, extencontent); }; static void _on_license_will_expire(byte_rtc_engine_t engine, int daysleft) { LOGI("----------------------->license will expire in %d days \n", daysleft); volc_msg_t msg_data = {0}; rtc_impl_t* rtc = (rtc_impl_t*) byte_rtc_get_user_data(engine); msg_data.code = VOLC_MSG_LICENSE_EXPIRED; _send_message_2_user(rtc, &msg_data); }; static void _on_fini_notify(byte_rtc_engine_t engine) { rtc_impl_t* rtc = (rtc_impl_t*) byte_rtc_get_user_data(engine); rtc->b_fini = true; } static int __rtc_init(rtc_impl_t* engine, cJSON* p_config) { int ret = 0; int video_codec = 0; int log_level = 0; int params_cnt = 0; cJSON* p_params = NULL; // 初始化结构体中的event_handler字段,避免局部变量导致的悬挂指针问题 // 初始化结构体中的event_handler字段,避免局部变量导致的悬挂指针问题 memset(&engine->event_handler, 0, sizeof(byte_rtc_event_handler_t)); engine->event_handler.on_global_error = _on_global_error; engine->event_handler.on_join_room_success = _on_join_channel_success; engine->event_handler.on_room_error = _on_channel_error; engine->event_handler.on_user_joined = _on_user_joined; engine->event_handler.on_user_offline = _on_user_offline; engine->event_handler.on_user_mute_audio = _on_user_mute_audio; engine->event_handler.on_user_mute_video = _on_user_mute_video; engine->event_handler.on_audio_data = _on_audio_data; engine->event_handler.on_video_data = _on_video_data; engine->event_handler.on_key_frame_gen_req = _on_key_frame_gen_req; engine->event_handler.on_target_bitrate_changed = _on_target_bitrate_changed; engine->event_handler.on_token_privilege_will_expire = _on_token_privilege_will_expire; engine->event_handler.on_message_received = _on_message_received; engine->event_handler.on_message_send_result = _on_message_send_result; engine->event_handler.on_license_expire_warning = _on_license_will_expire; engine->event_handler.on_fini_notify = _on_fini_notify; ret = volc_json_read_int(p_config, "audio.codec", &engine->audio_codec); if (ret != 0 || engine->audio_codec < 0 || engine->audio_codec > AUDIO_CODEC_TYPE_G711U) { LOGE("volc_rtc_create: read audio_codec failed"); return -1; } ret = volc_json_read_int(p_config, "video.codec", &video_codec); if (ret != 0 || video_codec < 0 || video_codec > VIDEO_CODEC_TYPE_BYTEVC1) { LOGE("volc_rtc_create: read video_codec failed"); return -1; } ret = volc_json_read_int(p_config, "log_level", &log_level); if (ret != 0) { log_level = BYTE_RTC_LOG_LEVEL_WARN; // default log level } ret = volc_json_read_bool(p_config, "audio.publish", &engine->b_audio_publish); if (ret != 0) { engine->b_audio_publish = true; // default to publish audio } ret = volc_json_read_bool(p_config, "video.publish", &engine->b_video_publish); if (ret != 0) { engine->b_video_publish = false; // default to no publish video } ret = volc_json_read_bool(p_config, "audio.subscribe", &engine->b_audio_subscribe); if (ret != 0) { engine->b_audio_subscribe = true; // default to subscribe audio } ret = volc_json_read_bool(p_config, "video.subscribe", &engine->b_video_subscribe); if (ret != 0) { engine->b_video_subscribe = false; // default to no subscribe video } engine->rtc = byte_rtc_create(engine->p_appid, &engine->event_handler); if (!engine->rtc) { LOGE("volc_rtc_create: byte_rtc_create failed"); return -1; } byte_rtc_set_user_data(engine->rtc, (void*) engine); byte_rtc_set_log_level(engine->rtc, log_level); ret = volc_json_read_object(p_config, "params", &p_params); if (0 == ret) { params_cnt = cJSON_GetArraySize(p_params); if (params_cnt > 0) { for (int i = 0; i < params_cnt; i++) { cJSON* p_param = cJSON_GetArrayItem(p_params, i); if (p_param && cJSON_IsString(p_param)) { const char* param_str = cJSON_GetStringValue(p_param); if (param_str && strlen(param_str) > 0) { byte_rtc_set_params(engine->rtc, param_str); LOGI("volc_rtc_create: set param[%d]: %s", i, param_str); } } } } cJSON_Delete(p_params); } int init_ret = byte_rtc_init(engine->rtc); if (init_ret != 0) { LOGE("volc_rtc_create: byte_rtc_init failed with ret=%d", init_ret); return -1; } byte_rtc_set_audio_codec(engine->rtc, engine->audio_codec); byte_rtc_set_video_codec(engine->rtc, video_codec - 1); // -1 for default codec return 0; } volc_rtc_t volc_rtc_create(const char* appid, void* context, cJSON* p_config, volc_msg_cb message_callback, volc_data_cb data_callback) { rtc_impl_t* rtc = (rtc_impl_t*) hal_calloc(1, sizeof(rtc_impl_t)); if (!rtc) { LOGE("volc_rtc_create: malloc rtc failed"); return NULL; } rtc->message_callback = message_callback; rtc->data_callback = data_callback; rtc->context = context; // 检查appid是否为空,如果为空则使用默认值 const char* actual_appid = appid; if (!appid || strlen(appid) == 0) { LOGW("appid is empty, using default value"); actual_appid = "default_appid"; } rtc->p_appid = strdup(actual_appid); if (NULL == rtc->p_appid) { LOGE("malloc appid memory failed"); goto err_out_label; } if (__rtc_init(rtc, p_config) != 0) { LOGE("volc_rtc_create: rtc init failed"); goto err_out_label; } LOGD("rtc create success"); return (volc_rtc_t)rtc; err_out_label: HAL_SAFE_FREE(rtc->p_appid); HAL_SAFE_FREE(rtc); return NULL; } void volc_rtc_destroy(volc_rtc_t handle) { rtc_impl_t* rtc = (rtc_impl_t*)handle; if (!rtc) { LOGE("rtc instance is NULL"); return; } __rtc_stop(rtc); byte_rtc_fini(rtc->rtc); while (!rtc->b_fini) { usleep(1000 * 10); } byte_rtc_destroy(rtc->rtc); HAL_SAFE_FREE(rtc->p_channel_name); HAL_SAFE_FREE(rtc->p_user_id); HAL_SAFE_FREE(rtc->p_remote_user_id); HAL_SAFE_FREE(rtc->p_token); HAL_SAFE_FREE(rtc); LOGD("rtc destroy success"); } // 新增:extra_params 用于传递额外的AgentConfig配置参数 int volc_rtc_start(volc_rtc_t rtc, const char* bot_id, volc_iot_info_t* iot_info, const char* extra_params) { // int ret = 0; volc_rtc_option_t *opt = NULL; rtc_impl_t* rtc_impl = (rtc_impl_t*) rtc; if (!rtc_impl) { LOGE("rtc instance is NULL"); return -1; } if (!iot_info) { LOGE("iot_info is NULL"); return -1; } // 检查bot_id是否为空,如果为空则使用默认值 const char* actual_bot_id = bot_id; if (!bot_id || strlen(bot_id) == 0) { LOGW("bot_id is empty, using default value"); actual_bot_id = "default_bot"; } char* task_id = "test"; LOGI("volc_rtc_start: bot_id=%s audio_codec=%d heap_free=%u", actual_bot_id, rtc_impl->audio_codec, (unsigned)heap_caps_get_free_size(MALLOC_CAP_DEFAULT)); // 新增:extra_params 用于传递额外的AgentConfig配置参数 if (volc_get_rtc_config(iot_info, rtc_impl->audio_codec, actual_bot_id, task_id, extra_params, &rtc_impl->info)) { LOGE("get rtc config failed"); return -1; } LOGI("volc_get_rtc_config success heap_free=%u", (unsigned)heap_caps_get_free_size(MALLOC_CAP_DEFAULT)); opt = &rtc_impl->info.rtc_opt; return __rtc_start(rtc_impl, opt); } int volc_rtc_stop(volc_rtc_t handle) { rtc_impl_t* rtc = (rtc_impl_t *)handle; if (!rtc) { LOGE("rtc instance is NULL"); return -1; } __rtc_stop(rtc); return 0; } int volc_rtc_send(volc_rtc_t handle, const void* data, int size, volc_data_info_t* data_info) { rtc_impl_t* rtc = (rtc_impl_t *)handle; audio_frame_info_t audio_info = {0}; video_frame_info_t video_info = {0}; if (NULL == rtc || NULL == data || NULL == data_info) { LOGE("input args is invalid, rtc(%p), data(%p), data_info(%p)", rtc, data, data_info); return -1; } if (rtc->p_channel_name == NULL || size <= 0) { LOGE("channel not ready or invalid size"); return -1; } LOGD("volc_rtc_send type=%d size=%d heap_free=%u", data_info->type, size, (unsigned)heap_caps_get_free_size(MALLOC_CAP_DEFAULT)); switch (data_info->type) { case VOLC_DATA_TYPE_AUDIO: { audio_info.data_type = (audio_data_type_e) data_info->info.audio.data_type; byte_rtc_send_audio_data(rtc->rtc, rtc->p_channel_name, data, size, &audio_info); break; } case VOLC_DATA_TYPE_VIDEO: { video_info.data_type = (video_data_type_e) data_info->info.video.data_type; video_info.stream_type = VIDEO_STREAM_HIGH; video_info.frame_type = VIDEO_FRAME_AUTO_DETECT; LOGD("Sending video to channel: %s, data length: %d, data type: %d", rtc->p_channel_name, size, video_info.data_type); byte_rtc_send_video_data(rtc->rtc, rtc->p_channel_name, data, size, &video_info); break; } case VOLC_DATA_TYPE_MESSAGE: { if (rtc->p_remote_user_id == NULL) { LOGW("skip message: remote user not joined"); return -1; } LOGD("Sending message to channel: %s, remote user: %s, data length: %d, is_binary: %d", rtc->p_channel_name, rtc->p_remote_user_id, size, data_info->info.message.is_binary); byte_rtc_rts_send_message(rtc->rtc, rtc->p_channel_name, rtc->p_remote_user_id, data, size, data_info->info.message.is_binary, RTS_MESSAGE_RELIABLE); break; } default: LOGW("unsupported data type: %d", data_info->type); return -1; } return 0; } int volc_rtc_interrupt(volc_rtc_t rtc) { int ret = 0; uint8_t* msg_ctrl = NULL; size_t msg_len = 0; rtc_impl_t* rtc_impl = (rtc_impl_t*) rtc; volc_data_info_t data_info = {0}; if (!rtc_impl) { LOGE("rtc instance is NULL"); return -1; } if (!rtc_impl->b_user_joined || rtc_impl->p_remote_user_id == NULL) { LOGW("interrupt skipped: remote user not joined"); return -1; } if (_build_binary_message(MAGIC_CONTROL, interrupt_str, &msg_ctrl, &msg_len) != 0) { LOGE("build control message failed"); return -1; } data_info.type = VOLC_DATA_TYPE_MESSAGE; data_info.info.message.is_binary = true; if ((ret = volc_rtc_send(rtc, msg_ctrl, msg_len, &data_info)) != 0) { LOGE("send interrupt message failed"); } HAL_SAFE_FREE(msg_ctrl); return ret; } int volc_rtc_send_jpg(volc_rtc_t rtc, void* data, int size) { return 0; }