diff --git a/CHANGELOG.md b/CHANGELOG.md index 0db12e009..1abfae0ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ All notable changes to this project will be documented in this file. ### Added - Support for second DNS server +### Changed +- ESP8266Audio library from v1.5.0 to v1.9.2 + ## [9.5.0.3] 20210729 ### Added - Command ``SetSensor1..127 0|1`` to globally disable individual sensor driver diff --git a/lib/lib_audio/ESP8266Audio/README.md b/lib/lib_audio/ESP8266Audio/README.md index c8c92ff67..f243db52a 100644 --- a/lib/lib_audio/ESP8266Audio/README.md +++ b/lib/lib_audio/ESP8266Audio/README.md @@ -1,4 +1,4 @@ -# ESP8266Audio - supports ESP8266 & ESP32 [![Gitter](https://badges.gitter.im/ESP8266Audio/community.svg)](https://gitter.im/ESP8266Audio/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +# ESP8266Audio - supports ESP8266 & ESP32 & Raspberry Pi RP2040[![Gitter](https://badges.gitter.im/ESP8266Audio/community.svg)](https://gitter.im/ESP8266Audio/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) Arduino library for parsing and decoding MOD, WAV, MP3, FLAC, MIDI, AAC, and RTTL files and playing them on an I2S DAC or even using a software-simulated delta-sigma DAC with dynamic 32x-128x oversampling. ESP8266 is fully supported and most mature, but ESP32 is also mostly there with built-in DAC as well as external ones. diff --git a/lib/lib_audio/ESP8266Audio/examples/PlayWAVFromFunction/PlayWAVFromFunction.ino b/lib/lib_audio/ESP8266Audio/examples/PlayWAVFromFunction/PlayWAVFromFunction.ino new file mode 100644 index 000000000..fe0cc896d --- /dev/null +++ b/lib/lib_audio/ESP8266Audio/examples/PlayWAVFromFunction/PlayWAVFromFunction.ino @@ -0,0 +1,78 @@ +#include +#include "AudioFileSourceFunction.h" +#include "AudioGeneratorWAV.h" +#include "AudioOutputI2SNoDAC.h" + +float hz = 440.f; + +// pre-defined function can also be used to generate the wave +float sine_wave(const float time) { + float v = sin(TWO_PI * hz * time); // C + v *= fmod(time, 1.f); // change linear + v *= 0.5; // scale + return v; +}; + +AudioGeneratorWAV* wav; +AudioFileSourceFunction* file; +AudioOutputI2SNoDAC* out; + +void setup() { + Serial.begin(115200); + delay(1000); + + // ===== create instance with length of song in [sec] ===== + file = new AudioFileSourceFunction(8.); + // + // you can set (sec, channels, hz, bit/sample) but you should care about + // the trade-off between performance and the audio quality + // + // file = new AudioFileSourceFunction(sec, channels, hz, bit/sample); + // channels : default = 1 + // hz : default = 8000 (8000, 11025, 22050, 44100, 48000, etc.) + // bit/sample : default = 16 (8, 16, 32) + + // ===== set your sound function ===== + file->addAudioGenerators([&](const float time) { + float v = sin(TWO_PI * hz * time); // generate sine wave + v *= fmod(time, 1.f); // change linear + v *= 0.5; // scale + return v; + }); + // + // sound function should have one argument(float) and one return(float) + // param : float (current time [sec] of the song) + // return : float (the amplitude of sound which varies from -1.f to +1.f) + // + // sound function can be registerd only one or the same number with channels + // if the channels > 1 && the number of function == 1, + // same function are used to generate the sound in every channel + // + // file = new AudioFileSourceFunction(8., 2); + // file->addAudioGenerators( + // // L (channel 0) + // [](const float time) { + // return 0.25 * sin(TWO_PI * 440.f * time) * fmod(time, 1.f); // C + // }, + // // R (channel 1) + // [](const float time) { + // return 0.25 * sin(TWO_PI * 550.f * time) * fmod(time, 1.f); // E + // } + // ); + // + // you can also use the pre-defined function + // file->addAudioGenerators(sine_wave); + + out = new AudioOutputI2SNoDAC(); + wav = new AudioGeneratorWAV(); + wav->begin(file, out); +} + +void loop() { + if (wav->isRunning()) { + if (!wav->loop()) wav->stop(); + } else { + Serial.println("function done!"); + delay(1000); + } +} diff --git a/lib/lib_audio/ESP8266Audio/examples/PlayWAVFromPROGMEM/PlayWAVFromPROGMEM.ino b/lib/lib_audio/ESP8266Audio/examples/PlayWAVFromPROGMEM/PlayWAVFromPROGMEM.ino index 5221c913c..cb488f1c3 100644 --- a/lib/lib_audio/ESP8266Audio/examples/PlayWAVFromPROGMEM/PlayWAVFromPROGMEM.ino +++ b/lib/lib_audio/ESP8266Audio/examples/PlayWAVFromPROGMEM/PlayWAVFromPROGMEM.ino @@ -1,9 +1,4 @@ #include -#ifdef ESP32 - #include -#else - #include -#endif #include "AudioFileSourcePROGMEM.h" #include "AudioGeneratorWAV.h" @@ -18,7 +13,6 @@ AudioOutputI2SNoDAC *out; void setup() { - WiFi.mode(WIFI_OFF); Serial.begin(115200); delay(1000); Serial.printf("WAV start\n"); diff --git a/lib/lib_audio/ESP8266Audio/examples/StreamMP3FromHTTP/StreamMP3FromHTTP.ino b/lib/lib_audio/ESP8266Audio/examples/StreamMP3FromHTTP/StreamMP3FromHTTP.ino index 8ea451576..84d7d5b37 100644 --- a/lib/lib_audio/ESP8266Audio/examples/StreamMP3FromHTTP/StreamMP3FromHTTP.ino +++ b/lib/lib_audio/ESP8266Audio/examples/StreamMP3FromHTTP/StreamMP3FromHTTP.ino @@ -22,7 +22,7 @@ const char* ssid = STASSID; const char* password = STAPSK; // Randomly picked URL -const char *URL="http://streaming.shoutcast.com/80sPlanet?lang=en-US"; +const char *URL="http://kvbstreams.dyndns.org:8000/wkvi-am"; AudioGeneratorMP3 *mp3; AudioFileSourceICYStream *file; diff --git a/lib/lib_audio/ESP8266Audio/examples/StreamOnHost/AudioOutputLinuxDSP.h b/lib/lib_audio/ESP8266Audio/examples/StreamOnHost/AudioOutputLinuxDSP.h new file mode 100644 index 000000000..743410e84 --- /dev/null +++ b/lib/lib_audio/ESP8266Audio/examples/StreamOnHost/AudioOutputLinuxDSP.h @@ -0,0 +1,118 @@ +/* + AudioOutput + Base class of an audio output player + + Copyright (C) 2017 Earle F. Philhower, III + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef _AUDIOOUTPUTNULLSLOW_H +#define _AUDIOOUTPUTNULLSLOW_H + +#include +#include +#include +#include +#include +#include +#include + +#include "AudioOutput.h" + +class AudioOutputNullSlow : public AudioOutput +{ + public: + AudioOutputNullSlow() { }; + ~AudioOutputNullSlow() {}; + virtual bool begin() { samples = 0; startms = millis(); return true; } + + virtual bool ConsumeSample(int16_t sample[2]) { + + if (fd < 0) { + fd = open("/dev/dsp", O_RDWR); + if (fd < 0) { + perror("open of /dev/dsp failed (Try with 'padsp this-exec')"); + exit(1); + } + } + + if (channels && lastchannels != channels) { + Serial.printf("CHANNELS=%d\n", channels); + int arg = channels; /* mono or stereo */ + int status = ioctl(fd, SOUND_PCM_WRITE_CHANNELS, &arg); + if (status == -1) { + perror("SOUND_PCM_WRITE_CHANNELS ioctl failed"); + exit(1); + } else if (arg != channels) { + perror("unable to set number of channels"); + exit(1); + } + lastchannels = channels; + } + + if (lastchannels > 0 && hertz && lasthertz != hertz) { + Serial.printf("FREQ=%d\n", hertz); + int arg = hertz*4/lastchannels; /* sampling rate */ + int status = ioctl(fd, SOUND_PCM_WRITE_RATE, &arg); + if (status == -1) { + perror("SOUND_PCM_WRITE_RATE ioctl failed"); + exit(1); + } + lasthertz = hertz; + } + + if (bps && lastbps != bps) { + Serial.printf("BPS=%d\n", bps); + int arg = bps; /* sample size */ + int status = ioctl(fd, SOUND_PCM_WRITE_BITS, &arg); + if (status == -1) { + perror("SOUND_PCM_WRITE_BITS ioctl failed"); + exit(1); + } else if (arg != bps) { + perror("unable to set sample size"); + exit(1); + } + lastbps = bps; + } + + if ((++samples & ((1<<9)-1)) == 0) { + // let the main loop a chance to run + return false; + } + + if (write(fd, sample, sizeof(sample)) != sizeof(sample)) { + perror("doing sound"); + exit(1); + } + + return true; + } + + virtual bool stop() { endms = millis(); return true; }; + unsigned long GetMilliseconds() { return endms - startms; } + int GetSamples() { return samples; } + int GetFrequency() { return hertz; } + + protected: + unsigned long startms; + unsigned long endms; + int samples; + int lastchannels = -1; + int lasthertz = -1; + int lastbps = -1; + int fd = -1; +}; + +#endif diff --git a/lib/lib_audio/ESP8266Audio/examples/StreamOnHost/AudioOutputNullSlow.h b/lib/lib_audio/ESP8266Audio/examples/StreamOnHost/AudioOutputNullSlow.h new file mode 100644 index 000000000..e85cd9ee9 --- /dev/null +++ b/lib/lib_audio/ESP8266Audio/examples/StreamOnHost/AudioOutputNullSlow.h @@ -0,0 +1,57 @@ +/* + AudioOutput + Base class of an audio output player + + Copyright (C) 2017 Earle F. Philhower, III + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef _AUDIOOUTPUTNULLSLOW_H +#define _AUDIOOUTPUTNULLSLOW_H + +#include "AudioOutput.h" + +class AudioOutputNullSlow : public AudioOutput +{ + public: + AudioOutputNullSlow() { }; + ~AudioOutputNullSlow() {}; + virtual bool begin() { samples = 0; startms = millis(); return true; } + virtual bool ConsumeSample(int16_t sample[2]) { + // return false (= output buffer full) + // sometimes to let the main loop running + constexpr int everylog2 = 10; + if ((++samples & ((1< 0) { + // simulate real time + delay(1000/(hertz >> everylog2)); + } + return false; + } + return true; + } + virtual bool stop() { endms = millis(); return true; }; + unsigned long GetMilliseconds() { return endms - startms; } + int GetSamples() { return samples; } + int GetFrequency() { return hertz; } + + protected: + unsigned long startms; + unsigned long endms; + int samples; +}; + +#endif + diff --git a/lib/lib_audio/ESP8266Audio/examples/StreamOnHost/StreamOnHost.ino b/lib/lib_audio/ESP8266Audio/examples/StreamOnHost/StreamOnHost.ino new file mode 100644 index 000000000..876562d1b --- /dev/null +++ b/lib/lib_audio/ESP8266Audio/examples/StreamOnHost/StreamOnHost.ino @@ -0,0 +1,118 @@ +#include + +#ifdef ESP32 + #include +#else + #include +#endif +#include "AudioFileSourceICYStream.h" +#include "AudioFileSourceBuffer.h" +#include "AudioGeneratorMP3.h" +#if AUDIO +#pragma message("Outputting audio") +#include "AudioOutputLinuxDSP.h" +#else +#pragma message("No audio") +#include "AudioOutputNullSlow.h" +#endif + +// To run, set your ESP8266 build to 160MHz, update the SSID info, and upload. + +// Enter your WiFi setup here: +#ifndef STASSID +#define STASSID "your-ssid" +#define STAPSK "your-password" +#endif + +const char* ssid = STASSID; +const char* password = STAPSK; + +// Randomly picked URL +//const char *URL="http://kvbstreams.dyndns.org:8000/wkvi-am"; +//const char *URL="http://stream2.pvpjamz.com:8706/stream"; +// that one is not well decoded: +const char *URL="http://icecast.radiofrance.fr/franceinter-lofi.mp3"; + +AudioGeneratorMP3 *mp3; +AudioFileSourceICYStream *file; +AudioFileSourceBuffer *buff; +AudioOutputNullSlow *out; + +// Called when a metadata event occurs (i.e. an ID3 tag, an ICY block, etc. +void MDCallback(void *cbData, const char *type, bool isUnicode, const char *string) +{ + const char *ptr = reinterpret_cast(cbData); + (void) isUnicode; // Punt this ball for now + // Note that the type and string may be in PROGMEM, so copy them to RAM for printf + char s1[32], s2[64]; + strncpy_P(s1, type, sizeof(s1)); + s1[sizeof(s1)-1]=0; + strncpy_P(s2, string, sizeof(s2)); + s2[sizeof(s2)-1]=0; + Serial.printf("METADATA(%s) '%s' = '%s'\n", ptr, s1, s2); + Serial.flush(); +} + +// Called when there's a warning or error (like a buffer underflow or decode hiccup) +void StatusCallback(void *cbData, int code, const char *string) +{ + const char *ptr = reinterpret_cast(cbData); + // Note that the string may be in PROGMEM, so copy it to RAM for printf + char s1[64]; + strncpy_P(s1, string, sizeof(s1)); + s1[sizeof(s1)-1]=0; + Serial.printf("STATUS(%s) '%d' = '%s'\n", ptr, code, s1); + Serial.flush(); +} + + +void setup() +{ + Serial.begin(115200); + delay(1000); + Serial.println("Connecting to WiFi"); + + WiFi.disconnect(); + WiFi.softAPdisconnect(true); + WiFi.mode(WIFI_STA); + + WiFi.begin(ssid, password); + + // Try forever + while (WiFi.status() != WL_CONNECTED) { + Serial.println("...Connecting to WiFi"); + delay(1000); + } + Serial.println("Connected"); + + audioLogger = &Serial; + file = new AudioFileSourceICYStream(); + file->RegisterMetadataCB(MDCallback, (void*)"ICY"); + file->useHTTP10(); + file->open(URL); + buff = new AudioFileSourceBuffer(file, 2048); + buff->RegisterStatusCB(StatusCallback, (void*)"buffer"); + out = new AudioOutputNullSlow(); + mp3 = new AudioGeneratorMP3(); + mp3->RegisterStatusCB(StatusCallback, (void*)"mp3"); + mp3->begin(buff, out); +} + + +void loop() +{ + static int lastms = 0; + + if (mp3->isRunning()) { + if (millis()-lastms > 1000) { + lastms = millis(); + Serial.printf("Running for %d ms...\n", lastms); + Serial.flush(); + } + if (!mp3->loop()) mp3->stop(); + } else { + Serial.printf("MP3 done\n"); + delay(1000); + } +} + diff --git a/lib/lib_audio/ESP8266Audio/examples/StreamOnHost/onHost b/lib/lib_audio/ESP8266Audio/examples/StreamOnHost/onHost new file mode 100755 index 000000000..014845692 --- /dev/null +++ b/lib/lib_audio/ESP8266Audio/examples/StreamOnHost/onHost @@ -0,0 +1,66 @@ +#!/bin/bash + +ino=${PWD##*/} + +if [ ! -d "${ESP8266ARDUINO}/tests/host" ]; then + echo "\${ESP8266ARDUINO} should point to ESP8266 Arduino core directory" + exit 1 +fi + +THISLIB=$(pwd)/../.. +MAD=$(ls ${THISLIB}/src/libmad/*.c) +PAGER=${PAGER:-less} + +cd ${ESP8266ARDUINO}/tests/host + +if [ "$1" = "clean" ]; then + make clean + cd ${THISLIB} + rm -f src/*.o src/libmad/*.o + exit 0 +elif [ "$1" = diff ]; then + cd ${THISLIB}/examples + diff -u StreamMP3FromHTTP/StreamMP3FromHTTP.ino ${ino}/${ino}.ino | ${PAGER} + exit 0 +else + echo "" + echo "usage:" + echo " $0" + echo " $0 clean" + echo " $0 diff" + echo " AUDIO=a VALGRIND=v FORCE32=f $0" + echo " a=1 play sound (use padsp, open /dev/dsp)" + echo " v=1 run in native mode (FORCE32=0) with valgrind" + echo " f=1 run in 32 bits mode (if gcc-multilib is installed)" + echo "variable ESP8266ARDUINO must point to esp8266 Arduino core directory" + echo "" + [ "$1" = "-h" ] && exit 0 + sleep 1 +fi + +run="" + +[ -z "${FORCE32}" ] && FORCE32=0 +[ -z "${AUDIO}" ] && AUDIO=1 + +if [ "${AUDIO}" = 1 ]; then + run="${run} padsp" +fi + +if [ "${VALGRIND}" = 1 ]; then + FORCE32=0 + run="$run valgrind" +fi + +touch ${THISLIB}/examples/${ino}/${ino}.ino # rebuild + +eval make FORCE32=${FORCE32} -j \ + USERCSOURCES=\"${MAD}\" \ + USERCXXSOURCES=\"${THISLIB}/src/AudioFileSourceBuffer.cpp ${THISLIB}/src/AudioLogger.cpp ${THISLIB}/src/AudioGeneratorMP3.cpp ${THISLIB}/src/AudioFileSourceICYStream.cpp ${THISLIB}/src/AudioFileSourceHTTPStream.cpp\" \ + USERCFLAGS=\"-I${THISLIB}/src/ -DAUDIO=${AUDIO}\" \ + ${THISLIB}/examples/${ino}/${ino} + +set -x + +$run ./bin/${ino}/${ino} "$@" +stty sane diff --git a/lib/lib_audio/ESP8266Audio/keywords.txt b/lib/lib_audio/ESP8266Audio/keywords.txt index 251f431e3..75b11139c 100644 --- a/lib/lib_audio/ESP8266Audio/keywords.txt +++ b/lib/lib_audio/ESP8266Audio/keywords.txt @@ -22,6 +22,7 @@ AudioGeneratorWAV KEYWORD1 AudioOutput KEYWORD1 AudioOutputI2S KEYWORD1 AudioOutputI2SNoDAC KEYWORD1 +AudioOutputI2SClass KEYWORD1 AudioOutputNull KEYWORD1 AudioOutputBuffer KEYWORD1 AudioOutputSerialWAV KEYWORD1 diff --git a/lib/lib_audio/ESP8266Audio/library.json b/lib/lib_audio/ESP8266Audio/library.json index 5986c2a41..139291880 100644 --- a/lib/lib_audio/ESP8266Audio/library.json +++ b/lib/lib_audio/ESP8266Audio/library.json @@ -1,6 +1,6 @@ { "name": "ESP8266Audio", - "description": "Audio file format and I2S DAC library", + "description": "Audio file format and I2S DAC library for ESP8266, ESP32, and Raspberry Pi Pico RP2040", "keywords": "ESP8266, ESP32, MP3, AAC, WAV, MOD, FLAC, RTTTL, MIDI, I2S, DAC, Delta-Sigma, TTS", "authors": [ { @@ -14,13 +14,9 @@ "type": "git", "url": "https://github.com/earlephilhower/ESP8266Audio" }, - "version": "1.5.0", + "version": "1.9.2", "homepage": "https://github.com/earlephilhower/ESP8266Audio", - "dependencies": { - "SPI": "1.0" - }, "frameworks": "Arduino", - "platforms": ["espressif8266", "espressif32"], "examples": [ "examples/*/*.ino" ] diff --git a/lib/lib_audio/ESP8266Audio/library.properties b/lib/lib_audio/ESP8266Audio/library.properties index 0eb36bee2..111915c1c 100644 --- a/lib/lib_audio/ESP8266Audio/library.properties +++ b/lib/lib_audio/ESP8266Audio/library.properties @@ -1,9 +1,9 @@ name=ESP8266Audio -version=1.5.0 +version=1.9.2 author=Earle F. Philhower, III maintainer=Earle F. Philhower, III -sentence=Audio file and I2S sound playing routines. +sentence=Audio file and I2S sound playing routines for ESP8266, ESP32, and Raspberry Pi Pico RP2040 paragraph=Decode compressed MP3, AAC, FLAC, Screamtracker MOD, MIDI, RTTL, TI Talkie, and WAV and play on an I2S DAC or a software-driven delta-sigma DAC and 1-transistor amplifier. category=Signal Input/Output url=https://github.com/earlephilhower/ESP8266Audio -architectures=esp8266,esp32 +architectures=esp8266,esp32,rp2040 diff --git a/lib/lib_audio/ESP8266Audio/src/AudioFileSourceFS.h b/lib/lib_audio/ESP8266Audio/src/AudioFileSourceFS.h index fea34c29e..4e1cb8ec0 100644 --- a/lib/lib_audio/ESP8266Audio/src/AudioFileSourceFS.h +++ b/lib/lib_audio/ESP8266Audio/src/AudioFileSourceFS.h @@ -29,8 +29,8 @@ class AudioFileSourceFS : public AudioFileSource { public: - AudioFileSourceFS(FS &fs) { filesystem = &fs; } - AudioFileSourceFS(FS &fs, const char *filename); + AudioFileSourceFS(fs::FS &fs) { filesystem = &fs; } + AudioFileSourceFS(fs::FS &fs, const char *filename); virtual ~AudioFileSourceFS() override; virtual bool open(const char *filename) override; @@ -42,8 +42,8 @@ class AudioFileSourceFS : public AudioFileSource virtual uint32_t getPos() override { if (!f) return 0; else return f.position(); }; private: - FS *filesystem; - File f; + fs::FS *filesystem; + fs::File f; }; diff --git a/lib/lib_audio/ESP8266Audio/src/AudioFileSourceFunction.cpp b/lib/lib_audio/ESP8266Audio/src/AudioFileSourceFunction.cpp new file mode 100644 index 000000000..9e86eff21 --- /dev/null +++ b/lib/lib_audio/ESP8266Audio/src/AudioFileSourceFunction.cpp @@ -0,0 +1,148 @@ +/* + AudioFileSourceFunction + Audio ouptut generator which can generate WAV file data from function + + Copyright (C) 2021 Hideaki Tai + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "AudioFileSourceFunction.h" + +AudioFileSourceFunction::AudioFileSourceFunction(float sec, uint16_t channels, uint32_t sample_per_sec, uint16_t bits_per_sample) { + uint32_t bytes_per_sec = sample_per_sec * channels * bits_per_sample / 8; + uint32_t len = uint32_t(sec * (float)bytes_per_sec); + + // RIFF chunk + strncpy(wav_header.riff.chunk_id, "RIFF", 4); + wav_header.riff.chunk_size = 4 // size of riff chunk w/o chunk_id and chunk_size + + 8 + 16 // size of format chunk + + 8 + len; // size of data chunk + strncpy(wav_header.riff.format, "WAVE", 4); + + // format chunk + strncpy(wav_header.format.chunk_id, "fmt ", 4); + wav_header.format.chunk_size = 16; + wav_header.format.format_tag = 0x0001; // PCM + wav_header.format.channels = channels; + wav_header.format.sample_per_sec = sample_per_sec; + wav_header.format.avg_bytes_per_sec = bytes_per_sec; + wav_header.format.block_align = channels * bits_per_sample / 8; + wav_header.format.bits_per_sample = bits_per_sample; + + // data chunk + strncpy(wav_header.data.chunk_id, "data", 4); + wav_header.data.chunk_size = len; + + funcs.reserve(channels); + pos = 0; + size = sizeof(WavHeader) + len; + is_ready = false; + is_unique = false; +} + +AudioFileSourceFunction::~AudioFileSourceFunction() { + close(); +} + +uint32_t AudioFileSourceFunction::read(void* data, uint32_t len) { + // callback size must be 1 or equal to channels + if (!is_ready) + return 0; + + uint8_t* d = reinterpret_cast(data); + uint32_t i = 0; + while (i < len) { + uint32_t p = pos + i; + if (p < sizeof(WavHeader)) { + // header bytes + d[i] = wav_header.bytes[p]; + i += 1; + } else { + // data bytes + float time = (float)(p - sizeof(WavHeader)) / (float)wav_header.format.avg_bytes_per_sec; + float v = funcs[0](time); + for (size_t ch = 0; ch < wav_header.format.channels; ++ch) { + if (!is_unique && ch > 0) + v = funcs[ch](time); + + switch (wav_header.format.bits_per_sample) { + case 8: { + Uint8AndInt8 vs {int8_t(v * (float)0x7F)}; + d[i] = vs.u; + break; + } + case 32: { + Uint8AndInt32 vs {int32_t(v * (float)0x7FFFFFFF)}; + d[i + 0] = vs.u[0]; + d[i + 1] = vs.u[1]; + d[i + 2] = vs.u[2]; + d[i + 3] = vs.u[3]; + break; + } + case 16: + default: { + Uint8AndInt16 vs {int16_t(v * (float)0x7FFF)}; + d[i + 0] = vs.u[0]; + d[i + 1] = vs.u[1]; + break; + } + } + } + i += wav_header.format.block_align; + } + } + pos += i; + return (pos >= size) ? 0 : i; +} + +bool AudioFileSourceFunction::seek(int32_t pos, int dir) { + if (dir == SEEK_SET) { + if (pos < 0 || (uint32_t)pos >= size) + return false; + this->pos = pos; + } else if (dir == SEEK_CUR) { + int32_t p = (int32_t)this->pos + pos; + if (p < 0 || (uint32_t)p >= size) + return false; + this->pos = p; + } else { + int32_t p = (int32_t)this->size + pos; + if (p < 0 || (uint32_t)p >= size) + return false; + this->pos = p; + } + return true; +} + +bool AudioFileSourceFunction::close() { + funcs.clear(); + pos = 0; + size = 0; + is_ready = false; + is_unique = false; + return true; +} + +bool AudioFileSourceFunction::isOpen() { + return is_ready; +} + +uint32_t AudioFileSourceFunction::getSize() { + return size; +} + +uint32_t AudioFileSourceFunction::getPos() { + return pos; +} diff --git a/lib/lib_audio/ESP8266Audio/src/AudioFileSourceFunction.h b/lib/lib_audio/ESP8266Audio/src/AudioFileSourceFunction.h new file mode 100644 index 000000000..6c41229ae --- /dev/null +++ b/lib/lib_audio/ESP8266Audio/src/AudioFileSourceFunction.h @@ -0,0 +1,119 @@ +/* + AudioFileSourceFunction + Audio ouptut generator which can generate WAV file data from function + + Copyright (C) 2021 Hideaki Tai + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef _AUDIOFILESOURCEFUNCTION_H +#define _AUDIOFILESOURCEFUNCTION_H + +#include +#include +#include + +#include "AudioFileSource.h" + +class AudioFileSourceFunction : public AudioFileSource { + union WavHeader { + struct { + // RIFF chunk + struct { + char chunk_id[4]; // "RIFF" + uint32_t chunk_size; // 4 + (8 + sizeof(format_chunk)(16)) + (8 + sizeof(data_chunk)) + char format[4]; // "WAVE" + } riff; + // format chunk + struct { + char chunk_id[4]; // "fmt " + uint32_t chunk_size; // 16 + uint16_t format_tag; // 1: PCM + uint16_t channels; // 1: MONO, 2: STEREO + uint32_t sample_per_sec; // 8000, 11025, 22050, 44100, 48000 + uint32_t avg_bytes_per_sec; // sample_per_sec * channels * bits_per_sample / 8 + uint16_t block_align; // channels * bits_per_sample / 8 + uint16_t bits_per_sample; // 8, 16, 32 + } format; + // data chunk + struct { + char chunk_id[4]; // "data" + uint32_t chunk_size; // num_samples * channels * bytes_per_sample + // audio data follows here... + } data; + }; + uint8_t bytes[44]; + } wav_header; + + union Uint8AndInt8 { + int8_t i; + uint8_t u; + }; + + union Uint8AndInt16 { + int16_t i; + uint8_t u[2]; + }; + + union Uint8AndInt32 { + int32_t i; + uint8_t u[4]; + }; + + using callback_t = std::function; + std::vector funcs; + uint32_t pos; + uint32_t size; + bool is_ready; + bool is_unique; + +public: + AudioFileSourceFunction(float sec, uint16_t channels = 1, uint32_t sample_per_sec = 8000, uint16_t bits_per_sample = 16); + virtual ~AudioFileSourceFunction() override; + + template + bool addAudioGenerators(const F& f, Fs&&... fs) { + funcs.emplace_back(f); + return addAudioGenerators(std::forward(fs)...); + } + bool addAudioGenerators() { + funcs.shrink_to_fit(); + if (funcs.size() == 1) { + is_ready = true; + is_unique = true; + return true; + } else if (funcs.size() == wav_header.format.channels) { + is_ready = true; + is_unique = false; + return true; + } else { + is_ready = false; + is_unique = false; + funcs.clear(); + return false; + } + } + + virtual uint32_t read(void* data, uint32_t len) override; + virtual bool seek(int32_t pos, int dir) override; + + virtual bool close() override; + virtual bool isOpen() override; + + virtual uint32_t getSize() override; + virtual uint32_t getPos() override; +}; + +#endif // _AUDIOFILESOURCEFUNCTION_H diff --git a/lib/lib_audio/ESP8266Audio/src/AudioFileSourceHTTPStream.cpp b/lib/lib_audio/ESP8266Audio/src/AudioFileSourceHTTPStream.cpp index c07eb0944..c5d0b8334 100644 --- a/lib/lib_audio/ESP8266Audio/src/AudioFileSourceHTTPStream.cpp +++ b/lib/lib_audio/ESP8266Audio/src/AudioFileSourceHTTPStream.cpp @@ -18,6 +18,8 @@ along with this program. If not, see . */ +#if defined(ESP32) || defined(ESP8266) + #include "AudioFileSourceHTTPStream.h" AudioFileSourceHTTPStream::AudioFileSourceHTTPStream() @@ -40,7 +42,7 @@ bool AudioFileSourceHTTPStream::open(const char *url) http.begin(client, url); http.setReuse(true); #ifndef ESP32 - http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); + http.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS); #endif int code = http.GET(); if (code != HTTP_CODE_OK) { @@ -84,7 +86,7 @@ retry: cb.st(STATUS_DISCONNECTED, PSTR("Stream disconnected")); http.end(); for (int i = 0; i < reconnectTries; i++) { - char buff[32]; + char buff[64]; sprintf_P(buff, PSTR("Attempting to reconnect, try %d"), i); cb.st(STATUS_RECONNECTING, buff); delay(reconnectDelayMs); @@ -152,3 +154,5 @@ uint32_t AudioFileSourceHTTPStream::getPos() { return pos; } + +#endif diff --git a/lib/lib_audio/ESP8266Audio/src/AudioFileSourceHTTPStream.h b/lib/lib_audio/ESP8266Audio/src/AudioFileSourceHTTPStream.h index e764b45d8..34e54663d 100644 --- a/lib/lib_audio/ESP8266Audio/src/AudioFileSourceHTTPStream.h +++ b/lib/lib_audio/ESP8266Audio/src/AudioFileSourceHTTPStream.h @@ -1,7 +1,7 @@ /* AudioFileSourceHTTPStream Connect to a HTTP based streaming service - + Copyright (C) 2017 Earle F. Philhower, III This program is free software: you can redistribute it and/or modify @@ -18,14 +18,13 @@ along with this program. If not, see . */ -#ifndef _AUDIOFILESOURCEHTTPSTREAM_H -#define _AUDIOFILESOURCEHTTPSTREAM_H +#if defined(ESP32) || defined(ESP8266) +#pragma once #include #ifdef ESP32 #include #else - #include #include #endif #include "AudioFileSource.h" @@ -38,7 +37,7 @@ class AudioFileSourceHTTPStream : public AudioFileSource AudioFileSourceHTTPStream(); AudioFileSourceHTTPStream(const char *url); virtual ~AudioFileSourceHTTPStream() override; - + virtual bool open(const char *url) override; virtual uint32_t read(void *data, uint32_t len) override; virtual uint32_t readNonBlock(void *data, uint32_t len) override; @@ -48,6 +47,7 @@ class AudioFileSourceHTTPStream : public AudioFileSource virtual uint32_t getSize() override; virtual uint32_t getPos() override; bool SetReconnect(int tries, int delayms) { reconnectTries = tries; reconnectDelayMs = delayms; return true; } + void useHTTP10 () { http.useHTTP10(true); } enum { STATUS_HTTPFAIL=2, STATUS_DISCONNECTED, STATUS_RECONNECTING, STATUS_RECONNECTED, STATUS_NODATA }; @@ -64,3 +64,4 @@ class AudioFileSourceHTTPStream : public AudioFileSource #endif + diff --git a/lib/lib_audio/ESP8266Audio/src/AudioFileSourceICYStream.cpp b/lib/lib_audio/ESP8266Audio/src/AudioFileSourceICYStream.cpp index cc7cc5e27..63c84f328 100644 --- a/lib/lib_audio/ESP8266Audio/src/AudioFileSourceICYStream.cpp +++ b/lib/lib_audio/ESP8266Audio/src/AudioFileSourceICYStream.cpp @@ -17,6 +17,9 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ + +#if defined(ESP32) || defined(ESP8266) + #define _GNU_SOURCE #include "AudioFileSourceICYStream.h" @@ -83,12 +86,16 @@ AudioFileSourceICYStream::~AudioFileSourceICYStream() uint32_t AudioFileSourceICYStream::readInternal(void *data, uint32_t len, bool nonBlock) { + // Ensure we can't possibly read 2 ICY headers in a single go #355 + if (icyMetaInt > 1) { + len = std::min((int)(icyMetaInt >> 1), (int)len); + } retry: if (!http.connected()) { cb.st(STATUS_DISCONNECTED, PSTR("Stream disconnected")); http.end(); for (int i = 0; i < reconnectTries; i++) { - char buff[32]; + char buff[64]; sprintf_P(buff, PSTR("Attempting to reconnect, try %d"), i); cb.st(STATUS_RECONNECTING, buff); delay(reconnectDelayMs); @@ -211,3 +218,5 @@ retry: icyByteCount += ret; return read; } + +#endif diff --git a/lib/lib_audio/ESP8266Audio/src/AudioFileSourceICYStream.h b/lib/lib_audio/ESP8266Audio/src/AudioFileSourceICYStream.h index dacbf7efd..97688a57d 100644 --- a/lib/lib_audio/ESP8266Audio/src/AudioFileSourceICYStream.h +++ b/lib/lib_audio/ESP8266Audio/src/AudioFileSourceICYStream.h @@ -1,7 +1,7 @@ /* AudioFileSourceHTTPStream Connect to a HTTP based streaming service - + Copyright (C) 2017 Earle F. Philhower, III This program is free software: you can redistribute it and/or modify @@ -18,8 +18,8 @@ along with this program. If not, see . */ -#ifndef _AUDIOFILESOURCEICYSTREAM_H -#define _AUDIOFILESOURCEICYSTREAM_H +#if defined(ESP32) || defined(ESP8266) +#pragma once #include #ifdef ESP32 @@ -36,7 +36,7 @@ class AudioFileSourceICYStream : public AudioFileSourceHTTPStream AudioFileSourceICYStream(); AudioFileSourceICYStream(const char *url); virtual ~AudioFileSourceICYStream() override; - + virtual bool open(const char *url) override; private: @@ -45,5 +45,4 @@ class AudioFileSourceICYStream : public AudioFileSourceHTTPStream int icyByteCount; }; - #endif diff --git a/lib/lib_audio/ESP8266Audio/src/AudioFileSourceID3.cpp b/lib/lib_audio/ESP8266Audio/src/AudioFileSourceID3.cpp index 1723b8d4d..2a22bb180 100644 --- a/lib/lib_audio/ESP8266Audio/src/AudioFileSourceID3.cpp +++ b/lib/lib_audio/ESP8266Audio/src/AudioFileSourceID3.cpp @@ -143,6 +143,7 @@ uint32_t AudioFileSourceID3::read(void *data, uint32_t len) if (ret<10) return ret; if ((buff[0]!='I') || (buff[1]!='D') || (buff[2]!='3') || (buff[3]>0x04) || (buff[3]<0x02) || (buff[4]!=0)) { + cb.md("eof", false, "id3"); return 10 + src->read(buff+10, len-10); } @@ -212,9 +213,9 @@ uint32_t AudioFileSourceID3::read(void *data, uint32_t len) // Read the value and send to callback char value[64]; - uint16_t i; + uint32_t i; bool isUnicode = (id3.getByte()==1) ? true : false; - for (i=0; iread(data, len); } diff --git a/lib/lib_audio/ESP8266Audio/src/AudioFileSourceSD.h b/lib/lib_audio/ESP8266Audio/src/AudioFileSourceSD.h index 3eb079bce..eacd99188 100644 --- a/lib/lib_audio/ESP8266Audio/src/AudioFileSourceSD.h +++ b/lib/lib_audio/ESP8266Audio/src/AudioFileSourceSD.h @@ -1,7 +1,7 @@ /* AudioFileSourceSPIFFS Input SD card "file" to be used by AudioGenerator - + Copyright (C) 2017 Earle F. Philhower, III This program is free software: you can redistribute it and/or modify @@ -22,10 +22,6 @@ #define _AUDIOFILESOURCESD_H #include "AudioFileSource.h" -#ifdef ESP8266 -#include -#include -#endif #include @@ -35,7 +31,7 @@ class AudioFileSourceSD : public AudioFileSource AudioFileSourceSD(); AudioFileSourceSD(const char *filename); virtual ~AudioFileSourceSD() override; - + virtual bool open(const char *filename) override; virtual uint32_t read(void *data, uint32_t len) override; virtual bool seek(int32_t pos, int dir) override; @@ -50,3 +46,4 @@ class AudioFileSourceSD : public AudioFileSource #endif + diff --git a/lib/lib_audio/ESP8266Audio/src/AudioFileSourceSPIRAMBuffer.cpp b/lib/lib_audio/ESP8266Audio/src/AudioFileSourceSPIRAMBuffer.cpp index fafadcea8..2c9eb3b96 100644 --- a/lib/lib_audio/ESP8266Audio/src/AudioFileSourceSPIRAMBuffer.cpp +++ b/lib/lib_audio/ESP8266Audio/src/AudioFileSourceSPIRAMBuffer.cpp @@ -22,6 +22,8 @@ along with this program. If not, see . */ +#if defined(ESP32) || defined(ESP8266) + #include #include "AudioFileSourceSPIRAMBuffer.h" @@ -165,3 +167,5 @@ bool AudioFileSourceSPIRAMBuffer::loop() } return true; } + +#endif diff --git a/lib/lib_audio/ESP8266Audio/src/AudioFileSourceSPIRAMBuffer.h b/lib/lib_audio/ESP8266Audio/src/AudioFileSourceSPIRAMBuffer.h index 19d915f1a..d8c05540b 100644 --- a/lib/lib_audio/ESP8266Audio/src/AudioFileSourceSPIRAMBuffer.h +++ b/lib/lib_audio/ESP8266Audio/src/AudioFileSourceSPIRAMBuffer.h @@ -19,8 +19,8 @@ along with this program. If not, see . */ -#ifndef _AUDIOFILESOURCESPIRAMBUFFER_H -#define _AUDIOFILESOURCESPIRAMBUFFER_H +#if defined(ESP32) || defined(ESP8266) +#pragma once #include "AudioFileSource.h" #include diff --git a/lib/lib_audio/ESP8266Audio/src/AudioGenerator.h b/lib/lib_audio/ESP8266Audio/src/AudioGenerator.h index 889b2285c..6d974876a 100644 --- a/lib/lib_audio/ESP8266Audio/src/AudioGenerator.h +++ b/lib/lib_audio/ESP8266Audio/src/AudioGenerator.h @@ -35,6 +35,7 @@ class AudioGenerator virtual bool loop() { return false; }; virtual bool stop() { return false; }; virtual bool isRunning() { return false;}; + virtual void desync () { }; public: virtual bool RegisterMetadataCB(AudioStatus::metadataCBFn fn, void *data) { return cb.RegisterMetadataCB(fn, data); } diff --git a/lib/lib_audio/ESP8266Audio/src/AudioGeneratorAAC.cpp b/lib/lib_audio/ESP8266Audio/src/AudioGeneratorAAC.cpp index 98f720786..c7568676a 100644 --- a/lib/lib_audio/ESP8266Audio/src/AudioGeneratorAAC.cpp +++ b/lib/lib_audio/ESP8266Audio/src/AudioGeneratorAAC.cpp @@ -144,8 +144,13 @@ bool AudioGeneratorAAC::loop() // If we've got data, try and pump it out... while (validSamples) { - lastSample[0] = outSample[curSample*2]; - lastSample[1] = outSample[curSample*2 + 1]; + if (lastChannels == 1) { + lastSample[0] = outSample[curSample]; + lastSample[1] = outSample[curSample]; + } else { + lastSample[0] = outSample[curSample*2]; + lastSample[1] = outSample[curSample*2 + 1]; + } if (!output->ConsumeSample(lastSample)) goto done; // Can't send, but no error detected validSamples--; curSample++; diff --git a/lib/lib_audio/ESP8266Audio/src/AudioGeneratorFLAC.cpp b/lib/lib_audio/ESP8266Audio/src/AudioGeneratorFLAC.cpp index 1af00ce3a..bd6e801c9 100644 --- a/lib/lib_audio/ESP8266Audio/src/AudioGeneratorFLAC.cpp +++ b/lib/lib_audio/ESP8266Audio/src/AudioGeneratorFLAC.cpp @@ -177,9 +177,9 @@ FLAC__StreamDecoderWriteStatus AudioGeneratorFLAC::write_cb(const FLAC__StreamDe // Hackish warning here. FLAC sends the buffer but doesn't free it until the next call to decode_frame, so we stash // the pointers here and use it in our loop() instead of memcpy()'ing into yet another buffer. buffLen = frame->header.blocksize; - buff[0] = buffer[0]; - if (frame->header.channels>1) buff[1] = buffer[1]; - else buff[1] = buffer[0]; + buff[0] = (const int *)buffer[0]; + if (frame->header.channels>1) buff[1] = (const int *)buffer[1]; + else buff[1] = (const int *)buffer[0]; buffPtr = 0; return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; } diff --git a/lib/lib_audio/ESP8266Audio/src/AudioGeneratorMP3.cpp b/lib/lib_audio/ESP8266Audio/src/AudioGeneratorMP3.cpp index 2979a5848..d962c9e0b 100644 --- a/lib/lib_audio/ESP8266Audio/src/AudioGeneratorMP3.cpp +++ b/lib/lib_audio/ESP8266Audio/src/AudioGeneratorMP3.cpp @@ -1,7 +1,7 @@ /* AudioGeneratorMP3 Wrap libmad MP3 library to play audio - + Copyright (C) 2017 Earle F. Philhower, III This program is free software: you can redistribute it and/or modify @@ -29,11 +29,23 @@ AudioGeneratorMP3::AudioGeneratorMP3() buff = NULL; nsCountMax = 1152/32; madInitted = false; - preallocateSpace = NULL; - preallocateSize = 0; } -AudioGeneratorMP3::AudioGeneratorMP3(void *space, int size) +AudioGeneratorMP3::AudioGeneratorMP3(void *space, int size): preallocateSpace(space), preallocateSize(size) +{ + running = false; + file = NULL; + output = NULL; + buff = NULL; + nsCountMax = 1152/32; + madInitted = false; +} + +AudioGeneratorMP3::AudioGeneratorMP3(void *buff, int buffSize, void *stream, int streamSize, void *frame, int frameSize, void *synth, int synthSize): + preallocateSpace(buff), preallocateSize(buffSize), + preallocateStreamSpace(stream), preallocateStreamSize(streamSize), + preallocateFrameSpace(frame), preallocateFrameSize(frameSize), + preallocateSynthSpace(synth), preallocateSynthSize(synthSize) { running = false; file = NULL; @@ -41,8 +53,6 @@ AudioGeneratorMP3::AudioGeneratorMP3(void *space, int size) buff = NULL; nsCountMax = 1152/32; madInitted = false; - preallocateSpace = space; - preallocateSize = size; } AudioGeneratorMP3::~AudioGeneratorMP3() @@ -52,7 +62,7 @@ AudioGeneratorMP3::~AudioGeneratorMP3() free(synth); free(frame); free(stream); - } + } } @@ -109,7 +119,12 @@ enum mad_flow AudioGeneratorMP3::Input() if (stream->next_frame) { unused = lastBuffLen - (stream->next_frame - buff); - memmove(buff, stream->next_frame, unused); + if (unused < 0) { + desync(); + unused = 0; + } else { + memmove(buff, stream->next_frame, unused); + } stream->next_frame = NULL; } @@ -125,6 +140,10 @@ enum mad_flow AudioGeneratorMP3::Input() // Can't read any from the file, and we don't have anything left. It's done.... return MAD_FLOW_STOP; } + if (len < 0) { + desync(); + unused = 0; + } lastBuffLen = len + unused; mad_stream_buffer(stream, buff, lastBuffLen); @@ -132,6 +151,16 @@ enum mad_flow AudioGeneratorMP3::Input() return MAD_FLOW_CONTINUE; } +void AudioGeneratorMP3::desync () +{ + audioLogger->printf_P(PSTR("MP3:desync\n")); + if (stream) { + stream->next_frame = nullptr; + stream->this_frame = nullptr; + stream->sync = 0; + } + lastBuffLen = 0; +} bool AudioGeneratorMP3::DecodeNextFrame() { @@ -153,7 +182,7 @@ bool AudioGeneratorMP3::GetOneSample(int16_t sample[2]) output->SetChannels(synth->pcm.channels); lastChannels = synth->pcm.channels; } - + // If we're here, we have one decoded frame and sent 0 or more samples out if (samplePtr < synth->pcm.length) { sample[AudioOutput::LEFTCHANNEL ] = synth->pcm.samples[0][samplePtr]; @@ -161,7 +190,7 @@ bool AudioGeneratorMP3::GetOneSample(int16_t sample[2]) samplePtr++; } else { samplePtr = 0; - + switch ( mad_synth_frame_onens(synth, frame, nsCount++) ) { case MAD_FLOW_STOP: case MAD_FLOW_BREAK: audioLogger->printf_P(PSTR("msf1ns failed\n")); @@ -196,6 +225,18 @@ retry: } if (!DecodeNextFrame()) { + if (stream->error == MAD_ERROR_BUFLEN) { + // randomly seeking can lead to endless + // and unrecoverable "MAD_ERROR_BUFLEN" loop + audioLogger->printf_P(PSTR("MP3:ERROR_BUFLEN %d\n"), unrecoverable); + if (++unrecoverable >= 3) { + unrecoverable = 0; + stop(); + return running; + } + } else { + unrecoverable = 0; + } goto retry; } samplePtr = 9999; @@ -229,6 +270,9 @@ bool AudioGeneratorMP3::begin(AudioFileSource *source, AudioOutput *output) return false; // Error } + // Reset error count from previous file + unrecoverable = 0; + output->SetBitsPerSample(16); // Constant for MP3 decoder output->SetChannels(2); @@ -243,16 +287,32 @@ bool AudioGeneratorMP3::begin(AudioFileSource *source, AudioOutput *output) lastBuffLen = 0; // Allocate all large memory chunks - if (preallocateSpace) { + if (preallocateStreamSize + preallocateFrameSize + preallocateSynthSize) { + if (preallocateSize >= preAllocBuffSize() && + preallocateStreamSize >= preAllocStreamSize() && + preallocateFrameSize >= preAllocFrameSize() && + preallocateSynthSize >= preAllocSynthSize()) { + buff = reinterpret_cast(preallocateSpace); + stream = reinterpret_cast(preallocateStreamSpace); + frame = reinterpret_cast(preallocateFrameSpace); + synth = reinterpret_cast(preallocateSynthSpace); + } + else { + audioLogger->printf_P("OOM error in MP3: Want %d/%d/%d/%d bytes, have %d/%d/%d/%d bytes preallocated.\n", + preAllocBuffSize(), preAllocStreamSize(), preAllocFrameSize(), preAllocSynthSize(), + preallocateSize, preallocateStreamSize, preallocateFrameSize, preallocateSynthSize); + return false; + } + } else if (preallocateSpace) { uint8_t *p = reinterpret_cast(preallocateSpace); buff = reinterpret_cast(p); - p += (buffLen+7) & ~7; + p += preAllocBuffSize(); stream = reinterpret_cast(p); - p += (sizeof(struct mad_stream)+7) & ~7; + p += preAllocStreamSize(); frame = reinterpret_cast(p); - p += (sizeof(struct mad_frame)+7) & ~7; + p += preAllocFrameSize(); synth = reinterpret_cast(p); - p += (sizeof(struct mad_synth)+7) & ~7; + p += preAllocSynthSize(); int neededBytes = p - reinterpret_cast(preallocateSpace); if (neededBytes > preallocateSize) { audioLogger->printf_P("OOM error in MP3: Want %d bytes, have %d bytes preallocated.\n", neededBytes, preallocateSize); @@ -272,19 +332,17 @@ bool AudioGeneratorMP3::begin(AudioFileSource *source, AudioOutput *output) stream = NULL; frame = NULL; synth = NULL; - uint32_t size = buffLen + sizeof(struct mad_stream) + sizeof(struct mad_frame) + sizeof(struct mad_synth); - audioLogger->printf_P("OOM error in MP3: Want %d bytes\n", size); return false; } } - + mad_stream_init(stream); mad_frame_init(frame); mad_synth_init(synth); synth->pcm.length = 0; mad_stream_options(stream, 0); // TODO - add options support madInitted = true; - + running = true; return true; } @@ -304,7 +362,7 @@ extern "C" { { return 8192; } -#elif defined(ESP8266) +#elif defined(ESP8266) && !defined(CORE_MOCK) #include extern cont_t g_cont; @@ -351,3 +409,4 @@ extern "C" { } #endif } + diff --git a/lib/lib_audio/ESP8266Audio/src/AudioGeneratorMP3.h b/lib/lib_audio/ESP8266Audio/src/AudioGeneratorMP3.h index 750cecaca..0f4e439e9 100644 --- a/lib/lib_audio/ESP8266Audio/src/AudioGeneratorMP3.h +++ b/lib/lib_audio/ESP8266Audio/src/AudioGeneratorMP3.h @@ -30,17 +30,31 @@ class AudioGeneratorMP3 : public AudioGenerator public: AudioGeneratorMP3(); AudioGeneratorMP3(void *preallocateSpace, int preallocateSize); + AudioGeneratorMP3(void *buff, int buffSize, void *stream, int streamSize, void *frame, int frameSize, void *synth, int synthSize); virtual ~AudioGeneratorMP3() override; virtual bool begin(AudioFileSource *source, AudioOutput *output) override; virtual bool loop() override; virtual bool stop() override; virtual bool isRunning() override; - - protected: - void *preallocateSpace; - int preallocateSize; + virtual void desync () override; - const int buffLen = 0x600; // Slightly larger than largest MP3 frame + static constexpr int preAllocSize () { return preAllocBuffSize() + preAllocStreamSize() + preAllocFrameSize() + preAllocSynthSize(); } + static constexpr int preAllocBuffSize () { return ((buffLen + 7) & ~7); } + static constexpr int preAllocStreamSize () { return ((sizeof(struct mad_stream) + 7) & ~7); } + static constexpr int preAllocFrameSize () { return (sizeof(struct mad_frame) + 7) & ~7; } + static constexpr int preAllocSynthSize () { return (sizeof(struct mad_synth) + 7) & ~7; } + + protected: + void *preallocateSpace = nullptr; + int preallocateSize = 0; + void *preallocateStreamSpace = nullptr; + int preallocateStreamSize = 0; + void *preallocateFrameSpace = nullptr; + int preallocateFrameSize = 0; + void *preallocateSynthSpace = nullptr; + int preallocateSynthSize = 0; + + static constexpr int buffLen = 0x600; // Slightly larger than largest MP3 frame unsigned char *buff; int lastReadPos; int lastBuffLen; @@ -62,6 +76,8 @@ class AudioGeneratorMP3 : public AudioGenerator bool DecodeNextFrame(); bool GetOneSample(int16_t sample[2]); + private: + int unrecoverable = 0; }; #endif diff --git a/lib/lib_audio/ESP8266Audio/src/AudioOutputFilterBiquad.cpp b/lib/lib_audio/ESP8266Audio/src/AudioOutputFilterBiquad.cpp new file mode 100644 index 000000000..5c8af8e5c --- /dev/null +++ b/lib/lib_audio/ESP8266Audio/src/AudioOutputFilterBiquad.cpp @@ -0,0 +1,245 @@ +/* + AudioOutputFilterBiquad + Implements a Biquad filter + + Copyright (C) 2012 Nigel Redmon + Copyright (C) 2021 William Bérubé + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "AudioOutputFilterBiquad.h" + +AudioOutputFilterBiquad::AudioOutputFilterBiquad(AudioOutput *sink) +{ + this->sink = sink; + + type = bq_type_lowpass; + a0 = 1.0; + a1 = a2 = b1 = b2 = 0.0; + Fc = 0.50; + Q = 0.707; + peakGain = 0.0; + z1 = z2 = 0.0; +} + +AudioOutputFilterBiquad::AudioOutputFilterBiquad(int type, float Fc, float Q, float peakGain, AudioOutput *sink) +{ + this->sink = sink; + + SetBiquad(type, Fc, Q, peakGain); + z1 = z2 = 0.0; +} + +AudioOutputFilterBiquad::~AudioOutputFilterBiquad() {} + +bool AudioOutputFilterBiquad::SetRate(int hz) +{ + return sink->SetRate(hz); +} + +bool AudioOutputFilterBiquad::SetBitsPerSample(int bits) +{ + return sink->SetBitsPerSample(bits); +} + +bool AudioOutputFilterBiquad::SetChannels(int channels) +{ + return sink->SetChannels(channels); +} + +bool AudioOutputFilterBiquad::SetGain(float gain) +{ + return sink->SetGain(gain); +} + +void AudioOutputFilterBiquad::SetType(int type) +{ + this->type = type; + CalcBiquad(); +} + +void AudioOutputFilterBiquad::SetFc(float Fc) +{ + this->Fc = Fc; + CalcBiquad(); +} + +void AudioOutputFilterBiquad::SetQ(float Q) +{ + this->Q = Q; + CalcBiquad(); +} + +void AudioOutputFilterBiquad::SetPeakGain(float peakGain) +{ + this->peakGain = peakGain; + CalcBiquad(); +} + +void AudioOutputFilterBiquad::SetBiquad(int type, float Fc, float Q, float peakGain) +{ + this->type = type; + this->Fc = Fc; + this->Q = Q; + this->peakGain = peakGain; + CalcBiquad(); +} + +void AudioOutputFilterBiquad::CalcBiquad() +{ + float norm; + float V = pow(10, fabs(peakGain) / 20.0); + float K = tan(M_PI * Fc); + + switch (this->type) { + case bq_type_lowpass: + norm = 1 / (1 + K / Q + K * K); + a0 = K * K * norm; + a1 = 2 * a0; + a2 = a0; + b1 = 2 * (K * K - 1) * norm; + b2 = (1 - K / Q + K * K) * norm; + break; + + case bq_type_highpass: + norm = 1 / (1 + K / Q + K * K); + a0 = 1 * norm; + a1 = -2 * a0; + a2 = a0; + b1 = 2 * (K * K - 1) * norm; + b2 = (1 - K / Q + K * K) * norm; + break; + + case bq_type_bandpass: + norm = 1 / (1 + K / Q + K * K); + a0 = K / Q * norm; + a1 = 0; + a2 = -a0; + b1 = 2 * (K * K - 1) * norm; + b2 = (1 - K / Q + K * K) * norm; + break; + + case bq_type_notch: + norm = 1 / (1 + K / Q + K * K); + a0 = (1 + K * K) * norm; + a1 = 2 * (K * K - 1) * norm; + a2 = a0; + b1 = a1; + b2 = (1 - K / Q + K * K) * norm; + break; + + case bq_type_peak: + if (peakGain >= 0) { // boost + norm = 1 / (1 + 1/Q * K + K * K); + a0 = (1 + V/Q * K + K * K) * norm; + a1 = 2 * (K * K - 1) * norm; + a2 = (1 - V/Q * K + K * K) * norm; + b1 = a1; + b2 = (1 - 1/Q * K + K * K) * norm; + } else { // cut + norm = 1 / (1 + V/Q * K + K * K); + a0 = (1 + 1/Q * K + K * K) * norm; + a1 = 2 * (K * K - 1) * norm; + a2 = (1 - 1/Q * K + K * K) * norm; + b1 = a1; + b2 = (1 - V/Q * K + K * K) * norm; + } + break; + + case bq_type_lowshelf: + if (peakGain >= 0) { // boost + norm = 1 / (1 + sqrt(2) * K + K * K); + a0 = (1 + sqrt(2*V) * K + V * K * K) * norm; + a1 = 2 * (V * K * K - 1) * norm; + a2 = (1 - sqrt(2*V) * K + V * K * K) * norm; + b1 = 2 * (K * K - 1) * norm; + b2 = (1 - sqrt(2) * K + K * K) * norm; + } + else { // cut + norm = 1 / (1 + sqrt(2*V) * K + V * K * K); + a0 = (1 + sqrt(2) * K + K * K) * norm; + a1 = 2 * (K * K - 1) * norm; + a2 = (1 - sqrt(2) * K + K * K) * norm; + b1 = 2 * (V * K * K - 1) * norm; + b2 = (1 - sqrt(2*V) * K + V * K * K) * norm; + } + break; + + case bq_type_highshelf: + if (peakGain >= 0) { // boost + norm = 1 / (1 + sqrt(2) * K + K * K); + a0 = (V + sqrt(2*V) * K + K * K) * norm; + a1 = 2 * (K * K - V) * norm; + a2 = (V - sqrt(2*V) * K + K * K) * norm; + b1 = 2 * (K * K - 1) * norm; + b2 = (1 - sqrt(2) * K + K * K) * norm; + } + else { // cut + norm = 1 / (V + sqrt(2*V) * K + K * K); + a0 = (1 + sqrt(2) * K + K * K) * norm; + a1 = 2 * (K * K - 1) * norm; + a2 = (1 - sqrt(2) * K + K * K) * norm; + b1 = 2 * (K * K - V) * norm; + b2 = (V - sqrt(2*V) * K + K * K) * norm; + } + break; + } + + i_a0 = a0 * BQ_DECAL; + i_a1 = a1 * BQ_DECAL; + i_a2 = a2 * BQ_DECAL; + + i_b1 = b1 * BQ_DECAL; + i_b2 = b2 * BQ_DECAL; + + i_lz1 = i_rz1 = z1 * BQ_DECAL; + i_lz2 = i_rz2 = z2 * BQ_DECAL; + + i_Fc = Fc * BQ_DECAL; + i_Q = Q * BQ_DECAL; + i_peakGain = peakGain * BQ_DECAL; +} + +bool AudioOutputFilterBiquad::begin() +{ + return sink->begin(); +} + +bool AudioOutputFilterBiquad::ConsumeSample(int16_t sample[2]) +{ + + int32_t leftSample = (sample[LEFTCHANNEL] << BQ_SHIFT) / 2; + int32_t rightSample = (sample[RIGHTCHANNEL] << BQ_SHIFT) / 2; + + int64_t leftOutput = ((leftSample * i_a0) >> BQ_SHIFT) + i_lz1; + i_lz1 = ((leftSample * i_a1) >> BQ_SHIFT) + i_lz2 - ((i_b1 * leftOutput) >> BQ_SHIFT); + i_lz2 = ((leftSample * i_a2) >> BQ_SHIFT) - ((i_b2 * leftOutput) >> BQ_SHIFT); + + int64_t rightOutput = ((rightSample * i_a0) >> BQ_SHIFT) + i_rz1; + i_rz1 = ((rightSample * i_a1) >> BQ_SHIFT) + i_rz2 - ((i_b1 * rightOutput) >> BQ_SHIFT); + i_rz2 = ((rightSample * i_a2) >> BQ_SHIFT) - ((i_b2 * rightOutput) >> BQ_SHIFT); + + int16_t out[2]; + out[LEFTCHANNEL] = (int16_t)(leftOutput >> BQ_SHIFT); + out[RIGHTCHANNEL] = (int16_t)(rightOutput >> BQ_SHIFT); + + return sink->ConsumeSample(out); +} + +bool AudioOutputFilterBiquad::stop() +{ + return sink->stop(); +} diff --git a/lib/lib_audio/ESP8266Audio/src/AudioOutputFilterBiquad.h b/lib/lib_audio/ESP8266Audio/src/AudioOutputFilterBiquad.h new file mode 100644 index 000000000..f4dc95f51 --- /dev/null +++ b/lib/lib_audio/ESP8266Audio/src/AudioOutputFilterBiquad.h @@ -0,0 +1,80 @@ +/* + AudioOutputFilterBiquad + Implements a Biquad filter + + Copyright (C) 2012 Nigel Redmon + Copyright (C) 2021 William Bérubé + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef _AudioOutputFilterBiquad_H +#define _AudioOutputFilterBiquad_H + +#include "AudioOutput.h" + +#define BQ_SHIFT 16 +#define BQ_DECAL 65536 + +enum { + bq_type_lowpass = 0, + bq_type_highpass, + bq_type_bandpass, + bq_type_notch, + bq_type_peak, + bq_type_lowshelf, + bq_type_highshelf +}; + +class AudioOutputFilterBiquad : public AudioOutput +{ + public: + AudioOutputFilterBiquad(AudioOutput *sink); + AudioOutputFilterBiquad(int type, float Fc, float Q, float peakGain, AudioOutput *sink); + virtual ~AudioOutputFilterBiquad() override; + virtual bool SetRate(int hz) override; + virtual bool SetBitsPerSample(int bits) override; + virtual bool SetChannels(int chan) override; + virtual bool SetGain(float f) override; + virtual bool begin() override; + virtual bool ConsumeSample(int16_t sample[2]) override; + virtual bool stop() override; + + private: + void SetType(int type); + void SetFc(float Fc); + void SetQ(float Q); + void SetPeakGain(float peakGain); + void SetBiquad(int type, float Fc, float Q, float peakGain); + + protected: + AudioOutput *sink; + int buffSize; + int16_t *leftSample; + int16_t *rightSample; + int writePtr; + int readPtr; + bool filled; + int type; + void CalcBiquad(); + int64_t i_a0, i_a1, i_a2, i_b1, i_b2; + int64_t i_Fc, i_Q, i_peakGain; + int64_t i_lz1, i_lz2, i_rz1, i_rz2; + float a0, a1, a2, b1, b2; + float Fc, Q, peakGain; + float z1, z2; +}; + +#endif + diff --git a/lib/lib_audio/ESP8266Audio/src/AudioOutputI2S.cpp b/lib/lib_audio/ESP8266Audio/src/AudioOutputI2S.cpp index c028df105..cd915f58b 100644 --- a/lib/lib_audio/ESP8266Audio/src/AudioOutputI2S.cpp +++ b/lib/lib_audio/ESP8266Audio/src/AudioOutputI2S.cpp @@ -1,7 +1,7 @@ /* AudioOutputI2S Base class for I2S interface port - + Copyright (C) 2017 Earle F. Philhower, III This program is free software: you can redistribute it and/or modify @@ -21,11 +21,12 @@ #include #ifdef ESP32 #include "driver/i2s.h" -#else +#elif defined(ARDUINO_ARCH_RP2040) || defined(ESP8266) #include #endif #include "AudioOutputI2S.h" +#if defined(ESP32) || defined(ESP8266) AudioOutputI2S::AudioOutputI2S(int port, int output_mode, int dma_buf_count, int use_apll) { this->portNo = port; @@ -35,115 +36,93 @@ AudioOutputI2S::AudioOutputI2S(int port, int output_mode, int dma_buf_count, int output_mode = EXTERNAL_I2S; } this->output_mode = output_mode; -#ifdef ESP32 - if (!i2sOn) { - if (use_apll == APLL_AUTO) { - // don't use audio pll on buggy rev0 chips - use_apll = APLL_DISABLE; - esp_chip_info_t out_info; - esp_chip_info(&out_info); - if(out_info.revision > 0) { - use_apll = APLL_ENABLE; - } - } + this->use_apll = use_apll; - i2s_mode_t mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX); -#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 - if (output_mode == INTERNAL_DAC) { - mode = (i2s_mode_t)(mode | I2S_MODE_DAC_BUILT_IN); - } else if (output_mode == INTERNAL_PDM) { - mode = (i2s_mode_t)(mode | I2S_MODE_PDM); - } -#endif - - i2s_comm_format_t comm_fmt = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB); - if (output_mode == INTERNAL_DAC) { - comm_fmt = (i2s_comm_format_t)I2S_COMM_FORMAT_I2S_MSB; - } - - i2s_config_t i2s_config_dac = { - .mode = mode, - .sample_rate = 44100, - .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, - .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, - .communication_format = comm_fmt, - .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // lowest interrupt priority - .dma_buf_count = dma_buf_count, - .dma_buf_len = 64, - .use_apll = use_apll // Use audio PLL - }; - audioLogger->printf("+%d %p\n", portNo, &i2s_config_dac); - if (i2s_driver_install((i2s_port_t)portNo, &i2s_config_dac, 0, NULL) != ESP_OK) { - audioLogger->println("ERROR: Unable to install I2S drives\n"); - } - if (output_mode == INTERNAL_DAC || output_mode == INTERNAL_PDM) { - i2s_set_pin((i2s_port_t)portNo, NULL); - i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN); - } else { - SetPinout(26, 25, 22); - } - i2s_zero_dma_buffer((i2s_port_t)portNo); - } -#else - (void) dma_buf_count; - (void) use_apll; - if (!i2sOn) { - orig_bck = READ_PERI_REG(PERIPHS_IO_MUX_MTDO_U); - orig_ws = READ_PERI_REG(PERIPHS_IO_MUX_GPIO2_U); - i2s_begin(); - } -#endif - i2sOn = true; + //set defaults mono = false; bps = 16; channels = 2; + hertz = 44100; + bclkPin = 26; + wclkPin = 25; + doutPin = 22; SetGain(1.0); - SetRate(44100); // Default } -AudioOutputI2S::~AudioOutputI2S() +bool AudioOutputI2S::SetPinout() { -#ifdef ESP32 - if (i2sOn) { - audioLogger->printf("UNINSTALL I2S\n"); - i2s_driver_uninstall((i2s_port_t)portNo); //stop & destroy i2s driver - } -#else - if (i2sOn) i2s_end(); -#endif - i2sOn = false; + #ifdef ESP32 + if (output_mode == INTERNAL_DAC || output_mode == INTERNAL_PDM) + return false; // Not allowed + + i2s_pin_config_t pins = { + .bck_io_num = bclkPin, + .ws_io_num = wclkPin, + .data_out_num = doutPin, + .data_in_num = I2S_PIN_NO_CHANGE}; + i2s_set_pin((i2s_port_t)portNo, &pins); + return true; + #else + (void)bclkPin; + (void)wclkPin; + (void)doutPin; + return false; + #endif } bool AudioOutputI2S::SetPinout(int bclk, int wclk, int dout) { -#ifdef ESP32 - if (output_mode == INTERNAL_DAC || output_mode == INTERNAL_PDM) return false; // Not allowed + bclkPin = bclk; + wclkPin = wclk; + doutPin = dout; + if (i2sOn) + return SetPinout(); - i2s_pin_config_t pins = { - .bck_io_num = bclk, - .ws_io_num = wclk, - .data_out_num = dout, - .data_in_num = I2S_PIN_NO_CHANGE - }; - i2s_set_pin((i2s_port_t)portNo, &pins); return true; -#else - (void) bclk; - (void) wclk; - (void) dout; - return false; +} +#elif defined(ARDUINO_ARCH_RP2040) +AudioOutputI2S::AudioOutputI2S(long sampleRate, pin_size_t sck, pin_size_t data) { + i2sOn = false; + mono = false; + bps = 16; + channels = 2; + hertz = sampleRate; + bclkPin = sck; + doutPin = data; + SetGain(1.0); +} #endif + +AudioOutputI2S::~AudioOutputI2S() +{ + #ifdef ESP32 + if (i2sOn) { + audioLogger->printf("UNINSTALL I2S\n"); + i2s_driver_uninstall((i2s_port_t)portNo); //stop & destroy i2s driver + } + #elif defined(ESP8266) + if (i2sOn) + i2s_end(); + #elif defined(ARDUINO_ARCH_RP2040) + stop(); + #endif + i2sOn = false; } bool AudioOutputI2S::SetRate(int hz) { // TODO - have a list of allowable rates from constructor, check them this->hertz = hz; -#ifdef ESP32 - i2s_set_sample_rates((i2s_port_t)portNo, AdjustI2SRate(hz)); -#else - i2s_set_rate(AdjustI2SRate(hz)); -#endif + if (i2sOn) + { + #ifdef ESP32 + i2s_set_sample_rates((i2s_port_t)portNo, AdjustI2SRate(hz)); + #elif defined(ESP8266) + i2s_set_rate(AdjustI2SRate(hz)); + #elif defined(ARDUINO_ARCH_RP2040) + I2S.setFrequency(hz); + #endif + } return true; } @@ -167,13 +146,106 @@ bool AudioOutputI2S::SetOutputModeMono(bool mono) return true; } -bool AudioOutputI2S::begin() +bool AudioOutputI2S::begin(bool txDAC) { + #ifdef ESP32 + if (!i2sOn) + { + if (use_apll == APLL_AUTO) + { + // don't use audio pll on buggy rev0 chips + use_apll = APLL_DISABLE; + esp_chip_info_t out_info; + esp_chip_info(&out_info); + if (out_info.revision > 0) + { + use_apll = APLL_ENABLE; + } + } + + i2s_mode_t mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX); + if (output_mode == INTERNAL_DAC) + { + mode = (i2s_mode_t)(mode | I2S_MODE_DAC_BUILT_IN); + } + else if (output_mode == INTERNAL_PDM) + { + mode = (i2s_mode_t)(mode | I2S_MODE_PDM); + } + + i2s_comm_format_t comm_fmt = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB); + if (output_mode == INTERNAL_DAC) + { + comm_fmt = (i2s_comm_format_t)I2S_COMM_FORMAT_I2S_MSB; + } + + i2s_config_t i2s_config_dac = { + .mode = mode, + .sample_rate = 44100, + .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, + .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, + .communication_format = comm_fmt, + .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // lowest interrupt priority + .dma_buf_count = dma_buf_count, + .dma_buf_len = 64, + .use_apll = use_apll // Use audio PLL + }; + audioLogger->printf("+%d %p\n", portNo, &i2s_config_dac); + if (i2s_driver_install((i2s_port_t)portNo, &i2s_config_dac, 0, NULL) != ESP_OK) + { + audioLogger->println("ERROR: Unable to install I2S drives\n"); + } + if (output_mode == INTERNAL_DAC || output_mode == INTERNAL_PDM) + { + i2s_set_pin((i2s_port_t)portNo, NULL); + i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN); + } + else + { + SetPinout(); + } + i2s_zero_dma_buffer((i2s_port_t)portNo); + } + #elif defined(ESP8266) + (void)dma_buf_count; + (void)use_apll; + if (!i2sOn) + { + orig_bck = READ_PERI_REG(PERIPHS_IO_MUX_MTDO_U); + orig_ws = READ_PERI_REG(PERIPHS_IO_MUX_GPIO2_U); + #ifdef I2S_HAS_BEGIN_RXTX_DRIVE_CLOCKS + if (!i2s_rxtxdrive_begin(false, true, false, txDAC)) { + return false; + } + #else + if (!i2s_rxtx_begin(false, true)) { + return false; + } + if (!txDAC) { + audioLogger->printf_P(PSTR("I2SNoDAC: esp8266 arduino core should be upgraded to avoid conflicts with SPI\n")); + } + #endif + } + #elif defined(ARDUINO_ARCH_RP2040) + (void)txDAC; + if (!i2sOn) { + I2S.setBCLK(bclkPin); + I2S.setDOUT(doutPin); + I2S.begin(hertz); + } + #endif + i2sOn = true; + SetRate(hertz); // Default return true; } bool AudioOutputI2S::ConsumeSample(int16_t sample[2]) { + + //return if we haven't called ::begin yet + if (!i2sOn) + return false; + int16_t ms[2]; ms[0] = sample[0]; @@ -185,43 +257,55 @@ bool AudioOutputI2S::ConsumeSample(int16_t sample[2]) int32_t ttl = ms[LEFTCHANNEL] + ms[RIGHTCHANNEL]; ms[LEFTCHANNEL] = ms[RIGHTCHANNEL] = (ttl>>1) & 0xffff; } -#ifdef ESP32 - uint32_t s32; - if (output_mode == INTERNAL_DAC) { - int16_t l = Amplify(ms[LEFTCHANNEL]) + 0x8000; - int16_t r = Amplify(ms[RIGHTCHANNEL]) + 0x8000; - s32 = (r<<16) | (l&0xffff); - } else { - s32 = ((Amplify(ms[RIGHTCHANNEL]))<<16) | (Amplify(ms[LEFTCHANNEL]) & 0xffff); - } -// Deprecated. Use i2s_write -// return i2s_write_bytes((i2s_port_t)portNo, (const char*)&s32, sizeof(uint32_t), 0); - size_t bytes_written; - i2s_write((i2s_port_t)portNo, (const char*)&s32, sizeof(uint32_t), &bytes_written, 0); - return bytes_written; -#else - uint32_t s32 = ((Amplify(ms[RIGHTCHANNEL]))<<16) | (Amplify(ms[LEFTCHANNEL]) & 0xffff); - return i2s_write_sample_nb(s32); // If we can't store it, return false. OTW true -#endif + #ifdef ESP32 + uint32_t s32; + if (output_mode == INTERNAL_DAC) + { + int16_t l = Amplify(ms[LEFTCHANNEL]) + 0x8000; + int16_t r = Amplify(ms[RIGHTCHANNEL]) + 0x8000; + s32 = (r << 16) | (l & 0xffff); + } + else + { + s32 = ((Amplify(ms[RIGHTCHANNEL])) << 16) | (Amplify(ms[LEFTCHANNEL]) & 0xffff); + } + return i2s_write_bytes((i2s_port_t)portNo, (const char *)&s32, sizeof(uint32_t), 0); + #elif defined(ESP8266) + uint32_t s32 = ((Amplify(ms[RIGHTCHANNEL])) << 16) | (Amplify(ms[LEFTCHANNEL]) & 0xffff); + return i2s_write_sample_nb(s32); // If we can't store it, return false. OTW true + #elif defined(ARDUINO_ARCH_RP2040) + return !!I2S.write((void*)ms, 4); + #endif } -void AudioOutputI2S::flush() { -#ifdef ESP32 - // makes sure that all stored DMA samples are consumed / played - int buffersize = 64 * this->dma_buf_count; - int16_t samples[2] = {0x0,0x0}; - for (int i=0;idma_buf_count; + int16_t samples[2] = {0x0, 0x0}; + for (int i = 0; i < buffersize; i++) + { + while (!ConsumeSample(samples)) + { + delay(10); + } } - } -#endif + #elif defined(ARDUINO_ARCH_RP2040) + I2S.flush(); + #endif } bool AudioOutputI2S::stop() { -#ifdef ESP32 - i2s_zero_dma_buffer((i2s_port_t)portNo); -#endif + if (!i2sOn) + return false; + + #ifdef ESP32 + i2s_zero_dma_buffer((i2s_port_t)portNo); + #elif defined(ARDUINO_ARCH_RP2040) + I2S.end(); + #endif + i2sOn = false; return true; } diff --git a/lib/lib_audio/ESP8266Audio/src/AudioOutputI2S.h b/lib/lib_audio/ESP8266Audio/src/AudioOutputI2S.h index 6070ca4e8..90370dc27 100644 --- a/lib/lib_audio/ESP8266Audio/src/AudioOutputI2S.h +++ b/lib/lib_audio/ESP8266Audio/src/AudioOutputI2S.h @@ -18,41 +18,47 @@ along with this program. If not, see . */ -#ifndef _AUDIOOUTPUTI2S_H -#define _AUDIOOUTPUTI2S_H +#pragma once #include "AudioOutput.h" class AudioOutputI2S : public AudioOutput { public: +#if defined(ESP32) || defined(ESP8266) AudioOutputI2S(int port=0, int output_mode=EXTERNAL_I2S, int dma_buf_count = 8, int use_apll=APLL_DISABLE); - virtual ~AudioOutputI2S() override; bool SetPinout(int bclkPin, int wclkPin, int doutPin); + enum : int { APLL_AUTO = -1, APLL_ENABLE = 1, APLL_DISABLE = 0 }; + enum : int { EXTERNAL_I2S = 0, INTERNAL_DAC = 1, INTERNAL_PDM = 2 }; +#elif defined(ARDUINO_ARCH_RP2040) + AudioOutputI2S(long sampleRate = 44100, pin_size_t sck = 26, pin_size_t data = 28); +#endif + virtual ~AudioOutputI2S() override; virtual bool SetRate(int hz) override; virtual bool SetBitsPerSample(int bits) override; virtual bool SetChannels(int channels) override; - virtual bool begin() override; + virtual bool begin() override { return begin(true); } virtual bool ConsumeSample(int16_t sample[2]) override; virtual void flush() override; virtual bool stop() override; + bool begin(bool txDAC); bool SetOutputModeMono(bool mono); // Force mono output no matter the input - enum : int { APLL_AUTO = -1, APLL_ENABLE = 1, APLL_DISABLE = 0 }; - enum : int { EXTERNAL_I2S = 0, INTERNAL_DAC = 1, INTERNAL_PDM = 2 }; - protected: + bool SetPinout(); virtual int AdjustI2SRate(int hz) { return hz; } uint8_t portNo; int output_mode; bool mono; bool i2sOn; int dma_buf_count; + int use_apll; // We can restore the old values and free up these pins when in NoDAC mode uint32_t orig_bck; uint32_t orig_ws; + + uint8_t bclkPin; + uint8_t wclkPin; + uint8_t doutPin; }; - -#endif - diff --git a/lib/lib_audio/ESP8266Audio/src/AudioOutputI2SNoDAC.cpp b/lib/lib_audio/ESP8266Audio/src/AudioOutputI2SNoDAC.cpp index 4f7b21ff7..774602fd9 100644 --- a/lib/lib_audio/ESP8266Audio/src/AudioOutputI2SNoDAC.cpp +++ b/lib/lib_audio/ESP8266Audio/src/AudioOutputI2SNoDAC.cpp @@ -1,7 +1,7 @@ /* AudioOutputI2SNoDAC Audio player using SW delta-sigma to generate "analog" on I2S data - + Copyright (C) 2017 Earle F. Philhower, III This program is free software: you can redistribute it and/or modify @@ -21,7 +21,7 @@ #include #ifdef ESP32 #include "driver/i2s.h" -#else +#elif defined(ARDUINO_ARCH_RP2040) || defined(ESP8266) #include #endif #include "AudioOutputI2SNoDAC.h" @@ -32,7 +32,7 @@ AudioOutputI2SNoDAC::AudioOutputI2SNoDAC(int port) : AudioOutputI2S(port, false) SetOversampling(32); lastSamp = 0; cumErr = 0; -#ifndef ESP32 +#ifdef ESP8266 WRITE_PERI_REG(PERIPHS_IO_MUX_MTDO_U, orig_bck); WRITE_PERI_REG(PERIPHS_IO_MUX_GPIO2_U, orig_ws); #endif @@ -66,7 +66,7 @@ void AudioOutputI2SNoDAC::DeltaSigma(int16_t sample[2], uint32_t dsBuff[8]) for (int j = 0; j < oversample32; j++) { uint32_t bits = 0; // The bits we convert the sample into, MSB to go on the wire first - + for (int i = 32; i > 0; i--) { bits = bits << 1; if (cumErr < 0) { @@ -95,19 +95,19 @@ bool AudioOutputI2SNoDAC::ConsumeSample(int16_t sample[2]) // Either send complete pulse stream or nothing #ifdef ESP32 - -// Deprecated. Use i2s_write -// if (!i2s_write_bytes((i2s_port_t)portNo, (const char *)dsBuff, sizeof(uint32_t) * (oversample/32), 0)) - size_t bytes_written; - i2s_write((i2s_port_t)portNo, (const char *)dsBuff, sizeof(uint32_t) * (oversample/32), &bytes_written, 0); - if (!bytes_written) + if (!i2s_write_bytes((i2s_port_t)portNo, (const char *)dsBuff, sizeof(uint32_t) * (oversample/32), 0)) return false; -#else +#elif defined(ESP8266) if (!i2s_write_sample_nb(dsBuff[0])) return false; // No room at the inn // At this point we've sent in first of possibly 8 32-bits, need to send // remaining ones even if they block. for (int i = 32; i < oversample; i+=32) i2s_write_sample( dsBuff[i / 32]); +#elif defined(ARDUINO_ARCH_RP2040) + int16_t *p = (int16_t *) dsBuff; + for (int i = 0; i < oversample / 16; i++) { + I2S.write(*(p++)); + } #endif return true; } diff --git a/lib/lib_audio/ESP8266Audio/src/AudioOutputI2SNoDAC.h b/lib/lib_audio/ESP8266Audio/src/AudioOutputI2SNoDAC.h index b5f321457..2a6322524 100644 --- a/lib/lib_audio/ESP8266Audio/src/AudioOutputI2SNoDAC.h +++ b/lib/lib_audio/ESP8266Audio/src/AudioOutputI2SNoDAC.h @@ -18,8 +18,7 @@ along with this program. If not, see . */ -#ifndef _AUDIOOUTPUTI2SNODAC_H -#define _AUDIOOUTPUTI2SNODAC_H +#pragma once #include "AudioOutputI2S.h" @@ -28,6 +27,7 @@ class AudioOutputI2SNoDAC : public AudioOutputI2S public: AudioOutputI2SNoDAC(int port = 0); virtual ~AudioOutputI2SNoDAC() override; + virtual bool begin() override { return AudioOutputI2S::begin(false); } virtual bool ConsumeSample(int16_t sample[2]) override; bool SetOversampling(int os); @@ -41,6 +41,3 @@ class AudioOutputI2SNoDAC : public AudioOutputI2S fixed24p8_t lastSamp; // Last sample value fixed24p8_t cumErr; // Running cumulative error since time began }; - -#endif - diff --git a/lib/lib_audio/ESP8266Audio/src/AudioOutputSPDIF.cpp b/lib/lib_audio/ESP8266Audio/src/AudioOutputSPDIF.cpp index 8c993c5f9..53483d4ac 100644 --- a/lib/lib_audio/ESP8266Audio/src/AudioOutputSPDIF.cpp +++ b/lib/lib_audio/ESP8266Audio/src/AudioOutputSPDIF.cpp @@ -37,6 +37,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ +#if defined(ESP32) || defined(ESP8266) #include #if defined(ESP32) @@ -286,3 +287,5 @@ bool AudioOutputSPDIF::stop() frame_num = 0; return true; } + +#endif diff --git a/lib/lib_audio/ESP8266Audio/src/AudioOutputSPDIF.h b/lib/lib_audio/ESP8266Audio/src/AudioOutputSPDIF.h index 92a4e46e4..5da160b3d 100644 --- a/lib/lib_audio/ESP8266Audio/src/AudioOutputSPDIF.h +++ b/lib/lib_audio/ESP8266Audio/src/AudioOutputSPDIF.h @@ -30,8 +30,8 @@ along with this program. If not, see . */ -#ifndef _AUDIOOUTPUTSPDIF_H -#define _AUDIOOUTPUTSPDIF_H +#if defined(ESP32) || defined(ESP8266) +#pragma once #include "AudioOutput.h" diff --git a/lib/lib_audio/ESP8266Audio/src/AudioOutputULP.cpp b/lib/lib_audio/ESP8266Audio/src/AudioOutputULP.cpp new file mode 100644 index 000000000..63177e3a1 --- /dev/null +++ b/lib/lib_audio/ESP8266Audio/src/AudioOutputULP.cpp @@ -0,0 +1,262 @@ +/* + AudioOutputULP + Outputs to ESP32 DAC through the ULP, freeing I2S for other uses + + Copyright (C) 2020 Martin Laclaustra, based on bitluni's code + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifdef ESP32 + +#include "AudioOutputULP.h" +#include +#include +#include +#include +#include + +uint32_t create_I_WR_REG(uint32_t reg, uint32_t low_bit, uint32_t high_bit, uint32_t val){ + typedef union {ulp_insn_t ulp_ins; uint32_t ulp_bin;} ulp_union; + const ulp_insn_t singleinstruction[] = {I_WR_REG(reg, low_bit, high_bit, val)}; + ulp_union recover_ins; + recover_ins.ulp_ins=singleinstruction[0]; + return (uint32_t)(recover_ins.ulp_bin); +} + +uint32_t create_I_BXI(uint32_t imm_pc){ + typedef union {ulp_insn_t ulp_ins; uint32_t ulp_bin;} ulp_union; + const ulp_insn_t singleinstruction[] = {I_BXI(imm_pc)}; + ulp_union recover_ins; + recover_ins.ulp_ins=singleinstruction[0]; + return (uint32_t)(recover_ins.ulp_bin); +} + +bool AudioOutputULP::begin() +{ + if(!stereoOutput){ + waitingOddSample = false; + //totalSampleWords += 512; + //dacTableStart2 = dacTableStart1; + } + + //calculate the actual ULP clock + unsigned long rtc_8md256_period = rtc_clk_cal(RTC_CAL_8MD256, 1000); + unsigned long rtc_fast_freq_hz = 1000000ULL * (1 << RTC_CLK_CAL_FRACT) * 256 / rtc_8md256_period; + + //initialize DACs + if(activeDACs & 1){ + dac_output_enable(DAC_CHANNEL_1); + dac_output_voltage(DAC_CHANNEL_1, 128); + } + if(activeDACs & 2){ + dac_output_enable(DAC_CHANNEL_2); + dac_output_voltage(DAC_CHANNEL_2, 128); + } + + int retAddress1 = 9; + int retAddress2 = 14; + + int loopCycles = 134; + int loopHalfCycles1 = 90; + int loopHalfCycles2 = 44; + + Serial.print("Real RTC clock: "); + Serial.println(rtc_fast_freq_hz); + + uint32_t dt = (rtc_fast_freq_hz / hertz) - loopCycles; + uint32_t dt2 = 0; + if(!stereoOutput){ + dt = (rtc_fast_freq_hz / hertz) - loopHalfCycles1; + dt2 = (rtc_fast_freq_hz / hertz) - loopHalfCycles2; + } + + Serial.print("dt: "); + Serial.println(dt); + + Serial.print("dt2: "); + Serial.println(dt2); + + const ulp_insn_t stereo[] = { + //reset offset register + I_MOVI(R3, 0), + //delay to get the right sampling rate + I_DELAY(dt), // 6 + dt + //reset sample index + I_MOVI(R0, 0), // 6 + //write the index back to memory for the main cpu + I_ST(R0, R3, indexAddress), // 8 + //load the samples + I_LD(R1, R0, bufferStart), // 8 + //mask the lower 8 bits + I_ANDI(R2, R1, 0x00ff), // 6 + //multiply by 2 + I_LSHI(R2, R2, 1), // 6 + //add start position + I_ADDI(R2, R2, dacTableStart1),// 6 + //jump to the dac opcode + I_BXR(R2), // 4 + //back from first dac + //delay between the two samples in mono rendering + I_DELAY(dt2), // 6 + dt2 + //mask the upper 8 bits + I_ANDI(R2, R1, 0xff00), // 6 + //shift the upper bits to right and multiply by 2 + I_RSHI(R2, R2, 8 - 1), // 6 + //add start position of second dac table + I_ADDI(R2, R2, dacTableStart2),// 6 + //jump to the dac opcode + I_BXR(R2), // 4 + //here we get back from writing the second sample + //load 0x8080 as sample + I_MOVI(R1, 0x8080), // 6 + //write 0x8080 in the sample buffer + I_ST(R1, R0, indexAddress), // 8 + //increment the sample index + I_ADDI(R0, R0, 1), // 6 + //if reached end of the buffer, jump relative to index reset + I_BGE(-16, totalSampleWords), // 4 + //wait to get the right sample rate (2 cycles more to compensate the index reset) + I_DELAY((unsigned int)dt + 2), // 8 + dt + //if not, jump absolute to where index is written to memory + I_BXI(3) // 4 + }; + // write io and jump back another 12 + 4 + 12 + 4 + + size_t load_addr = 0; + size_t size = sizeof(stereo)/sizeof(ulp_insn_t); + ulp_process_macros_and_load(load_addr, stereo, &size); + // this is how to get the opcodes + // for(int i = 0; i < size; i++) + // Serial.println(RTC_SLOW_MEM[i], HEX); + + //create DAC opcode tables + switch(activeDACs){ + case 1: + for(int i = 0; i < 256; i++) + { + RTC_SLOW_MEM[dacTableStart1 + i * 2] = create_I_WR_REG(RTC_IO_PAD_DAC1_REG,19,26,i); //dac1: 0x1D4C0121 | (i << 10) + RTC_SLOW_MEM[dacTableStart1 + 1 + i * 2] = create_I_BXI(retAddress1); // 0x80000000 + retAddress1 * 4 + RTC_SLOW_MEM[dacTableStart2 + i * 2] = create_I_WR_REG(RTC_IO_PAD_DAC1_REG,19,26,i); //dac2: 0x1D4C0122 | (i << 10) + RTC_SLOW_MEM[dacTableStart2 + 1 + i * 2] = create_I_BXI(retAddress2); // 0x80000000 + retAddress2 * 4 + } + break; + case 2: + for(int i = 0; i < 256; i++) + { + RTC_SLOW_MEM[dacTableStart1 + i * 2] = create_I_WR_REG(RTC_IO_PAD_DAC2_REG,19,26,i); //dac1: 0x1D4C0121 | (i << 10) + RTC_SLOW_MEM[dacTableStart1 + 1 + i * 2] = create_I_BXI(retAddress1); // 0x80000000 + retAddress1 * 4 + RTC_SLOW_MEM[dacTableStart2 + i * 2] = create_I_WR_REG(RTC_IO_PAD_DAC2_REG,19,26,i); //dac2: 0x1D4C0122 | (i << 10) + RTC_SLOW_MEM[dacTableStart2 + 1 + i * 2] = create_I_BXI(retAddress2); // 0x80000000 + retAddress2 * 4 + } + break; + case 3: + for(int i = 0; i < 256; i++) + { + RTC_SLOW_MEM[dacTableStart1 + i * 2] = create_I_WR_REG(RTC_IO_PAD_DAC1_REG,19,26,i); //dac1: 0x1D4C0121 | (i << 10) + RTC_SLOW_MEM[dacTableStart1 + 1 + i * 2] = create_I_BXI(retAddress1); // 0x80000000 + retAddress1 * 4 + RTC_SLOW_MEM[dacTableStart2 + i * 2] = create_I_WR_REG(RTC_IO_PAD_DAC1_REG,19,26,i); //dac2: 0x1D4C0122 | (i << 10) + RTC_SLOW_MEM[dacTableStart2 + 1 + i * 2] = create_I_BXI(retAddress2); // 0x80000000 + retAddress2 * 4 + } + break; + } + + //set all samples to 128 (silence) + for(int i = 0; i < totalSampleWords; i++) + RTC_SLOW_MEM[bufferStart + i] = 0x8080; + + //start + RTC_SLOW_MEM[indexAddress] = 0; + ulp_run(0); + + //wait until ULP starts using samples and the index of output sample advances + while(RTC_SLOW_MEM[indexAddress] == 0) + delay(1); + + return true; +} + +bool AudioOutputULP::ConsumeSample(int16_t sample[2]) +{ + int16_t ms[2]; + ms[0] = sample[0]; + ms[1] = sample[1]; + MakeSampleStereo16( ms ); + + // TODO: needs improvement (counting is different here with respect to ULP code) + int currentSample = RTC_SLOW_MEM[indexAddress] & 0xffff; + int currentWord = currentSample >> 1; + + for (int i=0; i<2; i++) { + ms[i] = ((ms[i] >> 8) + 128) & 0xff; + } + if(!stereoOutput) // mix both channels + ms[0] = (uint16_t)(( (uint32_t)((int32_t)(ms[0]) + (int32_t)(ms[1])) >> 1 ) & 0xff); + + if(waitingOddSample){ // always true for stereo because samples are consumed in pairs + if(lastFilledWord != currentWord) // accept sample if writing index lastFilledWord has not reached index of output sample + { + unsigned int w; + if(stereoOutput){ + w = ms[0]; + w |= ms[1] << 8; + } else { + w = bufferedOddSample; + w |= ms[0] << 8; + bufferedOddSample = 128; + waitingOddSample = false; + } + RTC_SLOW_MEM[bufferStart + lastFilledWord] = w; + lastFilledWord++; + if(lastFilledWord == totalSampleWords) + lastFilledWord = 0; + return true; + } else { + return false; + } + } else { + bufferedOddSample = ms[0]; + waitingOddSample = true; + return true; + } +} + + +bool AudioOutputULP::stop() +{ + audioLogger->printf_P(PSTR("\n\n\nstop\n\n\n")); + const ulp_insn_t stopulp[] = { + //stop the timer + I_END(), + //end the program + I_HALT()}; + + size_t load_addr = 0; + size_t size = sizeof(stopulp)/sizeof(ulp_insn_t); + ulp_process_macros_and_load(load_addr, stopulp, &size); + + //start + ulp_run(0); + + if(activeDACs & 1){ + dac_output_voltage(DAC_CHANNEL_1, 128); + } + if(activeDACs & 2){ + dac_output_voltage(DAC_CHANNEL_2, 128); + } + + return true; +} + +#endif diff --git a/lib/lib_audio/ESP8266Audio/src/AudioOutputULP.h b/lib/lib_audio/ESP8266Audio/src/AudioOutputULP.h new file mode 100644 index 000000000..fc4e438c1 --- /dev/null +++ b/lib/lib_audio/ESP8266Audio/src/AudioOutputULP.h @@ -0,0 +1,69 @@ +/* + AudioOutputULP + Outputs to ESP32 DAC through the ULP, freeing I2S for other uses + + Copyright (C) 2020 Martin Laclaustra, based on bitluni's code + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// Instructions: +// AudioOutputULP out = new AudioOutputULP(); // stereo +// Connect left channel on pin 25 +// Connect right channel on pin 26 +// OR +// Connect mono channel on either of them (stereo samples are downmixed) +// AudioOutputULP out = new AudioOutputULP(1); // mono, only DAC 1 +// OR +// AudioOutputULP out = new AudioOutputULP(2); // mono, only DAC 2 + + +#ifndef _AUDIOOUTPUTULP_H +#define _AUDIOOUTPUTULP_H + +#include "AudioOutput.h" + +#ifdef ESP32 + +class AudioOutputULP : public AudioOutput +{ + public: + AudioOutputULP(int argActiveDACs=3) {if(argActiveDACs<1||argActiveDACs>2)argActiveDACs=3;activeDACs=argActiveDACs;stereoOutput=activeDACs==3;}; + ~AudioOutputULP() {}; + virtual bool begin() override; + virtual bool ConsumeSample(int16_t sample[2]) override; + virtual bool stop() override; + enum : int { DAC1 = 1, DAC2 = 2 }; + private: + int lastFilledWord = 0; + uint8_t bufferedOddSample = 128; + bool waitingOddSample = true; // must be set to false for mono output + int activeDACs = 3; // 1:DAC1; 2:DAC2; 3:both; + bool stereoOutput = true; + const int opcodeCount = 20; + const uint32_t dacTableStart1 = 2048 - 512; + const uint32_t dacTableStart2 = dacTableStart1 - 512; + uint32_t totalSampleWords = 2048 - 512 - 512 - (opcodeCount + 1); // add 512 for mono + const int totalSamples = totalSampleWords * 2; + const uint32_t indexAddress = opcodeCount; + const uint32_t bufferStart = indexAddress + 1; +}; + +#else + +#error Only the ESP32 supports ULP audio output + +#endif + +#endif diff --git a/lib/lib_audio/ESP8266Audio/src/ESP8266Audio.h b/lib/lib_audio/ESP8266Audio/src/ESP8266Audio.h new file mode 100644 index 000000000..eb0acc52a --- /dev/null +++ b/lib/lib_audio/ESP8266Audio/src/ESP8266Audio.h @@ -0,0 +1,50 @@ +// Lazy "include all the things" header for simplicity. +// In general a user should only include the specific headers they need +// to miniimize build times. + +// Input stage +#include "AudioFileSourceBuffer.h" +#include "AudioFileSourceFATFS.h" +#include "AudioFileSourceFS.h" +#include "AudioFileSource.h" +#include "AudioFileSourceHTTPStream.h" +#include "AudioFileSourceICYStream.h" +#include "AudioFileSourceID3.h" +#include "AudioFileSourceLittleFS.h" +#include "AudioFileSourcePROGMEM.h" +#include "AudioFileSourceSD.h" +#include "AudioFileSourceSPIFFS.h" +#include "AudioFileSourceSPIRAMBuffer.h" +#include "AudioFileSourceSTDIO.h" + +// Misc. plumbing +#include "AudioFileStream.h" +#include "AudioLogger.h" +#include "AudioStatus.h" + +// Actual decode/audio generation logic +#include "AudioGeneratorAAC.h" +#include "AudioGeneratorFLAC.h" +#include "AudioGenerator.h" +#include "AudioGeneratorMIDI.h" +#include "AudioGeneratorMOD.h" +#include "AudioGeneratorMP3a.h" +#include "AudioGeneratorMP3.h" +#include "AudioGeneratorOpus.h" +#include "AudioGeneratorRTTTL.h" +#include "AudioGeneratorTalkie.h" +#include "AudioGeneratorWAV.h" + +// Render(output) sounds +#include "AudioOutputBuffer.h" +#include "AudioOutputFilterDecimate.h" +#include "AudioOutput.h" +#include "AudioOutputI2S.h" +#include "AudioOutputI2SNoDAC.h" +#include "AudioOutputMixer.h" +#include "AudioOutputNull.h" +#include "AudioOutputSerialWAV.h" +#include "AudioOutputSPDIF.h" +#include "AudioOutputSPIFFSWAV.h" +#include "AudioOutputSTDIO.h" +#include "AudioOutputULP.h" diff --git a/lib/lib_audio/ESP8266Audio/src/libflac/bitreader.c b/lib/lib_audio/ESP8266Audio/src/libflac/bitreader.c index a64c1ce53..b2a751d40 100644 --- a/lib/lib_audio/ESP8266Audio/src/libflac/bitreader.c +++ b/lib/lib_audio/ESP8266Audio/src/libflac/bitreader.c @@ -121,6 +121,8 @@ struct FLAC__BitReader { void *client_data; }; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstrict-aliasing" static inline void crc16_update_word_(FLAC__BitReader *br, brword word) { unsigned crc = br->read_crc16; @@ -149,6 +151,7 @@ static inline void crc16_update_word_(FLAC__BitReader *br, brword word) #endif br->crc16_align = 0; } +#pragma GCC diagnostic pop static FLAC__bool bitreader_read_from_client_(FLAC__BitReader *br) { @@ -338,6 +341,8 @@ void FLAC__bitreader_reset_read_crc16(FLAC__BitReader *br, FLAC__uint16 seed) br->crc16_align = br->consumed_bits; } +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstrict-aliasing" FLAC__uint16 FLAC__bitreader_get_read_crc16(FLAC__BitReader *br) { FLAC__ASSERT(0 != br); @@ -353,6 +358,7 @@ FLAC__uint16 FLAC__bitreader_get_read_crc16(FLAC__BitReader *br) } return br->read_crc16; } +#pragma GCC diagnostic pop inline FLAC__bool FLAC__bitreader_is_consumed_byte_aligned(const FLAC__BitReader *br) { diff --git a/lib/lib_audio/ESP8266Audio/src/libflac/config.h b/lib/lib_audio/ESP8266Audio/src/libflac/config.h index b6efd1dc8..cf6c07dfe 100644 --- a/lib/lib_audio/ESP8266Audio/src/libflac/config.h +++ b/lib/lib_audio/ESP8266Audio/src/libflac/config.h @@ -2,9 +2,7 @@ #ifdef DEBUG #undef NDEBUG -#endif - -#ifndef NDEBUG +#else #define NDEBUG #endif diff --git a/lib/lib_audio/ESP8266Audio/src/libflac/cpu.c b/lib/lib_audio/ESP8266Audio/src/libflac/cpu.c index efed11b7f..cd650a09c 100644 --- a/lib/lib_audio/ESP8266Audio/src/libflac/cpu.c +++ b/lib/lib_audio/ESP8266Audio/src/libflac/cpu.c @@ -31,7 +31,7 @@ */ //#ifdef HAVE_CONFIG_H -# include +# include "config.h" //#endif #include "private/cpu.h" diff --git a/lib/lib_audio/ESP8266Audio/src/libflac/crc.c b/lib/lib_audio/ESP8266Audio/src/libflac/crc.c index 1b531e3d1..88ef8cfbc 100644 --- a/lib/lib_audio/ESP8266Audio/src/libflac/crc.c +++ b/lib/lib_audio/ESP8266Audio/src/libflac/crc.c @@ -136,7 +136,8 @@ FLAC__uint8 FLAC__crc8(const FLAC__byte *data, unsigned len) return crc; } - +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstrict-aliasing" unsigned FLAC__crc16(const FLAC__byte *data, unsigned len) { unsigned crc = 0; @@ -146,3 +147,4 @@ unsigned FLAC__crc16(const FLAC__byte *data, unsigned len) return crc; } +#pragma GCC diagnostic pop diff --git a/lib/lib_audio/ESP8266Audio/src/libflac/stream_decoder.c b/lib/lib_audio/ESP8266Audio/src/libflac/stream_decoder.c index eabcf092c..ec172fe8d 100644 --- a/lib/lib_audio/ESP8266Audio/src/libflac/stream_decoder.c +++ b/lib/lib_audio/ESP8266Audio/src/libflac/stream_decoder.c @@ -2021,6 +2021,8 @@ FLAC__bool frame_sync_(FLAC__StreamDecoder *decoder) return true; } +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstrict-aliasing" FLAC__bool read_frame_(FLAC__StreamDecoder *decoder, FLAC__bool *got_a_frame, FLAC__bool do_full_decode) { uint32_t channel; @@ -2167,6 +2169,7 @@ FLAC__bool read_frame_(FLAC__StreamDecoder *decoder, FLAC__bool *got_a_frame, FL decoder->protected_->state = FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC; return true; } +#pragma GCC diagnostic pop FLAC__bool read_frame_header_(FLAC__StreamDecoder *decoder) { diff --git a/lib/lib_audio/ESP8266Audio/src/libhelix-aac/assembly.h b/lib/lib_audio/ESP8266Audio/src/libhelix-aac/assembly.h index fc4d76739..595a8fc97 100644 --- a/lib/lib_audio/ESP8266Audio/src/libhelix-aac/assembly.h +++ b/lib/lib_audio/ESP8266Audio/src/libhelix-aac/assembly.h @@ -223,7 +223,7 @@ Word64 MADD64(Word64 sum64, int x, int y); /* toolchain: ARM ADS or RealView * target architecture: ARM v.4 and above (requires 'M' type processor for 32x32->64 multiplier) */ -#elif defined (__arm) && defined (__ARMCC_VERSION) +#elif defined (XXX__arm) && defined (__ARMCC_VERSION) static __inline int MULSHIFT32(int x, int y) { @@ -336,7 +336,7 @@ static __inline Word64 MADD64(Word64 sum64, int x, int y) /* toolchain: ARM gcc * target architecture: ARM v.4 and above (requires 'M' type processor for 32x32->64 multiplier) */ -#elif defined(__GNUC__) && defined(__arm__) +#elif defined(__GNUC__) && defined(XXXX__arm__) static inline int MULSHIFT32(int x, int y) { diff --git a/lib/lib_audio/ESP8266Audio/src/libhelix-aac/sbr.c b/lib/lib_audio/ESP8266Audio/src/libhelix-aac/sbr.c index ec046720c..e46391585 100644 --- a/lib/lib_audio/ESP8266Audio/src/libhelix-aac/sbr.c +++ b/lib/lib_audio/ESP8266Audio/src/libhelix-aac/sbr.c @@ -44,6 +44,7 @@ **************************************************************************************/ #if defined(USE_DEFAULT_STDLIB) || defined(ARDUINO) +#include #include #else #include "hlxclib/stdlib.h" diff --git a/lib/lib_audio/ESP8266Audio/src/libhelix-aac/sbrhfgen.c b/lib/lib_audio/ESP8266Audio/src/libhelix-aac/sbrhfgen.c index 7ebc047af..e5f27eb6d 100644 --- a/lib/lib_audio/ESP8266Audio/src/libhelix-aac/sbrhfgen.c +++ b/lib/lib_audio/ESP8266Audio/src/libhelix-aac/sbrhfgen.c @@ -76,7 +76,7 @@ static const int newBWTab[4][4] PROGMEM = { * Notes: this is carefully written to be efficient on ARM * use the assembly code version in sbrcov.s when building for ARM! **************************************************************************************/ -#if (defined (__arm) && defined (__ARMCC_VERSION)) || (defined (_WIN32) && defined (_WIN32_WCE) && defined (ARM)) || (defined(__GNUC__) && defined(__arm__)) +#if (defined (XXXX__arm) && defined (__ARMCC_VERSION)) || (defined (_WIN32) && defined (_WIN32_WCE) && defined (ARM)) || (defined(__GNUC__) && defined(XXXX__arm__)) #ifdef __cplusplus extern "C" #endif @@ -237,7 +237,7 @@ static int CalcCovariance1(int *XBuf, int *p01reN, int *p01imN, int *p12reN, int * Notes: this is carefully written to be efficient on ARM * use the assembly code version in sbrcov.s when building for ARM! **************************************************************************************/ -#if (defined (__arm) && defined (__ARMCC_VERSION)) || (defined (_WIN32) && defined (_WIN32_WCE) && defined (ARM)) || (defined(__GNUC__) && defined(__arm__)) +#if (defined (XXXX__arm) && defined (__ARMCC_VERSION)) || (defined (_WIN32) && defined (_WIN32_WCE) && defined (ARM)) || (defined(__GNUC__) && defined(XXXX__arm__)) #ifdef __cplusplus extern "C" #endif diff --git a/lib/lib_audio/ESP8266Audio/src/libhelix-mp3/assembly.h b/lib/lib_audio/ESP8266Audio/src/libhelix-mp3/assembly.h index 9c10c4da0..b9e46c529 100644 --- a/lib/lib_audio/ESP8266Audio/src/libhelix-mp3/assembly.h +++ b/lib/lib_audio/ESP8266Audio/src/libhelix-mp3/assembly.h @@ -217,7 +217,7 @@ static __inline int CLZ(int x) return numZeros; } -#elif defined ARM_ADS +#elif defined XXXARM_ADS static __inline int MULSHIFT32(int x, int y) { @@ -267,7 +267,7 @@ static __inline int CLZ(int x) return numZeros; } -#elif defined(__GNUC__) && defined(__thumb__) +#elif defined(__GNUC__) && defined(XXXX__thumb__) static __inline int MULSHIFT32(int x, int y) diff --git a/lib/lib_audio/ESP8266Audio/src/libmad/global.h b/lib/lib_audio/ESP8266Audio/src/libmad/global.h index a6debfd8b..a688495f8 100644 --- a/lib/lib_audio/ESP8266Audio/src/libmad/global.h +++ b/lib/lib_audio/ESP8266Audio/src/libmad/global.h @@ -48,6 +48,7 @@ # endif # if !defined(HAVE_ASSERT_H) +# undef assert # if defined(NDEBUG) # define assert(x) /* nothing */ # else diff --git a/lib/lib_audio/ESP8266Audio/src/libopus/celt/celt_decoder.c b/lib/lib_audio/ESP8266Audio/src/libopus/celt/celt_decoder.c index 329b6f6cc..7e7296ed5 100644 --- a/lib/lib_audio/ESP8266Audio/src/libopus/celt/celt_decoder.c +++ b/lib/lib_audio/ESP8266Audio/src/libopus/celt/celt_decoder.c @@ -482,7 +482,7 @@ static int celt_plc_pitch_search(celt_sig *decode_mem[2], int C, int arch) int pitch_index; VARDECL( opus_val16, lp_pitch_buf ); SAVE_STACK; - ALLOC( lp_pitch_buf, DECODE_BUFFER_SIZE>>1, opus_val16 ); + opus_val16 *lp_pitch_buf = (opus_val16*)malloc((DECODE_BUFFER_SIZE>>1) * sizeof(opus_val16)); //ALLOC( lp_pitch_buf, DECODE_BUFFER_SIZE>>1, opus_val16 ); pitch_downsample(decode_mem, lp_pitch_buf, DECODE_BUFFER_SIZE, C, arch); pitch_search(lp_pitch_buf+(PLC_PITCH_LAG_MAX>>1), lp_pitch_buf, @@ -490,6 +490,7 @@ static int celt_plc_pitch_search(celt_sig *decode_mem[2], int C, int arch) PLC_PITCH_LAG_MAX-PLC_PITCH_LAG_MIN, &pitch_index, arch); pitch_index = PLC_PITCH_LAG_MAX-pitch_index; RESTORE_STACK; + free(lp_pitch_buf); return pitch_index; } diff --git a/lib/lib_audio/ESP8266Audio/src/libopus/config.h b/lib/lib_audio/ESP8266Audio/src/libopus/config.h index 3577204b6..dc1ee1e98 100644 --- a/lib/lib_audio/ESP8266Audio/src/libopus/config.h +++ b/lib/lib_audio/ESP8266Audio/src/libopus/config.h @@ -206,3 +206,5 @@ # define _Restrict # define __restrict__ #endif + +#include diff --git a/lib/lib_audio/ESP8266Audio/src/libopus/repacketizer.c b/lib/lib_audio/ESP8266Audio/src/libopus/repacketizer.c index 5a1eb675e..36732293f 100644 --- a/lib/lib_audio/ESP8266Audio/src/libopus/repacketizer.c +++ b/lib/lib_audio/ESP8266Audio/src/libopus/repacketizer.c @@ -239,21 +239,30 @@ opus_int32 opus_repacketizer_out(OpusRepacketizer *rp, unsigned char *data, opus int opus_packet_pad(unsigned char *data, opus_int32 len, opus_int32 new_len) { - OpusRepacketizer rp; + OpusRepacketizer *rp = (OpusRepacketizer*)malloc(sizeof(OpusRepacketizer)); opus_int32 ret; - if (len < 1) + if (len < 1) { + free(rp); return OPUS_BAD_ARG; - if (len==new_len) + } + if (len==new_len) { + free(rp); return OPUS_OK; - else if (len > new_len) + } + else if (len > new_len) { + free(rp); return OPUS_BAD_ARG; - opus_repacketizer_init(&rp); + } + opus_repacketizer_init(rp); /* Moving payload to the end of the packet so we can do in-place padding */ OPUS_MOVE(data+new_len-len, data, len); - ret = opus_repacketizer_cat(&rp, data+new_len-len, len); - if (ret != OPUS_OK) + ret = opus_repacketizer_cat(rp, data+new_len-len, len); + if (ret != OPUS_OK) { + free(rp); return ret; - ret = opus_repacketizer_out_range_impl(&rp, 0, rp.nb_frames, data, new_len, 0, 1); + } + ret = opus_repacketizer_out_range_impl(rp, 0, rp->nb_frames, data, new_len, 0, 1); + free(rp); if (ret > 0) return OPUS_OK; else @@ -262,15 +271,20 @@ int opus_packet_pad(unsigned char *data, opus_int32 len, opus_int32 new_len) opus_int32 opus_packet_unpad(unsigned char *data, opus_int32 len) { - OpusRepacketizer rp; + OpusRepacketizer *rp = (OpusRepacketizer*)malloc(sizeof(OpusRepacketizer)); opus_int32 ret; - if (len < 1) + if (len < 1) { + free(rp); return OPUS_BAD_ARG; - opus_repacketizer_init(&rp); - ret = opus_repacketizer_cat(&rp, data, len); - if (ret < 0) + } + opus_repacketizer_init(rp); + ret = opus_repacketizer_cat(rp, data, len); + if (ret < 0) { + free(rp); return ret; - ret = opus_repacketizer_out_range_impl(&rp, 0, rp.nb_frames, data, len, 0, 0); + } + ret = opus_repacketizer_out_range_impl(rp, 0, rp->nb_frames, data, len, 0, 0); + free(rp); celt_assert(ret > 0 && ret <= len); return ret; } @@ -312,12 +326,14 @@ opus_int32 opus_multistream_packet_unpad(unsigned char *data, opus_int32 len, in unsigned char toc; opus_int16 size[48]; opus_int32 packet_offset; - OpusRepacketizer rp; + OpusRepacketizer *rp = (OpusRepacketizer*)malloc(sizeof(OpusRepacketizer)); unsigned char *dst; opus_int32 dst_len; - if (len < 1) + if (len < 1){ + free(rp); return OPUS_BAD_ARG; + } dst = data; dst_len = 0; /* Unpad all frames */ @@ -325,25 +341,34 @@ opus_int32 opus_multistream_packet_unpad(unsigned char *data, opus_int32 len, in { opus_int32 ret; int self_delimited = s!=nb_streams-1; - if (len<=0) + if (len<=0) { + free(rp); return OPUS_INVALID_PACKET; - opus_repacketizer_init(&rp); + } + opus_repacketizer_init(rp); ret = opus_packet_parse_impl(data, len, self_delimited, &toc, NULL, size, NULL, &packet_offset); - if (ret<0) + if (ret<0) { + free(rp); return ret; - ret = opus_repacketizer_cat_impl(&rp, data, packet_offset, self_delimited); - if (ret < 0) + } + ret = opus_repacketizer_cat_impl(rp, data, packet_offset, self_delimited); + if (ret < 0) { + free(rp); return ret; - ret = opus_repacketizer_out_range_impl(&rp, 0, rp.nb_frames, dst, len, self_delimited, 0); - if (ret < 0) + } + ret = opus_repacketizer_out_range_impl(rp, 0, rp->nb_frames, dst, len, self_delimited, 0); + if (ret < 0) { + free(rp); return ret; + } else dst_len += ret; dst += ret; data += packet_offset; len -= packet_offset; } + free(rp); return dst_len; } diff --git a/lib/lib_audio/ESP8266Audio/src/libopus/silk/NLSF2A.c b/lib/lib_audio/ESP8266Audio/src/libopus/silk/NLSF2A.c index 40718e7a8..2b3e3340d 100644 --- a/lib/lib_audio/ESP8266Audio/src/libopus/silk/NLSF2A.c +++ b/lib/lib_audio/ESP8266Audio/src/libopus/silk/NLSF2A.c @@ -80,10 +80,11 @@ void silk_NLSF2A( }; const unsigned char *ordering; opus_int k, i, dd; - opus_int32 cos_LSF_QA[ SILK_MAX_ORDER_LPC ]; - opus_int32 P[ SILK_MAX_ORDER_LPC / 2 + 1 ], Q[ SILK_MAX_ORDER_LPC / 2 + 1 ]; + opus_int32 *cos_LSF_QA = (opus_int32*)malloc(sizeof(opus_int32) * SILK_MAX_ORDER_LPC ); + opus_int32 *P = (opus_int32*)malloc(sizeof(opus_int32) * (SILK_MAX_ORDER_LPC / 2 + 1)); + opus_int32 *Q= (opus_int32*)malloc(sizeof(opus_int32) * (SILK_MAX_ORDER_LPC / 2 + 1)); opus_int32 Ptmp, Qtmp, f_int, f_frac, cos_val, delta; - opus_int32 a32_QA1[ SILK_MAX_ORDER_LPC ]; + opus_int32 *a32_QA1 = (opus_int32*)malloc(sizeof(opus_int32) * SILK_MAX_ORDER_LPC ); silk_assert( LSF_COS_TAB_SZ_FIX == 128 ); celt_assert( d==10 || d==16 ); @@ -137,5 +138,9 @@ void silk_NLSF2A( a_Q12[ k ] = (opus_int16)silk_RSHIFT_ROUND( a32_QA1[ k ], QA + 1 - 12 ); /* QA+1 -> Q12 */ } } + free(cos_LSF_QA); + free(P); + free(Q); + free(a32_QA1); } diff --git a/lib/lib_audio/ESP8266Audio/src/libopus/silk/fixed/burg_modified_FIX.c b/lib/lib_audio/ESP8266Audio/src/libopus/silk/fixed/burg_modified_FIX.c index b4a31d605..dd7862983 100644 --- a/lib/lib_audio/ESP8266Audio/src/libopus/silk/fixed/burg_modified_FIX.c +++ b/lib/lib_audio/ESP8266Audio/src/libopus/silk/fixed/burg_modified_FIX.c @@ -57,12 +57,12 @@ void silk_burg_modified_c( opus_int k, n, s, lz, rshifts, reached_max_gain; opus_int32 C0, num, nrg, rc_Q31, invGain_Q30, Atmp_QA, Atmp1, tmp1, tmp2, x1, x2; const opus_int16 *x_ptr; - opus_int32 C_first_row[ SILK_MAX_ORDER_LPC ]; - opus_int32 C_last_row[ SILK_MAX_ORDER_LPC ]; - opus_int32 Af_QA[ SILK_MAX_ORDER_LPC ]; - opus_int32 CAf[ SILK_MAX_ORDER_LPC + 1 ]; - opus_int32 CAb[ SILK_MAX_ORDER_LPC + 1 ]; - opus_int32 xcorr[ SILK_MAX_ORDER_LPC ]; + opus_int32 *C_first_row = (opus_int32*)malloc(sizeof(opus_int32) * SILK_MAX_ORDER_LPC); + opus_int32 *C_last_row = (opus_int32*)malloc(sizeof(opus_int32) * SILK_MAX_ORDER_LPC); + opus_int32 *Af_QA = (opus_int32*)malloc(sizeof(opus_int32) * SILK_MAX_ORDER_LPC); + opus_int32 *CAf = (opus_int32*)malloc(sizeof(opus_int32) * (SILK_MAX_ORDER_LPC+1)); + opus_int32 *CAb = (opus_int32*)malloc(sizeof(opus_int32) * (SILK_MAX_ORDER_LPC+1)); + opus_int32 *xcorr = (opus_int32*)malloc(sizeof(opus_int32) * SILK_MAX_ORDER_LPC); opus_int64 C0_64; celt_assert( subfr_length * nb_subfr <= MAX_FRAME_SIZE ); @@ -277,4 +277,10 @@ void silk_burg_modified_c( *res_nrg = silk_SMLAWW( nrg, silk_SMMUL( SILK_FIX_CONST( FIND_LPC_COND_FAC, 32 ), C0 ), -tmp1 );/* Q( -rshifts ) */ *res_nrg_Q = -rshifts; } + free(C_first_row); + free(C_last_row); + free(Af_QA); + free(CAf); + free(CAb); + free(xcorr); } diff --git a/lib/lib_audio/ESP8266Audio/src/libopus/silk/fixed/warped_autocorrelation_FIX.c b/lib/lib_audio/ESP8266Audio/src/libopus/silk/fixed/warped_autocorrelation_FIX.c index 7ef3a7efc..9c21f2a9b 100644 --- a/lib/lib_audio/ESP8266Audio/src/libopus/silk/fixed/warped_autocorrelation_FIX.c +++ b/lib/lib_audio/ESP8266Audio/src/libopus/silk/fixed/warped_autocorrelation_FIX.c @@ -49,8 +49,8 @@ void silk_warped_autocorrelation_FIX_c( { opus_int n, i, lsh; opus_int32 tmp1_QS, tmp2_QS; - opus_int32 state_QS[ MAX_SHAPE_LPC_ORDER + 1 ] = { 0 }; - opus_int64 corr_QC[ MAX_SHAPE_LPC_ORDER + 1 ] = { 0 }; + opus_int32 *state_QS = (opus_int32*)calloc(MAX_SHAPE_LPC_ORDER + 1, sizeof(opus_int32)); + opus_int64 *corr_QC = (opus_int64*)calloc(MAX_SHAPE_LPC_ORDER + 1, sizeof(opus_int64)); /* Order must be even */ celt_assert( ( order & 1 ) == 0 ); @@ -88,5 +88,7 @@ void silk_warped_autocorrelation_FIX_c( } } silk_assert( corr_QC[ 0 ] >= 0 ); /* If breaking, decrease QC*/ + free(state_QS); + free(corr_QC); } #endif /* OVERRIDE_silk_warped_autocorrelation_FIX_c */ diff --git a/lib/lib_audio/ESP8266Audio/src/libopus/silk/resampler_down2_3.c b/lib/lib_audio/ESP8266Audio/src/libopus/silk/resampler_down2_3.c index d8ce95c68..62aab5682 100644 --- a/lib/lib_audio/ESP8266Audio/src/libopus/silk/resampler_down2_3.c +++ b/lib/lib_audio/ESP8266Audio/src/libopus/silk/resampler_down2_3.c @@ -48,7 +48,8 @@ void silk_resampler_down2_3( opus_int32 *buf_ptr; SAVE_STACK; - ALLOC( buf, RESAMPLER_MAX_BATCH_SIZE_IN + ORDER_FIR, opus_int32 ); +// ALLOC( buf, RESAMPLER_MAX_BATCH_SIZE_IN + ORDER_FIR, opus_int32 ); + opus_int32 *buf = (opus_int32*)malloc((RESAMPLER_MAX_BATCH_SIZE_IN + ORDER_FIR) * sizeof(opus_int32)); /* Copy buffered samples to start of buffer */ silk_memcpy( buf, S, ORDER_FIR * sizeof( opus_int32 ) ); @@ -99,5 +100,6 @@ void silk_resampler_down2_3( /* Copy last part of filtered signal to the state for the next call */ silk_memcpy( S, &buf[ nSamplesIn ], ORDER_FIR * sizeof( opus_int32 ) ); + free(buf); RESTORE_STACK; } diff --git a/lib/lib_audio/ESP8266Audio/src/libtinysoundfont/tsf.h b/lib/lib_audio/ESP8266Audio/src/libtinysoundfont/tsf.h index e1a43221e..4ac232d14 100644 --- a/lib/lib_audio/ESP8266Audio/src/libtinysoundfont/tsf.h +++ b/lib/lib_audio/ESP8266Audio/src/libtinysoundfont/tsf.h @@ -486,16 +486,17 @@ struct tsf_stream_memory { const char* buffer; unsigned int total, pos; }; static int tsf_stream_memory_read(struct tsf_stream_memory* m, void* ptr, unsigned int size) { if (size > m->total - m->pos) size = m->total - m->pos; TSF_MEMCPY(ptr, m->buffer+m->pos, size); m->pos += size; return size; } static int tsf_stream_memory_tell(struct tsf_stream_memory* m) { return m->pos; } static int tsf_stream_memory_size(struct tsf_stream_memory* m) { return m->total; } -static int tsf_stream_memory_skip(struct tsf_stream_memory* m, unsigned int count) { if (m->pos + count > m->total) return 0; m->pos += count; return 1; } +static int tsf_stream_memory_skip(struct tsf_stream_memory* m, unsigned int count) { if (m->pos + count > m->total) count = m->total - m->pos; m->pos += count; return 1; } static int tsf_stream_memory_seek(struct tsf_stream_memory* m, unsigned int pos) { if (pos > m->total) return 0; else m->pos = pos; return 1; } -static int tsf_stream_memory_close(struct tsf_stream_memory* m) { (void)m; return 1; } +static int tsf_stream_memory_close(struct tsf_stream_memory* m) { TSF_FREE(m); return 1; } TSFDEF tsf* tsf_load_memory(const void* buffer, int size) { struct tsf_stream stream = { TSF_NULL, (int(*)(void*,void*,unsigned int))&tsf_stream_memory_read, (int(*)(void*))&tsf_stream_memory_tell, (int(*)(void*,unsigned int))&tsf_stream_memory_skip, (int(*)(void*,unsigned int))&tsf_stream_memory_seek, (int(*)(void*))&tsf_stream_memory_close, (int(*)(void*))&tsf_stream_memory_size }; - struct tsf_stream_memory f = { 0, 0, 0 }; - f.buffer = (const char*)buffer; - f.total = size; - stream.data = &f; + struct tsf_stream_memory* f = (struct tsf_stream_memory*)TSF_MALLOC(sizeof(struct tsf_stream_memory)); + f->pos = 0; + f->buffer = (const char*)buffer; + f->total = size; + stream.data = f; return tsf_load(&stream); } diff --git a/lib/lib_audio/ESP8266Audio/src/opusfile/opusfile.c b/lib/lib_audio/ESP8266Audio/src/opusfile/opusfile.c index bfd3c9e5d..7ffe32f89 100644 --- a/lib/lib_audio/ESP8266Audio/src/opusfile/opusfile.c +++ b/lib/lib_audio/ESP8266Audio/src/opusfile/opusfile.c @@ -90,12 +90,12 @@ int op_test(OpusHead *_head, ogg_sync_init(&oy); data=ogg_sync_buffer(&oy,(long)_initial_bytes); if(data!=NULL){ - ogg_stream_state os; + ogg_stream_state *os = (ogg_stream_state*)malloc(sizeof(ogg_stream_state)); ogg_page og; int ret; memcpy(data,_initial_data,_initial_bytes); ogg_sync_wrote(&oy,(long)_initial_bytes); - ogg_stream_init(&os,-1); + ogg_stream_init(os,-1); err=OP_FALSE; do{ ogg_packet op; @@ -104,11 +104,11 @@ int op_test(OpusHead *_head, if(ret<0)continue; /*Stop if we run out of data.*/ if(!ret)break; - ogg_stream_reset_serialno(&os,ogg_page_serialno(&og)); - ogg_stream_pagein(&os,&og); + ogg_stream_reset_serialno(os,ogg_page_serialno(&og)); + ogg_stream_pagein(os,&og); /*Only process the first packet on this page (if it's a BOS packet, it's required to be the only one).*/ - if(ogg_stream_packetout(&os,&op)==1){ + if(ogg_stream_packetout(os,&op)==1){ if(op.b_o_s){ ret=opus_head_parse(_head,op.packet,op.bytes); /*If this didn't look like Opus, keep going.*/ @@ -122,7 +122,8 @@ int op_test(OpusHead *_head, } } while(err==OP_FALSE); - ogg_stream_clear(&os); + ogg_stream_clear(os); + free(os); } else err=OP_EFAULT; ogg_sync_clear(&oy); @@ -835,7 +836,7 @@ static int op_find_initial_pcm_offset(OggOpusFile *_of, ogg_int64_t cur_page_gp; ogg_uint32_t serialno; opus_int32 total_duration; - int durations[255]; + int *durations = (int*)malloc(255 * sizeof(int)); int cur_page_eos; int op_count; int pi; @@ -852,26 +853,31 @@ static int op_find_initial_pcm_offset(OggOpusFile *_of, Otherwise there are no audio data packets in the whole logical stream.*/ if(OP_UNLIKELY(page_offset<0)){ /*Fail if there was a read error.*/ - if(page_offsethead.pre_skip>0)return OP_EBADTIMESTAMP; + if(_link->head.pre_skip>0) {free(durations); return OP_EBADTIMESTAMP;} _link->pcm_file_offset=0; /*Set pcm_end and end_offset so we can skip the call to op_find_final_pcm_offset().*/ _link->pcm_start=_link->pcm_end=0; _link->end_offset=_link->data_offset; + free(durations); return 0; } /*Similarly, if we hit the next link in the chain, we've gone too far.*/ if(OP_UNLIKELY(ogg_page_bos(_og))){ - if(_link->head.pre_skip>0)return OP_EBADTIMESTAMP; + if(_link->head.pre_skip>0) { + free(durations); + return OP_EBADTIMESTAMP; + } /*Set pcm_end and end_offset so we can skip the call to op_find_final_pcm_offset().*/ _link->pcm_file_offset=0; _link->pcm_start=_link->pcm_end=0; _link->end_offset=_link->data_offset; /*Tell the caller we've got a buffered page for them.*/ + free(durations); return 1; } /*Ignore pages from other streams (not strictly necessary, because of the @@ -901,7 +907,10 @@ static int op_find_initial_pcm_offset(OggOpusFile *_of, cur_page_gp=_of->op[op_count-1].granulepos; /*But getting a packet without a valid granule position on the page is not okay.*/ - if(cur_page_gp==-1)return OP_EBADTIMESTAMP; + if(cur_page_gp==-1) { + free(durations); + return OP_EBADTIMESTAMP; + } cur_page_eos=_of->op[op_count-1].e_o_s; if(OP_LIKELY(!cur_page_eos)){ /*The EOS flag wasn't set. @@ -910,6 +919,7 @@ static int op_find_initial_pcm_offset(OggOpusFile *_of, if(OP_UNLIKELY(op_granpos_add(&pcm_start,cur_page_gp,-total_duration)<0)){ /*The starting granule position MUST not be smaller than the amount of audio on the first page with completed packets.*/ + free(durations); return OP_EBADTIMESTAMP; } } @@ -923,6 +933,7 @@ static int op_find_initial_pcm_offset(OggOpusFile *_of, /*However, the end-trimming MUST not ask us to trim more samples than exist after applying the pre-skip.*/ if(OP_UNLIKELY(op_granpos_cmp(cur_page_gp,_link->head.pre_skip)<0)){ + free(durations); return OP_EBADTIMESTAMP; } } @@ -957,6 +968,7 @@ static int op_find_initial_pcm_offset(OggOpusFile *_of, _link->pcm_file_offset=0; _of->prev_packet_gp=_link->pcm_start=pcm_start; _of->prev_page_offset=page_offset; + free(durations); return 0; } @@ -1391,32 +1403,34 @@ static int op_open_seekable2_impl(OggOpusFile *_of){ /*64 seek records should be enough for anybody. Actually, with a bisection search in a 63-bit range down to OP_CHUNK_SIZE granularity, much more than enough.*/ - OpusSeekRecord sr[64]; + OpusSeekRecord *sr = (OpusSeekRecord*)malloc(64 * sizeof(OpusSeekRecord)); opus_int64 data_offset; int ret; /*We can seek, so set out learning all about this file.*/ (*_of->callbacks.seek)(_of->stream,0,SEEK_END); _of->offset=_of->end=(*_of->callbacks.tell)(_of->stream); - if(OP_UNLIKELY(_of->end<0))return OP_EREAD; + if(OP_UNLIKELY(_of->end<0)){free(sr); return OP_EREAD;} data_offset=_of->links[0].data_offset; - if(OP_UNLIKELY(_of->endendend, _of->links[0].serialno,_of->serialnos,_of->nserialnos); - if(OP_UNLIKELY(ret<0))return ret; + if(OP_UNLIKELY(ret<0)){free(sr); return ret;} /*If there's any trailing junk, forget about it.*/ _of->end=sr[0].offset+sr[0].size; - if(OP_UNLIKELY(_of->endendserialnos,&_of->nserialnos,&_of->cserialnos); + free(sr); + return ret; } static int op_open_seekable2(OggOpusFile *_of){ ogg_sync_state oy_start; - ogg_stream_state os_start; + ogg_stream_state *os_start = (ogg_stream_state*)malloc(sizeof(ogg_stream_state)); ogg_packet *op_start; opus_int64 prev_page_offset; opus_int64 start_offset; @@ -1435,9 +1449,9 @@ static int op_open_seekable2(OggOpusFile *_of){ start_op_count=_of->op_count; /*This is a bit too large to put on the stack unconditionally.*/ op_start=(ogg_packet *)_ogg_malloc(sizeof(*op_start)*start_op_count); - if(op_start==NULL)return OP_EFAULT; + if(op_start==NULL){free(os_start); return OP_EFAULT;} *&oy_start=_of->oy; - *&os_start=_of->os; + *os_start=_of->os; prev_page_offset=_of->prev_page_offset; start_offset=_of->offset; memcpy(op_start,_of->op,sizeof(*op_start)*start_op_count); @@ -1449,7 +1463,7 @@ static int op_open_seekable2(OggOpusFile *_of){ ogg_stream_clear(&_of->os); ogg_sync_clear(&_of->oy); *&_of->oy=*&oy_start; - *&_of->os=*&os_start; + *&_of->os=*os_start; _of->offset=start_offset; _of->op_count=start_op_count; memcpy(_of->op,op_start,sizeof(*_of->op)*start_op_count); @@ -1457,9 +1471,10 @@ static int op_open_seekable2(OggOpusFile *_of){ _of->prev_packet_gp=_of->links[0].pcm_start; _of->prev_page_offset=prev_page_offset; _of->cur_discard_count=_of->links[0].head.pre_skip; - if(OP_UNLIKELY(ret<0))return ret; + if(OP_UNLIKELY(ret<0)){free(os_start); return ret;} /*And restore the position indicator.*/ ret=(*_of->callbacks.seek)(_of->stream,op_position(_of),SEEK_SET); + free(os_start); return OP_UNLIKELY(ret<0)?OP_EREAD:0; } @@ -1980,7 +1995,7 @@ static int op_fetch_and_process_page(OggOpusFile *_of, ogg_stream_pagein(&_of->os,&og); if(OP_LIKELY(_of->ready_state>=OP_INITSET)){ opus_int32 total_duration; - int durations[255]; + int *durations = (int*)malloc(255 * sizeof(int)); int op_count; int report_hole; report_hole=0; @@ -2037,7 +2052,7 @@ static int op_fetch_and_process_page(OggOpusFile *_of, Proceed to the next link, rather than risk playing back some samples that shouldn't have been played.*/ _of->op_count=0; - if(report_hole)return OP_HOLE; + if(report_hole){ free(durations); return OP_HOLE; } continue; } /*By default discard 80 ms of data after a seek, unless we seek @@ -2145,9 +2160,9 @@ static int op_fetch_and_process_page(OggOpusFile *_of, _of->prev_page_offset=_page_offset; _of->op_count=op_count=pi; } - if(report_hole)return OP_HOLE; + if(report_hole) { free(durations); return OP_HOLE; } /*If end-trimming didn't trim all the packets, we're done.*/ - if(op_count>0)return 0; + if(op_count>0) { free(durations); return 0; } } } } diff --git a/lib/lib_audio/ESP8266Audio/tests/common.sh b/lib/lib_audio/ESP8266Audio/tests/common.sh old mode 100644 new mode 100755 diff --git a/lib/lib_audio/ESP8266Audio/tests/host/Makefile b/lib/lib_audio/ESP8266Audio/tests/host/Makefile index 9a219465e..25156dbcd 100644 --- a/lib/lib_audio/ESP8266Audio/tests/host/Makefile +++ b/lib/lib_audio/ESP8266Audio/tests/host/Makefile @@ -73,12 +73,17 @@ libopus=../../src/libopus/opus_decoder.c ../../src/libopus/opus_projection_decod opusfile=../../src/opusfile/opusfile.c ../../src/opusfile/stream.c ../../src/opusfile/internal.c ../../src/opusfile/info.c -CCOPTS=-g -Wunused-parameter -Wall -m32 -include Arduino.h -CPPOPTS=-g -Wunused-parameter -Wall -std=c++11 -m32 -include Arduino.h +libflac=../../src/libflac/md5.c ../../src/libflac/window.c ../../src/libflac/memory.c ../../src/libflac/cpu.c \ +../../src/libflac/fixed.c ../../src/libflac/format.c ../../src/libflac/lpc.c ../../src/libflac/crc.c \ +../../src/libflac/bitreader.c ../../src/libflac/bitmath.c ../../src/libflac/stream_decoder.c ../../src/libflac/float.c + + +CCOPTS=-g -Wunused-parameter -Wall -m32 -include Arduino.h -Wstack-usage=300 +CPPOPTS=-g -Wunused-parameter -Wall -std=c++11 -m32 -Wstack-usage=300 -include Arduino.h .phony: all -all: mp3 aac wav midi opus +all: mp3 aac wav midi opus flac mp3: FORCE rm -f *.o @@ -94,6 +99,13 @@ aac: FORCE rm -f *.o echo valgrind --leak-check=full --track-origins=yes -v --error-limit=no --show-leak-kinds=all ./aac +flac: FORCE + rm -f *.o + gcc $(CCOPTS) -DUSE_DEFAULT_STDLIB -c $(libflac) -I ../../src/ -I ../../src/libflac -I. + g++ $(CPPOPTS) -o flac flac.cpp Serial.cpp *.o ../../src/AudioFileSourceSTDIO.cpp ../../src/AudioOutputSTDIO.cpp ../../src/AudioFileSourceID3.cpp ../../src/AudioGeneratorFLAC.cpp ../../src/AudioLogger.cpp -I ../../src/ -I. + rm -f *.o + echo valgrind --leak-check=full --track-origins=yes -v --error-limit=no --show-leak-kinds=all ./flac + wav: FORCE rm -f *.o g++ $(CPPOPTS) -o wav wav.cpp Serial.cpp ../../src/AudioFileSourceSTDIO.cpp ../../src/AudioOutputSTDIO.cpp ../../src/AudioGeneratorWAV.cpp ../../src/AudioLogger.cpp -I ../../src/ -I. @@ -116,6 +128,6 @@ opus: FORCE echo valgrind --leak-check=full --track-origins=yes -v --error-limit=no --show-leak-kinds=all ./opus clean: - rm -f mp3 aac wav midi opus *.o + rm -f mp3 aac wav midi opus flac *.o FORCE: diff --git a/lib/lib_audio/ESP8266Audio/tests/host/flac.cpp b/lib/lib_audio/ESP8266Audio/tests/host/flac.cpp new file mode 100644 index 000000000..2d7110da1 --- /dev/null +++ b/lib/lib_audio/ESP8266Audio/tests/host/flac.cpp @@ -0,0 +1,24 @@ +#include +#include "AudioFileSourceSTDIO.h" +#include "AudioOutputSTDIO.h" +#include "AudioGeneratorFLAC.h" + +#define AAC "gs-16b-2c-44100hz.flac" + +int main(int argc, char **argv) +{ + (void) argc; + (void) argv; + AudioFileSourceSTDIO *in = new AudioFileSourceSTDIO(AAC); + AudioOutputSTDIO *out = new AudioOutputSTDIO(); + out->SetFilename("out.flac.wav"); + AudioGeneratorFLAC *flac = new AudioGeneratorFLAC(); + + flac->begin(in, out); + while (flac->loop()) { /*noop*/ } + flac->stop(); + + delete flac; + delete out; + delete in; +} diff --git a/lib/lib_audio/ESP8266Audio/tests/host/gs-16b-2c-44100hz.flac b/lib/lib_audio/ESP8266Audio/tests/host/gs-16b-2c-44100hz.flac new file mode 100644 index 000000000..177132146 Binary files /dev/null and b/lib/lib_audio/ESP8266Audio/tests/host/gs-16b-2c-44100hz.flac differ diff --git a/lib/libesp32/Berry/default/be_i2s_audio_lib.c b/lib/libesp32/Berry/default/be_i2s_audio_lib.c index d41fc683f..888cb2d42 100644 --- a/lib/libesp32/Berry/default/be_i2s_audio_lib.c +++ b/lib/libesp32/Berry/default/be_i2s_audio_lib.c @@ -10,6 +10,7 @@ extern int i2s_output_i2s_init(bvm *vm); extern int i2s_output_i2s_deinit(bvm *vm); +extern int i2s_output_i2s_stop(bvm *vm); extern int i2s_generator_wav_init(bvm *vm); extern int i2s_generator_wav_deinit(bvm *vm); @@ -82,13 +83,12 @@ class be_class_audio_file_source (scope: global, name: AudioFileSource) { class be_class_audio_output_i2s (scope: global, name: AudioOutputI2S, super: be_class_audio_output) { init, func(i2s_output_i2s_init) deinit, func(i2s_output_i2s_deinit) - close, func(i2s_output_i2s_deinit) + stop, func(i2s_output_i2s_stop) } class be_class_audio_generator_wav (scope: global, name: AudioGeneratorWAV, super: be_class_audio_generator) { init, func(i2s_generator_wav_init) deinit, func(i2s_generator_wav_deinit) - close, func(i2s_generator_wav_deinit) begin, func(i2s_generator_wav_begin) loop, func(i2s_generator_wav_loop) stop, func(i2s_generator_wav_stop) @@ -98,7 +98,6 @@ class be_class_audio_generator_wav (scope: global, name: AudioGeneratorWAV, supe class be_class_audio_generator_mp3 (scope: global, name: AudioGeneratorMP3, super: be_class_audio_generator) { init, func(i2s_generator_mp3_init) deinit, func(i2s_generator_mp3_deinit) - close, func(i2s_generator_mp3_deinit) begin, func(i2s_generator_mp3_begin) loop, func(i2s_generator_mp3_loop) stop, func(i2s_generator_mp3_stop) @@ -108,7 +107,6 @@ class be_class_audio_generator_mp3 (scope: global, name: AudioGeneratorMP3, supe class be_class_audio_file_source_fs (scope: global, name: AudioFileSourceFS, super: be_class_audio_file_source) { init, func(i2s_file_source_fs_init) deinit, func(i2s_file_source_fs_deinit) - close, func(i2s_file_source_fs_deinit) } @const_object_info_end */ diff --git a/lib/libesp32/Berry/generate/be_const_strtab.h b/lib/libesp32/Berry/generate/be_const_strtab.h index 513a00d95..e4b933205 100644 --- a/lib/libesp32/Berry/generate/be_const_strtab.h +++ b/lib/libesp32/Berry/generate/be_const_strtab.h @@ -314,7 +314,6 @@ extern const bcstring be_const_str_number; extern const bcstring be_const_str_read_bytes; extern const bcstring be_const_str_dot_p; extern const bcstring be_const_str_bus; -extern const bcstring be_const_str_close; extern const bcstring be_const_str_lv_group_focus_cb; extern const bcstring be_const_str_SDS0X1_TX; extern const bcstring be_const_str_SDM72_TX; diff --git a/lib/libesp32/Berry/generate/be_const_strtab_def.h b/lib/libesp32/Berry/generate/be_const_strtab_def.h index b36a4fcc3..aeed1bb30 100644 --- a/lib/libesp32/Berry/generate/be_const_strtab_def.h +++ b/lib/libesp32/Berry/generate/be_const_strtab_def.h @@ -313,8 +313,7 @@ be_define_const_str(lv_switch, "lv_switch", 3407171508u, 0, 9, &be_const_str_num be_define_const_str(number, "number", 467038368u, 0, 6, &be_const_str_read_bytes); be_define_const_str(read_bytes, "read_bytes", 3576733173u, 0, 10, NULL); be_define_const_str(dot_p, ".p", 1171526419u, 0, 2, NULL); -be_define_const_str(bus, "bus", 1607822841u, 0, 3, &be_const_str_close); -be_define_const_str(close, "close", 667630371u, 0, 5, &be_const_str_lv_group_focus_cb); +be_define_const_str(bus, "bus", 1607822841u, 0, 3, &be_const_str_lv_group_focus_cb); be_define_const_str(lv_group_focus_cb, "lv_group_focus_cb", 4288873836u, 0, 17, NULL); be_define_const_str(SDS0X1_TX, "SDS0X1_TX", 165045983u, 0, 9, NULL); be_define_const_str(SDM72_TX, "SDM72_TX", 2042143269u, 0, 8, NULL); @@ -972,6 +971,6 @@ static const bstring* const m_string_table[] = { static const struct bconststrtab m_const_string_table = { .size = 315, - .count = 631, + .count = 630, .table = m_string_table }; diff --git a/lib/libesp32/Berry/generate/be_fixed_be_class_audio_file_source_fs.h b/lib/libesp32/Berry/generate/be_fixed_be_class_audio_file_source_fs.h index 07937ef47..6605ba27e 100644 --- a/lib/libesp32/Berry/generate/be_fixed_be_class_audio_file_source_fs.h +++ b/lib/libesp32/Berry/generate/be_fixed_be_class_audio_file_source_fs.h @@ -1,14 +1,13 @@ #include "be_constobj.h" static be_define_const_map_slots(be_class_audio_file_source_fs_map) { - { be_const_key(init, 2), be_const_func(i2s_file_source_fs_init) }, { be_const_key(deinit, -1), be_const_func(i2s_file_source_fs_deinit) }, - { be_const_key(close, -1), be_const_func(i2s_file_source_fs_deinit) }, + { be_const_key(init, -1), be_const_func(i2s_file_source_fs_init) }, }; static be_define_const_map( be_class_audio_file_source_fs_map, - 3 + 2 ); BE_EXPORT_VARIABLE be_define_const_class( diff --git a/lib/libesp32/Berry/generate/be_fixed_be_class_audio_generator_mp3.h b/lib/libesp32/Berry/generate/be_fixed_be_class_audio_generator_mp3.h index 785fb53b1..dbe5f0d4f 100644 --- a/lib/libesp32/Berry/generate/be_fixed_be_class_audio_generator_mp3.h +++ b/lib/libesp32/Berry/generate/be_fixed_be_class_audio_generator_mp3.h @@ -1,18 +1,17 @@ #include "be_constobj.h" static be_define_const_map_slots(be_class_audio_generator_mp3_map) { - { be_const_key(close, -1), be_const_func(i2s_generator_mp3_deinit) }, - { be_const_key(stop, -1), be_const_func(i2s_generator_mp3_stop) }, - { be_const_key(loop, 0), be_const_func(i2s_generator_mp3_loop) }, - { be_const_key(isrunning, 1), be_const_func(i2s_generator_mp3_isrunning) }, { be_const_key(begin, -1), be_const_func(i2s_generator_mp3_begin) }, - { be_const_key(deinit, 6), be_const_func(i2s_generator_mp3_deinit) }, - { be_const_key(init, -1), be_const_func(i2s_generator_mp3_init) }, + { be_const_key(loop, -1), be_const_func(i2s_generator_mp3_loop) }, + { be_const_key(isrunning, -1), be_const_func(i2s_generator_mp3_isrunning) }, + { be_const_key(init, 1), be_const_func(i2s_generator_mp3_init) }, + { be_const_key(deinit, -1), be_const_func(i2s_generator_mp3_deinit) }, + { be_const_key(stop, -1), be_const_func(i2s_generator_mp3_stop) }, }; static be_define_const_map( be_class_audio_generator_mp3_map, - 7 + 6 ); BE_EXPORT_VARIABLE be_define_const_class( diff --git a/lib/libesp32/Berry/generate/be_fixed_be_class_audio_generator_wav.h b/lib/libesp32/Berry/generate/be_fixed_be_class_audio_generator_wav.h index 3436ed6e3..f08522407 100644 --- a/lib/libesp32/Berry/generate/be_fixed_be_class_audio_generator_wav.h +++ b/lib/libesp32/Berry/generate/be_fixed_be_class_audio_generator_wav.h @@ -1,18 +1,17 @@ #include "be_constobj.h" static be_define_const_map_slots(be_class_audio_generator_wav_map) { - { be_const_key(close, -1), be_const_func(i2s_generator_wav_deinit) }, - { be_const_key(stop, -1), be_const_func(i2s_generator_wav_stop) }, - { be_const_key(loop, 0), be_const_func(i2s_generator_wav_loop) }, - { be_const_key(isrunning, 1), be_const_func(i2s_generator_wav_isrunning) }, { be_const_key(begin, -1), be_const_func(i2s_generator_wav_begin) }, - { be_const_key(deinit, 6), be_const_func(i2s_generator_wav_deinit) }, - { be_const_key(init, -1), be_const_func(i2s_generator_wav_init) }, + { be_const_key(loop, -1), be_const_func(i2s_generator_wav_loop) }, + { be_const_key(isrunning, -1), be_const_func(i2s_generator_wav_isrunning) }, + { be_const_key(init, 1), be_const_func(i2s_generator_wav_init) }, + { be_const_key(deinit, -1), be_const_func(i2s_generator_wav_deinit) }, + { be_const_key(stop, -1), be_const_func(i2s_generator_wav_stop) }, }; static be_define_const_map( be_class_audio_generator_wav_map, - 7 + 6 ); BE_EXPORT_VARIABLE be_define_const_class( diff --git a/lib/libesp32/Berry/generate/be_fixed_be_class_audio_output_i2s.h b/lib/libesp32/Berry/generate/be_fixed_be_class_audio_output_i2s.h index 295ea5cd5..4e6b84e0d 100644 --- a/lib/libesp32/Berry/generate/be_fixed_be_class_audio_output_i2s.h +++ b/lib/libesp32/Berry/generate/be_fixed_be_class_audio_output_i2s.h @@ -1,9 +1,9 @@ #include "be_constobj.h" static be_define_const_map_slots(be_class_audio_output_i2s_map) { - { be_const_key(init, 2), be_const_func(i2s_output_i2s_init) }, + { be_const_key(init, -1), be_const_func(i2s_output_i2s_init) }, { be_const_key(deinit, -1), be_const_func(i2s_output_i2s_deinit) }, - { be_const_key(close, -1), be_const_func(i2s_output_i2s_deinit) }, + { be_const_key(stop, -1), be_const_func(i2s_output_i2s_stop) }, }; static be_define_const_map( diff --git a/tasmota/xdrv_52_3_berry_audio.ino b/tasmota/xdrv_52_3_berry_audio.ino index 9612de208..06957bced 100644 --- a/tasmota/xdrv_52_3_berry_audio.ino +++ b/tasmota/xdrv_52_3_berry_audio.ino @@ -61,7 +61,9 @@ extern "C" { } // AudioOutputI2S(int port=0, int output_mode=EXTERNAL_I2S, int dma_buf_count = 8, int use_apll=APLL_DISABLE); AudioOutputI2S * audio = new AudioOutputI2S(port, mode, dma_buf_count); - audio->SetPinout(bclkPin, wclkPin, doutPin); // return value has no useful information for us + if (0 == mode) { + audio->SetPinout(bclkPin, wclkPin, doutPin); // return value has no useful information for us + } be_pushcomptr(vm, (void*) audio); be_setmember(vm, 1, ".p"); be_return_nil(vm); @@ -84,6 +86,16 @@ extern "C" { be_return_nil(vm); } + int i2s_output_i2s_stop(bvm *vm) { + int argc = be_top(vm); + be_getmember(vm, 1, ".p"); + AudioOutputI2S * audio = (AudioOutputI2S *) be_tocomptr(vm, -1); + if (audio) { + audio->stop(); + } + be_return_nil(vm); + } + // // AudioGeneratorWAV() //