问题(上次提交 d5239cf 之后实测):
1. EnterIdleHibernate 触发的 LeaveRoom() 顺手关闭了 codec output:
LeaveRoom → on_audio_channel_closed_ → player_pipeline_close → EnableOutput(false)
导致后续 PlaySound(P3_KAKA_DAIMING) 入队后 AudioLoop 写不出声音,
WaitForAudioPlayback 3 秒超时退出, 用户听不到待命音。
2. LeaveRoom 调用 volc_rtc_destroy 后 rtc_handle_ = nullptr,
WakeFromHibernate → ToggleChatState → OpenAudioChannel 直接返回 false,
触发 2 秒重试循环, 同时每次失败回退 idle 都重新 PlaySound,
codec 状态震荡产生杂音, 服务端 AI 任务也无法重新加入房间。
方案 C+ 修复:
- Protocol::LeaveRoom() 新增 bool notify_closed=true 参数 (默认行为不变)。
- VolcRtcProtocol::LeaveRoom(notify_closed):
* 只 volc_rtc_stop, 不 volc_rtc_destroy, 保留 rtc_handle_ 供唤醒复用。
* notify_closed=false 时跳过 on_audio_channel_closed_, 不连带关 codec。
- EnterIdleHibernate:
* 调用 LeaveRoom(false) → codec output 保留。
* 手动 background_task_->WaitForCompletion + 清空队列 + 关麦克风。
* SetDeviceState(idle) 后 PlaySound 真正能播出来。
* WaitForAudioPlayback 完才 player_pipeline_close (这里再正常关 output)。
- WakeFromHibernate:
* 先放下 hibernating_ 让 AudioLoop guard 通过, 再 ToggleChatState。
* 因 rtc_handle_ 仍有效, OpenAudioChannel 走 volc_rtc_start 重启路径,
on_audio_channel_opened_ 回调重开 player_pipeline + 灌 200ms silence。
编译: kapi.bin 0x2e6330 (3.04MB), 分区 42% 空闲。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
103 lines
3.9 KiB
C++
103 lines
3.9 KiB
C++
#ifndef PROTOCOL_H
|
||
#define PROTOCOL_H
|
||
|
||
#include <cJSON.h>
|
||
#include <string>
|
||
#include <functional>
|
||
#include <chrono>
|
||
|
||
struct BinaryProtocol3 {
|
||
uint8_t type;
|
||
uint8_t reserved;
|
||
uint16_t payload_size;
|
||
uint8_t payload[];
|
||
} __attribute__((packed));
|
||
|
||
enum AbortReason {
|
||
kAbortReasonNone,
|
||
kAbortReasonWakeWordDetected,
|
||
kAbortReasonVoiceInterrupt
|
||
//kAbortReasonNewStory // websocket推送新故事时中断当前播放
|
||
};
|
||
|
||
enum ListeningMode {
|
||
kListeningModeAutoStop,
|
||
kListeningModeManualStop,
|
||
kListeningModeRealtime // 需要 AEC 支持
|
||
};
|
||
|
||
class Protocol {
|
||
public:
|
||
virtual ~Protocol() = default;
|
||
|
||
inline int server_sample_rate() const {
|
||
return server_sample_rate_;
|
||
}
|
||
inline int server_frame_duration() const {
|
||
return server_frame_duration_;
|
||
}
|
||
inline bool downlink_is_pcm() const {
|
||
return downlink_is_pcm_;
|
||
}
|
||
inline const std::string& session_id() const {
|
||
return session_id_;
|
||
}
|
||
inline void SetSuppressIncomingMessageLog(bool v) { suppress_incoming_message_log_ = v; }
|
||
|
||
void OnIncomingAudio(std::function<void(std::vector<uint8_t>&& data)> callback);
|
||
void OnIncomingJson(std::function<void(const cJSON* root)> callback);
|
||
void OnAudioChannelOpened(std::function<void()> callback);
|
||
void OnAudioChannelClosed(std::function<void()> callback);
|
||
void OnNetworkError(std::function<void(const std::string& message)> callback);
|
||
void OnBotMessage(std::function<void()> callback);
|
||
|
||
virtual void Start() = 0;
|
||
virtual bool OpenAudioChannel() = 0;
|
||
virtual void CloseAudioChannel() = 0;
|
||
// Phase 6 移植:真退出 RTC 房间(释放 License)
|
||
// notify_closed=true: 触发 on_audio_channel_closed_ 回调(默认,兼容老路径)
|
||
// notify_closed=false: 不触发回调,供 EnterIdleHibernate 使用——避免回调里
|
||
// 的 player_pipeline_close → EnableOutput(false) 误关 codec output 导致待命音无声
|
||
virtual void LeaveRoom(bool notify_closed = true) { (void)notify_closed; CloseAudioChannel(); }
|
||
virtual bool IsAudioChannelOpened() const = 0;
|
||
virtual void SendAudio(const std::vector<uint8_t>& data) = 0;
|
||
virtual void SendPcm(const std::vector<uint8_t>& data) {}
|
||
virtual void SendG711A(const std::vector<uint8_t>& data) {}
|
||
virtual void SendWakeWordDetected(const std::string& wake_word);
|
||
virtual void SendStartListening(ListeningMode mode);
|
||
virtual void SendStopListening();
|
||
virtual void SendAbortSpeaking(AbortReason reason);
|
||
virtual void SendTextMessage(const std::string& text);
|
||
virtual void SendStoryRequest(); // 声明 发送讲故事请求 【新增】
|
||
virtual void SendIotDescriptors(const std::string& descriptors);
|
||
virtual void SendIotStates(const std::string& states);
|
||
virtual void SendFunctionResult(const std::string& tool_call_id, const std::string& content) {
|
||
(void)tool_call_id;
|
||
SendTextMessage(content);
|
||
}
|
||
|
||
protected:
|
||
std::function<void(const cJSON* root)> on_incoming_json_;
|
||
std::function<void(std::vector<uint8_t>&& data)> on_incoming_audio_;
|
||
std::function<void()> on_audio_channel_opened_;
|
||
std::function<void()> on_audio_channel_closed_;
|
||
std::function<void(const std::string& message)> on_network_error_;
|
||
std::function<void()> on_bot_message_;
|
||
|
||
int server_sample_rate_ = 24000;
|
||
int server_frame_duration_ = 60;
|
||
bool downlink_is_pcm_ = false;// 是否是PCM格式
|
||
bool error_occurred_ = false;
|
||
std::string session_id_;
|
||
bool start_listening_pending_ = false;// 是否有待处理的监听请求
|
||
ListeningMode pending_listening_mode_ = kListeningModeRealtime;// 待处理的监听模式
|
||
std::chrono::time_point<std::chrono::steady_clock> last_incoming_time_;
|
||
bool suppress_incoming_message_log_ = false;
|
||
|
||
virtual void SendText(const std::string& text) = 0;
|
||
virtual void SetError(const std::string& message);
|
||
virtual bool IsTimeout() const;
|
||
};
|
||
|
||
#endif // PROTOCOL_H
|