33 *
44 * Created on: Oct 26.2018
55 *
6- * Version 2.0.5e
7- * Updated on: Aug 02 .2022
6+ * Version 2.0.5f
7+ * Updated on: Aug 08 .2022
88 * Author: Wolle (schreibfaul1)
99 *
1010 */
@@ -171,9 +171,9 @@ Audio::Audio(bool internalDAC /* = false */, uint8_t channelEnabled /* = I2S_DAC
171171 m_i2s_config.sample_rate = 16000 ;
172172 m_i2s_config.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT;
173173 m_i2s_config.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT;
174- m_i2s_config.intr_alloc_flags = ESP_INTR_FLAG_LEVEL3 ; // interrupt priority
175- m_i2s_config.dma_buf_count = 8 ;
176- m_i2s_config.dma_buf_len = 1024 ;
174+ m_i2s_config.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1 ; // interrupt priority
175+ m_i2s_config.dma_buf_count = 16 ;
176+ m_i2s_config.dma_buf_len = 512 ;
177177 m_i2s_config.use_apll = APLL_DISABLE; // must be disabled in V2.0.1-RC1
178178 m_i2s_config.tx_desc_auto_clear = true ; // new in V1.0.1
179179 m_i2s_config.fixed_mclk = I2S_PIN_NO_CHANGE;
@@ -2144,6 +2144,79 @@ int Audio::read_OGG_Header(uint8_t *data, size_t len){
21442144 return 0 ;
21452145}
21462146// ---------------------------------------------------------------------------------------------------------------------
2147+ size_t Audio::process_m3u8_ID3_Header (uint8_t * packet){
2148+ uint8_t ID3version;
2149+ size_t id3Size;
2150+ bool m_f_unsync = false , m_f_exthdr = false ;
2151+ static uint64_t last_timestamp; // remember the last timestamp
2152+ static uint32_t lastSampleRate;
2153+ uint64_t current_timestamp = 0 ;
2154+ uint32_t newSampleRate = 0 ;
2155+
2156+ if (specialIndexOf (packet, " ID3" , 4 ) != 0 ) { // ID3 not found
2157+ if (m_f_Log) log_i (" m3u8 file has no mp3 tag" );
2158+ return 0 ; // error, no ID3 signature found
2159+ }
2160+ ID3version = *(packet + 3 );
2161+ switch (ID3version){
2162+ case 2 :
2163+ m_f_unsync = (*(packet + 5 ) & 0x80 );
2164+ m_f_exthdr = false ;
2165+ break ;
2166+ case 3 :
2167+ case 4 :
2168+ m_f_unsync = (*(packet + 5 ) & 0x80 ); // bit7
2169+ m_f_exthdr = (*(packet + 5 ) & 0x40 ); // bit6 extended header
2170+ break ;
2171+ };
2172+ id3Size = bigEndian (&packet[6 ], 4 , 7 ); // ID3v2 size 4 * %0xxxxxxx (shift left seven times!!)
2173+ id3Size += 10 ;
2174+ if (m_f_Log) log_i (" ID3 framesSize: %i" , id3Size);
2175+ if (m_f_Log) log_i (" ID3 version: 2.%i" , ID3version);
2176+
2177+ if (m_f_exthdr) {
2178+ log_e (" ID3 extended header in m3u8 files not supported" );
2179+ return 0 ;
2180+ }
2181+ if (m_f_Log) log_i (" ID3 normal frames" );
2182+
2183+ if (specialIndexOf (&packet[10 ], " PRIV" , 5 ) != 0 ) { // tag PRIV not found
2184+ log_e (" tag PRIV in m3u8 Id3 Header not found" );
2185+ return 0 ;
2186+ }
2187+ // if tag PRIV exists assume content is "com.apple.streaming.transportStreamTimestamp"
2188+
2189+
2190+ // so, the m3u8 sequence contains an ID3 header. A time stamp is expected in the header. After we got two
2191+ // m3u8 sequences we can estimate the time difference. Usually the runtime (that's the time difference) is slightly
2192+ // less than the targetDuration. Without correction, the audio memory quickly runs out
2193+ // (measured about 14KBytes per minute).For example, for the targetDuration 10s the time difference is 9.75s and
2194+ // thus 0.25s are missing.
2195+
2196+ current_timestamp = bigEndian (&packet[69 ], 4 );
2197+
2198+ double diff;
2199+ diff = current_timestamp - last_timestamp; // time difference in apple clock
2200+ diff /= 90000 ; // time difference in seconds (whole audiofile)
2201+ // log_i("diff %f,current_timestamp %lu last_timestamp %lu", diff, current_timestamp, last_timestamp);
2202+ diff = ((double )m_m3u8_targetDuration - diff) / m_m3u8_targetDuration; // time difference (per second)
2203+ diff = diff * (double )AACGetSampRate (); // abbreviation in samples/second
2204+ diff += 250 ; // it doesn't work without it, no idea why this is necessary
2205+ newSampleRate = AACGetSampRate () - diff;
2206+ if (m_f_Log) log_i (" newSampleRate %d" , newSampleRate);
2207+ last_timestamp = current_timestamp;
2208+ uint32_t delta = abs ((int32_t )(lastSampleRate - newSampleRate));
2209+ if (delta > 1 ) {
2210+ if (newSampleRate && newSampleRate < 96001 ){
2211+ log_i (" change lastSampleRate %d, newSampleRate %d" , lastSampleRate, newSampleRate);
2212+ setSampleRate (newSampleRate);
2213+ }
2214+ if (m_f_Log) log_i (" change lastSampleRate %d, newSampleRate %d" , lastSampleRate, newSampleRate);
2215+ lastSampleRate = newSampleRate;
2216+ }
2217+ return id3Size;
2218+ }
2219+ // ---------------------------------------------------------------------------------------------------------------------
21472220uint32_t Audio::stopSong () {
21482221 uint32_t pos = 0 ;
21492222 if (m_f_running) {
@@ -2252,6 +2325,7 @@ bool Audio::playChunk() {
22522325 }
22532326 }
22542327 if (getChannels () == 2 ) {
2328+ m_curSample = 0 ;
22552329 while (m_validSamples) {
22562330 if (!m_f_forceMono) { // stereo mode
22572331 sample[LEFTCHANNEL] = m_outBuff[m_curSample * 2 ];
@@ -2262,10 +2336,7 @@ bool Audio::playChunk() {
22622336 sample[LEFTCHANNEL] = xy;
22632337 sample[RIGHTCHANNEL] = xy;
22642338 }
2265- if (!playSample (sample)) {
2266- log_e (" can't send" );
2267- return false ;
2268- } // Can't send
2339+ playSample (sample);
22692340 m_validSamples--;
22702341 m_curSample++;
22712342 }
@@ -2337,12 +2408,13 @@ void Audio::loop() {
23372408 m_f_continue = false ;
23382409 if (timestamp2 < millis ()) {
23392410 httpPrint (m_lastHost);
2411+ remaintime = 1000 ;
23402412 }
23412413 }
23422414 else {
23432415 if (m_f_continue){ // processWebStream() needs more data
23442416 remaintime = (int32_t )(m_m3u8_targetDuration * 1000 ) - (millis () - timestamp1);
2345- if (m_m3u8_targetDuration < 10 ) remaintime += 1000 ;
2417+ // if(m_m3u8_targetDuration < 10) remaintime += 1000;
23462418 m_f_continue = false ;
23472419 setDatamode (AUDIO_PLAYLISTDATA);
23482420 }
@@ -2371,7 +2443,7 @@ bool Audio::readPlayListData() {
23712443 if (b > 9 ) b = b - 7 ; // Translate A..F to 10..15
23722444 chunksize = (chunksize << 4 ) + b;
23732445 }
2374- if (m_f_Log) log_i (" chunksize %d" , chunksize);
2446+ if (m_f_Log) log_i (" chunksize %d, contentLength %d " , chunksize, m_contentlength );
23752447 }
23762448
23772449 // reads the content of the playlist and stores it in the vector m_contentlength
@@ -2391,39 +2463,42 @@ bool Audio::readPlayListData() {
23912463 uint16_t pos = 0 ;
23922464 while (_client->available ()){ // super inner while :-))
23932465 pl[pos] = _client->read ();
2466+ ctl++;
23942467 if (pl[pos] == ' \n ' ) {pl[pos] = ' \0 ' ; pos++; break ;}
23952468 // if(pl[pos] == '&' ) {pl[pos] = '\0'; pos++; break;}
23962469 if (pl[pos] == ' \r ' ) {pl[pos] = ' \0 ' ; pos++; continue ;;}
23972470 pos++;
2398- if (pos == 511 ){ pos--; ctl++; continue ;}
2471+ if (pos == 511 ){ pos--; continue ;}
23992472 if (pos == 510 ) {pl[pos] = ' \0 ' ;}
2473+ if (ctl == chunksize) {pl[pos] = ' \0 ' ; break ;}
2474+ if (ctl == m_contentlength) {pl[pos] = ' \0 ' ; break ;}
24002475 }
2401- ctl += pos;
2476+ if (ctl == chunksize) break ;
2477+ if (ctl == m_contentlength) break ;
24022478 if (pos) {pl[pos] = ' \0 ' ; break ;}
2403- if (ctime + timeout < millis ()) {log_e (" timeout" ); goto exit;}
2404- } // inner while
24052479
2406- // if(m_contentlength > 0){
2407- // // we have one line only, terminate all crap
2408- // if(m_contentlength < strlen(pl)) {log_e("%i", m_contentlength); pl[m_contentlength] = '\0';}
2409- // // we have more line but no '\n' at the and -> terminate all crap
2410- // else if(m_contentlength < ctl){
2411- // int diff = ctl - m_contentlength;
2412- // int lastpos = strlen(pl) - diff;
2413- // pl[lastpos] = '\0';
2414- // }
2415- // }
2480+ if (ctime + timeout < millis ()) {
2481+ log_e (" timeout" );
2482+ for (int i = 0 ; i<m_playlistContent.size (); i++) log_e (" pl%i = %s" , i, m_playlistContent[i]);
2483+ goto exit;}
2484+ } // inner while
24162485
24172486 if (startsWith (pl, " <!DOCTYPE" )) {AUDIO_INFO (" url is a webpage!" ); goto exit;}
2487+ if (startsWith (pl, " <html" )) {AUDIO_INFO (" url is a webpage!" ); goto exit;}
24182488 if (strlen (pl) > 0 ) m_playlistContent.push_back (strdup ((const char *)pl));
24192489 if (m_playlistContent.size () == 100 ){
24202490 if (m_f_Log) log_i (" the maximum number of lines in the playlist has been reached" );
24212491 break ;
24222492 }
2493+ // termination conditions
2494+ // 1. The http response header returns a value for contentLength -> read chars until contentLength is reached
2495+ // 2. no contentLength, but Transfer-Encoding:chunked -> compute chunksize and read until chunksize is reached
2496+ // 3. no chunksize and no contentlengt, but Connection: close -> read all available chars
24232497 if (ctl == m_contentlength){while (_client->available ()) _client->read (); break ;} // read '\n\n' if exists
24242498 if (ctl == chunksize) {while (_client->available ()) _client->read (); break ;}
2499+ if (!_client->connected () && _client->available () == 0 ) break ;
24252500
2426- } // outer while6nUfOrsqhhT-331
2501+ } // outer while
24272502 lines = m_playlistContent.size ();
24282503 for (int i = 0 ; i < lines ; i++) { // print all string in first vector of 'arr'
24292504 if (m_f_Log) log_i (" pl=%i \" %s\" " , i, m_playlistContent[i]);
@@ -3184,6 +3259,7 @@ void Audio::processWebStreamTS() {
31843259 uint32_t availableBytes; // available bytes in stream
31853260 static bool f_tmr_1s;
31863261 static bool f_stream; // first audio data received
3262+ static bool f_firstPacket;
31873263 static int bytesDecoded;
31883264 static uint32_t byteCounter; // count received data
31893265 static uint32_t tmr_1s; // timer 1 sec
@@ -3197,45 +3273,44 @@ void Audio::processWebStreamTS() {
31973273 // first call, set some values to default - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
31983274 if (m_f_firstCall) { // runs only ont time per connection, prepare for start
31993275 f_stream = false ;
3276+ f_firstPacket = true ;
32003277 byteCounter = 0 ;
32013278 bytesDecoded = 0 ;
32023279 loopCnt = 0 ;
32033280 tmr_1s = millis ();
32043281 m_t0 = millis ();
32053282 ts_packetPtr = 0 ;
32063283 ts_parsePacket (0 , 0 , 0 ); // reset ts routine
3207- if (m_contentlength % 188 != 0 ){
3208- // For files without an ID3 header, the content length is always divided by 188 without a remainder
3209- static size_t ID3frameSize = 0 ;
3210- uint8_t ID3headerLength = 10 ;
3211- if (!ID3frameSize){
3212- uint8_t ID3headerLength = 10 ;
3213- if (_client->available () < ID3headerLength) return ;
3214- _client->read (ts_packet, ID3headerLength);
3215- read_ID3_Header (ts_packet, ID3headerLength);
3216- ID3frameSize = m_ID3Size;
3217- }
3218- if (ID3frameSize > 188 ) {log_e (" ID3 Header too long" ); stopSong (); return ;}
3219- if (_client->available () < ID3frameSize - ID3headerLength) return ;
3220- _client->read (ts_packet, ID3frameSize - ID3headerLength);
3221- read_ID3_Header (ts_packet, ID3frameSize - ID3headerLength);
3222- byteCounter += ID3frameSize;
3223- ID3frameSize = 0 ;
3224- }
3284+ m_controlCounter = 0 ;
32253285 m_f_firstCall = false ;
32263286 }
32273287
32283288 if (m_datamode != AUDIO_DATA) return ; // guard
3229- int framesize = 0 ;
32303289
3231- availableBytes = _client-> available ();
3290+ if (InBuff. freeSpace () < ts_packetsize && f_stream){ playAudioData (); return ;}
32323291
3233- while (InBuff.freeSpace () >= ts_packetsize && availableBytes){
3234- int res = _client->read (ts_packet + ts_packetPtr, 188 - ts_packetPtr);
3292+ availableBytes = _client->available ();
3293+ if (availableBytes){
3294+ int res = _client->read (ts_packet + ts_packetPtr, ts_packetsize - ts_packetPtr);
32353295 if (res > 0 ){
32363296 ts_packetPtr += res;
3297+ byteCounter += res;
32373298 if (ts_packetPtr < ts_packetsize) return ;
32383299 ts_packetPtr = 0 ;
3300+ if (f_firstPacket){ // search for ID3 Header in the first packet
3301+ f_firstPacket = false ;
3302+ uint8_t ID3_HeaderSize = process_m3u8_ID3_Header (ts_packet);
3303+ if (ID3_HeaderSize > ts_packetsize){
3304+ log_e (" ID3 Header is too big" );
3305+ stopSong ();
3306+ return ;
3307+ }
3308+ if (ID3_HeaderSize){
3309+ memcpy (ts_packet, &ts_packet[ID3_HeaderSize], ts_packetsize - ID3_HeaderSize);
3310+ ts_packetPtr = ts_packetsize - ID3_HeaderSize;
3311+ return ;
3312+ }
3313+ }
32393314 ts_parsePacket (&ts_packet[0 ], &ts_packetStart, &ts_packetLength);
32403315
32413316 if (ts_packetLength) {
@@ -3250,20 +3325,17 @@ void Audio::processWebStreamTS() {
32503325 memcpy (InBuff.getWritePtr (), &ts_packet[ws + ts_packetStart], ts_packetLength -ws);
32513326 InBuff.bytesWritten (ts_packetLength -ws);
32523327 }
3328+
32533329 }
3254- framesize += ts_packetLength;
3255- byteCounter += ts_packetsize;
32563330 if (byteCounter == m_contentlength){
32573331 byteCounter = 0 ;
32583332 m_f_continue = true ;
3259- break ;
32603333 }
32613334 if (byteCounter > m_contentlength) log_e (" byteCounter overflow" );
3262- availableBytes = _client->available ();
3263- if (framesize >= 512 ) break ;
32643335 }
3265- else {availableBytes = 0 ; break ;}
3336+
32663337 }
3338+
32673339 // timer, triggers every second - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
32683340 if ((tmr_1s + 1000 ) < millis ()) {
32693341 f_tmr_1s = true ; // flag will be set every second for one loop only
@@ -3275,7 +3347,7 @@ void Audio::processWebStreamTS() {
32753347 static uint8_t cnt_slow = 0 ;
32763348 cnt_slow ++;
32773349 if (f_tmr_1s) {
3278- if (cnt_slow > 25 && audio_info) audio_info (" slow stream, dropouts are possible" );
3350+ if (cnt_slow > 50 && audio_info) audio_info (" slow stream, dropouts are possible" );
32793351 f_tmr_1s = false ;
32803352 cnt_slow = 0 ;
32813353 }
@@ -3305,22 +3377,10 @@ void Audio::processWebStreamTS() {
33053377 }
33063378
33073379 // play audio data - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3308- if (!f_stream) return ; // 1. guard
3309- if (InBuff.bufferFilled () < 1024 ) return ; // 2. guard
3310- size_t data2decode = InBuff.bufferFilled ();
3311- bytesDecoded = sendBytes (InBuff.getReadPtr (), data2decode);
3312- if (bytesDecoded < 0 ) { // no syncword found or decode error, try next chunk
3313- uint8_t next = 200 ;
3314- if (InBuff.bufferFilled () < next) next = InBuff.bufferFilled ();
3315- InBuff.bytesWasRead (next); // try next chunk
3316- m_bytesNotDecoded += next;
3317- return ;
3380+ if (f_stream){
3381+ if (!availableBytes) {playAudioData (); return ;}
3382+ if (InBuff.freeSpace () < m_maxBlockSize) playAudioData ();
33183383 }
3319- else {
3320- if (bytesDecoded > 0 ) {InBuff.bytesWasRead (bytesDecoded); return ;}
3321- if (bytesDecoded == 0 ) return ; // syncword at pos0 found
3322- }
3323-
33243384 return ;
33253385}
33263386// ---------------------------------------------------------------------------------------------------------------------
@@ -3430,8 +3490,9 @@ void Audio::playAudioData(){
34303490 if (InBuff.bufferFilled () < InBuff.getMaxBlockSize ()) return ; // guard
34313491
34323492 int bytesDecoded = sendBytes (InBuff.getReadPtr (), InBuff.getMaxBlockSize ());
3433- // log_i("bytesDecoded %i", bytesDecoded);
3493+
34343494 if (bytesDecoded < 0 ) { // no syncword found or decode error, try next chunk
3495+ log_i (" err bytesDecoded %i" , bytesDecoded);
34353496 uint8_t next = 200 ;
34363497 if (InBuff.bufferFilled () < next) next = InBuff.bufferFilled ();
34373498 InBuff.bytesWasRead (next); // try next chunk
@@ -3519,7 +3580,7 @@ bool Audio::parseHttpResponseHeader() { // this is the response to a GET / reque
35193580 if (strcmp (c_host, m_lastHost) != 0 ) { // prevent a loop
35203581 int pos_slash = indexOf (c_host, " /" , 9 );
35213582 if (pos_slash > 9 ){
3522- if (!strncmp (c_host, m_lastHost, posColon )){
3583+ if (!strncmp (c_host, m_lastHost, pos_slash )){
35233584 AUDIO_INFO (" redirect to new extension at existing host \" %s\" " , c_host);
35243585 if (m_playlistFormat == FORMAT_M3U8) {
35253586 strcpy (m_lastHost, c_host);
@@ -3660,6 +3721,7 @@ bool Audio::parseHttpResponseHeader() { // this is the response to a GET / reque
36603721 if (m_f_Log) {log_i (" now parse playlist" );}
36613722 }
36623723 else {
3724+ AUDIO_INFO (" unknown content found at: %s" , m_lastHost);
36633725 goto exit;
36643726 }
36653727 return true ;
@@ -3797,6 +3859,7 @@ bool Audio::parseContentType(char* ct) {
37973859
37983860 else if (!strcmp (ct, " audio/scpls" )) ct_val = CT_PLS;
37993861 else if (!strcmp (ct, " audio/x-scpls" )) ct_val = CT_PLS;
3862+ else if (!strcmp (ct, " application/pls+xml" )) ct_val = CT_PLS;
38003863 else if (!strcmp (ct, " audio/mpegurl" )) ct_val = CT_M3U;
38013864 else if (!strcmp (ct, " audio/x-mpegurl" )) ct_val = CT_M3U;
38023865 else if (!strcmp (ct, " audio/ms-asf" )) ct_val = CT_ASX;
@@ -4447,8 +4510,8 @@ bool Audio::playSample(int16_t sample[2]) {
44474510 if (m_f_internalDAC) {
44484511 s32 += 0x80008000 ;
44494512 }
4450-
4451- esp_err_t err = i2s_write ((i2s_port_t ) m_i2s_num, (const char *) &s32, sizeof (uint32_t ), &m_i2s_bytesWritten, 50 );
4513+ m_i2s_bytesWritten = 0 ;
4514+ esp_err_t err = i2s_write ((i2s_port_t ) m_i2s_num, (const char *) &s32, sizeof (uint32_t ), &m_i2s_bytesWritten, 100 );
44524515 if (err != ESP_OK) {
44534516 log_e (" ESP32 Errorcode %i" , err);
44544517 return false ;
0 commit comments