[vlc-devel] [PATCH] http: handle shoutcast/icecast (ICY) metadata

Ludovic Fauvet etix at videolan.org
Wed Feb 17 17:36:33 CET 2016


This patch targets the new HTTP access module and was made to be as less
intrusive as possible while still maintaining the old "Now playing"
behavior.

The module suports icy frames exceeding the size of a single block_t and
extending up to multiple blocks. For instance it was tested with a block
size of 128 bytes and an icy payload >512.
---
 modules/access/http/Makefile.am |   3 +-
 modules/access/http/access.c    |  17 +++
 modules/access/http/file.c      |  43 +++++++
 modules/access/http/file.h      |  29 +++++
 modules/access/http/icy.c       | 265 ++++++++++++++++++++++++++++++++++++++++
 modules/access/http/icy.h       |  60 +++++++++
 6 files changed, 416 insertions(+), 1 deletion(-)
 create mode 100644 modules/access/http/icy.c
 create mode 100644 modules/access/http/icy.h

diff --git a/modules/access/http/Makefile.am b/modules/access/http/Makefile.am
index d447a42..630d5c1 100644
--- a/modules/access/http/Makefile.am
+++ b/modules/access/http/Makefile.am
@@ -13,7 +13,8 @@ libvlc_http_la_SOURCES = \
 	access/http/h2output.c access/http/h2output.h \
 	access/http/h2conn.c access/http/h1conn.c \
 	access/http/chunked.c access/http/tunnel.c access/http/conn.h \
-	access/http/connmgr.c access/http/connmgr.h
+	access/http/connmgr.c access/http/connmgr.h \
+	access/http/icy.c access/http/icy.h
 libvlc_http_la_CPPFLAGS = -Dneedsomethinghere
 libvlc_http_la_LIBADD = \
 	$(LTLIBVLCCORE) ../compat/libcompat.la \
diff --git a/modules/access/http/access.c b/modules/access/http/access.c
index 28d4d09..98fcd3c 100644
--- a/modules/access/http/access.c
+++ b/modules/access/http/access.c
@@ -35,6 +35,7 @@
 #include "connmgr.h"
 #include "file.h"
 #include "live.h"
+#include "icy.h"
 
 struct access_sys_t
 {
@@ -44,6 +45,7 @@ struct access_sys_t
         struct vlc_http_file *file;
         struct vlc_http_live *live;
     };
+    struct vlc_http_icy *icy;
 };
 
 static block_t *FileRead(access_t *access)
@@ -53,6 +55,7 @@ static block_t *FileRead(access_t *access)
     block_t *b = vlc_http_file_read(sys->file);
     if (b == NULL)
         access->info.b_eof = true;
+    vlc_http_icy_read_block(sys->icy, b);
     return b;
 }
 
@@ -178,6 +181,7 @@ static int Open(vlc_object_t *obj)
 
     sys->manager = NULL;
     sys->file = NULL;
+    sys->icy = NULL;
 
     void *jar = NULL;
     if (var_InheritBool(obj, "http-forward-cookies"))
@@ -258,9 +262,21 @@ static int Open(vlc_object_t *obj)
         access->pf_control = FileControl;
     }
     access->p_sys = sys;
+
+    if (!live)
+    {
+        sys->icy = vlc_http_icy_create(access);
+        if (sys->icy == NULL)
+            goto error;
+
+        vlc_http_icy_read_header(sys->icy, sys->file);
+    }
+
     return VLC_SUCCESS;
 
 error:
+    if (sys->icy != NULL)
+        vlc_http_icy_destroy(sys->icy);
     if (sys->file != NULL)
         vlc_http_file_destroy(sys->file);
     if (sys->manager != NULL)
@@ -274,6 +290,7 @@ static void Close(vlc_object_t *obj)
     access_t *access = (access_t *)obj;
     access_sys_t *sys = access->p_sys;
 
+    vlc_http_icy_destroy(sys->icy);
     if (access->pf_block == LiveRead)
         vlc_http_live_destroy(sys->live);
     else
diff --git a/modules/access/http/file.c b/modules/access/http/file.c
index 5695c7f..7d8a1f7 100644
--- a/modules/access/http/file.c
+++ b/modules/access/http/file.c
@@ -72,6 +72,9 @@ static int vlc_http_file_req(struct vlc_http_msg *req,
     if (vlc_http_msg_add_header(req, "Range", "bytes=%ju-", *offset)
      && *offset != 0)
         return -1;
+
+    /* ICY meta data request */
+    vlc_http_msg_add_header(req, "Icy-MetaData", "1");
     return 0;
 }
 
@@ -263,3 +266,43 @@ block_t *vlc_http_file_read(struct vlc_http_file *file)
     file->offset += block->i_buffer;
     return block;
 }
+
+int vlc_http_file_get_icy_metaint(struct vlc_http_file *file)
+{
+    int status = vlc_http_file_get_status(file);
+    if (status < 0)
+        return -1;
+
+    const char *icy = vlc_http_msg_get_header(file->resp, "icy-metaint");
+    if (icy != NULL) {
+        return atoi(icy);
+    }
+    return -1;
+}
+
+const char* vlc_http_file_get_icy_name(struct vlc_http_file *file)
+{
+    int status = vlc_http_file_get_status(file);
+    if (status < 0)
+        return NULL;
+
+    return vlc_http_msg_get_header(file->resp, "icy-name");
+}
+
+const char* vlc_http_file_get_icy_genre(struct vlc_http_file *file)
+{
+    int status = vlc_http_file_get_status(file);
+    if (status < 0)
+        return NULL;
+
+    return vlc_http_msg_get_header(file->resp, "icy-genre");
+}
+
+const char* vlc_http_file_get_icy_description(struct vlc_http_file *file)
+{
+    int status = vlc_http_file_get_status(file);
+    if (status < 0)
+        return NULL;
+
+    return vlc_http_msg_get_header(file->resp, "icy-description");
+}
\ No newline at end of file
diff --git a/modules/access/http/file.h b/modules/access/http/file.h
index f47795d..81f279d 100644
--- a/modules/access/http/file.h
+++ b/modules/access/http/file.h
@@ -101,4 +101,33 @@ int vlc_http_file_seek(struct vlc_http_file *, uintmax_t offset);
  */
 struct block_t *vlc_http_file_read(struct vlc_http_file *);
 
+/**
+ * Returns the ICY metaint value
+ *
+ * @return metaint value, or -1 if unavailable
+ */
+int vlc_http_file_get_icy_metaint(struct vlc_http_file *);
+
+/**
+ * Returns the current ICY stream name
+ *
+ * @return name string, or NULL if none
+ */
+const char* vlc_http_file_get_icy_name(struct vlc_http_file *);
+
+/**
+ * Returns the current ICY stream genre
+ *
+ * @return genre string, or NULL if none
+ */
+const char* vlc_http_file_get_icy_genre(struct vlc_http_file *);
+
+/**
+ * Returns the current ICY stream description
+ *
+ * @return description string, or NULL if none
+ */
+const char* vlc_http_file_get_icy_description(struct vlc_http_file *);
+
+
 /** @} */
diff --git a/modules/access/http/icy.c b/modules/access/http/icy.c
new file mode 100644
index 0000000..e75af0a
--- /dev/null
+++ b/modules/access/http/icy.c
@@ -0,0 +1,265 @@
+/*****************************************************************************
+ * icy.c: HTTP/TLS VLC ICY plug-in
+ *****************************************************************************
+ * Copyright © 2016 Ludovic Fauvet
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <string.h>
+
+#include <vlc_common.h>
+#include <vlc_access.h>
+#include <vlc_block.h>
+#include <vlc_input.h>
+#include <vlc_charset.h>
+
+#include "icy.h"
+#include "file.h"
+
+struct vlc_http_icy
+{
+    access_t *access;
+
+    int metaint;
+    int metaint_offset;
+
+    size_t pframe_size;
+    size_t pframe_remaining;
+    char *pframe_buffer;
+
+    char *title;
+};
+
+static void vlc_http_icy_metadata_parse(struct vlc_http_icy *icy, size_t size, char *buffer)
+{
+    if (size == 0 || buffer == NULL)
+        return;
+
+    char *title;
+
+    // Allocate a null terminated buffer to work on
+    char *meta = malloc(size + 1);
+    if (unlikely(meta == NULL))
+        return;
+
+    memcpy(meta, buffer, size);
+    meta[size] = '\0';
+
+    title = strcasestr(meta, "StreamTitle=");
+    if (title)
+    {
+        title += strlen("StreamTitle=");
+        if (*title == '\'' || *title == '"')
+        {
+            char closing[] = { title[0], ';', '\0' };
+            char *psz = strstr(&title[1], closing);
+            if (!psz)
+                psz = strchr( &title[1], ';' );
+
+            if (psz) *psz = '\0';
+        }
+        else
+        {
+            char *psz = strchr( &title[1], ';' );
+            if (psz) *psz = '\0';
+        }
+
+        if (!icy->title || strcmp(icy->title, &title[1]))
+        {
+            free(icy->title);
+            icy->title = NULL;
+            char *psz_tmp = strdup(&title[1]);
+            if (unlikely(psz_tmp == NULL))
+                goto error;
+
+            icy->title = EnsureUTF8(psz_tmp);
+            if (!icy->title)
+                free(psz_tmp);
+
+            msg_Dbg(icy->access, "Icy-Title: %s", icy->title);
+
+            input_item_t *input_item = input_GetItem(icy->access->p_input);
+            if (input_item)
+            {
+                input_item_SetMeta(input_item, vlc_meta_NowPlaying, icy->title);
+            }
+        }
+    }
+
+error:
+    free(meta);
+}
+
+void vlc_http_icy_read_block(struct vlc_http_icy *icy, block_t *b)
+{
+    if (icy == NULL || icy->metaint <= 0)
+        return;
+
+    if (b == NULL)
+    {
+        /* EOF */
+        free(icy->pframe_buffer);
+        icy->pframe_buffer = NULL;
+        icy->pframe_remaining = 0;
+        icy->pframe_size = 0;
+        return;
+    }
+
+    uint8_t *buf = b->p_buffer;
+    size_t until_next_meta = icy->metaint - icy->metaint_offset;
+    size_t consume = 0;
+
+    if (icy->pframe_remaining > 0)
+    {
+        /* Continuation of a previous metadata frame */
+        consume = __MIN(icy->pframe_remaining, b->i_buffer);
+
+        if (likely(icy->pframe_buffer != NULL))
+            memcpy(icy->pframe_buffer+(icy->pframe_size-icy->pframe_remaining), buf, consume);
+
+        icy->pframe_remaining -= consume;
+        icy->metaint_offset += b->i_buffer - consume;
+
+
+        if (icy->pframe_remaining == 0)
+        {
+            /* End of chunked metadata frame */
+            vlc_http_icy_metadata_parse(icy, icy->pframe_size, icy->pframe_buffer);
+
+            free(icy->pframe_buffer);
+            icy->pframe_buffer = NULL;
+            icy->pframe_remaining = 0;
+            icy->pframe_size = 0;
+        }
+    }
+    else if (until_next_meta < b->i_buffer)
+    {
+        /* Beginning of a new icy metadata frame */
+        uint8_t *p = buf = b->p_buffer + until_next_meta;
+        uint8_t size = (*p) * 16;
+        char *meta = (char *)p+1;
+
+        consume = size + 1;
+
+        if (b->i_buffer - (until_next_meta + 1) < size)
+        {
+            /* The frame exceeds the current block */
+            consume = b->i_buffer - until_next_meta;
+
+            icy->pframe_size = size;
+            icy->pframe_remaining = size - (b->i_buffer - (until_next_meta + 1));
+
+            free(icy->pframe_buffer);
+            icy->pframe_buffer = malloc(size + 1);
+            if (likely(icy->pframe_buffer != NULL))
+                memcpy(icy->pframe_buffer, meta, b->i_buffer - until_next_meta);
+
+            icy->metaint_offset = 0;
+        }
+        else
+        {
+            /* Frame complete */
+            vlc_http_icy_metadata_parse(icy, size, meta);
+            icy->metaint_offset = b->i_buffer - (until_next_meta + consume);
+        }
+    }
+    else
+    {
+        /* No icy frame in sight, do some accounting */
+        icy->metaint_offset += b->i_buffer;
+    }
+
+    if (consume > 0)
+    {
+        memmove(buf, buf+consume, icy->metaint_offset);
+        b->i_buffer -= consume;
+    }
+}
+
+void vlc_http_icy_read_header(struct vlc_http_icy *icy, struct vlc_http_file *file)
+{
+    if (icy == NULL || file == NULL)
+        return;
+
+    icy->metaint = vlc_http_file_get_icy_metaint(file);
+    if (icy->metaint > 0)
+    {
+        msg_Dbg(icy->access, "Icy-MetaInt: %d", icy->metaint);
+    }
+
+    input_item_t *input_item = NULL;
+
+    if (icy->access->p_input)
+    {
+        input_item = input_GetItem(icy->access->p_input);
+    }
+
+    const char *name = vlc_http_file_get_icy_name(file);
+    if (name)
+    {
+        msg_Dbg(icy->access, "Icy-Name: %s", name);
+        if (input_item)
+            input_item_SetMeta(input_item, vlc_meta_Title, name);
+    }
+
+    const char *genre = vlc_http_file_get_icy_genre(file);
+    if (genre)
+    {
+        msg_Dbg(icy->access, "Icy-Genre: %s", genre);
+        if (input_item)
+            input_item_SetMeta(input_item, vlc_meta_Genre, genre);
+    }
+
+    const char *description = vlc_http_file_get_icy_description(file);
+    if (description)
+    {
+        msg_Dbg(icy->access, "Icy-Description: %s", description);
+        if (input_item)
+            input_item_SetMeta(input_item, vlc_meta_Description, description);
+    }
+}
+
+struct vlc_http_icy *vlc_http_icy_create(access_t *access)
+{
+    struct vlc_http_icy *icy = malloc(sizeof(*icy));
+
+    if (unlikely(icy == NULL))
+        return NULL;
+
+    icy->access = access;
+    icy->metaint = 0;
+    icy->metaint_offset = 0;
+    icy->pframe_size = 0;
+    icy->pframe_remaining = 0;
+    icy->pframe_buffer = NULL;
+    icy->title = NULL;
+
+    return icy;
+}
+
+void vlc_http_icy_destroy(struct vlc_http_icy *icy)
+{
+    if (unlikely(icy == NULL))
+        return;
+
+    free(icy->pframe_buffer);
+    free(icy->title);
+    free(icy);
+}
\ No newline at end of file
diff --git a/modules/access/http/icy.h b/modules/access/http/icy.h
new file mode 100644
index 0000000..d056883
--- /dev/null
+++ b/modules/access/http/icy.h
@@ -0,0 +1,60 @@
+/*****************************************************************************
+ * icy.h: HTTP/TLS VLC ICY plug-in
+ *****************************************************************************
+ * Copyright © 2016 Ludovic Fauvet
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+struct vlc_http_icy;
+
+struct block_t;
+struct vlc_http_file;
+struct access_t;
+
+/**
+ * Reads a new block_t looking for ICY frames
+ *
+ * All block_t must be given to this function for
+ * proper accounting.
+ */
+void vlc_http_icy_read_block(struct vlc_http_icy *icy, block_t *b);
+
+/**
+ * Reads an HTTP header for ICY metadata
+ *
+ * @param icy ICY object instance
+ * @param file vlc_http_file object to read from
+ */
+void vlc_http_icy_read_header(struct vlc_http_icy *icy, struct vlc_http_file *file);
+
+/**
+ * Creates an HTTP ICY filter.
+ *
+ * Allocates a structure for an ICY metadata filter.
+ *
+ * @param access the associated access_t object
+ * @return ICY object instance, or NULL on error
+ */
+struct vlc_http_icy *vlc_http_icy_create(access_t *access);
+
+/**
+ * Destroys an HTTP ICY filter.
+ *
+ * Releases all resources allocated or held by the HTTP ICY object.
+ *
+ * @param icy the ICY object instance
+ */
+void vlc_http_icy_destroy(struct vlc_http_icy *icy);
\ No newline at end of file
-- 
2.7.1



More information about the vlc-devel mailing list