Rdzleo a3a476f857 feat: Phase 6 软退出方案 C+ — 修复待命音无声 + 唤醒重连失败
问题(上次提交 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>
2026-05-15 17:43:29 +08:00

103 lines
3.9 KiB
C++
Raw 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.

#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