diff options
Diffstat (limited to 'libavformat/oggdec.c')
-rw-r--r-- | libavformat/oggdec.c | 287 |
1 files changed, 204 insertions, 83 deletions
diff --git a/libavformat/oggdec.c b/libavformat/oggdec.c index 175feb79e6..0fce8986b1 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,10 @@ 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; + } } ogg->curidx = -1; @@ -153,33 +162,81 @@ 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) { + struct ogg *ogg = s->priv_data; + struct ogg_stream *os; + unsigned bufsize; + uint8_t *buf; + + if (ogg->nstreams != 1) { + av_log_missing_feature(s, "Changing stream parameters in multistream ogg", 0); + return AVERROR_PATCHWELCOME; + } + + os = &ogg->streams[0]; + + buf = os->buf; + bufsize = os->bufsize; + + if (!ogg->state || ogg->state->streams[0].private != os->private) + av_freep(&ogg->streams[0].private); + /* 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; + + return 0; +} + +static int ogg_new_stream(AVFormatContext *s, uint32_t serial) +{ struct ogg *ogg = s->priv_data; - int idx = ogg->nstreams++; + 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; + } - ogg->streams = av_realloc (ogg->streams, - ogg->nstreams * sizeof (*ogg->streams)); - memset (ogg->streams + idx, 0, sizeof (*ogg->streams)); + /* 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 (new_avstream) { - st = avformat_new_stream(s, NULL); - if (!st) - return AVERROR(ENOMEM); - - st->id = idx; - avpriv_set_pts_info(st, 64, 1, 1000000); + if (!os->buf) + return AVERROR(ENOMEM); + + /* 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; } @@ -199,7 +256,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; @@ -225,7 +292,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); @@ -235,8 +302,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); @@ -246,22 +315,15 @@ static int ogg_read_page(AVFormatContext *s, int *str) idx = ogg_find_stream (ogg, serial); if (idx < 0){ - if (ogg->headers) { - int n; + if (data_packets_seen(ogg)) + idx = ogg_replace_stream(s, serial); + else + idx = ogg_new_stream(s, serial); - 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); - } - 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; @@ -281,8 +343,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; @@ -312,13 +380,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; @@ -328,6 +403,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; @@ -370,12 +447,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); @@ -416,8 +495,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) @@ -426,6 +505,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; } @@ -444,40 +525,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\n", i); - 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; @@ -499,13 +552,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; } @@ -513,20 +590,34 @@ 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) - return ret; + do { + ret = ogg_packet(s, NULL, NULL, NULL, NULL); + if (ret < 0) + 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, "Number of headers (%d) mismatch for stream %d\n", os->nb_header, i); + } + 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; } @@ -560,11 +651,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; @@ -581,6 +685,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; @@ -621,21 +726,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; } @@ -646,6 +762,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 |