| /* |
| * 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 "gtest/gtest.h" |
| #include "vpx/vpx_encoder.h" |
| #include "vpx/vp8cx.h" |
| #include "webrtc/base/scoped_ptr.h" |
| #include "webrtc/modules/video_coding/include/video_codec_interface.h" |
| #include "webrtc/modules/video_coding/codecs/vp8/screenshare_layers.h" |
| #include "webrtc/modules/video_coding/utility/mock/mock_frame_dropper.h" |
| |
| using ::testing::_; |
| using ::testing::NiceMock; |
| using ::testing::Return; |
| |
| namespace webrtc { |
| |
| // 5 frames per second at 90 kHz. |
| const uint32_t kTimestampDelta5Fps = 90000 / 5; |
| const int kDefaultQp = 54; |
| const int kDefaultTl0BitrateKbps = 200; |
| const int kDefaultTl1BitrateKbps = 2000; |
| const int kFrameRate = 5; |
| const int kSyncPeriodSeconds = 5; |
| const int kMaxSyncPeriodSeconds = 10; |
| |
| class ScreenshareLayerTest : public ::testing::Test { |
| protected: |
| ScreenshareLayerTest() : min_qp_(2), max_qp_(kDefaultQp), frame_size_(-1) {} |
| virtual ~ScreenshareLayerTest() {} |
| |
| void EncodeFrame(uint32_t timestamp, |
| bool base_sync, |
| CodecSpecificInfoVP8* vp8_info, |
| int* flags) { |
| *flags = layers_->EncodeFlags(timestamp); |
| layers_->PopulateCodecSpecific(base_sync, vp8_info, timestamp); |
| ASSERT_NE(-1, frame_size_); |
| layers_->FrameEncoded(frame_size_, timestamp, kDefaultQp); |
| } |
| |
| void ConfigureBitrates() { |
| vpx_codec_enc_cfg_t vpx_cfg; |
| memset(&vpx_cfg, 0, sizeof(vpx_codec_enc_cfg_t)); |
| vpx_cfg.rc_min_quantizer = min_qp_; |
| vpx_cfg.rc_max_quantizer = max_qp_; |
| EXPECT_TRUE(layers_->ConfigureBitrates( |
| kDefaultTl0BitrateKbps, kDefaultTl1BitrateKbps, kFrameRate, &vpx_cfg)); |
| frame_size_ = ((vpx_cfg.rc_target_bitrate * 1000) / 8) / kFrameRate; |
| } |
| |
| void WithQpLimits(int min_qp, int max_qp) { |
| min_qp_ = min_qp; |
| max_qp_ = max_qp; |
| } |
| |
| int RunGracePeriod() { |
| int flags = 0; |
| uint32_t timestamp = 0; |
| CodecSpecificInfoVP8 vp8_info; |
| bool got_tl0 = false; |
| bool got_tl1 = false; |
| for (int i = 0; i < 10; ++i) { |
| EncodeFrame(timestamp, false, &vp8_info, &flags); |
| timestamp += kTimestampDelta5Fps; |
| if (vp8_info.temporalIdx == 0) { |
| got_tl0 = true; |
| } else { |
| got_tl1 = true; |
| } |
| if (got_tl0 && got_tl1) |
| return timestamp; |
| } |
| ADD_FAILURE() << "Frames from both layers not received in time."; |
| return 0; |
| } |
| |
| int SkipUntilTl(int layer, int timestamp) { |
| CodecSpecificInfoVP8 vp8_info; |
| for (int i = 0; i < 5; ++i) { |
| layers_->EncodeFlags(timestamp); |
| timestamp += kTimestampDelta5Fps; |
| layers_->PopulateCodecSpecific(false, &vp8_info, timestamp); |
| if (vp8_info.temporalIdx != layer) { |
| layers_->FrameEncoded(frame_size_, timestamp, kDefaultQp); |
| } else { |
| return timestamp; |
| } |
| } |
| ADD_FAILURE() << "Did not get a frame of TL" << layer << " in time."; |
| return 0; |
| } |
| |
| int min_qp_; |
| int max_qp_; |
| int frame_size_; |
| rtc::scoped_ptr<ScreenshareLayers> layers_; |
| }; |
| |
| TEST_F(ScreenshareLayerTest, 1Layer) { |
| layers_.reset(new ScreenshareLayers(1, 0)); |
| ConfigureBitrates(); |
| int flags = 0; |
| uint32_t timestamp = 0; |
| CodecSpecificInfoVP8 vp8_info; |
| // One layer screenshare should not use the frame dropper as all frames will |
| // belong to the base layer. |
| const int kSingleLayerFlags = 0; |
| flags = layers_->EncodeFlags(timestamp); |
| EXPECT_EQ(kSingleLayerFlags, flags); |
| layers_->PopulateCodecSpecific(false, &vp8_info, timestamp); |
| EXPECT_EQ(static_cast<uint8_t>(kNoTemporalIdx), vp8_info.temporalIdx); |
| EXPECT_FALSE(vp8_info.layerSync); |
| EXPECT_EQ(kNoTl0PicIdx, vp8_info.tl0PicIdx); |
| layers_->FrameEncoded(frame_size_, timestamp, kDefaultQp); |
| flags = layers_->EncodeFlags(timestamp); |
| EXPECT_EQ(kSingleLayerFlags, flags); |
| timestamp += kTimestampDelta5Fps; |
| layers_->PopulateCodecSpecific(false, &vp8_info, timestamp); |
| EXPECT_EQ(static_cast<uint8_t>(kNoTemporalIdx), vp8_info.temporalIdx); |
| EXPECT_FALSE(vp8_info.layerSync); |
| EXPECT_EQ(kNoTl0PicIdx, vp8_info.tl0PicIdx); |
| layers_->FrameEncoded(frame_size_, timestamp, kDefaultQp); |
| } |
| |
| TEST_F(ScreenshareLayerTest, 2Layer) { |
| layers_.reset(new ScreenshareLayers(2, 0)); |
| ConfigureBitrates(); |
| int flags = 0; |
| uint32_t timestamp = 0; |
| uint8_t expected_tl0_idx = 0; |
| CodecSpecificInfoVP8 vp8_info; |
| EncodeFrame(timestamp, false, &vp8_info, &flags); |
| EXPECT_EQ(ScreenshareLayers::kTl0Flags, flags); |
| EXPECT_EQ(0, vp8_info.temporalIdx); |
| EXPECT_FALSE(vp8_info.layerSync); |
| ++expected_tl0_idx; |
| EXPECT_EQ(expected_tl0_idx, vp8_info.tl0PicIdx); |
| |
| // Insert 5 frames, cover grace period. All should be in TL0. |
| for (int i = 0; i < 5; ++i) { |
| timestamp += kTimestampDelta5Fps; |
| EncodeFrame(timestamp, false, &vp8_info, &flags); |
| EXPECT_EQ(0, vp8_info.temporalIdx); |
| EXPECT_FALSE(vp8_info.layerSync); |
| ++expected_tl0_idx; |
| EXPECT_EQ(expected_tl0_idx, vp8_info.tl0PicIdx); |
| } |
| |
| // First frame in TL0. |
| timestamp += kTimestampDelta5Fps; |
| EncodeFrame(timestamp, false, &vp8_info, &flags); |
| EXPECT_EQ(ScreenshareLayers::kTl0Flags, flags); |
| EXPECT_EQ(0, vp8_info.temporalIdx); |
| EXPECT_FALSE(vp8_info.layerSync); |
| ++expected_tl0_idx; |
| EXPECT_EQ(expected_tl0_idx, vp8_info.tl0PicIdx); |
| |
| // Drop two frames from TL0, thus being coded in TL1. |
| timestamp += kTimestampDelta5Fps; |
| EncodeFrame(timestamp, false, &vp8_info, &flags); |
| // First frame is sync frame. |
| EXPECT_EQ(ScreenshareLayers::kTl1SyncFlags, flags); |
| EXPECT_EQ(1, vp8_info.temporalIdx); |
| EXPECT_TRUE(vp8_info.layerSync); |
| EXPECT_EQ(expected_tl0_idx, vp8_info.tl0PicIdx); |
| |
| timestamp += kTimestampDelta5Fps; |
| EncodeFrame(timestamp, false, &vp8_info, &flags); |
| EXPECT_EQ(ScreenshareLayers::kTl1Flags, flags); |
| EXPECT_EQ(1, vp8_info.temporalIdx); |
| EXPECT_FALSE(vp8_info.layerSync); |
| EXPECT_EQ(expected_tl0_idx, vp8_info.tl0PicIdx); |
| } |
| |
| TEST_F(ScreenshareLayerTest, 2LayersPeriodicSync) { |
| layers_.reset(new ScreenshareLayers(2, 0)); |
| ConfigureBitrates(); |
| int flags = 0; |
| uint32_t timestamp = 0; |
| CodecSpecificInfoVP8 vp8_info; |
| std::vector<int> sync_times; |
| |
| const int kNumFrames = kSyncPeriodSeconds * kFrameRate * 2 - 1; |
| for (int i = 0; i < kNumFrames; ++i) { |
| timestamp += kTimestampDelta5Fps; |
| EncodeFrame(timestamp, false, &vp8_info, &flags); |
| if (vp8_info.temporalIdx == 1 && vp8_info.layerSync) { |
| sync_times.push_back(timestamp); |
| } |
| } |
| |
| ASSERT_EQ(2u, sync_times.size()); |
| EXPECT_GE(sync_times[1] - sync_times[0], 90000 * kSyncPeriodSeconds); |
| } |
| |
| TEST_F(ScreenshareLayerTest, 2LayersSyncAfterTimeout) { |
| layers_.reset(new ScreenshareLayers(2, 0)); |
| ConfigureBitrates(); |
| uint32_t timestamp = 0; |
| CodecSpecificInfoVP8 vp8_info; |
| std::vector<int> sync_times; |
| |
| const int kNumFrames = kMaxSyncPeriodSeconds * kFrameRate * 2 - 1; |
| for (int i = 0; i < kNumFrames; ++i) { |
| timestamp += kTimestampDelta5Fps; |
| layers_->EncodeFlags(timestamp); |
| layers_->PopulateCodecSpecific(false, &vp8_info, timestamp); |
| |
| // Simulate TL1 being at least 8 qp steps better. |
| if (vp8_info.temporalIdx == 0) { |
| layers_->FrameEncoded(frame_size_, timestamp, kDefaultQp); |
| } else { |
| layers_->FrameEncoded(frame_size_, timestamp, kDefaultQp - 8); |
| } |
| |
| if (vp8_info.temporalIdx == 1 && vp8_info.layerSync) |
| sync_times.push_back(timestamp); |
| } |
| |
| ASSERT_EQ(2u, sync_times.size()); |
| EXPECT_GE(sync_times[1] - sync_times[0], 90000 * kMaxSyncPeriodSeconds); |
| } |
| |
| TEST_F(ScreenshareLayerTest, 2LayersSyncAfterSimilarQP) { |
| layers_.reset(new ScreenshareLayers(2, 0)); |
| ConfigureBitrates(); |
| uint32_t timestamp = 0; |
| CodecSpecificInfoVP8 vp8_info; |
| std::vector<int> sync_times; |
| |
| const int kNumFrames = (kSyncPeriodSeconds + |
| ((kMaxSyncPeriodSeconds - kSyncPeriodSeconds) / 2)) * |
| kFrameRate; |
| for (int i = 0; i < kNumFrames; ++i) { |
| timestamp += kTimestampDelta5Fps; |
| layers_->EncodeFlags(timestamp); |
| layers_->PopulateCodecSpecific(false, &vp8_info, timestamp); |
| |
| // Simulate TL1 being at least 8 qp steps better. |
| if (vp8_info.temporalIdx == 0) { |
| layers_->FrameEncoded(frame_size_, timestamp, kDefaultQp); |
| } else { |
| layers_->FrameEncoded(frame_size_, timestamp, kDefaultQp - 8); |
| } |
| |
| if (vp8_info.temporalIdx == 1 && vp8_info.layerSync) |
| sync_times.push_back(timestamp); |
| } |
| |
| ASSERT_EQ(1u, sync_times.size()); |
| |
| bool bumped_tl0_quality = false; |
| for (int i = 0; i < 3; ++i) { |
| timestamp += kTimestampDelta5Fps; |
| int flags = layers_->EncodeFlags(timestamp); |
| layers_->PopulateCodecSpecific(false, &vp8_info, timestamp); |
| |
| if (vp8_info.temporalIdx == 0) { |
| // Bump TL0 to same quality as TL1. |
| layers_->FrameEncoded(frame_size_, timestamp, kDefaultQp - 8); |
| bumped_tl0_quality = true; |
| } else { |
| layers_->FrameEncoded(frame_size_, timestamp, kDefaultQp - 8); |
| if (bumped_tl0_quality) { |
| EXPECT_TRUE(vp8_info.layerSync); |
| EXPECT_EQ(ScreenshareLayers::kTl1SyncFlags, flags); |
| return; |
| } |
| } |
| } |
| ADD_FAILURE() << "No TL1 frame arrived within time limit."; |
| } |
| |
| TEST_F(ScreenshareLayerTest, 2LayersToggling) { |
| layers_.reset(new ScreenshareLayers(2, 0)); |
| ConfigureBitrates(); |
| int flags = 0; |
| CodecSpecificInfoVP8 vp8_info; |
| uint32_t timestamp = RunGracePeriod(); |
| |
| // Insert 50 frames. 2/5 should be TL0. |
| int tl0_frames = 0; |
| int tl1_frames = 0; |
| for (int i = 0; i < 50; ++i) { |
| timestamp += kTimestampDelta5Fps; |
| EncodeFrame(timestamp, false, &vp8_info, &flags); |
| switch (vp8_info.temporalIdx) { |
| case 0: |
| ++tl0_frames; |
| break; |
| case 1: |
| ++tl1_frames; |
| break; |
| default: |
| abort(); |
| } |
| } |
| EXPECT_EQ(20, tl0_frames); |
| EXPECT_EQ(30, tl1_frames); |
| } |
| |
| TEST_F(ScreenshareLayerTest, AllFitsLayer0) { |
| layers_.reset(new ScreenshareLayers(2, 0)); |
| ConfigureBitrates(); |
| frame_size_ = ((kDefaultTl0BitrateKbps * 1000) / 8) / kFrameRate; |
| |
| int flags = 0; |
| uint32_t timestamp = 0; |
| CodecSpecificInfoVP8 vp8_info; |
| // Insert 50 frames, small enough that all fits in TL0. |
| for (int i = 0; i < 50; ++i) { |
| EncodeFrame(timestamp, false, &vp8_info, &flags); |
| timestamp += kTimestampDelta5Fps; |
| EXPECT_EQ(ScreenshareLayers::kTl0Flags, flags); |
| EXPECT_EQ(0, vp8_info.temporalIdx); |
| } |
| } |
| |
| TEST_F(ScreenshareLayerTest, TooHighBitrate) { |
| layers_.reset(new ScreenshareLayers(2, 0)); |
| ConfigureBitrates(); |
| frame_size_ = 2 * ((kDefaultTl1BitrateKbps * 1000) / 8) / kFrameRate; |
| int flags = 0; |
| CodecSpecificInfoVP8 vp8_info; |
| uint32_t timestamp = RunGracePeriod(); |
| |
| // Insert 100 frames. Half should be dropped. |
| int tl0_frames = 0; |
| int tl1_frames = 0; |
| int dropped_frames = 0; |
| for (int i = 0; i < 100; ++i) { |
| timestamp += kTimestampDelta5Fps; |
| EncodeFrame(timestamp, false, &vp8_info, &flags); |
| if (flags == -1) { |
| ++dropped_frames; |
| } else { |
| switch (vp8_info.temporalIdx) { |
| case 0: |
| ++tl0_frames; |
| break; |
| case 1: |
| ++tl1_frames; |
| break; |
| default: |
| abort(); |
| } |
| } |
| } |
| |
| EXPECT_EQ(5, tl0_frames); |
| EXPECT_EQ(45, tl1_frames); |
| EXPECT_EQ(50, dropped_frames); |
| } |
| |
| TEST_F(ScreenshareLayerTest, TargetBitrateCappedByTL0) { |
| layers_.reset(new ScreenshareLayers(2, 0)); |
| |
| vpx_codec_enc_cfg_t cfg; |
| layers_->ConfigureBitrates(100, 1000, 5, &cfg); |
| |
| EXPECT_EQ(static_cast<unsigned int>( |
| ScreenshareLayers::kMaxTL0FpsReduction * 100 + 0.5), |
| cfg.rc_target_bitrate); |
| } |
| |
| TEST_F(ScreenshareLayerTest, TargetBitrateCappedByTL1) { |
| layers_.reset(new ScreenshareLayers(2, 0)); |
| vpx_codec_enc_cfg_t cfg; |
| layers_->ConfigureBitrates(100, 450, 5, &cfg); |
| |
| EXPECT_EQ(static_cast<unsigned int>( |
| 450 / ScreenshareLayers::kAcceptableTargetOvershoot), |
| cfg.rc_target_bitrate); |
| } |
| |
| TEST_F(ScreenshareLayerTest, TargetBitrateBelowTL0) { |
| layers_.reset(new ScreenshareLayers(2, 0)); |
| vpx_codec_enc_cfg_t cfg; |
| layers_->ConfigureBitrates(100, 100, 5, &cfg); |
| |
| EXPECT_EQ(100U, cfg.rc_target_bitrate); |
| } |
| |
| TEST_F(ScreenshareLayerTest, EncoderDrop) { |
| layers_.reset(new ScreenshareLayers(2, 0)); |
| ConfigureBitrates(); |
| CodecSpecificInfoVP8 vp8_info; |
| vpx_codec_enc_cfg_t cfg; |
| cfg.rc_max_quantizer = kDefaultQp; |
| |
| uint32_t timestamp = RunGracePeriod(); |
| timestamp = SkipUntilTl(0, timestamp); |
| |
| // Size 0 indicates dropped frame. |
| layers_->FrameEncoded(0, timestamp, kDefaultQp); |
| timestamp += kTimestampDelta5Fps; |
| EXPECT_FALSE(layers_->UpdateConfiguration(&cfg)); |
| EXPECT_EQ(ScreenshareLayers::kTl0Flags, layers_->EncodeFlags(timestamp)); |
| layers_->PopulateCodecSpecific(false, &vp8_info, timestamp); |
| layers_->FrameEncoded(frame_size_, timestamp, kDefaultQp); |
| |
| timestamp = SkipUntilTl(0, timestamp); |
| EXPECT_TRUE(layers_->UpdateConfiguration(&cfg)); |
| EXPECT_LT(cfg.rc_max_quantizer, static_cast<unsigned int>(kDefaultQp)); |
| layers_->FrameEncoded(frame_size_, timestamp, kDefaultQp); |
| |
| layers_->EncodeFlags(timestamp); |
| timestamp += kTimestampDelta5Fps; |
| EXPECT_TRUE(layers_->UpdateConfiguration(&cfg)); |
| layers_->PopulateCodecSpecific(false, &vp8_info, timestamp); |
| EXPECT_EQ(cfg.rc_max_quantizer, static_cast<unsigned int>(kDefaultQp)); |
| layers_->FrameEncoded(frame_size_, timestamp, kDefaultQp); |
| |
| // Next drop in TL1. |
| |
| timestamp = SkipUntilTl(1, timestamp); |
| layers_->FrameEncoded(0, timestamp, kDefaultQp); |
| timestamp += kTimestampDelta5Fps; |
| EXPECT_FALSE(layers_->UpdateConfiguration(&cfg)); |
| EXPECT_EQ(ScreenshareLayers::kTl1Flags, layers_->EncodeFlags(timestamp)); |
| layers_->PopulateCodecSpecific(false, &vp8_info, timestamp); |
| layers_->FrameEncoded(frame_size_, timestamp, kDefaultQp); |
| |
| timestamp = SkipUntilTl(1, timestamp); |
| EXPECT_TRUE(layers_->UpdateConfiguration(&cfg)); |
| EXPECT_LT(cfg.rc_max_quantizer, static_cast<unsigned int>(kDefaultQp)); |
| layers_->FrameEncoded(frame_size_, timestamp, kDefaultQp); |
| |
| layers_->EncodeFlags(timestamp); |
| timestamp += kTimestampDelta5Fps; |
| EXPECT_TRUE(layers_->UpdateConfiguration(&cfg)); |
| layers_->PopulateCodecSpecific(false, &vp8_info, timestamp); |
| EXPECT_EQ(cfg.rc_max_quantizer, static_cast<unsigned int>(kDefaultQp)); |
| layers_->FrameEncoded(frame_size_, timestamp, kDefaultQp); |
| } |
| |
| } // namespace webrtc |