/* * Audio.h * * Created on: Oct 28,2018 * Updated on: Nov 22,2022 * Author: Wolle (schreibfaul1) */ //#define SDFATFS_USED // activate for SdFat #pragma once #pragma GCC optimize ("Ofast") #include #include #include #include #include #include #include #include #ifndef AUDIO_NO_SD_FS #include #ifdef SDFATFS_USED #include // https://github.com/greiman/SdFat #else #include #include #include #include #include #endif // SDFATFS_USED #ifdef SDFATFS_USED //typedef File32 File; typedef FsFile File; namespace fs { class FS : public SdFat { public: bool begin(SdCsPin_t csPin = SS, uint32_t maxSck = SD_SCK_MHZ(25)) { return SdFat::begin(csPin, maxSck); } }; class SDFATFS : public fs::FS { public: // sdcard_type_t cardType(); uint64_t cardSize() { return totalBytes(); } uint64_t usedBytes() { // set SdFatConfig MAINTAIN_FREE_CLUSTER_COUNT non-zero. Then only the first call will take time. return (uint64_t)(clusterCount() - freeClusterCount()) * (uint64_t)bytesPerCluster(); } uint64_t totalBytes() { return (uint64_t)clusterCount() * (uint64_t)bytesPerCluster(); } }; } extern fs::SDFATFS SD_SDFAT; using namespace fs; #define SD SD_SDFAT #endif //SDFATFS_USED #endif // AUDIO_NO_SD_FS using namespace std; extern __attribute__((weak)) void audio_info(const char *); extern __attribute__((weak)) void audio_id3data(const char *); // ID3 metadata #ifndef AUDIO_NO_SD_FS extern __attribute__((weak)) void audio_id3image(File& file, const size_t pos, const size_t size); // ID3 metadata image #endif extern __attribute__((weak)) void audio_eof_mp3(const char*); //end of mp3 file extern __attribute__((weak)) void audio_showstreamtitle(const char*); extern __attribute__((weak)) void audio_showstation(const char *); extern __attribute__((weak)) void audio_bitrate(const char*); extern __attribute__((weak)) void audio_commercial(const char*); extern __attribute__((weak)) void audio_icyurl(const char*); extern __attribute__((weak)) void audio_icydescription(const char*); extern __attribute__((weak)) void audio_lasthost(const char*); extern __attribute__((weak)) void audio_eof_speech(const char*); extern __attribute__((weak)) void audio_eof_stream(const char*); // The webstream comes to an end extern __attribute__((weak)) void audio_process_extern(int16_t* buff, uint16_t len, bool *continueI2S); // record audiodata or send via BT extern __attribute__((weak)) void audio_process_i2s(uint32_t* sample, bool *continueI2S); // record audiodata or send via BT #define AUDIO_INFO(...) {char buff[512 + 64]; sprintf(buff,__VA_ARGS__); if(audio_info) audio_info(buff);} //---------------------------------------------------------------------------------------------------------------------- class AudioBuffer { // AudioBuffer will be allocated in PSRAM, If PSRAM not available or has not enough space AudioBuffer will be // allocated in FlashRAM with reduced size // // m_buffer m_readPtr m_writePtr m_endPtr // | |<------dataLength------->|<------ writeSpace ----->| // ▼ ▼ ▼ ▼ // --------------------------------------------------------------------------------------------------------------- // | <--m_buffSize--> | <--m_resBuffSize --> | // --------------------------------------------------------------------------------------------------------------- // |<-----freeSpace------->| |<------freeSpace-------->| // // // // if the space between m_readPtr and buffend < m_resBuffSize copy data from the beginning to resBuff // so that the mp3/aac/flac frame is always completed // // m_buffer m_writePtr m_readPtr m_endPtr // | |<-------writeSpace------>|<--dataLength-->| // ▼ ▼ ▼ ▼ // --------------------------------------------------------------------------------------------------------------- // | <--m_buffSize--> | <--m_resBuffSize --> | // --------------------------------------------------------------------------------------------------------------- // |<--- ------dataLength-- ------>|<-------freeSpace------->| // // public: AudioBuffer(size_t maxBlockSize = 0); // constructor ~AudioBuffer(); // frees the buffer size_t init(); // set default values bool isInitialized() { return m_f_init; }; void setBufsize(int ram, int psram); void changeMaxBlockSize(uint16_t mbs); // is default 1600 for mp3 and aac, set 16384 for FLAC uint16_t getMaxBlockSize(); // returns maxBlockSize 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 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 uint32_t getWritePos(); // write position relative to the beginning uint32_t getReadPos(); // read position relative to the beginning void resetBuffer(); // restore defaults bool havePSRAM() { return m_f_psram; }; protected: size_t m_buffSizePSRAM = 300000; // most webstreams limit the advance to 100...300Kbytes size_t m_buffSizeRAM = 1600 * 5; size_t m_buffSize = 0; size_t m_freeSpace = 0; size_t m_writeSpace = 0; size_t m_dataLength = 0; size_t m_resBuffSizeRAM = 1600; // reserved buffspace, >= one mp3 frame size_t m_resBuffSizePSRAM = 4096 * 4; // reserved buffspace, >= one flac frame size_t m_maxBlockSize = 1600; uint8_t* m_buffer = NULL; uint8_t* m_writePtr = NULL; uint8_t* m_readPtr = NULL; uint8_t* m_endPtr = NULL; bool m_f_start = true; bool m_f_init = false; bool m_f_psram = false; // PSRAM is available (and used...) }; //---------------------------------------------------------------------------------------------------------------------- class Audio : private AudioBuffer{ AudioBuffer InBuff; // instance of input buffer public: Audio(bool internalDAC = false, uint8_t channelEnabled = 3, uint8_t i2sPort = I2S_NUM_0); // #99 ~Audio(); void setBufsize(int rambuf_sz, int psrambuf_sz); bool connecttohost(const char* host, const char* user = "", const char* pwd = ""); bool connecttospeech(const char* speech, const char* lang); bool connecttomarytts(const char* speech, const char* lang, const char* voice); #ifndef AUDIO_NO_SD_FS bool connecttoFS(fs::FS &fs, const char* path, uint32_t resumeFilePos = 0); bool connecttoSD(const char* path, uint32_t resumeFilePos = 0); #endif // AUDIO_NO_SD_FS bool setFileLoop(bool input);//TEST loop void setConnectionTimeout(uint16_t timeout_ms, uint16_t timeout_ms_ssl); bool setAudioPlayPosition(uint16_t sec); bool setFilePos(uint32_t pos); bool audioFileSeek(const float speed); bool setTimeOffset(int sec); bool setPinout(uint8_t BCLK, uint8_t LRC, uint8_t DOUT, int8_t DIN = I2S_PIN_NO_CHANGE, int8_t MCK = I2S_PIN_NO_CHANGE); bool pauseResume(); bool isRunning() {return m_f_running;} void loop(); uint32_t stopSong(); void forceMono(bool m); void setBalance(int8_t bal = 0); void setVolume(uint8_t vol); uint8_t getVolume(); uint8_t getI2sPort(); uint32_t getAudioDataStartPos(); uint32_t getFileSize(); uint32_t getFilePos(); uint32_t getSampleRate(); uint8_t getBitsPerSample(); uint8_t getChannels(); uint32_t getBitRate(bool avg = false); uint32_t getAudioFileDuration(); uint32_t getAudioCurrentTime(); uint32_t getTotalPlayingTime(); esp_err_t i2s_mclk_pin_select(const uint8_t pin); uint32_t inBufferFilled(); // returns the number of stored bytes in the inputbuffer uint32_t inBufferFree(); // returns the number of free bytes in the inputbuffer void setTone(int8_t gainLowPass, int8_t gainBandPass, int8_t gainHighPass); void setI2SCommFMT_LSB(bool commFMT); int getCodec() {return m_codec;} const char *getCodecname() {return codecname[m_codec];} private: #ifndef ESP_ARDUINO_VERSION_VAL #define ESP_ARDUINO_VERSION_MAJOR 0 #define ESP_ARDUINO_VERSION_MINOR 0 #define ESP_ARDUINO_VERSION_PATCH 0 #endif void UTF8toASCII(char* str); bool latinToUTF8(char* buff, size_t bufflen); void setDefaults(); // free buffers and set defaults void initInBuff(); bool httpPrint(const char* host); #ifndef AUDIO_NO_SD_FS void processLocalFile(); #endif // AUDIO_NO_SD_FS void processWebStream(); void processWebFile(); void processWebStreamTS(); void processWebStreamHLS(); void playAudioData(); bool readPlayListData(); const char* parsePlaylist_M3U(); const char* parsePlaylist_PLS(); const char* parsePlaylist_ASX(); const char* parsePlaylist_M3U8(); bool STfromEXTINF(char* str); void showCodecParams(); int findNextSync(uint8_t* data, size_t len); int sendBytes(uint8_t* data, size_t len); void compute_audioCurrentTime(int bd); void printDecodeError(int r); void showID3Tag(const char* tag, const char* val); void unicode2utf8(char* buff, uint32_t len); 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); int read_OGG_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); bool setBitrate(int br); bool playChunk(); bool playSample(int16_t sample[2]) ; void playI2Sremains(); int32_t Gain(int16_t s[2]); bool fill_InputBuf(); void showstreamtitle(const char* ml); bool parseContentType(char* ct); bool parseHttpResponseHeader(); bool initializeDecoder(); esp_err_t I2Sstart(uint8_t i2s_num); esp_err_t I2Sstop(uint8_t i2s_num); void urlencode(char* buff, uint16_t buffLen, bool spacesOnly = false); int16_t* IIR_filterChain0(int16_t iir_in[2], bool clear = false); int16_t* IIR_filterChain1(int16_t* iir_in, bool clear = false); int16_t* IIR_filterChain2(int16_t* iir_in, bool clear = false); inline void setDatamode(uint8_t dm){m_datamode=dm;} inline uint8_t getDatamode(){return m_datamode;} inline uint32_t streamavail(){ return _client ? _client->available() : 0;} void IIR_calculateCoefficients(int8_t G1, int8_t G2, int8_t G3); bool ts_parsePacket(uint8_t* packet, uint8_t* packetStart, uint8_t* packetLength); //+++ W E B S T R E A M - H E L P F U N C T I O N S +++ uint16_t readMetadata(uint16_t b, bool first = false); size_t chunkedDataTransfer(uint8_t* bytes); bool readID3V1Tag(); void slowStreamDetection(uint32_t inBuffFilled, uint32_t maxFrameSize); void lostStreamDetection(uint32_t bytesAvail); #ifndef AUDIO_NO_SD_FS void seek_m4a_stsz(); uint32_t m4a_correctResumeFilePos(uint32_t resumeFilePos); uint32_t flac_correctResumeFilePos(uint32_t resumeFilePos); uint32_t mp3_correctResumeFilePos(uint32_t resumeFilePos); #endif // AUDIO_NO_SD_FS //++++ implement several function with respect to the index of string ++++ void trim(char *s) { //fb trim in place char *pe; char *p = s; while ( isspace(*p) ) p++; //left pe = p; //right while ( *pe != '\0' ) pe++; do { pe--; } while ( (pe > p) && isspace(*pe) ); if (p == s) { *++pe = '\0'; } else { //move while ( p <= pe ) *s++ = *p++; *s = '\0'; } } bool startsWith (const char* base, const char* str) { //fb char c; while ( (c = *str++) != '\0' ) if (c != *base++) return false; return true; } bool endsWith (const char* base, const char* str) { //fb int slen = strlen(str) - 1; const char *p = base + strlen(base) - 1; while(p > base && isspace(*p)) p--; // rtrim p -= slen; if (p < base) return false; return (strncmp(p, str, slen) == 0); } int indexOf (const char* base, const char* str, int startIndex = 0) { //fb const char *p = base; for (; startIndex > 0; startIndex--) if (*p++ == '\0') return -1; char* pos = strstr(p, str); if (pos == nullptr) return -1; return pos - base; } int indexOf (const char* base, char ch, int startIndex = 0) { //fb const char *p = base; for (; startIndex > 0; startIndex--) if (*p++ == '\0') return -1; char *pos = strchr(p, ch); if (pos == nullptr) return -1; return pos - base; } int lastIndexOf(const char* haystack, const char* needle) { //fb int nlen = strlen(needle); if (nlen == 0) return -1; const char *p = haystack - nlen + strlen(haystack); while (p >= haystack) { int i = 0; while (needle[i] == p[i]) if (++i == nlen) return p - haystack; p--; } return -1; } int lastIndexOf(const char* haystack, const char needle) { //fb const char *p = strrchr(haystack, needle); return (p ? p - haystack : -1); } int specialIndexOf (uint8_t* base, const char* str, int baselen, bool exact = false){ int result; // seek for str in buffer or in header up to baselen, not nullterninated if (strlen(str) > baselen) return -1; // if exact == true seekstr in buffer must have "\0" at the end for (int i = 0; i < baselen - strlen(str); i++){ result = i; for (int j = 0; j < strlen(str) + exact; j++){ if (*(base + i + j) != *(str + j)){ result = -1; break; } } if (result >= 0) break; } return result; } // some other functions size_t bigEndian(uint8_t* base, uint8_t numBytes, uint8_t shiftLeft = 8){ uint64_t result = 0; if(numBytes < 1 || numBytes > 8) return 0; for (int i = 0; i < numBytes; i++) { result += *(base + i) << (numBytes -i - 1) * shiftLeft; } if(result > SIZE_MAX) {log_e("range overflow"); result = 0;} // overflow return (size_t)result; } bool b64encode(const char* source, uint16_t sourceLength, char* dest){ size_t size = base64_encode_expected_len(sourceLength) + 1; char * buffer = (char *) malloc(size); if(buffer) { base64_encodestate _state; base64_init_encodestate(&_state); int len = base64_encode_block(&source[0], sourceLength, &buffer[0], &_state); len = base64_encode_blockend((buffer + len), &_state); memcpy(dest, buffer, strlen(buffer)); dest[strlen(buffer)] = '\0'; free(buffer); return true; } return false; } size_t urlencode_expected_len(const char* source){ size_t expectedLen = strlen(source); for(int i = 0; i < strlen(source); i++) { if(isalnum(source[i])){;} else expectedLen += 2; } return expectedLen; } void vector_clear_and_shrink(vector&vec){ uint size = vec.size(); for (int i = 0; i < size; i++) { if(vec[i]){ free(vec[i]); vec[i] = NULL; } } vec.clear(); vec.shrink_to_fit(); } uint32_t simpleHash(const char* str){ if(str == NULL) return 0; uint32_t hash = 0; for(int i=0; i m_playlistContent; // m3u8 playlist buffer std::vector m_playlistURL; // m3u8 streamURLs buffer std::vector m_hashQueue; const size_t m_frameSizeWav = 1024; const size_t m_frameSizeMP3 = 1600; const size_t m_frameSizeAAC = 1600; const size_t m_frameSizeFLAC = 4096 * 4; static const uint8_t m_tsPacketSize = 188; static const uint8_t m_tsHeaderSize = 4; char* m_chbuf = NULL; uint16_t m_chbufSize = 0; // will set in constructor (depending on PSRAM) char m_lastHost[512]; // Store the last URL to a webstream char* m_playlistBuff = NULL; // stores playlistdata const uint16_t m_plsBuffEntryLen = 256; // length of each entry in playlistBuff filter_t m_filter[3]; // digital filters int m_LFcount = 0; // Detection of end of header uint32_t m_sampleRate=16000; uint32_t m_bitRate=0; // current bitrate given fom decoder uint32_t m_avr_bitrate = 0; // average bitrate, median computed by VBR 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() int8_t m_balance = 0; // -16 (mute left) ... +16 (mute right) uint8_t m_vol=64; // volume uint8_t m_bitsPerSample = 16; // bitsPerSample uint8_t m_channels = 2; uint8_t m_i2s_num = I2S_NUM_0; // I2S_NUM_0 or I2S_NUM_1 uint8_t m_playlistFormat = 0; // M3U, PLS, ASX uint8_t m_codec = CODEC_NONE; // 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_filterType[2]; // lowpass, highpass uint8_t m_streamType = ST_NONE; uint8_t m_ID3Size = 0; // lengt of ID3frame - ID3header int16_t m_outBuff[2048*2]; // Interleaved L/R 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; uint8_t m_flacBitsPerSample = 0; // bps should be 16 uint8_t m_flacNumChannels = 0; // can be read out in the FLAC file header uint32_t m_flacSampleRate = 0; // can be read out in the FLAC file header uint16_t m_flacMaxFrameSize = 0; // can be read out in the FLAC file header uint16_t m_flacMaxBlockSize = 0; // can be read out in the FLAC file header uint32_t m_flacTotalSamplesInStream = 0; // can be read out in the FLAC file header 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_contentlength = 0; // Stores the length if the stream comes from fileserver uint32_t m_bytesNotDecoded = 0; // pictures or something else that comes with the stream uint32_t m_PlayingStartTime = 0; // Stores the milliseconds after the start of the audio uint32_t m_resumeFilePos = 0; // the return value from stopSong() can be entered here 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 bool m_f_metadata = false; // assume stream without metadata 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_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_loop = false; // Set if audio file should loop bool m_f_forceMono = false; // if true stereo -> mono bool m_f_internalDAC = false; // false: output vis I2S, true output via internal DAC bool m_f_rtsp = false; // set if RTSP is used (m3u8 stream) bool m_f_m3u8data = false; // used in processM3U8entries bool m_f_Log = false; // set in platformio.ini -DAUDIO_LOG and -DCORE_DEBUG_LEVEL=3 or 4 bool m_f_continue = false; // next m3u8 chunk is available bool m_f_ts = true; // transport stream uint8_t m_f_channelEnabled = 3; // internal DAC, both channels uint32_t m_audioFileDuration = 0; float m_audioCurrentTime = 0; uint32_t m_audioDataStart = 0; // in bytes size_t m_audioDataSize = 0; // float m_filterBuff[3][2][2][2]; // IIR filters memory for Audio DSP size_t m_i2s_bytesWritten = 0; // set in i2s_write() but not used size_t m_file_size = 0; // size of the file uint16_t m_filterFrequency[2]; int8_t m_gain0 = 0; // cut or boost filters (EQ) int8_t m_gain1 = 0; int8_t m_gain2 = 0; pid_array m_pidsOfPMT; int16_t m_pidOfAAC; uint8_t m_packetBuff[m_tsPacketSize]; int16_t m_pesDataLength = 0; }; //----------------------------------------------------------------------------------------------------------------------