2004-07-11 22:21:18 -04:00
|
|
|
/* -*- c-basic-offset: 4; -*- */
|
2004-01-28 20:02:12 -05:00
|
|
|
/* Icecast
|
|
|
|
*
|
|
|
|
* This program is distributed under the GNU General Public License, version 2.
|
|
|
|
* A copy of this license is included with this source.
|
|
|
|
*
|
2015-01-10 13:53:44 -05:00
|
|
|
* Copyright 2000-2004, Jack Moffitt <jack@xiph.org,
|
2004-01-28 20:02:12 -05:00
|
|
|
* Michael Smith <msmith@xiph.org>,
|
|
|
|
* oddsock <oddsock@xiph.org>,
|
|
|
|
* Karl Heyes <karl@xiph.org>
|
|
|
|
* and others (see AUTHORS for details).
|
2012-10-11 18:54:53 -04:00
|
|
|
* Copyright 2011-2012, Philipp "ph3-der-loewe" Schafft <lion@lion.leolix.org>,
|
2004-01-28 20:02:12 -05:00
|
|
|
*/
|
|
|
|
|
2002-07-23 11:15:11 -04:00
|
|
|
/* format_mp3.c
|
2014-11-30 15:32:30 -05:00
|
|
|
**
|
|
|
|
** format plugin for mp3
|
|
|
|
**
|
|
|
|
*/
|
2002-07-23 11:15:11 -04:00
|
|
|
|
2003-07-20 21:58:54 -04:00
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
#include <config.h>
|
|
|
|
#endif
|
|
|
|
|
2002-07-23 11:15:11 -04:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
2003-07-22 20:27:10 -04:00
|
|
|
#ifdef HAVE_STRINGS_H
|
|
|
|
# include <strings.h>
|
|
|
|
#endif
|
2002-07-23 11:15:11 -04:00
|
|
|
|
|
|
|
#include "refbuf.h"
|
2002-12-30 02:55:56 -05:00
|
|
|
#include "source.h"
|
|
|
|
#include "client.h"
|
2002-07-23 11:15:11 -04:00
|
|
|
|
|
|
|
#include "stats.h"
|
|
|
|
#include "format.h"
|
2014-12-02 16:50:57 -05:00
|
|
|
#include "common/httpp/httpp.h"
|
2002-12-30 02:55:56 -05:00
|
|
|
|
|
|
|
#include "logging.h"
|
|
|
|
|
|
|
|
#include "format_mp3.h"
|
|
|
|
|
|
|
|
#define CATMODULE "format-mp3"
|
|
|
|
|
2003-02-17 07:05:45 -05:00
|
|
|
/* Note that this seems to be 8192 in shoutcast - perhaps we want to be the
|
|
|
|
* same for compability with crappy clients?
|
|
|
|
*/
|
2002-12-30 02:55:56 -05:00
|
|
|
#define ICY_METADATA_INTERVAL 16000
|
2002-07-23 11:15:11 -04:00
|
|
|
|
2002-12-29 03:10:10 -05:00
|
|
|
static void format_mp3_free_plugin(format_plugin_t *self);
|
2004-08-20 11:13:59 -04:00
|
|
|
static refbuf_t *mp3_get_filter_meta (source_t *source);
|
|
|
|
static refbuf_t *mp3_get_no_meta (source_t *source);
|
|
|
|
|
|
|
|
static int format_mp3_create_client_data (source_t *source, client_t *client);
|
2004-02-29 09:38:15 -05:00
|
|
|
static void free_mp3_client_data (client_t *client);
|
2005-06-07 21:36:51 -04:00
|
|
|
static int format_mp3_write_buf_to_client(client_t *client);
|
2004-08-20 11:13:59 -04:00
|
|
|
static void write_mp3_to_file (struct source_tag *source, refbuf_t *refbuf);
|
2007-08-30 19:36:19 -04:00
|
|
|
static void mp3_set_tag (format_plugin_t *plugin, const char *tag, const char *in_value, const char *charset);
|
2005-05-07 16:18:13 -04:00
|
|
|
static void format_mp3_apply_settings(client_t *client, format_plugin_t *format, mount_proxy *mount);
|
2004-08-20 11:13:59 -04:00
|
|
|
|
2002-12-30 02:55:56 -05:00
|
|
|
|
|
|
|
typedef struct {
|
2014-11-30 15:32:30 -05:00
|
|
|
unsigned int interval;
|
|
|
|
int metadata_offset;
|
|
|
|
unsigned int since_meta_block;
|
|
|
|
int in_metadata;
|
|
|
|
refbuf_t *associated;
|
2002-12-30 02:55:56 -05:00
|
|
|
} mp3_client_data;
|
2002-12-31 15:15:03 -05:00
|
|
|
|
2014-11-30 15:32:30 -05:00
|
|
|
int format_mp3_get_plugin(source_t *source)
|
2002-07-23 11:15:11 -04:00
|
|
|
{
|
2007-08-16 18:49:13 -04:00
|
|
|
const char *metadata;
|
2003-03-14 21:10:19 -05:00
|
|
|
format_plugin_t *plugin;
|
2002-12-30 06:22:59 -05:00
|
|
|
mp3_state *state = calloc(1, sizeof(mp3_state));
|
2004-08-20 11:13:59 -04:00
|
|
|
refbuf_t *meta;
|
2002-07-23 11:15:11 -04:00
|
|
|
|
2014-11-30 15:32:30 -05:00
|
|
|
plugin = (format_plugin_t *) calloc(1, sizeof(format_plugin_t));
|
2002-07-23 11:15:11 -04:00
|
|
|
|
2004-11-18 18:49:59 -05:00
|
|
|
plugin->type = FORMAT_TYPE_GENERIC;
|
2004-08-20 11:13:59 -04:00
|
|
|
plugin->get_buffer = mp3_get_no_meta;
|
2002-12-30 02:55:56 -05:00
|
|
|
plugin->write_buf_to_client = format_mp3_write_buf_to_client;
|
2004-08-20 11:13:59 -04:00
|
|
|
plugin->write_buf_to_file = write_mp3_to_file;
|
2002-12-29 03:10:10 -05:00
|
|
|
plugin->create_client_data = format_mp3_create_client_data;
|
2003-03-14 21:10:19 -05:00
|
|
|
plugin->free_plugin = format_mp3_free_plugin;
|
2004-12-07 16:06:26 -05:00
|
|
|
plugin->set_tag = mp3_set_tag;
|
2005-05-07 16:18:13 -04:00
|
|
|
plugin->apply_settings = format_mp3_apply_settings;
|
2004-11-18 18:49:59 -05:00
|
|
|
|
2014-11-30 15:32:30 -05:00
|
|
|
plugin->contenttype = httpp_getvar(source->parser, "content-type");
|
2004-11-18 18:49:59 -05:00
|
|
|
if (plugin->contenttype == NULL) {
|
|
|
|
/* We default to MP3 audio for old clients without content types */
|
|
|
|
plugin->contenttype = "audio/mpeg";
|
|
|
|
}
|
2002-07-23 11:15:11 -04:00
|
|
|
|
2003-03-14 21:10:19 -05:00
|
|
|
plugin->_state = state;
|
2002-12-30 06:22:59 -05:00
|
|
|
|
2004-11-17 15:35:35 -05:00
|
|
|
/* initial metadata needs to be blank for sending to clients and for
|
|
|
|
comparing with new metadata */
|
2005-06-06 11:39:26 -04:00
|
|
|
meta = refbuf_new (17);
|
|
|
|
memcpy (meta->data, "\001StreamTitle='';", 17);
|
2004-08-20 11:13:59 -04:00
|
|
|
state->metadata = meta;
|
2005-04-18 10:32:26 -04:00
|
|
|
state->interval = -1;
|
2002-07-23 11:15:11 -04:00
|
|
|
|
2004-08-20 11:13:59 -04:00
|
|
|
metadata = httpp_getvar (source->parser, "icy-metaint");
|
|
|
|
if (metadata)
|
|
|
|
{
|
|
|
|
state->inline_metadata_interval = atoi (metadata);
|
2005-04-18 10:32:26 -04:00
|
|
|
if (state->inline_metadata_interval > 0)
|
|
|
|
{
|
|
|
|
state->offset = 0;
|
|
|
|
plugin->get_buffer = mp3_get_filter_meta;
|
2005-05-07 16:18:13 -04:00
|
|
|
state->interval = state->inline_metadata_interval;
|
2005-04-18 10:32:26 -04:00
|
|
|
}
|
2004-08-20 11:13:59 -04:00
|
|
|
}
|
2014-12-09 11:08:27 -05:00
|
|
|
|
|
|
|
vorbis_comment_init(&plugin->vc);
|
2004-08-20 11:13:59 -04:00
|
|
|
source->format = plugin;
|
2014-11-30 15:32:30 -05:00
|
|
|
thread_mutex_create(&state->url_lock);
|
2003-02-17 07:05:45 -05:00
|
|
|
|
2004-08-20 11:13:59 -04:00
|
|
|
return 0;
|
2002-07-23 11:15:11 -04:00
|
|
|
}
|
|
|
|
|
2004-02-25 15:23:07 -05:00
|
|
|
|
2007-08-30 19:36:19 -04:00
|
|
|
static void mp3_set_tag (format_plugin_t *plugin, const char *tag, const char *in_value, const char *charset)
|
2002-12-30 06:22:59 -05:00
|
|
|
{
|
2004-08-20 11:13:59 -04:00
|
|
|
mp3_state *source_mp3 = plugin->_state;
|
2009-01-14 22:38:49 -05:00
|
|
|
char *value = NULL;
|
2004-08-20 11:13:59 -04:00
|
|
|
|
|
|
|
/* protect against multiple updaters */
|
|
|
|
thread_mutex_lock (&source_mp3->url_lock);
|
2007-08-30 19:36:19 -04:00
|
|
|
|
2009-01-14 22:38:49 -05:00
|
|
|
if (tag==NULL)
|
|
|
|
{
|
|
|
|
source_mp3->update_metadata = 1;
|
|
|
|
thread_mutex_unlock (&source_mp3->url_lock);
|
|
|
|
return;
|
|
|
|
}
|
2007-08-30 19:36:19 -04:00
|
|
|
|
2009-01-14 22:38:49 -05:00
|
|
|
if (in_value)
|
|
|
|
{
|
|
|
|
value = util_conv_string (in_value, charset, plugin->charset);
|
|
|
|
if (value == NULL)
|
|
|
|
value = strdup (in_value);
|
|
|
|
}
|
2007-08-30 19:36:19 -04:00
|
|
|
|
2014-12-09 11:08:27 -05:00
|
|
|
if (strcmp(tag, "title") == 0 || strcmp(tag, "song") == 0) {
|
|
|
|
tag = MP3_METADATA_TITLE;
|
|
|
|
} else if (strcmp(tag, "artist") == 0) {
|
|
|
|
tag = MP3_METADATA_ARTIST;
|
|
|
|
} else if (strcmp(tag, "url") == 0) {
|
|
|
|
tag = MP3_METADATA_URL;
|
2004-08-20 11:13:59 -04:00
|
|
|
}
|
2014-12-09 11:08:27 -05:00
|
|
|
|
|
|
|
format_set_vorbiscomment(plugin, tag, value);
|
|
|
|
free (value);
|
|
|
|
|
2004-08-20 11:13:59 -04:00
|
|
|
thread_mutex_unlock (&source_mp3->url_lock);
|
|
|
|
}
|
2002-12-31 14:48:28 -05:00
|
|
|
|
|
|
|
|
2004-08-20 11:13:59 -04:00
|
|
|
static void filter_shoutcast_metadata (source_t *source, char *metadata, unsigned int meta_len)
|
|
|
|
{
|
|
|
|
if (metadata)
|
|
|
|
{
|
|
|
|
char *end, *p;
|
|
|
|
int len;
|
2002-12-31 14:48:28 -05:00
|
|
|
|
2004-08-20 11:13:59 -04:00
|
|
|
do
|
2003-11-11 13:21:49 -05:00
|
|
|
{
|
2004-08-20 11:13:59 -04:00
|
|
|
metadata++;
|
|
|
|
if (strncmp (metadata, "StreamTitle='", 13))
|
|
|
|
break;
|
2007-08-16 19:03:03 -04:00
|
|
|
if ((end = strstr (metadata+13, "\';")) == NULL)
|
2004-08-20 11:13:59 -04:00
|
|
|
break;
|
|
|
|
len = (end - metadata) - 13;
|
|
|
|
p = calloc (1, len+1);
|
|
|
|
if (p)
|
|
|
|
{
|
|
|
|
memcpy (p, metadata+13, len);
|
2004-12-07 16:06:26 -05:00
|
|
|
logging_playlist (source->mount, p, source->listeners);
|
2007-08-24 11:44:37 -04:00
|
|
|
stats_event_conv (source->mount, "title", p, source->format->charset);
|
2004-08-20 11:13:59 -04:00
|
|
|
yp_touch (source->mount);
|
|
|
|
free (p);
|
2015-03-28 14:14:41 -04:00
|
|
|
playlist_push_track(source->history, &source->format->vc);
|
2004-08-20 11:13:59 -04:00
|
|
|
}
|
|
|
|
} while (0);
|
|
|
|
}
|
|
|
|
}
|
2003-11-11 13:21:49 -05:00
|
|
|
|
2002-12-30 06:27:21 -05:00
|
|
|
|
2005-05-07 16:18:13 -04:00
|
|
|
static void format_mp3_apply_settings (client_t *client, format_plugin_t *format, mount_proxy *mount)
|
|
|
|
{
|
|
|
|
mp3_state *source_mp3 = format->_state;
|
|
|
|
|
2007-08-24 11:44:37 -04:00
|
|
|
source_mp3->interval = -1;
|
|
|
|
free (format->charset);
|
|
|
|
format->charset = NULL;
|
|
|
|
|
|
|
|
if (mount)
|
|
|
|
{
|
2009-08-05 18:24:04 -04:00
|
|
|
if (mount->mp3_meta_interval >= 0)
|
2007-08-24 11:44:37 -04:00
|
|
|
source_mp3->interval = mount->mp3_meta_interval;
|
|
|
|
if (mount->charset)
|
|
|
|
format->charset = strdup (mount->charset);
|
|
|
|
}
|
2009-08-05 18:24:04 -04:00
|
|
|
if (source_mp3->interval < 0)
|
2005-05-07 16:18:13 -04:00
|
|
|
{
|
2007-08-16 18:49:13 -04:00
|
|
|
const char *metadata = httpp_getvar (client->parser, "icy-metaint");
|
2007-08-24 11:44:37 -04:00
|
|
|
source_mp3->interval = ICY_METADATA_INTERVAL;
|
2005-05-07 16:18:13 -04:00
|
|
|
if (metadata)
|
|
|
|
{
|
|
|
|
int interval = atoi (metadata);
|
|
|
|
if (interval > 0)
|
|
|
|
source_mp3->interval = interval;
|
|
|
|
}
|
|
|
|
}
|
2007-08-24 11:44:37 -04:00
|
|
|
|
|
|
|
if (format->charset == NULL)
|
|
|
|
format->charset = strdup ("ISO8859-1");
|
|
|
|
|
2014-10-31 04:46:58 -04:00
|
|
|
ICECAST_LOG_DEBUG("sending metadata interval %d", source_mp3->interval);
|
|
|
|
ICECAST_LOG_DEBUG("charset %s", format->charset);
|
2005-05-07 16:18:13 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-08-20 11:13:59 -04:00
|
|
|
/* called from the source thread when the metadata has been updated.
|
|
|
|
* The artist title are checked and made ready for clients to send
|
|
|
|
*/
|
2014-11-30 15:32:30 -05:00
|
|
|
static void mp3_set_title(source_t *source)
|
2004-08-20 11:13:59 -04:00
|
|
|
{
|
2009-01-14 22:38:49 -05:00
|
|
|
const char streamtitle[] = "StreamTitle='";
|
|
|
|
const char streamurl[] = "StreamUrl='";
|
2014-12-09 11:08:27 -05:00
|
|
|
const char *url_artist = vorbis_comment_query(&source->format->vc, MP3_METADATA_ARTIST, 0);
|
|
|
|
const char *url_title = vorbis_comment_query(&source->format->vc, MP3_METADATA_TITLE, 0);
|
|
|
|
const char *url = vorbis_comment_query(&source->format->vc, MP3_METADATA_URL, 0);
|
2009-01-14 22:38:49 -05:00
|
|
|
size_t size;
|
2004-08-20 11:13:59 -04:00
|
|
|
unsigned char len_byte;
|
|
|
|
refbuf_t *p;
|
2009-01-14 22:38:49 -05:00
|
|
|
unsigned int len = sizeof(streamtitle) + 2; /* the StreamTitle, quotes, ; and null */
|
2004-08-20 11:13:59 -04:00
|
|
|
mp3_state *source_mp3 = source->format->_state;
|
|
|
|
|
|
|
|
/* make sure the url data does not disappear from under us */
|
|
|
|
thread_mutex_lock (&source_mp3->url_lock);
|
|
|
|
|
|
|
|
/* work out message length */
|
2014-12-09 11:08:27 -05:00
|
|
|
if (url_artist)
|
|
|
|
len += strlen (url_artist);
|
|
|
|
if (url_title)
|
|
|
|
len += strlen (url_title);
|
|
|
|
if (url_artist && url_title)
|
2004-08-20 11:13:59 -04:00
|
|
|
len += 3;
|
2009-01-14 22:38:49 -05:00
|
|
|
if (source_mp3->inline_url)
|
|
|
|
{
|
|
|
|
char *end = strstr (source_mp3->inline_url, "';");
|
|
|
|
if (end)
|
|
|
|
len += end - source_mp3->inline_url+2;
|
|
|
|
}
|
2014-12-09 11:08:27 -05:00
|
|
|
else if (url)
|
|
|
|
len += strlen (url) + strlen (streamurl) + 2;
|
2004-08-20 11:13:59 -04:00
|
|
|
#define MAX_META_LEN 255*16
|
|
|
|
if (len > MAX_META_LEN)
|
|
|
|
{
|
|
|
|
thread_mutex_unlock (&source_mp3->url_lock);
|
2014-10-31 04:46:58 -04:00
|
|
|
ICECAST_LOG_WARN("Metadata too long at %d chars", len);
|
2004-08-20 11:13:59 -04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
/* work out the metadata len byte */
|
|
|
|
len_byte = (len-1) / 16 + 1;
|
2002-12-30 06:22:59 -05:00
|
|
|
|
2004-08-20 11:13:59 -04:00
|
|
|
/* now we know how much space to allocate, +1 for the len byte */
|
|
|
|
size = len_byte * 16 + 1;
|
2002-12-30 06:22:59 -05:00
|
|
|
|
2004-08-20 11:13:59 -04:00
|
|
|
p = refbuf_new (size);
|
|
|
|
if (p)
|
|
|
|
{
|
|
|
|
mp3_state *source_mp3 = source->format->_state;
|
2009-01-14 22:38:49 -05:00
|
|
|
int r;
|
2002-12-30 06:22:59 -05:00
|
|
|
|
2004-08-20 11:13:59 -04:00
|
|
|
memset (p->data, '\0', size);
|
2015-04-08 05:36:59 -04:00
|
|
|
if (url_artist && url_title) {
|
2009-01-14 22:38:49 -05:00
|
|
|
r = snprintf (p->data, size, "%c%s%s - %s';", len_byte, streamtitle,
|
2014-12-09 11:08:27 -05:00
|
|
|
url_artist, url_title);
|
2015-04-08 05:36:59 -04:00
|
|
|
} else if (url_title) {
|
2009-01-14 22:38:49 -05:00
|
|
|
r = snprintf (p->data, size, "%c%s%s';", len_byte, streamtitle,
|
2014-12-09 11:08:27 -05:00
|
|
|
url_title);
|
2015-04-08 05:36:59 -04:00
|
|
|
} else {
|
|
|
|
r = snprintf (p->data, size, "%c%s';", len_byte, streamtitle);
|
|
|
|
}
|
|
|
|
|
2009-01-14 22:38:49 -05:00
|
|
|
if (r > 0)
|
|
|
|
{
|
|
|
|
if (source_mp3->inline_url)
|
|
|
|
{
|
|
|
|
char *end = strstr (source_mp3->inline_url, "';");
|
2012-10-11 18:49:57 -04:00
|
|
|
ssize_t urllen = size;
|
2009-01-14 22:38:49 -05:00
|
|
|
if (end) urllen = end - source_mp3->inline_url + 2;
|
2012-10-11 18:49:57 -04:00
|
|
|
if ((ssize_t)(size-r) > urllen)
|
2009-01-14 22:38:49 -05:00
|
|
|
snprintf (p->data+r, size-r, "StreamUrl='%s';", source_mp3->inline_url+11);
|
|
|
|
}
|
2014-12-09 11:08:27 -05:00
|
|
|
else if (url)
|
|
|
|
snprintf (p->data+r, size-r, "StreamUrl='%s';", url);
|
2009-01-14 22:38:49 -05:00
|
|
|
}
|
2014-10-31 04:46:58 -04:00
|
|
|
ICECAST_LOG_DEBUG("shoutcast metadata block setup with %s", p->data+1);
|
2004-08-20 11:13:59 -04:00
|
|
|
filter_shoutcast_metadata (source, p->data, size);
|
2002-12-30 06:22:59 -05:00
|
|
|
|
2004-08-20 11:13:59 -04:00
|
|
|
refbuf_release (source_mp3->metadata);
|
|
|
|
source_mp3->metadata = p;
|
|
|
|
}
|
|
|
|
thread_mutex_unlock (&source_mp3->url_lock);
|
2002-12-30 06:22:59 -05:00
|
|
|
}
|
|
|
|
|
2004-08-20 11:13:59 -04:00
|
|
|
|
|
|
|
/* send the appropriate metadata, and return the number of bytes written
|
|
|
|
* which is 0 or greater. Check the client in_metadata value afterwards
|
|
|
|
* to see if all metadata has been sent
|
|
|
|
*/
|
2009-01-14 22:38:49 -05:00
|
|
|
static int send_stream_metadata (client_t *client, refbuf_t *associated)
|
2002-12-30 02:55:56 -05:00
|
|
|
{
|
2004-07-16 11:47:12 -04:00
|
|
|
int ret = 0;
|
2007-08-24 11:44:37 -04:00
|
|
|
char *metadata;
|
2004-08-20 11:13:59 -04:00
|
|
|
int meta_len;
|
|
|
|
mp3_client_data *client_mp3 = client->format_data;
|
|
|
|
|
|
|
|
/* If there is a change in metadata then send it else
|
|
|
|
* send a single zero value byte in its place
|
|
|
|
*/
|
2005-04-18 10:32:26 -04:00
|
|
|
if (associated && associated != client_mp3->associated)
|
2004-08-20 11:13:59 -04:00
|
|
|
{
|
2005-04-18 10:32:26 -04:00
|
|
|
metadata = associated->data + client_mp3->metadata_offset;
|
|
|
|
meta_len = associated->len - client_mp3->metadata_offset;
|
2004-08-20 11:13:59 -04:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2005-06-09 09:05:02 -04:00
|
|
|
if (associated)
|
|
|
|
{
|
|
|
|
metadata = "\0";
|
|
|
|
meta_len = 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
char *meta = "\001StreamTitle='';";
|
|
|
|
metadata = meta + client_mp3->metadata_offset;
|
|
|
|
meta_len = 17 - client_mp3->metadata_offset;
|
|
|
|
}
|
2004-08-20 11:13:59 -04:00
|
|
|
}
|
2014-11-30 15:32:30 -05:00
|
|
|
ret = client_send_bytes(client, metadata, meta_len);
|
2004-08-20 11:13:59 -04:00
|
|
|
|
|
|
|
if (ret == meta_len)
|
|
|
|
{
|
|
|
|
client_mp3->associated = associated;
|
|
|
|
client_mp3->metadata_offset = 0;
|
|
|
|
client_mp3->in_metadata = 0;
|
|
|
|
client_mp3->since_meta_block = 0;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
if (ret > 0)
|
|
|
|
client_mp3->metadata_offset += ret;
|
|
|
|
else
|
|
|
|
ret = 0;
|
|
|
|
client_mp3->in_metadata = 1;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Handler for writing mp3 data to a client, taking into account whether
|
|
|
|
* client has requested shoutcast style metadata updates
|
|
|
|
*/
|
2005-06-07 21:36:51 -04:00
|
|
|
static int format_mp3_write_buf_to_client(client_t *client)
|
2004-08-20 11:13:59 -04:00
|
|
|
{
|
|
|
|
int ret, written = 0;
|
|
|
|
mp3_client_data *client_mp3 = client->format_data;
|
|
|
|
refbuf_t *refbuf = client->refbuf;
|
2005-06-03 11:35:52 -04:00
|
|
|
char *buf = refbuf->data + client->pos;
|
|
|
|
unsigned int len = refbuf->len - client->pos;
|
2002-12-30 06:22:59 -05:00
|
|
|
|
2004-08-20 11:13:59 -04:00
|
|
|
do
|
|
|
|
{
|
|
|
|
/* send any unwritten metadata to the client */
|
|
|
|
if (client_mp3->in_metadata)
|
|
|
|
{
|
|
|
|
refbuf_t *associated = refbuf->associated;
|
2014-11-30 15:32:30 -05:00
|
|
|
ret = send_stream_metadata(client, associated);
|
2002-12-30 06:22:59 -05:00
|
|
|
|
2004-08-20 11:13:59 -04:00
|
|
|
if (client_mp3->in_metadata)
|
|
|
|
break;
|
|
|
|
written += ret;
|
2002-12-30 06:22:59 -05:00
|
|
|
}
|
2004-08-20 11:13:59 -04:00
|
|
|
/* see if we need to send the current metadata to the client */
|
2005-04-18 10:32:26 -04:00
|
|
|
if (client_mp3->interval)
|
2004-08-20 11:13:59 -04:00
|
|
|
{
|
2005-04-18 10:32:26 -04:00
|
|
|
unsigned int remaining = client_mp3->interval -
|
2004-08-20 11:13:59 -04:00
|
|
|
client_mp3->since_meta_block;
|
|
|
|
|
2009-01-14 22:38:49 -05:00
|
|
|
/* leading up to sending the metadata block */
|
2004-08-20 11:13:59 -04:00
|
|
|
if (remaining <= len)
|
|
|
|
{
|
|
|
|
/* send any mp3 before the metadata block */
|
|
|
|
if (remaining)
|
|
|
|
{
|
|
|
|
ret = client_send_bytes (client, buf, remaining);
|
|
|
|
|
|
|
|
if (ret > 0)
|
|
|
|
{
|
|
|
|
client_mp3->since_meta_block += ret;
|
|
|
|
client->pos += ret;
|
|
|
|
}
|
|
|
|
if (ret < (int)remaining)
|
|
|
|
break;
|
|
|
|
written += ret;
|
|
|
|
}
|
2009-01-14 22:38:49 -05:00
|
|
|
ret = send_stream_metadata (client, refbuf->associated);
|
2004-08-20 11:13:59 -04:00
|
|
|
if (client_mp3->in_metadata)
|
|
|
|
break;
|
|
|
|
written += ret;
|
|
|
|
buf += remaining;
|
|
|
|
len -= remaining;
|
2005-08-30 21:28:04 -04:00
|
|
|
/* limit how much mp3 we send if using small intervals */
|
|
|
|
if (len > client_mp3->interval)
|
|
|
|
len = client_mp3->interval;
|
2004-08-20 11:13:59 -04:00
|
|
|
}
|
2003-01-01 02:31:46 -05:00
|
|
|
}
|
2004-08-20 11:13:59 -04:00
|
|
|
/* write any mp3, maybe after the metadata block */
|
|
|
|
if (len)
|
|
|
|
{
|
|
|
|
ret = client_send_bytes (client, buf, len);
|
2002-12-31 14:48:28 -05:00
|
|
|
|
2004-08-20 11:13:59 -04:00
|
|
|
if (ret > 0)
|
|
|
|
{
|
|
|
|
client_mp3->since_meta_block += ret;
|
|
|
|
client->pos += ret;
|
|
|
|
}
|
|
|
|
if (ret < (int)len)
|
|
|
|
break;
|
|
|
|
written += ret;
|
|
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
} while (0);
|
2002-12-30 02:55:56 -05:00
|
|
|
|
2004-08-20 11:13:59 -04:00
|
|
|
if (ret > 0)
|
|
|
|
written += ret;
|
|
|
|
return written;
|
2002-12-30 02:55:56 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
static void format_mp3_free_plugin(format_plugin_t *self)
|
2002-07-23 11:15:11 -04:00
|
|
|
{
|
2003-03-14 21:10:19 -05:00
|
|
|
/* free the plugin instance */
|
2002-12-30 06:22:59 -05:00
|
|
|
mp3_state *state = self->_state;
|
2002-12-30 06:27:21 -05:00
|
|
|
|
2014-11-30 15:32:30 -05:00
|
|
|
thread_mutex_destroy(&state->url_lock);
|
|
|
|
free(self->charset);
|
|
|
|
refbuf_release(state->metadata);
|
|
|
|
refbuf_release(state->read_data);
|
2002-12-30 06:22:59 -05:00
|
|
|
free(state);
|
2014-12-09 11:08:27 -05:00
|
|
|
vorbis_comment_clear(&self->vc);
|
2003-03-14 21:10:19 -05:00
|
|
|
free(self);
|
2002-07-23 11:15:11 -04:00
|
|
|
}
|
|
|
|
|
2004-08-20 11:13:59 -04:00
|
|
|
|
2005-08-08 15:21:54 -04:00
|
|
|
/* This does the actual reading, making sure the read data is packaged in
|
|
|
|
* blocks of 1400 bytes (near the common MTU size). This is because many
|
2015-01-10 13:53:44 -05:00
|
|
|
* incoming streams come in small packets which could waste a lot of
|
2005-08-08 15:21:54 -04:00
|
|
|
* bandwidth with many listeners due to headers and such like.
|
|
|
|
*/
|
2014-11-30 15:32:30 -05:00
|
|
|
static int complete_read(source_t *source)
|
2002-07-23 11:15:11 -04:00
|
|
|
{
|
2004-08-20 11:13:59 -04:00
|
|
|
int bytes;
|
2005-05-08 09:51:05 -04:00
|
|
|
format_plugin_t *format = source->format;
|
2005-08-08 15:21:54 -04:00
|
|
|
mp3_state *source_mp3 = format->_state;
|
|
|
|
char *buf;
|
|
|
|
refbuf_t *refbuf;
|
2003-02-24 08:37:15 -05:00
|
|
|
|
2005-08-08 15:21:54 -04:00
|
|
|
#define REFBUF_SIZE 1400
|
|
|
|
|
|
|
|
if (source_mp3->read_data == NULL)
|
|
|
|
{
|
2015-01-10 13:53:44 -05:00
|
|
|
source_mp3->read_data = refbuf_new (REFBUF_SIZE);
|
2005-08-08 15:21:54 -04:00
|
|
|
source_mp3->read_count = 0;
|
|
|
|
}
|
|
|
|
buf = source_mp3->read_data->data + source_mp3->read_count;
|
2003-02-25 04:40:34 -05:00
|
|
|
|
2005-08-08 15:21:54 -04:00
|
|
|
bytes = client_read_bytes (source->client, buf, REFBUF_SIZE-source_mp3->read_count);
|
2005-05-08 09:51:05 -04:00
|
|
|
if (bytes < 0)
|
2004-08-20 11:13:59 -04:00
|
|
|
{
|
2005-08-08 15:21:54 -04:00
|
|
|
if (source->client->con->error)
|
|
|
|
{
|
|
|
|
refbuf_release (source_mp3->read_data);
|
|
|
|
source_mp3->read_data = NULL;
|
|
|
|
}
|
|
|
|
return 0;
|
2004-08-20 11:13:59 -04:00
|
|
|
}
|
2005-08-08 15:21:54 -04:00
|
|
|
source_mp3->read_count += bytes;
|
|
|
|
refbuf = source_mp3->read_data;
|
|
|
|
refbuf->len = source_mp3->read_count;
|
2005-05-08 09:51:05 -04:00
|
|
|
format->read_bytes += bytes;
|
2005-08-08 15:21:54 -04:00
|
|
|
|
|
|
|
if (source_mp3->read_count < REFBUF_SIZE)
|
|
|
|
{
|
|
|
|
if (source_mp3->read_count == 0)
|
|
|
|
{
|
|
|
|
refbuf_release (source_mp3->read_data);
|
|
|
|
source_mp3->read_data = NULL;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* read an mp3 stream which does not have shoutcast style metadata */
|
|
|
|
static refbuf_t *mp3_get_no_meta (source_t *source)
|
|
|
|
{
|
|
|
|
refbuf_t *refbuf;
|
|
|
|
mp3_state *source_mp3 = source->format->_state;
|
|
|
|
|
|
|
|
if (complete_read (source) == 0)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
refbuf = source_mp3->read_data;
|
|
|
|
source_mp3->read_data = NULL;
|
|
|
|
|
2004-08-20 11:13:59 -04:00
|
|
|
if (source_mp3->update_metadata)
|
|
|
|
{
|
|
|
|
mp3_set_title (source);
|
|
|
|
source_mp3->update_metadata = 0;
|
|
|
|
}
|
2005-08-08 15:21:54 -04:00
|
|
|
refbuf->associated = source_mp3->metadata;
|
|
|
|
refbuf_addref (source_mp3->metadata);
|
|
|
|
refbuf->sync_point = 1;
|
|
|
|
return refbuf;
|
2004-08-20 11:13:59 -04:00
|
|
|
}
|
2003-02-24 08:37:15 -05:00
|
|
|
|
|
|
|
|
2004-08-20 11:13:59 -04:00
|
|
|
/* read mp3 data with inlined metadata from the source. Filter out the
|
|
|
|
* metadata so that the mp3 data itself is store on the queue and the
|
|
|
|
* metadata is is associated with it
|
|
|
|
*/
|
2014-11-30 15:32:30 -05:00
|
|
|
static refbuf_t *mp3_get_filter_meta(source_t *source)
|
2004-08-20 11:13:59 -04:00
|
|
|
{
|
|
|
|
refbuf_t *refbuf;
|
|
|
|
format_plugin_t *plugin = source->format;
|
|
|
|
mp3_state *source_mp3 = plugin->_state;
|
|
|
|
unsigned char *src;
|
|
|
|
unsigned int bytes, mp3_block;
|
2003-02-24 08:37:15 -05:00
|
|
|
|
2005-08-08 15:21:54 -04:00
|
|
|
if (complete_read (source) == 0)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
refbuf = source_mp3->read_data;
|
|
|
|
source_mp3->read_data = NULL;
|
2007-08-24 11:44:37 -04:00
|
|
|
src = (unsigned char *)refbuf->data;
|
2003-02-24 08:37:15 -05:00
|
|
|
|
2004-08-20 11:13:59 -04:00
|
|
|
if (source_mp3->update_metadata)
|
|
|
|
{
|
|
|
|
mp3_set_title (source);
|
|
|
|
source_mp3->update_metadata = 0;
|
|
|
|
}
|
|
|
|
/* fill the buffer with the read data */
|
2005-08-08 15:21:54 -04:00
|
|
|
bytes = source_mp3->read_count;
|
2004-08-20 11:13:59 -04:00
|
|
|
refbuf->len = 0;
|
|
|
|
while (bytes > 0)
|
|
|
|
{
|
|
|
|
unsigned int metadata_remaining;
|
2003-02-24 08:37:15 -05:00
|
|
|
|
2004-08-20 11:13:59 -04:00
|
|
|
mp3_block = source_mp3->inline_metadata_interval - source_mp3->offset;
|
2003-02-24 08:37:15 -05:00
|
|
|
|
2004-08-20 11:13:59 -04:00
|
|
|
/* is there only enough to account for mp3 data */
|
|
|
|
if (bytes <= mp3_block)
|
|
|
|
{
|
|
|
|
refbuf->len += bytes;
|
|
|
|
source_mp3->offset += bytes;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
/* we have enough data to get to the metadata
|
|
|
|
* block, but only transfer upto it */
|
|
|
|
if (mp3_block)
|
|
|
|
{
|
|
|
|
src += mp3_block;
|
|
|
|
bytes -= mp3_block;
|
|
|
|
refbuf->len += mp3_block;
|
|
|
|
source_mp3->offset += mp3_block;
|
|
|
|
continue;
|
|
|
|
}
|
2003-02-24 08:37:15 -05:00
|
|
|
|
2004-08-20 11:13:59 -04:00
|
|
|
/* process the inline metadata, len == 0 indicates not seen any yet */
|
|
|
|
if (source_mp3->build_metadata_len == 0)
|
|
|
|
{
|
|
|
|
memset (source_mp3->build_metadata, 0,
|
|
|
|
sizeof (source_mp3->build_metadata));
|
|
|
|
source_mp3->build_metadata_offset = 0;
|
|
|
|
source_mp3->build_metadata_len = 1 + (*src * 16);
|
|
|
|
}
|
2003-02-25 04:40:34 -05:00
|
|
|
|
2004-08-20 11:13:59 -04:00
|
|
|
/* do we have all of the metatdata block */
|
|
|
|
metadata_remaining = source_mp3->build_metadata_len -
|
|
|
|
source_mp3->build_metadata_offset;
|
|
|
|
if (bytes < metadata_remaining)
|
|
|
|
{
|
|
|
|
memcpy (source_mp3->build_metadata +
|
|
|
|
source_mp3->build_metadata_offset, src, bytes);
|
|
|
|
source_mp3->build_metadata_offset += bytes;
|
|
|
|
break;
|
|
|
|
}
|
2015-01-10 13:53:44 -05:00
|
|
|
/* copy all bytes except the last one, that way we
|
2004-08-20 11:13:59 -04:00
|
|
|
* know a null byte terminates the message */
|
|
|
|
memcpy (source_mp3->build_metadata + source_mp3->build_metadata_offset,
|
|
|
|
src, metadata_remaining-1);
|
2003-02-24 08:37:15 -05:00
|
|
|
|
2004-08-20 11:13:59 -04:00
|
|
|
/* overwrite metadata in the buffer */
|
|
|
|
bytes -= metadata_remaining;
|
|
|
|
memmove (src, src+metadata_remaining, bytes);
|
2003-02-25 04:40:34 -05:00
|
|
|
|
2004-11-17 15:35:35 -05:00
|
|
|
/* assign metadata if it's greater than 1 byte, and the text has changed */
|
|
|
|
if (source_mp3->build_metadata_len > 1 &&
|
|
|
|
strcmp (source_mp3->build_metadata+1, source_mp3->metadata->data+1) != 0)
|
2004-08-20 11:13:59 -04:00
|
|
|
{
|
|
|
|
refbuf_t *meta = refbuf_new (source_mp3->build_metadata_len);
|
|
|
|
memcpy (meta->data, source_mp3->build_metadata,
|
|
|
|
source_mp3->build_metadata_len);
|
|
|
|
|
2015-01-09 20:48:15 -05:00
|
|
|
ICECAST_LOG_DEBUG("shoutcast metadata %.*s", 4080, meta->data+1);
|
2004-08-20 11:13:59 -04:00
|
|
|
if (strncmp (meta->data+1, "StreamTitle=", 12) == 0)
|
|
|
|
{
|
|
|
|
filter_shoutcast_metadata (source, source_mp3->build_metadata,
|
|
|
|
source_mp3->build_metadata_len);
|
|
|
|
refbuf_release (source_mp3->metadata);
|
|
|
|
source_mp3->metadata = meta;
|
2009-01-14 22:38:49 -05:00
|
|
|
source_mp3->inline_url = strstr (meta->data+1, "StreamUrl='");
|
2004-08-20 11:13:59 -04:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2014-10-31 04:46:58 -04:00
|
|
|
ICECAST_LOG_ERROR("Incorrect metadata format, ending stream");
|
2004-08-20 11:13:59 -04:00
|
|
|
source->running = 0;
|
|
|
|
refbuf_release (refbuf);
|
2005-08-08 15:21:54 -04:00
|
|
|
refbuf_release (meta);
|
2004-08-20 11:13:59 -04:00
|
|
|
return NULL;
|
2003-02-24 08:37:15 -05:00
|
|
|
}
|
|
|
|
}
|
2004-08-20 11:13:59 -04:00
|
|
|
source_mp3->offset = 0;
|
|
|
|
source_mp3->build_metadata_len = 0;
|
2003-02-17 07:05:45 -05:00
|
|
|
}
|
2004-08-20 11:13:59 -04:00
|
|
|
/* the data we have just read may of just been metadata */
|
|
|
|
if (refbuf->len == 0)
|
|
|
|
{
|
|
|
|
refbuf_release (refbuf);
|
|
|
|
return NULL;
|
2003-02-17 07:05:45 -05:00
|
|
|
}
|
2004-08-20 11:13:59 -04:00
|
|
|
refbuf->associated = source_mp3->metadata;
|
|
|
|
refbuf_addref (source_mp3->metadata);
|
2004-12-07 16:06:26 -05:00
|
|
|
refbuf->sync_point = 1;
|
2002-07-23 11:15:11 -04:00
|
|
|
|
2004-08-20 11:13:59 -04:00
|
|
|
return refbuf;
|
2002-07-23 11:15:11 -04:00
|
|
|
}
|
|
|
|
|
2004-08-20 11:13:59 -04:00
|
|
|
|
|
|
|
static int format_mp3_create_client_data(source_t *source, client_t *client)
|
2002-12-30 02:55:56 -05:00
|
|
|
{
|
2014-11-30 15:32:30 -05:00
|
|
|
mp3_client_data *client_mp3 = calloc(1, sizeof(mp3_client_data));
|
2005-04-18 10:32:26 -04:00
|
|
|
mp3_state *source_mp3 = source->format->_state;
|
2009-01-14 22:38:49 -05:00
|
|
|
const char *metadata;
|
2005-06-07 21:36:51 -04:00
|
|
|
/* the +-2 is for overwriting the last set of \r\n */
|
|
|
|
unsigned remaining = 4096 - client->refbuf->len + 2;
|
|
|
|
char *ptr = client->refbuf->data + client->refbuf->len - 2;
|
|
|
|
int bytes;
|
2007-10-19 10:06:07 -04:00
|
|
|
const char *useragent;
|
2002-12-30 02:55:56 -05:00
|
|
|
|
2005-04-18 10:32:26 -04:00
|
|
|
if (client_mp3 == NULL)
|
2004-08-20 11:13:59 -04:00
|
|
|
return -1;
|
2002-12-30 02:55:56 -05:00
|
|
|
|
2007-10-19 10:06:07 -04:00
|
|
|
/* hack for flash player, it wants a length. It has also been reported that the useragent
|
|
|
|
* appears as MSIE if run in internet explorer */
|
|
|
|
useragent = httpp_getvar (client->parser, "user-agent");
|
|
|
|
if (httpp_getvar(client->parser, "x-flash-version") ||
|
|
|
|
(useragent && strstr(useragent, "MSIE")))
|
2005-06-07 21:36:51 -04:00
|
|
|
{
|
2007-10-19 10:06:07 -04:00
|
|
|
bytes = snprintf (ptr, remaining, "Content-Length: 221183499\r\n");
|
2005-06-07 21:36:51 -04:00
|
|
|
remaining -= bytes;
|
|
|
|
ptr += bytes;
|
|
|
|
}
|
|
|
|
|
2005-04-18 10:32:26 -04:00
|
|
|
client->format_data = client_mp3;
|
2004-08-20 11:13:59 -04:00
|
|
|
client->free_client_data = free_mp3_client_data;
|
2002-12-31 14:48:28 -05:00
|
|
|
metadata = httpp_getvar(client->parser, "icy-metadata");
|
2005-04-18 10:32:26 -04:00
|
|
|
if (metadata && atoi(metadata))
|
|
|
|
{
|
2005-08-30 21:28:04 -04:00
|
|
|
if (source_mp3->interval >= 0)
|
2005-04-18 10:32:26 -04:00
|
|
|
client_mp3->interval = source_mp3->interval;
|
|
|
|
else
|
|
|
|
client_mp3->interval = ICY_METADATA_INTERVAL;
|
2005-08-30 21:28:04 -04:00
|
|
|
if (client_mp3->interval)
|
2005-06-07 21:36:51 -04:00
|
|
|
{
|
2005-08-30 21:28:04 -04:00
|
|
|
bytes = snprintf (ptr, remaining, "icy-metaint:%u\r\n",
|
|
|
|
client_mp3->interval);
|
|
|
|
if (bytes > 0)
|
|
|
|
{
|
|
|
|
remaining -= bytes;
|
|
|
|
ptr += bytes;
|
|
|
|
}
|
2005-06-07 21:36:51 -04:00
|
|
|
}
|
2005-04-18 10:32:26 -04:00
|
|
|
}
|
2005-06-07 21:36:51 -04:00
|
|
|
bytes = snprintf (ptr, remaining, "\r\n");
|
|
|
|
remaining -= bytes;
|
|
|
|
ptr += bytes;
|
2005-04-18 10:32:26 -04:00
|
|
|
|
2005-06-07 21:36:51 -04:00
|
|
|
client->refbuf->len = 4096 - remaining;
|
2002-12-30 02:55:56 -05:00
|
|
|
|
2004-08-20 11:13:59 -04:00
|
|
|
return 0;
|
2002-12-29 03:10:10 -05:00
|
|
|
}
|
2002-07-23 11:15:11 -04:00
|
|
|
|
2004-02-29 09:38:15 -05:00
|
|
|
|
|
|
|
static void free_mp3_client_data (client_t *client)
|
|
|
|
{
|
|
|
|
free (client->format_data);
|
|
|
|
client->format_data = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-08-20 11:13:59 -04:00
|
|
|
static void write_mp3_to_file (struct source_tag *source, refbuf_t *refbuf)
|
|
|
|
{
|
|
|
|
if (refbuf->len == 0)
|
|
|
|
return;
|
|
|
|
if (fwrite (refbuf->data, 1, refbuf->len, source->dumpfile) < (size_t)refbuf->len)
|
|
|
|
{
|
2014-10-31 04:46:58 -04:00
|
|
|
ICECAST_LOG_WARN("Write to dump file failed, disabling");
|
2004-08-20 11:13:59 -04:00
|
|
|
fclose (source->dumpfile);
|
|
|
|
source->dumpfile = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|