From ea29a8d98884305977d658a9e25ef716bfbb501e Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sun, 29 Mar 2015 00:29:06 +0100 Subject: [PATCH] Remove filescanner_icy.c and consolidate in http.c. Libav 9 does not support ICY metadata, so in that case we must be able to get it outselves. --- src/Makefile.am | 2 +- src/filescanner.c | 4 - src/filescanner_ffmpeg.c | 6 +- src/filescanner_icy.c | 348 --------------------------------------- src/http.c | 244 ++++++++++++++++++++++----- src/http.h | 29 ++-- 6 files changed, 229 insertions(+), 404 deletions(-) delete mode 100644 src/filescanner_icy.c diff --git a/src/Makefile.am b/src/Makefile.am index 8685f87c..ff779f86 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -101,7 +101,7 @@ forked_daapd_SOURCES = main.c \ conffile.c conffile.h \ cache.c cache.h \ filescanner.c filescanner.h \ - filescanner_ffmpeg.c filescanner_playlist.c filescanner_icy.c $(ITUNES_SRC) \ + filescanner_ffmpeg.c filescanner_playlist.c $(ITUNES_SRC) \ mdns_avahi.c mdns.h \ remote_pairing.c remote_pairing.h \ $(EVHTTP_SRC) \ diff --git a/src/filescanner.c b/src/filescanner.c index d4cb80b3..515d5f69 100644 --- a/src/filescanner.c +++ b/src/filescanner.c @@ -693,11 +693,7 @@ filescanner_process_media(char *path, time_t mtime, off_t size, int type, struct else if (type & F_SCAN_TYPE_URL) { mfi->data_kind = 1; /* url/stream */ -#if LIBAVFORMAT_VERSION_MAJOR >= 56 || (LIBAVFORMAT_VERSION_MAJOR == 55 && LIBAVFORMAT_VERSION_MINOR >= 13) ret = scan_metadata_ffmpeg(path, mfi); -#else - ret = scan_metadata_icy(path, mfi); -#endif } else if (type & F_SCAN_TYPE_SPOTIFY) { diff --git a/src/filescanner_ffmpeg.c b/src/filescanner_ffmpeg.c index 3c8637f9..db28e47f 100644 --- a/src/filescanner_ffmpeg.c +++ b/src/filescanner_ffmpeg.c @@ -498,7 +498,7 @@ scan_metadata_ffmpeg(char *file, struct media_file_info *mfi) icy_metadata = http_icy_metadata_get(ctx, 0); if (icy_metadata && icy_metadata->name) { - DPRINTF(E_DBG, L_SCAN, "libav/ffmpeg found ICY metadata, name is '%s'\n", icy_metadata->name); + DPRINTF(E_DBG, L_SCAN, "Found ICY metadata, name is '%s'\n", icy_metadata->name); if (mfi->title) free(mfi->title); @@ -513,7 +513,7 @@ scan_metadata_ffmpeg(char *file, struct media_file_info *mfi) } if (icy_metadata && icy_metadata->description) { - DPRINTF(E_DBG, L_SCAN, "libav/ffmpeg found ICY metadata, description is '%s'\n", icy_metadata->description); + DPRINTF(E_DBG, L_SCAN, "Found ICY metadata, description is '%s'\n", icy_metadata->description); if (mfi->album) free(mfi->album); @@ -522,7 +522,7 @@ scan_metadata_ffmpeg(char *file, struct media_file_info *mfi) } if (icy_metadata && icy_metadata->genre) { - DPRINTF(E_DBG, L_SCAN, "libav/ffmpeg found ICY metadata, genre is '%s'\n", icy_metadata->genre); + DPRINTF(E_DBG, L_SCAN, "Found ICY metadata, genre is '%s'\n", icy_metadata->genre); if (mfi->genre) free(mfi->genre); diff --git a/src/filescanner_icy.c b/src/filescanner_icy.c deleted file mode 100644 index 23507963..00000000 --- a/src/filescanner_icy.c +++ /dev/null @@ -1,348 +0,0 @@ -/* - * Copyright (C) 2009-2010 Julien BLACHE - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#ifdef HAVE_CONFIG_H -# include -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) -#include -#endif - -#include -#if defined HAVE_LIBEVENT2 -# include -#else -# include "evhttp/evhttp_compat.h" -#endif - -#include - -#include "logger.h" -#include "filescanner.h" -#include "misc.h" - -#define ICY_TIMEOUT 3 - -enum icy_request_status { ICY_INIT, ICY_WAITING, ICY_DONE }; - -static enum icy_request_status status; - -/* TODO why doesn't evbase_scan work... */ -extern struct event_base *evbase_main; - -struct icy_ctx -{ - char *url; - char address[INET6_ADDRSTRLEN]; - char hostname[PATH_MAX]; - char path[PATH_MAX]; - int port; - - char *icy_name; - char *icy_description; - char *icy_genre; - - pthread_mutex_t lck; - pthread_cond_t cond; -}; - -#ifndef HAVE_LIBEVENT2 -static int -resolve_address(char *hostname, char *s, size_t maxlen) -{ - struct addrinfo *result; - int ret; - - ret = getaddrinfo(hostname, NULL, NULL, &result); - if (ret != 0) - return -1; - - switch(result->ai_addr->sa_family) - { - case AF_INET: - inet_ntop(AF_INET, &(((struct sockaddr_in *)result->ai_addr)->sin_addr), s, maxlen); - break; - - case AF_INET6: - inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)result->ai_addr)->sin6_addr), s, maxlen); - break; - - default: - strncpy(s, "Unknown AF", maxlen); - freeaddrinfo(result); - return -1; - } - - freeaddrinfo(result); - return 0; -} -#endif - -#ifndef HAVE_LIBEVENT2_OLD -static void -scan_icy_request_cb(struct evhttp_request *req, void *arg) -{ - struct icy_ctx *ctx; - - ctx = (struct icy_ctx *)arg; - - pthread_mutex_lock(&ctx->lck); - - DPRINTF(E_DBG, L_SCAN, "ICY metadata request: Signal callback\n"); - - status = ICY_DONE; - pthread_cond_signal(&ctx->cond); - pthread_mutex_unlock(&ctx->lck); -} - -/* Will always return -1 to make evhttp close the connection - we only need the http headers */ -static int -scan_icy_header_cb(struct evhttp_request *req, void *arg) -{ - struct icy_ctx *ctx; - struct evkeyvalq *headers; - const char *ptr; - - ctx = (struct icy_ctx *)arg; - - DPRINTF(E_DBG, L_SCAN, "ICY metadata request: Headers received\n"); - - headers = evhttp_request_get_input_headers(req); - if ( (ptr = evhttp_find_header(headers, "icy-name")) ) - { - ctx->icy_name = strdup(ptr); - DPRINTF(E_DBG, L_SCAN, "Found ICY metadata, name is %s\n", ctx->icy_name); - } - if ( (ptr = evhttp_find_header(headers, "icy-description")) ) - { - ctx->icy_description = strdup(ptr); - DPRINTF(E_DBG, L_SCAN, "Found ICY metadata, description is %s\n", ctx->icy_description); - } - if ( (ptr = evhttp_find_header(headers, "icy-genre")) ) - { - ctx->icy_genre = strdup(ptr); - DPRINTF(E_DBG, L_SCAN, "Found ICY metadata, genre is %s\n", ctx->icy_genre); - } - - return -1; -} -#endif - -int -scan_metadata_icy(char *url, struct media_file_info *mfi) -{ - struct icy_ctx *ctx; - struct evhttp_connection *evcon; -#ifndef HAVE_LIBEVENT2_OLD - struct evhttp_request *req; - struct evkeyvalq *headers; - char s[PATH_MAX]; -#endif - time_t start; - time_t end; - int ret; - - status = ICY_INIT; - start = time(NULL); - - /* We can set this straight away */ - mfi->url = strdup(url); - - ctx = (struct icy_ctx *)malloc(sizeof(struct icy_ctx)); - if (!ctx) - { - DPRINTF(E_LOG, L_SCAN, "Out of memory for ICY metadata context\n"); - - return -1; - } - memset(ctx, 0, sizeof(struct icy_ctx)); - - pthread_mutex_init(&ctx->lck, NULL); - pthread_cond_init(&ctx->cond, NULL); - - ctx->url = url; - - /* TODO https */ - av_url_split(NULL, 0, NULL, 0, ctx->hostname, sizeof(ctx->hostname), &ctx->port, ctx->path, sizeof(ctx->path), ctx->url); - if ((!ctx->hostname) || (strlen(ctx->hostname) == 0)) - { - DPRINTF(E_LOG, L_SCAN, "Error extracting hostname from playlist URL: %s\n", ctx->url); - - return -1; - } - - if (ctx->port < 0) - ctx->port = 80; - - if (strlen(ctx->path) == 0) - { - ctx->path[0] = '/'; - ctx->path[1] = '\0'; - } - -#ifdef HAVE_LIBEVENT2 - evcon = evhttp_connection_base_new(evbase_main, NULL, ctx->hostname, (unsigned short)ctx->port); - if (!evcon) - { - DPRINTF(E_LOG, L_SCAN, "Could not create connection to %s\n", ctx->hostname); - - goto no_icy; - } -#else - /* Resolve IP address */ - ret = resolve_address(ctx->hostname, ctx->address, sizeof(ctx->address)); - if (ret < 0) - { - DPRINTF(E_LOG, L_SCAN, "Could not find IP address of %s\n", ctx->hostname); - - return -1; - } - - DPRINTF(E_DBG, L_SCAN, "URL %s converted to hostname %s, port %d, path %s, IP %s\n", ctx->url, ctx->hostname, ctx->port, ctx->path, ctx->address); - - /* Set up connection */ - evcon = evhttp_connection_new(ctx->address, (unsigned short)ctx->port); - if (!evcon) - { - DPRINTF(E_LOG, L_SCAN, "Could not create connection to %s\n", ctx->hostname); - - goto no_icy; - } - evhttp_connection_set_base(evcon, evbase_main); -#endif - -#ifdef HAVE_LIBEVENT2_OLD - DPRINTF(E_LOG, L_SCAN, "Skipping Shoutcast metadata request for %s (requires libevent>=2.1.4 or libav 10)\n", ctx->hostname); -#else - evhttp_connection_set_timeout(evcon, ICY_TIMEOUT); - - /* Set up request */ - req = evhttp_request_new(scan_icy_request_cb, ctx); - if (!req) - { - DPRINTF(E_LOG, L_SCAN, "Could not create request to %s\n", ctx->hostname); - - goto no_icy; - } - - evhttp_request_set_header_cb(req, scan_icy_header_cb); - - headers = evhttp_request_get_output_headers(req); - snprintf(s, PATH_MAX, "%s:%d", ctx->hostname, ctx->port); - evhttp_add_header(headers, "Host", s); - evhttp_add_header(headers, "Icy-MetaData", "1"); - - /* Make request */ - DPRINTF(E_INFO, L_SCAN, "Making request to %s asking for ICY (Shoutcast) metadata\n", ctx->hostname); - - status = ICY_WAITING; - ret = evhttp_make_request(evcon, req, EVHTTP_REQ_GET, ctx->path); - if (ret < 0) - { - DPRINTF(E_LOG, L_SCAN, "Error making request to %s\n", ctx->hostname); - - status = ICY_DONE; - goto no_icy; - } -#endif - - /* Can't count on server support for ICY metadata, so - * while waiting for a reply make a parallel call to scan_metadata_ffmpeg. - */ - no_icy: - ret = scan_metadata_ffmpeg(url, mfi); - if (ret < 0) - { - DPRINTF(E_LOG, L_SCAN, "Playlist URL is unavailable for probe/metadata, assuming MP3 encoding\n"); - mfi->type = strdup("mp3"); - mfi->codectype = strdup("mpeg"); - mfi->description = strdup("MPEG audio file"); - } - - /* Wait for ICY request to complete or timeout */ - pthread_mutex_lock(&ctx->lck); - - if (status == ICY_WAITING) - pthread_cond_wait(&ctx->cond, &ctx->lck); - - pthread_mutex_unlock(&ctx->lck); - - /* Copy result to mfi */ - if (ctx->icy_name) - { - if (mfi->title) - free(mfi->title); - if (mfi->artist) - free(mfi->artist); - if (mfi->album_artist) - free(mfi->album_artist); - - mfi->title = strdup(ctx->icy_name); - mfi->artist = strdup(ctx->icy_name); - mfi->album_artist = strdup(ctx->icy_name); - - free(ctx->icy_name); - } - - if (ctx->icy_description) - { - if (mfi->album) - free(mfi->album); - - mfi->album = ctx->icy_description; - } - - if (ctx->icy_genre) - { - if (mfi->genre) - free(mfi->genre); - - mfi->genre = ctx->icy_genre; - } - - /* Clean up */ - if (evcon) - evhttp_connection_free(evcon); - - pthread_cond_destroy(&ctx->cond); - pthread_mutex_destroy(&ctx->lck); - free(ctx); - - end = time(NULL); - - DPRINTF(E_DBG, L_SCAN, "ICY metadata scan of %s completed in %.f sec\n", url, difftime(end, start)); - - return 1; -} diff --git a/src/http.c b/src/http.c index cb71fd39..ec8694f3 100644 --- a/src/http.c +++ b/src/http.c @@ -46,6 +46,38 @@ // Number of seconds the client will wait for a response before aborting #define HTTP_CLIENT_TIMEOUT 5 +/* The strict libevent api does not permit walking through an evkeyvalq and saving + * all the http headers, so we predefine what we are looking for. You can add + * extra headers here that you would like to save. + */ +static char *header_list[] = +{ + "icy-name", + "icy-description", + "icy-metaint", + "icy-genre", +}; + +/* Copies headers we are searching for from one keyval struct to another + * + */ +static void +headers_save(struct keyval *kv, struct evkeyvalq *headers) +{ + const char *value; + int i; + + if (!kv || !headers) + return; + + for (i = 0; i < (sizeof(header_list) / sizeof(header_list[0])); i++) + { + if ( (value = evhttp_find_header(headers, header_list[i])) ) + keyval_add(kv, header_list[i], value); + } + +} + static void request_cb(struct evhttp_request *req, void *arg) { @@ -55,15 +87,28 @@ request_cb(struct evhttp_request *req, void *arg) ctx = (struct http_client_ctx *)arg; - response_code = evhttp_request_get_response_code(req); - response_code_line = evhttp_request_get_response_code_line(req); + if (ctx->headers_only) + { + ctx->ret = 0; - if (req == NULL) + event_base_loopbreak(ctx->evbase); + + if (ctx->async) + free(ctx); + + return; + } + + if (!req) { DPRINTF(E_LOG, L_HTTP, "Connection to %s failed: Connection timed out\n", ctx->url); goto connection_error; } - else if (response_code == 0) + + response_code = evhttp_request_get_response_code(req); + response_code_line = evhttp_request_get_response_code_line(req); + + if (response_code == 0) { DPRINTF(E_LOG, L_HTTP, "Connection to %s failed: Connection refused\n", ctx->url); goto connection_error; @@ -77,10 +122,15 @@ request_cb(struct evhttp_request *req, void *arg) /* Async: we make a callback to caller, Sync: we move the body into the callers evbuf */ ctx->ret = 0; - if (ctx->async) - ctx->cb(req, arg); + if (!ctx->async) + { + if (ctx->headers) + headers_save(ctx->headers, evhttp_request_get_input_headers(req)); + if (ctx->body) + evbuffer_add_buffer(ctx->body, evhttp_request_get_input_buffer(req)); + } else - evbuffer_add_buffer(ctx->evbuf, evhttp_request_get_input_buffer(req)); + ctx->cb(req, arg); event_base_loopbreak(ctx->evbase); @@ -101,6 +151,31 @@ request_cb(struct evhttp_request *req, void *arg) return; } +/* This callback is only invoked if ctx->headers_only is set. Since that means + * we only want headers, it will always return -1 to make evhttp close the + * connection. The headers will be saved in a keyval struct in ctx, since we + * cannot address the *evkeyvalq after the connection is free'd. + */ +#ifndef HAVE_LIBEVENT2_OLD +static int +request_header_cb(struct evhttp_request *req, void *arg) +{ + struct http_client_ctx *ctx; + + ctx = (struct http_client_ctx *)arg; + + if (!ctx->headers) + { + DPRINTF(E_LOG, L_HTTP, "BUG: Header callback invoked but caller did not say where to save the headers\n"); + return -1; + } + + headers_save(ctx->headers, evhttp_request_get_input_headers(req)); + + return -1; +} +#endif + static void * request_make(void *arg) { @@ -165,19 +240,25 @@ request_make(void *arg) return NULL; } +#ifndef HAVE_LIBEVENT2_OLD + if (ctx->headers_only) + evhttp_request_set_header_cb(req, request_header_cb); +#endif + headers = evhttp_request_get_output_headers(req); snprintf(s, PATH_MAX, "%s:%d", hostname, port); evhttp_add_header(headers, "Host", s); evhttp_add_header(headers, "Content-Length", "0"); evhttp_add_header(headers, "User-Agent", "forked-daapd/" VERSION); + evhttp_add_header(headers, "Icy-MetaData", "1"); /* Make request */ - DPRINTF(E_INFO, L_HTTP, "Making request to %s asking for playlist\n", hostname); + DPRINTF(E_INFO, L_HTTP, "Making request to %s:%d\n", hostname, port); ret = evhttp_make_request(evcon, req, EVHTTP_REQ_GET, path); if (ret < 0) { - DPRINTF(E_LOG, L_HTTP, "Error making http request to %s\n", hostname); + DPRINTF(E_LOG, L_HTTP, "Error making http request to %s:%d\n", hostname, port); evhttp_connection_free(evcon); event_base_free(ctx->evbase); @@ -254,7 +335,7 @@ http_stream_setup(char **stream, const char *url) ctx.async = 0; ctx.url = url; - ctx.evbuf = evbuf; + ctx.body = evbuf; ret = http_client_request(&ctx); if (ret < 0) @@ -269,7 +350,7 @@ http_stream_setup(char **stream, const char *url) * nothing is found in the first 10 lines */ n = 0; - while ((line = evbuffer_readln(ctx.evbuf, NULL, EVBUFFER_EOL_ANY)) && (n < 10)) + while ((line = evbuffer_readln(ctx.body, NULL, EVBUFFER_EOL_ANY)) && (n < 10)) { n++; if (strncasecmp(line, "http://", strlen("http://")) == 0) @@ -283,7 +364,7 @@ http_stream_setup(char **stream, const char *url) free(line); } - evbuffer_free(ctx.evbuf); + evbuffer_free(ctx.body); if (n != -1) { @@ -300,6 +381,8 @@ http_stream_setup(char **stream, const char *url) /* ======================= ICY metadata handling =============================*/ + +#if LIBAVFORMAT_VERSION_MAJOR >= 56 || (LIBAVFORMAT_VERSION_MAJOR == 55 && LIBAVFORMAT_VERSION_MINOR >= 13) static int metadata_packet_get(struct http_icy_metadata *metadata, AVFormatContext *fmtctx) { @@ -404,31 +487,6 @@ metadata_header_get(struct http_icy_metadata *metadata, AVFormatContext *fmtctx) return 0; } -void -http_icy_metadata_free(struct http_icy_metadata *metadata) -{ - if (metadata->name) - free(metadata->name); - - if (metadata->description) - free(metadata->description); - - if (metadata->genre) - free(metadata->genre); - - if (metadata->title) - free(metadata->title); - - if (metadata->artist) - free(metadata->artist); - - if (metadata->artwork_url) - free(metadata->artwork_url); - - free(metadata); -} - -#if LIBAVFORMAT_VERSION_MAJOR >= 56 || (LIBAVFORMAT_VERSION_MAJOR == 55 && LIBAVFORMAT_VERSION_MINOR >= 13) struct http_icy_metadata * http_icy_metadata_get(AVFormatContext *fmtctx, int packet_only) { @@ -462,11 +520,121 @@ http_icy_metadata_get(AVFormatContext *fmtctx, int packet_only) */ return metadata; } -#else + +#elif defined(HAVE_LIBEVENT2_OLD) struct http_icy_metadata * http_icy_metadata_get(AVFormatContext *fmtctx, int packet_only) { + DPRINTF(E_INFO, L_HTTP, "Skipping Shoutcast metadata request for %s (requires libevent>=2.1.4 or libav 10)\n", fmtctx->filename); return NULL; } + +#else +/* Earlier versions of ffmpeg/libav do not seem to allow access to the http + * headers, so we must instead open the stream ourselves to get the metadata. + * Sorry about the extra connections, you radio streaming people! + * + * TODO: Get packet metadata from fmtctx->packet_buffer + */ +struct http_icy_metadata * +http_icy_metadata_get(AVFormatContext *fmtctx, int packet_only) +{ + struct http_icy_metadata *metadata; + struct http_client_ctx ctx; + struct keyval *kv; + const char *value; + int got_header; + int ret; + + /* Can only get header metadata at the moment */ + if (packet_only) + return NULL; + + kv = keyval_alloc(); + if (!kv) + return NULL; + + memset(&ctx, 0, sizeof(struct http_client_ctx)); + ctx.async = 0; + ctx.url = fmtctx->filename; + ctx.headers = kv; + ctx.headers_only = 1; + ctx.body = NULL; + + ret = http_client_request(&ctx); + if (ret < 0) + { + DPRINTF(E_LOG, L_HTTP, "Error fetching %s\n", fmtctx->filename); + + free(kv); + return NULL; + } + + metadata = malloc(sizeof(struct http_icy_metadata)); + if (!metadata) + return NULL; + memset(metadata, 0, sizeof(struct http_icy_metadata)); + + got_header = 0; + if ( (value = keyval_get(ctx.headers, "icy-name")) ) + { + metadata->name = strdup(value); + got_header = 1; + } + if ( (value = keyval_get(ctx.headers, "icy-description")) ) + { + metadata->description = strdup(value); + got_header = 1; + } + if ( (value = keyval_get(ctx.headers, "icy-genre")) ) + { + metadata->genre = strdup(value); + got_header = 1; + } + + keyval_clear(kv); + free(kv); + + if (!got_header) + { + free(metadata); + return NULL; + } + +/* DPRINTF(E_DBG, L_HTTP, "Found ICY: N %s, D %s, G %s, T %s, A %s, U %s, I %" PRIu32 "\n", + metadata->name, + metadata->description, + metadata->genre, + metadata->title, + metadata->artist, + metadata->artwork_url, + metadata->hash + );*/ + + return metadata; +} #endif +void +http_icy_metadata_free(struct http_icy_metadata *metadata) +{ + if (metadata->name) + free(metadata->name); + + if (metadata->description) + free(metadata->description); + + if (metadata->genre) + free(metadata->genre); + + if (metadata->title) + free(metadata->title); + + if (metadata->artist) + free(metadata->artist); + + if (metadata->artwork_url) + free(metadata->artwork_url); + + free(metadata); +} diff --git a/src/http.h b/src/http.h index 70aa83f9..0f36da0f 100644 --- a/src/http.h +++ b/src/http.h @@ -4,6 +4,7 @@ #include #include +#include "misc.h" #include @@ -13,8 +14,17 @@ struct http_client_ctx const char *url; int ret; - /* For sync mode */ - struct evbuffer *evbuf; + /* For sync mode, a keyval/evbuf to store response headers and body + * Can be set to NULL to ignore that part of the response + */ + struct keyval *headers; + struct evbuffer *body; + + /* Cut the connection after the headers have been received + * Used for getting Shoutcast/ICY headers for old versions of libav/ffmpeg + * (requires libevent 1 or 2.1.4+) + */ + int headers_only; /* For async mode */ void (*cb)(struct evhttp_request *, void *); @@ -60,14 +70,6 @@ int http_stream_setup(char **stream, const char *url); -/* Frees a ICY metadata struct - * - * @param metadata struct to free - */ -void -http_icy_metadata_free(struct http_icy_metadata *metadata); - - /* Extracts ICY header and packet metadata (requires libav 10) * * example header metadata (standard http header format): @@ -86,4 +88,11 @@ struct http_icy_metadata * http_icy_metadata_get(AVFormatContext *fmtctx, int packet_only); +/* Frees an ICY metadata struct + * + * @param metadata struct to free + */ +void +http_icy_metadata_free(struct http_icy_metadata *metadata); + #endif /* !__HTTP_H__ */