changeset 1730:50d151b259bb

- Add lots of code to support the completely braindead concept that is StreamCast
author Ralf Ertzinger <ralf@skytale.net>
date Tue, 18 Sep 2007 18:01:00 +0200
parents eaa8a5747628
children 38375d565192
files src/neon/neon.c src/neon/neon.h
diffstat 2 files changed, 245 insertions(+), 14 deletions(-) [+]
line wrap: on
line diff
--- a/src/neon/neon.c	Tue Sep 18 11:35:15 2007 +0200
+++ b/src/neon/neon.c	Tue Sep 18 18:01:00 2007 +0200
@@ -62,6 +62,11 @@
     h->pos = 0;
     h->content_length = -1;
     h->can_ranges = FALSE;
+    h->icy_metaint = 0;
+    h->icy_metaleft = 0;
+    h->icy_metadata.stream_name = NULL;
+    h->icy_metadata.stream_title = NULL;
+    h->icy_metadata.stream_url = NULL;
     h->reader = NULL;
     h->reader_status.mutex = g_mutex_new();
     h->reader_status.cond = g_cond_new();
@@ -128,6 +133,126 @@
  * -----
  */
 
+static void add_icy(struct icy_metadata* m, gchar* name, gchar* value) { 
+
+    _ENTER;
+
+    if (0 == g_ascii_strncasecmp(name, "StreamTitle", 11)) {
+        _DEBUG("Found StreamTitle: %s", value);
+        if (NULL != m->stream_title) {
+            free(m->stream_title);
+        }
+        m->stream_title = g_strdup(value);
+    }
+
+    if (0 == g_ascii_strncasecmp(name, "StreamUrl", 9)) {
+        _DEBUG("Found StreamUrl: %s", value);
+        if (NULL != m->stream_url) {
+            free(m->stream_url);
+        }
+        m->stream_url = g_strdup(value);
+    }
+
+    _LEAVE;
+}
+
+/*
+ * -----
+ */
+
+static void parse_icy(struct icy_metadata* m, gchar* metadata, int len) {
+
+    gchar* p;
+    gchar* tstart;
+    gchar* tend;
+    gchar name[4096];
+    gchar value[4096];
+    int state;
+    int pos;
+
+    _ENTER;
+
+    p = metadata;
+    state = 1;
+    pos = 0;
+    name[0] = '\0';
+    value[0] = '\0';
+    tstart = metadata;
+    tend = metadata;
+    while ((pos < len) && (*p != '\0')) {
+        switch (state) {
+            case 1:
+                /*
+                 * Reading tag name
+                 */
+                if ('=' == *p) {
+                    /*
+                     * End of tag name.
+                     */
+                    *p = '\0';
+                    strcpy(name, tstart);
+                    _DEBUG("Found tag name: %s", name);
+                    state = 2;
+                } else {
+                    tend = p;
+                };
+                break;
+            case 2:
+                /*
+                 * Waiting for start of value
+                 */
+                if ('\'' == *p) {
+                    /*
+                     * Leading ' of value
+                     */
+                    tend = tstart = p + 1;
+                    state = 3;
+                    value[0] = '\0';
+                }
+                break;
+            case 3:
+                /*
+                 * Reading value
+                 */
+                if ('\'' == *p) {
+                    /*
+                     * End of value
+                     */
+                    *p = '\0';
+                    strcpy(value, tstart);
+                    _DEBUG("Found tag value: %s", value);
+                    add_icy(m, name, value);
+                    state = 4;
+                } else {
+                    tend = p;
+                }
+                break;
+            case 4:
+                /*
+                 * Waiting for next tag start
+                 */
+                if (';' == *p) {
+                    /*
+                     * Next tag name starts after this char
+                     */
+                    tend = tstart = p + 1;
+                    state = 1;
+                    name[0] = '\0';
+                    value[0] = '\0';
+                }
+                break;
+        }
+        p++;
+        pos++;
+    }
+
+    _LEAVE;
+}
+
+/*
+ * -----
+ */
+
 static void kill_reader(struct neon_handle* h) {
 
     _ENTER;
@@ -176,14 +301,44 @@
              * The server sent us the content length. Parse and store.
              */
             len = strtol(value, &endptr, 10);
-            if ((*value != '\0') && (*endptr == '\0')) {
+            if ((*value != '\0') && (*endptr == '\0') && (len >= 0)) {
                 /*
                  * Valid data.
                  */
-                _DEBUG("Content length as advertised by server: %d", len);
+                _DEBUG("Content length as advertised by server: %ld", len);
                 h->content_length = len;
+            } else {
+                _ERROR("Invalid content length header: %s", value);
             }
         }
+
+        if (0 == g_ascii_strncasecmp("icy-metaint", name, 11)) {
+            /*
+             * The server sent us a ICY metaint header. Parse and store.
+             */
+            len = strtol(value, &endptr, 10);
+            if ((*value != '\0') && (*endptr == '\0') && (len > 0)) {
+                /*
+                 * Valid data
+                 */
+                _DEBUG("ICY MetaInt as advertised by server: %ld", len);
+                h->icy_metaint = len;
+                h->icy_metaleft = len;
+            } else {
+                _ERROR("Invalid ICY MetaInt header: %s", value);
+            }
+        }
+
+        if (0 == g_ascii_strncasecmp("icy-name", name, 8)) {
+            /*
+             * The server sent us a ICY name. Save it for later
+             */
+            _DEBUG("ICY stream name: %s", value);
+            if (NULL != h->icy_metadata.stream_name) {
+                free(h->icy_metadata.stream_name);
+            }
+            h->icy_metadata.stream_name = g_strdup(value);
+        }
     }
 
     _LEAVE;
@@ -201,6 +356,7 @@
 
     handle->request = ne_request_create(handle->session, "GET", handle->purl->path);
     ne_print_request_header(handle->request, "Range", "bytes=%ld-", startbyte);
+    ne_print_request_header(handle->request, "Icy-MetaData", "1");
 
     /*
      * Try to connect to the server.
@@ -521,7 +677,10 @@
 
     struct neon_handle* h = (struct neon_handle*)file->handle;
     int belem;
+    int relem;
     int ret;
+    char icy_metadata[4096];
+    unsigned char icy_metalen;
 
     _ENTER;
 
@@ -549,6 +708,17 @@
             g_mutex_unlock(h->reader_status.mutex);
             _ERROR("Buffer underrun, trying rebuffering");
             kill_reader(h);
+
+            /*
+             * We have to check if the reader terminated gracefully
+             * again
+             */
+            if (NEON_READER_TERM != h->reader_status.status) {
+                /*
+                 * Reader thread did not terminate gracefully.
+                 */
+                _LEAVE 0;
+            }
         } else {
             g_mutex_unlock(h->reader_status.mutex);
         }
@@ -634,18 +804,59 @@
     /*
      * Deliver data from the buffer
      */
-    belem = used_rb(&h->rb) / size;
-
-    if (0 == belem) {
+    if (0 == used_rb(&h->rb)) {
         /*
-         * The buffer is empty, we can deliver no data!
+         * The buffer is still empty, we can deliver no data!
          */
         _ERROR("Buffer still underrun, fatal.");
         _LEAVE 0;
     }
 
-    _DEBUG("%d elements of data in the buffer", belem);
-    read_rb(&h->rb, ptr_, MIN(belem, nmemb)*size);
+    if (0 != h->icy_metaint) {
+        _DEBUG("%ld bytes left before next ICY metadata announcement", h->icy_metaleft);
+        if (0 == h->icy_metaleft) {
+            /*
+             * The next data in the buffer is a ICY metadata announcement.
+             * Get the length byte
+             */
+            read_rb(&h->rb, &icy_metalen, 1);
+
+            /*
+             * We need enough data in the buffer to
+             * a) Read the complete ICY metadata block
+             * b) deliver at least one byte to the reader
+             */
+            _DEBUG("Expecting %d bytes of ICY metadata", (icy_metalen*16));
+
+            if ((free_rb(&h->rb)-(icy_metalen*16)) < size) {
+                /* There is not enough data. We do not have much choice at this point,
+                 * so we'll deliver the metadata as normal data to the reader and
+                 * hope for the best.
+                 */
+                _ERROR("Buffer underrun when reading metadata. Expect audio degradation");
+                h->icy_metaleft = h->icy_metaint + (icy_metalen*16);
+            } else {
+                /*
+                 * Grab the metadata from the buffer and send it to the parser
+                 */
+                read_rb(&h->rb, icy_metadata, (icy_metalen*16));
+                parse_icy(&h->icy_metadata, icy_metadata, (icy_metalen*16));
+                h->icy_metaleft = h->icy_metaint;
+            }
+        }
+
+        /*
+         * The maximum number of bytes we can deliver is determined
+         * by the number of bytes left until the next metadata announcement
+         */
+        belem = h->icy_metaleft / size;
+    } else {
+        belem = used_rb(&h->rb) / size;
+    }
+
+    relem = MIN(belem, nmemb);
+    _DEBUG("%d elements of returnable data in the buffer", belem);
+    read_rb(&h->rb, ptr_, relem*size);
 
     /*
      * Signal the network thread to continue reading
@@ -655,11 +866,12 @@
     g_cond_signal(h->reader_status.cond);
     g_mutex_unlock(h->reader_status.mutex);
 
-    h->pos += (MIN(belem, nmemb)*size);
+    h->pos += (relem*size);
+    h->icy_metaleft -= (relem*size);
 
-    _DEBUG("Returning %d elements", MIN(belem, nmemb));
+    _DEBUG("Returning %d elements", relem);
 
-    _LEAVE MIN(belem, nmemb);
+    _LEAVE relem;
 }
 
 
@@ -730,7 +942,7 @@
 
     _ENTER;
 
-    _DEBUG("Current file position: %d", h->pos);
+    _DEBUG("Current file position: %ld", h->pos);
 
     _LEAVE h->pos;
 }
@@ -858,11 +1070,21 @@
  * -----
  */
 
-gchar *neon_vfs_metadata_impl(VFSFile* file, const gchar * field) {
+gchar *neon_vfs_metadata_impl(VFSFile* file, const gchar* field) {
+
+    struct neon_handle* h = (struct neon_handle*)file->handle;
 
     _ENTER;
 
-    _ERROR("NOT IMPLEMENTED");
+    _DEBUG("Field name: %s", field);
+
+    if (0 == g_ascii_strncasecmp(field, "track-name", 10)) {
+        _LEAVE g_strdup(h->icy_metadata.stream_title);
+    }
+
+    if (0 == g_ascii_strncasecmp(field, "stream-name", 11)) {
+        _LEAVE g_strdup(h->icy_metadata.stream_name);
+    }
 
     _LEAVE NULL;
 }
--- a/src/neon/neon.h	Tue Sep 18 11:35:15 2007 +0200
+++ b/src/neon/neon.h	Tue Sep 18 18:01:00 2007 +0200
@@ -43,6 +43,12 @@
     neon_reader_t status;
 };
 
+struct icy_metadata {
+    gchar* stream_name;
+    gchar* stream_title;
+    gchar* stream_url;
+};
+
 struct neon_handle {
     gchar* url;                         /* The URL, as passed to us */
     ne_uri* purl;                       /* The URL, parsed into a structure */
@@ -52,6 +58,9 @@
     unsigned long content_start;        /* Start position in the stream */
     long content_length;                /* Total content length, counting from content_start, if known. -1 if unknown */
     gboolean can_ranges;                /* TRUE if the webserver advertised accept-range: bytes */
+    unsigned long icy_metaint;          /* Interval in which the server will send metadata announcements. 0 if no announcments */
+    unsigned long icy_metaleft;         /* Bytes left until the next metadata block */
+    struct icy_metadata icy_metadata;   /* Current ICY metadata */
     ne_session* session;
     ne_request* request;
     GThread* reader;