This source file includes following definitions.
- ogg_save
- ogg_restore
- ogg_reset
- ogg_find_codec
- ogg_replace_stream
- ogg_new_stream
- ogg_new_buf
- data_packets_seen
- ogg_read_page
- ogg_packet
- ogg_get_length
- ogg_read_close
- ogg_read_header
- ogg_calc_pts
- ogg_validate_keyframe
- ogg_read_packet
- ogg_read_timestamp
- ogg_read_seek
- ogg_probe
#include <stdio.h>
#include "libavutil/avassert.h"
#include "libavutil/intreadwrite.h"
#include "oggdec.h"
#include "avformat.h"
#include "internal.h"
#include "vorbiscomment.h"
#define MAX_PAGE_SIZE 65307
#define DECODER_BUFFER_SIZE MAX_PAGE_SIZE
static const struct ogg_codec * const ogg_codecs[] = {
&ff_skeleton_codec,
&ff_dirac_codec,
&ff_speex_codec,
&ff_vorbis_codec,
&ff_theora_codec,
&ff_flac_codec,
&ff_celt_codec,
&ff_opus_codec,
&ff_vp8_codec,
&ff_old_dirac_codec,
&ff_old_flac_codec,
&ff_ogm_video_codec,
&ff_ogm_audio_codec,
&ff_ogm_text_codec,
&ff_ogm_old_codec,
NULL
};
static int64_t ogg_calc_pts(AVFormatContext *s, int idx, int64_t *dts);
static int ogg_new_stream(AVFormatContext *s, uint32_t serial);
static int ogg_save(AVFormatContext *s)
{
struct ogg *ogg = s->priv_data;
struct ogg_state *ost =
av_malloc(sizeof(*ost) + (ogg->nstreams - 1) * sizeof(*ogg->streams));
int i;
if (!ost)
return AVERROR(ENOMEM);
ost->pos = avio_tell(s->pb);
ost->curidx = ogg->curidx;
ost->next = ogg->state;
ost->nstreams = ogg->nstreams;
memcpy(ost->streams, ogg->streams, ogg->nstreams * sizeof(*ogg->streams));
for (i = 0; i < ogg->nstreams; i++) {
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;
return 0;
}
static int ogg_restore(AVFormatContext *s, int discard)
{
struct ogg *ogg = s->priv_data;
AVIOContext *bc = s->pb;
struct ogg_state *ost = ogg->state;
int i, err;
if (!ost)
return 0;
ogg->state = ost->next;
if (!discard) {
for (i = 0; i < ogg->nstreams; i++)
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,
sizeof(*ogg->streams))) < 0) {
ogg->nstreams = 0;
return err;
} else
memcpy(ogg->streams, ost->streams,
ost->nstreams * sizeof(*ogg->streams));
}
av_free(ost);
return 0;
}
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;
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;
os->incomplete = 0;
os->got_data = 0;
if (start_pos <= s->internal->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;
}
static const struct ogg_codec *ogg_find_codec(uint8_t *buf, int size)
{
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 ogg_replace_stream(AVFormatContext *s, uint32_t serial, int nsegs)
{
struct ogg *ogg = s->priv_data;
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 = &ogg->streams[i];
os->serial = serial;
return i;
#if 0
buf = os->buf;
bufsize = os->bufsize;
codec = os->codec;
if (!ogg->state || ogg->state->streams[i].private != os->private)
av_freep(&ogg->streams[i].private);
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;
}
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);
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;
}
static int ogg_new_buf(struct ogg *ogg, int idx)
{
struct ogg_stream *os = ogg->streams + idx;
uint8_t *nb = av_malloc(os->bufsize + FF_INPUT_BUFFER_PADDING_SIZE);
int size = os->bufpos - os->pstart;
if (!nb)
return AVERROR(ENOMEM);
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 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;
struct ogg_stream *os;
int ret, i = 0;
int flags, nsegs;
uint64_t gp;
uint32_t serial;
int size, idx;
uint8_t sync[4];
int sp = 0;
ret = avio_read(bc, sync, 4);
if (ret < 4)
return ret < 0 ? ret : AVERROR_EOF;
do {
int c;
if (sync[sp & 3] == 'O' &&
sync[(sp + 1) & 3] == 'g' &&
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 (avio_feof(bc))
return AVERROR_EOF;
sync[sp++ & 3] = c;
} while (i++ < MAX_PAGE_SIZE);
if (i >= MAX_PAGE_SIZE) {
av_log(s, AV_LOG_INFO, "cannot find sync word\n");
return AVERROR_INVALIDDATA;
}
if (avio_r8(bc) != 0) {
av_log (s, AV_LOG_ERROR, "ogg page, unsupported version\n");
return AVERROR_INVALIDDATA;
}
flags = avio_r8(bc);
gp = avio_rl64(bc);
serial = avio_rl32(bc);
avio_skip(bc, 8);
nsegs = avio_r8(bc);
idx = ogg_find_stream(ogg, serial);
if (idx < 0) {
if (data_packets_seen(ogg))
idx = ogg_replace_stream(s, serial, nsegs);
else
idx = ogg_new_stream(s, serial);
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) {
ret = ogg_new_buf(ogg, idx);
if (ret < 0)
return ret;
}
ret = avio_read(bc, os->segments, nsegs);
if (ret < nsegs)
return ret < 0 ? ret : AVERROR_EOF;
os->nsegs = nsegs;
os->segp = 0;
size = 0;
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) {
while (os->segp < os->nsegs) {
int seg = os->segments[os->segp++];
os->pstart += seg;
if (seg < 255)
break;
}
os->sync_pos = os->page_pos;
}
} else {
os->psize = 0;
os->sync_pos = os->page_pos;
}
if (os->bufsize - os->bufpos < size) {
uint8_t *nb = av_malloc((os->bufsize *= 2) + FF_INPUT_BUFFER_PADDING_SIZE);
if (!nb)
return AVERROR(ENOMEM);
memcpy(nb, os->buf, os->bufpos);
av_free(os->buf);
os->buf = nb;
}
ret = avio_read(bc, os->buf + os->bufpos, size);
if (ret < size)
return ret < 0 ? ret : AVERROR_EOF;
os->bufpos += size;
os->granule = gp;
os->flags = flags;
memset(os->buf + os->bufpos, 0, FF_INPUT_BUFFER_PADDING_SIZE);
if (sid)
*sid = idx;
return 0;
}
static int ogg_packet(AVFormatContext *s, int *sid, int *dstart, int *dsize,
int64_t *fpos)
{
struct ogg *ogg = s->priv_data;
int idx, i, ret;
struct ogg_stream *os;
int complete = 0;
int segp = 0, psize = 0;
av_dlog(s, "ogg_packet: curidx=%i\n", ogg->curidx);
if (sid)
*sid = -1;
do {
idx = ogg->curidx;
while (idx < 0) {
ret = ogg_read_page(s, &idx);
if (ret < 0)
return ret;
}
os = ogg->streams + idx;
av_dlog(s, "ogg_packet: idx=%d pstart=%d psize=%d segp=%d nsegs=%d\n",
idx, os->pstart, os->psize, os->segp, os->nsegs);
if (!os->codec) {
if (os->header < 0) {
os->codec = ogg_find_codec(os->buf, os->bufpos);
if (!os->codec) {
av_log(s, AV_LOG_WARNING, "Codec not found\n");
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;
os->incomplete = !!os->psize;
}
} while (!complete);
if (os->granule == -1)
av_log(s, AV_LOG_WARNING,
"Page at %"PRId64" is missing granule\n",
os->page_pos);
ogg->curidx = idx;
os->incomplete = 0;
if (os->header) {
os->header = os->codec->header(s, idx);
if (!os->header) {
os->segp = segp;
os->psize = psize;
ogg->headers = 1;
if (!s->internal->data_offset)
s->internal->data_offset = os->sync_pos;
for (i = 0; i < ogg->nstreams; i++) {
struct ogg_stream *cur_os = ogg->streams + i;
if (cur_os->incomplete)
s->internal->data_offset = FFMIN(s->internal->data_offset, cur_os->sync_pos);
}
} else {
os->nb_header++;
os->pstart += os->psize;
os->psize = 0;
}
} else {
os->pflags = 0;
os->pduration = 0;
if (os->codec && os->codec->packet)
os->codec->packet(s, idx);
if (sid)
*sid = idx;
if (dstart)
*dstart = os->pstart;
if (dsize)
*dsize = os->psize;
if (fpos)
*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;
}
os->page_end = 1;
for (i = os->segp; i < os->nsegs; i++)
if (os->segments[i] < 255) {
os->page_end = 0;
break;
}
if (os->segp == os->nsegs)
ogg->curidx = -1;
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;
int ret;
if (!s->pb->seekable)
return 0;
if (s->duration != AV_NOPTS_VALUE)
return 0;
size = avio_size(s->pb);
if (size < 0)
return 0;
end = size > MAX_PAGE_SIZE ? size - MAX_PAGE_SIZE : 0;
ret = ogg_save(s);
if (ret < 0)
return ret;
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) {
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);
ret = ogg_save(s);
if (ret < 0)
return ret;
avio_seek (s->pb, s->internal->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;
}
static int ogg_read_close(AVFormatContext *s)
{
struct ogg *ogg = s->priv_data;
int i;
for (i = 0; i < ogg->nstreams; i++) {
av_freep(&ogg->streams[i].buf);
if (ogg->streams[i].codec &&
ogg->streams[i].codec->cleanup) {
ogg->streams[i].codec->cleanup(s, i);
}
av_freep(&ogg->streams[i].private);
av_freep(&ogg->streams[i].new_metadata);
}
ogg->nstreams = 0;
av_freep(&ogg->streams);
return 0;
}
static int ogg_read_header(AVFormatContext *s)
{
struct ogg *ogg = s->priv_data;
int ret, i;
ogg->curidx = -1;
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++) {
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);
}
ogg_get_length(s);
return 0;
}
static int64_t ogg_calc_pts(AVFormatContext *s, int idx, int64_t *dts)
{
struct ogg *ogg = s->priv_data;
struct ogg_stream *os = ogg->streams + idx;
int64_t pts = AV_NOPTS_VALUE;
if (dts)
*dts = AV_NOPTS_VALUE;
if (os->lastpts != AV_NOPTS_VALUE) {
pts = os->lastpts;
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;
}
}
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, ret;
int pstart, psize;
int64_t fpos, pts, dts;
if (s->io_repositioned) {
ogg_reset(s);
s->io_repositioned = 0;
}
retry:
do {
ret = ogg_packet(s, &idx, &pstart, &psize, &fpos);
if (ret < 0)
return ret;
} while (idx < 0 || !s->streams[idx]);
ogg = s->priv_data;
os = ogg->streams + idx;
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;
os->keyframe_seek = 0;
ret = av_new_packet(pkt, psize);
if (ret < 0)
return ret;
pkt->stream_index = idx;
memcpy(pkt->data, os->buf + pstart, psize);
pkt->pts = pts;
pkt->dts = dts;
pkt->flags = os->pflags;
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,
int64_t *pos_arg, int64_t pos_limit)
{
struct ogg *ogg = s->priv_data;
AVIOContext *bc = s->pb;
int64_t pts = AV_NOPTS_VALUE;
int64_t keypos = -1;
int i;
int pstart, psize;
avio_seek(bc, *pos_arg, SEEK_SET);
ogg_reset(s);
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;
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);
ogg_validate_keyframe(s, i, pstart, psize);
if (os->pflags & AV_PKT_FLAG_KEY) {
keypos = *pos_arg;
} else if (os->keyframe_seek) {
if (keypos >= 0)
*pos_arg = keypos;
else
pts = AV_NOPTS_VALUE;
}
}
if (pts != AV_NOPTS_VALUE)
break;
}
ogg_reset(s);
return pts;
}
static int ogg_read_seek(AVFormatContext *s, int stream_index,
int64_t timestamp, int flags)
{
struct ogg *ogg = s->priv_data;
struct ogg_stream *os = ogg->streams + stream_index;
int ret;
av_assert0(stream_index < ogg->nstreams);
ogg_reset(s);
if (s->streams[stream_index]->codec->codec_type == AVMEDIA_TYPE_VIDEO
&& !(flags & AVSEEK_FLAG_ANY))
os->keyframe_seek = 1;
ret = ff_seek_frame_binary(s, stream_index, timestamp, flags);
os = ogg->streams + stream_index;
if (ret < 0)
os->keyframe_seek = 0;
return ret;
}
static int ogg_probe(AVProbeData *p)
{
if (!memcmp("OggS", p->buf, 5) && p->buf[5] <= 0x7)
return AVPROBE_SCORE_MAX;
return 0;
}
AVInputFormat ff_ogg_demuxer = {
.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",
.flags = AVFMT_GENERIC_INDEX | AVFMT_TS_DISCONT | AVFMT_NOBINSEARCH,
};