Update to 2.0.1

This commit is contained in:
2025-09-15 22:04:01 +08:00
parent 5c43129024
commit 7d7f5eae3d
74 changed files with 5253 additions and 439 deletions

View File

@@ -2,19 +2,21 @@
#include "system_info.h"
#include "settings.h"
#include "display/display.h"
#include "display/oled_display.h"
#include "assets/lang_config.h"
#include "esp32_music.h"
#include <esp_log.h>
#include <esp_ota_ops.h>
#include <esp_chip_info.h>
#include <esp_random.h>
#include "esp32_music.h"
#define TAG "Board"
Board::Board() {
music_ = nullptr; // 先初始化为空指针
Settings settings("board", true);
uuid_ = settings.GetString("uuid");
if (uuid_.empty()) {
@@ -22,10 +24,6 @@ Board::Board() {
settings.SetString("uuid", uuid_);
}
ESP_LOGI(TAG, "UUID=%s SKU=%s", uuid_.c_str(), BOARD_NAME);
// 初始化音乐播放器
music_ = new Esp32Music();
ESP_LOGI(TAG, "Music player initialized for all boards");
}
Board::~Board() {
@@ -173,6 +171,21 @@ std::string Board::GetSystemInfoJson() {
json += R"("label":")" + std::string(ota_partition->label) + R"(")";
json += R"(},)";
// Append display info
auto display = GetDisplay();
if (display) {
json += R"("display":{)";
if (dynamic_cast<OledDisplay*>(display)) {
json += R"("monochrome":)" + std::string("true") + R"(,)";
} else {
json += R"("monochrome":)" + std::string("false") + R"(,)";
}
json += R"("width":)" + std::to_string(display->width()) + R"(,)";
json += R"("height":)" + std::to_string(display->height()) + R"(,)";
json.pop_back(); // Remove the last comma
}
json += R"(},)";
json += R"("board":)" + GetBoardJson();
// Close the JSON object

View File

@@ -11,9 +11,10 @@
#include "led/led.h"
#include "backlight.h"
#include "camera.h"
#include "music.h"
#include "assets.h"
#include "music.h"
void* create_board();
class AudioCodec;
@@ -32,6 +33,7 @@ protected:
// 音乐播放器实例
Music* music_;
public:
static Board& GetInstance() {
static Board* instance = static_cast<Board*>(create_board());
@@ -64,4 +66,4 @@ void* create_board() { \
return new BOARD_CLASS_NAME(); \
}
#endif // BOARD_H
#endif // BOARD_H

View File

@@ -63,33 +63,26 @@ bool Esp32Camera::Capture() {
// 显示预览图片
auto display = dynamic_cast<LvglDisplay*>(Board::GetInstance().GetDisplay());
if (display != nullptr) {
// Create a new preview image
auto img_dsc = (lv_img_dsc_t*)heap_caps_calloc(1, sizeof(lv_img_dsc_t), MALLOC_CAP_8BIT);
img_dsc->header.magic = LV_IMAGE_HEADER_MAGIC;
img_dsc->header.cf = LV_COLOR_FORMAT_RGB565;
img_dsc->header.flags = 0;
img_dsc->header.w = fb_->width;
img_dsc->header.h = fb_->height;
img_dsc->header.stride = fb_->width * 2;
img_dsc->data_size = fb_->width * fb_->height * 2;
img_dsc->data = (uint8_t*)heap_caps_malloc(img_dsc->data_size, MALLOC_CAP_SPIRAM);
if (img_dsc->data == nullptr) {
auto data = (uint8_t*)heap_caps_malloc(fb_->len, MALLOC_CAP_SPIRAM);
if (data == nullptr) {
ESP_LOGE(TAG, "Failed to allocate memory for preview image");
heap_caps_free(img_dsc);
return false;
}
auto src = (uint16_t*)fb_->buf;
auto dst = (uint16_t*)img_dsc->data;
auto dst = (uint16_t*)data;
size_t pixel_count = fb_->len / 2;
for (size_t i = 0; i < pixel_count; i++) {
// 交换每个16位字内的字节
dst[i] = __builtin_bswap16(src[i]);
}
display->SetPreviewImage(img_dsc);
auto image = std::make_unique<LvglAllocatedImage>(data, fb_->len, fb_->width, fb_->height, fb_->width * 2, LV_COLOR_FORMAT_RGB565);
display->SetPreviewImage(std::move(image));
}
return true;
}
bool Esp32Camera::SetHMirror(bool enabled) {
sensor_t *s = esp_camera_sensor_get();
if (s == nullptr) {

View File

@@ -166,11 +166,12 @@ Esp32Music::Esp32Music() : last_downloaded_data_(), current_music_url_(), curren
song_name_displayed_(false), current_lyric_url_(), lyrics_(),
current_lyric_index_(-1), lyric_thread_(), is_lyric_running_(false),
display_mode_(DISPLAY_MODE_LYRICS), is_playing_(false), is_downloading_(false),
play_thread_(), download_thread_(), audio_buffer_(), buffer_mutex_(),
is_paused_(false), play_thread_(), download_thread_(), audio_buffer_(), buffer_mutex_(),
buffer_cv_(), buffer_size_(0), mp3_decoder_(nullptr), mp3_frame_info_(),
mp3_decoder_initialized_(false) {
ESP_LOGI(TAG, "Music player initialized with default spectrum display mode");
InitializeMp3Decoder();
// 延迟MP3解码器初始化避免在构造函数中初始化导致的问题
// InitializeMp3Decoder();
}
Esp32Music::~Esp32Music() {
@@ -282,9 +283,9 @@ Esp32Music::~Esp32Music() {
}
bool Esp32Music::Download(const std::string& song_name, const std::string& artist_name) {
ESP_LOGI(TAG, "Starting to get music details for: %s", song_name.c_str());
ESP_LOGI(TAG, "云端由MeowEmbeddedMusicServer喵波音律嵌入式提供");
ESP_LOGI(TAG, "喵波音律QQ交流群:865754861");
ESP_LOGI(TAG, "Starting to get music details for: %s", song_name.c_str());
// 清空之前的下载数据
last_downloaded_data_.clear();
@@ -357,21 +358,17 @@ bool Esp32Music::Download(const std::string& song_name, const std::string& artis
if (cJSON_IsString(audio_url) && audio_url->valuestring && strlen(audio_url->valuestring) > 0) {
ESP_LOGI(TAG, "Audio URL path: %s", audio_url->valuestring);
// 第二步:直接使用audio_url播放音乐
std::string audio_path = audio_url->valuestring;
current_music_url_ = audio_path;
// 第二步:直接使用音频URL开始流式播放
std::string current_music_url_ = audio_url->valuestring;
ESP_LOGI(TAG, "云端由MeowEmbeddedMusicServer喵波音律嵌入式提供");
ESP_LOGI(TAG, "喵波音律QQ交流群:865754861");
ESP_LOGI(TAG, "Starting streaming playback for: %s", song_name.c_str());
song_name_displayed_ = false; // 重置歌名显示标志
StartStreaming(current_music_url_);
// 处理歌词URL - 只有在歌词显示模式下才启动歌词
if (cJSON_IsString(lyric_url) && lyric_url->valuestring && strlen(lyric_url->valuestring) > 0) {
// 直接使用歌词URL
std::string lyric_path = lyric_url->valuestring;
current_lyric_url_ = lyric_path;
// 使用歌词URL获取歌词
std::string current_lyric_url_ = lyric_url->valuestring;
// 根据显示模式决定是否启动歌词
if (display_mode_ == DISPLAY_MODE_LYRICS) {
@@ -431,6 +428,14 @@ bool Esp32Music::StartStreaming(const std::string& music_url) {
ESP_LOGD(TAG, "Starting streaming for URL: %s", music_url.c_str());
// 确保MP3解码器已初始化
if (!mp3_decoder_initialized_) {
if (!InitializeMp3Decoder()) {
ESP_LOGE(TAG, "Failed to initialize MP3 decoder");
return false;
}
}
// 停止之前的播放和下载
is_downloading_ = false;
is_playing_ = false;
@@ -491,6 +496,7 @@ bool Esp32Music::StopStreaming() {
// 停止下载和播放标志
is_downloading_ = false;
is_playing_ = false;
is_paused_ = false; // 重置暂停状态
// 清空歌名显示
auto& board = Board::GetInstance();
@@ -723,8 +729,6 @@ void Esp32Music::PlayAudioStream() {
});
}
ESP_LOGI(TAG, "云端由MeowEmbeddedMusicServer喵波音律嵌入式提供");
ESP_LOGI(TAG, "喵波音律QQ交流群:865754861");
ESP_LOGI(TAG, "Starting playback with buffer size: %d", buffer_size_);
size_t total_played = 0;
@@ -744,18 +748,20 @@ void Esp32Music::PlayAudioStream() {
bool id3_processed = false;
while (is_playing_) {
// 检查是否被暂停
if (is_paused_) {
ESP_LOGD(TAG, "Music playback paused, waiting...");
vTaskDelay(pdMS_TO_TICKS(100));
continue;
}
// 检查设备状态,只有在空闲状态才播放音乐
auto& app = Application::GetInstance();
DeviceState current_state = app.GetDeviceState();
// 状态转换:说话中-》聆听中-》待机状态-》播放音乐
if (current_state == kDeviceStateListening || current_state == kDeviceStateSpeaking) {
if (current_state == kDeviceStateSpeaking) {
ESP_LOGI(TAG, "Device is in speaking state, switching to listening state for music playback");
}
if (current_state == kDeviceStateListening) {
ESP_LOGI(TAG, "Device is in listening state, switching to idle state for music playback");
}
// 等小智把话说完了,变成聆听状态之后,马上转成待机状态,进入音乐播放
if (current_state == kDeviceStateListening) {
ESP_LOGI(TAG, "Device is in listening state, switching to idle state for music playback");
// 切换状态
app.ToggleChatState(); // 变成待机状态
vTaskDelay(pdMS_TO_TICKS(300));
@@ -1133,8 +1139,6 @@ bool Esp32Music::DownloadLyrics(const std::string& lyric_url) {
add_auth_headers(http.get());
// 打开GET连接
ESP_LOGI(TAG, "云端由MeowEmbeddedMusicServer喵波音律嵌入式提供");
ESP_LOGI(TAG, "喵波音律QQ交流群:865754861");
if (!http->Open("GET", current_url)) {
ESP_LOGE(TAG, "Failed to open HTTP connection for lyrics");
// 移除delete http; 因为unique_ptr会自动管理内存
@@ -1426,4 +1430,99 @@ void Esp32Music::SetDisplayMode(DisplayMode mode) {
ESP_LOGI(TAG, "Display mode changed from %s to %s",
(old_mode == DISPLAY_MODE_SPECTRUM) ? "SPECTRUM" : "LYRICS",
(mode == DISPLAY_MODE_SPECTRUM) ? "SPECTRUM" : "LYRICS");
}
// MCP工具需要的方法实现
bool Esp32Music::SetVolume(int volume) {
ESP_LOGI(TAG, "SetVolume called with volume: %d", volume);
// 验证音量范围
if (volume < 0 || volume > 100) {
ESP_LOGW(TAG, "Invalid volume level: %d, must be between 0-100", volume);
return false;
}
// 通过Board获取AudioCodec并设置音量
auto& board = Board::GetInstance();
auto codec = board.GetAudioCodec();
if (codec) {
codec->SetOutputVolume(volume);
ESP_LOGI(TAG, "Volume set to %d%%", volume);
return true;
} else {
ESP_LOGE(TAG, "No audio codec available");
return false;
}
}
bool Esp32Music::PlaySong() {
ESP_LOGI(TAG, "PlaySong called");
return false;
}
bool Esp32Music::StopSong() {
ESP_LOGI(TAG, "StopSong called");
return StopStreaming();
}
bool Esp32Music::PauseSong() {
ESP_LOGI(TAG, "PauseSong called");
// 检查是否正在播放
if (!is_playing_) {
ESP_LOGW(TAG, "No music is currently playing");
return false;
}
// 检查是否已经暂停
if (is_paused_) {
ESP_LOGW(TAG, "Music is already paused");
return true;
}
// 设置暂停标志
is_paused_ = true;
ESP_LOGI(TAG, "Music playback paused");
// 更新显示状态
auto& board = Board::GetInstance();
auto display = board.GetDisplay();
if (display && !current_song_name_.empty()) {
std::string formatted_song_name = "" + current_song_name_ + "》已暂停";
display->SetMusicInfo(formatted_song_name.c_str());
ESP_LOGI(TAG, "Updated display: %s", formatted_song_name.c_str());
}
return true;
}
bool Esp32Music::ResumeSong() {
ESP_LOGI(TAG, "ResumeSong called");
// 检查是否正在播放
if (!is_playing_) {
ESP_LOGW(TAG, "No music is currently playing");
return false;
}
// 检查是否已经恢复
if (!is_paused_) {
ESP_LOGW(TAG, "Music is not paused");
return true;
}
// 清除暂停标志
is_paused_ = false;
ESP_LOGI(TAG, "Music playback resumed");
// 更新显示状态
auto& board = Board::GetInstance();
auto display = board.GetDisplay();
if (display && !current_song_name_.empty()) {
std::string formatted_song_name = "" + current_song_name_ + "》播放中...";
display->SetMusicInfo(formatted_song_name.c_str());
ESP_LOGI(TAG, "Updated display: %s", formatted_song_name.c_str());
}
return true;
}

View File

@@ -38,7 +38,6 @@ private:
std::string current_music_url_;
std::string current_song_name_;
bool song_name_displayed_;
std::atomic<bool> stop_flag_{false}; // 停止播放标志位
// 歌词相关
std::string current_lyric_url_;
@@ -51,6 +50,7 @@ private:
std::atomic<DisplayMode> display_mode_;
std::atomic<bool> is_playing_;
std::atomic<bool> is_downloading_;
std::atomic<bool> is_paused_;
std::thread play_thread_;
std::thread download_thread_;
int64_t current_play_time_ms_; // 当前播放时间(毫秒)
@@ -102,11 +102,20 @@ public:
virtual bool StopStreaming() override; // 停止流式播放
virtual size_t GetBufferSize() const override { return buffer_size_; }
virtual bool IsDownloading() const override { return is_downloading_; }
virtual bool IsPlaying() const override { return is_playing_; }
virtual bool IsPaused() const override { return is_paused_; }
virtual int16_t* GetAudioData() override { return final_pcm_data_fft; }
// 显示模式控制方法
void SetDisplayMode(DisplayMode mode);
DisplayMode GetDisplayMode() const { return display_mode_.load(); }
// MCP工具需要的方法
virtual bool PlaySong() override;
virtual bool SetVolume(int volume) override;
virtual bool StopSong() override;
virtual bool PauseSong() override;
virtual bool ResumeSong() override;
};
#endif // ESP32_MUSIC_H

View File

@@ -15,7 +15,16 @@ public:
virtual bool StopStreaming() = 0; // 停止流式播放
virtual size_t GetBufferSize() const = 0;
virtual bool IsDownloading() const = 0;
virtual bool IsPlaying() const = 0;
virtual bool IsPaused() const = 0;
virtual int16_t* GetAudioData() = 0;
// MCP工具需要的方法
virtual bool PlaySong() = 0;
virtual bool SetVolume(int volume) = 0;
virtual bool StopSong() = 0;
virtual bool PauseSong() = 0;
virtual bool ResumeSong() = 0;
};
#endif // MUSIC_H