libavformat/oggdec.c
9146ca37
 /*
  * Ogg bitstream support
  * Luca Barbato <lu_zero@gentoo.org>
  * Based on tcvp implementation
  */
 
e873c03a
 /*
9146ca37
     Copyright (C) 2005  Michael Ahlberg, Måns Rullgård
 
     Permission is hereby granted, free of charge, to any person
     obtaining a copy of this software and associated documentation
     files (the "Software"), to deal in the Software without
     restriction, including without limitation the rights to use, copy,
     modify, merge, publish, distribute, sublicense, and/or sell copies
     of the Software, and to permit persons to whom the Software is
     furnished to do so, subject to the following conditions:
 
     The above copyright notice and this permission notice shall be
     included in all copies or substantial portions of the Software.
 
     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
     EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
     MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
     HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
     WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
     DEALINGS IN THE SOFTWARE.
e873c03a
  */
9146ca37
 
 #include <stdio.h>
c7c976c6
 #include "libavutil/avassert.h"
23637f98
 #include "libavutil/intreadwrite.h"
a0ddef24
 #include "oggdec.h"
9146ca37
 #include "avformat.h"
a2faa951
 #include "internal.h"
66061a12
 #include "vorbiscomment.h"
9146ca37
 
 #define MAX_PAGE_SIZE 65307
 #define DECODER_BUFFER_SIZE MAX_PAGE_SIZE
 
77be08ee
 static const struct ogg_codec * const ogg_codecs[] = {
32ad8692
     &ff_skeleton_codec,
0f87cbdd
     &ff_daala_codec,
24ca518b
     &ff_dirac_codec,
547ea47d
     &ff_speex_codec,
     &ff_vorbis_codec,
     &ff_theora_codec,
     &ff_flac_codec,
e61b83d2
     &ff_celt_codec,
e62fd661
     &ff_opus_codec,
d890db5f
     &ff_vp8_codec,
24ca518b
     &ff_old_dirac_codec,
547ea47d
     &ff_old_flac_codec,
     &ff_ogm_video_codec,
     &ff_ogm_audio_codec,
     &ff_ogm_text_codec,
     &ff_ogm_old_codec,
9146ca37
     NULL
 };
 
8443082d
 static int64_t ogg_calc_pts(AVFormatContext *s, int idx, int64_t *dts);
7cb27d21
 static int ogg_new_stream(AVFormatContext *s, uint32_t serial);
3250d4b3
 static int ogg_restore(AVFormatContext *s);
8443082d
 
fb6fa48f
 static void free_stream(AVFormatContext *s, int i)
 {
     struct ogg *ogg = s->priv_data;
     struct ogg_stream *stream = &ogg->streams[i];
 
     av_freep(&stream->buf);
     if (stream->codec &&
         stream->codec->cleanup) {
         stream->codec->cleanup(s, i);
     }
 
     av_freep(&stream->private);
     av_freep(&stream->new_metadata);
 }
 
9146ca37
 //FIXME We could avoid some structure duplication
e575685f
 static int ogg_save(AVFormatContext *s)
9146ca37
 {
77be08ee
     struct ogg *ogg = s->priv_data;
     struct ogg_state *ost =
f5f1cf52
         av_malloc(sizeof(*ost) + (ogg->nstreams - 1) * sizeof(*ogg->streams));
9146ca37
     int i;
32d023eb
     int ret = 0;
40adcf57
 
     if (!ost)
         return AVERROR(ENOMEM);
 
f5f1cf52
     ost->pos      = avio_tell(s->pb);
     ost->curidx   = ogg->curidx;
     ost->next     = ogg->state;
20be72c8
     ost->nstreams = ogg->nstreams;
9146ca37
     memcpy(ost->streams, ogg->streams, ogg->nstreams * sizeof(*ogg->streams));
 
f5f1cf52
     for (i = 0; i < ogg->nstreams; i++) {
77be08ee
         struct ogg_stream *os = ogg->streams + i;
059a9348
         os->buf = av_mallocz(os->bufsize + AV_INPUT_BUFFER_PADDING_SIZE);
32d023eb
         if (os->buf)
             memcpy(os->buf, ost->streams[i].buf, os->bufpos);
         else
             ret = AVERROR(ENOMEM);
7eb84f2c
         os->new_metadata      = NULL;
         os->new_metadata_size = 0;
9146ca37
     }
 
     ogg->state = ost;
 
32d023eb
     if (ret < 0)
3250d4b3
         ogg_restore(s);
32d023eb
 
     return ret;
9146ca37
 }
 
3250d4b3
 static int ogg_restore(AVFormatContext *s)
9146ca37
 {
77be08ee
     struct ogg *ogg = s->priv_data;
471fe57e
     AVIOContext *bc = s->pb;
77be08ee
     struct ogg_state *ost = ogg->state;
f369b935
     int i, err;
9146ca37
 
     if (!ost)
         return 0;
 
     ogg->state = ost->next;
 
e46ab997
         for (i = 0; i < ogg->nstreams; i++) {
cd7a2954
             struct ogg_stream *stream = &ogg->streams[i];
             av_freep(&stream->buf);
             av_freep(&stream->new_metadata);
 
e46ab997
             if (i >= ost->nstreams || !ost->streams[i].private) {
                 free_stream(s, i);
             }
         }
9146ca37
 
f5f1cf52
         avio_seek(bc, ost->pos, SEEK_SET);
c5cf58d4
         ogg->page_pos = -1;
f5f1cf52
         ogg->curidx   = ost->curidx;
20be72c8
         ogg->nstreams = ost->nstreams;
f369b935
         if ((err = av_reallocp_array(&ogg->streams, ogg->nstreams,
                                      sizeof(*ogg->streams))) < 0) {
             ogg->nstreams = 0;
             return err;
         } else
bc851a29
             memcpy(ogg->streams, ost->streams,
                    ost->nstreams * sizeof(*ogg->streams));
9146ca37
 
f5f1cf52
     av_free(ost);
9146ca37
 
     return 0;
 }
 
5f9f78dc
 static int ogg_reset(AVFormatContext *s)
9146ca37
 {
5f9f78dc
     struct ogg *ogg = s->priv_data;
9146ca37
     int i;
96fb233e
     int64_t start_pos = avio_tell(s->pb);
9146ca37
 
f5f1cf52
     for (i = 0; i < ogg->nstreams; i++) {
77be08ee
         struct ogg_stream *os = ogg->streams + i;
f5f1cf52
         os->bufpos     = 0;
         os->pstart     = 0;
         os->psize      = 0;
         os->granule    = -1;
         os->lastpts    = AV_NOPTS_VALUE;
         os->lastdts    = AV_NOPTS_VALUE;
         os->sync_pos   = -1;
         os->page_pos   = 0;
         os->nsegs      = 0;
         os->segp       = 0;
ecc0027b
         os->incomplete = 0;
ea5bd7ea
         os->got_data = 0;
29245147
         if (start_pos <= s->internal->data_offset) {
96fb233e
             os->lastpts = 0;
         }
9c0dd7b4
         os->end_trimming = 0;
7eb84f2c
         av_freep(&os->new_metadata);
         os->new_metadata_size = 0;
9146ca37
     }
 
c5cf58d4
     ogg->page_pos = -1;
9146ca37
     ogg->curidx = -1;
 
     return 0;
 }
 
e575685f
 static const struct ogg_codec *ogg_find_codec(uint8_t *buf, int size)
9146ca37
 {
     int i;
 
     for (i = 0; ogg_codecs[i]; i++)
         if (size >= ogg_codecs[i]->magicsize &&
f5f1cf52
             !memcmp(buf, ogg_codecs[i]->magic, ogg_codecs[i]->magicsize))
9146ca37
             return ogg_codecs[i];
 
     return NULL;
 }
 
a218c5eb
 /**
  * Replace the current stream with a new one. This is a typical webradio
  * situation where a new audio stream spawn (identified with a new serial) and
  * must replace the previous one (track switch).
  */
7cb27d21
 static int ogg_replace_stream(AVFormatContext *s, uint32_t serial, int nsegs)
9146ca37
 {
a218c5eb
     struct ogg *ogg = s->priv_data;
     struct ogg_stream *os;
ec40d15d
     const struct ogg_codec *codec;
7cb27d21
     int i = 0;
 
4de591e6
     if (s->pb->seekable & AVIO_SEEKABLE_NORMAL) {
7cb27d21
         uint8_t magic[8];
         int64_t pos = avio_tell(s->pb);
         avio_skip(s->pb, nsegs);
         avio_read(s->pb, magic, sizeof(magic));
         avio_seek(s->pb, pos, SEEK_SET);
         codec = ogg_find_codec(magic, sizeof(magic));
         if (!codec) {
             av_log(s, AV_LOG_ERROR, "Cannot identify new stream\n");
             return AVERROR_INVALIDDATA;
         }
         for (i = 0; i < ogg->nstreams; i++) {
             if (ogg->streams[i].codec == codec)
                 break;
         }
         if (i >= ogg->nstreams)
             return ogg_new_stream(s, serial);
     } else if (ogg->nstreams != 1) {
a9b42487
         avpriv_report_missing_feature(s, "Changing stream parameters in multistream ogg");
a218c5eb
         return AVERROR_PATCHWELCOME;
     }
 
7cb27d21
     os = &ogg->streams[i];
a218c5eb
 
c994bb2f
     os->serial  = serial;
7cb27d21
     return i;
c994bb2f
 
c74d4658
 #if 0
c6664242
     buf     = os->buf;
a218c5eb
     bufsize = os->bufsize;
9db3fb6e
     codec   = os->codec;
a218c5eb
 
7cb27d21
     if (!ogg->state || ogg->state->streams[i].private != os->private)
         av_freep(&ogg->streams[i].private);
a218c5eb
 
     /* Set Ogg stream settings similar to what is done in ogg_new_stream(). We
      * also re-use the ogg_stream allocated buffer */
     memset(os, 0, sizeof(*os));
c6664242
     os->serial  = serial;
a218c5eb
     os->bufsize = bufsize;
c6664242
     os->buf     = buf;
     os->header  = -1;
     os->codec   = codec;
a218c5eb
 
7cb27d21
     return i;
c74d4658
 #endif
a218c5eb
 }
9146ca37
 
a218c5eb
 static int ogg_new_stream(AVFormatContext *s, uint32_t serial)
 {
77be08ee
     struct ogg *ogg = s->priv_data;
c6664242
     int idx         = ogg->nstreams;
9146ca37
     AVStream *st;
77be08ee
     struct ogg_stream *os;
3a895533
     size_t size;
9146ca37
 
ea5bd7ea
     if (ogg->state) {
         av_log(s, AV_LOG_ERROR, "New streams are not supposed to be added "
                "in between Ogg context save/restore operations.\n");
         return AVERROR_BUG;
     }
 
23f64200
     /* Allocate and init a new Ogg Stream */
3a895533
     if (av_size_mult(ogg->nstreams + 1, sizeof(*ogg->streams), &size) < 0 ||
         !(os = av_realloc(ogg->streams, size)))
         return AVERROR(ENOMEM);
d69238e9
     ogg->streams = os;
     os           = ogg->streams + idx;
e18ea765
     memset(os, 0, sizeof(*os));
f5f1cf52
     os->serial        = serial;
     os->bufsize       = DECODER_BUFFER_SIZE;
059a9348
     os->buf           = av_malloc(os->bufsize + AV_INPUT_BUFFER_PADDING_SIZE);
f5f1cf52
     os->header        = -1;
d1f05dd1
     os->start_granule = OGG_NOGRANULE_VALUE;
3a895533
     if (!os->buf)
         return AVERROR(ENOMEM);
9146ca37
 
23f64200
     /* Create the associated AVStream */
     st = avformat_new_stream(s, NULL);
     if (!st) {
         av_freep(&os->buf);
         return AVERROR(ENOMEM);
     }
     st->id = idx;
     avpriv_set_pts_info(st, 64, 1, 1000000);
9146ca37
 
3a895533
     ogg->nstreams++;
9146ca37
     return idx;
 }
 
e575685f
 static int ogg_new_buf(struct ogg *ogg, int idx)
12a195e3
 {
77be08ee
     struct ogg_stream *os = ogg->streams + idx;
059a9348
     uint8_t *nb = av_malloc(os->bufsize + AV_INPUT_BUFFER_PADDING_SIZE);
12a195e3
     int size = os->bufpos - os->pstart;
f5f1cf52
 
9b8152bf
     if (!nb)
         return AVERROR(ENOMEM);
 
f5f1cf52
     if (os->buf) {
12a195e3
         memcpy(nb, os->buf + os->pstart, size);
         av_free(os->buf);
     }
f5f1cf52
 
     os->buf    = nb;
12a195e3
     os->bufpos = size;
     os->pstart = 0;
 
     return 0;
 }
 
ea5bd7ea
 static int data_packets_seen(const struct ogg *ogg)
 {
     int i;
 
     for (i = 0; i < ogg->nstreams; i++)
         if (ogg->streams[i].got_data)
             return 1;
     return 0;
 }
 
277ddf12
 static int ogg_read_page(AVFormatContext *s, int *sid)
9146ca37
 {
471fe57e
     AVIOContext *bc = s->pb;
77be08ee
     struct ogg *ogg = s->priv_data;
     struct ogg_stream *os;
bec994df
     int ret, i = 0;
9146ca37
     int flags, nsegs;
     uint64_t gp;
     uint32_t serial;
     int size, idx;
191e8ca7
     uint8_t sync[4];
9146ca37
     int sp = 0;
 
9cec1bbd
     ret = avio_read(bc, sync, 4);
bec994df
     if (ret < 4)
         return ret < 0 ? ret : AVERROR_EOF;
9146ca37
 
f5f1cf52
     do {
9146ca37
         int c;
 
         if (sync[sp & 3] == 'O' &&
             sync[(sp + 1) & 3] == 'g' &&
             sync[(sp + 2) & 3] == 'g' && sync[(sp + 3) & 3] == 'S')
             break;
 
4de591e6
         if(!i && (bc->seekable & AVIO_SEEKABLE_NORMAL) && ogg->page_pos > 0) {
c5cf58d4
             memset(sync, 0, 4);
             avio_seek(bc, ogg->page_pos+4, SEEK_SET);
             ogg->page_pos = -1;
         }
 
1447dc59
         c = avio_r8(bc);
f5f1cf52
 
d34ec64a
         if (avio_feof(bc))
bec994df
             return AVERROR_EOF;
f5f1cf52
 
9146ca37
         sync[sp++ & 3] = c;
f5f1cf52
     } while (i++ < MAX_PAGE_SIZE);
9146ca37
 
f5f1cf52
     if (i >= MAX_PAGE_SIZE) {
         av_log(s, AV_LOG_INFO, "cannot find sync word\n");
bec994df
         return AVERROR_INVALIDDATA;
9146ca37
     }
 
c6664242
     if (avio_r8(bc) != 0) {      /* version */
6fd47806
         av_log (s, AV_LOG_ERROR, "ogg page, unsupported version\n");
bec994df
         return AVERROR_INVALIDDATA;
6fd47806
     }
9146ca37
 
f5f1cf52
     flags  = avio_r8(bc);
     gp     = avio_rl64(bc);
     serial = avio_rl32(bc);
e65ab9d9
     avio_skip(bc, 8); /* seq, crc */
f5f1cf52
     nsegs  = avio_r8(bc);
9146ca37
 
f5f1cf52
     idx = ogg_find_stream(ogg, serial);
     if (idx < 0) {
ea5bd7ea
         if (data_packets_seen(ogg))
7cb27d21
             idx = ogg_replace_stream(s, serial, nsegs);
a218c5eb
         else
             idx = ogg_new_stream(s, serial);
dc713546
 
a6bb09fc
         if (idx < 0) {
094991eb
             av_log(s, AV_LOG_ERROR, "failed to create or replace stream\n");
bec994df
             return idx;
a6bb09fc
         }
9146ca37
     }
 
     os = ogg->streams + idx;
c5cf58d4
     ogg->page_pos =
384c9c2f
     os->page_pos = avio_tell(bc) - 27;
9146ca37
 
9b8152bf
     if (os->psize > 0) {
         ret = ogg_new_buf(ogg, idx);
         if (ret < 0)
             return ret;
     }
12a195e3
 
9cec1bbd
     ret = avio_read(bc, os->segments, nsegs);
bec994df
     if (ret < nsegs)
         return ret < 0 ? ret : AVERROR_EOF;
9146ca37
 
     os->nsegs = nsegs;
f5f1cf52
     os->segp  = 0;
9146ca37
 
     size = 0;
     for (i = 0; i < nsegs; i++)
         size += os->segments[i];
 
ea5bd7ea
     if (!(flags & OGG_FLAG_BOS))
         os->got_data = 1;
 
f5f1cf52
     if (flags & OGG_FLAG_CONT || os->incomplete) {
         if (!os->psize) {
d7b542ae
             // If this is the very first segment we started
             // playback in the middle of a continuation packet.
             // Discard it since we missed the start of it.
f5f1cf52
             while (os->segp < os->nsegs) {
9146ca37
                 int seg = os->segments[os->segp++];
                 os->pstart += seg;
                 if (seg < 255)
bad4a6bb
                     break;
9146ca37
             }
73823cb9
             os->sync_pos = os->page_pos;
9146ca37
         }
f5f1cf52
     } else {
         os->psize    = 0;
73823cb9
         os->sync_pos = os->page_pos;
9146ca37
     }
 
f5f1cf52
     if (os->bufsize - os->bufpos < size) {
059a9348
         uint8_t *nb = av_malloc((os->bufsize *= 2) + AV_INPUT_BUFFER_PADDING_SIZE);
ba064ebe
         if (!nb)
             return AVERROR(ENOMEM);
f5f1cf52
         memcpy(nb, os->buf, os->bufpos);
         av_free(os->buf);
9146ca37
         os->buf = nb;
     }
 
9cec1bbd
     ret = avio_read(bc, os->buf + os->bufpos, size);
bec994df
     if (ret < size)
         return ret < 0 ? ret : AVERROR_EOF;
9146ca37
 
     os->bufpos += size;
     os->granule = gp;
f5f1cf52
     os->flags   = flags;
9146ca37
 
059a9348
     memset(os->buf + os->bufpos, 0, AV_INPUT_BUFFER_PADDING_SIZE);
277ddf12
     if (sid)
         *sid = idx;
9146ca37
 
     return 0;
 }
 
69aeba13
 /**
  * @brief find the next Ogg packet
277ddf12
  * @param *sid is set to the stream for the packet or -1 if there is
69aeba13
  *             no matching stream, in that case assume all other return
  *             values to be uninitialized.
  * @return negative value on error or EOF.
  */
277ddf12
 static int ogg_packet(AVFormatContext *s, int *sid, int *dstart, int *dsize,
e575685f
                       int64_t *fpos)
9146ca37
 {
77be08ee
     struct ogg *ogg = s->priv_data;
bec994df
     int idx, i, ret;
77be08ee
     struct ogg_stream *os;
9146ca37
     int complete = 0;
f5f1cf52
     int segp     = 0, psize = 0;
9146ca37
 
1a3eb042
     av_log(s, AV_LOG_TRACE, "ogg_packet: curidx=%i\n", ogg->curidx);
277ddf12
     if (sid)
         *sid = -1;
9146ca37
 
f5f1cf52
     do {
9146ca37
         idx = ogg->curidx;
 
f5f1cf52
         while (idx < 0) {
9cec1bbd
             ret = ogg_read_page(s, &idx);
bec994df
             if (ret < 0)
                 return ret;
9146ca37
         }
 
         os = ogg->streams + idx;
 
1a3eb042
         av_log(s, AV_LOG_TRACE, "ogg_packet: idx=%d pstart=%d psize=%d segp=%d nsegs=%d\n",
9146ca37
                 idx, os->pstart, os->psize, os->segp, os->nsegs);
 
f5f1cf52
         if (!os->codec) {
             if (os->header < 0) {
                 os->codec = ogg_find_codec(os->buf, os->bufpos);
                 if (!os->codec) {
bec994df
                     av_log(s, AV_LOG_WARNING, "Codec not found\n");
9146ca37
                     os->header = 0;
                     return 0;
                 }
f5f1cf52
             } else {
9146ca37
                 return 0;
             }
         }
 
f5f1cf52
         segp  = os->segp;
9146ca37
         psize = os->psize;
 
f5f1cf52
         while (os->segp < os->nsegs) {
9146ca37
             int ss = os->segments[os->segp++];
             os->psize += ss;
f5f1cf52
             if (ss < 255) {
9146ca37
                 complete = 1;
                 break;
             }
         }
 
f5f1cf52
         if (!complete && os->segp == os->nsegs) {
             ogg->curidx    = -1;
d7b542ae
             // Do not set incomplete for empty packets.
             // Together with the code in ogg_read_page
             // that discards all continuation of empty packets
             // we would get an infinite loop.
             os->incomplete = !!os->psize;
9146ca37
         }
f5f1cf52
     } while (!complete);
9146ca37
 
 
adc725b5
     if (os->granule == -1)
f5f1cf52
         av_log(s, AV_LOG_WARNING,
                "Page at %"PRId64" is missing granule\n",
                os->page_pos);
adc725b5
 
f5f1cf52
     ogg->curidx    = idx;
ecc0027b
     os->incomplete = 0;
9146ca37
 
81b743eb
     if (os->header) {
f5f1cf52
         os->header = os->codec->header(s, idx);
         if (!os->header) {
             os->segp  = segp;
bad4a6bb
             os->psize = psize;
365d8e47
 
94dfea71
             // We have reached the first non-header packet in this stream.
             // Unfortunately more header packets may still follow for others,
d459d8e2
             // but if we continue with header parsing we may lose data packets.
bad4a6bb
             ogg->headers = 1;
365d8e47
 
             // Update the header state for all streams and
             // compute the data_offset.
9deaec78
             if (!s->internal->data_offset)
                 s->internal->data_offset = os->sync_pos;
f5f1cf52
 
365d8e47
             for (i = 0; i < ogg->nstreams; i++) {
                 struct ogg_stream *cur_os = ogg->streams + i;
 
                 // if we have a partial non-header packet, its start is
                 // obviously at or after the data start
                 if (cur_os->incomplete)
9deaec78
                     s->internal->data_offset = FFMIN(s->internal->data_offset, cur_os->sync_pos);
365d8e47
             }
f5f1cf52
         } else {
7751e469
             os->nb_header++;
bad4a6bb
             os->pstart += os->psize;
f5f1cf52
             os->psize   = 0;
9146ca37
         }
81b743eb
     } else {
f5f1cf52
         os->pflags    = 0;
15299b38
         os->pduration = 0;
9146ca37
         if (os->codec && os->codec->packet)
f5f1cf52
             os->codec->packet(s, idx);
277ddf12
         if (sid)
             *sid = idx;
12a195e3
         if (dstart)
             *dstart = os->pstart;
         if (dsize)
             *dsize = os->psize;
73823cb9
         if (fpos)
             *fpos = os->sync_pos;
f5f1cf52
         os->pstart  += os->psize;
         os->psize    = 0;
49125aed
         if(os->pstart == os->bufpos)
             os->bufpos = os->pstart = 0;
73823cb9
         os->sync_pos = os->page_pos;
9146ca37
     }
 
5e15c7d9
     // determine whether there are more complete packets in this page
     // if not, the page's granule will apply to this packet
     os->page_end = 1;
     for (i = os->segp; i < os->nsegs; i++)
         if (os->segments[i] < 255) {
             os->page_end = 0;
             break;
         }
 
9146ca37
     if (os->segp == os->nsegs)
         ogg->curidx = -1;
 
     return 0;
 }
 
e575685f
 static int ogg_get_length(AVFormatContext *s)
9146ca37
 {
77be08ee
     struct ogg *ogg = s->priv_data;
656e31ed
     int i, ret;
bc5c918e
     int64_t size, end;
d2cab951
     int streams_left=0;
69599eea
 
83548fe8
     if (!(s->pb->seekable & AVIO_SEEKABLE_NORMAL))
69599eea
         return 0;
9146ca37
 
 // already set
     if (s->duration != AV_NOPTS_VALUE)
         return 0;
 
db44ea96
     size = avio_size(s->pb);
f5f1cf52
     if (size < 0)
56466d7b
         return 0;
f5f1cf52
     end = size > MAX_PAGE_SIZE ? size - MAX_PAGE_SIZE : 0;
56466d7b
 
40adcf57
     ret = ogg_save(s);
     if (ret < 0)
         return ret;
f5f1cf52
     avio_seek(s->pb, end, SEEK_SET);
c5cf58d4
     ogg->page_pos = -1;
9146ca37
 
f5f1cf52
     while (!ogg_read_page(s, &i)) {
e22f2aaf
         if (ogg->streams[i].granule != -1 && ogg->streams[i].granule != 0 &&
44a088ea
             ogg->streams[i].codec) {
             s->streams[i]->duration =
f5f1cf52
                 ogg_gptopts(s, i, ogg->streams[i].granule, NULL);
c6664242
             if (s->streams[i]->start_time != AV_NOPTS_VALUE) {
44a088ea
                 s->streams[i]->duration -= s->streams[i]->start_time;
d2cab951
                 streams_left-= (ogg->streams[i].got_start==-1);
                 ogg->streams[i].got_start= 1;
c6664242
             } else if(!ogg->streams[i].got_start) {
d2cab951
                 ogg->streams[i].got_start= -1;
                 streams_left++;
             }
44a088ea
         }
9146ca37
     }
 
3250d4b3
     ogg_restore(s);
9146ca37
 
40adcf57
     ret = ogg_save(s);
     if (ret < 0)
         return ret;
 
29245147
     avio_seek (s->pb, s->internal->data_offset, SEEK_SET);
8443082d
     ogg_reset(s);
a4163b2d
     while (streams_left > 0 && !ogg_packet(s, &i, NULL, NULL, NULL)) {
         int64_t pts;
         if (i < 0) continue;
         pts = ogg_calc_pts(s, i, NULL);
ee2a6f5d
         if (s->streams[i]->duration == AV_NOPTS_VALUE)
             continue;
c6664242
         if (pts != AV_NOPTS_VALUE && s->streams[i]->start_time == AV_NOPTS_VALUE && !ogg->streams[i].got_start) {
8443082d
             s->streams[i]->duration -= pts;
             ogg->streams[i].got_start= 1;
             streams_left--;
c6664242
         }else if(s->streams[i]->start_time != AV_NOPTS_VALUE && !ogg->streams[i].got_start) {
e0eaf100
             ogg->streams[i].got_start= 1;
             streams_left--;
8443082d
         }
5901cd62
     }
3250d4b3
     ogg_restore (s);
5901cd62
 
9146ca37
     return 0;
 }
 
89b51b57
 static int ogg_read_close(AVFormatContext *s)
 {
     struct ogg *ogg = s->priv_data;
     int i;
 
     for (i = 0; i < ogg->nstreams; i++) {
fb6fa48f
         free_stream(s, i);
89b51b57
     }
845414bb
 
     ogg->nstreams = 0;
 
6838e1f5
     av_freep(&ogg->streams);
89b51b57
     return 0;
 }
 
6e9651d1
 static int ogg_read_header(AVFormatContext *s)
9146ca37
 {
77be08ee
     struct ogg *ogg = s->priv_data;
bec994df
     int ret, i;
bf8bfc6a
 
9146ca37
     ogg->curidx = -1;
bf8bfc6a
 
9146ca37
     //linear headers seek from start
bf8bfc6a
     do {
         ret = ogg_packet(s, NULL, NULL, NULL, NULL);
07a86628
         if (ret < 0) {
             ogg_read_close(s);
bf8bfc6a
             return ret;
07a86628
         }
bf8bfc6a
     } while (!ogg->headers);
40d552da
     av_log(s, AV_LOG_TRACE, "found headers\n");
9146ca37
 
4c273eb6
     for (i = 0; i < ogg->nstreams; i++) {
         struct ogg_stream *os = ogg->streams + i;
 
cc4deafe
         if (ogg->streams[i].header < 0) {
             av_log(s, AV_LOG_ERROR, "Header parsing failed for stream %d\n", i);
c9da676d
             ogg->streams[i].codec = NULL;
542f7259
             av_freep(&ogg->streams[i].private);
4c273eb6
         } else if (os->codec && os->nb_header < os->codec->nb_header) {
780b1aa1
             av_log(s, AV_LOG_WARNING,
                    "Headers mismatch for stream %d: "
                    "expected %d received %d.\n",
                    i, os->codec->nb_header, os->nb_header);
35c7a1df
             if (s->error_recognition & AV_EF_EXPLODE) {
                 ogg_read_close(s);
780b1aa1
                 return AVERROR_INVALIDDATA;
35c7a1df
             }
cc4deafe
         }
0842e32f
         if (os->start_granule != OGG_NOGRANULE_VALUE)
             os->lastpts = s->streams[i]->start_time =
                 ogg_gptopts(s, i, os->start_granule, NULL);
4c273eb6
     }
c9da676d
 
9146ca37
     //linear granulepos seek from end
656e31ed
     ret = ogg_get_length(s);
     if (ret < 0) {
         ogg_read_close(s);
         return ret;
     }
9146ca37
 
     return 0;
 }
 
6abaa272
 static int64_t ogg_calc_pts(AVFormatContext *s, int idx, int64_t *dts)
 {
f5f1cf52
     struct ogg *ogg       = s->priv_data;
6abaa272
     struct ogg_stream *os = ogg->streams + idx;
f5f1cf52
     int64_t pts           = AV_NOPTS_VALUE;
6abaa272
 
     if (dts)
         *dts = AV_NOPTS_VALUE;
 
     if (os->lastpts != AV_NOPTS_VALUE) {
f5f1cf52
         pts         = os->lastpts;
6abaa272
         os->lastpts = AV_NOPTS_VALUE;
     }
     if (os->lastdts != AV_NOPTS_VALUE) {
         if (dts)
             *dts = os->lastdts;
         os->lastdts = AV_NOPTS_VALUE;
     }
     if (os->page_end) {
         if (os->granule != -1LL) {
             if (os->codec && os->codec->granule_is_start)
                 pts = ogg_gptopts(s, idx, os->granule, dts);
             else
                 os->lastpts = ogg_gptopts(s, idx, os->granule, &os->lastdts);
             os->granule = -1LL;
adc725b5
         }
6abaa272
     }
     return pts;
 }
9146ca37
 
787528b4
 static void ogg_validate_keyframe(AVFormatContext *s, int idx, int pstart, int psize)
 {
     struct ogg *ogg = s->priv_data;
     struct ogg_stream *os = ogg->streams + idx;
313a6c65
     int invalid = 0;
     if (psize) {
6f69f7a8
         switch (s->streams[idx]->codecpar->codec_id) {
313a6c65
         case AV_CODEC_ID_THEORA:
             invalid = !!(os->pflags & AV_PKT_FLAG_KEY) != !(os->buf[pstart] & 0x40);
         break;
         case AV_CODEC_ID_VP8:
             invalid = !!(os->pflags & AV_PKT_FLAG_KEY) != !(os->buf[pstart] & 1);
         }
         if (invalid) {
787528b4
             os->pflags ^= AV_PKT_FLAG_KEY;
030e96fe
             av_log(s, AV_LOG_WARNING, "Broken file, %skeyframe not correctly marked.\n",
                    (os->pflags & AV_PKT_FLAG_KEY) ? "" : "non-");
787528b4
         }
     }
 }
 
e575685f
 static int ogg_read_packet(AVFormatContext *s, AVPacket *pkt)
9146ca37
 {
77be08ee
     struct ogg *ogg;
     struct ogg_stream *os;
69aeba13
     int idx, ret;
12a195e3
     int pstart, psize;
d8b91fae
     int64_t fpos, pts, dts;
9146ca37
 
e2785001
     if (s->io_repositioned) {
         ogg_reset(s);
         s->io_repositioned = 0;
     }
 
115329f1
     //Get an ogg packet
d8b91fae
 retry:
f5f1cf52
     do {
9cec1bbd
         ret = ogg_packet(s, &idx, &pstart, &psize, &fpos);
392aa6e4
         if (ret < 0)
             return ret;
f5f1cf52
     } while (idx < 0 || !s->streams[idx]);
9146ca37
 
     ogg = s->priv_data;
f5f1cf52
     os  = ogg->streams + idx;
9146ca37
 
d8b91fae
     // pflags might not be set until after this
     pts = ogg_calc_pts(s, idx, &dts);
787528b4
     ogg_validate_keyframe(s, idx, pstart, psize);
d8b91fae
 
cc947f04
     if (os->keyframe_seek && !(os->pflags & AV_PKT_FLAG_KEY))
d8b91fae
         goto retry;
     os->keyframe_seek = 0;
 
9146ca37
     //Alloc a pkt
9cec1bbd
     ret = av_new_packet(pkt, psize);
392aa6e4
     if (ret < 0)
         return ret;
9146ca37
     pkt->stream_index = idx;
f5f1cf52
     memcpy(pkt->data, os->buf + pstart, psize);
5e15c7d9
 
f5f1cf52
     pkt->pts      = pts;
     pkt->dts      = dts;
     pkt->flags    = os->pflags;
15299b38
     pkt->duration = os->pduration;
f5f1cf52
     pkt->pos      = fpos;
e1a794b2
 
23637f98
     if (os->end_trimming) {
         uint8_t *side_data = av_packet_new_side_data(pkt,
                                                      AV_PKT_DATA_SKIP_SAMPLES,
                                                      10);
fb33bff9
         if(!side_data)
4ccd7cb4
             goto fail;
23637f98
         AV_WL32(side_data + 4, os->end_trimming);
551a6797
         os->end_trimming = 0;
23637f98
     }
 
7eb84f2c
     if (os->new_metadata) {
         uint8_t *side_data = av_packet_new_side_data(pkt,
                                                      AV_PKT_DATA_METADATA_UPDATE,
                                                      os->new_metadata_size);
fb33bff9
         if(!side_data)
4ccd7cb4
             goto fail;
 
7eb84f2c
         memcpy(side_data, os->new_metadata, os->new_metadata_size);
         av_freep(&os->new_metadata);
         os->new_metadata_size = 0;
     }
 
12a195e3
     return psize;
4ccd7cb4
 fail:
c2f861ca
     av_packet_unref(pkt);
4ccd7cb4
     return AVERROR(ENOMEM);
9146ca37
 }
 
e575685f
 static int64_t ogg_read_timestamp(AVFormatContext *s, int stream_index,
                                   int64_t *pos_arg, int64_t pos_limit)
9146ca37
 {
77be08ee
     struct ogg *ogg = s->priv_data;
471fe57e
     AVIOContext *bc = s->pb;
f5f1cf52
     int64_t pts     = AV_NOPTS_VALUE;
c6664242
     int64_t keypos  = -1;
69aeba13
     int i;
787528b4
     int pstart, psize;
f59d8ff8
     avio_seek(bc, *pos_arg, SEEK_SET);
5f9f78dc
     ogg_reset(s);
873d117e
 
c6664242
     while (   avio_tell(bc) <= pos_limit
            && !ogg_packet(s, &i, &pstart, &psize, pos_arg)) {
873d117e
         if (i == stream_index) {
4cc3467e
             struct ogg_stream *os = ogg->streams + stream_index;
06eef96b
             // Do not trust the last timestamps of an ogm video
5e0c7eab
             if (    (os->flags & OGG_FLAG_EOS)
                 && !(os->flags & OGG_FLAG_BOS)
                 && os->codec == &ff_ogm_video_codec)
                 continue;
873d117e
             pts = ogg_calc_pts(s, i, NULL);
787528b4
             ogg_validate_keyframe(s, i, pstart, psize);
1b0dc96f
             if (os->pflags & AV_PKT_FLAG_KEY) {
                 keypos = *pos_arg;
             } else if (os->keyframe_seek) {
                 // if we had a previous keyframe but no pts for it,
                 // return that keyframe with this pts value.
                 if (keypos >= 0)
                     *pos_arg = keypos;
                 else
                     pts = AV_NOPTS_VALUE;
             }
a1f29b95
         }
873d117e
         if (pts != AV_NOPTS_VALUE)
             break;
a1f29b95
     }
5f9f78dc
     ogg_reset(s);
a1f29b95
     return pts;
9146ca37
 }
 
e575685f
 static int ogg_read_seek(AVFormatContext *s, int stream_index,
                          int64_t timestamp, int flags)
d8b91fae
 {
f5f1cf52
     struct ogg *ogg       = s->priv_data;
d8b91fae
     struct ogg_stream *os = ogg->streams + stream_index;
     int ret;
 
c7c976c6
     av_assert0(stream_index < ogg->nstreams);
8162c6f0
     // Ensure everything is reset even when seeking via
     // the generated index.
5f9f78dc
     ogg_reset(s);
8162c6f0
 
d8b91fae
     // Try seeking to a keyframe first. If this fails (very possible),
     // av_seek_frame will fall back to ignoring keyframes
9200514a
     if (s->streams[stream_index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO
d8b91fae
         && !(flags & AVSEEK_FLAG_ANY))
         os->keyframe_seek = 1;
 
a2faa951
     ret = ff_seek_frame_binary(s, stream_index, timestamp, flags);
f8b81a02
     ogg_reset(s);
f5f1cf52
     os  = ogg->streams + stream_index;
d8b91fae
     if (ret < 0)
         os->keyframe_seek = 0;
     return ret;
 }
 
2e70e4aa
 static int ogg_probe(AVProbeData *p)
 {
f95257d2
     if (!memcmp("OggS", p->buf, 5) && p->buf[5] <= 0x7)
2e70e4aa
         return AVPROBE_SCORE_MAX;
f95257d2
     return 0;
2e70e4aa
 }
 
66355be3
 AVInputFormat ff_ogg_demuxer = {
b3bbc6fd
     .name           = "ogg",
     .long_name      = NULL_IF_CONFIG_SMALL("Ogg"),
     .priv_data_size = sizeof(struct ogg),
     .read_probe     = ogg_probe,
     .read_header    = ogg_read_header,
     .read_packet    = ogg_read_packet,
     .read_close     = ogg_read_close,
     .read_seek      = ogg_read_seek,
     .read_timestamp = ogg_read_timestamp,
     .extensions     = "ogg",
951a3655
     .flags          = AVFMT_GENERIC_INDEX | AVFMT_TS_DISCONT | AVFMT_NOBINSEARCH,
9146ca37
 };