mirror of https://github.com/arendst/Tasmota.git
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:
commit
7c775bb352
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
|
@ -22,6 +22,7 @@ AudioGeneratorWAV KEYWORD1
|
|||
AudioOutput KEYWORD1
|
||||
AudioOutputI2S KEYWORD1
|
||||
AudioOutputI2SNoDAC KEYWORD1
|
||||
AudioOutputI2SClass KEYWORD1
|
||||
AudioOutputNull KEYWORD1
|
||||
AudioOutputBuffer KEYWORD1
|
||||
AudioOutputSerialWAV KEYWORD1
|
||||
|
|
|
@ -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"
|
||||
]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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); }
|
||||
|
|
|
@ -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++;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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"
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -2,9 +2,7 @@
|
|||
|
||||
#ifdef DEBUG
|
||||
#undef NDEBUG
|
||||
#endif
|
||||
|
||||
#ifndef NDEBUG
|
||||
#else
|
||||
#define NDEBUG
|
||||
#endif
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
*/
|
||||
|
||||
//#ifdef HAVE_CONFIG_H
|
||||
# include <config.h>
|
||||
# include "config.h"
|
||||
//#endif
|
||||
|
||||
#include "private/cpu.h"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
**************************************************************************************/
|
||||
|
||||
#if defined(USE_DEFAULT_STDLIB) || defined(ARDUINO)
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#else
|
||||
#include "hlxclib/stdlib.h"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -48,6 +48,7 @@
|
|||
# endif
|
||||
|
||||
# if !defined(HAVE_ASSERT_H)
|
||||
# undef assert
|
||||
# if defined(NDEBUG)
|
||||
# define assert(x) /* nothing */
|
||||
# else
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -206,3 +206,5 @@
|
|||
# define _Restrict
|
||||
# define __restrict__
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
}
|
Binary file not shown.
|
@ -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 */
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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()
|
||||
//
|
||||
|
|
Loading…
Reference in New Issue