Add pre-packaged dependency libraries (ESP32-audioI2S and PNGdec)
This commit is contained in:
811
libraries/ESP32-audioI2S/src/Audio.h
Normal file
811
libraries/ESP32-audioI2S/src/Audio.h
Normal file
@@ -0,0 +1,811 @@
|
||||
/*
|
||||
* Audio.h
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#pragma GCC optimize("Ofast")
|
||||
#include "audiolib_structs.hpp"
|
||||
#include "esp_arduino_version.h"
|
||||
#include "esp_dsp.h"
|
||||
#include "psram_unique_ptr.hpp"
|
||||
#include <Arduino.h>
|
||||
#include <FFat.h>
|
||||
#include <FS.h>
|
||||
#include <NetworkClient.h>
|
||||
#include <NetworkClientSecure.h>
|
||||
#include <SD.h>
|
||||
#include <SD_MMC.h>
|
||||
#include <WiFi.h>
|
||||
#include <atomic>
|
||||
#include <charconv>
|
||||
#include <codecvt>
|
||||
#include <deque>
|
||||
#include <driver/i2s_std.h>
|
||||
#include <esp32-hal-log.h>
|
||||
#include <functional>
|
||||
#include <libb64/cencode.h>
|
||||
#include <locale>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
#ifndef I2S_GPIO_UNUSED
|
||||
#define I2S_GPIO_UNUSED -1 // = I2S_PIN_NO_CHANGE in IDF < 5
|
||||
#endif
|
||||
|
||||
extern __attribute__((weak)) void audio_process_raw_samples(int32_t* outBuff, int16_t validSamples); // before volume, gain and equalizer, record audiodata
|
||||
extern __attribute__((weak)) void audio_process_i2s(int32_t* outBuff, int16_t validSamples, bool* continueI2S); // after volume, gain and equalizer, send via BT
|
||||
extern char audioI2SVers[];
|
||||
class Decoder; // prototype
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------------
|
||||
class AudioBuffer {
|
||||
|
||||
public:
|
||||
AudioBuffer(); // constructor
|
||||
~AudioBuffer(); // frees the buffer
|
||||
size_t init(); // set default values
|
||||
bool isInitialized() { return m_init; };
|
||||
size_t getBufsize();
|
||||
size_t getMaxBlockSize(); // returns maxBlockSize
|
||||
void setMaxBlocksize(uint32_t mbs);
|
||||
size_t freeSpace(); // number of free bytes to overwrite
|
||||
size_t writeSpace(); // space fom writepointer to bufferend
|
||||
size_t bufferFilled(); // returns the number of filled bytes
|
||||
size_t readSpace(); // max readable bytes in one block
|
||||
void bytesWritten(size_t bw); // update writepointer
|
||||
void bytesWasRead(size_t br); // update readpointer
|
||||
uint8_t* getWritePtr(); // returns the current writepointer
|
||||
uint8_t* getReadPtr(); // returns the current readpointer
|
||||
void reset(); // restore defaults
|
||||
void showStatus();
|
||||
|
||||
protected:
|
||||
size_t m_mainBuffSize = 0; // most webstreams limit the advance to 100...300Kbytes
|
||||
size_t m_freeSpace = 0;
|
||||
size_t m_writeSpace = 0;
|
||||
size_t m_resBuffSize = 0;
|
||||
size_t m_maxBlockSize = 0;
|
||||
size_t m_readSpace = 0;
|
||||
const size_t m_maxRet = UINT16_MAX;
|
||||
ps_ptr<uint8_t> m_buffer;
|
||||
uint8_t* m_buffEnd = nullptr;
|
||||
uint8_t* m_writePtr = nullptr;
|
||||
uint8_t* m_readPtr = nullptr;
|
||||
uint8_t* m_endPtr = nullptr;
|
||||
uint8_t* m_startPtr = nullptr;
|
||||
ps_ptr<char> m_log;
|
||||
bool m_init = false;
|
||||
bool m_isEmpty = true;
|
||||
bool m_isFull = false;
|
||||
|
||||
private:
|
||||
SemaphoreHandle_t m_mutex = nullptr;
|
||||
#define ANSI_ESC_RED "\033[31m"
|
||||
};
|
||||
//----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
class Audio {
|
||||
private:
|
||||
AudioBuffer InBuff; // instance of input buffer
|
||||
|
||||
public:
|
||||
Audio(uint8_t i2sPort = I2S_NUM_0);
|
||||
~Audio();
|
||||
std::mutex mutex_info; // mutex_info as member
|
||||
|
||||
// callbacks ---------------------------------------------------------
|
||||
typedef enum {
|
||||
evt_info = 0,
|
||||
evt_id3data,
|
||||
evt_eof,
|
||||
evt_name,
|
||||
evt_icydescription,
|
||||
evt_streamtitle,
|
||||
evt_bitrate,
|
||||
evt_icyurl,
|
||||
evt_icylogo,
|
||||
evt_genre,
|
||||
evt_lasthost,
|
||||
evt_image,
|
||||
evt_lyrics,
|
||||
evt_log,
|
||||
} event_t;
|
||||
|
||||
// Audio event type descriptions
|
||||
static constexpr std::array<const char*, 14> eventStr = {
|
||||
"info", // evt_info
|
||||
"id3data", // evt_id3data
|
||||
"eof", // evt_eof
|
||||
"station_name", // evt_name
|
||||
"icy_description", // evt_icydescription
|
||||
"streamtitle", // evt_streamtitle
|
||||
"bitrate (b/s)", // evt_bitrate
|
||||
"icy_url", // evt_icyurl
|
||||
"icy_logo", // evt_icylogo
|
||||
"genre", // evt_genre
|
||||
"lasthost", // evt_lasthost
|
||||
"cover_image", // evt_image
|
||||
"lyrics", // evt_lyrics
|
||||
"log", // evt_log
|
||||
};
|
||||
|
||||
typedef struct _msg { // used in info(audio_info_callback());
|
||||
const char* msg = nullptr;
|
||||
const char* s = nullptr;
|
||||
event_t e = (event_t)0; // event type
|
||||
int32_t i2s_num = 0;
|
||||
int32_t arg1 = 0;
|
||||
int32_t arg2 = 0;
|
||||
std::vector<uint32_t> vec = {}; // apic [pos, len, pos, len, pos, len, ....]
|
||||
} msg_t;
|
||||
inline static std::function<void(msg_t i)> audio_info_callback;
|
||||
using VolumeCurveFn = std::function<float(float t)>;
|
||||
// -------------------------------------------------------------------
|
||||
typedef enum : uint32_t { SR_ORIGIN = 0, SR_44100 = 44100, SR_48000 = 48000 } OutputSR_t;
|
||||
|
||||
bool openai_speech(const String& api_key, const String& model, const String& input, const String& instructions, const String& voice, const String& response_format, const String& speed);
|
||||
audiolib::hwoe_t dismantle_host(const char* host);
|
||||
bool connecttohost(const char* host, const char* user = nullptr, const char* pwd = nullptr);
|
||||
bool connecttospeech(const char* speech, const char* lang);
|
||||
bool connecttoFS(fs::FS& fs, const char* path, int32_t fileStartTime = -1);
|
||||
void setConnectionTimeout(uint16_t timeout_ms, uint16_t timeout_ms_ssl);
|
||||
bool setAudioPlayTime(uint16_t sec);
|
||||
bool setTimeOffset(int sec);
|
||||
bool setPinout(uint8_t BCLK, uint8_t LRC, uint8_t DOUT, int8_t MCLK = I2S_GPIO_UNUSED);
|
||||
bool pauseResume();
|
||||
bool isRunning() { return m_f_running; }
|
||||
void loop();
|
||||
uint32_t stopSong();
|
||||
void forceMono(bool m);
|
||||
void setOutputSampleRate(OutputSR_t sr);
|
||||
void setBalance(float balance = 0.0f);
|
||||
void setVolumeSteps(uint8_t steps);
|
||||
uint8_t getVolumeSteps();
|
||||
void setVolume(uint8_t vol, uint8_t curve = 0);
|
||||
void setVolumeCurve(VolumeCurveFn curve);
|
||||
uint8_t getVolume();
|
||||
void setMute(bool mute);
|
||||
bool getMute();
|
||||
int32_t getI2sPort();
|
||||
uint32_t getFileSize();
|
||||
uint32_t getSampleRate();
|
||||
uint8_t getBitsPerSample();
|
||||
uint8_t getChannels();
|
||||
uint32_t getBitRate();
|
||||
uint32_t getAudioFileDuration();
|
||||
uint32_t getAudioCurrentTime();
|
||||
uint32_t getAudioFilePosition();
|
||||
bool setAudioFilePosition(uint32_t pos);
|
||||
uint16_t getVUlevel();
|
||||
uint32_t inBufferFilled(); // returns the number of stored bytes in the inputbuffer
|
||||
uint32_t inBufferFree(); // returns the number of free bytes in the inputbuffer
|
||||
uint32_t getInBufferSize(); // returns the size of the inputbuffer in bytes
|
||||
void inBufferStatus() { InBuff.showStatus(); }
|
||||
void setTone(float gainLowPass, float gainBandPass, float gainHighPass);
|
||||
void setI2SCommFMT_LSB(bool commFMT);
|
||||
int getCodec() { return m_codec; }
|
||||
const char* getCodecname() { return codecname[m_codec]; }
|
||||
const char* getVersion();
|
||||
// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
|
||||
|
||||
private:
|
||||
// ------- PRIVATE MEMBERS ----------------------------------------
|
||||
bool i2s_config();
|
||||
std::unique_ptr<Decoder> createDecoder(const std::string& type);
|
||||
void destroy_decoder();
|
||||
bool fsRange(uint32_t range);
|
||||
void latinToUTF8(ps_ptr<char>& buff, bool UTF8check = true);
|
||||
void htmlToUTF8(char* str);
|
||||
void setDefaults(); // free buffers and set defaults
|
||||
int32_t audioFileRead();
|
||||
int32_t audioFileRead(uint16_t timeout_ms);
|
||||
int32_t audioFileRead(uint8_t* buff, size_t len);
|
||||
int32_t audioFileRead(uint8_t* buff, size_t len, uint16_t timeout_ms);
|
||||
int32_t audioFileSeek(uint32_t position, size_t len = 0);
|
||||
void initInBuff();
|
||||
bool httpPrint(const char* host);
|
||||
bool httpRange(uint32_t range, uint32_t length = UINT32_MAX);
|
||||
void processLocalFile();
|
||||
void processWebStream();
|
||||
void processWebFile();
|
||||
void processWebStreamTS();
|
||||
void processWebStreamHLS();
|
||||
void playAudioData();
|
||||
bool readPlayListData();
|
||||
const char* parsePlaylist_M3U();
|
||||
const char* parsePlaylist_PLS();
|
||||
const char* parsePlaylist_ASX();
|
||||
ps_ptr<char> parsePlaylist_M3U8();
|
||||
uint16_t accomplish_m3u8_url();
|
||||
int16_t prepare_first_m3u8_url(ps_ptr<char>& playlistBuff);
|
||||
ps_ptr<char> m3u8redirection(uint8_t* codec);
|
||||
void showCodecParams();
|
||||
int findNextSync(uint8_t* data, size_t len);
|
||||
uint32_t decodeError(int8_t res, uint8_t* data, int32_t bytesDecoded);
|
||||
uint32_t decodeContinue(int8_t res, uint8_t* data, int32_t bytesDecoded, int32_t* bytesLeft);
|
||||
int sendBytes(uint8_t* data, size_t len);
|
||||
void setDecoderItems();
|
||||
void calculateAudioTime(uint16_t bytesDecoderIn, uint16_t bytesDecoderOut);
|
||||
void showID3Tag(const char* tag, const char* val);
|
||||
size_t readAudioHeader(uint32_t bytes);
|
||||
int read_WAV_Header(uint8_t* data, size_t len);
|
||||
int read_FLAC_Header(uint8_t* data, size_t len);
|
||||
int read_ID3_Header(uint8_t* data, size_t len);
|
||||
int read_M4A_Header(uint8_t* data, size_t len);
|
||||
size_t process_m3u8_ID3_Header(uint8_t* packet);
|
||||
bool setSampleRate(uint32_t hz);
|
||||
bool setBitsPerSample(int bits);
|
||||
bool setChannels(int channels);
|
||||
uint32_t resampleI2Soutput(audiolib::resampler_t& resampler, int32_t* input, uint32_t inputSamples, int32_t* output);
|
||||
void playChunk();
|
||||
void calculateVUlevel(int32_t* sample);
|
||||
void processSpectrum();
|
||||
void gain_ramp();
|
||||
void calculateVolumeLimits();
|
||||
void Gain(int32_t* sample);
|
||||
void showstreamtitle(char* ml);
|
||||
bool parseContentType(ps_ptr<char> ct);
|
||||
bool parseHttpResponseHeader();
|
||||
bool parseHttpRangeHeader();
|
||||
bool initializeDecoder();
|
||||
esp_err_t I2Sstart();
|
||||
esp_err_t I2Sstop();
|
||||
void zeroI2Sbuff();
|
||||
void reconfigI2S();
|
||||
void stereo2mono(int32_t* buff, uint16_t validSamples);
|
||||
void IIR_calculateCoefficients();
|
||||
void IIR_filter(int32_t* iir_in);
|
||||
uint32_t streamavail() { return m_client ? m_client->available() : 0; }
|
||||
bool ts_parsePacket(uint8_t* packet, uint8_t* packetStart, uint8_t* packetLength);
|
||||
uint64_t getLastGranulePosition(uint8_t codec);
|
||||
|
||||
//+++ create a T A S K for playAudioData(), output via I2S +++
|
||||
public:
|
||||
void setAudioTaskCore(uint8_t coreID);
|
||||
uint32_t getHighWatermark();
|
||||
|
||||
private:
|
||||
void startAudioTask(); // starts a task for decode and play
|
||||
void stopAudioTask(); // stops task for audio
|
||||
static void audioTaskWrapper(void* param);
|
||||
void audioTask();
|
||||
void performAudioTask();
|
||||
|
||||
//+++ H E L P F U N C T I O N S +++
|
||||
bool readMetadata(uint32_t b, uint16_t* readedBytes, bool first = false);
|
||||
int32_t getChunkSize(uint16_t* readedBytes, bool first = false);
|
||||
bool readID3V1Tag();
|
||||
int32_t newInBuffStart(int32_t resumeFilePos);
|
||||
boolean streamDetection(uint32_t bytesAvail);
|
||||
uint32_t m4a_correctResumeFilePos();
|
||||
uint32_t ogg_correctResumeFilePos();
|
||||
int32_t flac_correctResumeFilePos();
|
||||
int32_t mp3_correctResumeFilePos();
|
||||
int32_t wav_correctResumeFilePos();
|
||||
uint8_t determineCodec(uint8_t presumed_codec);
|
||||
bool get_info();
|
||||
void trim(char* str);
|
||||
bool startsWith(const char* base, const char* str);
|
||||
int indexOf(const char* base, const char* str, int startIndex = 0);
|
||||
int indexOf(const char* base, char ch, int startIndex = 0);
|
||||
int specialIndexOf(uint8_t* base, const char* str, int baselen, bool exact = false);
|
||||
int32_t min3(int32_t a, int32_t b, int32_t c);
|
||||
uint64_t bigEndian(uint8_t* base, uint8_t numBytes, uint8_t shiftLeft = 8);
|
||||
bool b64encode(const char* source, uint16_t sourceLength, char* dest);
|
||||
void vector_clear_and_shrink(std::vector<ps_ptr<char>>& vec);
|
||||
void deque_clear_and_shrink(std::deque<ps_ptr<char>>& deq);
|
||||
uint32_t simpleHash(const char* str);
|
||||
ps_ptr<char> urlencode(const char* str, bool spacesOnly);
|
||||
audiolib::BiquadCoeffs makeButterworthLPF_Q31(float fs);
|
||||
|
||||
private:
|
||||
enum : int { APLL_AUTO = -1, APLL_ENABLE = 1, APLL_DISABLE = 0 };
|
||||
enum : int { EXTERNAL_I2S = 0, INTERNAL_DAC = 1, INTERNAL_PDM = 2 };
|
||||
enum : int { FORMAT_NONE = 0, FORMAT_M3U = 1, FORMAT_PLS = 2, FORMAT_ASX = 3, FORMAT_M3U8 = 4 }; // playlist formats
|
||||
const char* plsFmtStr[5] = {"NONE", "M3U", "PLS", "ASX", "M3U8"}; // playlist format string
|
||||
enum : int { AUDIO_NONE, HTTP_RESPONSE_HEADER, HTTP_RANGE_HEADER, AUDIO_DATA, AUDIO_LOCALFILE, AUDIO_PLAYLISTINIT, AUDIO_PLAYLISTHEADER, AUDIO_PLAYLISTDATA };
|
||||
const char* dataModeStr[8] = {"AUDIO_NONE", "HTTP_RESPONSE_HEADER", "HTTP_RANGE_HEADER", "AUDIO_DATA", "AUDIO_LOCALFILE", "AUDIO_PLAYLISTINIT", "AUDIO_PLAYLISTHEADER", "AUDIO_PLAYLISTDATA"};
|
||||
enum : int { FLAC_BEGIN = 0, FLAC_MAGIC = 1, FLAC_MBH = 2, FLAC_SINFO = 3, FLAC_PADDING = 4, FLAC_APP = 5, FLAC_SEEK = 6, FLAC_VORBIS = 7, FLAC_CUESHEET = 8, FLAC_PICTURE = 9, FLAC_OKAY = 100 };
|
||||
enum : int { MP3_BEGIN = 0, MP3_ID3HEADER, MP3_NEXTID3, MP3_EXTHEADER, MP3_ID3FRAME, MP3_FRAMESIZE, MP3_SKIP, MP3_TAG, MP3_SYLT, MP3_ID3V22, MP3_LASTFRAMES, MP3_XING, MP3_OKAY = 100 };
|
||||
enum : int {
|
||||
M4A_BEGIN = 0,
|
||||
M4A_FTYP = 1,
|
||||
M4A_CHK = 2,
|
||||
M4A_MOOV = 3,
|
||||
M4A_FREE = 4,
|
||||
M4A_TRAK = 5,
|
||||
M4A_MDAT = 6,
|
||||
M4A_ILST = 7,
|
||||
M4A_MP4A = 8,
|
||||
M4A_ESDS = 9,
|
||||
M4A_MDIA = 10,
|
||||
M4A_MINF = 11,
|
||||
M4A_STBL = 12,
|
||||
M4A_STSD = 13,
|
||||
M4A_UDTA = 14,
|
||||
M4A_STSZ = 15,
|
||||
M4A_META = 16,
|
||||
M4A_MDHD = 17,
|
||||
M4A_CHPL = 18,
|
||||
M4A_AMRDY = 99,
|
||||
M4A_OKAY = 100,
|
||||
};
|
||||
enum : int { CODEC_NONE = 0, CODEC_WAV = 1, CODEC_MP3 = 2, CODEC_AAC = 3, CODEC_M4A = 4, CODEC_FLAC = 5, CODEC_OPUS = 6, CODEC_VORBIS = 7, CODEC_OGG = 8 };
|
||||
const char* codecname[10] = {"unknown", "WAV", "MP3", "AAC", "M4A", "FLAC", "OPUS", "VORBIS", "OGG"};
|
||||
enum : int { ST_NONE = 0, ST_WEBFILE = 1, ST_WEBSTREAM = 2 };
|
||||
const char* streamTypeStr[3] = {"NONE", "WEBFILE", "WEBSTREAM"};
|
||||
typedef enum { LEFTCHANNEL = 0, RIGHTCHANNEL = 1 } SampleIndex;
|
||||
typedef enum { LOWSHELF = 0, PEAKINGEQ = 1, HIFGSHELF = 2 } FilterType;
|
||||
|
||||
private:
|
||||
typedef struct _filter {
|
||||
float a0;
|
||||
float a1;
|
||||
float a2;
|
||||
float b1;
|
||||
float b2;
|
||||
} filter_t;
|
||||
|
||||
typedef struct _pis_array {
|
||||
int number;
|
||||
int pids[4];
|
||||
} pid_array;
|
||||
|
||||
public:
|
||||
struct audioSettings {
|
||||
uint16_t DMA_DESC_NUM = 32; // number of I2S DMA buffer
|
||||
uint16_t DMA_FRAME_NUM = 256; // number of frames in one DMA buffer
|
||||
uint16_t FREQ_LS_HZ = 500; // IIR Filter, lowshelf
|
||||
uint16_t FREQ_PEAK_HZ = 1800; // IIR Filter, peakingEQ
|
||||
uint16_t FREQ_HS_HZ = 6000; // IIR Filter, highshelf
|
||||
float QUALITY_SLOPE = 0.707; // Quality (all shelfes)
|
||||
uint16_t PEAK_HOLD_SAMPLES = 2000; // VU_meter, (2000) ca. 20 ms @ 48 kHz
|
||||
uint8_t PEAK_RELEASE = 1; // VU_meter, Fall rate
|
||||
bool VU_LEVEL = true; // true: vu meter is enabled
|
||||
bool IIR_FILTER = true; // true: IIR filter (highshelf, bandpass, lowshelf) are enabled
|
||||
bool SPECTRUM = false; // true: spectrum analyzer is enabled
|
||||
bool VOLUME_CONTROL = true; // true: volume and balance control is enabled
|
||||
float VOL_FADING_SPEED = 50.0; // mute, volume fading 1.0f (fast) ... 100.0f (slow)
|
||||
uint32_t BUFFER_TRESHOLD_HLS = 120000; // Level at which the HLS-TS stream starts and is reloaded
|
||||
} settings;
|
||||
|
||||
private:
|
||||
File m_audiofile;
|
||||
NetworkClient client;
|
||||
NetworkClientSecure clientsecure;
|
||||
NetworkClient* m_client = nullptr;
|
||||
|
||||
SemaphoreHandle_t mutex_playAudioData;
|
||||
SemaphoreHandle_t mutex_audioTask;
|
||||
SemaphoreHandle_t mutex_audioTaskIsDecoding;
|
||||
TaskHandle_t m_audioTaskHandle = nullptr;
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
|
||||
|
||||
i2s_chan_handle_t m_i2s_tx_handle = {};
|
||||
i2s_chan_config_t m_i2s_chan_cfg = {}; // stores I2S channel values
|
||||
i2s_std_config_t m_i2s_std_cfg = {}; // stores I2S driver values
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
std::vector<ps_ptr<char>> m_playlistContent; // m3u8 playlist buffer from responseHeader
|
||||
std::vector<ps_ptr<char>> m_playlistURL; // m3u8 streamURLs buffer
|
||||
std::deque<ps_ptr<char>> m_linesWithURL; // extract from m_playlistContent, contains URL and MediaSequenceNumber
|
||||
std::vector<ps_ptr<char>> m_linesWithEXTINF; // extract from m_playlistContent, contains length and metadata
|
||||
std::vector<ps_ptr<char>> m_syltLines; // SYLT line table
|
||||
std::vector<uint32_t> m_syltTimeStamp; // SYLT time table
|
||||
|
||||
static const uint8_t m_tsPacketSize = 188;
|
||||
static const uint8_t m_tsHeaderSize = 4;
|
||||
|
||||
std::unique_ptr<Decoder> m_decoder = {};
|
||||
ps_ptr<int32_t> m_outBuff; // Interleaved L/R
|
||||
ps_ptr<int32_t> m_resamplesBuff; // Interleaved L/R
|
||||
ps_ptr<char> m_metadataBuff; // icy-metadata max (16 * 256 + 1) bytes
|
||||
ps_ptr<char> m_httpRespHdrBuff; // store http response header
|
||||
ps_ptr<char> m_ibuff; // used in log_info()
|
||||
ps_ptr<char> m_lastHost; // Store the last URL to a webstream
|
||||
ps_ptr<char> m_currentHost; // can be changed by redirection or playlist
|
||||
ps_ptr<char> m_m3u8_host;
|
||||
ps_ptr<char> m_speechtxt; // stores tts text
|
||||
ps_ptr<char> m_streamTitle; // stores the last StreamTitle
|
||||
ps_ptr<char> m_streamURL; // stores the last StreamURL
|
||||
ps_ptr<char> m_playlistBuff;
|
||||
VolumeCurveFn m_volumeCurve = nullptr;
|
||||
|
||||
const uint16_t m_plsBuffEntryLen = 256; // length of each entry in playlistBuff
|
||||
int m_LFcount = 0; // Detection of end of header
|
||||
uint32_t m_avr_bitrate = 0; // average bitrate, median calculated by VBR
|
||||
uint32_t m_nominal_bitrate = 0; // given br from header
|
||||
uint32_t m_audioFilePosition = 0; // current position, counts every readed byte
|
||||
uint32_t m_audioDataReadPtr = 0; // used in playAudioData
|
||||
uint32_t m_audioFileSize = 0; // local and web files
|
||||
int m_readbytes = 0; // bytes read
|
||||
uint32_t m_metacount = 0; // counts down bytes between metadata
|
||||
int m_controlCounter = 0; // Status within readID3data() and readWaveHeader()
|
||||
uint8_t m_timeoutCounter = 0; // timeout counter
|
||||
uint8_t m_bitsPerSample = 16; // bitsPerSample
|
||||
uint8_t m_channels = 2; //
|
||||
uint8_t m_playlistFormat = 0; // M3U, PLS, ASX
|
||||
uint8_t m_codec = CODEC_NONE; //
|
||||
uint8_t m_m3u8Codec = CODEC_AAC; // codec of m3u8 stream
|
||||
uint8_t m_expectedCodec = CODEC_NONE; // set in connecttohost (e.g. http://url.mp3 -> CODEC_MP3)
|
||||
uint8_t m_expectedPlsFmt = FORMAT_NONE; // set in connecttohost (e.g. streaming01.m3u) -> FORMAT_M3U)
|
||||
uint8_t m_streamType = ST_NONE; //
|
||||
uint8_t m_ID3Size = 0; // lengt of ID3frame - ID3header
|
||||
uint8_t m_audioTaskCoreId = 0; //
|
||||
uint8_t m_M4A_objectType = 0; // set in read_M4A_Header
|
||||
uint8_t m_M4A_chConfig = 0; // set in read_M4A_Header
|
||||
uint16_t m_M4A_sampleRate = 0; // set in read_M4A_Header
|
||||
int16_t m_validSamples = 0; //
|
||||
int16_t m_curSample = 0; //
|
||||
uint16_t m_dataMode = 0; // Statemaschine
|
||||
uint16_t m_streamTitleHash = 0; // remember streamtitle, ignore multiple occurence in metadata
|
||||
uint16_t m_timeout_ms = 250; //
|
||||
uint16_t m_timeout_ms_ssl = 2700; //
|
||||
uint32_t m_metaint = 0; // Number of databytes between metadata
|
||||
uint32_t m_chunkcount = 0; // Counter for chunked transfer
|
||||
uint32_t m_t0 = 0; // store millis(), is needed for a small delay
|
||||
uint32_t m_bytesNotConsumed = 0; // pictures or something else that comes with the stream
|
||||
uint64_t m_lastGranulePosition = 0; // necessary to calculate the duration in OPUS and VORBIS
|
||||
int32_t m_resumeFilePos = -1; // the return value from stopSong(), (-1) is idle
|
||||
int32_t m_fileStartTime = -1; // may be set in connecttoFS()
|
||||
uint16_t m_m3u8_targetDuration = 10; //
|
||||
uint32_t m_stsz_numEntries = 0; // num of entries inside stsz atom (uint32_t)
|
||||
uint32_t m_stsz_position = 0; // pos of stsz atom within file
|
||||
uint32_t m_haveNewFilePos = 0; // user changed the file position
|
||||
bool m_f_alt_user_agent = false; // use default or alternative user agent
|
||||
bool m_f_I2S_init = false; //
|
||||
bool m_f_unsync = false; // set within ID3 tag but not used
|
||||
bool m_f_exthdr = false; // ID3 extended header
|
||||
bool m_f_ssl = false; //
|
||||
bool m_f_running = false; //
|
||||
bool m_f_firstCall = false; // InitSequence for processWebstream and processLokalFile
|
||||
bool m_f_firstLoop = false; // InitSequence in loop()
|
||||
bool m_f_firstPlayCall = false; // InitSequence for playAudioData
|
||||
bool m_f_ID3v1TagFound = false; // ID3v1 tag found
|
||||
bool m_f_chunked = false; // Station provides chunked transfer
|
||||
bool m_f_firstmetabyte = false; // True if first metabyte (counter)
|
||||
bool m_f_playing = false; // valid mp3 stream recognized
|
||||
bool m_f_tts = false; // text to speech
|
||||
bool m_f_ogg = false; // OGG stream
|
||||
bool m_f_forceMono = false; // if true stereo -> mono
|
||||
bool m_f_rtsp = false; // set if RTSP is used (m3u8 stream)
|
||||
bool m_f_m3u8data = false; // used in processM3U8entries
|
||||
bool m_f_continue = false; // next m3u8 chunk is available
|
||||
bool m_f_ts = true; // transport stream
|
||||
bool m_f_m4aID3dataAreRead = false; // has the m4a-ID3data already been read?
|
||||
bool m_f_psramFound = false; // set in constructor, result of psramInit()
|
||||
bool m_f_timeout = false; //
|
||||
bool m_f_audioTaskIsRunning = false; //
|
||||
bool m_f_allDataReceived = false; //
|
||||
bool m_f_stream = false; // stream ready for output?
|
||||
bool m_f_decode_ready = false; // if true data for decode are ready
|
||||
bool m_f_eof = false; // end of file
|
||||
bool m_f_lockInBuffer = false; // lock inBuffer for manipulation
|
||||
bool m_f_audioTaskIsDecoding = false; //
|
||||
bool m_f_acceptRanges = false; //
|
||||
bool m_f_reset_m3u8Codec = true; // reset codec for m3u8 stream
|
||||
bool m_f_connectionClose = false; // set in parseHttpResponseHeader
|
||||
bool m_f_i2s_channel_enabled = false; // true if enabled
|
||||
uint32_t m_audioFileDuration = 0; // seconds
|
||||
uint32_t m_audioCurrentTime = 0; // seconds
|
||||
uint32_t m_audioDataStart = 0; // in bytes
|
||||
OutputSR_t m_output_sr = SR_ORIGIN; // output samplerate
|
||||
size_t m_audioDataSize = 0; //
|
||||
size_t m_ibuffSize = 0; // log buffer size for audio_info()
|
||||
size_t m_i2s_bytesWritten = 0; // set in i2s_write() but not used
|
||||
|
||||
pid_array m_pidsOfPMT;
|
||||
int16_t m_pidOfAAC;
|
||||
uint8_t m_packetBuff[m_tsPacketSize];
|
||||
int16_t m_pesDataLength = 0;
|
||||
|
||||
// audiolib structs
|
||||
audiolib::ID3Hdr_t m_ID3Hdr;
|
||||
audiolib::pwsHLS_t m_pwsHLS;
|
||||
audiolib::pplM3u8_t m_pplM3U8;
|
||||
audiolib::m4aHdr_t m_m4aHdr;
|
||||
audiolib::plCh_t m_plCh;
|
||||
audiolib::lVar_t m_lVar;
|
||||
audiolib::prlf_t m_prlf;
|
||||
audiolib::cat_t m_cat;
|
||||
audiolib::ifCh_t m_ifCh;
|
||||
audiolib::tspp_t m_tspp;
|
||||
audiolib::pwst_t m_pwst;
|
||||
audiolib::gchs_t m_gchs;
|
||||
audiolib::pwf_t m_pwf;
|
||||
audiolib::pad_t m_pad;
|
||||
audiolib::sbyt_t m_sbyt;
|
||||
audiolib::rmet_t m_rmet;
|
||||
audiolib::pwsts_t m_pwsst;
|
||||
audiolib::rwh_t m_rwh;
|
||||
audiolib::rflh_t m_rflh;
|
||||
audiolib::phreh_t m_phreh;
|
||||
audiolib::phrah_t m_phrah;
|
||||
audiolib::sdet_t m_sdet;
|
||||
audiolib::fnsy_t m_fnsy;
|
||||
audiolib::audioItems_t m_audio_items;
|
||||
audiolib::vu_items_t m_vu_items;
|
||||
audiolib::fft_items_t m_fft_items;
|
||||
audiolib::i2s_items_t m_i2s_items;
|
||||
audiolib::resampler_t m_resampler;
|
||||
audiolib::info_queue_t m_info_queue;
|
||||
audiolib::icy_items_t m_icy_items;
|
||||
|
||||
// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
|
||||
public:
|
||||
template <typename... Args> static bool info(Audio& instance, event_t e, const char* fmt, Args&&... args) {
|
||||
std::lock_guard<std::mutex> lock(instance.mutex_info);
|
||||
if (!fmt) return false;
|
||||
if (!audio_info_callback) return false;
|
||||
|
||||
ps_ptr<char> result;
|
||||
result.assignf(fmt, std::forward<Args>(args)...);
|
||||
if (!result.get()) return false;
|
||||
|
||||
auto extract_last_number = [](std::string_view s) -> std::optional<int32_t> {
|
||||
auto is_space = [](char c) { return std::isspace(static_cast<unsigned char>(c)); };
|
||||
auto is_digit = [](char c) { return std::isdigit(static_cast<unsigned char>(c)); };
|
||||
|
||||
auto it = s.end();
|
||||
// skip trailing whitespace
|
||||
while (it != s.begin() && is_space(*(it - 1))) { --it; }
|
||||
auto end = it;
|
||||
// Reading numbers backwards
|
||||
while (it != s.begin() && is_digit(*(it - 1))) { --it; }
|
||||
// optional sign
|
||||
if (it != s.begin()) {
|
||||
char c = *(it - 1);
|
||||
if (c == '+' || c == '-') { --it; }
|
||||
}
|
||||
|
||||
// found nothing?
|
||||
if (it == end) { return std::nullopt; }
|
||||
// There must be a leading space or a space before the number
|
||||
if (it != s.begin() && !is_space(*(it - 1))) { return std::nullopt; }
|
||||
int32_t value{};
|
||||
auto [ptr, ec] = std::from_chars(it, end, value);
|
||||
|
||||
// Was the full parse successful?
|
||||
if (ec == std::errc{} && ptr == end) { return value; }
|
||||
return std::nullopt;
|
||||
};
|
||||
|
||||
std::vector<uint32_t> v;
|
||||
v.push_back(0);
|
||||
instance.m_info_queue.msg.emplace_front(result);
|
||||
instance.m_info_queue.s.emplace_front(eventStr[e]);
|
||||
instance.m_info_queue.arg1.emplace_front(extract_last_number(result.c_get()).value_or(0));
|
||||
instance.m_info_queue.arg2.emplace_front(0);
|
||||
instance.m_info_queue.vec.emplace_front(v);
|
||||
instance.m_info_queue.e.emplace_front((uint8_t)e);
|
||||
result.reset();
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool info(Audio& instance, event_t e, std::vector<uint32_t>& v) {
|
||||
if (!audio_info_callback) return false;
|
||||
std::lock_guard<std::mutex> lock(instance.mutex_info); // lock mutex
|
||||
ps_ptr<char> apic;
|
||||
apic.assignf("APIC found at pos {}", v[0]);
|
||||
// msg_t i;
|
||||
// i.msg = apic.c_get();
|
||||
// i.e = e;
|
||||
// i.s = eventStr[e];
|
||||
// i.i2s_num = instance.m_i2s_items.i2s_num;
|
||||
// i.vec = v;
|
||||
// audio_info_callback(i);
|
||||
|
||||
instance.m_info_queue.msg.emplace_front(apic);
|
||||
instance.m_info_queue.s.emplace_front(eventStr[e]);
|
||||
instance.m_info_queue.arg1.emplace_front(0);
|
||||
instance.m_info_queue.arg2.emplace_front(0);
|
||||
instance.m_info_queue.vec.emplace_front(v);
|
||||
instance.m_info_queue.e.emplace_front((uint8_t)e);
|
||||
return true;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
template <typename... Args> static void AUDIO_LOG_IMPL(uint8_t level, const char* path, int line, const char* func, const char* fmt, Args&&... args) {
|
||||
|
||||
#define ANSI_ESC_RESET "\033[0m"
|
||||
#define ANSI_ESC_BLACK "\033[30m"
|
||||
#define ANSI_ESC_RED "\033[31m"
|
||||
#define ANSI_ESC_GREEN "\033[32m"
|
||||
#define ANSI_ESC_YELLOW "\033[33m"
|
||||
#define ANSI_ESC_BLUE "\033[34m"
|
||||
#define ANSI_ESC_MAGENTA "\033[35m"
|
||||
#define ANSI_ESC_CYAN "\033[36m"
|
||||
#define ANSI_ESC_WHITE "\033[37m"
|
||||
|
||||
ps_ptr<char> logStr = path;
|
||||
while (logStr.contains("/")) { logStr.remove_before('/', false); }
|
||||
logStr.appendf(":{} {}] ", line, func ? func : "");
|
||||
logStr.insert("[", 0);
|
||||
|
||||
if (level == 1 && CORE_DEBUG_LEVEL >= 1) {
|
||||
logStr.append(ANSI_ESC_RED);
|
||||
} else if (level == 2 && CORE_DEBUG_LEVEL >= 2) {
|
||||
logStr.append(ANSI_ESC_YELLOW);
|
||||
} else if (level == 3 && CORE_DEBUG_LEVEL >= 3) {
|
||||
logStr.append(ANSI_ESC_GREEN);
|
||||
} else if (level == 4 && CORE_DEBUG_LEVEL >= 4) {
|
||||
logStr.append(ANSI_ESC_CYAN);
|
||||
} // debug
|
||||
else if (level == 5 && CORE_DEBUG_LEVEL >= 4) {
|
||||
logStr.append(ANSI_ESC_WHITE);
|
||||
} // verbose
|
||||
else
|
||||
return;
|
||||
|
||||
int add_len = std::snprintf(nullptr, 0, fmt, std::forward<Args>(args)...);
|
||||
if (add_len > 0) {
|
||||
logStr.appendf(fmt, std::forward<Args>(args)...); // <-- neue appendf()
|
||||
}
|
||||
logStr.append(ANSI_ESC_RESET);
|
||||
|
||||
msg_t msg;
|
||||
msg.msg = logStr.get();
|
||||
const char* tag[7] = {"", "LOGE", "LOGW", "LOGI", "LOGD", "LOGV", ""};
|
||||
msg.s = tag[level];
|
||||
msg.e = evt_log;
|
||||
|
||||
if (audio_info_callback)
|
||||
audio_info_callback(msg);
|
||||
else {
|
||||
if (level == 1)
|
||||
log_e("%s", logStr.c_get());
|
||||
else if (level == 2)
|
||||
log_w("%s", logStr.c_get());
|
||||
else if (level == 3)
|
||||
log_i("%s", logStr.c_get());
|
||||
else if (level == 4)
|
||||
log_d("%s", logStr.c_get());
|
||||
else
|
||||
log_v("%s", logStr.c_get());
|
||||
}
|
||||
logStr.reset();
|
||||
}
|
||||
|
||||
// Macro for comfortable calls
|
||||
#define AUDIO_LOG_ERROR(fmt, ...) AUDIO_LOG_IMPL(1, __FILE__, __LINE__, __func__, fmt, ##__VA_ARGS__)
|
||||
#define AUDIO_LOG_WARN(fmt, ...) AUDIO_LOG_IMPL(2, __FILE__, __LINE__, __func__, fmt, ##__VA_ARGS__)
|
||||
#define AUDIO_LOG_INFO(fmt, ...) AUDIO_LOG_IMPL(3, __FILE__, __LINE__, __func__, fmt, ##__VA_ARGS__)
|
||||
#define AUDIO_LOG_DEBUG(fmt, ...) AUDIO_LOG_IMPL(4, __FILE__, __LINE__, __func__, fmt, ##__VA_ARGS__)
|
||||
};
|
||||
// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
|
||||
// 📌📌📌 D E C O D E R 📌📌📌
|
||||
// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
|
||||
class Decoder {
|
||||
public:
|
||||
virtual ~Decoder() = default;
|
||||
virtual bool init() = 0;
|
||||
virtual void clear() = 0;
|
||||
virtual void reset() = 0;
|
||||
virtual bool isValid() = 0;
|
||||
virtual int32_t findSyncWord(uint8_t* buf, int32_t nBytes) = 0;
|
||||
virtual uint8_t getChannels() = 0;
|
||||
virtual uint32_t getSampleRate() = 0;
|
||||
virtual uint8_t getBitsPerSample() = 0;
|
||||
virtual uint32_t getBitRate() = 0;
|
||||
virtual uint32_t getAudioDataStart() = 0;
|
||||
virtual uint32_t getAudioFileDuration() = 0;
|
||||
virtual uint32_t getOutputSamples() = 0;
|
||||
virtual int32_t decode(uint8_t* inbuf, int32_t* bytesLeft, int32_t* outbuf1) = 0;
|
||||
virtual void setRawBlockParams(uint8_t param1, uint32_t param2, uint8_t param3, uint32_t param4, uint32_t param5) = 0;
|
||||
virtual const char* getStreamTitle();
|
||||
virtual const char* whoIsIt();
|
||||
virtual std::vector<uint32_t> getMetadataBlockPicture() = 0;
|
||||
virtual const char* arg1() = 0; // decoder specific
|
||||
virtual const char* arg2() = 0; // decoder specific
|
||||
virtual int32_t val1() = 0; // decoder specific
|
||||
virtual int32_t val2() = 0; // decoder specific
|
||||
|
||||
protected:
|
||||
Decoder(Audio& audioRef) : audio(audioRef) {}
|
||||
Audio& audio; // protected reference, usable by all subclasses
|
||||
private:
|
||||
Decoder() = delete; // Deactivate default constructor explicitly (optional but good against abuse)
|
||||
};
|
||||
// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
|
||||
// 📌📌📌 A U T O L O G G E R for detecting memory leaks 📌📌📌
|
||||
// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
|
||||
/* usage
|
||||
void myFunction(){
|
||||
HEAP_GUARD(); // <--- automatic check
|
||||
my code ...
|
||||
my code ...
|
||||
}
|
||||
|
||||
{ // Or in small critical code blocks:
|
||||
HEAP_GUARD();
|
||||
fill_content(inbuf, to_read);
|
||||
}
|
||||
*/
|
||||
|
||||
struct _HeapGuardSnapshot {
|
||||
size_t free_dram_before{};
|
||||
size_t free_psram_before{};
|
||||
bool integrity_before{};
|
||||
const char* func{};
|
||||
bool active{false};
|
||||
|
||||
_HeapGuardSnapshot(const char* f) : func(f), active(true) {
|
||||
free_dram_before = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
|
||||
free_psram_before = heap_caps_get_free_size(MALLOC_CAP_SPIRAM);
|
||||
integrity_before = heap_caps_check_integrity_all(true);
|
||||
if (!integrity_before) {
|
||||
printf(ANSI_ESC_RED "HEAPGUARD [%s] ❌ Heap corruption detected BEFORE!" ANSI_ESC_RESET "\n", func);
|
||||
} else {
|
||||
printf(ANSI_ESC_GREEN "HEAPGUARD [%s] Begin: DRAM=%u, PSRAM=%u" ANSI_ESC_RESET "\n", func, (unsigned)free_dram_before, (unsigned)free_psram_before);
|
||||
}
|
||||
}
|
||||
|
||||
~_HeapGuardSnapshot() {
|
||||
if (!active) return; // falls moved / deaktiviert
|
||||
size_t free_dram_after = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
|
||||
size_t free_psram_after = heap_caps_get_free_size(MALLOC_CAP_SPIRAM);
|
||||
bool ok = heap_caps_check_integrity_all(true);
|
||||
|
||||
int delta_dram = (int)(free_dram_after - free_dram_before);
|
||||
int delta_psram = (int)(free_psram_after - free_psram_before);
|
||||
|
||||
if (!ok) {
|
||||
printf(ANSI_ESC_RED "HEAPGUARD [%s] ❌ Heap corruption detected AFTER!" ANSI_ESC_RESET "\n", func);
|
||||
} else {
|
||||
printf(ANSI_ESC_GREEN "HEAPGUARD [%s] ✅ Heap OK | ΔDRAM=%+d | ΔPSRAM=%+d" ANSI_ESC_RESET "\n", func, delta_dram, delta_psram);
|
||||
}
|
||||
}
|
||||
};
|
||||
#define HEAP_GUARD() _HeapGuardSnapshot _heapguard_instance_##__LINE__(__func__)
|
||||
// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
|
||||
// 📌📌📌 A U T O P R O F I L E R RAII-class for timekeeping 📌📌📌
|
||||
// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
|
||||
/* usage
|
||||
void decodeNative(uint8_t* inbuf, int bytesLeft, uint8_t* outbuf) {
|
||||
PROFILE_SCOPE_N(1000); // automatically measures 1000 views on average
|
||||
|
||||
// ... my code ...
|
||||
}
|
||||
|
||||
{ // Or in small critical code blocks:
|
||||
PROFILE_SCOPE_N(100); // measures this block over 100 runs
|
||||
do_fft_processing(data);
|
||||
}
|
||||
*/
|
||||
class _AutoProfiler {
|
||||
public:
|
||||
_AutoProfiler(const char* name, uint32_t report_interval) : tag(name), N(report_interval) { start = esp_timer_get_time(); }
|
||||
|
||||
~_AutoProfiler() {
|
||||
uint64_t elapsed = esp_timer_get_time() - start;
|
||||
sum += elapsed;
|
||||
count++;
|
||||
if(max_dt < elapsed) max_dt = elapsed;
|
||||
|
||||
if (count >= N) {
|
||||
double avg_us = (double)sum / count;
|
||||
printf(ANSI_ESC_CYAN "PROFILER [%s] avg: %.2f µs over %lu runs, max %lu µs" ANSI_ESC_RESET "\n", tag, avg_us, count, max_dt);
|
||||
sum = 0;
|
||||
count = 0;
|
||||
max_dt = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const char* tag;
|
||||
uint32_t N;
|
||||
uint64_t start;
|
||||
static inline uint32_t max_dt = 0;
|
||||
static inline uint64_t sum = 0;
|
||||
static inline uint32_t count = 0;
|
||||
};
|
||||
|
||||
// Macro for automatic use with function name
|
||||
#define PROFILE_SCOPE_N(N) _AutoProfiler _prof_instance_##__LINE__(__func__, N)
|
||||
// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
|
||||
Reference in New Issue
Block a user