/* * 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 "media/engine/internalencoderfactory.h" #include "media/engine/simulcast_encoder_adapter.h" #include "modules/rtp_rtcp/source/rtp_format.h" #include "rtc_base/numerics/sequence_number_util.h" #include "test/call_test.h" #include "test/field_trial.h" namespace webrtc { namespace { const int kFrameMaxWidth = 1280; const int kFrameMaxHeight = 720; const int kFrameRate = 30; const int kMaxSecondsLost = 5; const int kMaxFramesLost = kFrameRate * kMaxSecondsLost; const int kMinPacketsToObserve = 10; const int kEncoderBitrateBps = 100000; const uint32_t kPictureIdWraparound = (1 << 15); const char kVp8ForcedFallbackEncoderEnabled[] = "WebRTC-VP8-Forced-Fallback-Encoder-v2/Enabled/"; } // namespace class PictureIdObserver : public test::RtpRtcpObserver { public: PictureIdObserver() : test::RtpRtcpObserver(test::CallTest::kDefaultTimeoutMs), max_expected_picture_id_gap_(0), num_ssrcs_to_observe_(1) {} void SetExpectedSsrcs(size_t num_expected_ssrcs) { rtc::CritScope lock(&crit_); num_ssrcs_to_observe_ = num_expected_ssrcs; } void ResetObservedSsrcs() { rtc::CritScope lock(&crit_); // Do not clear the timestamp and picture_id, to ensure that we check // consistency between reinits and recreations. num_packets_sent_.clear(); observed_ssrcs_.clear(); } void SetMaxExpectedPictureIdGap(int max_expected_picture_id_gap) { rtc::CritScope lock(&crit_); max_expected_picture_id_gap_ = max_expected_picture_id_gap; } private: Action OnSendRtp(const uint8_t* packet, size_t length) override { rtc::CritScope lock(&crit_); // RTP header. RTPHeader header; EXPECT_TRUE(parser_->Parse(packet, length, &header)); const uint32_t timestamp = header.timestamp; const uint32_t ssrc = header.ssrc; const bool known_ssrc = (ssrc == test::CallTest::kVideoSendSsrcs[0] || ssrc == test::CallTest::kVideoSendSsrcs[1] || ssrc == test::CallTest::kVideoSendSsrcs[2]); EXPECT_TRUE(known_ssrc) << "Unknown SSRC sent."; const bool is_padding = (length == header.headerLength + header.paddingLength); if (is_padding) { return SEND_PACKET; } // VP8 header. std::unique_ptr depacketizer( RtpDepacketizer::Create(kRtpVideoVp8)); RtpDepacketizer::ParsedPayload parsed_payload; EXPECT_TRUE(depacketizer->Parse( &parsed_payload, &packet[header.headerLength], length - header.headerLength - header.paddingLength)); const uint16_t picture_id = parsed_payload.type.Video.codecHeader.VP8.pictureId; // If this is the first packet, we have nothing to compare to. if (last_observed_timestamp_.find(ssrc) == last_observed_timestamp_.end()) { last_observed_timestamp_[ssrc] = timestamp; last_observed_picture_id_[ssrc] = picture_id; ++num_packets_sent_[ssrc]; return SEND_PACKET; } // Verify continuity and monotonicity of picture_id sequence. if (last_observed_timestamp_[ssrc] == timestamp) { // Packet belongs to same frame as before. EXPECT_EQ(last_observed_picture_id_[ssrc], picture_id); } else { // Packet is a new frame. // Picture id should be increasing. const bool picture_id_is_increasing = AheadOf( picture_id, last_observed_picture_id_[ssrc]); EXPECT_TRUE(picture_id_is_increasing); // Picture id should not increase more than expected. const int picture_id_diff = ForwardDiff( last_observed_picture_id_[ssrc], picture_id); // For delta frames, expect continuously increasing picture id. if (parsed_payload.frame_type != kVideoFrameKey) { EXPECT_EQ(picture_id_diff, 1); } // Any frames still in queue is lost when a VideoSendStream is destroyed. // The first frame after recreation should be a key frame. if (picture_id_diff > 1) { EXPECT_EQ(kVideoFrameKey, parsed_payload.frame_type); EXPECT_LE(picture_id_diff - 1, max_expected_picture_id_gap_); } } last_observed_timestamp_[ssrc] = timestamp; last_observed_picture_id_[ssrc] = picture_id; // Pass the test when enough media packets have been received // on all streams. if (++num_packets_sent_[ssrc] >= kMinPacketsToObserve && observed_ssrcs_.find(ssrc) == observed_ssrcs_.end()) { observed_ssrcs_.insert(ssrc); if (observed_ssrcs_.size() == num_ssrcs_to_observe_) { observation_complete_.Set(); } } return SEND_PACKET; } rtc::CriticalSection crit_; std::map last_observed_timestamp_ RTC_GUARDED_BY(crit_); std::map last_observed_picture_id_ RTC_GUARDED_BY(crit_); std::map num_packets_sent_ RTC_GUARDED_BY(crit_); int max_expected_picture_id_gap_ RTC_GUARDED_BY(crit_); size_t num_ssrcs_to_observe_ RTC_GUARDED_BY(crit_); std::set observed_ssrcs_ RTC_GUARDED_BY(crit_); }; class PictureIdTest : public test::CallTest, public ::testing::WithParamInterface { public: PictureIdTest() : scoped_field_trial_(GetParam()) {} virtual ~PictureIdTest() { EXPECT_EQ(nullptr, video_send_stream_); EXPECT_TRUE(video_receive_streams_.empty()); task_queue_.SendTask([this]() { Stop(); DestroyStreams(); send_transport_.reset(); receive_transport_.reset(); DestroyCalls(); }); } void SetupEncoder(VideoEncoder* encoder); void TestPictureIdContinuousAfterReconfigure( const std::vector& ssrc_counts); void TestPictureIdIncreaseAfterRecreateStreams( const std::vector& ssrc_counts); private: test::ScopedFieldTrials scoped_field_trial_; PictureIdObserver observer; }; INSTANTIATE_TEST_CASE_P(TestWithForcedFallbackEncoderEnabled, PictureIdTest, ::testing::Values(kVp8ForcedFallbackEncoderEnabled, "")); // Use a special stream factory to ensure that all simulcast streams are being // sent. class VideoStreamFactory : public VideoEncoderConfig::VideoStreamFactoryInterface { public: VideoStreamFactory() = default; private: std::vector CreateEncoderStreams( int width, int height, const VideoEncoderConfig& encoder_config) override { std::vector streams = test::CreateVideoStreams(width, height, encoder_config); if (encoder_config.number_of_streams > 1) { RTC_DCHECK_EQ(3, encoder_config.number_of_streams); for (size_t i = 0; i < encoder_config.number_of_streams; ++i) { streams[i].min_bitrate_bps = kEncoderBitrateBps; streams[i].target_bitrate_bps = kEncoderBitrateBps; streams[i].max_bitrate_bps = kEncoderBitrateBps; } // test::CreateVideoStreams does not return frame sizes for the lower // streams that are accepted by VP8Impl::InitEncode. // TODO(brandtr): Fix the problem in test::CreateVideoStreams, rather // than overriding the values here. streams[1].width = streams[2].width / 2; streams[1].height = streams[2].height / 2; streams[0].width = streams[1].width / 2; streams[0].height = streams[1].height / 2; } else { // Use the same total bitrates when sending a single stream to avoid // lowering the bitrate estimate and requiring a subsequent rampup. streams[0].min_bitrate_bps = 3 * kEncoderBitrateBps; streams[0].target_bitrate_bps = 3 * kEncoderBitrateBps; streams[0].max_bitrate_bps = 3 * kEncoderBitrateBps; } return streams; } }; void PictureIdTest::SetupEncoder(VideoEncoder* encoder) { task_queue_.SendTask([this, &encoder]() { Call::Config config(event_log_.get()); CreateCalls(config, config); send_transport_.reset(new test::PacketTransport( &task_queue_, sender_call_.get(), &observer, test::PacketTransport::kSender, payload_type_map_, FakeNetworkPipe::Config())); CreateSendConfig(kNumSsrcs, 0, 0, send_transport_.get()); video_send_config_.encoder_settings.encoder = encoder; video_send_config_.encoder_settings.payload_name = "VP8"; video_encoder_config_.video_stream_factory = new rtc::RefCountedObject(); video_encoder_config_.number_of_streams = 1; }); } void PictureIdTest::TestPictureIdContinuousAfterReconfigure( const std::vector& ssrc_counts) { task_queue_.SendTask([this]() { CreateVideoStreams(); CreateFrameGeneratorCapturer(kFrameRate, kFrameMaxWidth, kFrameMaxHeight); // Initial test with a single stream. Start(); }); EXPECT_TRUE(observer.Wait()) << "Timed out waiting for packets."; // Reconfigure VideoEncoder and test picture id increase. // Expect continuously increasing picture id, equivalent to no gaps. observer.SetMaxExpectedPictureIdGap(0); for (int ssrc_count : ssrc_counts) { video_encoder_config_.number_of_streams = ssrc_count; observer.SetExpectedSsrcs(ssrc_count); observer.ResetObservedSsrcs(); // Make sure the picture_id sequence is continuous on reinit and recreate. task_queue_.SendTask([this]() { video_send_stream_->ReconfigureVideoEncoder(video_encoder_config_.Copy()); }); EXPECT_TRUE(observer.Wait()) << "Timed out waiting for packets."; } task_queue_.SendTask([this]() { Stop(); DestroyStreams(); send_transport_.reset(); receive_transport_.reset(); DestroyCalls(); }); } void PictureIdTest::TestPictureIdIncreaseAfterRecreateStreams( const std::vector& ssrc_counts) { task_queue_.SendTask([this]() { CreateVideoStreams(); CreateFrameGeneratorCapturer(kFrameRate, kFrameMaxWidth, kFrameMaxHeight); // Initial test with a single stream. Start(); }); EXPECT_TRUE(observer.Wait()) << "Timed out waiting for packets."; // Recreate VideoSendStream and test picture id increase. // When the VideoSendStream is destroyed, any frames still in queue is lost // with it, therefore it is expected that some frames might be lost. observer.SetMaxExpectedPictureIdGap(kMaxFramesLost); for (int ssrc_count : ssrc_counts) { task_queue_.SendTask([this, &ssrc_count]() { video_encoder_config_.number_of_streams = ssrc_count; frame_generator_capturer_->Stop(); sender_call_->DestroyVideoSendStream(video_send_stream_); observer.SetExpectedSsrcs(ssrc_count); observer.ResetObservedSsrcs(); video_send_stream_ = sender_call_->CreateVideoSendStream( video_send_config_.Copy(), video_encoder_config_.Copy()); video_send_stream_->Start(); CreateFrameGeneratorCapturer(kFrameRate, kFrameMaxWidth, kFrameMaxHeight); frame_generator_capturer_->Start(); }); EXPECT_TRUE(observer.Wait()) << "Timed out waiting for packets."; } task_queue_.SendTask([this]() { Stop(); DestroyStreams(); send_transport_.reset(); receive_transport_.reset(); }); } TEST_P(PictureIdTest, PictureIdContinuousAfterReconfigureVp8) { std::unique_ptr encoder(VP8Encoder::Create()); SetupEncoder(encoder.get()); TestPictureIdContinuousAfterReconfigure({1, 3, 3, 1, 1}); } TEST_P(PictureIdTest, PictureIdIncreasingAfterRecreateStreamVp8) { std::unique_ptr encoder(VP8Encoder::Create()); SetupEncoder(encoder.get()); TestPictureIdIncreaseAfterRecreateStreams({1, 3, 3, 1, 1}); } TEST_P(PictureIdTest, PictureIdIncreasingAfterStreamCountChangeVp8) { std::unique_ptr encoder(VP8Encoder::Create()); // Make sure that that the picture id is not reset if the stream count goes // down and then up. std::vector ssrc_counts = {3, 1, 3}; SetupEncoder(encoder.get()); TestPictureIdContinuousAfterReconfigure(ssrc_counts); } TEST_P(PictureIdTest, PictureIdContinuousAfterReconfigureSimulcastEncoderAdapter) { InternalEncoderFactory internal_encoder_factory; SimulcastEncoderAdapter simulcast_encoder_adapter(&internal_encoder_factory); SetupEncoder(&simulcast_encoder_adapter); TestPictureIdContinuousAfterReconfigure({1, 3, 3, 1, 1}); } TEST_P(PictureIdTest, PictureIdIncreasingAfterRecreateStreamSimulcastEncoderAdapter) { InternalEncoderFactory internal_encoder_factory; SimulcastEncoderAdapter simulcast_encoder_adapter(&internal_encoder_factory); SetupEncoder(&simulcast_encoder_adapter); TestPictureIdIncreaseAfterRecreateStreams({1, 3, 3, 1, 1}); } // When using the simulcast encoder adapter, the picture id is randomly set // when the ssrc count is reduced and then increased. This means that we are // not spec compliant in that particular case. TEST_P(PictureIdTest, PictureIdIncreasingAfterStreamCountChangeSimulcastEncoderAdapter) { // If forced fallback is enabled, the picture id is set in the PayloadRouter // and the sequence should be continuous. if (GetParam() == kVp8ForcedFallbackEncoderEnabled) { InternalEncoderFactory internal_encoder_factory; SimulcastEncoderAdapter simulcast_encoder_adapter( &internal_encoder_factory); // Make sure that that the picture id is not reset if the stream count goes // down and then up. std::vector ssrc_counts = {3, 1, 3}; SetupEncoder(&simulcast_encoder_adapter); TestPictureIdContinuousAfterReconfigure(ssrc_counts); } } } // namespace webrtc