[xiph-commits] r8341 - in icecast/trunk/icecast: . src

karl at motherfish-iii.xiph.org karl at motherfish-iii.xiph.org
Tue Dec 7 13:06:26 PST 2004


Author: karl
Date: 2004-12-07 13:06:26 -0800 (Tue, 07 Dec 2004)
New Revision: 8341

Added:
   icecast/trunk/icecast/src/format_ogg.c
   icecast/trunk/icecast/src/format_ogg.h
   icecast/trunk/icecast/src/format_theora.c
   icecast/trunk/icecast/src/format_theora.h
Modified:
   icecast/trunk/icecast/configure.in
   icecast/trunk/icecast/src/Makefile.am
   icecast/trunk/icecast/src/admin.c
   icecast/trunk/icecast/src/format.c
   icecast/trunk/icecast/src/format.h
   icecast/trunk/icecast/src/format_mp3.c
   icecast/trunk/icecast/src/format_vorbis.c
   icecast/trunk/icecast/src/format_vorbis.h
   icecast/trunk/icecast/src/refbuf.c
   icecast/trunk/icecast/src/refbuf.h
   icecast/trunk/icecast/src/source.c
Log:
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 


Modified: icecast/trunk/icecast/configure.in
===================================================================
--- icecast/trunk/icecast/configure.in	2004-12-07 20:25:02 UTC (rev 8340)
+++ icecast/trunk/icecast/configure.in	2004-12-07 21:06:26 UTC (rev 8341)
@@ -73,10 +73,24 @@
 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])
 XIPH_VAR_APPEND([XIPH_CPPFLAGS],[$PTHREAD_CPPFLAGS])
@@ -109,6 +123,7 @@
 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)

Modified: icecast/trunk/icecast/src/Makefile.am
===================================================================
--- icecast/trunk/icecast/src/Makefile.am	2004-12-07 20:25:02 UTC (rev 8340)
+++ icecast/trunk/icecast/src/Makefile.am	2004-12-07 21:06:26 UTC (rev 8341)
@@ -6,13 +6,16 @@
 
 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 @@
 
 AM_CFLAGS = @XIPH_CFLAGS@
 AM_CPPFLAGS = @XIPH_CPPFLAGS@
+AM_LDFLAGS = @XIPH_LDFLAGS@
 
 
 debug:

Modified: icecast/trunk/icecast/src/admin.c
===================================================================
--- icecast/trunk/icecast/src/admin.c	2004-12-07 20:25:02 UTC (rev 8340)
+++ icecast/trunk/icecast/src/admin.c	2004-12-07 21:06:26 UTC (rev 8341)
@@ -825,41 +825,48 @@
 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);
+    COMMAND_OPTIONAL(client, "song", song);
+    COMMAND_OPTIONAL(client, "title", title);
+    COMMAND_OPTIONAL(client, "artist", artist);
 
-    if (source->format->type == FORMAT_TYPE_VORBIS) {
-        client_send_400 (client, "Cannot update metadata on vorbis streams");
-        return;
-    }
-
     if (strcmp (action, "updinfo") != 0)
     {
         client_send_400 (client, "No such action");
         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 @@
     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 @@
 
     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");
 }

Modified: icecast/trunk/icecast/src/format.c
===================================================================
--- icecast/trunk/icecast/src/format.c	2004-12-07 20:25:02 UTC (rev 8340)
+++ icecast/trunk/icecast/src/format.c	2004-12-07 21:06:26 UTC (rev 8341)
@@ -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 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);

Modified: icecast/trunk/icecast/src/format.h
===================================================================
--- icecast/trunk/icecast/src/format.h	2004-12-07 20:25:02 UTC (rev 8340)
+++ icecast/trunk/icecast/src/format.h	2004-12-07 21:06:26 UTC (rev 8341)
@@ -26,9 +26,9 @@
 
 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 @@
     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 */

Modified: icecast/trunk/icecast/src/format_mp3.c
===================================================================
--- icecast/trunk/icecast/src/format_mp3.c	2004-12-07 20:25:02 UTC (rev 8340)
+++ icecast/trunk/icecast/src/format_mp3.c	2004-12-07 21:06:26 UTC (rev 8341)
@@ -80,7 +80,7 @@
     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 @@
     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 @@
             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 @@
         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 @@
     }
     refbuf->associated = source_mp3->metadata;
     refbuf_addref (source_mp3->metadata);
+    refbuf->sync_point = 1;
 
     return refbuf;
 }

Added: icecast/trunk/icecast/src/format_ogg.c
===================================================================
--- icecast/trunk/icecast/src/format_ogg.c	2004-12-07 20:25:02 UTC (rev 8340)
+++ icecast/trunk/icecast/src/format_ogg.c	2004-12-07 21:06:26 UTC (rev 8341)
@@ -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 <jack at xiph.org, 
+ *                      Michael Smith <msmith at xiph.org>,
+ *                      oddsock <oddsock at xiph.org>,
+ *                      Karl Heyes <karl at 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");
+            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);
+}
+

Added: icecast/trunk/icecast/src/format_ogg.h
===================================================================
--- icecast/trunk/icecast/src/format_ogg.h	2004-12-07 20:25:02 UTC (rev 8340)
+++ icecast/trunk/icecast/src/format_ogg.h	2004-12-07 21:06:26 UTC (rev 8341)
@@ -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 <jack at xiph.org, 
+ *                      Michael Smith <msmith at xiph.org>,
+ *                      oddsock <oddsock at xiph.org>,
+ *                      Karl Heyes <karl at xiph.org>
+ *                      and others (see AUTHORS for details).
+ */
+
+/* format_ogg.h
+**
+** vorbis format plugin header
+**
+*/
+#ifndef __FORMAT_OGG_H__
+#define __FORMAT_OGG_H__
+
+#include <ogg/ogg.h>
+#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__ */

Added: icecast/trunk/icecast/src/format_theora.c
===================================================================
--- icecast/trunk/icecast/src/format_theora.c	2004-12-07 20:25:02 UTC (rev 8340)
+++ icecast/trunk/icecast/src/format_theora.c	2004-12-07 21:06:26 UTC (rev 8341)
@@ -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 <jack at xiph.org, 
+ *                      Michael Smith <msmith at xiph.org>,
+ *                      oddsock <oddsock at xiph.org>,
+ *                      Karl Heyes <karl at xiph.org>
+ *                      and others (see AUTHORS for details).
+ */
+
+
+/* Ogg codec handler for theora logical streams */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <ogg/ogg.h>
+#include <theora/theora.h>
+
+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;
+}
+

Added: icecast/trunk/icecast/src/format_theora.h
===================================================================
--- icecast/trunk/icecast/src/format_theora.h	2004-12-07 20:25:02 UTC (rev 8340)
+++ icecast/trunk/icecast/src/format_theora.h	2004-12-07 21:06:26 UTC (rev 8341)
@@ -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 <jack at xiph.org, 
+ *                      Michael Smith <msmith at xiph.org>,
+ *                      oddsock <oddsock at xiph.org>,
+ *                      Karl Heyes <karl at xiph.org>
+ *                      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 */

Modified: icecast/trunk/icecast/src/format_vorbis.c
===================================================================
--- icecast/trunk/icecast/src/format_vorbis.c	2004-12-07 20:25:02 UTC (rev 8340)
+++ icecast/trunk/icecast/src/format_vorbis.c	2004-12-07 21:06:26 UTC (rev 8341)
@@ -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 <config.h>
 #endif
 
-#include <stdio.h>
 #include <stdlib.h>
-#include <string.h>
-
 #include <ogg/ogg.h>
 #include <vorbis/codec.h>
+#include <memory.h>
 
 #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
-{
-    refbuf_t *headers;
-    refbuf_t *header_page;
-    unsigned int pos;
-    int processing_headers;
-};
+    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;
 
-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 (*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;
 
-int format_vorbis_get_plugin(source_t *source)
-{
-    format_plugin_t *plugin;
-    vstate_t *state;
+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);
 
-    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";
+static void free_ogg_packet (ogg_packet *packet)
+{
+    if (packet)
+    {
+        free (packet->packet);
+        free (packet);
+    }
+}
 
-    state = (vstate_t *)calloc(1, sizeof(vstate_t));
-    ogg_sync_init(&state->oy);
 
-    plugin->_state = (void *)state;
-    source->format = plugin;
+static void vorbis_codec_free (ogg_state_t *ogg_info, ogg_codec_t *codec)
+{
+    vorbis_codec_t *vorbis = codec->specific;
 
-    return 0;
+    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);
 }
 
-void format_vorbis_free_plugin(format_plugin_t *self)
+
+static ogg_packet *copy_ogg_packet (ogg_packet *packet)
 {
-    vstate_t *state = (vstate_t *)self->_state;
-    refbuf_t *header = state->header_pages;
+    ogg_packet *next;
+    do
+    {
+        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);
 
-    /* free memory associated with this plugin instance */
+    if (next)
+        free (next);
+    return NULL;
+}
 
-    /* free state memory */
-    while (header)
+
+static void add_audio_packet (vorbis_codec_t *source_vorbis, ogg_packet *packet)
+{
+    if (source_vorbis->initial_audio_packet)
     {
-        refbuf_t *to_release = header;
-        header = header->next;
-        refbuf_release (to_release);
+        packet->granulepos = 0;
+        source_vorbis->initial_audio_packet = 0;
     }
-    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);
+    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 *format_vorbis_get_buffer (source_t *source)
+
+static refbuf_t *get_buffer_audio (ogg_state_t *ogg_info, ogg_codec_t *codec)
 {
-    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;
+    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;
 
-    data = ogg_sync_buffer (&state->oy, 4096);
+    if (source_vorbis->samples_in_page > source_vorbis->page_samples_trigger)
+        get_ogg_page = ogg_stream_flush;
 
-    bytes = sock_read_bytes (source->con->sock, data, 4096);
-    if (bytes < 0)
+    if (get_ogg_page (&source_vorbis->new_os, &page) > 0)
     {
-        if (sock_recoverable (sock_error()))
-            return NULL;
-        WARN0 ("source connection has died");
-        ogg_sync_wrote (&state->oy, 0);
-        source->running = 0;
-        return NULL;
+        /* 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);
     }
-    if (bytes == 0)
+    return refbuf;
+}
+
+
+static refbuf_t *get_buffer_header (ogg_state_t *ogg_info, ogg_codec_t *codec)
+{
+    int headers_flushed = 0;
+    ogg_page page;
+    vorbis_codec_t *source_vorbis = codec->specific;
+
+    while (ogg_stream_flush (&source_vorbis->new_os, &page) > 0)
     {
-        INFO1 ("End of Stream %s", source->mount);
-        ogg_sync_wrote (&state->oy, 0);
-        source->running = 0;
-        return NULL;
+        format_ogg_attach_header (ogg_info, &page);
+        headers_flushed = 1;
     }
-    ogg_sync_wrote (&state->oy, bytes);
+    if (headers_flushed)
+    {
+        source_vorbis->get_buffer_page = get_buffer_audio;
+    }
+    return NULL;
+}
 
-    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;
+static refbuf_t *get_buffer_finished (ogg_state_t *ogg_info, ogg_codec_t *codec)
+{
+    vorbis_codec_t *source_vorbis = codec->specific;
+    ogg_page page;
+    refbuf_t *refbuf;
 
-            /* 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;
+    if (ogg_stream_flush (&source_vorbis->new_os, &page) > 0)
+    {
+        source_vorbis->samples_in_page -= (ogg_page_granulepos (&page) - source_vorbis->prev_page_samples);
+        source_vorbis->prev_page_samples = ogg_page_granulepos (&page);
 
-            state->serialno = ogg_page_serialno(&state->og);
-            ogg_stream_init(&state->os, state->serialno);
-            vorbis_info_init(&state->vi);
-            vorbis_comment_init(&state->vc);
-        }
+        refbuf = make_refbuf_with_page (&page);
+        DEBUG0 ("flushing page");
+        return refbuf;
+    }
+    ogg_stream_clear (&source_vorbis->new_os);
+    ogg_stream_init (&source_vorbis->new_os, rand());
 
-        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;
+    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;
 
-                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");
+    return NULL;
+}
 
-                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);
-            }
-        }
+/* 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;
+}
 
-        /* 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;
+/* 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;
 
-            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;
-                    }
+    while (1)
+    {
+        int window;
+        ogg_packet packet;
 
-                    state->packets++;
+        /* now, lets extract what packets we can */
+        if (ogg_stream_packetout (&codec->os, &packet) <= 0)
+            break;
 
-                    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;
+        /* 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;
         }
-        /* increase ref counts on each header page going out */
-        header = state->header_pages;
-        while (header)
+        else
         {
-            refbuf_addref (header);
-            header = header->next;
+            packet . granulepos = 0;
         }
-        refbuf->associated = state->header_pages;
+
+        /* 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;
     }
-
-    return refbuf;
+    if (source_vorbis->stream_notify)
+    {
+        initiate_flush (source_vorbis);
+        source_vorbis->stream_notify = 0;
+    }
+    return -1;
 }
 
-static void free_ogg_client_data (client_t *client)
+
+/* 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)
 {
-    free (client->format_data);
-    client->format_data = NULL;
+    vorbis_codec_t *source_vorbis = codec->specific;
+
+    if (source_vorbis->header [0] == NULL)
+        return 0;
+
+    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)
+    {
+        vorbis_comment vc;
+        ogg_packet header;
+
+        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;
 }
 
-static int format_vorbis_create_client_data (source_t *source, client_t *client)
+
+/* 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)
 {
-    struct client_vorbis *client_data = calloc (1, sizeof (struct client_vorbis));
-    int ret = -1;
+    // ogg_state_t *ogg_info = plugin->_state;
+    ogg_codec_t *codec = calloc (1, sizeof (ogg_codec_t));
+    ogg_packet packet;
 
-    if (client_data)
+    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)
     {
-        client->format_data = client_data;
-        client->free_client_data = free_ogg_client_data;
-        ret = 0;
+        ogg_stream_clear (&codec->os);
+        vorbis_info_clear (&vorbis->vi);
+        vorbis_comment_clear (&vorbis->vc);
+        free (vorbis);
+        free (codec);
+        return NULL;
     }
-    return ret;
-}
+    INFO0 ("seen initial vorbis header");
+    codec->specific = vorbis;
+    codec->codec_free = vorbis_codec_free;
+    codec->headers = 1;
 
-static void format_vorbis_send_headers(format_plugin_t *self,
-        source_t *source, client_t *client)
-{
-    int bytes;
+    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);
     
-    client->respcode = 200;
-    bytes = sock_write(client->con->sock, 
-            "HTTP/1.0 200 OK\r\n" 
-            "Content-Type: %s\r\n", 
-            source->format->contenttype);
+    memcpy (vorbis->bos_page.header, page->header, page->header_len);
+    vorbis->bos_page.header_len = page->header_len;
 
-    if(bytes > 0) client->con->sent_bytes += bytes;
+    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;
 
-    format_send_general_headers(self, source, client);
+    return codec;
 }
 
-static int send_ogg_headers (client_t *client, refbuf_t *headers)
-{
-    struct client_vorbis *client_data = client->format_data;
-    refbuf_t *refbuf;
-    int written = 0;
 
-    if (client_data->processing_headers == 0)
+/* 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)
+{   
+    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 (strcmp (tag, "artist") == 0)
     {
-        client_data->header_page = headers;
-        client_data->pos = 0;
-        client_data->processing_headers = 1;
+        char *p = strdup (value);
+        if (p)
+        {
+            free (ogg_info->artist);
+            ogg_info->artist = p;
+            change = 1;
+        }
     }
-    refbuf = client_data->header_page;
-    while (refbuf)
+    if (strcmp (tag, "title") == 0)
     {
-        char *data = refbuf->data + client_data->pos;
-        unsigned int len = refbuf->len - client_data->pos;
-        int ret;
-
-        ret = client_send_bytes (client, data, len);
-        if (ret > 0)
+        char *p = strdup (value);
+        if (p)
         {
-           written += ret;
-           client_data->pos += ret;
+            free (ogg_info->title);
+            ogg_info->title = p;
+            change = 1;
         }
-        if (ret < (int)len)
-            return written;
-        if (client_data->pos == refbuf->len)
+    }
+    if (strcmp (tag, "song") == 0)
+    {
+        char *p = strdup (value);
+        if (p)
         {
-            refbuf = refbuf->next;
-            client_data->header_page = refbuf;
-            client_data->pos = 0;
+            free (ogg_info->artist);
+            free (ogg_info->title);
+            ogg_info->artist = NULL;
+            ogg_info->title = p;
+            change = 1;
         }
     }
-    /* update client info on headers sent */
-    client_data->processing_headers = 0;
-    client_data->headers = headers;
-    return written;
+    if (change)
+    {
+        source_vorbis->stream_notify = 1;
+        source_vorbis->rebuild_comment = 1;
+    }
 }
 
-static int write_buf_to_client (format_plugin_t *self, client_t *client)
+
+/* 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)
 {
-    refbuf_t *refbuf = client->refbuf;
-    char *buf;
-    unsigned int len;
-    struct client_vorbis *client_data = client->format_data;
-    int ret, written = 0;
+    vorbis_codec_t *source_vorbis = codec->specific;
+    refbuf_t *refbuf;
 
-    if (refbuf->next == NULL && client->pos == refbuf->len)
-        return 0;
-
-    if (refbuf->next && client->pos == refbuf->len)
+    while (1)
     {
-        client_set_queue (client, refbuf->next);
-        refbuf = client->refbuf;
-    }
-    do
-    {
-        if (client_data->headers != refbuf->associated)
+        if (source_vorbis->get_buffer_page)
         {
-            /* different headers seen so send the new ones */
-            ret = send_ogg_headers (client, refbuf->associated);
-            if (client_data->processing_headers)
-                break;
-            written += ret;
+            refbuf = source_vorbis->get_buffer_page (ogg_info, codec);
+            if (refbuf)
+                return refbuf;
         }
-        buf = refbuf->data + client->pos;
-        len = refbuf->len - client->pos;
-        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;
+        if (source_vorbis->process_packet &&
+                source_vorbis->process_packet (ogg_info, codec) > 0)
+            continue;
+        return NULL;
+    }
 }
 
-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;
+/* 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);
 }
 
 
-static void write_ogg_to_file (struct source_tag *source, refbuf_t *refbuf)
-{   
-    vstate_t *state = (vstate_t *)source->format->_state;
+/* 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;
 
-    if (state->file_headers != refbuf->associated)
+    while (codec->headers < 3)
     {
-        refbuf_t *header = refbuf->associated;
-        while (header) 
+        /* now, lets extract the packets */
+        DEBUG1 ("processing incoming header packet (%d)", codec->headers);
+
+        if (ogg_stream_packetout (&codec->os, &header) <= 0)
         {
-            if (write_ogg_data (source, header) == 0)
-                return;
-            header = header->next;
+            if (ogg_info->codecs->next)
+                format_ogg_attach_header (ogg_info, page);
+            return NULL;
         }
-        state->file_headers = refbuf->associated;
+
+        /* 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++;
     }
-    write_ogg_data (source, refbuf);
+    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;
 }
 

Modified: icecast/trunk/icecast/src/format_vorbis.h
===================================================================
--- icecast/trunk/icecast/src/format_vorbis.h	2004-12-07 20:25:02 UTC (rev 8340)
+++ icecast/trunk/icecast/src/format_vorbis.h	2004-12-07 21:06:26 UTC (rev 8341)
@@ -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 */

Modified: icecast/trunk/icecast/src/refbuf.c
===================================================================
--- icecast/trunk/icecast/src/refbuf.c	2004-12-07 20:25:02 UTC (rev 8340)
+++ icecast/trunk/icecast/src/refbuf.c	2004-12-07 21:06:26 UTC (rev 8341)
@@ -51,6 +51,7 @@
         }
     }
     refbuf->len = size;
+    refbuf->sync_point = 0;
     refbuf->_count = 1;
     refbuf->next = NULL;
     refbuf->associated = NULL;

Modified: icecast/trunk/icecast/src/refbuf.h
===================================================================
--- icecast/trunk/icecast/src/refbuf.h	2004-12-07 20:25:02 UTC (rev 8340)
+++ icecast/trunk/icecast/src/refbuf.h	2004-12-07 21:06:26 UTC (rev 8341)
@@ -22,6 +22,7 @@
 {
     char *data;
     long len;
+    int sync_point;
     struct _refbuf_tag *associated;
     struct _refbuf_tag *next;
 

Modified: icecast/trunk/icecast/src/source.c
===================================================================
--- icecast/trunk/icecast/src/source.c	2004-12-07 20:25:02 UTC (rev 8340)
+++ icecast/trunk/icecast/src/source.c	2004-12-07 21:06:26 UTC (rev 8341)
@@ -381,6 +381,26 @@
     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 @@
     /* 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)



More information about the commits mailing list