[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