lavf/http: add support for reading streamcast metadata
authorwm4 <nfxjfg@googlemail.com>
Tue, 25 Jun 2013 22:53:26 +0000 (00:53 +0200)
committerStefano Sabatini <stefasab@gmail.com>
Tue, 2 Jul 2013 00:00:56 +0000 (02:00 +0200)
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: Stefano Sabatini <stefasab@gmail.com>
doc/protocols.texi
libavformat/http.c
libavformat/version.h

index 97ff62d..e8427aa 100644 (file)
@@ -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
index 91f8d1f..be82352 100644 (file)
@@ -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);
 }
 
index 45932d4..3614c44 100644 (file)
@@ -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, \