这是一次长久的提交:

1. 应用界面增加了返回主页的按钮
2. 修复了gif渲染内存泄漏的严重bug
3. 将PetDao当中的cJSON API替换为cpp_json,完美通过测试
4. 整合已经实现的各种上层建筑,实现了一个宠物对话基本业务应用,用于样品测试展示用
5. 重构了音频播放类,使其更modern,更加便于移植和拓展
This commit is contained in:
Misaki
2025-10-16 11:36:45 +08:00
parent 801138631e
commit ba5e47bc77
38 changed files with 2487 additions and 2008 deletions
@@ -1,7 +1,6 @@
//
// Created by misaki on 2025/9/9.
//
#include "AudioOutput.h"
#include "esp_log.h"
#include <memory>
@@ -81,8 +80,8 @@ bool AudioOutput::playSync(const char* directory, const char* fileName) {
return false;
}
void AudioOutput::playAsync(const char* directory, const char* fileName, AudioCallback callback) {
ThreadConfig config = getThreadConfig("play_async");
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]() {
@@ -90,9 +89,9 @@ void AudioOutput::playAsync(const char* directory, const char* fileName, AudioCa
}).detach();
}
void AudioOutput::playInternal(const char* directory, const char* fileName, AudioCallback callback) {
void AudioOutput::playInternal(const char* directory, const char* fileName, const AudioCallback& callback) {
bool success = false;
AudioState finalState = AudioState::ERROR;
auto finalState = AudioState::ERROR;
{
std::lock_guard<std::mutex> lock(stateMutex);
@@ -245,4 +244,122 @@ ThreadConfig AudioOutput::getThreadConfig(const char* operation) {
void AudioOutput::setState(AudioState newState) {
std::lock_guard<std::mutex> 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<const char *>(source), "rb");
if (!f) {
if (cb) cb(AudioState::ERROR, static_cast<const char *>(source));
return;
}
} else {
mem = static_cast<const uint8_t *>(source);
memLeft = bytes;
}
// 状态置为 PLAYING
{
std::lock_guard<std::mutex> lk(stateMutex);
currentState = AudioState::PLAYING;
currentFilePath = isFile ? static_cast<const char *>(source) : "<stream>";
}
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<int16_t>(buf[i] * vf);
// 写 I2S
i2s_channel_write(i2s_tx_chan, buf, rd, &bw, portMAX_DELAY);
}
// 播放结束
if (f) fclose(f);
{
std::lock_guard<std::mutex> lk(stateMutex);
currentState = AudioState::STOPPED;
}
if (cb) cb(AudioState::STOPPED, currentFilePath.c_str());
}
@@ -1,14 +1,12 @@
//
// Created by misaki on 2025/9/9.
//
#pragma once
#include <mutex>
#include <functional>
#include <string>
#include <hal/i2s_types.h>
#include "ThreadManager.h"
#include "SDFileManager.h"
@@ -88,7 +86,7 @@ public:
* @param fileName 文件名
* @param callback 回调函数
*/
void playAsync(const char* directory, const char* fileName, AudioCallback callback = nullptr);
void playAsync(const char* directory, const char* fileName, const AudioCallback& callback = nullptr);
/**
* 暂停播放
@@ -175,6 +173,58 @@ public:
*/
bool isFinished() const;
/**
* 播放 PCM 文件(阻塞)
* @param filePath PCM 文件路径
* @param sampleRate 采样率
* @param bits 数据位宽
* @param ch 插槽模式
* @return 是否成功
*/
bool playPcmFile(const char* filePath,
uint32_t sampleRate = 16000,
i2s_data_bit_width_t bits = I2S_DATA_BIT_WIDTH_16BIT,
i2s_slot_mode_t ch = I2S_SLOT_MODE_MONO);
// 异步播放 PCM 文件
void playPcmFileAsync(const char* filePath,
uint32_t sampleRate = 16000,
i2s_data_bit_width_t bits = I2S_DATA_BIT_WIDTH_16BIT,
i2s_slot_mode_t ch = I2S_SLOT_MODE_MONO,
const AudioCallback& cb = nullptr);
/**
* 播放内存 PCM 流(阻塞)
* @param pcmData PCM 数据
* @param dataBytes 数据字节数
* @param sampleRate 采样率
* @param bits 数据位宽
* @param ch 插槽模式
* @return 是否成功
*/
bool playPcmStream(const uint8_t* pcmData,
size_t dataBytes,
uint32_t sampleRate = 16000,
i2s_data_bit_width_t bits = I2S_DATA_BIT_WIDTH_16BIT,
i2s_slot_mode_t ch = I2S_SLOT_MODE_MONO);
// 异步播放 PCM 流
void playPcmStreamAsync(const uint8_t* pcmData,
size_t dataBytes,
uint32_t sampleRate = 16000,
i2s_data_bit_width_t bits = I2S_DATA_BIT_WIDTH_16BIT,
i2s_slot_mode_t ch = I2S_SLOT_MODE_MONO,
const AudioCallback& cb = nullptr);
private:
// 通用 PCM 播放实现
void 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);
private:
// 私有构造函数
AudioOutput();
@@ -184,7 +234,7 @@ private:
ThreadConfig getThreadConfig(const char* operation);
// 内部播放实现
void playInternal(const char* directory, const char* fileName, AudioCallback callback);
void playInternal(const char* directory, const char* fileName, const AudioCallback& callback);
// 状态转换辅助方法
void setState(AudioState newState);