[2/8] lavf/http: add support for reading ICY metadata

Message ID 1394118691-19478-3-git-send-email-alessandro@ghedini.me
State New
Headers show

Commit Message

Alessandro Ghedini March 6, 2014, 3:11 p.m.
This allows applications to request reading ICY metadata.

Original ffmpeg commit a92fbe1 by wm4.

Bug-Debian: https://bugs.debian.org/739936
---
 doc/protocols.texi | 20 ++++++++++++++++++++
 libavformat/http.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 71 insertions(+)

Comments

wm4 March 6, 2014, 3:34 p.m. | #1
On Thu,  6 Mar 2014 16:11:25 +0100
Alessandro Ghedini <alessandro@ghedini.me> wrote:

> This allows applications to request reading ICY metadata.
> 
> Original ffmpeg commit a92fbe1 by wm4.
> 
> Bug-Debian: https://bugs.debian.org/739936
> ---
>  doc/protocols.texi | 20 ++++++++++++++++++++
>  libavformat/http.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 71 insertions(+)
> 

I just realized this is missing commit 636273d3d4a8c42f5 from ffmpeg,
maybe this could be squashed into this patch?
Alessandro Ghedini March 6, 2014, 3:42 p.m. | #2
On gio, mar 06, 2014 at 04:34:05 +0100, wm4 wrote:
> On Thu,  6 Mar 2014 16:11:25 +0100
> Alessandro Ghedini <alessandro@ghedini.me> wrote:
> 
> > This allows applications to request reading ICY metadata.
> > 
> > Original ffmpeg commit a92fbe1 by wm4.
> > 
> > Bug-Debian: https://bugs.debian.org/739936
> > ---
> >  doc/protocols.texi | 20 ++++++++++++++++++++
> >  libavformat/http.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++
> >  2 files changed, 71 insertions(+)
> > 
> 
> I just realized this is missing commit 636273d3d4a8c42f5 from ffmpeg,
> maybe this could be squashed into this patch?

Yep, you are right, I missed that commit. Note that after this patch set gets
merged, 636273d applies cleanly (i.e. with a simple git-am), so it can just be
added at the end of the set (I'm ok either way).

Cheers

Patch

diff --git a/doc/protocols.texi b/doc/protocols.texi
index 1a9f575..58fa8a0 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 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. It should be
+polled in regular intervals by applications interested in metadata updates
+mid-stream.
+@end table
+
 @section mmst
 
 MMS (Microsoft Media Server) protocol over TCP.
diff --git a/libavformat/http.c b/libavformat/http.c
index 96f56f8..13e2de4 100644
--- a/libavformat/http.c
+++ b/libavformat/http.c
@@ -51,6 +51,8 @@  typedef struct {
     int http_code;
     int64_t chunksize;      /**< Used if "Transfer-Encoding: chunked" otherwise -1. */
     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;
     HTTPAuthState auth_state;
     HTTPAuthState proxy_auth_state;
@@ -62,6 +64,9 @@  typedef struct {
     int multiple_requests;  /**< A flag which indicates if we use persistent connections. */
     uint8_t *post_data;
     int post_datalen;
+    int icy;
+    char *icy_metadata_headers;
+    char *icy_metadata_packet;
 #if CONFIG_ZLIB
     int compressed;
     z_stream inflate_stream;
@@ -79,6 +84,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, 0 },
+{"icy_metadata_packet", "return current ICY metadata packet", OFFSET(icy_metadata_packet), AV_OPT_TYPE_STRING, {0}, 0, 0, 0 },
 {"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" },
@@ -219,6 +227,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)
@@ -376,6 +385,16 @@  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)) {
+            // 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;
         } else if (!av_strcasecmp (tag, "Content-Encoding")) {
             if (!av_strncasecmp(p, "gzip", 4) || !av_strncasecmp(p, "deflate", 7)) {
 #if CONFIG_ZLIB
@@ -510,6 +529,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)
@@ -543,6 +566,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;
@@ -582,6 +606,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;
     }
@@ -655,6 +680,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);
+    }
 #if CONFIG_ZLIB
     if (s->compressed)
         return http_buf_read_compressed(h, buf, size);