📄 shoutcast.c
字号:
/*
* Copyright (C) 2006-2007 by egnite Software GmbH. All rights reserved.
* Copyright (C) 2008 by egnite GmbH. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holders nor the names of
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* For additional information see http://www.ethernut.de/
*/
/*!
* \file shoutcast.c
* \brief SHOUTcast communication routines.
*
* \verbatim
*
* $Log$
*
* \endverbatim
*/
#include <cfg/os.h>
#include <cfg/clock.h>
#include <dev/board.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <io.h>
#include <sys/version.h>
#include <sys/confnet.h>
#include <sys/atom.h>
#include <sys/heap.h>
#include <sys/thread.h>
#include <sys/timer.h>
#include <sys/event.h>
#include <sys/socket.h>
#if defined(USE_SOFTWARE_CODEC)
#include <hxmp3/mp3dec.h>
#endif
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <net/route.h>
#include <pro/dhcp.h>
#include <netdb.h>
#include <pro/uxml.h>
#include <dev/vscodec.h>
#include "config.h"
#include "logmsg.h"
#include "favlist.h"
#include "utils.h"
#include "userif.h"
#include "xmlserv.h"
#include "shoutcast.h"
#ifndef SHOUTCAST_THREAD_STACK
#ifdef AT91SAM7X_EK
#define SHOUTCAST_THREAD_STACK 1024
#else
#define SHOUTCAST_THREAD_STACK 2048
#endif
#endif
#define SHOUTCAST_QLIST "www.shoutcast.com/sbin/newxml.phtml"
#define SHOUTCAST_QTUNE "www.shoutcast.com/sbin/tunein-station.pls"
static char station_genre[32];
static UXML_NODE *genre_tree;
#define ENCTYP_MPEG 1
#define ENCTYP_AAC 2
#define ENCTYP_AACP 3
#define ENCTYP_OGG 4
#define ENCTYP_WMA 5
typedef struct {
char *s_name; /* Station name. */
long s_id; /* SHOUTcast ID. */
long s_br; /* Bitrate. */
int s_enc; /* Encoding. */
} STATION_INFO;
static int station_cnt;
static STATION_INFO *station_list;
static FILE *OpenTcpStream(TCPSOCKET *sock, CONST char *host, u_short port)
{
u_long rx_to = MAX_TCPRCV_WAIT;
/* Set socket options. Failures are ignored. */
NutTcpSetSockOpt(sock, SO_RCVTIMEO, &rx_to, sizeof(rx_to));
/* Connect the stream server. */
LogMsg(LOG_STATION, "Connecting %s:%u\n", host, port);
if (TcpHostConnect(sock, host, port)) {
return NULL;
}
LogMsg(LOG_SHOUTCAST, "Connected\n");
/* Associate a binary stream with the socket. */
return _fdopen((int) ((uptr_t) sock), "r+b");
}
static int SendHttpRequest(FILE *stream, HTTP_SCHEME *schm)
{
fputs("GET ", stream);
if (proxy.proxy_port) {
fprintf(stream, "http://%s", schm->schm_uri);
}
fprintf(stream, "/%s HTTP/1.1\r\n", schm->schm_path);
fprintf(stream, "Host: %s\r\n", schm->schm_host);
fputs("User-Agent: WinampMPEG/2.7\r\n" /* */
"Icy-MetaData: 1\r\n" /* */
"Connection: close\r\n\r\n", stream);
fflush(stream);
return 0;
}
/*!
* \brief Retrieve a genre list from the SHOUTcast server.
*/
static int ShoutCastGetGenreList(void)
{
int rc = -1;
TCPSOCKET *sock;
HTTP_SCHEME *schm;
FILE *stream;
char *f_tags[] = { "genre", NULL };
char *f_attribs[] = { "name", NULL };
if ((schm = HttpSchemeParse(SHOUTCAST_QLIST)) == NULL) {
return -1;
}
if ((sock = NutTcpCreateSocket()) != NULL) {
if ((stream = OpenTcpStream(sock, schm->schm_host, schm->schm_portnum)) != NULL) {
SendHttpRequest(stream, schm);
if ((genre_tree = UxmlParseStream(stream, f_tags, f_attribs)) != NULL) {
rc = 0;
}
fclose(stream);
}
NutTcpCloseSocket(sock);
}
HttpSchemeRelease(schm);
return rc;
}
/*
* List sort helper.
*/
int station_cmp(CONST void *ip1, CONST void *ip2)
{
NutThreadYield();
return strcasecmp(((STATION_INFO *)ip1)->s_name, ((STATION_INFO *)ip2)->s_name);
}
/*!
* \brief Retrieve station list of a specified genre from SHOUTcast.
*/
static int ShoutCastGetStationList(CONST char *genre)
{
int rc = -1;
UXML_NODE *station_tree = NULL;
TCPSOCKET *sock;
HTTP_SCHEME *schm;
FILE *stream;
char *uri;
char *f_tags[] = { "station", NULL };
char *f_attribs[] = { "name", "mt", "id", "br", NULL };
/* Release the current list. */
if (station_list) {
while (station_cnt) {
station_cnt--;
free(station_list[station_cnt].s_name);
}
free(station_list);
station_list = NULL;
}
strcpy(station_genre, genre);
/* Create the request string. */
uri = malloc(sizeof(SHOUTCAST_QLIST) + strlen(genre) + sizeof("?genre="));
strcpy(uri, SHOUTCAST_QLIST);
strcat(uri, "?genre=");
strcat(uri, genre);
schm = HttpSchemeParse(uri);
free(uri);
if (schm == NULL) {
return -1;
}
if ((sock = NutTcpCreateSocket()) != NULL) {
if ((stream = OpenTcpStream(sock, schm->schm_host, schm->schm_portnum)) != NULL) {
SendHttpRequest(stream, schm);
if ((station_tree = UxmlParseStream(stream, f_tags, f_attribs)) != NULL) {
rc = 0;
}
fclose(stream);
}
NutTcpCloseSocket(sock);
}
HttpSchemeRelease(schm);
if (rc == 0) {
UXML_ATTRIB *attr;
UXML_NODE *node = station_tree;
for (node = station_tree; node; node = node->xmln_next) {
//Optional filter attr = node->xmln_attribs;
station_cnt++;
}
LogMsg(LOG_SHOUTCAST, "\n%d Stations", station_cnt);
if (station_cnt) {
station_list = malloc(station_cnt * sizeof(STATION_INFO));
memset(station_list, 0, station_cnt * sizeof(STATION_INFO));
station_cnt = 0;
for (node = station_tree; node; node = node->xmln_next) {
attr = node->xmln_attribs;
while (attr) {
// Repeat optional filter
if (strcasecmp(attr->xmla_name, "name") == 0) {
station_list[station_cnt].s_name = attr->xmla_value;
attr->xmla_value = NULL;
}
if (strcasecmp(attr->xmla_name, "id") == 0) {
station_list[station_cnt].s_id = atol(attr->xmla_value);
}
attr = attr->xmla_next;
}
if (station_list[station_cnt].s_name) {
station_cnt++;
}
}
qsort(station_list, station_cnt, sizeof(STATION_INFO), station_cmp);
}
UxmlTreeDestroy(station_tree);
}
return rc;
}
char * ShoutCastGetGenre(int idx)
{
UXML_NODE *node;
UXML_ATTRIB *attr;
if (genre_tree == NULL) {
ShoutCastGetGenreList();
}
node = genre_tree;
while (node) {
attr = node->xmln_attribs;
while (attr) {
if (idx == 0) {
return attr->xmla_value;
}
idx--;
attr = attr->xmla_next;
}
node = node->xmln_next;
}
return NULL;
}
char * ShoutCastGetStationName(char *genre, int idx)
{
if (strcmp(station_genre, genre)) {
ShoutCastGetStationList(genre);
}
if (idx < station_cnt) {
return station_list[idx].s_name;
}
return NULL;
}
/*!
* \brief Query streams of a specified station and create a favorites list entry.
*
* \param idx SHOUTcast station list index.
* \param pos Favorites list index.
*
* \return 0 on success, -1 otherwise.
*/
int ShoutCastAddStation(int idx, int pos)
{
int rc = -1;
TCPSOCKET *sock;
HTTP_SCHEME *schm;
FILE *stream;
char *line;
char *uri;
int inlist;
char *cp;
if (idx >= station_cnt) {
return -1;
}
uri = malloc(sizeof(SHOUTCAST_QTUNE) + 16);
sprintf(uri, SHOUTCAST_QTUNE "?id=%ld", station_list[idx].s_id);
schm = HttpSchemeParse(uri);
free(uri);
if (schm == NULL) {
return -1;
}
if ((sock = NutTcpCreateSocket()) != NULL) {
if ((stream = OpenTcpStream(sock, schm->schm_host, schm->schm_portnum)) != NULL) {
SendHttpRequest(stream, schm);
line = malloc(128);
line[127] = 0;
inlist = 0;
pos = FavListSet(pos, station_list[idx].s_name, NULL);
while (fgets(line, 127, stream)) {
if (inlist) {
if ((cp = strchr(line, '\r')) == NULL) {
cp = strchr(line, '\n');
}
if (cp) {
*cp = 0;
}
cp = strchr(line, '=');
if (cp && strncmp(line, "File", 4) == 0) {
FavListSet(pos, NULL, cp + 8);
LogMsg(LOG_SHOUTCAST, "Added %s\n", cp + 8);
}
}
else if (strncmp(line, "[playlist]", sizeof("[playlist]") - 1) == 0) {
inlist = 1;
}
}
free(line);
fclose(stream);
rc = 0;
}
NutTcpCloseSocket(sock);
}
HttpSchemeRelease(schm);
return rc;
}
int ShoutCastGetPlayList(int id)
{
TCPSOCKET *sock;
u_long rx_to = MAX_TCPRCV_WAIT;
int cr;
int err = 0;
char *line;
int len;
HTTP_SCHEME *schm;
char *uri;
int entries = 0;
uri = malloc(sizeof(SHOUTCAST_QTUNE) + 16);
sprintf(uri, SHOUTCAST_QTUNE "?id=%d", id);
schm = HttpSchemeParse(uri);
free(uri);
if (schm == NULL) {
return -1;
}
/* Create a TCP socket. */
if ((sock = NutTcpCreateSocket()) == NULL) {
HttpSchemeRelease(schm);
return -1;
}
/* Set socket options. Failures are ignored. */
NutTcpSetSockOpt(sock, SO_RCVTIMEO, &rx_to, sizeof(rx_to));
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -