summaryrefslogtreecommitdiffstats
path: root/libavformat/oggdec.c
diff options
context:
space:
mode:
Diffstat (limited to 'libavformat/oggdec.c')
-rw-r--r--libavformat/oggdec.c397
1 files changed, 298 insertions, 99 deletions
diff --git a/libavformat/oggdec.c b/libavformat/oggdec.c
index ae9da3a..8f146e4 100644
--- a/libavformat/oggdec.c
+++ b/libavformat/oggdec.c
@@ -28,8 +28,9 @@
DEALINGS IN THE SOFTWARE.
*/
-
#include <stdio.h>
+#include "libavutil/avassert.h"
+#include "libavutil/intreadwrite.h"
#include "oggdec.h"
#include "avformat.h"
#include "internal.h"
@@ -47,6 +48,7 @@ static const struct ogg_codec * const ogg_codecs[] = {
&ff_flac_codec,
&ff_celt_codec,
&ff_opus_codec,
+ &ff_vp8_codec,
&ff_old_dirac_codec,
&ff_old_flac_codec,
&ff_ogm_video_codec,
@@ -56,6 +58,9 @@ static const struct ogg_codec * const ogg_codecs[] = {
NULL
};
+static int64_t ogg_calc_pts(AVFormatContext *s, int idx, int64_t *dts);
+static int ogg_new_stream(AVFormatContext *s, uint32_t serial);
+
//FIXME We could avoid some structure duplication
static int ogg_save(AVFormatContext *s)
{
@@ -73,6 +78,8 @@ static int ogg_save(AVFormatContext *s)
struct ogg_stream *os = ogg->streams + i;
os->buf = av_mallocz(os->bufsize + FF_INPUT_BUFFER_PADDING_SIZE);
memcpy(os->buf, ost->streams[i].buf, os->bufpos);
+ os->new_metadata = NULL;
+ os->new_metadata_size = 0;
}
ogg->state = ost;
@@ -95,9 +102,10 @@ static int ogg_restore(AVFormatContext *s, int discard)
if (!discard) {
for (i = 0; i < ogg->nstreams; i++)
- av_free(ogg->streams[i].buf);
+ av_freep(&ogg->streams[i].buf);
avio_seek(bc, ost->pos, SEEK_SET);
+ ogg->page_pos = -1;
ogg->curidx = ost->curidx;
ogg->nstreams = ost->nstreams;
if ((err = av_reallocp_array(&ogg->streams, ogg->nstreams,
@@ -114,9 +122,11 @@ static int ogg_restore(AVFormatContext *s, int discard)
return 0;
}
-static int ogg_reset(struct ogg *ogg)
+static int ogg_reset(AVFormatContext *s)
{
+ struct ogg *ogg = s->priv_data;
int i;
+ int64_t start_pos = avio_tell(s->pb);
for (i = 0; i < ogg->nstreams; i++) {
struct ogg_stream *os = ogg->streams + i;
@@ -131,8 +141,16 @@ static int ogg_reset(struct ogg *ogg)
os->nsegs = 0;
os->segp = 0;
os->incomplete = 0;
+ os->got_data = 0;
+ if (start_pos <= s->data_offset) {
+ os->lastpts = 0;
+ }
+ os->end_trimming = 0;
+ av_freep(&os->new_metadata);
+ os->new_metadata_size = 0;
}
+ ogg->page_pos = -1;
ogg->curidx = -1;
return 0;
@@ -150,38 +168,105 @@ static const struct ogg_codec *ogg_find_codec(uint8_t *buf, int size)
return NULL;
}
-static int ogg_new_stream(AVFormatContext *s, uint32_t serial, int new_avstream)
+/**
+ * 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).
+ */
+static int ogg_replace_stream(AVFormatContext *s, uint32_t serial, int nsegs)
{
struct ogg *ogg = s->priv_data;
- int idx = ogg->nstreams++;
- AVStream *st;
struct ogg_stream *os;
+ const struct ogg_codec *codec;
+ int i = 0;
+
+ if (s->pb->seekable) {
+ 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) {
+ avpriv_report_missing_feature(s, "Changing stream parameters in multistream ogg");
+ return AVERROR_PATCHWELCOME;
+ }
- os = av_realloc(ogg->streams, ogg->nstreams * sizeof(*ogg->streams));
+ os = &ogg->streams[i];
- if (!os)
- return AVERROR(ENOMEM);
+ os->serial = serial;
+ return i;
- ogg->streams = os;
+#if 0
+ buf = os->buf;
+ bufsize = os->bufsize;
+ codec = os->codec;
- memset(ogg->streams + idx, 0, sizeof(*ogg->streams));
+ if (!ogg->state || ogg->state->streams[i].private != os->private)
+ av_freep(&ogg->streams[i].private);
- os = ogg->streams + idx;
+ /* 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));
+ os->serial = serial;
+ os->bufsize = bufsize;
+ os->buf = buf;
+ os->header = -1;
+ os->codec = codec;
+
+ return i;
+#endif
+}
+
+static int ogg_new_stream(AVFormatContext *s, uint32_t serial)
+{
+ struct ogg *ogg = s->priv_data;
+ int idx = ogg->nstreams;
+ AVStream *st;
+ struct ogg_stream *os;
+ size_t size;
+
+ 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;
+ }
+
+ /* Allocate and init a new Ogg Stream */
+ if (av_size_mult(ogg->nstreams + 1, sizeof(*ogg->streams), &size) < 0 ||
+ !(os = av_realloc(ogg->streams, size)))
+ return AVERROR(ENOMEM);
+ ogg->streams = os;
+ os = ogg->streams + idx;
+ memset(os, 0, sizeof(*os));
os->serial = serial;
os->bufsize = DECODER_BUFFER_SIZE;
os->buf = av_malloc(os->bufsize + FF_INPUT_BUFFER_PADDING_SIZE);
os->header = -1;
os->start_granule = OGG_NOGRANULE_VALUE;
+ if (!os->buf)
+ return AVERROR(ENOMEM);
- if (new_avstream) {
- st = avformat_new_stream(s, NULL);
- if (!st)
- return AVERROR(ENOMEM);
-
- st->id = idx;
- avpriv_set_pts_info(st, 64, 1, 1000000);
+ /* 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);
+ ogg->nstreams++;
return idx;
}
@@ -203,7 +288,17 @@ static int ogg_new_buf(struct ogg *ogg, int idx)
return 0;
}
-static int ogg_read_page(AVFormatContext *s, int *str)
+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;
+}
+
+static int ogg_read_page(AVFormatContext *s, int *sid)
{
AVIOContext *bc = s->pb;
struct ogg *ogg = s->priv_data;
@@ -228,9 +323,15 @@ static int ogg_read_page(AVFormatContext *s, int *str)
sync[(sp + 2) & 3] == 'g' && sync[(sp + 3) & 3] == 'S')
break;
+ if(!i && bc->seekable && ogg->page_pos > 0) {
+ memset(sync, 0, 4);
+ avio_seek(bc, ogg->page_pos+4, SEEK_SET);
+ ogg->page_pos = -1;
+ }
+
c = avio_r8(bc);
- if (bc->eof_reached)
+ if (avio_feof(bc))
return AVERROR_EOF;
sync[sp++ & 3] = c;
@@ -241,8 +342,10 @@ static int ogg_read_page(AVFormatContext *s, int *str)
return AVERROR_INVALIDDATA;
}
- if (avio_r8(bc) != 0) /* version */
+ if (avio_r8(bc) != 0) { /* version */
+ av_log (s, AV_LOG_ERROR, "ogg page, unsupported version\n");
return AVERROR_INVALIDDATA;
+ }
flags = avio_r8(bc);
gp = avio_rl64(bc);
@@ -252,28 +355,19 @@ static int ogg_read_page(AVFormatContext *s, int *str)
idx = ogg_find_stream(ogg, serial);
if (idx < 0) {
- if (ogg->headers) {
- int n;
-
- for (n = 0; n < ogg->nstreams; n++) {
- av_freep(&ogg->streams[n].buf);
- if (!ogg->state ||
- ogg->state->streams[n].private != ogg->streams[n].private)
- av_freep(&ogg->streams[n].private);
- }
+ if (data_packets_seen(ogg))
+ idx = ogg_replace_stream(s, serial, nsegs);
+ else
+ idx = ogg_new_stream(s, serial);
- ogg->curidx = -1;
- ogg->nstreams = 0;
-
- idx = ogg_new_stream(s, serial, 0);
- } else {
- idx = ogg_new_stream(s, serial, 1);
- }
- if (idx < 0)
+ if (idx < 0) {
+ av_log(s, AV_LOG_ERROR, "failed to create or replace stream\n");
return idx;
+ }
}
os = ogg->streams + idx;
+ ogg->page_pos =
os->page_pos = avio_tell(bc) - 27;
if (os->psize > 0)
@@ -290,8 +384,14 @@ static int ogg_read_page(AVFormatContext *s, int *str)
for (i = 0; i < nsegs; i++)
size += os->segments[i];
+ if (!(flags & OGG_FLAG_BOS))
+ os->got_data = 1;
+
if (flags & OGG_FLAG_CONT || os->incomplete) {
if (!os->psize) {
+ // 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.
while (os->segp < os->nsegs) {
int seg = os->segments[os->segp++];
os->pstart += seg;
@@ -323,13 +423,20 @@ static int ogg_read_page(AVFormatContext *s, int *str)
os->flags = flags;
memset(os->buf + os->bufpos, 0, FF_INPUT_BUFFER_PADDING_SIZE);
- if (str)
- *str = idx;
+ if (sid)
+ *sid = idx;
return 0;
}
-static int ogg_packet(AVFormatContext *s, int *str, int *dstart, int *dsize,
+/**
+ * @brief find the next Ogg packet
+ * @param *sid is set to the stream for the packet or -1 if there is
+ * no matching stream, in that case assume all other return
+ * values to be uninitialized.
+ * @return negative value on error or EOF.
+ */
+static int ogg_packet(AVFormatContext *s, int *sid, int *dstart, int *dsize,
int64_t *fpos)
{
struct ogg *ogg = s->priv_data;
@@ -339,6 +446,8 @@ static int ogg_packet(AVFormatContext *s, int *str, int *dstart, int *dsize,
int segp = 0, psize = 0;
av_dlog(s, "ogg_packet: curidx=%i\n", ogg->curidx);
+ if (sid)
+ *sid = -1;
do {
idx = ogg->curidx;
@@ -389,8 +498,6 @@ static int ogg_packet(AVFormatContext *s, int *str, int *dstart, int *dsize,
}
} while (!complete);
- av_dlog(s, "ogg_packet: idx %i, frame size %i, start %i\n",
- idx, os->psize, os->pstart);
if (os->granule == -1)
av_log(s, AV_LOG_WARNING,
@@ -434,8 +541,8 @@ static int ogg_packet(AVFormatContext *s, int *str, int *dstart, int *dsize,
os->pduration = 0;
if (os->codec && os->codec->packet)
os->codec->packet(s, idx);
- if (str)
- *str = idx;
+ if (sid)
+ *sid = idx;
if (dstart)
*dstart = os->pstart;
if (dsize)
@@ -444,6 +551,8 @@ static int ogg_packet(AVFormatContext *s, int *str, int *dstart, int *dsize,
*fpos = os->sync_pos;
os->pstart += os->psize;
os->psize = 0;
+ if(os->pstart == os->bufpos)
+ os->bufpos = os->pstart = 0;
os->sync_pos = os->page_pos;
}
@@ -462,43 +571,12 @@ static int ogg_packet(AVFormatContext *s, int *str, int *dstart, int *dsize,
return 0;
}
-static int ogg_get_headers(AVFormatContext *s)
-{
- struct ogg *ogg = s->priv_data;
- int ret, i;
-
- do {
- ret = ogg_packet(s, NULL, NULL, NULL, NULL);
- if (ret < 0)
- return ret;
- } while (!ogg->headers);
-
- for (i = 0; i < ogg->nstreams; i++) {
- struct ogg_stream *os = ogg->streams + i;
-
- if (os->codec && os->codec->nb_header &&
- os->nb_header < os->codec->nb_header) {
- av_log(s, AV_LOG_ERROR,
- "Headers mismatch for stream %d: "
- "expected %d received %d.\n",
- i, os->codec->nb_header, os->nb_header);
- if (s->error_recognition & AV_EF_EXPLODE)
- return AVERROR_INVALIDDATA;
- }
- if (os->start_granule != OGG_NOGRANULE_VALUE)
- os->lastpts = s->streams[i]->start_time =
- ogg_gptopts(s, i, os->start_granule, NULL);
- }
- av_dlog(s, "found headers\n");
-
- return 0;
-}
-
static int ogg_get_length(AVFormatContext *s)
{
struct ogg *ogg = s->priv_data;
int i;
int64_t size, end;
+ int streams_left=0;
if (!s->pb->seekable)
return 0;
@@ -514,19 +592,44 @@ static int ogg_get_length(AVFormatContext *s)
ogg_save(s);
avio_seek(s->pb, end, SEEK_SET);
+ ogg->page_pos = -1;
while (!ogg_read_page(s, &i)) {
if (ogg->streams[i].granule != -1 && ogg->streams[i].granule != 0 &&
ogg->streams[i].codec) {
s->streams[i]->duration =
ogg_gptopts(s, i, ogg->streams[i].granule, NULL);
- if (s->streams[i]->start_time != AV_NOPTS_VALUE)
+ if (s->streams[i]->start_time != AV_NOPTS_VALUE) {
s->streams[i]->duration -= s->streams[i]->start_time;
+ streams_left-= (ogg->streams[i].got_start==-1);
+ ogg->streams[i].got_start= 1;
+ } else if(!ogg->streams[i].got_start) {
+ ogg->streams[i].got_start= -1;
+ streams_left++;
+ }
}
}
ogg_restore(s, 0);
+ ogg_save (s);
+ avio_seek (s->pb, s->data_offset, SEEK_SET);
+ ogg_reset(s);
+ 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);
+ if (pts != AV_NOPTS_VALUE && s->streams[i]->start_time == AV_NOPTS_VALUE && !ogg->streams[i].got_start) {
+ s->streams[i]->duration -= pts;
+ ogg->streams[i].got_start= 1;
+ streams_left--;
+ }else if(s->streams[i]->start_time != AV_NOPTS_VALUE && !ogg->streams[i].got_start) {
+ ogg->streams[i].got_start= 1;
+ streams_left--;
+ }
+ }
+ ogg_restore (s, 0);
+
return 0;
}
@@ -536,14 +639,18 @@ static int ogg_read_close(AVFormatContext *s)
int i;
for (i = 0; i < ogg->nstreams; i++) {
- av_free(ogg->streams[i].buf);
+ av_freep(&ogg->streams[i].buf);
if (ogg->streams[i].codec &&
ogg->streams[i].codec->cleanup) {
ogg->streams[i].codec->cleanup(s, i);
}
- av_free(ogg->streams[i].private);
+ av_freep(&ogg->streams[i].private);
+ av_freep(&ogg->streams[i].new_metadata);
}
- av_free(ogg->streams);
+
+ ogg->nstreams = 0;
+
+ av_freep(&ogg->streams);
return 0;
}
@@ -551,22 +658,41 @@ static int ogg_read_header(AVFormatContext *s)
{
struct ogg *ogg = s->priv_data;
int ret, i;
+
ogg->curidx = -1;
+
//linear headers seek from start
- ret = ogg_get_headers(s);
- if (ret < 0) {
- ogg_read_close(s);
- return ret;
- }
+ do {
+ ret = ogg_packet(s, NULL, NULL, NULL, NULL);
+ if (ret < 0) {
+ ogg_read_close(s);
+ return ret;
+ }
+ } while (!ogg->headers);
+ av_dlog(s, "found headers\n");
- for (i = 0; i < ogg->nstreams; i++)
- if (ogg->streams[i].header < 0)
+ for (i = 0; i < ogg->nstreams; i++) {
+ struct ogg_stream *os = ogg->streams + i;
+
+ if (ogg->streams[i].header < 0) {
+ av_log(s, AV_LOG_ERROR, "Header parsing failed for stream %d\n", i);
ogg->streams[i].codec = NULL;
+ } else if (os->codec && os->nb_header < os->codec->nb_header) {
+ av_log(s, AV_LOG_WARNING,
+ "Headers mismatch for stream %d: "
+ "expected %d received %d.\n",
+ i, os->codec->nb_header, os->nb_header);
+ if (s->error_recognition & AV_EF_EXPLODE)
+ return AVERROR_INVALIDDATA;
+ }
+ if (os->start_granule != OGG_NOGRANULE_VALUE)
+ os->lastpts = s->streams[i]->start_time =
+ ogg_gptopts(s, i, os->start_granule, NULL);
+ }
//linear granulepos seek from end
ogg_get_length(s);
- //fill the extradata in the per codec callbacks
return 0;
}
@@ -600,14 +726,40 @@ static int64_t ogg_calc_pts(AVFormatContext *s, int idx, int64_t *dts)
return pts;
}
+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;
+ int invalid = 0;
+ if (psize) {
+ switch (s->streams[idx]->codec->codec_id) {
+ 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) {
+ os->pflags ^= AV_PKT_FLAG_KEY;
+ av_log(s, AV_LOG_WARNING, "Broken file, %skeyframe not correctly marked.\n",
+ (os->pflags & AV_PKT_FLAG_KEY) ? "" : "non-");
+ }
+ }
+}
+
static int ogg_read_packet(AVFormatContext *s, AVPacket *pkt)
{
struct ogg *ogg;
struct ogg_stream *os;
- int idx = -1, ret;
+ int idx, ret;
int pstart, psize;
int64_t fpos, pts, dts;
+ if (s->io_repositioned) {
+ ogg_reset(s);
+ s->io_repositioned = 0;
+ }
+
//Get an ogg packet
retry:
do {
@@ -621,6 +773,7 @@ retry:
// pflags might not be set until after this
pts = ogg_calc_pts(s, idx, &dts);
+ ogg_validate_keyframe(s, idx, pstart, psize);
if (os->keyframe_seek && !(os->pflags & AV_PKT_FLAG_KEY))
goto retry;
@@ -639,7 +792,32 @@ retry:
pkt->duration = os->pduration;
pkt->pos = fpos;
+ if (os->end_trimming) {
+ uint8_t *side_data = av_packet_new_side_data(pkt,
+ AV_PKT_DATA_SKIP_SAMPLES,
+ 10);
+ if(!side_data)
+ goto fail;
+ AV_WL32(side_data + 4, os->end_trimming);
+ os->end_trimming = 0;
+ }
+
+ if (os->new_metadata) {
+ uint8_t *side_data = av_packet_new_side_data(pkt,
+ AV_PKT_DATA_METADATA_UPDATE,
+ os->new_metadata_size);
+ if(!side_data)
+ goto fail;
+
+ memcpy(side_data, os->new_metadata, os->new_metadata_size);
+ av_freep(&os->new_metadata);
+ os->new_metadata_size = 0;
+ }
+
return psize;
+fail:
+ av_free_packet(pkt);
+ return AVERROR(ENOMEM);
}
static int64_t ogg_read_timestamp(AVFormatContext *s, int stream_index,
@@ -648,22 +826,38 @@ static int64_t ogg_read_timestamp(AVFormatContext *s, int stream_index,
struct ogg *ogg = s->priv_data;
AVIOContext *bc = s->pb;
int64_t pts = AV_NOPTS_VALUE;
- int i = -1;
+ int64_t keypos = -1;
+ int i;
+ int pstart, psize;
avio_seek(bc, *pos_arg, SEEK_SET);
- ogg_reset(ogg);
+ ogg_reset(s);
- while (avio_tell(bc) < pos_limit &&
- !ogg_packet(s, &i, NULL, NULL, pos_arg)) {
+ while ( avio_tell(bc) <= pos_limit
+ && !ogg_packet(s, &i, &pstart, &psize, pos_arg)) {
if (i == stream_index) {
struct ogg_stream *os = ogg->streams + stream_index;
+ // Do not trust the last timestamps of a ogm video
+ if ( (os->flags & OGG_FLAG_EOS)
+ && !(os->flags & OGG_FLAG_BOS)
+ && os->codec == &ff_ogm_video_codec)
+ continue;
pts = ogg_calc_pts(s, i, NULL);
- if (os->keyframe_seek && !(os->pflags & AV_PKT_FLAG_KEY))
- pts = AV_NOPTS_VALUE;
+ ogg_validate_keyframe(s, i, pstart, psize);
+ 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;
+ }
}
if (pts != AV_NOPTS_VALUE)
break;
}
- ogg_reset(ogg);
+ ogg_reset(s);
return pts;
}
@@ -674,6 +868,11 @@ static int ogg_read_seek(AVFormatContext *s, int stream_index,
struct ogg_stream *os = ogg->streams + stream_index;
int ret;
+ av_assert0(stream_index < ogg->nstreams);
+ // Ensure everything is reset even when seeking via
+ // the generated index.
+ ogg_reset(s);
+
// Try seeking to a keyframe first. If this fails (very possible),
// av_seek_frame will fall back to ignoring keyframes
if (s->streams[stream_index]->codec->codec_type == AVMEDIA_TYPE_VIDEO
@@ -705,5 +904,5 @@ AVInputFormat ff_ogg_demuxer = {
.read_seek = ogg_read_seek,
.read_timestamp = ogg_read_timestamp,
.extensions = "ogg",
- .flags = AVFMT_GENERIC_INDEX | AVFMT_NOBINSEARCH,
+ .flags = AVFMT_GENERIC_INDEX | AVFMT_TS_DISCONT | AVFMT_NOBINSEARCH,
};
OpenPOWER on IntegriCloud