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

Ludovic Fauvet etix at videolan.org
Wed Feb 17 19:42:28 CET 2016


On Wed, Feb 17, 2016, at 18:50, Denis Charmet wrote:
> Hi,
> 
> On 2016-02-17 17:36, Ludovic Fauvet wrote:
> > 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);
> 
> Ok for the pure sake of nitpicking what if you get a buggy header and 
> title[0] is the last '\0'  ?:)

Then we shouldn't be in this scope but in the "else". Considering this,
the worst that can happen would be to add a useless \0 somewhere after
the first \0 but still in the allocated space.

> > +            if (!psz)
> > +                psz = strchr( &title[1], ';' );
> > +
> > +            if (psz) *psz = '\0';
> > +        }
> > +        else
> > +        {
> > +            char *psz = strchr( &title[1], ';' );
> 
> Why ignoring title[0] if it's not a quotation sign?

Good catch, that's a mistake and it needs to be fixed in the legacy http
module too.
This might also be simplified if there's no known implementation still
skipping quotes.

> > +            if (psz) *psz = '\0';
> > +        }
> > +
> > +        if (!icy->title || strcmp(icy->title, &title[1]))
> 
> Same question than previously. In the "else" case shouldn't you use 
> &title[0]?

Ditto.

> > +        {
> > +            free(icy->title);
> > +            icy->title = NULL;
> > +            char *psz_tmp = strdup(&title[1]);
> 
> Again

Ditto.

> 
> > +            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;
> 
> Maybe check for integer overflow.

Holy guacamole! This is supposed to be a size_t, or at least an
uint16_t. During development I used an unsigned int and did this stupid
mistake during cleanup.

> > +        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);
> > +    }
> > +
> 
> Maybe check input_item here once and for all instead of everytime 
> beneath :)

My point here was to, at least, print the debug messages (even if the
meta cannot be set on the 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
> 
> Regards,
> -- 
> Denis Charmet - TypX
> Le mauvais esprit est un art de vivre

Thanks Denis.

-- 
Ludovic Fauvet
www.videolan.org


More information about the vlc-devel mailing list