From 0bae1fab4adb9bb8164e53142bf419049eafec38 Mon Sep 17 00:00:00 2001 From: "stefan@webrtc.org" Date: Wed, 5 Nov 2014 14:05:29 +0000 Subject: [PATCH] Wire up bandwidth stats to the new API and webrtcvideoengine2. Adds stats to verify bandwidth and pacer stats. BUG=1788 R=mflodman@webrtc.org, pbos@webrtc.org Review URL: https://webrtc-codereview.appspot.com/24969004 git-svn-id: http://webrtc.googlecode.com/svn/trunk@7634 4adac7df-926f-26a2-2b94-8c16560cd09d --- talk/media/webrtc/webrtcvideoengine2.cc | 39 ++++++++- talk/media/webrtc/webrtcvideoengine2.h | 1 + .../webrtc/webrtcvideoengine2_unittest.cc | 9 +-- .../webrtc/webrtcvideoengine2_unittest.h | 3 +- webrtc/call.h | 18 +++-- webrtc/common_types.h | 4 +- webrtc/config.h | 11 ++- webrtc/modules/rtp_rtcp/source/rtp_sender.cc | 80 +++++++++++++++---- webrtc/modules/rtp_rtcp/source/rtp_sender.h | 10 +-- .../rtp_rtcp/source/rtp_sender_unittest.cc | 22 ++--- .../main/source/media_optimization.cc | 2 +- webrtc/test/fake_encoder.cc | 10 ++- webrtc/video/call.cc | 31 ++++--- webrtc/video/call_perf_tests.cc | 3 +- webrtc/video/end_to_end_tests.cc | 55 ++++++++++++- webrtc/video/receive_statistics_proxy.cc | 4 +- webrtc/video/receive_statistics_proxy.h | 2 +- webrtc/video/send_statistics_proxy.cc | 21 ++--- webrtc/video/send_statistics_proxy.h | 6 +- .../video/send_statistics_proxy_unittest.cc | 68 ++++++++++------ webrtc/video/video_send_stream.cc | 7 ++ webrtc/video/video_send_stream.h | 2 + webrtc/video/video_send_stream_tests.cc | 24 +++--- webrtc/video_engine/vie_channel.h | 6 +- webrtc/video_receive_stream.h | 2 +- webrtc/video_send_stream.h | 4 +- 26 files changed, 315 insertions(+), 129 deletions(-) diff --git a/talk/media/webrtc/webrtcvideoengine2.cc b/talk/media/webrtc/webrtcvideoengine2.cc index d79f71d1e4..5062fb94d6 100644 --- a/talk/media/webrtc/webrtcvideoengine2.cc +++ b/talk/media/webrtc/webrtcvideoengine2.cc @@ -1202,7 +1202,21 @@ void WebRtcVideoChannel2::FillReceiverStats(VideoMediaInfo* video_media_info) { void WebRtcVideoChannel2::FillBandwidthEstimationStats( VideoMediaInfo* video_media_info) { - // TODO(pbos): Implement. + BandwidthEstimationInfo bwe_info; + webrtc::Call::Stats stats = call_->GetStats(); + bwe_info.available_send_bandwidth = stats.send_bandwidth_bps; + bwe_info.available_recv_bandwidth = stats.recv_bandwidth_bps; + bwe_info.bucket_delay = stats.pacer_delay_ms; + + // Get send stream bitrate stats. + rtc::CritScope stream_lock(&stream_crit_); + for (std::map::iterator stream = + send_streams_.begin(); + stream != send_streams_.end(); + ++stream) { + stream->second->FillBandwidthEstimationInfo(&bwe_info); + } + video_media_info->bw_estimations.push_back(bwe_info); } bool WebRtcVideoChannel2::SetCapturer(uint32 ssrc, VideoCapturer* capturer) { @@ -1842,12 +1856,12 @@ WebRtcVideoChannel2::WebRtcVideoSendStream::GetVideoSenderInfo() { info.framerate_input = stats.input_frame_rate; info.framerate_sent = stats.encode_frame_rate; - for (std::map::iterator it = + for (std::map::iterator it = stats.substreams.begin(); it != stats.substreams.end(); ++it) { // TODO(pbos): Wire up additional stats, such as padding bytes. - webrtc::StreamStats stream_stats = it->second; + webrtc::SsrcStats stream_stats = it->second; info.bytes_sent += stream_stats.rtp_stats.bytes + stream_stats.rtp_stats.header_bytes + stream_stats.rtp_stats.padding_bytes; @@ -1857,7 +1871,7 @@ WebRtcVideoChannel2::WebRtcVideoSendStream::GetVideoSenderInfo() { if (!stats.substreams.empty()) { // TODO(pbos): Report fraction lost per SSRC. - webrtc::StreamStats first_stream_stats = stats.substreams.begin()->second; + webrtc::SsrcStats first_stream_stats = stats.substreams.begin()->second; info.fraction_lost = static_cast(first_stream_stats.rtcp_stats.fraction_lost) / (1 << 8); @@ -1884,6 +1898,23 @@ WebRtcVideoChannel2::WebRtcVideoSendStream::GetVideoSenderInfo() { return info; } +void WebRtcVideoChannel2::WebRtcVideoSendStream::FillBandwidthEstimationInfo( + BandwidthEstimationInfo* bwe_info) { + rtc::CritScope cs(&lock_); + if (stream_ == NULL) { + return; + } + webrtc::VideoSendStream::Stats stats = stream_->GetStats(); + for (std::map::iterator it = + stats.substreams.begin(); + it != stats.substreams.end(); + ++it) { + bwe_info->transmit_bitrate += it->second.total_bitrate_bps; + bwe_info->retransmit_bitrate += it->second.retransmit_bitrate_bps; + } + bwe_info->actual_enc_bitrate = stats.media_bitrate_bps; +} + void WebRtcVideoChannel2::WebRtcVideoSendStream::OnCpuResolutionRequest( CoordinatedVideoAdapter::AdaptRequest adapt_request) { rtc::CritScope cs(&lock_); diff --git a/talk/media/webrtc/webrtcvideoengine2.h b/talk/media/webrtc/webrtcvideoengine2.h index 0b812efa31..299ac35ec4 100644 --- a/talk/media/webrtc/webrtcvideoengine2.h +++ b/talk/media/webrtc/webrtcvideoengine2.h @@ -315,6 +315,7 @@ class WebRtcVideoChannel2 : public rtc::MessageHandler, void Stop(); VideoSenderInfo GetVideoSenderInfo(); + void FillBandwidthEstimationInfo(BandwidthEstimationInfo* bwe_info); void OnCpuResolutionRequest( CoordinatedVideoAdapter::AdaptRequest adapt_request); diff --git a/talk/media/webrtc/webrtcvideoengine2_unittest.cc b/talk/media/webrtc/webrtcvideoengine2_unittest.cc index 0b857238a7..afea370ec7 100644 --- a/talk/media/webrtc/webrtcvideoengine2_unittest.cc +++ b/talk/media/webrtc/webrtcvideoengine2_unittest.cc @@ -294,12 +294,9 @@ webrtc::PacketReceiver* FakeCall::Receiver() { return NULL; } -uint32_t FakeCall::SendBitrateEstimate() { - return 0; -} - -uint32_t FakeCall::ReceiveBitrateEstimate() { - return 0; +webrtc::Call::Stats FakeCall::GetStats() const { + webrtc::Call::Stats stats; + return stats; } void FakeCall::SignalNetworkState(webrtc::Call::NetworkState state) { diff --git a/talk/media/webrtc/webrtcvideoengine2_unittest.h b/talk/media/webrtc/webrtcvideoengine2_unittest.h index 3b622897f0..48c4f64fdc 100644 --- a/talk/media/webrtc/webrtcvideoengine2_unittest.h +++ b/talk/media/webrtc/webrtcvideoengine2_unittest.h @@ -127,8 +127,7 @@ class FakeCall : public webrtc::Call { webrtc::VideoReceiveStream* receive_stream) OVERRIDE; virtual webrtc::PacketReceiver* Receiver() OVERRIDE; - virtual uint32_t SendBitrateEstimate() OVERRIDE; - virtual uint32_t ReceiveBitrateEstimate() OVERRIDE; + virtual webrtc::Call::Stats GetStats() const OVERRIDE; virtual void SignalNetworkState(webrtc::Call::NetworkState state) OVERRIDE; diff --git a/webrtc/call.h b/webrtc/call.h index f21425fb99..c6596f86e5 100644 --- a/webrtc/call.h +++ b/webrtc/call.h @@ -88,6 +88,14 @@ class Call { int stream_start_bitrate_bps; }; + struct Stats { + Stats() : send_bandwidth_bps(0), recv_bandwidth_bps(0), pacer_delay_ms(0) {} + + int send_bandwidth_bps; + int recv_bandwidth_bps; + int pacer_delay_ms; + }; + static Call* Create(const Call::Config& config); static Call* Create(const Call::Config& config, @@ -109,13 +117,9 @@ class Call { // Call instance exists. virtual PacketReceiver* Receiver() = 0; - // Returns the estimated total send bandwidth. Note: this can differ from the - // actual encoded bitrate. - virtual uint32_t SendBitrateEstimate() = 0; - - // Returns the total estimated receive bandwidth for the call. Note: this can - // differ from the actual receive bitrate. - virtual uint32_t ReceiveBitrateEstimate() = 0; + // Returns the call statistics, such as estimated send and receive bandwidth, + // pacing delay, etc. + virtual Stats GetStats() const = 0; virtual void SignalNetworkState(NetworkState state) = 0; diff --git a/webrtc/common_types.h b/webrtc/common_types.h index 7bcfd6d427..0b4af26e6a 100644 --- a/webrtc/common_types.h +++ b/webrtc/common_types.h @@ -271,7 +271,9 @@ class BitrateStatisticsObserver { public: virtual ~BitrateStatisticsObserver() {} - virtual void Notify(const BitrateStatistics& stats, uint32_t ssrc) = 0; + virtual void Notify(const BitrateStatistics& total_stats, + const BitrateStatistics& retransmit_stats, + uint32_t ssrc) = 0; }; // Callback, used to notify an observer whenever frame counts have been updated diff --git a/webrtc/config.h b/webrtc/config.h index 8ea28288ae..ee1097fc31 100644 --- a/webrtc/config.h +++ b/webrtc/config.h @@ -33,16 +33,19 @@ struct RtpStatistics { int extended_max_sequence_number; }; -struct StreamStats { - StreamStats() +struct SsrcStats { + SsrcStats() : key_frames(0), delta_frames(0), - bitrate_bps(0), + total_bitrate_bps(0), + retransmit_bitrate_bps(0), avg_delay_ms(0), max_delay_ms(0) {} uint32_t key_frames; uint32_t delta_frames; - int32_t bitrate_bps; + // TODO(holmer): Move bitrate_bps out to the webrtc::Call layer. + int total_bitrate_bps; + int retransmit_bitrate_bps; int avg_delay_ms; int max_delay_ms; StreamDataCounters rtp_stats; diff --git a/webrtc/modules/rtp_rtcp/source/rtp_sender.cc b/webrtc/modules/rtp_rtcp/source/rtp_sender.cc index 0438b9f7d6..677f3fc498 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_sender.cc +++ b/webrtc/modules/rtp_rtcp/source/rtp_sender.cc @@ -40,6 +40,57 @@ const char* FrameTypeToString(const FrameType frame_type) { } // namespace +class BitrateAggregator { + public: + explicit BitrateAggregator(BitrateStatisticsObserver* bitrate_callback) + : callback_(bitrate_callback), + total_bitrate_observer_(*this), + retransmit_bitrate_observer_(*this), + ssrc_(0) {} + + void OnStatsUpdated() const { + if (callback_) + callback_->Notify(total_bitrate_observer_.statistics(), + retransmit_bitrate_observer_.statistics(), + ssrc_); + } + + Bitrate::Observer* total_bitrate_observer() { + return &total_bitrate_observer_; + } + Bitrate::Observer* retransmit_bitrate_observer() { + return &retransmit_bitrate_observer_; + } + + void set_ssrc(uint32_t ssrc) { ssrc_ = ssrc; } + + private: + // We assume that these observers are called on the same thread, which is + // true for RtpSender as they are called on the Process thread. + class BitrateObserver : public Bitrate::Observer { + public: + explicit BitrateObserver(const BitrateAggregator& aggregator) + : aggregator_(aggregator) {} + + // Implements Bitrate::Observer. + virtual void BitrateUpdated(const BitrateStatistics& stats) OVERRIDE { + statistics_ = stats; + aggregator_.OnStatsUpdated(); + } + + BitrateStatistics statistics() const { return statistics_; } + + private: + BitrateStatistics statistics_; + const BitrateAggregator& aggregator_; + }; + + BitrateStatisticsObserver* const callback_; + BitrateObserver total_bitrate_observer_; + BitrateObserver retransmit_bitrate_observer_; + uint32_t ssrc_; +}; + RTPSender::RTPSender(const int32_t id, const bool audio, Clock* clock, @@ -54,7 +105,8 @@ RTPSender::RTPSender(const int32_t id, // TickTime. clock_delta_ms_(clock_->TimeInMilliseconds() - TickTime::MillisecondTimestamp()), - bitrate_sent_(clock, this), + bitrates_(new BitrateAggregator(bitrate_callback)), + total_bitrate_sent_(clock, bitrates_->total_bitrate_observer()), id_(id), audio_configured_(audio), audio_(NULL), @@ -74,12 +126,11 @@ RTPSender::RTPSender(const int32_t id, // NACK. nack_byte_count_times_(), nack_byte_count_(), - nack_bitrate_(clock, NULL), + nack_bitrate_(clock, bitrates_->retransmit_bitrate_observer()), packet_history_(clock), // Statistics statistics_crit_(CriticalSectionWrapper::CreateCriticalSection()), rtp_stats_callback_(NULL), - bitrate_callback_(bitrate_callback), frame_count_observer_(frame_count_observer), send_side_delay_observer_(send_side_delay_observer), // RTP variables @@ -108,6 +159,7 @@ RTPSender::RTPSender(const int32_t id, srand(static_cast(clock_->TimeInMilliseconds())); ssrc_ = ssrc_db_.CreateSSRC(); // Can't be 0. ssrc_rtx_ = ssrc_db_.CreateSSRC(); // Can't be 0. + bitrates_->set_ssrc(ssrc_); // Random start, 16 bits. Can't be 0. sequence_number_rtx_ = static_cast(rand() + 1) & 0x7FFF; sequence_number_ = static_cast(rand() + 1) & 0x7FFF; @@ -149,7 +201,7 @@ uint32_t RTPSender::GetTargetBitrate() { } uint16_t RTPSender::ActualSendBitrateKbit() const { - return (uint16_t)(bitrate_sent_.BitrateNow() / 1000); + return (uint16_t)(total_bitrate_sent_.BitrateNow() / 1000); } uint32_t RTPSender::VideoBitrateSent() const { @@ -864,7 +916,7 @@ void RTPSender::UpdateRtpStats(const uint8_t* buffer, counters = &rtp_stats_; } - bitrate_sent_.Update(size); + total_bitrate_sent_.Update(size); ++counters->packets; if (IsFecPacket(buffer, header)) { ++counters->fec_packets; @@ -997,7 +1049,7 @@ void RTPSender::UpdateDelayStatistics(int64_t capture_time_ms, int64_t now_ms) { void RTPSender::ProcessBitrate() { CriticalSectionScoped cs(send_critsect_); - bitrate_sent_.Process(); + total_bitrate_sent_.Process(); nack_bitrate_.Process(); if (audio_configured_) { return; @@ -1420,6 +1472,7 @@ void RTPSender::SetSendingStatus(bool enabled) { // Generate a new SSRC. ssrc_db_.ReturnSSRC(ssrc_); ssrc_ = ssrc_db_.CreateSSRC(); // Can't be 0. + bitrates_->set_ssrc(ssrc_); } // Don't initialize seq number if SSRC passed externally. if (!sequence_number_forced_ && !ssrc_forced_) { @@ -1470,6 +1523,7 @@ uint32_t RTPSender::GenerateNewSSRC() { return 0; } ssrc_ = ssrc_db_.CreateSSRC(); // Can't be 0. + bitrates_->set_ssrc(ssrc_); return ssrc_; } @@ -1484,6 +1538,7 @@ void RTPSender::SetSSRC(uint32_t ssrc) { ssrc_db_.ReturnSSRC(ssrc_); ssrc_db_.RegisterSSRC(ssrc); ssrc_ = ssrc; + bitrates_->set_ssrc(ssrc_); if (!sequence_number_forced_) { sequence_number_ = rand() / (RAND_MAX / MAX_INIT_RTP_SEQ_NUMBER); // NOLINT @@ -1681,17 +1736,8 @@ StreamDataCountersCallback* RTPSender::GetRtpStatisticsCallback() const { return rtp_stats_callback_; } -uint32_t RTPSender::BitrateSent() const { return bitrate_sent_.BitrateLast(); } - -void RTPSender::BitrateUpdated(const BitrateStatistics& stats) { - uint32_t ssrc; - { - CriticalSectionScoped ssrc_lock(send_critsect_); - ssrc = ssrc_; - } - if (bitrate_callback_) { - bitrate_callback_->Notify(stats, ssrc); - } +uint32_t RTPSender::BitrateSent() const { + return total_bitrate_sent_.BitrateLast(); } void RTPSender::SetRtpState(const RtpState& rtp_state) { diff --git a/webrtc/modules/rtp_rtcp/source/rtp_sender.h b/webrtc/modules/rtp_rtcp/source/rtp_sender.h index 780baa1fdc..6564d47bc4 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_sender.h +++ b/webrtc/modules/rtp_rtcp/source/rtp_sender.h @@ -31,6 +31,7 @@ namespace webrtc { +class BitrateAggregator; class CriticalSectionWrapper; class RTPSenderAudio; class RTPSenderVideo; @@ -65,7 +66,7 @@ class RTPSenderInterface { PacedSender::Priority priority) = 0; }; -class RTPSender : public RTPSenderInterface, public Bitrate::Observer { +class RTPSender : public RTPSenderInterface { public: RTPSender(const int32_t id, const bool audio, Clock *clock, Transport *transport, RtpAudioFeedback *audio_feedback, @@ -276,8 +277,6 @@ class RTPSender : public RTPSenderInterface, public Bitrate::Observer { uint32_t BitrateSent() const; - virtual void BitrateUpdated(const BitrateStatistics& stats) OVERRIDE; - void SetRtpState(const RtpState& rtp_state); RtpState GetRtpState() const; void SetRtxRtpState(const RtpState& rtp_state); @@ -337,7 +336,9 @@ class RTPSender : public RTPSenderInterface, public Bitrate::Observer { Clock* clock_; int64_t clock_delta_ms_; - Bitrate bitrate_sent_; + + scoped_ptr bitrates_; + Bitrate total_bitrate_sent_; int32_t id_; const bool audio_configured_; @@ -375,7 +376,6 @@ class RTPSender : public RTPSenderInterface, public Bitrate::Observer { StreamDataCounters rtp_stats_ GUARDED_BY(statistics_crit_); StreamDataCounters rtx_rtp_stats_ GUARDED_BY(statistics_crit_); StreamDataCountersCallback* rtp_stats_callback_ GUARDED_BY(statistics_crit_); - BitrateStatisticsObserver* const bitrate_callback_; FrameCountObserver* const frame_count_observer_; SendSideDelayObserver* const send_side_delay_observer_; diff --git a/webrtc/modules/rtp_rtcp/source/rtp_sender_unittest.cc b/webrtc/modules/rtp_rtcp/source/rtp_sender_unittest.cc index 9c6a7207d3..2a49477883 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_sender_unittest.cc +++ b/webrtc/modules/rtp_rtcp/source/rtp_sender_unittest.cc @@ -855,20 +855,22 @@ TEST_F(RtpSenderTest, FrameCountCallbacks) { TEST_F(RtpSenderTest, BitrateCallbacks) { class TestCallback : public BitrateStatisticsObserver { public: - TestCallback() - : BitrateStatisticsObserver(), num_calls_(0), ssrc_(0), bitrate_() {} + TestCallback() : BitrateStatisticsObserver(), num_calls_(0), ssrc_(0) {} virtual ~TestCallback() {} - virtual void Notify(const BitrateStatistics& stats, + virtual void Notify(const BitrateStatistics& total_stats, + const BitrateStatistics& retransmit_stats, uint32_t ssrc) OVERRIDE { ++num_calls_; ssrc_ = ssrc; - bitrate_ = stats; + total_stats_ = total_stats; + retransmit_stats_ = retransmit_stats; } uint32_t num_calls_; uint32_t ssrc_; - BitrateStatistics bitrate_; + BitrateStatistics total_stats_; + BitrateStatistics retransmit_stats_; } callback; rtp_sender_.reset(new RTPSender(0, false, &fake_clock_, &transport_, NULL, &mock_paced_sender_, &callback, NULL, NULL)); @@ -909,13 +911,15 @@ TEST_F(RtpSenderTest, BitrateCallbacks) { const uint32_t expected_packet_rate = 1000 / kPacketInterval; - EXPECT_EQ(1U, callback.num_calls_); + // We get one call for every stats updated, thus two calls since both the + // stream stats and the retransmit stats are updated once. + EXPECT_EQ(2u, callback.num_calls_); EXPECT_EQ(ssrc, callback.ssrc_); EXPECT_EQ(start_time + (kNumPackets * kPacketInterval), - callback.bitrate_.timestamp_ms); - EXPECT_EQ(expected_packet_rate, callback.bitrate_.packet_rate); + callback.total_stats_.timestamp_ms); + EXPECT_EQ(expected_packet_rate, callback.total_stats_.packet_rate); EXPECT_EQ((kPacketOverhead + sizeof(payload)) * 8 * expected_packet_rate, - callback.bitrate_.bitrate_bps); + callback.total_stats_.bitrate_bps); rtp_sender_.reset(); } diff --git a/webrtc/modules/video_coding/main/source/media_optimization.cc b/webrtc/modules/video_coding/main/source/media_optimization.cc index 0d9a4bdf67..578948076d 100644 --- a/webrtc/modules/video_coding/main/source/media_optimization.cc +++ b/webrtc/modules/video_coding/main/source/media_optimization.cc @@ -542,7 +542,7 @@ void MediaOptimization::UpdateSentBitrate(int64_t now_ms) { now_ms - encoded_frame_samples_.front().time_complete_ms); if (denom >= 1.0f) { avg_sent_bit_rate_bps_ = - static_cast(framesize_sum * 8 * 1000 / denom + 0.5f); + static_cast(framesize_sum * 8.0f * 1000.0f / denom + 0.5f); } else { avg_sent_bit_rate_bps_ = framesize_sum * 8; } diff --git a/webrtc/test/fake_encoder.cc b/webrtc/test/fake_encoder.cc index 9551c8204e..0573c8af6d 100644 --- a/webrtc/test/fake_encoder.cc +++ b/webrtc/test/fake_encoder.cc @@ -51,7 +51,8 @@ int32_t FakeEncoder::Encode( assert(config_.maxFramerate > 0); int time_since_last_encode_ms = 1000 / config_.maxFramerate; int64_t time_now_ms = clock_->TimeInMilliseconds(); - if (last_encode_time_ms_ > 0) { + const bool first_encode = last_encode_time_ms_ == 0; + if (!first_encode) { // For all frames but the first we can estimate the display time by looking // at the display time of the previous frame. time_since_last_encode_ms = time_now_ms - last_encode_time_ms_; @@ -80,6 +81,12 @@ int32_t FakeEncoder::Encode( int stream_bits = (bits_available > max_stream_bits) ? max_stream_bits : bits_available; int stream_bytes = (stream_bits + 7) / 8; + if (first_encode) { + // The first frame is a key frame and should be larger. + // TODO(holmer): The FakeEncoder should store the bits_available between + // encodes so that it can compensate for oversized frames. + stream_bytes *= 10; + } if (static_cast(stream_bytes) > sizeof(encoded_buffer_)) stream_bytes = sizeof(encoded_buffer_); @@ -96,7 +103,6 @@ int32_t FakeEncoder::Encode( assert(callback_ != NULL); if (callback_->Encoded(encoded, &specifics, NULL) != 0) return -1; - bits_available -= encoded._length * 8; } return 0; diff --git a/webrtc/video/call.cc b/webrtc/video/call.cc index b03b6c91a2..2b4f76f4c5 100644 --- a/webrtc/video/call.cc +++ b/webrtc/video/call.cc @@ -114,8 +114,7 @@ class Call : public webrtc::Call, public PacketReceiver { virtual void DestroyVideoReceiveStream( webrtc::VideoReceiveStream* receive_stream) OVERRIDE; - virtual uint32_t SendBitrateEstimate() OVERRIDE; - virtual uint32_t ReceiveBitrateEstimate() OVERRIDE; + virtual Stats GetStats() const OVERRIDE; virtual DeliveryStatus DeliverPacket(const uint8_t* packet, size_t length) OVERRIDE; @@ -321,14 +320,26 @@ void Call::DestroyVideoReceiveStream( delete receive_stream_impl; } -uint32_t Call::SendBitrateEstimate() { - // TODO(pbos): Return send-bitrate estimate - return 0; -} - -uint32_t Call::ReceiveBitrateEstimate() { - // TODO(pbos): Return receive-bitrate estimate - return 0; +Call::Stats Call::GetStats() const { + Stats stats; + // Ignoring return values. + uint32_t send_bandwidth = 0; + rtp_rtcp_->GetEstimatedSendBandwidth(base_channel_id_, &send_bandwidth); + stats.send_bandwidth_bps = send_bandwidth; + uint32_t recv_bandwidth = 0; + rtp_rtcp_->GetEstimatedReceiveBandwidth(base_channel_id_, &recv_bandwidth); + stats.recv_bandwidth_bps = recv_bandwidth; + { + ReadLockScoped read_lock(*send_crit_); + for (std::map::const_iterator it = + send_ssrcs_.begin(); + it != send_ssrcs_.end(); + ++it) { + stats.pacer_delay_ms = + std::max(it->second->GetPacerQueuingDelayMs(), stats.pacer_delay_ms); + } + } + return stats; } void Call::SignalNetworkState(NetworkState state) { diff --git a/webrtc/video/call_perf_tests.cc b/webrtc/video/call_perf_tests.cc index 9776fb7e17..9194b088dc 100644 --- a/webrtc/video/call_perf_tests.cc +++ b/webrtc/video/call_perf_tests.cc @@ -502,7 +502,8 @@ void CallPerfTest::TestMinTransmitBitrate(bool pad_to_min_bitrate) { VideoSendStream::Stats stats = send_stream_->GetStats(); if (stats.substreams.size() > 0) { assert(stats.substreams.size() == 1); - int bitrate_kbps = stats.substreams.begin()->second.bitrate_bps / 1000; + int bitrate_kbps = + stats.substreams.begin()->second.total_bitrate_bps / 1000; if (bitrate_kbps > 0) { test::PrintResult( "bitrate_stats_", diff --git a/webrtc/video/end_to_end_tests.cc b/webrtc/video/end_to_end_tests.cc index 96249c3c25..80d8e19a78 100644 --- a/webrtc/video/end_to_end_tests.cc +++ b/webrtc/video/end_to_end_tests.cc @@ -1202,6 +1202,53 @@ TEST_F(EndToEndTest, ReceiveStreamSendsRemb) { RunBaseTest(&test); } +TEST_F(EndToEndTest, VerifyBandwidthStats) { + class RtcpObserver : public test::EndToEndTest, public PacketReceiver { + public: + RtcpObserver() + : EndToEndTest(kDefaultTimeoutMs), + sender_call_(NULL), + receiver_call_(NULL), + has_seen_pacer_delay_(false) {} + + virtual DeliveryStatus DeliverPacket(const uint8_t* packet, + size_t length) OVERRIDE { + Call::Stats sender_stats = sender_call_->GetStats(); + Call::Stats receiver_stats = receiver_call_->GetStats(); + if (!has_seen_pacer_delay_) + has_seen_pacer_delay_ = sender_stats.pacer_delay_ms > 0; + if (sender_stats.send_bandwidth_bps > 0 && + receiver_stats.recv_bandwidth_bps > 0 && has_seen_pacer_delay_) + observation_complete_->Set(); + return receiver_call_->Receiver()->DeliverPacket(packet, length); + } + + virtual void OnCallsCreated(Call* sender_call, + Call* receiver_call) OVERRIDE { + sender_call_ = sender_call; + receiver_call_ = receiver_call; + } + + virtual void PerformTest() OVERRIDE { + EXPECT_EQ(kEventSignaled, Wait()) << "Timed out while waiting for " + "non-zero bandwidth stats."; + } + + virtual void SetReceivers( + PacketReceiver* send_transport_receiver, + PacketReceiver* receive_transport_receiver) OVERRIDE { + test::RtpRtcpObserver::SetReceivers(this, receive_transport_receiver); + } + + private: + Call* sender_call_; + Call* receiver_call_; + bool has_seen_pacer_delay_; + } test; + + RunBaseTest(&test); +} + void EndToEndTest::TestXrReceiverReferenceTimeReport(bool enable_rrtr) { static const int kNumRtcpReportPacketsToObserve = 5; class RtcpXrObserver : public test::EndToEndTest { @@ -1434,7 +1481,7 @@ TEST_F(EndToEndTest, GetStats) { // Make sure all fields have been populated. receive_stats_filled_["IncomingRate"] |= - stats.network_frame_rate != 0 || stats.bitrate_bps != 0; + stats.network_frame_rate != 0 || stats.total_bitrate_bps != 0; receive_stats_filled_["FrameCallback"] |= stats.decode_frame_rate != 0; @@ -1465,7 +1512,7 @@ TEST_F(EndToEndTest, GetStats) { send_stats_filled_["NumStreams"] |= stats.substreams.size() == expected_send_ssrcs_.size(); - for (std::map::const_iterator it = + for (std::map::const_iterator it = stats.substreams.begin(); it != stats.substreams.end(); ++it) { @@ -1475,7 +1522,7 @@ TEST_F(EndToEndTest, GetStats) { send_stats_filled_[CompoundKey("IncomingRate", it->first)] |= stats.input_frame_rate != 0; - const StreamStats& stream_stats = it->second; + const SsrcStats& stream_stats = it->second; send_stats_filled_[CompoundKey("StatisticsUpdated", it->first)] |= stream_stats.rtcp_stats.cumulative_lost != 0 || @@ -1490,7 +1537,7 @@ TEST_F(EndToEndTest, GetStats) { send_stats_filled_[CompoundKey("BitrateStatisticsObserver", it->first)] |= - stream_stats.bitrate_bps != 0; + stream_stats.total_bitrate_bps != 0; send_stats_filled_[CompoundKey("FrameCountObserver", it->first)] |= stream_stats.delta_frames != 0 || stream_stats.key_frames != 0; diff --git a/webrtc/video/receive_statistics_proxy.cc b/webrtc/video/receive_statistics_proxy.cc index 3826283447..ca0bcdfbda 100644 --- a/webrtc/video/receive_statistics_proxy.cc +++ b/webrtc/video/receive_statistics_proxy.cc @@ -58,10 +58,10 @@ std::string ReceiveStatisticsProxy::GetCName() const { void ReceiveStatisticsProxy::IncomingRate(const int video_channel, const unsigned int framerate, - const unsigned int bitrate) { + const unsigned int bitrate_bps) { CriticalSectionScoped lock(crit_.get()); stats_.network_frame_rate = framerate; - stats_.bitrate_bps = bitrate; + stats_.total_bitrate_bps = bitrate_bps; } void ReceiveStatisticsProxy::StatisticsUpdated( diff --git a/webrtc/video/receive_statistics_proxy.h b/webrtc/video/receive_statistics_proxy.h index b5fbf860fb..a1b6735b0b 100644 --- a/webrtc/video/receive_statistics_proxy.h +++ b/webrtc/video/receive_statistics_proxy.h @@ -52,7 +52,7 @@ class ReceiveStatisticsProxy : public ViEDecoderObserver, const VideoCodec& video_codec) OVERRIDE {} virtual void IncomingRate(const int video_channel, const unsigned int framerate, - const unsigned int bitrate) OVERRIDE; + const unsigned int bitrate_bps) OVERRIDE; virtual void DecoderTiming(int decode_ms, int max_decode_ms, int current_delay_ms, diff --git a/webrtc/video/send_statistics_proxy.cc b/webrtc/video/send_statistics_proxy.cc index 1b6081d2a1..f2df0ed2f5 100644 --- a/webrtc/video/send_statistics_proxy.cc +++ b/webrtc/video/send_statistics_proxy.cc @@ -29,6 +29,7 @@ void SendStatisticsProxy::OutgoingRate(const int video_channel, const unsigned int bitrate) { CriticalSectionScoped lock(crit_.get()); stats_.encode_frame_rate = framerate; + stats_.media_bitrate_bps = bitrate; } void SendStatisticsProxy::SuspendChange(int video_channel, bool is_suspended) { @@ -47,8 +48,8 @@ VideoSendStream::Stats SendStatisticsProxy::GetStats() const { return stats_; } -StreamStats* SendStatisticsProxy::GetStatsEntry(uint32_t ssrc) { - std::map::iterator it = stats_.substreams.find(ssrc); +SsrcStats* SendStatisticsProxy::GetStatsEntry(uint32_t ssrc) { + std::map::iterator it = stats_.substreams.find(ssrc); if (it != stats_.substreams.end()) return &it->second; @@ -66,7 +67,7 @@ StreamStats* SendStatisticsProxy::GetStatsEntry(uint32_t ssrc) { void SendStatisticsProxy::StatisticsUpdated(const RtcpStatistics& statistics, uint32_t ssrc) { CriticalSectionScoped lock(crit_.get()); - StreamStats* stats = GetStatsEntry(ssrc); + SsrcStats* stats = GetStatsEntry(ssrc); if (stats == NULL) return; @@ -77,28 +78,30 @@ void SendStatisticsProxy::DataCountersUpdated( const StreamDataCounters& counters, uint32_t ssrc) { CriticalSectionScoped lock(crit_.get()); - StreamStats* stats = GetStatsEntry(ssrc); + SsrcStats* stats = GetStatsEntry(ssrc); if (stats == NULL) return; stats->rtp_stats = counters; } -void SendStatisticsProxy::Notify(const BitrateStatistics& bitrate, +void SendStatisticsProxy::Notify(const BitrateStatistics& total_stats, + const BitrateStatistics& retransmit_stats, uint32_t ssrc) { CriticalSectionScoped lock(crit_.get()); - StreamStats* stats = GetStatsEntry(ssrc); + SsrcStats* stats = GetStatsEntry(ssrc); if (stats == NULL) return; - stats->bitrate_bps = bitrate.bitrate_bps; + stats->total_bitrate_bps = total_stats.bitrate_bps; + stats->retransmit_bitrate_bps = retransmit_stats.bitrate_bps; } void SendStatisticsProxy::FrameCountUpdated(FrameType frame_type, uint32_t frame_count, const unsigned int ssrc) { CriticalSectionScoped lock(crit_.get()); - StreamStats* stats = GetStatsEntry(ssrc); + SsrcStats* stats = GetStatsEntry(ssrc); if (stats == NULL) return; @@ -120,7 +123,7 @@ void SendStatisticsProxy::SendSideDelayUpdated(int avg_delay_ms, int max_delay_ms, uint32_t ssrc) { CriticalSectionScoped lock(crit_.get()); - StreamStats* stats = GetStatsEntry(ssrc); + SsrcStats* stats = GetStatsEntry(ssrc); if (stats == NULL) return; stats->avg_delay_ms = avg_delay_ms; diff --git a/webrtc/video/send_statistics_proxy.h b/webrtc/video/send_statistics_proxy.h index ef459dab55..2f645b1ebe 100644 --- a/webrtc/video/send_statistics_proxy.h +++ b/webrtc/video/send_statistics_proxy.h @@ -46,7 +46,9 @@ class SendStatisticsProxy : public RtcpStatisticsCallback, uint32_t ssrc) OVERRIDE; // From BitrateStatisticsObserver. - virtual void Notify(const BitrateStatistics& stats, uint32_t ssrc) OVERRIDE; + virtual void Notify(const BitrateStatistics& total_stats, + const BitrateStatistics& retransmit_stats, + uint32_t ssrc) OVERRIDE; // From FrameCountObserver. virtual void FrameCountUpdated(FrameType frame_type, @@ -75,7 +77,7 @@ class SendStatisticsProxy : public RtcpStatisticsCallback, uint32_t ssrc) OVERRIDE; private: - StreamStats* GetStatsEntry(uint32_t ssrc) EXCLUSIVE_LOCKS_REQUIRED(crit_); + SsrcStats* GetStatsEntry(uint32_t ssrc) EXCLUSIVE_LOCKS_REQUIRED(crit_); const VideoSendStream::Config config_; scoped_ptr crit_; diff --git a/webrtc/video/send_statistics_proxy_unittest.cc b/webrtc/video/send_statistics_proxy_unittest.cc index f768452f09..d7750f8aaf 100644 --- a/webrtc/video/send_statistics_proxy_unittest.cc +++ b/webrtc/video/send_statistics_proxy_unittest.cc @@ -44,22 +44,23 @@ class SendStatisticsProxyTest : public ::testing::Test { void ExpectEqual(VideoSendStream::Stats one, VideoSendStream::Stats other) { EXPECT_EQ(one.input_frame_rate, other.input_frame_rate); EXPECT_EQ(one.encode_frame_rate, other.encode_frame_rate); + EXPECT_EQ(one.media_bitrate_bps, other.media_bitrate_bps); EXPECT_EQ(one.suspended, other.suspended); EXPECT_EQ(one.substreams.size(), other.substreams.size()); - for (std::map::const_iterator it = + for (std::map::const_iterator it = one.substreams.begin(); it != one.substreams.end(); ++it) { - std::map::const_iterator corresponding_it = + std::map::const_iterator corresponding_it = other.substreams.find(it->first); ASSERT_TRUE(corresponding_it != other.substreams.end()); - const StreamStats& a = it->second; - const StreamStats& b = corresponding_it->second; + const SsrcStats& a = it->second; + const SsrcStats& b = corresponding_it->second; EXPECT_EQ(a.key_frames, b.key_frames); EXPECT_EQ(a.delta_frames, b.delta_frames); - EXPECT_EQ(a.bitrate_bps, b.bitrate_bps); + EXPECT_EQ(a.total_bitrate_bps, b.total_bitrate_bps); EXPECT_EQ(a.avg_delay_ms, b.avg_delay_ms); EXPECT_EQ(a.max_delay_ms, b.max_delay_ms); @@ -84,7 +85,7 @@ class SendStatisticsProxyTest : public ::testing::Test { int avg_delay_ms_; int max_delay_ms_; VideoSendStream::Stats expected_; - typedef std::map::const_iterator StreamIterator; + typedef std::map::const_iterator StreamIterator; }; TEST_F(SendStatisticsProxyTest, RtcpStatistics) { @@ -93,7 +94,7 @@ TEST_F(SendStatisticsProxyTest, RtcpStatistics) { it != config_.rtp.ssrcs.end(); ++it) { const uint32_t ssrc = *it; - StreamStats& ssrc_stats = expected_.substreams[ssrc]; + SsrcStats& ssrc_stats = expected_.substreams[ssrc]; // Add statistics with some arbitrary, but unique, numbers. uint32_t offset = ssrc * sizeof(RtcpStatistics); @@ -107,7 +108,7 @@ TEST_F(SendStatisticsProxyTest, RtcpStatistics) { it != config_.rtp.rtx.ssrcs.end(); ++it) { const uint32_t ssrc = *it; - StreamStats& ssrc_stats = expected_.substreams[ssrc]; + SsrcStats& ssrc_stats = expected_.substreams[ssrc]; // Add statistics with some arbitrary, but unique, numbers. uint32_t offset = ssrc * sizeof(RtcpStatistics); @@ -121,17 +122,25 @@ TEST_F(SendStatisticsProxyTest, RtcpStatistics) { ExpectEqual(expected_, stats); } -TEST_F(SendStatisticsProxyTest, FrameRates) { +TEST_F(SendStatisticsProxyTest, CaptureFramerate) { const int capture_fps = 31; - const int encode_fps = 29; ViECaptureObserver* capture_observer = statistics_proxy_.get(); capture_observer->CapturedFrameRate(0, capture_fps); - ViEEncoderObserver* encoder_observer = statistics_proxy_.get(); - encoder_observer->OutgoingRate(0, encode_fps, 0); VideoSendStream::Stats stats = statistics_proxy_->GetStats(); EXPECT_EQ(capture_fps, stats.input_frame_rate); +} + +TEST_F(SendStatisticsProxyTest, EncodedBitrateAndFramerate) { + const int media_bitrate_bps = 500; + const int encode_fps = 29; + + ViEEncoderObserver* encoder_observer = statistics_proxy_.get(); + encoder_observer->OutgoingRate(0, encode_fps, media_bitrate_bps); + + VideoSendStream::Stats stats = statistics_proxy_->GetStats(); + EXPECT_EQ(media_bitrate_bps, stats.media_bitrate_bps); EXPECT_EQ(encode_fps, stats.encode_frame_rate); } @@ -156,8 +165,8 @@ TEST_F(SendStatisticsProxyTest, FrameCounts) { ++it) { const uint32_t ssrc = *it; // Add statistics with some arbitrary, but unique, numbers. - StreamStats& stats = expected_.substreams[ssrc]; - uint32_t offset = ssrc * sizeof(StreamStats); + SsrcStats& stats = expected_.substreams[ssrc]; + uint32_t offset = ssrc * sizeof(SsrcStats); stats.key_frames = offset; stats.delta_frames = offset + 1; observer->FrameCountUpdated(kVideoFrameKey, stats.key_frames, ssrc); @@ -168,8 +177,8 @@ TEST_F(SendStatisticsProxyTest, FrameCounts) { ++it) { const uint32_t ssrc = *it; // Add statistics with some arbitrary, but unique, numbers. - StreamStats& stats = expected_.substreams[ssrc]; - uint32_t offset = ssrc * sizeof(StreamStats); + SsrcStats& stats = expected_.substreams[ssrc]; + uint32_t offset = ssrc * sizeof(SsrcStats); stats.key_frames = offset; stats.delta_frames = offset + 1; observer->FrameCountUpdated(kVideoFrameKey, stats.key_frames, ssrc); @@ -223,21 +232,27 @@ TEST_F(SendStatisticsProxyTest, Bitrate) { it != config_.rtp.ssrcs.end(); ++it) { const uint32_t ssrc = *it; - BitrateStatistics bitrate; + BitrateStatistics total; + BitrateStatistics retransmit; // Use ssrc as bitrate_bps to get a unique value for each stream. - bitrate.bitrate_bps = ssrc; - observer->Notify(bitrate, ssrc); - expected_.substreams[ssrc].bitrate_bps = ssrc; + total.bitrate_bps = ssrc; + retransmit.bitrate_bps = ssrc + 1; + observer->Notify(total, retransmit, ssrc); + expected_.substreams[ssrc].total_bitrate_bps = total.bitrate_bps; + expected_.substreams[ssrc].retransmit_bitrate_bps = retransmit.bitrate_bps; } for (std::vector::const_iterator it = config_.rtp.rtx.ssrcs.begin(); it != config_.rtp.rtx.ssrcs.end(); ++it) { const uint32_t ssrc = *it; - BitrateStatistics bitrate; + BitrateStatistics total; + BitrateStatistics retransmit; // Use ssrc as bitrate_bps to get a unique value for each stream. - bitrate.bitrate_bps = ssrc; - observer->Notify(bitrate, ssrc); - expected_.substreams[ssrc].bitrate_bps = ssrc; + total.bitrate_bps = ssrc; + retransmit.bitrate_bps = ssrc + 1; + observer->Notify(total, retransmit, ssrc); + expected_.substreams[ssrc].total_bitrate_bps = total.bitrate_bps; + expected_.substreams[ssrc].retransmit_bitrate_bps = retransmit.bitrate_bps; } VideoSendStream::Stats stats = statistics_proxy_->GetStats(); @@ -292,9 +307,10 @@ TEST_F(SendStatisticsProxyTest, NoSubstreams) { rtp_callback->DataCountersUpdated(rtp_stats, exluded_ssrc); // From BitrateStatisticsObserver. - BitrateStatistics bitrate; + BitrateStatistics total; + BitrateStatistics retransmit; BitrateStatisticsObserver* bitrate_observer = statistics_proxy_.get(); - bitrate_observer->Notify(bitrate, exluded_ssrc); + bitrate_observer->Notify(total, retransmit, exluded_ssrc); // From FrameCountObserver. FrameCountObserver* fps_observer = statistics_proxy_.get(); diff --git a/webrtc/video/video_send_stream.cc b/webrtc/video/video_send_stream.cc index 28231b005c..489cd14081 100644 --- a/webrtc/video/video_send_stream.cc +++ b/webrtc/video/video_send_stream.cc @@ -492,5 +492,12 @@ void VideoSendStream::SignalNetworkState(Call::NetworkState state) { rtp_rtcp_->SetRTCPStatus(channel_, kRtcpNone); } +int VideoSendStream::GetPacerQueuingDelayMs() const { + int pacer_delay_ms = 0; + if (rtp_rtcp_->GetPacerQueuingDelayMs(channel_, &pacer_delay_ms) != 0) { + return 0; + } + return pacer_delay_ms; +} } // namespace internal } // namespace webrtc diff --git a/webrtc/video/video_send_stream.h b/webrtc/video/video_send_stream.h index f7874308eb..873785dc9e 100644 --- a/webrtc/video/video_send_stream.h +++ b/webrtc/video/video_send_stream.h @@ -74,6 +74,8 @@ class VideoSendStream : public webrtc::VideoSendStream, void SignalNetworkState(Call::NetworkState state); + int GetPacerQueuingDelayMs() const; + private: void ConfigureSsrcs(); TransportAdapter transport_adapter_; diff --git a/webrtc/video/video_send_stream_tests.cc b/webrtc/video/video_send_stream_tests.cc index b863957bed..3ab1127444 100644 --- a/webrtc/video/video_send_stream_tests.cc +++ b/webrtc/video/video_send_stream_tests.cc @@ -962,8 +962,8 @@ TEST_F(VideoSendStreamTest, ProducesStats) { config_.rtp.ssrcs.begin(), config_.rtp.ssrcs.end(), ssrc)); // Check for data populated by various sources. RTCP excluded as this // data is received from remote side. Tested in call tests instead. - const StreamStats& entry = stats.substreams[ssrc]; - if (entry.key_frames > 0u && entry.bitrate_bps > 0 && + const SsrcStats& entry = stats.substreams[ssrc]; + if (entry.key_frames > 0u && entry.total_bitrate_bps > 0 && entry.rtp_stats.packets > 0u && entry.avg_delay_ms > 0 && entry.max_delay_ms > 0) { return true; @@ -1045,20 +1045,20 @@ TEST_F(VideoSendStreamTest, MinTransmitBitrateRespectsRemb) { VideoSendStream::Stats stats = stream_->GetStats(); if (!stats.substreams.empty()) { EXPECT_EQ(1u, stats.substreams.size()); - int bitrate_bps = stats.substreams.begin()->second.bitrate_bps; - test::PrintResult( - "bitrate_stats_", - "min_transmit_bitrate_low_remb", - "bitrate_bps", - static_cast(bitrate_bps), - "bps", - false); - if (bitrate_bps > kHighBitrateBps) { + int total_bitrate_bps = + stats.substreams.begin()->second.total_bitrate_bps; + test::PrintResult("bitrate_stats_", + "min_transmit_bitrate_low_remb", + "bitrate_bps", + static_cast(total_bitrate_bps), + "bps", + false); + if (total_bitrate_bps > kHighBitrateBps) { rtp_rtcp_->SetREMBData(kRembBitrateBps, 1, &header.ssrc); rtp_rtcp_->Process(); bitrate_capped_ = true; } else if (bitrate_capped_ && - bitrate_bps < kRembRespectedBitrateBps) { + total_bitrate_bps < kRembRespectedBitrateBps) { observation_complete_->Set(); } } diff --git a/webrtc/video_engine/vie_channel.h b/webrtc/video_engine/vie_channel.h index 0327906999..3b8d96a283 100644 --- a/webrtc/video_engine/vie_channel.h +++ b/webrtc/video_engine/vie_channel.h @@ -415,10 +415,12 @@ class ViEChannel class RegisterableBitrateStatisticsObserver: public RegisterableCallback { - virtual void Notify(const BitrateStatistics& stats, uint32_t ssrc) { + virtual void Notify(const BitrateStatistics& total_stats, + const BitrateStatistics& retransmit_stats, + uint32_t ssrc) { CriticalSectionScoped cs(critsect_.get()); if (callback_) - callback_->Notify(stats, ssrc); + callback_->Notify(total_stats, retransmit_stats, ssrc); } } send_bitrate_observer_; diff --git a/webrtc/video_receive_stream.h b/webrtc/video_receive_stream.h index 5ab898c77d..a8620d98c3 100644 --- a/webrtc/video_receive_stream.h +++ b/webrtc/video_receive_stream.h @@ -63,7 +63,7 @@ class VideoReceiveStream { int expected_delay_ms; }; - struct Stats : public StreamStats { + struct Stats : public SsrcStats { Stats() : network_frame_rate(0), decode_frame_rate(0), diff --git a/webrtc/video_send_stream.h b/webrtc/video_send_stream.h index aa5033afc2..a9aba94526 100644 --- a/webrtc/video_send_stream.h +++ b/webrtc/video_send_stream.h @@ -41,11 +41,13 @@ class VideoSendStream { Stats() : input_frame_rate(0), encode_frame_rate(0), + media_bitrate_bps(0), suspended(false) {} int input_frame_rate; int encode_frame_rate; + int media_bitrate_bps; bool suspended; - std::map substreams; + std::map substreams; }; struct Config {