From 2bd23d9050bcc881da8589b20f39ba0693dd4ca6 Mon Sep 17 00:00:00 2001 From: Karl Heyes Date: Tue, 7 Dec 2004 21:06:26 +0000 Subject: [PATCH] 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 --- configure.in | 21 +- src/Makefile.am | 18 +- src/admin.c | 57 +-- src/format.c | 8 +- src/format.h | 7 +- src/format_mp3.c | 6 +- src/format_ogg.c | 565 ++++++++++++++++++++++++++++++ src/format_ogg.h | 68 ++++ src/format_theora.c | 170 +++++++++ src/format_theora.h | 21 ++ src/format_vorbis.c | 827 +++++++++++++++++++++++++------------------- src/format_vorbis.h | 16 +- src/refbuf.c | 1 + src/refbuf.h | 1 + src/source.c | 25 +- 15 files changed, 1394 insertions(+), 417 deletions(-) create mode 100644 src/format_ogg.c create mode 100644 src/format_ogg.h create mode 100644 src/format_theora.c create mode 100644 src/format_theora.h diff --git a/configure.in b/configure.in index a3fdc4bc..e19cf19b 100644 --- a/configure.in +++ b/configure.in @@ -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) diff --git a/src/Makefile.am b/src/Makefile.am index 23ac1e66..fcfe680e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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: diff --git a/src/admin.c b/src/admin.c index c83c5808..6bb8ff40 100644 --- a/src/admin.c +++ b/src/admin.c @@ -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; + plugin = source->format; - mp3_set_tag (source->format, "title", value); + 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); + } + } - DEBUG2("Metadata on mountpoint %s changed to \"%s\"", - source->mount, value); - stats_event(source->mount, "title", value); - - /* 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); - - 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"); } diff --git a/src/format.c b/src/format.c index e4143eeb..b077d2cb 100644 --- a/src/format.c +++ b/src/format.c @@ -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); diff --git a/src/format.h b/src/format.h index cedb98e7..f3bf3602 100644 --- a/src/format.h +++ b/src/format.h @@ -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 */ diff --git a/src/format_mp3.c b/src/format_mp3.c index ba8a1e03..3e7d90d8 100644 --- a/src/format_mp3.c +++ b/src/format_mp3.c @@ -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; } diff --git a/src/format_ogg.c b/src/format_ogg.c new file mode 100644 index 00000000..560019d2 --- /dev/null +++ b/src/format_ogg.c @@ -0,0 +1,565 @@ +/* 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 , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +/* format_ogg.c + * + * format plugin for Ogg + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include + +#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"); + ogg_sync_wrote (&ogg_info->oy, 0); + source->running = 0; + return NULL; + } + if (bytes == 0) + { + INFO1 ("End of Stream %s", source->mount); + ogg_sync_wrote (&ogg_info->oy, 0); + source->running = 0; + return NULL; + } + ogg_sync_wrote (&ogg_info->oy, bytes); + } +} + + +static int create_ogg_client_data (source_t *source, client_t *client) +{ + struct ogg_client *client_data = calloc (1, sizeof (struct ogg_client)); + int ret = -1; + + if (client_data) + { + client_data->headers_sent = 1; + client->format_data = client_data; + client->free_client_data = free_ogg_client_data; + ret = 0; + } + return ret; +} + + +static void free_ogg_client_data (client_t *client) +{ + free (client->format_data); + client->format_data = NULL; +} + + +/* send out the header pages. These are for all codecs but are + * in the order for the stream, ie BOS pages first + */ +static int send_ogg_headers (client_t *client, refbuf_t *headers) +{ + struct ogg_client *client_data = client->format_data; + refbuf_t *refbuf; + int written = 0; + + if (client_data->headers_sent) + { + client_data->header_page = headers; + client_data->pos = 0; + client_data->headers_sent = 0; + } + refbuf = client_data->header_page; + while (refbuf) + { + char *data = refbuf->data + client_data->pos; + unsigned len = refbuf->len - client_data->pos; + int ret; + + ret = client_send_bytes (client, data, len); + if (ret > 0) + written += ret; + if (ret < (int)len) + return written ? written : -1; + client_data->pos += ret; + if (client_data->pos == refbuf->len) + { + refbuf = refbuf->next; + client_data->header_page = refbuf; + client_data->pos = 0; + } + } + client_data->headers_sent = 1; + client_data->headers = headers; + return written; +} + + +/* main client write routine for sending ogg data. Each refbuf has a + * single page so we only need to determine if there are new headers + */ +static int write_buf_to_client (format_plugin_t *self, client_t *client) +{ + refbuf_t *refbuf = client->refbuf; + char *buf; + unsigned len; + struct ogg_client *client_data = client->format_data; + int ret, written = 0; + + if (refbuf->next == NULL && client->pos == refbuf->len) + return 0; + + if (refbuf->next && client->pos == refbuf->len) + { + client_set_queue (client, refbuf->next); + refbuf = client->refbuf; + } + buf = refbuf->data + client->pos; + len = refbuf->len - client->pos; + do + { + if (client_data->headers != refbuf->associated) + { + ret = send_ogg_headers (client, refbuf->associated); + if (client_data->headers_sent == 0) + break; + written += ret; + } + ret = client_send_bytes (client, buf, len); + + if (ret > 0) + client->pos += ret; + + if (ret < (int)len) + break; + written += ret; + /* we have now written the page(s) */ + ret = 0; + } while (0); + + if (ret > 0) + written += ret; + return written; +} + + +static int write_ogg_data (struct source_tag *source, refbuf_t *refbuf) +{ + int ret = 1; + + if (fwrite (refbuf->data, 1, refbuf->len, source->dumpfile) != refbuf->len) + { + WARN0 ("Write to dump file failed, disabling"); + fclose (source->dumpfile); + source->dumpfile = NULL; + ret = 0; + } + return ret; +} + + +static void write_ogg_to_file (struct source_tag *source, refbuf_t *refbuf) +{ + ogg_state_t *ogg_info = source->format->_state; + + if (ogg_info->file_headers != refbuf->associated) + { + refbuf_t *header = refbuf->associated; + while (header) + { + if (write_ogg_data (source, header) == 0) + return; + header = header->next; + } + ogg_info->file_headers = refbuf->associated; + } + write_ogg_data (source, refbuf); +} + + +static void format_ogg_send_headers(format_plugin_t *self, + source_t *source, client_t *client) +{ + int bytes; + + client->respcode = 200; + bytes = sock_write(client->con->sock, + "HTTP/1.0 200 OK\r\n" + "Content-Type: %s\r\n", + source->format->contenttype); + + if(bytes > 0) client->con->sent_bytes += bytes; + + format_send_general_headers(self, source, client); +} + diff --git a/src/format_ogg.h b/src/format_ogg.h new file mode 100644 index 00000000..4c308643 --- /dev/null +++ b/src/format_ogg.h @@ -0,0 +1,68 @@ +/* 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 , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + +/* format_ogg.h +** +** vorbis format plugin header +** +*/ +#ifndef __FORMAT_OGG_H__ +#define __FORMAT_OGG_H__ + +#include +#include "refbuf.h" +#include "format.h" + +typedef struct ogg_state_tag +{ + char *mount; + ogg_sync_state oy; + int error; + + struct ogg_codec_tag *codecs; + char *artist; + char *title; + int log_metadata; + refbuf_t *file_headers; + refbuf_t *header_pages; + refbuf_t *header_pages_tail; + refbuf_t **bos_end; + int bos_completed; + long bitrate; + struct ogg_codec_tag *current; + struct ogg_codec_tag *codec_sync; +} ogg_state_t; + + +/* per codec/logical structure */ +typedef struct ogg_codec_tag +{ + struct ogg_codec_tag *next; + ogg_stream_state os; + unsigned headers; + void *specific; + refbuf_t *possible_start; + refbuf_t *page; + + refbuf_t *(*process)(ogg_state_t *ogg_info, struct ogg_codec_tag *codec); + refbuf_t *(*process_page)(ogg_state_t *ogg_info, + struct ogg_codec_tag *codec, ogg_page *page); + void (*codec_free)(ogg_state_t *ogg_info, struct ogg_codec_tag *codec); +} ogg_codec_t; + + +refbuf_t *make_refbuf_with_page (ogg_page *page); +void format_ogg_attach_header (ogg_state_t *ogg_info, ogg_page *page); +void format_ogg_free_headers (ogg_state_t *ogg_info); +int format_ogg_get_plugin (source_t *source); + +#endif /* __FORMAT_OGG_H__ */ diff --git a/src/format_theora.c b/src/format_theora.c new file mode 100644 index 00000000..5ce4b354 --- /dev/null +++ b/src/format_theora.c @@ -0,0 +1,170 @@ +/* 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 , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + + +/* Ogg codec handler for theora logical streams */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +typedef struct source_tag source_t; + +#include "refbuf.h" +#include "format_ogg.h" +#include "format_theora.h" +#include "client.h" +#include "stats.h" + +#define CATMODULE "format-theora" +#include "logging.h" + + +typedef struct _theora_codec_tag +{ + theora_info ti; + theora_comment tc; + int granule_shift; + ogg_int64_t last_iframe; + ogg_int64_t prev_granulepos; +} theora_codec_t; + + +static void theora_codec_free (ogg_state_t *ogg_info, ogg_codec_t *codec) +{ + theora_codec_t *theora = codec->specific; + + DEBUG0 ("freeing theora codec"); + stats_event (ogg_info->mount, "video_bitrate", NULL); + stats_event (ogg_info->mount, "framerate", NULL); + stats_event (ogg_info->mount, "frame_size", NULL); + theora_info_clear (&theora->ti); + theora_comment_clear (&theora->tc); + ogg_stream_clear (&codec->os); + free (theora); + free (codec); +} + + +/* theora pages are not rebuilt, so here we just for headers and then + * pass them straight through to the the queue + */ +static refbuf_t *process_theora_page (ogg_state_t *ogg_info, ogg_codec_t *codec, ogg_page *page) +{ + theora_codec_t *theora = codec->specific; + ogg_packet packet; + int header_page = 0; + int has_keyframe = 0; + refbuf_t *refbuf = NULL; + ogg_int64_t granulepos; + + if (ogg_stream_pagein (&codec->os, page) < 0) + { + ogg_info->error = 1; + return NULL; + } + granulepos = ogg_page_granulepos (page); + + while (ogg_stream_packetout (&codec->os, &packet) > 0) + { + if (theora_packet_isheader (&packet)) + { + if (theora_decode_header (&theora->ti, &theora->tc, &packet) < 0) + { + ogg_info->error = 1; + WARN0 ("problem with theora header"); + return NULL; + } + header_page = 1; + codec->headers++; + continue; + } + if (codec->headers < 3) + { + ogg_info->error = 1; + ERROR0 ("Not enough header packets"); + return NULL; + } + if (theora_packet_iskeyframe (&packet)) + has_keyframe = 1; + } + if (header_page) + { + format_ogg_attach_header (ogg_info, page); + return NULL; + } + + refbuf = make_refbuf_with_page (page); + /* DEBUG3 ("refbuf %p has pageno %ld, %llu", refbuf, ogg_page_pageno (page), (uint64_t)granulepos); */ + + if (has_keyframe && codec->possible_start) + { + codec->possible_start->sync_point = 1; + refbuf_release (codec->possible_start); + codec->possible_start = NULL; + } + if (granulepos != theora->prev_granulepos || granulepos == 0) + { + if (codec->possible_start) + refbuf_release (codec->possible_start); + refbuf_addref (refbuf); + codec->possible_start = refbuf; + } + theora->prev_granulepos = granulepos; + + return refbuf; +} + + +/* Check if specified BOS page is the start of a theora stream and + * if so, create a codec structure for handling it + */ +ogg_codec_t *initial_theora_page (format_plugin_t *plugin, ogg_page *page) +{ + ogg_state_t *ogg_info = plugin->_state; + ogg_codec_t *codec = calloc (1, sizeof (ogg_codec_t)); + ogg_packet packet; + + theora_codec_t *theora_codec = calloc (1, sizeof (theora_codec_t)); + + ogg_stream_init (&codec->os, ogg_page_serialno (page)); + ogg_stream_pagein (&codec->os, page); + + theora_info_init (&theora_codec->ti); + theora_comment_init (&theora_codec->tc); + + ogg_stream_packetout (&codec->os, &packet); + + DEBUG0("checking for theora codec"); + if (theora_decode_header (&theora_codec->ti, &theora_codec->tc, &packet) < 0) + { + theora_info_clear (&theora_codec->ti); + theora_comment_clear (&theora_codec->tc); + ogg_stream_clear (&codec->os); + free (theora_codec); + free (codec); + return NULL; + } + INFO0 ("seen initial theora header"); + codec->specific = theora_codec; + codec->process_page = process_theora_page; + codec->codec_free = theora_codec_free; + codec->headers = 1; + format_ogg_attach_header (ogg_info, page); + ogg_info->codec_sync = codec; + return codec; +} + diff --git a/src/format_theora.h b/src/format_theora.h new file mode 100644 index 00000000..73fd4b4f --- /dev/null +++ b/src/format_theora.h @@ -0,0 +1,21 @@ +/* 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 , + * oddsock , + * Karl Heyes + * and others (see AUTHORS for details). + */ + + +#ifndef __FORMAT_THEORA_H +#define __FORMAT_THEORA_H + +#include "format_ogg.h" + +ogg_codec_t *initial_theora_page (format_plugin_t *plugin, ogg_page *page); + +#endif /* __FORMAT_THEORA_H */ diff --git a/src/format_vorbis.c b/src/format_vorbis.c index c394612a..51783747 100644 --- a/src/format_vorbis.c +++ b/src/format_vorbis.c @@ -10,439 +10,546 @@ * and others (see AUTHORS for details). */ -/* format_vorbis.c -** -** format plugin for vorbis -** -*/ + +/* Ogg codec handler for vorbis streams */ #ifdef HAVE_CONFIG_H #include #endif -#include #include -#include - #include #include +#include #include "refbuf.h" #include "source.h" #include "client.h" +#include "format_ogg.h" #include "stats.h" #include "format.h" #define CATMODULE "format-vorbis" #include "logging.h" -#define MAX_HEADER_PAGES 10 -typedef struct _vstate_tag +typedef struct vorbis_codec_tag { - ogg_sync_state oy; - ogg_stream_state os; vorbis_info vi; vorbis_comment vc; - ogg_page og; - unsigned long serialno; - int header; - refbuf_t *file_headers; - refbuf_t *header_pages; - refbuf_t *header_pages_tail; - int packets; -} vstate_t; + int rebuild_comment; + int stream_notify; -struct client_vorbis + ogg_stream_state new_os; + int page_samples_trigger; + ogg_int64_t prev_granulepos; + ogg_packet *prev_packet; + ogg_int64_t granulepos; + ogg_int64_t samples_in_page; + int prev_window; + int initial_audio_packet; + + ogg_page bos_page; + ogg_packet *header [3]; + ogg_int64_t prev_page_samples; + + int (*process_packet)(ogg_state_t *ogg_info, ogg_codec_t *codec); + refbuf_t *(*get_buffer_page)(ogg_state_t *ogg_info, ogg_codec_t *codec); + +} vorbis_codec_t; + +static int process_vorbis_headers (ogg_state_t *ogg_info, ogg_codec_t *codec); +static refbuf_t *process_vorbis_page (ogg_state_t *ogg_info, + ogg_codec_t *codec, ogg_page *page); +static refbuf_t *process_vorbis (ogg_state_t *ogg_info, ogg_codec_t *codec); +static void vorbis_set_tag (format_plugin_t *plugin, char *tag, char *value); + + +static void free_ogg_packet (ogg_packet *packet) { - refbuf_t *headers; - refbuf_t *header_page; - unsigned int pos; - int processing_headers; -}; - - -static void format_vorbis_free_plugin(format_plugin_t *self); -static refbuf_t *format_vorbis_get_buffer (source_t *source); -static int format_vorbis_create_client_data (source_t *source, client_t *client); -static void format_vorbis_send_headers(format_plugin_t *self, - source_t *source, client_t *client); -static int write_buf_to_client (format_plugin_t *self, client_t *client); -static void write_ogg_to_file (struct source_tag *source, refbuf_t *refbuf); - - -int format_vorbis_get_plugin(source_t *source) -{ - format_plugin_t *plugin; - vstate_t *state; - - plugin = (format_plugin_t *)malloc(sizeof(format_plugin_t)); - - plugin->type = FORMAT_TYPE_VORBIS; - plugin->write_buf_to_file = write_ogg_to_file; - plugin->get_buffer = format_vorbis_get_buffer; - plugin->write_buf_to_client = write_buf_to_client; - plugin->create_client_data = format_vorbis_create_client_data; - plugin->client_send_headers = format_vorbis_send_headers; - plugin->free_plugin = format_vorbis_free_plugin; - plugin->contenttype = "application/ogg"; - - state = (vstate_t *)calloc(1, sizeof(vstate_t)); - ogg_sync_init(&state->oy); - - plugin->_state = (void *)state; - source->format = plugin; - - return 0; + if (packet) + { + free (packet->packet); + free (packet); + } } -void format_vorbis_free_plugin(format_plugin_t *self) + +static void vorbis_codec_free (ogg_state_t *ogg_info, ogg_codec_t *codec) { - vstate_t *state = (vstate_t *)self->_state; - refbuf_t *header = state->header_pages; + vorbis_codec_t *vorbis = codec->specific; - /* free memory associated with this plugin instance */ - - /* free state memory */ - while (header) - { - refbuf_t *to_release = header; - header = header->next; - refbuf_release (to_release); - } - ogg_sync_clear(&state->oy); - ogg_stream_clear(&state->os); - vorbis_comment_clear(&state->vc); - vorbis_info_clear(&state->vi); - - free(state); - - /* free the plugin instance */ - free(self); + DEBUG0 ("freeing vorbis codec"); + stats_event (ogg_info->mount, "audio-bitrate", NULL); + stats_event (ogg_info->mount, "audio-channels", NULL); + stats_event (ogg_info->mount, "audio-samplerate", NULL); + vorbis_info_clear (&vorbis->vi); + vorbis_comment_clear (&vorbis->vc); + ogg_stream_clear (&codec->os); + ogg_stream_clear (&vorbis->new_os); + free_ogg_packet (vorbis->header[0]); + free_ogg_packet (vorbis->header[1]); + free_ogg_packet (vorbis->header[2]); + free_ogg_packet (vorbis->prev_packet); + free (vorbis->bos_page.header); + free (vorbis); + free (codec); } -static refbuf_t *format_vorbis_get_buffer (source_t *source) + +static ogg_packet *copy_ogg_packet (ogg_packet *packet) { - int result; - ogg_packet op; - char *title_tag; - char *artist_tag; - char *metadata = NULL; - int metadata_len = 0; - refbuf_t *refbuf, *header; - char *data; - format_plugin_t *self = source->format; - int bytes; - vstate_t *state = (vstate_t *)self->_state; - - data = ogg_sync_buffer (&state->oy, 4096); - - bytes = sock_read_bytes (source->con->sock, data, 4096); - if (bytes < 0) + ogg_packet *next; + do { - if (sock_recoverable (sock_error())) - return NULL; - WARN0 ("source connection has died"); - ogg_sync_wrote (&state->oy, 0); - source->running = 0; - return NULL; - } - if (bytes == 0) + next = malloc (sizeof (ogg_packet)); + if (next == NULL) + break; + memcpy (next, packet, sizeof (ogg_packet)); + next->packet = malloc (next->bytes); + if (next->packet == NULL) + break; + memcpy (next->packet, packet->packet, next->bytes); + return next; + } while (0); + + if (next) + free (next); + return NULL; +} + + +static void add_audio_packet (vorbis_codec_t *source_vorbis, ogg_packet *packet) +{ + if (source_vorbis->initial_audio_packet) { - INFO1 ("End of Stream %s", source->mount); - ogg_sync_wrote (&state->oy, 0); - source->running = 0; - return NULL; + packet->granulepos = 0; + source_vorbis->initial_audio_packet = 0; } - ogg_sync_wrote (&state->oy, bytes); - - refbuf = NULL; - if (ogg_sync_pageout(&state->oy, &state->og) == 1) { - refbuf = refbuf_new(state->og.header_len + state->og.body_len); - memcpy(refbuf->data, state->og.header, state->og.header_len); - memcpy(&refbuf->data[state->og.header_len], state->og.body, state->og.body_len); - - if (state->serialno != ogg_page_serialno(&state->og)) { - DEBUG0("new stream"); - /* this is a new logical bitstream */ - state->header = 0; - state->packets = 0; - - /* Clear old stuff. Rarely but occasionally needed. */ - header = state->header_pages; - while (header) - { - refbuf_t *to_release = header; - DEBUG0 ("clearing out header page"); - header = header->next; - refbuf_release (to_release); - } - ogg_stream_clear(&state->os); - vorbis_comment_clear(&state->vc); - vorbis_info_clear(&state->vi); - state->header_pages = NULL; - state->header_pages_tail = NULL; - - state->serialno = ogg_page_serialno(&state->og); - ogg_stream_init(&state->os, state->serialno); - vorbis_info_init(&state->vi); - vorbis_comment_init(&state->vc); - } - - if (state->header >= 0) { - /* FIXME: In some streams (non-vorbis ogg streams), this could get - * extras pages beyond the header. We need to collect the pages - * here anyway, but they may have to be discarded later. - */ - DEBUG1 ("header %d", state->header); - if (ogg_page_granulepos(&state->og) <= 0) { - state->header++; - } else { - /* we're done caching headers */ - state->header = -1; - - DEBUG0 ("doing stats"); - /* put known comments in the stats */ - title_tag = vorbis_comment_query(&state->vc, "TITLE", 0); - if (title_tag) stats_event(source->mount, "title", title_tag); - else stats_event(source->mount, "title", "unknown"); - artist_tag = vorbis_comment_query(&state->vc, "ARTIST", 0); - if (artist_tag) stats_event(source->mount, "artist", artist_tag); - else stats_event(source->mount, "artist", "unknown"); - - metadata = NULL; - if (artist_tag) { - if (title_tag) { - metadata_len = strlen(artist_tag) + strlen(title_tag) + - strlen(" - ") + 1; - metadata = (char *)calloc(1, metadata_len); - sprintf(metadata, "%s - %s", artist_tag, title_tag); - } - else { - metadata_len = strlen(artist_tag) + 1; - metadata = (char *)calloc(1, metadata_len); - sprintf(metadata, "%s", artist_tag); - } - } - else { - if (title_tag) { - metadata_len = strlen(title_tag) + 1; - metadata = (char *)calloc(1, metadata_len); - sprintf(metadata, "%s", title_tag); - } - } - if (metadata) { - logging_playlist(source->mount, metadata, source->listeners); - free(metadata); - metadata = NULL; - } - /* don't need these now */ - ogg_stream_clear(&state->os); - vorbis_comment_clear(&state->vc); - vorbis_info_clear(&state->vi); - - yp_touch (source->mount); - } - } - - /* cache header pages */ - if (state->header > 0 && state->packets < 3) { - /* build a list of headers pages for attaching */ - if (state->header_pages_tail) - state->header_pages_tail->next = refbuf; - state->header_pages_tail = refbuf; - - if (state->header_pages == NULL) - state->header_pages = refbuf; - - if (state->packets >= 0 && state->packets < 3) { - ogg_stream_pagein(&state->os, &state->og); - while (state->packets < 3) { - result = ogg_stream_packetout(&state->os, &op); - if (result == 0) break; /* need more data */ - if (result < 0) { - state->packets = -1; - break; - } - - state->packets++; - - if (vorbis_synthesis_headerin(&state->vi, &state->vc, &op) < 0) { - state->packets = -1; - break; - } - } - } - /* we do not place ogg headers on the main queue */ - return NULL; - } - /* increase ref counts on each header page going out */ - header = state->header_pages; - while (header) - { - refbuf_addref (header); - header = header->next; - } - refbuf->associated = state->header_pages; + else + { + source_vorbis->samples_in_page += + (packet->granulepos - source_vorbis->prev_granulepos); + source_vorbis->prev_granulepos = packet->granulepos; + source_vorbis->granulepos += source_vorbis->prev_window; } + ogg_stream_packetin (&source_vorbis->new_os, packet); +} + +static refbuf_t *get_buffer_audio (ogg_state_t *ogg_info, ogg_codec_t *codec) +{ + refbuf_t *refbuf = NULL; + ogg_page page; + vorbis_codec_t *source_vorbis = codec->specific; + int (*get_ogg_page)(ogg_stream_state*, ogg_page *) = ogg_stream_pageout; + + if (source_vorbis->samples_in_page > source_vorbis->page_samples_trigger) + get_ogg_page = ogg_stream_flush; + + if (get_ogg_page (&source_vorbis->new_os, &page) > 0) + { + /* squeeze a page copy into a buffer */ + source_vorbis->samples_in_page -= (ogg_page_granulepos (&page) - source_vorbis->prev_page_samples); + source_vorbis->prev_page_samples = ogg_page_granulepos (&page); + + refbuf = make_refbuf_with_page (&page); + } return refbuf; } -static void free_ogg_client_data (client_t *client) -{ - free (client->format_data); - client->format_data = NULL; -} -static int format_vorbis_create_client_data (source_t *source, client_t *client) +static refbuf_t *get_buffer_header (ogg_state_t *ogg_info, ogg_codec_t *codec) { - struct client_vorbis *client_data = calloc (1, sizeof (struct client_vorbis)); - int ret = -1; + int headers_flushed = 0; + ogg_page page; + vorbis_codec_t *source_vorbis = codec->specific; - if (client_data) + while (ogg_stream_flush (&source_vorbis->new_os, &page) > 0) { - client->format_data = client_data; - client->free_client_data = free_ogg_client_data; - ret = 0; + format_ogg_attach_header (ogg_info, &page); + headers_flushed = 1; } - return ret; + if (headers_flushed) + { + source_vorbis->get_buffer_page = get_buffer_audio; + } + return NULL; } -static void format_vorbis_send_headers(format_plugin_t *self, - source_t *source, client_t *client) + +static refbuf_t *get_buffer_finished (ogg_state_t *ogg_info, ogg_codec_t *codec) { - int bytes; - - client->respcode = 200; - bytes = sock_write(client->con->sock, - "HTTP/1.0 200 OK\r\n" - "Content-Type: %s\r\n", - source->format->contenttype); - - if(bytes > 0) client->con->sent_bytes += bytes; - - format_send_general_headers(self, source, client); -} - -static int send_ogg_headers (client_t *client, refbuf_t *headers) -{ - struct client_vorbis *client_data = client->format_data; + vorbis_codec_t *source_vorbis = codec->specific; + ogg_page page; refbuf_t *refbuf; - int written = 0; - if (client_data->processing_headers == 0) + if (ogg_stream_flush (&source_vorbis->new_os, &page) > 0) { - client_data->header_page = headers; - client_data->pos = 0; - client_data->processing_headers = 1; - } - refbuf = client_data->header_page; - while (refbuf) - { - char *data = refbuf->data + client_data->pos; - unsigned int len = refbuf->len - client_data->pos; - int ret; + source_vorbis->samples_in_page -= (ogg_page_granulepos (&page) - source_vorbis->prev_page_samples); + source_vorbis->prev_page_samples = ogg_page_granulepos (&page); - ret = client_send_bytes (client, data, len); - if (ret > 0) - { - written += ret; - client_data->pos += ret; - } - if (ret < (int)len) - return written; - if (client_data->pos == refbuf->len) - { - refbuf = refbuf->next; - client_data->header_page = refbuf; - client_data->pos = 0; - } + refbuf = make_refbuf_with_page (&page); + DEBUG0 ("flushing page"); + return refbuf; } - /* update client info on headers sent */ - client_data->processing_headers = 0; - client_data->headers = headers; - return written; + ogg_stream_clear (&source_vorbis->new_os); + ogg_stream_init (&source_vorbis->new_os, rand()); + + format_ogg_free_headers (ogg_info); + source_vorbis->get_buffer_page = NULL; + source_vorbis->process_packet = process_vorbis_headers; + if (source_vorbis->initial_audio_packet == 0) + source_vorbis->prev_window = 0; + + return NULL; } -static int write_buf_to_client (format_plugin_t *self, client_t *client) -{ - refbuf_t *refbuf = client->refbuf; - char *buf; - unsigned int len; - struct client_vorbis *client_data = client->format_data; - int ret, written = 0; - if (refbuf->next == NULL && client->pos == refbuf->len) +/* push last packet into stream marked with eos */ +static void initiate_flush (vorbis_codec_t *source_vorbis) +{ + DEBUG0 ("adding EOS packet"); + if (source_vorbis->prev_packet) + { + /* insert prev_packet with eos */ + source_vorbis->prev_packet->e_o_s = 1; + add_audio_packet (source_vorbis, source_vorbis->prev_packet); + source_vorbis->prev_packet->e_o_s = 0; + } + source_vorbis->get_buffer_page = get_buffer_finished; + source_vorbis->initial_audio_packet = 1; +} + + +/* process the vorbis audio packets. Here we just take each packet out + * and add them into the new stream, flushing after so many samples. We + * also check if an new headers are requested after each processed page + */ +static int process_vorbis_audio (ogg_state_t *ogg_info, ogg_codec_t *codec) +{ + vorbis_codec_t *source_vorbis = codec->specific; + + while (1) + { + int window; + ogg_packet packet; + + /* now, lets extract what packets we can */ + if (ogg_stream_packetout (&codec->os, &packet) <= 0) + break; + + /* calculate granulepos for the packet */ + window = vorbis_packet_blocksize (&source_vorbis->vi, &packet) / 4; + + source_vorbis->granulepos += window; + if (source_vorbis->prev_packet) + { + ogg_packet *prev_packet = source_vorbis->prev_packet; + + add_audio_packet (source_vorbis, prev_packet); + free_ogg_packet (prev_packet); + packet . granulepos = source_vorbis->granulepos; + } + else + { + packet . granulepos = 0; + } + + /* store the current packet details */ + source_vorbis->prev_window = window; + source_vorbis->prev_packet = copy_ogg_packet (&packet); + if (packet.e_o_s) + { + initiate_flush (source_vorbis); + return 1; + } + + /* allow for pages to be flushed if there's over a certain number of samples */ + if (source_vorbis->samples_in_page > source_vorbis->page_samples_trigger) + return 1; + } + if (source_vorbis->stream_notify) + { + initiate_flush (source_vorbis); + source_vorbis->stream_notify = 0; + } + return -1; +} + + +/* This handles the headers at the backend, here we insert the header packets + * we want for the queue. + */ +static int process_vorbis_headers (ogg_state_t *ogg_info, ogg_codec_t *codec) +{ + vorbis_codec_t *source_vorbis = codec->specific; + + if (source_vorbis->header [0] == NULL) return 0; - if (refbuf->next && client->pos == refbuf->len) + DEBUG0 ("Adding the 3 header packets"); + ogg_stream_packetin (&source_vorbis->new_os, source_vorbis->header [0]); + /* NOTE: we could build a separate comment packet each time */ + if (source_vorbis->rebuild_comment) { - client_set_queue (client, refbuf->next); - refbuf = client->refbuf; - } - do - { - if (client_data->headers != refbuf->associated) - { - /* different headers seen so send the new ones */ - ret = send_ogg_headers (client, refbuf->associated); - if (client_data->processing_headers) - break; - written += ret; - } - buf = refbuf->data + client->pos; - len = refbuf->len - client->pos; - ret = client_send_bytes (client, buf, len); + vorbis_comment vc; + ogg_packet header; - if (ret > 0) - client->pos += ret; + vorbis_comment_init (&vc); + if (ogg_info->artist) + vorbis_comment_add_tag (&vc, "artist", ogg_info->artist); + if (ogg_info->title) + vorbis_comment_add_tag (&vc, "title", ogg_info->title); + vorbis_comment_add (&vc, "server=" ICECAST_VERSION_STRING); + vorbis_commentheader_out (&vc, &header); + + ogg_stream_packetin (&source_vorbis->new_os, &header); + vorbis_comment_clear (&vc); + ogg_packet_clear (&header); + } + else + ogg_stream_packetin (&source_vorbis->new_os, source_vorbis->header [1]); + ogg_stream_packetin (&source_vorbis->new_os, source_vorbis->header [2]); + source_vorbis->rebuild_comment = 0; + + ogg_info->log_metadata = 1; + source_vorbis->get_buffer_page = get_buffer_header; + source_vorbis->process_packet = process_vorbis_audio; + source_vorbis->granulepos = source_vorbis->prev_window; + source_vorbis->initial_audio_packet = 1; + return 1; +} + + +/* check if the provided BOS page is the start of a vorbis stream. If so + * then setup a structure so it can be used + */ +ogg_codec_t *initial_vorbis_page (format_plugin_t *plugin, ogg_page *page) +{ + // ogg_state_t *ogg_info = plugin->_state; + ogg_codec_t *codec = calloc (1, sizeof (ogg_codec_t)); + ogg_packet packet; + + vorbis_codec_t *vorbis = calloc (1, sizeof (vorbis_codec_t)); + + ogg_stream_init (&codec->os, ogg_page_serialno (page)); + ogg_stream_pagein (&codec->os, page); + + vorbis_info_init (&vorbis->vi); + vorbis_comment_init (&vorbis->vc); + + ogg_stream_packetout (&codec->os, &packet); + + DEBUG0("checking for vorbis codec"); + if (vorbis_synthesis_headerin (&vorbis->vi, &vorbis->vc, &packet) < 0) + { + ogg_stream_clear (&codec->os); + vorbis_info_clear (&vorbis->vi); + vorbis_comment_clear (&vorbis->vc); + free (vorbis); + free (codec); + return NULL; + } + INFO0 ("seen initial vorbis header"); + codec->specific = vorbis; + codec->codec_free = vorbis_codec_free; + codec->headers = 1; + + free_ogg_packet (vorbis->header[0]); + free_ogg_packet (vorbis->header[1]); + free_ogg_packet (vorbis->header[2]); + memset (vorbis->header, 0, sizeof (vorbis->header)); + vorbis->header [0] = copy_ogg_packet (&packet); + ogg_stream_init (&vorbis->new_os, rand()); + + codec->process_page = process_vorbis_page; + codec->process = process_vorbis; + plugin->set_tag = vorbis_set_tag; + + vorbis->bos_page.header = malloc (page->header_len + page->body_len); - if (ret < (int)len) - break; - written += ret; - /* we have now written the page(s) */ - ret = 0; - } while (0); + memcpy (vorbis->bos_page.header, page->header, page->header_len); + vorbis->bos_page.header_len = page->header_len; - if (ret > 0) - written += ret; - return written; -} + vorbis->bos_page.body = vorbis->bos_page.header + page->header_len; + memcpy (vorbis->bos_page.body, page->body, page->body_len); + vorbis->bos_page.body_len = page->body_len; -static int write_ogg_data (struct source_tag *source, refbuf_t *refbuf) -{ - int ret = 1; - - if (fwrite (refbuf->data, 1, refbuf->len, source->dumpfile) != refbuf->len) - { - WARN0 ("Write to dump file failed, disabling"); - fclose (source->dumpfile); - source->dumpfile = NULL; - ret = 0; - } - return ret; + return codec; } -static void write_ogg_to_file (struct source_tag *source, refbuf_t *refbuf) +/* called from the admin interface, here we update the artist/title info + * and schedule a new set of header pages + */ +static void vorbis_set_tag (format_plugin_t *plugin, char *tag, char *value) { - vstate_t *state = (vstate_t *)source->format->_state; + ogg_state_t *ogg_info = plugin->_state; + ogg_codec_t *codec = ogg_info->codecs; + vorbis_codec_t *source_vorbis; + int change = 0; + /* avoid updating if multiple codecs in use */ + if (codec && codec->next == NULL) + source_vorbis = codec->specific; + else + return; - if (state->file_headers != refbuf->associated) + if (strcmp (tag, "artist") == 0) { - refbuf_t *header = refbuf->associated; - while (header) + char *p = strdup (value); + if (p) { - if (write_ogg_data (source, header) == 0) - return; - header = header->next; + free (ogg_info->artist); + ogg_info->artist = p; + change = 1; } - state->file_headers = refbuf->associated; } - write_ogg_data (source, refbuf); + if (strcmp (tag, "title") == 0) + { + char *p = strdup (value); + if (p) + { + free (ogg_info->title); + ogg_info->title = p; + change = 1; + } + } + if (strcmp (tag, "song") == 0) + { + char *p = strdup (value); + if (p) + { + free (ogg_info->artist); + free (ogg_info->title); + ogg_info->artist = NULL; + ogg_info->title = p; + change = 1; + } + } + if (change) + { + source_vorbis->stream_notify = 1; + source_vorbis->rebuild_comment = 1; + } +} + + +/* main backend routine when rebuilding streams. Here we loop until we either + * have a refbuf to add onto the queue, or we want more data to process. + */ +static refbuf_t *process_vorbis (ogg_state_t *ogg_info, ogg_codec_t *codec) +{ + vorbis_codec_t *source_vorbis = codec->specific; + refbuf_t *refbuf; + + while (1) + { + if (source_vorbis->get_buffer_page) + { + refbuf = source_vorbis->get_buffer_page (ogg_info, codec); + if (refbuf) + return refbuf; + } + + if (source_vorbis->process_packet && + source_vorbis->process_packet (ogg_info, codec) > 0) + continue; + return NULL; + } +} + + +/* no processing of pages, just wrap them up in a refbuf and pass + * back for adding to the queue + */ +static refbuf_t *process_vorbis_passthru_page (ogg_state_t *ogg_info, + ogg_codec_t *codec, ogg_page *page) +{ + return make_refbuf_with_page (page); +} + + +/* handle incoming page. as the stream is being rebuilt, we need to + * add all pages from the stream before processing packets + */ +static refbuf_t *process_vorbis_page (ogg_state_t *ogg_info, + ogg_codec_t *codec, ogg_page *page) +{ + ogg_packet header; + vorbis_codec_t *source_vorbis = codec->specific; + char *comment; + + if (ogg_stream_pagein (&codec->os, page) < 0) + { + ogg_info->error = 1; + return NULL; + } + if (codec->headers == 3) + return NULL; + + while (codec->headers < 3) + { + /* now, lets extract the packets */ + DEBUG1 ("processing incoming header packet (%d)", codec->headers); + + if (ogg_stream_packetout (&codec->os, &header) <= 0) + { + if (ogg_info->codecs->next) + format_ogg_attach_header (ogg_info, page); + return NULL; + } + + /* change comments here if need be */ + if (vorbis_synthesis_headerin (&source_vorbis->vi, &source_vorbis->vc, &header) < 0) + { + ogg_info->error = 1; + WARN0 ("Problem parsing ogg vorbis header"); + return NULL; + } + header.granulepos = 0; + source_vorbis->header [codec->headers] = copy_ogg_packet (&header); + codec->headers++; + } + DEBUG0 ("we have the header packets now"); + + /* if vorbis is the only codec then allow rebuilding of the streams */ + if (ogg_info->codecs->next == NULL) + { + /* set queued vorbis pages to contain about 1/2 of a second worth of samples */ + source_vorbis->page_samples_trigger = source_vorbis->vi.rate / 2; + source_vorbis->process_packet = process_vorbis_headers; + } + else + { + format_ogg_attach_header (ogg_info, &source_vorbis->bos_page); + format_ogg_attach_header (ogg_info, page); + codec->process_page = process_vorbis_passthru_page; + } + + free (ogg_info->title); + comment = vorbis_comment_query (&source_vorbis->vc, "TITLE", 0); + if (comment) + ogg_info->title = strdup (comment); + else + ogg_info->title = NULL; + + free (ogg_info->artist); + comment = vorbis_comment_query (&source_vorbis->vc, "ARTIST", 0); + if (comment) + ogg_info->artist = strdup (comment); + else + ogg_info->artist = NULL; + ogg_info->log_metadata = 1; + + stats_event_args (ogg_info->mount, "audio-samplerate", "%ld", (long)source_vorbis->vi.rate); + stats_event_args (ogg_info->mount, "audio-channels", "%ld", (long)source_vorbis->vi.channels); + stats_event_args (ogg_info->mount, "audio-bitrate", "%ld", (long)source_vorbis->vi.bitrate_nominal); + stats_event_args (ogg_info->mount, "ice-bitrate", "%ld", (long)source_vorbis->vi.bitrate_nominal/1000); + + return NULL; } diff --git a/src/format_vorbis.h b/src/format_vorbis.h index 22e87665..887cb22e 100644 --- a/src/format_vorbis.h +++ b/src/format_vorbis.h @@ -10,14 +10,12 @@ * and others (see AUTHORS for details). */ -/* format_vorbis.h -** -** vorbis format plugin header -** -*/ -#ifndef __FORMAT_VORBIS_H__ -#define __FORMAT_VORBIS_H__ -int format_vorbis_get_plugin(source_t *source); +#ifndef __FORMAT_VORBIS_H +#define __FORMAT_VORBIS_H -#endif /* __FORMAT_VORBIS_H__ */ +#include "format_ogg.h" + +ogg_codec_t *initial_vorbis_page (format_plugin_t *plugin, ogg_page *page); + +#endif /* __FORMAT_VORBIS_H */ diff --git a/src/refbuf.c b/src/refbuf.c index dbd6dd51..ac9dc14f 100644 --- a/src/refbuf.c +++ b/src/refbuf.c @@ -51,6 +51,7 @@ refbuf_t *refbuf_new(unsigned long size) } } refbuf->len = size; + refbuf->sync_point = 0; refbuf->_count = 1; refbuf->next = NULL; refbuf->associated = NULL; diff --git a/src/refbuf.h b/src/refbuf.h index df182842..16bc1256 100644 --- a/src/refbuf.h +++ b/src/refbuf.h @@ -22,6 +22,7 @@ typedef struct _refbuf_tag { char *data; long len; + int sync_point; struct _refbuf_tag *associated; struct _refbuf_tag *next; diff --git a/src/source.c b/src/source.c index b3dac39f..3bc18f47 100644 --- a/src/source.c +++ b/src/source.c @@ -381,6 +381,26 @@ void source_move_clients (source_t *source, source_t *dest) thread_mutex_unlock (&move_clients_mutex); } + +/* clients need to be start from somewhere in the queue + * * so we will look for a refbuf which has been previous + * * marked as a sync point */ +static void find_client_start (source_t *source, client_t *client) +{ + refbuf_t *refbuf = source->burst_point; + + while (refbuf) + { + if (refbuf->sync_point) + { + client_set_queue (client, refbuf); + break; + } + refbuf = refbuf->next; + } +} + + /* get some data from the source. The stream data is placed in a refbuf * and sent back, however NULL is also valid as in the case of a short * timeout and there's no data pending. @@ -442,10 +462,9 @@ static void send_to_listener (source_t *source, client_t *client, int deletion_e /* new users need somewhere to start from */ if (client->refbuf == NULL) { - /* make clients start at the per source burst point on the queue */ - client_set_queue (client, source->burst_point); + find_client_start (source, client); if (client->refbuf == NULL) - return; + return; } while (1)