chromium/third_party/ffmpeg/libavformat/matroskadec.c

/*
 * Matroska file demuxer
 * Copyright (c) 2003-2008 The FFmpeg Project
 *
 * This file is part of FFmpeg.
 *
 * FFmpeg is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * FFmpeg is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with FFmpeg; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

/**
 * @file
 * Matroska file demuxer
 * @author Ronald Bultje <[email protected]>
 * @author with a little help from Moritz Bunkus <[email protected]>
 * @author totally reworked by Aurelien Jacobs <[email protected]>
 * @see specs available on the Matroska project page: http://www.matroska.org/
 */

#include "config.h"
#include "config_components.h"

#include <inttypes.h>
#include <stdio.h>

#include "libavutil/avstring.h"
#include "libavutil/base64.h"
#include "libavutil/bprint.h"
#include "libavutil/dict.h"
#include "libavutil/dict_internal.h"
#include "libavutil/display.h"
#include "libavutil/hdr_dynamic_metadata.h"
#include "libavutil/intfloat.h"
#include "libavutil/intreadwrite.h"
// Chromium: Continue to disable LZO and SIPR. crbug.com/1293918
#if CONFIG_LZO
#include "libavutil/lzo.h"
#endif
#include "libavutil/mastering_display_metadata.h"
#include "libavutil/mathematics.h"
#include "libavutil/mem.h"
#include "libavutil/opt.h"
#include "libavutil/pixdesc.h"
#include "libavutil/time_internal.h"
#include "libavutil/spherical.h"

#include "libavcodec/bytestream.h"
#include "libavcodec/defs.h"
#include "libavcodec/flac.h"
#include "libavcodec/itut35.h"
#include "libavcodec/mpeg4audio.h"
#include "libavcodec/packet_internal.h"

#include "avformat.h"
#include "avio_internal.h"
#include "demux.h"
#include "dovi_isom.h"
#include "internal.h"
#include "isom.h"
#include "matroska.h"
#include "oggdec.h"
/* For ff_codec_get_id(). */
#include "riff.h"
// Chromium: Continue to disable LZO and SIPR. crbug.com/1293918
#if CONFIG_SIPR_DECODER
#include "rmsipr.h"
#endif

#if CONFIG_BZLIB
#include <bzlib.h>
#endif
#if CONFIG_ZLIB
#include <zlib.h>
#endif

#include "qtpalette.h"

#define EBML_UNKNOWN_LENGTH
#define NEEDS_CHECKING
#define LEVEL_ENDED
#define SKIP_THRESHOLD
#define UNKNOWN_EQUIV

EbmlType;

CountedElement;

EbmlSyntax;

EbmlList;

EbmlBin;

Ebml;

MatroskaTrackCompression;

MatroskaTrackEncryption;

MatroskaTrackEncoding;

MatroskaMasteringMeta;

MatroskaTrackVideoColor;

MatroskaTrackVideoProjection;

MatroskaTrackVideo;

MatroskaTrackAudio;

MatroskaTrackPlane;

MatroskaTrackOperation;

MatroskaBlockAdditionMapping;

MatroskaTrack;

MatroskaAttachment;

MatroskaChapter;

MatroskaIndexPos;

MatroskaIndex;

MatroskaTag;

MatroskaTagTarget;

MatroskaTags;

MatroskaSeekhead;

MatroskaLevel;

MatroskaBlockMore;

MatroskaBlock;

MatroskaCluster;

MatroskaLevel1Element;

MatroskaDemuxContext;

#define CHILD_OF

// The following forward declarations need their size because
// a tentative definition with internal linkage must not be an
// incomplete type (6.7.2 in C90, 6.9.2 in C99).
// Removing the sizes breaks MSVC.
static EbmlSyntax ebml_syntax[3], matroska_segment[9], matroska_track_video_color[15], matroska_track_video[19],
                  matroska_track[33], matroska_track_encoding[6], matroska_track_encodings[2],
                  matroska_track_combine_planes[2], matroska_track_operation[2], matroska_block_addition_mapping[5], matroska_tracks[2],
                  matroska_attachments[2], matroska_chapter_entry[9], matroska_chapter[6], matroska_chapters[2],
                  matroska_index_entry[3], matroska_index[2], matroska_tag[3], matroska_tags[2], matroska_seekhead[2],
                  matroska_blockadditions[2], matroska_blockgroup[8], matroska_cluster_parsing[8];

static EbmlSyntax ebml_header[] =;

static EbmlSyntax ebml_syntax[] =;

static EbmlSyntax matroska_info[] =;

static EbmlSyntax matroska_mastering_meta[] =;

static EbmlSyntax matroska_track_video_color[] =;

static EbmlSyntax matroska_track_video_projection[] =;

static EbmlSyntax matroska_track_video[] =;

static EbmlSyntax matroska_track_audio[] =;

static EbmlSyntax matroska_track_encoding_compression[] =;

static EbmlSyntax matroska_track_encoding_encryption[] =;
static EbmlSyntax matroska_track_encoding[] =;

static EbmlSyntax matroska_track_encodings[] =;

static EbmlSyntax matroska_track_plane[] =;

static EbmlSyntax matroska_track_combine_planes[] =;

static EbmlSyntax matroska_track_operation[] =;

static EbmlSyntax matroska_block_addition_mapping[] =;

static EbmlSyntax matroska_track[] =;

static EbmlSyntax matroska_tracks[] =;

static EbmlSyntax matroska_attachment[] =;

static EbmlSyntax matroska_attachments[] =;

static EbmlSyntax matroska_chapter_display[] =;

static EbmlSyntax matroska_chapter_entry[] =;

static EbmlSyntax matroska_chapter[] =;

static EbmlSyntax matroska_chapters[] =;

static EbmlSyntax matroska_index_pos[] =;

static EbmlSyntax matroska_index_entry[] =;

static EbmlSyntax matroska_index[] =;

static EbmlSyntax matroska_simpletag[] =;

static EbmlSyntax matroska_tagtargets[] =;

static EbmlSyntax matroska_tag[] =;

static EbmlSyntax matroska_tags[] =;

static EbmlSyntax matroska_seekhead_entry[] =;

static EbmlSyntax matroska_seekhead[] =;

static EbmlSyntax matroska_segment[] =;

static EbmlSyntax matroska_segments[] =;

static EbmlSyntax matroska_blockmore[] =;

static EbmlSyntax matroska_blockadditions[] =;

static EbmlSyntax matroska_blockgroup[] =;

// The following array contains SimpleBlock and BlockGroup twice
// in order to reuse the other values for matroska_cluster_enter.
static EbmlSyntax matroska_cluster_parsing[] =;

static EbmlSyntax matroska_cluster_enter[] =;
#undef CHILD_OF

static const CodecMime mkv_image_mime_tags[] =;

static const CodecMime mkv_mime_tags[] =;

static const char * const matroska_video_stereo_plane[MATROSKA_VIDEO_STEREO_PLANE_COUNT] =;

static const char *const matroska_doctypes[] =;

/*
 * This function prepares the status for parsing of level 1 elements.
 */
static int matroska_reset_status(MatroskaDemuxContext *matroska,
                                 uint32_t id, int64_t position)
{}

static int matroska_resync(MatroskaDemuxContext *matroska, int64_t last_pos)
{}

/*
 * Read: an "EBML number", which is defined as a variable-length
 * array of bytes. The first byte indicates the length by giving a
 * number of 0-bits followed by a one. The position of the first
 * "one" bit inside the first byte indicates the length of this
 * number.
 * Returns: number of bytes read, < 0 on error
 */
static int ebml_read_num(MatroskaDemuxContext *matroska, AVIOContext *pb,
                         int max_size, uint64_t *number, int eof_forbidden)
{}

/**
 * Read a EBML length value.
 * This needs special handling for the "unknown length" case which has multiple
 * encodings.
 */
static int ebml_read_length(MatroskaDemuxContext *matroska, AVIOContext *pb,
                            uint64_t *number)
{}

/*
 * Read the next element as an unsigned int.
 * Returns NEEDS_CHECKING unless size == 0.
 */
static int ebml_read_uint(AVIOContext *pb, int size,
                          uint64_t default_value, uint64_t *num)
{}

/*
 * Read the next element as a signed int.
 * Returns NEEDS_CHECKING unless size == 0.
 */
static int ebml_read_sint(AVIOContext *pb, int size,
                          int64_t default_value, int64_t *num)
{}

/*
 * Read the next element as a float.
 * Returns 0 if size == 0, NEEDS_CHECKING or < 0 on obvious failure.
 */
static int ebml_read_float(AVIOContext *pb, int size,
                           double default_value, double *num)
{}

/*
 * Read the next element as an ASCII string.
 * 0 is success, < 0 or NEEDS_CHECKING is failure.
 */
static int ebml_read_ascii(AVIOContext *pb, int size,
                           const char *default_value, char **str)
{}

/*
 * Read the next element as binary data.
 * 0 is success, < 0 or NEEDS_CHECKING is failure.
 */
static int ebml_read_binary(AVIOContext *pb, int length,
                            int64_t pos, EbmlBin *bin)
{}

/*
 * Read the next element, but only the header. The contents
 * are supposed to be sub-elements which can be read separately.
 * 0 is success, < 0 is failure.
 */
static int ebml_read_master(MatroskaDemuxContext *matroska,
                            uint64_t length, int64_t pos)
{}

/*
 * Read a signed "EBML number"
 * Return: number of bytes processed, < 0 on error
 */
static int matroska_ebmlnum_sint(MatroskaDemuxContext *matroska,
                                 AVIOContext *pb, int64_t *num)
{}

static int ebml_parse(MatroskaDemuxContext *matroska,
                      EbmlSyntax *syntax, void *data);

static EbmlSyntax *ebml_parse_id(EbmlSyntax *syntax, uint32_t id)
{}

static int ebml_parse_nest(MatroskaDemuxContext *matroska, EbmlSyntax *syntax,
                           void *data)
{}

static int is_ebml_id_valid(uint32_t id)
{}

/*
 * Allocate and return the entry for the level1 element with the given ID. If
 * an entry already exists, return the existing entry.
 */
static MatroskaLevel1Element *matroska_find_level1_elem(MatroskaDemuxContext *matroska,
                                                        uint32_t id, int64_t pos)
{}

static int ebml_parse(MatroskaDemuxContext *matroska,
                      EbmlSyntax *syntax, void *data)
{}

static void ebml_free(EbmlSyntax *syntax, void *data)
{}

/*
 * Autodetecting...
 */
static int matroska_probe(const AVProbeData *p)
{}

static MatroskaTrack *matroska_find_track_by_num(MatroskaDemuxContext *matroska,
                                                 uint64_t num)
{}

static int matroska_decode_buffer(uint8_t **buf, int *buf_size,
                                  MatroskaTrack *track)
{}

static void matroska_convert_tag(AVFormatContext *s, EbmlList *list,
                                 AVDictionary **metadata, char *prefix)
{}

static void matroska_convert_tags(AVFormatContext *s)
{}

static int matroska_parse_seekhead_entry(MatroskaDemuxContext *matroska,
                                         int64_t pos)
{}

static void matroska_execute_seekhead(MatroskaDemuxContext *matroska)
{}

static void matroska_add_index_entries(MatroskaDemuxContext *matroska)
{}

static void matroska_parse_cues(MatroskaDemuxContext *matroska) {}

static int matroska_parse_content_encodings(MatroskaTrackEncoding *encodings,
                                            unsigned nb_encodings,
                                            MatroskaTrack *track,
                                            char **key_id_base64, void *logctx)
{}

static int matroska_aac_profile(char *codec_id)
{}

static int matroska_aac_sri(int samplerate)
{}

static void matroska_metadata_creation_time(AVDictionary **metadata, int64_t date_utc)
{}

static int matroska_parse_flac(AVFormatContext *s,
                               MatroskaTrack *track,
                               int *offset)
{}

static int mkv_field_order(const MatroskaDemuxContext *matroska, uint64_t field_order)
{}

static void mkv_stereo_mode_display_mul(int stereo_mode,
                                        int *h_width, int *h_height)
{}

static int mkv_stereo3d_conv(AVStream *st, MatroskaVideoStereoModeType stereo_mode)
{}

static int mkv_parse_video_color(AVStream *st, const MatroskaTrack *track) {}

static int mkv_create_display_matrix(AVStream *st,
                                     const MatroskaTrackVideoProjection *proj,
                                     void *logctx)
{}

static int mkv_parse_video_projection(AVStream *st, const MatroskaTrack *track,
                                      void *logctx)
{}

static int mkv_parse_dvcc_dvvc(AVFormatContext *s, AVStream *st, const MatroskaTrack *track,
                               EbmlBin *bin)
{}

static int mkv_parse_block_addition_mappings(AVFormatContext *s, AVStream *st, MatroskaTrack *track)
{}

static int get_qt_codec(MatroskaTrack *track, uint32_t *fourcc, enum AVCodecID *codec_id)
{}

/* An enum with potential return values of the functions for parsing a track.
 * Apart from that all these functions can also indicate ordinary errors via
 * negative return values. */
enum {};

#define AAC_MAX_EXTRADATA_SIZE
#define TTA_EXTRADATA_SIZE
#define WAVPACK_EXTRADATA_SIZE
/* Performs the codec-specific part of parsing an audio track. */
static int mka_parse_audio_codec(MatroskaTrack *track, AVCodecParameters *par,
                                 const MatroskaDemuxContext *matroska,
                                 AVFormatContext *s, int *extradata_offset)
{}

/* Performs the generic part of parsing an audio track. */
static int mka_parse_audio(MatroskaTrack *track, AVStream *st,
                           AVCodecParameters *par,
                           const MatroskaDemuxContext *matroska,
                           AVFormatContext *s, int *extradata_offset)
{}

/* Performs the codec-specific part of parsing a video track. */
static int mkv_parse_video_codec(MatroskaTrack *track, AVCodecParameters *par,
                                 const MatroskaDemuxContext *matroska,
                                 int *extradata_offset)
{}

/* Performs the generic part of parsing a video track. */
static int mkv_parse_video(MatroskaTrack *track, AVStream *st,
                           AVCodecParameters *par,
                           const MatroskaDemuxContext *matroska,
                           int *extradata_offset)
{}

/* Performs the codec-specific part of parsing a subtitle track. */
static int mkv_parse_subtitle_codec(MatroskaTrack *track, AVStream *st,
                                    AVCodecParameters *par,
                                    const MatroskaDemuxContext *matroska)
{}

static int matroska_parse_tracks(AVFormatContext *s)
{}

static int matroska_read_header(AVFormatContext *s)
{}

/*
 * Put one packet in an application-supplied AVPacket struct.
 * Returns 0 on success or -1 on failure.
 */
static int matroska_deliver_packet(MatroskaDemuxContext *matroska,
                                   AVPacket *pkt)
{}

/*
 * Free all packets in our internal queue.
 */
static void matroska_clear_queue(MatroskaDemuxContext *matroska)
{}

static int matroska_parse_laces(MatroskaDemuxContext *matroska, uint8_t **buf,
                                int size, int type, AVIOContext *pb,
                                uint32_t lace_size[256], int *laces)
{}

static int matroska_parse_rm_audio(MatroskaDemuxContext *matroska,
                                   MatroskaTrack *track, AVStream *st,
                                   uint8_t *data, int size, uint64_t timecode,
                                   int64_t pos)
{}

/* reconstruct full wavpack blocks from mangled matroska ones */
static int matroska_parse_wavpack(MatroskaTrack *track,
                                  uint8_t **data, int *size)
{}

static int matroska_parse_prores(MatroskaTrack *track,
                                 uint8_t **data, int *size)
{}

static int matroska_parse_webvtt(MatroskaDemuxContext *matroska,
                                 MatroskaTrack *track,
                                 AVStream *st,
                                 uint8_t *data, int data_len,
                                 uint64_t timecode,
                                 uint64_t duration,
                                 int64_t pos)
{}

static int matroska_parse_block_additional(MatroskaDemuxContext *matroska,
                                           MatroskaTrack *track, AVPacket *pkt,
                                           const uint8_t *data, int size, uint64_t id)
{}

static int matroska_parse_frame(MatroskaDemuxContext *matroska,
                                MatroskaTrack *track, AVStream *st,
                                AVBufferRef *buf, uint8_t *data, int pkt_size,
                                uint64_t timecode, uint64_t lace_duration,
                                int64_t pos, int is_keyframe,
                                MatroskaBlockMore *blockmore, int nb_blockmore,
                                int64_t discard_padding)
{}

static int matroska_parse_block(MatroskaDemuxContext *matroska, AVBufferRef *buf, uint8_t *data,
                                int size, int64_t pos, uint64_t cluster_time,
                                uint64_t block_duration, int is_keyframe,
                                MatroskaBlockMore *blockmore, int nb_blockmore,
                                int64_t cluster_pos, int64_t discard_padding)
{}

static int matroska_parse_cluster(MatroskaDemuxContext *matroska)
{}

static int matroska_read_packet(AVFormatContext *s, AVPacket *pkt)
{}

static int matroska_read_seek(AVFormatContext *s, int stream_index,
                              int64_t timestamp, int flags)
{}

static int matroska_read_close(AVFormatContext *s)
{}

#if CONFIG_WEBM_DASH_MANIFEST_DEMUXER
typedef struct {
    int64_t start_time_ns;
    int64_t end_time_ns;
    int64_t start_offset;
    int64_t end_offset;
} CueDesc;

/* This function searches all the Cues and returns the CueDesc corresponding to
 * the timestamp ts. Returned CueDesc will be such that start_time_ns <= ts <
 * end_time_ns. All 4 fields will be set to -1 if ts >= file's duration or
 * if an error occurred.
 */
static CueDesc get_cue_desc(AVFormatContext *s, int64_t ts, int64_t cues_start) {
    MatroskaDemuxContext *matroska = s->priv_data;
    FFStream *const sti = ffstream(s->streams[0]);
    AVIndexEntry *const index_entries = sti->index_entries;
    int nb_index_entries = sti->nb_index_entries;
    CueDesc cue_desc;
    int i;

    if (ts >= (int64_t)(matroska->duration * matroska->time_scale))
        return (CueDesc) {-1, -1, -1, -1};
    for (i = 1; i < nb_index_entries; i++) {
        if (index_entries[i - 1].timestamp * matroska->time_scale <= ts &&
            index_entries[i].timestamp * matroska->time_scale > ts) {
            break;
        }
    }
    --i;
    if (index_entries[i].timestamp > matroska->duration)
        return (CueDesc) {-1, -1, -1, -1};
    cue_desc.start_time_ns = index_entries[i].timestamp * matroska->time_scale;
    cue_desc.start_offset = index_entries[i].pos - matroska->segment_start;
    if (i != nb_index_entries - 1) {
        cue_desc.end_time_ns = index_entries[i + 1].timestamp * matroska->time_scale;
        cue_desc.end_offset = index_entries[i + 1].pos - matroska->segment_start;
    } else {
        cue_desc.end_time_ns = matroska->duration * matroska->time_scale;
        // FIXME: this needs special handling for files where Cues appear
        // before Clusters. the current logic assumes Cues appear after
        // Clusters.
        cue_desc.end_offset = cues_start - matroska->segment_start;
    }
    return cue_desc;
}

static int webm_clusters_start_with_keyframe(AVFormatContext *s)
{
    MatroskaDemuxContext *matroska = s->priv_data;
    AVStream *const st  = s->streams[0];
    FFStream *const sti = ffstream(st);
    uint32_t id = matroska->current_id;
    int64_t cluster_pos, before_pos;
    int index, rv = 1;

    if (sti->nb_index_entries <= 0)
        return 0;

    // seek to the first cluster using cues.
    index = av_index_search_timestamp(st, 0, 0);
    if (index < 0)
        return 0;
    cluster_pos = sti->index_entries[index].pos;
    before_pos = avio_tell(s->pb);
    while (1) {
        uint64_t cluster_id, cluster_length;
        int read;
        AVPacket *pkt;
        avio_seek(s->pb, cluster_pos, SEEK_SET);
        // read cluster id and length
        read = ebml_read_num(matroska, matroska->ctx->pb, 4, &cluster_id, 1);
        if (read < 0 || cluster_id != 0xF43B675) // done with all clusters
            break;
        read = ebml_read_length(matroska, matroska->ctx->pb, &cluster_length);
        if (read < 0)
            break;

        matroska_reset_status(matroska, 0, cluster_pos);
        matroska_clear_queue(matroska);
        if (matroska_parse_cluster(matroska) < 0 ||
            !matroska->queue.head) {
            break;
        }
        pkt = &matroska->queue.head->pkt;
        // 4 + read is the length of the cluster id and the cluster length field.
        cluster_pos += 4 + read + cluster_length;
        if (!(pkt->flags & AV_PKT_FLAG_KEY)) {
            rv = 0;
            break;
        }
    }

    /* Restore the status after matroska_read_header: */
    matroska_reset_status(matroska, id, before_pos);

    return rv;
}

static int buffer_size_after_time_downloaded(int64_t time_ns, double search_sec, int64_t bps,
                                             double min_buffer, double* buffer,
                                             double* sec_to_download, AVFormatContext *s,
                                             int64_t cues_start)
{
    double nano_seconds_per_second = 1000000000.0;
    double time_sec = time_ns / nano_seconds_per_second;
    int rv = 0;
    int64_t time_to_search_ns = (int64_t)(search_sec * nano_seconds_per_second);
    int64_t end_time_ns = time_ns + time_to_search_ns;
    double sec_downloaded = 0.0;
    CueDesc desc_curr = get_cue_desc(s, time_ns, cues_start);
    if (desc_curr.start_time_ns == -1)
      return -1;
    *sec_to_download = 0.0;

    // Check for non cue start time.
    if (time_ns > desc_curr.start_time_ns) {
      int64_t cue_nano = desc_curr.end_time_ns - time_ns;
      double percent = (double)(cue_nano) / (desc_curr.end_time_ns - desc_curr.start_time_ns);
      double cueBytes = (desc_curr.end_offset - desc_curr.start_offset) * percent;
      double timeToDownload = (cueBytes * 8.0) / bps;

      sec_downloaded += (cue_nano / nano_seconds_per_second) - timeToDownload;
      *sec_to_download += timeToDownload;

      // Check if the search ends within the first cue.
      if (desc_curr.end_time_ns >= end_time_ns) {
          double desc_end_time_sec = desc_curr.end_time_ns / nano_seconds_per_second;
          double percent_to_sub = search_sec / (desc_end_time_sec - time_sec);
          sec_downloaded = percent_to_sub * sec_downloaded;
          *sec_to_download = percent_to_sub * *sec_to_download;
      }

      if ((sec_downloaded + *buffer) <= min_buffer) {
          return 1;
      }

      // Get the next Cue.
      desc_curr = get_cue_desc(s, desc_curr.end_time_ns, cues_start);
    }

    while (desc_curr.start_time_ns != -1) {
        int64_t desc_bytes = desc_curr.end_offset - desc_curr.start_offset;
        int64_t desc_ns = desc_curr.end_time_ns - desc_curr.start_time_ns;
        double desc_sec = desc_ns / nano_seconds_per_second;
        double bits = (desc_bytes * 8.0);
        double time_to_download = bits / bps;

        sec_downloaded += desc_sec - time_to_download;
        *sec_to_download += time_to_download;

        if (desc_curr.end_time_ns >= end_time_ns) {
            double desc_end_time_sec = desc_curr.end_time_ns / nano_seconds_per_second;
            double percent_to_sub = search_sec / (desc_end_time_sec - time_sec);
            sec_downloaded = percent_to_sub * sec_downloaded;
            *sec_to_download = percent_to_sub * *sec_to_download;

            if ((sec_downloaded + *buffer) <= min_buffer)
                rv = 1;
            break;
        }

        if ((sec_downloaded + *buffer) <= min_buffer) {
            rv = 1;
            break;
        }

        desc_curr = get_cue_desc(s, desc_curr.end_time_ns, cues_start);
    }
    *buffer = *buffer + sec_downloaded;
    return rv;
}

/* This function computes the bandwidth of the WebM file with the help of
 * buffer_size_after_time_downloaded() function. Both of these functions are
 * adapted from WebM Tools project and are adapted to work with FFmpeg's
 * Matroska parsing mechanism.
 *
 * Returns the bandwidth of the file on success; -1 on error.
 * */
static int64_t webm_dash_manifest_compute_bandwidth(AVFormatContext *s, int64_t cues_start)
{
    MatroskaDemuxContext *matroska = s->priv_data;
    AVStream *st = s->streams[0];
    FFStream *const sti = ffstream(st);
    double bandwidth = 0.0;

    for (int i = 0; i < sti->nb_index_entries; i++) {
        int64_t prebuffer_ns = 1000000000;
        int64_t time_ns = sti->index_entries[i].timestamp * matroska->time_scale;
        double nano_seconds_per_second = 1000000000.0;
        int64_t prebuffered_ns;
        double prebuffer_bytes = 0.0;
        int64_t temp_prebuffer_ns = prebuffer_ns;
        int64_t pre_bytes, pre_ns;
        double pre_sec, prebuffer, bits_per_second;
        CueDesc desc_beg = get_cue_desc(s, time_ns, cues_start);
        // Start with the first Cue.
        CueDesc desc_end = desc_beg;

        if (time_ns > INT64_MAX - prebuffer_ns)
            return -1;
        prebuffered_ns = time_ns + prebuffer_ns;

        // Figure out how much data we have downloaded for the prebuffer. This will
        // be used later to adjust the bits per sample to try.
        while (desc_end.start_time_ns != -1 && desc_end.end_time_ns < prebuffered_ns) {
            // Prebuffered the entire Cue.
            prebuffer_bytes += desc_end.end_offset - desc_end.start_offset;
            temp_prebuffer_ns -= desc_end.end_time_ns - desc_end.start_time_ns;
            desc_end = get_cue_desc(s, desc_end.end_time_ns, cues_start);
        }
        if (desc_end.start_time_ns == -1) {
            // The prebuffer is larger than the duration.
            if (matroska->duration * matroska->time_scale >= prebuffered_ns)
              return -1;
            bits_per_second = 0.0;
        } else {
            // The prebuffer ends in the last Cue. Estimate how much data was
            // prebuffered.
            pre_bytes = desc_end.end_offset - desc_end.start_offset;
            pre_ns = desc_end.end_time_ns - desc_end.start_time_ns;
            if (pre_ns <= 0)
                return -1;
            pre_sec = pre_ns / nano_seconds_per_second;
            prebuffer_bytes +=
                pre_bytes * ((temp_prebuffer_ns / nano_seconds_per_second) / pre_sec);

            prebuffer = prebuffer_ns / nano_seconds_per_second;

            // Set this to 0.0 in case our prebuffer buffers the entire video.
            bits_per_second = 0.0;
            do {
                int64_t desc_bytes = desc_end.end_offset - desc_beg.start_offset;
                int64_t desc_ns = desc_end.end_time_ns - desc_beg.start_time_ns;
                double desc_sec, calc_bits_per_second, percent, mod_bits_per_second;
                if (desc_bytes <= 0)
                    return -1;

                desc_sec = desc_ns / nano_seconds_per_second;
                calc_bits_per_second = (desc_bytes * 8) / desc_sec;

                // Drop the bps by the percentage of bytes buffered.
                percent = (desc_bytes - prebuffer_bytes) / desc_bytes;
                mod_bits_per_second = calc_bits_per_second * percent;

                if (prebuffer < desc_sec) {
                    double search_sec =
                        (double)(matroska->duration * matroska->time_scale) / nano_seconds_per_second;

                    // Add 1 so the bits per second should be a little bit greater than file
                    // datarate.
                    int64_t bps = (int64_t)(mod_bits_per_second) + 1;
                    const double min_buffer = 0.0;
                    double buffer = prebuffer;
                    double sec_to_download = 0.0;

                    int rv = buffer_size_after_time_downloaded(prebuffered_ns, search_sec, bps,
                                                               min_buffer, &buffer, &sec_to_download,
                                                               s, cues_start);
                    if (rv < 0) {
                        return -1;
                    } else if (rv == 0) {
                        bits_per_second = (double)(bps);
                        break;
                    }
                }

                desc_end = get_cue_desc(s, desc_end.end_time_ns, cues_start);
            } while (desc_end.start_time_ns != -1);
        }
        if (bandwidth < bits_per_second) bandwidth = bits_per_second;
    }
    return (int64_t)bandwidth;
}

static int webm_dash_manifest_cues(AVFormatContext *s, int64_t init_range)
{
    MatroskaDemuxContext *matroska = s->priv_data;
    EbmlList *seekhead_list = &matroska->seekhead;
    MatroskaSeekhead *seekhead = seekhead_list->elem;
    AVStream *const st = s->streams[0];
    FFStream *const sti = ffstream(st);
    AVBPrint bprint;
    char *buf;
    int64_t cues_start = -1, cues_end = -1, before_pos, bandwidth;
    int i;
    int ret;

    // determine cues start and end positions
    for (i = 0; i < seekhead_list->nb_elem; i++)
        if (seekhead[i].id == MATROSKA_ID_CUES)
            break;

    if (i >= seekhead_list->nb_elem) return -1;

    before_pos = avio_tell(matroska->ctx->pb);
    cues_start = seekhead[i].pos + matroska->segment_start;
    if (avio_seek(matroska->ctx->pb, cues_start, SEEK_SET) == cues_start) {
        // cues_end is computed as cues_start + cues_length + length of the
        // Cues element ID (i.e. 4) + EBML length of the Cues element.
        // cues_end is inclusive and the above sum is reduced by 1.
        uint64_t cues_length, cues_id;
        int bytes_read;
        bytes_read = ebml_read_num   (matroska, matroska->ctx->pb, 4, &cues_id, 1);
        if (bytes_read < 0 || cues_id != (MATROSKA_ID_CUES & 0xfffffff))
            return bytes_read < 0 ? bytes_read : AVERROR_INVALIDDATA;
        bytes_read = ebml_read_length(matroska, matroska->ctx->pb, &cues_length);
        if (bytes_read < 0)
            return bytes_read;
        cues_end = cues_start + 4 + bytes_read + cues_length - 1;
    }
    avio_seek(matroska->ctx->pb, before_pos, SEEK_SET);
    if (cues_start == -1 || cues_end == -1) return -1;

    // parse the cues
    matroska_parse_cues(matroska);

    if (!sti->nb_index_entries)
        return AVERROR_INVALIDDATA;

    // cues start
    av_dict_set_int(&s->streams[0]->metadata, CUES_START, cues_start, 0);

    // cues end
    av_dict_set_int(&s->streams[0]->metadata, CUES_END, cues_end, 0);

    // if the file has cues at the start, fix up the init range so that
    // it does not include it
    if (cues_start <= init_range)
        av_dict_set_int(&s->streams[0]->metadata, INITIALIZATION_RANGE, cues_start - 1, 0);

    // bandwidth
    bandwidth = webm_dash_manifest_compute_bandwidth(s, cues_start);
    if (bandwidth < 0) return -1;
    av_dict_set_int(&s->streams[0]->metadata, BANDWIDTH, bandwidth, 0);

    // check if all clusters start with key frames
    av_dict_set_int(&s->streams[0]->metadata, CLUSTER_KEYFRAME, webm_clusters_start_with_keyframe(s), 0);

    // Store cue point timestamps as a comma separated list
    // for checking subsegment alignment in the muxer.
    av_bprint_init(&bprint, 0, AV_BPRINT_SIZE_UNLIMITED);
    for (int i = 0; i < sti->nb_index_entries; i++)
        av_bprintf(&bprint, "%" PRId64",", sti->index_entries[i].timestamp);
    if (!av_bprint_is_complete(&bprint)) {
        av_bprint_finalize(&bprint, NULL);
        return AVERROR(ENOMEM);
    }
    // Remove the trailing ','
    bprint.str[--bprint.len] = '\0';
    if ((ret = av_bprint_finalize(&bprint, &buf)) < 0)
        return ret;
    av_dict_set(&s->streams[0]->metadata, CUE_TIMESTAMPS,
                buf, AV_DICT_DONT_STRDUP_VAL);

    return 0;
}

static int webm_dash_manifest_read_header(AVFormatContext *s)
{
    char *buf;
    int ret = matroska_read_header(s);
    int64_t init_range;
    MatroskaTrack *tracks;
    MatroskaDemuxContext *matroska = s->priv_data;
    if (ret) {
        av_log(s, AV_LOG_ERROR, "Failed to read file headers\n");
        return -1;
    }
    if (!matroska->tracks.nb_elem || !s->nb_streams) {
        av_log(s, AV_LOG_ERROR, "No track found\n");
        return AVERROR_INVALIDDATA;
    }

    if (!matroska->is_live) {
        buf = av_asprintf("%g", matroska->duration);
        if (!buf)
            return AVERROR(ENOMEM);
        av_dict_set(&s->streams[0]->metadata, DURATION,
                    buf, AV_DICT_DONT_STRDUP_VAL);

        // initialization range
        // 5 is the offset of Cluster ID.
        init_range = avio_tell(s->pb) - 5;
        av_dict_set_int(&s->streams[0]->metadata, INITIALIZATION_RANGE, init_range, 0);
    }

    // basename of the file
    buf = strrchr(s->url, '/');
    av_dict_set(&s->streams[0]->metadata, FILENAME, buf ? ++buf : s->url, 0);

    // track number
    tracks = matroska->tracks.elem;
    av_dict_set_int(&s->streams[0]->metadata, TRACK_NUMBER, tracks[0].num, 0);

    // parse the cues and populate Cue related fields
    if (!matroska->is_live) {
        ret = webm_dash_manifest_cues(s, init_range);
        if (ret < 0) {
            av_log(s, AV_LOG_ERROR, "Error parsing Cues\n");
            return ret;
        }
    }

    // use the bandwidth from the command line if it was provided
    if (matroska->bandwidth > 0) {
        av_dict_set_int(&s->streams[0]->metadata, BANDWIDTH,
                        matroska->bandwidth, 0);
    }
    return 0;
}

static int webm_dash_manifest_read_packet(AVFormatContext *s, AVPacket *pkt)
{
    return AVERROR_EOF;
}

#define OFFSET
static const AVOption options[] = {
    { "live", "flag indicating that the input is a live file that only has the headers.", OFFSET(is_live), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, AV_OPT_FLAG_DECODING_PARAM },
    { "bandwidth", "bandwidth of this stream to be specified in the DASH manifest.", OFFSET(bandwidth), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, AV_OPT_FLAG_DECODING_PARAM },
    { NULL },
};

static const AVClass webm_dash_class = {
    .class_name = "WebM DASH Manifest demuxer",
    .item_name  = av_default_item_name,
    .option     = options,
    .version    = LIBAVUTIL_VERSION_INT,
};

const FFInputFormat ff_webm_dash_manifest_demuxer = {
    .p.name         = "webm_dash_manifest",
    .p.long_name    = NULL_IF_CONFIG_SMALL("WebM DASH Manifest"),
    .p.priv_class   = &webm_dash_class,
    .priv_data_size = sizeof(MatroskaDemuxContext),
    .flags_internal = FF_INFMT_FLAG_INIT_CLEANUP,
    .read_header    = webm_dash_manifest_read_header,
    .read_packet    = webm_dash_manifest_read_packet,
    .read_close     = matroska_read_close,
};
#endif

const FFInputFormat ff_matroska_demuxer =;