00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00045 #include <cfg/os.h>
00046 #include <cfg/clock.h>
00047 #include <dev/board.h>
00048
00049 #include <stdlib.h>
00050 #include <string.h>
00051 #include <stdio.h>
00052 #include <fcntl.h>
00053 #include <io.h>
00054
00055 #include <sys/version.h>
00056 #include <sys/confnet.h>
00057 #include <sys/atom.h>
00058 #include <sys/heap.h>
00059 #include <sys/thread.h>
00060 #include <sys/timer.h>
00061 #include <sys/event.h>
00062 #include <sys/socket.h>
00063
00064 #if defined(USE_SOFTWARE_CODEC)
00065 #include <hxmp3/mp3dec.h>
00066 #endif
00067
00068 #include <netinet/tcp.h>
00069 #include <arpa/inet.h>
00070 #include <net/route.h>
00071 #include <pro/dhcp.h>
00072 #include <netdb.h>
00073
00074 #include <pro/uxml.h>
00075 #include <dev/vscodec.h>
00076
00077 #include "config.h"
00078 #include "logmsg.h"
00079 #include "favlist.h"
00080 #include "utils.h"
00081 #include "userif.h"
00082 #include "xmlserv.h"
00083 #include "shoutcast.h"
00084
00085 #ifndef SHOUTCAST_THREAD_STACK
00086 #ifdef AT91SAM7X_EK
00087 #define SHOUTCAST_THREAD_STACK 1024
00088 #else
00089 #define SHOUTCAST_THREAD_STACK 2048
00090 #endif
00091 #endif
00092
00093 #define SHOUTCAST_QLIST "www.shoutcast.com/sbin/newxml.phtml"
00094 #define SHOUTCAST_QTUNE "www.shoutcast.com/sbin/tunein-station.pls"
00095
00096 static char station_genre[32];
00097 static UXML_NODE *genre_tree;
00098
00099 #define ENCTYP_MPEG 1
00100 #define ENCTYP_AAC 2
00101 #define ENCTYP_AACP 3
00102 #define ENCTYP_OGG 4
00103 #define ENCTYP_WMA 5
00104
00105
00106 typedef struct {
00107 char *s_name;
00108 long s_id;
00109 long s_br;
00110 int s_enc;
00111 } STATION_INFO;
00112
00113 static int station_cnt;
00114 static STATION_INFO *station_list;
00115
00116 static FILE *OpenTcpStream(TCPSOCKET *sock, CONST char *host, u_short port)
00117 {
00118 u_long rx_to = MAX_TCPRCV_WAIT;
00119
00120
00121 NutTcpSetSockOpt(sock, SO_RCVTIMEO, &rx_to, sizeof(rx_to));
00122
00123
00124 LogMsg(LOG_STATION, "Connecting %s:%u\n", host, port);
00125 if (TcpHostConnect(sock, host, port)) {
00126 return NULL;
00127 }
00128 LogMsg(LOG_SHOUTCAST, "Connected\n");
00129
00130 return _fdopen((int) ((uptr_t) sock), "r+b");
00131 }
00132
00133 static int SendHttpRequest(FILE *stream, HTTP_SCHEME *schm)
00134 {
00135 fputs("GET ", stream);
00136 if (proxy.proxy_port) {
00137 fprintf(stream, "http://%s", schm->schm_uri);
00138 }
00139 fprintf(stream, "/%s HTTP/1.1\r\n", schm->schm_path);
00140 fprintf(stream, "Host: %s\r\n", schm->schm_host);
00141 fputs("User-Agent: WinampMPEG/2.7\r\n"
00142 "Icy-MetaData: 1\r\n"
00143 "Connection: close\r\n\r\n", stream);
00144 fflush(stream);
00145
00146 return 0;
00147 }
00148
00152 static int ShoutCastGetGenreList(void)
00153 {
00154 int rc = -1;
00155 TCPSOCKET *sock;
00156 HTTP_SCHEME *schm;
00157 FILE *stream;
00158 char *f_tags[] = { "genre", NULL };
00159 char *f_attribs[] = { "name", NULL };
00160
00161 if ((schm = HttpSchemeParse(SHOUTCAST_QLIST)) == NULL) {
00162 return -1;
00163 }
00164
00165 if ((sock = NutTcpCreateSocket()) != NULL) {
00166 if ((stream = OpenTcpStream(sock, schm->schm_host, schm->schm_portnum)) != NULL) {
00167 SendHttpRequest(stream, schm);
00168 if ((genre_tree = UxmlParseStream(stream, f_tags, f_attribs)) != NULL) {
00169 rc = 0;
00170 }
00171 fclose(stream);
00172 }
00173 NutTcpCloseSocket(sock);
00174 }
00175 HttpSchemeRelease(schm);
00176
00177 return rc;
00178 }
00179
00180
00181
00182
00183 int station_cmp(CONST void *ip1, CONST void *ip2)
00184 {
00185 NutThreadYield();
00186 return strcasecmp(((STATION_INFO *)ip1)->s_name, ((STATION_INFO *)ip2)->s_name);
00187 }
00188
00192 static int ShoutCastGetStationList(CONST char *genre)
00193 {
00194 int rc = -1;
00195 UXML_NODE *station_tree = NULL;
00196 TCPSOCKET *sock;
00197 HTTP_SCHEME *schm;
00198 FILE *stream;
00199 char *uri;
00200 char *f_tags[] = { "station", NULL };
00201 char *f_attribs[] = { "name", "mt", "id", "br", NULL };
00202
00203
00204 if (station_list) {
00205 while (station_cnt) {
00206 station_cnt--;
00207 free(station_list[station_cnt].s_name);
00208 }
00209 free(station_list);
00210 station_list = NULL;
00211 }
00212
00213 strcpy(station_genre, genre);
00214
00215
00216 uri = malloc(sizeof(SHOUTCAST_QLIST) + strlen(genre) + sizeof("?genre="));
00217 strcpy(uri, SHOUTCAST_QLIST);
00218 strcat(uri, "?genre=");
00219 strcat(uri, genre);
00220 schm = HttpSchemeParse(uri);
00221 free(uri);
00222 if (schm == NULL) {
00223 return -1;
00224 }
00225
00226 if ((sock = NutTcpCreateSocket()) != NULL) {
00227 if ((stream = OpenTcpStream(sock, schm->schm_host, schm->schm_portnum)) != NULL) {
00228 SendHttpRequest(stream, schm);
00229 if ((station_tree = UxmlParseStream(stream, f_tags, f_attribs)) != NULL) {
00230 rc = 0;
00231 }
00232 fclose(stream);
00233 }
00234 NutTcpCloseSocket(sock);
00235 }
00236 HttpSchemeRelease(schm);
00237
00238 if (rc == 0) {
00239 UXML_ATTRIB *attr;
00240 UXML_NODE *node = station_tree;
00241
00242 for (node = station_tree; node; node = node->xmln_next) {
00243
00244 station_cnt++;
00245 }
00246 LogMsg(LOG_SHOUTCAST, "\n%d Stations", station_cnt);
00247 if (station_cnt) {
00248 station_list = malloc(station_cnt * sizeof(STATION_INFO));
00249 memset(station_list, 0, station_cnt * sizeof(STATION_INFO));
00250 station_cnt = 0;
00251 for (node = station_tree; node; node = node->xmln_next) {
00252 attr = node->xmln_attribs;
00253 while (attr) {
00254
00255 if (strcasecmp(attr->xmla_name, "name") == 0) {
00256 station_list[station_cnt].s_name = attr->xmla_value;
00257 attr->xmla_value = NULL;
00258 }
00259 if (strcasecmp(attr->xmla_name, "id") == 0) {
00260 station_list[station_cnt].s_id = atol(attr->xmla_value);
00261 }
00262 attr = attr->xmla_next;
00263 }
00264 if (station_list[station_cnt].s_name) {
00265 station_cnt++;
00266 }
00267 }
00268 qsort(station_list, station_cnt, sizeof(STATION_INFO), station_cmp);
00269 }
00270 UxmlTreeDestroy(station_tree);
00271 }
00272 return rc;
00273 }
00274
00275 char * ShoutCastGetGenre(int idx)
00276 {
00277 UXML_NODE *node;
00278 UXML_ATTRIB *attr;
00279
00280 if (genre_tree == NULL) {
00281 ShoutCastGetGenreList();
00282 }
00283 node = genre_tree;
00284 while (node) {
00285 attr = node->xmln_attribs;
00286 while (attr) {
00287 if (idx == 0) {
00288 return attr->xmla_value;
00289 }
00290 idx--;
00291 attr = attr->xmla_next;
00292 }
00293 node = node->xmln_next;
00294 }
00295 return NULL;
00296 }
00297
00298 char * ShoutCastGetStationName(char *genre, int idx)
00299 {
00300 if (strcmp(station_genre, genre)) {
00301 ShoutCastGetStationList(genre);
00302 }
00303 if (idx < station_cnt) {
00304 return station_list[idx].s_name;
00305 }
00306 return NULL;
00307 }
00308
00317 int ShoutCastAddStation(int idx, int pos)
00318 {
00319 int rc = -1;
00320 TCPSOCKET *sock;
00321 HTTP_SCHEME *schm;
00322 FILE *stream;
00323 char *line;
00324 char *uri;
00325 int inlist;
00326 char *cp;
00327
00328 if (idx >= station_cnt) {
00329 return -1;
00330 }
00331 uri = malloc(sizeof(SHOUTCAST_QTUNE) + 16);
00332 sprintf(uri, SHOUTCAST_QTUNE "?id=%ld", station_list[idx].s_id);
00333 schm = HttpSchemeParse(uri);
00334 free(uri);
00335 if (schm == NULL) {
00336 return -1;
00337 }
00338 if ((sock = NutTcpCreateSocket()) != NULL) {
00339 if ((stream = OpenTcpStream(sock, schm->schm_host, schm->schm_portnum)) != NULL) {
00340 SendHttpRequest(stream, schm);
00341 line = malloc(128);
00342 line[127] = 0;
00343 inlist = 0;
00344 pos = FavListSet(pos, station_list[idx].s_name, NULL);
00345 while (fgets(line, 127, stream)) {
00346 if (inlist) {
00347 if ((cp = strchr(line, '\r')) == NULL) {
00348 cp = strchr(line, '\n');
00349 }
00350 if (cp) {
00351 *cp = 0;
00352 }
00353 cp = strchr(line, '=');
00354 if (cp && strncmp(line, "File", 4) == 0) {
00355 FavListSet(pos, NULL, cp + 8);
00356 LogMsg(LOG_SHOUTCAST, "Added %s\n", cp + 8);
00357 }
00358 }
00359 else if (strncmp(line, "[playlist]", sizeof("[playlist]") - 1) == 0) {
00360 inlist = 1;
00361 }
00362 }
00363 free(line);
00364 fclose(stream);
00365 rc = 0;
00366 }
00367 NutTcpCloseSocket(sock);
00368 }
00369 HttpSchemeRelease(schm);
00370
00371 return rc;
00372 }
00373
00374 int ShoutCastGetPlayList(int id)
00375 {
00376 TCPSOCKET *sock;
00377 u_long rx_to = MAX_TCPRCV_WAIT;
00378 int cr;
00379 int err = 0;
00380 char *line;
00381 int len;
00382 HTTP_SCHEME *schm;
00383 char *uri;
00384 int entries = 0;
00385
00386 uri = malloc(sizeof(SHOUTCAST_QTUNE) + 16);
00387 sprintf(uri, SHOUTCAST_QTUNE "?id=%d", id);
00388 schm = HttpSchemeParse(uri);
00389 free(uri);
00390 if (schm == NULL) {
00391 return -1;
00392 }
00393
00394
00395 if ((sock = NutTcpCreateSocket()) == NULL) {
00396 HttpSchemeRelease(schm);
00397 return -1;
00398 }
00399
00400
00401 NutTcpSetSockOpt(sock, SO_RCVTIMEO, &rx_to, sizeof(rx_to));
00402
00403
00404 LogMsg(LOG_STATION, "Connecting %s:%u", schm->schm_host, schm->schm_portnum);
00405 cr = TcpHostConnect(sock, schm->schm_host, schm->schm_portnum);
00406 if (cr == 0) {
00407 LogMsg(LOG_SHOUTCAST, "[CNCTD]");
00408
00409
00410
00411
00412 line = malloc(256);
00413
00414 if (proxy.proxy_port) {
00415
00416 strcpy(line, "GET http://");
00417 strcat(line, schm->schm_uri);
00418 }
00419 else {
00420 strcpy(line, "GET /");
00421 strcat(line, schm->schm_path);
00422 }
00423 strcat(line, " HTTP/1.1\r\n");
00424 err = TcpPutString(sock, line);
00425
00426 if (err == 0) {
00427
00428 sprintf(line, "Host: %s\r\n", schm->schm_host);
00429 err = TcpPutString(sock, line);
00430 LogMsg(LOG_SHOUTCAST, "%s", line);
00431 }
00432
00433 if (err == 0) {
00434
00435
00436 TcpPutString(sock, "User-Agent: WinampMPEG/2.7\r\n"
00437 "Icy-MetaData: 1\r\n"
00438 "Connection: close\r\n\r\n");
00439 }
00440 while (err == 0) {
00441 if ((len = TcpGetLine(sock, line, 255)) < 0) {
00442 break;
00443 }
00444 if (entries) {
00445 if (strncmp(line, "File", 4) == 0) {
00446 LogMsg(LOG_SHOUTCAST, "OK\n");
00447 }
00448 }
00449 else if (strncmp(line, "numberofentries=", 16) == 0) {
00450 entries = atoi(line + 16);
00451 }
00452 }
00453 LogMsg(LOG_SHOUTCAST, "Collected %d entries\n", entries);
00454 }
00455 NutTcpCloseSocket(sock);
00456 HttpSchemeRelease(schm);
00457
00458 return err;
00459 }
00460
00464 static int ProcessMetaData(TCPSOCKET * sock, SHOUTCASTINFO * sci, u_int *status)
00465 {
00466 u_char blks = 0;
00467 u_int mlen;
00468 char *mbuf;
00469 char *mn1;
00470 char *mn2;
00471 char *md1;
00472 char *md2;
00473
00474
00475
00476
00477 if (TcpGetBuffer(sock, (char *)&blks, 1, status)) {
00478
00479 return -1;
00480 }
00481
00482 if (blks == 0) {
00483
00484 return 0;
00485 }
00486 if (blks > 32) {
00487
00488 return -1;
00489 }
00490 mlen = (u_int)blks * 16;
00491
00492
00493
00494
00495 if ((mbuf = malloc(mlen + 1)) == 0) {
00496 return -1;
00497 }
00498 mbuf[mlen] = 0;
00499 if (TcpGetBuffer(sock, mbuf, mlen, status)) {
00500
00501 return -1;
00502 }
00503
00504
00505 LogMsg(LOG_SHOUTCAST, "Meta=\"%s\"\n", mbuf);
00506 mn1 = mbuf;
00507 while (mn1) {
00508 if ((mn2 = strchr(mn1, ';')) != 0)
00509 *mn2++ = 0;
00510 if ((md1 = strchr(mn1, '=')) != 0) {
00511 *md1++ = 0;
00512 while (*md1 == ' ' || *md1 == '\'')
00513 md1++;
00514 if ((md2 = strrchr(md1, '\'')) != 0)
00515 *md2 = 0;
00516 if (strcasecmp(mn1, "StreamTitle") == 0) {
00517 if (sci->sci_metatitle) {
00518 free(sci->sci_metatitle);
00519 }
00520 sci->sci_metatitle = strdup(md1);
00521 UserIfShowStatus(DIST_FORCE);
00522 } else if (strcasecmp(mn1, "StreamUrl") == 0) {
00523 if (sci->sci_metaurl) {
00524 free(sci->sci_metaurl);
00525 }
00526 sci->sci_metaurl = strdup(md1);
00527 }
00528 }
00529 mn1 = mn2;
00530 }
00531 free(mbuf);
00532
00533 return 0;
00534 }
00535
00544 THREAD(ShoutCastThread, arg)
00545 {
00546 RECEIVERINFO *rip = (RECEIVERINFO *) arg;
00547 SHOUTCASTINFO *sci = (SHOUTCASTINFO *) rip->ri_bcast;
00548 STATIONINFO *sip;
00549 int rbytes;
00550 int sent;
00551 int got;
00552 char *tcpbuf;
00553 char *bp;
00554 time_t buffering;
00555 int timeouts;
00556
00557 for (;;) {
00558
00559
00560
00561 rip->ri_decoder = -1;
00562 for (;;) {
00563 memset(sci, 0, sizeof(SHOUTCASTINFO));
00564 rip->ri_status &= ~RSTAT_STOP;
00565 rip->ri_status |= RSTAT_IDLE;
00566 NutEventBroadcast(&rip->ri_stsevt);
00567 LogMsg(LOG_SHOUTCAST, "Receiver idle\n");
00568
00569
00570 NutEventWait(&rip->ri_cmdevt, 0);
00571 if (rip->ri_status & RSTAT_START) {
00572 if ((rip->ri_decoder = _open("audio0", _O_WRONLY | _O_BINARY)) != -1) {
00573 rip->ri_status &= ~(RSTAT_START | RSTAT_IDLE);
00574 break;
00575 }
00576 LogMsg(LOG_ERROR, "No codec device\n");
00577 }
00578 }
00579
00580 sip = rip->ri_sip;
00581 sci->sci_metapos = sci->sci_metaint;
00582
00583 rip->ri_status |= RSTAT_BUFFERING;
00584 NutEventBroadcast(&rip->ri_stsevt);
00585
00586
00587 {
00588 u_long sz;
00589
00590
00591 _ioctl(rip->ri_decoder, AUDIO_GET_PBSIZE, &sz);
00592
00593 sz /= 3;
00594 _ioctl(rip->ri_decoder, AUDIO_SET_PBWLOW, &sz);
00595
00596 sz *= 2;
00597 _ioctl(rip->ri_decoder, AUDIO_SET_PBWHIGH, &sz);
00598 }
00599
00600
00601 tcpbuf = malloc(4096);
00602
00603
00604 time(&buffering);
00605
00606
00607 timeouts = 0;
00608
00609
00610
00611
00612
00613 while ((rip->ri_status & RSTAT_STOP) == 0) {
00614 rbytes = 4096;
00615
00616 if (sci->sci_metaint && rbytes > sci->sci_metapos) {
00617 rbytes = sci->sci_metapos;
00618 }
00619
00620
00621 if ((got = NutTcpReceive(sip->si_sock, tcpbuf, rbytes)) < 0) {
00622 LogMsg(LOG_SHOUTCAST, "Receiver error %d\n", NutTcpError(sip->si_sock));
00623 break;
00624 }
00625
00626
00627 if (got == 0) {
00628 if (timeouts++ > 10) {
00629 break;
00630 }
00631 continue;
00632 }
00633 if (timeouts) {
00634 timeouts--;
00635 }
00636
00637
00638 bp = tcpbuf;
00639 rbytes = got;
00640 while (rbytes) {
00641 sent = _write(rip->ri_decoder, bp, rbytes);
00642 if (sent < 0) {
00643 rip->ri_status |= RSTAT_STOP;
00644 LogMsg(LOG_WARN, "Codec write failed\n");
00645 break;
00646 }
00647 rbytes -= sent;
00648 bp += sent;
00649 }
00650
00651
00652 if (sci->sci_metaint) {
00653 sci->sci_metapos -= got;
00654 if (sci->sci_metapos == 0) {
00655 if (ProcessMetaData(sip->si_sock, sci, &rip->ri_status)) {
00656 rip->ri_status |= RSTAT_STOP;
00657 LogMsg(LOG_WARN, "Metadata sync lost\n");
00658 break;
00659 }
00660 sci->sci_metapos = sci->sci_metaint;
00661 }
00662 }
00663
00664
00665 if (buffering) {
00666 int pbstat;
00667 _ioctl(rip->ri_decoder, AUDIO_GET_STATUS, &pbstat);
00668 if (pbstat) {
00669 rip->ri_status |= RSTAT_RUNNING;
00670 LogMsg(LOG_SHOUTCAST, "Playback started\n");
00671 UserIfShowStatus(DIST_FORCE);
00672 buffering = 0;
00673 }
00674 }
00675 if (buffering) {
00676 if(time(NULL) - buffering > MAX_WAIT_MP3BUF_FILLED) {
00677 rip->ri_status &= ~RSTAT_BUFFERING;
00678 _ioctl(rip->ri_decoder, AUDIO_PLAY, NULL);
00679 NutSleep(500);
00680 }
00681 }
00682 }
00683 free(tcpbuf);
00684
00685
00686 _ioctl(rip->ri_decoder, AUDIO_CANCEL, NULL);
00687 rip->ri_status &= ~(RSTAT_BUFFERING | RSTAT_RUNNING);
00688 _close(rip->ri_decoder);
00689 }
00690 }
00691
00699 int ShoutCastCreate(RECEIVERINFO * rip)
00700 {
00701
00702 if ((rip->ri_bcast = malloc(sizeof(SHOUTCASTINFO))) != NULL) {
00703 memset(rip->ri_bcast, 0, sizeof(SHOUTCASTINFO));
00704
00705
00706 if (NutThreadCreate("scast", ShoutCastThread, rip, SHOUTCAST_THREAD_STACK)) {
00707
00708 return 0;
00709 }
00710 free(rip->ri_bcast);
00711 rip->ri_bcast = NULL;
00712 }
00713
00714 return -1;
00715 }
00716
00725 int ShoutCastSetup(RECEIVERINFO * rip, STATIONINFO *sip)
00726 {
00727 SHOUTCASTINFO *sci = (SHOUTCASTINFO *) rip->ri_bcast;
00728 int i;
00729 char *cp;
00730 int rcode;
00731
00732
00733 if (strlen(sip->si_header[0]) > 6 && strncmp(sip->si_header[0], "ICY", 3) == 0) {
00734 rcode = atoi(sip->si_header[0] + 4);
00735 sip->si_protocol = PROTOCOL_TYPE_SHOUTCAST;
00736 }
00737
00738 else if (strlen(sip->si_header[0]) > 11 && strncmp(sip->si_header[0], "HTTP/1", 6) == 0) {
00739 rcode = atoi(sip->si_header[0] + 9);
00740 sip->si_protocol = PROTOCOL_TYPE_ICECAST;
00741 }
00742
00743 else {
00744 return -1;
00745 }
00746
00747 if (rcode != 200) {
00748 return -1;
00749 }
00750 sip->si_content = CONTENT_TYPE_MP3;
00751 sci->sci_metaint = 0;
00752
00753 for (i = 1; sip->si_header[i]; i++) {
00754 if ((cp = strchr(sip->si_header[i], ':')) != NULL) {
00755 cp++;
00756 while (*cp == ' ') {
00757 cp++;
00758 }
00759 if (strncmp(sip->si_header[i], "icy-name:", 9) == 0) {
00760
00761 sip->si_name = cp;
00762 } else if (strncmp(sip->si_header[i], "icy-genre:", 10) == 0) {
00763
00764 sip->si_genre = cp;
00765 } else if (strncmp(sip->si_header[i], "icy-metaint:", 12) == 0) {
00766
00767 sci->sci_metaint = atol(cp);
00768 } else if (strncmp(sip->si_header[i], "icy-br:", 7) == 0) {
00769
00770 sip->si_bitrate = atoi(cp);
00771 } else if (strncmp(sip->si_header[i], "content-type:", 13) == 0) {
00772
00773 if (strcmp(cp, "audio/mpeg") == 0) {
00774 sip->si_content = CONTENT_TYPE_MP3;
00775 }
00776 else if (strcmp(cp, "application/ogg") == 0) {
00777 sip->si_content = CONTENT_TYPE_OGG;
00778 }
00779 else if (strcmp(cp, "audio/aacp") == 0) {
00780 sip->si_content = CONTENT_TYPE_AACP;
00781 }
00782 else {
00783 sip->si_content = CONTENT_TYPE_UNKNOWN;
00784 }
00785 }
00786 }
00787 }
00788 return 0;
00789 }
00790
00796 RECEIVERPLUGIN rpiShoutcast = {
00797 ShoutCastCreate,
00798 ShoutCastSetup
00799 };
00800