/* shout.c * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #endif #include "shout.h" #include "sock.h" #include "timing.h" #include "mp3.h" /* * INTERNAL PROTOTYPES */ int _icy_login(shout_conn_t *self); int _xaudiocast_login(shout_conn_t *self); void shout_init_connection(shout_conn_t *self) { self->ip = NULL; self->port = 0; self->mount = NULL; self->connected = 0; self->_socket = 0; self->password = NULL; self->icy_compat = 0; self->aim = NULL; self->icq = NULL; self->irc = NULL; self->dumpfile = NULL; self->name = NULL; self->url = NULL; self->genre = NULL; self->description = NULL; self->bitrate = 0; self->ispublic = 0; self->error = 0; self->frames = 0; self->_starttime = 0; self->_senttime = 0; self->_frame_samples = 0; self->_frame_left = 0; self->_frame_samplerate = 0; self->_header_bridges = 0; self->_header_bridge[0] = 0; self->_header_bridge[1] = 0; self->_header_bridge[2] = 0; memset (self->_servermsg, 0, 4096); } int shout_connect(shout_conn_t *self) { /* sanity check */ if ((self->ip == NULL) || (self->password == NULL) || (self->port <= 0) || self->connected) { self->error = SHOUTERR_INSANE; return 0; } if (self->icy_compat) { self->_socket = sock_connect(self->ip, self->port + 1); } else { self->_socket = sock_connect(self->ip, self->port); } if (self->_socket <= 0) { self->error = SHOUTERR_NOCONNECT; return 0; } if (self->icy_compat) { if (_icy_login(self)) { self->connected = 1; return 1; } } else { if (_xaudiocast_login(self)) { self->connected = 1; return 1; } } self->error = SHOUTERR_NOLOGIN; return 0; } int shout_disconnect(shout_conn_t *self) { if (!sock_valid_socket(self->_socket)) { self->error = SHOUTERR_INSANE; return 0; } self->connected = 0; sock_close(self->_socket); return 1; } /* * Blocking function for the time being. * * This function should never get ID3 tags or anything but a valid MPEG Layer III stream. */ int shout_send_data(shout_conn_t *self, unsigned char *buff, unsigned long len) { unsigned long pos; unsigned long head; int ret, count; int start, end, error, i; unsigned char *bridge_buff; mp3_header_t mh; if (self->_socket <= 0) { self->error = SHOUTERR_NOCONNECT; return 0; } if (self->_starttime <= 0) self->_starttime = timing_get_time(); bridge_buff = NULL; pos = 0; start = 0; error = 0; end = len - 1; memset(&mh, 0, sizeof(mp3_header_t)); /* finish the previous frame */ if (self->_frame_left > 0) { /* is the rest of the frame here? */ if (self->_frame_left <= len) { self->_senttime += ((double)self->_frame_samples / (double)self->_frame_samplerate * 1000000); self->frames++; pos += self->_frame_left; self->_frame_left = 0; } else { self->_frame_left -= len; pos = len; } } /* header was over the boundary, so build a new build a new buffer */ if (self->_header_bridges) { bridge_buff = (unsigned char *)malloc(len + self->_header_bridges); if (bridge_buff == NULL) { self->error = SHOUTERR_MALLOC; return 0; } bridge_buff[0] = self->_header_bridge[0]; bridge_buff[1] = self->_header_bridge[1]; bridge_buff[2] = self->_header_bridge[2]; memcpy(&bridge_buff[self->_header_bridges], buff, len); buff = bridge_buff; len += self->_header_bridges; end = len - 1; self->_header_bridges = 0; } /** this is the main loop *** we handle everything but the last 4 bytes... **/ while (pos <= (len - 4)) { /* find mp3 header */ head = (buff[pos] << 24) | (buff[pos + 1] << 16) | (buff[pos + 2] << 8) | (buff[pos + 3]); /* is this a valid header? */ if (mp3_header(head, &mh)) { if (error) { start = pos; end = len - 1; error = 0; } self->_frame_samples = mh.samples; self->_frame_samplerate = mh.samplerate; /* do we have a complete frame in this buffer? */ if (len - pos >= mh.framesize) { self->_senttime += ((double)self->_frame_samples / (double)self->_frame_samplerate * 1000000); self->frames++; pos += mh.framesize; } else { self->_frame_left = mh.framesize - (len - pos); pos = len; } } else { /* there was an error ** so we send all the valid data up to this point */ if (!error) { error = 1; end = pos - 1; count = end - start + 1; if (count > 0) ret = sock_write_bytes(self->_socket, (char *)&buff[start], count); else ret = 0; if (ret != count) { self->error = SHOUTERR_SOCKET; if (bridge_buff != NULL) free(bridge_buff); return 0; } } pos++; } } /* catch the tail if there is one */ if ((pos > (len - 4)) && (pos < len)) { end = pos - 1; i = 0; while (pos < len) { self->_header_bridge[i] = buff[pos]; pos++; i++; } self->_header_bridges = i; } if (!error) { /* if there's no errors, lets send the frames */ count = end - start + 1; if (count > 0) ret = sock_write_bytes(self->_socket, (char *)&buff[start], count); else ret = 0; if (bridge_buff != NULL) free(bridge_buff); if (ret == count) { return 1; } else { self->error = SHOUTERR_SOCKET; return 0; } } if (bridge_buff != NULL) free(bridge_buff); return 1; } void shout_sleep(shout_conn_t *self) { long long sleep; if (self->_senttime == 0) return; sleep = ((double)self->_senttime / 1000) - (timing_get_time() - self->_starttime); if (sleep > 0) { timing_sleep(sleep); } } int shout_update_metadata(shout_conn_t *self, char *metadata) { int s, res; char *metatemp; /* sanity check */ if ((self->ip == NULL) || (self->password == NULL) || (self->port <= 0) || !self->connected) { self->error = SHOUTERR_INSANE; return 0; } s = sock_connect(self->ip, self->port); if (s <= 0) { self->error = SHOUTERR_NOCONNECT; return 0; } metatemp = (char *)malloc(strlen(metadata) * 3 + 1); if (metatemp == NULL) { sock_close(s); self->error = SHOUTERR_MALLOC; return 0; } urlencode(metatemp, metadata); if (self->icy_compat) res = sock_write(s, "GET /admin.cgi?mode=updinfo&pass=%s&song=%s HTTP/1.0\r\nUser-Agent: libshout/%s (Mozilla compatible)\r\n\r\n", self->password, metatemp, VERSION); else res = sock_write(s, "GET /admin.cgi?mode=updinfo&pass=%s&mount=%s&song=%s HTTP/1.0\r\nUser-Agent: libshout/%s\r\n\r\n", self->password, self->mount, metatemp, VERSION); free(metatemp); sock_close(s); if (res <=0) { self->error = SHOUTERR_METADATA; return 0; } return 1; } int _icy_login(shout_conn_t *self) { int res; res = sock_write(self->_socket, "%s\n", self->password); if (!res) return 0; res = sock_write(self->_socket, "icy-name:%s\n", self->name != NULL ? self->name : "unnamed"); if (!res) return 0; res = sock_write(self->_socket, "icy-url:%s\n", self->url != NULL ? self->url : "http://www.icecast.org/"); if (!res) return 0; res = sock_write(self->_socket, "icy-irc:%s\n", self->irc != NULL ? self->irc : ""); if (!res) return 0; res = sock_write(self->_socket, "icy-aim:%s\n", self->aim != NULL ? self->aim : ""); if (!res) return 0; res = sock_write(self->_socket, "icy-icq:%s\n", self->icq != NULL ? self->icq : ""); if (!res) return 0; res = sock_write(self->_socket, "icy-genre:%s\n", self->genre != NULL ? self->genre : "icecast"); if (!res) return 0; res = sock_write(self->_socket, "icy-br:%i\n", self->bitrate); if (!res) return 0; res = sock_write(self->_socket, "icy-pub:%i\n", self->ispublic); if (!res) return 0; res = sock_write(self->_socket, "\n"); if (!res) return 0; res = sock_read_line(self->_socket, self->_servermsg, 4096); if (!res) return 0; if (strstr (self->_servermsg, "OK") == NULL) return 0; return 1; } int _xaudiocast_login(shout_conn_t *self) { int res; res = sock_write(self->_socket, "SOURCE %s %s\n", self->password, self->mount); if (!res) return 0; res = sock_write(self->_socket, "x-audiocast-name: %s\n", self->name != NULL ? self->name : "unnamed"); if (!res) return 0; res = sock_write(self->_socket, "x-audiocast-url: %s\n", self->url != NULL ? self->url : "http://www.icecast.org/"); if (!res) return 0; res = sock_write(self->_socket, "x-audiocast-genre: %s\n", self->genre != NULL ? self->genre : "icecast"); if (!res) return 0; res = sock_write(self->_socket, "x-audiocast-bitrate: %i\n", self->bitrate); if (!res) return 0; res = sock_write(self->_socket, "x-audiocast-public: %i\n", self->ispublic); if (!res) return 0; res = sock_write(self->_socket, "x-audiocast-description: %s\n", self->description != NULL ? self->description : "Broadcasting with the icecast streaming media server!"); if (!res) return 0; if (self->dumpfile != NULL) { res = sock_write(self->_socket, "x-audiocast-dumpfile: %s\n", self->dumpfile); if (!res) return 0; } res = sock_write(self->_socket, "\n"); if (!res) return 0; res = sock_read_line(self->_socket, self->_servermsg, 4096); if (!res) return 0; if (strstr(self->_servermsg, "OK") == NULL) return 0; return 1; } char *shout_strerror (shout_conn_t *self, int error, char *namespace, int maxlen) { if (!namespace || maxlen <= 0) return NULL; switch (error) { case SHOUTERR_INSANE: strncpy(namespace, "Libshout encountered invalid values or NULL pointers.", maxlen); break; case SHOUTERR_NOCONNECT: strncpy(namespace, "Not connected to server.", maxlen); break; case SHOUTERR_NOLOGIN: strncpy(namespace, "Could not login on server. Server message: ", maxlen); strncat(namespace, self->_servermsg, maxlen - 45); break; case SHOUTERR_SOCKET: strncpy(namespace, "Libshout socket error.", maxlen); break; case SHOUTERR_MALLOC: strncpy(namespace, "Libshout malloc error.", maxlen); break; case SHOUTERR_METADATA: strncpy(namespace, "Libshout metadata error.", maxlen); break; default: strncpy(namespace, "Unknown libshout error.", maxlen); break; } return namespace; }