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:
parent
dc80bae2a6
commit
2a03498825
@ -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) {
|
||||||
|
@ -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) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user