📄 shoutcast.c
字号:
/* Connect the stream server. */
LogMsg(LOG_STATION, "Connecting %s:%u", schm->schm_host, schm->schm_portnum);
cr = TcpHostConnect(sock, schm->schm_host, schm->schm_portnum);
if (cr == 0) {
LogMsg(LOG_SHOUTCAST, "[CNCTD]");
/*
* Send the HTTP request.
*/
line = malloc(256);
if (proxy.proxy_port) {
/* A proxy requires an absolute URI. */
strcpy(line, "GET http://");
strcat(line, schm->schm_uri);
}
else {
strcpy(line, "GET /");
strcat(line, schm->schm_path);
}
strcat(line, " HTTP/1.1\r\n");
err = TcpPutString(sock, line);
if (err == 0) {
/* HTTP 1.1 requires this. So just in case... */
sprintf(line, "Host: %s\r\n", schm->schm_host);
err = TcpPutString(sock, line);
LogMsg(LOG_SHOUTCAST, "%s", line);
}
if (err == 0) {
/* Some SHOUTcast servers seem to require a user-agent line.
"Eat chalk" to get in. */
TcpPutString(sock, "User-Agent: WinampMPEG/2.7\r\n" /* */
"Icy-MetaData: 1\r\n" /* */
"Connection: close\r\n\r\n");
}
while (err == 0) {
if ((len = TcpGetLine(sock, line, 255)) < 0) {
break;
}
if (entries) {
if (strncmp(line, "File", 4) == 0) {
LogMsg(LOG_SHOUTCAST, "OK\n");
}
}
else if (strncmp(line, "numberofentries=", 16) == 0) {
entries = atoi(line + 16);
}
}
LogMsg(LOG_SHOUTCAST, "Collected %d entries\n", entries);
}
NutTcpCloseSocket(sock);
HttpSchemeRelease(schm);
return err;
}
/*!
* \brief Process embedded meta data.
*/
static int ProcessMetaData(TCPSOCKET * sock, SHOUTCASTINFO * sci, u_int *status)
{
u_char blks = 0;
u_int mlen;
char *mbuf;
char *mn1;
char *mn2;
char *md1;
char *md2;
/*
* Wait for the lenght byte.
*/
if (TcpGetBuffer(sock, (char *)&blks, 1, status)) {
/* Status change or receive error. */
return -1;
}
if (blks == 0) {
/* Empty metadata block. */
return 0;
}
if (blks > 32) {
/* We are probably out of sync. */
return -1;
}
mlen = (u_int)blks * 16;
/*
* Wait for the metadata block.
*/
if ((mbuf = malloc(mlen + 1)) == 0) {
return -1;
}
mbuf[mlen] = 0;
if (TcpGetBuffer(sock, mbuf, mlen, status)) {
/* Status change or receive error. */
return -1;
}
//ClearMetaData();
LogMsg(LOG_SHOUTCAST, "Meta=\"%s\"\n", mbuf);
mn1 = mbuf;
while (mn1) {
if ((mn2 = strchr(mn1, ';')) != 0)
*mn2++ = 0;
if ((md1 = strchr(mn1, '=')) != 0) {
*md1++ = 0;
while (*md1 == ' ' || *md1 == '\'')
md1++;
if ((md2 = strrchr(md1, '\'')) != 0)
*md2 = 0;
if (strcasecmp(mn1, "StreamTitle") == 0) {
if (sci->sci_metatitle) {
free(sci->sci_metatitle);
}
sci->sci_metatitle = strdup(md1);
UserIfShowStatus(DIST_FORCE);
} else if (strcasecmp(mn1, "StreamUrl") == 0) {
if (sci->sci_metaurl) {
free(sci->sci_metaurl);
}
sci->sci_metaurl = strdup(md1);
}
}
mn1 = mn2;
}
free(mbuf);
return 0;
}
/*!
* \brief SHOUTcast receiver thread.
*
* Reads audio data from a TCP socket and passes it to a ring buffer.
* Meta data is parsed and removed from the audio stream.
*
* \param arg Pointer to the player info structure.
*/
THREAD(ShoutCastThread, arg)
{
RECEIVERINFO *rip = (RECEIVERINFO *) arg;
SHOUTCASTINFO *sci = (SHOUTCASTINFO *) rip->ri_bcast;
STATIONINFO *sip;
int rbytes;
int sent;
int got;
char *tcpbuf;
char *bp;
time_t buffering;
int timeouts;
for (;;) {
/*
* Idle loop.
*/
rip->ri_decoder = -1;
for (;;) {
memset(sci, 0, sizeof(SHOUTCASTINFO));
rip->ri_status &= ~RSTAT_STOP;
rip->ri_status |= RSTAT_IDLE;
NutEventBroadcast(&rip->ri_stsevt);
LogMsg(LOG_SHOUTCAST, "Receiver idle\n");
/* Wait for start event. */
NutEventWait(&rip->ri_cmdevt, 0);
if (rip->ri_status & RSTAT_START) {
if ((rip->ri_decoder = _open("audio0", _O_WRONLY | _O_BINARY)) != -1) {
rip->ri_status &= ~(RSTAT_START | RSTAT_IDLE);
break;
}
LogMsg(LOG_ERROR, "No codec device\n");
}
}
sip = rip->ri_sip;
sci->sci_metapos = sci->sci_metaint;
/* Broadcast running event. */
rip->ri_status |= RSTAT_BUFFERING;
NutEventBroadcast(&rip->ri_stsevt);
/* Set initial audio buffer watermarks and streaming start time. */
{
u_long sz;
/* Get codec buffer size. */
_ioctl(rip->ri_decoder, AUDIO_GET_PBSIZE, &sz);
/* Use 1/3 as low watermark. */
sz /= 3;
_ioctl(rip->ri_decoder, AUDIO_SET_PBWLOW, &sz);
/* Use 2/3 as high watermark. */
sz *= 2;
_ioctl(rip->ri_decoder, AUDIO_SET_PBWHIGH, &sz);
}
/* So much copying! This is the disadvantage of the independent audio driver. */
tcpbuf = malloc(4096);
/* Start with buffering mode. */
time(&buffering);
/* Clear timeout counter. */
timeouts = 0;
/*
* This loop reads audio data from the SHOUTcast server and passes
* it to the audio decoder.
*/
while ((rip->ri_status & RSTAT_STOP) == 0) {
rbytes = 4096;
/* Do not read beyond meta data. */
if (sci->sci_metaint && rbytes > sci->sci_metapos) {
rbytes = sci->sci_metapos;
}
/* Receive audio data from the Internet radio server. */
if ((got = NutTcpReceive(sip->si_sock, tcpbuf, rbytes)) < 0) {
LogMsg(LOG_SHOUTCAST, "Receiver error %d\n", NutTcpError(sip->si_sock));
break;
}
/* Stop receiver on too many timeouts. */
if (got == 0) {
if (timeouts++ > 10) {
break;
}
continue;
}
if (timeouts) {
timeouts--;
}
/* Send audio data to the audio codec. */
bp = tcpbuf;
rbytes = got;
while (rbytes) {
sent = _write(rip->ri_decoder, bp, rbytes);
if (sent < 0) {
rip->ri_status |= RSTAT_STOP;
LogMsg(LOG_WARN, "Codec write failed\n");
break;
}
rbytes -= sent;
bp += sent;
}
/* Receive optional meta data. */
if (sci->sci_metaint) {
sci->sci_metapos -= got;
if (sci->sci_metapos == 0) {
if (ProcessMetaData(sip->si_sock, sci, &rip->ri_status)) {
rip->ri_status |= RSTAT_STOP;
LogMsg(LOG_WARN, "Metadata sync lost\n");
break;
}
sci->sci_metapos = sci->sci_metaint;
}
}
/* Force decoder start after 10 seconds. */
if (buffering) {
int pbstat;
_ioctl(rip->ri_decoder, AUDIO_GET_STATUS, &pbstat);
if (pbstat) {
rip->ri_status |= RSTAT_RUNNING;
LogMsg(LOG_SHOUTCAST, "Playback started\n");
UserIfShowStatus(DIST_FORCE);
buffering = 0;
}
}
if (buffering) {
if(time(NULL) - buffering > MAX_WAIT_MP3BUF_FILLED) {
rip->ri_status &= ~RSTAT_BUFFERING;
_ioctl(rip->ri_decoder, AUDIO_PLAY, NULL);
NutSleep(500);
}
}
}
free(tcpbuf);
/* Discard any buffered audio data. */
_ioctl(rip->ri_decoder, AUDIO_CANCEL, NULL);
rip->ri_status &= ~(RSTAT_BUFFERING | RSTAT_RUNNING);
_close(rip->ri_decoder);
}
}
/*!
* \brief Create a SHOUTcast receiver instance.
*
* \param rip Pointer to the receiver information structure.
*
* \return 0 on success, -1 otherwise.
*/
int ShoutCastCreate(RECEIVERINFO * rip)
{
/* Allocate local info structure. */
if ((rip->ri_bcast = malloc(sizeof(SHOUTCASTINFO))) != NULL) {
memset(rip->ri_bcast, 0, sizeof(SHOUTCASTINFO));
/* Start the receiver thread. */
if (NutThreadCreate("scast", ShoutCastThread, rip, SHOUTCAST_THREAD_STACK)) {
/* Success! */
return 0;
}
free(rip->ri_bcast);
rip->ri_bcast = NULL;
}
/* Not enough memory. */
return -1;
}
/*!
* \brief Setup SHOUTcast receiver.
*
* Parses the header lines of the HTTP response.
*
* \return 0 on success, or -1 if the server responded with an error code
* or doesn't seem to provide a SHOUTcast stream.
*/
int ShoutCastSetup(RECEIVERINFO * rip, STATIONINFO *sip)
{
SHOUTCASTINFO *sci = (SHOUTCASTINFO *) rip->ri_bcast;
int i;
char *cp;
int rcode;
/* Check if we really have a SHOUTcast server. */
if (strlen(sip->si_header[0]) > 6 && strncmp(sip->si_header[0], "ICY", 3) == 0) {
rcode = atoi(sip->si_header[0] + 4);
sip->si_protocol = PROTOCOL_TYPE_SHOUTCAST;
}
/* Alternatively check for Icecast. */
else if (strlen(sip->si_header[0]) > 11 && strncmp(sip->si_header[0], "HTTP/1", 6) == 0) {
rcode = atoi(sip->si_header[0] + 9);
sip->si_protocol = PROTOCOL_TYPE_ICECAST;
}
/* Reject unknown server responses. */
else {
return -1;
}
if (rcode != 200) {
return -1;
}
sip->si_content = CONTENT_TYPE_MP3;
sci->sci_metaint = 0;
for (i = 1; sip->si_header[i]; i++) {
if ((cp = strchr(sip->si_header[i], ':')) != NULL) {
cp++;
while (*cp == ' ') {
cp++;
}
if (strncmp(sip->si_header[i], "icy-name:", 9) == 0) {
/* Set the station name. */
sip->si_name = cp;
} else if (strncmp(sip->si_header[i], "icy-genre:", 10) == 0) {
/* Set the station's genre. */
sip->si_genre = cp;
} else if (strncmp(sip->si_header[i], "icy-metaint:", 12) == 0) {
/* Set the metadata interval. */
sci->sci_metaint = atol(cp);
} else if (strncmp(sip->si_header[i], "icy-br:", 7) == 0) {
/* Set the bit rate. */
sip->si_bitrate = atoi(cp);
} else if (strncmp(sip->si_header[i], "content-type:", 13) == 0) {
/* Check content type. Modify this to add more decoders. */
if (strcmp(cp, "audio/mpeg") == 0) {
sip->si_content = CONTENT_TYPE_MP3;
}
else if (strcmp(cp, "application/ogg") == 0) {
sip->si_content = CONTENT_TYPE_OGG;
}
else if (strcmp(cp, "audio/aacp") == 0) {
sip->si_content = CONTENT_TYPE_AACP;
}
else {
sip->si_content = CONTENT_TYPE_UNKNOWN;
}
}
}
}
return 0;
}
/*!
* \brief Receiver plug-in reference structure.
*
* Used by the application to create a SHOUTcast receiver instance.
*/
RECEIVERPLUGIN rpiShoutcast = {
ShoutCastCreate, /*!< Plugin method rp_create. */
ShoutCastSetup /*!< Plugin method rp_setup. */
};
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -