Skip to content

Commit

Permalink
lavf: Provide a monotonic timestamp to the outside world
Browse files Browse the repository at this point in the history
This detects and removes single timestamp wraps, which fixes seeking for affected video files.
  • Loading branch information
axmhari committed Dec 16, 2012
1 parent 99e0ce5 commit b2d3b83
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 10 deletions.
37 changes: 37 additions & 0 deletions lib/ffmpeg/libavformat/avformat.h
Expand Up @@ -627,6 +627,13 @@ typedef struct AVIndexEntry {
#define AV_DISPOSITION_VISUAL_IMPAIRED 0x0100 /**< stream for visual impaired audiences */
#define AV_DISPOSITION_CLEAN_EFFECTS 0x0200 /**< stream without voice */

/*
* Options for behavior on timestamp wrap detection.
*/
#define AV_PTS_WRAP_IGNORE 0 ///< ignore the wrap
#define AV_PTS_WRAP_ADD_OFFSET 1 ///< add the format specific offset on wrap detection
#define AV_PTS_WRAP_SUB_OFFSET -1 ///< subtract the format specific offset on wrap detection

/**
* Stream structure.
* New fields can be added to the end with minor version bumps.
Expand Down Expand Up @@ -857,6 +864,24 @@ typedef struct AVStream {

int pts_wrap_bits; /**< number of bits in pts (used for wrapping control) */
#endif

/**
* Internal data to check for wrapping of the time stamp
*/
int64_t pts_wrap_reference;

/**
* Options for behavior, when a wrap is detected.
*
* Defined by AV_PTS_WRAP_ values.
*
* If correction is enabled, there are two possibilities:
* If the first time stamp is near the wrap point, the wrap offset
* will be subtracted, which will create negative time stamps.
* Otherwise the offset will be added.
*/
int pts_wrap_behavior;

} AVStream;

#define AV_PROGRAM_RUNNING 1
Expand All @@ -878,6 +903,10 @@ typedef struct AVProgram {
int program_num;
int pmt_pid;
int pcr_pid;

int64_t pts_wrap_reference; ///< reference dts for wrap detection
int pts_wrap_behavior; ///< behavior on wrap detection

} AVProgram;

#define AVFMTCTX_NOHEADER 0x0001 /**< signal that no header is present
Expand Down Expand Up @@ -1234,6 +1263,14 @@ typedef struct AVFormatContext {
/* av_seek_frame() support */
int64_t data_offset; /**< offset of the first packet */
#endif

/**
* Correct single timestamp overflows
* - encoding: unused
* - decoding: Set by user via AVOPtions (NO direct access)
*/
unsigned int correct_ts_overflow;

} AVFormatContext;

typedef struct AVPacketList {
Expand Down
1 change: 1 addition & 0 deletions lib/ffmpeg/libavformat/options.c
Expand Up @@ -127,6 +127,7 @@ static const AVOption options[]={
{"careful", "consider things that violate the spec and have not been seen in the wild as errors", 0, AV_OPT_TYPE_CONST, {.dbl = AV_EF_CAREFUL }, INT_MIN, INT_MAX, D, "err_detect"},
{"compliant", "consider all spec non compliancies as errors", 0, AV_OPT_TYPE_CONST, {.dbl = AV_EF_COMPLIANT }, INT_MIN, INT_MAX, D, "err_detect"},
{"aggressive", "consider things that a sane encoder shouldnt do as an error", 0, AV_OPT_TYPE_CONST, {.dbl = AV_EF_AGGRESSIVE }, INT_MIN, INT_MAX, D, "err_detect"},
{"correct_ts_overflow", "correct single timestamp overflows", OFFSET(correct_ts_overflow), AV_OPT_TYPE_INT, {.dbl = 1}, 0, 1, D},
{NULL},
};

Expand Down
123 changes: 113 additions & 10 deletions lib/ffmpeg/libavformat/utils.c
Expand Up @@ -122,6 +122,27 @@ static void frac_add(AVFrac *f, int64_t incr)
f->num = num;
}

/**
* Wrap a given time stamp, if there is an indication for an overflow
*
* @param st stream
* @param timestamp the time stamp to wrap
* @return resulting time stamp
*/
static int64_t wrap_timestamp(AVStream *st, int64_t timestamp)
{
if (st->pts_wrap_behavior != AV_PTS_WRAP_IGNORE &&
st->pts_wrap_reference != AV_NOPTS_VALUE && timestamp != AV_NOPTS_VALUE) {
if (st->pts_wrap_behavior == AV_PTS_WRAP_ADD_OFFSET &&
timestamp < st->pts_wrap_reference)
return timestamp + (1LL<<st->pts_wrap_bits);
else if (st->pts_wrap_behavior == AV_PTS_WRAP_SUB_OFFSET &&
timestamp >= st->pts_wrap_reference)
return timestamp - (1LL<<st->pts_wrap_bits);
}
return timestamp;
}

/** head of registered input format linked list */
static AVInputFormat *first_iformat = NULL;
/** head of registered output format linked list */
Expand Down Expand Up @@ -798,6 +819,8 @@ int av_read_packet(AVFormatContext *s, AVPacket *pkt)
}

st= s->streams[pkt->stream_index];
pkt->dts = wrap_timestamp(st, pkt->dts);
pkt->pts = wrap_timestamp(st, pkt->pts);

switch(st->codec->codec_type){
case AVMEDIA_TYPE_VIDEO:
Expand Down Expand Up @@ -948,8 +971,67 @@ static int is_intra_only(AVCodecContext *enc){
return 0;
}

static int update_wrap_reference(AVFormatContext *s, AVStream *st, int stream_index)
{
if (s->correct_ts_overflow && st->pts_wrap_bits != 64 &&
st->pts_wrap_reference == AV_NOPTS_VALUE && st->first_dts != AV_NOPTS_VALUE) {
int i;

// reference time stamp should be 60 s before first time stamp
int64_t pts_wrap_reference = st->first_dts - av_rescale(60, st->time_base.den, st->time_base.num);
// if first time stamp is not more than 1/8 and 60s before the wrap point, subtract rather than add wrap offset
int pts_wrap_behavior = (st->first_dts < (1LL<<st->pts_wrap_bits) - (1LL<<st->pts_wrap_bits-3)) ||
(st->first_dts < (1LL<<st->pts_wrap_bits) - av_rescale(60, st->time_base.den, st->time_base.num)) ?
AV_PTS_WRAP_ADD_OFFSET : AV_PTS_WRAP_SUB_OFFSET;

AVProgram *first_program = av_find_program_from_stream(s, NULL, stream_index);

if (!first_program) {
int default_stream_index = av_find_default_stream_index(s);
if (s->streams[default_stream_index]->pts_wrap_reference == AV_NOPTS_VALUE) {
for (i=0; i<s->nb_streams; i++) {
s->streams[i]->pts_wrap_reference = pts_wrap_reference;
s->streams[i]->pts_wrap_behavior = pts_wrap_behavior;
}
}
else {
st->pts_wrap_reference = s->streams[default_stream_index]->pts_wrap_reference;
st->pts_wrap_behavior = s->streams[default_stream_index]->pts_wrap_behavior;
}
}
else {
AVProgram *program = first_program;
while (program) {
if (program->pts_wrap_reference != AV_NOPTS_VALUE) {
pts_wrap_reference = program->pts_wrap_reference;
pts_wrap_behavior = program->pts_wrap_behavior;
break;
}
program = av_find_program_from_stream(s, program, stream_index);
}

// update every program with differing pts_wrap_reference
program = first_program;
while(program) {
if (program->pts_wrap_reference != pts_wrap_reference) {
for (i=0; i<program->nb_stream_indexes; i++) {
s->streams[program->stream_index[i]]->pts_wrap_reference = pts_wrap_reference;
s->streams[program->stream_index[i]]->pts_wrap_behavior = pts_wrap_behavior;
}

program->pts_wrap_reference = pts_wrap_reference;
program->pts_wrap_behavior = pts_wrap_behavior;
}
program = av_find_program_from_stream(s, program, stream_index);
}
}
return 1;
}
return 0;
}

static void update_initial_timestamps(AVFormatContext *s, int stream_index,
int64_t dts, int64_t pts)
int64_t dts, int64_t pts, AVPacket *pkt)
{
AVStream *st= s->streams[stream_index];
AVPacketList *pktl= s->packet_buffer;
Expand All @@ -973,6 +1055,16 @@ static void update_initial_timestamps(AVFormatContext *s, int stream_index,
if(st->start_time == AV_NOPTS_VALUE && pktl->pkt.pts != AV_NOPTS_VALUE)
st->start_time= pktl->pkt.pts;
}

if (update_wrap_reference(s, st, stream_index) && st->pts_wrap_behavior == AV_PTS_WRAP_SUB_OFFSET) {
// correct first time stamps to negative values
st->first_dts = wrap_timestamp(st, st->first_dts);
st->cur_dts = wrap_timestamp(st, st->cur_dts);
pkt->dts = wrap_timestamp(st, pkt->dts);
pkt->pts = wrap_timestamp(st, pkt->pts);
pts = wrap_timestamp(st, pts);
}

if (st->start_time == AV_NOPTS_VALUE)
st->start_time = pts;
}
Expand Down Expand Up @@ -1104,7 +1196,7 @@ static void compute_pkt_fields(AVFormatContext *s, AVStream *st,
/* PTS = presentation timestamp */
if (pkt->dts == AV_NOPTS_VALUE)
pkt->dts = st->last_IP_pts;
update_initial_timestamps(s, pkt->stream_index, pkt->dts, pkt->pts);
update_initial_timestamps(s, pkt->stream_index, pkt->dts, pkt->pts, pkt);
if (pkt->dts == AV_NOPTS_VALUE)
pkt->dts = st->cur_dts;

Expand All @@ -1131,7 +1223,7 @@ static void compute_pkt_fields(AVFormatContext *s, AVStream *st,
/* presentation is not delayed : PTS and DTS are the same */
if(pkt->pts == AV_NOPTS_VALUE)
pkt->pts = pkt->dts;
update_initial_timestamps(s, pkt->stream_index, pkt->pts, pkt->pts);
update_initial_timestamps(s, pkt->stream_index, pkt->pts, pkt->pts, pkt);
if(pkt->pts == AV_NOPTS_VALUE)
pkt->pts = st->cur_dts;
pkt->dts = pkt->pts;
Expand All @@ -1147,7 +1239,7 @@ static void compute_pkt_fields(AVFormatContext *s, AVStream *st,
if(pkt->dts == AV_NOPTS_VALUE)
pkt->dts= st->pts_buffer[0];
if(st->codec->codec_id == CODEC_ID_H264){ // we skipped it above so we try here
update_initial_timestamps(s, pkt->stream_index, pkt->dts, pkt->pts); // this should happen on the first packet
update_initial_timestamps(s, pkt->stream_index, pkt->dts, pkt->pts, pkt); // this should happen on the first packet
}
if(pkt->dts > st->cur_dts)
st->cur_dts = pkt->dts;
Expand Down Expand Up @@ -1550,6 +1642,7 @@ int ff_add_index_entry(AVIndexEntry **index_entries,
int av_add_index_entry(AVStream *st,
int64_t pos, int64_t timestamp, int size, int distance, int flags)
{
timestamp = wrap_timestamp(st, timestamp);
return ff_add_index_entry(&st->index_entries, &st->nb_index_entries,
&st->index_entries_allocated_size, pos,
timestamp, size, distance, flags);
Expand Down Expand Up @@ -1602,6 +1695,12 @@ int av_seek_frame_binary(AVFormatContext *s, int stream_index, int64_t target_ts
}
#endif

static int64_t ff_read_timestamp(AVFormatContext *s, int stream_index, int64_t *ppos, int64_t pos_limit,
int64_t (*read_timestamp)(struct AVFormatContext *, int , int64_t *, int64_t ))
{
return wrap_timestamp(s->streams[stream_index], read_timestamp(s, stream_index, ppos, pos_limit));
}

int ff_seek_frame_binary(AVFormatContext *s, int stream_index, int64_t target_ts, int flags)
{
AVInputFormat *avif= s->iformat;
Expand Down Expand Up @@ -1689,7 +1788,7 @@ int64_t ff_gen_search(AVFormatContext *s, int stream_index, int64_t target_ts,

if(ts_min == AV_NOPTS_VALUE){
pos_min = s->data_offset;
ts_min = read_timestamp(s, stream_index, &pos_min, INT64_MAX);
ts_min = ff_read_timestamp(s, stream_index, &pos_min, INT64_MAX, read_timestamp);
if (ts_min == AV_NOPTS_VALUE)
return -1;
}
Expand All @@ -1705,15 +1804,15 @@ int64_t ff_gen_search(AVFormatContext *s, int stream_index, int64_t target_ts,
pos_max = filesize - 1;
do{
pos_max -= step;
ts_max = read_timestamp(s, stream_index, &pos_max, pos_max + step);
ts_max = ff_read_timestamp(s, stream_index, &pos_max, pos_max + step, read_timestamp);
step += step;
}while(ts_max == AV_NOPTS_VALUE && pos_max >= step);
if (ts_max == AV_NOPTS_VALUE)
return -1;

for(;;){
int64_t tmp_pos= pos_max + 1;
int64_t tmp_ts= read_timestamp(s, stream_index, &tmp_pos, INT64_MAX);
int64_t tmp_ts= ff_read_timestamp(s, stream_index, &tmp_pos, INT64_MAX, read_timestamp);
if(tmp_ts == AV_NOPTS_VALUE)
break;
ts_max= tmp_ts;
Expand Down Expand Up @@ -1760,7 +1859,7 @@ int64_t ff_gen_search(AVFormatContext *s, int stream_index, int64_t target_ts,
pos= pos_limit;
start_pos= pos;

ts = read_timestamp(s, stream_index, &pos, INT64_MAX); //may pass pos_limit instead of -1
ts = ff_read_timestamp(s, stream_index, &pos, INT64_MAX, read_timestamp); //may pass pos_limit instead of -1
if(pos == pos_max)
no_change++;
else
Expand Down Expand Up @@ -1788,9 +1887,9 @@ int64_t ff_gen_search(AVFormatContext *s, int stream_index, int64_t target_ts,
ts = (flags & AVSEEK_FLAG_BACKWARD) ? ts_min : ts_max;
#if 0
pos_min = pos;
ts_min = read_timestamp(s, stream_index, &pos_min, INT64_MAX);
ts_min = ff_read_timestamp(s, stream_index, &pos_min, INT64_MAX, read_timestamp);
pos_min++;
ts_max = read_timestamp(s, stream_index, &pos_min, INT64_MAX);
ts_max = ff_read_timestamp(s, stream_index, &pos_min, INT64_MAX, read_timestamp);
av_dlog(s, "pos=0x%"PRIx64" %"PRId64"<=%"PRId64"<=%"PRId64"\n",
pos, ts_min, target_ts, ts_max);
#endif
Expand Down Expand Up @@ -2992,6 +3091,8 @@ AVStream *avformat_new_stream(AVFormatContext *s, AVCodec *c)
st->cur_dts = 0;
st->first_dts = AV_NOPTS_VALUE;
st->probe_packets = MAX_PROBE_PACKETS;
st->pts_wrap_reference = AV_NOPTS_VALUE;
st->pts_wrap_behavior = AV_PTS_WRAP_IGNORE;

/* default pts setting is MPEG-like */
avpriv_set_pts_info(st, 33, 1, 90000);
Expand Down Expand Up @@ -3025,6 +3126,8 @@ AVProgram *av_new_program(AVFormatContext *ac, int id)
program->discard = AVDISCARD_NONE;
}
program->id = id;
program->pts_wrap_reference = AV_NOPTS_VALUE;
program->pts_wrap_behavior = AV_PTS_WRAP_IGNORE;

return program;
}
Expand Down

0 comments on commit b2d3b83

Please sign in to comment.