Stream Ripper This program will download MP3 data from SHOUTcast stream and save mp3 file in microSD card. Metadata also will be saved as another file when stream includes metadata.

Dependencies:   EthernetNetIf mbed SDFileSystem

main.cpp

Committer:
xshige
Date:
2010-09-20
Revision:
0:5490b791ee3d

File content as of revision 0:5490b791ee3d:


// Stream Ripper
//
// This program will download MP3 data from SHOUTcast stream
// and save mp3 file in microSD card.
// Metadata also will be saved as another file
// when stream includes metadata.
//
// written by: xshige

// 2010/9/20 version 0.5

// Set Recording Size for Downloading
// 1MB means around 1 minutes play (assume 128kbps)
#define RECSIZE (15*1000000)

// define this if you want metadata stream
#define METADATA

#include "mbed.h"
#include "EthernetNetIf.h"
#include "TCPSocket.h"
#include "NTPClient.h"

// define this if you use DHCP
#define DHCP

// define this if you use time&date as output file name
#define TDFILE

#define WRTFILE
#ifdef WRTFILE
#include "SDFileSystem.h"
SDFileSystem sd(p5, p6, p7, p8, "sd");
#endif


// maybe we need 4KB at least
#define SBUFSIZE (1024*4)
//#define SBUFSIZE (1024*8)
//#define SBUFSIZE (1024*16) //NG
//#define SBUFSIZE (1024*10) //NG

//#define TICK_DRIVEN

NTPClient ntp;
time_t ctTime;

/*
----------------

Message Smaples

*/

// Client Side:

// GET /live HTTP/1.0
// Host: gw
// Accept: */*
// User-Agent: mbed
// Icy-MetaData: 1
// Connection: close


/*

Server#0 Resopose:

HTTP/1.0 200 OK
Content-Type: audio/mpeg
icy-br:96
ice-audio-info: ice-samplerate=44100;ice-bitrate=96;ice-channels=2
icy-br:96
icy-description:www.SoloPianoRadio.com
icy-genre:Classical
icy-name:Whisperings: Solo Piano Radio
icy-pub:1
icy-url:http://www.solopianoradio.com
Server: Icecast 2.3.1
icy-metaint:16000


Server#1 Resopose:

ICY 200 OK
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.8<BR>
icy-name:SMOOTHJAZZ.COM - The Internet's Original Smooth Jazz Radio Station - Live from the Monterey Bay
icy-genre:Smooth Jazz
icy-url:http://www.smoothjazz.com
content-type:audio/mpeg
icy-pub:1
icy-metaint:32768
icy-br:128

------------------------
Notes:

* icy-metadata:1
a client can request to have metadata
(information about the music - ) included in the stream,
by adding this.

* ICY [CODE]
whereas code refers to http status code
(200 - ok, 404 - resource not found, 401 - service unavailable, etc.).

* icy-pub
 - Not sure, believe it indicates if the stream is public or private

* icy-metaint
 - an interval (in bytes) that specifies how often metadata updates
 will be sent to the client.

* icy-br
 - BitRate, seems mostly informational as most clients
 encountered seem to support VBR (Variable BitRate).

-------------------------
*/

#ifdef DHCP
EthernetNetIf eth;
#else
EthernetNetIf eth(
    IpAddr(192,168,0,25), //IP Address
    IpAddr(255,255,255,0), //Network Mask
    IpAddr(192,168,0,1), //Gateway
    IpAddr(192,168,0,1)  //DNS
);
#endif

DigitalOut led1(LED1, "led1");
DigitalOut led2(LED2, "led2");
DigitalOut led3(LED3, "led3");
DigitalOut led4(LED4, "led4");

TCPSocket sock;
char inbuf[2048]; // IP input buffer
char* cp;

// stream
int slen; // stream length
unsigned long int tlen=0; // total lenth of downloaded file


//-------------------------------------------------

class RingBuffer
{
    protected:
        unsigned char *buffer;
        int buffersize;
        int rp; // read index
        int wp; // write index
    
    public:
        RingBuffer(int bufsiz)
        {
            buffersize = bufsiz;
     
            buffer = new unsigned char[buffersize];
        //    memset(buffer, 0, sizeof(char) * buffersize);
 
            // reset pointer to make empty
            rp = 0;
            wp = 0;
        };
 
        ~RingBuffer(void)
        {
            delete[] buffer;
        };
 
        int GetByte(unsigned char *cp);
        int PutByte(unsigned char c);
        int IsEmpty(void); 
};


int RingBuffer::GetByte(unsigned char *cp )
{
   if( rp != wp ) {    // not empty
      *cp = buffer[rp];
      rp = (rp + 1) % buffersize;
      return 0;
   } else {            // empty
      return -1;
   }
}


int RingBuffer::PutByte(unsigned char c )
{
   int next = (wp + 1) % buffersize;
   if( next == rp ) {
      printf("*** Buffer Full ***\r\n" );
//        printf("*");//debug
        return -1;
   }
   buffer[wp] = c;
   wp = next;
   return 0;
}


int RingBuffer::IsEmpty(void)
{
   return  (rp==wp)?(true):(false);
}

//-----------------


// create RingBuffer
RingBuffer  sbuf(SBUFSIZE); // 


char sResponse[800]; // keeps Stream Response
char sMetadata[200]; // keeps Stream Metadata
char sPrevMetadata[200]; // keeps Previous Stream Metadata

// client state
enum wSTATE{NOP,PUT_HEADER} WriteState;
enum rSTATE{GET_RESPONSE,GET_STREAM} ReadState;

// metadata
char mbuf[512];
int mbyte; // lenth byte, -1 means this value is not effective
int mlen; // metadata length
int metaint=0; // metadate interval

void initDec(void); // prototype


void onTCPSocketEvent(TCPSocketEvent e) {
    int i,len;
    switch (e) {
        case TCPSOCKET_CONNECTED:
        printf("TCP Socket Connected\r\n"); // this printf disturb socket work correctly
//        break; // we must comment out ?
        case TCPSOCKET_WRITEABLE:
            //Can now write some data...
//        printf("TCP Socket Writable\r\n"); // debug
            switch(WriteState) {
                case PUT_HEADER:
                    const char* str =
//                  "GET /live HTTP/1.0\r\n"  // use this if solopiano
                    "GET / HTTP/1.0\r\n"
                    "Host: gw\r\n"
                    "Accept: */*\r\n"
                    "User-Agent: mbed\r\n"
#ifdef METADATA
                  "Icy-MetaData: 1\r\n"   // send this if you want Metadata
#endif
//                    "Connection: close\r\n"
                    "\r\n"
                    ;
                    sock.send(str, strlen(str));
                    printf("\r\nHEADER:\r\n%s\r\n", str); // display PUT_HEADER
                    WriteState=NOP;
                    ReadState=GET_RESPONSE;
                    break;
                case NOP:
                    break;
            } // switch(WriteState)
            break;
        case TCPSOCKET_READABLE:
            //Can now read some data...
//           printf("TCP Socket Readable\r\n"); // debug
            switch(ReadState) {
                case GET_RESPONSE:
                    len=sock.recv(inbuf, sizeof inbuf);
                    cp=strstr(inbuf,"\r\n\r\n");
                    if (cp==NULL) {
                        inbuf[len]=0;
                        // printf("%s",inbuf); // debug
                        sprintf(sResponse,"%s",inbuf);
                        return;  // still read response again
                    }
                    //
                    *cp=0;
                    sprintf(sResponse+strlen(sResponse),"%s\r\n\r\n",inbuf);
                    printf("RESPONSE:\r\n%s",sResponse);
                    // get metaint value
                    cp=strstr(sResponse,"icy-metaint:");
                    if (cp==NULL) metaint=0; else sscanf(cp+strlen("icy-metaint:"),"%d",&metaint);
                    printf("\r\nmetaint: %d\r\n\r\n",metaint); //debug
                    //
                    i=strlen(inbuf)+4; // bump bitstream right after response
                    ReadState=GET_STREAM;
                    initDec();
                    //
                    while(i<len) {
                            // write one bye
                            sbuf.PutByte(inbuf[i]);i++;
                    };
                    return;
                    break;
                   //
                case GET_STREAM:
                    // receive data ****repeatedly****
                    while(len=sock.recv(inbuf, sizeof inbuf)) {
                        i=0;                    
                        // save len bytes
                        while (i<len) {
                            sbuf.PutByte(inbuf[i]);i++;
                        } 
                     } // while (len=sock...)
                    return; // get more stream
                    break;           
                    //
                } // switch (ReadState)
                break;//
        case TCPSOCKET_CONTIMEOUT:
            printf("TCP Socket Timeout\r\n");
            break;
        case TCPSOCKET_CONRST:
            printf("TCP Socket CONRST\r\n");
            break;
        case TCPSOCKET_CONABRT:
            printf("TCP Socket CONABRT\r\n");
            printf("Maybe Server Down...\r\n");
            break;
        case TCPSOCKET_ERROR:
            printf("TCP Socket Error\r\n");
            break;
        case TCPSOCKET_DISCONNECTED:
            //Close socket...
            printf("TCP Socket Disconnected\r\n");
            sock.close();
            break;
        }// switch(e)
}


//--------------------------------


#ifdef WRTFILE
FILE *fp, *fp2;
char outfile1[32];
char outfile2[32];
#endif

void initDec(void) {
    slen=0;mbyte=-1;mlen=0;
#ifdef WRTFILE
#ifdef TDFILE
//    char outfile[32];
    // make output file for MP3
    ctTime=time(NULL);
    struct tm *t=localtime(&ctTime);
    sprintf(outfile1,"/sd/%02d%02d%02d%02d.mp3",
//        t->tm_year+1900,
        t->tm_mon+1,
        t->tm_mday,
        t->tm_hour,
        t->tm_min);
//    printf("%s\r\n",outfile); //debug
    fp = fopen(outfile1, "wb");
#else
    fp = fopen("/sd/stream.mp3", "wb");
#endif
    if(fp == NULL) {
        error("Could not open file for download\n");
    }
#ifdef METADATA
#ifdef TDFILE
    // make output file for Metadata
    sprintf(outfile2,"/sd/%02d%02d%02d%02d.met",
//        t->tm_year+1900,
        t->tm_mon+1,
        t->tm_mday,
        t->tm_hour,
        t->tm_min);
//    printf("%s\r\n",outfile); //debug
    fp2 = fopen(outfile2, "w");
#else
    fp2 = fopen("/sd/stream.met", "w");
#endif
    if(fp2 == NULL) {
        error("Could not open file for metadata\n");
    }
    ctTime=time(NULL); // get seconds
    fprintf(fp2,"%s\n%s\n---------------\n",
            sResponse,ctime(&ctTime)); // write stream information on file
#endif    
    // display time&date
    ctTime = time(NULL); // get seconds
    printf("%s\r\n", ctime(&ctTime)); 
    //
    float edt=(RECSIZE)/1000000;
    if (metaint==0) {
        printf("Now Downloading(Metadata-less Stream)...\r\n");
        printf("Estimated Donwload Time: %3.2f minutes\r\n\r\n",edt);
    } else {
        printf("Now Downloading(Stream with Metadata)...\r\n");
        printf("Estimated Donwload Time: %3.2f minutes\r\n\r\n",edt);        
    }
#endif
    return;
}

void sendDec(unsigned char sbyte) {
     unsigned char wc=sbyte;
     if (metaint==0) {
         // we have no metadata in a stream
         // put one byte to decoder
#ifdef WRTFILE
        fwrite(&wc,1,1,fp);
//         fputc(sbyte,fp); /////
        tlen++;
       if (tlen==RECSIZE) {
            fclose(fp);
            printf("\r\n*** Download Complete.***\r\n");
            while(true); // dynamic stop
       }
#endif
         return;
     }
     //
     if (slen==metaint) {
         mbyte=sbyte;
         mlen=0;
         slen++;
         return;
     }
     //
     if (mbyte==-1) {
         // put one byte to decoder
#ifdef WRTFILE
        fwrite(&wc,1,1,fp);
//         fputc(sbyte,fp); /////
#endif
#if 1
        if (0!=strcmp(sMetadata,sPrevMetadata)) {
            printf("Metadata: %s\r\n",sMetadata); // display metadata
//            printf("mbyte:%d slen:%d tlen:%d\r\n\r\n",mbyte,slen,tlen); // debug
            printf("tlen:%d\r\n\r\n",tlen); // display total length
            sprintf(sPrevMetadata,"%s",sMetadata); // copy to previous
/////            fclose(fp);fp=fopen(outfile1,"ab"); // switch append mode
/////            fclose(fp2);fp2=fopen(outfile2,"a");
            fprintf(fp2,"%d\n%s\n",tlen,sMetadata); // save metadata on TXT file

         }
#endif
         slen++;
         tlen++;
         return;     
     }
     //
     // comming here when we are in reading metadata
     if (slen==(metaint+16*mbyte+1)) {
         // we have all bytes for metadata
         mbuf[mlen]=0; // make terminator
         if (0<mbyte) {
            sprintf(sMetadata,"%s",mbuf); // coy new metadata
         }
#ifdef WRTFILE
        fwrite(&wc,1,1,fp);
//         fputc(sbyte,fp); //// 
        tlen++;
        if (tlen>RECSIZE) {
                printf("\r\n*** Download Complete.***\r\n");
                fclose(fp);fclose(fp2);
                // display time&date
                ctTime = time(NULL); // get seconds in UTC
                printf("%s\r\n", ctime(&ctTime)); 
                while(true); // dynamic stop
        }
#endif
 //        printf("metaint: %d slen:%d\r\n",metaint,slen); //debug
         mbyte=-1;mlen=0;
         slen=1; //OK
         return;
     }  // if  (slen==(metaint+16*mbyte+1))
    //
    // coming here still reading metadata 
    // we still read one byte of metadata
    mbuf[mlen]=sbyte;mlen++; slen++;
    return;
}


#ifdef TICK_DRIVEN
void TickProcess(void) {
    int x;
    for(x=0;x<(SBUFSIEZ/2);x++) {
        unsigned char sbyte;
        if (-1==sbuf.GetByte(&sbyte)) {
          //  Net::poll();
            return;
        }
        sendDec(sbyte);
    }
    return;
}

//-----------------------

Ticker tick;
#endif

int main() {

//    set_time();

    // make debug port Fast
   Serial pc(USBTX, USBRX);
//    pc.baud(9600);
    pc.baud(115200);
//  pc.baud(230400);

// init string
sprintf(sResponse,"");

// init medatadata string
sprintf(sMetadata,"StreamTitle='Currently, Unknown';");
sprintf(sPrevMetadata,"");



    printf("\r\n");
    printf("Setting up...\r\n");

    EthernetErr ethErr = eth.setup();
    if (ethErr) {
        printf("Error %d in setup.\r\n", ethErr);
        return -1;
    };

// Init State for Read/Write
    ReadState=GET_RESPONSE;
    WriteState=PUT_HEADER;


//----------------------------------
// setup clock for time stamp
//    NTPClient ntp;
//    time_t ctTime;

    Host timeServer(IpAddr(), 123, "0.uk.pool.ntp.org");
    ntp.setTime(timeServer);
    
    ctTime = time(NULL); // get seconds in UTC
    ctTime = ctTime+(3600*9); // convert JST (please change to fit your localtime)
    set_time(ctTime); // re-setup  RTC 
    printf("\r\nTime is setup now (JST): %s\r\n", ctime(&ctTime)); 

#if 0
// test program for time
    ctTime=time(NULL);
    struct tm *t=localtime(&ctTime);
    printf("%4d %02d/%02d %02d:%02d:%02d\r\n\r\n",
        t->tm_year+1900,
        t->tm_mon+1,
        t->tm_mday,
        t->tm_hour,
        t->tm_min,
        t->tm_sec);
#endif
    
//-----------------------------

   printf("Setup OK\r\n");


// ************** STATION DEFINITIONS  **************

//// http://pianosolo.streamguys.net:80/live
//Host server(IpAddr(216,246,105,11), 80); //solo piano
//Host server(IpAddr(), 80,"pianosolo.streamguys.net"); //solo piano

// SMOOTHJAZZ.COM - The Internet's Original Smooth Jazz Radio Station
// - Live from the Monterey Bay
//   Host server(IpAddr(67,213,217,212), 80); //smooth jazz

//Folk Alley  (( All Folk.  All The Time.  ))
// http://66.225.205.8:8000
    Host server(IpAddr(66,225,205,8), 8000);
    
// 1-ONE NATION FM.COM GOSPEL RADIO
// http://208.85.240.2:8094
//  Host server(IpAddr(208,85,240,2), 8094);


// GotRadio - Celti
//http://64.62.164.211:3000
//    Host server(IpAddr(64,62,164,211), 3000);

// AnimeNfo Radio | Serving you the best Anime music!
//http://216.18.227.252:8000
//   Host server(IpAddr(216,18,227,252), 8000);

// ************** end of STATION DEFINITIONS  **************

    // display IP address and port# of server
    IpAddr serverIp = server.getIp();
    int port = server.getPort();      
    printf("Connecting... %d.%d.%d.%d:%d\r\n", 
        serverIp[0],serverIp[1],serverIp[2],serverIp[3],port);


    TCPSocketErr bindErr = sock.connect(server);

    sock.setOnEvent(&onTCPSocketEvent);
    
    Timer tmr;
    tmr.start();

#ifdef TICK_DRIVEN
    tick.attach(&TickProcess,0.1); // 100ms tick, this interval depens on SBUFSIZE
#endif

    while (true) {
        Net::poll();

#ifndef TICK_DRIVEN
         int x;
         for(x=0;x<(SBUFSIZE/2);x++) {
            unsigned char sbyte;
            if (-1==sbuf.GetByte(&sbyte)) {
             //   Net::poll();
                break;
            }
            sendDec(sbyte);
         }
#endif
        if (tmr.read() > 0.05) {
            tmr.reset();
             if (metaint>0) {
                 led4=!led4; //Show that we are alive
                 if ((metaint/4)<slen) led3=1; else led3=0;
                 if ((metaint*2/4)<slen) led2=1; else led2=0;
                 if ((metaint*3/4)<slen) led1=1; else led1=0;             
             } else {
                led1=!led1; // indicate no metadata
                led4=0;
             }
        }
    }
    
}