>From 6664a876146e2a2f1e13ffab0e31c214142f6c67 Mon Sep 17 00:00:00 2001
From: Ulrik Dickow <u.dickow@gmail.com>
Date: Mon, 13 Aug 2012 13:36:22 +0200
Subject: [PATCH 2/2] Massively improved and corrected automatic backwards
 time jump handling.

The previous monotonicity patch wrongly dropped the initial keyframe packet
after a seek (as when using 'rtmpdump -A').  Initial seek now works, but
further seeks by apps other than rtmpdump haven't been tested.

Also, we automatically disable the BUFX hack if a non-zero time jump backwards
is observed.  This should greatly reduce the number of further backwards jumps
in the same session (to just one more, assuming 1 video and 1 audio stream).

We log the timestamps at every jump backwards, and when hitting the hitherto
largest timestamp again.  We don't currently log/warn if crossing the
jump-limit from behind without hitting it exactly (invitation to trouble).
---
 librtmp/rtmp.c |   81 ++++++++++++++++++++++++++++++++++++++++++++++++-------
 librtmp/rtmp.h |    4 ++-
 2 files changed, 73 insertions(+), 12 deletions(-)

diff --git a/librtmp/rtmp.c b/librtmp/rtmp.c
index 62ede41..e744c7c 100644
--- a/librtmp/rtmp.c
+++ b/librtmp/rtmp.c
@@ -4257,6 +4257,53 @@ static const char flvHeader[] = { 'F', 'L', 'V', 0x01,
   0x00, 0x00, 0x00, 0x00
 };
 
+/* Returns TRUE if packet should be skipped due to non-new timestamp, otherwise
+ * FALSE.  We warn right after a non-zero jump backwards in time has happened.
+ * If the BUFX hack is enabled at jump time, we disable it to minimize the risk
+ * of further jumps backwards.
+ */
+static inline int
+skip_old_timestamp(RTMP *r, uint32_t prev_av_timestamp, uint32_t last_av_timestamp)
+{
+  if (last_av_timestamp == 0 || r->m_read.timestamp > last_av_timestamp)
+    return FALSE; /* First and really new packets surely shouldn't be skipped */
+
+  if (r->m_read.timestamp == last_av_timestamp)
+    {
+      if (r->m_read.timestamp == prev_av_timestamp)
+	{
+	  RTMP_Log(RTMP_LOGDEBUG, "Keeping ts = last = prev = %u ms", r->m_read.timestamp);
+	  return FALSE;
+	}
+      else
+	{
+	  RTMP_Log(RTMP_LOGINFO, "Successfully hit ts = %u ms again (skipped, prev = %u ms)",
+		   r->m_read.timestamp, prev_av_timestamp);
+	  return TRUE;
+	}
+    }
+  /* ts < last.  The packet should be skipped (return TRUE). */
+
+  if (r->m_read.timestamp >= prev_av_timestamp)
+    return TRUE; /* Skip silently.  We already logged and handled BUFX appropriately. */
+
+  /* ts < prev <= last.  At this point we hit a jump backwards. */
+
+  if (r->Link.lFlags & RTMP_LF_BUFX)
+    {
+      r->Link.lFlags ^= RTMP_LF_BUFX;
+      RTMP_Log(RTMP_LOGINFO,
+	       "Timestamp jumped from %u ms to %u ms.  Disabling BUFX hack and catching up.",
+	       prev_av_timestamp, r->m_read.timestamp);
+    }
+  else /* Expected exactly once if stream has both audio and video; otherwise we're worse off */
+    RTMP_Log(RTMP_LOGWARNING, /* ... so shout a bit louder here, although inelegantly */
+	     "Timestamp jumped from %u ms to %u ms (BUFX hack no longer enabled).",
+	     prev_av_timestamp, r->m_read.timestamp);
+
+  return TRUE;
+}
+
 #define HEADERBUF	(128*1024)
 int
 RTMP_Read(RTMP *r, char *buf, int size)
@@ -4320,8 +4367,8 @@ fail:
 	  r->m_read.bufpos = mybuf;
 	}
       r->m_read.flags |= RTMP_READ_HEADER;
-      r->m_read.last_video_ts = 0;
-      r->m_read.last_audio_ts = 0;
+      r->m_read.prev_video_ts = r->m_read.last_video_ts = 0;
+      r->m_read.prev_audio_ts = r->m_read.last_audio_ts = 0;
     }
 
   if ((r->m_read.flags & RTMP_READ_SEEKING) && r->m_read.buf)
@@ -4359,15 +4406,27 @@ fail:
   while (size > 0 && (nRead = Read_1_Packet(r, buf, size)) >= 0)
     {
       if (!nRead) continue;
-      if (*buf == RTMP_PACKET_TYPE_VIDEO) {
-          if (r->m_read.last_video_ts && r->m_read.timestamp <= r->m_read.last_video_ts)
-              continue;
-          r->m_read.last_video_ts = r->m_read.timestamp;
-      } else if (*buf == RTMP_PACKET_TYPE_AUDIO) {
-          if (r->m_read.last_audio_ts && r->m_read.timestamp <= r->m_read.last_audio_ts)
-              continue;
-          r->m_read.last_audio_ts = r->m_read.timestamp;
-      }
+
+      if (*buf == RTMP_PACKET_TYPE_VIDEO)
+	{
+	  if (skip_old_timestamp(r, r->m_read.prev_video_ts, r->m_read.last_video_ts))
+	    {
+	      r->m_read.prev_video_ts = r->m_read.timestamp;
+	      continue;
+	    }
+	  r->m_read.prev_video_ts = r->m_read.timestamp;
+	  r->m_read.last_video_ts = r->m_read.timestamp;
+	}
+      else if (*buf == RTMP_PACKET_TYPE_AUDIO)
+	{
+	  if (skip_old_timestamp(r, r->m_read.prev_audio_ts, r->m_read.last_audio_ts))
+	    {
+	      r->m_read.prev_audio_ts = r->m_read.timestamp;
+	      continue;
+	    }
+	  r->m_read.prev_audio_ts = r->m_read.timestamp;
+	  r->m_read.last_audio_ts = r->m_read.timestamp;
+	}
       buf += nRead;
       total += nRead;
       size -= nRead;
diff --git a/librtmp/rtmp.h b/librtmp/rtmp.h
index 6b68126..adabfb2 100644
--- a/librtmp/rtmp.h
+++ b/librtmp/rtmp.h
@@ -198,7 +198,9 @@ extern "C"
     char *bufpos;
     unsigned int buflen;
     uint32_t timestamp;
-    uint32_t last_video_ts;
+    uint32_t prev_video_ts; /* Timestamp in the previous packet in RTMP_Read (maybe skipped) */
+    uint32_t prev_audio_ts;
+    uint32_t last_video_ts; /* Last timestamp returned from RTMP_Read (not skipped) */
     uint32_t last_audio_ts;
     uint8_t dataType;
     uint8_t flags;
-- 
1.7.7.6

