Implement FEC support in VideoReceiveStream.

Added an FEC end-to-end test. NACK+FEC is probably working but not yet tested
as the test for it must introduce packet delays as the underlying API prefers
NACK over FEC if RTT is low.

BUG=3174
R=stefan@webrtc.org

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

git-svn-id: http://webrtc.googlecode.com/svn/trunk@5862 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
pbos@webrtc.org 2014-04-08 11:21:45 +00:00
parent dc80bae2a6
commit 2a03498825
2 changed files with 131 additions and 0 deletions

View File

@ -46,6 +46,8 @@ static const uint32_t kSendRtxSsrc = 0x424242;
static const uint32_t kReceiverLocalSsrc = 0x123456; static const uint32_t kReceiverLocalSsrc = 0x123456;
static const uint8_t kSendPayloadType = 125; static const uint8_t kSendPayloadType = 125;
static const uint8_t kSendRtxPayloadType = 126; static const uint8_t kSendRtxPayloadType = 126;
static const int kRedPayloadType = 118;
static const int kUlpfecPayloadType = 119;
class CallTest : public ::testing::Test { class CallTest : public ::testing::Test {
public: public:
@ -488,6 +490,109 @@ TEST_F(CallTest, ReceivesAndRetransmitsNack) {
DestroyStreams(); DestroyStreams();
} }
TEST_F(CallTest, CanReceiveFec) {
class FecRenderObserver : public test::RtpRtcpObserver, public VideoRenderer {
public:
FecRenderObserver()
: RtpRtcpObserver(kDefaultTimeoutMs),
state_(kFirstPacket),
protected_sequence_number_(0),
protected_frame_timestamp_(0) {}
private:
virtual Action OnSendRtp(const uint8_t* packet, size_t length) OVERRIDE {
RTPHeader header;
EXPECT_TRUE(parser_->Parse(packet, static_cast<int>(length), &header));
EXPECT_EQ(kRedPayloadType, header.payloadType);
int encapsulated_payload_type =
static_cast<int>(packet[header.headerLength]);
if (encapsulated_payload_type != kSendPayloadType)
EXPECT_EQ(kUlpfecPayloadType, encapsulated_payload_type);
switch(state_) {
case kFirstPacket:
state_ = kDropEveryOtherPacketUntilFec;
break;
case kDropEveryOtherPacketUntilFec:
if (encapsulated_payload_type == kUlpfecPayloadType) {
state_ = kDropNextMediaPacket;
return SEND_PACKET;
}
if (header.sequenceNumber % 2 == 0)
return DROP_PACKET;
break;
case kDropNextMediaPacket:
if (encapsulated_payload_type == kSendPayloadType) {
protected_sequence_number_ = header.sequenceNumber;
protected_frame_timestamp_ = header.timestamp;
state_ = kProtectedPacketDropped;
return DROP_PACKET;
}
break;
case kProtectedPacketDropped:
EXPECT_NE(header.sequenceNumber, protected_sequence_number_)
<< "Protected packet retransmitted. Should not happen with FEC.";
break;
}
return SEND_PACKET;
}
virtual void RenderFrame(const I420VideoFrame& video_frame,
int time_to_render_ms) OVERRIDE {
CriticalSectionScoped crit_(lock_.get());
// Rendering frame with timestamp associated with dropped packet -> FEC
// protection worked.
if (state_ == kProtectedPacketDropped &&
video_frame.timestamp() == protected_frame_timestamp_) {
observation_complete_->Set();
}
}
enum {
kFirstPacket,
kDropEveryOtherPacketUntilFec,
kDropNextMediaPacket,
kProtectedPacketDropped,
} state_;
uint32_t protected_sequence_number_;
uint32_t protected_frame_timestamp_;
} observer;
CreateCalls(Call::Config(observer.SendTransport()),
Call::Config(observer.ReceiveTransport()));
observer.SetReceivers(receiver_call_->Receiver(), sender_call_->Receiver());
CreateTestConfigs();
// TODO(pbos): Run this test with combined NACK/FEC enabled as well.
// int rtp_history_ms = 1000;
// receive_config_.rtp.nack.rtp_history_ms = rtp_history_ms;
// send_config_.rtp.nack.rtp_history_ms = rtp_history_ms;
send_config_.rtp.fec.red_payload_type = kRedPayloadType;
send_config_.rtp.fec.ulpfec_payload_type = kUlpfecPayloadType;
receive_config_.rtp.fec.red_payload_type = kRedPayloadType;
receive_config_.rtp.fec.ulpfec_payload_type = kUlpfecPayloadType;
receive_config_.renderer = &observer;
CreateStreams();
CreateFrameGenerator();
StartSending();
// Wait() waits for an event triggered when NACKs have been received, NACKed
// packets retransmitted and frames rendered again.
EXPECT_EQ(kEventSignaled, observer.Wait());
StopSending();
observer.StopSending();
DestroyStreams();
}
// This test drops second RTP packet with a marker bit set, makes sure it's // This test drops second RTP packet with a marker bit set, makes sure it's
// retransmitted and renders. Retransmission SSRCs are also checked. // retransmitted and renders. Retransmission SSRCs are also checked.
void CallTest::DecodesRetransmittedFrame(bool retransmit_over_rtx) { void CallTest::DecodesRetransmittedFrame(bool retransmit_over_rtx) {

View File

@ -17,6 +17,7 @@
#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h" #include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
#include "webrtc/system_wrappers/interface/clock.h" #include "webrtc/system_wrappers/interface/clock.h"
#include "webrtc/system_wrappers/interface/logging.h"
#include "webrtc/video/receive_statistics_proxy.h" #include "webrtc/video/receive_statistics_proxy.h"
#include "webrtc/video_engine/include/vie_base.h" #include "webrtc/video_engine/include/vie_base.h"
#include "webrtc/video_engine/include/vie_capture.h" #include "webrtc/video_engine/include/vie_capture.h"
@ -99,6 +100,31 @@ VideoReceiveStream::VideoReceiveStream(webrtc::VideoEngine* video_engine,
codec_ = ViECodec::GetInterface(video_engine); codec_ = ViECodec::GetInterface(video_engine);
if (config_.rtp.fec.ulpfec_payload_type != -1) {
// ULPFEC without RED doesn't make sense.
assert(config_.rtp.fec.red_payload_type != -1);
VideoCodec codec;
memset(&codec, 0, sizeof(codec));
codec.codecType = kVideoCodecULPFEC;
strcpy(codec.plName, "ulpfec");
codec.plType = config_.rtp.fec.ulpfec_payload_type;
if (codec_->SetReceiveCodec(channel_, codec) != 0) {
LOG(LS_ERROR) << "Could not set ULPFEC codec. This shouldn't happen.";
abort();
}
}
if (config_.rtp.fec.red_payload_type != -1) {
VideoCodec codec;
memset(&codec, 0, sizeof(codec));
codec.codecType = kVideoCodecRED;
strcpy(codec.plName, "red");
codec.plType = config_.rtp.fec.red_payload_type;
if (codec_->SetReceiveCodec(channel_, codec) != 0) {
LOG(LS_ERROR) << "Could not set RED codec. This shouldn't happen.";
abort();
}
}
assert(!config_.codecs.empty()); assert(!config_.codecs.empty());
for (size_t i = 0; i < config_.codecs.size(); ++i) { for (size_t i = 0; i < config_.codecs.size(); ++i) {
if (codec_->SetReceiveCodec(channel_, config_.codecs[i]) != 0) { if (codec_->SetReceiveCodec(channel_, config_.codecs[i]) != 0) {