Add callbacks for send channel rtcp statistics

BUG=2235
R=mflodman@webrtc.org, pbos@webrtc.org, stefan@webrtc.org

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

git-svn-id: http://webrtc.googlecode.com/svn/trunk@5220 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/webrtc/common_types.h b/webrtc/common_types.h
index a45cf06..28099df 100644
--- a/webrtc/common_types.h
+++ b/webrtc/common_types.h
@@ -240,14 +240,12 @@
     : fraction_lost(0),
       cumulative_lost(0),
       extended_max_sequence_number(0),
-      jitter(0),
-      max_jitter(0) {}
+      jitter(0) {}
 
   uint8_t fraction_lost;
   uint32_t cumulative_lost;
   uint32_t extended_max_sequence_number;
   uint32_t jitter;
-  uint32_t max_jitter;
 };
 
 // Callback, called whenever a new rtcp report block is transmitted.
diff --git a/webrtc/modules/rtp_rtcp/interface/rtp_rtcp.h b/webrtc/modules/rtp_rtcp/interface/rtp_rtcp.h
index 9fb8199..51471ba 100644
--- a/webrtc/modules/rtp_rtcp/interface/rtp_rtcp.h
+++ b/webrtc/modules/rtp_rtcp/interface/rtp_rtcp.h
@@ -590,6 +590,12 @@
     // Returns true if the module is configured to store packets.
     virtual bool StorePackets() const = 0;
 
+    // Called on receipt of RTCP report block from remote side.
+    virtual void RegisterSendChannelRtcpStatisticsCallback(
+        RtcpStatisticsCallback* callback) = 0;
+    virtual RtcpStatisticsCallback*
+        GetSendChannelRtcpStatisticsCallback() = 0;
+
     /**************************************************************************
     *
     *   Audio
diff --git a/webrtc/modules/rtp_rtcp/mocks/mock_rtp_rtcp.h b/webrtc/modules/rtp_rtcp/mocks/mock_rtp_rtcp.h
index 93230da..9ca4897 100644
--- a/webrtc/modules/rtp_rtcp/mocks/mock_rtp_rtcp.h
+++ b/webrtc/modules/rtp_rtcp/mocks/mock_rtp_rtcp.h
@@ -201,6 +201,10 @@
   MOCK_METHOD2(SetStorePacketsStatus,
       int32_t(const bool enable, const uint16_t numberToStore));
   MOCK_CONST_METHOD0(StorePackets, bool());
+  MOCK_METHOD1(RegisterSendChannelRtcpStatisticsCallback,
+               void(RtcpStatisticsCallback*));
+  MOCK_METHOD0(GetSendChannelRtcpStatisticsCallback,
+               RtcpStatisticsCallback*());
   MOCK_METHOD1(RegisterAudioCallback,
       int32_t(RtpAudioFeedback* messagesCallback));
   MOCK_METHOD1(SetAudioPacketSize,
diff --git a/webrtc/modules/rtp_rtcp/source/rtcp_receiver.cc b/webrtc/modules/rtp_rtcp/source/rtcp_receiver.cc
index fb7adf6..a95fdde 100644
--- a/webrtc/modules/rtp_rtcp/source/rtcp_receiver.cc
+++ b/webrtc/modules/rtp_rtcp/source/rtcp_receiver.cc
@@ -54,7 +54,8 @@
     _receivedInfoMap(),
     _packetTimeOutMS(0),
     _lastReceivedRrMs(0),
-    _lastIncreasedSequenceNumberMs(0) {
+    _lastIncreasedSequenceNumberMs(0),
+    stats_callback_(NULL) {
     memset(&_remoteSenderInfo, 0, sizeof(_remoteSenderInfo));
     WEBRTC_TRACE(kTraceMemory, kTraceRtpRtcp, id, "%s created", __FUNCTION__);
 }
@@ -1359,6 +1360,19 @@
   return 0;
 }
 
+void RTCPReceiver::RegisterRtcpStatisticsCallback(
+    RtcpStatisticsCallback* callback) {
+  CriticalSectionScoped cs(_criticalSectionFeedbacks);
+  if (callback != NULL)
+    assert(stats_callback_ == NULL);
+  stats_callback_ = callback;
+}
+
+RtcpStatisticsCallback* RTCPReceiver::GetRtcpStatisticsCallback() {
+  CriticalSectionScoped cs(_criticalSectionFeedbacks);
+  return stats_callback_;
+}
+
 // Holding no Critical section
 void RTCPReceiver::TriggerCallbacksFromRTCPPacket(
     RTCPPacketInformation& rtcpPacketInformation) {
@@ -1453,6 +1467,24 @@
       }
     }
   }
+
+  {
+    CriticalSectionScoped cs(_criticalSectionFeedbacks);
+    if (stats_callback_) {
+      for (ReportBlockList::const_iterator it =
+          rtcpPacketInformation.report_blocks.begin();
+          it != rtcpPacketInformation.report_blocks.end();
+          ++it) {
+        RtcpStatistics stats;
+        stats.cumulative_lost = it->cumulativeLost;
+        stats.extended_max_sequence_number = it->extendedHighSeqNum;
+        stats.fraction_lost = it->fractionLost;
+        stats.jitter = it->jitter;
+
+        stats_callback_->StatisticsUpdated(stats, local_ssrc);
+      }
+    }
+  }
 }
 
 int32_t RTCPReceiver::CNAME(const uint32_t remoteSSRC,
diff --git a/webrtc/modules/rtp_rtcp/source/rtcp_receiver.h b/webrtc/modules/rtp_rtcp/source/rtcp_receiver.h
index 5c6f732..637773d 100644
--- a/webrtc/modules/rtp_rtcp/source/rtcp_receiver.h
+++ b/webrtc/modules/rtp_rtcp/source/rtcp_receiver.h
@@ -109,6 +109,9 @@
 
     int32_t UpdateTMMBR();
 
+    void RegisterRtcpStatisticsCallback(RtcpStatisticsCallback* callback);
+    RtcpStatisticsCallback* GetRtcpStatisticsCallback();
+
 protected:
     RTCPHelp::RTCPReportBlockInformation* CreateReportBlockInformation(const uint32_t remoteSSRC);
     RTCPHelp::RTCPReportBlockInformation* GetReportBlockInformation(const uint32_t remoteSSRC) const;
@@ -262,6 +265,7 @@
   // delivered RTP packet to the remote side.
   int64_t _lastIncreasedSequenceNumberMs;
 
+  RtcpStatisticsCallback* stats_callback_;
 };
 }  // namespace webrtc
 #endif // WEBRTC_MODULES_RTP_RTCP_SOURCE_RTCP_RECEIVER_H_
diff --git a/webrtc/modules/rtp_rtcp/source/rtcp_receiver_unittest.cc b/webrtc/modules/rtp_rtcp/source/rtcp_receiver_unittest.cc
index 25670f5..47befea 100644
--- a/webrtc/modules/rtp_rtcp/source/rtcp_receiver_unittest.cc
+++ b/webrtc/modules/rtp_rtcp/source/rtcp_receiver_unittest.cc
@@ -35,16 +35,18 @@
 
   struct ReportBlock {
     ReportBlock(uint32_t ssrc, uint32_t extended_max, uint8_t fraction_loss,
-                uint32_t cumulative_loss)
+                uint32_t cumulative_loss, uint32_t jitter)
         : ssrc(ssrc),
           extended_max(extended_max),
           fraction_loss(fraction_loss),
-          cumulative_loss(cumulative_loss) {}
+          cumulative_loss(cumulative_loss),
+          jitter(jitter) {}
 
     uint32_t ssrc;
     uint32_t extended_max;
     uint8_t fraction_loss;
     uint32_t cumulative_loss;
+    uint32_t jitter;
   };
 
   PacketBuilder()
@@ -108,9 +110,9 @@
 
   void AddRrPacket(uint32_t sender_ssrc, uint32_t rtp_ssrc,
                    uint32_t extended_max, uint8_t fraction_loss,
-                   uint32_t cumulative_loss) {
+                   uint32_t cumulative_loss, uint32_t jitter) {
     ReportBlock report_block(rtp_ssrc, extended_max, fraction_loss,
-                             cumulative_loss);
+                             cumulative_loss, jitter);
     std::list<ReportBlock> report_block_vector(&report_block,
                                                &report_block + 1);
     AddRrPacketMultipleReportBlocks(sender_ssrc, report_block_vector);
@@ -123,16 +125,17 @@
     for (std::list<ReportBlock>::const_iterator it = report_blocks.begin();
          it != report_blocks.end(); ++it) {
       AddReportBlock(it->ssrc, it->extended_max, it->fraction_loss,
-                     it->cumulative_loss);
+                     it->cumulative_loss, it->jitter);
     }
   }
 
   void AddReportBlock(uint32_t rtp_ssrc, uint32_t extended_max,
-                      uint8_t fraction_loss, uint32_t cumulative_loss) {
+                      uint8_t fraction_loss, uint32_t cumulative_loss,
+                      uint32_t jitter) {
     Add32(rtp_ssrc);
     Add32((fraction_loss << 24) + cumulative_loss);
     Add32(extended_max);
-    Add32(0);  // Jitter.
+    Add32(jitter);
     Add32(0);  // Last SR.
     Add32(0);  // Delay since last SR.
   }
@@ -283,8 +286,9 @@
                                          true);  // Allow non-compound RTCP
 
     RTCPHelp::RTCPPacketInformation rtcpPacketInformation;
-    int result = rtcp_receiver_->IncomingRTCPPacket(rtcpPacketInformation,
-                                                    &rtcpParser);
+    EXPECT_EQ(0, rtcp_receiver_->IncomingRTCPPacket(rtcpPacketInformation,
+                                                    &rtcpParser));
+    rtcp_receiver_->TriggerCallbacksFromRTCPPacket(rtcpPacketInformation);
     // The NACK list is on purpose not copied below as it isn't needed by the
     // test.
     rtcp_packet_info_.rtcpPacketTypeFlags =
@@ -308,7 +312,7 @@
     if (rtcpPacketInformation.VoIPMetric) {
       rtcp_packet_info_.AddVoIPMetric(rtcpPacketInformation.VoIPMetric);
     }
-    return result;
+    return 0;
   }
 
   OverUseDetectorOptions over_use_detector_options_;
@@ -543,7 +547,7 @@
 
   // Add a RR and advance the clock just enough to not trigger a timeout.
   PacketBuilder p1;
-  p1.AddRrPacket(kSenderSsrc, kSourceSsrc, sequence_number, 0, 0);
+  p1.AddRrPacket(kSenderSsrc, kSourceSsrc, sequence_number, 0, 0, 0);
   EXPECT_EQ(0, InjectRtcpPacket(p1.packet(), p1.length()));
   system_clock_.AdvanceTimeMilliseconds(3 * kRtcpIntervalMs - 1);
   EXPECT_FALSE(rtcp_receiver_->RtcpRrTimeout(kRtcpIntervalMs));
@@ -552,7 +556,7 @@
   // Add a RR with the same extended max as the previous RR to trigger a
   // sequence number timeout, but not a RR timeout.
   PacketBuilder p2;
-  p2.AddRrPacket(kSenderSsrc, kSourceSsrc, sequence_number, 0, 0);
+  p2.AddRrPacket(kSenderSsrc, kSourceSsrc, sequence_number, 0, 0, 0);
   EXPECT_EQ(0, InjectRtcpPacket(p2.packet(), p2.length()));
   system_clock_.AdvanceTimeMilliseconds(2);
   EXPECT_FALSE(rtcp_receiver_->RtcpRrTimeout(kRtcpIntervalMs));
@@ -570,7 +574,7 @@
   // Add a new RR with increase sequence number to reset timers.
   PacketBuilder p3;
   sequence_number++;
-  p2.AddRrPacket(kSenderSsrc, kSourceSsrc, sequence_number, 0, 0);
+  p2.AddRrPacket(kSenderSsrc, kSourceSsrc, sequence_number, 0, 0, 0);
   EXPECT_EQ(0, InjectRtcpPacket(p2.packet(), p2.length()));
   EXPECT_FALSE(rtcp_receiver_->RtcpRrTimeout(kRtcpIntervalMs));
   EXPECT_FALSE(rtcp_receiver_->RtcpRrSequenceNumberTimeout(kRtcpIntervalMs));
@@ -578,7 +582,7 @@
   // Verify we can get a timeout again once we've received new RR.
   system_clock_.AdvanceTimeMilliseconds(2 * kRtcpIntervalMs);
   PacketBuilder p4;
-  p4.AddRrPacket(kSenderSsrc, kSourceSsrc, sequence_number, 0, 0);
+  p4.AddRrPacket(kSenderSsrc, kSourceSsrc, sequence_number, 0, 0, 0);
   EXPECT_EQ(0, InjectRtcpPacket(p4.packet(), p4.length()));
   system_clock_.AdvanceTimeMilliseconds(kRtcpIntervalMs + 1);
   EXPECT_FALSE(rtcp_receiver_->RtcpRrTimeout(kRtcpIntervalMs));
@@ -604,9 +608,9 @@
   PacketBuilder packet;
   std::list<PacketBuilder::ReportBlock> report_blocks;
   report_blocks.push_back(PacketBuilder::ReportBlock(
-      kSourceSsrcs[0], sequence_numbers[0], 10, 5));
+      kSourceSsrcs[0], sequence_numbers[0], 10, 5, 0));
   report_blocks.push_back(PacketBuilder::ReportBlock(
-      kSourceSsrcs[1], sequence_numbers[1], 0, 0));
+      kSourceSsrcs[1], sequence_numbers[1], 0, 0, 0));
   packet.AddRrPacketMultipleReportBlocks(kSenderSsrc, report_blocks);
   EXPECT_EQ(0, InjectRtcpPacket(packet.packet(), packet.length()));
   ASSERT_EQ(2u, rtcp_packet_info_.report_blocks.size());
@@ -616,9 +620,9 @@
   PacketBuilder packet2;
   report_blocks.clear();
   report_blocks.push_back(PacketBuilder::ReportBlock(
-      kSourceSsrcs[0], sequence_numbers[0], 0, 0));
+      kSourceSsrcs[0], sequence_numbers[0], 0, 0, 0));
   report_blocks.push_back(PacketBuilder::ReportBlock(
-      kSourceSsrcs[1], sequence_numbers[1], 20, 10));
+      kSourceSsrcs[1], sequence_numbers[1], 20, 10, 0));
   packet2.AddRrPacketMultipleReportBlocks(kSenderSsrc, report_blocks);
   EXPECT_EQ(0, InjectRtcpPacket(packet2.packet(), packet2.length()));
   ASSERT_EQ(2u, rtcp_packet_info_.report_blocks.size());
@@ -736,6 +740,61 @@
   EXPECT_EQ(kMediaRecipientSsrc + 2, candidate_set.Ssrc(0));
 }
 
+TEST_F(RtcpReceiverTest, Callbacks) {
+  class RtcpCallbackImpl : public RtcpStatisticsCallback {
+   public:
+    RtcpCallbackImpl() : RtcpStatisticsCallback(), ssrc_(0) {}
+    virtual ~RtcpCallbackImpl() {}
+
+    virtual void StatisticsUpdated(const RtcpStatistics& statistics,
+                                   uint32_t ssrc) {
+      stats_ = statistics;
+      ssrc_ = ssrc;
+    }
+
+    bool Matches(uint32_t ssrc, uint32_t extended_max, uint8_t fraction_loss,
+                 uint32_t cumulative_loss, uint32_t jitter) {
+      return ssrc_ == ssrc &&
+          stats_.fraction_lost == fraction_loss &&
+          stats_.cumulative_lost == cumulative_loss &&
+          stats_.extended_max_sequence_number == extended_max &&
+          stats_.jitter == jitter;
+    }
+
+    RtcpStatistics stats_;
+    uint32_t ssrc_;
+  } callback;
+
+  rtcp_receiver_->RegisterRtcpStatisticsCallback(&callback);
+
+  const uint32_t kSenderSsrc = 0x10203;
+  const uint32_t kSourceSsrc = 0x123456;
+  const uint8_t fraction_loss = 3;
+  const uint32_t cumulative_loss = 7;
+  const uint32_t jitter = 9;
+  uint32_t sequence_number = 1234;
+
+  std::set<uint32_t> ssrcs;
+  ssrcs.insert(kSourceSsrc);
+  rtcp_receiver_->SetSsrcs(kSourceSsrc, ssrcs);
+
+  // First packet, all numbers should just propagate
+  PacketBuilder p1;
+  p1.AddRrPacket(kSenderSsrc, kSourceSsrc, sequence_number,
+                 fraction_loss, cumulative_loss, jitter);
+  EXPECT_EQ(0, InjectRtcpPacket(p1.packet(), p1.length()));
+  EXPECT_TRUE(callback.Matches(kSourceSsrc, sequence_number, fraction_loss,
+                               cumulative_loss, jitter));
+
+  rtcp_receiver_->RegisterRtcpStatisticsCallback(NULL);
+
+  // Add arbitrary numbers, callback should not be called (retain old values)
+  PacketBuilder p2;
+  p2.AddRrPacket(kSenderSsrc, kSourceSsrc, sequence_number + 1, 42, 137, 4711);
+  EXPECT_EQ(0, InjectRtcpPacket(p2.packet(), p2.length()));
+  EXPECT_TRUE(callback.Matches(kSourceSsrc, sequence_number, fraction_loss,
+                               cumulative_loss, jitter));
+}
 
 }  // Anonymous namespace
 
diff --git a/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.cc b/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.cc
index 027d17d..4af8cde 100644
--- a/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.cc
+++ b/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.cc
@@ -1189,6 +1189,16 @@
   return rtp_sender_.StorePackets();
 }
 
+void ModuleRtpRtcpImpl::RegisterSendChannelRtcpStatisticsCallback(
+    RtcpStatisticsCallback* callback) {
+  rtcp_receiver_.RegisterRtcpStatisticsCallback(callback);
+}
+
+RtcpStatisticsCallback* ModuleRtpRtcpImpl::
+        GetSendChannelRtcpStatisticsCallback() {
+  return rtcp_receiver_.GetRtcpStatisticsCallback();
+}
+
 // Send a TelephoneEvent tone using RFC 2833 (4733).
 int32_t ModuleRtpRtcpImpl::SendTelephoneEventOutband(
     const uint8_t key,
diff --git a/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.h b/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.h
index c8ab063..0dd2549 100644
--- a/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.h
+++ b/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.h
@@ -246,6 +246,12 @@
 
   virtual bool StorePackets() const OVERRIDE;
 
+  // Called on receipt of RTCP report block from remote side.
+  virtual void RegisterSendChannelRtcpStatisticsCallback(
+      RtcpStatisticsCallback* callback) OVERRIDE;
+  virtual RtcpStatisticsCallback*
+      GetSendChannelRtcpStatisticsCallback() OVERRIDE;
+
   // (APP) Application specific data.
   virtual int32_t SetRTCPApplicationSpecificData(
       const uint8_t sub_type,
diff --git a/webrtc/video_engine/vie_channel.cc b/webrtc/video_engine/vie_channel.cc
index 59c628e..0c595fc 100644
--- a/webrtc/video_engine/vie_channel.cc
+++ b/webrtc/video_engine/vie_channel.cc
@@ -357,6 +357,7 @@
       rtp_rtcp->SetSendingStatus(false);
       rtp_rtcp->SetSendingMediaStatus(false);
       rtp_rtcp->RegisterSendFrameCountObserver(NULL);
+      rtp_rtcp->RegisterSendChannelRtcpStatisticsCallback(NULL);
       simulcast_rtp_rtcp_.pop_back();
       removed_rtp_rtcp_.push_front(rtp_rtcp);
     }
@@ -413,6 +414,8 @@
       rtp_rtcp->SetRtcpXrRrtrStatus(rtp_rtcp_->RtcpXrRrtrStatus());
       rtp_rtcp->RegisterSendFrameCountObserver(
           rtp_rtcp_->GetSendFrameCountObserver());
+      rtp_rtcp->RegisterSendChannelRtcpStatisticsCallback(
+          rtp_rtcp_->GetSendChannelRtcpStatisticsCallback());
     }
     // |RegisterSimulcastRtpRtcpModules| resets all old weak pointers and old
     // modules can be deleted after this step.
@@ -424,6 +427,7 @@
       rtp_rtcp->SetSendingStatus(false);
       rtp_rtcp->SetSendingMediaStatus(false);
       rtp_rtcp->RegisterSendFrameCountObserver(NULL);
+      rtp_rtcp->RegisterSendChannelRtcpStatisticsCallback(NULL);
       simulcast_rtp_rtcp_.pop_back();
       removed_rtp_rtcp_.push_front(rtp_rtcp);
     }
@@ -1264,6 +1268,19 @@
   return 0;
 }
 
+void ViEChannel::RegisterSendChannelRtcpStatisticsCallback(
+    RtcpStatisticsCallback* callback) {
+  WEBRTC_TRACE(kTraceInfo, kTraceVideo, ViEId(engine_id_, channel_id_), "%s",
+               __FUNCTION__);
+  rtp_rtcp_->RegisterSendChannelRtcpStatisticsCallback(callback);
+  CriticalSectionScoped cs(rtp_rtcp_cs_.get());
+  for (std::list<RtpRtcp*>::const_iterator it = simulcast_rtp_rtcp_.begin();
+       it != simulcast_rtp_rtcp_.end();
+       ++it) {
+    (*it)->RegisterSendChannelRtcpStatisticsCallback(callback);
+  }
+}
+
 // TODO(holmer): This is a bad function name as it implies that it returns the
 // received RTCP, while it actually returns the statistics which will be sent
 // in the RTCP.
diff --git a/webrtc/video_engine/vie_channel.h b/webrtc/video_engine/vie_channel.h
index a16a65d..a17bb2b 100644
--- a/webrtc/video_engine/vie_channel.h
+++ b/webrtc/video_engine/vie_channel.h
@@ -174,6 +174,10 @@
                                 uint32_t* jitter_samples,
                                 int32_t* rtt_ms);
 
+  // Called on receipt of RTCP report block from remote side.
+  void RegisterSendChannelRtcpStatisticsCallback(
+      RtcpStatisticsCallback* callback);
+
   // Returns our localy created statistics of the received RTP stream.
   int32_t GetReceivedRtcpStatistics(uint16_t* fraction_lost,
                                     uint32_t* cumulative_lost,
diff --git a/webrtc/video_engine/vie_rtp_rtcp_impl.cc b/webrtc/video_engine/vie_rtp_rtcp_impl.cc
index edad270..9822b9d 100644
--- a/webrtc/video_engine/vie_rtp_rtcp_impl.cc
+++ b/webrtc/video_engine/vie_rtp_rtcp_impl.cc
@@ -1119,15 +1119,39 @@
 }
 
 int ViERTP_RTCPImpl::RegisterSendChannelRtcpStatisticsCallback(
-    int channel, RtcpStatisticsCallback* callback) {
-  // TODO(sprang): Implement
-  return -1;
+  int video_channel, RtcpStatisticsCallback* callback) {
+  WEBRTC_TRACE(kTraceApiCall, kTraceVideo,
+               ViEId(shared_data_->instance_id(), video_channel),
+               "%s(channel: %d)", __FUNCTION__, video_channel);
+  ViEChannelManagerScoped cs(*(shared_data_->channel_manager()));
+  ViEChannel* vie_channel = cs.Channel(video_channel);
+  if (!vie_channel) {
+    WEBRTC_TRACE(kTraceError, kTraceVideo,
+                 ViEId(shared_data_->instance_id(), video_channel),
+                 "%s: Channel %d doesn't exist", __FUNCTION__, video_channel);
+    shared_data_->SetLastError(kViERtpRtcpInvalidChannelId);
+    return -1;
+  }
+  vie_channel->RegisterSendChannelRtcpStatisticsCallback(callback);
+  return 0;
 }
 
 int ViERTP_RTCPImpl::DeregisterSendChannelRtcpStatisticsCallback(
-    int channel, RtcpStatisticsCallback* callback) {
-  // TODO(sprang): Implement
-  return -1;
+    int video_channel, RtcpStatisticsCallback* callback) {
+  WEBRTC_TRACE(kTraceApiCall, kTraceVideo,
+               ViEId(shared_data_->instance_id(), video_channel),
+               "%s(channel: %d)", __FUNCTION__, video_channel);
+  ViEChannelManagerScoped cs(*(shared_data_->channel_manager()));
+  ViEChannel* vie_channel = cs.Channel(video_channel);
+  if (!vie_channel) {
+    WEBRTC_TRACE(kTraceError, kTraceVideo,
+                 ViEId(shared_data_->instance_id(), video_channel),
+                 "%s: Channel %d doesn't exist", __FUNCTION__, video_channel);
+    shared_data_->SetLastError(kViERtpRtcpInvalidChannelId);
+    return -1;
+  }
+  vie_channel->RegisterSendChannelRtcpStatisticsCallback(NULL);
+  return 0;
 }
 
 int ViERTP_RTCPImpl::RegisterReceiveChannelRtcpStatisticsCallback(