[vlc-devel] [PATCH] Handle icecast streams within a stream filter module

Ludovic Fauvet etix at videolan.org
Fri Mar 4 16:54:53 CET 2016


Icecast streams are intimately related to an HTTP access module but most
of the actual metadata stream parsing can be done outside of the HTTP
read loop. This improves code modularity and alleviates the code burden
within the current HTTP access.

Last but not least, this also removes one of the few dependencies that
left to the legacy HTTP module.
---
 modules/access/http.c             | 106 ++---------------
 modules/access/http/access.c      |  47 +++++++-
 modules/access/http/file.c        |  51 +++++++-
 modules/access/http/file.h        |  32 ++++-
 modules/access/http/h1conn.c      |  12 +-
 modules/access/http/message.c     |  14 +++
 modules/access/http/message.h     |   2 +
 modules/access/http/resource.c    |   8 +-
 modules/stream_filter/Makefile.am |   3 +
 modules/stream_filter/icy.c       | 245 ++++++++++++++++++++++++++++++++++++++
 10 files changed, 408 insertions(+), 112 deletions(-)
 create mode 100644 modules/stream_filter/icy.c

diff --git a/modules/access/http.c b/modules/access/http.c
index 075cd42..2759ddb 100644
--- a/modules/access/http.c
+++ b/modules/access/http.c
@@ -46,6 +46,7 @@
 #include <vlc_http.h>
 #include <vlc_interrupt.h>
 #include <vlc_keystore.h>
+#include <vlc_modules.h>
 
 #include <assert.h>
 #include <limits.h>
@@ -148,13 +149,13 @@ struct access_sys_t
     char       *psz_mime;
     char       *psz_location;
     bool b_mms;
+    bool b_icy_enabled;
     bool b_icecast;
 
     bool b_chunked;
     int64_t    i_chunk;
 
     int        i_icy_meta;
-    uint64_t   i_icy_offset;
     char       *psz_icy_name;
     char       *psz_icy_genre;
     char       *psz_icy_title;
@@ -221,7 +222,6 @@ static int Open( vlc_object_t *p_this )
     p_sys->p_creds = NULL;
     p_sys->p_tls = NULL;
     p_sys->i_icy_meta = 0;
-    p_sys->i_icy_offset = 0;
     p_sys->psz_icy_name = NULL;
     p_sys->psz_icy_genre = NULL;
     p_sys->psz_icy_title = NULL;
@@ -231,6 +231,9 @@ static int Open( vlc_object_t *p_this )
     p_sys->size = 0;
     p_access->info.b_eof  = false;
 
+    var_Create(p_access->p_input, "icy-metaint", VLC_VAR_INTEGER);
+    p_sys->b_icy_enabled = module_exists("icy");
+
     /* Only forward an store cookies if the corresponding option is activated */
     if( var_CreateGetBool( p_access, "http-forward-cookies" ) )
         p_sys->cookies = GetCookieJar( p_this );
@@ -558,7 +561,6 @@ static int ReadData( access_t *p_access, int *pi_read,
  * Read: Read up to i_len bytes from the http connection and place in
  * p_buffer. Return the actual number of bytes read
  *****************************************************************************/
-static int ReadICYMeta( access_t *p_access );
 static ssize_t Read( access_t *p_access, uint8_t *p_buffer, size_t i_len )
 {
     access_sys_t *p_sys = p_access->p_sys;
@@ -581,20 +583,6 @@ static ssize_t Read( access_t *p_access, uint8_t *p_buffer, size_t i_len )
     if( i_len == 0 )
         goto fatal;
 
-    if( p_sys->i_icy_meta > 0 && p_sys->offset - p_sys->i_icy_offset > 0 )
-    {
-        int64_t i_next = p_sys->i_icy_meta -
-                                    (p_sys->offset - p_sys->i_icy_offset ) % p_sys->i_icy_meta;
-
-        if( i_next == p_sys->i_icy_meta )
-        {
-            if( ReadICYMeta( p_access ) )
-                goto fatal;
-        }
-        if( i_len > i_next )
-            i_len = i_next;
-    }
-
     if( ReadData( p_access, &i_read, p_buffer, i_len ) )
         goto fatal;
 
@@ -631,81 +619,6 @@ fatal:
     return 0;
 }
 
-static int ReadICYMeta( access_t *p_access )
-{
-    access_sys_t *p_sys = p_access->p_sys;
-
-    uint8_t buffer;
-    char *p, *psz_meta;
-    int i_read;
-
-    /* Read meta data length */
-    if( ReadData( p_access, &i_read, &buffer, 1 ) )
-        return VLC_EGENERIC;
-    if( i_read != 1 )
-        return VLC_EGENERIC;
-    const int i_size = buffer << 4;
-    /* msg_Dbg( p_access, "ICY meta size=%u", i_size); */
-
-    psz_meta = malloc( i_size + 1 );
-    for( i_read = 0; i_read < i_size; )
-    {
-        int i_tmp;
-        if( ReadData( p_access, &i_tmp, (uint8_t *)&psz_meta[i_read], i_size - i_read ) || i_tmp <= 0 )
-        {
-            free( psz_meta );
-            return VLC_EGENERIC;
-        }
-        i_read += i_tmp;
-    }
-    psz_meta[i_read] = '\0'; /* Just in case */
-
-    /* msg_Dbg( p_access, "icy-meta=%s", psz_meta ); */
-
-    /* Now parse the meta */
-    /* Look for StreamTitle= */
-    p = strcasestr( (char *)psz_meta, "StreamTitle=" );
-    if( p )
-    {
-        p += strlen( "StreamTitle=" );
-        if( *p == '\'' || *p == '"' )
-        {
-            char closing[] = { p[0], ';', '\0' };
-            char *psz = strstr( &p[1], closing );
-            if( !psz )
-                psz = strchr( &p[1], ';' );
-
-            if( psz ) *psz = '\0';
-        }
-        else
-        {
-            char *psz = strchr( &p[1], ';' );
-            if( psz ) *psz = '\0';
-        }
-
-        if( !p_sys->psz_icy_title ||
-            strcmp( p_sys->psz_icy_title, &p[1] ) )
-        {
-            free( p_sys->psz_icy_title );
-            char *psz_tmp = strdup( &p[1] );
-            p_sys->psz_icy_title = EnsureUTF8( psz_tmp );
-            if( !p_sys->psz_icy_title )
-                free( psz_tmp );
-
-            msg_Dbg( p_access, "New Icy-Title=%s", p_sys->psz_icy_title );
-            input_thread_t *p_input = p_access->p_input;
-            if( p_input )
-            {
-                input_item_t *p_input_item = input_GetItem( p_access->p_input );
-                if( p_input_item )
-                    input_item_SetMeta( p_input_item, vlc_meta_NowPlaying, p_sys->psz_icy_title );
-            }
-        }
-    }
-    free( psz_meta );
-
-    return VLC_SUCCESS;
-}
 
 /*****************************************************************************
  * Seek: close and re-open a connection at the right place
@@ -857,7 +770,6 @@ static int Connect( access_t *p_access, uint64_t i_tell )
     p_sys->b_chunked = false;
     p_sys->i_chunk = 0;
     p_sys->i_icy_meta = 0;
-    p_sys->i_icy_offset = i_tell;
     p_sys->psz_icy_name = NULL;
     p_sys->psz_icy_genre = NULL;
     p_sys->psz_icy_title = NULL;
@@ -1002,7 +914,8 @@ static int Request( access_t *p_access, uint64_t i_tell )
         AuthReply( p_access, "Proxy-", &p_sys->proxy, &p_sys->proxy_auth );
 
     /* ICY meta data request */
-    WriteHeaders( p_access, "Icy-MetaData: 1\r\n" );
+    if( p_sys->b_icy_enabled )
+        WriteHeaders( p_access, "Icy-MetaData: 1\r\n" );
 
     if( WriteHeaders( p_access, "\r\n" ) < 0 )
     {
@@ -1116,7 +1029,6 @@ static int Request( access_t *p_access, uint64_t i_tell )
             sscanf(p,"bytes %"SCNu64"-%"SCNu64"/%"SCNu64,&i_ntell,&i_nend,&i_nsize);
             if(i_nend > i_ntell ) {
                 p_sys->offset = i_ntell;
-                p_sys->i_icy_offset  = i_ntell;
                 p_sys->i_remaining = i_nend+1-i_ntell;
                 uint64_t i_size = (i_nsize > i_nend) ? i_nsize : (i_nend + 1);
                 if(i_size > p_sys->size) {
@@ -1207,7 +1119,11 @@ static int Request( access_t *p_access, uint64_t i_tell )
             if( p_sys->i_icy_meta < 0 )
                 p_sys->i_icy_meta = 0;
             if( p_sys->i_icy_meta > 0 )
+            {
+                var_SetInteger(p_access->p_input, "icy-metaint", p_sys->i_icy_meta);
+                var_SetString(p_access->p_input, "stream-filter", "icy");
                 p_sys->b_icecast = true;
+            }
 
             msg_Warn( p_access, "ICY metaint=%d", p_sys->i_icy_meta );
         }
diff --git a/modules/access/http/access.c b/modules/access/http/access.c
index 28d4d09..cfed44d 100644
--- a/modules/access/http/access.c
+++ b/modules/access/http/access.c
@@ -31,6 +31,9 @@
 #include <vlc_access.h>
 #include <vlc_plugin.h>
 #include <vlc_network.h> /* FIXME: only for vlc_getProxyUrl() */
+#include <vlc_input.h>
+#include <vlc_modules.h>
+#include <vlc_variables.h>
 
 #include "connmgr.h"
 #include "file.h"
@@ -162,7 +165,8 @@ static int Open(vlc_object_t *obj)
 {
     access_t *access = (access_t *)obj;
 
-    if (!strcasecmp(access->psz_access, "http"))
+    if (!strcasecmp(access->psz_access, "http") ||
+        !strcasecmp(access->psz_access, "icyx"))
     {
         char *proxy = vlc_getProxyUrl(access->psz_url);
         free(proxy);
@@ -179,6 +183,9 @@ static int Open(vlc_object_t *obj)
     sys->manager = NULL;
     sys->file = NULL;
 
+    var_Create(access->p_input, "icy-metaint", VLC_VAR_INTEGER);
+    bool icy_enabled = module_exists("icy");
+
     void *jar = NULL;
     if (var_InheritBool(obj, "http-forward-cookies"))
         jar = var_InheritAddress(obj, "http-cookies");
@@ -198,7 +205,7 @@ static int Open(vlc_object_t *obj)
                                          referer);
     else
         sys->file = vlc_http_file_create(sys->manager, access->psz_url, ua,
-                                         referer);
+                                         referer, icy_enabled);
     free(referer);
     free(ua);
 
@@ -258,6 +265,40 @@ static int Open(vlc_object_t *obj)
         access->pf_control = FileControl;
     }
     access->p_sys = sys;
+
+    if (!live && icy_enabled == true)
+    {
+        int metaint = vlc_http_file_get_icy_metaint(sys->file);
+        if (metaint > 0)
+        {
+            var_SetInteger(access->p_input, "icy-metaint", metaint);
+            var_SetString(access->p_input, "stream-filter", "icy");
+
+            input_item_t *input_item = input_GetItem(access->p_input);
+            if (input_item != NULL)
+            {
+                const char *name = vlc_http_file_get_icy_name(sys->file);
+                if (name)
+                {
+                    msg_Dbg(access, "Icy-Name: %s", name);
+                    input_item_SetMeta(input_item, vlc_meta_Title, name);
+                }
+                const char *genre = vlc_http_file_get_icy_genre(sys->file);
+                if (genre)
+                {
+                    msg_Dbg(access, "Icy-Genre: %s", genre);
+                    input_item_SetMeta(input_item, vlc_meta_Genre, genre);
+                }
+                const char *description = vlc_http_file_get_icy_description(sys->file);
+                if (description)
+                {
+                    msg_Dbg(access, "Icy-Description: %s", description);
+                    input_item_SetMeta(input_item, vlc_meta_Description, description);
+                }
+            }
+        }
+    }
+
     return VLC_SUCCESS;
 
 error:
@@ -288,7 +329,7 @@ vlc_module_begin()
     set_category(CAT_INPUT)
     set_subcategory(SUBCAT_INPUT_ACCESS)
     set_capability("access", 2)
-    add_shortcut("https", "http")
+    add_shortcut("https", "http", "icyx")
     set_callbacks(Open, Close)
 
     add_bool("http2", false, N_("Force HTTP/2"),
diff --git a/modules/access/http/file.c b/modules/access/http/file.c
index 5695c7f..8a15ba8 100644
--- a/modules/access/http/file.c
+++ b/modules/access/http/file.c
@@ -44,6 +44,7 @@ struct vlc_http_file
     struct vlc_http_resource resource;
     struct vlc_http_msg *resp;
     uintmax_t offset;
+    bool icy_enabled;
 };
 
 static int vlc_http_file_req(struct vlc_http_msg *req,
@@ -72,6 +73,12 @@ 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;
+
+    if (file->icy_enabled)
+    {
+        /* ICY meta data request */
+        vlc_http_msg_add_header(req, "Icy-MetaData", "1");
+    }
     return 0;
 }
 
@@ -118,7 +125,8 @@ void vlc_http_file_destroy(struct vlc_http_file *file)
 
 struct vlc_http_file *vlc_http_file_create(struct vlc_http_mgr *mgr,
                                            const char *uri, const char *ua,
-                                           const char *ref)
+                                           const char *ref,
+                                           bool icy_enabled)
 {
     struct vlc_http_file *file = malloc(sizeof (*file));
     if (unlikely(file == NULL))
@@ -132,6 +140,7 @@ struct vlc_http_file *vlc_http_file_create(struct vlc_http_mgr *mgr,
 
     file->resp = NULL;
     file->offset = 0;
+    file->icy_enabled = icy_enabled;
     return file;
 }
 
@@ -263,3 +272,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..edc9453 100644
--- a/modules/access/http/file.h
+++ b/modules/access/http/file.h
@@ -42,7 +42,8 @@ struct block_t;
  */
 struct vlc_http_file *vlc_http_file_create(struct vlc_http_mgr *mgr,
                                            const char *url, const char *ua,
-                                           const char *ref);
+                                           const char *ref,
+                                           bool icy_enabled);
 
 /**
  * Destroys an HTTP file.
@@ -101,4 +102,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/h1conn.c b/modules/access/http/h1conn.c
index 9801d36..f05c7ce 100644
--- a/modules/access/http/h1conn.c
+++ b/modules/access/http/h1conn.c
@@ -173,7 +173,7 @@ static struct vlc_http_msg *vlc_h1_stream_wait(struct vlc_http_stream *stream)
     struct vlc_http_msg *resp;
     const char *str;
     size_t len;
-    int minor;
+    int minor = -1;
 
     assert(conn->active);
 
@@ -187,13 +187,15 @@ static struct vlc_http_msg *vlc_h1_stream_wait(struct vlc_http_stream *stream)
     msg_Dbg(CO(conn), "incoming response:\n%.*s", (int)len, payload);
 
     resp = vlc_http_msg_headers(payload);
-    minor = vlc_http_minor(payload);
-    free(payload);
-
     if (resp == NULL)
         return vlc_h1_stream_fatal(conn);
 
-    assert(minor >= 0);
+    if (!vlc_http_msg_is_icy(resp))
+    {
+        minor = vlc_http_minor(payload);
+        assert(minor >= 0);
+    }
+    free(payload);
 
     conn->content_length = vlc_http_msg_get_size(resp);
     conn->connection_close = false;
diff --git a/modules/access/http/message.c b/modules/access/http/message.c
index 7bea981..2a20075 100644
--- a/modules/access/http/message.c
+++ b/modules/access/http/message.c
@@ -43,6 +43,7 @@ struct vlc_http_msg
     char *authority;
     char *path;
     char *(*headers)[2];
+    bool icy;
     unsigned count;
     struct vlc_http_stream *payload;
 };
@@ -214,6 +215,7 @@ vlc_http_req_create(const char *method, const char *scheme,
     m->scheme = (scheme != NULL) ? strdup(scheme) : NULL;
     m->authority = (authority != NULL) ? strdup(authority) : NULL;
     m->path = (path != NULL) ? strdup(path) : NULL;
+    m->icy = false;
     m->count = 0;
     m->headers = NULL;
     m->payload = NULL;
@@ -349,6 +351,13 @@ struct vlc_http_msg *vlc_http_msg_headers(const char *msg)
         if (unlikely(m == NULL))
             return NULL;
     }
+    else if (sscanf(msg, "ICY %3hu %*s", &code) == 1)
+    {
+        m = vlc_http_resp_create(code);
+        if (unlikely(m == NULL))
+            return NULL;
+        m->icy = true;
+    }
     else
         return NULL; /* TODO: request support */
 
@@ -901,3 +910,8 @@ int vlc_http_msg_add_cookies(struct vlc_http_msg *m,
     }
     return val;
 }
+
+bool vlc_http_msg_is_icy(struct vlc_http_msg *m)
+{
+    return m->icy;
+}
\ No newline at end of file
diff --git a/modules/access/http/message.h b/modules/access/http/message.h
index 08284a6..a42c0e3 100644
--- a/modules/access/http/message.h
+++ b/modules/access/http/message.h
@@ -384,3 +384,5 @@ struct vlc_h2_frame *vlc_http_msg_h2_frame(const struct vlc_http_msg *m,
  */
 struct vlc_http_msg *vlc_http_msg_h2_headers(unsigned count,
                                              const char *const headers[][2]);
+
+bool vlc_http_msg_is_icy(struct vlc_http_msg *m);
\ No newline at end of file
diff --git a/modules/access/http/resource.c b/modules/access/http/resource.c
index 34f0cc9..f3729a2 100644
--- a/modules/access/http/resource.c
+++ b/modules/access/http/resource.c
@@ -161,7 +161,7 @@ int vlc_http_res_init(struct vlc_http_resource *restrict res,
 
     if (!vlc_ascii_strcasecmp(url.psz_protocol, "https"))
         secure = true;
-    else if (!vlc_ascii_strcasecmp(url.psz_protocol, "http"))
+    else if (!vlc_ascii_strcasecmp(url.psz_protocol, "http") || !vlc_ascii_strcasecmp(url.psz_protocol, "icyx"))
         secure = false;
     else
     {
@@ -217,12 +217,6 @@ char *vlc_http_res_get_redirect(const struct vlc_http_resource *restrict res,
         if (vlc_http_msg_get_token(resp, "Pragma", "features") != NULL
          && asprintf(&url, "mmsh://%s%s", res->authority, res->path) >= 0)
             return url;
-
-        /* HACK: Seems like an ICY server. Redirect to ICYX scheme. */
-        if ((vlc_http_msg_get_header(resp, "Icy-Name") != NULL
-          || vlc_http_msg_get_header(resp, "Icy-Genre") != NULL)
-         && asprintf(&url, "icyx://%s%s", res->authority, res->path) >= 0)
-            return url;
     }
 
     /* TODO: if (status == 426 Upgrade Required) */
diff --git a/modules/stream_filter/Makefile.am b/modules/stream_filter/Makefile.am
index 064e229..9bb0ac9 100644
--- a/modules/stream_filter/Makefile.am
+++ b/modules/stream_filter/Makefile.am
@@ -35,6 +35,9 @@ stream_filter_LTLIBRARIES += libhds_plugin.la
 librecord_plugin_la_SOURCES = stream_filter/record.c
 stream_filter_LTLIBRARIES += librecord_plugin.la
 
+libicy_plugin_la_SOURCES = stream_filter/icy.c
+stream_filter_LTLIBRARIES += libicy_plugin.la
+
 libaribcam_plugin_la_SOURCES = stream_filter/aribcam.c
 libaribcam_plugin_la_CFLAGS = $(AM_CFLAGS) $(ARIBB25_CFLAGS)
 libaribcam_plugin_la_LDFLAGS = $(AM_LDFLAGS) $(ARIBB25_LDFLAGS) -rpath '$(stream_filterdir)'
diff --git a/modules/stream_filter/icy.c b/modules/stream_filter/icy.c
new file mode 100644
index 0000000..33b4a76
--- /dev/null
+++ b/modules/stream_filter/icy.c
@@ -0,0 +1,245 @@
+/*****************************************************************************
+ * icy.c: ICY metadata filter module for VLC
+ *****************************************************************************
+ * Copyright © 2016 Ludovic Fauvet
+ * Copyright © 2016 Videolabs
+ *
+ * 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 <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_stream.h>
+#include <vlc_input.h>
+#include <vlc_charset.h>
+
+static int  Open(vlc_object_t *);
+static void Close(vlc_object_t *);
+
+vlc_module_begin()
+    set_category(CAT_INPUT)
+    set_subcategory(SUBCAT_INPUT_STREAM_FILTER)
+    set_capability ("stream_filter", 0)
+    add_shortcut("icy")
+    set_description(N_("ICY metadata filter"))
+    set_callbacks(Open, Close)
+vlc_module_end()
+
+struct stream_sys_t
+{
+    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(stream_t *p_stream, size_t size, char *buffer)
+{
+    stream_sys_t *p_sys = p_stream->p_sys;
+
+    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' };
+            title++;
+            char *psz = strstr(title, closing);
+            if (!psz)
+                psz = strchr(title, ';');
+
+            if (psz) *psz = '\0';
+        }
+        else
+        {
+            char *psz = strchr(title, ';');
+            if (psz) *psz = '\0';
+        }
+
+        if (!p_sys->title || strcmp(p_sys->title, title))
+        {
+            free(p_sys->title);
+            p_sys->title = NULL;
+            char *psz_tmp = strdup(title);
+            if (unlikely(psz_tmp == NULL))
+                goto error;
+
+            p_sys->title = EnsureUTF8(psz_tmp);
+            if (!p_sys->title)
+                free(psz_tmp);
+
+            msg_Dbg(p_stream, "Icy-Title: %s", p_sys->title);
+
+            input_item_t *input_item = input_GetItem(p_stream->p_input);
+            if (input_item)
+            {
+                input_item_SetMeta(input_item, vlc_meta_NowPlaying, p_sys->title);
+            }
+        }
+    }
+
+error:
+    free(meta);
+}
+
+static int Seek(stream_t *p_stream, uint64_t offset)
+{
+    return stream_Seek(p_stream->p_source, offset);
+}
+
+static int Control(stream_t *p_stream, int i_query, va_list args)
+{
+    return stream_vaControl(p_stream->p_source, i_query, args);
+}
+
+static ssize_t Read(stream_t *p_stream, void *p_buf, size_t i_toread)
+{
+    stream_sys_t *p_sys = p_stream->p_sys;
+    uint8_t *p_dst = p_buf;
+    uint8_t *buf = p_buf;
+    size_t consume = 0;
+
+    if ( !p_dst || !i_toread )
+        return -1;
+
+    const size_t until_next_meta = p_sys->metaint - p_sys->metaint_offset;
+    const ssize_t i_srcread = stream_Read(p_stream->p_source, p_dst, i_toread);
+
+    if (i_srcread <= 0)
+    {
+        if (i_srcread < 0)
+        msg_Err(p_stream, "Reading %lu bytes from source failed: %zd ", i_toread, i_srcread);
+        return -1;
+    }
+
+    if (p_sys->pframe_remaining > 0)
+    {
+        /* Continuation of a previous metadata frame */
+        consume = __MIN(p_sys->pframe_remaining, (size_t)i_srcread);
+
+        if (likely(p_sys->pframe_buffer != NULL))
+            memcpy(p_sys->pframe_buffer+(p_sys->pframe_size-p_sys->pframe_remaining), buf, consume);
+
+        p_sys->pframe_remaining -= consume;
+        p_sys->metaint_offset += i_srcread - consume;
+
+        if (p_sys->pframe_remaining == 0)
+        {
+            /* End of chunked metadata frame */
+            vlc_http_icy_metadata_parse(p_stream, p_sys->pframe_size, p_sys->pframe_buffer);
+
+            free(p_sys->pframe_buffer);
+            p_sys->pframe_buffer = NULL;
+            p_sys->pframe_remaining = 0;
+            p_sys->pframe_size = 0;
+        }
+    }
+    else if (until_next_meta < (size_t)i_srcread)
+    {
+        /* Beginning of a new icy metadata frame */
+        uint8_t *p = buf = p_dst + until_next_meta;
+        size_t size = (*p) << 4;
+        char *meta = (char *)p+1;
+
+        consume = size + 1;
+
+        if (i_srcread - (until_next_meta + 1) < size)
+        {
+            /* The frame exceeds the current block */
+            consume = i_srcread - until_next_meta;
+
+            p_sys->pframe_size = size;
+            p_sys->pframe_remaining = size - (i_srcread - (until_next_meta + 1));
+
+            free(p_sys->pframe_buffer);
+            p_sys->pframe_buffer = malloc(size + 1);
+
+            if (likely(p_sys->pframe_buffer != NULL))
+                memcpy(p_sys->pframe_buffer, meta, i_srcread - until_next_meta);
+
+            p_sys->metaint_offset = 0;
+        }
+        else
+        {
+            /* Frame complete */
+            vlc_http_icy_metadata_parse(p_stream, size, meta);
+            p_sys->metaint_offset = i_srcread - (until_next_meta + consume);
+        }
+    }
+    else
+    {
+        /* No icy frame in sight, do some accounting */
+        p_sys->metaint_offset += i_srcread;
+    }
+
+    if (consume > 0)
+    {
+        memmove(buf, buf+consume, p_sys->metaint_offset);
+    }
+
+    return i_srcread - consume;
+}
+
+static int Open(vlc_object_t *p_object)
+{
+    stream_t *p_stream = (stream_t *) p_object;
+
+    int metaint = var_GetInteger(p_stream->p_input, "icy-metaint");
+    if (metaint <= 0)
+        return VLC_EGENERIC;
+
+    stream_sys_t *p_sys = p_stream->p_sys = calloc(1, sizeof(*p_sys));
+    if (p_sys == NULL)
+        return VLC_ENOMEM;
+
+    p_sys->metaint = metaint;
+    p_stream->pf_read = Read;
+    p_stream->pf_seek = Seek;
+    p_stream->pf_control = Control;
+
+    return VLC_SUCCESS;
+}
+
+static void Close(vlc_object_t *p_object)
+{
+    stream_t *p_stream = (stream_t *)p_object;
+    stream_sys_t *p_sys = p_stream->p_sys;
+
+    free(p_sys->pframe_buffer);
+    free(p_sys->title);
+    free(p_sys);
+}
-- 
2.7.2



More information about the vlc-devel mailing list