Add pre-packaged dependency libraries (ESP32-audioI2S and PNGdec)

This commit is contained in:
drjones
2026-07-01 19:18:01 -07:00
parent e207c9a5b7
commit 7606b9d5df
146 changed files with 106590 additions and 0 deletions

View File

@@ -0,0 +1,292 @@
// example AUDIO RECORDER
// starts an audio stream and records 10 seconds of it on the SD card.
#include "Arduino.h"
#include "Audio.h"
#include "WiFiMulti.h"
#include <atomic>
#include <algorithm>
Audio audio;
WiFiMulti wifiMulti;
//______________________________________________________________________________________________________________________________________________________________________________________________________
// A U D I O R E C O R D E R
//______________________________________________________________________________________________________________________________________________________________________________________________________
struct WAVHeader {
char riff[4] = {'R', 'I', 'F', 'F'};
uint32_t size;
char wave[4] = {'W', 'A', 'V', 'E'};
char fmt[4] = {'f', 'm', 't', ' '};
uint32_t fmtSize = 16;
uint16_t format = 1;
uint16_t channels = 2;
uint32_t sampleRate;
uint32_t byteRate;
uint16_t blockAlign;
uint16_t bits;
char data[4] = {'d', 'a', 't', 'a'};
uint32_t dataSize;
};
constexpr size_t REC_BUFFER_SIZE = 512 * 1024; // 512KB für 2-3 Sekunden Puffer
constexpr size_t WRITE_CHUNK_SIZE = 10242; // not too big!
constexpr size_t SD_FLUSH_INTERVAL = 65536; // Alle 64KB flush
ps_ptr<uint8_t> rec_buffer;
ps_ptr<uint8_t> writeBuffer;
class AudioRecorder {
public:
std::atomic<size_t> writePos{0};
std::atomic<size_t> readPos{0};
uint32_t totalBytes = 0;
uint16_t sampleRate = 44100;
uint32_t overflowCount = 0;
volatile bool startRequested = false;
volatile bool stopRequested = false;
volatile bool running = false;
bool push16(const int32_t* data, size_t frames) {
// frames = Stereo-Frames
size_t bytes16 = frames * 2 * sizeof(int16_t);
size_t currentWrite = writePos.load(std::memory_order_relaxed);
size_t currentRead = readPos.load(std::memory_order_acquire);
size_t free = (currentRead + REC_BUFFER_SIZE - currentWrite - 1) % REC_BUFFER_SIZE;
if (bytes16 > free) {
overflowCount++;
return false;
}
for (size_t i = 0; i < frames * 2; i++) {
// 32 → 16 Bit (High word)
int32_t v = data[i] >> 16;
// Optional Clipping (save)
if (v > 32767) v = 32767;
if (v < -32768) v = -32768;
int16_t s = (int16_t)v;
// Write byte by byte (LE)
rec_buffer[currentWrite] = s & 0xFF;
currentWrite = (currentWrite + 1) % REC_BUFFER_SIZE;
rec_buffer[currentWrite] = (s >> 8) & 0xFF;
currentWrite = (currentWrite + 1) % REC_BUFFER_SIZE;
}
writePos.store(currentWrite, std::memory_order_release);
return true;
}
// Copies data to dest, returns bytes actually read
size_t pop(uint8_t* dest, size_t maxLen) {
size_t currentRead = readPos.load(std::memory_order_relaxed);
size_t currentWrite = writePos.load(std::memory_order_acquire);
if (currentRead == currentWrite) return 0;
size_t avail = (currentWrite > currentRead) ? (currentWrite - currentRead) : (REC_BUFFER_SIZE - currentRead);
size_t toRead = std::min(avail, maxLen);
// Wrap-around handling
size_t firstChunk = std::min(toRead, REC_BUFFER_SIZE - currentRead);
memcpy(dest, &rec_buffer[currentRead], firstChunk);
if (toRead > firstChunk) { memcpy(dest + firstChunk, &rec_buffer[0], toRead - firstChunk); }
readPos.store((currentRead + toRead) % REC_BUFFER_SIZE, std::memory_order_release);
return toRead;
}
// For external access to buffers (e.g. for pop with pointer math, but not recommended)
size_t available() {
size_t w = writePos.load(std::memory_order_acquire);
size_t r = readPos.load(std::memory_order_acquire);
return (w >= r) ? (w - r) : (REC_BUFFER_SIZE - r + w);
}
};
AudioRecorder recorder;
void wavWriterTask(void*) {
File file;
WAVHeader hdr;
bool fileOpen = false;
size_t writeBufferFill = 0;
uint32_t bytesSinceFlush = 0;
while (true) {
// --- START REQUEST ---
if (recorder.startRequested && !fileOpen) {
recorder.startRequested = false;
// Datei mit Zeitstempel erstellen
char filename[64];
snprintf(filename, sizeof(filename), "/recording.wav");
file = SD_MMC.open(filename, FILE_WRITE);
if (!file) {
Serial.println("Failed to open file! \"/recording.wav\"");
continue;
}
// prepeare header
hdr.sampleRate = recorder.sampleRate;
hdr.byteRate = recorder.sampleRate * 2 * 2; // Stereo, 16-bit
hdr.blockAlign = 2 * 2; // 8 bytes per frame
hdr.bits = 16;
hdr.dataSize = 0;
hdr.size = 36; // 44 - 8 (RIFF header)
file.write((uint8_t*)&hdr, sizeof(hdr));
recorder.totalBytes = 0;
writeBufferFill = 0;
bytesSinceFlush = 0;
fileOpen = true;
recorder.running = true;
Serial.printf("Recording started: %s\n", filename);
}
// --- WRITE DATA ---
if (fileOpen) {
// fill local buffer
while (writeBufferFill < WRITE_CHUNK_SIZE) {
size_t spaceInLocalBuffer = WRITE_CHUNK_SIZE - writeBufferFill;
size_t bytesRead = recorder.pop(writeBuffer + writeBufferFill, spaceInLocalBuffer);
if (bytesRead == 0) break; // ringbuffer is empty
writeBufferFill += bytesRead;
}
// write full block to SD
if (writeBufferFill >= WRITE_CHUNK_SIZE) {
size_t written = file.write(writeBuffer.get(), WRITE_CHUNK_SIZE);
if (written != WRITE_CHUNK_SIZE) {
Serial.println("SD write error!");
// Optional: error handling, hold buffer?
}
recorder.totalBytes += written;
bytesSinceFlush += written;
writeBufferFill = 0; // buffer is empty (or move remaining data)
// Periodic flush for data integrity
if (bytesSinceFlush >= SD_FLUSH_INTERVAL) {
file.flush();
bytesSinceFlush = 0;
}
}
}
// --- STOP REQUEST ---
if (recorder.stopRequested && fileOpen) {
recorder.stopRequested = false;
// Write remaining data to local buffer
if (writeBufferFill > 0) {
file.write(writeBuffer.get(), writeBufferFill);
recorder.totalBytes += writeBufferFill;
}
// Update header
hdr.dataSize = recorder.totalBytes;
hdr.size = recorder.totalBytes + 36;
file.seek(0);
file.write((uint8_t*)&hdr, sizeof(hdr));
file.flush();
file.close();
fileOpen = false;
writeBufferFill = 0;
recorder.running = false;
Serial.printf("Recording stopped. Total bytes: %u, Overflows: %u\n", recorder.totalBytes, recorder.overflowCount);
}
// Small delay to feed watchdog and release CPU
// But not too long, so that the ring buffer does not overflow!
vTaskDelay(pdMS_TO_TICKS(1)); // 1ms = ~176 Bytes bei 44.1kHz Stereo 32-bit
}
}
//______________________________________________________________________________________________________________________________________________________________________________________________________
#define I2S_DOUT 25
#define I2S_BCLK 27
#define I2S_LRC 26
#define SD_MMC_D0 2
#define SD_MMC_CLK 14
#define SD_MMC_CMD 15
String ssid = "*****";
String password = "*****";
uint32_t t;
enum {IDLE, RECORDING, FINISH, PLAYBACK};
uint8_t state = IDLE;
void my_audio_info(Audio::msg_t m) {
Serial.printf("%s: %s\n", m.s, m.msg);
}
void setup() {
Serial.begin(115200);
Audio::audio_info_callback = my_audio_info;
Serial.print("\n\n");
wifiMulti.addAP(ssid.c_str(), password.c_str());
wifiMulti.run(); // if there are multiple access points, use the strongest one
while (WiFi.status() != WL_CONNECTED) delay(1500);
pinMode(SD_MMC_D0, INPUT_PULLUP);
SD_MMC.setPins(SD_MMC_CLK, SD_MMC_CMD, SD_MMC_D0);
SD_MMC.begin("/sdcard", true);
audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
audio.setVolume(20); // default 0...21
audio.connecttohost("http://stream.antennethueringen.de/live/aac-64/stream.antennethueringen.de/"); // aac
rec_buffer.alloc_array(REC_BUFFER_SIZE, "rec_buffer"); // allocate in PSRAM
writeBuffer.alloc_array(WRITE_CHUNK_SIZE, "writeBuffer"); // allocate in PSRAM
xTaskCreatePinnedToCore(wavWriterTask, "wavWriter", 4096, nullptr, 1, nullptr, 0); // start recorder task
Serial.printf("recorder task started, Free heap: %u\n", ESP.getFreeHeap());
t = millis();
}
void loop() {
audio.loop();
vTaskDelay(1);
if (t + 5000 < millis() && state == IDLE && audio.isRunning()) {
Serial.println("start recording");
state = RECORDING;
recorder.sampleRate = audio.getSampleRate();
recorder.startRequested = true;
}
if (t + 15000 < millis() && state == RECORDING) {
Serial.println("stop recording");
state = FINISH;
recorder.stopRequested = true;
audio.stopSong();
}
if (t + 17000 < millis() && state == FINISH) {
state = PLAYBACK;
audio.connecttoFS(SD_MMC, "/recording.wav");
}
if (t + 29000 < millis() && state == PLAYBACK && !audio.isRunning()) {
state = IDLE;
audio.connecttohost("http://stream.antennethueringen.de/live/aac-64/stream.antennethueringen.de/");
t = millis();
}
}
//______________________________________________________________________________________________________________________________________________________________________________________________________
void audio_process_raw_samples(int32_t* outBuff, int16_t validSamples) { // samples are available, write in wav file
if (recorder.running == true) {
recorder.push16(outBuff, validSamples);
}
}