diff options
Diffstat (limited to 'libavformat/oggdec.c')
-rw-r--r-- | libavformat/oggdec.c | 120 |
1 files changed, 104 insertions, 16 deletions
diff --git a/libavformat/oggdec.c b/libavformat/oggdec.c index e04a4e7..26aeb28 100644 --- a/libavformat/oggdec.c +++ b/libavformat/oggdec.c @@ -28,8 +28,8 @@ DEALINGS IN THE SOFTWARE. */ - #include <stdio.h> +#include "libavutil/avassert.h" #include "oggdec.h" #include "avformat.h" #include "internal.h" @@ -46,6 +46,7 @@ static const struct ogg_codec * const ogg_codecs[] = { &ff_theora_codec, &ff_flac_codec, &ff_celt_codec, + &ff_opus_codec, &ff_old_dirac_codec, &ff_old_flac_codec, &ff_ogm_video_codec, @@ -55,6 +56,8 @@ static const struct ogg_codec * const ogg_codecs[] = { NULL }; +static int64_t ogg_calc_pts(AVFormatContext *s, int idx, int64_t *dts); + //FIXME We could avoid some structure duplication static int ogg_save(AVFormatContext *s) { @@ -117,9 +120,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; @@ -134,6 +139,9 @@ static int ogg_reset(struct ogg *ogg) os->nsegs = 0; os->segp = 0; os->incomplete = 0; + if (start_pos <= s->data_offset) { + os->lastpts = 0; + } } ogg->curidx = -1; @@ -224,7 +232,7 @@ static int ogg_read_page(AVFormatContext *s, int *str) break; c = avio_r8(bc); - if (bc->eof_reached) + if (url_feof(bc)) return AVERROR_EOF; sync[sp++ & 3] = c; }while (i++ < MAX_PAGE_SIZE); @@ -234,8 +242,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); @@ -248,6 +258,11 @@ static int ogg_read_page(AVFormatContext *s, int *str) if (ogg->headers) { int n; + if (ogg->nstreams != 1) { + av_log_missing_feature(s, "Changing stream parameters in multistream ogg is", 0); + return idx; + } + 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) @@ -259,8 +274,10 @@ static int ogg_read_page(AVFormatContext *s, int *str) } else { idx = ogg_new_stream(s, serial, 1); } - if (idx < 0) + if (idx < 0) { + av_log (s, AV_LOG_ERROR, "failed to create stream (OOM?)\n"); return idx; + } } os = ogg->streams + idx; @@ -282,6 +299,9 @@ static int ogg_read_page(AVFormatContext *s, int *str) 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; @@ -317,6 +337,13 @@ static int ogg_read_page(AVFormatContext *s, int *str) return 0; } +/** + * @brief find the next Ogg packet + * @param *str 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 *str, int *dstart, int *dsize, int64_t *fpos) { @@ -327,6 +354,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 (str) + *str = -1; do{ idx = ogg->curidx; @@ -369,12 +398,14 @@ static int ogg_packet(AVFormatContext *s, int *str, int *dstart, int *dsize, if (!complete && os->segp == os->nsegs){ ogg->curidx = -1; - os->incomplete = 1; + // 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; } }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, "Page at %"PRId64" is missing granule\n", os->page_pos); @@ -424,6 +455,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; } @@ -463,6 +496,7 @@ 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; @@ -484,13 +518,37 @@ static int ogg_get_length(AVFormatContext *s) 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; } @@ -545,11 +603,24 @@ 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; + if (psize && s->streams[idx]->codec->codec_id == AV_CODEC_ID_THEORA) { + if (!!(os->pflags & AV_PKT_FLAG_KEY) != !(os->buf[pstart] & 0x40)) { + 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; @@ -566,6 +637,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; @@ -606,21 +678,32 @@ 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; 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; } @@ -631,6 +714,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 |