baldram / ESP_VS1053_Library

A library for VS1053 MP3 Codec Breakout adapted for Espressif ESP8266 and ESP32 boards.
https://platformio.org/lib/show/1744/ESP_VS1053_Library
GNU General Public License v3.0
113 stars 37 forks source link

About examples->WebRadioDemo #47

Open nopnop2002 opened 4 years ago

nopnop2002 commented 4 years ago

Hello.

client.read() also reads the HTTP header.

There is no fatal problem, but you can exclude the HTTP header as follows.

My Code:

void HexDump(uint8_t * mp3buff, uint8_t bytesread) {
  int loop = (bytesread + 9) / 10;
  uint8_t index = 0;
  char str[20];
  //Serial.print("bytesread=");
  //Serial.println(bytesread);
  Serial.println();
  for (int i=0;i<loop;i++) {
    for(int j=0;j<10;j++) {
      if (index < bytesread) {
        if (mp3buff[index] < 0x10) Serial.print("0");
        Serial.print(mp3buff[index], HEX);
        Serial.print(" ");
        if (mp3buff[index] >= 0x20 && mp3buff[index] <= 0x7F) {
          str[j] = mp3buff[index]; 
        } else {
          str[j] = 0x20;
        }
        str[j+1] = 0;
        index++;
      }
    }
    Serial.println();
    Serial.println(str);
    delay(1);
  }
}

uint8_t IsHeaderEnd(uint8_t * mp3buff, uint8_t bytesread) {
  static char previus = 0;
  static int status = 0;
  for(int i=0;i<bytesread;i++) {
    if (mp3buff[i] == 0x0D) {
      Serial.print("0D i=");
      Serial.print(i);
      Serial.print(" previus=");
      Serial.println(previus,HEX);
      if (previus != 0x0A) status=0;
      if (previus == 0x0A) status++;
    }
    if (mp3buff[i] == 0x0A) {
      Serial.print("0A i=");
      Serial.print(i);
      Serial.print(" status=");
      Serial.print(status);
      Serial.print(" previus=");
      Serial.println(previus,HEX);
      if (previus == 0x0D) status++;
      if (status == 3) return i+1;
    }
    previus = mp3buff[i];
  }
  return 0;
}

void loop() {
    static uint8_t startData;
    static bool detectedHeader = false;

    if(!client.connected()){
      Serial.println("Reconnecting...");
      if(client.connect(host, httpPort)){
        client.print(String("GET ") + path + " HTTP/1.1\r\n" +
                  "Host: " + host + "\r\n" + 
                  "Connection: close\r\n\r\n");
      }
    }

    if(client.available() > 0){
      // The buffer size 64 seems to be optimal. At 32 and 128 the sound might be brassy.
      uint8_t bytesread = client.read(mp3buff, 64);
      startData = 0;
      if (!detectedHeader) {
        HexDump(mp3buff, bytesread);
        startData = IsHeaderEnd(mp3buff, bytesread);
        Serial.print("startData=");
        Serial.println(startData);
        if (startData > 0) detectedHeader = true;
      }

      player.playChunk(&mp3buff[startData], bytesread);
    }
}

Test result:

Simple Radio Node WiFi Radio
Connecting to SSID aterm-d5a4ee-g
.....WiFi connected
IP address: 
192.168.10.121
connecting to ice2.somafm.com
Requesting stream: /seventies-128-mp3

48 54 54 50 2F 31 2E 31 20 32 
HTTP/1.1 2
30 30 20 4F 4B 0D 0A 43 6F 6E 
00 OK  Con
74 65 6E 74 2D 54 79 70 65 3A 
tent-Type:
20 61 75 64 69 6F 2F 6D 70 65 
 audio/mpe
67 0D 0A 44 61 74 65 3A 20 4D 
g  Date: M
6F 6E 2C 20 30 39 20 4D 61 72 
on, 09 Mar
20 32 30 32 
 202
0D i=15 previus=4B
0A i=16 status=0 previus=D
0D i=41 previus=67
0A i=42 status=0 previus=D
startData=0                    ------> Not detect end of HTTP header

30 20 31 30 3A 35 38 3A 33 36 
0 10:58:36
20 47 4D 54 0D 0A 69 63 79 2D 
 GMT  icy-
62 72 3A 31 32 38 0D 0A 69 63 
br:128  ic
79 2D 67 65 6E 72 65 3A 4D 65 
y-genre:Me
6C 6C 6F 77 20 37 30 73 0D 0A 
llow 70s  
69 63 79 2D 6E 61 6D 65 3A 4C 
icy-name:L
65 66 74 20 
eft 
0D i=14 previus=54
0A i=15 status=0 previus=D
0D i=26 previus=38
0A i=27 status=0 previus=D
0D i=48 previus=73
0A i=49 status=0 previus=D
startData=0                    ------> Not detect end of HTTP header

43 6F 61 73 74 20 37 30 73 3A 
Coast 70s:
20 4D 65 6C 6C 6F 77 20 61 6C 
 Mellow al
62 75 6D 20 72 6F 63 6B 20 66 
bum rock f
72 6F 6D 20 74 68 65 20 53 65 
rom the Se
76 65 6E 74 69 65 73 2E 20 59 
venties. Y
61 63 68 74 20 66 72 69 65 6E 
acht frien
64 6C 79 2E 
dly.
startData=0                    ------> Not detect end of HTTP header

20 5B 53 6F 6D 61 46 4D 5D 0D 
 [SomaFM] 
0A 69 63 79 2D 6E 6F 74 69 63 
 icy-notic
65 31 3A 3C 42 52 3E 54 68 69 
e1:<BR>Thi
73 20 73 74 72 65 61 6D 20 72 
s stream r
65 71 75 69 72 65 73 20 3C 61 
equires <a
20 68 72 65 66 3D 22 68 74 74 
 href="htt
70 3A 2F 2F 
p://
0D i=9 previus=5D
0A i=10 status=0 previus=D
startData=0                    ------> Not detect end of HTTP header

77 77 77 2E 77 69 6E 61 6D 70 
www.winamp
2E 63 6F 6D 2F 22 3E 57 69 6E 
.com/">Win
61 6D 70 3C 2F 61 3E 3C 42 52 
amp</a><BR
3E 0D 0A 69 63 79 2D 6E 6F 74 
>  icy-not
69 63 65 32 3A 53 48 4F 55 54 
ice2:SHOUT
63 61 73 74 20 44 69 73 74 72 
cast Distr
69 62 75 74 
ibut
0D i=31 previus=3E
0A i=32 status=0 previus=D
startData=0                    ------> Not detect end of HTTP header

65 64 20 4E 65 74 77 6F 72 6B 
ed Network
20 41 75 64 69 6F 20 53 65 72 
 Audio Ser
76 65 72 2F 4C 69 6E 75 78 20 
ver/Linux 
76 31 2E 39 2E 35 3C 42 52 3E 
v1.9.5<BR>
0D 0A 69 63 79 2D 70 75 62 3A 
  icy-pub:
30 0D 0A 69 63 79 2D 75 72 6C 
0  icy-url
3A 68 74 74 
:htt
0D i=40 previus=3E
0A i=41 status=0 previus=D
0D i=51 previus=30
0A i=52 status=0 previus=D
startData=0                    ------> Not detect end of HTTP header

70 3A 2F 2F 73 6F 6D 61 66 6D 
p://somafm
2E 63 6F 6D 0D 0A 53 65 72 76 
.com  Serv
65 72 3A 20 49 63 65 63 61 73 
er: Icecas
74 20 32 2E 34 2E 30 2D 6B 68 
t 2.4.0-kh
31 30 0D 0A 43 61 63 68 65 2D 
10  Cache-
43 6F 6E 74 72 6F 6C 3A 20 6E 
Control: n
6F 2D 63 61 
o-ca
0D i=14 previus=6D
0A i=15 status=0 previus=D
0D i=42 previus=30
0A i=43 status=0 previus=D
startData=0                    ------> Not detect end of HTTP header

63 68 65 2C 20 6E 6F 2D 73 74 
che, no-st
6F 72 65 0D 0A 41 63 63 65 73 
ore  Acces
73 2D 43 6F 6E 74 72 6F 6C 2D 
s-Control-
41 6C 6C 6F 77 2D 4F 72 69 67 
Allow-Orig
69 6E 3A 20 2A 0D 0A 41 63 63 
in: *  Acc
65 73 73 2D 43 6F 6E 74 72 6F 
ess-Contro
6C 2D 41 6C 
l-Al
0D i=13 previus=65
0A i=14 status=0 previus=D
0D i=45 previus=2A
0A i=46 status=0 previus=D
startData=0                    ------> Not detect end of HTTP header

6C 6F 77 2D 48 65 61 64 65 72 
low-Header
73 3A 20 4F 72 69 67 69 6E 2C 
s: Origin,
20 41 63 63 65 70 74 2C 20 58 
 Accept, X
2D 52 65 71 75 65 73 74 65 64 
-Requested
2D 57 69 74 68 2C 20 43 6F 6E 
-With, Con
74 65 6E 74 2D 54 79 70 65 0D 
tent-Type 
0A 41 63 63 
 Acc
0D i=59 previus=65
0A i=60 status=0 previus=D
startData=0                    ------> Not detect end of HTTP header

65 73 73 2D 43 6F 6E 74 72 6F 
ess-Contro
6C 2D 41 6C 6C 6F 77 2D 4D 65 
l-Allow-Me
74 68 6F 64 73 3A 20 47 45 54 
thods: GET
2C 20 4F 50 54 49 4F 4E 53 2C 
, OPTIONS,
20 48 45 41 44 0D 0A 43 6F 6E 
 HEAD  Con
6E 65 63 74 69 6F 6E 3A 20 43 
nection: C
6C 6F 73 65 
lose
0D i=45 previus=44
0A i=46 status=0 previus=D
startData=0                    ------> Not detect end of HTTP header

0D 0A 45 78 70 69 72 65 73 3A 
  Expires:
20 4D 6F 6E 2C 20 32 36 20 4A 
 Mon, 26 J
75 6C 20 31 39 39 37 20 30 35 
ul 1997 05
3A 30 30 3A 30 30 20 47 4D 54 
:00:00 GMT
0D 0A 0D 0A FF FB 92 04 ED 0F -------> 0D 0A 0D 0A is end of HTTP header

F3 32 3F 55 83 38 42 E0 65 E7 
 2?U 8B e 
EA B0 65 E8 
  e 
0D i=0 previus=65
0A i=1 status=0 previus=D
0D i=40 previus=54
0A i=41 status=0 previus=D
0D i=42 previus=A
0A i=43 status=2 previus=D
startData=44                 -------> Detect end of HTTP header
nopnop2002 commented 4 years ago

I don't think it needs to be fixed.

It works fine without any changes.

Please close after reading this issue.

baldram commented 4 years ago

Hi @nopnop2002! Thank you. The community delivered the radio example. The author's idea was to provide a simple example. While people can extend it according to their needs, I think it's important to emphasise what are the potential issues to handle. I will add a comment to the example pointing to this thread as well as the other one regarding buffer.

nopnop2002 commented 4 years ago

Yes I think it's important to provide a simple example.

fabitom commented 4 years ago

Yup, metadata while streaming are disabled by default on most servers. You can control this by "Icy-MetaData: 0" header. Unhandled metadata will cause clicking. But support for metadata is not simple, you need additional buffer or ringbuffer, grap icy-metaint, skip metadata chunks, parse icy-* headers...

philippedc commented 4 years ago

Hi, I made several tries and I understand now why sometime the sound is brassy, and sometime it is clicking. Sorry but for my experience ring buffer does not solve my problem.

First: For some radios the sound is brassy. This is solve by increasing the buffer size : the brassy effect seems to be the routine is too low compare to the streaming. By increasing the buffer the client.read function is done less time, so it is faster.

Second: For some stations that make many clicking effect after a moment (few seconds to few minutes) the VS1053 crash, I must reboot to come back to make it running again. To solve this problem, I add some delays: the SPI.writeBytes seems to need a while between 2 calls. So I modify the VS1053.cpp to the following:

void VS1053::sdi_send_buffer(uint8_t *data, size_t len) { size_t chunk_length; // Length of chunk 32 byte or shorter

data_mode_on();
while (len >= vs1053_chunk_size ) // More to do?
{
    await_data_request(); // Wait for space available
    len -= vs1053_chunk_size;
    SPI.writeBytes(data, vs1053_chunk_size);
    delay(1);
    data += vs1053_chunk_size;
}
data_mode_off();

}

Third: I have told in another thread that when I compare a radio reception from the device and a PC, I noticed that the streaming from this web radio is much faster that on the PC. After a while the web radio either looses the signal, either crashes. Depending of the signal bitrate, the client.read should be cadenced at the right time. I do not know how to do, because for the moment I do not know how to automatically define how long client.read takes to fill its buffer. However I test this code that makes to web radio working much better:

static byte tempo = 0; if( tempo %2 == 0 ) { // it is not use to run each time ArduinoOTA.handle(); // for code update available from wifi server.handleClient(); // call to web server }

// if signal is lost if( !client.connected() ) { WiFi.status(); // wifi connexion checkup Serial.println("Tentative de reconnexion..."); if( client.connect(urlStation, portStation.toInt()) ) { client.print(String("GET ") + pathStation + " HTTP/1.1\r\n" + "Host: " + urlStation + "\r\n" + "Connection: close\r\n\r\n"); } return; } // end of !client.connected() test

// if connected to the radio station :) //if( client.available() > 0 ) { // not usefull if( !pause && tempo%10 == 0 ) { if( client.read(mp3buff, 256) == 256 ) { delay(1); player.playChunk(mp3buff, 256); } else player.playChunk(sampleMp3, sizeof(sampleMp3)); // for debugging if buffer is not full } tempo++; } // end of loop

This code uses very short time of 'pause' that synchronize the streaming. To improve this code, tempo%10 == 0 should be define automatically depending of the data flow speed.

fabitom commented 4 years ago

Whey you send 256 buffer to VS1053 then sdi_send_buffer will split that buffer to 32 (vs1053_chunk_size) and will send to VS1053 in loop. sdi_send_buffer already have proper delay in await_data_request() function. When Delay(1) in sdi_send_buffer helps you then I don't understand why... await_data_request checking dreq pin, in other words asking vs1053 if we can send new data. Hm... it looks like something is not making proper clock speed. Can you test only mono output ?

  write_register(0x07, 0x1e09);
  write_register(0x06, 0x0001);
philippedc commented 4 years ago

@fabitom include now in the VS1053::begin() function, however I don't notice significant improvement.

fabitom commented 4 years ago

Ok, you can try this:

if( client.available() > 0 ) {
...
} else {
Serial.print('.');
}

and watch if problems occour when client don't have data.

philippedc commented 4 years ago

I did similar yet: // if connected to the radio station :) if( client.available() > 0 ) {
if( !pause && tempo == 0 ) { bytesread = client.read(mp3buff, 512); delay(1); player.playChunk(mp3buff, bytesread); } } else player.playChunk(sampleMp3, sizeof(sampleMp3)); // for debugging if buffer is not full tempo++; if( tempo > 10 ) tempo=0;

I never hear the specific sampleMp3 when a radio is listened. So the client.available is always true, and in fact I do not understand this test must be performed each loop. However tempo is required for these clicking radios can play for more than 10 minutes. I improve duration with 30, 40, 50 instead of 10, but in this case most of other radios become brassy....

philippedc commented 4 years ago

.... I cannot see the way Edzelf has coded the equivalent of the client.read in its Esp-radio....

baldram commented 4 years ago

@philippedc Thanks for this analysis.

To solve this problem, I add some delays: the SPI.writeBytes seems to need a while between 2 calls. So I modify the VS1053.cpp

Are you using ESP8266? Right? I wonder whether instead of adding delay(1), the yield() would do the job? Would you please check and adjust your VS1053.cpp modification?

yield

AFIK the "yield" is designed to solve such issues (As described here, the reason for Yielding is preventing from various issues. The ESP8266 runs a lot of utility functions in the background – keeping WiFi connected, managing the TCP/IP stack, and performing other duties. Blocking these functions from running can cause the ESP8266 to crash and reset itself).

If the yield works fine, I would add it to VS1053.cpp before SPI.writeBytes like you did.

fabitom commented 4 years ago

.... I cannot see the way Edzelf has coded the equivalent of the client.read in its Esp-radio....

Esp-radio client.read

philippedc commented 4 years ago

@baldram it sounds much better !

If the yield works fine, I would add it to VS1053.cpp before SPI.writeBytes like you did.

il is after. I've also had a yield in the loop: if( client.available() > 0 ) { bytesread = client.read(mp3buff, 256); yield(); player.playChunk(mp3buff, bytesread); }

It make twenty minutes the web radio did not crash. I have a remark: most of the time the crash consists of the infinite repetition of the a noise that could be the playChunk buffer. Why in this case the ESP8266 does not perform a watchdog reset as the loop is stopped ?

philippedc commented 4 years ago

.... there is no WD reset because of the yield() in await_data_request(); I suppose ?

nopnop2002 commented 4 years ago

I will release the version corresponding to ESP-IDF.

https://github.com/nopnop2002/esp-idf-vs1053

It use RingBuffer.

But sometimes it lose network connection.

I don't know why.

Your modifications are welcome.

baldram commented 4 years ago

@philippedc

it sounds much better!

I'm not sure I follow. Does this yield help when it's put after SPI.writeBytes(...)?

I've also had a yield in the loop It makes twenty minutes the web radio did not crash.

Do I understand correct? It crashes after 20 minutes when putting yield in the loop. Right? But when putting after SPI.writeBytes, everything is fine like with dealy(1). Right?

I try to clarify whether to add yield or delay after SPI.writeBytes to the library's VS1053.cpp.

I have a remark: most of the time the crash consists of the infinite repetition of the a noise that could be the playChunk buffer. Why in this case the ESP8266 does not perform a watchdog reset > as the loop is stopped ? .... there is no WD reset because of the yield() in await_data_request(); I suppose ?

To be honest I don't have an answer to this question, unfortunately. But I guess the indirect yield might help too.

philippedc commented 4 years ago

@baldram I have 3 of these web radios at home. So it is easy to compare different code version. I had the idea of a delay(1) after the SPI.writeBytes just like it is for an anlogRead(), just to let the time for the device to settle... In fact I did not test with the yield() before SPI.writeBytes , I will test this afternoon. When I compare the new version of web radio with these yield() with the original code - they are identical except the yield(), I have less clicks - but there are - with the use of yield(), and if the streaming still takes some advance, this advance is very less than with the original version.
More over the clicking effect - the couiiiik should be the best expression - happens neither at the same streaming moment, nor at the same time. So I suppose it is a internal problem of the radio.

baldram commented 4 years ago

@philippedc

I had the idea of a delay(1) after the SPI.writeBytes just like it is for an anlogRead(), just to let the time for the device to settle... In fact I did not test with the yield() before SPI.writeBytes , I will test this afternoon.

I mean using yield after the SPI.writeBytes only. Not before. So I would like to clarify only whether modifying your code snippet with yield() like showed below is better? Or maybe it's better to keep it as it is with delay(1)? I mean your modification to VS1053.cpp.

yield

I will add the final solution to the library as it should not harm, but help in some cases, like yours.

So I suppose it is an internal problem of the radio.

It could be too, indeed.

nopnop2002 commented 4 years ago

If you are developing an ESP32 application in an Arduino environment, it will be built with a single task and usually scheduled on Core 1, unless you use xTaskCreate ().

delay () returns control to the scheduler and grants execution rights to other tasks waiting to run, but has no effect if core1 has no other tasks.

On the other hand, SPI transfer requests are temporarily stored in the system ring buffer and processed by the system task, but the system task is executed by the core 0.

In the case of ESP32, Delay () is not effective in a single task environment.

This is my personal opinion.

fabitom commented 4 years ago

IMHO putting Delay() between SPI.BeginTransaction and SPI.EndTransaction is quite risky. From what I see esp8266 need yield in main loop Adafruit VS1053 as example

        bytesread = client.read(mp3buff, 32);
        while (!player.data_request()) {
          yield();
        }
        player.playChunk(mp3buff, bytesread);

or after playChunk like here: frokats-project

fabitom commented 4 years ago

But sometimes it lose network connection.

I don't know why.

Your modifications are welcome.

Maybe wifi power save mode. I saw some similar problems. esp_wifi_set_ps

nopnop2002 commented 4 years ago

@fabitom Thank you. I'll try.

nopnop2002 commented 4 years ago

I added esp_wifi_set_ps, but still sometimes lose network connection.

There are other causes.

baldram commented 4 years ago

@fabitom

IMHO putting Delay() between SPI.BeginTransaction and SPI.EndTransaction is quite risky. From what I see esp8266 need yield in the main loop Adafruit VS1053 as example

Thanks. I see something similar in Edzelf's radio: https://github.com/Edzelf/Esp-radio/blob/e1639010a80a6516191cc15a900599f8a6794dd4/Esp_radio.ino#L369-L375

Now, looking at the ESP_VS1053_Library it was correctly extracted and has this part too: https://github.com/baldram/ESP_VS1053_Library/blob/master/src/VS1053.h#L71-L74

I wonder whether anything else is needed or just the client code might deal with yield additionally if required? I would put then comment to web radio example that in case of any issues it's worth to experiment in different ways, like @philippedc in own source code.

philippedc commented 4 years ago

@nopnop2002 does it lose the signal by the same way with all stations?

philippedc commented 4 years ago

@baldram I have left the idea of a yield or a delay in the library. I'm sure now the problem of clicking comes from client.read for some radio stations, unfortunately it happens for the one I listen the most. It is not a question of speed, this station is 64k, therefore some 128k reception are ok. The strangest thing is that reception is good at the beginning, the clicking effect grows slowly after a while (about 10 seconds)

baldram commented 4 years ago

@philippedc

I have left the idea of a yield or a delay in the library problem of clicking comes from client.read for some radio stations,

Thank you for clarification.

unfortunately, it happens for the one I listen the most

Sadly to hear that.

PS: On the other hand, looking at Edzelf's main radio loop, he is yielding a lot in the method mentioned below. So it's worth to keep this ESP feature in mind for some cases. https://github.com/Edzelf/Esp-radio/blob/e1639010a80a6516191cc15a900599f8a6794dd4/Esp_radio.ino#L2069-L2070

fabitom commented 4 years ago

@philippedc please send me address of that 64k station. I will check on my radio.

philippedc commented 4 years ago

@fabitom here is the clicking station: http://icecast.radiofrance.fr/franceculture-lofi.mp3 -> 64k or http://icecast.radiofrance.fr/franceculture-midfi.mp3 -> the same in 128K, worst reception, crash after few minutes

This one is loosing signal from time to time, to get it back I change of station then I come back to it: http://live02.rfi.fr/rfiafrique-64.mp3 or http://live-reflector.ice.infomaniak.ch/rfiafrique-64.mp3 -> the same radio station with the same loosing signal.

I have not yet studied in detail how Edzelf get the streaming, may be its method is more accurate.

fabitom commented 4 years ago

@philippedc All good here. Streams are ok.

philippedc commented 4 years ago

That does not help me.... :(

in this case: ' if( client.available() && !pause ) { for( byte i=0; i<32; i++ ) { mp3buff[i] = client.read(); delayMicroseconds(1); } player.playChunk(mp3buff, 32); ' the return value of client.read() is a byte. In this case: 'uint32_t maxfilechunk = client.available(); if(maxfilechunk > 0) { if( maxfilechunk > 1024 ) maxfilechunk = 1024; while( ringspace() && maxfilechunk-- ) putring(client.read()); while(rcount) { while(!player.data_request()) delay(1); playRing(getring());' }
} the return value of client.available() is a long. On the documentation I read "data available" but it consists of what ?

fabitom commented 4 years ago

Can you provide your full source ? and please use 4x -> ```` before and after code. At first you can't read in for loop 32 bytes if you don't konw if 32 bytes available, you need check client.available() >= 32. Idk rest of your code, what is rcount etc. please attach full source. client.available() return how much you can read at this moment.

philippedc commented 4 years ago

@fabitom it is the code you provide for a ring buffer: https://github.com/baldram/ESP_VS1053_Library/issues/44#issuecomment-576849568

baldram commented 4 years ago

@philippedc But further you modified it slightly I guess. So I think @fabitom asks about the version you use right now. Or is it exactly like in the mentioned comment? Also, @fabitom mentioned that streams "are ok". However, if I remember you said that the clicking effect or rebooting shows up after some time of listening (not immediately). I'm not sure whether @fabitom did a quick check only.

fabitom commented 4 years ago

I checked first three station for about 10-15 minutes and 5 minutes for the last one. There was 0 (breaks or lost packages), client.available() always was at >0.

fabitom commented 4 years ago

Please check exactly that code: and try to test with: CLIENT_BUFF from 256 to 8192 RING_BUF_SIZE from 10000 to 30000 VS_BUFF_SIZE only 32 or 64

of course adjust VS1053 PINS :)

#include <Arduino.h>
#include <VS1053.h>
#include <WiFi.h>

#define VS1053_CS     5
#define VS1053_DCS    16
#define VS1053_DREQ   4

#define VOLUME  80

#define VS_BUFF_SIZE 32
#define RING_BUF_SIZE 20000
#define CLIENT_BUFF   2048

VS1053 player(VS1053_CS, VS1053_DCS, VS1053_DREQ);
WiFiClient client;

const char* ssid = "TP-Link";
const char* password = "xxxxxxxx";

const char *host = "icecast.radiofrance.fr";
const char *path = "/franceculture-midfi.mp3";
int httpPort = 80;

uint16_t rcount = 0;
uint8_t* ringbuf;
uint16_t rbwindex = 0;
uint8_t* mp3buff;
uint16_t rbrindex = RING_BUF_SIZE - 1;

inline bool ringspace() {
  return ( rcount < RING_BUF_SIZE );     
}

void putring(uint8_t b ) {
  *(ringbuf + rbwindex) = b;
  if ( ++rbwindex == RING_BUF_SIZE ) {
    rbwindex = 0;
  }
  rcount++;
}
uint8_t getring() {
  if ( ++rbrindex == RING_BUF_SIZE ) {
    rbrindex = 0;
  }
  rcount--;
  return *(ringbuf + rbrindex);
}

void playRing(uint8_t b) {
  static int bufcnt = 0;
  mp3buff[bufcnt++] = b;
  if (bufcnt == sizeof(mp3buff)) {
    player.playChunk(mp3buff, bufcnt); 
    bufcnt = 0; 
  }  
}   

void setup() {
    Serial.begin(115200);
    mp3buff = (uint8_t *) malloc (VS_BUFF_SIZE);
    ringbuf = (uint8_t *) malloc (RING_BUF_SIZE);
    SPI.begin();
    player.begin();
    player.switchToMp3Mode();
    player.setVolume(VOLUME);
    WiFi.begin(ssid, password);      
    while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.print(".");
    }
    if (!client.connect(host, httpPort)) {
      Serial.println("Connection failed");
      return;
    }    
    client.print(String("GET ") + path + " HTTP/1.1\r\n" +
                  "Host: " + host + "\r\n" + 
              "Icy-MetaData: 1" + "\r\n" +
                  "Connection: close\r\n\r\n");
}

void loop() {
    uint32_t maxfilechunk;

    if(!client.connected()){
      Serial.println("Reconnecting...");
      if(client.connect(host, httpPort)){
        client.print(String("GET ") + path + " HTTP/1.1\r\n" +
                  "Host: " + host + "\r\n" + 
              "Icy-MetaData: 1" + "\r\n" +
                  "Connection: close\r\n\r\n");
      }
    }

    maxfilechunk = client.available();
    if(maxfilechunk > 0){
      if ( maxfilechunk > CLIENT_BUFF ) {
        maxfilechunk = CLIENT_BUFF;
      }     
      while ( ringspace() && maxfilechunk-- ) {
        putring(client.read());
      }
      yield();
      while (rcount && (player.data_request())) {
        playRing(getring());
      }   
    }
}

edited: please modify Icy-MetaData: 1 to Icy-MetaData: 0

philippedc commented 4 years ago

@baldram yes I make many tries, however the main code I use is still your example.

@fabitom I test your program. I replace #include by #include because I use a wemos/lolin wemos D1 mini, not an ESP32. I program runs but I do not get anything audible. After a while it scratches.

If you remember few months ago I opened a thread because the code did not run with updated ESP8266 core. Since this time the version grows up from 2.4.2 to 2.63, I've tried this last one but it still aborts for the same wire.h problem.

May be I do not have a good reception for a few particular radio stations because of the compiling environment ? I mean may be the Arduino IDE 1.8.10 with 2.4.2 ESP8266 core only gives a result not optimized ? Must I install platformIO to get a better use of the ESP ?

fabitom commented 4 years ago

Latest 8266 package for platformio is 2.3.3 Did you try some old versions (entry in platformio.ini)?

platform = espressif8266@2.2.3
or
platform = espressif8266@2.3.1
or 
platform = espressif8266@2.3.3

I've d1 mini here but no free vs1053 for test. Maybe on Friday.

nopnop2002 commented 4 years ago

@fabitom I tested your code using my esp32 + ArduinoIDE after modify Icy-MetaData: 1 to Icy-MetaData: 0.

The ringbuffer works well and plays smoothly. But sometimes loses network connection.

I have added errno indication to my esp-idf code. errno is 128 when an error occurs.

W (1826146) CLIENT: read_len = -1
W (1826146) CLIENT: errno = 128
I (1826146) CLIENT: Finish

There is some information about errno 128. But there is no solution.

https://github.com/espressif/esp-idf/issues/2540

fabitom commented 4 years ago

@nopnop2002 What is your RSSI value ? WiFi.RSSI()

nopnop2002 commented 4 years ago

@fabitom

void setup() {
    Serial.begin(115200);

    mp3buff = (uint8_t *) malloc (VS_BUFF_SIZE);
    ringbuf = (uint8_t *) malloc (RING_BUF_SIZE);
    SPI.begin();
    player.begin();
    player.switchToMp3Mode();
    player.setVolume(VOLUME);
    WiFi.begin(ssid, password);      
    while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.print(".");
    }

    for(int i=0;i<10;i++) {
      Serial.print("RSSI=");
      Serial.println(WiFi.RSSI());
      delay(1000);
    }

    Serial.print("ESP-IDF version:");
    Serial.println(esp_get_idf_version());

    if (!client.connect(host, httpPort)) {
      Serial.println("Connection failed");
      return;
    }    
    client.print(String("GET ") + path + " HTTP/1.1\r\n" +
                  "Host: " + host + "\r\n" + 
            "Icy-MetaData: 0" + "\r\n" +
                  "Connection: close\r\n\r\n");
}

void loop() {
    uint32_t maxfilechunk;

    if(!client.connected()){
      Serial.println("Reconnecting...");

      for(int i=0;i<10;i++) {
        Serial.print("RSSI=");
        Serial.println(WiFi.RSSI());
        delay(1000);
      }

      if(client.connect(host, httpPort)){
        client.print(String("GET ") + path + " HTTP/1.1\r\n" +
                  "Host: " + host + "\r\n" + 
            "Icy-MetaData: 0" + "\r\n" +
                  "Connection: close\r\n\r\n");
      }
    }

    maxfilechunk = client.available();
    if(maxfilechunk > 0){
      if ( maxfilechunk > CLIENT_BUFF ) {
        maxfilechunk = CLIENT_BUFF;
      }   
      while ( ringspace() && maxfilechunk-- ) {
        putring(client.read());
      }
      yield();
      while (rcount && (player.data_request())) {
        playRing(getring());
      }   
    }
}
RSSI=-47
RSSI=-48
RSSI=-49
RSSI=-47
RSSI=-48
RSSI=-49
RSSI=-49
RSSI=-49
RSSI=-49
RSSI=-51
ESP-IDF version:v3.2.3-14-gd3e562907
Reconnecting...
RSSI=-52
RSSI=-53
RSSI=-53
RSSI=-51
RSSI=-53
RSSI=-51
RSSI=-52
RSSI=-51
RSSI=-51
RSSI=-51

My Environment: Arduino 1.8.12

ESP-IDF included in Arduino-Core is very old. https://github.com/espressif/esp-idf/releases/tag/v3.2.3

fabitom commented 4 years ago

Your RSSI are verry good. Can you remove that client.connected{} block in loop function and test it ? Try only with connection in setup function. Put some logs if maxfilechunk < 1


if(maxfilechunk > 0){
...
} else {
  Serial.println('no data');
  Delay(100);
}
nopnop2002 commented 4 years ago
    maxfilechunk = client.available();
    if(maxfilechunk > 0){
      if ( maxfilechunk > CLIENT_BUFF ) {
        maxfilechunk = CLIENT_BUFF;
      }   
      while ( ringspace() && maxfilechunk-- ) {
        putring(client.read());
      }
      yield();
      while (rcount && (player.data_request())) {
        playRing(getring());
      }   
    } else {
      Serial.print("maxfilechunk=");
      Serial.println(maxfilechunk);
      //Serial.println("no data");
      delay(100);
    }

Immediately after startup, maxfilechunk = 0 continues for a while. After that, it will play normally.

RSSI=-46
RSSI=-49
RSSI=-48
RSSI=-49
RSSI=-49
RSSI=-51
RSSI=-48
RSSI=-49
RSSI=-49
RSSI=-49
ESP-IDF version:v3.2.3-14-gd3e562907
maxfilechunk=0
maxfilechunk=0
maxfilechunk=0
maxfilechunk=0
maxfilechunk=0
maxfilechunk=0
maxfilechunk=0
maxfilechunk=0
maxfilechunk=0
maxfilechunk=0
maxfilechunk=0
maxfilechunk=0
maxfilechunk=0
maxfilechunk=0
fabitom commented 4 years ago

or just count about 5-10sec when client.available = 0 and then reconnect. Hm... need to test this leter.

nopnop2002 commented 4 years ago

I changed my code a bit:

    maxfilechunk = client.available();
    if(maxfilechunk > 0){
      if ( maxfilechunk > CLIENT_BUFF ) {
        maxfilechunk = CLIENT_BUFF;
      }   
      while ( ringspace() && maxfilechunk-- ) {
        putring(client.read());
      }
      yield();
      while (rcount && (player.data_request())) {
        playRing(getring());
      }   
    } else {
      Serial.print(millis());
      Serial.print(" ");
      Serial.print("maxfilechunk=");
      Serial.println(maxfilechunk);
      //Serial.println('no data');
      delay(100);
    }

Immediately after startup, maxfilechunk = 0 continues for a while. After that, it will play normally for a while. (Between 26153 and 916683) Then again maxfilechunk = 0 forever.

RSSI=-53
RSSI=-53
RSSI=-52
RSSI=-52
RSSI=-52
RSSI=-53
RSSI=-53
RSSI=-52
RSSI=-53
RSSI=-51
ESP-IDF version:v3.2.3-14-gd3e562907
24635 maxfilechunk=0
24736 maxfilechunk=0
24836 maxfilechunk=0
24960 maxfilechunk=0
25060 maxfilechunk=0
25175 maxfilechunk=0
25275 maxfilechunk=0
25390 maxfilechunk=0
25490 maxfilechunk=0
25605 maxfilechunk=0
25705 maxfilechunk=0
25820 maxfilechunk=0
25920 maxfilechunk=0
26052 maxfilechunk=0
26153 maxfilechunk=0
916683 maxfilechunk=0
916783 maxfilechunk=0
916883 maxfilechunk=0
916983 maxfilechunk=0
917083 maxfilechunk=0
917183 maxfilechunk=0
917283 maxfilechunk=0
917383 maxfilechunk=0
917483 maxfilechunk=0
917583 maxfilechunk=0
917683 maxfilechunk=0
917783 maxfilechunk=0
917883 maxfilechunk=0
917983 maxfilechunk=0
918083 maxfilechunk=0
918183 maxfilechunk=0
918283 maxfilechunk=0
918383 maxfilechunk=0
918483 maxfilechunk=0
918583 maxfilechunk=0
918683 maxfilechunk=0
918783 maxfilechunk=0
918883 maxfilechunk=0
918983 maxfilechunk=0
919083 maxfilechunk=0
919183 maxfilechunk=0
919283 maxfilechunk=0
919383 maxfilechunk=0
919483 maxfilechunk=0
919583 maxfilechunk=0
919683 maxfilechunk=0
919783 maxfilechunk=0
919883 maxfilechunk=0
919983 maxfilechunk=0
920083 maxfilechunk=0
920183 maxfilechunk=0
wmarkow commented 4 years ago

Hi guys, I have also did some tests in this case. I have used the code from https://github.com/baldram/ESP_VS1053_Library/issues/47#issuecomment-599659073

My settings are:

#define VS_BUFF_SIZE 32
#define RING_BUF_SIZE 20000
#define CLIENT_BUFF   2048

My hardware is: NodeMCU v0.9 (which uses ESP8266) with VS1053 board. My software is: esp8266-2.3.0

First URL under test: ice3.somafm.com/u80s-64-aac it plays perfect, no sound clicks or glitches.

Second URL under test: icecast.radiofrance.fr/franceculture-lofi.mp3 it plays but not perfect. I heard some "clicks" or mostly some kind of short glitches (like in duration of 100ms?). Those glitches took place every few seconds. Sometimes I heard like I was under water or something. Those glitches are a bit annoying.

I have found a difference between those two streams: the first one is aac and the second is mpeg. Maybe it is related? I didn't do further tests.

I have used curl to get the stream informations: For the first stream:

$ curl -H "Icy-MetaData: 1" -v "ice3.somafm.com/u80s-64-aac" >/dev/null
* STATE: INIT => CONNECT handle 0x6000830c8; line 1491 (connection #-5000)
* Added connection 0. The cache now contains 1 members
* STATE: CONNECT => WAITRESOLVE handle 0x6000830c8; line 1532 (connection #0)
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0*   Trying 198.24.44.210:80...
* TCP_NODELAY set
* STATE: WAITRESOLVE => WAITCONNECT handle 0x6000830c8; line 1611 (connection #0)
* Connected to ice3.somafm.com (198.24.44.210) port 80 (#0)
* STATE: WAITCONNECT => SENDPROTOCONNECT handle 0x6000830c8; line 1667 (connection #0)
* Marked for [keep alive]: HTTP default
* STATE: SENDPROTOCONNECT => DO handle 0x6000830c8; line 1685 (connection #0)
> GET /u80s-64-aac HTTP/1.1
> Host: ice3.somafm.com
> User-Agent: curl/7.66.0
> Accept: */*
> Icy-MetaData: 1
>
* STATE: DO => DO_DONE handle 0x6000830c8; line 1756 (connection #0)
* STATE: DO_DONE => PERFORM handle 0x6000830c8; line 1877 (connection #0)
* Mark bundle as not supporting multiuse
* HTTP 1.0, assume close after body
* Marked for [closure]: HTTP/1.0 close after body
< HTTP/1.0 200 OK
< Content-Type: audio/aacp
< Date: Tue, 17 Mar 2020 21:59:03 GMT
< icy-br:64
< icy-genre:80s Synthpop
< icy-name:Underground 80s [SomaFM]
< icy-notice1:<BR>This stream requires <a href="http://www.winamp.com/">Winamp</a><BR>
< icy-notice2:SHOUTcast Distributed Network Audio Server/Linux v1.9.5<BR>
< icy-pub:0
< icy-url:http://somafm.com
< Server: Icecast 2.4.0-kh10
< Cache-Control: no-cache, no-store
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Headers: Origin, Accept, X-Requested-With, Content-Type
< Access-Control-Allow-Methods: GET, OPTIONS, HEAD
< Connection: Close
< Expires: Mon, 26 Jul 1997 05:00:00 GMT
< icy-metaint:16000
<
{ [2822 bytes data]
100  308k    0  308k    0     0  36411      0 --:--:--  0:00:08 --:--:--  7993

and for the second

$ curl -H "Icy-MetaData: 1" -v "icecast.radiofrance.fr/franceculture-lofi.mp3" >/dev/null
* STATE: INIT => CONNECT handle 0x6000830c8; line 1491 (connection #-5000)
* Added connection 0. The cache now contains 1 members
* STATE: CONNECT => WAITRESOLVE handle 0x6000830c8; line 1532 (connection #0)
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0*   Trying 143.204.233.75:80...
* TCP_NODELAY set
* STATE: WAITRESOLVE => WAITCONNECT handle 0x6000830c8; line 1611 (connection #0)
* Connected to icecast.radiofrance.fr (143.204.233.75) port 80 (#0)
* STATE: WAITCONNECT => SENDPROTOCONNECT handle 0x6000830c8; line 1667 (connection #0)
* Marked for [keep alive]: HTTP default
* STATE: SENDPROTOCONNECT => DO handle 0x6000830c8; line 1685 (connection #0)
> GET /franceculture-lofi.mp3 HTTP/1.1
> Host: icecast.radiofrance.fr
> User-Agent: curl/7.66.0
> Accept: */*
> Icy-MetaData: 1
>
* STATE: DO => DO_DONE handle 0x6000830c8; line 1756 (connection #0)
* STATE: DO_DONE => PERFORM handle 0x6000830c8; line 1877 (connection #0)
* Mark bundle as not supporting multiuse
* HTTP 1.1 or later with persistent connection
< HTTP/1.1 200 OK
< Content-Type: audio/mpeg
< Transfer-Encoding: chunked
< Connection: keep-alive
< Date: Tue, 17 Mar 2020 22:00:03 GMT
< icy-br: 32
< ice-audio-info: channels=1;samplerate=48000;bitrate=32
< icy-br: 32
< icy-name: franceculture-lofi.mp3
< icy-pub: 1
< Server: Icecast 2.4.0-kh13
< Cache-Control: no-cache, no-store
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Headers: Origin, Accept, X-Requested-With, Content-Type, Icy-MetaData
< Access-Control-Allow-Methods: GET, OPTIONS, HEAD
< Expires: Mon, 26 Jul 1997 05:00:00 GMT
< X-Cache: Miss from cloudfront
< Via: 1.1 c2c75215aa2ab067e062055fa68a3fdf.cloudfront.net (CloudFront)
< X-Amz-Cf-Pop: CPH50-C1
< X-Amz-Cf-Id: SX5KCxOVfP1YruXaagwqvvyJ6fb7nTc6jWCkzJmKDaIF-BGL-0K0-g==
<
{ [2403 bytes data]
100  103k    0  103k    0     0  35869      0 --:--:--  0:00:02 --:--:-- 36100

Maybe it is harder for VS1053 to play mpeg than aac?

philippedc commented 4 years ago

Thanks @wmarkow I'm feeling less alone :)

fabitom commented 4 years ago

My working radio is much more complicated then code I send you guys so I've some more code for vs1053. The main question, do you guys tested on vs1053 latest patch ? I've included that patch. Maybe thats diffirence... especially for aac.

philippedc commented 4 years ago

@fabitom I've tested several Arduino IDE version (1.8.5, 1.8.7, 1.8.10) and several ESP8266 core (2.3.0, 2.4.0, 2.4.1, 2.4.2, 2.6.3) from arduino.esp8266.com/stable/package_esp8266com_index.json only the 2.4.2 package allows the baldram web radio example to work as I described, with some clicks and lost signal. Your ring buffer version is not giving sound. I have not tested compilation from platformio yet, it looks much more complicated to set....

philippedc commented 4 years ago

@wmarkow would you mind to test with RFI http://live02.rfi.fr/rfiafrique-64.mp3 if you have the signal lost after a while ( like me ) ? The signal can disappear after few seconds to more than half an hour.