Commit a92fbe16 authored by wm4's avatar wm4 Committed by Stefano Sabatini

lavf/http: add support for reading streamcast metadata

Allow applications to request reading streamcast metadata. This uses
AVOptions as API, and requires the application to explicitly request
and read metadata. Metadata can be updated mid-stream; if an
application is interested in that, it has to poll for the data by
reading the "icy_metadata_packet" option in regular intervals.

There doesn't seem to be a nice way to transfer the metadata in a nicer
way. Converting the metadata to ID3v2 tags might be a nice idea, but
the libavformat mp3 demuxer doesn't seem to read these tags mid-stream,
and even then we couldn't guarantee that tags are not inserted in the
middle of mp3 packet data.

This commit provides the minimum to enable applications to retrieve
this information at all.
Signed-off-by: 's avatarStefano Sabatini <stefasab@gmail.com>
parent bf87908c
......@@ -228,6 +228,20 @@ not specified.
@item mime_type
Set MIME type.
@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 with 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.
@item cookies
Set the cookies to be sent in future requests. The format of each cookie is the
same as the value of a Set-Cookie HTTP response field. Multiple cookies can be
......
......@@ -49,6 +49,8 @@ typedef struct {
char *content_type;
char *user_agent;
int64_t off, filesize;
int icy_data_read; ///< how much data was read since last ICY metadata packet
int icy_metaint; ///< after how many bytes of read data a new metadata packet will be found
char location[MAX_URL_SIZE];
HTTPAuthState auth_state;
HTTPAuthState proxy_auth_state;
......@@ -65,6 +67,9 @@ typedef struct {
int rw_timeout;
char *mime_type;
char *cookies; ///< holds newline (\n) delimited Set-Cookie header field values (without the "Set-Cookie: " field name)
int icy;
char *icy_metadata_headers;
char *icy_metadata_packet;
} HTTPContext;
#define OFFSET(x) offsetof(HTTPContext, x)
......@@ -82,6 +87,9 @@ static const AVOption options[] = {
{"timeout", "set timeout of socket I/O operations", OFFSET(rw_timeout), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, D|E },
{"mime_type", "set MIME type", OFFSET(mime_type), AV_OPT_TYPE_STRING, {0}, 0, 0, 0 },
{"cookies", "set cookies to be sent in applicable future requests, use newline delimited Set-Cookie HTTP field value syntax", OFFSET(cookies), AV_OPT_TYPE_STRING, {0}, 0, 0, 0 },
{"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, 0 },
{"icy_metadata_packet", "return current ICY metadata packet", OFFSET(icy_metadata_packet), AV_OPT_TYPE_STRING, {0}, 0, 0, 0 },
{NULL}
};
#define HTTP_CLASS(flavor)\
......@@ -218,6 +226,7 @@ int ff_http_do_new_request(URLContext *h, const char *uri)
HTTPContext *s = h->priv_data;
s->off = 0;
s->icy_data_read = 0;
av_strlcpy(s->location, uri, sizeof(s->location));
return http_open_cnx(h);
......@@ -375,6 +384,16 @@ static int process_line(URLContext *h, char *line, int line_count,
snprintf(s->cookies, str_size, "%s\n%s", tmp, p);
av_free(tmp);
}
} else if (!av_strcasecmp (tag, "Icy-MetaInt")) {
s->icy_metaint = strtoll(p, NULL, 10);
} else if (!av_strncasecmp(tag, "Icy-", 4)) {
// Concat all Icy- header lines
char *buf = av_asprintf("%s%s: %s\n",
s->icy_metadata_headers ? s->icy_metadata_headers : "", tag, p);
if (!buf)
return AVERROR(ENOMEM);
av_freep(&s->icy_metadata_headers);
s->icy_metadata_headers = buf;
}
}
return 1;
......@@ -580,6 +599,10 @@ static int http_connect(URLContext *h, const char *path, const char *local_path,
av_free(cookies);
}
}
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)
......@@ -613,6 +636,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;
......@@ -652,6 +676,7 @@ static int http_buf_read(URLContext *h, uint8_t *buf, int size)
}
if (len > 0) {
s->off += len;
s->icy_data_read += len;
if (s->chunksize > 0)
s->chunksize -= len;
}
......@@ -693,6 +718,32 @@ static int http_read(URLContext *h, uint8_t *buf, int size)
}
size = FFMIN(size, s->chunksize);
}
if (s->icy_metaint > 0) {
int remaining = s->icy_metaint - s->icy_data_read; /* until next metadata packet */
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.
int ch = http_getc(s);
if (ch < 0)
return ch;
if (ch > 0) {
char data[255 * 16 + 1];
int n;
int ret;
ch *= 16;
for (n = 0; n < ch; n++)
data[n] = http_getc(s);
data[ch + 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;
}
size = FFMIN(size, remaining);
}
return http_buf_read(h, buf, size);
}
......
......@@ -31,7 +31,7 @@
#define LIBAVFORMAT_VERSION_MAJOR 55
#define LIBAVFORMAT_VERSION_MINOR 10
#define LIBAVFORMAT_VERSION_MICRO 100
#define LIBAVFORMAT_VERSION_MICRO 101
#define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \
LIBAVFORMAT_VERSION_MINOR, \
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment