[FFmpeg-devel] [RFC] mpegts: Provide a monotonic timestamp to the outside world

Harald Axmann harald.axmann at hotmail.com
Tue Nov 13 02:35:51 CET 2012


On 02.11.2012 04:16, Michael Niedermayer wrote:
> [...]
> this ignores st->time_base.num
>
> also consider that a file starts with dts -1/30 sec, that is one frame
> in the past so that the first PTS is 0, this is not uncommon.
> this would result in the first stored timestamp to be around the
> maximum value and a wrap happening immedeatly, the above code would
> then move the whole 1<<33 in the future. While this is just ugly
> the bigger problem is that audio might start at 0 and it wont wrap
> total AV desync would result.
>
> I had not realized this corner case when suggesting to use streams
> instead of programs for storing the first dts.
>
> maybe you can add a new field to AVStream and use that instead of
> first_dts and then synchronize this at a convenient place so that
> streams in the same program have the exact same wrap point.
> But i did not deeply think about this ...

Thanks, I did not think about that.

I now had time to implement the needed changes. I use av_rescale to 
calculate the offset. The resulting wrap detection reference point is 
saved in AVStream. It will be calculated when calling 
update_initial_timestamps. If there are programs available, I use them 
to determine the associated streams. Otherwise I use one reference time 
stamp for all streams of the context.

To solve the ugly case when the first ts is "-1" or maximum timestamp 
respectively, I subtract the offset, if the first time stamp is less 
than 60 seconds before the wrap point. So we get a real "-1" here and 
the next frame would have a correct PTS of 0 again.

Unfortunately I have problems with fate-nuv-rtjpeg-fh. There is an MP3 
track starting at 4294965711 or -1585, but the video track starts later 
at PTS 20. With default FFmpeg, the first time stamp of the whole stream 
is 20, but with the wrap removal it is of course -1585. So the test 
fails, because the output time stamp has an offset. A patch with some 
debug output for reproducing this issue is attached. I am very unsure, 
how to deal with that...
-------------- next part --------------
diff --git a/libavformat/avformat.h b/libavformat/avformat.h
index 61bc4a6..dcbe04e 100644
--- a/libavformat/avformat.h
+++ b/libavformat/avformat.h
@@ -847,6 +847,20 @@ typedef struct AVStream {
      */
     int64_t mux_ts_offset;
 
+    /**
+     * Internal data to check for wrapping of the time stamp
+     */
+    int64_t pts_wrap_reference;
+
+    /**
+     * Add or subtract the offset for wrap correction
+     *
+     * 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_add_offset;
+
 } AVStream;
 
 #define AV_PROGRAM_RUNNING 1
@@ -878,6 +892,9 @@ typedef struct AVProgram {
      */
     int64_t start_time;
     int64_t end_time;
+
+    int64_t pts_wrap_reference;    ///< reference dts for wrap detection
+    int pts_wrap_add_offset;       ///< add or subtract the offset for wrap correction
 } AVProgram;
 
 #define AVFMTCTX_NOHEADER      0x0001 /**< signal that no header is present
diff --git a/libavformat/utils.c b/libavformat/utils.c
index 0240b0c..ee78118 100644
--- a/libavformat/utils.c
+++ b/libavformat/utils.c
@@ -78,6 +78,25 @@ static int is_relative(int64_t ts) {
     return ts > (RELATIVE_TS_BASE - (1LL<<48));
 }
 
+/**
+ * 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_bits != 64 && st->pts_wrap_reference != AV_NOPTS_VALUE &&
+        timestamp != AV_NOPTS_VALUE) {
+        if (st->pts_wrap_add_offset && timestamp < st->pts_wrap_reference)
+            return timestamp + (1LL<<st->pts_wrap_bits);
+        else if (!st->pts_wrap_add_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 */
@@ -739,6 +758,9 @@ int ff_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);
+        av_log(s, AV_LOG_INFO, "stream %d: dts %lld, pts %lld\n", pkt->stream_index, pkt->dts, pkt->pts);
 
         force_codec_ids(s, st);
 
@@ -885,8 +907,64 @@ static AVPacketList *get_next_pkt(AVFormatContext *s, AVStream *st, AVPacketList
     return NULL;
 }
 
+static int update_wrap_reference(AVFormatContext *s, AVStream *st, int stream_index)
+{
+    if (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 within 60 s before wrap point, subtract rather than add wrap offset
+        int pts_wrap_add_offset = st->first_dts < (1LL<<st->pts_wrap_bits) -
+            av_rescale(60, st->time_base.den, st->time_base.num);
+
+        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_add_offset = pts_wrap_add_offset;
+                }
+            }
+            else {
+                st->pts_wrap_reference = s->streams[default_stream_index]->pts_wrap_reference;
+                st->pts_wrap_add_offset = s->streams[default_stream_index]->pts_wrap_add_offset;
+            }
+        }
+        else {
+            AVProgram *program = first_program;
+            while (program) {
+                if (program->pts_wrap_reference != AV_NOPTS_VALUE) {
+                    pts_wrap_reference = program->pts_wrap_reference;
+                    pts_wrap_add_offset = program->pts_wrap_add_offset;
+                    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_add_offset = pts_wrap_add_offset;
+                    }
+
+                    program->pts_wrap_reference = pts_wrap_reference;
+                    program->pts_wrap_add_offset = pts_wrap_add_offset;
+                }
+                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->parse_queue ? s->parse_queue : s->packet_buffer;
@@ -928,8 +1006,19 @@ static void update_initial_timestamps(AVFormatContext *s, int stream_index,
                 pktl->pkt.dts= pts_buffer[0];
         }
     }
+
+    if (update_wrap_reference(s, st, stream_index) && !st->pts_wrap_add_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;
+    av_log(s, AV_LOG_INFO, "st->start_time stream %d, %lld\n", stream_index, st->start_time);
 }
 
 static void update_initial_durations(AVFormatContext *s, AVStream *st,
@@ -1073,7 +1162,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;
 
@@ -1110,7 +1199,7 @@ static void compute_pkt_fields(AVFormatContext *s, AVStream *st,
             if (pkt->pts == AV_NOPTS_VALUE)
                 pkt->pts = pkt->dts;
             update_initial_timestamps(s, pkt->stream_index, pkt->pts,
-                                      pkt->pts);
+                                      pkt->pts, pkt);
             if (pkt->pts == AV_NOPTS_VALUE)
                 pkt->pts = st->cur_dts;
             pkt->dts = pkt->pts;
@@ -1127,7 +1216,7 @@ static void compute_pkt_fields(AVFormatContext *s, AVStream *st,
             pkt->dts= st->pts_buffer[0];
     }
     if(st->codec->codec_id == AV_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;
@@ -1614,6 +1703,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);
@@ -1660,6 +1750,12 @@ int av_index_search_timestamp(AVStream *st, int64_t wanted_timestamp,
                                      wanted_timestamp, flags);
 }
 
+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;
@@ -1735,7 +1831,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;
     }
@@ -1751,7 +1847,7 @@ 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)
@@ -1759,7 +1855,7 @@ int64_t ff_gen_search(AVFormatContext *s, int stream_index, int64_t target_ts,
 
         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;
@@ -1806,7 +1902,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
@@ -1835,9 +1931,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" %s<=%s<=%s\n",
             pos, av_ts2str(ts_min), av_ts2str(target_ts), av_ts2str(ts_max));
 #endif
@@ -3138,6 +3234,7 @@ AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c)
     st->cur_dts = s->iformat ? RELATIVE_TS_BASE : 0;
     st->first_dts = AV_NOPTS_VALUE;
     st->probe_packets = MAX_PROBE_PACKETS;
+    st->pts_wrap_reference = AV_NOPTS_VALUE;
 
     /* default pts setting is MPEG-like */
     avpriv_set_pts_info(st, 33, 1, 90000);
@@ -3177,6 +3274,7 @@ AVProgram *av_new_program(AVFormatContext *ac, int id)
         program->discard = AVDISCARD_NONE;
     }
     program->id = id;
+    program->pts_wrap_reference = AV_NOPTS_VALUE;
 
     program->start_time =
     program->end_time   = AV_NOPTS_VALUE;


More information about the ffmpeg-devel mailing list