static void id3v2_parse(AVIOContext *pb, AVDictionary **metadata,

                        AVFormatContext *s, 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(pb) + len;

    int taghdrlen;

    const char *reason = NULL;

    AVIOContext pb_local;

    AVIOContext *pbx;

    unsigned char *buffer = NULL;

    int buffer_size       = 0;

    const ID3v2EMFunc *extra_func = NULL;

    unsigned char *uncompressed_buffer = NULL;

    av_unused int uncompressed_buffer_size = 0;



    av_log(s, AV_LOG_DEBUG, "id3v2 ver:%d flags:%02X len:%d\n", version, flags, len);



    switch (version) {

    case 2:

        if (flags & 0x40) {

            reason = "compression";

            goto error;

        }

        isv34     = 0;

        taghdrlen = 6;

        break;



    case 3:

    case 4:

        isv34     = 1;

        taghdrlen = 10;

        break;



    default:

        reason = "version";

        goto error;

    }



    unsync = flags & 0x80;



    if (isv34 && flags & 0x40) { /* Extended header present, just skip over it */

        int extlen = get_size(pb, 4);

        if (version == 4)

            /* In v2.4 the length includes the length field we just read. */

            extlen -= 4;



        if (extlen < 0) {

            reason = "invalid extended header length";

            goto error;

        }

        avio_skip(pb, extlen);

        len -= extlen + 4;

        if (len < 0) {

            reason = "extended header too long.";

            goto error;

        }

    }



    while (len >= taghdrlen) {

        unsigned int tflags = 0;

        int tunsync         = 0;

        int tcomp           = 0;

        int tencr           = 0;

        unsigned long av_unused dlen;



        if (isv34) {

            if (avio_read(pb, tag, 4) < 4)

                break;

            tag[4] = 0;

            if (version == 3) {

                tlen = avio_rb32(pb);

            } else

                tlen = get_size(pb, 4);

            tflags  = avio_rb16(pb);

            tunsync = tflags & ID3v2_FLAG_UNSYNCH;

        } else {

            if (avio_read(pb, tag, 3) < 3)

                break;

            tag[3] = 0;

            tlen   = avio_rb24(pb);

        }

        if (tlen > (1<<28))

            break;

        len -= taghdrlen + tlen;



        if (len < 0)

            break;



        next = avio_tell(pb) + tlen;



        if (!tlen) {

            if (tag[0])

                av_log(s, AV_LOG_DEBUG, "Invalid empty frame %s, skipping.\n",

                       tag);

            continue;

        }



        if (tflags & ID3v2_FLAG_DATALEN) {

            if (tlen < 4)

                break;

            dlen = avio_rb32(pb);

            tlen -= 4;

        } else

            dlen = tlen;



        tcomp = tflags & ID3v2_FLAG_COMPRESSION;

        tencr = tflags & ID3v2_FLAG_ENCRYPTION;



        /* skip encrypted tags and, if no zlib, compressed tags */

        if (tencr || (!CONFIG_ZLIB && tcomp)) {

            const char *type;

            if (!tcomp)

                type = "encrypted";

            else if (!tencr)

                type = "compressed";

            else

                type = "encrypted and compressed";



            av_log(s, 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 = 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);

                    goto seek;

                }

            }

            if (unsync || tunsync) {

                int64_t end = avio_tell(pb) + tlen;

                uint8_t *b;



                b = buffer;

                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 &&

                        !pb->eof_reached ) {

                        uint8_t val = avio_r8(pb);

                        *b++ = val ? val : avio_r8(pb);

                    }

                }

                ffio_init_context(&pb_local, buffer, b - buffer, 0, NULL, NULL, NULL,

                                  NULL);

                tlen = b - 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_fast_malloc(&uncompressed_buffer, &uncompressed_buffer_size, dlen);

                    if (!uncompressed_buffer) {

                        av_log(s, AV_LOG_ERROR, "Failed to alloc %ld bytes\n", dlen);

                        goto seek;

                    }



                    if (!(unsync || tunsync)) {

                        err = avio_read(pb, buffer, tlen);

                        if (err < 0) {

                            av_log(s, AV_LOG_ERROR, "Failed to read compressed tag\n");

                            goto seek;

                        }

                        tlen = err;

                    }



                    err = uncompress(uncompressed_buffer, &dlen, buffer, tlen);

                    if (err != Z_OK) {

                        av_log(s, AV_LOG_ERROR, "Failed to uncompress tag: %d\n", err);

                        goto seek;

                    }

                    ffio_init_context(&pb_local, uncompressed_buffer, dlen, 0, NULL, NULL, NULL, NULL);

                    tlen = dlen;

                    pbx = &pb_local; // read from sync buffer

                }

#endif

            if (tag[0] == 'T')

                /* parse text tag */

                read_ttag(s, pbx, tlen, metadata, tag);

            else

                /* parse special meta tag */

                extra_func->read(s, 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(pb, tlen);

            break;

        }

        /* Skip to end of tag */

seek:

        avio_seek(pb, next, SEEK_SET);

    }



    /* Footer preset, always 10 bytes, skip over it */

    if (version == 4 && flags & 0x10)

        end += 10;



error:

    if (reason)

        av_log(s, AV_LOG_INFO, "ID3v2.%d tag skipped, cannot handle %s\n",

               version, reason);

    avio_seek(pb, end, SEEK_SET);

    av_free(buffer);

    av_free(uncompressed_buffer);

    return;

}
