Implement advanced gestures (1=next, 2=pause, 3=prev, 4=vol) and ESP32-audioI2S MP3/WAV playback

This commit is contained in:
drjones
2026-06-30 01:15:32 -07:00
parent 3669b43cc6
commit e04ee7cfe4
3 changed files with 215 additions and 238 deletions

View File

@@ -1,186 +1,94 @@
#include "Audio_Player.h"
#include <ESP_I2S.h>
#include <algorithm>
#include "Audio.h"
I2SClass i2s;
Audio audio;
TaskHandle_t audioTaskHandle = NULL;
File audioFile;
volatile bool isAudioPlaying = false;
char currentPlayPath[128];
volatile bool isAudioTaskRunning = false;
uint8_t currentVolume = 12; // 0 to 21 range
uint16_t Wav_Count = 0;
char Wav_Names[100][100];
uint16_t Audio_Count = 0;
char Audio_Names[100][100];
void Search_Wavs(const char* directory) {
Wav_Count = Folder_retrieval(directory, ".wav", Wav_Names, 100);
}
// Parse WAV header and skip meta chunks (like LIST) to find the "fmt " and "data" segments
bool parseWavHeader(File& file, uint32_t& sampleRate, uint16_t& channels, uint16_t& bitsPerSample, uint32_t& dataStartOffset, uint32_t& dataSize) {
char id[4];
// Read RIFF header
file.seek(0);
if (file.readBytes(id, 4) != 4 || memcmp(id, "RIFF", 4) != 0) {
printf("WAV Header Error: RIFF signature not found!\r\n");
return false;
// FreeRTOS background task on Core 0 to run ESP32-audioI2S decode loop
void audioLoopTask(void* parameter) {
isAudioTaskRunning = true;
while (isAudioTaskRunning) {
audio.loop();
vTaskDelay(pdMS_TO_TICKS(1)); // Yield to let other RTOS tasks run
}
file.seek(8);
if (file.readBytes(id, 4) != 4 || memcmp(id, "WAVE", 4) != 0) {
printf("WAV Header Error: WAVE signature not found!\r\n");
return false;
}
uint32_t offset = 12;
bool foundFmt = false;
bool foundData = false;
while (offset < file.size() && (!foundFmt || !foundData)) {
file.seek(offset);
if (file.readBytes(id, 4) != 4) break;
uint32_t chunkSize;
if (file.read((uint8_t*)&chunkSize, 4) != 4) break;
offset += 8; // move past chunk ID and Size field
if (memcmp(id, "fmt ", 4) == 0) {
uint16_t audioFormat;
file.read((uint8_t*)&audioFormat, 2);
if (audioFormat != 1) { // 1 = PCM (uncompressed)
printf("WAV Header Error: Only uncompressed PCM WAV is supported!\r\n");
return false;
}
file.read((uint8_t*)&channels, 2);
file.read((uint8_t*)&sampleRate, 4);
file.seek(offset + 8); // skip byte rate and block align
file.read((uint8_t*)&bitsPerSample, 2);
foundFmt = true;
}
else if (memcmp(id, "data", 4) == 0) {
dataStartOffset = offset;
dataSize = chunkSize;
foundData = true;
}
offset += chunkSize;
}
return foundFmt && foundData;
}
void audioPlayTask(void* parameter) {
uint32_t sampleRate = 44100;
uint16_t channels = 2;
uint16_t bitsPerSample = 16;
uint32_t dataStartOffset = 44;
uint32_t dataSize = 0;
audioFile = SD_MMC.open(currentPlayPath, "r");
if (!audioFile) {
printf("Failed to open audio file: %s\r\n", currentPlayPath);
isAudioPlaying = false;
audioTaskHandle = NULL;
vTaskDelete(NULL);
return;
}
if (!parseWavHeader(audioFile, sampleRate, channels, bitsPerSample, dataStartOffset, dataSize)) {
printf("Failed to parse WAV header\r\n");
audioFile.close();
isAudioPlaying = false;
audioTaskHandle = NULL;
vTaskDelete(NULL);
return;
}
printf("WAV File: Rate=%d Hz, Channels=%d, Bits=%d, Size=%d bytes\r\n",
sampleRate, channels, bitsPerSample, dataSize);
// Configure I2S based on WAV parameters
i2s_data_bit_width_t bitWidth = I2S_DATA_BIT_WIDTH_16BIT;
if (bitsPerSample == 8) bitWidth = I2S_DATA_BIT_WIDTH_8BIT;
else if (bitsPerSample == 24) bitWidth = I2S_DATA_BIT_WIDTH_24BIT;
else if (bitsPerSample == 32) bitWidth = I2S_DATA_BIT_WIDTH_32BIT;
i2s_slot_mode_t slotMode = (channels == 1) ? I2S_SLOT_MODE_MONO : I2S_SLOT_MODE_STEREO;
// Set standard pins
i2s.setPins(I2S_BCLK, I2S_LRCK, I2S_DIN, -1, -1);
// Stop any active driver configuration
i2s.end();
if (!i2s.begin(I2S_MODE_STD, sampleRate, bitWidth, slotMode)) {
printf("Failed to initialize I2S!\r\n");
audioFile.close();
isAudioPlaying = false;
audioTaskHandle = NULL;
vTaskDelete(NULL);
return;
}
// Seek past the parsed headers to raw data
audioFile.seek(dataStartOffset);
const size_t bufferSize = 1024;
uint8_t buffer[bufferSize];
uint32_t bytesRead = 0;
uint32_t totalBytesWritten = 0;
while (isAudioPlaying && totalBytesWritten < dataSize) {
size_t toRead = std::min(bufferSize, (size_t)(dataSize - totalBytesWritten));
bytesRead = audioFile.read(buffer, toRead);
if (bytesRead == 0) {
break;
}
size_t written = i2s.write(buffer, bytesRead);
if (written == 0) {
vTaskDelay(pdMS_TO_TICKS(1));
continue;
}
totalBytesWritten += written;
}
printf("Playback done. Written %d bytes.\r\n", totalBytesWritten);
audioFile.close();
i2s.end();
isAudioPlaying = false;
audioTaskHandle = NULL;
vTaskDelete(NULL);
}
void playWav(const char* filePath) {
if (isAudioPlaying) {
stopWav();
}
strncpy(currentPlayPath, filePath, sizeof(currentPlayPath) - 1);
currentPlayPath[sizeof(currentPlayPath) - 1] = '\0';
isAudioPlaying = true;
void initAudioPlayer() {
audio.setPinout(I2S_BCLK, I2S_LRCK, I2S_DIN);
audio.setVolume(currentVolume);
xTaskCreatePinnedToCore(
audioPlayTask,
"audioPlayTask",
4096,
NULL,
5,
&audioTaskHandle,
0
);
}
void stopWav() {
if (isAudioPlaying) {
isAudioPlaying = false;
while (audioTaskHandle != NULL) {
vTaskDelay(pdMS_TO_TICKS(5));
}
// Start the background loop task on Core 0
if (audioTaskHandle == NULL) {
xTaskCreatePinnedToCore(
audioLoopTask,
"audioLoopTask",
8192, // Stack size (ESP32-audioI2S requires a larger stack size for decoding)
NULL,
5, // Priority
&audioTaskHandle,
0 // Core 0
);
}
}
bool isWavPlaying() {
return isAudioPlaying;
void playAudio(const char* filePath) {
printf("AudioPlayer: Connecting to FS for file: %s\r\n", filePath);
audio.connecttoFS(SD_MMC, filePath);
}
void stopAudio() {
audio.stopSong();
}
void pauseResumeAudio() {
audio.pauseResume();
}
bool isAudioPlaying() {
return audio.isRunning();
}
void setPlayerVolume(uint8_t volume) {
if (volume > 21) volume = 21;
currentVolume = volume;
audio.setVolume(currentVolume);
printf("AudioPlayer: Volume set to %d / 21\r\n", currentVolume);
}
uint8_t getPlayerVolume() {
return currentVolume;
}
void Search_Audio(const char* directory) {
File Path = SD_MMC.open(directory);
if (!Path) {
printf("Path: <%s> does not exist\r\n", directory);
Audio_Count = 0;
return;
}
uint16_t fileCount = 0;
File file = Path.openNextFile();
while (file && fileCount < 100) {
if (!file.isDirectory()) {
const char* name = file.name();
// Match WAV or MP3 files
if (strstr(name, ".wav") || strstr(name, ".mp3") || strstr(name, ".WAV") || strstr(name, ".MP3")) {
strncpy(Audio_Names[fileCount], name, sizeof(Audio_Names[fileCount]) - 1);
Audio_Names[fileCount][sizeof(Audio_Names[fileCount]) - 1] = '\0';
fileCount++;
}
}
file = Path.openNextFile();
}
Path.close();
Audio_Count = fileCount;
printf("Found %d audio files (.mp3/.wav)\r\n", Audio_Count);
}