Make PayloadRouter own the picture id and tl0 pic idx sequences.

It previously owned only the picture id and only in the
WebRTC-VP8-Forced-Fallback-Encoder-v2 experiment.

Moving responsibility to PayloadRouter ensures that  both
picture id and tl0 idx are continuous over codec changes,
as required by the specs for VP8 and VP9 over RTP.

Bug: webrtc:8830
Change-Id: Ie77356dfec6d1e372b6970189e4c3888451920e6
Reviewed-on: https://webrtc-review.googlesource.com/61640
Commit-Queue: Niels Moller <nisse@webrtc.org>
Reviewed-by: Åsa Persson <asapersson@webrtc.org>
Reviewed-by: Rasmus Brandt <brandtr@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#22448}
diff --git a/modules/video_coding/codecs/vp9/vp9_impl.cc b/modules/video_coding/codecs/vp9/vp9_impl.cc
index c7c6247..06f485b 100644
--- a/modules/video_coding/codecs/vp9/vp9_impl.cc
+++ b/modules/video_coding/codecs/vp9/vp9_impl.cc
@@ -646,6 +646,8 @@
     vp9_info->inter_layer_predicted = true;
   }
 
+  vp9_info->first_frame_in_picture = is_first_frame;
+
   if (pkt.data.frame.flags & VPX_FRAME_IS_KEY) {
     frames_since_kf_ = 0;
   }
diff --git a/modules/video_coding/include/video_codec_interface.h b/modules/video_coding/include/video_codec_interface.h
index 6616053..7524631 100644
--- a/modules/video_coding/include/video_codec_interface.h
+++ b/modules/video_coding/include/video_codec_interface.h
@@ -28,23 +28,28 @@
 // Note: if any pointers are added to this struct, it must be fitted
 // with a copy-constructor. See below.
 struct CodecSpecificInfoVP8 {
+  // TODO(nisse): Delete, set by PayloadRouter.
   int16_t pictureId;  // Negative value to skip pictureId.
   bool nonReference;
   uint8_t simulcastIdx;
   uint8_t temporalIdx;
   bool layerSync;
+  // TODO(nisse): Delete, set by PayloadRouter.
   int tl0PicIdx;  // Negative value to skip tl0PicIdx.
   int8_t keyIdx;  // Negative value to skip keyIdx.
 };
 
 struct CodecSpecificInfoVP9 {
+  // TODO(nisse): Delete, set by PayloadRouter.
   int16_t picture_id;  // Negative value to skip pictureId.
 
+  bool first_frame_in_picture;  // First frame, increment picture_id.
   bool inter_pic_predicted;  // This layer frame is dependent on previously
                              // coded frame(s).
   bool flexible_mode;
   bool ss_data_available;
 
+  // TODO(nisse): Delete, set by PayloadRouter.
   int tl0_pic_idx;  // Negative value to skip tl0PicIdx.
   uint8_t temporal_idx;
   uint8_t spatial_idx;
diff --git a/video/payload_router.cc b/video/payload_router.cc
index 4ecb620..f980bc4 100644
--- a/video/payload_router.cc
+++ b/video/payload_router.cc
@@ -16,7 +16,6 @@
 #include "rtc_base/checks.h"
 #include "rtc_base/random.h"
 #include "rtc_base/timeutils.h"
-#include "system_wrappers/include/field_trial.h"
 
 namespace webrtc {
 
@@ -28,11 +27,9 @@
     case kVideoCodecVP8: {
       rtp->codec = kRtpVideoVp8;
       rtp->codecHeader.VP8.InitRTPVideoHeaderVP8();
-      rtp->codecHeader.VP8.pictureId = info->codecSpecific.VP8.pictureId;
       rtp->codecHeader.VP8.nonReference = info->codecSpecific.VP8.nonReference;
       rtp->codecHeader.VP8.temporalIdx = info->codecSpecific.VP8.temporalIdx;
       rtp->codecHeader.VP8.layerSync = info->codecSpecific.VP8.layerSync;
-      rtp->codecHeader.VP8.tl0PicIdx = info->codecSpecific.VP8.tl0PicIdx;
       rtp->codecHeader.VP8.keyIdx = info->codecSpecific.VP8.keyIdx;
       rtp->simulcastIdx = info->codecSpecific.VP8.simulcastIdx;
       return;
@@ -46,8 +43,6 @@
           info->codecSpecific.VP9.flexible_mode;
       rtp->codecHeader.VP9.ss_data_available =
           info->codecSpecific.VP9.ss_data_available;
-      rtp->codecHeader.VP9.picture_id = info->codecSpecific.VP9.picture_id;
-      rtp->codecHeader.VP9.tl0_pic_idx = info->codecSpecific.VP9.tl0_pic_idx;
       rtp->codecHeader.VP9.temporal_idx = info->codecSpecific.VP9.temporal_idx;
       rtp->codecHeader.VP9.spatial_idx = info->codecSpecific.VP9.spatial_idx;
       rtp->codecHeader.VP9.temporal_up_switch =
@@ -93,9 +88,8 @@
 
 }  // namespace
 
-// Currently only used if forced fallback for VP8 is enabled.
-// Consider adding tl0idx and set for VP8 and VP9.
-// Make picture id not codec specific.
+// State for setting picture id and tl0 pic idx, for VP8 and VP9
+// TODO(nisse): Make these properties not codec specific.
 class PayloadRouter::RtpPayloadParams final {
  public:
   RtpPayloadParams(const uint32_t ssrc, const RtpPayloadState* state)
@@ -103,14 +97,42 @@
     Random random(rtc::TimeMicros());
     state_.picture_id =
         state ? state->picture_id : (random.Rand<int16_t>() & 0x7FFF);
+    state_.tl0_pic_idx = state ? state->tl0_pic_idx : (random.Rand<uint8_t>());
   }
   ~RtpPayloadParams() {}
 
-  void Set(RTPVideoHeader* rtp_video_header) {
-    if (rtp_video_header->codec == kRtpVideoVp8 &&
-        rtp_video_header->codecHeader.VP8.pictureId != kNoPictureId) {
+  void Set(RTPVideoHeader* rtp_video_header, bool first_frame_in_picture) {
+    // Always set picture id. Set tl0_pic_idx iff temporal index is set.
+    if (first_frame_in_picture) {
+      state_.picture_id =
+          (static_cast<uint16_t>(state_.picture_id) + 1) & 0x7FFF;
+    }
+    if (rtp_video_header->codec == kRtpVideoVp8) {
       rtp_video_header->codecHeader.VP8.pictureId = state_.picture_id;
-      state_.picture_id = (state_.picture_id + 1) & 0x7FFF;
+
+      if (rtp_video_header->codecHeader.VP8.temporalIdx != kNoTemporalIdx) {
+        if (rtp_video_header->codecHeader.VP8.temporalIdx == 0) {
+          ++state_.tl0_pic_idx;
+        }
+        rtp_video_header->codecHeader.VP8.tl0PicIdx = state_.tl0_pic_idx;
+      }
+    }
+    if (rtp_video_header->codec == kRtpVideoVp9) {
+      rtp_video_header->codecHeader.VP9.picture_id = state_.picture_id;
+
+      // Note that in the case that we have no temporal layers but we do have
+      // spatial layers, packets will carry layering info with a temporal_idx of
+      // zero, and we then have to set and increment tl0_pic_idx.
+      if (rtp_video_header->codecHeader.VP9.temporal_idx != kNoTemporalIdx ||
+          rtp_video_header->codecHeader.VP9.spatial_idx != kNoSpatialIdx) {
+        if (first_frame_in_picture &&
+            (rtp_video_header->codecHeader.VP9.temporal_idx == 0 ||
+             rtp_video_header->codecHeader.VP9.temporal_idx ==
+                 kNoTemporalIdx)) {
+          ++state_.tl0_pic_idx;
+        }
+        rtp_video_header->codecHeader.VP9.tl0_pic_idx = state_.tl0_pic_idx;
+      }
     }
   }
 
@@ -127,11 +149,7 @@
                              const std::vector<uint32_t>& ssrcs,
                              int payload_type,
                              const std::map<uint32_t, RtpPayloadState>& states)
-    : active_(false),
-      rtp_modules_(rtp_modules),
-      payload_type_(payload_type),
-      forced_fallback_enabled_((webrtc::field_trial::IsEnabled(
-          "WebRTC-VP8-Forced-Fallback-Encoder-v2"))) {
+    : active_(false), rtp_modules_(rtp_modules), payload_type_(payload_type) {
   RTC_DCHECK_EQ(ssrcs.size(), rtp_modules.size());
   // SSRCs are assumed to be sorted in the same order as |rtp_modules|.
   for (uint32_t ssrc : ssrcs) {
@@ -221,12 +239,14 @@
 
   int stream_index = rtp_video_header.simulcastIdx;
   RTC_DCHECK_LT(stream_index, rtp_modules_.size());
-  if (forced_fallback_enabled_) {
-    // Sets picture id. The SW and HW encoder have separate picture id
-    // sequences, set picture id to not cause sequence discontinuties at encoder
-    // changes.
-    params_[stream_index].Set(&rtp_video_header);
-  }
+
+  // Sets picture id and tl0 pic idx.
+  const bool first_frame_in_picture =
+      (codec_specific_info && codec_specific_info->codecType == kVideoCodecVP9)
+          ? codec_specific_info->codecSpecific.VP9.first_frame_in_picture
+          : true;
+  params_[stream_index].Set(&rtp_video_header, first_frame_in_picture);
+
   uint32_t frame_id;
   if (!rtp_modules_[stream_index]->Sending()) {
     // The payload router could be active but this module isn't sending.
diff --git a/video/payload_router.h b/video/payload_router.h
index 169eda4..e32d607 100644
--- a/video/payload_router.h
+++ b/video/payload_router.h
@@ -29,6 +29,7 @@
 // Currently only VP8/VP9 specific.
 struct RtpPayloadState {
   int16_t picture_id = -1;
+  uint8_t tl0_pic_idx = 0;
 };
 
 // PayloadRouter routes outgoing data to the correct sending RTP module, based
@@ -73,7 +74,6 @@
   const std::vector<RtpRtcp*> rtp_modules_;
   const int payload_type_;
 
-  const bool forced_fallback_enabled_;
   std::vector<RtpPayloadParams> params_ RTC_GUARDED_BY(crit_);
 
   RTC_DISALLOW_COPY_AND_ASSIGN(PayloadRouter);
diff --git a/video/payload_router_unittest.cc b/video/payload_router_unittest.cc
index b193da8..9e09dab 100644
--- a/video/payload_router_unittest.cc
+++ b/video/payload_router_unittest.cc
@@ -38,6 +38,8 @@
 const uint8_t kTemporalIdx = 1;
 const int16_t kInitialPictureId1 = 222;
 const int16_t kInitialPictureId2 = 44;
+const int16_t kInitialTl0PicIdx1 = 99;
+const int16_t kInitialTl0PicIdx2 = 199;
 }  // namespace
 
 TEST(PayloadRouterTest, SendOnOneModule) {
@@ -307,7 +309,12 @@
   NiceMock<MockRtpRtcp> rtp1;
   NiceMock<MockRtpRtcp> rtp2;
   std::vector<RtpRtcp*> modules = {&rtp1, &rtp2};
-  PayloadRouter payload_router(modules, {kSsrc1, kSsrc2}, kPayloadType, {});
+  RtpPayloadState state2;
+  state2.picture_id = kPictureId;
+  state2.tl0_pic_idx = kTl0PicIdx;
+  std::map<uint32_t, RtpPayloadState> states = {{kSsrc2, state2}};
+
+  PayloadRouter payload_router(modules, {kSsrc1, kSsrc2}, kPayloadType, states);
   payload_router.SetActive(true);
 
   EncodedImage encoded_image;
@@ -318,9 +325,8 @@
   memset(&codec_info, 0, sizeof(CodecSpecificInfo));
   codec_info.codecType = kVideoCodecVP8;
   codec_info.codecSpecific.VP8.simulcastIdx = 1;
-  codec_info.codecSpecific.VP8.pictureId = kPictureId;
+  codec_info.codecSpecific.VP8.pictureId = -1;
   codec_info.codecSpecific.VP8.temporalIdx = kTemporalIdx;
-  codec_info.codecSpecific.VP8.tl0PicIdx = kTl0PicIdx;
   codec_info.codecSpecific.VP8.keyIdx = kNoKeyIdx;
   codec_info.codecSpecific.VP8.layerSync = true;
   codec_info.codecSpecific.VP8.nonReference = true;
@@ -333,7 +339,7 @@
         EXPECT_EQ(VideoContentType::SCREENSHARE, header->content_type);
         EXPECT_EQ(1, header->simulcastIdx);
         EXPECT_EQ(kRtpVideoVp8, header->codec);
-        EXPECT_EQ(kPictureId, header->codecHeader.VP8.pictureId);
+        EXPECT_EQ(kPictureId + 1, header->codecHeader.VP8.pictureId);
         EXPECT_EQ(kTemporalIdx, header->codecHeader.VP8.temporalIdx);
         EXPECT_EQ(kTl0PicIdx, header->codecHeader.VP8.tl0PicIdx);
         EXPECT_EQ(kNoKeyIdx, header->codecHeader.VP8.keyIdx);
@@ -393,8 +399,10 @@
 TEST(PayloadRouterTest, CreateWithPreviousStates) {
   RtpPayloadState state1;
   state1.picture_id = kInitialPictureId1;
+  state1.tl0_pic_idx = kInitialTl0PicIdx1;
   RtpPayloadState state2;
   state2.picture_id = kInitialPictureId2;
+  state2.tl0_pic_idx = kInitialTl0PicIdx2;
   std::map<uint32_t, RtpPayloadState> states = {{kSsrc1, state1},
                                                 {kSsrc2, state2}};
 
@@ -408,63 +416,18 @@
       payload_router.GetRtpPayloadStates();
   EXPECT_EQ(2u, initial_states.size());
   EXPECT_EQ(kInitialPictureId1, initial_states[kSsrc1].picture_id);
+  EXPECT_EQ(kInitialTl0PicIdx1, initial_states[kSsrc1].tl0_pic_idx);
   EXPECT_EQ(kInitialPictureId2, initial_states[kSsrc2].picture_id);
+  EXPECT_EQ(kInitialTl0PicIdx2, initial_states[kSsrc2].tl0_pic_idx);
 }
 
-class PayloadRouterTest : public ::testing::Test {
- public:
-  explicit PayloadRouterTest(const std::string& field_trials)
-      : override_field_trials_(field_trials) {}
-  virtual ~PayloadRouterTest() {}
-
- protected:
-  virtual void SetUp() { memset(&codec_info_, 0, sizeof(CodecSpecificInfo)); }
-
-  test::ScopedFieldTrials override_field_trials_;
-  EncodedImage image_;
-  CodecSpecificInfo codec_info_;
-};
-
-class TestWithForcedFallbackDisabled : public PayloadRouterTest {
- public:
-  TestWithForcedFallbackDisabled()
-      : PayloadRouterTest("WebRTC-VP8-Forced-Fallback-Encoder-v2/Disabled/") {}
-};
-
-class TestWithForcedFallbackEnabled : public PayloadRouterTest {
- public:
-  TestWithForcedFallbackEnabled()
-      : PayloadRouterTest(
-            "WebRTC-VP8-Forced-Fallback-Encoder-v2/Enabled-1,2,3/") {}
-};
-
-TEST_F(TestWithForcedFallbackDisabled, PictureIdIsNotChangedForVp8) {
-  NiceMock<MockRtpRtcp> rtp;
-  std::vector<RtpRtcp*> modules = {&rtp};
-  PayloadRouter router(modules, {kSsrc1}, kPayloadType, {});
-  router.SetActive(true);
-
-  codec_info_.codecType = kVideoCodecVP8;
-  codec_info_.codecSpecific.VP8.pictureId = kPictureId;
-
-  EXPECT_CALL(rtp, SendOutgoingData(_, _, _, _, _, _, nullptr, _, _))
-      .WillOnce(Invoke([](Unused, Unused, Unused, Unused, Unused, Unused,
-                          Unused, const RTPVideoHeader* header, Unused) {
-        EXPECT_EQ(kRtpVideoVp8, header->codec);
-        EXPECT_EQ(kPictureId, header->codecHeader.VP8.pictureId);
-        return true;
-      }));
-  EXPECT_CALL(rtp, Sending()).WillOnce(Return(true));
-
-  EXPECT_EQ(EncodedImageCallback::Result::OK,
-            router.OnEncodedImage(image_, &codec_info_, nullptr).error);
-}
-
-TEST_F(TestWithForcedFallbackEnabled, PictureIdIsSetForVp8) {
+TEST(PayloadRouterTest, PictureIdIsSetForVp8) {
   RtpPayloadState state1;
   state1.picture_id = kInitialPictureId1;
+  state1.tl0_pic_idx = kInitialTl0PicIdx1;
   RtpPayloadState state2;
   state2.picture_id = kInitialPictureId2;
+  state2.tl0_pic_idx = kInitialTl0PicIdx2;
   std::map<uint32_t, RtpPayloadState> states = {{kSsrc1, state1},
                                                 {kSsrc2, state2}};
 
@@ -474,119 +437,210 @@
   PayloadRouter router(modules, {kSsrc1, kSsrc2}, kPayloadType, states);
   router.SetActive(true);
 
+  EncodedImage encoded_image;
   // Modules are sending for this test.
   // OnEncodedImage, simulcastIdx: 0.
-  codec_info_.codecType = kVideoCodecVP8;
-  codec_info_.codecSpecific.VP8.pictureId = kPictureId;
-  codec_info_.codecSpecific.VP8.simulcastIdx = 0;
+  CodecSpecificInfo codec_info;
+  memset(&codec_info, 0, sizeof(CodecSpecificInfo));
+  codec_info.codecType = kVideoCodecVP8;
+  codec_info.codecSpecific.VP8.simulcastIdx = 0;
 
   EXPECT_CALL(rtp1, SendOutgoingData(_, _, _, _, _, _, nullptr, _, _))
       .WillOnce(Invoke([](Unused, Unused, Unused, Unused, Unused, Unused,
                           Unused, const RTPVideoHeader* header, Unused) {
         EXPECT_EQ(kRtpVideoVp8, header->codec);
-        EXPECT_EQ(kInitialPictureId1, header->codecHeader.VP8.pictureId);
+        EXPECT_EQ(kInitialPictureId1 + 1, header->codecHeader.VP8.pictureId);
         return true;
       }));
   EXPECT_CALL(rtp1, Sending()).WillOnce(Return(true));
 
   EXPECT_EQ(EncodedImageCallback::Result::OK,
-            router.OnEncodedImage(image_, &codec_info_, nullptr).error);
+            router.OnEncodedImage(encoded_image, &codec_info, nullptr).error);
 
   // OnEncodedImage, simulcastIdx: 1.
-  codec_info_.codecSpecific.VP8.pictureId = kPictureId;
-  codec_info_.codecSpecific.VP8.simulcastIdx = 1;
+  codec_info.codecSpecific.VP8.simulcastIdx = 1;
 
   EXPECT_CALL(rtp2, SendOutgoingData(_, _, _, _, _, _, nullptr, _, _))
       .WillOnce(Invoke([](Unused, Unused, Unused, Unused, Unused, Unused,
                           Unused, const RTPVideoHeader* header, Unused) {
         EXPECT_EQ(kRtpVideoVp8, header->codec);
-        EXPECT_EQ(kInitialPictureId2, header->codecHeader.VP8.pictureId);
+        EXPECT_EQ(kInitialPictureId2 + 1, header->codecHeader.VP8.pictureId);
         return true;
       }));
   EXPECT_CALL(rtp2, Sending()).WillOnce(Return(true));
 
   EXPECT_EQ(EncodedImageCallback::Result::OK,
-            router.OnEncodedImage(image_, &codec_info_, nullptr).error);
+            router.OnEncodedImage(encoded_image, &codec_info, nullptr).error);
 
-  // State should hold next picture id to use.
+  // State should hold latest used picture id and tl0_pic_idx.
   states = router.GetRtpPayloadStates();
   EXPECT_EQ(2u, states.size());
   EXPECT_EQ(kInitialPictureId1 + 1, states[kSsrc1].picture_id);
+  EXPECT_EQ(kInitialTl0PicIdx1 + 1, states[kSsrc1].tl0_pic_idx);
   EXPECT_EQ(kInitialPictureId2 + 1, states[kSsrc2].picture_id);
+  EXPECT_EQ(kInitialTl0PicIdx2 + 1, states[kSsrc2].tl0_pic_idx);
 }
 
-TEST_F(TestWithForcedFallbackEnabled, PictureIdWraps) {
+TEST(PayloadRouterTest, PictureIdWraps) {
   RtpPayloadState state1;
   state1.picture_id = kMaxTwoBytePictureId;
+  state1.tl0_pic_idx = kInitialTl0PicIdx1;
 
   NiceMock<MockRtpRtcp> rtp;
   std::vector<RtpRtcp*> modules = {&rtp};
   PayloadRouter router(modules, {kSsrc1}, kPayloadType, {{kSsrc1, state1}});
   router.SetActive(true);
 
-  codec_info_.codecType = kVideoCodecVP8;
-  codec_info_.codecSpecific.VP8.pictureId = kPictureId;
+  EncodedImage encoded_image;
+  CodecSpecificInfo codec_info;
+  memset(&codec_info, 0, sizeof(CodecSpecificInfo));
+  codec_info.codecType = kVideoCodecVP8;
+  codec_info.codecSpecific.VP8.temporalIdx = kNoTemporalIdx;
 
   EXPECT_CALL(rtp, SendOutgoingData(_, _, _, _, _, _, nullptr, _, _))
       .WillOnce(Invoke([](Unused, Unused, Unused, Unused, Unused, Unused,
                           Unused, const RTPVideoHeader* header, Unused) {
         EXPECT_EQ(kRtpVideoVp8, header->codec);
-        EXPECT_EQ(kMaxTwoBytePictureId, header->codecHeader.VP8.pictureId);
+        EXPECT_EQ(0, header->codecHeader.VP8.pictureId);
         return true;
       }));
   EXPECT_CALL(rtp, Sending()).WillOnce(Return(true));
 
   EXPECT_EQ(EncodedImageCallback::Result::OK,
-            router.OnEncodedImage(image_, &codec_info_, nullptr).error);
+            router.OnEncodedImage(encoded_image, &codec_info, nullptr).error);
 
-  // State should hold next picture id to use.
+  // State should hold latest used picture id and tl0_pic_idx.
   std::map<uint32_t, RtpPayloadState> states = router.GetRtpPayloadStates();
   EXPECT_EQ(1u, states.size());
   EXPECT_EQ(0, states[kSsrc1].picture_id);  // Wrapped.
+  EXPECT_EQ(kInitialTl0PicIdx1, states[kSsrc1].tl0_pic_idx);
 }
 
-TEST_F(TestWithForcedFallbackEnabled, PictureIdIsNotSetIfNoPictureId) {
+TEST(PayloadRouterTest, Tl0PicIdxUpdatedForVp8) {
+  RtpPayloadState state;
+  state.picture_id = kInitialPictureId1;
+  state.tl0_pic_idx = kInitialTl0PicIdx1;
+  std::map<uint32_t, RtpPayloadState> states = {{kSsrc1, state}};
+
   NiceMock<MockRtpRtcp> rtp;
   std::vector<RtpRtcp*> modules = {&rtp};
-  PayloadRouter router(modules, {kSsrc1}, kPayloadType, {});
+  PayloadRouter router(modules, {kSsrc1}, kPayloadType, states);
   router.SetActive(true);
 
-  codec_info_.codecType = kVideoCodecVP8;
-  codec_info_.codecSpecific.VP8.pictureId = kNoPictureId;
+  EncodedImage encoded_image;
+  // Modules are sending for this test.
+  // OnEncodedImage, temporalIdx: 1.
+  CodecSpecificInfo codec_info;
+  memset(&codec_info, 0, sizeof(CodecSpecificInfo));
+  codec_info.codecType = kVideoCodecVP8;
+  codec_info.codecSpecific.VP8.temporalIdx = 1;
 
   EXPECT_CALL(rtp, SendOutgoingData(_, _, _, _, _, _, nullptr, _, _))
       .WillOnce(Invoke([](Unused, Unused, Unused, Unused, Unused, Unused,
                           Unused, const RTPVideoHeader* header, Unused) {
         EXPECT_EQ(kRtpVideoVp8, header->codec);
-        EXPECT_EQ(kNoPictureId, header->codecHeader.VP8.pictureId);
+        EXPECT_EQ(kInitialPictureId1 + 1, header->codecHeader.VP8.pictureId);
+        EXPECT_EQ(kInitialTl0PicIdx1, header->codecHeader.VP8.tl0PicIdx);
         return true;
       }));
   EXPECT_CALL(rtp, Sending()).WillOnce(Return(true));
 
   EXPECT_EQ(EncodedImageCallback::Result::OK,
-            router.OnEncodedImage(image_, &codec_info_, nullptr).error);
+            router.OnEncodedImage(encoded_image, &codec_info, nullptr).error);
+
+  // OnEncodedImage, temporalIdx: 0.
+  codec_info.codecSpecific.VP8.temporalIdx = 0;
+
+  EXPECT_CALL(rtp, SendOutgoingData(_, _, _, _, _, _, nullptr, _, _))
+      .WillOnce(Invoke([](Unused, Unused, Unused, Unused, Unused, Unused,
+                          Unused, const RTPVideoHeader* header, Unused) {
+        EXPECT_EQ(kRtpVideoVp8, header->codec);
+        EXPECT_EQ(kInitialPictureId1 + 2, header->codecHeader.VP8.pictureId);
+        EXPECT_EQ(kInitialTl0PicIdx1 + 1, header->codecHeader.VP8.tl0PicIdx);
+        return true;
+      }));
+  EXPECT_CALL(rtp, Sending()).WillOnce(Return(true));
+
+  EXPECT_EQ(EncodedImageCallback::Result::OK,
+            router.OnEncodedImage(encoded_image, &codec_info, nullptr).error);
+
+  // State should hold latest used picture id and tl0_pic_idx.
+  states = router.GetRtpPayloadStates();
+  EXPECT_EQ(1u, states.size());
+  EXPECT_EQ(kInitialPictureId1 + 2, states[kSsrc1].picture_id);
+  EXPECT_EQ(kInitialTl0PicIdx1 + 1, states[kSsrc1].tl0_pic_idx);
 }
 
-TEST_F(TestWithForcedFallbackEnabled, PictureIdIsNotSetForVp9) {
+TEST(PayloadRouterTest, Tl0PicIdxUpdatedForVp9) {
+  RtpPayloadState state;
+  state.picture_id = kInitialPictureId1;
+  state.tl0_pic_idx = kInitialTl0PicIdx1;
+  std::map<uint32_t, RtpPayloadState> states = {{kSsrc1, state}};
+
   NiceMock<MockRtpRtcp> rtp;
   std::vector<RtpRtcp*> modules = {&rtp};
-  PayloadRouter router(modules, {kSsrc1}, kPayloadType, {});
+  PayloadRouter router(modules, {kSsrc1}, kPayloadType, states);
   router.SetActive(true);
 
-  codec_info_.codecType = kVideoCodecVP9;
-  codec_info_.codecSpecific.VP9.picture_id = kPictureId;
+  EncodedImage encoded_image;
+  // Modules are sending for this test.
+  // OnEncodedImage, temporalIdx: 1.
+  CodecSpecificInfo codec_info;
+  memset(&codec_info, 0, sizeof(CodecSpecificInfo));
+  codec_info.codecType = kVideoCodecVP9;
+  codec_info.codecSpecific.VP9.temporal_idx = 1;
+  codec_info.codecSpecific.VP9.first_frame_in_picture = true;
 
   EXPECT_CALL(rtp, SendOutgoingData(_, _, _, _, _, _, nullptr, _, _))
       .WillOnce(Invoke([](Unused, Unused, Unused, Unused, Unused, Unused,
                           Unused, const RTPVideoHeader* header, Unused) {
         EXPECT_EQ(kRtpVideoVp9, header->codec);
-        EXPECT_EQ(kPictureId, header->codecHeader.VP9.picture_id);
+        EXPECT_EQ(kInitialPictureId1 + 1, header->codecHeader.VP9.picture_id);
+        EXPECT_EQ(kInitialTl0PicIdx1, header->codecHeader.VP9.tl0_pic_idx);
         return true;
       }));
   EXPECT_CALL(rtp, Sending()).WillOnce(Return(true));
 
   EXPECT_EQ(EncodedImageCallback::Result::OK,
-            router.OnEncodedImage(image_, &codec_info_, nullptr).error);
+            router.OnEncodedImage(encoded_image, &codec_info, nullptr).error);
+
+  // OnEncodedImage, temporalIdx: 0.
+  codec_info.codecSpecific.VP9.temporal_idx = 0;
+
+  EXPECT_CALL(rtp, SendOutgoingData(_, _, _, _, _, _, nullptr, _, _))
+      .WillOnce(Invoke([](Unused, Unused, Unused, Unused, Unused, Unused,
+                          Unused, const RTPVideoHeader* header, Unused) {
+        EXPECT_EQ(kRtpVideoVp9, header->codec);
+        EXPECT_EQ(kInitialPictureId1 + 2, header->codecHeader.VP9.picture_id);
+        EXPECT_EQ(kInitialTl0PicIdx1 + 1, header->codecHeader.VP9.tl0_pic_idx);
+        return true;
+      }));
+  EXPECT_CALL(rtp, Sending()).WillOnce(Return(true));
+
+  EXPECT_EQ(EncodedImageCallback::Result::OK,
+            router.OnEncodedImage(encoded_image, &codec_info, nullptr).error);
+
+  // OnEncodedImage, first_frame_in_picture = false
+  codec_info.codecSpecific.VP9.first_frame_in_picture = false;
+
+  EXPECT_CALL(rtp, SendOutgoingData(_, _, _, _, _, _, nullptr, _, _))
+      .WillOnce(Invoke([](Unused, Unused, Unused, Unused, Unused, Unused,
+                          Unused, const RTPVideoHeader* header, Unused) {
+        EXPECT_EQ(kRtpVideoVp9, header->codec);
+        EXPECT_EQ(kInitialPictureId1 + 2, header->codecHeader.VP9.picture_id);
+        EXPECT_EQ(kInitialTl0PicIdx1 + 1, header->codecHeader.VP9.tl0_pic_idx);
+        return true;
+      }));
+  EXPECT_CALL(rtp, Sending()).WillOnce(Return(true));
+
+  EXPECT_EQ(EncodedImageCallback::Result::OK,
+            router.OnEncodedImage(encoded_image, &codec_info, nullptr).error);
+
+  // State should hold latest used picture id and tl0_pic_idx.
+  states = router.GetRtpPayloadStates();
+  EXPECT_EQ(1u, states.size());
+  EXPECT_EQ(kInitialPictureId1 + 2, states[kSsrc1].picture_id);
+  EXPECT_EQ(kInitialTl0PicIdx1 + 1, states[kSsrc1].tl0_pic_idx);
 }
 
 }  // namespace webrtc