Ethernet MP3 Player

There is someone else working on this with the rMP3 module.
Although it has its own processor, that is for controlling the module and you are much better off using an arduino as well to control the ethernet shield and the rMP3

As someone is already working on it, I'd be tempted towards the rMP3 if I were you. It also has a load of features that the MP3 board from sparkfun does not have.

Thanks! Do you know who is working on that?

What about PIN conflicts? I see that rMP3 both use D11 to D13. Are these shareable?

mynab:
Thanks! Do you know who is working on that?

What about PIN conflicts? I see that rMP3 both use D11 to D13. Are these shareable?

Have a read from the last post on this thread onwards.

The rMP3 only requires pins +5, GND, Digital 6 and Digital 7. The other pins are unused in normal operation.

You can however connect pins 6 and 7 to whatever pins you like on the board as they are simply used for serial communication (NewSoftSerial) and you can define them as any pins on the arduino.

Thanks. I contacted LB01 but from what I understand, music MP3 (128kps+) will not be possible with that kind of design because of the serial communication between Ethernet and MP3 right? So I need Rogue Robotics to put an Ethernet plug on their card :slight_smile:

Yeah, I thought that might be the bottleneck in the system.

I'll nudge bhagman this way later and see if he has any ideas.

Hi. Did you have a chance to talk to bhagman? I sent him a message but got no answer :frowning:

Oh sorry I forgot...

I'll send him an email. :slight_smile:
I know he's a busy man though :wink:

Sorry guys - got a few things on my plate.

I've been wanting to write this streaming MP3 player, but I just don't have the time.

I'm pretty sure it can be done - with some limitations. The MP3 bitrate will have to be pretty low (96 kbps or lower), but in most cases, that can still sound pretty good - especially mono (which you can do at 64 kbps and get pretty decent quality).

You will need to communicate with the rMP3 using a hardware serial port @ 115200 or better. Some boards, like our LEDHead for example, have 2 or more hardware serial ports. You can use the first serial port, but you won't have any feedback to the serial monitor, if that's your intention (and you may have to disconnect the USB cable, if your board has an on-board USB controller).

Furthermore, it will require buffering data to files, and switching on the fly as data is received. I can get into more detail about this, if someone wants to take on the task of building the streaming MP3 player.

b

Hi all,

I hope mynab is still working on that project? I did something similar last December. I have been stopped for a while but I managed to restart recently.

I use an ethershield with the ship enc28j60 (the cheap one), and that needs a special library. You may not be able to re-use my code if you don't use that kind of ethernet shield.

So far, I have been able to capture a stream on my serial port. My arduino connects to a given site, requests the stream, find the beggining of the data and sends the rest to the serial port so that if you capture it, you get an mp3 file that can be played normally.

Here is the code, modified from the ethershield Pachube example (to connect to a french radio):

/*
 * Read a pachube data streem and report results as csv
 */
# define WWW_client

#include <EtherShield.h>

static uint8_t mymac[6] = {0x54,0x55,0x99,0x10,0x00,0x25}; 
static uint8_t myip[4] = {192,168,1,1};
// Default gateway. The ip address of your DSL router. It can be set to the same as
// websrvip the case where there is no default GW to access the
// web server (=web server is on the same lan as this host)
static uint8_t gwip[4] = {192,168,1,254};

//============================================================================================================
// Pachube declarations
//============================================================================================================
#define PORT 80                   // HTTP

// the etherShield library does not really support sending additional info in a get request
// here we fudge it in the host field to add the API key
// Http header is
// Host: GET http://mp3.live.tv-radio.com/franceinter/all/franceinterhautdebit.mp3
// User-Agent: Arduino/1.0
// Accept: text/html
#define HOSTNAME "mp3.live.tv-radio.com\r\nKeep-Alive: 300"      // API key
static uint8_t websrvip[4] = { 0,0,0,0 };	// Get pachube ip by DNS call
#define WEBSERVER_VHOST "mp3.live.tv-radio.com"
#define HTTPPATH "/franceinter/all/franceinterhautdebit.mp3"      // Set your own feed ID here

//#define HOSTNAME "www.pachube.com\r\nX-PachubeApiKey: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"      // API key
//static uint8_t websrvip[4] = { 0,0,0,0 };	// Get pachube ip by DNS call
//#define WEBSERVER_VHOST "www.pachube.com"
//#define HTTPPATH "/api/0000.csv"      // Set your own feed ID here

static uint8_t resend=0;
static int8_t dns_state=0;

EtherShield es=EtherShield();

#define BUFFER_SIZE 900
static uint8_t buf[BUFFER_SIZE+1];

void browserresult_callback(uint8_t statuscode,uint16_t datapos){
//  if (datapos != 0)
//  {
//    // now search for the csv data - it follows the first blank line
//    // I'm sure that there is an easier way to search for a blank line - but I threw this together quickly
//    // and it works for me.
//    uint16_t pos = datapos;
//    while (buf[pos])    // loop until end of buffer (or we break out having found what we wanted)
//    {
//      while (buf[pos]) if (buf[pos++] == '\n') break;   // find the first line feed
//      if (buf[pos] == 0) break; // run out of buffer
//      if (buf[pos++] == '\r') break; // if it is followed by a carriage return then it is a blank line (\r\n\r\n)
//    }
//    if (buf[pos])  // we didn't run out of buffer
//    {
//      pos++;  //skip over the '\n' remaining
//      Serial.println((char*)&buf[pos]);
//    }
//  }
}

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

  /*initialize enc28j60*/
  es.ES_enc28j60Init(mymac);

  //init the ethernet/ip layer:
  es.ES_init_ip_arp_udp_tcp(mymac, myip, PORT);

  // init the web client:
  es.ES_client_set_gwip(gwip);  // e.g internal IP of dsl router

}

void loop()
{
  static uint32_t timetosend;
  uint16_t dat_p;
  int sec = 0;
  long lastDnsRequest = 0L;
  int plen = 0;
  int reqsent = 0; // flags the request sending
  int streamon = 0; // flags the streaming

  dns_state=0;

  while(1) {
    // handle ping and wait for a tcp packet - calling this routine powers the sending and receiving of data
    plen = es.ES_enc28j60PacketReceive(BUFFER_SIZE, buf);
    dat_p=es.ES_packetloop_icmp_tcp(buf,plen);
    if( plen > 0 ) {
      // We have a packet     
      //Serial.println("Packet received"); // debug
      // Check if IP data
      if (dat_p == 0 && reqsent == 0) {
        if (es.ES_client_waiting_gw() ){
          // No ARP received for gateway
          continue;
        }
        // It has IP data
        if (dns_state==0){
          sec=0;
          dns_state=1;
          lastDnsRequest = millis();
          es.ES_dnslkup_request(buf,(uint8_t*)WEBSERVER_VHOST);
          continue;
        }
        if (dns_state==1 && es.ES_udp_client_check_for_dns_answer( buf, plen ) ){
          dns_state=2;
          es.ES_client_set_wwwip(es.ES_dnslkup_getip());
        }
        if (dns_state!=2){
          // retry every minute if dns-lookup failed:
          if (millis() > (lastDnsRequest + 60000L) ){
            dns_state=0;
            lastDnsRequest = millis();
          }
          // don't try to use web client before
          // we have a result of dns-lookup
          Serial.println("DS lookup failed");
          continue;
        }
      }
      else
      {
        if (dns_state==1 && es.ES_udp_client_check_for_dns_answer( buf, plen ) ){
          dns_state=2;
          es.ES_client_set_wwwip(es.ES_dnslkup_getip());
        }
      }
      if (dat_p == 0 && reqsent == 1)  // case where data was received
      {
        
        // case where the request was sent previously and we keep receiving data
        if ( dns_state == 2 && reqsent == 1)
        {
          
          if (streamon==0) // here the stream has not started yet
          //The stream data starts after a double linefeed (0x0a 0x0d 0x0a 0x0d), so we need to find that first
          {
            uint16_t pos = 54;  // we start at 54 bytes, that falls just after the minimum TCP header
            while (pos<=plen)    // loop until end of buffer (or we break out having found what we wanted)
            {
              while (buf[pos]) if (buf[pos++] == '\n') break;   // find the first line feed
              if (pos>plen) break; // run out of buffer
              if (buf[pos++] == '\r') // if it is followed by a carriage return then it is a blank line (\r\n\r\n)
              {
                pos++;  //skip over the '\n' remaining
                if (pos>=plen) continue; // if there are no data, wait for the next packet
                streamon = 1; // if the data is there, yeepee ! start the streaming officially
                while (pos<=plen) // write the remaining data to the buffer
                {
                  Serial.print(buf[pos]); // could use pos++, need to see that later
                  pos++;
                }
                continue; 
              }
            }
          }
          else // if we are here, the streaming has already started and we just copy the data
          {
            for (int ii=dat_p+54;ii<plen;ii++)
            {
              Serial.print(buf[ii]);
            }
          }
          
          // need acknowledgement ?
          //es.ES_enc28j60PacketSend(plen,buf);
        }
        
      }
    }
    // If we have IP address for server and its time then request data

    if( dns_state == 2 && reqsent == 0 && millis() - timetosend > 31000)  // every 31 seconds
    {
      timetosend = millis();
      // note the use of PSTR - this puts the string into code space and is compulsory in this call
      // second parameter is a variable string to append to HTTPPATH, this string is NOT a PSTR
      es.ES_client_browse_url(PSTR(HTTPPATH), NULL, PSTR(HOSTNAME), &browserresult_callback);
      reqsent = 1; // data sent, so flag up
    }   
  }
}

Now, I have a problem with the rMP3: I cannot connect it. I remember having that problem before, but I don't remember how I solved it. Basically, when I load a basic rMP3 script, the program stops when it tries to use the rmp3 object. For instance, it stops when calling rmp3.getmoduletype().
I tried to change the baudrate, but it does not make a difference. I don't understand where the problem is...

@LB01: Which Arduino board are you using? Can you post the code you used to connect to the rMP3?

Well, the issue resolved on its own. I am not sure of what I did but now it works. I was probably confused with the baudrates. For info, I am using the arduino demilanove, with atmega 328.

Anyway, I tidied up the code and I regrouped all the writing operations in one callback (fillsdbuffer). If you just leave the serial.print operation in this function, and you remove all the operations on the rMP3, this code sends the stream to the serial port, ready to be read.

The problem I have now is that, when I added the rMP3 code, I could not open the file /buf1 where I plan to write the stream (line 108). Is there anything obviously wrong?

Here is the code:

/*
 * Stores a stream on the SD card, in buffer 1 (/stream/buf1)
 */
# define WWW_client

#include <EtherShield.h>
#include <NewSoftSerial.h>
#include <RogueMP3.h>
#include <RogueSD.h>

static uint8_t mymac[6] = {0x54,0x55,0x99,0x10,0x00,0x25}; 
static uint8_t myip[4] = {192,168,1,1};
// Default gateway. The ip address of your DSL router. It can be set to the same as
// websrvip the case where there is no default GW to access the
// web server (=web server is on the same lan as this host)
static uint8_t gwip[4] = {192,168,1,254};

//=========================== =================================================================================
// Website declarations
//============================================================================================================
#define PORT 80                   // HTTP

// the etherShield library does not really support sending additional info in a get request
// here we fudge it in the host field to add the API key
// Http header is
// Keep-Alive: 330
// Host: mp3.live.tv-radio.com
// User-Agent: Arduino/1.0
// Accept: text/html
#define HOSTNAME "mp3.live.tv-radio.com\r\nKeep-Alive: 300"      // API key
static uint8_t websrvip[4] = { 0,0,0,0 };	// Get pachube ip by DNS call
#define WEBSERVER_VHOST "mp3.live.tv-radio.com"
#define HTTPPATH "/franceinter/all/franceinterhautdebit.mp3"      // Set your own feed ID here

static uint8_t resend=0;
static int8_t dns_state=0;

EtherShield es=EtherShield();
#define BUFFER_SIZE 900
static uint8_t buf[BUFFER_SIZE+1];

// setting the rMP3
NewSoftSerial rmp3_serial(6, 7);
RogueMP3 rmp3(rmp3_serial);
RogueSD filecommands(rmp3_serial);
char path[96];  // path of the buffers
uint8_t volume = 40; // sound volume
#define SD_BUFFER_SIZE 128000 // size of each buffer on the SD card


void browserresult_callback(uint8_t statuscode,uint16_t datapos){
// left here in case it is needed later 
}

// fillsdbuffer transfers the data from the ethernet buffer to the SD cards
// fillsdbuffer( ethernet buffer, starting position, ending position,
//               SD buffer handle, current SD position)
void fillsdbuffer(uint8_t *buf, int pos, int plen, uint8_t filehandle, uint16_t sdpos){
  
  char s;
  while (pos<=plen) // write the remaining data to the buffer
  {
    s = char(buf[pos]);
    filecommands.write(filehandle,1,&s);
    Serial.print(buf[pos]); // could use pos++, need to see that later
    pos++;
    sdpos++;
  }
                    
}

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

  /*initialize enc28j60*/
  es.ES_enc28j60Init(mymac);

  //init the ethernet/ip layer:
  es.ES_init_ip_arp_udp_tcp(mymac, myip, PORT);

  // init the web client:
  es.ES_client_set_gwip(gwip);  // e.g internal IP of dsl router
  
  rmp3_serial.begin(9600); // parameter D3 in the RMP3.CFG config file (root dir)
  
  rmp3.sync();
  rmp3.stop();
  rmp3.setvolume(volume);
  filecommands.sync();
  
}

void loop()
{
  static uint32_t timetosend;
  int pos;
  int dat_p;
  int sec = 0;
  long lastDnsRequest = 0L;
  int plen = 0;
  int reqsent = 0; // flags the request sending
  int streamon = 0; // flags the streaming
  int8_t filehandle; // handle to the active SD buffer
  uint16_t sdpos = 0; // position of last data in the SD  buffer
  dns_state=0;

  // open the 1st SD buffer, directory /stream/
  filehandle = filecommands.open("/buf1",open_mode(OPEN_WRITE));
  if (filehandle<1) // in case of an error during the opening
  {
    Serial.println("Error: cannot open the file.");
    while (1) {}; // stops here, cannot continue anyway.
  }

  while(1) {
    // handle ping and wait for a tcp packet - calling this routine powers the sending and receiving of data
    plen = es.ES_enc28j60PacketReceive(BUFFER_SIZE, buf);
    dat_p=es.ES_packetloop_icmp_tcp(buf,plen);
    if( plen > 0 ) {
      // We have a packet
      // Check if IP data
      if (dat_p == 0 && reqsent == 0) {
        if (es.ES_client_waiting_gw() ){
          // No ARP received for gateway
          continue;
        }
        // It has IP data
        if (dns_state==0){
          sec=0;
          dns_state=1;
          lastDnsRequest = millis();
          es.ES_dnslkup_request(buf,(uint8_t*)WEBSERVER_VHOST);
          continue;
        }
        if (dns_state==1 && es.ES_udp_client_check_for_dns_answer( buf, plen ) ){
          dns_state=2;
          es.ES_client_set_wwwip(es.ES_dnslkup_getip());
        }
        if (dns_state!=2){
          // retry every minute if dns-lookup failed:
          if (millis() > (lastDnsRequest + 60000L) ){
            dns_state=0;
            lastDnsRequest = millis();
          }
          // don't try to use web client before
          // we have a result of dns-lookup
          Serial.println("DS lookup failed");
          continue;
        }
      }
      else
      {
        if (dns_state==1 && es.ES_udp_client_check_for_dns_answer( buf, plen ) ){
          dns_state=2;
          es.ES_client_set_wwwip(es.ES_dnslkup_getip());
        }
      }
      if (dat_p == 0 && reqsent == 1)  // case where data was received
      {
        
        // case where the request was sent previously and we keep receiving data
        if ( dns_state == 2 && reqsent == 1)
        {
          
          if (streamon==0) // here the stream has not started yet
          //The stream data starts after a double linefeed (0x0a 0x0d 0x0a 0x0d), so we need to find that first
          {
            pos = 54;  // we start at 54 bytes, that falls just after the minimum TCP header
            while (pos<=plen)    // loop until end of buffer (or we break out having found what we wanted)
            {
              while (buf[pos]) if (buf[pos++] == '\n') break;   // find the first line feed
              if (pos>plen) break; // run out of buffer
              if (buf[pos++] == '\r') // if it is followed by a carriage return then it is a blank line (\r\n\r\n)
              {
                pos++;  //skip over the '\n' remaining
                if (pos>=plen) continue; // if there are no data, wait for the next packet
                streamon = 1; // if the data is there, yeepee ! start the streaming officially
                fillsdbuffer(buf,pos,plen,filehandle,sdpos);
                continue; 
              }
            }
          }
          else // if we are here, the streaming has already started and we just copy the data
          {
            fillsdbuffer(buf,dat_p+54,plen,filehandle,sdpos);
//            for (int ii=dat_p+54;ii<plen;ii++)
//            {
//              Serial.print(buf[ii]);
//            }
          }
          
          // need acknowledgement ?
          //es.ES_enc28j60PacketSend(plen,buf);
        }
        
      }
    }
    // If we have IP address for server and its time then request data

    if( dns_state == 2 && reqsent == 0 && millis() - timetosend > 31000)  // every 31 seconds
    {
      timetosend = millis();
      // note the use of PSTR - this puts the string into code space and is compulsory in this call
      // second parameter is a variable string to append to HTTPPATH, this string is NOT a PSTR
      es.ES_client_browse_url(PSTR(HTTPPATH), NULL, PSTR(HOSTNAME), &browserresult_callback);
      reqsent = 1; // data sent, so flag up
    }
    
  }
}

Ok, I am nearly there, but I have a problem with the rMP3 module. It has problems to sync when the baudrate is different from 9600.
I have tried to include the baudrate change in the code because I first thought that the problem was coming from the SD card. So I start th communication at 9600, change to 57600, ask a confirmation by checking the baudrate parameters from the rMP3 (it gives a '3', indicating that it has changed correctly and it is still woking), and then I use the sync function and the program stops. What is the problem?
I upgraded the rMP3 firmware last December to reduce the latency between two files. Could there be a problem in the new firmware?

Here is the section of code I use for changing the baudrate:

Serial.println("starting rmp3");
  rmp3_serial.begin(9600); // default baudrate

  char setbaud = 0x44; //D
  uint8_t baudratecode = 3; //230400: parameter D7 in the config file
  //rmp3.changesetting(setbaud, baudratecode);
  Serial.println(rmp3.getsetting(setbaud));
  rmp3.changesetting(setbaud, baudratecode);
  delay(1000);
  rmp3_serial.begin(57600);
  Serial.println(rmp3.getsetting(setbaud)); // check that everything is fine
  
//  // back to 9600, for debugging purposes (so you don't have to power off the arduino every time)
//  baudratecode = 0;
//  rmp3.changesetting(setbaud, baudratecode);
//  delay(1000);
//  rmp3_serial.begin(9600);
//  Serial.println(rmp3.getsetting(setbaud));
  
  Serial.println("Sync");
  rmp3.sync();
  Serial.println("Stop");
  rmp3.stop();
  Serial.println("Set volume");
  rmp3.setvolume(volume);
  Serial.println("Sync filesystem");
  filecommands.sync();
  
  Serial.println("ready");

Have you tried setting the baud rate with the boot config file on the SD card instead of in code?

Yes, I did that first and I could not sync either when I used D5 and D7 as parameters.
Using D0 was not a problem, but 9600 is too low for streaming.

Since you're using the Duemilanove, you won't be able to get much better than 57600 bps on a SoftwareSerial port. Can you try setting the rMP3 to 38400 bps? (Setting: "D2")

See if that works. If it does, you'll have to:

  1. use Serial for your communications with the rMP3 (you will have to add jumpers from pins 6 and 7 to pins 0 and 1 - and you will have to remove the rMP3 each time you upload a sketch).

  2. use another board which has an extra hardware serial port (e.g. LEDHead, Arduino Mega, etc...

Oh I had forgotten about the Software Serial speed limitations - glad you came to the rescue with answers :smiley:

ok, so 38400 works for the sync but the stream is corrupted. I suppose the write operation to the SD takes too long at that baudrate.
So I will try to do as bhagman says. I will see if I can go up to 57600 using the serial port. Will I still need the SerialSoftware library?

If you use the hardware serial port, you won't need SoftwareSerial any more.

Just remember that you will have to remove the rMP3 (with power removed) each time you want to upload! (This only applies when using the same hardware serial port that is used for uploading the sketch).

e.g.

#include "RogueMP3.h"

RogueMP3 rmp3(Serial);

void setup(void)
{
  Serial.begin(57600);
  rmp3.sync();

  ...
}

I had some spare time to work on that issue.

I soldered a couple of pins to make it easier to connect/disconnect the rMP3. It works well, the only things is that it is confusing that the Tx/Rx pins on the solderable pads must be connected to the Rx/Tx of the arduino serial pins respectively (it must be a crossed cable). Apart from this point, I like this system, it is very flexible.

So I connected everything and modified a simple script to play a tune with the rMP3 using the serial port, and it works very well at 9600 but still fails at 57600. Basically, when I change the option D0 to D3 with the correct serial baudrate, the music does not start. Here is the setup function:

void setup()
{
  delay(2000);
  Serial.begin(9600);  // initialise the serial rMP3 connection
  
  rmp3.print("ST D0"); // if changed to D3, the module fails
  delay(100);
  Serial.begin(9600); // that would be changed to 57600
  delay(100);
  
  rmp3.sync();
  rmp3.stop();
  rmp3.setvolume(volume);
  
  filecommands.sync();

  // mix up our random number generator
  randomSeed(analogRead(0));

  updateSongList();
  
}

I suspect that the sync is still a problem. Any suggestion?

  Serial.begin(9600);  // initialise the serial rMP3 connection

rmp3.print("ST D0"); // if changed to D3, the module fails
  delay(100);
  Serial.begin(9600); // that would be changed to 57600
  delay(100);

You have two 'Serial.begin(####)' lines in there...