VCM: Adding API for the size(duration) of the jitter buffer.
Refers to the duration in time of the frames which are ready to be sent to the decoder.

Review URL: https://webrtc-codereview.appspot.com/1319004

git-svn-id: http://webrtc.googlecode.com/svn/trunk@3903 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/webrtc/modules/video_coding/main/interface/video_coding.h b/webrtc/modules/video_coding/main/interface/video_coding.h
index 41a63d1..9f5ddef 100644
--- a/webrtc/modules/video_coding/main/interface/video_coding.h
+++ b/webrtc/modules/video_coding/main/interface/video_coding.h
@@ -412,6 +412,10 @@
     //                     < 0,         on error.
     virtual int32_t Decode(uint16_t maxWaitTimeMs = 200) = 0;
 
+    // Registers a callback which conveys the size of the render buffer.
+    virtual int RegisterRenderBufferSizeCallback(
+        VCMRenderBufferSizeCallback* callback) = 0;
+
     // Waits for the next frame in the dual jitter buffer to become complete
     // (waits no longer than maxWaitTimeMs), then passes it to the dual decoder
     // for decoding. This will never trigger a render callback. Should be
diff --git a/webrtc/modules/video_coding/main/interface/video_coding_defines.h b/webrtc/modules/video_coding/main/interface/video_coding_defines.h
index fcee34b..b1b52bd 100644
--- a/webrtc/modules/video_coding/main/interface/video_coding_defines.h
+++ b/webrtc/modules/video_coding/main/interface/video_coding_defines.h
@@ -182,6 +182,17 @@
   }
 };
 
+// Callback class used for telling the user about the size (in time) of the
+// render buffer, that is the size in time of the complete continuous frames.
+class VCMRenderBufferSizeCallback {
+ public:
+  virtual void RenderBufferSizeMs(int buffer_size_ms) = 0;
+
+ protected:
+  virtual ~VCMRenderBufferSizeCallback() {
+  }
+};
+
 }  // namespace webrtc
 
 #endif // WEBRTC_MODULES_INTERFACE_VIDEO_CODING_DEFINES_H_
diff --git a/webrtc/modules/video_coding/main/source/decoding_state.cc b/webrtc/modules/video_coding/main/source/decoding_state.cc
index eb03705..0974584 100644
--- a/webrtc/modules/video_coding/main/source/decoding_state.cc
+++ b/webrtc/modules/video_coding/main/source/decoding_state.cc
@@ -72,6 +72,16 @@
   in_initial_state_ = false;
 }
 
+void VCMDecodingState::CopyFrom(const VCMDecodingState& state) {
+  sequence_num_ = state.sequence_num_;
+  time_stamp_ = state.time_stamp_;
+  picture_id_ = state.picture_id_;
+  temporal_id_ = state.temporal_id_;
+  tl0_pic_id_ = state.tl0_pic_id_;
+  full_sync_ = state.full_sync_;
+  in_initial_state_ = state.in_initial_state_;
+}
+
 void VCMDecodingState::SetStateOneBack(const VCMFrameBuffer* frame) {
   assert(frame != NULL && frame->GetHighSeqNum() >= 0);
   sequence_num_ = static_cast<uint16_t>(frame->GetHighSeqNum()) - 1u;
diff --git a/webrtc/modules/video_coding/main/source/decoding_state.h b/webrtc/modules/video_coding/main/source/decoding_state.h
index 2fd143b..f001966 100644
--- a/webrtc/modules/video_coding/main/source/decoding_state.h
+++ b/webrtc/modules/video_coding/main/source/decoding_state.h
@@ -31,6 +31,7 @@
   // possible, i.e. temporal info, picture ID or sequence number.
   bool ContinuousFrame(const VCMFrameBuffer* frame) const;
   void SetState(const VCMFrameBuffer* frame);
+  void CopyFrom(const VCMDecodingState& state);
   // Set the decoding state one frame back.
   void SetStateOneBack(const VCMFrameBuffer* frame);
   void UpdateEmptyFrame(const VCMFrameBuffer* frame);
diff --git a/webrtc/modules/video_coding/main/source/jitter_buffer.cc b/webrtc/modules/video_coding/main/source/jitter_buffer.cc
index 0988242..6f133b3 100644
--- a/webrtc/modules/video_coding/main/source/jitter_buffer.cc
+++ b/webrtc/modules/video_coding/main/source/jitter_buffer.cc
@@ -982,6 +982,46 @@
   return last_decoded_state_.time_stamp();
 }
 
+int VCMJitterBuffer::RenderBufferSizeMs() {
+  CriticalSectionScoped cs(crit_sect_);
+  CleanUpOldOrEmptyFrames();
+  if (frame_list_.empty()) {
+    return 0;
+  }
+  FrameList::iterator frame_it = frame_list_.begin();
+  VCMFrameBuffer* current_frame = *frame_it;
+  // Search for a complete and continuous sequence (starting from the last
+  // decoded state or current frame if in initial state).
+  VCMDecodingState previous_state;
+  if (last_decoded_state_.in_initial_state()) {
+    // Start with a key frame.
+    frame_it = find_if(frame_list_.begin(), frame_list_.end(),
+        CompleteKeyFrameCriteria());
+    if (frame_it == frame_list_.end()) {
+      return 0;
+    }
+    current_frame = *frame_it;
+    previous_state.SetState(current_frame);
+  } else {
+    previous_state.CopyFrom(last_decoded_state_);
+  }
+  bool continuous_complete = true;
+  int64_t start_render = current_frame->RenderTimeMs();
+  ++frame_it;
+  while (frame_it != frame_list_.end() && continuous_complete) {
+    current_frame = *frame_it;
+    continuous_complete = current_frame->IsSessionComplete() &&
+        previous_state.ContinuousFrame(current_frame);
+    previous_state.SetState(current_frame);
+    ++frame_it;
+  }
+  // Desired frame is the previous one.
+  --frame_it;
+  current_frame = *frame_it;
+  // Got the frame, now compute the time delta.
+  return static_cast<int>(current_frame->RenderTimeMs() - start_render);
+}
+
 // Set the frame state to free and remove it from the sorted
 // frame list. Must be called from inside the critical section crit_sect_.
 void VCMJitterBuffer::ReleaseFrameIfNotDecoding(VCMFrameBuffer* frame) {
diff --git a/webrtc/modules/video_coding/main/source/jitter_buffer.h b/webrtc/modules/video_coding/main/source/jitter_buffer.h
index ec35d1f..81a637a 100644
--- a/webrtc/modules/video_coding/main/source/jitter_buffer.h
+++ b/webrtc/modules/video_coding/main/source/jitter_buffer.h
@@ -166,6 +166,9 @@
   int64_t LastDecodedTimestamp() const;
   bool decode_with_errors() const {return decode_with_errors_;}
 
+  // Returns size in time (milliseconds) of complete continuous frames.
+  int RenderBufferSizeMs();
+
  private:
   class SequenceNumberLessThan {
    public:
@@ -211,6 +214,8 @@
   // Can return a decodable, incomplete frame when enabled.
   FrameList::iterator FindOldestCompleteContinuousFrame();
 
+  // Cleans the frame list in the JB from old/empty frames.
+  // Should only be called prior to actual use.
   void CleanUpOldOrEmptyFrames();
 
   // Sets the "decodable" and "frame loss" flags of a frame depending on which
diff --git a/webrtc/modules/video_coding/main/source/jitter_buffer_unittest.cc b/webrtc/modules/video_coding/main/source/jitter_buffer_unittest.cc
index bf81973..7971fae 100644
--- a/webrtc/modules/video_coding/main/source/jitter_buffer_unittest.cc
+++ b/webrtc/modules/video_coding/main/source/jitter_buffer_unittest.cc
@@ -16,146 +16,12 @@
 #include "webrtc/modules/video_coding/main/source/jitter_buffer.h"
 #include "webrtc/modules/video_coding/main/source/media_opt_util.h"
 #include "webrtc/modules/video_coding/main/source/packet.h"
+#include "webrtc/modules/video_coding/main/source/stream_generator.h"
 #include "webrtc/modules/video_coding/main/test/test_util.h"
 #include "webrtc/system_wrappers/interface/clock.h"
 
 namespace webrtc {
 
-enum { kDefaultFrameRate = 25u };
-enum { kDefaultFramePeriodMs = 1000u / kDefaultFrameRate };
-const unsigned int kDefaultBitrateKbps = 1000;
-const unsigned int kFrameSize = (kDefaultBitrateKbps + kDefaultFrameRate * 4) /
-    (kDefaultFrameRate * 8);
-const unsigned int kMaxPacketSize = 1500;
-
-class StreamGenerator {
- public:
-  StreamGenerator(uint16_t start_seq_num, uint32_t start_timestamp,
-                  int64_t current_time)
-      : packets_(),
-        sequence_number_(start_seq_num),
-        timestamp_(start_timestamp),
-        start_time_(current_time) {}
-
-  void Init(uint16_t start_seq_num, uint32_t start_timestamp,
-            int64_t current_time) {
-    packets_.clear();
-    sequence_number_ = start_seq_num;
-    timestamp_ = start_timestamp;
-    start_time_ = current_time;
-    memset(&packet_buffer, 0, sizeof(packet_buffer));
-  }
-
-  void GenerateFrame(FrameType type, int num_media_packets,
-                     int num_empty_packets, int64_t current_time) {
-    timestamp_ += 90 * (current_time - start_time_);
-    // Move the sequence number counter if all packets from the previous frame
-    // wasn't collected.
-    sequence_number_ += packets_.size();
-    packets_.clear();
-    for (int i = 0; i < num_media_packets; ++i) {
-      const int packet_size = (kFrameSize + num_media_packets / 2) /
-          num_media_packets;
-      packets_.push_back(GeneratePacket(sequence_number_,
-                                        timestamp_,
-                                        packet_size,
-                                        (i == 0),
-                                        (i == num_media_packets - 1),
-                                        type));
-      ++sequence_number_;
-    }
-    for (int i = 0; i < num_empty_packets; ++i) {
-      packets_.push_back(GeneratePacket(sequence_number_,
-                                        timestamp_,
-                                        0,
-                                        false,
-                                        false,
-                                        kFrameEmpty));
-      ++sequence_number_;
-    }
-  }
-
-  VCMPacket GeneratePacket(uint16_t sequence_number,
-                           uint32_t timestamp,
-                           unsigned int size,
-                           bool first_packet,
-                           bool marker_bit,
-                           FrameType type) {
-    EXPECT_LT(size, kMaxPacketSize);
-    VCMPacket packet;
-    packet.seqNum = sequence_number;
-    packet.timestamp = timestamp;
-    packet.frameType = type;
-    packet.isFirstPacket = first_packet;
-    packet.markerBit = marker_bit;
-    packet.sizeBytes = size;
-    packet.dataPtr = packet_buffer;
-    if (packet.isFirstPacket)
-      packet.completeNALU = kNaluStart;
-    else if (packet.markerBit)
-      packet.completeNALU = kNaluEnd;
-    else
-      packet.completeNALU = kNaluIncomplete;
-    return packet;
-  }
-
-  bool PopPacket(VCMPacket* packet, int index) {
-    std::list<VCMPacket>::iterator it = GetPacketIterator(index);
-    if (it == packets_.end())
-      return false;
-    if (packet)
-      *packet = (*it);
-    packets_.erase(it);
-    return true;
-  }
-
-  bool GetPacket(VCMPacket* packet, int index) {
-    std::list<VCMPacket>::iterator it = GetPacketIterator(index);
-    if (it == packets_.end())
-      return false;
-    if (packet)
-      *packet = (*it);
-    return true;
-  }
-
-  bool NextPacket(VCMPacket* packet) {
-    if (packets_.empty())
-      return false;
-    if (packet != NULL)
-      *packet = packets_.front();
-    packets_.pop_front();
-    return true;
-  }
-
-  uint16_t NextSequenceNumber() const {
-    if (packets_.empty())
-      return sequence_number_;
-    return packets_.front().seqNum;
-  }
-
-  int PacketsRemaining() const {
-    return packets_.size();
-  }
-
- private:
-  std::list<VCMPacket>::iterator GetPacketIterator(int index) {
-    std::list<VCMPacket>::iterator it = packets_.begin();
-    for (int i = 0; i < index; ++i) {
-      ++it;
-      if (it == packets_.end()) break;
-    }
-    return it;
-  }
-
-  std::list<VCMPacket> packets_;
-  uint16_t sequence_number_;
-  uint32_t timestamp_;
-  int64_t start_time_;
-  uint8_t packet_buffer[kMaxPacketSize];
-
-  DISALLOW_COPY_AND_ASSIGN(StreamGenerator);
-};
-
 class TestRunningJitterBuffer : public ::testing::Test {
  protected:
   enum { kDataBufferSize = 10 };
@@ -166,7 +32,7 @@
     oldest_packet_to_nack_ = 250;
     jitter_buffer_ = new VCMJitterBuffer(clock_.get(), &event_factory_, -1, -1,
                                          true);
-    stream_generator = new StreamGenerator(0, 0, clock_->TimeInMilliseconds());
+    stream_generator_ = new StreamGenerator(0, 0, clock_->TimeInMilliseconds());
     jitter_buffer_->Start();
     jitter_buffer_->SetNackSettings(max_nack_list_size_,
                                     oldest_packet_to_nack_);
@@ -175,7 +41,7 @@
 
   virtual void TearDown() {
     jitter_buffer_->Stop();
-    delete stream_generator;
+    delete stream_generator_;
     delete jitter_buffer_;
   }
 
@@ -184,7 +50,7 @@
     VCMEncodedFrame* frame;
 
     packet.dataPtr = data_buffer_;
-    bool packet_available = stream_generator->PopPacket(&packet, index);
+    bool packet_available = stream_generator_->PopPacket(&packet, index);
     EXPECT_TRUE(packet_available);
     if (!packet_available)
       return kStateError;  // Return here to avoid crashes below.
@@ -197,7 +63,7 @@
     VCMEncodedFrame* frame;
 
     packet.dataPtr = data_buffer_;
-    bool packet_available = stream_generator->GetPacket(&packet, index);
+    bool packet_available = stream_generator_->GetPacket(&packet, index);
     EXPECT_TRUE(packet_available);
     if (!packet_available)
       return kStateError;  // Return here to avoid crashes below.
@@ -206,7 +72,7 @@
   }
 
   VCMFrameBufferEnum InsertFrame(FrameType frame_type) {
-    stream_generator->GenerateFrame(frame_type,
+    stream_generator_->GenerateFrame(frame_type,
                                     (frame_type != kFrameEmpty) ? 1 : 0,
                                     (frame_type == kFrameEmpty) ? 1 : 0,
                                     clock_->TimeInMilliseconds());
@@ -229,7 +95,7 @@
   }
 
   void DropFrame(int num_packets) {
-    stream_generator->GenerateFrame(kVideoFrameDelta, num_packets, 0,
+    stream_generator_->GenerateFrame(kVideoFrameDelta, num_packets, 0,
                                     clock_->TimeInMilliseconds());
     clock_->AdvanceTimeMilliseconds(kDefaultFramePeriodMs);
   }
@@ -250,7 +116,7 @@
   }
 
   VCMJitterBuffer* jitter_buffer_;
-  StreamGenerator* stream_generator;
+  StreamGenerator* stream_generator_;
   scoped_ptr<SimulatedClock> clock_;
   NullEventFactory event_factory_;
   size_t max_nack_list_size_;
@@ -288,7 +154,7 @@
 
 TEST_F(TestRunningJitterBuffer, EmptyPackets) {
   // Make sure a frame can get complete even though empty packets are missing.
-  stream_generator->GenerateFrame(kVideoFrameKey, 3, 3,
+  stream_generator_->GenerateFrame(kVideoFrameKey, 3, 3,
                                   clock_->TimeInMilliseconds());
   bool request_key_frame = false;
   EXPECT_EQ(kFirstPacket, InsertPacketAndPop(4));
@@ -488,11 +354,11 @@
 }
 
 TEST_F(TestJitterBufferNack, NackListBuiltBeforeFirstDecode) {
-  stream_generator->Init(0, 0, clock_->TimeInMilliseconds());
+  stream_generator_->Init(0, 0, clock_->TimeInMilliseconds());
   InsertFrame(kVideoFrameKey);
-  stream_generator->GenerateFrame(kVideoFrameDelta, 2, 0,
+  stream_generator_->GenerateFrame(kVideoFrameDelta, 2, 0,
                                   clock_->TimeInMilliseconds());
-  stream_generator->NextPacket(NULL);  // Drop packet.
+  stream_generator_->NextPacket(NULL);  // Drop packet.
   EXPECT_EQ(kFirstPacket, InsertPacketAndPop(0));
   EXPECT_TRUE(DecodeCompleteFrame());
   uint16_t nack_list_size = 0;
@@ -503,8 +369,8 @@
 }
 
 TEST_F(TestJitterBufferNack, UseNackToRecoverFirstKeyFrame) {
-  stream_generator->Init(0, 0, clock_->TimeInMilliseconds());
-  stream_generator->GenerateFrame(kVideoFrameKey, 3, 0,
+  stream_generator_->Init(0, 0, clock_->TimeInMilliseconds());
+  stream_generator_->GenerateFrame(kVideoFrameKey, 3, 0,
                                   clock_->TimeInMilliseconds());
   EXPECT_EQ(kFirstPacket, InsertPacketAndPop(0));
   // Drop second packet.
@@ -516,7 +382,7 @@
   EXPECT_EQ(1, nack_list_size);
   ASSERT_TRUE(list != NULL);
   VCMPacket packet;
-  stream_generator->GetPacket(&packet, 0);
+  stream_generator_->GetPacket(&packet, 0);
   EXPECT_EQ(packet.seqNum, list[0]);
 }
 
@@ -530,21 +396,21 @@
   //  ----------------------------------------------------------------
   // | 1 | 2 | .. | 8 | 9 | x | 11 | 12 | .. | 19 | x | 21 | .. | 100 |
   //  ----------------------------------------------------------------
-  stream_generator->GenerateFrame(kVideoFrameKey, 100, 0,
+  stream_generator_->GenerateFrame(kVideoFrameKey, 100, 0,
                                   clock_->TimeInMilliseconds());
   clock_->AdvanceTimeMilliseconds(kDefaultFramePeriodMs);
   EXPECT_EQ(kFirstPacket, InsertPacketAndPop(0));
   // Verify that the frame is incomplete.
   EXPECT_FALSE(DecodeCompleteFrame());
-  while (stream_generator->PacketsRemaining() > 1) {
-    if (stream_generator->NextSequenceNumber() % 10 != 0) {
+  while (stream_generator_->PacketsRemaining() > 1) {
+    if (stream_generator_->NextSequenceNumber() % 10 != 0) {
       EXPECT_EQ(kIncomplete, InsertPacketAndPop(0));
     } else {
-      stream_generator->NextPacket(NULL);  // Drop packet
+      stream_generator_->NextPacket(NULL);  // Drop packet
     }
   }
   EXPECT_EQ(kIncomplete, InsertPacketAndPop(0));
-  EXPECT_EQ(0, stream_generator->PacketsRemaining());
+  EXPECT_EQ(0, stream_generator_->PacketsRemaining());
   EXPECT_FALSE(DecodeCompleteFrame());
   EXPECT_FALSE(DecodeIncompleteFrame());
   uint16_t nack_list_size = 0;
@@ -563,24 +429,24 @@
   //  -------   ------------------------------------------------------------
   // | 65532 | | 65533 | 65534 | 65535 | x | 1 | .. | 9 | x | 11 |.....| 96 |
   //  -------   ------------------------------------------------------------
-  stream_generator->Init(65532, 0, clock_->TimeInMilliseconds());
+  stream_generator_->Init(65532, 0, clock_->TimeInMilliseconds());
   InsertFrame(kVideoFrameKey);
   EXPECT_FALSE(request_key_frame);
   EXPECT_TRUE(DecodeCompleteFrame());
-  stream_generator->GenerateFrame(kVideoFrameDelta, 100, 0,
+  stream_generator_->GenerateFrame(kVideoFrameDelta, 100, 0,
                                   clock_->TimeInMilliseconds());
   EXPECT_EQ(kFirstPacket, InsertPacketAndPop(0));
-  while (stream_generator->PacketsRemaining() > 1) {
-    if (stream_generator->NextSequenceNumber() % 10 != 0) {
+  while (stream_generator_->PacketsRemaining() > 1) {
+    if (stream_generator_->NextSequenceNumber() % 10 != 0) {
       EXPECT_EQ(kIncomplete, InsertPacketAndPop(0));
       EXPECT_FALSE(request_key_frame);
     } else {
-      stream_generator->NextPacket(NULL);  // Drop packet.
+      stream_generator_->NextPacket(NULL);  // Drop packet
     }
   }
   EXPECT_EQ(kIncomplete, InsertPacketAndPop(0));
   EXPECT_FALSE(request_key_frame);
-  EXPECT_EQ(0, stream_generator->PacketsRemaining());
+  EXPECT_EQ(0, stream_generator_->PacketsRemaining());
   EXPECT_FALSE(DecodeCompleteFrame());
   EXPECT_FALSE(DecodeCompleteFrame());
   uint16_t nack_list_size = 0;
diff --git a/webrtc/modules/video_coding/main/source/receiver.cc b/webrtc/modules/video_coding/main/source/receiver.cc
index fffe609..65d7a68 100644
--- a/webrtc/modules/video_coding/main/source/receiver.cc
+++ b/webrtc/modules/video_coding/main/source/receiver.cc
@@ -432,6 +432,10 @@
   return 0;
 }
 
+int VCMReceiver::RenderBufferSizeMs() {
+  return jitter_buffer_.RenderBufferSizeMs();
+}
+
 void VCMReceiver::UpdateState(VCMReceiverState new_state) {
   CriticalSectionScoped cs(crit_sect_);
   assert(!(state_ == kPassive && new_state == kWaitForPrimaryDecode));
diff --git a/webrtc/modules/video_coding/main/source/receiver.h b/webrtc/modules/video_coding/main/source/receiver.h
index f0cf865..a62ae2f 100644
--- a/webrtc/modules/video_coding/main/source/receiver.h
+++ b/webrtc/modules/video_coding/main/source/receiver.h
@@ -80,6 +80,10 @@
   void SetDecodeWithErrors(bool enable);
   bool DecodeWithErrors() const;
 
+  // Returns size in time (milliseconds) of complete continuous frames in the
+  // jitter buffer.
+  int RenderBufferSizeMs();
+
  private:
   VCMEncodedFrame* FrameForDecoding(uint16_t max_wait_time_ms,
                                     int64_t nextrender_time_ms,
diff --git a/webrtc/modules/video_coding/main/source/receiver_unittest.cc b/webrtc/modules/video_coding/main/source/receiver_unittest.cc
new file mode 100644
index 0000000..8854335
--- /dev/null
+++ b/webrtc/modules/video_coding/main/source/receiver_unittest.cc
@@ -0,0 +1,125 @@
+/*  Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <string.h>
+
+#include <list>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webrtc/modules/video_coding/main/source/receiver.h"
+#include "webrtc/modules/video_coding/main/source/packet.h"
+#include "webrtc/modules/video_coding/main/source/timing.h"
+#include "webrtc/modules/video_coding/main/test/test_util.h"
+#include "webrtc/modules/video_coding/main/source/stream_generator.h"
+#include "webrtc/system_wrappers/interface/clock.h"
+#include "webrtc/system_wrappers/interface/critical_section_wrapper.h"
+
+namespace webrtc {
+
+class TestVCMReceiver : public ::testing::Test {
+ protected:
+  enum { kDataBufferSize = 10 };
+  enum { kWidth = 640 };
+  enum { kHeight = 480 };
+
+  TestVCMReceiver()
+      : clock_(new SimulatedClock(0)),
+        timing_(clock_.get()),
+        receiver_(&timing_, clock_.get(), &event_factory_, 1, 1, true) {
+    stream_generator_.reset(new
+        StreamGenerator(0, 0, clock_->TimeInMilliseconds()));
+    memset(data_buffer_, 0, kDataBufferSize);
+  }
+
+  virtual void SetUp() {
+    receiver_.Reset();
+  }
+
+  int32_t InsertPacket(int index) {
+    VCMPacket packet;
+    packet.dataPtr = data_buffer_;
+    bool packet_available = stream_generator_->GetPacket(&packet, index);
+    EXPECT_TRUE(packet_available);
+    if (!packet_available)
+      return kStateError;  // Return here to avoid crashes below.
+    // Arbitrary width and height.
+    return receiver_.InsertPacket(packet, 640, 480);
+  }
+
+  int32_t InsertPacketAndPop(int index) {
+    VCMPacket packet;
+    packet.dataPtr = data_buffer_;
+    bool packet_available = stream_generator_->PopPacket(&packet, index);
+    EXPECT_TRUE(packet_available);
+    if (!packet_available)
+      return kStateError;  // Return here to avoid crashes below.
+    return receiver_.InsertPacket(packet, kWidth, kHeight);
+  }
+
+  int32_t InsertFrame(FrameType frame_type, bool complete) {
+    int num_of_packets = complete ? 1 : 2;
+    stream_generator_->GenerateFrame(
+        frame_type,
+        (frame_type != kFrameEmpty) ? num_of_packets : 0,
+        (frame_type == kFrameEmpty) ? 1 : 0,
+        clock_->TimeInMilliseconds());
+    int32_t ret = InsertPacketAndPop(0);
+    clock_->AdvanceTimeMilliseconds(kDefaultFramePeriodMs);
+    return ret;
+  }
+
+  scoped_ptr<SimulatedClock> clock_;
+  VCMTiming timing_;
+  NullEventFactory event_factory_;
+  VCMReceiver receiver_;
+  scoped_ptr<StreamGenerator> stream_generator_;
+  uint8_t data_buffer_[kDataBufferSize];
+};
+
+TEST_F(TestVCMReceiver, RenderBufferSize_AllComplete) {
+  EXPECT_EQ(0, receiver_.RenderBufferSizeMs());
+  EXPECT_GE(InsertFrame(kVideoFrameKey, true), kNoError);
+  int num_of_frames = 10;
+  for (int i = 0; i < num_of_frames; ++i) {
+    EXPECT_GE(InsertFrame(kVideoFrameDelta, true), kNoError);
+  }
+  EXPECT_EQ(num_of_frames  * kDefaultFramePeriodMs,
+      receiver_.RenderBufferSizeMs());
+}
+
+TEST_F(TestVCMReceiver, RenderBufferSize_NotAllComplete) {
+  EXPECT_EQ(0, receiver_.RenderBufferSizeMs());
+  EXPECT_GE(InsertFrame(kVideoFrameKey, true), kNoError);
+  int num_of_frames = 10;
+  for (int i = 0; i < num_of_frames; ++i) {
+    EXPECT_GE(InsertFrame(kVideoFrameDelta, true), kNoError);
+  }
+  num_of_frames++;
+  EXPECT_GE(InsertFrame(kVideoFrameDelta, false), kNoError);
+  for (int i = 0; i < num_of_frames; ++i) {
+    EXPECT_GE(InsertFrame(kVideoFrameDelta, true), kNoError);
+  }
+  EXPECT_EQ(num_of_frames * kDefaultFramePeriodMs,
+      receiver_.RenderBufferSizeMs());
+}
+
+TEST_F(TestVCMReceiver, RenderBufferSize_NoKeyFrame) {
+  EXPECT_EQ(0, receiver_.RenderBufferSizeMs());
+  int num_of_frames = 10;
+  for (int i = 0; i < num_of_frames; ++i) {
+    EXPECT_GE(InsertFrame(kVideoFrameDelta, true), kNoError);
+  }
+  EXPECT_GE(InsertFrame(kVideoFrameDelta, false), kNoError);
+  for (int i = 0; i < num_of_frames; ++i) {
+    EXPECT_GE(InsertFrame(kVideoFrameDelta, true), kNoError);
+  }
+  EXPECT_EQ(0, receiver_.RenderBufferSizeMs());
+}
+
+}  // namespace webrtc
diff --git a/webrtc/modules/video_coding/main/source/stream_generator.cc b/webrtc/modules/video_coding/main/source/stream_generator.cc
new file mode 100644
index 0000000..b4be4ab
--- /dev/null
+++ b/webrtc/modules/video_coding/main/source/stream_generator.cc
@@ -0,0 +1,145 @@
+/*
+ *  Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/modules/video_coding/main/source/stream_generator.h"
+
+#include <string.h>
+
+#include <list>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webrtc/modules/video_coding/main/source/packet.h"
+#include "webrtc/modules/video_coding/main/test/test_util.h"
+#include "webrtc/system_wrappers/interface/clock.h"
+
+
+namespace webrtc {
+
+StreamGenerator::StreamGenerator(uint16_t start_seq_num,
+                                 uint32_t start_timestamp,
+                                 int64_t current_time)
+    : packets_(),
+      sequence_number_(start_seq_num),
+      timestamp_(start_timestamp),
+      start_time_(current_time) {}
+
+void StreamGenerator::Init(uint16_t start_seq_num, uint32_t start_timestamp,
+                           int64_t current_time) {
+  packets_.clear();
+  sequence_number_ = start_seq_num;
+  timestamp_ = start_timestamp;
+  start_time_ = current_time;
+  memset(&packet_buffer, 0, sizeof(packet_buffer));
+}
+
+void StreamGenerator::GenerateFrame(FrameType type,
+                                    int num_media_packets,
+                                    int num_empty_packets,
+                                    int64_t current_time) {
+  timestamp_ += 90 * (current_time - start_time_);
+  // Move the sequence number counter if all packets from the previous frame
+  // wasn't collected.
+  sequence_number_ += packets_.size();
+  packets_.clear();
+  for (int i = 0; i < num_media_packets; ++i) {
+    const int packet_size = (kFrameSize + num_media_packets / 2) /
+        num_media_packets;
+    bool marker_bit = (i == num_media_packets - 1);
+    packets_.push_back(GeneratePacket(sequence_number_,
+                                      timestamp_,
+                                      packet_size,
+                                      (i == 0),
+                                      marker_bit,
+                                      type));
+    ++sequence_number_;
+  }
+  for (int i = 0; i < num_empty_packets; ++i) {
+    packets_.push_back(GeneratePacket(sequence_number_,
+                                      timestamp_,
+                                      0,
+                                      false,
+                                      false,
+                                      kFrameEmpty));
+    ++sequence_number_;
+  }
+}
+
+VCMPacket StreamGenerator::GeneratePacket(uint16_t sequence_number,
+                                          uint32_t timestamp,
+                                          unsigned int size,
+                                          bool first_packet,
+                                          bool marker_bit,
+                                          FrameType type) {
+  EXPECT_LT(size, kMaxPacketSize);
+  VCMPacket packet;
+  packet.seqNum = sequence_number;
+  packet.timestamp = timestamp;
+  packet.frameType = type;
+  packet.isFirstPacket = first_packet;
+  packet.markerBit = marker_bit;
+  packet.sizeBytes = size;
+  packet.dataPtr = packet_buffer;
+  if (packet.isFirstPacket)
+    packet.completeNALU = kNaluStart;
+  else if (packet.markerBit)
+    packet.completeNALU = kNaluEnd;
+  else
+    packet.completeNALU = kNaluIncomplete;
+  return packet;
+}
+
+bool StreamGenerator::PopPacket(VCMPacket* packet, int index) {
+  std::list<VCMPacket>::iterator it = GetPacketIterator(index);
+  if (it == packets_.end())
+    return false;
+  if (packet)
+    *packet = (*it);
+  packets_.erase(it);
+  return true;
+}
+
+bool StreamGenerator::GetPacket(VCMPacket* packet, int index) {
+  std::list<VCMPacket>::iterator it = GetPacketIterator(index);
+  if (it == packets_.end())
+    return false;
+  if (packet)
+    *packet = (*it);
+  return true;
+}
+
+bool StreamGenerator::NextPacket(VCMPacket* packet) {
+  if (packets_.empty())
+    return false;
+  if (packet != NULL)
+    *packet = packets_.front();
+  packets_.pop_front();
+  return true;
+}
+
+uint16_t StreamGenerator::NextSequenceNumber() const {
+  if (packets_.empty())
+    return sequence_number_;
+  return packets_.front().seqNum;
+}
+
+int StreamGenerator::PacketsRemaining() const {
+  return packets_.size();
+}
+
+std::list<VCMPacket>::iterator StreamGenerator::GetPacketIterator(int index) {
+  std::list<VCMPacket>::iterator it = packets_.begin();
+  for (int i = 0; i < index; ++i) {
+    ++it;
+    if (it == packets_.end()) break;
+  }
+  return it;
+}
+
+}  // namespace webrtc
diff --git a/webrtc/modules/video_coding/main/source/stream_generator.h b/webrtc/modules/video_coding/main/source/stream_generator.h
new file mode 100644
index 0000000..5630504
--- /dev/null
+++ b/webrtc/modules/video_coding/main/source/stream_generator.h
@@ -0,0 +1,72 @@
+/*
+ *  Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+#ifndef WEBRTC_MODULES_VIDEO_CODING_MAIN_SOURCE_STREAM_GENERATOR_H_
+#define WEBRTC_MODULES_VIDEO_CODING_MAIN_SOURCE_STREAM_GENERATOR_H_
+
+#include <string.h>
+
+#include <list>
+
+#include "webrtc/modules/video_coding/main/source/packet.h"
+#include "webrtc/modules/video_coding/main/test/test_util.h"
+
+namespace webrtc {
+
+const unsigned int kDefaultBitrateKbps = 1000;
+enum { kDefaultFrameRate = 25u };
+const unsigned int kMaxPacketSize = 1500;
+const unsigned int kFrameSize = (kDefaultBitrateKbps + kDefaultFrameRate * 4) /
+    (kDefaultFrameRate * 8);
+enum { kDefaultFramePeriodMs = 1000u / kDefaultFrameRate };
+
+
+
+class StreamGenerator {
+ public:
+  StreamGenerator(uint16_t start_seq_num, uint32_t start_timestamp,
+                  int64_t current_time);
+  void Init(uint16_t start_seq_num, uint32_t start_timestamp,
+            int64_t current_time);
+
+  void GenerateFrame(FrameType type, int num_media_packets,
+                     int num_empty_packets, int64_t current_time);
+
+  VCMPacket GeneratePacket(uint16_t sequence_number,
+                           uint32_t timestamp,
+                           unsigned int size,
+                           bool first_packet,
+                           bool marker_bit,
+                           FrameType type);
+
+  bool PopPacket(VCMPacket* packet, int index);
+
+  bool GetPacket(VCMPacket* packet, int index);
+
+  bool NextPacket(VCMPacket* packet);
+
+  uint16_t NextSequenceNumber() const;
+
+  int PacketsRemaining() const;
+
+ private:
+  std::list<VCMPacket>::iterator GetPacketIterator(int index);
+
+  std::list<VCMPacket> packets_;
+  uint16_t sequence_number_;
+  uint32_t timestamp_;
+  int64_t start_time_;
+  uint8_t packet_buffer[kMaxPacketSize];
+
+  DISALLOW_COPY_AND_ASSIGN(StreamGenerator);
+};
+
+}  // namespace webrtc
+
+#endif  // WEBRTC_MODULES_VIDEO_CODING_MAIN_SOURCE_STREAM_GENERATOR_H_
diff --git a/webrtc/modules/video_coding/main/source/video_coding_impl.cc b/webrtc/modules/video_coding/main/source/video_coding_impl.cc
index 74730ec..aa7cd49 100644
--- a/webrtc/modules/video_coding/main/source/video_coding_impl.cc
+++ b/webrtc/modules/video_coding/main/source/video_coding_impl.cc
@@ -62,6 +62,7 @@
       _frameStorageCallback(NULL),
       _receiveStatsCallback(NULL),
       _packetRequestCallback(NULL),
+      render_buffer_callback_(NULL),
       _decoder(NULL),
       _dualDecoder(NULL),
 #ifdef DEBUG_DECODER_BIT_STREAM
@@ -154,6 +155,12 @@
             _receiver.ReceiveStatistics(&bitRate, &frameRate);
             _receiveStatsCallback->ReceiveStatistics(bitRate, frameRate);
         }
+
+        // Size of render buffer.
+        if (render_buffer_callback_) {
+          int buffer_size_ms = _receiver.RenderBufferSizeMs();
+          render_buffer_callback_->RenderBufferSizeMs(buffer_size_ms);
+      }
     }
 
     // Send-side statistics
@@ -870,6 +877,13 @@
     return VCM_OK;
 }
 
+int VideoCodingModuleImpl::RegisterRenderBufferSizeCallback(
+  VCMRenderBufferSizeCallback* callback) {
+  CriticalSectionScoped cs(_receiveCritSect);
+  render_buffer_callback_ = callback;
+  return VCM_OK;
+}
+
 // Decode next frame, blocking.
 // Should be called as often as possible to get the most out of the decoder.
 int32_t
diff --git a/webrtc/modules/video_coding/main/source/video_coding_impl.h b/webrtc/modules/video_coding/main/source/video_coding_impl.h
index 91f151c..8497273 100644
--- a/webrtc/modules/video_coding/main/source/video_coding_impl.h
+++ b/webrtc/modules/video_coding/main/source/video_coding_impl.h
@@ -196,6 +196,10 @@
     virtual int32_t RegisterPacketRequestCallback(
         VCMPacketRequestCallback* callback);
 
+    // Render buffer size callback.
+    virtual int RegisterRenderBufferSizeCallback(
+        VCMRenderBufferSizeCallback* callback);
+
     // Decode next frame, blocks for a maximum of maxWaitTimeMs milliseconds.
     // Should be called as often as possible to get the most out of the decoder.
     virtual int32_t Decode(uint16_t maxWaitTimeMs = 200);
@@ -294,6 +298,7 @@
     VCMFrameStorageCallback*            _frameStorageCallback;
     VCMReceiveStatisticsCallback*       _receiveStatsCallback;
     VCMPacketRequestCallback*           _packetRequestCallback;
+    VCMRenderBufferSizeCallback*        render_buffer_callback_;
     VCMGenericDecoder*                  _decoder;
     VCMGenericDecoder*                  _dualDecoder;
 #ifdef DEBUG_DECODER_BIT_STREAM
diff --git a/webrtc/modules/video_coding/main/source/video_coding_test.gypi b/webrtc/modules/video_coding/main/source/video_coding_test.gypi
index f18048e..c3eed23 100644
--- a/webrtc/modules/video_coding/main/source/video_coding_test.gypi
+++ b/webrtc/modules/video_coding/main/source/video_coding_test.gypi
@@ -105,7 +105,10 @@
         '../interface/mock/mock_vcm_callbacks.h',
         'decoding_state_unittest.cc',
         'jitter_buffer_unittest.cc',
+        'receiver_unittest.cc',
         'session_info_unittest.cc',
+        'stream_generator.cc',
+        'stream_generator.h',
         'video_coding_robustness_unittest.cc',
         'video_coding_impl_unittest.cc',
         'qm_select_unittest.cc',