Commit 2bd23d90 authored by Karl Heyes's avatar Karl Heyes
Browse files

merge multi ogg codec handling. Handle theora and/or vorbis. Place new

clients before keyframe. For vorbis-only streams, perform rebuild to
flush pages more frequently and to provide url updating mechanism for
titles 

svn path=/icecast/trunk/icecast/; revision=8341
parent 82497129
......@@ -73,9 +73,23 @@ XIPH_PATH_XSLT
XIPH_VAR_APPEND([XIPH_CPPFLAGS],[$XSLT_CFLAGS])
XIPH_VAR_PREPEND([XIPH_LIBS],[$XSLT_LIBS])
XIPH_PATH_VORBIS(, AC_MSG_ERROR([must have Ogg Vorbis v1.0 or above installed!]))
XIPH_VAR_APPEND([XIPH_CPPFLAGS],[$VORBIS_CFLAGS])
XIPH_VAR_PREPEND([XIPH_LIBS],[$VORBIS_LIBS])
XIPH_PATH_VORBIS([
XIPH_VAR_APPEND([XIPH_CPPFLAGS],[$VORBIS_CFLAGS])
XIPH_VAR_PREPEND([XIPH_LIBS],[$VORBIS_LIBS])
XIPH_VAR_APPEND([XIPH_LDFLAGS],[$VORBIS_LDFLAGS])
ICECAST_OPTIONAL="$ICECAST_OPTIONAL format_vorbis.o"
],
[AC_MSG_ERROR([must have Ogg Vorbis v1.0 or above installed])
])
XIPH_PATH_THEORA([
XIPH_VAR_APPEND([XIPH_CPPFLAGS],[$THEORA_CFLAGS])
XIPH_VAR_APPEND([XIPH_LDFLAGS],[$THEORA_LDFLAGS])
XIPH_VAR_PREPEND([XIPH_LIBS],[$THEORA_LIBS])
ICECAST_OPTIONAL="$ICECAST_OPTIONAL format_theora.o"
],
[ AC_MSG_WARN([Theora disabled!])
])
ACX_PTHREAD(, AC_MSG_ERROR([POSIX threads missing]))
XIPH_VAR_APPEND([XIPH_CFLAGS],[$PTHREAD_CFLAGS])
......@@ -109,6 +123,7 @@ dnl Make substitutions
AC_SUBST(XIPH_CPPFLAGS)
AC_SUBST(XIPH_CFLAGS)
AC_SUBST(XIPH_LIBS)
AC_SUBST(XIPH_LDFLAGS)
AC_SUBST(PTHREAD_CPPFLAGS)
AC_SUBST(PTHREAD_CFLAGS)
AC_SUBST(PTHREAD_LIBS)
......
......@@ -6,13 +6,16 @@ SUBDIRS = avl thread httpp net log timing
bin_PROGRAMS = icecast
noinst_HEADERS = admin.h cfgfile.h os.h logging.h sighandler.h connection.h global.h\
util.h slave.h source.h stats.h refbuf.h client.h format.h format_vorbis.h\
compat.h format_mp3.h fserve.h xslt.h yp.h event.h auth.h md5.h
icecast_SOURCES = cfgfile.c main.c logging.c sighandler.c connection.c global.c\
util.c slave.c source.c stats.c refbuf.c client.c format.c format_vorbis.c\
format_mp3.c xslt.c fserve.c event.c admin.c auth.c md5.c
EXTRA_icecast_SOURCES = yp.c
noinst_HEADERS = admin.h cfgfile.h os.h logging.h sighandler.h connection.h \
global.h util.h slave.h source.h stats.h refbuf.h client.h format.h \
compat.h format_mp3.h fserve.h xslt.h yp.h event.h md5.h \
auth.h format_ogg.h \
format_vorbis.h format_theora.h
icecast_SOURCES = cfgfile.c main.c logging.c sighandler.c connection.c global.c \
util.c slave.c source.c stats.c refbuf.c client.c format.c format_ogg.c \
format_mp3.c xslt.c fserve.c event.c admin.c auth.c md5.c
EXTRA_icecast_SOURCES = yp.c \
format_vorbis.c format_theora.c
icecast_DEPENDENCIES = @ICECAST_OPTIONAL@ net/libicenet.la thread/libicethread.la \
httpp/libicehttpp.la log/libicelog.la avl/libiceavl.la timing/libicetiming.la
......@@ -20,6 +23,7 @@ icecast_LDADD = $(icecast_DEPENDENCIES) @XIPH_LIBS@
AM_CFLAGS = @XIPH_CFLAGS@
AM_CPPFLAGS = @XIPH_CPPFLAGS@
AM_LDFLAGS = @XIPH_LDFLAGS@
debug:
......
......@@ -825,18 +825,15 @@ static void command_fallback(client_t *client, source_t *source,
static void command_metadata(client_t *client, source_t *source)
{
char *action;
char *value;
mp3_state *state;
char *song, *title, *artist;
format_plugin_t *plugin;
DEBUG0("Got metadata update request");
COMMAND_REQUIRE(client, "mode", action);
COMMAND_REQUIRE(client, "song", value);
if (source->format->type == FORMAT_TYPE_VORBIS) {
client_send_400 (client, "Cannot update metadata on vorbis streams");
return;
}
COMMAND_OPTIONAL(client, "song", song);
COMMAND_OPTIONAL(client, "title", title);
COMMAND_OPTIONAL(client, "artist", artist);
if (strcmp (action, "updinfo") != 0)
{
......@@ -844,22 +841,32 @@ static void command_metadata(client_t *client, source_t *source)
return;
}
state = source->format->_state;
mp3_set_tag (source->format, "title", value);
DEBUG2("Metadata on mountpoint %s changed to \"%s\"",
source->mount, value);
stats_event(source->mount, "title", value);
plugin = source->format;
/* At this point, we assume that the metadata passed in
is encoded in UTF-8 */
logging_playlist(source->mount, value, source->listeners);
/* If we get an update on the mountpoint, force a
yp touch */
yp_touch (source->mount);
if (plugin && plugin->set_tag)
{
if (song)
{
plugin->set_tag (plugin, "song", song);
DEBUG2("Metadata on mountpoint %s changed to \"%s\"", source->mount, song);
}
else
{
if (artist && title)
{
plugin->set_tag (plugin, "title", title);
plugin->set_tag (plugin, "artist", artist);
INFO3("Metadata on mountpoint %s changed to \"%s - %s\"",
source->mount, artist, title);
}
}
html_success(client, "Metadata update successful");
html_success(client, "Metadata update successful");
}
else
{
client_send_400 (client, "mountpoint will not accept URL updates");
}
}
static void command_shoutcast_metadata(client_t *client, source_t *source)
......@@ -873,7 +880,7 @@ static void command_shoutcast_metadata(client_t *client, source_t *source)
COMMAND_REQUIRE(client, "mode", action);
COMMAND_REQUIRE(client, "song", value);
if (source->format->type == FORMAT_TYPE_VORBIS) {
if (source->format->type == FORMAT_TYPE_OGG) {
client_send_400 (client, "Cannot update metadata on vorbis streams");
return;
}
......@@ -890,11 +897,7 @@ static void command_shoutcast_metadata(client_t *client, source_t *source)
DEBUG2("Metadata on mountpoint %s changed to \"%s\"",
source->mount, value);
stats_event(source->mount, "title", value);
/* If we get an update on the mountpoint, force a
yp touch */
yp_touch (source->mount);
html_success(client, "Metadata update successful");
}
......
......@@ -51,9 +51,9 @@
format_type_t format_get_type(char *contenttype)
{
if(strcmp(contenttype, "application/x-ogg") == 0)
return FORMAT_TYPE_VORBIS; /* Backwards compatibility */
return FORMAT_TYPE_OGG; /* Backwards compatibility */
else if(strcmp(contenttype, "application/ogg") == 0)
return FORMAT_TYPE_VORBIS; /* Now blessed by IANA */
return FORMAT_TYPE_OGG; /* Now blessed by IANA */
else
/* We default to the Generic format handler, which
can handle many more formats than just mp3 */
......@@ -65,8 +65,8 @@ int format_get_plugin(format_type_t type, source_t *source)
int ret = -1;
switch (type) {
case FORMAT_TYPE_VORBIS:
ret = format_vorbis_get_plugin (source);
case FORMAT_TYPE_OGG:
ret = format_ogg_get_plugin (source);
break;
case FORMAT_TYPE_GENERIC:
ret = format_mp3_get_plugin (source);
......
......@@ -26,9 +26,9 @@ struct source_tag;
typedef enum _format_type_tag
{
FORMAT_TYPE_VORBIS,
FORMAT_TYPE_GENERIC,
FORMAT_ERROR /* No format, source not processable */
FORMAT_ERROR, /* No format, source not processable */
FORMAT_TYPE_OGG,
FORMAT_TYPE_GENERIC
} format_type_t;
typedef struct _format_plugin_tag
......@@ -46,6 +46,7 @@ typedef struct _format_plugin_tag
int (*create_client_data)(struct source_tag *source, client_t *client);
void (*client_send_headers)(struct _format_plugin_tag *format,
struct source_tag *source, client_t *client);
void (*set_tag)(struct _format_plugin_tag *plugin, char *tag, char *value);
void (*free_plugin)(struct _format_plugin_tag *self);
/* for internal state management */
......
......@@ -80,7 +80,7 @@ int format_mp3_get_plugin (source_t *source)
mp3_state *state = calloc(1, sizeof(mp3_state));
refbuf_t *meta;
plugin = (format_plugin_t *)malloc(sizeof(format_plugin_t));
plugin = (format_plugin_t *)calloc(1, sizeof(format_plugin_t));
plugin->type = FORMAT_TYPE_GENERIC;
plugin->get_buffer = mp3_get_no_meta;
......@@ -89,6 +89,7 @@ int format_mp3_get_plugin (source_t *source)
plugin->create_client_data = format_mp3_create_client_data;
plugin->client_send_headers = format_mp3_send_headers;
plugin->free_plugin = format_mp3_free_plugin;
plugin->set_tag = mp3_set_tag;
plugin->contenttype = httpp_getvar (source->parser, "content-type");
if (plugin->contenttype == NULL) {
......@@ -179,6 +180,7 @@ static void filter_shoutcast_metadata (source_t *source, char *metadata, unsigne
if (p)
{
memcpy (p, metadata+13, len);
logging_playlist (source->mount, p, source->listeners);
stats_event (source->mount, "title", p);
yp_touch (source->mount);
free (p);
......@@ -421,6 +423,7 @@ static refbuf_t *mp3_get_no_meta (source_t *source)
refbuf->len = bytes;
refbuf->associated = source_mp3->metadata;
refbuf_addref (source_mp3->metadata);
refbuf->sync_point = 1;
return refbuf;
}
refbuf_release (refbuf);
......@@ -561,6 +564,7 @@ static refbuf_t *mp3_get_filter_meta (source_t *source)
}
refbuf->associated = source_mp3->metadata;
refbuf_addref (source_mp3->metadata);
refbuf->sync_point = 1;
return refbuf;
}
......
/* Icecast
*
* This program is distributed under the GNU General Public License, version 2.
* A copy of this license is included with this source.
*
* Copyright 2000-2004, Jack Moffitt <jack@xiph.org,
* Michael Smith <msmith@xiph.org>,
* oddsock <oddsock@xiph.org>,
* Karl Heyes <karl@xiph.org>
* and others (see AUTHORS for details).
*/
/* format_ogg.c
*
* format plugin for Ogg
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ogg/ogg.h>
#include "refbuf.h"
#include "source.h"
#include "client.h"
#include "stats.h"
#include "format.h"
#include "format_ogg.h"
#include "format_vorbis.h"
#ifdef HAVE_THEORA
#include "format_theora.h"
#endif
#define CATMODULE "format-ogg"
#include "logging.h"
struct _ogg_state_tag;
static void format_ogg_free_plugin (format_plugin_t *plugin);
static int create_ogg_client_data(source_t *source, client_t *client);
static void format_ogg_send_headers(format_plugin_t *self,
source_t *source, client_t *client);
static void free_ogg_client_data (client_t *client);
static void write_ogg_to_file (struct source_tag *source, refbuf_t *refbuf);
static refbuf_t *ogg_get_buffer (source_t *source);
static int write_buf_to_client (format_plugin_t *self, client_t *client);
struct ogg_client
{
refbuf_t *headers;
refbuf_t *header_page;
unsigned pos;
int headers_sent;
};
refbuf_t *make_refbuf_with_page (ogg_page *page)
{
refbuf_t *refbuf = refbuf_new (page->header_len + page->body_len);
memcpy (refbuf->data, page->header, page->header_len);
memcpy (refbuf->data+page->header_len, page->body, page->body_len);
return refbuf;
}
/* routine for taking the provided page (should be a header page) and
* placing it on the collection of header pages
*/
void format_ogg_attach_header (ogg_state_t *ogg_info, ogg_page *page)
{
refbuf_t *refbuf = make_refbuf_with_page (page);
if (ogg_page_bos (page))
{
DEBUG0 ("attaching BOS page");
if (*ogg_info->bos_end == NULL)
ogg_info->header_pages_tail = refbuf;
refbuf->next = *ogg_info->bos_end;
*ogg_info->bos_end = refbuf;
ogg_info->bos_end = &refbuf->next;
return;
}
DEBUG0 ("attaching header page");
if (ogg_info->header_pages_tail)
ogg_info->header_pages_tail->next = refbuf;
ogg_info->header_pages_tail = refbuf;
if (ogg_info->header_pages == NULL)
ogg_info->header_pages = refbuf;
}
void format_ogg_free_headers (ogg_state_t *ogg_info)
{
refbuf_t *header;
/* release the header pages first */
DEBUG0 ("releasing header pages");
header = ogg_info->header_pages;
while (header)
{
refbuf_t *to_release = header;
header = header->next;
refbuf_release (to_release);
}
ogg_info->header_pages = NULL;
ogg_info->header_pages_tail = NULL;
ogg_info->bos_end = &ogg_info->header_pages;
}
/* release the memory used for the codec and header pages from the module */
static void free_ogg_codecs (ogg_state_t *ogg_info)
{
ogg_codec_t *codec;
if (ogg_info == NULL)
return;
format_ogg_free_headers (ogg_info);
/* now free the codecs */
codec = ogg_info->codecs;
DEBUG0 ("freeing codecs");
while (codec)
{
ogg_codec_t *next = codec->next;
codec->codec_free (ogg_info, codec);
codec = next;
}
ogg_info->codecs = NULL;
ogg_info->current = NULL;
ogg_info->bos_completed = 0;
}
int format_ogg_get_plugin (source_t *source)
{
format_plugin_t *plugin;
ogg_state_t *state = calloc (1, sizeof (ogg_state_t));
plugin = (format_plugin_t *)calloc(1, sizeof(format_plugin_t));
plugin->type = FORMAT_TYPE_OGG;
plugin->get_buffer = ogg_get_buffer;
plugin->write_buf_to_client = write_buf_to_client;
plugin->write_buf_to_file = write_ogg_to_file;
plugin->create_client_data = create_ogg_client_data;
plugin->client_send_headers = format_ogg_send_headers;
plugin->free_plugin = format_ogg_free_plugin;
plugin->set_tag = NULL;
plugin->contenttype = "application/ogg";
ogg_sync_init (&state->oy);
plugin->_state = state;
source->format = plugin;
state->mount = source->mount;
state->bos_end = &state->header_pages;
return 0;
}
void format_ogg_free_plugin (format_plugin_t *plugin)
{
ogg_state_t *state = plugin->_state;
/* free memory associated with this plugin instance */
free_ogg_codecs (state);
free (state->artist);
free (state->title);
ogg_sync_clear (&state->oy);
free (state);
free (plugin);
}
/* a new BOS page has been seen so check which codec it is */
static int process_initial_page (format_plugin_t *plugin, ogg_page *page)
{
ogg_state_t *ogg_info = plugin->_state;
ogg_codec_t *codec;
if (ogg_info->bos_completed)
{
ogg_info->bitrate = 0;
ogg_info->codec_sync = NULL;
/* need to zap old list of codecs when next group of BOS pages appear */
free_ogg_codecs (ogg_info);
}
do
{
codec = initial_vorbis_page (plugin, page);
if (codec)
break;
#ifdef HAVE_THEORA
codec = initial_theora_page (plugin, page);
if (codec)
break;
#endif
/* any others */
INFO0 ("Seen BOS page with unknown type");
return -1;
} while (0);
if (codec)
{
/* add codec to list */
codec->next = ogg_info->codecs;
ogg_info->codecs = codec;
}
return 0;
}
/* This is called when there has been a change in the metadata. Usually
* artist and title are provided separately so here we update the stats
* and write log entry if required.
*/
static void update_comments (source_t *source)
{
ogg_state_t *ogg_info = source->format->_state;
char *title = ogg_info->title;
char *artist = ogg_info->artist;
char *metadata = NULL;
unsigned int len = 0;
if (ogg_info->artist)
{
if (title)
{
len += strlen(artist) + strlen(title) + 3;
metadata = calloc (1, len);
snprintf (metadata, len, "%s - %s", artist, title);
}
else
{
len += strlen(artist);
metadata = calloc (1, len);
snprintf (metadata, len, "%s", artist);
}
}
else
{
if (title)
{
len += strlen (title);
metadata = calloc (1, len);
snprintf (metadata, len, "%s", title);
}
}
if (metadata)
{
logging_playlist (source->mount, metadata, source->listeners);
free (metadata);
}
stats_event (source->mount, "artist", artist);
stats_event (source->mount, "title", title);
yp_touch (source->mount);
}
/* called when preparing a refbuf with audio data to be passed
* back for queueing
*/
static refbuf_t *complete_buffer (source_t *source, refbuf_t *refbuf)
{
ogg_state_t *ogg_info = source->format->_state;
refbuf_t *header = ogg_info->header_pages;
while (header)
{
refbuf_addref (header);
header = header->next;
}
refbuf->associated = ogg_info->header_pages;
if (ogg_info->log_metadata)
{
update_comments (source);
ogg_info->log_metadata = 0;
}
/* listeners can start anywhere unless the codecs themselves are
* marking starting points */
if (ogg_info->codec_sync == NULL)
refbuf->sync_point = 1;
return refbuf;
}
/* process the incoming page. this requires searching through the
* currently known codecs that have been seen in the stream
*/
static refbuf_t *process_ogg_page (ogg_state_t *ogg_info, ogg_page *page)
{
ogg_codec_t *codec = ogg_info->codecs;
refbuf_t *refbuf = NULL;
while (codec)
{
if (ogg_page_serialno (page) == codec->os.serialno)
{
if (codec->process_page)
refbuf = codec->process_page (ogg_info, codec, page);
break;
}
codec = codec->next;
}
ogg_info->current = codec;
return refbuf;
}
/* main plugin handler for getting a buffer for the queue. In here we
* just add an incoming page to the codecs and process it until either
* more data is needed or we prodice a buffer for the queue.
*/
static refbuf_t *ogg_get_buffer (source_t *source)
{
ogg_state_t *ogg_info = source->format->_state;
char *data = NULL;
int bytes;
while (1)
{
while (1)
{
ogg_page page;
refbuf_t *refbuf;
ogg_codec_t *codec = ogg_info->current;
/* if a codec has just been given a page then process it */
if (codec && codec->process)
{
refbuf = codec->process (ogg_info, codec);
if (refbuf)
return complete_buffer (source, refbuf);
ogg_info->current = NULL;
}
if (ogg_sync_pageout (&ogg_info->oy, &page) > 0)
{
if (ogg_page_bos (&page))
{
process_initial_page (source->format, &page);
continue;
}
ogg_info->bos_completed = 1;
refbuf = process_ogg_page (ogg_info, &page);
if (ogg_info->error)
{
ERROR0 ("Problem processing stream");
source->running = 0;
return NULL;
}
if (refbuf)
return complete_buffer (source, refbuf);
continue;
}
/* need more stream data */
break;
}
/* we need more data to continue getting pages */
data = ogg_sync_buffer (&ogg_info->oy, 4096);
bytes = sock_read_bytes (source->con->sock, data, 4096);
if (bytes < 0)
{
if (sock_recoverable (sock_error()))
return NULL;
WARN0 ("source connection has died");