toy-Kapi_Rtc/02Kapi_Rtc_火山RTC替换实现方案.md
2026-01-20 16:55:17 +08:00

622 lines
20 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Kapi_Rtc项目火山RTC替换实现方案
## 1. 背景概述
当前Kapi_Rtc项目使用WebSocket协议进行音频数据传输和通信但由于业务需求变化需要替换为火山RTCByte RTC协议。本方案基于对Airhub_Rtc_h项目中火山RTC实现的分析结合Kapi_Rtc项目的现有架构提供详细的替换实现方案。
## 2. 技术分析
### 2.1 现有WebSocket实现
Kapi_Rtc项目当前使用`WebsocketProtocol`类实现通信,主要功能包括:
- 建立和维护WebSocket连接
- 发送和接收音频数据
- 发送文本消息
- 处理连接状态变化
主要接口:
```cpp
void Start() override;
void SendAudio(const std::vector<uint8_t>& data) override;
bool OpenAudioChannel() override;
void CloseAudioChannel() override;
bool IsAudioChannelOpened() const override;
void SendText(const std::string& text) override;
```
### 2.2 火山RTC实现来自Airhub_Rtc_h
Airhub_Rtc_h项目提供了完整的火山RTC封装主要功能包括
- RTC引擎初始化和销毁
- 加入/离开房间
- 发送音频/视频/消息数据
- 处理RTC事件回调
主要接口:
```cpp
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 handle);
int volc_rtc_start(volc_rtc_t rtc, const char* bot_id, volc_iot_info_t* iot_info);
int volc_rtc_stop(volc_rtc_t handle);
int volc_rtc_send(volc_rtc_t handle, const void* data, int size, volc_data_info_t* data_info);
int volc_rtc_interrupt(volc_rtc_t rtc);
```
### 2.3 架构对比
| 特性 | WebSocket | 火山RTC |
|------|-----------|---------|
| 连接管理 | 简单的TCP连接 | 复杂的RTC连接管理ICE/STUN/TURN |
| 音频质量 | 依赖网络质量 | 自适应网络,支持丢包重传和抗抖动 |
| 延迟 | 相对较高 | 低延迟(适合实时通信) |
| 功能丰富度 | 基础的消息和二进制数据传输 | 提供完整的RTC功能音频/视频/消息) |
| 集成复杂度 | 低 | 中等 |
## 3. 替换方案
### 3.1 架构设计
保留Kapi_Rtc项目现有的`Protocol`抽象接口,创建新的`VolcRtcProtocol`类继承自`Protocol`,实现与`WebsocketProtocol`相同的接口,从而实现无缝替换。
```
┌─────────────────┐
│ Application │
└────────┬────────┘
┌────────▼────────┐
│ Protocol │
└────────┬────────┘
├───────────────┐
┌────────▼────────┐ ┌─────▼──────────┐
│WebsocketProtocol│ │VolcRtcProtocol │
└─────────────────┘ └────────────────┘
```
### 3.2 实现步骤
#### 3.2.1 引入火山RTC依赖
1. 将Airhub_Rtc_h项目中的火山RTC相关文件复制到Kapi_Rtc项目
- `/components/volc_engine_rtc_lite/` - 火山RTC SDK
- `/components/common/src/volc_rtc.c` - RTC封装实现
- `/components/common/include/volc_rtc.h` - RTC封装头文件
2. 更新CMakeLists.txt文件添加火山RTC组件
```cmake
# 添加火山RTC组件
set(COMPONENTS ${COMPONENTS} volc_engine_rtc_lite)
set(COMPONENTS ${COMPONENTS} common)
```
#### 3.2.2 SDK配置与鉴权
火山RTC需要以下关键配置项进行鉴权和连接
1.`Kconfig.projbuild`中添加配置项:
```
config VOLC_INSTANCE_ID
string "volcano instance id"
default ""
help
Instance ID for Volc RTC service.
config VOLC_PRODUCT_KEY
string "volcano product key"
default ""
help
Product Key for Volc RTC service.
config VOLC_PRODUCT_SECRET
string "volcano product secret"
default ""
help
Product Secret for Volc RTC service.
config VOLC_DEVICE_NAME
string "volcano device name"
default ""
help
Device Name for Volc RTC service.
config VOLC_BOT_ID
string "volcano bot id"
default ""
help
Bot ID for Volc RTC service.
config USE_VOLC_RTC
bool "Use Volc RTC instead of WebSocket"
default n
help
Select to use Volc RTC protocol instead of WebSocket.
```
2.`sdkconfig.defaults`中设置默认值:
```
# Volc RTC Configuration
CONFIG_VOLC_INSTANCE_ID="your_instance_id"
CONFIG_VOLC_PRODUCT_KEY="your_product_key"
CONFIG_VOLC_PRODUCT_SECRET="your_product_secret"
CONFIG_VOLC_DEVICE_NAME="your_device_name"
CONFIG_VOLC_BOT_ID="your_bot_id"
CONFIG_USE_VOLC_RTC=y
```
3. 创建配置JSON模板
```cpp
// 对话AI配置格式
#define CONV_AI_CONFIG_FORMAT "{\"ver\": 1,\"iot\":{\"instance_id\":\"%s\",\"product_key\":\"%s\",\"product_secret\":\"%s\",\"device_name\":\"%s\"},\"rtc\":{\"log_level\":1,\"audio\":{\"publish\":true,\"subscribe\":true,\"codec\":4},\"video\":{\"publish\":false,\"subscribe\":false,\"codec\":1},\"params\":[\"{\\\"debug\\\":{\\\"log_to_console\\\":1}}\",\"{\\\"audio\\\":{\\\"codec\\\":{\\\"internal\\\":{\\\"enable\\\":1}}}}\",\"{\\\"rtc\\\":{\\\"access\\\":{\\\"concurrent_requests\\\":1}}}\",\"{\\\"rtc\\\":{\\\"ice\\\":{\\\"concurrent_agents\\\":1}}}\"]}}"
```
#### 3.2.3 创建VolcRtcProtocol类
创建新的`volc_rtc_protocol.h``volc_rtc_protocol.cc`文件,实现`Protocol`接口:
```cpp
// volc_rtc_protocol.h
#ifndef _VOLC_RTC_PROTOCOL_H_
#define _VOLC_RTC_PROTOCOL_H_
#include "protocol.h"
#include "volc_rtc.h"
#include <mutex>
#include <cJSON.h>
class VolcRtcProtocol : public Protocol {
public:
VolcRtcProtocol();
~VolcRtcProtocol();
void Start() override;
void SendAudio(const std::vector<uint8_t>& data) override;
bool OpenAudioChannel() override;
void CloseAudioChannel() override;
bool IsAudioChannelOpened() const override;
private:
void SendText(const std::string& text) override;
void OnRtcMessage(volc_msg_t* msg);
void OnRtcData(const void* data, int data_len, volc_data_info_t* info);
volc_rtc_t rtc_handle_ = nullptr;
std::mutex rtc_mutex_;
bool is_audio_channel_opened_ = false;
cJSON* rtc_config_ = nullptr;
};
#endif
```
```cpp
// volc_rtc_protocol.cc
#include "volc_rtc_protocol.h"
#include "board.h"
#include "system_info.h"
#include "application.h"
#include "assets/lang_config.h"
#include <cstring>
#include <cJSON.h>
#include <esp_log.h>
#define TAG "VolcRTC"
// RTC消息回调函数
static void rtc_message_callback(void* context, volc_msg_t* msg) {
if (context) {
((VolcRtcProtocol*)context)->OnRtcMessage(msg);
}
}
// RTC数据回调函数
static void rtc_data_callback(void* context, const void* data, int data_len, volc_data_info_t* info) {
if (context) {
((VolcRtcProtocol*)context)->OnRtcData(data, data_len, info);
}
}
VolcRtcProtocol::VolcRtcProtocol() {
// 创建完整的火山RTC配置
char config_buf[1024] = {0};
// 使用配置模板构建完整配置
snprintf(config_buf, sizeof(config_buf), CONV_AI_CONFIG_FORMAT,
CONFIG_VOLC_INSTANCE_ID,
CONFIG_VOLC_PRODUCT_KEY,
CONFIG_VOLC_PRODUCT_SECRET,
CONFIG_VOLC_DEVICE_NAME);
// 解析配置JSON
rtc_config_ = cJSON_Parse(config_buf);
if (!rtc_config_) {
ESP_LOGE(TAG, "Failed to parse RTC config");
rtc_config_ = cJSON_CreateObject();
// 设置默认配置
cJSON_AddNumberToObject(rtc_config_, "audio.codec", AUDIO_CODEC_TYPE_OPUS);
cJSON_AddNumberToObject(rtc_config_, "video.codec", VIDEO_CODEC_TYPE_NONE);
cJSON_AddBoolToObject(rtc_config_, "audio.publish", true);
cJSON_AddBoolToObject(rtc_config_, "video.publish", false);
cJSON_AddBoolToObject(rtc_config_, "audio.subscribe", true);
cJSON_AddBoolToObject(rtc_config_, "video.subscribe", false);
cJSON_AddNumberToObject(rtc_config_, "log_level", BYTE_RTC_LOG_LEVEL_INFO);
}
}
VolcRtcProtocol::~VolcRtcProtocol() {
CloseAudioChannel();
if (rtc_config_) {
cJSON_Delete(rtc_config_);
}
}
void VolcRtcProtocol::Start() {
// RTC协议在OpenAudioChannel时启动这里不需要额外操作
}
bool VolcRtcProtocol::OpenAudioChannel() {
std::lock_guard<std::mutex> lock(rtc_mutex_);
if (rtc_handle_ != nullptr) {
ESP_LOGW(TAG, "RTC handle already exists");
return true;
}
// 创建RTC实例 - 传入完整配置,包括鉴权信息
const char* app_id = CONFIG_VOLC_INSTANCE_ID; // 使用实例ID作为app_id
rtc_handle_ = volc_rtc_create(app_id, this, rtc_config_, rtc_message_callback, rtc_data_callback);
if (rtc_handle_ == nullptr) {
ESP_LOGE(TAG, "Failed to create RTC instance");
SetError(Lang::Strings::SERVER_ERROR);
return false;
}
// 构造IoT信息
volc_iot_info_t iot_info = {0};
// 设置设备ID
strncpy(iot_info.device_id, SystemInfo::GetMacAddress().c_str(), sizeof(iot_info.device_id));
// 设置产品信息用于鉴权
strncpy(iot_info.product_key, CONFIG_VOLC_PRODUCT_KEY, sizeof(iot_info.product_key));
strncpy(iot_info.product_secret, CONFIG_VOLC_PRODUCT_SECRET, sizeof(iot_info.product_secret));
strncpy(iot_info.device_name, CONFIG_VOLC_DEVICE_NAME, sizeof(iot_info.device_name));
// 设置房间信息
const char* bot_id = CONFIG_VOLC_BOT_ID;
// 启动RTC并加入房间 - 内部会处理token生成和鉴权
int ret = volc_rtc_start(rtc_handle_, bot_id, &iot_info);
if (ret != 0) {
ESP_LOGE(TAG, "Failed to start RTC: %d", ret);
volc_rtc_destroy(rtc_handle_);
rtc_handle_ = nullptr;
SetError(Lang::Strings::SERVER_ERROR);
return false;
}
is_audio_channel_opened_ = true;
if (on_audio_channel_opened_ != nullptr) {
on_audio_channel_opened_();
}
return true;
}
void VolcRtcProtocol::CloseAudioChannel() {
std::lock_guard<std::mutex> lock(rtc_mutex_);
if (rtc_handle_ != nullptr) {
// 停止RTC
volc_rtc_stop(rtc_handle_);
// 销毁RTC实例
volc_rtc_destroy(rtc_handle_);
rtc_handle_ = nullptr;
}
is_audio_channel_opened_ = false;
if (on_audio_channel_closed_ != nullptr) {
on_audio_channel_closed_();
}
}
bool VolcRtcProtocol::IsAudioChannelOpened() const {
return is_audio_channel_opened_;
}
void VolcRtcProtocol::SendAudio(const std::vector<uint8_t>& data) {
std::lock_guard<std::mutex> lock(rtc_mutex_);
if (!is_audio_channel_opened_ || rtc_handle_ == nullptr) {
ESP_LOGD(TAG, "RTC not connected, dropping audio data");
return;
}
// 构造音频数据信息
volc_data_info_t data_info = {0};
data_info.type = VOLC_DATA_TYPE_AUDIO;
data_info.info.audio.data_type = VOLC_AUDIO_DATA_TYPE_OPUS;
// 发送音频数据
int ret = volc_rtc_send(rtc_handle_, data.data(), data.size(), &data_info);
if (ret != 0) {
ESP_LOGE(TAG, "Failed to send audio data: %d", ret);
}
}
void VolcRtcProtocol::SendText(const std::string& text) {
std::lock_guard<std::mutex> lock(rtc_mutex_);
if (!is_audio_channel_opened_ || rtc_handle_ == nullptr) {
ESP_LOGD(TAG, "RTC not connected, dropping text message");
return;
}
// 构造消息数据信息
volc_data_info_t data_info = {0};
data_info.type = VOLC_DATA_TYPE_MESSAGE;
data_info.info.message.is_binary = false;
// 发送文本消息
int ret = volc_rtc_send(rtc_handle_, text.c_str(), text.size(), &data_info);
if (ret != 0) {
ESP_LOGE(TAG, "Failed to send text message: %d", ret);
}
}
void VolcRtcProtocol::OnRtcMessage(volc_msg_t* msg) {
if (!msg) {
return;
}
switch (msg->code) {
case VOLC_MSG_CONNECTED:
ESP_LOGI(TAG, "RTC connected");
break;
case VOLC_MSG_DISCONNECTED:
ESP_LOGI(TAG, "RTC disconnected");
CloseAudioChannel();
break;
case VOLC_MSG_USER_JOINED:
ESP_LOGI(TAG, "Remote user joined");
break;
case VOLC_MSG_USER_OFFLINE:
ESP_LOGI(TAG, "Remote user offline");
break;
case VOLC_MSG_CONV_STATUS:
ESP_LOGI(TAG, "Conversation status: %d", msg->data.conv_status);
break;
default:
ESP_LOGD(TAG, "Unhandled RTC message: %d", msg->code);
break;
}
}
void VolcRtcProtocol::OnRtcData(const void* data, int data_len, volc_data_info_t* info) {
if (!data || !info) {
return;
}
switch (info->type) {
case VOLC_DATA_TYPE_AUDIO:
if (on_incoming_audio_ != nullptr) {
on_incoming_audio_(std::vector<uint8_t>((uint8_t*)data, (uint8_t*)data + data_len));
}
break;
case VOLC_DATA_TYPE_MESSAGE:
if (info->info.message.is_binary) {
// 处理二进制消息
ESP_LOGD(TAG, "Received binary message, length: %d", data_len);
} else {
// 处理文本消息
std::string text((char*)data, data_len);
auto root = cJSON_Parse(text.c_str());
if (root != nullptr) {
if (on_incoming_json_ != nullptr) {
on_incoming_json_(root);
}
cJSON_Delete(root);
}
}
break;
default:
ESP_LOGD(TAG, "Unhandled RTC data type: %d", info->type);
break;
}
last_incoming_time_ = std::chrono::steady_clock::now();
}
```
#### 3.2.4 更新Protocol工厂
修改创建协议实例的代码根据配置选择使用WebSocket或火山RTC
```cpp
// 在application.cc或相关文件中
#include "websocket_protocol.h"
#include "volc_rtc_protocol.h"
// 创建协议实例
#ifdef CONFIG_USE_VOLC_RTC
protocol_ = new VolcRtcProtocol();
#else
protocol_ = new WebsocketProtocol();
#endif
```
#### 3.2.5 添加语音打断支持
集成火山RTC的语音打断功能
```cpp
// 在volc_rtc_protocol.h中添加
int SendInterrupt();
// 在volc_rtc_protocol.cc中实现
int VolcRtcProtocol::SendInterrupt() {
std::lock_guard<std::mutex> lock(rtc_mutex_);
if (!is_audio_channel_opened_ || rtc_handle_ == nullptr) {
ESP_LOGD(TAG, "RTC not connected, cannot send interrupt");
return -1;
}
return volc_rtc_interrupt(rtc_handle_);
}
```
`Application`类中添加打断功能:
```cpp
// 在application.h中添加
void SendInterrupt();
// 在application.cc中实现
void Application::SendInterrupt() {
if (!protocol_) {
ESP_LOGE(TAG, "Protocol not initialized");
return;
}
#ifdef CONFIG_USE_VOLC_RTC
auto* rtc_protocol = dynamic_cast<VolcRtcProtocol*>(protocol_);
if (rtc_protocol) {
rtc_protocol->SendInterrupt();
}
#endif
}
```
## 4. 业务功能实现
### 4.1 完整RTC连接通讯实现链路
#### 4.1.1 初始化与鉴权流程
1. **配置加载**从Kconfig和sdkconfig加载火山RTC配置INSTANCE_ID, PRODUCT_KEY, PRODUCT_SECRET, DEVICE_NAME, BOT_ID
2. **时间同步**初始化SNTP服务确保设备时间与服务器同步RTC连接需要准确时间戳
3. **配置构建**使用配置模板构建完整的JSON配置字符串包含鉴权信息
4. **引擎初始化**:调用`volc_rtc_create()`创建RTC实例传入配置信息
5. **IoT信息准备**构建包含设备ID、产品信息的IoT结构体
6. **加入房间**:调用`volc_rtc_start()`加入RTC房间内部自动处理token生成和鉴权
#### 4.1.2 启动引擎对话
1. **用户触发**:用户通过唤醒词或按钮触发对话
2. **开始监听**`Application`类调用`protocol_->SendStartListening()`
3. **指令发送**通过火山RTC发送开始监听指令给AI引擎
4. **音频采集**:启动麦克风采集音频数据
5. **音频发送**:将处理后的音频数据通过`protocol_->SendAudio()`发送给火山RTC
6. **AI处理**火山RTC将音频数据转发给AI引擎进行处理
7. **结果返回**AI引擎处理结果通过火山RTC返回给设备
8. **音频播放**:接收到的音频数据通过`on_incoming_audio_`回调传递给`Application`类播放
#### 4.1.3 关闭引擎
1. **对话结束**AI引擎返回对话结束信号或用户手动触发关闭
2. **离开房间**`Application`类调用`protocol_->CloseAudioChannel()`
3. **停止RTC**:调用`volc_rtc_stop()`停止RTC服务
4. **销毁实例**:调用`volc_rtc_destroy()`销毁RTC实例释放资源
### 4.2 鉴权机制详解
火山RTC使用基于产品密钥的鉴权机制
1. **配置信息**设备需要提前配置INSTANCE_ID, PRODUCT_KEY, PRODUCT_SECRET, DEVICE_NAME
2. **Token生成**RTC SDK内部使用产品密钥生成临时token
3. **Token使用**:在`byte_rtc_join_room()`调用时传入token进行身份验证
4. **Token过期处理**SDK监听`on_token_privilege_will_expire`事件在token即将过期时自动更新
### 4.3 音频数据流程
1. **音频输入**:麦克风采集的音频数据经过`AudioProcessor`处理后,通过`protocol_->SendAudio()`发送给火山RTC
2. **音频传输**火山RTC使用优化的RTC协议传输音频数据支持丢包重传和抗抖动
3. **AI处理**火山引擎接收音频数据进行语音识别和AI处理
4. **结果返回**AI处理结果音频和文本通过火山RTC返回给设备
5. **音频输出**:接收到的音频数据通过`on_incoming_audio_`回调传递给`Application`类,然后播放出来
### 4.4 消息处理
1. **文本消息**:通过`protocol_->SendText()`发送文本消息
2. **JSON消息**接收到的JSON消息通过`on_incoming_json_`回调传递给`Application`类进行处理
3. **二进制消息**:支持发送和接收二进制消息,用于特殊功能扩展
## 5. 测试和验证
### 5.1 功能测试
1. **连接测试**验证设备能否成功连接到火山RTC服务器
2. **音频传输测试**:验证音频数据能否正常发送和接收
3. **语音打断测试**:验证语音打断功能能否正常工作
4. **异常处理测试**:验证网络异常或服务器异常时的处理逻辑
### 5.2 性能测试
1. **延迟测试**:测量音频从采集到播放的延迟
2. **稳定性测试**:长时间运行测试,验证系统稳定性
3. **资源占用测试**监控CPU和内存占用情况
### 5.3 兼容性测试
1. **设备兼容性**:测试在不同硬件设备上的运行情况
2. **网络兼容性**:测试在不同网络环境下的运行情况
## 6. 注意事项
1. **配置管理**
- 确保火山RTC的INSTANCE_ID, PRODUCT_KEY, PRODUCT_SECRET, DEVICE_NAME和BOT_ID等配置正确
- 避免将敏感信息如PRODUCT_SECRET硬编码到代码中应通过配置文件管理
- 定期更新密钥,确保安全性
2. **时间同步**
- RTC连接需要准确的设备时间确保SNTP服务正常工作
- 在网络不稳定时考虑使用本地RTC作为备用时间源
3. **错误处理**
- 完善异常处理逻辑,特别是网络异常和服务器异常情况
- 实现token过期自动更新机制
- 添加连接重试逻辑,提高系统稳定性
4. **资源管理**
- 合理管理RTC资源避免内存泄漏
- 在不需要RTC服务时及时关闭连接
- 监控系统资源占用情况,避免资源耗尽
5. **日志管理**
- 添加详细的日志,便于调试和问题定位
- 分类管理日志级别,在生产环境中降低日志级别以减少性能开销
6. **版本兼容性**
- 确保火山RTC SDK版本与项目兼容
- 定期更新SDK获取最新的功能和安全修复
7. **网络环境**
- 确保设备处于稳定的网络环境中
- 考虑网络切换场景如WiFi/4G切换的处理逻辑
## 7. 结论
本方案通过创建新的`VolcRtcProtocol`类实现了与`WebsocketProtocol`相同的接口从而实现了无缝替换。火山RTC提供了更丰富的功能和更好的音频质量能够满足实时通信的需求。同时本方案保持了Kapi_Rtc项目的现有架构不变降低了迁移风险和成本。
## 8. 后续支持
如果您需要进一步的帮助,我可以:
1. 协助实现文档中的方案,包括代码编写和配置
2. 回答关于RTC连接鉴权和SDK配置的具体问题
3. 提供完整的实时AI对话业务实现链路的技术支持
4. 帮助进行测试和调试确保RTC连接和音频传输正常工作
5. 优化系统性能,提高实时对话的稳定性和响应速度
请随时告诉我您的需求。