Adds two tests for verifying padding and ramp-up behavior.
BUG=1837
R=pbos@webrtc.org
Review URL: https://webrtc-codereview.appspot.com/2073004
git-svn-id: http://webrtc.googlecode.com/svn/trunk@4591 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/webrtc/video_engine/internal/video_send_stream.cc b/webrtc/video_engine/internal/video_send_stream.cc
index 7d9e680..da72877 100644
--- a/webrtc/video_engine/internal/video_send_stream.cc
+++ b/webrtc/video_engine/internal/video_send_stream.cc
@@ -16,6 +16,7 @@
#include "webrtc/video_engine/include/vie_base.h"
#include "webrtc/video_engine/include/vie_capture.h"
#include "webrtc/video_engine/include/vie_codec.h"
+#include "webrtc/video_engine/include/vie_external_codec.h"
#include "webrtc/video_engine/include/vie_network.h"
#include "webrtc/video_engine/include/vie_rtp_rtcp.h"
#include "webrtc/video_engine/new_include/video_send_stream.h"
@@ -77,7 +78,7 @@
bool overuse_detection,
webrtc::VideoEngine* video_engine,
const newapi::VideoSendStream::Config& config)
- : transport_(transport), config_(config) {
+ : transport_(transport), config_(config), external_codec_(NULL) {
if (config_.codec.numberOfSimulcastStreams > 0) {
assert(config_.rtp.ssrcs.size() == config_.codec.numberOfSimulcastStreams);
@@ -92,9 +93,17 @@
rtp_rtcp_ = ViERTP_RTCP::GetInterface(video_engine);
assert(rtp_rtcp_ != NULL);
- assert(config_.rtp.ssrcs.size() == 1);
- rtp_rtcp_->SetLocalSSRC(channel_, config_.rtp.ssrcs[0]);
+ if (config_.rtp.ssrcs.size() == 1) {
+ rtp_rtcp_->SetLocalSSRC(channel_, config_.rtp.ssrcs[0]);
+ } else {
+ for (size_t i = 0; i < config_.rtp.ssrcs.size(); ++i) {
+ rtp_rtcp_->SetLocalSSRC(channel_, config_.rtp.ssrcs[i],
+ kViEStreamTypeNormal, i);
+ }
+ }
rtp_rtcp_->SetNACKStatus(channel_, config_.rtp.nack.rtp_history_ms > 0);
+ rtp_rtcp_->SetTransmissionSmoothingStatus(channel_, config_.pacing);
+ rtp_rtcp_->SetSendTimestampOffsetStatus(channel_, true, 1);
capture_ = ViECapture::GetInterface(video_engine);
capture_->AllocateExternalCaptureDevice(capture_id_, external_capture_);
@@ -105,6 +114,15 @@
network_->RegisterSendTransport(channel_, *this);
+ if (config.encoder) {
+ external_codec_ = ViEExternalCodec::GetInterface(video_engine);
+ if (external_codec_->RegisterExternalSendCodec(
+ channel_, config.codec.plType, config.encoder,
+ config.internal_source) != 0) {
+ abort();
+ }
+ }
+
codec_ = ViECodec::GetInterface(video_engine);
if (codec_->SetSendCodec(channel_, config_.codec) != 0) {
abort();
@@ -126,9 +144,16 @@
capture_->DisconnectCaptureDevice(channel_);
capture_->ReleaseCaptureDevice(capture_id_);
+ if (external_codec_) {
+ external_codec_->DeRegisterExternalSendCodec(channel_,
+ config_.codec.plType);
+ }
+
video_engine_base_->Release();
capture_->Release();
codec_->Release();
+ if (external_codec_)
+ external_codec_->Release();
network_->Release();
rtp_rtcp_->Release();
}
@@ -199,7 +224,7 @@
assert(length >= 0);
bool success = transport_->SendRTP(static_cast<const uint8_t*>(packet),
static_cast<size_t>(length));
- return success ? 0 : -1;
+ return success ? length : -1;
}
int VideoSendStream::SendRTCPPacket(int /*channel*/,
@@ -208,7 +233,7 @@
assert(length >= 0);
bool success = transport_->SendRTCP(static_cast<const uint8_t*>(packet),
static_cast<size_t>(length));
- return success ? 0 : -1;
+ return success ? length : -1;
}
bool VideoSendStream::DeliverRtcp(const uint8_t* packet, size_t length) {
diff --git a/webrtc/video_engine/internal/video_send_stream.h b/webrtc/video_engine/internal/video_send_stream.h
index 686e5d5..ac0f55b 100644
--- a/webrtc/video_engine/internal/video_send_stream.h
+++ b/webrtc/video_engine/internal/video_send_stream.h
@@ -24,6 +24,7 @@
class ViECapture;
class ViECodec;
class ViEExternalCapture;
+class ViEExternalCodec;
class ViENetwork;
class ViERTP_RTCP;
@@ -74,6 +75,7 @@
ViECapture* capture_;
ViECodec* codec_;
ViEExternalCapture* external_capture_;
+ ViEExternalCodec* external_codec_;
ViENetwork* network_;
ViERTP_RTCP* rtp_rtcp_;
diff --git a/webrtc/video_engine/new_include/video_send_stream.h b/webrtc/video_engine/new_include/video_send_stream.h
index 75edad3..86ecc78 100644
--- a/webrtc/video_engine/new_include/video_send_stream.h
+++ b/webrtc/video_engine/new_include/video_send_stream.h
@@ -81,6 +81,7 @@
encoder(NULL),
internal_source(false),
target_delay_ms(0),
+ pacing(false),
stats_callback(NULL),
start_state(NULL) {}
VideoCodec codec;
@@ -138,6 +139,10 @@
// used for streaming instead of a real-time call.
int target_delay_ms;
+ // True if network a send-side packet buffer should be used to pace out
+ // packets onto the network.
+ bool pacing;
+
// Callback for periodically receiving send stats.
StatsCallback* stats_callback;
diff --git a/webrtc/video_engine/test/common/fake_encoder.cc b/webrtc/video_engine/test/common/fake_encoder.cc
new file mode 100644
index 0000000..d9674b3
--- /dev/null
+++ b/webrtc/video_engine/test/common/fake_encoder.cc
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/video_engine/test/common/fake_encoder.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace webrtc {
+
+FakeEncoder::FakeEncoder(Clock* clock)
+ : clock_(clock),
+ callback_(NULL),
+ target_bitrate_kbps_(0),
+ last_encode_time_ms_(0) {
+ memset(encoded_buffer_, 0, sizeof(encoded_buffer_));
+}
+
+FakeEncoder::~FakeEncoder() {}
+
+int32_t FakeEncoder::InitEncode(const VideoCodec* config,
+ int32_t number_of_cores,
+ uint32_t max_payload_size) {
+ config_ = *config;
+ target_bitrate_kbps_ = config_.startBitrate;
+ return 0;
+}
+
+int32_t FakeEncoder::Encode(
+ const I420VideoFrame& input_image,
+ const CodecSpecificInfo* codec_specific_info,
+ const std::vector<VideoFrameType>* frame_types) {
+ assert(config_.maxFramerate > 0);
+ int delta_since_last_encode = 1000 / config_.maxFramerate;
+ int64_t time_now_ms = clock_->TimeInMilliseconds();
+ if (last_encode_time_ms_ > 0) {
+ // For all frames but the first we can estimate the display time by looking
+ // at the display time of the previous frame.
+ delta_since_last_encode = time_now_ms - last_encode_time_ms_;
+ }
+
+ int bits_available = target_bitrate_kbps_ * delta_since_last_encode;
+ last_encode_time_ms_ = time_now_ms;
+
+ for (int i = 0; i < config_.numberOfSimulcastStreams; ++i) {
+ CodecSpecificInfo specifics;
+ memset(&specifics, 0, sizeof(specifics));
+ specifics.codecType = kVideoCodecVP8;
+ specifics.codecSpecific.VP8.simulcastIdx = i;
+ int min_stream_bits = config_.simulcastStream[i].minBitrate *
+ delta_since_last_encode;
+ int max_stream_bits = config_.simulcastStream[i].maxBitrate *
+ delta_since_last_encode;
+ int stream_bits = (bits_available > max_stream_bits) ? max_stream_bits :
+ bits_available;
+ int stream_bytes = (stream_bits + 7) / 8;
+ EXPECT_LT(stream_bytes, kMaxFrameSizeBytes);
+ if (stream_bytes > kMaxFrameSizeBytes)
+ return -1;
+
+ EncodedImage encoded(encoded_buffer_, stream_bytes, kMaxFrameSizeBytes);
+ encoded._timeStamp = input_image.timestamp();
+ encoded.capture_time_ms_ = input_image.render_time_ms();
+ if (min_stream_bits > bits_available) {
+ encoded._length = 0;
+ encoded._frameType = kSkipFrame;
+ }
+ if (callback_->Encoded(encoded, &specifics, NULL) != 0)
+ return -1;
+
+ bits_available -= encoded._length * 8;
+ }
+ return 0;
+}
+
+int32_t FakeEncoder::RegisterEncodeCompleteCallback(
+ EncodedImageCallback* callback) {
+ callback_ = callback;
+ return 0;
+}
+
+int32_t FakeEncoder::Release() { return 0; }
+
+int32_t FakeEncoder::SetChannelParameters(uint32_t packet_loss, int rtt) {
+ return 0;
+}
+
+int32_t FakeEncoder::SetRates(uint32_t new_target_bitrate, uint32_t framerate) {
+ target_bitrate_kbps_ = new_target_bitrate;
+ return 0;
+}
+} // namespace webrtc
diff --git a/webrtc/video_engine/test/common/fake_encoder.h b/webrtc/video_engine/test/common/fake_encoder.h
new file mode 100644
index 0000000..8a14f88
--- /dev/null
+++ b/webrtc/video_engine/test/common/fake_encoder.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_VIDEO_ENGINE_TEST_COMMON_FAKE_ENCODER_H_
+#define WEBRTC_VIDEO_ENGINE_TEST_COMMON_FAKE_ENCODER_H_
+
+#include <vector>
+
+#include "webrtc/modules/video_coding/codecs/interface/video_codec_interface.h"
+#include "webrtc/system_wrappers/interface/clock.h"
+
+namespace webrtc {
+
+class FakeEncoder : public VideoEncoder {
+ public:
+ explicit FakeEncoder(Clock* clock);
+
+ virtual ~FakeEncoder();
+
+ virtual int32_t InitEncode(const VideoCodec* config,
+ int32_t number_of_cores,
+ uint32_t max_payload_size) OVERRIDE;
+
+ virtual int32_t Encode(
+ const I420VideoFrame& input_image,
+ const CodecSpecificInfo* codec_specific_info,
+ const std::vector<VideoFrameType>* frame_types) OVERRIDE;
+
+ virtual int32_t RegisterEncodeCompleteCallback(
+ EncodedImageCallback* callback) OVERRIDE;
+
+ virtual int32_t Release() OVERRIDE;
+
+ virtual int32_t SetChannelParameters(uint32_t packet_loss, int rtt) OVERRIDE;
+
+ virtual int32_t SetRates(uint32_t new_target_bitrate,
+ uint32_t framerate) OVERRIDE;
+
+ private:
+ enum { kMaxFrameSizeBytes = 100000 };
+
+ Clock* clock_;
+ VideoCodec config_;
+ EncodedImageCallback* callback_;
+ int target_bitrate_kbps_;
+ int64_t last_encode_time_ms_;
+ uint8_t encoded_buffer_[kMaxFrameSizeBytes];
+};
+} // namespace webrtc
+
+#endif // WEBRTC_VIDEO_ENGINE_TEST_COMMON_FAKE_ENCODER_H_
diff --git a/webrtc/video_engine/test/engine_tests.cc b/webrtc/video_engine/test/engine_tests.cc
index 71c3c81..e564912 100644
--- a/webrtc/video_engine/test/engine_tests.cc
+++ b/webrtc/video_engine/test/engine_tests.cc
@@ -13,13 +13,17 @@
#include "testing/gtest/include/gtest/gtest.h"
+#include "webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h"
+#include "webrtc/modules/rtp_rtcp/interface/receive_statistics.h"
#include "webrtc/modules/rtp_rtcp/interface/rtp_header_parser.h"
+#include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp.h"
#include "webrtc/modules/rtp_rtcp/source/rtcp_utility.h"
#include "webrtc/system_wrappers/interface/critical_section_wrapper.h"
#include "webrtc/system_wrappers/interface/scoped_ptr.h"
#include "webrtc/system_wrappers/interface/event_wrapper.h"
#include "webrtc/video_engine/new_include/video_call.h"
#include "webrtc/video_engine/test/common/direct_transport.h"
+#include "webrtc/video_engine/test/common/fake_encoder.h"
#include "webrtc/video_engine/test/common/frame_generator.h"
#include "webrtc/video_engine/test/common/frame_generator_capturer.h"
#include "webrtc/video_engine/test/common/generate_ssrcs.h"
@@ -27,6 +31,199 @@
namespace webrtc {
+class StreamObserver : public newapi::Transport, public RemoteBitrateObserver {
+ public:
+ typedef std::map<uint32_t, int> BytesSentMap;
+ StreamObserver(int num_expected_ssrcs, newapi::Transport* feedback_transport,
+ Clock* clock)
+ : critical_section_(CriticalSectionWrapper::CreateCriticalSection()),
+ all_ssrcs_sent_(EventWrapper::Create()),
+ rtp_parser_(RtpHeaderParser::Create()),
+ feedback_transport_(new TransportWrapper(feedback_transport)),
+ receive_stats_(ReceiveStatistics::Create(clock)),
+ clock_(clock),
+ num_expected_ssrcs_(num_expected_ssrcs) {
+ // Ideally we would only have to instantiate an RtcpSender, an
+ // RtpHeaderParser and a RemoteBitrateEstimator here, but due to the current
+ // state of the RTP module we need a full module and receive statistics to
+ // be able to produce an RTCP with REMB.
+ RtpRtcp::Configuration config;
+ config.receive_statistics = receive_stats_.get();
+ config.outgoing_transport = feedback_transport_.get();
+ rtp_rtcp_.reset(RtpRtcp::CreateRtpRtcp(config));
+ rtp_rtcp_->SetREMBStatus(true);
+ rtp_rtcp_->SetRTCPStatus(kRtcpNonCompound);
+ rtp_parser_->RegisterRtpHeaderExtension(kRtpExtensionTransmissionTimeOffset,
+ 1);
+ AbsoluteSendTimeRemoteBitrateEstimatorFactory rbe_factory;
+ remote_bitrate_estimator_.reset(rbe_factory.Create(this, clock));
+ }
+
+ virtual void OnReceiveBitrateChanged(const std::vector<unsigned int>& ssrcs,
+ unsigned int bitrate) {
+ CriticalSectionScoped lock(critical_section_.get());
+ if (ssrcs.size() == num_expected_ssrcs_ && bitrate >= kExpectedBitrateBps)
+ all_ssrcs_sent_->Set();
+ rtp_rtcp_->SetREMBData(bitrate, static_cast<uint8_t>(ssrcs.size()),
+ &ssrcs[0]);
+ rtp_rtcp_->Process();
+ }
+
+ virtual bool SendRTP(const uint8_t* packet, size_t length) OVERRIDE {
+ CriticalSectionScoped lock(critical_section_.get());
+ RTPHeader header;
+ EXPECT_TRUE(rtp_parser_->Parse(packet, static_cast<int>(length),
+ &header));
+ receive_stats_->IncomingPacket(header, length, false, true);
+ rtp_rtcp_->SetRemoteSSRC(header.ssrc);
+ remote_bitrate_estimator_->IncomingPacket(clock_->TimeInMilliseconds(),
+ static_cast<int>(length - 12),
+ header);
+ if (remote_bitrate_estimator_->TimeUntilNextProcess() <= 0) {
+ remote_bitrate_estimator_->Process();
+ }
+ return true;
+ }
+
+ virtual bool SendRTCP(const uint8_t* packet, size_t length) OVERRIDE {
+ return true;
+ }
+
+ EventTypeWrapper Wait() {
+ return all_ssrcs_sent_->Wait(120 * 1000);
+ }
+
+ private:
+ class TransportWrapper : public webrtc::Transport {
+ public:
+ explicit TransportWrapper(newapi::Transport* new_transport)
+ : new_transport_(new_transport) {}
+
+ virtual int SendPacket(int channel, const void *data, int len) OVERRIDE {
+ return new_transport_->SendRTP(static_cast<const uint8_t*>(data), len) ?
+ len : -1;
+ }
+
+ virtual int SendRTCPPacket(int channel, const void *data,
+ int len) OVERRIDE {
+ return new_transport_->SendRTCP(static_cast<const uint8_t*>(data), len) ?
+ len : -1;
+ }
+
+ private:
+ newapi::Transport* new_transport_;
+ };
+
+ static const unsigned int kExpectedBitrateBps = 1200000;
+
+ scoped_ptr<CriticalSectionWrapper> critical_section_;
+ scoped_ptr<EventWrapper> all_ssrcs_sent_;
+ scoped_ptr<RtpHeaderParser> rtp_parser_;
+ scoped_ptr<RtpRtcp> rtp_rtcp_;
+ scoped_ptr<TransportWrapper> feedback_transport_;
+ scoped_ptr<ReceiveStatistics> receive_stats_;
+ scoped_ptr<RemoteBitrateEstimator> remote_bitrate_estimator_;
+ Clock* clock_;
+ const size_t num_expected_ssrcs_;
+};
+
+class RampUpTest : public ::testing::TestWithParam<bool> {
+ public:
+ virtual void SetUp() {
+ reserved_ssrcs_.clear();
+ }
+
+ static void SetCodecStreamSettings(VideoCodec* video_codec) {
+ video_codec->width = 1280;
+ video_codec->height = 720;
+ video_codec->startBitrate = 300;
+ video_codec->minBitrate = 50;
+ video_codec->maxBitrate = 1800;
+
+ video_codec->numberOfSimulcastStreams = 3;
+ video_codec->simulcastStream[0].width = 320;
+ video_codec->simulcastStream[0].height = 180;
+ video_codec->simulcastStream[0].numberOfTemporalLayers = 0;
+ video_codec->simulcastStream[0].maxBitrate = 150;
+ video_codec->simulcastStream[0].targetBitrate = 150;
+ video_codec->simulcastStream[0].minBitrate = 50;
+ video_codec->simulcastStream[0].qpMax = video_codec->qpMax;
+
+ video_codec->simulcastStream[1].width = 640;
+ video_codec->simulcastStream[1].height = 360;
+ video_codec->simulcastStream[1].numberOfTemporalLayers = 0;
+ video_codec->simulcastStream[1].maxBitrate = 500;
+ video_codec->simulcastStream[1].targetBitrate = 500;
+ video_codec->simulcastStream[1].minBitrate = 150;
+ video_codec->simulcastStream[1].qpMax = video_codec->qpMax;
+
+ video_codec->simulcastStream[2].width = 1280;
+ video_codec->simulcastStream[2].height = 720;
+ video_codec->simulcastStream[2].numberOfTemporalLayers = 0;
+ video_codec->simulcastStream[2].maxBitrate = 1200;
+ video_codec->simulcastStream[2].targetBitrate = 1200;
+ video_codec->simulcastStream[2].minBitrate = 600;
+ video_codec->simulcastStream[2].qpMax = video_codec->qpMax;
+ }
+
+ protected:
+ std::map<uint32_t, bool> reserved_ssrcs_;
+};
+
+TEST_P(RampUpTest, RampUpWithPadding) {
+ test::DirectTransport receiver_transport;
+ StreamObserver stream_observer(3, &receiver_transport,
+ Clock::GetRealTimeClock());
+ newapi::VideoCall::Config call_config(&stream_observer);
+ scoped_ptr<newapi::VideoCall> call(newapi::VideoCall::Create(call_config));
+ newapi::VideoSendStream::Config send_config =
+ call->GetDefaultSendConfig();
+
+ receiver_transport.SetReceiver(call->Receiver());
+
+ FakeEncoder encoder(Clock::GetRealTimeClock());
+ send_config.encoder = &encoder;
+ send_config.internal_source = false;
+ SetCodecStreamSettings(&send_config.codec);
+ send_config.codec.plType = 100;
+ send_config.pacing = GetParam();
+
+ test::GenerateRandomSsrcs(&send_config, &reserved_ssrcs_);
+
+ newapi::VideoSendStream* send_stream =
+ call->CreateSendStream(send_config);
+
+ newapi::VideoReceiveStream::Config receive_config;
+ receive_config.rtp.ssrc = send_config.rtp.ssrcs[0];
+ receive_config.rtp.nack.rtp_history_ms =
+ send_config.rtp.nack.rtp_history_ms;
+ newapi::VideoReceiveStream* receive_stream = call->CreateReceiveStream(
+ receive_config);
+
+ scoped_ptr<test::FrameGeneratorCapturer> frame_generator_capturer(
+ test::FrameGeneratorCapturer::Create(
+ send_stream->Input(),
+ test::FrameGenerator::Create(
+ send_config.codec.width, send_config.codec.height,
+ Clock::GetRealTimeClock()),
+ 30));
+
+ receive_stream->StartReceive();
+ send_stream->StartSend();
+ frame_generator_capturer->Start();
+
+ EXPECT_EQ(kEventSignaled, stream_observer.Wait());
+
+ frame_generator_capturer->Stop();
+ send_stream->StopSend();
+ receive_stream->StopReceive();
+
+ call->DestroyReceiveStream(receive_stream);
+ call->DestroySendStream(send_stream);
+}
+
+INSTANTIATE_TEST_CASE_P(RampUpTest, RampUpTest, ::testing::Bool());
+
struct EngineTestParams {
size_t width, height;
struct {
diff --git a/webrtc/video_engine/test/tests.gypi b/webrtc/video_engine/test/tests.gypi
index 4a76b22..f0e8e5f 100644
--- a/webrtc/video_engine/test/tests.gypi
+++ b/webrtc/video_engine/test/tests.gypi
@@ -14,6 +14,8 @@
'sources': [
'common/direct_transport.cc',
'common/direct_transport.h',
+ 'common/fake_encoder.cc',
+ 'common/fake_encoder.h',
'common/file_capturer.cc',
'common/file_capturer.h',
'common/flags.cc',