|  | /* | 
|  | *  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/codecs/interface/video_codec_interface.h" | 
|  | #include "webrtc/modules/video_coding/codecs/vp8/screenshare_layers.h" | 
|  | #include "webrtc/modules/video_coding/utility/include/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 |