static int mov_read_sv3d(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
    AVStream *st;
    MOVStreamContext *sc;
    int size, version, layout;
    int32_t yaw, pitch, roll;
    uint32_t l = 0, t = 0, r = 0, b = 0;
    uint32_t tag, padding = 0;
    enum AVSphericalProjection projection;

    if (c->fc->nb_streams < 1)
        return 0;

    st = c->fc->streams[c->fc->nb_streams - 1];
    sc = st->priv_data;

    if (atom.size < 8) {
        av_log(c->fc, AV_LOG_ERROR, "Empty spherical video box\n");
        return AVERROR_INVALIDDATA;
    }

    size = avio_rb32(pb);
    if (size <= 12 || size > atom.size)
        return AVERROR_INVALIDDATA;

    tag = avio_rl32(pb);
    if (tag != MKTAG('s','v','h','d')) {
        av_log(c->fc, AV_LOG_ERROR, "Missing spherical video header\n");
        return 0;
    }
    version = avio_r8(pb);
    if (version != 0) {
        av_log(c->fc, AV_LOG_WARNING, "Unknown spherical version %d\n",
               version);
        return 0;
    }
    avio_skip(pb, 3); /* flags */
    avio_skip(pb, size - 12); /* metadata_source */

    size = avio_rb32(pb);
    if (size > atom.size)
        return AVERROR_INVALIDDATA;

    tag = avio_rl32(pb);
    if (tag != MKTAG('p','r','o','j')) {
        av_log(c->fc, AV_LOG_ERROR, "Missing projection box\n");
        return 0;
    }

    size = avio_rb32(pb);
    if (size > atom.size)
        return AVERROR_INVALIDDATA;

    tag = avio_rl32(pb);
    if (tag != MKTAG('p','r','h','d')) {
        av_log(c->fc, AV_LOG_ERROR, "Missing projection header box\n");
        return 0;
    }
    version = avio_r8(pb);
    if (version != 0) {
        av_log(c->fc, AV_LOG_WARNING, "Unknown spherical version %d\n",
               version);
        return 0;
    }
    avio_skip(pb, 3); /* flags */

    /* 16.16 fixed point */
    yaw   = avio_rb32(pb);
    pitch = avio_rb32(pb);
    roll  = avio_rb32(pb);

    size = avio_rb32(pb);
    if (size > atom.size)
        return AVERROR_INVALIDDATA;

    tag = avio_rl32(pb);
    version = avio_r8(pb);
    if (version != 0) {
        av_log(c->fc, AV_LOG_WARNING, "Unknown spherical version %d\n",
               version);
        return 0;
    }
    avio_skip(pb, 3); /* flags */
    switch (tag) {
    case MKTAG('c','b','m','p'):
        layout = avio_rb32(pb);
        if (layout) {
            av_log(c->fc, AV_LOG_WARNING,
                   "Unsupported cubemap layout %d\n", layout);
            return 0;
        }
        projection = AV_SPHERICAL_CUBEMAP;
        padding = avio_rb32(pb);
        break;
    case MKTAG('e','q','u','i'):
        t = avio_rb32(pb);
        b = avio_rb32(pb);
        l = avio_rb32(pb);
        r = avio_rb32(pb);

        if (b >= UINT_MAX - t || r >= UINT_MAX - l) {
            av_log(c->fc, AV_LOG_ERROR,
                   "Invalid bounding rectangle coordinates "
                   "%"PRIu32",%"PRIu32",%"PRIu32",%"PRIu32"\n", l, t, r, b);
            return AVERROR_INVALIDDATA;
        }

        if (l || t || r || b)
            projection = AV_SPHERICAL_EQUIRECTANGULAR_TILE;
        else
            projection = AV_SPHERICAL_EQUIRECTANGULAR;
        break;
    default:
        av_log(c->fc, AV_LOG_ERROR, "Unknown projection type: %s\n", av_fourcc2str(tag));
        return 0;
    }

    sc->spherical = av_spherical_alloc(&sc->spherical_size);
    if (!sc->spherical)
        return AVERROR(ENOMEM);

    sc->spherical->projection = projection;

    sc->spherical->yaw   = yaw;
    sc->spherical->pitch = pitch;
    sc->spherical->roll  = roll;

    sc->spherical->padding = padding;

    sc->spherical->bound_left   = l;
    sc->spherical->bound_top    = t;
    sc->spherical->bound_right  = r;
    sc->spherical->bound_bottom = b;

    return 0;
}


avformat/mov: Check count sums in build_open_gop_key_points()

Fixes: ffmpeg.md
Fixes: Out of array access
Fixes: CVE-2022-2566

Found-by: Andy Nguyen <theflow@google.com>
Found-by: 3pvd <3pvd@google.com>
Reviewed-by: Andy Nguyen <theflow@google.com>
Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>

static void mov_update_dts_shift(MOVStreamContext *sc, int duration, void *logctx)
{
    if (duration < 0) {
        if (duration == INT_MIN) {
            av_log(logctx, AV_LOG_WARNING, "mov_update_dts_shift(): dts_shift set to %d\n", INT_MAX);
            duration++;
        }
        sc->dts_shift = FFMAX(sc->dts_shift, -duration);
    }
}


static inline int mov_stsc_index_valid(unsigned int index, unsigned int count)
{
    return index < count - 1;
}


static int mov_read_pssh(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
    AVEncryptionInitInfo *info, *old_init_info;
    uint8_t **key_ids;
    AVStream *st;
    uint8_t *side_data, *extra_data, *old_side_data;
    size_t side_data_size, old_side_data_size;
    int ret = 0;
    unsigned int version, kid_count, extra_data_size, alloc_size = 0;

    if (c->fc->nb_streams < 1)
        return 0;
    st = c->fc->streams[c->fc->nb_streams-1];

    version = avio_r8(pb); /* version */
    avio_rb24(pb);  /* flags */

    info = av_encryption_init_info_alloc(/* system_id_size */ 16, /* num_key_ids */ 0,
                                         /* key_id_size */ 16, /* data_size */ 0);
    if (!info)
        return AVERROR(ENOMEM);

    if ((ret = ffio_read_size(pb, info->system_id, 16)) < 0) {
        av_log(c->fc, AV_LOG_ERROR, "Failed to read the system id\n");
        goto finish;
    }

    if (version > 0) {
        kid_count = avio_rb32(pb);
        if (kid_count >= INT_MAX / sizeof(*key_ids)) {
            ret = AVERROR(ENOMEM);
            goto finish;
        }

        for (unsigned int i = 0; i < kid_count && !pb->eof_reached; i++) {
            unsigned int min_kid_count = FFMIN(FFMAX(i + 1, 1024), kid_count);
            key_ids = av_fast_realloc(info->key_ids, &alloc_size,
                                      min_kid_count * sizeof(*key_ids));
            if (!key_ids) {
                ret = AVERROR(ENOMEM);
                goto finish;
            }
            info->key_ids = key_ids;

            info->key_ids[i] = av_mallocz(16);
            if (!info->key_ids[i]) {
                ret = AVERROR(ENOMEM);
                goto finish;
            }
            info->num_key_ids = i + 1;

            if ((ret = ffio_read_size(pb, info->key_ids[i], 16)) < 0) {
                av_log(c->fc, AV_LOG_ERROR, "Failed to read the key id\n");
                goto finish;
            }
        }

        if (pb->eof_reached) {
            av_log(c->fc, AV_LOG_ERROR, "Hit EOF while reading pssh\n");
            ret = AVERROR_INVALIDDATA;
            goto finish;
        }
    }

    extra_data_size = avio_rb32(pb);
    ret = mov_try_read_block(pb, extra_data_size, &extra_data);
    if (ret < 0)
        goto finish;

    av_freep(&info->data);  // malloc(0) may still allocate something.
    info->data = extra_data;
    info->data_size = extra_data_size;

    // If there is existing initialization data, append to the list.
    old_side_data = av_stream_get_side_data(st, AV_PKT_DATA_ENCRYPTION_INIT_INFO, &old_side_data_size);
    if (old_side_data) {
        old_init_info = av_encryption_init_info_get_side_data(old_side_data, old_side_data_size);
        if (old_init_info) {
            // Append to the end of the list.
            for (AVEncryptionInitInfo *cur = old_init_info;; cur = cur->next) {
                if (!cur->next) {
                    cur->next = info;
                    break;
                }
            }
            info = old_init_info;
        } else {
            // Assume existing side-data will be valid, so the only error we could get is OOM.
            ret = AVERROR(ENOMEM);
            goto finish;
        }
    }

    side_data = av_encryption_init_info_add_side_data(info, &side_data_size);
    if (!side_data) {
        ret = AVERROR(ENOMEM);
        goto finish;
    }
    ret = av_stream_add_side_data(st, AV_PKT_DATA_ENCRYPTION_INIT_INFO,
                                  side_data, side_data_size);
    if (ret < 0)
        av_free(side_data);

finish:
    av_encryption_init_info_free(info);
    return ret;
}


static int mov_read_st3d(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
    AVStream *st;
    MOVStreamContext *sc;
    enum AVStereo3DType type;
    int mode;

    if (c->fc->nb_streams < 1)
        return 0;

    st = c->fc->streams[c->fc->nb_streams - 1];
    sc = st->priv_data;

    if (atom.size < 5) {
        av_log(c->fc, AV_LOG_ERROR, "Empty stereoscopic video box\n");
        return AVERROR_INVALIDDATA;
    }

    if (sc->stereo3d)
        return AVERROR_INVALIDDATA;

    avio_skip(pb, 4); /* version + flags */

    mode = avio_r8(pb);
    switch (mode) {
    case 0:
        type = AV_STEREO3D_2D;
        break;
    case 1:
        type = AV_STEREO3D_TOPBOTTOM;
        break;
    case 2:
        type = AV_STEREO3D_SIDEBYSIDE;
        break;
    default:
        av_log(c->fc, AV_LOG_WARNING, "Unknown st3d mode value %d\n", mode);
        return 0;
    }

    sc->stereo3d = av_stereo3d_alloc();
    if (!sc->stereo3d)
        return AVERROR(ENOMEM);

    sc->stereo3d->type = type;
    return 0;
}


int ff_mov_read_stsd_entries(MOVContext *c, AVIOContext *pb, int entries)
{
    AVStream *st;
    MOVStreamContext *sc;
    int pseudo_stream_id;

    av_assert0 (c->fc->nb_streams >= 1);
    st = c->fc->streams[c->fc->nb_streams-1];
    sc = st->priv_data;

    for (pseudo_stream_id = 0;
         pseudo_stream_id < entries && !pb->eof_reached;
         pseudo_stream_id++) {
        //Parsing Sample description table
        enum AVCodecID id;
        int ret, dref_id = 1;
        MOVAtom a = { AV_RL32("stsd") };
        int64_t start_pos = avio_tell(pb);
        int64_t size    = avio_rb32(pb); /* size */
        uint32_t format = avio_rl32(pb); /* data format */

        if (size >= 16) {
            avio_rb32(pb); /* reserved */
            avio_rb16(pb); /* reserved */
            dref_id = avio_rb16(pb);
        } else if (size <= 7) {
            av_log(c->fc, AV_LOG_ERROR,
                   "invalid size %"PRId64" in stsd\n", size);
            return AVERROR_INVALIDDATA;
        }

        if (mov_skip_multiple_stsd(c, pb, st->codecpar->codec_tag, format,
                                   size - (avio_tell(pb) - start_pos))) {
            sc->stsd_count++;
            continue;
        }

        sc->pseudo_stream_id = st->codecpar->codec_tag ? -1 : pseudo_stream_id;
        sc->dref_id= dref_id;
        sc->format = format;

        id = mov_codec_id(st, format);

        av_log(c->fc, AV_LOG_TRACE,
               "size=%"PRId64" 4CC=%s codec_type=%d\n", size,
               av_fourcc2str(format), st->codecpar->codec_type);

        st->codecpar->codec_id = id;
        if (st->codecpar->codec_type==AVMEDIA_TYPE_VIDEO) {
            mov_parse_stsd_video(c, pb, st, sc);
        } else if (st->codecpar->codec_type==AVMEDIA_TYPE_AUDIO) {
            mov_parse_stsd_audio(c, pb, st, sc);
            if (st->codecpar->sample_rate < 0) {
                av_log(c->fc, AV_LOG_ERROR, "Invalid sample rate %d\n", st->codecpar->sample_rate);
                return AVERROR_INVALIDDATA;
            }
            if (st->codecpar->ch_layout.nb_channels < 0) {
                av_log(c->fc, AV_LOG_ERROR, "Invalid channels %d\n", st->codecpar->ch_layout.nb_channels);
                return AVERROR_INVALIDDATA;
            }
        } else if (st->codecpar->codec_type==AVMEDIA_TYPE_SUBTITLE){
            mov_parse_stsd_subtitle(c, pb, st, sc,
                                    size - (avio_tell(pb) - start_pos));
        } else {
            ret = mov_parse_stsd_data(c, pb, st, sc,
                                      size - (avio_tell(pb) - start_pos));
            if (ret < 0)
                return ret;
        }
        /* this will read extra atoms at the end (wave, alac, damr, avcC, hvcC, SMI ...) */
        a.size = size - (avio_tell(pb) - start_pos);
        if (a.size > 8) {
            if ((ret = mov_read_default(c, pb, a)) < 0)
                return ret;
        } else if (a.size > 0)
            avio_skip(pb, a.size);

        if (sc->extradata && st->codecpar->extradata) {
            int extra_size = st->codecpar->extradata_size;

            /* Move the current stream extradata to the stream context one. */
            sc->extradata_size[pseudo_stream_id] = extra_size;
            sc->extradata[pseudo_stream_id] = st->codecpar->extradata;
            st->codecpar->extradata      = NULL;
            st->codecpar->extradata_size = 0;
        }
        sc->stsd_count++;
    }

    if (pb->eof_reached) {
        av_log(c->fc, AV_LOG_WARNING, "reached eof, corrupted STSD atom\n");
        return AVERROR_EOF;
    }

    return 0;
}


static int mov_read_moof(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
    // Set by mov_read_tfhd(). mov_read_trun() will reject files missing tfhd.
    c->fragment.found_tfhd = 0;

    if (!c->has_looked_for_mfra && c->use_mfra_for > 0) {
        c->has_looked_for_mfra = 1;
        if (pb->seekable & AVIO_SEEKABLE_NORMAL) {
            int ret;
            av_log(c->fc, AV_LOG_VERBOSE, "stream has moof boxes, will look "
                    "for a mfra\n");
            if ((ret = mov_read_mfra(c, pb)) < 0) {
                av_log(c->fc, AV_LOG_VERBOSE, "found a moof box but failed to "
                        "read the mfra (may be a live ismv)\n");
            }
        } else {
            av_log(c->fc, AV_LOG_VERBOSE, "found a moof box but stream is not "
                    "seekable, can not look for mfra\n");
        }
    }
    c->fragment.moof_offset = c->fragment.implicit_offset = avio_tell(pb) - 8;
    av_log(c->fc, AV_LOG_TRACE, "moof offset %"PRIx64"\n", c->fragment.moof_offset);
    c->frag_index.current = update_frag_index(c, c->fragment.moof_offset);
    return mov_read_default(c, pb, atom);
}


static void set_last_stream_little_endian(AVFormatContext *fc)
{
    AVStream *st;

    if (fc->nb_streams < 1)
        return;
    st = fc->streams[fc->nb_streams-1];

    switch (st->codecpar->codec_id) {
    case AV_CODEC_ID_PCM_S16BE:
        st->codecpar->codec_id = AV_CODEC_ID_PCM_S16LE;
        break;
    case AV_CODEC_ID_PCM_S24BE:
        st->codecpar->codec_id = AV_CODEC_ID_PCM_S24LE;
        break;
    case AV_CODEC_ID_PCM_S32BE:
        st->codecpar->codec_id = AV_CODEC_ID_PCM_S32LE;
        break;
    case AV_CODEC_ID_PCM_F32BE:
        st->codecpar->codec_id = AV_CODEC_ID_PCM_F32LE;
        break;
    case AV_CODEC_ID_PCM_F64BE:
        st->codecpar->codec_id = AV_CODEC_ID_PCM_F64LE;
        break;
    default:
        break;
    }
}


static int mov_read_tenc(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
    AVStream *st;
    MOVStreamContext *sc;
    unsigned int version, pattern, is_protected, iv_size;

    if (c->fc->nb_streams < 1)
        return 0;
    st = c->fc->streams[c->fc->nb_streams-1];
    sc = st->priv_data;

    if (sc->pseudo_stream_id != 0) {
        av_log(c->fc, AV_LOG_ERROR, "tenc atom are only supported in first sample descriptor\n");
        return AVERROR_PATCHWELCOME;
    }

    if (!sc->cenc.default_encrypted_sample) {
        sc->cenc.default_encrypted_sample = av_encryption_info_alloc(0, 16, 16);
        if (!sc->cenc.default_encrypted_sample) {
            return AVERROR(ENOMEM);
        }
    }

    if (atom.size < 20)
        return AVERROR_INVALIDDATA;

    version = avio_r8(pb); /* version */
    avio_rb24(pb); /* flags */

    avio_r8(pb); /* reserved */
    pattern = avio_r8(pb);

    if (version > 0) {
        sc->cenc.default_encrypted_sample->crypt_byte_block = pattern >> 4;
        sc->cenc.default_encrypted_sample->skip_byte_block = pattern & 0xf;
    }

    is_protected = avio_r8(pb);
    if (is_protected && !sc->cenc.encryption_index) {
        // The whole stream should be by-default encrypted.
        sc->cenc.encryption_index = av_mallocz(sizeof(MOVEncryptionIndex));
        if (!sc->cenc.encryption_index)
            return AVERROR(ENOMEM);
    }
    sc->cenc.per_sample_iv_size = avio_r8(pb);
    if (sc->cenc.per_sample_iv_size != 0 && sc->cenc.per_sample_iv_size != 8 &&
        sc->cenc.per_sample_iv_size != 16) {
        av_log(c->fc, AV_LOG_ERROR, "invalid per-sample IV size value\n");
        return AVERROR_INVALIDDATA;
    }
    if (avio_read(pb, sc->cenc.default_encrypted_sample->key_id, 16) != 16) {
        av_log(c->fc, AV_LOG_ERROR, "failed to read the default key ID\n");
        return AVERROR_INVALIDDATA;
    }

    if (is_protected && !sc->cenc.per_sample_iv_size) {
        iv_size = avio_r8(pb);
        if (iv_size != 8 && iv_size != 16) {
            av_log(c->fc, AV_LOG_ERROR, "invalid default_constant_IV_size in tenc atom\n");
            return AVERROR_INVALIDDATA;
        }

        if (avio_read(pb, sc->cenc.default_encrypted_sample->iv, iv_size) != iv_size) {
            av_log(c->fc, AV_LOG_ERROR, "failed to read the default IV\n");
            return AVERROR_INVALIDDATA;
        }
    }

    return 0;
}


static int read_tfra(MOVContext *mov, AVIOContext *f)
{
    int version, fieldlength, i, j;
    int64_t pos = avio_tell(f);
    uint32_t size = avio_rb32(f);
    unsigned track_id, item_count;

    if (avio_rb32(f) != MKBETAG('t', 'f', 'r', 'a')) {
        return 1;
    }
    av_log(mov->fc, AV_LOG_VERBOSE, "found tfra\n");

    version = avio_r8(f);
    avio_rb24(f);
    track_id = avio_rb32(f);
    fieldlength = avio_rb32(f);
    item_count = avio_rb32(f);
    for (i = 0; i < item_count; i++) {
        int64_t time, offset;
        int index;
        MOVFragmentStreamInfo * frag_stream_info;

        if (avio_feof(f)) {
            return AVERROR_INVALIDDATA;
        }

        if (version == 1) {
            time   = avio_rb64(f);
            offset = avio_rb64(f);
        } else {
            time   = avio_rb32(f);
            offset = avio_rb32(f);
        }

        // The first sample of each stream in a fragment is always a random
        // access sample.  So it's entry in the tfra can be used as the
        // initial PTS of the fragment.
        index = update_frag_index(mov, offset);
        frag_stream_info = get_frag_stream_info(&mov->frag_index, index, track_id);
        if (frag_stream_info &&
            frag_stream_info->first_tfra_pts == AV_NOPTS_VALUE)
            frag_stream_info->first_tfra_pts = time;

        for (j = 0; j < ((fieldlength >> 4) & 3) + 1; j++)
            avio_r8(f);
        for (j = 0; j < ((fieldlength >> 2) & 3) + 1; j++)
            avio_r8(f);
        for (j = 0; j < ((fieldlength >> 0) & 3) + 1; j++)
            avio_r8(f);
    }

    avio_seek(f, pos + size, SEEK_SET);
    return 0;
}


static int build_open_gop_key_points(AVStream *st)
{
    int k;
    int sample_id = 0;
    uint32_t cra_index;
    MOVStreamContext *sc = st->priv_data;

    if (st->codecpar->codec_id != AV_CODEC_ID_HEVC || !sc->sync_group_count)
        return 0;

    /* Build an unrolled index of the samples */
    sc->sample_offsets_count = 0;
    for (uint32_t i = 0; i < sc->ctts_count; i++) {
        if (sc->ctts_data[i].count > INT_MAX - sc->sample_offsets_count)
            return AVERROR(ENOMEM);
        sc->sample_offsets_count += sc->ctts_data[i].count;
    }
    av_freep(&sc->sample_offsets);
    sc->sample_offsets = av_calloc(sc->sample_offsets_count, sizeof(*sc->sample_offsets));
    if (!sc->sample_offsets)
        return AVERROR(ENOMEM);
    k = 0;
    for (uint32_t i = 0; i < sc->ctts_count; i++)
        for (int j = 0; j < sc->ctts_data[i].count; j++)
             sc->sample_offsets[k++] = sc->ctts_data[i].duration;

    /* The following HEVC NAL type reveal the use of open GOP sync points
     * (TODO: BLA types may also be concerned) */
    cra_index = get_sgpd_sync_index(sc, HEVC_NAL_CRA_NUT); /* Clean Random Access */
    if (!cra_index)
        return 0;

    /* Build a list of open-GOP key samples */
    sc->open_key_samples_count = 0;
    for (uint32_t i = 0; i < sc->sync_group_count; i++)
        if (sc->sync_group[i].index == cra_index) {
            if (sc->sync_group[i].count > INT_MAX - sc->open_key_samples_count)
                return AVERROR(ENOMEM);
            sc->open_key_samples_count += sc->sync_group[i].count;
        }
    av_freep(&sc->open_key_samples);
    sc->open_key_samples = av_calloc(sc->open_key_samples_count, sizeof(*sc->open_key_samples));
    if (!sc->open_key_samples)
        return AVERROR(ENOMEM);
    k = 0;
    for (uint32_t i = 0; i < sc->sync_group_count; i++) {
        const MOVSbgp *sg = &sc->sync_group[i];
        if (sg->index == cra_index)
            for (uint32_t j = 0; j < sg->count; j++)
                sc->open_key_samples[k++] = sample_id;
        if (sg->count > INT_MAX - sample_id)
            return AVERROR_PATCHWELCOME;
        sample_id += sg->count;
    }

    /* Identify the minimal time step between samples */
    sc->min_sample_duration = UINT_MAX;
    for (uint32_t i = 0; i < sc->stts_count; i++)
        sc->min_sample_duration = FFMIN(sc->min_sample_duration, sc->stts_data[i].duration);

    return 0;
}


static int mov_read_saio(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
    uint64_t *auxiliary_offsets;
    MOVEncryptionIndex *encryption_index;
    MOVStreamContext *sc;
    int i, ret;
    unsigned int version, entry_count, aux_info_type, aux_info_param;
    unsigned int alloc_size = 0;

    ret = get_current_encryption_info(c, &encryption_index, &sc);
    if (ret != 1)
        return ret;

    if (encryption_index->nb_encrypted_samples) {
        // This can happen if we have both saio/saiz and senc atoms.
        av_log(c->fc, AV_LOG_DEBUG, "Ignoring duplicate encryption info in saio\n");
        return 0;
    }

    if (encryption_index->auxiliary_offsets_count) {
        av_log(c->fc, AV_LOG_ERROR, "Duplicate saio atom\n");
        return AVERROR_INVALIDDATA;
    }

    version = avio_r8(pb); /* version */
    if (avio_rb24(pb) & 0x01) {  /* flags */
        aux_info_type = avio_rb32(pb);
        aux_info_param = avio_rb32(pb);
        if (sc->cenc.default_encrypted_sample) {
            if (aux_info_type != sc->cenc.default_encrypted_sample->scheme) {
                av_log(c->fc, AV_LOG_DEBUG, "Ignoring saio box with non-zero aux_info_type\n");
                return 0;
            }
            if (aux_info_param != 0) {
                av_log(c->fc, AV_LOG_DEBUG, "Ignoring saio box with non-zero aux_info_type_parameter\n");
                return 0;
            }
        } else {
            // Didn't see 'schm' or 'tenc', so this isn't encrypted.
            if ((aux_info_type == MKBETAG('c','e','n','c') ||
                 aux_info_type == MKBETAG('c','e','n','s') ||
                 aux_info_type == MKBETAG('c','b','c','1') ||
                 aux_info_type == MKBETAG('c','b','c','s')) &&
                aux_info_param == 0) {
                av_log(c->fc, AV_LOG_ERROR, "Saw encrypted saio without schm/tenc\n");
                return AVERROR_INVALIDDATA;
            } else {
                return 0;
            }
        }
    } else if (!sc->cenc.default_encrypted_sample) {
        // Didn't see 'schm' or 'tenc', so this isn't encrypted.
        return 0;
    }

    entry_count = avio_rb32(pb);
    if (entry_count >= INT_MAX / sizeof(*auxiliary_offsets))
        return AVERROR(ENOMEM);

    for (i = 0; i < entry_count && !pb->eof_reached; i++) {
        unsigned int min_offsets = FFMIN(FFMAX(i + 1, 1024), entry_count);
        auxiliary_offsets = av_fast_realloc(
            encryption_index->auxiliary_offsets, &alloc_size,
            min_offsets * sizeof(*auxiliary_offsets));
        if (!auxiliary_offsets) {
            av_freep(&encryption_index->auxiliary_offsets);
            return AVERROR(ENOMEM);
        }
        encryption_index->auxiliary_offsets = auxiliary_offsets;

        if (version == 0) {
            encryption_index->auxiliary_offsets[i] = avio_rb32(pb);
        } else {
            encryption_index->auxiliary_offsets[i] = avio_rb64(pb);
        }
        if (c->frag_index.current >= 0) {
            encryption_index->auxiliary_offsets[i] += c->fragment.base_data_offset;
        }
    }

    if (pb->eof_reached) {
        av_log(c->fc, AV_LOG_ERROR, "Hit EOF while reading saio\n");
        av_freep(&encryption_index->auxiliary_offsets);
        return AVERROR_INVALIDDATA;
    }

    encryption_index->auxiliary_offsets_count = entry_count;

    if (encryption_index->auxiliary_info_sample_count) {
        return mov_parse_auxiliary_info(c, sc, pb, encryption_index);
    }

    return 0;
}


static int mov_read_pitm(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
    avio_rb32(pb);  // version & flags.
    c->primary_item_id = avio_rb16(pb);
    return atom.size;
}


static int mov_read_stco(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
    AVStream *st;
    MOVStreamContext *sc;
    unsigned int i, entries;

    if (c->trak_index < 0) {
        av_log(c->fc, AV_LOG_WARNING, "STCO outside TRAK\n");
        return 0;
    }
    if (c->fc->nb_streams < 1)
        return 0;
    st = c->fc->streams[c->fc->nb_streams-1];
    sc = st->priv_data;

    avio_r8(pb); /* version */
    avio_rb24(pb); /* flags */

    entries = avio_rb32(pb);

    if (!entries)
        return 0;

    if (sc->chunk_offsets) {
        av_log(c->fc, AV_LOG_WARNING, "Ignoring duplicated STCO atom\n");
        return 0;
    }
    av_free(sc->chunk_offsets);
    sc->chunk_count = 0;
    sc->chunk_offsets = av_malloc_array(entries, sizeof(*sc->chunk_offsets));
    if (!sc->chunk_offsets)
        return AVERROR(ENOMEM);
    sc->chunk_count = entries;

    if      (atom.type == MKTAG('s','t','c','o'))
        for (i = 0; i < entries && !pb->eof_reached; i++)
            sc->chunk_offsets[i] = avio_rb32(pb);
    else if (atom.type == MKTAG('c','o','6','4'))
        for (i = 0; i < entries && !pb->eof_reached; i++)
            sc->chunk_offsets[i] = avio_rb64(pb);
    else
        return AVERROR_INVALIDDATA;

    sc->chunk_count = i;

    if (pb->eof_reached) {
        av_log(c->fc, AV_LOG_WARNING, "reached eof, corrupted STCO atom\n");
        return AVERROR_EOF;
    }

    return 0;
}


static MOVFragmentStreamInfo * get_current_frag_stream_info(
    MOVFragmentIndex *frag_index)
{
    MOVFragmentIndexItem *item;
    if (frag_index->current < 0 ||
        frag_index->current >= frag_index->nb_items)
        return NULL;

    item = &frag_index->item[frag_index->current];
    if (item->current >= 0 && item->current < item->nb_stream_info)
        return &item->stream_info[item->current];

    // This shouldn't happen
    return NULL;
}


static int mov_read_stps(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
    AVStream *st;
    MOVStreamContext *sc;
    unsigned i, entries;

    if (c->fc->nb_streams < 1)
        return 0;
    st = c->fc->streams[c->fc->nb_streams-1];
    sc = st->priv_data;

    avio_rb32(pb); // version + flags

    entries = avio_rb32(pb);
    if (sc->stps_data)
        av_log(c->fc, AV_LOG_WARNING, "Duplicated STPS atom\n");
    av_free(sc->stps_data);
    sc->stps_count = 0;
    sc->stps_data = av_malloc_array(entries, sizeof(*sc->stps_data));
    if (!sc->stps_data)
        return AVERROR(ENOMEM);

    for (i = 0; i < entries && !pb->eof_reached; i++) {
        sc->stps_data[i] = avio_rb32(pb);
    }

    sc->stps_count = i;

    if (pb->eof_reached) {
        av_log(c->fc, AV_LOG_WARNING, "reached eof, corrupted STPS atom\n");
        return AVERROR_EOF;
    }

    return 0;
}


static int mov_read_udta_string(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
    char tmp_key[AV_FOURCC_MAX_STRING_SIZE] = {0};
    char key2[32], language[4] = {0};
    char *str = NULL;
    const char *key = NULL;
    uint16_t langcode = 0;
    uint32_t data_type = 0, str_size, str_size_alloc;
    int (*parse)(MOVContext*, AVIOContext*, unsigned, const char*) = NULL;
    int raw = 0;
    int num = 0;

    switch (atom.type) {
    case MKTAG( '@','P','R','M'): key = "premiere_version"; raw = 1; break;
    case MKTAG( '@','P','R','Q'): key = "quicktime_version"; raw = 1; break;
    case MKTAG( 'X','M','P','_'):
        if (c->export_xmp) { key = "xmp"; raw = 1; } break;
    case MKTAG( 'a','A','R','T'): key = "album_artist";    break;
    case MKTAG( 'a','k','I','D'): key = "account_type";
        parse = mov_metadata_int8_no_padding; break;
    case MKTAG( 'a','p','I','D'): key = "account_id"; break;
    case MKTAG( 'c','a','t','g'): key = "category"; break;
    case MKTAG( 'c','p','i','l'): key = "compilation";
        parse = mov_metadata_int8_no_padding; break;
    case MKTAG( 'c','p','r','t'): key = "copyright"; break;
    case MKTAG( 'd','e','s','c'): key = "description"; break;
    case MKTAG( 'd','i','s','k'): key = "disc";
        parse = mov_metadata_track_or_disc_number; break;
    case MKTAG( 'e','g','i','d'): key = "episode_uid";
        parse = mov_metadata_int8_no_padding; break;
    case MKTAG( 'F','I','R','M'): key = "firmware"; raw = 1; break;
    case MKTAG( 'g','n','r','e'): key = "genre";
        parse = mov_metadata_gnre; break;
    case MKTAG( 'h','d','v','d'): key = "hd_video";
        parse = mov_metadata_int8_no_padding; break;
    case MKTAG( 'H','M','M','T'):
        return mov_metadata_hmmt(c, pb, atom.size);
    case MKTAG( 'k','e','y','w'): key = "keywords";  break;
    case MKTAG( 'l','d','e','s'): key = "synopsis";  break;
    case MKTAG( 'l','o','c','i'):
        return mov_metadata_loci(c, pb, atom.size);
    case MKTAG( 'm','a','n','u'): key = "make"; break;
    case MKTAG( 'm','o','d','l'): key = "model"; break;
    case MKTAG( 'p','c','s','t'): key = "podcast";
        parse = mov_metadata_int8_no_padding; break;
    case MKTAG( 'p','g','a','p'): key = "gapless_playback";
        parse = mov_metadata_int8_no_padding; break;
    case MKTAG( 'p','u','r','d'): key = "purchase_date"; break;
    case MKTAG( 'r','t','n','g'): key = "rating";
        parse = mov_metadata_int8_no_padding; break;
    case MKTAG( 's','o','a','a'): key = "sort_album_artist"; break;
    case MKTAG( 's','o','a','l'): key = "sort_album";   break;
    case MKTAG( 's','o','a','r'): key = "sort_artist";  break;
    case MKTAG( 's','o','c','o'): key = "sort_composer"; break;
    case MKTAG( 's','o','n','m'): key = "sort_name";    break;
    case MKTAG( 's','o','s','n'): key = "sort_show";    break;
    case MKTAG( 's','t','i','k'): key = "media_type";
        parse = mov_metadata_int8_no_padding; break;
    case MKTAG( 't','r','k','n'): key = "track";
        parse = mov_metadata_track_or_disc_number; break;
    case MKTAG( 't','v','e','n'): key = "episode_id"; break;
    case MKTAG( 't','v','e','s'): key = "episode_sort";
        parse = mov_metadata_int8_bypass_padding; break;
    case MKTAG( 't','v','n','n'): key = "network";   break;
    case MKTAG( 't','v','s','h'): key = "show";      break;
    case MKTAG( 't','v','s','n'): key = "season_number";
        parse = mov_metadata_int8_bypass_padding; break;
    case MKTAG(0xa9,'A','R','T'): key = "artist";    break;
    case MKTAG(0xa9,'P','R','D'): key = "producer";  break;
    case MKTAG(0xa9,'a','l','b'): key = "album";     break;
    case MKTAG(0xa9,'a','u','t'): key = "artist";    break;
    case MKTAG(0xa9,'c','h','p'): key = "chapter";   break;
    case MKTAG(0xa9,'c','m','t'): key = "comment";   break;
    case MKTAG(0xa9,'c','o','m'): key = "composer";  break;
    case MKTAG(0xa9,'c','p','y'): key = "copyright"; break;
    case MKTAG(0xa9,'d','a','y'): key = "date";      break;
    case MKTAG(0xa9,'d','i','r'): key = "director";  break;
    case MKTAG(0xa9,'d','i','s'): key = "disclaimer"; break;
    case MKTAG(0xa9,'e','d','1'): key = "edit_date"; break;
    case MKTAG(0xa9,'e','n','c'): key = "encoder";   break;
    case MKTAG(0xa9,'f','m','t'): key = "original_format"; break;
    case MKTAG(0xa9,'g','e','n'): key = "genre";     break;
    case MKTAG(0xa9,'g','r','p'): key = "grouping";  break;
    case MKTAG(0xa9,'h','s','t'): key = "host_computer"; break;
    case MKTAG(0xa9,'i','n','f'): key = "comment";   break;
    case MKTAG(0xa9,'l','y','r'): key = "lyrics";    break;
    case MKTAG(0xa9,'m','a','k'): key = "make";      break;
    case MKTAG(0xa9,'m','o','d'): key = "model";     break;
    case MKTAG(0xa9,'n','a','m'): key = "title";     break;
    case MKTAG(0xa9,'o','p','e'): key = "original_artist"; break;
    case MKTAG(0xa9,'p','r','d'): key = "producer";  break;
    case MKTAG(0xa9,'p','r','f'): key = "performers"; break;
    case MKTAG(0xa9,'r','e','q'): key = "playback_requirements"; break;
    case MKTAG(0xa9,'s','r','c'): key = "original_source"; break;
    case MKTAG(0xa9,'s','t','3'): key = "subtitle";  break;
    case MKTAG(0xa9,'s','w','r'): key = "encoder";   break;
    case MKTAG(0xa9,'t','o','o'): key = "encoder";   break;
    case MKTAG(0xa9,'t','r','k'): key = "track";     break;
    case MKTAG(0xa9,'u','r','l'): key = "URL";       break;
    case MKTAG(0xa9,'w','r','n'): key = "warning";   break;
    case MKTAG(0xa9,'w','r','t'): key = "composer";  break;
    case MKTAG(0xa9,'x','y','z'): key = "location";  break;
    }
retry:
    if (c->itunes_metadata && atom.size > 8) {
        int data_size = avio_rb32(pb);
        int tag = avio_rl32(pb);
        if (tag == MKTAG('d','a','t','a') && data_size <= atom.size && data_size >= 16) {
            data_type = avio_rb32(pb); // type
            avio_rb32(pb); // unknown
            str_size = data_size - 16;
            atom.size -= 16;

            if (!key && c->found_hdlr_mdta && c->meta_keys) {
                uint32_t index = av_bswap32(atom.type); // BE number has been read as LE
                if (index < c->meta_keys_count && index > 0) {
                    key = c->meta_keys[index];
                } else if (atom.type != MKTAG('c', 'o', 'v', 'r')) {
                    av_log(c->fc, AV_LOG_WARNING,
                           "The index of 'data' is out of range: %"PRId32" < 1 or >= %d.\n",
                           index, c->meta_keys_count);
                }
            }
            if (atom.type == MKTAG('c', 'o', 'v', 'r') ||
                (key && !strcmp(key, "com.apple.quicktime.artwork"))) {
                int ret = mov_read_covr(c, pb, data_type, str_size);
                if (ret < 0) {
                    av_log(c->fc, AV_LOG_ERROR, "Error parsing cover art.\n");
                    return ret;
                }
                atom.size -= str_size;
                if (atom.size > 8)
                    goto retry;
                return ret;
            }
        } else return 0;
    } else if (atom.size > 4 && key && !c->itunes_metadata && !raw) {
        str_size = avio_rb16(pb); // string length
        if (str_size > atom.size) {
            raw = 1;
            avio_seek(pb, -2, SEEK_CUR);
            av_log(c->fc, AV_LOG_WARNING, "UDTA parsing failed retrying raw\n");
            goto retry;
        }
        langcode = avio_rb16(pb);
        ff_mov_lang_to_iso639(langcode, language);
        atom.size -= 4;
    } else
        str_size = atom.size;

    if (c->export_all && !key) {
        key = av_fourcc_make_string(tmp_key, atom.type);
    }

    if (!key)
        return 0;
    if (atom.size < 0 || str_size >= INT_MAX/2)
        return AVERROR_INVALIDDATA;

    // Allocates enough space if data_type is a int32 or float32 number, otherwise
    // worst-case requirement for output string in case of utf8 coded input
    num = (data_type >= 21 && data_type <= 23);
    str_size_alloc = (num ? 512 : (raw ? str_size : str_size * 2)) + 1;
    str = av_mallocz(str_size_alloc);
    if (!str)
        return AVERROR(ENOMEM);

    if (parse)
        parse(c, pb, str_size, key);
    else {
        if (!raw && (data_type == 3 || (data_type == 0 && (langcode < 0x400 || langcode == 0x7fff)))) { // MAC Encoded
            mov_read_mac_string(c, pb, str_size, str, str_size_alloc);
        } else if (data_type == 21) { // BE signed integer, variable size
            int val = 0;
            if (str_size == 1)
                val = (int8_t)avio_r8(pb);
            else if (str_size == 2)
                val = (int16_t)avio_rb16(pb);
            else if (str_size == 3)
                val = ((int32_t)(avio_rb24(pb)<<8))>>8;
            else if (str_size == 4)
                val = (int32_t)avio_rb32(pb);
            if (snprintf(str, str_size_alloc, "%d", val) >= str_size_alloc) {
                av_log(c->fc, AV_LOG_ERROR,
                       "Failed to store the number (%d) in string.\n", val);
                av_free(str);
                return AVERROR_INVALIDDATA;
            }
        } else if (data_type == 22) { // BE unsigned integer, variable size
            unsigned int val = 0;
            if (str_size == 1)
                val = avio_r8(pb);
            else if (str_size == 2)
                val = avio_rb16(pb);
            else if (str_size == 3)
                val = avio_rb24(pb);
            else if (str_size == 4)
                val = avio_rb32(pb);
            if (snprintf(str, str_size_alloc, "%u", val) >= str_size_alloc) {
                av_log(c->fc, AV_LOG_ERROR,
                       "Failed to store the number (%u) in string.\n", val);
                av_free(str);
                return AVERROR_INVALIDDATA;
            }
        } else if (data_type == 23 && str_size >= 4) {  // BE float32
            float val = av_int2float(avio_rb32(pb));
            if (snprintf(str, str_size_alloc, "%f", val) >= str_size_alloc) {
                av_log(c->fc, AV_LOG_ERROR,
                       "Failed to store the float32 number (%f) in string.\n", val);
                av_free(str);
                return AVERROR_INVALIDDATA;
            }
        } else if (data_type > 1 && data_type != 4) {
            // data_type can be 0 if not set at all above. data_type 1 means
            // UTF8 and 4 means "UTF8 sort". For any other type (UTF16 or e.g.
            // a picture), don't return it blindly in a string that is supposed
            // to be UTF8 text.
            av_log(c->fc, AV_LOG_WARNING, "Skipping unhandled metadata %s of type %d\n", key, data_type);
            av_free(str);
            return 0;
        } else {
            int ret = ffio_read_size(pb, str, str_size);
            if (ret < 0) {
                av_free(str);
                return ret;
            }
            str[str_size] = 0;
        }
        c->fc->event_flags |= AVFMT_EVENT_FLAG_METADATA_UPDATED;
        av_dict_set(&c->fc->metadata, key, str, 0);
        if (*language && strcmp(language, "und")) {
            snprintf(key2, sizeof(key2), "%s-%s", key, language);
            av_dict_set(&c->fc->metadata, key2, str, 0);
        }
        if (!strcmp(key, "encoder")) {
            int major, minor, micro;
            if (sscanf(str, "HandBrake %d.%d.%d", &major, &minor, &micro) == 3) {
                c->handbrake_version = 1000000*major + 1000*minor + micro;
            }
        }
    }

    av_freep(&str);
    return 0;
}


static void mov_estimate_video_delay(MOVContext *c, AVStream* st)
{
    MOVStreamContext *msc = st->priv_data;
    FFStream *const sti = ffstream(st);
    int ctts_ind = 0;
    int ctts_sample = 0;
    int64_t pts_buf[MAX_REORDER_DELAY + 1]; // Circular buffer to sort pts.
    int buf_start = 0;
    int j, r, num_swaps;

    for (j = 0; j < MAX_REORDER_DELAY + 1; j++)
        pts_buf[j] = INT64_MIN;

    if (st->codecpar->video_delay <= 0 && msc->ctts_data &&
        st->codecpar->codec_id == AV_CODEC_ID_H264) {
        st->codecpar->video_delay = 0;
        for (int ind = 0; ind < sti->nb_index_entries && ctts_ind < msc->ctts_count; ++ind) {
            // Point j to the last elem of the buffer and insert the current pts there.
            j = buf_start;
            buf_start = (buf_start + 1);
            if (buf_start == MAX_REORDER_DELAY + 1)
                buf_start = 0;

            pts_buf[j] = sti->index_entries[ind].timestamp + msc->ctts_data[ctts_ind].duration;

            // The timestamps that are already in the sorted buffer, and are greater than the
            // current pts, are exactly the timestamps that need to be buffered to output PTS
            // in correct sorted order.
            // Hence the video delay (which is the buffer size used to sort DTS and output PTS),
            // can be computed as the maximum no. of swaps any particular timestamp needs to
            // go through, to keep this buffer in sorted order.
            num_swaps = 0;
            while (j != buf_start) {
                r = j - 1;
                if (r < 0) r = MAX_REORDER_DELAY;
                if (pts_buf[j] < pts_buf[r]) {
                    FFSWAP(int64_t, pts_buf[j], pts_buf[r]);
                    ++num_swaps;
                } else {
                    break;
                }
                j = r;
            }
            st->codecpar->video_delay = FFMAX(st->codecpar->video_delay, num_swaps);

            ctts_sample++;
            if (ctts_sample == msc->ctts_data[ctts_ind].count) {
                ctts_ind++;
                ctts_sample = 0;
            }
        }
        av_log(c->fc, AV_LOG_DEBUG, "Setting codecpar->delay to %d for stream st: %d\n",
               st->codecpar->video_delay, st->index);
    }
}


static int mov_read_dvc1(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
    AVStream *st;
    uint8_t profile_level;
    int ret;

    if (c->fc->nb_streams < 1)
        return 0;
    st = c->fc->streams[c->fc->nb_streams-1];

    if (atom.size >= (1<<28) || atom.size < 7)
        return AVERROR_INVALIDDATA;

    profile_level = avio_r8(pb);
    if ((profile_level & 0xf0) != 0xc0)
        return 0;

    avio_seek(pb, 6, SEEK_CUR);
    ret = ff_get_extradata(c->fc, st->codecpar, pb, atom.size - 7);
    if (ret < 0)
        return ret;

    return 0;
}


static int mov_read_schm(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
    AVStream *st;
    MOVStreamContext *sc;

    if (c->fc->nb_streams < 1)
        return 0;
    st = c->fc->streams[c->fc->nb_streams-1];
    sc = st->priv_data;

    if (sc->pseudo_stream_id != 0) {
        av_log(c->fc, AV_LOG_ERROR, "schm boxes are only supported in first sample descriptor\n");
        return AVERROR_PATCHWELCOME;
    }

    if (atom.size < 8)
        return AVERROR_INVALIDDATA;

    avio_rb32(pb); /* version and flags */

    if (!sc->cenc.default_encrypted_sample) {
        sc->cenc.default_encrypted_sample = av_encryption_info_alloc(0, 16, 16);
        if (!sc->cenc.default_encrypted_sample) {
            return AVERROR(ENOMEM);
        }
    }

    sc->cenc.default_encrypted_sample->scheme = avio_rb32(pb);
    return 0;
}


static int mov_read_ftyp(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
    uint32_t minor_ver;
    int comp_brand_size;
    char* comp_brands_str;
    uint8_t type[5] = {0};
    int ret = ffio_read_size(pb, type, 4);
    if (ret < 0)
        return ret;

    if (strcmp(type, "qt  "))
        c->isom = 1;
    av_log(c->fc, AV_LOG_DEBUG, "ISO: File Type Major Brand: %.4s\n",(char *)&type);
    av_dict_set(&c->fc->metadata, "major_brand", type, 0);
    c->is_still_picture_avif = !strncmp(type, "avif", 4);
    minor_ver = avio_rb32(pb); /* minor version */
    av_dict_set_int(&c->fc->metadata, "minor_version", minor_ver, 0);

    comp_brand_size = atom.size - 8;
    if (comp_brand_size < 0 || comp_brand_size == INT_MAX)
        return AVERROR_INVALIDDATA;
    comp_brands_str = av_malloc(comp_brand_size + 1); /* Add null terminator */
    if (!comp_brands_str)
        return AVERROR(ENOMEM);

    ret = ffio_read_size(pb, comp_brands_str, comp_brand_size);
    if (ret < 0) {
        av_freep(&comp_brands_str);
        return ret;
    }
    comp_brands_str[comp_brand_size] = 0;
    av_dict_set(&c->fc->metadata, "compatible_brands",
                comp_brands_str, AV_DICT_DONT_STRDUP_VAL);

    // Logic for handling Audible's .aaxc files
    if (!strcmp(type, "aaxc")) {
        mov_aaxc_crypto(c);
    }

    return 0;
}


static int mov_read_sample_encryption_info(MOVContext *c, AVIOContext *pb, MOVStreamContext *sc, AVEncryptionInfo **sample, int use_subsamples)
{
    int i, ret;
    unsigned int subsample_count;
    AVSubsampleEncryptionInfo *subsamples;

    if (!sc->cenc.default_encrypted_sample) {
        av_log(c->fc, AV_LOG_ERROR, "Missing schm or tenc\n");
        return AVERROR_INVALIDDATA;
    }

    *sample = av_encryption_info_clone(sc->cenc.default_encrypted_sample);
    if (!*sample)
        return AVERROR(ENOMEM);

    if (sc->cenc.per_sample_iv_size != 0) {
        if ((ret = ffio_read_size(pb, (*sample)->iv, sc->cenc.per_sample_iv_size)) < 0) {
            av_log(c->fc, AV_LOG_ERROR, "failed to read the initialization vector\n");
            av_encryption_info_free(*sample);
            *sample = NULL;
            return ret;
        }
    }

    if (use_subsamples) {
        subsample_count = avio_rb16(pb);
        av_free((*sample)->subsamples);
        (*sample)->subsamples = av_calloc(subsample_count, sizeof(*subsamples));
        if (!(*sample)->subsamples) {
            av_encryption_info_free(*sample);
            *sample = NULL;
            return AVERROR(ENOMEM);
        }

        for (i = 0; i < subsample_count && !pb->eof_reached; i++) {
            (*sample)->subsamples[i].bytes_of_clear_data = avio_rb16(pb);
            (*sample)->subsamples[i].bytes_of_protected_data = avio_rb32(pb);
        }

        if (pb->eof_reached) {
            av_log(c->fc, AV_LOG_ERROR, "hit EOF while reading sub-sample encryption info\n");
            av_encryption_info_free(*sample);
            *sample = NULL;
            return AVERROR_INVALIDDATA;
        }
        (*sample)->subsample_count = subsample_count;
    }

    return 0;
}


static int mov_read_covr(MOVContext *c, AVIOContext *pb, int type, int len)
{
    AVStream *st;
    MOVStreamContext *sc;
    enum AVCodecID id;
    int ret;

    switch (type) {
    case 0xd:  id = AV_CODEC_ID_MJPEG; break;
    case 0xe:  id = AV_CODEC_ID_PNG;   break;
    case 0x1b: id = AV_CODEC_ID_BMP;   break;
    default:
        av_log(c->fc, AV_LOG_WARNING, "Unknown cover type: 0x%x.\n", type);
        avio_skip(pb, len);
        return 0;
    }

    sc = av_mallocz(sizeof(*sc));
    if (!sc)
        return AVERROR(ENOMEM);
    ret = ff_add_attached_pic(c->fc, NULL, pb, NULL, len);
    if (ret < 0) {
        av_free(sc);
        return ret;
    }
    st = c->fc->streams[c->fc->nb_streams - 1];
    st->priv_data = sc;

    if (st->attached_pic.size >= 8 && id != AV_CODEC_ID_BMP) {
        if (AV_RB64(st->attached_pic.data) == 0x89504e470d0a1a0a) {
            id = AV_CODEC_ID_PNG;
        } else {
            id = AV_CODEC_ID_MJPEG;
        }
    }
    st->codecpar->codec_id   = id;

    return 0;
}


static int mov_aaxc_crypto(MOVContext *c)
{
    if (c->audible_key_size != 16) {
        av_log(c->fc, AV_LOG_FATAL, "[aaxc] audible_key value needs to be 16 bytes!\n");
        return AVERROR(EINVAL);
    }

    if (c->audible_iv_size != 16) {
        av_log(c->fc, AV_LOG_FATAL, "[aaxc] audible_iv value needs to be 16 bytes!\n");
        return AVERROR(EINVAL);
    }

    c->aes_decrypt = av_aes_alloc();
    if (!c->aes_decrypt) {
        return AVERROR(ENOMEM);
    }

    memcpy(c->file_key, c->audible_key, 16);
    memcpy(c->file_iv, c->audible_iv, 16);
    c->aax_mode = 1;

    return 0;
}


static int mov_read_sgpd(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
    AVStream *st;
    MOVStreamContext *sc;
    uint8_t version;
    uint32_t grouping_type;
    uint32_t default_length;
    av_unused uint32_t default_group_description_index;
    uint32_t entry_count;

    if (c->fc->nb_streams < 1)
        return 0;
    st = c->fc->streams[c->fc->nb_streams - 1];
    sc = st->priv_data;

    version = avio_r8(pb); /* version */
    avio_rb24(pb); /* flags */
    grouping_type = avio_rl32(pb);

    /*
     * This function only supports "sync" boxes, but the code is able to parse
     * other boxes (such as "tscl", "tsas" and "stsa")
     */
    if (grouping_type != MKTAG('s','y','n','c'))
        return 0;

    default_length = version >= 1 ? avio_rb32(pb) : 0;
    default_group_description_index = version >= 2 ? avio_rb32(pb) : 0;
    entry_count = avio_rb32(pb);

    av_freep(&sc->sgpd_sync);
    sc->sgpd_sync_count = entry_count;
    sc->sgpd_sync = av_calloc(entry_count, sizeof(*sc->sgpd_sync));
    if (!sc->sgpd_sync)
        return AVERROR(ENOMEM);

    for (uint32_t i = 0; i < entry_count && !pb->eof_reached; i++) {
        uint32_t description_length = default_length;
        if (version >= 1 && default_length == 0)
            description_length = avio_rb32(pb);
        if (grouping_type == MKTAG('s','y','n','c')) {
            const uint8_t nal_unit_type = avio_r8(pb) & 0x3f;
            sc->sgpd_sync[i] = nal_unit_type;
            description_length -= 1;
        }
        avio_skip(pb, description_length);
    }

    if (pb->eof_reached) {
        av_log(c->fc, AV_LOG_WARNING, "reached eof, corrupted SGPD atom\n");
        return AVERROR_EOF;
    }

    return 0;
}


static uint32_t get_sgpd_sync_index(const MOVStreamContext *sc, int nal_unit_type)
{
    for (uint32_t i = 0; i < sc->sgpd_sync_count; i++)
        if (sc->sgpd_sync[i] == HEVC_NAL_CRA_NUT)
            return i + 1;
    return 0;
}


static int mov_read_smdm(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
    MOVStreamContext *sc;
    int i, version;

    if (c->fc->nb_streams < 1)
        return AVERROR_INVALIDDATA;

    sc = c->fc->streams[c->fc->nb_streams - 1]->priv_data;

    if (atom.size < 5) {
        av_log(c->fc, AV_LOG_ERROR, "Empty Mastering Display Metadata box\n");
        return AVERROR_INVALIDDATA;
    }

    version = avio_r8(pb);
    if (version) {
        av_log(c->fc, AV_LOG_WARNING, "Unsupported Mastering Display Metadata box version %d\n", version);
        return 0;
    }
    if (sc->mastering)
        return AVERROR_INVALIDDATA;

    avio_skip(pb, 3); /* flags */

    sc->mastering = av_mastering_display_metadata_alloc();
    if (!sc->mastering)
        return AVERROR(ENOMEM);

    for (i = 0; i < 3; i++) {
        sc->mastering->display_primaries[i][0] = av_make_q(avio_rb16(pb), 1 << 16);
        sc->mastering->display_primaries[i][1] = av_make_q(avio_rb16(pb), 1 << 16);
    }
    sc->mastering->white_point[0] = av_make_q(avio_rb16(pb), 1 << 16);
    sc->mastering->white_point[1] = av_make_q(avio_rb16(pb), 1 << 16);

    sc->mastering->max_luminance = av_make_q(avio_rb32(pb), 1 << 8);
    sc->mastering->min_luminance = av_make_q(avio_rb32(pb), 1 << 14);

    sc->mastering->has_primaries = 1;
    sc->mastering->has_luminance = 1;

    return 0;
}


static int mov_try_read_block(AVIOContext *pb, size_t size, uint8_t **data)
{
    const unsigned int block_size = 1024 * 1024;
    uint8_t *buffer = NULL;
    unsigned int alloc_size = 0, offset = 0;
    while (offset < size) {
        unsigned int new_size =
            alloc_size >= INT_MAX - block_size ? INT_MAX : alloc_size + block_size;
        uint8_t *new_buffer = av_fast_realloc(buffer, &alloc_size, new_size);
        unsigned int to_read = FFMIN(size, alloc_size) - offset;
        if (!new_buffer) {
            av_free(buffer);
            return AVERROR(ENOMEM);
        }
        buffer = new_buffer;

        if (avio_read(pb, buffer + offset, to_read) != to_read) {
            av_free(buffer);
            return AVERROR_INVALIDDATA;
        }
        offset += to_read;
    }

    *data = buffer;
    return 0;
}


static int mov_read_enda(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
    int little_endian = avio_rb16(pb) & 0xFF;
    av_log(c->fc, AV_LOG_TRACE, "enda %d\n", little_endian);
    if (little_endian == 1)
        set_last_stream_little_endian(c->fc);
    return 0;
}


static int update_frag_index(MOVContext *c, int64_t offset)
{
    int index, i;
    MOVFragmentIndexItem * item;
    MOVFragmentStreamInfo * frag_stream_info;

    // If moof_offset already exists in frag_index, return index to it
    index = search_frag_moof_offset(&c->frag_index, offset);
    if (index < c->frag_index.nb_items &&
        c->frag_index.item[index].moof_offset == offset)
        return index;

    // offset is not yet in frag index.
    // Insert new item at index (sorted by moof offset)
    item = av_fast_realloc(c->frag_index.item,
                           &c->frag_index.allocated_size,
                           (c->frag_index.nb_items + 1) *
                           sizeof(*c->frag_index.item));
    if (!item)
        return -1;
    c->frag_index.item = item;

    frag_stream_info = av_realloc_array(NULL, c->fc->nb_streams,
                                        sizeof(*item->stream_info));
    if (!frag_stream_info)
        return -1;

    for (i = 0; i < c->fc->nb_streams; i++) {
        // Avoid building frag index if streams lack track id.
        if (c->fc->streams[i]->id < 0) {
            av_free(frag_stream_info);
            return AVERROR_INVALIDDATA;
        }

        frag_stream_info[i].id = c->fc->streams[i]->id;
        frag_stream_info[i].sidx_pts = AV_NOPTS_VALUE;
        frag_stream_info[i].tfdt_dts = AV_NOPTS_VALUE;
        frag_stream_info[i].next_trun_dts = AV_NOPTS_VALUE;
        frag_stream_info[i].first_tfra_pts = AV_NOPTS_VALUE;
        frag_stream_info[i].index_base = -1;
        frag_stream_info[i].index_entry = -1;
        frag_stream_info[i].encryption_index = NULL;
    }

    if (index < c->frag_index.nb_items)
        memmove(c->frag_index.item + index + 1, c->frag_index.item + index,
                (c->frag_index.nb_items - index) * sizeof(*c->frag_index.item));

    item = &c->frag_index.item[index];
    item->headers_read = 0;
    item->current = 0;
    item->nb_stream_info = c->fc->nb_streams;
    item->moof_offset = offset;
    item->stream_info = frag_stream_info;
    c->frag_index.nb_items++;

    return index;
}


static int mov_read_sidx(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
    int64_t stream_size = avio_size(pb);
    int64_t offset = av_sat_add64(avio_tell(pb), atom.size), pts, timestamp;
    uint8_t version, is_complete;
    int64_t offadd;
    unsigned i, j, track_id, item_count;
    AVStream *st = NULL;
    AVStream *ref_st = NULL;
    MOVStreamContext *sc, *ref_sc = NULL;
    AVRational timescale;

    version = avio_r8(pb);
    if (version > 1) {
        avpriv_request_sample(c->fc, "sidx version %u", version);
        return 0;
    }

    avio_rb24(pb); // flags

    track_id = avio_rb32(pb); // Reference ID
    for (i = 0; i < c->fc->nb_streams; i++) {
        if (c->fc->streams[i]->id == track_id) {
            st = c->fc->streams[i];
            break;
        }
    }
    if (!st) {
        av_log(c->fc, AV_LOG_WARNING, "could not find corresponding track id %d\n", track_id);
        return 0;
    }

    sc = st->priv_data;

    timescale = av_make_q(1, avio_rb32(pb));

    if (timescale.den <= 0) {
        av_log(c->fc, AV_LOG_ERROR, "Invalid sidx timescale 1/%d\n", timescale.den);
        return AVERROR_INVALIDDATA;
    }

    if (version == 0) {
        pts = avio_rb32(pb);
        offadd= avio_rb32(pb);
    } else {
        pts = avio_rb64(pb);
        offadd= avio_rb64(pb);
    }
    if (av_sat_add64(offset, offadd) != offset + (uint64_t)offadd)
        return AVERROR_INVALIDDATA;

    offset += (uint64_t)offadd;

    avio_rb16(pb); // reserved

    item_count = avio_rb16(pb);
    if (item_count == 0)
        return AVERROR_INVALIDDATA;

    for (i = 0; i < item_count; i++) {
        int index;
        MOVFragmentStreamInfo * frag_stream_info;
        uint32_t size = avio_rb32(pb);
        uint32_t duration = avio_rb32(pb);
        if (size & 0x80000000) {
            avpriv_request_sample(c->fc, "sidx reference_type 1");
            return AVERROR_PATCHWELCOME;
        }
        avio_rb32(pb); // sap_flags
        timestamp = av_rescale_q(pts, timescale, st->time_base);

        index = update_frag_index(c, offset);
        frag_stream_info = get_frag_stream_info(&c->frag_index, index, track_id);
        if (frag_stream_info)
            frag_stream_info->sidx_pts = timestamp;

        if (av_sat_add64(offset, size) != offset + (uint64_t)size ||
            av_sat_add64(pts, duration) != pts + (uint64_t)duration
        )
            return AVERROR_INVALIDDATA;
        offset += size;
        pts += duration;
    }

    st->duration = sc->track_end = pts;

    sc->has_sidx = 1;

    // See if the remaining bytes are just an mfra which we can ignore.
    is_complete = offset == stream_size;
    if (!is_complete && (pb->seekable & AVIO_SEEKABLE_NORMAL) && stream_size > 0 ) {
        int64_t ret;
        int64_t original_pos = avio_tell(pb);
        if (!c->have_read_mfra_size) {
            if ((ret = avio_seek(pb, stream_size - 4, SEEK_SET)) < 0)
                return ret;
            c->mfra_size = avio_rb32(pb);
            c->have_read_mfra_size = 1;
            if ((ret = avio_seek(pb, original_pos, SEEK_SET)) < 0)
                return ret;
        }
        if (offset == stream_size - c->mfra_size)
            is_complete = 1;
    }

    if (is_complete) {
        // Find first entry in fragment index that came from an sidx.
        // This will pretty much always be the first entry.
        for (i = 0; i < c->frag_index.nb_items; i++) {
            MOVFragmentIndexItem * item = &c->frag_index.item[i];
            for (j = 0; ref_st == NULL && j < item->nb_stream_info; j++) {
                MOVFragmentStreamInfo * si;
                si = &item->stream_info[j];
                if (si->sidx_pts != AV_NOPTS_VALUE) {
                    ref_st = c->fc->streams[j];
                    ref_sc = ref_st->priv_data;
                    break;
                }
            }
        }
        if (ref_st) for (i = 0; i < c->fc->nb_streams; i++) {
            st = c->fc->streams[i];
            sc = st->priv_data;
            if (!sc->has_sidx) {
                st->duration = sc->track_end = av_rescale(ref_st->duration, sc->time_scale, ref_sc->time_scale);
            }
        }

        c->frag_index.complete = 1;
    }

    return 0;
}


static int cenc_scheme_decrypt(MOVContext *c, MOVStreamContext *sc, AVEncryptionInfo *sample, uint8_t *input, int size)
{
    int i, ret;
    int bytes_of_protected_data;

    if (!sc->cenc.aes_ctr) {
        /* initialize the cipher */
        sc->cenc.aes_ctr = av_aes_ctr_alloc();
        if (!sc->cenc.aes_ctr) {
            return AVERROR(ENOMEM);
        }

        ret = av_aes_ctr_init(sc->cenc.aes_ctr, c->decryption_key);
        if (ret < 0) {
            return ret;
        }
    }

    av_aes_ctr_set_full_iv(sc->cenc.aes_ctr, sample->iv);

    if (!sample->subsample_count) {
        /* decrypt the whole packet */
        av_aes_ctr_crypt(sc->cenc.aes_ctr, input, input, size);
        return 0;
    }

    for (i = 0; i < sample->subsample_count; i++) {
        if (sample->subsamples[i].bytes_of_clear_data + sample->subsamples[i].bytes_of_protected_data > size) {
            av_log(c->fc, AV_LOG_ERROR, "subsample size exceeds the packet size left\n");
            return AVERROR_INVALIDDATA;
        }

        /* skip the clear bytes */
        input += sample->subsamples[i].bytes_of_clear_data;
        size -= sample->subsamples[i].bytes_of_clear_data;

        /* decrypt the encrypted bytes */

        bytes_of_protected_data = sample->subsamples[i].bytes_of_protected_data;
        av_aes_ctr_crypt(sc->cenc.aes_ctr, input, input, bytes_of_protected_data);

        input += bytes_of_protected_data;
        size -= bytes_of_protected_data;
    }

    if (size > 0) {
        av_log(c->fc, AV_LOG_ERROR, "leftover packet bytes after subsample processing\n");
        return AVERROR_INVALIDDATA;
    }

    return 0;
}


static int get_eia608_packet(AVIOContext *pb, AVPacket *pkt, int size)
{
    int new_size, ret;

    if (size <= 8)
        return AVERROR_INVALIDDATA;
    new_size = ((size - 8) / 2) * 3;
    ret = av_new_packet(pkt, new_size);
    if (ret < 0)
        return ret;

    avio_skip(pb, 8);
    for (int j = 0; j < new_size; j += 3) {
        pkt->data[j] = 0xFC;
        pkt->data[j+1] = avio_r8(pb);
        pkt->data[j+2] = avio_r8(pb);
    }

    return 0;
}


static int mov_read_close(AVFormatContext *s)
{
    MOVContext *mov = s->priv_data;
    int i, j;

    for (i = 0; i < s->nb_streams; i++) {
        AVStream *st = s->streams[i];
        MOVStreamContext *sc = st->priv_data;

        if (!sc)
            continue;

        av_freep(&sc->ctts_data);
        for (j = 0; j < sc->drefs_count; j++) {
            av_freep(&sc->drefs[j].path);
            av_freep(&sc->drefs[j].dir);
        }
        av_freep(&sc->drefs);

        sc->drefs_count = 0;

        if (!sc->pb_is_copied)
            ff_format_io_close(s, &sc->pb);

        sc->pb = NULL;
        av_freep(&sc->chunk_offsets);
        av_freep(&sc->stsc_data);
        av_freep(&sc->sample_sizes);
        av_freep(&sc->keyframes);
        av_freep(&sc->stts_data);
        av_freep(&sc->sdtp_data);
        av_freep(&sc->stps_data);
        av_freep(&sc->elst_data);
        av_freep(&sc->rap_group);
        av_freep(&sc->sync_group);
        av_freep(&sc->sgpd_sync);
        av_freep(&sc->sample_offsets);
        av_freep(&sc->open_key_samples);
        av_freep(&sc->display_matrix);
        av_freep(&sc->index_ranges);

        if (sc->extradata)
            for (j = 0; j < sc->stsd_count; j++)
                av_free(sc->extradata[j]);
        av_freep(&sc->extradata);
        av_freep(&sc->extradata_size);

        mov_free_encryption_index(&sc->cenc.encryption_index);
        av_encryption_info_free(sc->cenc.default_encrypted_sample);
        av_aes_ctr_free(sc->cenc.aes_ctr);

        av_freep(&sc->stereo3d);
        av_freep(&sc->spherical);
        av_freep(&sc->mastering);
        av_freep(&sc->coll);
    }

    av_freep(&mov->dv_demux);
    avformat_free_context(mov->dv_fctx);
    mov->dv_fctx = NULL;

    if (mov->meta_keys) {
        for (i = 1; i < mov->meta_keys_count; i++) {
            av_freep(&mov->meta_keys[i]);
        }
        av_freep(&mov->meta_keys);
    }

    av_freep(&mov->trex_data);
    av_freep(&mov->bitrates);

    for (i = 0; i < mov->frag_index.nb_items; i++) {
        MOVFragmentStreamInfo *frag = mov->frag_index.item[i].stream_info;
        for (j = 0; j < mov->frag_index.item[i].nb_stream_info; j++) {
            mov_free_encryption_index(&frag[j].encryption_index);
        }
        av_freep(&mov->frag_index.item[i].stream_info);
    }
    av_freep(&mov->frag_index.item);

    av_freep(&mov->aes_decrypt);
    av_freep(&mov->chapter_tracks);
    av_freep(&mov->avif_info);

    return 0;
}


static int mov_read_pcmc(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
    int format_flags;

    if (atom.size < 6) {
        av_log(c->fc, AV_LOG_ERROR, "Empty pcmC box\n");
        return AVERROR_INVALIDDATA;
    }

    avio_r8(pb);    // version
    avio_rb24(pb);  // flags
    format_flags = avio_r8(pb);
    if (format_flags == 1) // indicates little-endian format. If not present, big-endian format is used
        set_last_stream_little_endian(c->fc);

    return 0;
}


static void mov_parse_stsd_subtitle(MOVContext *c, AVIOContext *pb,
                                    AVStream *st, MOVStreamContext *sc,
                                    int64_t size)
{
    // ttxt stsd contains display flags, justification, background
    // color, fonts, and default styles, so fake an atom to read it
    MOVAtom fake_atom = { .size = size };
    // mp4s contains a regular esds atom
    if (st->codecpar->codec_tag != AV_RL32("mp4s"))
        mov_read_glbl(c, pb, fake_atom);
    st->codecpar->width  = sc->width;
    st->codecpar->height = sc->height;
}


static int mov_read_rtmd_track(AVFormatContext *s, AVStream *st)
{
    MOVStreamContext *sc = st->priv_data;
    FFStream *const sti = ffstream(st);
    char buf[AV_TIMECODE_STR_SIZE];
    int64_t cur_pos = avio_tell(sc->pb);
    int hh, mm, ss, ff, drop;

    if (!sti->nb_index_entries)
        return -1;

    avio_seek(sc->pb, sti->index_entries->pos, SEEK_SET);
    avio_skip(s->pb, 13);
    hh = avio_r8(s->pb);
    mm = avio_r8(s->pb);
    ss = avio_r8(s->pb);
    drop = avio_r8(s->pb);
    ff = avio_r8(s->pb);
    snprintf(buf, AV_TIMECODE_STR_SIZE, "%02d:%02d:%02d%c%02d",
             hh, mm, ss, drop ? ';' : ':', ff);
    av_dict_set(&st->metadata, "timecode", buf, 0);

    avio_seek(sc->pb, cur_pos, SEEK_SET);
    return 0;
}


static void mov_metadata_creation_time(AVDictionary **metadata, int64_t time, void *logctx)
{
    if (time) {
        if (time >= 2082844800)
            time -= 2082844800;  /* seconds between 1904-01-01 and Epoch */

        if ((int64_t)(time * 1000000ULL) / 1000000 != time) {
            av_log(logctx, AV_LOG_DEBUG, "creation_time is not representable\n");
            return;
        }

        avpriv_dict_set_timestamp(metadata, "creation_time", time * 1000000);
    }
}


static int mov_read_meta(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
    while (atom.size > 8) {
        uint32_t tag;
        if (avio_feof(pb))
            return AVERROR_EOF;
        tag = avio_rl32(pb);
        atom.size -= 4;
        if (tag == MKTAG('h','d','l','r')) {
            int ret;
            avio_seek(pb, -8, SEEK_CUR);
            atom.size += 8;
            if ((ret = mov_read_default(c, pb, atom)) < 0)
                return ret;
            if (c->is_still_picture_avif) {
                int ret;
                // Add a stream for the YUV planes (primary item).
                if ((ret = avif_add_stream(c, c->primary_item_id)) < 0)
                    return ret;
                // For still AVIF images, the meta box contains all the
                // necessary information that would generally be provided by the
                // moov box. So simply mark that we have found the moov box so
                // that parsing can continue.
                c->found_moov = 1;
            }
            return ret;
        }
    }
    return 0;
}


static int mov_probe(const AVProbeData *p)
{
    int64_t offset;
    uint32_t tag;
    int score = 0;
    int moov_offset = -1;

    /* check file header */
    offset = 0;
    for (;;) {
        int64_t size;
        int minsize = 8;
        /* ignore invalid offset */
        if ((offset + 8ULL) > (unsigned int)p->buf_size)
            break;
        size = AV_RB32(p->buf + offset);
        if (size == 1 && offset + 16 <= (unsigned int)p->buf_size) {
            size = AV_RB64(p->buf+offset + 8);
            minsize = 16;
        } else if (size == 0) {
            size = p->buf_size - offset;
        }
        if (size < minsize) {
            offset += 4;
            continue;
        }
        tag = AV_RL32(p->buf + offset + 4);
        switch(tag) {
        /* check for obvious tags */
        case MKTAG('m','o','o','v'):
            moov_offset = offset + 4;
        case MKTAG('m','d','a','t'):
        case MKTAG('p','n','o','t'): /* detect movs with preview pics like ew.mov and april.mov */
        case MKTAG('u','d','t','a'): /* Packet Video PVAuthor adds this and a lot of more junk */
        case MKTAG('f','t','y','p'):
            if (tag == MKTAG('f','t','y','p') &&
                       (   AV_RL32(p->buf + offset + 8) == MKTAG('j','p','2',' ')
                        || AV_RL32(p->buf + offset + 8) == MKTAG('j','p','x',' ')
                        || AV_RL32(p->buf + offset + 8) == MKTAG('j','x','l',' ')
                    )) {
                score = FFMAX(score, 5);
            } else {
                score = AVPROBE_SCORE_MAX;
            }
            break;
        /* those are more common words, so rate then a bit less */
        case MKTAG('e','d','i','w'): /* xdcam files have reverted first tags */
        case MKTAG('w','i','d','e'):
        case MKTAG('f','r','e','e'):
        case MKTAG('j','u','n','k'):
        case MKTAG('p','i','c','t'):
            score  = FFMAX(score, AVPROBE_SCORE_MAX - 5);
            break;
        case MKTAG(0x82,0x82,0x7f,0x7d):
        case MKTAG('s','k','i','p'):
        case MKTAG('u','u','i','d'):
        case MKTAG('p','r','f','l'):
            /* if we only find those cause probedata is too small at least rate them */
            score  = FFMAX(score, AVPROBE_SCORE_EXTENSION);
            break;
        }
        if (size > INT64_MAX - offset)
            break;
        offset += size;
    }
    if (score > AVPROBE_SCORE_MAX - 50 && moov_offset != -1) {
        /* moov atom in the header - we should make sure that this is not a
         * MOV-packed MPEG-PS */
        offset = moov_offset;

        while (offset < (p->buf_size - 16)) { /* Sufficient space */
               /* We found an actual hdlr atom */
            if (AV_RL32(p->buf + offset     ) == MKTAG('h','d','l','r') &&
                AV_RL32(p->buf + offset +  8) == MKTAG('m','h','l','r') &&
                AV_RL32(p->buf + offset + 12) == MKTAG('M','P','E','G')) {
                av_log(NULL, AV_LOG_WARNING, "Found media data tag MPEG indicating this is a MOV-packed MPEG-PS.\n");
                /* We found a media handler reference atom describing an
                 * MPEG-PS-in-MOV, return a
                 * low score to force expanding the probe window until
                 * mpegps_probe finds what it needs */
                return 5;
            } else {
                /* Keep looking */
                offset += 2;
            }
        }
    }

    return score;
}


static void fix_frag_index_entries(MOVFragmentIndex *frag_index, int index,
                                   int id, int entries)
{
    int i;
    MOVFragmentStreamInfo * frag_stream_info;

    if (index < 0)
        return;
    for (i = index; i < frag_index->nb_items; i++) {
        frag_stream_info = get_frag_stream_info(frag_index, i, id);
        if (frag_stream_info && frag_stream_info->index_entry >= 0)
            frag_stream_info->index_entry += entries;
    }
}


static int mov_read_header(AVFormatContext *s)
{
    MOVContext *mov = s->priv_data;
    AVIOContext *pb = s->pb;
    int j, err;
    MOVAtom atom = { AV_RL32("root") };
    int i;

    if (mov->decryption_key_len != 0 && mov->decryption_key_len != AES_CTR_KEY_SIZE) {
        av_log(s, AV_LOG_ERROR, "Invalid decryption key len %d expected %d\n",
            mov->decryption_key_len, AES_CTR_KEY_SIZE);
        return AVERROR(EINVAL);
    }

    mov->fc = s;
    mov->trak_index = -1;
    /* .mov and .mp4 aren't streamable anyway (only progressive download if moov is before mdat) */
    if (pb->seekable & AVIO_SEEKABLE_NORMAL)
        atom.size = avio_size(pb);
    else
        atom.size = INT64_MAX;

    /* check MOV header */
    do {
        if (mov->moov_retry)
            avio_seek(pb, 0, SEEK_SET);
        if ((err = mov_read_default(mov, pb, atom)) < 0) {
            av_log(s, AV_LOG_ERROR, "error reading header\n");
            return err;
        }
    } while ((pb->seekable & AVIO_SEEKABLE_NORMAL) && !mov->found_moov && !mov->moov_retry++);
    if (!mov->found_moov) {
        av_log(s, AV_LOG_ERROR, "moov atom not found\n");
        return AVERROR_INVALIDDATA;
    }
    av_log(mov->fc, AV_LOG_TRACE, "on_parse_exit_offset=%"PRId64"\n", avio_tell(pb));

    if (pb->seekable & AVIO_SEEKABLE_NORMAL) {
        if (mov->nb_chapter_tracks > 0 && !mov->ignore_chapters)
            mov_read_chapters(s);
        for (i = 0; i < s->nb_streams; i++)
            if (s->streams[i]->codecpar->codec_tag == AV_RL32("tmcd")) {
                mov_read_timecode_track(s, s->streams[i]);
            } else if (s->streams[i]->codecpar->codec_tag == AV_RL32("rtmd")) {
                mov_read_rtmd_track(s, s->streams[i]);
            }
    }

    /* copy timecode metadata from tmcd tracks to the related video streams */
    for (i = 0; i < s->nb_streams; i++) {
        AVStream *st = s->streams[i];
        MOVStreamContext *sc = st->priv_data;
        if (sc->timecode_track > 0) {
            AVDictionaryEntry *tcr;
            int tmcd_st_id = -1;

            for (j = 0; j < s->nb_streams; j++)
                if (s->streams[j]->id == sc->timecode_track)
                    tmcd_st_id = j;

            if (tmcd_st_id < 0 || tmcd_st_id == i)
                continue;
            tcr = av_dict_get(s->streams[tmcd_st_id]->metadata, "timecode", NULL, 0);
            if (tcr)
                av_dict_set(&st->metadata, "timecode", tcr->value, 0);
        }
    }
    export_orphan_timecode(s);

    for (i = 0; i < s->nb_streams; i++) {
        AVStream *st = s->streams[i];
        FFStream *const sti = ffstream(st);
        MOVStreamContext *sc = st->priv_data;
        fix_timescale(mov, sc);
        if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO &&
            st->codecpar->codec_id   == AV_CODEC_ID_AAC) {
            sti->skip_samples = sc->start_pad;
        }
        if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && sc->nb_frames_for_fps > 0 && sc->duration_for_fps > 0)
            av_reduce(&st->avg_frame_rate.num, &st->avg_frame_rate.den,
                      sc->time_scale*(int64_t)sc->nb_frames_for_fps, sc->duration_for_fps, INT_MAX);
        if (st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) {
            if (st->codecpar->width <= 0 || st->codecpar->height <= 0) {
                st->codecpar->width  = sc->width;
                st->codecpar->height = sc->height;
            }
            if (st->codecpar->codec_id == AV_CODEC_ID_DVD_SUBTITLE) {
                if ((err = mov_rewrite_dvd_sub_extradata(st)) < 0)
                    return err;
            }
        }
        if (mov->handbrake_version &&
            mov->handbrake_version <= 1000000*0 + 1000*10 + 2 &&  // 0.10.2
            st->codecpar->codec_id == AV_CODEC_ID_MP3) {
            av_log(s, AV_LOG_VERBOSE, "Forcing full parsing for mp3 stream\n");
            sti->need_parsing = AVSTREAM_PARSE_FULL;
        }
    }

    if (mov->trex_data) {
        for (i = 0; i < s->nb_streams; i++) {
            AVStream *st = s->streams[i];
            MOVStreamContext *sc = st->priv_data;
            if (st->duration > 0) {
                /* Akin to sc->data_size * 8 * sc->time_scale / st->duration but accounting for overflows. */
                st->codecpar->bit_rate = av_rescale(sc->data_size, ((int64_t) sc->time_scale) * 8, st->duration);
                if (st->codecpar->bit_rate == INT64_MIN) {
                    av_log(s, AV_LOG_WARNING, "Overflow during bit rate calculation %"PRId64" * 8 * %d\n",
                           sc->data_size, sc->time_scale);
                    st->codecpar->bit_rate = 0;
                    if (s->error_recognition & AV_EF_EXPLODE)
                        return AVERROR_INVALIDDATA;
                }
            }
        }
    }

    if (mov->use_mfra_for > 0) {
        for (i = 0; i < s->nb_streams; i++) {
            AVStream *st = s->streams[i];
            MOVStreamContext *sc = st->priv_data;
            if (sc->duration_for_fps > 0) {
                /* Akin to sc->data_size * 8 * sc->time_scale / sc->duration_for_fps but accounting for overflows. */
                st->codecpar->bit_rate = av_rescale(sc->data_size, ((int64_t) sc->time_scale) * 8, sc->duration_for_fps);
                if (st->codecpar->bit_rate == INT64_MIN) {
                    av_log(s, AV_LOG_WARNING, "Overflow during bit rate calculation %"PRId64" * 8 * %d\n",
                           sc->data_size, sc->time_scale);
                    st->codecpar->bit_rate = 0;
                    if (s->error_recognition & AV_EF_EXPLODE)
                        return AVERROR_INVALIDDATA;
                }
            }
        }
    }

    for (i = 0; i < mov->bitrates_count && i < s->nb_streams; i++) {
        if (mov->bitrates[i]) {
            s->streams[i]->codecpar->bit_rate = mov->bitrates[i];
        }
    }

    ff_rfps_calculate(s);

    for (i = 0; i < s->nb_streams; i++) {
        AVStream *st = s->streams[i];
        MOVStreamContext *sc = st->priv_data;

        switch (st->codecpar->codec_type) {
        case AVMEDIA_TYPE_AUDIO:
            err = ff_replaygain_export(st, s->metadata);
            if (err < 0)
                return err;
            break;
        case AVMEDIA_TYPE_VIDEO:
            if (sc->display_matrix) {
                err = av_stream_add_side_data(st, AV_PKT_DATA_DISPLAYMATRIX, (uint8_t*)sc->display_matrix,
                                              sizeof(int32_t) * 9);
                if (err < 0)
                    return err;

                sc->display_matrix = NULL;
            }
            if (sc->stereo3d) {
                err = av_stream_add_side_data(st, AV_PKT_DATA_STEREO3D,
                                              (uint8_t *)sc->stereo3d,
                                              sizeof(*sc->stereo3d));
                if (err < 0)
                    return err;

                sc->stereo3d = NULL;
            }
            if (sc->spherical) {
                err = av_stream_add_side_data(st, AV_PKT_DATA_SPHERICAL,
                                              (uint8_t *)sc->spherical,
                                              sc->spherical_size);
                if (err < 0)
                    return err;

                sc->spherical = NULL;
            }
            if (sc->mastering) {
                err = av_stream_add_side_data(st, AV_PKT_DATA_MASTERING_DISPLAY_METADATA,
                                              (uint8_t *)sc->mastering,
                                              sizeof(*sc->mastering));
                if (err < 0)
                    return err;

                sc->mastering = NULL;
            }
            if (sc->coll) {
                err = av_stream_add_side_data(st, AV_PKT_DATA_CONTENT_LIGHT_LEVEL,
                                              (uint8_t *)sc->coll,
                                              sc->coll_size);
                if (err < 0)
                    return err;

                sc->coll = NULL;
            }
            break;
        }
    }
    ff_configure_buffers_for_index(s, AV_TIME_BASE);

    for (i = 0; i < mov->frag_index.nb_items; i++)
        if (mov->frag_index.item[i].moof_offset <= mov->fragment.moof_offset)
            mov->frag_index.item[i].headers_read = 1;

    return 0;
}


static int mov_read_stsz(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
    AVStream *st;
    MOVStreamContext *sc;
    unsigned int i, entries, sample_size, field_size, num_bytes;
    GetBitContext gb;
    unsigned char* buf;
    int ret;

    if (c->fc->nb_streams < 1)
        return 0;
    st = c->fc->streams[c->fc->nb_streams-1];
    sc = st->priv_data;

    avio_r8(pb); /* version */
    avio_rb24(pb); /* flags */

    if (atom.type == MKTAG('s','t','s','z')) {
        sample_size = avio_rb32(pb);
        if (!sc->sample_size) /* do not overwrite value computed in stsd */
            sc->sample_size = sample_size;
        sc->stsz_sample_size = sample_size;
        field_size = 32;
    } else {
        sample_size = 0;
        avio_rb24(pb); /* reserved */
        field_size = avio_r8(pb);
    }
    entries = avio_rb32(pb);

    av_log(c->fc, AV_LOG_TRACE, "sample_size = %u sample_count = %u\n", sc->sample_size, entries);

    sc->sample_count = entries;
    if (sample_size)
        return 0;

    if (field_size != 4 && field_size != 8 && field_size != 16 && field_size != 32) {
        av_log(c->fc, AV_LOG_ERROR, "Invalid sample field size %u\n", field_size);
        return AVERROR_INVALIDDATA;
    }

    if (!entries)
        return 0;
    if (entries >= (INT_MAX - 4 - 8 * AV_INPUT_BUFFER_PADDING_SIZE) / field_size)
        return AVERROR_INVALIDDATA;
    if (sc->sample_sizes)
        av_log(c->fc, AV_LOG_WARNING, "Duplicated STSZ atom\n");
    av_free(sc->sample_sizes);
    sc->sample_count = 0;
    sc->sample_sizes = av_malloc_array(entries, sizeof(*sc->sample_sizes));
    if (!sc->sample_sizes)
        return AVERROR(ENOMEM);

    num_bytes = (entries*field_size+4)>>3;

    buf = av_malloc(num_bytes+AV_INPUT_BUFFER_PADDING_SIZE);
    if (!buf) {
        av_freep(&sc->sample_sizes);
        return AVERROR(ENOMEM);
    }

    ret = ffio_read_size(pb, buf, num_bytes);
    if (ret < 0) {
        av_freep(&sc->sample_sizes);
        av_free(buf);
        av_log(c->fc, AV_LOG_WARNING, "STSZ atom truncated\n");
        return 0;
    }

    init_get_bits(&gb, buf, 8*num_bytes);

    for (i = 0; i < entries; i++) {
        sc->sample_sizes[i] = get_bits_long(&gb, field_size);
        if (sc->sample_sizes[i] < 0) {
            av_free(buf);
            av_log(c->fc, AV_LOG_ERROR, "Invalid sample size %d\n", sc->sample_sizes[i]);
            return AVERROR_INVALIDDATA;
        }
        sc->data_size += sc->sample_sizes[i];
    }

    sc->sample_count = i;

    av_free(buf);

    return 0;
}


static int rb_size(AVIOContext *pb, uint64_t* value, int size)
{
    if (size == 0)
        *value = 0;
    else if (size == 1)
        *value = avio_r8(pb);
    else if (size == 2)
        *value = avio_rb16(pb);
    else if (size == 4)
        *value = avio_rb32(pb);
    else if (size == 8)
        *value = avio_rb64(pb);
    else
        return -1;
    return size;
}


static int mov_read_strf(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
    AVStream *st;
    int ret;

    if (c->fc->nb_streams < 1)
        return 0;
    if (atom.size <= 40)
        return 0;
    st = c->fc->streams[c->fc->nb_streams-1];

    if ((uint64_t)atom.size > (1<<30))
        return AVERROR_INVALIDDATA;

    avio_skip(pb, 40);
    ret = ff_get_extradata(c->fc, st->codecpar, pb, atom.size - 40);
    if (ret < 0)
        return ret;

    return 0;
}


static int64_t add_ctts_entry(MOVCtts** ctts_data, unsigned int* ctts_count, unsigned int* allocated_size,
                              int count, int duration)
{
    MOVCtts *ctts_buf_new;
    const size_t min_size_needed = (*ctts_count + 1) * sizeof(MOVCtts);
    const size_t requested_size =
        min_size_needed > *allocated_size ?
        FFMAX(min_size_needed, 2 * (*allocated_size)) :
        min_size_needed;

    if ((unsigned)(*ctts_count) >= UINT_MAX / sizeof(MOVCtts) - 1)
        return -1;

    ctts_buf_new = av_fast_realloc(*ctts_data, allocated_size, requested_size);

    if (!ctts_buf_new)
        return -1;

    *ctts_data = ctts_buf_new;

    ctts_buf_new[*ctts_count].count = count;
    ctts_buf_new[*ctts_count].duration = duration;

    *ctts_count = (*ctts_count) + 1;
    return *ctts_count;
}


static int mov_read_senc(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
    AVEncryptionInfo **encrypted_samples;
    MOVEncryptionIndex *encryption_index;
    MOVStreamContext *sc;
    int use_subsamples, ret;
    unsigned int sample_count, i, alloc_size = 0;

    ret = get_current_encryption_info(c, &encryption_index, &sc);
    if (ret != 1)
        return ret;

    if (encryption_index->nb_encrypted_samples) {
        // This can happen if we have both saio/saiz and senc atoms.
        av_log(c->fc, AV_LOG_DEBUG, "Ignoring duplicate encryption info in senc\n");
        return 0;
    }

    avio_r8(pb); /* version */
    use_subsamples = avio_rb24(pb) & 0x02; /* flags */

    sample_count = avio_rb32(pb);
    if (sample_count >= INT_MAX / sizeof(*encrypted_samples))
        return AVERROR(ENOMEM);

    for (i = 0; i < sample_count; i++) {
        unsigned int min_samples = FFMIN(FFMAX(i + 1, 1024 * 1024), sample_count);
        encrypted_samples = av_fast_realloc(encryption_index->encrypted_samples, &alloc_size,
                                            min_samples * sizeof(*encrypted_samples));
        if (encrypted_samples) {
            encryption_index->encrypted_samples = encrypted_samples;

            ret = mov_read_sample_encryption_info(
                c, pb, sc, &encryption_index->encrypted_samples[i], use_subsamples);
        } else {
            ret = AVERROR(ENOMEM);
        }
        if (pb->eof_reached) {
            av_log(c->fc, AV_LOG_ERROR, "Hit EOF while reading senc\n");
            if (ret >= 0)
                av_encryption_info_free(encryption_index->encrypted_samples[i]);
            ret = AVERROR_INVALIDDATA;
        }

        if (ret < 0) {
            for (; i > 0; i--)
                av_encryption_info_free(encryption_index->encrypted_samples[i - 1]);
            av_freep(&encryption_index->encrypted_samples);
            return ret;
        }
    }
    encryption_index->nb_encrypted_samples = sample_count;

    return 0;
}


static int can_seek_to_key_sample(AVStream *st, int sample, int64_t requested_pts)
{
    MOVStreamContext *sc = st->priv_data;
    FFStream *const sti = ffstream(st);
    int64_t key_sample_dts, key_sample_pts;

    if (st->codecpar->codec_id != AV_CODEC_ID_HEVC)
        return 1;

    if (sample >= sc->sample_offsets_count)
        return 1;

    key_sample_dts = sti->index_entries[sample].timestamp;
    key_sample_pts = key_sample_dts + sc->sample_offsets[sample] + sc->dts_shift;

    /*
     * If the sample needs to be presented before an open key sample, they may
     * not be decodable properly, even though they come after in decoding
     * order.
     */
    if (is_open_key_sample(sc, sample) && key_sample_pts > requested_pts)
        return 0;

    return 1;
}


static int mov_read_custom(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
    int64_t end = av_sat_add64(avio_tell(pb), atom.size);
    uint8_t *key = NULL, *val = NULL, *mean = NULL;
    int i;
    int ret = 0;
    AVStream *st;
    MOVStreamContext *sc;

    if (c->fc->nb_streams < 1)
        return 0;
    st = c->fc->streams[c->fc->nb_streams-1];
    sc = st->priv_data;

    for (i = 0; i < 3; i++) {
        uint8_t **p;
        uint32_t len, tag;

        if (end - avio_tell(pb) <= 12)
            break;

        len = avio_rb32(pb);
        tag = avio_rl32(pb);
        avio_skip(pb, 4); // flags

        if (len < 12 || len - 12 > end - avio_tell(pb))
            break;
        len -= 12;

        if (tag == MKTAG('m', 'e', 'a', 'n'))
            p = &mean;
        else if (tag == MKTAG('n', 'a', 'm', 'e'))
            p = &key;
        else if (tag == MKTAG('d', 'a', 't', 'a') && len > 4) {
            avio_skip(pb, 4);
            len -= 4;
            p = &val;
        } else
            break;

        if (*p)
            break;

        *p = av_malloc(len + 1);
        if (!*p) {
            ret = AVERROR(ENOMEM);
            break;
        }
        ret = ffio_read_size(pb, *p, len);
        if (ret < 0) {
            av_freep(p);
            break;
        }
        (*p)[len] = 0;
    }

    if (mean && key && val) {
        if (strcmp(key, "iTunSMPB") == 0) {
            int priming, remainder, samples;
            if(sscanf(val, "%*X %X %X %X", &priming, &remainder, &samples) == 3){
                if(priming>0 && priming<16384)
                    sc->start_pad = priming;
            }
        }
        if (strcmp(key, "cdec") != 0) {
            av_dict_set(&c->fc->metadata, key, val,
                        AV_DICT_DONT_STRDUP_KEY | AV_DICT_DONT_STRDUP_VAL);
            key = val = NULL;
        }
    } else {
        av_log(c->fc, AV_LOG_VERBOSE,
               "Unhandled or malformed custom metadata of size %"PRId64"\n", atom.size);
    }

    avio_seek(pb, end, SEEK_SET);
    av_freep(&key);
    av_freep(&val);
    av_freep(&mean);
    return ret;
}


static int avif_add_stream(MOVContext *c, int item_id)
{
    MOVStreamContext *sc;
    AVStream *st;
    int item_index = -1;
    for (int i = 0; i < c->avif_info_size; i++)
        if (c->avif_info[i].item_id == item_id) {
            item_index = i;
            break;
        }
    if (item_index < 0)
        return AVERROR_INVALIDDATA;
    st = avformat_new_stream(c->fc, NULL);
    if (!st)
        return AVERROR(ENOMEM);
    st->id = c->fc->nb_streams;
    sc = av_mallocz(sizeof(MOVStreamContext));
    if (!sc)
        return AVERROR(ENOMEM);

    st->priv_data = sc;
    st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
    st->codecpar->codec_id = AV_CODEC_ID_AV1;
    sc->ffindex = st->index;
    c->trak_index = st->index;
    st->avg_frame_rate.num = st->avg_frame_rate.den = 1;
    st->time_base.num = st->time_base.den = 1;
    st->nb_frames = 1;
    sc->time_scale = 1;
    sc = st->priv_data;
    sc->pb = c->fc->pb;
    sc->pb_is_copied = 1;

    // Populate the necessary fields used by mov_build_index.
    sc->stsc_count = 1;
    sc->stsc_data = av_malloc_array(1, sizeof(*sc->stsc_data));
    if (!sc->stsc_data)
        return AVERROR(ENOMEM);
    sc->stsc_data[0].first = 1;
    sc->stsc_data[0].count = 1;
    sc->stsc_data[0].id = 1;
    sc->chunk_count = 1;
    sc->chunk_offsets = av_malloc_array(1, sizeof(*sc->chunk_offsets));
    if (!sc->chunk_offsets)
        return AVERROR(ENOMEM);
    sc->sample_count = 1;
    sc->sample_sizes = av_malloc_array(1, sizeof(*sc->sample_sizes));
    if (!sc->sample_sizes)
        return AVERROR(ENOMEM);
    sc->stts_count = 1;
    sc->stts_data = av_malloc_array(1, sizeof(*sc->stts_data));
    if (!sc->stts_data)
        return AVERROR(ENOMEM);
    sc->stts_data[0].count = 1;
    // Not used for still images. But needed by mov_build_index.
    sc->stts_data[0].duration = 0;
    sc->sample_sizes[0] = c->avif_info[item_index].extent_length;
    sc->chunk_offsets[0] = c->avif_info[item_index].extent_offset;

    mov_build_index(c, st);
    return 0;
}


static int mov_read_mfra(MOVContext *c, AVIOContext *f)
{
    int64_t stream_size = avio_size(f);
    int64_t original_pos = avio_tell(f);
    int64_t seek_ret;
    int ret = -1;
    if ((seek_ret = avio_seek(f, stream_size - 4, SEEK_SET)) < 0) {
        ret = seek_ret;
        goto fail;
    }
    c->mfra_size = avio_rb32(f);
    c->have_read_mfra_size = 1;
    if (!c->mfra_size || c->mfra_size > stream_size) {
        av_log(c->fc, AV_LOG_DEBUG, "doesn't look like mfra (unreasonable size)\n");
        goto fail;
    }
    if ((seek_ret = avio_seek(f, -((int64_t) c->mfra_size), SEEK_CUR)) < 0) {
        ret = seek_ret;
        goto fail;
    }
    if (avio_rb32(f) != c->mfra_size) {
        av_log(c->fc, AV_LOG_DEBUG, "doesn't look like mfra (size mismatch)\n");
        goto fail;
    }
    if (avio_rb32(f) != MKBETAG('m', 'f', 'r', 'a')) {
        av_log(c->fc, AV_LOG_DEBUG, "doesn't look like mfra (tag mismatch)\n");
        goto fail;
    }
    av_log(c->fc, AV_LOG_VERBOSE, "stream has mfra\n");
    do {
        ret = read_tfra(c, f);
        if (ret < 0)
            goto fail;
    } while (!ret);
    ret = 0;
    c->frag_index.complete = 1;
fail:
    seek_ret = avio_seek(f, original_pos, SEEK_SET);
    if (seek_ret < 0) {
        av_log(c->fc, AV_LOG_ERROR,
               "failed to seek back after looking for mfra\n");
        ret = seek_ret;
    }
    return ret;
}


static int mov_read_ctts(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
    AVStream *st;
    MOVStreamContext *sc;
    unsigned int i, entries, ctts_count = 0;

    if (c->fc->nb_streams < 1)
        return 0;
    st = c->fc->streams[c->fc->nb_streams-1];
    sc = st->priv_data;

    avio_r8(pb); /* version */
    avio_rb24(pb); /* flags */
    entries = avio_rb32(pb);

    av_log(c->fc, AV_LOG_TRACE, "track[%u].ctts.entries = %u\n", c->fc->nb_streams - 1, entries);

    if (!entries)
        return 0;
    if (entries >= UINT_MAX / sizeof(*sc->ctts_data))
        return AVERROR_INVALIDDATA;
    av_freep(&sc->ctts_data);
    sc->ctts_data = av_fast_realloc(NULL, &sc->ctts_allocated_size, entries * sizeof(*sc->ctts_data));
    if (!sc->ctts_data)
        return AVERROR(ENOMEM);

    for (i = 0; i < entries && !pb->eof_reached; i++) {
        int count    = avio_rb32(pb);
        int duration = avio_rb32(pb);

        if (count <= 0) {
            av_log(c->fc, AV_LOG_TRACE,
                   "ignoring CTTS entry with count=%d duration=%d\n",
                   count, duration);
            continue;
        }

        add_ctts_entry(&sc->ctts_data, &ctts_count, &sc->ctts_allocated_size,
                       count, duration);

        av_log(c->fc, AV_LOG_TRACE, "count=%d, duration=%d\n",
                count, duration);

        if (FFNABS(duration) < -(1<<28) && i+2<entries) {
            av_log(c->fc, AV_LOG_WARNING, "CTTS invalid\n");
            av_freep(&sc->ctts_data);
            sc->ctts_count = 0;
            return 0;
        }

        if (i+2<entries)
            mov_update_dts_shift(sc, duration, c->fc);
    }

    sc->ctts_count = ctts_count;

    if (pb->eof_reached) {
        av_log(c->fc, AV_LOG_WARNING, "reached eof, corrupted CTTS atom\n");
        return AVERROR_EOF;
    }

    av_log(c->fc, AV_LOG_TRACE, "dts shift %d\n", sc->dts_shift);

    return 0;
}


static void mov_parse_stsd_audio(MOVContext *c, AVIOContext *pb,
                                 AVStream *st, MOVStreamContext *sc)
{
    int bits_per_sample, flags;
    uint16_t version = avio_rb16(pb);
    uint32_t id = 0;
    AVDictionaryEntry *compatible_brands = av_dict_get(c->fc->metadata, "compatible_brands", NULL, AV_DICT_MATCH_CASE);
    int channel_count;

    avio_rb16(pb); /* revision level */
    id = avio_rl32(pb); /* vendor */
    av_dict_set(&st->metadata, "vendor_id", av_fourcc2str(id), 0);

    channel_count = avio_rb16(pb);

    st->codecpar->ch_layout.order = AV_CHANNEL_ORDER_UNSPEC;
    st->codecpar->ch_layout.nb_channels = channel_count;
    st->codecpar->bits_per_coded_sample = avio_rb16(pb); /* sample size */
    av_log(c->fc, AV_LOG_TRACE, "audio channels %d\n", channel_count);

    sc->audio_cid = avio_rb16(pb);
    avio_rb16(pb); /* packet size = 0 */

    st->codecpar->sample_rate = ((avio_rb32(pb) >> 16));

    // Read QT version 1 fields. In version 0 these do not exist.
    av_log(c->fc, AV_LOG_TRACE, "version =%d, isom =%d\n", version, c->isom);
    if (!c->isom ||
        (compatible_brands && strstr(compatible_brands->value, "qt  ")) ||
        (sc->stsd_version == 0 && version > 0)) {
        if (version == 1) {
            sc->samples_per_frame = avio_rb32(pb);
            avio_rb32(pb); /* bytes per packet */
            sc->bytes_per_frame = avio_rb32(pb);
            avio_rb32(pb); /* bytes per sample */
        } else if (version == 2) {
            avio_rb32(pb); /* sizeof struct only */
            st->codecpar->sample_rate = av_int2double(avio_rb64(pb));
            channel_count = avio_rb32(pb);
            st->codecpar->ch_layout.order = AV_CHANNEL_ORDER_UNSPEC;
            st->codecpar->ch_layout.nb_channels = channel_count;
            avio_rb32(pb); /* always 0x7F000000 */
            st->codecpar->bits_per_coded_sample = avio_rb32(pb);

            flags = avio_rb32(pb); /* lpcm format specific flag */
            sc->bytes_per_frame   = avio_rb32(pb);
            sc->samples_per_frame = avio_rb32(pb);
            if (st->codecpar->codec_tag == MKTAG('l','p','c','m'))
                st->codecpar->codec_id =
                    ff_mov_get_lpcm_codec_id(st->codecpar->bits_per_coded_sample,
                                             flags);
        }
        if (version == 0 || (version == 1 && sc->audio_cid != -2)) {
            /* can't correctly handle variable sized packet as audio unit */
            switch (st->codecpar->codec_id) {
            case AV_CODEC_ID_MP2:
            case AV_CODEC_ID_MP3:
                ffstream(st)->need_parsing = AVSTREAM_PARSE_FULL;
                break;
            }
        }
    }

    if (sc->format == 0) {
        if (st->codecpar->bits_per_coded_sample == 8)
            st->codecpar->codec_id = mov_codec_id(st, MKTAG('r','a','w',' '));
        else if (st->codecpar->bits_per_coded_sample == 16)
            st->codecpar->codec_id = mov_codec_id(st, MKTAG('t','w','o','s'));
    }

    switch (st->codecpar->codec_id) {
    case AV_CODEC_ID_PCM_S8:
    case AV_CODEC_ID_PCM_U8:
        if (st->codecpar->bits_per_coded_sample == 16)
            st->codecpar->codec_id = AV_CODEC_ID_PCM_S16BE;
        break;
    case AV_CODEC_ID_PCM_S16LE:
    case AV_CODEC_ID_PCM_S16BE:
        if (st->codecpar->bits_per_coded_sample == 8)
            st->codecpar->codec_id = AV_CODEC_ID_PCM_S8;
        else if (st->codecpar->bits_per_coded_sample == 24)
            st->codecpar->codec_id =
                st->codecpar->codec_id == AV_CODEC_ID_PCM_S16BE ?
                AV_CODEC_ID_PCM_S24BE : AV_CODEC_ID_PCM_S24LE;
        else if (st->codecpar->bits_per_coded_sample == 32)
             st->codecpar->codec_id =
                st->codecpar->codec_id == AV_CODEC_ID_PCM_S16BE ?
                AV_CODEC_ID_PCM_S32BE : AV_CODEC_ID_PCM_S32LE;
        break;
    /* set values for old format before stsd version 1 appeared */
    case AV_CODEC_ID_MACE3:
        sc->samples_per_frame = 6;
        sc->bytes_per_frame   = 2 * st->codecpar->ch_layout.nb_channels;
        break;
    case AV_CODEC_ID_MACE6:
        sc->samples_per_frame = 6;
        sc->bytes_per_frame   = 1 * st->codecpar->ch_layout.nb_channels;
        break;
    case AV_CODEC_ID_ADPCM_IMA_QT:
        sc->samples_per_frame = 64;
        sc->bytes_per_frame   = 34 * st->codecpar->ch_layout.nb_channels;
        break;
    case AV_CODEC_ID_GSM:
        sc->samples_per_frame = 160;
        sc->bytes_per_frame   = 33;
        break;
    default:
        break;
    }

    bits_per_sample = av_get_bits_per_sample(st->codecpar->codec_id);
    if (bits_per_sample && (bits_per_sample >> 3) * (uint64_t)st->codecpar->ch_layout.nb_channels <= INT_MAX) {
        st->codecpar->bits_per_coded_sample = bits_per_sample;
        sc->sample_size = (bits_per_sample >> 3) * st->codecpar->ch_layout.nb_channels;
    }
}