Skip to content

Commit 9ed92d0

Browse files
authored
fix broken playlists #193
1 parent 50b82ba commit 9ed92d0

File tree

2 files changed

+139
-75
lines changed

2 files changed

+139
-75
lines changed

src/Audio.cpp

Lines changed: 137 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
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+
//---------------------------------------------------------------------------------------------------------------------
21472220
uint32_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

Comments
 (0)