[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