ESP8266Audio library from v1.5.0 to v1.9.2

This commit is contained in:
Stephan Hadinger 2021-07-30 16:34:50 +02:00
parent 173b9d8c88
commit 14c4be9d51
69 changed files with 2049 additions and 337 deletions

View File

@ -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. 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. ESP8266 is fully supported and most mature, but ESP32 is also mostly there with built-in DAC as well as external ones.

View File

@ -0,0 +1,78 @@
#include <Arduino.h>
#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);
}
}

View File

@ -1,9 +1,4 @@
#include <Arduino.h> #include <Arduino.h>
#ifdef ESP32
#include <WiFi.h>
#else
#include <ESP8266WiFi.h>
#endif
#include "AudioFileSourcePROGMEM.h" #include "AudioFileSourcePROGMEM.h"
#include "AudioGeneratorWAV.h" #include "AudioGeneratorWAV.h"
@ -18,7 +13,6 @@ AudioOutputI2SNoDAC *out;
void setup() void setup()
{ {
WiFi.mode(WIFI_OFF);
Serial.begin(115200); Serial.begin(115200);
delay(1000); delay(1000);
Serial.printf("WAV start\n"); Serial.printf("WAV start\n");

View File

@ -22,7 +22,7 @@ const char* ssid = STASSID;
const char* password = STAPSK; const char* password = STAPSK;
// Randomly picked URL // 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; AudioGeneratorMP3 *mp3;
AudioFileSourceICYStream *file; AudioFileSourceICYStream *file;

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef _AUDIOOUTPUTNULLSLOW_H
#define _AUDIOOUTPUTNULLSLOW_H
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <stdio.h>
#include <linux/soundcard.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]) {
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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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<<everylog2)-1)) == 0) {
if (hertz > 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

View File

@ -0,0 +1,118 @@
#include <Arduino.h>
#ifdef ESP32
#include <WiFi.h>
#else
#include <ESP8266WiFi.h>
#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<const char *>(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<const char *>(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);
}
}

View File

@ -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

View File

@ -22,6 +22,7 @@ AudioGeneratorWAV KEYWORD1
AudioOutput KEYWORD1 AudioOutput KEYWORD1
AudioOutputI2S KEYWORD1 AudioOutputI2S KEYWORD1
AudioOutputI2SNoDAC KEYWORD1 AudioOutputI2SNoDAC KEYWORD1
AudioOutputI2SClass KEYWORD1
AudioOutputNull KEYWORD1 AudioOutputNull KEYWORD1
AudioOutputBuffer KEYWORD1 AudioOutputBuffer KEYWORD1
AudioOutputSerialWAV KEYWORD1 AudioOutputSerialWAV KEYWORD1

View File

@ -1,6 +1,6 @@
{ {
"name": "ESP8266Audio", "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", "keywords": "ESP8266, ESP32, MP3, AAC, WAV, MOD, FLAC, RTTTL, MIDI, I2S, DAC, Delta-Sigma, TTS",
"authors": [ "authors": [
{ {
@ -14,13 +14,9 @@
"type": "git", "type": "git",
"url": "https://github.com/earlephilhower/ESP8266Audio" "url": "https://github.com/earlephilhower/ESP8266Audio"
}, },
"version": "1.5.0", "version": "1.9.2",
"homepage": "https://github.com/earlephilhower/ESP8266Audio", "homepage": "https://github.com/earlephilhower/ESP8266Audio",
"dependencies": {
"SPI": "1.0"
},
"frameworks": "Arduino", "frameworks": "Arduino",
"platforms": ["espressif8266", "espressif32"],
"examples": [ "examples": [
"examples/*/*.ino" "examples/*/*.ino"
] ]

View File

@ -1,9 +1,9 @@
name=ESP8266Audio name=ESP8266Audio
version=1.5.0 version=1.9.2
author=Earle F. Philhower, III author=Earle F. Philhower, III
maintainer=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. 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 category=Signal Input/Output
url=https://github.com/earlephilhower/ESP8266Audio url=https://github.com/earlephilhower/ESP8266Audio
architectures=esp8266,esp32 architectures=esp8266,esp32,rp2040

View File

@ -29,8 +29,8 @@
class AudioFileSourceFS : public AudioFileSource class AudioFileSourceFS : public AudioFileSource
{ {
public: public:
AudioFileSourceFS(FS &fs) { filesystem = &fs; } AudioFileSourceFS(fs::FS &fs) { filesystem = &fs; }
AudioFileSourceFS(FS &fs, const char *filename); AudioFileSourceFS(fs::FS &fs, const char *filename);
virtual ~AudioFileSourceFS() override; virtual ~AudioFileSourceFS() override;
virtual bool open(const char *filename) 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(); }; virtual uint32_t getPos() override { if (!f) return 0; else return f.position(); };
private: private:
FS *filesystem; fs::FS *filesystem;
File f; fs::File f;
}; };

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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<uint8_t*>(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;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef _AUDIOFILESOURCEFUNCTION_H
#define _AUDIOFILESOURCEFUNCTION_H
#include <Arduino.h>
#include <vector>
#include <functional>
#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<float(float)>;
std::vector<callback_t> 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 <typename F, typename... Fs>
bool addAudioGenerators(const F& f, Fs&&... fs) {
funcs.emplace_back(f);
return addAudioGenerators(std::forward<Fs>(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

View File

@ -18,6 +18,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#if defined(ESP32) || defined(ESP8266)
#include "AudioFileSourceHTTPStream.h" #include "AudioFileSourceHTTPStream.h"
AudioFileSourceHTTPStream::AudioFileSourceHTTPStream() AudioFileSourceHTTPStream::AudioFileSourceHTTPStream()
@ -40,7 +42,7 @@ bool AudioFileSourceHTTPStream::open(const char *url)
http.begin(client, url); http.begin(client, url);
http.setReuse(true); http.setReuse(true);
#ifndef ESP32 #ifndef ESP32
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); http.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS);
#endif #endif
int code = http.GET(); int code = http.GET();
if (code != HTTP_CODE_OK) { if (code != HTTP_CODE_OK) {
@ -84,7 +86,7 @@ retry:
cb.st(STATUS_DISCONNECTED, PSTR("Stream disconnected")); cb.st(STATUS_DISCONNECTED, PSTR("Stream disconnected"));
http.end(); http.end();
for (int i = 0; i < reconnectTries; i++) { for (int i = 0; i < reconnectTries; i++) {
char buff[32]; char buff[64];
sprintf_P(buff, PSTR("Attempting to reconnect, try %d"), i); sprintf_P(buff, PSTR("Attempting to reconnect, try %d"), i);
cb.st(STATUS_RECONNECTING, buff); cb.st(STATUS_RECONNECTING, buff);
delay(reconnectDelayMs); delay(reconnectDelayMs);
@ -152,3 +154,5 @@ uint32_t AudioFileSourceHTTPStream::getPos()
{ {
return pos; return pos;
} }
#endif

View File

@ -1,7 +1,7 @@
/* /*
AudioFileSourceHTTPStream AudioFileSourceHTTPStream
Connect to a HTTP based streaming service Connect to a HTTP based streaming service
Copyright (C) 2017 Earle F. Philhower, III Copyright (C) 2017 Earle F. Philhower, III
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
@ -18,14 +18,13 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef _AUDIOFILESOURCEHTTPSTREAM_H #if defined(ESP32) || defined(ESP8266)
#define _AUDIOFILESOURCEHTTPSTREAM_H #pragma once
#include <Arduino.h> #include <Arduino.h>
#ifdef ESP32 #ifdef ESP32
#include <HTTPClient.h> #include <HTTPClient.h>
#else #else
#include <WiFiClient.h>
#include <ESP8266HTTPClient.h> #include <ESP8266HTTPClient.h>
#endif #endif
#include "AudioFileSource.h" #include "AudioFileSource.h"
@ -38,7 +37,7 @@ class AudioFileSourceHTTPStream : public AudioFileSource
AudioFileSourceHTTPStream(); AudioFileSourceHTTPStream();
AudioFileSourceHTTPStream(const char *url); AudioFileSourceHTTPStream(const char *url);
virtual ~AudioFileSourceHTTPStream() override; virtual ~AudioFileSourceHTTPStream() override;
virtual bool open(const char *url) override; virtual bool open(const char *url) override;
virtual uint32_t read(void *data, uint32_t len) override; virtual uint32_t read(void *data, uint32_t len) override;
virtual uint32_t readNonBlock(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 getSize() override;
virtual uint32_t getPos() override; virtual uint32_t getPos() override;
bool SetReconnect(int tries, int delayms) { reconnectTries = tries; reconnectDelayMs = delayms; return true; } 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 }; enum { STATUS_HTTPFAIL=2, STATUS_DISCONNECTED, STATUS_RECONNECTING, STATUS_RECONNECTED, STATUS_NODATA };
@ -64,3 +64,4 @@ class AudioFileSourceHTTPStream : public AudioFileSource
#endif #endif

View File

@ -17,6 +17,9 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#if defined(ESP32) || defined(ESP8266)
#define _GNU_SOURCE #define _GNU_SOURCE
#include "AudioFileSourceICYStream.h" #include "AudioFileSourceICYStream.h"
@ -83,12 +86,16 @@ AudioFileSourceICYStream::~AudioFileSourceICYStream()
uint32_t AudioFileSourceICYStream::readInternal(void *data, uint32_t len, bool nonBlock) 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: retry:
if (!http.connected()) { if (!http.connected()) {
cb.st(STATUS_DISCONNECTED, PSTR("Stream disconnected")); cb.st(STATUS_DISCONNECTED, PSTR("Stream disconnected"));
http.end(); http.end();
for (int i = 0; i < reconnectTries; i++) { for (int i = 0; i < reconnectTries; i++) {
char buff[32]; char buff[64];
sprintf_P(buff, PSTR("Attempting to reconnect, try %d"), i); sprintf_P(buff, PSTR("Attempting to reconnect, try %d"), i);
cb.st(STATUS_RECONNECTING, buff); cb.st(STATUS_RECONNECTING, buff);
delay(reconnectDelayMs); delay(reconnectDelayMs);
@ -211,3 +218,5 @@ retry:
icyByteCount += ret; icyByteCount += ret;
return read; return read;
} }
#endif

View File

@ -1,7 +1,7 @@
/* /*
AudioFileSourceHTTPStream AudioFileSourceHTTPStream
Connect to a HTTP based streaming service Connect to a HTTP based streaming service
Copyright (C) 2017 Earle F. Philhower, III Copyright (C) 2017 Earle F. Philhower, III
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
@ -18,8 +18,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef _AUDIOFILESOURCEICYSTREAM_H #if defined(ESP32) || defined(ESP8266)
#define _AUDIOFILESOURCEICYSTREAM_H #pragma once
#include <Arduino.h> #include <Arduino.h>
#ifdef ESP32 #ifdef ESP32
@ -36,7 +36,7 @@ class AudioFileSourceICYStream : public AudioFileSourceHTTPStream
AudioFileSourceICYStream(); AudioFileSourceICYStream();
AudioFileSourceICYStream(const char *url); AudioFileSourceICYStream(const char *url);
virtual ~AudioFileSourceICYStream() override; virtual ~AudioFileSourceICYStream() override;
virtual bool open(const char *url) override; virtual bool open(const char *url) override;
private: private:
@ -45,5 +45,4 @@ class AudioFileSourceICYStream : public AudioFileSourceHTTPStream
int icyByteCount; int icyByteCount;
}; };
#endif #endif

View File

@ -143,6 +143,7 @@ uint32_t AudioFileSourceID3::read(void *data, uint32_t len)
if (ret<10) return ret; if (ret<10) return ret;
if ((buff[0]!='I') || (buff[1]!='D') || (buff[2]!='3') || (buff[3]>0x04) || (buff[3]<0x02) || (buff[4]!=0)) { 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); 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 // Read the value and send to callback
char value[64]; char value[64];
uint16_t i; uint32_t i;
bool isUnicode = (id3.getByte()==1) ? true : false; bool isUnicode = (id3.getByte()==1) ? true : false;
for (i=0; i<framesize-1; i++) { for (i=0; i<(uint32_t)framesize-1; i++) {
if (i<sizeof(value)-1) value[i] = id3.getByte(); if (i<sizeof(value)-1) value[i] = id3.getByte();
else (void)id3.getByte(); else (void)id3.getByte();
} }
@ -231,10 +232,24 @@ uint32_t AudioFileSourceID3::read(void *data, uint32_t len)
} else if ( (frameid[0]=='T' && frameid[1]=='Y' && frameid[2]=='E' && frameid[3] == 'R') || } else if ( (frameid[0]=='T' && frameid[1]=='Y' && frameid[2]=='E' && frameid[3] == 'R') ||
(frameid[0]=='T' && frameid[1]=='Y' && frameid[2]=='E' && rev==2) ) { (frameid[0]=='T' && frameid[1]=='Y' && frameid[2]=='E' && rev==2) ) {
cb.md("Year", isUnicode, value); cb.md("Year", isUnicode, value);
} else if ( (frameid[0]=='T' && frameid[1]=='R' && frameid[2]=='C' && frameid[3] == 'K') ||
(frameid[0]=='T' && frameid[1]=='R' && frameid[2]=='K' && rev==2) ) {
cb.md("track", isUnicode, value);
} else if ( (frameid[0]=='T' && frameid[1]=='P' && frameid[2]=='O' && frameid[3] == 'S') ||
(frameid[0]=='T' && frameid[1]=='P' && frameid[2]=='A' && rev==2) ) {
cb.md("Set", isUnicode, value);
} else if ( (frameid[0]=='P' && frameid[1]=='O' && frameid[2]=='P' && frameid[3] == 'M') ||
(frameid[0]=='P' && frameid[1]=='O' && frameid[2]=='P' && rev==2) ) {
cb.md("Popularimeter", isUnicode, value);
} else if ( (frameid[0]=='T' && frameid[1]=='C' && frameid[2]=='M' && frameid[3] == 'P') ) {
cb.md("Compilation", isUnicode, value);
} }
} }
} while (!id3.eof()); } while (!id3.eof());
// use callback function to signal end of tags and beginning of content.
cb.md("eof", false, "id3");
// All ID3 processing done, return to main caller // All ID3 processing done, return to main caller
return src->read(data, len); return src->read(data, len);
} }

View File

@ -1,7 +1,7 @@
/* /*
AudioFileSourceSPIFFS AudioFileSourceSPIFFS
Input SD card "file" to be used by AudioGenerator Input SD card "file" to be used by AudioGenerator
Copyright (C) 2017 Earle F. Philhower, III Copyright (C) 2017 Earle F. Philhower, III
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
@ -22,10 +22,6 @@
#define _AUDIOFILESOURCESD_H #define _AUDIOFILESOURCESD_H
#include "AudioFileSource.h" #include "AudioFileSource.h"
#ifdef ESP8266
#include <SdFat.h>
#include <SDFS.h>
#endif
#include <SD.h> #include <SD.h>
@ -35,7 +31,7 @@ class AudioFileSourceSD : public AudioFileSource
AudioFileSourceSD(); AudioFileSourceSD();
AudioFileSourceSD(const char *filename); AudioFileSourceSD(const char *filename);
virtual ~AudioFileSourceSD() override; virtual ~AudioFileSourceSD() override;
virtual bool open(const char *filename) override; virtual bool open(const char *filename) override;
virtual uint32_t read(void *data, uint32_t len) override; virtual uint32_t read(void *data, uint32_t len) override;
virtual bool seek(int32_t pos, int dir) override; virtual bool seek(int32_t pos, int dir) override;
@ -50,3 +46,4 @@ class AudioFileSourceSD : public AudioFileSource
#endif #endif

View File

@ -22,6 +22,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#if defined(ESP32) || defined(ESP8266)
#include <Arduino.h> #include <Arduino.h>
#include "AudioFileSourceSPIRAMBuffer.h" #include "AudioFileSourceSPIRAMBuffer.h"
@ -165,3 +167,5 @@ bool AudioFileSourceSPIRAMBuffer::loop()
} }
return true; return true;
} }
#endif

View File

@ -19,8 +19,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef _AUDIOFILESOURCESPIRAMBUFFER_H #if defined(ESP32) || defined(ESP8266)
#define _AUDIOFILESOURCESPIRAMBUFFER_H #pragma once
#include "AudioFileSource.h" #include "AudioFileSource.h"
#include <SPI.h> #include <SPI.h>

View File

@ -35,6 +35,7 @@ class AudioGenerator
virtual bool loop() { return false; }; virtual bool loop() { return false; };
virtual bool stop() { return false; }; virtual bool stop() { return false; };
virtual bool isRunning() { return false;}; virtual bool isRunning() { return false;};
virtual void desync () { };
public: public:
virtual bool RegisterMetadataCB(AudioStatus::metadataCBFn fn, void *data) { return cb.RegisterMetadataCB(fn, data); } virtual bool RegisterMetadataCB(AudioStatus::metadataCBFn fn, void *data) { return cb.RegisterMetadataCB(fn, data); }

View File

@ -144,8 +144,13 @@ bool AudioGeneratorAAC::loop()
// If we've got data, try and pump it out... // If we've got data, try and pump it out...
while (validSamples) { while (validSamples) {
lastSample[0] = outSample[curSample*2]; if (lastChannels == 1) {
lastSample[1] = outSample[curSample*2 + 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 if (!output->ConsumeSample(lastSample)) goto done; // Can't send, but no error detected
validSamples--; validSamples--;
curSample++; curSample++;

View File

@ -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 // 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. // the pointers here and use it in our loop() instead of memcpy()'ing into yet another buffer.
buffLen = frame->header.blocksize; buffLen = frame->header.blocksize;
buff[0] = buffer[0]; buff[0] = (const int *)buffer[0];
if (frame->header.channels>1) buff[1] = buffer[1]; if (frame->header.channels>1) buff[1] = (const int *)buffer[1];
else buff[1] = buffer[0]; else buff[1] = (const int *)buffer[0];
buffPtr = 0; buffPtr = 0;
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
} }

View File

@ -1,7 +1,7 @@
/* /*
AudioGeneratorMP3 AudioGeneratorMP3
Wrap libmad MP3 library to play audio Wrap libmad MP3 library to play audio
Copyright (C) 2017 Earle F. Philhower, III Copyright (C) 2017 Earle F. Philhower, III
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
@ -29,11 +29,23 @@ AudioGeneratorMP3::AudioGeneratorMP3()
buff = NULL; buff = NULL;
nsCountMax = 1152/32; nsCountMax = 1152/32;
madInitted = false; 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; running = false;
file = NULL; file = NULL;
@ -41,8 +53,6 @@ AudioGeneratorMP3::AudioGeneratorMP3(void *space, int size)
buff = NULL; buff = NULL;
nsCountMax = 1152/32; nsCountMax = 1152/32;
madInitted = false; madInitted = false;
preallocateSpace = space;
preallocateSize = size;
} }
AudioGeneratorMP3::~AudioGeneratorMP3() AudioGeneratorMP3::~AudioGeneratorMP3()
@ -52,7 +62,7 @@ AudioGeneratorMP3::~AudioGeneratorMP3()
free(synth); free(synth);
free(frame); free(frame);
free(stream); free(stream);
} }
} }
@ -109,7 +119,12 @@ enum mad_flow AudioGeneratorMP3::Input()
if (stream->next_frame) { if (stream->next_frame) {
unused = lastBuffLen - (stream->next_frame - buff); 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; 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.... // Can't read any from the file, and we don't have anything left. It's done....
return MAD_FLOW_STOP; return MAD_FLOW_STOP;
} }
if (len < 0) {
desync();
unused = 0;
}
lastBuffLen = len + unused; lastBuffLen = len + unused;
mad_stream_buffer(stream, buff, lastBuffLen); mad_stream_buffer(stream, buff, lastBuffLen);
@ -132,6 +151,16 @@ enum mad_flow AudioGeneratorMP3::Input()
return MAD_FLOW_CONTINUE; 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() bool AudioGeneratorMP3::DecodeNextFrame()
{ {
@ -153,7 +182,7 @@ bool AudioGeneratorMP3::GetOneSample(int16_t sample[2])
output->SetChannels(synth->pcm.channels); output->SetChannels(synth->pcm.channels);
lastChannels = synth->pcm.channels; lastChannels = synth->pcm.channels;
} }
// If we're here, we have one decoded frame and sent 0 or more samples out // If we're here, we have one decoded frame and sent 0 or more samples out
if (samplePtr < synth->pcm.length) { if (samplePtr < synth->pcm.length) {
sample[AudioOutput::LEFTCHANNEL ] = synth->pcm.samples[0][samplePtr]; sample[AudioOutput::LEFTCHANNEL ] = synth->pcm.samples[0][samplePtr];
@ -161,7 +190,7 @@ bool AudioGeneratorMP3::GetOneSample(int16_t sample[2])
samplePtr++; samplePtr++;
} else { } else {
samplePtr = 0; samplePtr = 0;
switch ( mad_synth_frame_onens(synth, frame, nsCount++) ) { switch ( mad_synth_frame_onens(synth, frame, nsCount++) ) {
case MAD_FLOW_STOP: case MAD_FLOW_STOP:
case MAD_FLOW_BREAK: audioLogger->printf_P(PSTR("msf1ns failed\n")); case MAD_FLOW_BREAK: audioLogger->printf_P(PSTR("msf1ns failed\n"));
@ -196,6 +225,18 @@ retry:
} }
if (!DecodeNextFrame()) { 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; goto retry;
} }
samplePtr = 9999; samplePtr = 9999;
@ -229,6 +270,9 @@ bool AudioGeneratorMP3::begin(AudioFileSource *source, AudioOutput *output)
return false; // Error return false; // Error
} }
// Reset error count from previous file
unrecoverable = 0;
output->SetBitsPerSample(16); // Constant for MP3 decoder output->SetBitsPerSample(16); // Constant for MP3 decoder
output->SetChannels(2); output->SetChannels(2);
@ -243,16 +287,32 @@ bool AudioGeneratorMP3::begin(AudioFileSource *source, AudioOutput *output)
lastBuffLen = 0; lastBuffLen = 0;
// Allocate all large memory chunks // Allocate all large memory chunks
if (preallocateSpace) { if (preallocateStreamSize + preallocateFrameSize + preallocateSynthSize) {
if (preallocateSize >= preAllocBuffSize() &&
preallocateStreamSize >= preAllocStreamSize() &&
preallocateFrameSize >= preAllocFrameSize() &&
preallocateSynthSize >= preAllocSynthSize()) {
buff = reinterpret_cast<unsigned char *>(preallocateSpace);
stream = reinterpret_cast<struct mad_stream *>(preallocateStreamSpace);
frame = reinterpret_cast<struct mad_frame *>(preallocateFrameSpace);
synth = reinterpret_cast<struct mad_synth *>(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<uint8_t *>(preallocateSpace); uint8_t *p = reinterpret_cast<uint8_t *>(preallocateSpace);
buff = reinterpret_cast<unsigned char *>(p); buff = reinterpret_cast<unsigned char *>(p);
p += (buffLen+7) & ~7; p += preAllocBuffSize();
stream = reinterpret_cast<struct mad_stream *>(p); stream = reinterpret_cast<struct mad_stream *>(p);
p += (sizeof(struct mad_stream)+7) & ~7; p += preAllocStreamSize();
frame = reinterpret_cast<struct mad_frame *>(p); frame = reinterpret_cast<struct mad_frame *>(p);
p += (sizeof(struct mad_frame)+7) & ~7; p += preAllocFrameSize();
synth = reinterpret_cast<struct mad_synth *>(p); synth = reinterpret_cast<struct mad_synth *>(p);
p += (sizeof(struct mad_synth)+7) & ~7; p += preAllocSynthSize();
int neededBytes = p - reinterpret_cast<uint8_t *>(preallocateSpace); int neededBytes = p - reinterpret_cast<uint8_t *>(preallocateSpace);
if (neededBytes > preallocateSize) { if (neededBytes > preallocateSize) {
audioLogger->printf_P("OOM error in MP3: Want %d bytes, have %d bytes preallocated.\n", 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; stream = NULL;
frame = NULL; frame = NULL;
synth = 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; return false;
} }
} }
mad_stream_init(stream); mad_stream_init(stream);
mad_frame_init(frame); mad_frame_init(frame);
mad_synth_init(synth); mad_synth_init(synth);
synth->pcm.length = 0; synth->pcm.length = 0;
mad_stream_options(stream, 0); // TODO - add options support mad_stream_options(stream, 0); // TODO - add options support
madInitted = true; madInitted = true;
running = true; running = true;
return true; return true;
} }
@ -304,7 +362,7 @@ extern "C" {
{ {
return 8192; return 8192;
} }
#elif defined(ESP8266) #elif defined(ESP8266) && !defined(CORE_MOCK)
#include <cont.h> #include <cont.h>
extern cont_t g_cont; extern cont_t g_cont;
@ -351,3 +409,4 @@ extern "C" {
} }
#endif #endif
} }

View File

@ -30,17 +30,31 @@ class AudioGeneratorMP3 : public AudioGenerator
public: public:
AudioGeneratorMP3(); AudioGeneratorMP3();
AudioGeneratorMP3(void *preallocateSpace, int preallocateSize); 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 ~AudioGeneratorMP3() override;
virtual bool begin(AudioFileSource *source, AudioOutput *output) override; virtual bool begin(AudioFileSource *source, AudioOutput *output) override;
virtual bool loop() override; virtual bool loop() override;
virtual bool stop() override; virtual bool stop() override;
virtual bool isRunning() override; virtual bool isRunning() override;
virtual void desync () override;
protected:
void *preallocateSpace;
int preallocateSize;
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; unsigned char *buff;
int lastReadPos; int lastReadPos;
int lastBuffLen; int lastBuffLen;
@ -62,6 +76,8 @@ class AudioGeneratorMP3 : public AudioGenerator
bool DecodeNextFrame(); bool DecodeNextFrame();
bool GetOneSample(int16_t sample[2]); bool GetOneSample(int16_t sample[2]);
private:
int unrecoverable = 0;
}; };
#endif #endif

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <Arduino.h>
#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();
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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

View File

@ -1,7 +1,7 @@
/* /*
AudioOutputI2S AudioOutputI2S
Base class for I2S interface port Base class for I2S interface port
Copyright (C) 2017 Earle F. Philhower, III Copyright (C) 2017 Earle F. Philhower, III
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
@ -21,11 +21,12 @@
#include <Arduino.h> #include <Arduino.h>
#ifdef ESP32 #ifdef ESP32
#include "driver/i2s.h" #include "driver/i2s.h"
#else #elif defined(ARDUINO_ARCH_RP2040) || defined(ESP8266)
#include <i2s.h> #include <I2S.h>
#endif #endif
#include "AudioOutputI2S.h" #include "AudioOutputI2S.h"
#if defined(ESP32) || defined(ESP8266)
AudioOutputI2S::AudioOutputI2S(int port, int output_mode, int dma_buf_count, int use_apll) AudioOutputI2S::AudioOutputI2S(int port, int output_mode, int dma_buf_count, int use_apll)
{ {
this->portNo = port; this->portNo = port;
@ -35,115 +36,93 @@ AudioOutputI2S::AudioOutputI2S(int port, int output_mode, int dma_buf_count, int
output_mode = EXTERNAL_I2S; output_mode = EXTERNAL_I2S;
} }
this->output_mode = output_mode; this->output_mode = output_mode;
#ifdef ESP32 this->use_apll = use_apll;
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); //set defaults
#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;
mono = false; mono = false;
bps = 16; bps = 16;
channels = 2; channels = 2;
hertz = 44100;
bclkPin = 26;
wclkPin = 25;
doutPin = 22;
SetGain(1.0); SetGain(1.0);
SetRate(44100); // Default
} }
AudioOutputI2S::~AudioOutputI2S() bool AudioOutputI2S::SetPinout()
{ {
#ifdef ESP32 #ifdef ESP32
if (i2sOn) { if (output_mode == INTERNAL_DAC || output_mode == INTERNAL_PDM)
audioLogger->printf("UNINSTALL I2S\n"); return false; // Not allowed
i2s_driver_uninstall((i2s_port_t)portNo); //stop & destroy i2s driver
} i2s_pin_config_t pins = {
#else .bck_io_num = bclkPin,
if (i2sOn) i2s_end(); .ws_io_num = wclkPin,
#endif .data_out_num = doutPin,
i2sOn = false; .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) bool AudioOutputI2S::SetPinout(int bclk, int wclk, int dout)
{ {
#ifdef ESP32 bclkPin = bclk;
if (output_mode == INTERNAL_DAC || output_mode == INTERNAL_PDM) return false; // Not allowed 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; return true;
#else }
(void) bclk; #elif defined(ARDUINO_ARCH_RP2040)
(void) wclk; AudioOutputI2S::AudioOutputI2S(long sampleRate, pin_size_t sck, pin_size_t data) {
(void) dout; i2sOn = false;
return false; mono = false;
bps = 16;
channels = 2;
hertz = sampleRate;
bclkPin = sck;
doutPin = data;
SetGain(1.0);
}
#endif #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) bool AudioOutputI2S::SetRate(int hz)
{ {
// TODO - have a list of allowable rates from constructor, check them // TODO - have a list of allowable rates from constructor, check them
this->hertz = hz; this->hertz = hz;
#ifdef ESP32 if (i2sOn)
i2s_set_sample_rates((i2s_port_t)portNo, AdjustI2SRate(hz)); {
#else #ifdef ESP32
i2s_set_rate(AdjustI2SRate(hz)); i2s_set_sample_rates((i2s_port_t)portNo, AdjustI2SRate(hz));
#endif #elif defined(ESP8266)
i2s_set_rate(AdjustI2SRate(hz));
#elif defined(ARDUINO_ARCH_RP2040)
I2S.setFrequency(hz);
#endif
}
return true; return true;
} }
@ -167,13 +146,106 @@ bool AudioOutputI2S::SetOutputModeMono(bool mono)
return true; 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; return true;
} }
bool AudioOutputI2S::ConsumeSample(int16_t sample[2]) bool AudioOutputI2S::ConsumeSample(int16_t sample[2])
{ {
//return if we haven't called ::begin yet
if (!i2sOn)
return false;
int16_t ms[2]; int16_t ms[2];
ms[0] = sample[0]; ms[0] = sample[0];
@ -185,43 +257,55 @@ bool AudioOutputI2S::ConsumeSample(int16_t sample[2])
int32_t ttl = ms[LEFTCHANNEL] + ms[RIGHTCHANNEL]; int32_t ttl = ms[LEFTCHANNEL] + ms[RIGHTCHANNEL];
ms[LEFTCHANNEL] = ms[RIGHTCHANNEL] = (ttl>>1) & 0xffff; ms[LEFTCHANNEL] = ms[RIGHTCHANNEL] = (ttl>>1) & 0xffff;
} }
#ifdef ESP32 #ifdef ESP32
uint32_t s32; uint32_t s32;
if (output_mode == INTERNAL_DAC) { if (output_mode == INTERNAL_DAC)
int16_t l = Amplify(ms[LEFTCHANNEL]) + 0x8000; {
int16_t r = Amplify(ms[RIGHTCHANNEL]) + 0x8000; int16_t l = Amplify(ms[LEFTCHANNEL]) + 0x8000;
s32 = (r<<16) | (l&0xffff); int16_t r = Amplify(ms[RIGHTCHANNEL]) + 0x8000;
} else { s32 = (r << 16) | (l & 0xffff);
s32 = ((Amplify(ms[RIGHTCHANNEL]))<<16) | (Amplify(ms[LEFTCHANNEL]) & 0xffff); }
} else
// Deprecated. Use i2s_write {
// return i2s_write_bytes((i2s_port_t)portNo, (const char*)&s32, sizeof(uint32_t), 0); s32 = ((Amplify(ms[RIGHTCHANNEL])) << 16) | (Amplify(ms[LEFTCHANNEL]) & 0xffff);
size_t bytes_written; }
i2s_write((i2s_port_t)portNo, (const char*)&s32, sizeof(uint32_t), &bytes_written, 0); return i2s_write_bytes((i2s_port_t)portNo, (const char *)&s32, sizeof(uint32_t), 0);
return bytes_written; #elif defined(ESP8266)
#else uint32_t s32 = ((Amplify(ms[RIGHTCHANNEL])) << 16) | (Amplify(ms[LEFTCHANNEL]) & 0xffff);
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
return i2s_write_sample_nb(s32); // If we can't store it, return false. OTW true #elif defined(ARDUINO_ARCH_RP2040)
#endif return !!I2S.write((void*)ms, 4);
#endif
} }
void AudioOutputI2S::flush() { void AudioOutputI2S::flush()
#ifdef ESP32 {
// makes sure that all stored DMA samples are consumed / played #ifdef ESP32
int buffersize = 64 * this->dma_buf_count; // makes sure that all stored DMA samples are consumed / played
int16_t samples[2] = {0x0,0x0}; int buffersize = 64 * this->dma_buf_count;
for (int i=0;i<buffersize; i++) { int16_t samples[2] = {0x0, 0x0};
while (!ConsumeSample(samples)) { for (int i = 0; i < buffersize; i++)
delay(10); {
while (!ConsumeSample(samples))
{
delay(10);
}
} }
} #elif defined(ARDUINO_ARCH_RP2040)
#endif I2S.flush();
#endif
} }
bool AudioOutputI2S::stop() bool AudioOutputI2S::stop()
{ {
#ifdef ESP32 if (!i2sOn)
i2s_zero_dma_buffer((i2s_port_t)portNo); return false;
#endif
#ifdef ESP32
i2s_zero_dma_buffer((i2s_port_t)portNo);
#elif defined(ARDUINO_ARCH_RP2040)
I2S.end();
#endif
i2sOn = false;
return true; return true;
} }

View File

@ -18,41 +18,47 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef _AUDIOOUTPUTI2S_H #pragma once
#define _AUDIOOUTPUTI2S_H
#include "AudioOutput.h" #include "AudioOutput.h"
class AudioOutputI2S : public AudioOutput class AudioOutputI2S : public AudioOutput
{ {
public: 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); 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); 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 SetRate(int hz) override;
virtual bool SetBitsPerSample(int bits) override; virtual bool SetBitsPerSample(int bits) override;
virtual bool SetChannels(int channels) 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 bool ConsumeSample(int16_t sample[2]) override;
virtual void flush() override; virtual void flush() override;
virtual bool stop() override; virtual bool stop() override;
bool begin(bool txDAC);
bool SetOutputModeMono(bool mono); // Force mono output no matter the input 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: protected:
bool SetPinout();
virtual int AdjustI2SRate(int hz) { return hz; } virtual int AdjustI2SRate(int hz) { return hz; }
uint8_t portNo; uint8_t portNo;
int output_mode; int output_mode;
bool mono; bool mono;
bool i2sOn; bool i2sOn;
int dma_buf_count; int dma_buf_count;
int use_apll;
// We can restore the old values and free up these pins when in NoDAC mode // We can restore the old values and free up these pins when in NoDAC mode
uint32_t orig_bck; uint32_t orig_bck;
uint32_t orig_ws; uint32_t orig_ws;
uint8_t bclkPin;
uint8_t wclkPin;
uint8_t doutPin;
}; };
#endif

View File

@ -1,7 +1,7 @@
/* /*
AudioOutputI2SNoDAC AudioOutputI2SNoDAC
Audio player using SW delta-sigma to generate "analog" on I2S data Audio player using SW delta-sigma to generate "analog" on I2S data
Copyright (C) 2017 Earle F. Philhower, III Copyright (C) 2017 Earle F. Philhower, III
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
@ -21,8 +21,8 @@
#include <Arduino.h> #include <Arduino.h>
#ifdef ESP32 #ifdef ESP32
#include "driver/i2s.h" #include "driver/i2s.h"
#else #elif defined(ARDUINO_ARCH_RP2040) || defined(ESP8266)
#include <i2s.h> #include <I2S.h>
#endif #endif
#include "AudioOutputI2SNoDAC.h" #include "AudioOutputI2SNoDAC.h"
@ -32,7 +32,7 @@ AudioOutputI2SNoDAC::AudioOutputI2SNoDAC(int port) : AudioOutputI2S(port, false)
SetOversampling(32); SetOversampling(32);
lastSamp = 0; lastSamp = 0;
cumErr = 0; cumErr = 0;
#ifndef ESP32 #ifdef ESP8266
WRITE_PERI_REG(PERIPHS_IO_MUX_MTDO_U, orig_bck); WRITE_PERI_REG(PERIPHS_IO_MUX_MTDO_U, orig_bck);
WRITE_PERI_REG(PERIPHS_IO_MUX_GPIO2_U, orig_ws); WRITE_PERI_REG(PERIPHS_IO_MUX_GPIO2_U, orig_ws);
#endif #endif
@ -66,7 +66,7 @@ void AudioOutputI2SNoDAC::DeltaSigma(int16_t sample[2], uint32_t dsBuff[8])
for (int j = 0; j < oversample32; j++) { 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 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--) { for (int i = 32; i > 0; i--) {
bits = bits << 1; bits = bits << 1;
if (cumErr < 0) { if (cumErr < 0) {
@ -95,19 +95,19 @@ bool AudioOutputI2SNoDAC::ConsumeSample(int16_t sample[2])
// Either send complete pulse stream or nothing // Either send complete pulse stream or nothing
#ifdef ESP32 #ifdef ESP32
if (!i2s_write_bytes((i2s_port_t)portNo, (const char *)dsBuff, sizeof(uint32_t) * (oversample/32), 0))
// 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)
return false; return false;
#else #elif defined(ESP8266)
if (!i2s_write_sample_nb(dsBuff[0])) return false; // No room at the inn 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 // At this point we've sent in first of possibly 8 32-bits, need to send
// remaining ones even if they block. // remaining ones even if they block.
for (int i = 32; i < oversample; i+=32) for (int i = 32; i < oversample; i+=32)
i2s_write_sample( dsBuff[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 #endif
return true; return true;
} }

View File

@ -18,8 +18,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef _AUDIOOUTPUTI2SNODAC_H #pragma once
#define _AUDIOOUTPUTI2SNODAC_H
#include "AudioOutputI2S.h" #include "AudioOutputI2S.h"
@ -28,6 +27,7 @@ class AudioOutputI2SNoDAC : public AudioOutputI2S
public: public:
AudioOutputI2SNoDAC(int port = 0); AudioOutputI2SNoDAC(int port = 0);
virtual ~AudioOutputI2SNoDAC() override; virtual ~AudioOutputI2SNoDAC() override;
virtual bool begin() override { return AudioOutputI2S::begin(false); }
virtual bool ConsumeSample(int16_t sample[2]) override; virtual bool ConsumeSample(int16_t sample[2]) override;
bool SetOversampling(int os); bool SetOversampling(int os);
@ -41,6 +41,3 @@ class AudioOutputI2SNoDAC : public AudioOutputI2S
fixed24p8_t lastSamp; // Last sample value fixed24p8_t lastSamp; // Last sample value
fixed24p8_t cumErr; // Running cumulative error since time began fixed24p8_t cumErr; // Running cumulative error since time began
}; };
#endif

View File

@ -37,6 +37,7 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#if defined(ESP32) || defined(ESP8266)
#include <Arduino.h> #include <Arduino.h>
#if defined(ESP32) #if defined(ESP32)
@ -286,3 +287,5 @@ bool AudioOutputSPDIF::stop()
frame_num = 0; frame_num = 0;
return true; return true;
} }
#endif

View File

@ -30,8 +30,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef _AUDIOOUTPUTSPDIF_H #if defined(ESP32) || defined(ESP8266)
#define _AUDIOOUTPUTSPDIF_H #pragma once
#include "AudioOutput.h" #include "AudioOutput.h"

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifdef ESP32
#include "AudioOutputULP.h"
#include <esp32/ulp.h>
#include <driver/rtc_io.h>
#include <driver/dac.h>
#include <soc/rtc.h>
#include <math.h>
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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
// 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

View File

@ -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"

View File

@ -121,6 +121,8 @@ struct FLAC__BitReader {
void *client_data; void *client_data;
}; };
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstrict-aliasing"
static inline void crc16_update_word_(FLAC__BitReader *br, brword word) static inline void crc16_update_word_(FLAC__BitReader *br, brword word)
{ {
unsigned crc = br->read_crc16; unsigned crc = br->read_crc16;
@ -149,6 +151,7 @@ static inline void crc16_update_word_(FLAC__BitReader *br, brword word)
#endif #endif
br->crc16_align = 0; br->crc16_align = 0;
} }
#pragma GCC diagnostic pop
static FLAC__bool bitreader_read_from_client_(FLAC__BitReader *br) 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; 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__uint16 FLAC__bitreader_get_read_crc16(FLAC__BitReader *br)
{ {
FLAC__ASSERT(0 != br); FLAC__ASSERT(0 != br);
@ -353,6 +358,7 @@ FLAC__uint16 FLAC__bitreader_get_read_crc16(FLAC__BitReader *br)
} }
return br->read_crc16; return br->read_crc16;
} }
#pragma GCC diagnostic pop
inline FLAC__bool FLAC__bitreader_is_consumed_byte_aligned(const FLAC__BitReader *br) inline FLAC__bool FLAC__bitreader_is_consumed_byte_aligned(const FLAC__BitReader *br)
{ {

View File

@ -2,9 +2,7 @@
#ifdef DEBUG #ifdef DEBUG
#undef NDEBUG #undef NDEBUG
#endif #else
#ifndef NDEBUG
#define NDEBUG #define NDEBUG
#endif #endif

View File

@ -31,7 +31,7 @@
*/ */
//#ifdef HAVE_CONFIG_H //#ifdef HAVE_CONFIG_H
# include <config.h> # include "config.h"
//#endif //#endif
#include "private/cpu.h" #include "private/cpu.h"

View File

@ -136,7 +136,8 @@ FLAC__uint8 FLAC__crc8(const FLAC__byte *data, unsigned len)
return crc; return crc;
} }
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstrict-aliasing"
unsigned FLAC__crc16(const FLAC__byte *data, unsigned len) unsigned FLAC__crc16(const FLAC__byte *data, unsigned len)
{ {
unsigned crc = 0; unsigned crc = 0;
@ -146,3 +147,4 @@ unsigned FLAC__crc16(const FLAC__byte *data, unsigned len)
return crc; return crc;
} }
#pragma GCC diagnostic pop

View File

@ -2021,6 +2021,8 @@ FLAC__bool frame_sync_(FLAC__StreamDecoder *decoder)
return true; 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) FLAC__bool read_frame_(FLAC__StreamDecoder *decoder, FLAC__bool *got_a_frame, FLAC__bool do_full_decode)
{ {
uint32_t channel; 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; decoder->protected_->state = FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC;
return true; return true;
} }
#pragma GCC diagnostic pop
FLAC__bool read_frame_header_(FLAC__StreamDecoder *decoder) FLAC__bool read_frame_header_(FLAC__StreamDecoder *decoder)
{ {

View File

@ -223,7 +223,7 @@ Word64 MADD64(Word64 sum64, int x, int y);
/* toolchain: ARM ADS or RealView /* toolchain: ARM ADS or RealView
* target architecture: ARM v.4 and above (requires 'M' type processor for 32x32->64 multiplier) * 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) 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 /* toolchain: ARM gcc
* target architecture: ARM v.4 and above (requires 'M' type processor for 32x32->64 multiplier) * 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) static inline int MULSHIFT32(int x, int y)
{ {

View File

@ -44,6 +44,7 @@
**************************************************************************************/ **************************************************************************************/
#if defined(USE_DEFAULT_STDLIB) || defined(ARDUINO) #if defined(USE_DEFAULT_STDLIB) || defined(ARDUINO)
#include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#else #else
#include "hlxclib/stdlib.h" #include "hlxclib/stdlib.h"

View File

@ -76,7 +76,7 @@ static const int newBWTab[4][4] PROGMEM = {
* Notes: this is carefully written to be efficient on ARM * Notes: this is carefully written to be efficient on ARM
* use the assembly code version in sbrcov.s when building for 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 #ifdef __cplusplus
extern "C" extern "C"
#endif #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 * Notes: this is carefully written to be efficient on ARM
* use the assembly code version in sbrcov.s when building for 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 #ifdef __cplusplus
extern "C" extern "C"
#endif #endif

View File

@ -217,7 +217,7 @@ static __inline int CLZ(int x)
return numZeros; return numZeros;
} }
#elif defined ARM_ADS #elif defined XXXARM_ADS
static __inline int MULSHIFT32(int x, int y) static __inline int MULSHIFT32(int x, int y)
{ {
@ -267,7 +267,7 @@ static __inline int CLZ(int x)
return numZeros; return numZeros;
} }
#elif defined(__GNUC__) && defined(__thumb__) #elif defined(__GNUC__) && defined(XXXX__thumb__)
static __inline int MULSHIFT32(int x, int y) static __inline int MULSHIFT32(int x, int y)

View File

@ -48,6 +48,7 @@
# endif # endif
# if !defined(HAVE_ASSERT_H) # if !defined(HAVE_ASSERT_H)
# undef assert
# if defined(NDEBUG) # if defined(NDEBUG)
# define assert(x) /* nothing */ # define assert(x) /* nothing */
# else # else

View File

@ -482,7 +482,7 @@ static int celt_plc_pitch_search(celt_sig *decode_mem[2], int C, int arch)
int pitch_index; int pitch_index;
VARDECL( opus_val16, lp_pitch_buf ); VARDECL( opus_val16, lp_pitch_buf );
SAVE_STACK; 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, pitch_downsample(decode_mem, lp_pitch_buf,
DECODE_BUFFER_SIZE, C, arch); DECODE_BUFFER_SIZE, C, arch);
pitch_search(lp_pitch_buf+(PLC_PITCH_LAG_MAX>>1), lp_pitch_buf, 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); PLC_PITCH_LAG_MAX-PLC_PITCH_LAG_MIN, &pitch_index, arch);
pitch_index = PLC_PITCH_LAG_MAX-pitch_index; pitch_index = PLC_PITCH_LAG_MAX-pitch_index;
RESTORE_STACK; RESTORE_STACK;
free(lp_pitch_buf);
return pitch_index; return pitch_index;
} }

View File

@ -206,3 +206,5 @@
# define _Restrict # define _Restrict
# define __restrict__ # define __restrict__
#endif #endif
#include <stdlib.h>

View File

@ -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) 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; opus_int32 ret;
if (len < 1) if (len < 1) {
free(rp);
return OPUS_BAD_ARG; return OPUS_BAD_ARG;
if (len==new_len) }
if (len==new_len) {
free(rp);
return OPUS_OK; return OPUS_OK;
else if (len > new_len) }
else if (len > new_len) {
free(rp);
return OPUS_BAD_ARG; 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 */ /* Moving payload to the end of the packet so we can do in-place padding */
OPUS_MOVE(data+new_len-len, data, len); OPUS_MOVE(data+new_len-len, data, len);
ret = opus_repacketizer_cat(&rp, data+new_len-len, len); ret = opus_repacketizer_cat(rp, data+new_len-len, len);
if (ret != OPUS_OK) if (ret != OPUS_OK) {
free(rp);
return ret; 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) if (ret > 0)
return OPUS_OK; return OPUS_OK;
else 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) opus_int32 opus_packet_unpad(unsigned char *data, opus_int32 len)
{ {
OpusRepacketizer rp; OpusRepacketizer *rp = (OpusRepacketizer*)malloc(sizeof(OpusRepacketizer));
opus_int32 ret; opus_int32 ret;
if (len < 1) if (len < 1) {
free(rp);
return OPUS_BAD_ARG; return OPUS_BAD_ARG;
opus_repacketizer_init(&rp); }
ret = opus_repacketizer_cat(&rp, data, len); opus_repacketizer_init(rp);
if (ret < 0) ret = opus_repacketizer_cat(rp, data, len);
if (ret < 0) {
free(rp);
return ret; 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); celt_assert(ret > 0 && ret <= len);
return ret; return ret;
} }
@ -312,12 +326,14 @@ opus_int32 opus_multistream_packet_unpad(unsigned char *data, opus_int32 len, in
unsigned char toc; unsigned char toc;
opus_int16 size[48]; opus_int16 size[48];
opus_int32 packet_offset; opus_int32 packet_offset;
OpusRepacketizer rp; OpusRepacketizer *rp = (OpusRepacketizer*)malloc(sizeof(OpusRepacketizer));
unsigned char *dst; unsigned char *dst;
opus_int32 dst_len; opus_int32 dst_len;
if (len < 1) if (len < 1){
free(rp);
return OPUS_BAD_ARG; return OPUS_BAD_ARG;
}
dst = data; dst = data;
dst_len = 0; dst_len = 0;
/* Unpad all frames */ /* Unpad all frames */
@ -325,25 +341,34 @@ opus_int32 opus_multistream_packet_unpad(unsigned char *data, opus_int32 len, in
{ {
opus_int32 ret; opus_int32 ret;
int self_delimited = s!=nb_streams-1; int self_delimited = s!=nb_streams-1;
if (len<=0) if (len<=0) {
free(rp);
return OPUS_INVALID_PACKET; return OPUS_INVALID_PACKET;
opus_repacketizer_init(&rp); }
opus_repacketizer_init(rp);
ret = opus_packet_parse_impl(data, len, self_delimited, &toc, NULL, ret = opus_packet_parse_impl(data, len, self_delimited, &toc, NULL,
size, NULL, &packet_offset); size, NULL, &packet_offset);
if (ret<0) if (ret<0) {
free(rp);
return ret; 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; 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; return ret;
}
else else
dst_len += ret; dst_len += ret;
dst += ret; dst += ret;
data += packet_offset; data += packet_offset;
len -= packet_offset; len -= packet_offset;
} }
free(rp);
return dst_len; return dst_len;
} }

View File

@ -80,10 +80,11 @@ void silk_NLSF2A(
}; };
const unsigned char *ordering; const unsigned char *ordering;
opus_int k, i, dd; opus_int k, i, dd;
opus_int32 cos_LSF_QA[ SILK_MAX_ORDER_LPC ]; opus_int32 *cos_LSF_QA = (opus_int32*)malloc(sizeof(opus_int32) * SILK_MAX_ORDER_LPC );
opus_int32 P[ SILK_MAX_ORDER_LPC / 2 + 1 ], Q[ SILK_MAX_ORDER_LPC / 2 + 1 ]; 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 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 ); silk_assert( LSF_COS_TAB_SZ_FIX == 128 );
celt_assert( d==10 || d==16 ); 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 */ 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);
} }

View File

@ -57,12 +57,12 @@ void silk_burg_modified_c(
opus_int k, n, s, lz, rshifts, reached_max_gain; 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; opus_int32 C0, num, nrg, rc_Q31, invGain_Q30, Atmp_QA, Atmp1, tmp1, tmp2, x1, x2;
const opus_int16 *x_ptr; const opus_int16 *x_ptr;
opus_int32 C_first_row[ SILK_MAX_ORDER_LPC ]; opus_int32 *C_first_row = (opus_int32*)malloc(sizeof(opus_int32) * SILK_MAX_ORDER_LPC);
opus_int32 C_last_row[ SILK_MAX_ORDER_LPC ]; opus_int32 *C_last_row = (opus_int32*)malloc(sizeof(opus_int32) * SILK_MAX_ORDER_LPC);
opus_int32 Af_QA[ SILK_MAX_ORDER_LPC ]; opus_int32 *Af_QA = (opus_int32*)malloc(sizeof(opus_int32) * SILK_MAX_ORDER_LPC);
opus_int32 CAf[ SILK_MAX_ORDER_LPC + 1 ]; opus_int32 *CAf = (opus_int32*)malloc(sizeof(opus_int32) * (SILK_MAX_ORDER_LPC+1));
opus_int32 CAb[ SILK_MAX_ORDER_LPC + 1 ]; opus_int32 *CAb = (opus_int32*)malloc(sizeof(opus_int32) * (SILK_MAX_ORDER_LPC+1));
opus_int32 xcorr[ SILK_MAX_ORDER_LPC ]; opus_int32 *xcorr = (opus_int32*)malloc(sizeof(opus_int32) * SILK_MAX_ORDER_LPC);
opus_int64 C0_64; opus_int64 C0_64;
celt_assert( subfr_length * nb_subfr <= MAX_FRAME_SIZE ); 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 = silk_SMLAWW( nrg, silk_SMMUL( SILK_FIX_CONST( FIND_LPC_COND_FAC, 32 ), C0 ), -tmp1 );/* Q( -rshifts ) */
*res_nrg_Q = -rshifts; *res_nrg_Q = -rshifts;
} }
free(C_first_row);
free(C_last_row);
free(Af_QA);
free(CAf);
free(CAb);
free(xcorr);
} }

View File

@ -49,8 +49,8 @@ void silk_warped_autocorrelation_FIX_c(
{ {
opus_int n, i, lsh; opus_int n, i, lsh;
opus_int32 tmp1_QS, tmp2_QS; opus_int32 tmp1_QS, tmp2_QS;
opus_int32 state_QS[ 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[ MAX_SHAPE_LPC_ORDER + 1 ] = { 0 }; opus_int64 *corr_QC = (opus_int64*)calloc(MAX_SHAPE_LPC_ORDER + 1, sizeof(opus_int64));
/* Order must be even */ /* Order must be even */
celt_assert( ( order & 1 ) == 0 ); 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*/ silk_assert( corr_QC[ 0 ] >= 0 ); /* If breaking, decrease QC*/
free(state_QS);
free(corr_QC);
} }
#endif /* OVERRIDE_silk_warped_autocorrelation_FIX_c */ #endif /* OVERRIDE_silk_warped_autocorrelation_FIX_c */

View File

@ -48,7 +48,8 @@ void silk_resampler_down2_3(
opus_int32 *buf_ptr; opus_int32 *buf_ptr;
SAVE_STACK; 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 */ /* Copy buffered samples to start of buffer */
silk_memcpy( buf, S, ORDER_FIR * sizeof( opus_int32 ) ); 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 */ /* Copy last part of filtered signal to the state for the next call */
silk_memcpy( S, &buf[ nSamplesIn ], ORDER_FIR * sizeof( opus_int32 ) ); silk_memcpy( S, &buf[ nSamplesIn ], ORDER_FIR * sizeof( opus_int32 ) );
free(buf);
RESTORE_STACK; RESTORE_STACK;
} }

View File

@ -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_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_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_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_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) 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 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 }; struct tsf_stream_memory* f = (struct tsf_stream_memory*)TSF_MALLOC(sizeof(struct tsf_stream_memory));
f.buffer = (const char*)buffer; f->pos = 0;
f.total = size; f->buffer = (const char*)buffer;
stream.data = &f; f->total = size;
stream.data = f;
return tsf_load(&stream); return tsf_load(&stream);
} }

View File

@ -90,12 +90,12 @@ int op_test(OpusHead *_head,
ogg_sync_init(&oy); ogg_sync_init(&oy);
data=ogg_sync_buffer(&oy,(long)_initial_bytes); data=ogg_sync_buffer(&oy,(long)_initial_bytes);
if(data!=NULL){ if(data!=NULL){
ogg_stream_state os; ogg_stream_state *os = (ogg_stream_state*)malloc(sizeof(ogg_stream_state));
ogg_page og; ogg_page og;
int ret; int ret;
memcpy(data,_initial_data,_initial_bytes); memcpy(data,_initial_data,_initial_bytes);
ogg_sync_wrote(&oy,(long)_initial_bytes); ogg_sync_wrote(&oy,(long)_initial_bytes);
ogg_stream_init(&os,-1); ogg_stream_init(os,-1);
err=OP_FALSE; err=OP_FALSE;
do{ do{
ogg_packet op; ogg_packet op;
@ -104,11 +104,11 @@ int op_test(OpusHead *_head,
if(ret<0)continue; if(ret<0)continue;
/*Stop if we run out of data.*/ /*Stop if we run out of data.*/
if(!ret)break; if(!ret)break;
ogg_stream_reset_serialno(&os,ogg_page_serialno(&og)); ogg_stream_reset_serialno(os,ogg_page_serialno(&og));
ogg_stream_pagein(&os,&og); ogg_stream_pagein(os,&og);
/*Only process the first packet on this page (if it's a BOS packet, /*Only process the first packet on this page (if it's a BOS packet,
it's required to be the only one).*/ 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){ if(op.b_o_s){
ret=opus_head_parse(_head,op.packet,op.bytes); ret=opus_head_parse(_head,op.packet,op.bytes);
/*If this didn't look like Opus, keep going.*/ /*If this didn't look like Opus, keep going.*/
@ -122,7 +122,8 @@ int op_test(OpusHead *_head,
} }
} }
while(err==OP_FALSE); while(err==OP_FALSE);
ogg_stream_clear(&os); ogg_stream_clear(os);
free(os);
} }
else err=OP_EFAULT; else err=OP_EFAULT;
ogg_sync_clear(&oy); ogg_sync_clear(&oy);
@ -835,7 +836,7 @@ static int op_find_initial_pcm_offset(OggOpusFile *_of,
ogg_int64_t cur_page_gp; ogg_int64_t cur_page_gp;
ogg_uint32_t serialno; ogg_uint32_t serialno;
opus_int32 total_duration; opus_int32 total_duration;
int durations[255]; int *durations = (int*)malloc(255 * sizeof(int));
int cur_page_eos; int cur_page_eos;
int op_count; int op_count;
int pi; 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.*/ Otherwise there are no audio data packets in the whole logical stream.*/
if(OP_UNLIKELY(page_offset<0)){ if(OP_UNLIKELY(page_offset<0)){
/*Fail if there was a read error.*/ /*Fail if there was a read error.*/
if(page_offset<OP_FALSE)return (int)page_offset; if(page_offset<OP_FALSE) { free(durations); return (int)page_offset; }
/*Fail if the pre-skip is non-zero, since it's asking us to skip more /*Fail if the pre-skip is non-zero, since it's asking us to skip more
samples than exist.*/ samples than exist.*/
if(_link->head.pre_skip>0)return OP_EBADTIMESTAMP; if(_link->head.pre_skip>0) {free(durations); return OP_EBADTIMESTAMP;}
_link->pcm_file_offset=0; _link->pcm_file_offset=0;
/*Set pcm_end and end_offset so we can skip the call to /*Set pcm_end and end_offset so we can skip the call to
op_find_final_pcm_offset().*/ op_find_final_pcm_offset().*/
_link->pcm_start=_link->pcm_end=0; _link->pcm_start=_link->pcm_end=0;
_link->end_offset=_link->data_offset; _link->end_offset=_link->data_offset;
free(durations);
return 0; return 0;
} }
/*Similarly, if we hit the next link in the chain, we've gone too far.*/ /*Similarly, if we hit the next link in the chain, we've gone too far.*/
if(OP_UNLIKELY(ogg_page_bos(_og))){ 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 /*Set pcm_end and end_offset so we can skip the call to
op_find_final_pcm_offset().*/ op_find_final_pcm_offset().*/
_link->pcm_file_offset=0; _link->pcm_file_offset=0;
_link->pcm_start=_link->pcm_end=0; _link->pcm_start=_link->pcm_end=0;
_link->end_offset=_link->data_offset; _link->end_offset=_link->data_offset;
/*Tell the caller we've got a buffered page for them.*/ /*Tell the caller we've got a buffered page for them.*/
free(durations);
return 1; return 1;
} }
/*Ignore pages from other streams (not strictly necessary, because of the /*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; cur_page_gp=_of->op[op_count-1].granulepos;
/*But getting a packet without a valid granule position on the page is not /*But getting a packet without a valid granule position on the page is not
okay.*/ 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; cur_page_eos=_of->op[op_count-1].e_o_s;
if(OP_LIKELY(!cur_page_eos)){ if(OP_LIKELY(!cur_page_eos)){
/*The EOS flag wasn't set. /*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)){ 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 /*The starting granule position MUST not be smaller than the amount of
audio on the first page with completed packets.*/ audio on the first page with completed packets.*/
free(durations);
return OP_EBADTIMESTAMP; 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 /*However, the end-trimming MUST not ask us to trim more samples than
exist after applying the pre-skip.*/ exist after applying the pre-skip.*/
if(OP_UNLIKELY(op_granpos_cmp(cur_page_gp,_link->head.pre_skip)<0)){ if(OP_UNLIKELY(op_granpos_cmp(cur_page_gp,_link->head.pre_skip)<0)){
free(durations);
return OP_EBADTIMESTAMP; return OP_EBADTIMESTAMP;
} }
} }
@ -957,6 +968,7 @@ static int op_find_initial_pcm_offset(OggOpusFile *_of,
_link->pcm_file_offset=0; _link->pcm_file_offset=0;
_of->prev_packet_gp=_link->pcm_start=pcm_start; _of->prev_packet_gp=_link->pcm_start=pcm_start;
_of->prev_page_offset=page_offset; _of->prev_page_offset=page_offset;
free(durations);
return 0; return 0;
} }
@ -1391,32 +1403,34 @@ static int op_open_seekable2_impl(OggOpusFile *_of){
/*64 seek records should be enough for anybody. /*64 seek records should be enough for anybody.
Actually, with a bisection search in a 63-bit range down to OP_CHUNK_SIZE Actually, with a bisection search in a 63-bit range down to OP_CHUNK_SIZE
granularity, much more than enough.*/ granularity, much more than enough.*/
OpusSeekRecord sr[64]; OpusSeekRecord *sr = (OpusSeekRecord*)malloc(64 * sizeof(OpusSeekRecord));
opus_int64 data_offset; opus_int64 data_offset;
int ret; int ret;
/*We can seek, so set out learning all about this file.*/ /*We can seek, so set out learning all about this file.*/
(*_of->callbacks.seek)(_of->stream,0,SEEK_END); (*_of->callbacks.seek)(_of->stream,0,SEEK_END);
_of->offset=_of->end=(*_of->callbacks.tell)(_of->stream); _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; data_offset=_of->links[0].data_offset;
if(OP_UNLIKELY(_of->end<data_offset))return OP_EBADLINK; if(OP_UNLIKELY(_of->end<data_offset)){ free(sr); return OP_EBADLINK;}
/*Get the offset of the last page of the physical bitstream, or, if we're /*Get the offset of the last page of the physical bitstream, or, if we're
lucky, the last Opus page of the first link, as most Ogg Opus files will lucky, the last Opus page of the first link, as most Ogg Opus files will
contain a single logical bitstream.*/ contain a single logical bitstream.*/
ret=op_get_prev_page_serial(_of,sr,_of->end, ret=op_get_prev_page_serial(_of,sr,_of->end,
_of->links[0].serialno,_of->serialnos,_of->nserialnos); _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.*/ /*If there's any trailing junk, forget about it.*/
_of->end=sr[0].offset+sr[0].size; _of->end=sr[0].offset+sr[0].size;
if(OP_UNLIKELY(_of->end<data_offset))return OP_EBADLINK; if(OP_UNLIKELY(_of->end<data_offset)){free(sr); return OP_EBADLINK;}
/*Now enumerate the bitstream structure.*/ /*Now enumerate the bitstream structure.*/
return op_bisect_forward_serialno(_of,data_offset,sr,sizeof(sr)/sizeof(*sr), ret = op_bisect_forward_serialno(_of,data_offset,sr,sizeof(sr)/sizeof(*sr),
&_of->serialnos,&_of->nserialnos,&_of->cserialnos); &_of->serialnos,&_of->nserialnos,&_of->cserialnos);
free(sr);
return ret;
} }
static int op_open_seekable2(OggOpusFile *_of){ static int op_open_seekable2(OggOpusFile *_of){
ogg_sync_state oy_start; 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; ogg_packet *op_start;
opus_int64 prev_page_offset; opus_int64 prev_page_offset;
opus_int64 start_offset; opus_int64 start_offset;
@ -1435,9 +1449,9 @@ static int op_open_seekable2(OggOpusFile *_of){
start_op_count=_of->op_count; start_op_count=_of->op_count;
/*This is a bit too large to put on the stack unconditionally.*/ /*This is a bit too large to put on the stack unconditionally.*/
op_start=(ogg_packet *)_ogg_malloc(sizeof(*op_start)*start_op_count); 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; *&oy_start=_of->oy;
*&os_start=_of->os; *os_start=_of->os;
prev_page_offset=_of->prev_page_offset; prev_page_offset=_of->prev_page_offset;
start_offset=_of->offset; start_offset=_of->offset;
memcpy(op_start,_of->op,sizeof(*op_start)*start_op_count); 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_stream_clear(&_of->os);
ogg_sync_clear(&_of->oy); ogg_sync_clear(&_of->oy);
*&_of->oy=*&oy_start; *&_of->oy=*&oy_start;
*&_of->os=*&os_start; *&_of->os=*os_start;
_of->offset=start_offset; _of->offset=start_offset;
_of->op_count=start_op_count; _of->op_count=start_op_count;
memcpy(_of->op,op_start,sizeof(*_of->op)*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_packet_gp=_of->links[0].pcm_start;
_of->prev_page_offset=prev_page_offset; _of->prev_page_offset=prev_page_offset;
_of->cur_discard_count=_of->links[0].head.pre_skip; _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.*/ /*And restore the position indicator.*/
ret=(*_of->callbacks.seek)(_of->stream,op_position(_of),SEEK_SET); ret=(*_of->callbacks.seek)(_of->stream,op_position(_of),SEEK_SET);
free(os_start);
return OP_UNLIKELY(ret<0)?OP_EREAD:0; 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); ogg_stream_pagein(&_of->os,&og);
if(OP_LIKELY(_of->ready_state>=OP_INITSET)){ if(OP_LIKELY(_of->ready_state>=OP_INITSET)){
opus_int32 total_duration; opus_int32 total_duration;
int durations[255]; int *durations = (int*)malloc(255 * sizeof(int));
int op_count; int op_count;
int report_hole; int report_hole;
report_hole=0; 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 Proceed to the next link, rather than risk playing back some
samples that shouldn't have been played.*/ samples that shouldn't have been played.*/
_of->op_count=0; _of->op_count=0;
if(report_hole)return OP_HOLE; if(report_hole){ free(durations); return OP_HOLE; }
continue; continue;
} }
/*By default discard 80 ms of data after a seek, unless we seek /*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->prev_page_offset=_page_offset;
_of->op_count=op_count=pi; _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 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; }
} }
} }
} }

0
lib/lib_audio/ESP8266Audio/tests/common.sh Normal file → Executable file
View File

View File

@ -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 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 libflac=../../src/libflac/md5.c ../../src/libflac/window.c ../../src/libflac/memory.c ../../src/libflac/cpu.c \
CPPOPTS=-g -Wunused-parameter -Wall -std=c++11 -m32 -include Arduino.h ../../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 .phony: all
all: mp3 aac wav midi opus all: mp3 aac wav midi opus flac
mp3: FORCE mp3: FORCE
rm -f *.o rm -f *.o
@ -94,6 +99,13 @@ aac: FORCE
rm -f *.o rm -f *.o
echo valgrind --leak-check=full --track-origins=yes -v --error-limit=no --show-leak-kinds=all ./aac 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 wav: FORCE
rm -f *.o 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. 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 echo valgrind --leak-check=full --track-origins=yes -v --error-limit=no --show-leak-kinds=all ./opus
clean: clean:
rm -f mp3 aac wav midi opus *.o rm -f mp3 aac wav midi opus flac *.o
FORCE: FORCE:

View File

@ -0,0 +1,24 @@
#include <Arduino.h>
#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;
}

View File

@ -10,6 +10,7 @@
extern int i2s_output_i2s_init(bvm *vm); extern int i2s_output_i2s_init(bvm *vm);
extern int i2s_output_i2s_deinit(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_init(bvm *vm);
extern int i2s_generator_wav_deinit(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) { class be_class_audio_output_i2s (scope: global, name: AudioOutputI2S, super: be_class_audio_output) {
init, func(i2s_output_i2s_init) init, func(i2s_output_i2s_init)
deinit, func(i2s_output_i2s_deinit) 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) { class be_class_audio_generator_wav (scope: global, name: AudioGeneratorWAV, super: be_class_audio_generator) {
init, func(i2s_generator_wav_init) init, func(i2s_generator_wav_init)
deinit, func(i2s_generator_wav_deinit) deinit, func(i2s_generator_wav_deinit)
close, func(i2s_generator_wav_deinit)
begin, func(i2s_generator_wav_begin) begin, func(i2s_generator_wav_begin)
loop, func(i2s_generator_wav_loop) loop, func(i2s_generator_wav_loop)
stop, func(i2s_generator_wav_stop) 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) { class be_class_audio_generator_mp3 (scope: global, name: AudioGeneratorMP3, super: be_class_audio_generator) {
init, func(i2s_generator_mp3_init) init, func(i2s_generator_mp3_init)
deinit, func(i2s_generator_mp3_deinit) deinit, func(i2s_generator_mp3_deinit)
close, func(i2s_generator_mp3_deinit)
begin, func(i2s_generator_mp3_begin) begin, func(i2s_generator_mp3_begin)
loop, func(i2s_generator_mp3_loop) loop, func(i2s_generator_mp3_loop)
stop, func(i2s_generator_mp3_stop) 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) { class be_class_audio_file_source_fs (scope: global, name: AudioFileSourceFS, super: be_class_audio_file_source) {
init, func(i2s_file_source_fs_init) init, func(i2s_file_source_fs_init)
deinit, func(i2s_file_source_fs_deinit) deinit, func(i2s_file_source_fs_deinit)
close, func(i2s_file_source_fs_deinit)
} }
@const_object_info_end */ @const_object_info_end */

View File

@ -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_read_bytes;
extern const bcstring be_const_str_dot_p; extern const bcstring be_const_str_dot_p;
extern const bcstring be_const_str_bus; 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_lv_group_focus_cb;
extern const bcstring be_const_str_SDS0X1_TX; extern const bcstring be_const_str_SDS0X1_TX;
extern const bcstring be_const_str_SDM72_TX; extern const bcstring be_const_str_SDM72_TX;

View File

@ -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(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(read_bytes, "read_bytes", 3576733173u, 0, 10, NULL);
be_define_const_str(dot_p, ".p", 1171526419u, 0, 2, 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(bus, "bus", 1607822841u, 0, 3, &be_const_str_lv_group_focus_cb);
be_define_const_str(close, "close", 667630371u, 0, 5, &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(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(SDS0X1_TX, "SDS0X1_TX", 165045983u, 0, 9, NULL);
be_define_const_str(SDM72_TX, "SDM72_TX", 2042143269u, 0, 8, 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 = { static const struct bconststrtab m_const_string_table = {
.size = 315, .size = 315,
.count = 631, .count = 630,
.table = m_string_table .table = m_string_table
}; };

View File

@ -1,14 +1,13 @@
#include "be_constobj.h" #include "be_constobj.h"
static be_define_const_map_slots(be_class_audio_file_source_fs_map) { 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(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( static be_define_const_map(
be_class_audio_file_source_fs_map, be_class_audio_file_source_fs_map,
3 2
); );
BE_EXPORT_VARIABLE be_define_const_class( BE_EXPORT_VARIABLE be_define_const_class(

View File

@ -1,18 +1,17 @@
#include "be_constobj.h" #include "be_constobj.h"
static be_define_const_map_slots(be_class_audio_generator_mp3_map) { 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(begin, -1), be_const_func(i2s_generator_mp3_begin) },
{ be_const_key(deinit, 6), be_const_func(i2s_generator_mp3_deinit) }, { be_const_key(loop, -1), be_const_func(i2s_generator_mp3_loop) },
{ be_const_key(init, -1), be_const_func(i2s_generator_mp3_init) }, { 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( static be_define_const_map(
be_class_audio_generator_mp3_map, be_class_audio_generator_mp3_map,
7 6
); );
BE_EXPORT_VARIABLE be_define_const_class( BE_EXPORT_VARIABLE be_define_const_class(

View File

@ -1,18 +1,17 @@
#include "be_constobj.h" #include "be_constobj.h"
static be_define_const_map_slots(be_class_audio_generator_wav_map) { 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(begin, -1), be_const_func(i2s_generator_wav_begin) },
{ be_const_key(deinit, 6), be_const_func(i2s_generator_wav_deinit) }, { be_const_key(loop, -1), be_const_func(i2s_generator_wav_loop) },
{ be_const_key(init, -1), be_const_func(i2s_generator_wav_init) }, { 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( static be_define_const_map(
be_class_audio_generator_wav_map, be_class_audio_generator_wav_map,
7 6
); );
BE_EXPORT_VARIABLE be_define_const_class( BE_EXPORT_VARIABLE be_define_const_class(

View File

@ -1,9 +1,9 @@
#include "be_constobj.h" #include "be_constobj.h"
static be_define_const_map_slots(be_class_audio_output_i2s_map) { 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(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( static be_define_const_map(

View File

@ -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(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); 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_pushcomptr(vm, (void*) audio);
be_setmember(vm, 1, ".p"); be_setmember(vm, 1, ".p");
be_return_nil(vm); be_return_nil(vm);
@ -84,6 +86,16 @@ extern "C" {
be_return_nil(vm); 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() // AudioGeneratorWAV()
// //