From 85252605971869f97e23df4bf7a8b9fede234f9c Mon Sep 17 00:00:00 2001 From: Mykola Stepanets Date: Sat, 13 Sep 2025 21:32:40 +0300 Subject: [PATCH 1/4] split processWebStream/processWebFile --- yoRadio/src/audioI2S/Audio.cpp | 147 +++++++++++++++++++++++++++------ yoRadio/src/audioI2S/AudioEx.h | 7 +- 2 files changed, 124 insertions(+), 30 deletions(-) diff --git a/yoRadio/src/audioI2S/Audio.cpp b/yoRadio/src/audioI2S/Audio.cpp index 413ab3b..2d5f84b 100644 --- a/yoRadio/src/audioI2S/Audio.cpp +++ b/yoRadio/src/audioI2S/Audio.cpp @@ -5,8 +5,8 @@ * * Created on: Oct 26.2018 * - * Version 2.0.5g - * Updated on: Aug 12.2022 + * Version 2.0.5h + * Updated on: Aug 17.2022 * Author: Wolle (schreibfaul1) * */ @@ -333,7 +333,7 @@ void Audio::setDefaults() { m_f_firstmetabyte = false; m_f_playing = false; m_f_ssl = false; - m_f_swm = true; // Assume no metaint (stream without metadata) + m_f_metadata = false; m_f_tts = false; m_f_firstCall = true; // InitSequence for processWebstream and processLokalFile m_f_running = false; @@ -2548,7 +2548,8 @@ void Audio::loop() { if(m_playlistFormat == FORMAT_ASX) connecttohost(parsePlaylist_ASX()); break; case AUDIO_DATA: - processWebStream(); + if(m_streamType == ST_WEBSTREAM) processWebStream(); + if(m_streamType == ST_WEBFILE) processWebFile(); break; } } @@ -2603,12 +2604,20 @@ void Audio::loop() { } } //--------------------------------------------------------------------------------------------------------------------- -size_t Audio::chunkedDataTransfer(){ +size_t Audio::chunkedDataTransfer(uint8_t* bytes){ size_t chunksize = 0; int b = 0; + uint32_t ctime = millis(); + uint32_t timeout = 2000; // ms while(true){ + if(ctime + timeout < millis()) { + log_e("timeout"); + stopSong(); + return 0; + } b = _client->read(); - if(b < 0) break; + *bytes++; + if(b < 0) continue; // -1 no data available if(b == '\n') break; if(b < '0') continue; // We have received a hexadecimal character. Decode it and add to the result. @@ -2625,8 +2634,8 @@ bool Audio::readPlayListData() { if(getDatamode() != AUDIO_PLAYLISTINIT) return false; if(_client->available() == 0) return false; - uint32_t chunksize = 0; - if(m_f_chunked) chunksize = chunkedDataTransfer(); + uint32_t chunksize = 0; uint8_t readedBytes = 0; + if(m_f_chunked) chunksize = chunkedDataTransfer(&readedBytes); // reads the content of the playlist and stores it in the vector m_contentlength // m_contentlength is a table of pointers to the lines @@ -3113,9 +3122,90 @@ void Audio::processLocalFile() { if(afn) {free(afn); afn = NULL;} } } -//--------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- void Audio::processWebStream() { + const uint16_t maxFrameSize = InBuff.getMaxBlockSize(); // every mp3/aac frame is not bigger + static bool f_tmr_1s; + static bool f_stream; // first audio data received + static uint8_t cnt_slow; + static uint32_t chunkSize; // chunkcount read from stream + static uint32_t tmr_1s; // timer 1 sec + static uint32_t loopCnt; // count loops if clientbuffer is empty + + // first call, set some values to default - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_f_firstCall) { // runs only ont time per connection, prepare for start + m_f_firstCall = false; + f_stream = false; + cnt_slow = 0; + chunkSize = 0; + loopCnt = 0; + tmr_1s = millis(); + m_metacount = m_metaint; + readMetadata(0, true); // reset all static vars + } + + if(getDatamode() != AUDIO_DATA) return; // guard + uint32_t availableBytes = _client->available(); // available from stream + // chunked data tramsfer - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_f_chunked){ + uint8_t readedBytes = 0; + if(!chunkSize) chunkSize = chunkedDataTransfer(&readedBytes); + availableBytes = min(availableBytes, chunkSize); + } + // we have metadata - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_f_metadata){ + if(availableBytes) if(m_metacount == 0) {chunkSize -= readMetadata(availableBytes); return;} + availableBytes = min(availableBytes, m_metacount); + } + // timer, triggers every second - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if((tmr_1s + 1000) < millis()) { + f_tmr_1s = true; // flag will be set every second for one loop only + tmr_1s = millis(); + } + // if the buffer is often almost empty issue a warning - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(InBuff.bufferFilled() > maxFrameSize) {f_tmr_1s = false; cnt_slow = 0; loopCnt = 0;} + if(f_tmr_1s){ + cnt_slow ++; + if(cnt_slow > 25){cnt_slow = 0; AUDIO_INFO("slow stream, dropouts are possible");} + } + // if the buffer can't filled for several seconds try a new connection - - - - - - - - - - - - - - - - - - - - - - - + if(f_stream && !availableBytes){ + loopCnt++; + if(loopCnt > 200000) { // wait several seconds + AUDIO_INFO("Stream lost -> try new connection"); + connecttohost(m_lastHost); + return; + } + } + // buffer fill routine - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(availableBytes) { + availableBytes = min(availableBytes, (uint32_t)InBuff.writeSpace()); + int16_t bytesAddedToBuffer = _client->read(InBuff.getWritePtr(), availableBytes); + + if(bytesAddedToBuffer > 0) { + if(m_f_metadata) m_metacount -= bytesAddedToBuffer; + if(m_f_chunked) chunkSize -= bytesAddedToBuffer; + InBuff.bytesWritten(bytesAddedToBuffer); + } + + if(InBuff.bufferFilled() > maxFrameSize && !f_stream) { // waiting for buffer filled + f_stream = true; // ready to play the audio data + AUDIO_INFO("stream ready"); + } + if(!f_stream) return; + } + + // play audio data - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(f_stream){ + static uint8_t cnt = 0; + cnt++; + if(cnt == 3){playAudioData(); cnt = 0;} + } +} +//--------------------------------------------------------------------------------------------------------------------- +void Audio::processWebFile() { + const uint16_t maxFrameSize = InBuff.getMaxBlockSize(); // every mp3/aac frame is not bigger uint32_t availableBytes; // available bytes in stream static bool f_tmr_1s; @@ -3201,15 +3291,15 @@ void Audio::processWebStream() { } // if we have metadata: get them - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if(!m_metacount && !m_f_swm){ - int bytes = 0; - int res = 0; - if(m_f_chunked) bytes = min(m_chunkcount, availableBytes); - else bytes = availableBytes; - res = readMetadata(bytes); - if(m_f_chunked) m_chunkcount -= res; - if(!m_metacount) return; - } + // if(!m_metacount && !m_f_swm){ + // int bytes = 0; + // int res = 0; + // if(m_f_chunked) bytes = min(m_chunkcount, availableBytes); + // else bytes = availableBytes; + // res = readMetadata(bytes); + // if(m_f_chunked) m_chunkcount -= res; + // if(!m_metacount) return; + //} // if the buffer is often almost empty issue a warning - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if(InBuff.bufferFilled() < maxFrameSize && f_stream && !f_webFileDataComplete){ @@ -3237,7 +3327,7 @@ void Audio::processWebStream() { // buffer fill routine - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if(true) { // statement has no effect uint32_t bytesCanBeWritten = InBuff.writeSpace(); - if(!m_f_swm) bytesCanBeWritten = min(m_metacount, bytesCanBeWritten); + // if(!m_f_swm) bytesCanBeWritten = min(m_metacount, bytesCanBeWritten); if(m_f_chunked) bytesCanBeWritten = min(m_chunkcount, bytesCanBeWritten); int16_t bytesAddedToBuffer = 0; @@ -3266,7 +3356,7 @@ void Audio::processWebStream() { if(bytesAddedToBuffer > 0) { if(m_streamType == ST_WEBFILE) byteCounter += bytesAddedToBuffer; // Pull request #42 - if(!m_f_swm) m_metacount -= bytesAddedToBuffer; + // if(!m_f_swm) m_metacount -= bytesAddedToBuffer; if(m_f_chunked) m_chunkcount -= bytesAddedToBuffer; InBuff.bytesWritten(bytesAddedToBuffer); } @@ -3436,7 +3526,8 @@ void Audio::processWebStreamTS() { availableBytes = _client->available(); if(availableBytes){ - if(m_f_chunked) chunkSize = chunkedDataTransfer(); + uint8_t readedBytes = 0; + if(m_f_chunked) chunkSize = chunkedDataTransfer(&readedBytes); int res = _client->read(ts_packet + ts_packetPtr, ts_packetsize - ts_packetPtr); if(res > 0){ ts_packetPtr += res; @@ -3558,7 +3649,8 @@ void Audio::processWebStreamHLS() { availableBytes = _client->available(); if(availableBytes){ - if(m_f_chunked) chunkSize = chunkedDataTransfer(); + uint8_t readedBytes = 0; + if(m_f_chunked) chunkSize = chunkedDataTransfer(&readedBytes); size_t bytesWasWritten = 0; if(InBuff.writeSpace() >= availableBytes){ bytesWasWritten = _client->read(InBuff.getWritePtr(), availableBytes); @@ -3792,7 +3884,7 @@ bool Audio::parseHttpResponseHeader() { // this is the response to a GET / reque const char* c_metaint = (rhl + 12); int32_t i_metaint = atoi(c_metaint); m_metaint = i_metaint; - if(m_metaint) m_f_swm = false ; // Multimediastream + if(m_metaint) m_f_metadata = true; // Multimediastream } else if(startsWith(rhl, "icy-name:")) { @@ -3940,20 +4032,19 @@ uint16_t Audio::readMetadata(uint16_t maxBytes, bool first) { metalen = b * 16 ; // New count for metadata including length byte if(metalen > 512){ AUDIO_INFO("Metadata block to long! Skipping all Metadata from now on."); - m_f_swm = true; // expect stream without metadata + m_f_metadata = false; // expect stream without metadata + return 1; } pos_ml = 0; chbuf[pos_ml] = 0; // Prepare for new line res = 1; } - if(!metalen) {m_metacount = m_metaint; return res;} + if(!metalen) {m_metacount = m_metaint; return res;} // metalen is 0 uint16_t a = _client->readBytes(&chbuf[pos_ml], min((uint16_t)(metalen - pos_ml), (uint16_t)(maxBytes -1))); res += a; pos_ml += a; if(pos_ml == metalen) { - metalen = 0; chbuf[pos_ml] = '\0'; - m_metacount = m_metaint; if(strlen(chbuf)) { // Any info present? // metaline contains artist and song name. For example: // "StreamTitle='Don McLean - American Pie';StreamUrl='';" @@ -3968,6 +4059,8 @@ uint16_t Audio::readMetadata(uint16_t maxBytes, bool first) { } showstreamtitle(chbuf); // Show artist and title if present in metadata } + m_metacount = m_metaint; + metalen = 0; pos_ml = 0; } return res; diff --git a/yoRadio/src/audioI2S/AudioEx.h b/yoRadio/src/audioI2S/AudioEx.h index 50b4b95..9518e92 100644 --- a/yoRadio/src/audioI2S/AudioEx.h +++ b/yoRadio/src/audioI2S/AudioEx.h @@ -2,7 +2,7 @@ * Audio.h * * Created on: Oct 28,2018 - * Updated on: Aug 12,2022 + * Updated on: Aug 17,2022 * Author: Wolle (schreibfaul1) */ @@ -239,10 +239,11 @@ private: bool httpPrint(const char* host); void processLocalFile(); void processWebStream(); + void processWebFile(); void processWebStreamTS(); void processWebStreamHLS(); void playAudioData(); - size_t chunkedDataTransfer(); + size_t chunkedDataTransfer(uint8_t* bytes); bool readPlayListData(); const char* parsePlaylist_M3U(); const char* parsePlaylist_PLS(); @@ -542,7 +543,7 @@ private: 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; // - bool m_f_swm = true; // Stream without metadata + 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; From c88239fb96b4896e04a9a4ad3f29d4ae586fc8e6 Mon Sep 17 00:00:00 2001 From: Mykola Stepanets Date: Sat, 13 Sep 2025 22:57:04 +0300 Subject: [PATCH 2/4] fix stuttering if datatransfer is chunked --- yoRadio/src/audioI2S/Audio.cpp | 527 +++++++++++++-------------------- yoRadio/src/audioI2S/AudioEx.h | 17 +- 2 files changed, 222 insertions(+), 322 deletions(-) diff --git a/yoRadio/src/audioI2S/Audio.cpp b/yoRadio/src/audioI2S/Audio.cpp index 2d5f84b..b717bf2 100644 --- a/yoRadio/src/audioI2S/Audio.cpp +++ b/yoRadio/src/audioI2S/Audio.cpp @@ -5,8 +5,8 @@ * * Created on: Oct 26.2018 * - * Version 2.0.5h - * Updated on: Aug 17.2022 + * Version 2.0.5j + * Updated on: Aug 21.2022 * Author: Wolle (schreibfaul1) * */ @@ -1705,6 +1705,10 @@ int Audio::read_ID3_Header(uint8_t *data, size_t len) { m_controlCounter = 5; // only read 256 bytes char value[256]; char ch = *(data + 0); + // $00 – ISO-8859-1 (LATIN-1, Identical to ASCII for values smaller than 0x80). + // $01 – UCS-2 encoded Unicode with BOM, in ID3v2.2 and ID3v2.3. + // $02 – UTF-16BE encoded Unicode without BOM, in ID3v2.4. + // $03 – UTF-8 encoded Unicode, in ID3v2.4. bool isUnicode = (ch==1) ? true : false; if(startsWith(tag, "APIC")) { // a image embedded in file, passing it to external function @@ -2604,31 +2608,6 @@ void Audio::loop() { } } //--------------------------------------------------------------------------------------------------------------------- -size_t Audio::chunkedDataTransfer(uint8_t* bytes){ - size_t chunksize = 0; - int b = 0; - uint32_t ctime = millis(); - uint32_t timeout = 2000; // ms - while(true){ - if(ctime + timeout < millis()) { - log_e("timeout"); - stopSong(); - return 0; - } - b = _client->read(); - *bytes++; - if(b < 0) continue; // -1 no data available - if(b == '\n') break; - if(b < '0') continue; - // We have received a hexadecimal character. Decode it and add to the result. - b = toupper(b) - '0'; // Be sure we have uppercase - if(b > 9) b = b - 7; // Translate A..F to 10..15 - chunksize = (chunksize << 4) + b; - } - if(m_f_Log) log_i("chunksize %d", chunksize); - return chunksize; -} -//--------------------------------------------------------------------------------------------------------------------- bool Audio::readPlayListData() { if(getDatamode() != AUDIO_PLAYLISTINIT) return false; @@ -3148,14 +3127,14 @@ void Audio::processWebStream() { if(getDatamode() != AUDIO_DATA) return; // guard uint32_t availableBytes = _client->available(); // available from stream // chunked data tramsfer - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if(m_f_chunked){ + if(m_f_chunked && availableBytes){ uint8_t readedBytes = 0; if(!chunkSize) chunkSize = chunkedDataTransfer(&readedBytes); availableBytes = min(availableBytes, chunkSize); } // we have metadata - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if(m_f_metadata){ - if(availableBytes) if(m_metacount == 0) {chunkSize -= readMetadata(availableBytes); return;} + if(m_f_metadata && availableBytes){ + if(m_metacount == 0) {chunkSize -= readMetadata(availableBytes); return;} availableBytes = min(availableBytes, m_metacount); } // timer, triggers every second - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -3167,7 +3146,7 @@ void Audio::processWebStream() { if(InBuff.bufferFilled() > maxFrameSize) {f_tmr_1s = false; cnt_slow = 0; loopCnt = 0;} if(f_tmr_1s){ cnt_slow ++; - if(cnt_slow > 25){cnt_slow = 0; AUDIO_INFO("slow stream, dropouts are possible");} + if(cnt_slow > 50){cnt_slow = 0; AUDIO_INFO("slow stream, dropouts are possible");} } // if the buffer can't filled for several seconds try a new connection - - - - - - - - - - - - - - - - - - - - - - - if(f_stream && !availableBytes){ @@ -3206,211 +3185,74 @@ void Audio::processWebStream() { //--------------------------------------------------------------------------------------------------------------------- void Audio::processWebFile() { - const uint16_t maxFrameSize = InBuff.getMaxBlockSize(); // every mp3/aac frame is not bigger - uint32_t availableBytes; // available bytes in stream - static bool f_tmr_1s; + const uint32_t maxFrameSize = InBuff.getMaxBlockSize(); // every mp3/aac frame is not bigger static bool f_stream; // first audio data received static bool f_webFileDataComplete; // all file data received - static bool f_webFileAudioComplete; // all audio data received - static int bytesDecoded; static uint32_t byteCounter; // count received data - static uint32_t chunksize; // chunkcount read from stream - static uint32_t tmr_1s; // timer 1 sec - static uint32_t loopCnt; // count loops if clientbuffer is empty + static uint32_t chunkSize; // chunkcount read from stream static size_t audioDataCount; // counts the decoded audiodata only // first call, set some values to default - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if(m_f_firstCall) { // runs only ont time per connection, prepare for start m_f_firstCall = false; f_webFileDataComplete = false; - f_webFileAudioComplete = false; f_stream = false; byteCounter = 0; - chunksize = 0; - bytesDecoded = 0; - loopCnt = 0; + chunkSize = 0; audioDataCount = 0; - tmr_1s = millis(); - m_t0 = millis(); - m_metacount = m_metaint; - readMetadata(0, true); // reset all static vars } - if(getDatamode() != AUDIO_DATA) return; // guard + if(!m_contentlength) {log_e("webfile without contentlength!"); stopSong(); return;} // guard - if(m_streamType == ST_WEBFILE){ + uint32_t availableBytes = _client->available(); // available from stream + // chunked data tramsfer - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_f_chunked){ + uint8_t readedBytes = 0; + if(!chunkSize) chunkSize = chunkedDataTransfer(&readedBytes); + availableBytes = min(availableBytes, chunkSize); } - availableBytes = _client->available(); // available from stream - - // timer, triggers every second - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if((tmr_1s + 1000) < millis()) { - f_tmr_1s = true; // flag will be set every second for one loop only - tmr_1s = millis(); - } - - if(ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_VERBOSE){ - // Here you can see how much data comes in, a summary is displayed in every 10 calls - static uint8_t i = 0; - static uint32_t t = 0; (void)t; - static uint32_t t0 = 0; - static uint16_t avb[10]; - if(!i) t = millis(); - avb[i] = availableBytes; - if(!avb[i]){if(!t0) t0 = millis();} - else{if(t0 && (millis() - t0) > 400) log_v("\033[31m%dms no data received", millis() - t0); t0 = 0;} - i++; - if(i == 10) i = 0; - if(!i){ - log_d("bytes available, 10 polls in %dms %d, %d, %d, %d, %d, %d, %d, %d, %d, %d", millis() - t, - avb[0], avb[1], avb[2], avb[3], avb[4], avb[5], avb[6], avb[7], avb[8], avb[9]); - } - } - - // if we have chunked data transfer: get the chunksize- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if(m_f_chunked && !m_chunkcount && availableBytes) { // Expecting a new chunkcount? - int b; - b = _client->read(); - - if(b == '\r') return; - if(b == '\n'){ - m_chunkcount = chunksize; - chunksize = 0; - if(m_f_tts){ - m_contentlength = m_chunkcount; // tts has one chunk only - m_streamType = ST_WEBFILE; - m_f_chunked = false; - } - return; - } - // We have received a hexadecimal character. Decode it and add to the result. - b = toupper(b) - '0'; // Be sure we have uppercase - if(b > 9) b = b - 7; // Translate A..F to 10..15 - chunksize = (chunksize << 4) + b; - return; - } - - // if we have metadata: get them - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // if(!m_metacount && !m_f_swm){ - // int bytes = 0; - // int res = 0; - // if(m_f_chunked) bytes = min(m_chunkcount, availableBytes); - // else bytes = availableBytes; - // res = readMetadata(bytes); - // if(m_f_chunked) m_chunkcount -= res; - // if(!m_metacount) return; - //} // if the buffer is often almost empty issue a warning - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if(InBuff.bufferFilled() < maxFrameSize && f_stream && !f_webFileDataComplete){ - static uint8_t cnt_slow = 0; - cnt_slow ++; - if(f_tmr_1s) { - if(cnt_slow > 25 && audio_info) audio_info("slow stream, dropouts are possible"); - f_tmr_1s = false; - cnt_slow = 0; - } + if(!f_webFileDataComplete && f_stream){ + slowStreamDetection(InBuff.bufferFilled(), maxFrameSize); } - // if the buffer can't filled for several seconds try a new connection - - - - - - - - - - - - - - - - - - - - - - - if(f_stream && !availableBytes && !f_webFileAudioComplete){ - loopCnt++; - if(loopCnt > 200000) { // wait several seconds - loopCnt = 0; - AUDIO_INFO("Stream lost -> try new connection"); - connecttohost(m_lastHost); - return; - } - } - if(availableBytes) loopCnt = 0; + availableBytes = min((uint32_t)InBuff.writeSpace(), availableBytes); + availableBytes = min(m_contentlength - byteCounter, availableBytes); + if(m_audioDataSize) availableBytes = min(m_audioDataSize - (byteCounter - m_audioDataStart), availableBytes); - // buffer fill routine - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if(true) { // statement has no effect - uint32_t bytesCanBeWritten = InBuff.writeSpace(); - // if(!m_f_swm) bytesCanBeWritten = min(m_metacount, bytesCanBeWritten); - if(m_f_chunked) bytesCanBeWritten = min(m_chunkcount, bytesCanBeWritten); + int16_t bytesAddedToBuffer = _client->read(InBuff.getWritePtr(), availableBytes); - int16_t bytesAddedToBuffer = 0; - - // Audiobuffer throttle - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if(m_codec == CODEC_AAC || m_codec == CODEC_MP3 || m_codec == CODEC_M4A){ - if(bytesCanBeWritten > maxFrameSize) bytesCanBeWritten = maxFrameSize; - } - if(m_codec == CODEC_WAV){ - if(bytesCanBeWritten > maxFrameSize - 500) bytesCanBeWritten = maxFrameSize - 600; - } - if(m_codec == CODEC_FLAC){ - if(bytesCanBeWritten > maxFrameSize) bytesCanBeWritten = maxFrameSize; - } - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if(m_streamType == ST_WEBFILE){ - // normally there is nothing to do here, if byteCounter == contentLength - // then the file is completely read, but: - // m4a files can have more data (e.g. pictures ..) after the audio Block - // therefore it is bad to read anything else (this can generate noise) - if(byteCounter + bytesCanBeWritten >= m_contentlength) bytesCanBeWritten = m_contentlength - byteCounter; - } - - bytesAddedToBuffer = _client->read(InBuff.getWritePtr(), bytesCanBeWritten); - - if(bytesAddedToBuffer > 0) { - if(m_streamType == ST_WEBFILE) byteCounter += bytesAddedToBuffer; // Pull request #42 - // if(!m_f_swm) m_metacount -= bytesAddedToBuffer; - if(m_f_chunked) m_chunkcount -= bytesAddedToBuffer; - InBuff.bytesWritten(bytesAddedToBuffer); - } - - if(InBuff.bufferFilled() > maxFrameSize && !f_stream) { // waiting for buffer filled - f_stream = true; // ready to play the audio data - uint16_t filltime = millis() - m_t0; - - AUDIO_INFO("stream ready"); - AUDIO_INFO("buffer filled in %d ms", filltime); - } - if(!f_stream) return; + if(bytesAddedToBuffer > 0) { + byteCounter += bytesAddedToBuffer; // Pull request #42 + if(m_f_chunked) m_chunkcount -= bytesAddedToBuffer; + if(m_controlCounter == 100) audioDataCount += bytesAddedToBuffer; + InBuff.bytesWritten(bytesAddedToBuffer); } - // if we have a webfile, read the file header first - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if(m_streamType == ST_WEBFILE && m_controlCounter != 100 ){ // m3u8call, audiochunk has no header - if(InBuff.bufferFilled() < maxFrameSize) return; - if(m_codec == CODEC_WAV){ - int res = read_WAV_Header(InBuff.getReadPtr(), InBuff.bufferFilled()); - if(res >= 0) bytesDecoded = res; - else{stopSong(); return;} - } - if(m_codec == CODEC_MP3){ - int res = read_ID3_Header(InBuff.getReadPtr(), InBuff.bufferFilled()); - if(res >= 0) bytesDecoded = res; - else{m_controlCounter = 100;} // error, skip header - } - if(m_codec == CODEC_M4A){ - int res = read_M4A_Header(InBuff.getReadPtr(), InBuff.bufferFilled()); - if(res >= 0) bytesDecoded = res; - else{stopSong(); return;} - } - if(m_codec == CODEC_FLAC){ - int res = read_FLAC_Header(InBuff.getReadPtr(), InBuff.bufferFilled()); - if(res >= 0) bytesDecoded = res; - else{stopSong(); return;} // error, skip header - } - if(m_codec == CODEC_AAC){ // aac has no header - if(m_playlistFormat == FORMAT_M3U8){ // except m3u8 stream - int res = read_ID3_Header(InBuff.getReadPtr(), InBuff.bufferFilled()); - if(res >= 0) bytesDecoded = res; - else m_controlCounter = 100; - } - else{ - m_controlCounter = 100; - } - } - InBuff.bytesWasRead(bytesDecoded); + if(InBuff.bufferFilled() > maxFrameSize && !f_stream) { // waiting for buffer filled + f_stream = true; // ready to play the audio data + uint16_t filltime = millis() - m_t0; + AUDIO_INFO("stream ready\nbuffer filled in %d ms", filltime); + } + + if(!f_stream) return; + + // we have a webfile, read the file header first - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(m_controlCounter != 100){ + InBuff.bytesWasRead(readAudioHeader(availableBytes)); return; } // end of webfile reached? - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if(f_webFileAudioComplete){ - if(m_playlistFormat == FORMAT_M3U8) return + if(f_webFileDataComplete && InBuff.bufferFilled() < InBuff.getMaxBlockSize()){ + if(InBuff.bufferFilled()){ + if(!readID3V1Tag()){ + int bytesDecoded = sendBytes(InBuff.getReadPtr(), InBuff.bufferFilled()); + if(bytesDecoded > 2){InBuff.bytesWasRead(bytesDecoded); return;} + } + } playI2Sremains(); stopSong(); // Correct close when play known length sound #74 and before callback #11 if(m_f_tts){ @@ -3424,64 +3266,18 @@ void Audio::processWebFile() { return; } + if(byteCounter == m_contentlength) {f_webFileDataComplete = true;} + if(byteCounter - m_audioDataStart == m_audioDataSize) {f_webFileDataComplete = true;} + // play audio data - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if(!f_stream) return; // 1. guard - bool a = InBuff.bufferFilled() >= maxFrameSize; - bool b = (m_audioDataSize > 0) && (m_audioDataSize <= audioDataCount + maxFrameSize); - if(!a && !b) return; // 2. guard fill < frame && last frame(s) - - size_t data2decode = InBuff.bufferFilled(); - - if(data2decode < maxFrameSize){ - if(m_audioDataSize - audioDataCount < maxFrameSize){ - data2decode = m_audioDataSize - audioDataCount; - } - else return; - } - else data2decode = maxFrameSize; - - if(m_streamType == ST_WEBFILE){ - - bytesDecoded = sendBytes(InBuff.getReadPtr(), data2decode); - if(bytesDecoded > 0) audioDataCount += bytesDecoded; - - if(byteCounter == m_contentlength){ - if(m_playlistFormat == FORMAT_M3U8){ - byteCounter = 0; - m_metacount = m_metaint; - m_f_continue = true; - return; - } - f_webFileDataComplete = true; - } - if(m_audioDataSize == audioDataCount && m_controlCounter == 100) f_webFileAudioComplete = true; - } - else { // not a webfile - if(m_controlCounter != 100 && (m_codec == CODEC_OGG || m_codec == CODEC_OGG_FLAC)) { //application/ogg - int res = read_OGG_Header(InBuff.getReadPtr(), InBuff.bufferFilled()); - if(res >= 0) bytesDecoded = res; - else { // error, skip header - stopSong(); - m_controlCounter = 100; - } - } - else{ - bytesDecoded = sendBytes(InBuff.getReadPtr(), data2decode); - } - } - - if(bytesDecoded < 0) { // no syncword found or decode error, try next chunk - uint8_t next = 200; - if(InBuff.bufferFilled() < next) next = InBuff.bufferFilled(); - InBuff.getReadPtr(); - InBuff.bytesWasRead(next); // try next chunk - m_bytesNotDecoded += next; - if(m_streamType == ST_WEBFILE) audioDataCount += next; - return; - } - else { - if(bytesDecoded > 0) {InBuff.bytesWasRead(bytesDecoded); return;} - if(bytesDecoded == 0) return; // syncword at pos0 found + if(f_stream){ + static uint8_t cnt = 0; + uint8_t compression; + if(m_codec == CODEC_WAV) compression = 1; + if(m_codec == CODEC_FLAC) compression = 2; + else compression = 6; + cnt++; + if(cnt == compression){playAudioData(); cnt = 0;} } return; } @@ -4013,59 +3809,6 @@ bool Audio:: initializeDecoder(){ return false; } //--------------------------------------------------------------------------------------------------------------------- -uint16_t Audio::readMetadata(uint16_t maxBytes, bool first) { - - static uint16_t pos_ml = 0; // determines the current position in metaline - static uint16_t metalen = 0; - uint16_t res = 0; - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if(first){ - pos_ml = 0; - metalen = 0; - return 0; - } - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if(!maxBytes) return 0; // guard - - if(!metalen) { - int b = _client->read(); // First byte of metadata? - metalen = b * 16 ; // New count for metadata including length byte - if(metalen > 512){ - AUDIO_INFO("Metadata block to long! Skipping all Metadata from now on."); - m_f_metadata = false; // expect stream without metadata - return 1; - } - pos_ml = 0; chbuf[pos_ml] = 0; // Prepare for new line - res = 1; - } - if(!metalen) {m_metacount = m_metaint; return res;} // metalen is 0 - - uint16_t a = _client->readBytes(&chbuf[pos_ml], min((uint16_t)(metalen - pos_ml), (uint16_t)(maxBytes -1))); - res += a; - pos_ml += a; - if(pos_ml == metalen) { - chbuf[pos_ml] = '\0'; - if(strlen(chbuf)) { // Any info present? - // metaline contains artist and song name. For example: - // "StreamTitle='Don McLean - American Pie';StreamUrl='';" - // Sometimes it is just other info like: - // "StreamTitle='60s 03 05 Magic60s';StreamUrl='';" - // Isolate the StreamTitle, remove leading and trailing quotes if present. - if(m_f_Log) log_i("metaline %s", chbuf); - latinToUTF8(chbuf, sizeof(chbuf)); // convert to UTF-8 if necessary - int pos = indexOf(chbuf, "song_spot", 0); // remove some irrelevant infos - if(pos > 3) { // e.g. song_spot="T" MediaBaseId="0" itunesTrackId="0" - chbuf[pos] = 0; - } - showstreamtitle(chbuf); // Show artist and title if present in metadata - } - m_metacount = m_metaint; - metalen = 0; - pos_ml = 0; - } - return res; -} -//--------------------------------------------------------------------------------------------------------------------- bool Audio::parseContentType(char* ct) { enum : int {CT_NONE, CT_MP3, CT_AAC, CT_M4A, CT_WAV, CT_OGG, CT_FLAC, CT_PLS, CT_M3U, CT_ASX, @@ -4089,6 +3832,7 @@ bool Audio::parseContentType(char* ct) { else if(!strcmp(ct, "video/mp2t")){ ct_val = CT_AAC; m_f_ts = true;} // assume AAC transport stream else if(!strcmp(ct, "audio/mp4")) ct_val = CT_M4A; else if(!strcmp(ct, "audio/m4a")) ct_val = CT_M4A; + else if(!strcmp(ct, "audio/x-m4a")) ct_val = CT_M4A; else if(!strcmp(ct, "audio/wav")) ct_val = CT_WAV; else if(!strcmp(ct, "audio/x-wav")) ct_val = CT_WAV; @@ -5259,4 +5003,153 @@ bool Audio::ts_parsePacket(uint8_t* packet, uint8_t* packetStart, uint8_t* packe return false; } //---------------------------------------------------------------------------------------------------------------------- +// W E B S T R E A M - H E L P F U N C T I O N S +//---------------------------------------------------------------------------------------------------------------------- +uint16_t Audio::readMetadata(uint16_t maxBytes, bool first) { + + static uint16_t pos_ml = 0; // determines the current position in metaline + static uint16_t metalen = 0; + uint16_t res = 0; + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(first){ + pos_ml = 0; + metalen = 0; + return 0; + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if(!maxBytes) return 0; // guard + + if(!metalen) { + int b = _client->read(); // First byte of metadata? + metalen = b * 16 ; // New count for metadata including length byte + if(metalen > 512){ + AUDIO_INFO("Metadata block to long! Skipping all Metadata from now on."); + m_f_metadata = false; // expect stream without metadata + return 1; + } + pos_ml = 0; chbuf[pos_ml] = 0; // Prepare for new line + res = 1; + } + if(!metalen) {m_metacount = m_metaint; return res;} // metalen is 0 + uint16_t a = _client->readBytes(&chbuf[pos_ml], min((uint16_t)(metalen - pos_ml), (uint16_t)(maxBytes -1))); + res += a; + pos_ml += a; + if(pos_ml == metalen) { + chbuf[pos_ml] = '\0'; + if(strlen(chbuf)) { // Any info present? + // metaline contains artist and song name. For example: + // "StreamTitle='Don McLean - American Pie';StreamUrl='';" + // Sometimes it is just other info like: + // "StreamTitle='60s 03 05 Magic60s';StreamUrl='';" + // Isolate the StreamTitle, remove leading and trailing quotes if present. + if(m_f_Log) log_i("metaline %s", chbuf); + latinToUTF8(chbuf, sizeof(chbuf)); // convert to UTF-8 if necessary + int pos = indexOf(chbuf, "song_spot", 0); // remove some irrelevant infos + if(pos > 3) { // e.g. song_spot="T" MediaBaseId="0" itunesTrackId="0" + chbuf[pos] = 0; + } + showstreamtitle(chbuf); // Show artist and title if present in metadata + } + m_metacount = m_metaint; + metalen = 0; + pos_ml = 0; + } + return res; +} +//---------------------------------------------------------------------------------------------------------------------- +size_t Audio::chunkedDataTransfer(uint8_t* bytes){ + size_t chunksize = 0; + int b = 0; + uint32_t ctime = millis(); + uint32_t timeout = 2000; // ms + while(true){ + if(ctime + timeout < millis()) { + log_e("timeout"); + stopSong(); + return 0; + } + b = _client->read(); + *bytes++; + if(b < 0) continue; // -1 no data available + if(b == '\n') break; + if(b < '0') continue; + // We have received a hexadecimal character. Decode it and add to the result. + b = toupper(b) - '0'; // Be sure we have uppercase + if(b > 9) b = b - 7; // Translate A..F to 10..15 + chunksize = (chunksize << 4) + b; + } + if(m_f_Log) log_i("chunksize %d", chunksize); + return chunksize; +} +//---------------------------------------------------------------------------------------------------------------------- +bool Audio::readID3V1Tag(){ + // this is an V1.x id3tag after an audio block, ID3 v1 tags are ASCII + // Version 1.x is a fixed size at the end of the file (128 bytes) after a keyword. + if(m_codec != CODEC_MP3) return false; + if(InBuff.bufferFilled() == 128 && startsWith((const char*)InBuff.getReadPtr(), "TAG")){ // maybe a V1.x TAG + char title[31]; + memcpy(title, InBuff.getReadPtr() + 3 + 0, 30); title[30] = '\0'; latinToUTF8(title, sizeof(title)); + char artist[31]; + memcpy(artist, InBuff.getReadPtr() + 3 + 30, 30); artist[30] = '\0'; latinToUTF8(artist, sizeof(artist)); + char album[31]; + memcpy(album, InBuff.getReadPtr() + 3 + 60, 30); album[30] = '\0'; latinToUTF8(album, sizeof(album)); + char year[5]; + memcpy(year, InBuff.getReadPtr() + 3 + 90, 4); year[4] = '\0'; latinToUTF8(year, sizeof(year)); + char comment[31]; + memcpy(comment, InBuff.getReadPtr() + 3 + 94, 30); comment[30] = '\0'; latinToUTF8(comment, sizeof(comment)); + uint8_t zeroByte = *(InBuff.getReadPtr() + 125); + uint8_t track = *(InBuff.getReadPtr() + 126); + uint8_t genre = *(InBuff.getReadPtr() + 127); + if(zeroByte) {AUDIO_INFO("ID3 version: 1");} //[2] + else {AUDIO_INFO("ID3 Version 1.1");} + if(strlen(title)) {sprintf(chbuf, "Title: %s", title); if(audio_id3data) audio_id3data(chbuf);} + if(strlen(artist)) {sprintf(chbuf, "Artist: %s", artist); if(audio_id3data) audio_id3data(chbuf);} + if(strlen(album)) {sprintf(chbuf, "Album: %s", album); if(audio_id3data) audio_id3data(chbuf);} + if(strlen(year)) {sprintf(chbuf, "Year: %s", year); if(audio_id3data) audio_id3data(chbuf);} + if(strlen(comment)){sprintf(chbuf, "Comment: %s", comment); if(audio_id3data) audio_id3data(chbuf);} + if(zeroByte == 0) {sprintf(chbuf, "Track Number: %d", track); if(audio_id3data) audio_id3data(chbuf);} + if(genre < 192) {sprintf(chbuf, "Genre: %d", genre); if(audio_id3data) audio_id3data(chbuf);} //[1] + return true; + } + if(InBuff.bufferFilled() == 227 && startsWith((const char*)InBuff.getReadPtr(), "TAG+")){ // ID3V1EnhancedTAG + AUDIO_INFO("ID3 version: 1 - Enhanced TAG"); + char title[61]; + memcpy(title, InBuff.getReadPtr() + 4 + 0, 60); title[60] = '\0'; latinToUTF8(title, sizeof(title)); + char artist[61]; + memcpy(artist, InBuff.getReadPtr() + 4 + 60, 60); artist[60] = '\0'; latinToUTF8(artist, sizeof(artist)); + char album[61]; + memcpy(album, InBuff.getReadPtr() + 4 + 120, 60); album[60] = '\0'; latinToUTF8(album, sizeof(album)); + // one byte "speed" 0=unset, 1=slow, 2= medium, 3=fast, 4=hardcore + char genre[31]; + memcpy(genre, InBuff.getReadPtr() + 5 + 180, 30); genre[30] = '\0'; latinToUTF8(genre, sizeof(genre)); + // six bytes "start-time", the start of the music as mmm:ss + // six bytes "end-time", the end of the music as mmm:ss + if(strlen(title)) {sprintf(chbuf, "Title: %s", title); if(audio_id3data) audio_id3data(chbuf);} + if(strlen(artist)) {sprintf(chbuf, "Artist: %s", artist); if(audio_id3data) audio_id3data(chbuf);} + if(strlen(album)) {sprintf(chbuf, "Album: %s", album); if(audio_id3data) audio_id3data(chbuf);} + if(strlen(genre)) {sprintf(chbuf, "Genre: %s", genre); if(audio_id3data) audio_id3data(chbuf);} + return true; + } + return false; + // [1] https://en.wikipedia.org/wiki/List_of_ID3v1_Genres + // [2] https://en.wikipedia.org/wiki/ID3#ID3v1_and_ID3v1.1[5] +} +//---------------------------------------------------------------------------------------------------------------------- +void Audio::slowStreamDetection(uint32_t inBuffFilled, uint32_t maxFrameSize){ + static uint32_t tmr_1s = millis(); // timer 1 sec + static bool f_tmr_1s = false; + static uint8_t cnt_slow = 0; + if(tmr_1s + 1000 < millis()) {f_tmr_1s = true; tmr_1s = millis();} + if(m_codec == CODEC_WAV) maxFrameSize /= 4; + if(m_codec == CODEC_FLAC) maxFrameSize /= 2; + if(inBuffFilled < maxFrameSize){ + cnt_slow ++; + if(f_tmr_1s) { + if(cnt_slow > 50) AUDIO_INFO("slow stream, dropouts are possible"); + f_tmr_1s = false; + cnt_slow = 0; + } + } + else cnt_slow = 0; +} #endif // if VS1053_CS==255 diff --git a/yoRadio/src/audioI2S/AudioEx.h b/yoRadio/src/audioI2S/AudioEx.h index 9518e92..b8db6c1 100644 --- a/yoRadio/src/audioI2S/AudioEx.h +++ b/yoRadio/src/audioI2S/AudioEx.h @@ -2,7 +2,7 @@ * Audio.h * * Created on: Oct 28,2018 - * Updated on: Aug 17,2022 + * Updated on: Aug 21,2022 * Author: Wolle (schreibfaul1) */ @@ -243,7 +243,6 @@ private: void processWebStreamTS(); void processWebStreamHLS(); void playAudioData(); - size_t chunkedDataTransfer(uint8_t* bytes); bool readPlayListData(); const char* parsePlaylist_M3U(); const char* parsePlaylist_PLS(); @@ -277,7 +276,6 @@ private: bool parseContentType(char* ct); bool parseHttpResponseHeader(); bool initializeDecoder(); - uint16_t readMetadata(uint16_t b, bool first = false); 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); @@ -291,7 +289,14 @@ private: bool ts_parsePacket(uint8_t* packet, uint8_t* packetStart, uint8_t* packetLength); void _computeVUlevel(int16_t sample[2]); static void connectTask(void* pvParams); - // implement several function with respect to the index of string + + //+++ 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); + + //++++ implement several function with respect to the index of string ++++ void trim(char *s) { //fb trim in place char *pe; @@ -383,6 +388,8 @@ private: } return result; } + + // some other functions size_t bigEndian(uint8_t* base, uint8_t numBytes, uint8_t shiftLeft = 8){ size_t result = 0; if(numBytes < 1 or numBytes > 4) return 0; @@ -490,7 +497,7 @@ private: volatile bool _connectionResult; TaskHandle_t _connectTaskHandle = nullptr; - const size_t m_frameSizeWav = 1600; + const size_t m_frameSizeWav = 1024 * 8; const size_t m_frameSizeMP3 = 1600; const size_t m_frameSizeAAC = 1600; const size_t m_frameSizeFLAC = 4096 * 4; From fe6af54f94f7bb5f91fddf2627fa42e0f210c826 Mon Sep 17 00:00:00 2001 From: Mykola Stepanets Date: Sun, 14 Sep 2025 02:50:36 +0300 Subject: [PATCH 3/4] fix packets lost in HLS-TS --- yoRadio/src/audioI2S/Audio.cpp | 10 ++++++---- yoRadio/src/audioI2S/AudioEx.h | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/yoRadio/src/audioI2S/Audio.cpp b/yoRadio/src/audioI2S/Audio.cpp index b717bf2..222d11b 100644 --- a/yoRadio/src/audioI2S/Audio.cpp +++ b/yoRadio/src/audioI2S/Audio.cpp @@ -5,8 +5,8 @@ * * Created on: Oct 26.2018 * - * Version 2.0.5j - * Updated on: Aug 21.2022 + * Version 2.0.5k + * Updated on: Aug 23.2022 * Author: Wolle (schreibfaul1) * */ @@ -326,6 +326,7 @@ void Audio::setDefaults() { _client = static_cast(&client); /* default to *something* so that no NULL deref can happen */ } playI2Sremains(); + ts_parsePacket(0, 0, 0); // reset ts routine AUDIO_INFO("buffers freed, free Heap: %lu bytes", ESP.getFreeHeap()); @@ -3311,7 +3312,6 @@ void Audio::processWebStreamTS() { tmr_1s = millis(); m_t0 = millis(); ts_packetPtr = 0; - ts_parsePacket(0, 0, 0); // reset ts routine m_controlCounter = 0; m_f_firstCall = false; } @@ -4999,7 +4999,9 @@ bool Audio::ts_parsePacket(uint8_t* packet, uint8_t* packetStart, uint8_t* packe *packetLength = 0; return true; } - if(m_f_Log) log_e("invalid ts packet!"); + // PES received before PAT and PMT seen + *packetStart = 0; + *packetLength = 0; return false; } //---------------------------------------------------------------------------------------------------------------------- diff --git a/yoRadio/src/audioI2S/AudioEx.h b/yoRadio/src/audioI2S/AudioEx.h index b8db6c1..e1ab126 100644 --- a/yoRadio/src/audioI2S/AudioEx.h +++ b/yoRadio/src/audioI2S/AudioEx.h @@ -2,7 +2,7 @@ * Audio.h * * Created on: Oct 28,2018 - * Updated on: Aug 21,2022 + * Updated on: Aug 23,2022 * Author: Wolle (schreibfaul1) */ From 9a3bb03f82f1eec8fe240f30799ada5b790f5fc8 Mon Sep 17 00:00:00 2001 From: e2002 Date: Mon, 15 Sep 2025 17:01:48 +0300 Subject: [PATCH 4/4] v0.9.720 --- README.md | 10 ++++++++++ yoRadio/src/audioI2S/Audio.cpp | 2 +- yoRadio/src/core/mqtt.cpp | 17 ++--------------- yoRadio/src/core/network.cpp | 24 +++++++++++------------- yoRadio/src/core/options.h | 2 +- yoRadio/src/displays/displayGC9106.cpp | 6 +----- yoRadio/src/displays/displayGC9A01A.cpp | 5 ----- yoRadio/src/displays/displayILI9488.cpp | 5 ----- yoRadio/src/displays/displaySSD1305.cpp | 8 ++------ yoRadio/src/displays/displaySSD1322.cpp | 7 ++----- yoRadio/src/displays/displayST7735.cpp | 5 ----- yoRadio/src/displays/displayST7789.cpp | 5 ----- yoRadio/src/displays/displayST7796.cpp | 5 ----- yoRadio/src/displays/displayST7920.cpp | 8 ++------ yoRadio/src/main.cpp | 5 +---- 15 files changed, 33 insertions(+), 81 deletions(-) diff --git a/README.md b/README.md index 0cd03cc..090dc12 100644 --- a/README.md +++ b/README.md @@ -235,6 +235,16 @@ Work is in progress... --- ## Version history +#### v0.9.720 +- PR #211 https://github.com/e2002/yoradio/pull/211 +- fix packets lost in HLS-TS +- fix stuttering if datatransfer is chunked +- split processWebStream/processWebFile +- fixed the error of connecting to the next access points from the list if the first one is unavailable +- fixed playback control errors from Home Assistant +- the DEF_SPI_FREQ parameter, intended for setting the user speed SPI of displays, has been removed from the settings (they work fine anyway) +- fixed an error connecting to the MQTT server during initial boot in SDCARD mode + #### v0.9.711 - fixed compilation error for LCD displays #210 diff --git a/yoRadio/src/audioI2S/Audio.cpp b/yoRadio/src/audioI2S/Audio.cpp index 222d11b..099722e 100644 --- a/yoRadio/src/audioI2S/Audio.cpp +++ b/yoRadio/src/audioI2S/Audio.cpp @@ -3147,7 +3147,7 @@ void Audio::processWebStream() { if(InBuff.bufferFilled() > maxFrameSize) {f_tmr_1s = false; cnt_slow = 0; loopCnt = 0;} if(f_tmr_1s){ cnt_slow ++; - if(cnt_slow > 50){cnt_slow = 0; AUDIO_INFO("slow stream, dropouts are possible");} + if(cnt_slow > 50){cnt_slow = 0; f_tmr_1s = false; AUDIO_INFO("slow stream, dropouts are possible");} } // if the buffer can't filled for several seconds try a new connection - - - - - - - - - - - - - - - - - - - - - - - if(f_stream && !availableBytes){ diff --git a/yoRadio/src/core/mqtt.cpp b/yoRadio/src/core/mqtt.cpp index 276c3fe..3a94d0c 100644 --- a/yoRadio/src/core/mqtt.cpp +++ b/yoRadio/src/core/mqtt.cpp @@ -5,13 +5,13 @@ #include "mqtt.h" #include "WiFi.h" #include "player.h" +#include "commandhandler.h" AsyncMqttClient mqttClient; TimerHandle_t mqttReconnectTimer; char topic[100], status[BUFLEN*2]; void connectToMqtt() { - //config.waitConnection(); mqttClient.connect(); } @@ -82,20 +82,7 @@ void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties char buf[len+1]; strncpy(buf, payload, len); buf[len]='\0'; - if (strcmp(buf, "prev") == 0) { player.sendCommand({PR_PREV, 0}); return; } - if (strcmp(buf, "next") == 0) { player.sendCommand({PR_NEXT, 0}); return; } - if (strcmp(buf, "toggle") == 0) { player.sendCommand({PR_TOGGLE, 0}); return; } - if (strcmp(buf, "stop") == 0) { player.sendCommand({PR_STOP, 0}); return; } - if (strcmp(buf, "start") == 0 || strcmp(buf, "play") == 0) { player.sendCommand({PR_PLAY, config.lastStation()}); return; } - if (strcmp(buf, "boot") == 0 || strcmp(buf, "reboot") == 0) { ESP.restart(); return; } - if (strcmp(buf, "volm") == 0) { - player.stepVol(false); - return; - } - if (strcmp(buf, "volp") == 0) { - player.stepVol(true); - return; - } + if(cmd.exec(buf, "")) return; if (strcmp(buf, "turnoff") == 0) { uint8_t sst = config.store.smartstart; config.setDspOn(0); diff --git a/yoRadio/src/core/network.cpp b/yoRadio/src/core/network.cpp index 4bd7ba1..1a61a30 100644 --- a/yoRadio/src/core/network.cpp +++ b/yoRadio/src/core/network.cpp @@ -58,25 +58,16 @@ bool MyNetwork::wifiBegin(bool silent){ uint8_t ls = (config.store.lastSSID == 0 || config.store.lastSSID > config.ssidsCount) ? 0 : config.store.lastSSID - 1; uint8_t startedls = ls; uint8_t errcnt = 0; - WiFi.mode(WIFI_STA); - /* - char buf[MDNS_LENGTH]; - WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, INADDR_NONE); - if(strlen(config.store.mdnsname)>0){ - WiFi.setHostname(config.store.mdnsname); - }else{ - snprintf(buf, MDNS_LENGTH, "yoradio-%x", config.getChipId()); - WiFi.setHostname(buf); - } - */ + //WiFi.mode(WIFI_STA); while (true) { if(!silent){ Serial.printf("##[BOOT]#\tAttempt to connect to %s\n", config.ssids[ls].ssid); Serial.print("##[BOOT]#\t"); display.putRequest(BOOTSTRING, ls); } - WiFi.disconnect(true, true); //disconnect & erase internal credentials https://github.com/e2002/yoradio/pull/164/commits/89d8b4450dde99cd7930b84bb14d81dab920b879 - delay(100); + //WiFi.disconnect(true, true); //disconnect & erase internal credentials https://github.com/e2002/yoradio/pull/164/commits/89d8b4450dde99cd7930b84bb14d81dab920b879 + //delay(100); + WiFi.mode(WIFI_STA); WiFi.begin(config.ssids[ls].ssid, config.ssids[ls].password); while (WiFi.status() != WL_CONNECTED) { if(!silent) Serial.print("."); @@ -88,6 +79,7 @@ bool MyNetwork::wifiBegin(bool silent){ ls++; if (ls > config.ssidsCount - 1) ls = 0; if(!silent) Serial.println(); + WiFi.mode(WIFI_OFF); break; } } @@ -112,6 +104,9 @@ void searchWiFi(void * pvParameters){ telnet.begin(true); network.setWifiParams(); display.putRequest(NEWIP, 0); + #ifdef MQTT_ROOT_TOPIC + mqttInit(); + #endif } vTaskDelete( NULL ); } @@ -134,6 +129,9 @@ void MyNetwork::begin() { Serial.println("."); status = CONNECTED; setWifiParams(); + #ifdef MQTT_ROOT_TOPIC + mqttInit(); + #endif }else{ status = SDREADY; xTaskCreatePinnedToCore(searchWiFi, "searchWiFi", 1024 * 4, NULL, 0, NULL, SEARCH_WIFI_CORE_ID); diff --git a/yoRadio/src/core/options.h b/yoRadio/src/core/options.h index ea02ed7..d5b3601 100644 --- a/yoRadio/src/core/options.h +++ b/yoRadio/src/core/options.h @@ -2,7 +2,7 @@ #define options_h #pragma once -#define YOVERSION "0.9.711" +#define YOVERSION "0.9.720" /******************************************************* DO NOT EDIT THIS FILE. diff --git a/yoRadio/src/displays/displayGC9106.cpp b/yoRadio/src/displays/displayGC9106.cpp index 6e9f3e4..05a42d2 100644 --- a/yoRadio/src/displays/displayGC9106.cpp +++ b/yoRadio/src/displays/displayGC9106.cpp @@ -3,10 +3,6 @@ #include "dspcore.h" #include "../core/config.h" -#ifndef DEF_SPI_FREQ - #define DEF_SPI_FREQ 24000000 /* set it to 0 for system default */ -#endif - #if DSP_HSPI DspCore::DspCore(): Adafruit_GC9106Ex(&SPI2, TFT_DC, TFT_CS, TFT_RST) {} #else @@ -14,7 +10,7 @@ DspCore::DspCore(): Adafruit_GC9106Ex(TFT_CS, TFT_DC, TFT_RST) {} #endif void DspCore::initDisplay() { - begin(DEF_SPI_FREQ); + begin(); cp437(true); invert(); flip(); diff --git a/yoRadio/src/displays/displayGC9A01A.cpp b/yoRadio/src/displays/displayGC9A01A.cpp index 81ef6b8..ee96bf7 100644 --- a/yoRadio/src/displays/displayGC9A01A.cpp +++ b/yoRadio/src/displays/displayGC9A01A.cpp @@ -3,10 +3,6 @@ #include "dspcore.h" #include "../core/config.h" -#ifndef DEF_SPI_FREQ - #define DEF_SPI_FREQ 40000000UL /* set it to 0 for system default */ -#endif - #if DSP_HSPI DspCore::DspCore(): Adafruit_GC9A01A(&SPI2, TFT_CS, TFT_DC, TFT_RST) {} #else @@ -15,7 +11,6 @@ DspCore::DspCore(): Adafruit_GC9A01A(TFT_CS, TFT_DC, TFT_RST) {} void DspCore::initDisplay() { begin(); - if(DEF_SPI_FREQ > 0) setSPISpeed(DEF_SPI_FREQ); invert(); cp437(true); flip(); diff --git a/yoRadio/src/displays/displayILI9488.cpp b/yoRadio/src/displays/displayILI9488.cpp index a425088..3af057e 100644 --- a/yoRadio/src/displays/displayILI9488.cpp +++ b/yoRadio/src/displays/displayILI9488.cpp @@ -3,10 +3,6 @@ #include "dspcore.h" #include "../core/config.h" -#ifndef DEF_SPI_FREQ - #define DEF_SPI_FREQ 40000000UL /* set it to 0 for system default */ -#endif - #if DSP_HSPI DspCore::DspCore(): ILI9486_SPI(&SPI2, TFT_CS, TFT_DC, TFT_RST) {} #else @@ -16,7 +12,6 @@ void DspCore::initDisplay() { setSpiKludge(false); init(); - //if(DEF_SPI_FREQ > 0) setSPISpeed(DEF_SPI_FREQ); cp437(true); setTextWrap(false); setTextSize(1); diff --git a/yoRadio/src/displays/displaySSD1305.cpp b/yoRadio/src/displays/displaySSD1305.cpp index 12c4ef6..2fbe965 100644 --- a/yoRadio/src/displays/displaySSD1305.cpp +++ b/yoRadio/src/displays/displaySSD1305.cpp @@ -7,15 +7,11 @@ #define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32 or scan it https://create.arduino.cc/projecthub/abdularbi17/how-to-scan-i2c-address-in-arduino-eaadda #endif -#ifndef DEF_SPI_FREQ - #define DEF_SPI_FREQ 8000000UL /* set it to 0 for system default */ -#endif - #if DSP_MODEL==DSP_SSD1305 #if DSP_HSPI - DspCore::DspCore(): Adafruit_SSD1305(128, 64, &SPI2, TFT_DC, TFT_RST, TFT_CS, DEF_SPI_FREQ) {} + DspCore::DspCore(): Adafruit_SSD1305(128, 64, &SPI2, TFT_DC, TFT_RST, TFT_CS) {} #else - DspCore::DspCore(): Adafruit_SSD1305(128, 64, &SPI, TFT_DC, TFT_RST, TFT_CS, DEF_SPI_FREQ) {} + DspCore::DspCore(): Adafruit_SSD1305(128, 64, &SPI, TFT_DC, TFT_RST, TFT_CS) {} #endif #else #include diff --git a/yoRadio/src/displays/displaySSD1322.cpp b/yoRadio/src/displays/displaySSD1322.cpp index f96362a..4e9a994 100644 --- a/yoRadio/src/displays/displaySSD1322.cpp +++ b/yoRadio/src/displays/displaySSD1322.cpp @@ -3,17 +3,14 @@ #include "dspcore.h" #include "../core/config.h" -#ifndef DEF_SPI_FREQ - #define DEF_SPI_FREQ 16000000UL /* set it to 0 for system default */ -#endif #ifndef SSD1322_GRAYSCALE #define SSD1322_GRAYSCALE false #endif #if DSP_HSPI - DspCore::DspCore(): Jamis_SSD1322(256, 64, &SPI2, TFT_DC, TFT_RST, TFT_CS, DEF_SPI_FREQ) {} + DspCore::DspCore(): Jamis_SSD1322(256, 64, &SPI2, TFT_DC, TFT_RST, TFT_CS) {} #else - DspCore::DspCore(): Jamis_SSD1322(256, 64, &SPI, TFT_DC, TFT_RST, TFT_CS, DEF_SPI_FREQ) {} + DspCore::DspCore(): Jamis_SSD1322(256, 64, &SPI, TFT_DC, TFT_RST, TFT_CS) {} #endif void DspCore::initDisplay() { diff --git a/yoRadio/src/displays/displayST7735.cpp b/yoRadio/src/displays/displayST7735.cpp index 364a72b..24258a6 100644 --- a/yoRadio/src/displays/displayST7735.cpp +++ b/yoRadio/src/displays/displayST7735.cpp @@ -3,10 +3,6 @@ #include "dspcore.h" #include "../core/config.h" -#ifndef DEF_SPI_FREQ -#define DEF_SPI_FREQ 0 //26000000UL /* set it to 0 for system default */ -#endif - #if DSP_HSPI DspCore::DspCore(): Adafruit_ST7735(&SPI2, TFT_CS, TFT_DC, TFT_RST) {} #else @@ -15,7 +11,6 @@ DspCore::DspCore(): Adafruit_ST7735(&SPI, TFT_CS, TFT_DC, TFT_RST) {} void DspCore::initDisplay() { initR(DTYPE); - if(DEF_SPI_FREQ > 0) setSPISpeed(DEF_SPI_FREQ); cp437(true); invert(); flip(); diff --git a/yoRadio/src/displays/displayST7789.cpp b/yoRadio/src/displays/displayST7789.cpp index 620b401..504d402 100644 --- a/yoRadio/src/displays/displayST7789.cpp +++ b/yoRadio/src/displays/displayST7789.cpp @@ -3,10 +3,6 @@ #include "dspcore.h" #include "../core/config.h" -#ifndef DEF_SPI_FREQ - #define DEF_SPI_FREQ 40000000UL /* set it to 0 for system default */ -#endif - #if DSP_HSPI DspCore::DspCore(): Adafruit_ST7789(&SPI2, TFT_CS, TFT_DC, TFT_RST) {} #else @@ -21,7 +17,6 @@ void DspCore::initDisplay() { }else{ init(240,(DSP_MODEL==DSP_ST7789)?320:240); } - if(DEF_SPI_FREQ > 0) setSPISpeed(DEF_SPI_FREQ); invert(); cp437(true); flip(); diff --git a/yoRadio/src/displays/displayST7796.cpp b/yoRadio/src/displays/displayST7796.cpp index 1903e20..6b856a1 100644 --- a/yoRadio/src/displays/displayST7796.cpp +++ b/yoRadio/src/displays/displayST7796.cpp @@ -3,10 +3,6 @@ #include "dspcore.h" #include "../core/config.h" -#ifndef DEF_SPI_FREQ - #define DEF_SPI_FREQ 40000000UL /* set it to 0 for system default */ -#endif - #if DSP_HSPI DspCore::DspCore(): Adafruit_ST7796S_kbv(&SPI2, TFT_DC, TFT_CS, TFT_RST) {} #else @@ -15,7 +11,6 @@ DspCore::DspCore(): Adafruit_ST7796S_kbv(TFT_CS, TFT_DC, TFT_RST) {} void DspCore::initDisplay() { begin(); - if(DEF_SPI_FREQ > 0) setSPISpeed(DEF_SPI_FREQ); invert(); cp437(true); flip(); diff --git a/yoRadio/src/displays/displayST7920.cpp b/yoRadio/src/displays/displayST7920.cpp index 68a2965..0798553 100644 --- a/yoRadio/src/displays/displayST7920.cpp +++ b/yoRadio/src/displays/displayST7920.cpp @@ -3,14 +3,10 @@ #include "dspcore.h" #include "../core/config.h" -#ifndef DEF_SPI_FREQ - #define DEF_SPI_FREQ 8000000UL -#endif - #if DSP_HSPI - DspCore::DspCore(): ST7920(&SPI2, TFT_CS, DEF_SPI_FREQ) {} + DspCore::DspCore(): ST7920(&SPI2, TFT_CS) {} #else - DspCore::DspCore(): ST7920(&SPI, TFT_CS, DEF_SPI_FREQ) {} + DspCore::DspCore(): ST7920(&SPI, TFT_CS) {} #endif void DspCore::initDisplay() { diff --git a/yoRadio/src/main.cpp b/yoRadio/src/main.cpp index 73f09e3..c8bdafa 100644 --- a/yoRadio/src/main.cpp +++ b/yoRadio/src/main.cpp @@ -8,7 +8,7 @@ #include "core/network.h" #include "core/netserver.h" #include "core/controls.h" -#include "core/mqtt.h" +//#include "core/mqtt.h" #include "core/optionschecker.h" #include "core/timekeeper.h" #ifdef USE_NEXTION @@ -94,9 +94,6 @@ void setup() { initControls(); display.putRequest(DSP_START); while(!display.ready()) delay(10); - #ifdef MQTT_ROOT_TOPIC - mqttInit(); - #endif #if USE_OTA setupOTA(); #endif