Implement RTP keepalive in native stack.

BUG=webrtc:7907

Review-Url: https://codereview.webrtc.org/2960363002
Cr-Commit-Position: refs/heads/master@{#18912}
diff --git a/webrtc/common_types.h b/webrtc/common_types.h
index bf7566c..c3752f5 100644
--- a/webrtc/common_types.h
+++ b/webrtc/common_types.h
@@ -912,6 +912,17 @@
   kNetworkDown,
 };
 
+struct RtpKeepAliveConfig {
+  // If no packet has been sent for |timeout_interval_ms|, send a keep-alive
+  // packet. The keep-alive packet is an empty (no payload) RTP packet with a
+  // payload type of 20 as long as the other end has not negotiated the use of
+  // this value. If this value has already been negotiated, then some other
+  // unused static payload type from table 5 of RFC 3551 shall be used and set
+  // in |payload_type|.
+  int64_t timeout_interval_ms = -1;
+  uint8_t payload_type = 20;
+};
+
 }  // namespace webrtc
 
 #endif  // WEBRTC_COMMON_TYPES_H_
diff --git a/webrtc/modules/rtp_rtcp/include/rtp_rtcp.h b/webrtc/modules/rtp_rtcp/include/rtp_rtcp.h
index 6fc6b86..a5d1b0b 100644
--- a/webrtc/modules/rtp_rtcp/include/rtp_rtcp.h
+++ b/webrtc/modules/rtp_rtcp/include/rtp_rtcp.h
@@ -19,6 +19,7 @@
 #include "webrtc/base/constructormagic.h"
 #include "webrtc/base/deprecation.h"
 #include "webrtc/base/optional.h"
+#include "webrtc/common_types.h"
 #include "webrtc/modules/include/module.h"
 #include "webrtc/modules/rtp_rtcp/include/flexfec_sender.h"
 #include "webrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.h"
@@ -92,6 +93,7 @@
     SendPacketObserver* send_packet_observer = nullptr;
     RateLimiter* retransmission_rate_limiter = nullptr;
     OverheadObserver* overhead_observer = nullptr;
+    RtpKeepAliveConfig keepalive_config;
 
    private:
     RTC_DISALLOW_COPY_AND_ASSIGN(Configuration);
diff --git a/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.cc b/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.cc
index d79e689..446022b 100644
--- a/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.cc
+++ b/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.cc
@@ -12,6 +12,7 @@
 
 #include <string.h>
 
+#include <algorithm>
 #include <set>
 #include <string>
 
@@ -26,6 +27,11 @@
 #endif
 
 namespace webrtc {
+namespace {
+const int64_t kRtpRtcpMaxIdleTimeProcessMs = 5;
+const int64_t kRtpRtcpRttProcessTimeMs = 1000;
+const int64_t kRtpRtcpBitrateProcessTimeMs = 10;
+}  // namespace
 
 RTPExtensionType StringToRtpExtensionType(const std::string& extension) {
   if (extension == RtpExtension::kTimestampOffsetUri)
@@ -89,9 +95,12 @@
                      this),
       clock_(configuration.clock),
       audio_(configuration.audio),
-      last_process_time_(configuration.clock->TimeInMilliseconds()),
-      last_bitrate_process_time_(configuration.clock->TimeInMilliseconds()),
-      last_rtt_process_time_(configuration.clock->TimeInMilliseconds()),
+      keepalive_config_(configuration.keepalive_config),
+      last_bitrate_process_time_(clock_->TimeInMilliseconds()),
+      last_rtt_process_time_(clock_->TimeInMilliseconds()),
+      next_process_time_(clock_->TimeInMilliseconds() +
+                         kRtpRtcpMaxIdleTimeProcessMs),
+      next_keepalive_time_(-1),
       packet_overhead_(28),  // IPV4 UDP.
       nack_last_time_sent_full_(0),
       nack_last_time_sent_full_prev_(0),
@@ -118,6 +127,11 @@
         configuration.overhead_observer));
     // Make sure rtcp sender use same timestamp offset as rtp sender.
     rtcp_sender_.SetTimestampOffset(rtp_sender_->TimestampOffset());
+
+    if (keepalive_config_.timeout_interval_ms != -1) {
+      next_keepalive_time_ =
+          clock_->TimeInMilliseconds() + keepalive_config_.timeout_interval_ms;
+    }
   }
 
   // Set default packet size limit.
@@ -130,24 +144,38 @@
 // Returns the number of milliseconds until the module want a worker thread
 // to call Process.
 int64_t ModuleRtpRtcpImpl::TimeUntilNextProcess() {
-  const int64_t now = clock_->TimeInMilliseconds();
-  const int64_t kRtpRtcpMaxIdleTimeProcessMs = 5;
-  return kRtpRtcpMaxIdleTimeProcessMs - (now - last_process_time_);
+  return std::max<int64_t>(0,
+                           next_process_time_ - clock_->TimeInMilliseconds());
 }
 
 // Process any pending tasks such as timeouts (non time critical events).
 void ModuleRtpRtcpImpl::Process() {
   const int64_t now = clock_->TimeInMilliseconds();
-  last_process_time_ = now;
+  next_process_time_ = now + kRtpRtcpMaxIdleTimeProcessMs;
 
   if (rtp_sender_) {
-    const int64_t kRtpRtcpBitrateProcessTimeMs = 10;
     if (now >= last_bitrate_process_time_ + kRtpRtcpBitrateProcessTimeMs) {
       rtp_sender_->ProcessBitrate();
       last_bitrate_process_time_ = now;
+      next_process_time_ =
+          std::min(next_process_time_, now + kRtpRtcpBitrateProcessTimeMs);
+    }
+    if (keepalive_config_.timeout_interval_ms > 0 &&
+        now >= next_keepalive_time_) {
+      int64_t last_send_time_ms = rtp_sender_->LastTimestampTimeMs();
+      // If no packet has been sent, |last_send_time_ms| will be 0, and so the
+      // keep-alive will be triggered as expected.
+      if (now >= last_send_time_ms + keepalive_config_.timeout_interval_ms) {
+        rtp_sender_->SendKeepAlive(keepalive_config_.payload_type);
+        next_keepalive_time_ = now + keepalive_config_.timeout_interval_ms;
+      } else {
+        next_keepalive_time_ =
+            last_send_time_ms + keepalive_config_.timeout_interval_ms;
+      }
+      next_process_time_ = std::min(next_process_time_, next_keepalive_time_);
     }
   }
-  const int64_t kRtpRtcpRttProcessTimeMs = 1000;
+
   bool process_rtt = now >= last_rtt_process_time_ + kRtpRtcpRttProcessTimeMs;
   if (rtcp_sender_.Sending()) {
     // Process RTT if we have received a receiver report and we haven't
@@ -201,6 +229,8 @@
   // Get processed rtt.
   if (process_rtt) {
     last_rtt_process_time_ = now;
+    next_process_time_ = std::min(
+        next_process_time_, last_rtt_process_time_ + kRtpRtcpRttProcessTimeMs);
     if (rtt_stats_) {
       // Make sure we have a valid RTT before setting.
       int64_t last_rtt = rtt_stats_->LastProcessedRtt();
diff --git a/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.h b/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.h
index 0c46e40..edec70a 100644
--- a/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.h
+++ b/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.h
@@ -335,9 +335,12 @@
   const Clock* const clock_;
 
   const bool audio_;
-  int64_t last_process_time_;
+
+  const RtpKeepAliveConfig keepalive_config_;
   int64_t last_bitrate_process_time_;
   int64_t last_rtt_process_time_;
+  int64_t next_process_time_;
+  int64_t next_keepalive_time_;
   uint16_t packet_overhead_;
 
   // Send side
diff --git a/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl_unittest.cc b/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl_unittest.cc
index 73bdd7a..0c84719 100644
--- a/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl_unittest.cc
+++ b/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl_unittest.cc
@@ -53,11 +53,12 @@
                       public RtpData {
  public:
   SendTransport()
-      : receiver_(NULL),
-        clock_(NULL),
+      : receiver_(nullptr),
+        clock_(nullptr),
         delay_ms_(0),
-        rtp_packets_sent_(0) {
-  }
+        rtp_packets_sent_(0),
+        keepalive_payload_type_(0),
+        num_keepalive_sent_(0) {}
 
   void SetRtpRtcpModule(ModuleRtpRtcpImpl* receiver) {
     receiver_ = receiver;
@@ -73,6 +74,8 @@
     std::unique_ptr<RtpHeaderParser> parser(RtpHeaderParser::Create());
     EXPECT_TRUE(parser->Parse(static_cast<const uint8_t*>(data), len, &header));
     ++rtp_packets_sent_;
+    if (header.payloadType == keepalive_payload_type_)
+      ++num_keepalive_sent_;
     last_rtp_header_ = header;
     return true;
   }
@@ -93,12 +96,18 @@
                                 const WebRtcRTPHeader* rtp_header) override {
     return 0;
   }
+  void SetKeepalivePayloadType(uint8_t payload_type) {
+    keepalive_payload_type_ = payload_type;
+  }
+  size_t NumKeepaliveSent() { return num_keepalive_sent_; }
   ModuleRtpRtcpImpl* receiver_;
   SimulatedClock* clock_;
   int64_t delay_ms_;
   int rtp_packets_sent_;
   RTPHeader last_rtp_header_;
   std::vector<uint16_t> last_nack_list_;
+  uint8_t keepalive_payload_type_;
+  size_t num_keepalive_sent_;
 };
 
 class RtpRtcpModule : public RtcpPacketTypeCounterObserver {
@@ -106,19 +115,9 @@
   explicit RtpRtcpModule(SimulatedClock* clock)
       : receive_statistics_(ReceiveStatistics::Create(clock)),
         remote_ssrc_(0),
-        retransmission_rate_limiter_(clock, kMaxRttMs) {
-    RtpRtcp::Configuration config;
-    config.audio = false;
-    config.clock = clock;
-    config.outgoing_transport = &transport_;
-    config.receive_statistics = receive_statistics_.get();
-    config.rtcp_packet_type_counter_observer = this;
-    config.rtt_stats = &rtt_stats_;
-    config.retransmission_rate_limiter = &retransmission_rate_limiter_;
-
-    impl_.reset(new ModuleRtpRtcpImpl(config));
-    impl_->SetRTCPStatus(RtcpMode::kCompound);
-
+        retransmission_rate_limiter_(clock, kMaxRttMs),
+        clock_(clock) {
+    CreateModuleImpl();
     transport_.SimulateNetworkDelay(kOneWayNetworkDelayMs, clock);
   }
 
@@ -130,6 +129,7 @@
   std::unique_ptr<ModuleRtpRtcpImpl> impl_;
   uint32_t remote_ssrc_;
   RateLimiter retransmission_rate_limiter_;
+  RtpKeepAliveConfig keepalive_config_;
 
   void SetRemoteSsrc(uint32_t ssrc) {
     remote_ssrc_ = ssrc;
@@ -160,8 +160,30 @@
   std::vector<uint16_t> LastNackListSent() {
     return transport_.last_nack_list_;
   }
+  void SetKeepaliveConfigAndReset(const RtpKeepAliveConfig& config) {
+    keepalive_config_ = config;
+    // Need to create a new module impl, since it's configured at creation.
+    CreateModuleImpl();
+    transport_.SetKeepalivePayloadType(config.payload_type);
+  }
 
  private:
+  void CreateModuleImpl() {
+    RtpRtcp::Configuration config;
+    config.audio = false;
+    config.clock = clock_;
+    config.outgoing_transport = &transport_;
+    config.receive_statistics = receive_statistics_.get();
+    config.rtcp_packet_type_counter_observer = this;
+    config.rtt_stats = &rtt_stats_;
+    config.retransmission_rate_limiter = &retransmission_rate_limiter_;
+    config.keepalive_config = keepalive_config_;
+
+    impl_.reset(new ModuleRtpRtcpImpl(config));
+    impl_->SetRTCPStatus(RtcpMode::kCompound);
+  }
+
+  SimulatedClock* const clock_;
   std::map<uint32_t, RtcpPacketTypeCounter> counter_map_;
 };
 }  // namespace
@@ -169,9 +191,9 @@
 class RtpRtcpImplTest : public ::testing::Test {
  protected:
   RtpRtcpImplTest()
-      : clock_(133590000000000),
-        sender_(&clock_),
-        receiver_(&clock_) {
+      : clock_(133590000000000), sender_(&clock_), receiver_(&clock_) {}
+
+  void SetUp() override {
     // Send module.
     sender_.impl_->SetSSRC(kSenderSsrc);
     EXPECT_EQ(0, sender_.impl_->SetSendingStatus(true));
@@ -196,6 +218,7 @@
     sender_.transport_.SetRtpRtcpModule(receiver_.impl_.get());
     receiver_.transport_.SetRtpRtcpModule(sender_.impl_.get());
   }
+
   SimulatedClock clock_;
   RtpRtcpModule sender_;
   RtpRtcpModule receiver_;
@@ -567,4 +590,58 @@
   EXPECT_EQ(6U, sender_.RtcpReceived().unique_nack_requests);
   EXPECT_EQ(75, sender_.RtcpReceived().UniqueNackRequestsInPercent());
 }
+
+TEST_F(RtpRtcpImplTest, SendsKeepaliveAfterTimout) {
+  const int kTimeoutMs = 1500;
+
+  RtpKeepAliveConfig config;
+  config.timeout_interval_ms = kTimeoutMs;
+
+  // Recreate sender impl with new configuration, and redo setup.
+  sender_.SetKeepaliveConfigAndReset(config);
+  SetUp();
+
+  // Initial process call.
+  sender_.impl_->Process();
+  EXPECT_EQ(0U, sender_.transport_.NumKeepaliveSent());
+
+  // After one time, a single keep-alive packet should be sent.
+  clock_.AdvanceTimeMilliseconds(kTimeoutMs);
+  sender_.impl_->Process();
+  EXPECT_EQ(1U, sender_.transport_.NumKeepaliveSent());
+
+  // Process for the same timestamp again, no new packet should be sent.
+  sender_.impl_->Process();
+  EXPECT_EQ(1U, sender_.transport_.NumKeepaliveSent());
+
+  // Move ahead to the last ms before a keep-alive is expected, no action.
+  clock_.AdvanceTimeMilliseconds(kTimeoutMs - 1);
+  sender_.impl_->Process();
+  EXPECT_EQ(1U, sender_.transport_.NumKeepaliveSent());
+
+  // Move the final ms, timeout relative last KA. Should create new keep-alive.
+  clock_.AdvanceTimeMilliseconds(1);
+  sender_.impl_->Process();
+  EXPECT_EQ(2U, sender_.transport_.NumKeepaliveSent());
+
+  // Move ahead to the last ms before Christmas.
+  clock_.AdvanceTimeMilliseconds(kTimeoutMs - 1);
+  sender_.impl_->Process();
+  EXPECT_EQ(2U, sender_.transport_.NumKeepaliveSent());
+
+  // Send actual payload data, no keep-alive expected.
+  SendFrame(&sender_, 0);
+  sender_.impl_->Process();
+  EXPECT_EQ(2U, sender_.transport_.NumKeepaliveSent());
+
+  // Move ahead as far as possible again, timeout now relative payload. No KA.
+  clock_.AdvanceTimeMilliseconds(kTimeoutMs - 1);
+  sender_.impl_->Process();
+  EXPECT_EQ(2U, sender_.transport_.NumKeepaliveSent());
+
+  // Timeout relative payload, send new keep-alive.
+  clock_.AdvanceTimeMilliseconds(1);
+  sender_.impl_->Process();
+  EXPECT_EQ(3U, sender_.transport_.NumKeepaliveSent());
+}
 }  // namespace webrtc
diff --git a/webrtc/modules/rtp_rtcp/source/rtp_sender.cc b/webrtc/modules/rtp_rtcp/source/rtp_sender.cc
index 759bc9c..06c2ea9 100644
--- a/webrtc/modules/rtp_rtcp/source/rtp_sender.cc
+++ b/webrtc/modules/rtp_rtcp/source/rtp_sender.cc
@@ -1289,4 +1289,24 @@
   overhead_observer_->OnOverheadChanged(overhead_bytes_per_packet);
 }
 
+int64_t RTPSender::LastTimestampTimeMs() const {
+  rtc::CritScope lock(&send_critsect_);
+  return last_timestamp_time_ms_;
+}
+
+void RTPSender::SendKeepAlive(uint8_t payload_type) {
+  std::unique_ptr<RtpPacketToSend> packet = AllocatePacket();
+  packet->SetPayloadType(payload_type);
+  // Set marker bit and timestamps in the same manner as plain padding packets.
+  packet->SetMarker(false);
+  {
+    rtc::CritScope lock(&send_critsect_);
+    packet->SetTimestamp(last_rtp_timestamp_);
+    packet->set_capture_time_ms(capture_time_ms_);
+  }
+  AssignSequenceNumber(packet.get());
+  SendToNetwork(std::move(packet), StorageType::kDontRetransmit,
+                RtpPacketSender::Priority::kLowPriority);
+}
+
 }  // namespace webrtc
diff --git a/webrtc/modules/rtp_rtcp/source/rtp_sender.h b/webrtc/modules/rtp_rtcp/source/rtp_sender.h
index 83bdc64..56739ae 100644
--- a/webrtc/modules/rtp_rtcp/source/rtp_sender.h
+++ b/webrtc/modules/rtp_rtcp/source/rtp_sender.h
@@ -205,6 +205,9 @@
   void SetRtxRtpState(const RtpState& rtp_state);
   RtpState GetRtxRtpState() const;
 
+  int64_t LastTimestampTimeMs() const;
+  void SendKeepAlive(uint8_t payload_type);
+
  protected:
   int32_t CheckPayloadType(int8_t payload_type, RtpVideoCodecTypes* video_type);
 
diff --git a/webrtc/modules/rtp_rtcp/source/rtp_sender_unittest.cc b/webrtc/modules/rtp_rtcp/source/rtp_sender_unittest.cc
index 89c1d1d..f786c5d 100644
--- a/webrtc/modules/rtp_rtcp/source/rtp_sender_unittest.cc
+++ b/webrtc/modules/rtp_rtcp/source/rtp_sender_unittest.cc
@@ -57,6 +57,7 @@
 
 using ::testing::_;
 using ::testing::ElementsAreArray;
+using ::testing::Invoke;
 
 uint64_t ConvertMsToAbsSendTime(int64_t time_ms) {
   return (((time_ms << 18) + 500) / 1000) & 0x00ffffff;
@@ -1711,6 +1712,40 @@
       rtp_sender_->TimeToSendPadding(kMinPaddingSize - 5, PacedPacketInfo()));
 }
 
+TEST_P(RtpSenderTest, SendsKeepAlive) {
+  MockTransport transport;
+  rtp_sender_.reset(new RTPSender(false, &fake_clock_, &transport, nullptr,
+                                  nullptr, nullptr, nullptr, nullptr, nullptr,
+                                  nullptr, &mock_rtc_event_log_, nullptr,
+                                  &retransmission_rate_limiter_, nullptr));
+  rtp_sender_->SetSendPayloadType(kPayload);
+  rtp_sender_->SetSequenceNumber(kSeqNum);
+  rtp_sender_->SetTimestampOffset(0);
+  rtp_sender_->SetSSRC(kSsrc);
+
+  const uint8_t kKeepalivePayloadType = 20;
+  RTC_CHECK_NE(kKeepalivePayloadType, kPayload);
+
+  EXPECT_CALL(transport, SendRtp(_, _, _))
+      .WillOnce(
+          Invoke([&kKeepalivePayloadType](const uint8_t* packet, size_t len,
+                                          const PacketOptions& options) {
+            webrtc::RTPHeader rtp_header;
+            RtpUtility::RtpHeaderParser parser(packet, len);
+            EXPECT_TRUE(parser.Parse(&rtp_header, nullptr));
+            EXPECT_FALSE(rtp_header.markerBit);
+            EXPECT_EQ(0U, rtp_header.paddingLength);
+            EXPECT_EQ(kKeepalivePayloadType, rtp_header.payloadType);
+            EXPECT_EQ(kSeqNum, rtp_header.sequenceNumber);
+            EXPECT_EQ(kSsrc, rtp_header.ssrc);
+            EXPECT_EQ(0u, len - rtp_header.headerLength);
+            return true;
+          }));
+
+  rtp_sender_->SendKeepAlive(kKeepalivePayloadType);
+  EXPECT_EQ(kSeqNum + 1, rtp_sender_->SequenceNumber());
+}
+
 INSTANTIATE_TEST_CASE_P(WithAndWithoutOverhead,
                         RtpSenderTest,
                         ::testing::Bool());
diff --git a/webrtc/video/replay.cc b/webrtc/video/replay.cc
index 6f4ce67..25c71c4 100644
--- a/webrtc/video/replay.cc
+++ b/webrtc/video/replay.cc
@@ -302,8 +302,8 @@
         std::unique_ptr<RtpHeaderParser> parser(RtpHeaderParser::Create());
         parser->Parse(packet.data, packet.length, &header);
         fprintf(stderr, "Packet len=%zu pt=%u seq=%u ts=%u ssrc=0x%8x\n",
-            packet.length, header.payloadType, header.sequenceNumber,
-            header.timestamp, header.ssrc);
+                packet.length, header.payloadType, header.sequenceNumber,
+                header.timestamp, header.ssrc);
         break;
       }
     }
diff --git a/webrtc/video/video_send_stream.cc b/webrtc/video/video_send_stream.cc
index f4c82f4..fba3315 100644
--- a/webrtc/video/video_send_stream.cc
+++ b/webrtc/video/video_send_stream.cc
@@ -58,7 +58,8 @@
     RtcEventLog* event_log,
     RateLimiter* retransmission_rate_limiter,
     OverheadObserver* overhead_observer,
-    size_t num_modules) {
+    size_t num_modules,
+    RtpKeepAliveConfig keepalive_config) {
   RTC_DCHECK_GT(num_modules, 0);
   RtpRtcp::Configuration configuration;
   ReceiveStatistics* null_receive_statistics = configuration.receive_statistics;
@@ -83,6 +84,7 @@
   configuration.event_log = event_log;
   configuration.retransmission_rate_limiter = retransmission_rate_limiter;
   configuration.overhead_observer = overhead_observer;
+  configuration.keepalive_config = keepalive_config;
   std::vector<RtpRtcp*> modules;
   for (size_t i = 0; i < num_modules; ++i) {
     RtpRtcp* rtp_rtcp = RtpRtcp::CreateRtpRtcp(configuration);
@@ -802,7 +804,8 @@
           event_log,
           transport->send_side_cc()->GetRetransmissionRateLimiter(),
           this,
-          config_->rtp.ssrcs.size())),
+          config_->rtp.ssrcs.size(),
+          config_->rtp.keep_alive)),
       payload_router_(rtp_rtcp_modules_,
                       config_->encoder_settings.payload_type),
       weak_ptr_factory_(this),
diff --git a/webrtc/video/video_send_stream_tests.cc b/webrtc/video/video_send_stream_tests.cc
index 37a0249..7fa12e1 100644
--- a/webrtc/video/video_send_stream_tests.cc
+++ b/webrtc/video/video_send_stream_tests.cc
@@ -32,13 +32,14 @@
 #include "webrtc/test/call_test.h"
 #include "webrtc/test/configurable_frame_size_encoder.h"
 #include "webrtc/test/fake_texture_frame.h"
+#include "webrtc/test/field_trial.h"
 #include "webrtc/test/frame_generator.h"
+#include "webrtc/test/frame_generator_capturer.h"
 #include "webrtc/test/frame_utils.h"
 #include "webrtc/test/gtest.h"
 #include "webrtc/test/null_transport.h"
 #include "webrtc/test/rtcp_packet_parser.h"
 #include "webrtc/test/testsupport/perf_test.h"
-#include "webrtc/test/field_trial.h"
 
 #include "webrtc/video/send_statistics_proxy.h"
 #include "webrtc/video/transport_adapter.h"
@@ -3407,4 +3408,51 @@
   RunBaseTest(&test);
 }
 
+TEST_F(VideoSendStreamTest, SendsKeepAlive) {
+  const int kTimeoutMs = 50;  // Really short timeout for testing.
+  const int kPayloadType = 20;
+
+  class KeepaliveObserver : public test::SendTest {
+   public:
+    KeepaliveObserver() : SendTest(kDefaultTimeoutMs) {}
+
+   private:
+    Action OnSendRtp(const uint8_t* packet, size_t length) override {
+      RTPHeader header;
+      EXPECT_TRUE(parser_->Parse(packet, length, &header));
+
+      if (header.payloadType != kPayloadType) {
+        // The video stream has started. Stop it now.
+        if (capturer_)
+          capturer_->Stop();
+      } else {
+        observation_complete_.Set();
+      }
+
+      return SEND_PACKET;
+    }
+
+    void ModifyVideoConfigs(
+        VideoSendStream::Config* send_config,
+        std::vector<VideoReceiveStream::Config>* receive_configs,
+        VideoEncoderConfig* encoder_config) override {
+      send_config->rtp.keep_alive.timeout_interval_ms = kTimeoutMs;
+      send_config->rtp.keep_alive.payload_type = kPayloadType;
+    }
+
+    void PerformTest() override {
+      EXPECT_TRUE(Wait()) << "Timed out while waiting for keep-alive packet.";
+    }
+
+    void OnFrameGeneratorCapturerCreated(
+        test::FrameGeneratorCapturer* frame_generator_capturer) override {
+      capturer_ = frame_generator_capturer;
+    }
+
+    test::FrameGeneratorCapturer* capturer_ = nullptr;
+  } test;
+
+  RunBaseTest(&test);
+}
+
 }  // namespace webrtc
diff --git a/webrtc/video_send_stream.h b/webrtc/video_send_stream.h
index 266112e..53be83f 100644
--- a/webrtc/video_send_stream.h
+++ b/webrtc/video_send_stream.h
@@ -168,6 +168,8 @@
         int payload_type = -1;
       } rtx;
 
+      RtpKeepAliveConfig keep_alive;
+
       // RTCP CNAME, see RFC 3550.
       std::string c_name;
     } rtp;