libavformat/oggdec.c
9146ca37
 /*
  * Ogg bitstream support
  * Luca Barbato <lu_zero@gentoo.org>
  * Based on tcvp implementation
115329f1
  *
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.
 **/
 
 
 #include <stdio.h>
a0ddef24
 #include "oggdec.h"
9146ca37
 #include "avformat.h"
 
 #define MAX_PAGE_SIZE 65307
 #define DECODER_BUFFER_SIZE MAX_PAGE_SIZE
 
77be08ee
 static const struct ogg_codec * const ogg_codecs[] = {
547ea47d
     &ff_speex_codec,
     &ff_vorbis_codec,
     &ff_theora_codec,
     &ff_flac_codec,
     &ff_old_flac_codec,
     &ff_ogm_video_codec,
     &ff_ogm_audio_codec,
     &ff_ogm_text_codec,
     &ff_ogm_old_codec,
9146ca37
     NULL
 };
 
 //FIXME We could avoid some structure duplication
 static int
 ogg_save (AVFormatContext * s)
 {
77be08ee
     struct ogg *ogg = s->priv_data;
     struct ogg_state *ost =
ad3aa874
         av_malloc(sizeof (*ost) + (ogg->nstreams-1) * sizeof (*ogg->streams));
9146ca37
     int i;
899681cd
     ost->pos = url_ftell (s->pb);
9146ca37
     ost->curidx = ogg->curidx;
     ost->next = ogg->state;
20be72c8
     ost->nstreams = ogg->nstreams;
9146ca37
     memcpy(ost->streams, ogg->streams, ogg->nstreams * sizeof(*ogg->streams));
 
     for (i = 0; i < ogg->nstreams; i++){
77be08ee
         struct ogg_stream *os = ogg->streams + i;
9146ca37
         os->buf = av_malloc (os->bufsize);
         memset (os->buf, 0, os->bufsize);
         memcpy (os->buf, ost->streams[i].buf, os->bufpos);
     }
 
     ogg->state = ost;
 
     return 0;
 }
 
 static int
 ogg_restore (AVFormatContext * s, int discard)
 {
77be08ee
     struct ogg *ogg = s->priv_data;
899681cd
     ByteIOContext *bc = s->pb;
77be08ee
     struct ogg_state *ost = ogg->state;
9146ca37
     int i;
 
     if (!ost)
         return 0;
 
     ogg->state = ost->next;
 
     if (!discard){
52b8edc9
         struct ogg_stream *old_streams = ogg->streams;
 
9146ca37
         for (i = 0; i < ogg->nstreams; i++)
             av_free (ogg->streams[i].buf);
 
         url_fseek (bc, ost->pos, SEEK_SET);
         ogg->curidx = ost->curidx;
20be72c8
         ogg->nstreams = ost->nstreams;
52b8edc9
         ogg->streams = av_realloc (ogg->streams,
                                    ogg->nstreams * sizeof (*ogg->streams));
 
         if (ogg->streams) {
             memcpy(ogg->streams, ost->streams,
                    ost->nstreams * sizeof(*ogg->streams));
         } else {
             av_free(old_streams);
             ogg->nstreams = 0;
         }
9146ca37
     }
 
     av_free (ost);
 
     return 0;
 }
 
 static int
77be08ee
 ogg_reset (struct ogg * ogg)
9146ca37
 {
     int i;
 
     for (i = 0; i < ogg->nstreams; i++){
77be08ee
         struct ogg_stream *os = ogg->streams + i;
9146ca37
         os->bufpos = 0;
         os->pstart = 0;
         os->psize = 0;
         os->granule = -1;
         os->lastgp = -1;
         os->nsegs = 0;
         os->segp = 0;
     }
 
     ogg->curidx = -1;
 
     return 0;
 }
 
77be08ee
 static const struct ogg_codec *
2d2f443d
 ogg_find_codec (uint8_t * buf, int size)
9146ca37
 {
     int i;
 
     for (i = 0; ogg_codecs[i]; i++)
         if (size >= ogg_codecs[i]->magicsize &&
             !memcmp (buf, ogg_codecs[i]->magic, ogg_codecs[i]->magicsize))
             return ogg_codecs[i];
 
     return NULL;
 }
 
 static int
77be08ee
 ogg_find_stream (struct ogg * ogg, int serial)
9146ca37
 {
     int i;
 
     for (i = 0; i < ogg->nstreams; i++)
         if (ogg->streams[i].serial == serial)
             return i;
 
     return -1;
 }
 
 static int
 ogg_new_stream (AVFormatContext * s, uint32_t serial)
 {
 
77be08ee
     struct ogg *ogg = s->priv_data;
9146ca37
     int idx = ogg->nstreams++;
     AVStream *st;
77be08ee
     struct ogg_stream *os;
9146ca37
 
     ogg->streams = av_realloc (ogg->streams,
                                ogg->nstreams * sizeof (*ogg->streams));
     memset (ogg->streams + idx, 0, sizeof (*ogg->streams));
     os = ogg->streams + idx;
     os->serial = serial;
     os->bufsize = DECODER_BUFFER_SIZE;
40c5e1fa
     os->buf = av_malloc(os->bufsize);
9146ca37
     os->header = -1;
 
     st = av_new_stream (s, idx);
     if (!st)
769e10f0
         return AVERROR(ENOMEM);
9146ca37
 
     av_set_pts_info(st, 64, 1, 1000000);
 
     return idx;
 }
 
 static int
77be08ee
 ogg_new_buf(struct ogg *ogg, int idx)
12a195e3
 {
77be08ee
     struct ogg_stream *os = ogg->streams + idx;
ea02862a
     uint8_t *nb = av_malloc(os->bufsize);
12a195e3
     int size = os->bufpos - os->pstart;
     if(os->buf){
         memcpy(nb, os->buf + os->pstart, size);
         av_free(os->buf);
     }
     os->buf = nb;
     os->bufpos = size;
     os->pstart = 0;
 
     return 0;
 }
 
 static int
9146ca37
 ogg_read_page (AVFormatContext * s, int *str)
 {
899681cd
     ByteIOContext *bc = s->pb;
77be08ee
     struct ogg *ogg = s->priv_data;
     struct ogg_stream *os;
9146ca37
     int i = 0;
     int flags, nsegs;
     uint64_t gp;
     uint32_t serial;
     uint32_t seq;
     uint32_t crc;
     int size, idx;
191e8ca7
     uint8_t sync[4];
9146ca37
     int sp = 0;
 
     if (get_buffer (bc, sync, 4) < 4)
         return -1;
 
     do{
         int c;
 
         if (sync[sp & 3] == 'O' &&
             sync[(sp + 1) & 3] == 'g' &&
             sync[(sp + 2) & 3] == 'g' && sync[(sp + 3) & 3] == 'S')
             break;
 
         c = url_fgetc (bc);
         if (c < 0)
             return -1;
         sync[sp++ & 3] = c;
     }while (i++ < MAX_PAGE_SIZE);
 
     if (i >= MAX_PAGE_SIZE){
         av_log (s, AV_LOG_INFO, "ogg, can't find sync word\n");
         return -1;
     }
 
     if (url_fgetc (bc) != 0)      /* version */
         return -1;
 
     flags = url_fgetc (bc);
     gp = get_le64 (bc);
     serial = get_le32 (bc);
     seq = get_le32 (bc);
     crc = get_le32 (bc);
     nsegs = url_fgetc (bc);
 
     idx = ogg_find_stream (ogg, serial);
     if (idx < 0){
         idx = ogg_new_stream (s, serial);
         if (idx < 0)
             return -1;
     }
 
     os = ogg->streams + idx;
 
40c5e1fa
     if(os->psize > 0)
12a195e3
         ogg_new_buf(ogg, idx);
 
9146ca37
     if (get_buffer (bc, os->segments, nsegs) < nsegs)
         return -1;
 
     os->nsegs = nsegs;
     os->segp = 0;
 
     size = 0;
     for (i = 0; i < nsegs; i++)
         size += os->segments[i];
 
     if (flags & OGG_FLAG_CONT){
         if (!os->psize){
             while (os->segp < os->nsegs){
                 int seg = os->segments[os->segp++];
                 os->pstart += seg;
                 if (seg < 255)
bad4a6bb
                     break;
9146ca37
             }
         }
     }else{
bad4a6bb
         os->psize = 0;
9146ca37
     }
 
     if (os->bufsize - os->bufpos < size){
2d2f443d
         uint8_t *nb = av_malloc (os->bufsize *= 2);
9146ca37
         memcpy (nb, os->buf, os->bufpos);
         av_free (os->buf);
         os->buf = nb;
     }
 
     if (get_buffer (bc, os->buf + os->bufpos, size) < size)
         return -1;
 
     os->lastgp = os->granule;
     os->bufpos += size;
     os->granule = gp;
     os->flags = flags;
 
     if (str)
         *str = idx;
 
     return 0;
 }
 
 static int
12a195e3
 ogg_packet (AVFormatContext * s, int *str, int *dstart, int *dsize)
9146ca37
 {
77be08ee
     struct ogg *ogg = s->priv_data;
9146ca37
     int idx;
77be08ee
     struct ogg_stream *os;
9146ca37
     int complete = 0;
     int segp = 0, psize = 0;
 
 #if 0
     av_log (s, AV_LOG_DEBUG, "ogg_packet: curidx=%i\n", ogg->curidx);
 #endif
 
     do{
         idx = ogg->curidx;
 
         while (idx < 0){
             if (ogg_read_page (s, &idx) < 0)
                 return -1;
         }
 
         os = ogg->streams + idx;
 
 #if 0
         av_log (s, AV_LOG_DEBUG,
                 "ogg_packet: idx=%d pstart=%d psize=%d segp=%d nsegs=%d\n",
                 idx, os->pstart, os->psize, os->segp, os->nsegs);
 #endif
 
         if (!os->codec){
             if (os->header < 0){
                 os->codec = ogg_find_codec (os->buf, os->bufpos);
                 if (!os->codec){
                     os->header = 0;
                     return 0;
                 }
             }else{
                 return 0;
             }
         }
 
         segp = os->segp;
         psize = os->psize;
 
         while (os->segp < os->nsegs){
             int ss = os->segments[os->segp++];
             os->psize += ss;
             if (ss < 255){
                 complete = 1;
                 break;
             }
         }
 
         if (!complete && os->segp == os->nsegs){
             ogg->curidx = -1;
         }
     }while (!complete);
 
 #if 0
     av_log (s, AV_LOG_DEBUG,
             "ogg_packet: idx %i, frame size %i, start %i\n",
             idx, os->psize, os->pstart);
 #endif
 
     ogg->curidx = idx;
 
     if (os->header < 0){
         int hdr = os->codec->header (s, idx);
         if (!hdr){
bad4a6bb
             os->header = os->seq;
             os->segp = segp;
             os->psize = psize;
             ogg->headers = 1;
9146ca37
         }else{
bad4a6bb
             os->pstart += os->psize;
             os->psize = 0;
9146ca37
         }
     }
 
     if (os->header > -1 && os->seq > os->header){
e1a794b2
         os->pflags = 0;
9146ca37
         if (os->codec && os->codec->packet)
             os->codec->packet (s, idx);
         if (str)
             *str = idx;
12a195e3
         if (dstart)
             *dstart = os->pstart;
         if (dsize)
             *dsize = os->psize;
         os->pstart += os->psize;
         os->psize = 0;
9146ca37
     }
 
     os->seq++;
     if (os->segp == os->nsegs)
         ogg->curidx = -1;
 
     return 0;
 }
 
 static int
 ogg_get_headers (AVFormatContext * s)
 {
77be08ee
     struct ogg *ogg = s->priv_data;
9146ca37
 
     do{
12a195e3
         if (ogg_packet (s, NULL, NULL, NULL) < 0)
9146ca37
             return -1;
     }while (!ogg->headers);
 
 #if 0
     av_log (s, AV_LOG_DEBUG, "found headers\n");
 #endif
 
     return 0;
 }
 
 static uint64_t
 ogg_gptopts (AVFormatContext * s, int i, uint64_t gp)
 {
77be08ee
     struct ogg *ogg = s->priv_data;
     struct ogg_stream *os = ogg->streams + i;
9146ca37
     uint64_t pts = AV_NOPTS_VALUE;
 
1ed923ea
     if(os->codec->gptopts){
bb270c08
         pts = os->codec->gptopts(s, i, gp);
3644cb8f
     } else {
1ed923ea
         pts = gp;
9146ca37
     }
 
     return pts;
 }
 
 
 static int
 ogg_get_length (AVFormatContext * s)
 {
77be08ee
     struct ogg *ogg = s->priv_data;
9146ca37
     int idx = -1, i;
bc5c918e
     int64_t size, end;
69599eea
 
ceeacce6
     if(url_is_streamed(s->pb))
69599eea
         return 0;
9146ca37
 
 // already set
     if (s->duration != AV_NOPTS_VALUE)
         return 0;
 
899681cd
     size = url_fsize(s->pb);
56466d7b
     if(size < 0)
         return 0;
     end = size > MAX_PAGE_SIZE? size - MAX_PAGE_SIZE: size;
 
9146ca37
     ogg_save (s);
899681cd
     url_fseek (s->pb, end, SEEK_SET);
9146ca37
 
     while (!ogg_read_page (s, &i)){
e22f2aaf
         if (ogg->streams[i].granule != -1 && ogg->streams[i].granule != 0 &&
             ogg->streams[i].codec)
9146ca37
             idx = i;
     }
 
     if (idx != -1){
         s->streams[idx]->duration =
             ogg_gptopts (s, idx, ogg->streams[idx].granule);
     }
 
56466d7b
     ogg->size = size;
9146ca37
     ogg_restore (s, 0);
 
     return 0;
 }
 
 
 static int
 ogg_read_header (AVFormatContext * s, AVFormatParameters * ap)
 {
77be08ee
     struct ogg *ogg = s->priv_data;
95f90d27
     int i;
9146ca37
     ogg->curidx = -1;
     //linear headers seek from start
     if (ogg_get_headers (s) < 0){
bad4a6bb
         return -1;
9146ca37
     }
 
95f90d27
     for (i = 0; i < ogg->nstreams; i++)
         if (ogg->streams[i].header < 0)
             ogg->streams[i].codec = NULL;
 
9146ca37
     //linear granulepos seek from end
     ogg_get_length (s);
 
     //fill the extradata in the per codec callbacks
     return 0;
 }
 
 
 static int
 ogg_read_packet (AVFormatContext * s, AVPacket * pkt)
 {
77be08ee
     struct ogg *ogg;
     struct ogg_stream *os;
9146ca37
     int idx = -1;
12a195e3
     int pstart, psize;
9146ca37
 
115329f1
     //Get an ogg packet
9146ca37
     do{
12a195e3
         if (ogg_packet (s, &idx, &pstart, &psize) < 0)
6f3e0b21
             return AVERROR(EIO);
9146ca37
     }while (idx < 0 || !s->streams[idx]);
 
     ogg = s->priv_data;
     os = ogg->streams + idx;
 
     //Alloc a pkt
12a195e3
     if (av_new_packet (pkt, psize) < 0)
6f3e0b21
         return AVERROR(EIO);
9146ca37
     pkt->stream_index = idx;
12a195e3
     memcpy (pkt->data, os->buf + pstart, psize);
9146ca37
     if (os->lastgp != -1LL){
         pkt->pts = ogg_gptopts (s, idx, os->lastgp);
         os->lastgp = -1;
     }
12a195e3
 
e1a794b2
     pkt->flags = os->pflags;
 
12a195e3
     return psize;
9146ca37
 }
 
 
 static int
 ogg_read_close (AVFormatContext * s)
 {
77be08ee
     struct ogg *ogg = s->priv_data;
9146ca37
     int i;
 
     for (i = 0; i < ogg->nstreams; i++){
         av_free (ogg->streams[i].buf);
1ed923ea
         av_free (ogg->streams[i].private);
9146ca37
     }
     av_free (ogg->streams);
     return 0;
 }
 
 
 static int64_t
 ogg_read_timestamp (AVFormatContext * s, int stream_index, int64_t * pos_arg,
                     int64_t pos_limit)
 {
77be08ee
     struct ogg *ogg = s->priv_data;
899681cd
     ByteIOContext *bc = s->pb;
a1f29b95
     int64_t pts = AV_NOPTS_VALUE;
     int i;
     url_fseek(bc, *pos_arg, SEEK_SET);
     while (url_ftell(bc) < pos_limit && !ogg_read_page (s, &i)) {
         if (ogg->streams[i].granule != -1 && ogg->streams[i].granule != 0 &&
             ogg->streams[i].codec && i == stream_index) {
             pts = ogg_gptopts(s, i, ogg->streams[i].granule);
             // FIXME: this is the position of the packet after the one with above
             // pts.
             *pos_arg = url_ftell(bc);
             break;
         }
     }
     ogg_reset(ogg);
     return pts;
9146ca37
 }
 
2e70e4aa
 static int ogg_probe(AVProbeData *p)
 {
     if (p->buf[0] == 'O' && p->buf[1] == 'g' &&
         p->buf[2] == 'g' && p->buf[3] == 'S' &&
         p->buf[4] == 0x0 && p->buf[5] <= 0x7 )
         return AVPROBE_SCORE_MAX;
     else
         return 0;
 }
 
ff70e601
 AVInputFormat ogg_demuxer = {
9146ca37
     "ogg",
bde15e74
     NULL_IF_CONFIG_SMALL("Ogg"),
77be08ee
     sizeof (struct ogg),
2e70e4aa
     ogg_probe,
9146ca37
     ogg_read_header,
     ogg_read_packet,
     ogg_read_close,
ce3132be
     NULL,
a1f29b95
     ogg_read_timestamp,
9146ca37
     .extensions = "ogg",
 };