diff options
Diffstat (limited to 'third_party/libwebrtc/webrtc/video/picture_id_tests.cc')
-rw-r--r-- | third_party/libwebrtc/webrtc/video/picture_id_tests.cc | 382 |
1 files changed, 382 insertions, 0 deletions
diff --git a/third_party/libwebrtc/webrtc/video/picture_id_tests.cc b/third_party/libwebrtc/webrtc/video/picture_id_tests.cc new file mode 100644 index 0000000000..f1545d80e3 --- /dev/null +++ b/third_party/libwebrtc/webrtc/video/picture_id_tests.cc @@ -0,0 +1,382 @@ +/* + * 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<RtpDepacketizer> 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<uint16_t, kPictureIdWraparound>( + 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<uint16_t, kPictureIdWraparound>( + 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<uint32_t, uint32_t> last_observed_timestamp_ RTC_GUARDED_BY(crit_); + std::map<uint32_t, uint16_t> last_observed_picture_id_ RTC_GUARDED_BY(crit_); + std::map<uint32_t, size_t> 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<uint32_t> observed_ssrcs_ RTC_GUARDED_BY(crit_); +}; + +class PictureIdTest : public test::CallTest, + public ::testing::WithParamInterface<std::string> { + 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<int>& ssrc_counts); + void TestPictureIdIncreaseAfterRecreateStreams( + const std::vector<int>& 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<VideoStream> CreateEncoderStreams( + int width, + int height, + const VideoEncoderConfig& encoder_config) override { + std::vector<VideoStream> 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<VideoStreamFactory>(); + video_encoder_config_.number_of_streams = 1; + }); +} + +void PictureIdTest::TestPictureIdContinuousAfterReconfigure( + const std::vector<int>& 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<int>& 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<VideoEncoder> encoder(VP8Encoder::Create()); + SetupEncoder(encoder.get()); + TestPictureIdContinuousAfterReconfigure({1, 3, 3, 1, 1}); +} + +TEST_P(PictureIdTest, PictureIdIncreasingAfterRecreateStreamVp8) { + std::unique_ptr<VideoEncoder> encoder(VP8Encoder::Create()); + SetupEncoder(encoder.get()); + TestPictureIdIncreaseAfterRecreateStreams({1, 3, 3, 1, 1}); +} + +TEST_P(PictureIdTest, PictureIdIncreasingAfterStreamCountChangeVp8) { + std::unique_ptr<VideoEncoder> encoder(VP8Encoder::Create()); + // Make sure that that the picture id is not reset if the stream count goes + // down and then up. + std::vector<int> 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<int> ssrc_counts = {3, 1, 3}; + SetupEncoder(&simulcast_encoder_adapter); + TestPictureIdContinuousAfterReconfigure(ssrc_counts); + } +} + +} // namespace webrtc |