diff --git a/webrtc/video_engine/internal/video_send_stream.cc b/webrtc/video_engine/internal/video_send_stream.cc index 7d9e680320..da72877f5f 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 @@ VideoSendStream::VideoSendStream(newapi::Transport* transport, 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 @@ VideoSendStream::VideoSendStream(newapi::Transport* transport, 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 @@ VideoSendStream::VideoSendStream(newapi::Transport* transport, 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 @@ VideoSendStream::~VideoSendStream() { 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 @@ int VideoSendStream::SendPacket(int /*channel*/, assert(length >= 0); bool success = transport_->SendRTP(static_cast(packet), static_cast(length)); - return success ? 0 : -1; + return success ? length : -1; } int VideoSendStream::SendRTCPPacket(int /*channel*/, @@ -208,7 +233,7 @@ int VideoSendStream::SendRTCPPacket(int /*channel*/, assert(length >= 0); bool success = transport_->SendRTCP(static_cast(packet), static_cast(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 686e5d5735..ac0f55b330 100644 --- a/webrtc/video_engine/internal/video_send_stream.h +++ b/webrtc/video_engine/internal/video_send_stream.h @@ -24,6 +24,7 @@ class ViEBase; class ViECapture; class ViECodec; class ViEExternalCapture; +class ViEExternalCodec; class ViENetwork; class ViERTP_RTCP; @@ -74,6 +75,7 @@ class VideoSendStream : public newapi::VideoSendStream, 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 75edad3045..86ecc787b7 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 @@ class VideoSendStream { encoder(NULL), internal_source(false), target_delay_ms(0), + pacing(false), stats_callback(NULL), start_state(NULL) {} VideoCodec codec; @@ -138,6 +139,10 @@ class VideoSendStream { // 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 0000000000..d9674b3bb5 --- /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* 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 0000000000..8a14f88231 --- /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 + +#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* 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 71c3c81ee2..e564912f87 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 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& 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(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(length), + &header)); + receive_stats_->IncomingPacket(header, length, false, true); + rtp_rtcp_->SetRemoteSSRC(header.ssrc); + remote_bitrate_estimator_->IncomingPacket(clock_->TimeInMilliseconds(), + static_cast(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(data), len) ? + len : -1; + } + + virtual int SendRTCPPacket(int channel, const void *data, + int len) OVERRIDE { + return new_transport_->SendRTCP(static_cast(data), len) ? + len : -1; + } + + private: + newapi::Transport* new_transport_; + }; + + static const unsigned int kExpectedBitrateBps = 1200000; + + scoped_ptr critical_section_; + scoped_ptr all_ssrcs_sent_; + scoped_ptr rtp_parser_; + scoped_ptr rtp_rtcp_; + scoped_ptr feedback_transport_; + scoped_ptr receive_stats_; + scoped_ptr remote_bitrate_estimator_; + Clock* clock_; + const size_t num_expected_ssrcs_; +}; + +class RampUpTest : public ::testing::TestWithParam { + 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 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 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 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 4a76b22d61..f0e8e5fd65 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',