Baji_Rtc_Toy/main/application.h
Rdzleo 919bf8f28f feat: GIF动画表情系统 + 情绪映射增强 + HTTPS音频中止修复
一、新增功能:
1、新增8种GIF动画表情(200x89) + 3种叠加图标(45x45),实现22种情绪标签到GIF的映射表;
2、新增30+组英文近义词情绪fallback映射(如worried→sad),防止AI使用非标准标签时GIF无法切换;
3、新增HTTPS中止后诊断日志,自动追踪前20帧音频处理流程便于定位无声问题;

二、Bug修复:
4、修复HTTPS播放中止后RTC音频解码参数未恢复(16000/60→8000/20),通过background_task_串行化恢复;
5、修复AbortHttpsPlayback解码器竞态崩溃,将重置/恢复/DMA flush全部串行化执行;
6、修复LVGL gifdec不支持无全局颜色表GIF的问题,支持仅使用局部颜色表的压缩GIF;
7、修复GIF透明区域显示黑色方块,canvas初始alpha改为0x00;
8、修复lv_gif定时器gif对象为NULL时的空指针崩溃;

三、优化:
9、情绪标签从等待is_final改为第一条字幕即时触发GIF切换,新增去重和回复结束自动恢复neutral;
10、对话状态表情映射优化:THINKING→thinking、ANSWERING→happy、INTERRUPTED→surprised;
11、CPU核心绑定:LVGL任务Core0,音频循环Core1,避免GIF解码与音频争抢;
12、中文情绪词映射扩展,新增担心/心疼/着急等映射;

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 15:28:14 +08:00

212 lines
10 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 _APPLICATION_H_
#define _APPLICATION_H_
#include <freertos/FreeRTOS.h>
#include <freertos/event_groups.h>
#include <freertos/task.h>
#include <esp_timer.h>
#include <string>
#include <mutex>
#include <list>
#include <atomic>
#include <opus_encoder.h>
#include <opus_decoder.h>
#include <opus_resampler.h>
#include "protocol.h"
#include "websocket_protocol.h"
#include "ota.h"
#include "background_task.h"
#include "audio/simple_pipeline.h"
// #include "ble_service.h" // BLE JSON Service 暂不使用
#if CONFIG_USE_WAKE_WORD_DETECT
#include "wake_word_detect.h"
#elif CONFIG_USE_CUSTOM_WAKE_WORD
#include "custom_wake_word.h"
#endif
#if CONFIG_USE_AUDIO_PROCESSOR
#include "audio_processor.h"
#endif
#define SCHEDULE_EVENT (1 << 0)
#define AUDIO_INPUT_READY_EVENT (1 << 1)
#define AUDIO_OUTPUT_READY_EVENT (1 << 2)
// 未知状态、启动中、WiFi配网模式、空闲待命、连接服务器、语音监听中、语音播报中、固件升级中、设备激活中、致命错误
enum DeviceState {
kDeviceStateUnknown,
kDeviceStateStarting,
kDeviceStateWifiConfiguring,
kDeviceStateIdle,
kDeviceStateConnecting,
kDeviceStateListening,
kDeviceStateSpeaking,
kDeviceStateDialog,
kDeviceStateUpgrading,
kDeviceStateActivating,
kDeviceStateFatalError
};
// OPUS音频帧时长60ms
#define OPUS_FRAME_DURATION_MS 60
// 应用程序主类(单例模式)
class Application {
public:
static Application& GetInstance() {
static Application instance;
return instance;
}
// 删除拷贝构造函数和赋值运算符
Application(const Application&) = delete;
Application& operator=(const Application&) = delete;
void Start(); // 启动应用程序
DeviceState GetDeviceState() const { return device_state_; } // 获取当前状态
bool IsVoiceDetected() const { return voice_detected_; } // 语音检测状态
void Schedule(std::function<void()> callback); // 任务调度
void SetDeviceState(DeviceState state); // 状态变更
void Alert(const char* status, const char* message, const char* emotion = "", const std::string_view& sound = "");// 警报管理 状态、消息、情感、声音
void DismissAlert();// 关闭警报
void AbortSpeaking(AbortReason reason);// 打断语音播报
void AbortHttpsPlayback(const char* reason);// 中止HTTPS音频播放并清空DMA
void HttpsPlaybackFromUrl(const std::string& url); // 通过HTTPS下载JSON并播放音频故事/歌曲等)
void SendStoryRequest(); // 通过HTTPS故事API请求并播放故事
void SendMusicRequest(); // 通过HTTPS音乐API请求并播放音乐
void ToggleChatState();// 切换聊天状态
void ToggleListeningState();// 切换监听状态
void StartListening();// 开始监听
void StopListening();// 停止监听
void SendTextMessage(const std::string& text);// 发送文本消息
void UpdateIotStates();// 更新IOT设备状态
void Reboot();// 系统重启
void WakeWordInvoke(const std::string& wake_word);// 唤醒词回调
void PlaySound(const std::string_view& sound);// 播放声音
void WaitForAudioPlayback();// 等待音频播报完成
bool IsAudioQueueEmpty(); // 检查音频队列是否为空
void ClearAudioQueue(); // 清空音频播放队列
bool CanEnterSleepMode();// 检查是否可以进入睡眠模式
void StopAudioProcessor();// 停止音频处理器
void ResetDecoder();// 重置解码器状态(用于修复音频播放问题)
bool IsSafeToOperate(); // 🔧 检查当前是否可以安全执行操作
void AbortSpeakingAndReturnToIdle(); // 🔴 专门处理从说话状态到空闲状态的切换
void AbortSpeakingAndReturnToListening(); // 🔵 专门处理从说话状态到聆听状态的切换
void PauseAudioPlayback(); // ⏸️ 暂停音频播放
void ResumeAudioPlayback(); // ▶️ 恢复音频播放
void SuppressNextIdleSound(); // 🔇 抑制下一个空闲状态的声音播放
void SetLowBatteryTransition(bool value);
bool IsLowBatteryTransition() const;
void InitializeWebsocketProtocol(); // 🌐 初始化WebSocket协议RTC连接成功后调用
// void SendTextViaWebsocket(const std::string& text);// 🌐 通过WebSocket发送文本消息
// 姿态传感器接口
bool IsImuSensorAvailable(); // 检查IMU传感器是否可用
bool GetImuData(float* acc_x, float* acc_y, float* acc_z,
float* gyro_x, float* gyro_y, float* gyro_z,
float* temperature); // 获取IMU传感器数据
void OnMotionDetected(); // 运动检测事件处理
bool IsAudioPaused() const { return audio_paused_; } // 检查音频是否暂停
bool ShouldSkipDialogIdleSession() const { return skip_dialog_idle_session_; }// 是否跳过对话待机会话
void ClearDialogIdleSkipSession();// 清除对话待机会话标志位
bool IsDialogUploadEnabled() const { return dialog_upload_enabled_; }// 是否启用对话上传
void SetDialogUploadEnabled(bool enabled);// 设置对话上传状态
// // BLE JSON 命令处理(暂不使用)
// void HandleBleJsonCommand(const std::string& cmd, int msg_id, cJSON* data, BleJsonService& service);
private:
Application();// 构造函数
~Application();// 析构函数
// 配置使用唤醒词检测
#if CONFIG_USE_WAKE_WORD_DETECT
WakeWordDetect wake_word_detect_;
#elif CONFIG_USE_CUSTOM_WAKE_WORD
CustomWakeWord wake_word_detect_;
#endif
// 音频处理器
#if CONFIG_USE_AUDIO_PROCESSOR
AudioProcessor audio_processor_;
#endif
Ota ota_;
std::mutex mutex_;
std::list<std::function<void()>> main_tasks_;
std::unique_ptr<Protocol> protocol_;
std::unique_ptr<WebsocketProtocol> websocket_protocol_; // 🌐 WebSocket协议实例RTC连接后初始化
EventGroupHandle_t event_group_ = nullptr;
esp_timer_handle_t clock_timer_handle_ = nullptr;
volatile DeviceState device_state_ = kDeviceStateUnknown;
std::atomic<bool> is_aborting_{false}; // 🔧 原子标志:防止重复中止操作
std::atomic<std::chrono::steady_clock::time_point> last_safe_operation_; // 🔧 最后安全操作时间戳
std::atomic<bool> is_switching_to_listening_{false}; // 🔵 标志:正在主动切换到聆听状态
std::atomic<bool> is_low_battery_transition_{false};
ListeningMode listening_mode_ = kListeningModeAutoStop;
#if CONFIG_USE_REALTIME_CHAT
bool realtime_chat_enabled_ = true;
#else
bool realtime_chat_enabled_ = false;
#endif
std::atomic<bool> ws_downlink_enabled_{true};// 🌐 WebSocket下行通道是否启用
std::atomic<bool> ws_playback_active_{false};// 🌐 WebSocket下行播放活跃标志
std::atomic<bool> opus_playback_active_{false};// Opus解码播放活跃标志WS/HTTPS共用
std::atomic<bool> https_playback_active_{false};// HTTPS音频播放进行中标志
std::atomic<bool> https_playback_abort_{false};// HTTPS音频播放中止标志
std::atomic<int> post_abort_debug_frames_{0};// HTTPS中止后诊断日志计数追踪前N帧音频
bool aborted_ = false;
bool voice_detected_ = false;
bool audio_paused_ = false; // 音频暂停状态标志
float current_speaker_volume_ = 0.0f; // 当前扬声器音量,用于语音打断判断
bool provisioning_mode_ = false; // 配网模式标志缓存避免重复读NVS
bool first_idle_location_checked_ = false;// 是否首次查询城市天气
bool send_pcm_uplink_ = true; // 是否发送PCM音频数据到服务器由SDK内部转码为G711A
bool send_g711a_uplink_ = false;// 是否直接发送G711A音频数据到服务器
std::chrono::time_point<std::chrono::steady_clock> last_audio_input_time_;
std::chrono::time_point<std::chrono::steady_clock> last_audible_output_time_; // 最后一次有声音输出的时间点
bool skip_dialog_idle_session_; // 是否跳过对话待机会话标志
bool dialog_upload_enabled_ = true; // 对话上传状态标志
bool dialog_watchdog_running_; // 对话看门狗运行标志
int dialog_watchdog_last_logged_; // 对话看门狗上次记录的日志时间
TaskHandle_t dialog_watchdog_task_handle_; // 对话看门狗任务句柄
int clock_ticks_;
TaskHandle_t main_loop_task_handle_;
TaskHandle_t check_new_version_task_handle_;
// Audio encode / decode
TaskHandle_t audio_loop_task_handle_;
BackgroundTask* background_task_;
std::chrono::steady_clock::time_point last_output_time_;
std::list<std::vector<uint8_t>> audio_decode_queue_;
std::unique_ptr<OpusEncoderWrapper> opus_encoder_;
std::unique_ptr<OpusDecoderWrapper> opus_decoder_;
OpusResampler input_resampler_;// 输入音频采样器
OpusResampler reference_resampler_;// 参考音频采样器
OpusResampler output_resampler_;// 输出音频采样器
OpusResampler uplink_resampler_;// 上传音频采样器
player_pipeline_handle_t player_pipeline_ = nullptr;
recorder_pipeline_handle_t recorder_pipeline_ = nullptr;
void MainLoop();// 主事件循环
void OnAudioInput();// 音频输入回调
void OnAudioOutput();// 音频输出回调
void ReadAudio(std::vector<int16_t>& data, int sample_rate, int samples);// 读取音频数据
void SetDecodeSampleRate(int sample_rate, int frame_duration);// 设置解码采样率
void CheckNewVersion();// 检查新固件版本
void ShowActivationCode();// 显示激活码
void OnClockTimer();// 时钟定时器回调
void SetListeningMode(ListeningMode mode);// 设置监听模式
void AudioLoop();// 音频处理循环
bool suppress_next_idle_sound_ = false;// 标志:是否抑制下一个空闲状态的声音播放
void StartDialogWatchdog();// 启动对话看门狗
void StopDialogWatchdog(); // 停止对话看门狗
void HttpsApiPlayback(const char* api_url_base, const char* tag, const char* task_name); // HTTPS API音频播放通用实现
const char* DeviceStateToString(DeviceState state); // 状态枚举转字符串
};
#endif // _APPLICATION_H_