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

Denis Charmet typx at dinauz.org
Wed Feb 17 18:50:56 CET 2016


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'  ?:)

> +            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?

> +            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]?

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

Again

> +            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.

> +        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 :)

> +    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


More information about the vlc-devel mailing list