[FFmpeg-devel] [PATCH] http: add support for reading streamcast metadata

wm4 nfxjfg at googlemail.com
Wed Jun 26 01:20:52 CEST 2013


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_meta_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.
---
 libavformat/http.c | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 53 insertions(+)

diff --git a/libavformat/http.c b/libavformat/http.c
index 91f8d1f..d29822a 100644
--- a/libavformat/http.c
+++ b/libavformat/http.c
@@ -28,6 +28,7 @@
 #include "httpauth.h"
 #include "url.h"
 #include "libavutil/opt.h"
+#include "libavutil/bprint.h"
 
 /* XXX: POST protocol is not completely implemented because ffmpeg uses
    only a subset of it. */
@@ -49,6 +50,8 @@ typedef struct {
     char *content_type;
     char *user_agent;
     int64_t off, filesize;
+    int icy_data_read;
+    int icy_metaint;
     char location[MAX_URL_SIZE];
     HTTPAuthState auth_state;
     HTTPAuthState proxy_auth_state;
@@ -65,6 +68,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_meta_header;
+    char *icy_meta_packet;
 } HTTPContext;
 
 #define OFFSET(x) offsetof(HTTPContext, x)
@@ -82,6 +88,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_meta_header", "return ICY metadata header", OFFSET(icy_meta_header), AV_OPT_TYPE_STRING, {0}, 0, 0, 0 },
+{"icy_meta_packet", "return current ICY metadata packet", OFFSET(icy_meta_packet), AV_OPT_TYPE_STRING, {0}, 0, 0, 0 },
 {NULL}
 };
 #define HTTP_CLASS(flavor)\
@@ -218,6 +227,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 +385,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)) {
+            AVBPrint bp;
+            av_bprint_init(&bp, 1, -1);
+            if (s->icy_meta_header)
+                av_bprintf(&bp, "%s", s->icy_meta_header);
+            av_bprintf(&bp, "%s=%s\n", tag, p);
+            av_freep(&s->icy_meta_header);
+            av_bprint_finalize(&bp, &s->icy_meta_header);
         }
     }
     return 1;
@@ -580,6 +600,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 +637,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 +677,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 +719,30 @@ 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;
+        if (!remaining) {
+            char data[4096];
+            char *buf;
+            int n;
+            int ch = http_getc(s);
+            if (ch < 0)
+                return ch;
+            if (ch > 0) {
+                ch *= 16;
+                for (n = 0; n < ch; n++)
+                    data[n] = http_getc(s);
+                buf = av_mallocz(ch + 1);
+                if (buf)
+                    memcpy(buf, data, ch);
+                av_freep(&s->icy_meta_packet);
+                s->icy_meta_packet = buf;
+            }
+            s->icy_data_read = 0;
+            remaining = s->icy_metaint;
+        }
+        size = FFMIN(size, remaining);
+    }
     return http_buf_read(h, buf, size);
 }
 
@@ -744,6 +794,9 @@ static int http_close(URLContext *h)
     int ret = 0;
     HTTPContext *s = h->priv_data;
 
+    av_freep(&s->icy_meta_header);
+    av_freep(&s->icy_meta_packet);
+
     if (!s->end_chunked_post) {
         /* Close the write direction by sending the end of chunked encoding. */
         ret = http_shutdown(h, h->flags);
-- 
1.8.3.1



More information about the ffmpeg-devel mailing list