支持频谱显示,感谢哈哈哈群友的代码

This commit is contained in:
2025-09-05 19:28:01 +08:00
parent 10edbc6402
commit 536ef62412
9 changed files with 1171 additions and 3571 deletions

View File

@@ -17,6 +17,7 @@
#include <sstream>
#include <algorithm>
#include <cctype> // 为isdigit函数
#include <thread> // 为线程ID比较
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
@@ -185,12 +186,12 @@ static std::string buildUrlWithParams(const std::string &base_url, const std::st
Esp32Music::Esp32Music() : last_downloaded_data_(), current_music_url_(), current_song_name_(),
song_name_displayed_(false), current_lyric_url_(), lyrics_(),
current_lyric_index_(-1), lyric_thread_(), is_lyric_running_(false),
is_playing_(false), is_downloading_(false),
display_mode_(DISPLAY_MODE_LYRICS), is_playing_(false), is_downloading_(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");
ESP_LOGI(TAG, "Music player initialized with default spectrum display mode");
InitializeMp3Decoder();
}
@@ -317,7 +318,7 @@ Esp32Music::~Esp32Music()
ESP_LOGI(TAG, "Music player destroyed successfully");
}
bool Esp32Music::Download(const std::string &song_name)
bool Esp32Music::Download(const std::string &song_name, const std::string &artist_name)
{
ESP_LOGI(TAG, "喵波音律QQ交流群:865754861");
ESP_LOGI(TAG, "Starting to get music details for: %s", song_name.c_str());
@@ -329,8 +330,9 @@ bool Esp32Music::Download(const std::string &song_name)
current_song_name_ = song_name;
// 第一步请求stream_pcm接口获取音频信息
std::string api_url = "https://api.yuafeng.cn/API/ly/mgmusic.php";
std::string full_url = api_url + "?msg=" + url_encode(song_name) + "&n=1";
std::string base_url = "https://music.miao-lab.top";
// std::string full_url = base_url + "/stream_pcm?song=" + url_encode(song_name) + "&artist=" + url_encode(artist_name);
std::string full_url = base_url + "/api?msg=" + url_encode(song_name);
ESP_LOGI(TAG, "Request URL: %s", full_url.c_str());
@@ -368,9 +370,9 @@ bool Esp32Music::Download(const std::string &song_name)
ESP_LOGI(TAG, "HTTP GET Status = %d, content_length = %d", status_code, last_downloaded_data_.length());
ESP_LOGD(TAG, "Complete music details response: %s", last_downloaded_data_.c_str());
// 检查认证响应
if (last_downloaded_data_.find("\"code\": 0") == std::string::npos)
{ // 如果"code"不为0则为失败
// 简单的认证响应检查(可选)
if (last_downloaded_data_.find("ESP32动态密钥验证失败") != std::string::npos)
{
ESP_LOGE(TAG, "Authentication failed for song: %s", song_name.c_str());
return false;
}
@@ -381,155 +383,118 @@ bool Esp32Music::Download(const std::string &song_name)
cJSON *response_json = cJSON_Parse(last_downloaded_data_.c_str());
if (response_json)
{
cJSON *data = cJSON_GetObjectItem(response_json, "data");
// 提取关键信息
if (data)
// 提取data数组
cJSON *data_array = cJSON_GetObjectItem(response_json, "data");
if (cJSON_IsArray(data_array))
{
// 提取关键信息
cJSON *artist = cJSON_GetObjectItem(data, "singer");
cJSON *title = cJSON_GetObjectItem(data, "song");
cJSON *audio_url = cJSON_GetObjectItem(data, "music");
cJSON *lyric_url = cJSON_GetObjectItem(data, "lyric");
if (cJSON_IsString(artist))
cJSON *item = nullptr;
cJSON *min_item = nullptr;
int min_num = INT_MAX;
// 遍历data数组以找到最小num的项
cJSON_ArrayForEach(item, data_array)
{
ESP_LOGI(TAG, "Artist: %s", artist->valuestring);
}
if (cJSON_IsString(title))
{
ESP_LOGI(TAG, "Title: %s", title->valuestring);
}
// 检查audio_url是否有效
if (cJSON_IsString(audio_url) && audio_url->valuestring && strlen(audio_url->valuestring) > 0)
{
ESP_LOGI(TAG, "Audio URL: %s", audio_url->valuestring);
current_music_url_ = audio_url->valuestring;
song_name_displayed_ = false; // 重置歌名显示标志
StartStreaming(current_music_url_);
// 处理歌词URL
if (cJSON_IsString(lyric_url) && lyric_url->valuestring && strlen(lyric_url->valuestring) > 0)
cJSON *num = cJSON_GetObjectItem(item, "num");
if (cJSON_IsNumber(num) && num->valueint < min_num)
{
ESP_LOGI(TAG, "Loading lyrics for: %s", song_name.c_str());
current_lyric_url_ = lyric_url->valuestring;
min_num = num->valueint;
min_item = item;
}
}
if (min_item)
{
// 提取关键信息
cJSON *artist = cJSON_GetObjectItem(min_item, "singer");
cJSON *title = cJSON_GetObjectItem(min_item, "song");
cJSON *music_url = cJSON_GetObjectItem(min_item, "music_url");
cJSON *audio_url = music_url ? cJSON_GetObjectItem(music_url, "standard") : nullptr;
cJSON *lyric = cJSON_GetObjectItem(min_item, "lyric");
cJSON *lyric_url = lyric ? cJSON_GetObjectItem(lyric, "lrc") : nullptr;
if (cJSON_IsString(artist))
{
ESP_LOGI(TAG, "Artist: %s", artist->valuestring);
}
if (cJSON_IsString(title))
{
ESP_LOGI(TAG, "Title: %s", title->valuestring);
}
// 检查audio_url是否有效
if (cJSON_IsString(audio_url) && audio_url->valuestring && strlen(audio_url->valuestring) > 0)
{
ESP_LOGI(TAG, "Audio URL path: %s", audio_url->valuestring);
// 启动歌词下载和显示
if (is_lyric_running_)
// 第二步获取audio_url并开始流式播放
current_music_url_ = audio_url->valuestring;
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)
{
is_lyric_running_ = false;
if (lyric_thread_.joinable())
// 直接返回歌词URL
current_lyric_url_ = lyric_url->valuestring;
// 根据显示模式决定是否启动歌词
if (display_mode_ == DISPLAY_MODE_LYRICS)
{
lyric_thread_.join();
ESP_LOGI(TAG, "Loading lyrics for: %s (lyrics display mode)", song_name.c_str());
// 启动歌词下载和显示
if (is_lyric_running_)
{
is_lyric_running_ = false;
if (lyric_thread_.joinable())
{
lyric_thread_.join();
}
}
is_lyric_running_ = true;
current_lyric_index_ = -1;
lyrics_.clear();
lyric_thread_ = std::thread(&Esp32Music::LyricDisplayThread, this);
}
else
{
ESP_LOGI(TAG, "Lyric URL found but spectrum display mode is active, skipping lyrics");
}
}
else
{
ESP_LOGW(TAG, "No lyric URL found for this song");
}
is_lyric_running_ = true;
current_lyric_index_ = -1;
lyrics_.clear();
lyric_thread_ = std::thread(&Esp32Music::LyricDisplayThread, this);
cJSON_Delete(response_json);
return true;
}
else
{
ESP_LOGW(TAG, "The lyrics URL for this song was not found");
// audio_url为空或无效
ESP_LOGE(TAG, "Audio URL not found or empty for song: %s", song_name.c_str());
ESP_LOGE(TAG, "Failed to find music: 没有找到歌曲 '%s'", song_name.c_str());
cJSON_Delete(response_json);
return false;
}
cJSON_Delete(response_json);
return true;
}
else
{
ESP_LOGE(TAG, "No valid audio URL found, song: %s", song_name.c_str());
ESP_LOGE(TAG, "Unable to find song: '%s'", song_name.c_str());
cJSON_Delete(response_json);
return false;
}
}
else
{
ESP_LOGE(TAG, "The 'data' field was not found in the response data");
cJSON_Delete(response_json);
return false;
}
}
else
{
ESP_LOGE(TAG, "Unable to parse JSON response");
ESP_LOGE(TAG, "Failed to parse JSON response");
}
}
else
{
ESP_LOGE(TAG, "Music API returns empty response");
ESP_LOGE(TAG, "Empty response from music API");
}
return false;
}
bool Esp32Music::Play()
{
if (is_playing_.load())
{ // 使用atomic的load()
ESP_LOGW(TAG, "Music is already playing");
return true;
}
if (last_downloaded_data_.empty())
{
ESP_LOGE(TAG, "No music data to play");
return false;
}
// 清理之前的播放线程
if (play_thread_.joinable())
{
play_thread_.join();
}
// 实际应调用流式播放接口
return StartStreaming(current_music_url_);
}
bool Esp32Music::Stop()
{
if (!is_playing_ && !is_downloading_)
{
ESP_LOGW(TAG, "Music is not playing or downloading");
return true;
}
ESP_LOGI(TAG, "Stopping music playback and download");
// 停止下载和播放
is_downloading_ = false;
is_playing_ = false;
// 重置采样率到原始值
ResetSampleRate();
// 通知所有等待的线程
{
std::lock_guard<std::mutex> lock(buffer_mutex_);
buffer_cv_.notify_all();
}
// 等待线程结束
if (download_thread_.joinable())
{
download_thread_.join();
}
if (play_thread_.joinable())
{
play_thread_.join();
}
// 清空缓冲区
ClearAudioBuffer();
ESP_LOGI(TAG, "Music stopped successfully");
return true;
}
std::string Esp32Music::GetDownloadResult()
{
return last_downloaded_data_;
@@ -587,6 +552,7 @@ bool Esp32Music::StartStreaming(const std::string &music_url)
play_thread_ = std::thread(&Esp32Music::PlayAudioStream, this);
ESP_LOGI(TAG, "Streaming threads started successfully");
return true;
}
@@ -625,6 +591,69 @@ bool Esp32Music::StopStreaming()
buffer_cv_.notify_all();
}
// 等待线程结束避免重复代码让StopStreaming也能等待线程完全停止
if (download_thread_.joinable())
{
download_thread_.join();
ESP_LOGI(TAG, "Download thread joined in StopStreaming");
}
// 等待播放线程结束,使用更安全的方式
if (play_thread_.joinable())
{
// 先设置停止标志
is_playing_ = false;
// 通知条件变量,确保线程能够退出
{
std::lock_guard<std::mutex> lock(buffer_mutex_);
buffer_cv_.notify_all();
}
// 使用超时机制等待线程结束,避免死锁
bool thread_finished = false;
int wait_count = 0;
const int max_wait = 100; // 最多等待1秒
while (!thread_finished && wait_count < max_wait)
{
vTaskDelay(pdMS_TO_TICKS(10));
wait_count++;
// 检查线程是否仍然可join
if (!play_thread_.joinable())
{
thread_finished = true;
break;
}
}
if (play_thread_.joinable())
{
if (wait_count >= max_wait)
{
ESP_LOGW(TAG, "Play thread join timeout, detaching thread");
play_thread_.detach();
}
else
{
play_thread_.join();
ESP_LOGI(TAG, "Play thread joined in StopStreaming");
}
}
}
// 在线程完全结束后只在频谱模式下停止FFT显示
if (display && display_mode_ == DISPLAY_MODE_SPECTRUM)
{
display->stopFft();
ESP_LOGI(TAG, "Stopped FFT display in StopStreaming (spectrum mode)");
}
else if (display)
{
ESP_LOGI(TAG, "Not in spectrum mode, skipping FFT stop in StopStreaming");
}
ESP_LOGI(TAG, "Music streaming stop signal sent");
return true;
}
@@ -862,21 +891,8 @@ void Esp32Music::PlayAudioStream()
continue;
}
// 设备状态检查通过,显示当前播放的歌名或歌词
if (!song_name_displayed_ && !current_lyric_url_.empty())
{
auto &board = Board::GetInstance();
auto display = board.GetDisplay();
if (display)
{
// 格式化歌词显示
std::string formatted_lyric = current_lyric_url_;
display->SetMusicInfo(formatted_lyric.c_str());
ESP_LOGI(TAG, "Displaying lyric: %s", formatted_lyric.c_str());
song_name_displayed_ = true;
}
}
else if (!song_name_displayed_ && !current_song_name_.empty())
// 设备状态检查通过,显示当前播放的歌名
if (!song_name_displayed_ && !current_song_name_.empty())
{
auto &board = Board::GetInstance();
auto display = board.GetDisplay();
@@ -888,6 +904,20 @@ void Esp32Music::PlayAudioStream()
ESP_LOGI(TAG, "Displaying song name: %s", formatted_song_name.c_str());
song_name_displayed_ = true;
}
// 根据显示模式启动相应的显示功能
if (display)
{
if (display_mode_ == DISPLAY_MODE_SPECTRUM)
{
display->start();
ESP_LOGI(TAG, "Display start() called for spectrum visualization");
}
else
{
ESP_LOGI(TAG, "Lyrics display mode active, FFT visualization disabled");
}
}
}
// 如果需要更多MP3数据从缓冲区读取
@@ -1060,6 +1090,18 @@ void Esp32Music::PlayAudioStream()
packet.payload.resize(pcm_size_bytes);
memcpy(packet.payload.data(), final_pcm_data, pcm_size_bytes);
if (final_pcm_data_fft == nullptr)
{
final_pcm_data_fft = (int16_t *)heap_caps_malloc(
final_sample_count * sizeof(int16_t),
MALLOC_CAP_SPIRAM);
}
memcpy(
final_pcm_data_fft,
final_pcm_data,
final_sample_count * sizeof(int16_t));
ESP_LOGD(TAG, "Sending %d PCM samples (%d bytes, rate=%d, channels=%d->1) to Application",
final_sample_count, pcm_size_bytes, mp3_frame_info_.samprate, mp3_frame_info_.nChans);
@@ -1098,23 +1140,28 @@ void Esp32Music::PlayAudioStream()
heap_caps_free(mp3_input_buffer);
}
// 播放结束时清空歌名显示
auto &board = Board::GetInstance();
auto display = board.GetDisplay();
if (display)
{
display->SetMusicInfo(""); // 清空歌名显示
ESP_LOGI(TAG, "Cleared song name display on playback end");
}
// 重置采样率到原始值
ResetSampleRate();
// 播放结束时保持音频输出启用状态让Application管理
// 不在这里禁用音频输出,避免干扰其他音频功能
// 播放结束时进行基本清理但不调用StopStreaming避免线程自我等待
ESP_LOGI(TAG, "Audio stream playback finished, total played: %d bytes", total_played);
ESP_LOGI(TAG, "Performing basic cleanup from play thread");
// 停止播放标志
is_playing_ = false;
// 只在频谱显示模式下才停止FFT显示
if (display_mode_ == DISPLAY_MODE_SPECTRUM)
{
auto &board = Board::GetInstance();
auto display = board.GetDisplay();
if (display)
{
display->stopFft();
ESP_LOGI(TAG, "Stopped FFT display from play thread (spectrum mode)");
}
}
else
{
ESP_LOGI(TAG, "Not in spectrum mode, skipping FFT stop");
}
}
// 清空音频缓冲区
@@ -1597,4 +1644,15 @@ void Esp32Music::UpdateLyricDisplay(int64_t current_time_ms)
*/
// 删除复杂的AddAuthHeaders方法使用简单的静态函数
// 删除复杂的认证验证和配置方法,使用简单的静态函数
// 删除复杂的认证验证和配置方法,使用简单的静态函数
// 显示模式控制方法实现
void Esp32Music::SetDisplayMode(DisplayMode mode)
{
DisplayMode old_mode = display_mode_.load();
display_mode_ = mode;
ESP_LOGI(TAG, "Display mode changed from %s to %s",
(old_mode == DISPLAY_MODE_SPECTRUM) ? "SPECTRUM" : "LYRICS",
(mode == DISPLAY_MODE_SPECTRUM) ? "SPECTRUM" : "LYRICS");
}