Network up/down signaling in Call.
BUG=2429
R=mflodman@webrtc.org, stefan@webrtc.org
Review URL: https://webrtc-codereview.appspot.com/13109005
git-svn-id: http://webrtc.googlecode.com/svn/trunk/webrtc@7044 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/call.h b/call.h
index 86cf1c6..3030074 100644
--- a/call.h
+++ b/call.h
@@ -56,6 +56,10 @@
// etc.
class Call {
public:
+ enum NetworkState {
+ kNetworkUp,
+ kNetworkDown,
+ };
struct Config {
explicit Config(newapi::Transport* send_transport)
: webrtc_config(NULL),
@@ -111,6 +115,8 @@
// differ from the actual receive bitrate.
virtual uint32_t ReceiveBitrateEstimate() = 0;
+ virtual void SignalNetworkState(NetworkState state) = 0;
+
virtual ~Call() {}
};
} // namespace webrtc
diff --git a/video/call.cc b/video/call.cc
index cd29d62..8b71acf 100644
--- a/video/call.cc
+++ b/video/call.cc
@@ -28,6 +28,8 @@
#include "webrtc/video_engine/include/vie_base.h"
#include "webrtc/video_engine/include/vie_codec.h"
#include "webrtc/video_engine/include/vie_rtp_rtcp.h"
+#include "webrtc/video_engine/include/vie_network.h"
+#include "webrtc/video_engine/include/vie_rtp_rtcp.h"
namespace webrtc {
const char* RtpExtension::kTOffset = "urn:ietf:params:rtp-hdrext:toffset";
@@ -93,18 +95,26 @@
virtual DeliveryStatus DeliverPacket(const uint8_t* packet,
size_t length) OVERRIDE;
+ virtual void SignalNetworkState(NetworkState state) OVERRIDE;
+
private:
DeliveryStatus DeliverRtcp(const uint8_t* packet, size_t length);
DeliveryStatus DeliverRtp(const uint8_t* packet, size_t length);
Call::Config config_;
- std::map<uint32_t, VideoReceiveStream*> receive_ssrcs_
- GUARDED_BY(receive_lock_);
- scoped_ptr<RWLockWrapper> receive_lock_;
+ // Needs to be held while write-locking |receive_crit_| or |send_crit_|. This
+ // ensures that we have a consistent network state signalled to all senders
+ // and receivers.
+ scoped_ptr<CriticalSectionWrapper> network_enabled_crit_;
+ bool network_enabled_ GUARDED_BY(network_enabled_crit_);
- std::map<uint32_t, VideoSendStream*> send_ssrcs_ GUARDED_BY(send_lock_);
- scoped_ptr<RWLockWrapper> send_lock_;
+ scoped_ptr<RWLockWrapper> receive_crit_;
+ std::map<uint32_t, VideoReceiveStream*> receive_ssrcs_
+ GUARDED_BY(receive_crit_);
+
+ scoped_ptr<RWLockWrapper> send_crit_;
+ std::map<uint32_t, VideoSendStream*> send_ssrcs_ GUARDED_BY(send_crit_);
scoped_ptr<CpuOveruseObserverProxy> overuse_observer_proxy_;
@@ -135,8 +145,10 @@
Call::Call(webrtc::VideoEngine* video_engine, const Call::Config& config)
: config_(config),
- receive_lock_(RWLockWrapper::CreateRWLock()),
- send_lock_(RWLockWrapper::CreateRWLock()),
+ network_enabled_crit_(CriticalSectionWrapper::CreateCriticalSection()),
+ network_enabled_(true),
+ receive_crit_(RWLockWrapper::CreateRWLock()),
+ send_crit_(RWLockWrapper::CreateRWLock()),
video_engine_(video_engine),
base_channel_id_(-1) {
assert(video_engine != NULL);
@@ -192,11 +204,16 @@
config_.start_bitrate_bps != -1 ? config_.start_bitrate_bps
: kDefaultVideoStreamBitrateBps);
- WriteLockScoped write_lock(*send_lock_);
+ // This needs to be taken before send_crit_ as both locks need to be held
+ // while changing network state.
+ CriticalSectionScoped lock(network_enabled_crit_.get());
+ WriteLockScoped write_lock(*send_crit_);
for (size_t i = 0; i < config.rtp.ssrcs.size(); ++i) {
assert(send_ssrcs_.find(config.rtp.ssrcs[i]) == send_ssrcs_.end());
send_ssrcs_[config.rtp.ssrcs[i]] = send_stream;
}
+ if (!network_enabled_)
+ send_stream->SignalNetworkState(kNetworkDown);
return send_stream;
}
@@ -207,7 +224,7 @@
VideoSendStream* send_stream_impl = NULL;
{
- WriteLockScoped write_lock(*send_lock_);
+ WriteLockScoped write_lock(*send_crit_);
std::map<uint32_t, VideoSendStream*>::iterator it = send_ssrcs_.begin();
while (it != send_ssrcs_.end()) {
if (it->second == static_cast<VideoSendStream*>(send_stream)) {
@@ -240,7 +257,10 @@
config_.voice_engine,
base_channel_id_);
- WriteLockScoped write_lock(*receive_lock_);
+ // This needs to be taken before receive_crit_ as both locks need to be held
+ // while changing network state.
+ CriticalSectionScoped lock(network_enabled_crit_.get());
+ WriteLockScoped write_lock(*receive_crit_);
assert(receive_ssrcs_.find(config.rtp.remote_ssrc) == receive_ssrcs_.end());
receive_ssrcs_[config.rtp.remote_ssrc] = receive_stream;
// TODO(pbos): Configure different RTX payloads per receive payload.
@@ -249,6 +269,8 @@
if (it != config.rtp.rtx.end())
receive_ssrcs_[it->second.ssrc] = receive_stream;
+ if (!network_enabled_)
+ receive_stream->SignalNetworkState(kNetworkDown);
return receive_stream;
}
@@ -258,7 +280,7 @@
VideoReceiveStream* receive_stream_impl = NULL;
{
- WriteLockScoped write_lock(*receive_lock_);
+ WriteLockScoped write_lock(*receive_crit_);
// Remove all ssrcs pointing to a receive stream. As RTX retransmits on a
// separate SSRC there can be either one or two.
std::map<uint32_t, VideoReceiveStream*>::iterator it =
@@ -289,6 +311,31 @@
return 0;
}
+void Call::SignalNetworkState(NetworkState state) {
+ // Take crit for entire function, it needs to be held while updating streams
+ // to guarantee a consistent state across streams.
+ CriticalSectionScoped lock(network_enabled_crit_.get());
+ network_enabled_ = state == kNetworkUp;
+ {
+ ReadLockScoped write_lock(*send_crit_);
+ for (std::map<uint32_t, VideoSendStream*>::iterator it =
+ send_ssrcs_.begin();
+ it != send_ssrcs_.end();
+ ++it) {
+ it->second->SignalNetworkState(state);
+ }
+ }
+ {
+ ReadLockScoped write_lock(*receive_crit_);
+ for (std::map<uint32_t, VideoReceiveStream*>::iterator it =
+ receive_ssrcs_.begin();
+ it != receive_ssrcs_.end();
+ ++it) {
+ it->second->SignalNetworkState(state);
+ }
+ }
+}
+
PacketReceiver::DeliveryStatus Call::DeliverRtcp(const uint8_t* packet,
size_t length) {
// TODO(pbos): Figure out what channel needs it actually.
@@ -297,7 +344,7 @@
// there's no receiver of the packet.
bool rtcp_delivered = false;
{
- ReadLockScoped read_lock(*receive_lock_);
+ ReadLockScoped read_lock(*receive_crit_);
for (std::map<uint32_t, VideoReceiveStream*>::iterator it =
receive_ssrcs_.begin();
it != receive_ssrcs_.end();
@@ -308,7 +355,7 @@
}
{
- ReadLockScoped read_lock(*send_lock_);
+ ReadLockScoped read_lock(*send_crit_);
for (std::map<uint32_t, VideoSendStream*>::iterator it =
send_ssrcs_.begin();
it != send_ssrcs_.end();
@@ -329,7 +376,7 @@
const uint8_t* ptr = &packet[8];
uint32_t ssrc = ptr[0] << 24 | ptr[1] << 16 | ptr[2] << 8 | ptr[3];
- ReadLockScoped read_lock(*receive_lock_);
+ ReadLockScoped read_lock(*receive_crit_);
std::map<uint32_t, VideoReceiveStream*>::iterator it =
receive_ssrcs_.find(ssrc);
diff --git a/video/end_to_end_tests.cc b/video/end_to_end_tests.cc
index 739944a..11ce642 100644
--- a/video/end_to_end_tests.cc
+++ b/video/end_to_end_tests.cc
@@ -43,6 +43,7 @@
namespace webrtc {
+static const unsigned long kSilenceTimeoutMs = 2000;
static const int kRedPayloadType = 118;
static const int kUlpfecPayloadType = 119;
@@ -56,6 +57,19 @@
}
protected:
+ class UnusedTransport : public newapi::Transport {
+ private:
+ virtual bool SendRtp(const uint8_t* packet, size_t length) OVERRIDE {
+ ADD_FAILURE() << "Unexpected RTP sent.";
+ return false;
+ }
+
+ virtual bool SendRtcp(const uint8_t* packet, size_t length) OVERRIDE {
+ ADD_FAILURE() << "Unexpected RTCP sent.";
+ return false;
+ }
+ };
+
void DecodesRetransmittedFrame(bool retransmit_over_rtx);
void ReceivesPliAndRecovers(int rtp_history_ms);
void RespectsRtcpMode(newapi::RtcpMode rtcp_mode);
@@ -1840,6 +1854,228 @@
TestRtpStatePreservation(true);
}
+TEST_F(EndToEndTest, RespectsNetworkState) {
+ // TODO(pbos): Remove accepted downtime packets etc. when signaling network
+ // down blocks until no more packets will be sent.
+
+ // Pacer will send from its packet list and then send required padding before
+ // checking paused_ again. This should be enough for one round of pacing,
+ // otherwise increase.
+ static const int kNumAcceptedDowntimeRtp = 5;
+ // A single RTCP may be in the pipeline.
+ static const int kNumAcceptedDowntimeRtcp = 1;
+ class NetworkStateTest : public test::EndToEndTest, public test::FakeEncoder {
+ public:
+ NetworkStateTest()
+ : EndToEndTest(kDefaultTimeoutMs),
+ FakeEncoder(Clock::GetRealTimeClock()),
+ test_crit_(CriticalSectionWrapper::CreateCriticalSection()),
+ encoded_frames_(EventWrapper::Create()),
+ sender_packets_(EventWrapper::Create()),
+ receiver_packets_(EventWrapper::Create()),
+ sender_state_(Call::kNetworkUp),
+ down_sender_rtp_(0),
+ down_sender_rtcp_(0),
+ receiver_state_(Call::kNetworkUp),
+ down_receiver_rtcp_(0),
+ down_frames_(0) {}
+
+ virtual Action OnSendRtp(const uint8_t* packet, size_t length) OVERRIDE {
+ CriticalSectionScoped lock(test_crit_.get());
+ if (sender_state_ == Call::kNetworkDown) {
+ ++down_sender_rtp_;
+ EXPECT_LE(down_sender_rtp_, kNumAcceptedDowntimeRtp)
+ << "RTP sent during sender-side downtime.";
+ if (down_sender_rtp_> kNumAcceptedDowntimeRtp)
+ sender_packets_->Set();
+ } else {
+ sender_packets_->Set();
+ }
+ return SEND_PACKET;
+ }
+
+ virtual Action OnSendRtcp(const uint8_t* packet, size_t length) OVERRIDE {
+ CriticalSectionScoped lock(test_crit_.get());
+ if (sender_state_ == Call::kNetworkDown) {
+ ++down_sender_rtcp_;
+ EXPECT_LE(down_sender_rtcp_, kNumAcceptedDowntimeRtcp)
+ << "RTCP sent during sender-side downtime.";
+ if (down_sender_rtcp_ > kNumAcceptedDowntimeRtcp)
+ sender_packets_->Set();
+ } else {
+ sender_packets_->Set();
+ }
+ return SEND_PACKET;
+ }
+
+ virtual Action OnReceiveRtp(const uint8_t* packet, size_t length) OVERRIDE {
+ ADD_FAILURE() << "Unexpected receiver RTP, should not be sending.";
+ return SEND_PACKET;
+ }
+
+ virtual Action OnReceiveRtcp(const uint8_t* packet,
+ size_t length) OVERRIDE {
+ CriticalSectionScoped lock(test_crit_.get());
+ if (receiver_state_ == Call::kNetworkDown) {
+ ++down_receiver_rtcp_;
+ EXPECT_LE(down_receiver_rtcp_, kNumAcceptedDowntimeRtcp)
+ << "RTCP sent during receiver-side downtime.";
+ if (down_receiver_rtcp_ > kNumAcceptedDowntimeRtcp)
+ receiver_packets_->Set();
+ } else {
+ receiver_packets_->Set();
+ }
+ return SEND_PACKET;
+ }
+
+ virtual void OnCallsCreated(Call* sender_call,
+ Call* receiver_call) OVERRIDE {
+ sender_call_ = sender_call;
+ receiver_call_ = receiver_call;
+ }
+
+ virtual void ModifyConfigs(
+ VideoSendStream::Config* send_config,
+ std::vector<VideoReceiveStream::Config>* receive_configs,
+ std::vector<VideoStream>* video_streams) OVERRIDE {
+ send_config->encoder_settings.encoder = this;
+ }
+
+ virtual void PerformTest() OVERRIDE {
+ EXPECT_EQ(kEventSignaled, encoded_frames_->Wait(kDefaultTimeoutMs))
+ << "No frames received by the encoder.";
+ EXPECT_EQ(kEventSignaled, sender_packets_->Wait(kDefaultTimeoutMs))
+ << "Timed out waiting for send-side packets.";
+ EXPECT_EQ(kEventSignaled, receiver_packets_->Wait(kDefaultTimeoutMs))
+ << "Timed out waiting for receiver-side packets.";
+
+ // Sender-side network down.
+ sender_call_->SignalNetworkState(Call::kNetworkDown);
+ {
+ CriticalSectionScoped lock(test_crit_.get());
+ sender_packets_->Reset(); // Earlier packets should not count.
+ sender_state_ = Call::kNetworkDown;
+ }
+ EXPECT_EQ(kEventTimeout, sender_packets_->Wait(kSilenceTimeoutMs))
+ << "Packets sent during sender-network downtime.";
+ EXPECT_EQ(kEventSignaled, receiver_packets_->Wait(kDefaultTimeoutMs))
+ << "Timed out waiting for receiver-side packets.";
+ // Receiver-side network down.
+ receiver_call_->SignalNetworkState(Call::kNetworkDown);
+ {
+ CriticalSectionScoped lock(test_crit_.get());
+ receiver_packets_->Reset(); // Earlier packets should not count.
+ receiver_state_ = Call::kNetworkDown;
+ }
+ EXPECT_EQ(kEventTimeout, receiver_packets_->Wait(kSilenceTimeoutMs))
+ << "Packets sent during receiver-network downtime.";
+
+ // Network back up again for both.
+ {
+ CriticalSectionScoped lock(test_crit_.get());
+ sender_packets_->Reset(); // Earlier packets should not count.
+ receiver_packets_->Reset(); // Earlier packets should not count.
+ sender_state_ = receiver_state_ = Call::kNetworkUp;
+ }
+ sender_call_->SignalNetworkState(Call::kNetworkUp);
+ receiver_call_->SignalNetworkState(Call::kNetworkUp);
+ EXPECT_EQ(kEventSignaled, sender_packets_->Wait(kDefaultTimeoutMs))
+ << "Timed out waiting for send-side packets.";
+ EXPECT_EQ(kEventSignaled, receiver_packets_->Wait(kDefaultTimeoutMs))
+ << "Timed out waiting for receiver-side packets.";
+ }
+
+ virtual int32_t Encode(const I420VideoFrame& input_image,
+ const CodecSpecificInfo* codec_specific_info,
+ const std::vector<VideoFrameType>* frame_types)
+ OVERRIDE {
+ {
+ CriticalSectionScoped lock(test_crit_.get());
+ if (sender_state_ == Call::kNetworkDown) {
+ ++down_frames_;
+ EXPECT_LE(down_frames_, 1)
+ << "Encoding more than one frame while network is down.";
+ if (down_frames_ > 1)
+ encoded_frames_->Set();
+ } else {
+ encoded_frames_->Set();
+ }
+ }
+ return test::FakeEncoder::Encode(
+ input_image, codec_specific_info, frame_types);
+ }
+
+ private:
+ const scoped_ptr<CriticalSectionWrapper> test_crit_;
+ scoped_ptr<EventWrapper> encoded_frames_;
+ scoped_ptr<EventWrapper> sender_packets_;
+ scoped_ptr<EventWrapper> receiver_packets_;
+ Call* sender_call_;
+ Call* receiver_call_;
+ Call::NetworkState sender_state_ GUARDED_BY(test_crit_);
+ int down_sender_rtp_ GUARDED_BY(test_crit_);
+ int down_sender_rtcp_ GUARDED_BY(test_crit_);
+ Call::NetworkState receiver_state_ GUARDED_BY(test_crit_);
+ int down_receiver_rtcp_ GUARDED_BY(test_crit_);
+ int down_frames_ GUARDED_BY(test_crit_);
+ } test;
+
+ RunBaseTest(&test);
+}
+
+TEST_F(EndToEndTest, NewSendStreamsRespectNetworkDown) {
+ class UnusedEncoder : public test::FakeEncoder {
+ public:
+ UnusedEncoder() : FakeEncoder(Clock::GetRealTimeClock()) {}
+ virtual int32_t Encode(const I420VideoFrame& input_image,
+ const CodecSpecificInfo* codec_specific_info,
+ const std::vector<VideoFrameType>* frame_types)
+ OVERRIDE {
+ ADD_FAILURE() << "Unexpected frame encode.";
+ return test::FakeEncoder::Encode(
+ input_image, codec_specific_info, frame_types);
+ }
+ };
+
+ UnusedTransport transport;
+ CreateSenderCall(Call::Config(&transport));
+ sender_call_->SignalNetworkState(Call::kNetworkDown);
+
+ CreateSendConfig(1);
+ UnusedEncoder unused_encoder;
+ send_config_.encoder_settings.encoder = &unused_encoder;
+ CreateStreams();
+ CreateFrameGeneratorCapturer();
+
+ Start();
+ SleepMs(kSilenceTimeoutMs);
+ Stop();
+
+ DestroyStreams();
+}
+
+TEST_F(EndToEndTest, NewReceiveStreamsRespectNetworkDown) {
+ test::DirectTransport sender_transport;
+ CreateSenderCall(Call::Config(&sender_transport));
+ UnusedTransport transport;
+ CreateReceiverCall(Call::Config(&transport));
+ sender_transport.SetReceiver(receiver_call_->Receiver());
+
+ receiver_call_->SignalNetworkState(Call::kNetworkDown);
+
+ CreateSendConfig(1);
+ CreateMatchingReceiveConfigs();
+ CreateStreams();
+ CreateFrameGeneratorCapturer();
+
+ Start();
+ SleepMs(kSilenceTimeoutMs);
+ Stop();
+
+ sender_transport.StopSending();
+
+ DestroyStreams();
+}
} // namespace webrtc
#endif // !WEBRTC_ANDROID
diff --git a/video/video_receive_stream.cc b/video/video_receive_stream.cc
index 9c3298c..41a800f 100644
--- a/video/video_receive_stream.cc
+++ b/video/video_receive_stream.cc
@@ -52,14 +52,7 @@
// TODO(pbos): This is not fine grained enough...
rtp_rtcp_->SetNACKStatus(channel_, config_.rtp.nack.rtp_history_ms > 0);
rtp_rtcp_->SetKeyFrameRequestMethod(channel_, kViEKeyFrameRequestPliRtcp);
- switch (config_.rtp.rtcp_mode) {
- case newapi::kRtcpCompound:
- rtp_rtcp_->SetRTCPStatus(channel_, kRtcpCompound_RFC4585);
- break;
- case newapi::kRtcpReducedSize:
- rtp_rtcp_->SetRTCPStatus(channel_, kRtcpNonCompound_RFC5506);
- break;
- }
+ SetRtcpMode(config_.rtp.rtcp_mode);
assert(config_.rtp.remote_ssrc != 0);
// TODO(pbos): What's an appropriate local_ssrc for receive-only streams?
@@ -264,5 +257,24 @@
return 0;
}
+
+void VideoReceiveStream::SignalNetworkState(Call::NetworkState state) {
+ if (state == Call::kNetworkUp)
+ SetRtcpMode(config_.rtp.rtcp_mode);
+ network_->SetNetworkTransmissionState(channel_, state == Call::kNetworkUp);
+ if (state == Call::kNetworkDown)
+ rtp_rtcp_->SetRTCPStatus(channel_, kRtcpNone);
+}
+
+void VideoReceiveStream::SetRtcpMode(newapi::RtcpMode mode) {
+ switch (mode) {
+ case newapi::kRtcpCompound:
+ rtp_rtcp_->SetRTCPStatus(channel_, kRtcpCompound_RFC4585);
+ break;
+ case newapi::kRtcpReducedSize:
+ rtp_rtcp_->SetRTCPStatus(channel_, kRtcpNonCompound_RFC5506);
+ break;
+ }
+}
} // namespace internal
} // namespace webrtc
diff --git a/video/video_receive_stream.h b/video/video_receive_stream.h
index c45ebac..6894828 100644
--- a/video/video_receive_stream.h
+++ b/video/video_receive_stream.h
@@ -13,6 +13,7 @@
#include <vector>
+#include "webrtc/call.h"
#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
#include "webrtc/modules/video_render/include/video_render_defines.h"
#include "webrtc/system_wrappers/interface/clock.h"
@@ -61,11 +62,14 @@
virtual int32_t RenderFrame(const uint32_t stream_id,
I420VideoFrame& video_frame) OVERRIDE;
- public:
+ void SignalNetworkState(Call::NetworkState state);
+
virtual bool DeliverRtcp(const uint8_t* packet, size_t length);
virtual bool DeliverRtp(const uint8_t* packet, size_t length);
private:
+ void SetRtcpMode(newapi::RtcpMode mode);
+
TransportAdapter transport_adapter_;
EncodedFrameCallbackAdapter encoded_frame_proxy_;
const VideoReceiveStream::Config config_;
diff --git a/video/video_send_stream.cc b/video/video_send_stream.cc
index 6597029..624aca8 100644
--- a/video/video_send_stream.cc
+++ b/video/video_send_stream.cc
@@ -460,5 +460,16 @@
return rtp_states;
}
+void VideoSendStream::SignalNetworkState(Call::NetworkState state) {
+ // When network goes up, enable RTCP status before setting transmission state.
+ // When it goes down, disable RTCP afterwards. This ensures that any packets
+ // sent due to the network state changed will not be dropped.
+ if (state == Call::kNetworkUp)
+ rtp_rtcp_->SetRTCPStatus(channel_, kRtcpCompound_RFC4585);
+ network_->SetNetworkTransmissionState(channel_, state == Call::kNetworkUp);
+ if (state == Call::kNetworkDown)
+ rtp_rtcp_->SetRTCPStatus(channel_, kRtcpNone);
+}
+
} // namespace internal
} // namespace webrtc
diff --git a/video/video_send_stream.h b/video/video_send_stream.h
index f1f8f7d..130c1c5 100644
--- a/video/video_send_stream.h
+++ b/video/video_send_stream.h
@@ -14,6 +14,7 @@
#include <map>
#include <vector>
+#include "webrtc/call.h"
#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
#include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h"
#include "webrtc/video/encoded_frame_callback_adapter.h"
@@ -72,6 +73,8 @@
typedef std::map<uint32_t, RtpState> RtpStateMap;
RtpStateMap GetRtpStates() const;
+ void SignalNetworkState(Call::NetworkState state);
+
private:
void ConfigureSsrcs();
TransportAdapter transport_adapter_;