Merge pull request #12795 from s-hadinger/update_audio

ESP8266Audio library from v1.5.0 to v1.9.2 and support for ESP32 internal DAC
This commit is contained in:
s-hadinger 2021-07-30 18:09:34 +02:00 committed by GitHub
commit 7c775bb352
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
70 changed files with 2050 additions and 335 deletions

View File

@ -7,6 +7,9 @@ All notable changes to this project will be documented in this file.
### Added
- Support for second DNS server
### Changed
- ESP8266Audio library from v1.5.0 to v1.9.2
## [9.5.0.3] 20210729
### Added
- Command ``SetSensor1..127 0|1`` to globally disable individual sensor driver

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

View File

@ -22,7 +22,7 @@ const char* ssid = STASSID;
const char* password = STAPSK;
// Randomly picked URL
const char *URL="http://streaming.shoutcast.com/80sPlanet?lang=en-US";
const char *URL="http://kvbstreams.dyndns.org:8000/wkvi-am";
AudioGeneratorMP3 *mp3;
AudioFileSourceICYStream *file;

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
AudioOutputI2S KEYWORD1
AudioOutputI2SNoDAC KEYWORD1
AudioOutputI2SClass KEYWORD1
AudioOutputNull KEYWORD1
AudioOutputBuffer KEYWORD1
AudioOutputSerialWAV KEYWORD1

View File

@ -1,6 +1,6 @@
{
"name": "ESP8266Audio",
"description": "Audio file format and I2S DAC library",
"description": "Audio file format and I2S DAC library for ESP8266, ESP32, and Raspberry Pi Pico RP2040",
"keywords": "ESP8266, ESP32, MP3, AAC, WAV, MOD, FLAC, RTTTL, MIDI, I2S, DAC, Delta-Sigma, TTS",
"authors": [
{
@ -14,13 +14,9 @@
"type": "git",
"url": "https://github.com/earlephilhower/ESP8266Audio"
},
"version": "1.5.0",
"version": "1.9.2",
"homepage": "https://github.com/earlephilhower/ESP8266Audio",
"dependencies": {
"SPI": "1.0"
},
"frameworks": "Arduino",
"platforms": ["espressif8266", "espressif32"],
"examples": [
"examples/*/*.ino"
]

View File

@ -1,9 +1,9 @@
name=ESP8266Audio
version=1.5.0
version=1.9.2
author=Earle F. Philhower, III
maintainer=Earle F. Philhower, III
sentence=Audio file and I2S sound playing routines.
sentence=Audio file and I2S sound playing routines for ESP8266, ESP32, and Raspberry Pi Pico RP2040
paragraph=Decode compressed MP3, AAC, FLAC, Screamtracker MOD, MIDI, RTTL, TI Talkie, and WAV and play on an I2S DAC or a software-driven delta-sigma DAC and 1-transistor amplifier.
category=Signal Input/Output
url=https://github.com/earlephilhower/ESP8266Audio
architectures=esp8266,esp32
architectures=esp8266,esp32,rp2040

View File

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

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

View File

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

View File

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

View File

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

View File

@ -143,6 +143,7 @@ uint32_t AudioFileSourceID3::read(void *data, uint32_t len)
if (ret<10) return ret;
if ((buff[0]!='I') || (buff[1]!='D') || (buff[2]!='3') || (buff[3]>0x04) || (buff[3]<0x02) || (buff[4]!=0)) {
cb.md("eof", false, "id3");
return 10 + src->read(buff+10, len-10);
}
@ -212,9 +213,9 @@ uint32_t AudioFileSourceID3::read(void *data, uint32_t len)
// Read the value and send to callback
char value[64];
uint16_t i;
uint32_t i;
bool isUnicode = (id3.getByte()==1) ? true : false;
for (i=0; i<framesize-1; i++) {
for (i=0; i<(uint32_t)framesize-1; i++) {
if (i<sizeof(value)-1) value[i] = 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') ||
(frameid[0]=='T' && frameid[1]=='Y' && frameid[2]=='E' && rev==2) ) {
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());
// 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
return src->read(data, len);
}

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

@ -30,17 +30,31 @@ class AudioGeneratorMP3 : public AudioGenerator
public:
AudioGeneratorMP3();
AudioGeneratorMP3(void *preallocateSpace, int preallocateSize);
AudioGeneratorMP3(void *buff, int buffSize, void *stream, int streamSize, void *frame, int frameSize, void *synth, int synthSize);
virtual ~AudioGeneratorMP3() override;
virtual bool begin(AudioFileSource *source, AudioOutput *output) override;
virtual bool loop() override;
virtual bool stop() override;
virtual bool isRunning() override;
protected:
void *preallocateSpace;
int preallocateSize;
virtual void desync () override;
const int buffLen = 0x600; // Slightly larger than largest MP3 frame
static constexpr int preAllocSize () { return preAllocBuffSize() + preAllocStreamSize() + preAllocFrameSize() + preAllocSynthSize(); }
static constexpr int preAllocBuffSize () { return ((buffLen + 7) & ~7); }
static constexpr int preAllocStreamSize () { return ((sizeof(struct mad_stream) + 7) & ~7); }
static constexpr int preAllocFrameSize () { return (sizeof(struct mad_frame) + 7) & ~7; }
static constexpr int preAllocSynthSize () { return (sizeof(struct mad_synth) + 7) & ~7; }
protected:
void *preallocateSpace = nullptr;
int preallocateSize = 0;
void *preallocateStreamSpace = nullptr;
int preallocateStreamSize = 0;
void *preallocateFrameSpace = nullptr;
int preallocateFrameSize = 0;
void *preallocateSynthSpace = nullptr;
int preallocateSynthSize = 0;
static constexpr int buffLen = 0x600; // Slightly larger than largest MP3 frame
unsigned char *buff;
int lastReadPos;
int lastBuffLen;
@ -62,6 +76,8 @@ class AudioGeneratorMP3 : public AudioGenerator
bool DecodeNextFrame();
bool GetOneSample(int16_t sample[2]);
private:
int unrecoverable = 0;
};
#endif

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
Base class for I2S interface port
Copyright (C) 2017 Earle F. Philhower, III
This program is free software: you can redistribute it and/or modify
@ -21,11 +21,12 @@
#include <Arduino.h>
#ifdef ESP32
#include "driver/i2s.h"
#else
#elif defined(ARDUINO_ARCH_RP2040) || defined(ESP8266)
#include <i2s.h>
#endif
#include "AudioOutputI2S.h"
#if defined(ESP32) || defined(ESP8266)
AudioOutputI2S::AudioOutputI2S(int port, int output_mode, int dma_buf_count, int use_apll)
{
this->portNo = port;
@ -35,115 +36,93 @@ AudioOutputI2S::AudioOutputI2S(int port, int output_mode, int dma_buf_count, int
output_mode = EXTERNAL_I2S;
}
this->output_mode = output_mode;
#ifdef ESP32
if (!i2sOn) {
if (use_apll == APLL_AUTO) {
// don't use audio pll on buggy rev0 chips
use_apll = APLL_DISABLE;
esp_chip_info_t out_info;
esp_chip_info(&out_info);
if(out_info.revision > 0) {
use_apll = APLL_ENABLE;
}
}
this->use_apll = use_apll;
i2s_mode_t mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX);
#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4
if (output_mode == INTERNAL_DAC) {
mode = (i2s_mode_t)(mode | I2S_MODE_DAC_BUILT_IN);
} else if (output_mode == INTERNAL_PDM) {
mode = (i2s_mode_t)(mode | I2S_MODE_PDM);
}
#endif
i2s_comm_format_t comm_fmt = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB);
if (output_mode == INTERNAL_DAC) {
comm_fmt = (i2s_comm_format_t)I2S_COMM_FORMAT_I2S_MSB;
}
i2s_config_t i2s_config_dac = {
.mode = mode,
.sample_rate = 44100,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
.communication_format = comm_fmt,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // lowest interrupt priority
.dma_buf_count = dma_buf_count,
.dma_buf_len = 64,
.use_apll = use_apll // Use audio PLL
};
audioLogger->printf("+%d %p\n", portNo, &i2s_config_dac);
if (i2s_driver_install((i2s_port_t)portNo, &i2s_config_dac, 0, NULL) != ESP_OK) {
audioLogger->println("ERROR: Unable to install I2S drives\n");
}
if (output_mode == INTERNAL_DAC || output_mode == INTERNAL_PDM) {
i2s_set_pin((i2s_port_t)portNo, NULL);
i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN);
} else {
SetPinout(26, 25, 22);
}
i2s_zero_dma_buffer((i2s_port_t)portNo);
}
#else
(void) dma_buf_count;
(void) use_apll;
if (!i2sOn) {
orig_bck = READ_PERI_REG(PERIPHS_IO_MUX_MTDO_U);
orig_ws = READ_PERI_REG(PERIPHS_IO_MUX_GPIO2_U);
i2s_begin();
}
#endif
i2sOn = true;
//set defaults
mono = false;
bps = 16;
channels = 2;
hertz = 44100;
bclkPin = 26;
wclkPin = 25;
doutPin = 22;
SetGain(1.0);
SetRate(44100); // Default
}
AudioOutputI2S::~AudioOutputI2S()
bool AudioOutputI2S::SetPinout()
{
#ifdef ESP32
if (i2sOn) {
audioLogger->printf("UNINSTALL I2S\n");
i2s_driver_uninstall((i2s_port_t)portNo); //stop & destroy i2s driver
}
#else
if (i2sOn) i2s_end();
#endif
i2sOn = false;
#ifdef ESP32
if (output_mode == INTERNAL_DAC || output_mode == INTERNAL_PDM)
return false; // Not allowed
i2s_pin_config_t pins = {
.bck_io_num = bclkPin,
.ws_io_num = wclkPin,
.data_out_num = doutPin,
.data_in_num = I2S_PIN_NO_CHANGE};
i2s_set_pin((i2s_port_t)portNo, &pins);
return true;
#else
(void)bclkPin;
(void)wclkPin;
(void)doutPin;
return false;
#endif
}
bool AudioOutputI2S::SetPinout(int bclk, int wclk, int dout)
{
#ifdef ESP32
if (output_mode == INTERNAL_DAC || output_mode == INTERNAL_PDM) return false; // Not allowed
bclkPin = bclk;
wclkPin = wclk;
doutPin = dout;
if (i2sOn)
return SetPinout();
i2s_pin_config_t pins = {
.bck_io_num = bclk,
.ws_io_num = wclk,
.data_out_num = dout,
.data_in_num = I2S_PIN_NO_CHANGE
};
i2s_set_pin((i2s_port_t)portNo, &pins);
return true;
#else
(void) bclk;
(void) wclk;
(void) dout;
return false;
}
#elif defined(ARDUINO_ARCH_RP2040)
AudioOutputI2S::AudioOutputI2S(long sampleRate, pin_size_t sck, pin_size_t data) {
i2sOn = false;
mono = false;
bps = 16;
channels = 2;
hertz = sampleRate;
bclkPin = sck;
doutPin = data;
SetGain(1.0);
}
#endif
AudioOutputI2S::~AudioOutputI2S()
{
#ifdef ESP32
if (i2sOn) {
audioLogger->printf("UNINSTALL I2S\n");
i2s_driver_uninstall((i2s_port_t)portNo); //stop & destroy i2s driver
}
#elif defined(ESP8266)
if (i2sOn)
i2s_end();
#elif defined(ARDUINO_ARCH_RP2040)
stop();
#endif
i2sOn = false;
}
bool AudioOutputI2S::SetRate(int hz)
{
// TODO - have a list of allowable rates from constructor, check them
this->hertz = hz;
#ifdef ESP32
i2s_set_sample_rates((i2s_port_t)portNo, AdjustI2SRate(hz));
#else
i2s_set_rate(AdjustI2SRate(hz));
#endif
if (i2sOn)
{
#ifdef ESP32
i2s_set_sample_rates((i2s_port_t)portNo, AdjustI2SRate(hz));
#elif defined(ESP8266)
i2s_set_rate(AdjustI2SRate(hz));
#elif defined(ARDUINO_ARCH_RP2040)
I2S.setFrequency(hz);
#endif
}
return true;
}
@ -167,13 +146,106 @@ bool AudioOutputI2S::SetOutputModeMono(bool mono)
return true;
}
bool AudioOutputI2S::begin()
bool AudioOutputI2S::begin(bool txDAC)
{
#ifdef ESP32
if (!i2sOn)
{
if (use_apll == APLL_AUTO)
{
// don't use audio pll on buggy rev0 chips
use_apll = APLL_DISABLE;
esp_chip_info_t out_info;
esp_chip_info(&out_info);
if (out_info.revision > 0)
{
use_apll = APLL_ENABLE;
}
}
i2s_mode_t mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX);
if (output_mode == INTERNAL_DAC)
{
mode = (i2s_mode_t)(mode | I2S_MODE_DAC_BUILT_IN);
}
else if (output_mode == INTERNAL_PDM)
{
mode = (i2s_mode_t)(mode | I2S_MODE_PDM);
}
i2s_comm_format_t comm_fmt = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB);
if (output_mode == INTERNAL_DAC)
{
comm_fmt = (i2s_comm_format_t)I2S_COMM_FORMAT_I2S_MSB;
}
i2s_config_t i2s_config_dac = {
.mode = mode,
.sample_rate = 44100,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
.communication_format = comm_fmt,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // lowest interrupt priority
.dma_buf_count = dma_buf_count,
.dma_buf_len = 64,
.use_apll = use_apll // Use audio PLL
};
audioLogger->printf("+%d %p\n", portNo, &i2s_config_dac);
if (i2s_driver_install((i2s_port_t)portNo, &i2s_config_dac, 0, NULL) != ESP_OK)
{
audioLogger->println("ERROR: Unable to install I2S drives\n");
}
if (output_mode == INTERNAL_DAC || output_mode == INTERNAL_PDM)
{
i2s_set_pin((i2s_port_t)portNo, NULL);
i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN);
}
else
{
SetPinout();
}
i2s_zero_dma_buffer((i2s_port_t)portNo);
}
#elif defined(ESP8266)
(void)dma_buf_count;
(void)use_apll;
if (!i2sOn)
{
orig_bck = READ_PERI_REG(PERIPHS_IO_MUX_MTDO_U);
orig_ws = READ_PERI_REG(PERIPHS_IO_MUX_GPIO2_U);
#ifdef I2S_HAS_BEGIN_RXTX_DRIVE_CLOCKS
if (!i2s_rxtxdrive_begin(false, true, false, txDAC)) {
return false;
}
#else
if (!i2s_rxtx_begin(false, true)) {
return false;
}
if (!txDAC) {
audioLogger->printf_P(PSTR("I2SNoDAC: esp8266 arduino core should be upgraded to avoid conflicts with SPI\n"));
}
#endif
}
#elif defined(ARDUINO_ARCH_RP2040)
(void)txDAC;
if (!i2sOn) {
I2S.setBCLK(bclkPin);
I2S.setDOUT(doutPin);
I2S.begin(hertz);
}
#endif
i2sOn = true;
SetRate(hertz); // Default
return true;
}
bool AudioOutputI2S::ConsumeSample(int16_t sample[2])
{
//return if we haven't called ::begin yet
if (!i2sOn)
return false;
int16_t ms[2];
ms[0] = sample[0];
@ -185,43 +257,55 @@ bool AudioOutputI2S::ConsumeSample(int16_t sample[2])
int32_t ttl = ms[LEFTCHANNEL] + ms[RIGHTCHANNEL];
ms[LEFTCHANNEL] = ms[RIGHTCHANNEL] = (ttl>>1) & 0xffff;
}
#ifdef ESP32
uint32_t s32;
if (output_mode == INTERNAL_DAC) {
int16_t l = Amplify(ms[LEFTCHANNEL]) + 0x8000;
int16_t r = Amplify(ms[RIGHTCHANNEL]) + 0x8000;
s32 = (r<<16) | (l&0xffff);
} else {
s32 = ((Amplify(ms[RIGHTCHANNEL]))<<16) | (Amplify(ms[LEFTCHANNEL]) & 0xffff);
}
// Deprecated. Use i2s_write
// return i2s_write_bytes((i2s_port_t)portNo, (const char*)&s32, sizeof(uint32_t), 0);
size_t bytes_written;
i2s_write((i2s_port_t)portNo, (const char*)&s32, sizeof(uint32_t), &bytes_written, 0);
return bytes_written;
#else
uint32_t s32 = ((Amplify(ms[RIGHTCHANNEL]))<<16) | (Amplify(ms[LEFTCHANNEL]) & 0xffff);
return i2s_write_sample_nb(s32); // If we can't store it, return false. OTW true
#endif
#ifdef ESP32
uint32_t s32;
if (output_mode == INTERNAL_DAC)
{
int16_t l = Amplify(ms[LEFTCHANNEL]) + 0x8000;
int16_t r = Amplify(ms[RIGHTCHANNEL]) + 0x8000;
s32 = (r << 16) | (l & 0xffff);
}
else
{
s32 = ((Amplify(ms[RIGHTCHANNEL])) << 16) | (Amplify(ms[LEFTCHANNEL]) & 0xffff);
}
return i2s_write_bytes((i2s_port_t)portNo, (const char *)&s32, sizeof(uint32_t), 0);
#elif defined(ESP8266)
uint32_t s32 = ((Amplify(ms[RIGHTCHANNEL])) << 16) | (Amplify(ms[LEFTCHANNEL]) & 0xffff);
return i2s_write_sample_nb(s32); // If we can't store it, return false. OTW true
#elif defined(ARDUINO_ARCH_RP2040)
return !!I2S.write((void*)ms, 4);
#endif
}
void AudioOutputI2S::flush() {
#ifdef ESP32
// makes sure that all stored DMA samples are consumed / played
int buffersize = 64 * this->dma_buf_count;
int16_t samples[2] = {0x0,0x0};
for (int i=0;i<buffersize; i++) {
while (!ConsumeSample(samples)) {
delay(10);
void AudioOutputI2S::flush()
{
#ifdef ESP32
// makes sure that all stored DMA samples are consumed / played
int buffersize = 64 * this->dma_buf_count;
int16_t samples[2] = {0x0, 0x0};
for (int i = 0; i < buffersize; i++)
{
while (!ConsumeSample(samples))
{
delay(10);
}
}
}
#endif
#elif defined(ARDUINO_ARCH_RP2040)
I2S.flush();
#endif
}
bool AudioOutputI2S::stop()
{
#ifdef ESP32
i2s_zero_dma_buffer((i2s_port_t)portNo);
#endif
if (!i2sOn)
return false;
#ifdef ESP32
i2s_zero_dma_buffer((i2s_port_t)portNo);
#elif defined(ARDUINO_ARCH_RP2040)
I2S.end();
#endif
i2sOn = false;
return true;
}

View File

@ -18,41 +18,47 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _AUDIOOUTPUTI2S_H
#define _AUDIOOUTPUTI2S_H
#pragma once
#include "AudioOutput.h"
class AudioOutputI2S : public AudioOutput
{
public:
#if defined(ESP32) || defined(ESP8266)
AudioOutputI2S(int port=0, int output_mode=EXTERNAL_I2S, int dma_buf_count = 8, int use_apll=APLL_DISABLE);
virtual ~AudioOutputI2S() override;
bool SetPinout(int bclkPin, int wclkPin, int doutPin);
enum : int { APLL_AUTO = -1, APLL_ENABLE = 1, APLL_DISABLE = 0 };
enum : int { EXTERNAL_I2S = 0, INTERNAL_DAC = 1, INTERNAL_PDM = 2 };
#elif defined(ARDUINO_ARCH_RP2040)
AudioOutputI2S(long sampleRate = 44100, pin_size_t sck = 26, pin_size_t data = 28);
#endif
virtual ~AudioOutputI2S() override;
virtual bool SetRate(int hz) override;
virtual bool SetBitsPerSample(int bits) override;
virtual bool SetChannels(int channels) override;
virtual bool begin() override;
virtual bool begin() override { return begin(true); }
virtual bool ConsumeSample(int16_t sample[2]) override;
virtual void flush() override;
virtual bool stop() override;
bool begin(bool txDAC);
bool SetOutputModeMono(bool mono); // Force mono output no matter the input
enum : int { APLL_AUTO = -1, APLL_ENABLE = 1, APLL_DISABLE = 0 };
enum : int { EXTERNAL_I2S = 0, INTERNAL_DAC = 1, INTERNAL_PDM = 2 };
protected:
bool SetPinout();
virtual int AdjustI2SRate(int hz) { return hz; }
uint8_t portNo;
int output_mode;
bool mono;
bool i2sOn;
int dma_buf_count;
int use_apll;
// We can restore the old values and free up these pins when in NoDAC mode
uint32_t orig_bck;
uint32_t orig_ws;
uint8_t bclkPin;
uint8_t wclkPin;
uint8_t doutPin;
};
#endif

View File

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

View File

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

View File

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

View File

@ -30,8 +30,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _AUDIOOUTPUTSPDIF_H
#define _AUDIOOUTPUTSPDIF_H
#if defined(ESP32) || defined(ESP8266)
#pragma once
#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;
};
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstrict-aliasing"
static inline void crc16_update_word_(FLAC__BitReader *br, brword word)
{
unsigned crc = br->read_crc16;
@ -149,6 +151,7 @@ static inline void crc16_update_word_(FLAC__BitReader *br, brword word)
#endif
br->crc16_align = 0;
}
#pragma GCC diagnostic pop
static FLAC__bool bitreader_read_from_client_(FLAC__BitReader *br)
{
@ -338,6 +341,8 @@ void FLAC__bitreader_reset_read_crc16(FLAC__BitReader *br, FLAC__uint16 seed)
br->crc16_align = br->consumed_bits;
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstrict-aliasing"
FLAC__uint16 FLAC__bitreader_get_read_crc16(FLAC__BitReader *br)
{
FLAC__ASSERT(0 != br);
@ -353,6 +358,7 @@ FLAC__uint16 FLAC__bitreader_get_read_crc16(FLAC__BitReader *br)
}
return br->read_crc16;
}
#pragma GCC diagnostic pop
inline FLAC__bool FLAC__bitreader_is_consumed_byte_aligned(const FLAC__BitReader *br)
{

View File

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

View File

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

View File

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

View File

@ -2021,6 +2021,8 @@ FLAC__bool frame_sync_(FLAC__StreamDecoder *decoder)
return true;
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstrict-aliasing"
FLAC__bool read_frame_(FLAC__StreamDecoder *decoder, FLAC__bool *got_a_frame, FLAC__bool do_full_decode)
{
uint32_t channel;
@ -2167,6 +2169,7 @@ FLAC__bool read_frame_(FLAC__StreamDecoder *decoder, FLAC__bool *got_a_frame, FL
decoder->protected_->state = FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC;
return true;
}
#pragma GCC diagnostic pop
FLAC__bool read_frame_header_(FLAC__StreamDecoder *decoder)
{

View File

@ -223,7 +223,7 @@ Word64 MADD64(Word64 sum64, int x, int y);
/* toolchain: ARM ADS or RealView
* target architecture: ARM v.4 and above (requires 'M' type processor for 32x32->64 multiplier)
*/
#elif defined (__arm) && defined (__ARMCC_VERSION)
#elif defined (XXX__arm) && defined (__ARMCC_VERSION)
static __inline int MULSHIFT32(int x, int y)
{
@ -336,7 +336,7 @@ static __inline Word64 MADD64(Word64 sum64, int x, int y)
/* toolchain: ARM gcc
* target architecture: ARM v.4 and above (requires 'M' type processor for 32x32->64 multiplier)
*/
#elif defined(__GNUC__) && defined(__arm__)
#elif defined(__GNUC__) && defined(XXXX__arm__)
static inline int MULSHIFT32(int x, int y)
{

View File

@ -44,6 +44,7 @@
**************************************************************************************/
#if defined(USE_DEFAULT_STDLIB) || defined(ARDUINO)
#include <stdio.h>
#include <stdlib.h>
#else
#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
* use the assembly code version in sbrcov.s when building for ARM!
**************************************************************************************/
#if (defined (__arm) && defined (__ARMCC_VERSION)) || (defined (_WIN32) && defined (_WIN32_WCE) && defined (ARM)) || (defined(__GNUC__) && defined(__arm__))
#if (defined (XXXX__arm) && defined (__ARMCC_VERSION)) || (defined (_WIN32) && defined (_WIN32_WCE) && defined (ARM)) || (defined(__GNUC__) && defined(XXXX__arm__))
#ifdef __cplusplus
extern "C"
#endif
@ -237,7 +237,7 @@ static int CalcCovariance1(int *XBuf, int *p01reN, int *p01imN, int *p12reN, int
* Notes: this is carefully written to be efficient on ARM
* use the assembly code version in sbrcov.s when building for ARM!
**************************************************************************************/
#if (defined (__arm) && defined (__ARMCC_VERSION)) || (defined (_WIN32) && defined (_WIN32_WCE) && defined (ARM)) || (defined(__GNUC__) && defined(__arm__))
#if (defined (XXXX__arm) && defined (__ARMCC_VERSION)) || (defined (_WIN32) && defined (_WIN32_WCE) && defined (ARM)) || (defined(__GNUC__) && defined(XXXX__arm__))
#ifdef __cplusplus
extern "C"
#endif

View File

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

View File

@ -48,6 +48,7 @@
# endif
# if !defined(HAVE_ASSERT_H)
# undef assert
# if defined(NDEBUG)
# define assert(x) /* nothing */
# 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;
VARDECL( opus_val16, lp_pitch_buf );
SAVE_STACK;
ALLOC( lp_pitch_buf, DECODE_BUFFER_SIZE>>1, opus_val16 );
opus_val16 *lp_pitch_buf = (opus_val16*)malloc((DECODE_BUFFER_SIZE>>1) * sizeof(opus_val16)); //ALLOC( lp_pitch_buf, DECODE_BUFFER_SIZE>>1, opus_val16 );
pitch_downsample(decode_mem, lp_pitch_buf,
DECODE_BUFFER_SIZE, C, arch);
pitch_search(lp_pitch_buf+(PLC_PITCH_LAG_MAX>>1), lp_pitch_buf,
@ -490,6 +490,7 @@ static int celt_plc_pitch_search(celt_sig *decode_mem[2], int C, int arch)
PLC_PITCH_LAG_MAX-PLC_PITCH_LAG_MIN, &pitch_index, arch);
pitch_index = PLC_PITCH_LAG_MAX-pitch_index;
RESTORE_STACK;
free(lp_pitch_buf);
return pitch_index;
}

View File

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

View File

@ -80,10 +80,11 @@ void silk_NLSF2A(
};
const unsigned char *ordering;
opus_int k, i, dd;
opus_int32 cos_LSF_QA[ SILK_MAX_ORDER_LPC ];
opus_int32 P[ SILK_MAX_ORDER_LPC / 2 + 1 ], Q[ SILK_MAX_ORDER_LPC / 2 + 1 ];
opus_int32 *cos_LSF_QA = (opus_int32*)malloc(sizeof(opus_int32) * SILK_MAX_ORDER_LPC );
opus_int32 *P = (opus_int32*)malloc(sizeof(opus_int32) * (SILK_MAX_ORDER_LPC / 2 + 1));
opus_int32 *Q= (opus_int32*)malloc(sizeof(opus_int32) * (SILK_MAX_ORDER_LPC / 2 + 1));
opus_int32 Ptmp, Qtmp, f_int, f_frac, cos_val, delta;
opus_int32 a32_QA1[ SILK_MAX_ORDER_LPC ];
opus_int32 *a32_QA1 = (opus_int32*)malloc(sizeof(opus_int32) * SILK_MAX_ORDER_LPC );
silk_assert( LSF_COS_TAB_SZ_FIX == 128 );
celt_assert( d==10 || d==16 );
@ -137,5 +138,9 @@ void silk_NLSF2A(
a_Q12[ k ] = (opus_int16)silk_RSHIFT_ROUND( a32_QA1[ k ], QA + 1 - 12 ); /* QA+1 -> Q12 */
}
}
free(cos_LSF_QA);
free(P);
free(Q);
free(a32_QA1);
}

View File

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

View File

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

View File

@ -48,7 +48,8 @@ void silk_resampler_down2_3(
opus_int32 *buf_ptr;
SAVE_STACK;
ALLOC( buf, RESAMPLER_MAX_BATCH_SIZE_IN + ORDER_FIR, opus_int32 );
// ALLOC( buf, RESAMPLER_MAX_BATCH_SIZE_IN + ORDER_FIR, opus_int32 );
opus_int32 *buf = (opus_int32*)malloc((RESAMPLER_MAX_BATCH_SIZE_IN + ORDER_FIR) * sizeof(opus_int32));
/* Copy buffered samples to start of buffer */
silk_memcpy( buf, S, ORDER_FIR * sizeof( opus_int32 ) );
@ -99,5 +100,6 @@ void silk_resampler_down2_3(
/* Copy last part of filtered signal to the state for the next call */
silk_memcpy( S, &buf[ nSamplesIn ], ORDER_FIR * sizeof( opus_int32 ) );
free(buf);
RESTORE_STACK;
}

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_tell(struct tsf_stream_memory* m) { return m->pos; }
static int tsf_stream_memory_size(struct tsf_stream_memory* m) { return m->total; }
static int tsf_stream_memory_skip(struct tsf_stream_memory* m, unsigned int count) { if (m->pos + count > m->total) return 0; m->pos += count; return 1; }
static int tsf_stream_memory_skip(struct tsf_stream_memory* m, unsigned int count) { if (m->pos + count > m->total) count = m->total - m->pos; m->pos += count; return 1; }
static int tsf_stream_memory_seek(struct tsf_stream_memory* m, unsigned int pos) { if (pos > m->total) return 0; else m->pos = pos; return 1; }
static int tsf_stream_memory_close(struct tsf_stream_memory* m) { (void)m; return 1; }
static int tsf_stream_memory_close(struct tsf_stream_memory* m) { TSF_FREE(m); return 1; }
TSFDEF tsf* tsf_load_memory(const void* buffer, int size)
{
struct tsf_stream stream = { TSF_NULL, (int(*)(void*,void*,unsigned int))&tsf_stream_memory_read, (int(*)(void*))&tsf_stream_memory_tell, (int(*)(void*,unsigned int))&tsf_stream_memory_skip, (int(*)(void*,unsigned int))&tsf_stream_memory_seek, (int(*)(void*))&tsf_stream_memory_close, (int(*)(void*))&tsf_stream_memory_size };
struct tsf_stream_memory f = { 0, 0, 0 };
f.buffer = (const char*)buffer;
f.total = size;
stream.data = &f;
struct tsf_stream_memory* f = (struct tsf_stream_memory*)TSF_MALLOC(sizeof(struct tsf_stream_memory));
f->pos = 0;
f->buffer = (const char*)buffer;
f->total = size;
stream.data = f;
return tsf_load(&stream);
}

View File

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

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
CCOPTS=-g -Wunused-parameter -Wall -m32 -include Arduino.h
CPPOPTS=-g -Wunused-parameter -Wall -std=c++11 -m32 -include Arduino.h
libflac=../../src/libflac/md5.c ../../src/libflac/window.c ../../src/libflac/memory.c ../../src/libflac/cpu.c \
../../src/libflac/fixed.c ../../src/libflac/format.c ../../src/libflac/lpc.c ../../src/libflac/crc.c \
../../src/libflac/bitreader.c ../../src/libflac/bitmath.c ../../src/libflac/stream_decoder.c ../../src/libflac/float.c
CCOPTS=-g -Wunused-parameter -Wall -m32 -include Arduino.h -Wstack-usage=300
CPPOPTS=-g -Wunused-parameter -Wall -std=c++11 -m32 -Wstack-usage=300 -include Arduino.h
.phony: all
all: mp3 aac wav midi opus
all: mp3 aac wav midi opus flac
mp3: FORCE
rm -f *.o
@ -94,6 +99,13 @@ aac: FORCE
rm -f *.o
echo valgrind --leak-check=full --track-origins=yes -v --error-limit=no --show-leak-kinds=all ./aac
flac: FORCE
rm -f *.o
gcc $(CCOPTS) -DUSE_DEFAULT_STDLIB -c $(libflac) -I ../../src/ -I ../../src/libflac -I.
g++ $(CPPOPTS) -o flac flac.cpp Serial.cpp *.o ../../src/AudioFileSourceSTDIO.cpp ../../src/AudioOutputSTDIO.cpp ../../src/AudioFileSourceID3.cpp ../../src/AudioGeneratorFLAC.cpp ../../src/AudioLogger.cpp -I ../../src/ -I.
rm -f *.o
echo valgrind --leak-check=full --track-origins=yes -v --error-limit=no --show-leak-kinds=all ./flac
wav: FORCE
rm -f *.o
g++ $(CPPOPTS) -o wav wav.cpp Serial.cpp ../../src/AudioFileSourceSTDIO.cpp ../../src/AudioOutputSTDIO.cpp ../../src/AudioGeneratorWAV.cpp ../../src/AudioLogger.cpp -I ../../src/ -I.
@ -116,6 +128,6 @@ opus: FORCE
echo valgrind --leak-check=full --track-origins=yes -v --error-limit=no --show-leak-kinds=all ./opus
clean:
rm -f mp3 aac wav midi opus *.o
rm -f mp3 aac wav midi opus flac *.o
FORCE:

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

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_dot_p;
extern const bcstring be_const_str_bus;
extern const bcstring be_const_str_close;
extern const bcstring be_const_str_lv_group_focus_cb;
extern const bcstring be_const_str_SDS0X1_TX;
extern const bcstring be_const_str_SDM72_TX;

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

View File

@ -1,14 +1,13 @@
#include "be_constobj.h"
static be_define_const_map_slots(be_class_audio_file_source_fs_map) {
{ be_const_key(init, 2), be_const_func(i2s_file_source_fs_init) },
{ be_const_key(deinit, -1), be_const_func(i2s_file_source_fs_deinit) },
{ be_const_key(close, -1), be_const_func(i2s_file_source_fs_deinit) },
{ be_const_key(init, -1), be_const_func(i2s_file_source_fs_init) },
};
static be_define_const_map(
be_class_audio_file_source_fs_map,
3
2
);
BE_EXPORT_VARIABLE be_define_const_class(

View File

@ -1,18 +1,17 @@
#include "be_constobj.h"
static be_define_const_map_slots(be_class_audio_generator_mp3_map) {
{ be_const_key(close, -1), be_const_func(i2s_generator_mp3_deinit) },
{ be_const_key(stop, -1), be_const_func(i2s_generator_mp3_stop) },
{ be_const_key(loop, 0), be_const_func(i2s_generator_mp3_loop) },
{ be_const_key(isrunning, 1), be_const_func(i2s_generator_mp3_isrunning) },
{ be_const_key(begin, -1), be_const_func(i2s_generator_mp3_begin) },
{ be_const_key(deinit, 6), be_const_func(i2s_generator_mp3_deinit) },
{ be_const_key(init, -1), be_const_func(i2s_generator_mp3_init) },
{ be_const_key(loop, -1), be_const_func(i2s_generator_mp3_loop) },
{ be_const_key(isrunning, -1), be_const_func(i2s_generator_mp3_isrunning) },
{ be_const_key(init, 1), be_const_func(i2s_generator_mp3_init) },
{ be_const_key(deinit, -1), be_const_func(i2s_generator_mp3_deinit) },
{ be_const_key(stop, -1), be_const_func(i2s_generator_mp3_stop) },
};
static be_define_const_map(
be_class_audio_generator_mp3_map,
7
6
);
BE_EXPORT_VARIABLE be_define_const_class(

View File

@ -1,18 +1,17 @@
#include "be_constobj.h"
static be_define_const_map_slots(be_class_audio_generator_wav_map) {
{ be_const_key(close, -1), be_const_func(i2s_generator_wav_deinit) },
{ be_const_key(stop, -1), be_const_func(i2s_generator_wav_stop) },
{ be_const_key(loop, 0), be_const_func(i2s_generator_wav_loop) },
{ be_const_key(isrunning, 1), be_const_func(i2s_generator_wav_isrunning) },
{ be_const_key(begin, -1), be_const_func(i2s_generator_wav_begin) },
{ be_const_key(deinit, 6), be_const_func(i2s_generator_wav_deinit) },
{ be_const_key(init, -1), be_const_func(i2s_generator_wav_init) },
{ be_const_key(loop, -1), be_const_func(i2s_generator_wav_loop) },
{ be_const_key(isrunning, -1), be_const_func(i2s_generator_wav_isrunning) },
{ be_const_key(init, 1), be_const_func(i2s_generator_wav_init) },
{ be_const_key(deinit, -1), be_const_func(i2s_generator_wav_deinit) },
{ be_const_key(stop, -1), be_const_func(i2s_generator_wav_stop) },
};
static be_define_const_map(
be_class_audio_generator_wav_map,
7
6
);
BE_EXPORT_VARIABLE be_define_const_class(

View File

@ -1,9 +1,9 @@
#include "be_constobj.h"
static be_define_const_map_slots(be_class_audio_output_i2s_map) {
{ be_const_key(init, 2), be_const_func(i2s_output_i2s_init) },
{ be_const_key(init, -1), be_const_func(i2s_output_i2s_init) },
{ be_const_key(deinit, -1), be_const_func(i2s_output_i2s_deinit) },
{ be_const_key(close, -1), be_const_func(i2s_output_i2s_deinit) },
{ be_const_key(stop, -1), be_const_func(i2s_output_i2s_stop) },
};
static be_define_const_map(

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 * audio = new AudioOutputI2S(port, mode, dma_buf_count);
audio->SetPinout(bclkPin, wclkPin, doutPin); // return value has no useful information for us
if (0 == mode) {
audio->SetPinout(bclkPin, wclkPin, doutPin); // return value has no useful information for us
}
be_pushcomptr(vm, (void*) audio);
be_setmember(vm, 1, ".p");
be_return_nil(vm);
@ -84,6 +86,16 @@ extern "C" {
be_return_nil(vm);
}
int i2s_output_i2s_stop(bvm *vm) {
int argc = be_top(vm);
be_getmember(vm, 1, ".p");
AudioOutputI2S * audio = (AudioOutputI2S *) be_tocomptr(vm, -1);
if (audio) {
audio->stop();
}
be_return_nil(vm);
}
//
// AudioGeneratorWAV()
//