[FFmpeg-devel] [PATCH 03/18] avformat/id3v2: allow ID3 parsing without AVFormatContext

Anssi Hannula anssi.hannula at iki.fi
Mon Dec 30 12:14:17 CET 2013


Add ff_id3v2_read_dict() for parsing without AVFormatContext, but
instead with AVIOContext and AVDictionary.

Chapter parsing is the only functionality that actually needs
AVFormatContext, and AFAICS it should be modified to write the data to
ID3v2ExtraMeta first, from where it can be implanted to AVFormatContext
(like it is done with read_apic() and ff_id3v2_parse_apic()). That is
outside the scope of this patch, though, so AVFormatContext parameter is
still kept in a few more codepaths than I would prefer.

Signed-off-by: Anssi Hannula <anssi.hannula at iki.fi>
---

This causes a lot of av_logs with NULL context, does it matter and if
so, how should it be solved?

 libavformat/id3v2.c | 160 ++++++++++++++++++++++++++++++----------------------
 libavformat/id3v2.h |  16 +++++-
 2 files changed, 106 insertions(+), 70 deletions(-)

diff --git a/libavformat/id3v2.c b/libavformat/id3v2.c
index 3eb368e..0ff2df0 100644
--- a/libavformat/id3v2.c
+++ b/libavformat/id3v2.c
@@ -193,7 +193,7 @@ static void free_geobtag(void *obj)
  * actually read.
  * @returns 0 if no error occurred, dst is uninitialized on error
  */
-static int decode_str(AVFormatContext *s, AVIOContext *pb, int encoding,
+static int decode_str(AVIOContext *pb, int encoding,
                       uint8_t **dst, int *maxread)
 {
     int ret;
@@ -204,7 +204,7 @@ static int decode_str(AVFormatContext *s, AVIOContext *pb, int encoding,
     AVIOContext *dynbuf;
 
     if ((ret = avio_open_dyn_buf(&dynbuf)) < 0) {
-        av_log(s, AV_LOG_ERROR, "Error opening memory stream\n");
+        av_log(NULL, AV_LOG_ERROR, "Error opening memory stream\n");
         return ret;
     }
 
@@ -219,7 +219,7 @@ static int decode_str(AVFormatContext *s, AVIOContext *pb, int encoding,
 
     case ID3v2_ENCODING_UTF16BOM:
         if ((left -= 2) < 0) {
-            av_log(s, AV_LOG_ERROR, "Cannot read BOM value, input too short\n");
+            av_log(NULL, AV_LOG_ERROR, "Cannot read BOM value, input too short\n");
             avio_close_dyn_buf(dynbuf, dst);
             av_freep(dst);
             return AVERROR_INVALIDDATA;
@@ -230,7 +230,7 @@ static int decode_str(AVFormatContext *s, AVIOContext *pb, int encoding,
         case 0xfeff:
             break;
         default:
-            av_log(s, AV_LOG_ERROR, "Incorrect BOM value\n");
+            av_log(NULL, AV_LOG_ERROR, "Incorrect BOM value\n");
             avio_close_dyn_buf(dynbuf, dst);
             av_freep(dst);
             *maxread = left;
@@ -255,7 +255,7 @@ static int decode_str(AVFormatContext *s, AVIOContext *pb, int encoding,
         }
         break;
     default:
-        av_log(s, AV_LOG_WARNING, "Unknown encoding\n");
+        av_log(NULL, AV_LOG_WARNING, "Unknown encoding\n");
     }
 
     if (ch)
@@ -270,7 +270,7 @@ static int decode_str(AVFormatContext *s, AVIOContext *pb, int encoding,
 /**
  * Parse a text tag.
  */
-static void read_ttag(AVFormatContext *s, AVIOContext *pb, int taglen,
+static void read_ttag(AVIOContext *pb, int taglen,
                       AVDictionary **metadata, const char *key)
 {
     uint8_t *dst;
@@ -283,8 +283,8 @@ static void read_ttag(AVFormatContext *s, AVIOContext *pb, int taglen,
     encoding = avio_r8(pb);
     taglen--; /* account for encoding type byte */
 
-    if (decode_str(s, pb, encoding, &dst, &taglen) < 0) {
-        av_log(s, AV_LOG_ERROR, "Error reading frame %s, skipped\n", key);
+    if (decode_str(pb, encoding, &dst, &taglen) < 0) {
+        av_log(NULL, AV_LOG_ERROR, "Error reading frame %s, skipped\n", key);
         return;
     }
 
@@ -296,8 +296,8 @@ static void read_ttag(AVFormatContext *s, AVIOContext *pb, int taglen,
     } else if (!(strcmp(key, "TXXX") && strcmp(key, "TXX"))) {
         /* dst now contains the key, need to get value */
         key = dst;
-        if (decode_str(s, pb, encoding, &dst, &taglen) < 0) {
-            av_log(s, AV_LOG_ERROR, "Error reading frame %s, skipped\n", key);
+        if (decode_str(pb, encoding, &dst, &taglen) < 0) {
+            av_log(NULL, AV_LOG_ERROR, "Error reading frame %s, skipped\n", key);
             av_freep(&key);
             return;
         }
@@ -342,18 +342,18 @@ static void read_geobtag(AVFormatContext *s, AVIOContext *pb, int taglen,
     taglen--;
 
     /* read MIME type (always ISO-8859) */
-    if (decode_str(s, pb, ID3v2_ENCODING_ISO8859, &geob_data->mime_type,
+    if (decode_str(pb, ID3v2_ENCODING_ISO8859, &geob_data->mime_type,
                    &taglen) < 0 ||
         taglen <= 0)
         goto fail;
 
     /* read file name */
-    if (decode_str(s, pb, encoding, &geob_data->file_name, &taglen) < 0 ||
+    if (decode_str(pb, encoding, &geob_data->file_name, &taglen) < 0 ||
         taglen <= 0)
         goto fail;
 
     /* read content description */
-    if (decode_str(s, pb, encoding, &geob_data->description, &taglen) < 0 ||
+    if (decode_str(pb, encoding, &geob_data->description, &taglen) < 0 ||
         taglen < 0)
         goto fail;
 
@@ -498,7 +498,7 @@ static void read_apic(AVFormatContext *s, AVIOContext *pb, int taglen,
     apic->type = ff_id3v2_picture_types[pic_type];
 
     /* description and picture data */
-    if (decode_str(s, pb, enc, &apic->description, &taglen) < 0) {
+    if (decode_str(pb, enc, &apic->description, &taglen) < 0) {
         av_log(s, AV_LOG_ERROR,
                "Error decoding attached picture description.\n");
         goto fail;
@@ -523,6 +523,9 @@ fail:
     avio_seek(pb, end, SEEK_SET);
 }
 
+/* TODO: This should not touch AVFormatContext directly but store the
+ * information in extra_meta, see e.g. read_apic() and ff_id3v2_parse_apic().
+ * Then AVFormatContext parameter can be removed. */
 static void read_chapter(AVFormatContext *s, AVIOContext *pb, int len, char *ttag, ID3v2ExtraMeta **extra_meta, int isv34)
 {
     AVRational time_base = {1, 1000};
@@ -532,7 +535,10 @@ static void read_chapter(AVFormatContext *s, AVIOContext *pb, int len, char *tta
     int taglen;
     char tag[5];
 
-    if (decode_str(s, pb, 0, &dst, &len) < 0)
+    if (!s)
+        return;
+
+    if (decode_str(pb, 0, &dst, &len) < 0)
         return;
     if (len < 16)
         return;
@@ -558,7 +564,7 @@ static void read_chapter(AVFormatContext *s, AVIOContext *pb, int len, char *tta
         if (taglen < 0 || taglen > len)
             goto end;
         if (tag[0] == 'T')
-            read_ttag(s, pb, taglen, &chapter->metadata, tag);
+            read_ttag(pb, taglen, &chapter->metadata, tag);
         else
             avio_skip(pb, taglen);
         len -= taglen;
@@ -573,6 +579,8 @@ end:
 typedef struct ID3v2EMFunc {
     const char *tag3;
     const char *tag4;
+    /* NOTE: AVFormatContext can be NULL. Preferably it would not
+     * be here at all, but it is used by read_chapter(). */
     void (*read)(AVFormatContext *, AVIOContext *, int, char *,
                  ID3v2ExtraMeta **, int isv34);
     void (*free)(void *obj);
@@ -604,16 +612,17 @@ static const ID3v2EMFunc *get_extra_meta_func(const char *tag, int isv34)
     return NULL;
 }
 
-static void id3v2_parse(AVFormatContext *s, int len, uint8_t version,
+static void id3v2_parse(AVIOContext *pb, AVDictionary **metadata,
+                        AVFormatContext *avfctx, int len, uint8_t version,
                         uint8_t flags, ID3v2ExtraMeta **extra_meta)
 {
     int isv34, unsync;
     unsigned tlen;
     char tag[5];
-    int64_t next, end = avio_tell(s->pb) + len;
+    int64_t next, end = avio_tell(pb) + len;
     int taghdrlen;
     const char *reason = NULL;
-    AVIOContext pb;
+    AVIOContext pb_local;
     AVIOContext *pbx;
     unsigned char *buffer = NULL;
     int buffer_size       = 0;
@@ -621,7 +630,7 @@ static void id3v2_parse(AVFormatContext *s, int len, uint8_t version,
     unsigned char *uncompressed_buffer = NULL;
     int uncompressed_buffer_size = 0;
 
-    av_log(s, AV_LOG_DEBUG, "id3v2 ver:%d flags:%02X len:%d\n", version, flags, len);
+    av_log(avfctx, AV_LOG_DEBUG, "id3v2 ver:%d flags:%02X len:%d\n", version, flags, len);
 
     switch (version) {
     case 2:
@@ -647,7 +656,7 @@ static void id3v2_parse(AVFormatContext *s, int len, uint8_t version,
     unsync = flags & 0x80;
 
     if (isv34 && flags & 0x40) { /* Extended header present, just skip over it */
-        int extlen = get_size(s->pb, 4);
+        int extlen = get_size(pb, 4);
         if (version == 4)
             /* In v2.4 the length includes the length field we just read. */
             extlen -= 4;
@@ -656,7 +665,7 @@ static void id3v2_parse(AVFormatContext *s, int len, uint8_t version,
             reason = "invalid extended header length";
             goto error;
         }
-        avio_skip(s->pb, extlen);
+        avio_skip(pb, extlen);
         len -= extlen + 4;
         if (len < 0) {
             reason = "extended header too long.";
@@ -672,20 +681,20 @@ static void id3v2_parse(AVFormatContext *s, int len, uint8_t version,
         unsigned long dlen;
 
         if (isv34) {
-            if (avio_read(s->pb, tag, 4) < 4)
+            if (avio_read(pb, tag, 4) < 4)
                 break;
             tag[4] = 0;
             if (version == 3) {
-                tlen = avio_rb32(s->pb);
+                tlen = avio_rb32(pb);
             } else
-                tlen = get_size(s->pb, 4);
-            tflags  = avio_rb16(s->pb);
+                tlen = get_size(pb, 4);
+            tflags  = avio_rb16(pb);
             tunsync = tflags & ID3v2_FLAG_UNSYNCH;
         } else {
-            if (avio_read(s->pb, tag, 3) < 3)
+            if (avio_read(pb, tag, 3) < 3)
                 break;
             tag[3] = 0;
-            tlen   = avio_rb24(s->pb);
+            tlen   = avio_rb24(pb);
         }
         if (tlen > (1<<28))
             break;
@@ -694,11 +703,11 @@ static void id3v2_parse(AVFormatContext *s, int len, uint8_t version,
         if (len < 0)
             break;
 
-        next = avio_tell(s->pb) + tlen;
+        next = avio_tell(pb) + tlen;
 
         if (!tlen) {
             if (tag[0])
-                av_log(s, AV_LOG_DEBUG, "Invalid empty frame %s, skipping.\n",
+                av_log(avfctx, AV_LOG_DEBUG, "Invalid empty frame %s, skipping.\n",
                        tag);
             continue;
         }
@@ -706,7 +715,7 @@ static void id3v2_parse(AVFormatContext *s, int len, uint8_t version,
         if (tflags & ID3v2_FLAG_DATALEN) {
             if (tlen < 4)
                 break;
-            dlen = avio_rb32(s->pb);
+            dlen = avio_rb32(pb);
             tlen -= 4;
         } else
             dlen = tlen;
@@ -724,57 +733,57 @@ static void id3v2_parse(AVFormatContext *s, int len, uint8_t version,
             else
                 type = "encrypted and compressed";
 
-            av_log(s, AV_LOG_WARNING, "Skipping %s ID3v2 frame %s.\n", type, tag);
-            avio_skip(s->pb, tlen);
+            av_log(avfctx, AV_LOG_WARNING, "Skipping %s ID3v2 frame %s.\n", type, tag);
+            avio_skip(pb, tlen);
         /* check for text tag or supported special meta tag */
         } else if (tag[0] == 'T' ||
                    (extra_meta &&
                     (extra_func = get_extra_meta_func(tag, isv34)))) {
-            pbx = s->pb;
+            pbx = pb;
 
             if (unsync || tunsync || tcomp) {
                 av_fast_malloc(&buffer, &buffer_size, tlen);
                 if (!buffer) {
-                    av_log(s, AV_LOG_ERROR, "Failed to alloc %d bytes\n", tlen);
+                    av_log(avfctx, AV_LOG_ERROR, "Failed to alloc %d bytes\n", tlen);
                     goto seek;
                 }
             }
             if (unsync || tunsync) {
-                int64_t end = avio_tell(s->pb) + tlen;
+                int64_t end = avio_tell(pb) + tlen;
                 uint8_t *b;
 
                 b = buffer;
-                while (avio_tell(s->pb) < end && b - buffer < tlen && !s->pb->eof_reached) {
-                    *b++ = avio_r8(s->pb);
-                    if (*(b - 1) == 0xff && avio_tell(s->pb) < end - 1 &&
+                while (avio_tell(pb) < end && b - buffer < tlen && !pb->eof_reached) {
+                    *b++ = avio_r8(pb);
+                    if (*(b - 1) == 0xff && avio_tell(pb) < end - 1 &&
                         b - buffer < tlen &&
-                        !s->pb->eof_reached ) {
-                        uint8_t val = avio_r8(s->pb);
-                        *b++ = val ? val : avio_r8(s->pb);
+                        !pb->eof_reached ) {
+                        uint8_t val = avio_r8(pb);
+                        *b++ = val ? val : avio_r8(pb);
                     }
                 }
-                ffio_init_context(&pb, buffer, b - buffer, 0, NULL, NULL, NULL,
+                ffio_init_context(&pb_local, buffer, b - buffer, 0, NULL, NULL, NULL,
                                   NULL);
                 tlen = b - buffer;
-                pbx  = &pb; // read from sync buffer
+                pbx  = &pb_local; // read from sync buffer
             }
 
 #if CONFIG_ZLIB
                 if (tcomp) {
                     int err;
 
-                    av_log(s, AV_LOG_DEBUG, "Compresssed frame %s tlen=%d dlen=%ld\n", tag, tlen, dlen);
+                    av_log(avfctx, AV_LOG_DEBUG, "Compresssed frame %s tlen=%d dlen=%ld\n", tag, tlen, dlen);
 
                     av_fast_malloc(&uncompressed_buffer, &uncompressed_buffer_size, dlen);
                     if (!uncompressed_buffer) {
-                        av_log(s, AV_LOG_ERROR, "Failed to alloc %ld bytes\n", dlen);
+                        av_log(avfctx, AV_LOG_ERROR, "Failed to alloc %ld bytes\n", dlen);
                         goto seek;
                     }
 
                     if (!(unsync || tunsync)) {
-                        err = avio_read(s->pb, buffer, tlen);
+                        err = avio_read(pb, buffer, tlen);
                         if (err < 0) {
-                            av_log(s, AV_LOG_ERROR, "Failed to read compressed tag\n");
+                            av_log(avfctx, AV_LOG_ERROR, "Failed to read compressed tag\n");
                             goto seek;
                         }
                         tlen = err;
@@ -782,29 +791,29 @@ static void id3v2_parse(AVFormatContext *s, int len, uint8_t version,
 
                     err = uncompress(uncompressed_buffer, &dlen, buffer, tlen);
                     if (err != Z_OK) {
-                        av_log(s, AV_LOG_ERROR, "Failed to uncompress tag: %d\n", err);
+                        av_log(avfctx, AV_LOG_ERROR, "Failed to uncompress tag: %d\n", err);
                         goto seek;
                     }
-                    ffio_init_context(&pb, uncompressed_buffer, dlen, 0, NULL, NULL, NULL, NULL);
+                    ffio_init_context(&pb_local, uncompressed_buffer, dlen, 0, NULL, NULL, NULL, NULL);
                     tlen = dlen;
-                    pbx = &pb; // read from sync buffer
+                    pbx = &pb_local; // read from sync buffer
                 }
 #endif
             if (tag[0] == 'T')
                 /* parse text tag */
-                read_ttag(s, pbx, tlen, &s->metadata, tag);
+                read_ttag(pbx, tlen, metadata, tag);
             else
                 /* parse special meta tag */
-                extra_func->read(s, pbx, tlen, tag, extra_meta, isv34);
+                extra_func->read(avfctx, pbx, tlen, tag, extra_meta, isv34);
         } else if (!tag[0]) {
             if (tag[1])
-                av_log(s, AV_LOG_WARNING, "invalid frame id, assuming padding\n");
-            avio_skip(s->pb, tlen);
+                av_log(avfctx, AV_LOG_WARNING, "invalid frame id, assuming padding\n");
+            avio_skip(pb, tlen);
             break;
         }
         /* Skip to end of tag */
 seek:
-        avio_seek(s->pb, next, SEEK_SET);
+        avio_seek(pb, next, SEEK_SET);
     }
 
     /* Footer preset, always 10 bytes, skip over it */
@@ -813,16 +822,17 @@ seek:
 
 error:
     if (reason)
-        av_log(s, AV_LOG_INFO, "ID3v2.%d tag skipped, cannot handle %s\n",
+        av_log(avfctx, AV_LOG_INFO, "ID3v2.%d tag skipped, cannot handle %s\n",
                version, reason);
-    avio_seek(s->pb, end, SEEK_SET);
+    avio_seek(pb, end, SEEK_SET);
     av_free(buffer);
     av_free(uncompressed_buffer);
     return;
 }
 
-void ff_id3v2_read(AVFormatContext *s, const char *magic,
-                   ID3v2ExtraMeta **extra_meta)
+static void ff_id3v2_read_internal(AVIOContext *pb, AVDictionary **metadata,
+                                   AVFormatContext *avfctx, const char *magic,
+                                   ID3v2ExtraMeta **extra_meta)
 {
     int len, ret;
     uint8_t buf[ID3v2_HEADER_SIZE];
@@ -831,10 +841,10 @@ void ff_id3v2_read(AVFormatContext *s, const char *magic,
 
     do {
         /* save the current offset in case there's nothing to read/skip */
-        off = avio_tell(s->pb);
-        ret = avio_read(s->pb, buf, ID3v2_HEADER_SIZE);
+        off = avio_tell(pb);
+        ret = avio_read(pb, buf, ID3v2_HEADER_SIZE);
         if (ret != ID3v2_HEADER_SIZE) {
-            avio_seek(s->pb, off, SEEK_SET);
+            avio_seek(pb, off, SEEK_SET);
             break;
         }
         found_header = ff_id3v2_match(buf, magic);
@@ -844,15 +854,27 @@ void ff_id3v2_read(AVFormatContext *s, const char *magic,
                   ((buf[7] & 0x7f) << 14) |
                   ((buf[8] & 0x7f) << 7) |
                    (buf[9] & 0x7f);
-            id3v2_parse(s, len, buf[3], buf[5], extra_meta);
+            id3v2_parse(pb, metadata, avfctx, len, buf[3], buf[5], extra_meta);
         } else {
-            avio_seek(s->pb, off, SEEK_SET);
+            avio_seek(pb, off, SEEK_SET);
         }
     } while (found_header);
-    ff_metadata_conv(&s->metadata, NULL, ff_id3v2_34_metadata_conv);
-    ff_metadata_conv(&s->metadata, NULL, id3v2_2_metadata_conv);
-    ff_metadata_conv(&s->metadata, NULL, ff_id3v2_4_metadata_conv);
-    merge_date(&s->metadata);
+    ff_metadata_conv(metadata, NULL, ff_id3v2_34_metadata_conv);
+    ff_metadata_conv(metadata, NULL, id3v2_2_metadata_conv);
+    ff_metadata_conv(metadata, NULL, ff_id3v2_4_metadata_conv);
+    merge_date(metadata);
+}
+
+void ff_id3v2_read_dict(AVIOContext *pb, AVDictionary **metadata,
+                        const char *magic, ID3v2ExtraMeta **extra_meta)
+{
+    ff_id3v2_read_internal(pb, metadata, NULL, magic, extra_meta);
+}
+
+void ff_id3v2_read(AVFormatContext *s, const char *magic,
+                   ID3v2ExtraMeta **extra_meta)
+{
+    ff_id3v2_read_internal(s->pb, &s->metadata, s, magic, extra_meta);
 }
 
 void ff_id3v2_free_extra_meta(ID3v2ExtraMeta **extra_meta)
diff --git a/libavformat/id3v2.h b/libavformat/id3v2.h
index e893922..4119e93 100644
--- a/libavformat/id3v2.h
+++ b/libavformat/id3v2.h
@@ -89,7 +89,21 @@ int ff_id3v2_match(const uint8_t *buf, const char *magic);
 int ff_id3v2_tag_len(const uint8_t *buf);
 
 /**
- * Read an ID3v2 tag, including supported extra metadata
+ * Read an ID3v2 tag into specified dictionary and retrieve supported extra metadata.
+ *
+ * Chapters are not currently read by this variant.
+ *
+ * @param metadata Parsed metadata is stored here
+ * @param extra_meta If not NULL, extra metadata is parsed into a list of
+ * ID3v2ExtraMeta structs and *extra_meta points to the head of the list
+ */
+void ff_id3v2_read_dict(AVIOContext *pb, AVDictionary **metadata, const char *magic, ID3v2ExtraMeta **extra_meta);
+
+/**
+ * Read an ID3v2 tag, including supported extra metadata and chapters.
+ *
+ * Data is read from and stored to AVFormatContext.
+ *
  * @param extra_meta If not NULL, extra metadata is parsed into a list of
  * ID3v2ExtraMeta structs and *extra_meta points to the head of the list
  */
-- 
1.8.1.5



More information about the ffmpeg-devel mailing list