// // Created by misaki on 2025/9/9. // #include "AudioOutput.h" #include "esp_log.h" #include #include "PCM5101.h" // 初始化静态成员 AudioOutput* AudioOutput::instance = nullptr; std::mutex AudioOutput::instanceMutex; AudioOutput* AudioOutput::getInstance() { std::lock_guard lock(instanceMutex); if (instance == nullptr) { instance = new AudioOutput(); } return instance; } AudioOutput::AudioOutput() : currentState(AudioState::IDLE), currentVolume(Volume_MAX - 2), hardwareInitialized(false) { // 初始化硬件 init(); } AudioOutput::~AudioOutput() { // 停止播放并清理资源 stop(); } bool AudioOutput::init() { std::lock_guard lock(stateMutex); if (hardwareInitialized) { ESP_LOGI("AudioOutput", "Audio hardware already initialized"); return true; } // 初始化SD卡 SDFileManager::getInstance()->tryInitSDCard(); // 初始化音频硬件 Audio_Init(); hardwareInitialized = true; ESP_LOGI("AudioOutput", "Audio hardware initialized successfully"); return hardwareInitialized; } void AudioOutput::tryInitAudioOutput() { ESP_LOGI("AudioOutput", "Trying to initialize audio output......"); } bool AudioOutput::playSync(const char* directory, const char* fileName) { std::lock_guard lock(stateMutex); if (!hardwareInitialized) { ESP_LOGE("AudioOutput", "Audio hardware not initialized"); return false; } // 停止当前播放 TODO:有bug,需要fix // stop(); // 播放新文件 Play_Music(directory, fileName); if (audio_player_get_state() == AUDIO_PLAYER_STATE_PLAYING) { currentState = AudioState::PLAYING; currentFilePath = std::string(directory) + "/" + fileName; return true; } currentState = AudioState::ERROR; return false; } void AudioOutput::playAsync(const char* directory, const char* fileName, const AudioCallback& callback) { const ThreadConfig config = getThreadConfig("play_async"); ThreadManager::createThread(config, [this, directory = std::string(directory), fileName = std::string(fileName), callback]() { this->playInternal(directory.c_str(), fileName.c_str(), callback); }).detach(); } void AudioOutput::playInternal(const char* directory, const char* fileName, const AudioCallback& callback) { bool success = false; auto finalState = AudioState::ERROR; { std::lock_guard lock(stateMutex); if (!hardwareInitialized) { ESP_LOGE("AudioOutput", "Audio hardware not initialized"); finalState = AudioState::ERROR; } else { // 停止当前播放 // stop(); // 播放新文件 Play_Music(directory, fileName); if (audio_player_get_state() == AUDIO_PLAYER_STATE_PLAYING) { currentState = AudioState::PLAYING; currentFilePath = std::string(directory) + "/" + fileName; success = true; finalState = AudioState::PLAYING; } else { currentState = AudioState::ERROR; finalState = AudioState::ERROR; } } } if (callback) { callback(finalState, success ? currentFilePath.c_str() : nullptr); } } bool AudioOutput::pause() { std::lock_guard lock(stateMutex); if (currentState != AudioState::PLAYING) { return false; } Music_pause(); if (audio_player_get_state() == AUDIO_PLAYER_STATE_PAUSE) { currentState = AudioState::PAUSED; return true; } return false; } bool AudioOutput::resume() { std::lock_guard lock(stateMutex); if (currentState != AudioState::PAUSED) { return false; } Music_resume(); if (audio_player_get_state() == AUDIO_PLAYER_STATE_PLAYING) { currentState = AudioState::PLAYING; return true; } return false; } bool AudioOutput::stop() { std::lock_guard lock(stateMutex); if (currentState == AudioState::IDLE || currentState == AudioState::STOPPED) { // 如果当前状态为IDLE或STOPPED,则直接返回成功 return true; } // 暂停播放 Music_pause(); // 内部已经完成关闭文件的操作了 currentState = AudioState::STOPPED; currentFilePath.clear(); return true; } bool AudioOutput::setVolume(uint8_t volume) { std::lock_guard lock(stateMutex); if (volume > Volume_MAX) { ESP_LOGE("AudioOutput", "Volume value %d is out of range (0-%d)", volume, Volume_MAX); return false; } Volume_adjustment(volume); currentVolume = volume; return true; } uint8_t AudioOutput::getVolume() const { std::lock_guard lock(stateMutex); return currentVolume; } uint8_t AudioOutput::getMaxVolume() const { return Volume_MAX; } uint32_t AudioOutput::getDuration() const { return Music_Duration(); } uint32_t AudioOutput::getElapsed() const { return Music_Elapsed(); } uint16_t AudioOutput::getEnergy() const { return Music_Energy(); } AudioState AudioOutput::getState() const { std::lock_guard lock(stateMutex); return currentState; } bool AudioOutput::isPlaying() const { std::lock_guard lock(stateMutex); return currentState == AudioState::PLAYING; } bool AudioOutput::isPaused() const { std::lock_guard lock(stateMutex); return currentState == AudioState::PAUSED; } bool AudioOutput::isStopped() const { std::lock_guard lock(stateMutex); return currentState == AudioState::STOPPED || currentState == AudioState::IDLE; } bool AudioOutput::isFinished() const { return Music_Next_Flag; } ThreadConfig AudioOutput::getThreadConfig(const char* operation) { ThreadConfig config; config.name = "audio_" + std::string(operation); config.core_id = -1; // 不绑定核心 config.stack_size = 4096; config.priority = 5; config.inherit_cfg = false; return config; } void AudioOutput::setState(AudioState newState) { std::lock_guard lock(stateMutex); currentState = newState; } bool AudioOutput::playPcmFile(const char* filePath, uint32_t sampleRate, i2s_data_bit_width_t bits, i2s_slot_mode_t ch) { // 简单文件尺寸获取 FILE* f = fopen(filePath, "rb"); if (!f) return false; fseek(f, 0, SEEK_END); const size_t bytes = ftell(f); fclose(f); playPcmCommon(filePath, bytes, true, sampleRate, bits, ch, nullptr); return getState() == AudioState::PLAYING; } bool AudioOutput::playPcmStream(const uint8_t* pcmData, size_t dataBytes, uint32_t sampleRate, i2s_data_bit_width_t bits, i2s_slot_mode_t ch) { playPcmCommon(pcmData, dataBytes, false, sampleRate, bits, ch, nullptr); return getState() == AudioState::PLAYING; } void AudioOutput::playPcmFileAsync(const char* filePath, uint32_t sampleRate, i2s_data_bit_width_t bits, i2s_slot_mode_t ch, const AudioCallback& cb) { ThreadConfig cfg = getThreadConfig("pcm_file"); std::thread([=](){ playPcmCommon(filePath, 0, true, sampleRate, bits, ch, cb); }).detach(); } void AudioOutput::playPcmStreamAsync(const uint8_t* pcmData, size_t dataBytes, uint32_t sampleRate, i2s_data_bit_width_t bits, i2s_slot_mode_t ch, const AudioCallback& cb) { ThreadConfig cfg = getThreadConfig("pcm_stream"); std::thread([=](){ playPcmCommon(pcmData, dataBytes, false, sampleRate, bits, ch, cb); }).detach(); } void AudioOutput::playPcmCommon(const void* source, size_t bytes, bool isFile, uint32_t sampleRate, i2s_data_bit_width_t bits, i2s_slot_mode_t ch, const AudioCallback& cb) { // 停止旧播放 stop(); // 重新配置 I2S 时钟/位宽/声道 bsp_i2s_reconfig_clk(sampleRate, bits, ch); // 打开“文件”或“内存”数据源 FILE* f = nullptr; const uint8_t* mem = nullptr; size_t memLeft = 0; if (isFile) { f = fopen(static_cast(source), "rb"); if (!f) { if (cb) cb(AudioState::ERROR, static_cast(source)); return; } } else { mem = static_cast(source); memLeft = bytes; } // 状态置为 PLAYING { std::lock_guard lk(stateMutex); currentState = AudioState::PLAYING; currentFilePath = isFile ? static_cast(source) : ""; } if (cb) cb(AudioState::PLAYING, currentFilePath.c_str()); // 循环送 PCM 数据到 I2S constexpr size_t CHUNK = 512; // 任意 2 的幂 int16_t buf[CHUNK]; size_t bw; while (true) { size_t rd = 0; if (isFile) { rd = fread(buf, 1, sizeof(buf), f); } else { rd = memLeft > sizeof(buf) ? sizeof(buf) : memLeft; memcpy(buf, mem, rd); mem += rd; memLeft -= rd; } if (rd == 0) break; // 音量实时缩放(复用已有逻辑) const float vf = currentVolume / 100.0f; for (size_t i = 0; i < rd / 2; ++i) buf[i] = static_cast(buf[i] * vf); // 写 I2S i2s_channel_write(i2s_tx_chan, buf, rd, &bw, portMAX_DELAY); } // 播放结束 if (f) fclose(f); { std::lock_guard lk(stateMutex); currentState = AudioState::STOPPED; } if (cb) cb(AudioState::STOPPED, currentFilePath.c_str()); }