From 8075c3d8bb1f6aade0cc7c5c40db9bc1bcd84cab Mon Sep 17 00:00:00 2001 From: Luca Barbato Date: Mon, 10 Mar 2014 21:11:35 +0100 Subject: [PATCH] http: Add support reading ICY metadata Export the metadata as a icy_metadata_packet avoption. Based on the work of wm4 and Alessandro Ghedini. Bug-Id: https://bugs.debian.org/739936 Signed-off-by: Luca Barbato --- doc/protocols.texi | 20 +++++++++ libavformat/http.c | 104 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 123 insertions(+), 1 deletion(-) diff --git a/doc/protocols.texi b/doc/protocols.texi index 1a9f5755a0..5d7a1a6d3e 100644 --- a/doc/protocols.texi +++ b/doc/protocols.texi @@ -89,6 +89,26 @@ m3u8 files. HTTP (Hyper Text Transfer Protocol). +This protocol accepts the following options: + +@table @option +@item icy +If set to 1 request ICY (SHOUTcast) metadata from the server. If the server +supports this, the metadata has to be retrieved by the application by reading +the @option{icy_metadata_headers} and @option{icy_metadata_packet} options. +The default is 0. + +@item icy_metadata_headers +If the server supports ICY metadata, this contains the ICY-specific HTTP reply +headers, separated by newline characters. + +@item icy_metadata_packet +If the server supports ICY metadata, and @option{icy} was set to 1, this +contains the last non-empty metadata packet sent by the server. It should be +polled in regular intervals by applications interested in mid-stream metadata +updates. +@end table + @section mmst MMS (Microsoft Media Server) protocol over TCP. diff --git a/libavformat/http.c b/libavformat/http.c index 161bb6bf49..41b1f1ba20 100644 --- a/libavformat/http.c +++ b/libavformat/http.c @@ -68,6 +68,13 @@ typedef struct { int multiple_requests; uint8_t *post_data; int post_datalen; + int icy; + /* how much data was read since the last ICY metadata packet */ + int icy_data_read; + /* after how many bytes of read data a new metadata packet will be found */ + int icy_metaint; + char *icy_metadata_headers; + char *icy_metadata_packet; #if CONFIG_ZLIB int compressed; z_stream inflate_stream; @@ -85,6 +92,9 @@ static const AVOption options[] = { {"headers", "custom HTTP headers, can override built in default headers", OFFSET(headers), AV_OPT_TYPE_STRING, { 0 }, 0, 0, D|E }, {"multiple_requests", "use persistent connections", OFFSET(multiple_requests), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, D|E }, {"post_data", "custom HTTP post data", OFFSET(post_data), AV_OPT_TYPE_BINARY, .flags = D|E }, +{"icy", "request ICY metadata", OFFSET(icy), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, D }, +{"icy_metadata_headers", "return ICY metadata headers", OFFSET(icy_metadata_headers), AV_OPT_TYPE_STRING, {0}, 0, 0, AV_OPT_FLAG_EXPORT }, +{"icy_metadata_packet", "return current ICY metadata packet", OFFSET(icy_metadata_packet), AV_OPT_TYPE_STRING, {0}, 0, 0, AV_OPT_FLAG_EXPORT }, {"auth_type", "HTTP authentication type", OFFSET(auth_state.auth_type), AV_OPT_TYPE_INT, {.i64 = HTTP_AUTH_NONE}, HTTP_AUTH_NONE, HTTP_AUTH_BASIC, D|E, "auth_type" }, {"none", "No auth method set, autodetect", 0, AV_OPT_TYPE_CONST, {.i64 = HTTP_AUTH_NONE}, 0, 0, D|E, "auth_type" }, {"basic", "HTTP basic authentication", 0, AV_OPT_TYPE_CONST, {.i64 = HTTP_AUTH_BASIC}, 0, 0, D|E, "auth_type" }, @@ -225,6 +235,7 @@ int ff_http_do_new_request(URLContext *h, const char *uri) int ret; s->off = 0; + s->icy_data_read = 0; av_free(s->location); s->location = av_strdup(uri); if (!s->location) @@ -380,6 +391,23 @@ static int parse_content_encoding(URLContext *h, char *p) return 0; } +// Concat all Icy- header lines +static int parse_icy(HTTPContext *s, const char *tag, const char *p) +{ + int len = 4 + strlen(p) + strlen(tag); + int ret; + + if (s->icy_metadata_headers) + len += strlen(s->icy_metadata_headers); + + if ((ret = av_reallocp(&s->icy_metadata_headers, len)) < 0) + return ret; + + av_strlcatf(s->icy_metadata_headers, len, "%s: %s\n", tag, p); + + return 0; +} + static int process_line(URLContext *h, char *line, int line_count, int *new_location) { @@ -440,6 +468,11 @@ static int process_line(URLContext *h, char *line, int line_count, } else if (!av_strcasecmp(tag, "Connection")) { if (!strcmp(p, "close")) s->willclose = 1; + } else if (!av_strcasecmp (tag, "Icy-MetaInt")) { + s->icy_metaint = strtoll(p, NULL, 10); + } else if (!av_strncasecmp(tag, "Icy-", 4)) { + if ((ret = parse_icy(s, tag, p)) < 0) + return ret; } else if (!av_strcasecmp(tag, "Content-Encoding")) { if ((ret = parse_content_encoding(h, p)) < 0) return ret; @@ -552,6 +585,10 @@ static int http_connect(URLContext *h, const char *path, const char *local_path, if (!has_header(s->headers, "\r\nContent-Length: ") && s->post_data) len += av_strlcatf(headers + len, sizeof(headers) - len, "Content-Length: %d\r\n", s->post_datalen); + if (!has_header(s->headers, "\r\nIcy-MetaData: ") && s->icy) { + len += av_strlcatf(headers + len, sizeof(headers) - len, + "Icy-MetaData: %d\r\n", 1); + } /* now add in custom headers */ if (s->headers) @@ -585,6 +622,7 @@ static int http_connect(URLContext *h, const char *path, const char *local_path, s->buf_end = s->buffer; s->line_count = 0; s->off = 0; + s->icy_data_read = 0; s->filesize = -1; s->willclose = 0; s->end_chunked_post = 0; @@ -662,7 +700,7 @@ static int http_buf_read_compressed(URLContext *h, uint8_t *buf, int size) } #endif -static int http_read(URLContext *h, uint8_t *buf, int size) +static int http_read_stream(URLContext *h, uint8_t *buf, int size) { HTTPContext *s = h->priv_data; int err, new_location; @@ -704,6 +742,70 @@ static int http_read(URLContext *h, uint8_t *buf, int size) return http_buf_read(h, buf, size); } +static int http_read_stream_all(URLContext *h, uint8_t *buf, int size) +{ + int pos = 0; + while (pos < size) { + int len = http_read_stream(h, buf + pos, size - pos); + if (len < 0) + return len; + pos += len; + } + return pos; +} + +static int store_icy(URLContext *h, int size) +{ + HTTPContext *s = h->priv_data; + /* until next metadata packet */ + int remaining = s->icy_metaint - s->icy_data_read; + + if (remaining < 0) + return AVERROR_INVALIDDATA; + + if (!remaining) { + // The metadata packet is variable sized. It has a 1 byte header + // which sets the length of the packet (divided by 16). If it's 0, + // the metadata doesn't change. After the packet, icy_metaint bytes + // of normal data follow. + uint8_t ch; + int len = http_read_stream_all(h, &ch, 1); + if (len < 0) + return len; + if (ch > 0) { + char data[255 * 16 + 1]; + int ret; + len = ch * 16; + ret = http_read_stream_all(h, data, len); + if (ret < 0) + return ret; + data[len + 1] = 0; + if ((ret = av_opt_set(s, "icy_metadata_packet", data, 0)) < 0) + return ret; + } + s->icy_data_read = 0; + remaining = s->icy_metaint; + } + + return FFMIN(size, remaining); +} + +static int http_read(URLContext *h, uint8_t *buf, int size) +{ + HTTPContext *s = h->priv_data; + + if (s->icy_metaint > 0) { + size = store_icy(h, size); + if (size < 0) + return size; + } + + size = http_read_stream(h, buf, size); + if (size > 0) + s->icy_data_read += size; + return size; +} + /* used only when posting data */ static int http_write(URLContext *h, const uint8_t *buf, int size) {