| /* |
| * Copyright (c) 2012 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 <sstream> |
| #include <string> |
| #include <vector> |
| |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "webrtc/test/testsupport/fileutils.h" |
| #include "webrtc/test/testsupport/metrics/video_metrics.h" |
| #include "webrtc/test/testsupport/perf_test.h" |
| #include "webrtc/video_engine/test/auto_test/interface/vie_autotest.h" |
| #include "webrtc/video_engine/test/auto_test/interface/vie_file_based_comparison_tests.h" |
| #include "webrtc/video_engine/test/auto_test/primitives/framedrop_primitives.h" |
| #include "webrtc/video_engine/test/libvietest/include/tb_external_transport.h" |
| #include "webrtc/video_engine/test/libvietest/include/vie_to_file_renderer.h" |
| |
| namespace { |
| |
| // The input file must be QCIF since I420 gets scaled to that in the tests |
| // (it is so bandwidth-heavy we have no choice). Our comparison algorithms |
| // wouldn't like scaling, so this will work when we compare with the original. |
| const int kInputWidth = 176; |
| const int kInputHeight = 144; |
| |
| class ViEVideoVerificationTest : public testing::Test { |
| protected: |
| void SetUp() { |
| input_file_ = webrtc::test::ResourcePath("paris_qcif", "yuv"); |
| local_file_renderer_ = NULL; |
| remote_file_renderer_ = NULL; |
| } |
| |
| void InitializeFileRenderers() { |
| local_file_renderer_ = new ViEToFileRenderer(); |
| remote_file_renderer_ = new ViEToFileRenderer(); |
| SetUpLocalFileRenderer(local_file_renderer_); |
| SetUpRemoteFileRenderer(remote_file_renderer_); |
| } |
| |
| void SetUpLocalFileRenderer(ViEToFileRenderer* file_renderer) { |
| SetUpFileRenderer(file_renderer, "-local-preview.yuv"); |
| } |
| |
| void SetUpRemoteFileRenderer(ViEToFileRenderer* file_renderer) { |
| SetUpFileRenderer(file_renderer, "-remote.yuv"); |
| } |
| |
| // Must be called manually inside the tests. |
| void StopRenderers() { |
| local_file_renderer_->StopRendering(); |
| remote_file_renderer_->StopRendering(); |
| } |
| |
| void CompareFiles(const std::string& reference_file, |
| const std::string& test_file, |
| double* psnr_result, double *ssim_result) { |
| webrtc::test::QualityMetricsResult psnr; |
| int error = I420PSNRFromFiles(reference_file.c_str(), test_file.c_str(), |
| kInputWidth, kInputHeight, &psnr); |
| |
| EXPECT_EQ(0, error) << "PSNR routine failed - output files missing?"; |
| *psnr_result = psnr.average; |
| |
| webrtc::test::QualityMetricsResult ssim; |
| error = I420SSIMFromFiles(reference_file.c_str(), test_file.c_str(), |
| kInputWidth, kInputHeight, &ssim); |
| EXPECT_EQ(0, error) << "SSIM routine failed - output files missing?"; |
| *ssim_result = ssim.average; |
| |
| ViETest::Log("Results: PSNR is %f (dB; 48 is max), " |
| "SSIM is %f (1 is perfect)", |
| psnr.average, ssim.average); |
| } |
| |
| // Note: must call AFTER CompareFiles. |
| void TearDownFileRenderers() { |
| TearDownFileRenderer(local_file_renderer_); |
| TearDownFileRenderer(remote_file_renderer_); |
| } |
| |
| std::string input_file_; |
| ViEToFileRenderer* local_file_renderer_; |
| ViEToFileRenderer* remote_file_renderer_; |
| ViEFileBasedComparisonTests tests_; |
| |
| private: |
| void SetUpFileRenderer(ViEToFileRenderer* file_renderer, |
| const std::string& suffix) { |
| std::string output_path = ViETest::GetResultOutputPath(); |
| std::string filename = "render_output" + suffix; |
| |
| if (!file_renderer->PrepareForRendering(output_path, filename)) { |
| FAIL() << "Could not open output file " << filename << |
| " for writing."; |
| } |
| } |
| |
| void TearDownFileRenderer(ViEToFileRenderer* file_renderer) { |
| assert(file_renderer); |
| bool test_failed = ::testing::UnitTest::GetInstance()-> |
| current_test_info()->result()->Failed(); |
| if (test_failed) { |
| // Leave the files for analysis if the test failed. |
| file_renderer->SaveOutputFile("failed-"); |
| } |
| delete file_renderer; |
| } |
| }; |
| |
| class ParameterizedFullStackTest : public ViEVideoVerificationTest, |
| public ::testing::WithParamInterface<int> { |
| public: |
| static const int kNumFullStackInstances = 4; |
| |
| protected: |
| struct TestParameters { |
| NetworkParameters network; |
| std::string file_name; |
| int width; |
| int height; |
| int bitrate; |
| double avg_psnr_threshold; |
| double avg_ssim_threshold; |
| ProtectionMethod protection_method; |
| std::string test_label; |
| }; |
| |
| void SetUp() { |
| for (int i = 0; i < kNumFullStackInstances; ++i) { |
| parameter_table_[i].file_name = webrtc::test::ResourcePath("foreman_cif", |
| "yuv"); |
| parameter_table_[i].width = 352; |
| parameter_table_[i].height = 288; |
| } |
| int i = 0; |
| parameter_table_[i].protection_method = kNack; |
| // Uniform loss => Setting burst length to -1. |
| parameter_table_[i].network.loss_model = kUniformLoss; |
| parameter_table_[i].network.packet_loss_rate = 0; |
| parameter_table_[i].network.burst_length = -1; |
| parameter_table_[i].network.mean_one_way_delay = 0; |
| parameter_table_[i].network.std_dev_one_way_delay = 0; |
| parameter_table_[i].bitrate = 300; |
| // TODO(holmer): Enable for Win and Mac when the file rendering has been |
| // moved to a separate thread. |
| #ifdef WEBRTC_LINUX |
| parameter_table_[i].avg_psnr_threshold = 35; |
| parameter_table_[i].avg_ssim_threshold = 0.96; |
| #else |
| parameter_table_[i].avg_psnr_threshold = 0; |
| parameter_table_[i].avg_ssim_threshold = 0.0; |
| #endif |
| parameter_table_[i].test_label = "net_delay_0_0_plr_0"; |
| ++i; |
| parameter_table_[i].protection_method = kNack; |
| parameter_table_[i].network.loss_model = kUniformLoss; |
| parameter_table_[i].network.packet_loss_rate = 5; |
| parameter_table_[i].network.burst_length = -1; |
| parameter_table_[i].network.mean_one_way_delay = 50; |
| parameter_table_[i].network.std_dev_one_way_delay = 5; |
| parameter_table_[i].bitrate = 300; |
| // TODO(holmer): Enable for Win and Mac when the file rendering has been |
| // moved to a separate thread. |
| #ifdef WEBRTC_LINUX |
| parameter_table_[i].avg_psnr_threshold = 35; |
| parameter_table_[i].avg_ssim_threshold = 0.96; |
| #else |
| parameter_table_[i].avg_psnr_threshold = 0; |
| parameter_table_[i].avg_ssim_threshold = 0.0; |
| #endif |
| parameter_table_[i].test_label = "net_delay_50_5_plr_5"; |
| ++i; |
| parameter_table_[i].protection_method = kNack; |
| parameter_table_[i].network.loss_model = kUniformLoss; |
| parameter_table_[i].network.packet_loss_rate = 0; |
| parameter_table_[i].network.burst_length = -1; |
| parameter_table_[i].network.mean_one_way_delay = 100; |
| parameter_table_[i].network.std_dev_one_way_delay = 10; |
| parameter_table_[i].bitrate = 300; |
| // TODO(holmer): Enable for Win and Mac when the file rendering has been |
| // moved to a separate thread. |
| #ifdef WEBRTC_LINUX |
| parameter_table_[i].avg_psnr_threshold = 35; |
| parameter_table_[i].avg_ssim_threshold = 0.96; |
| #else |
| parameter_table_[i].avg_psnr_threshold = 0; |
| parameter_table_[i].avg_ssim_threshold = 0.0; |
| #endif |
| parameter_table_[i].test_label = "net_delay_100_10_plr_0"; |
| ++i; |
| parameter_table_[i].protection_method = kNack; |
| parameter_table_[i].network.loss_model = kGilbertElliotLoss; |
| parameter_table_[i].network.packet_loss_rate = 5; |
| parameter_table_[i].network.burst_length = 3; |
| parameter_table_[i].network.mean_one_way_delay = 100; |
| parameter_table_[i].network.std_dev_one_way_delay = 10; |
| parameter_table_[i].bitrate = 300; |
| // Thresholds disabled for now. This is being run mainly to get a graph. |
| parameter_table_[i].avg_psnr_threshold = 0; |
| parameter_table_[i].avg_ssim_threshold = 0.0; |
| parameter_table_[i].test_label = "net_delay_100_10_plr_5_gilbert_elliot"; |
| |
| ASSERT_EQ(kNumFullStackInstances - 1, i); |
| } |
| |
| TestParameters parameter_table_[kNumFullStackInstances]; |
| }; |
| |
| TEST_F(ViEVideoVerificationTest, RunsBaseStandardTestWithoutErrors) { |
| // I420 is lossless, so the I420 test should obviously get perfect results - |
| // the local preview and remote output files should be bit-exact. This test |
| // runs on external transport to ensure we do not drop packets. |
| // However, it's hard to make 100% stringent requirements on the video engine |
| // since for instance the jitter buffer has non-deterministic elements. If it |
| // breaks five times in a row though, you probably introduced a bug. |
| const double kReasonablePsnr = webrtc::test::kMetricsPerfectPSNR - 2.0f; |
| const double kReasonableSsim = 0.99f; |
| const int kNumAttempts = 5; |
| for (int attempt = 0; attempt < kNumAttempts; ++attempt) { |
| InitializeFileRenderers(); |
| ASSERT_TRUE(tests_.TestCallSetup(input_file_, kInputWidth, kInputHeight, |
| local_file_renderer_, |
| remote_file_renderer_)); |
| std::string remote_file = remote_file_renderer_->GetFullOutputPath(); |
| std::string local_preview = local_file_renderer_->GetFullOutputPath(); |
| StopRenderers(); |
| |
| double actual_psnr = 0; |
| double actual_ssim = 0; |
| CompareFiles(local_preview, remote_file, &actual_psnr, &actual_ssim); |
| |
| TearDownFileRenderers(); |
| |
| if (actual_psnr > kReasonablePsnr && actual_ssim > kReasonableSsim) { |
| // Test successful. |
| return; |
| } else { |
| ViETest::Log("Retrying; attempt %d of %d.", attempt + 1, kNumAttempts); |
| } |
| } |
| |
| FAIL() << "Failed to achieve near-perfect PSNR and SSIM results after " << |
| kNumAttempts << " attempts."; |
| } |
| |
| // Runs a whole stack processing with tracking of which frames are dropped |
| // in the encoder. Tests show that they start at the same frame, which is |
| // the important thing when doing frame-to-frame comparison with PSNR/SSIM. |
| TEST_P(ParameterizedFullStackTest, RunsFullStackWithoutErrors) { |
| // Using CIF here since it's a more common resolution than QCIF, and higher |
| // resolutions shouldn't be a problem for a test using VP8. |
| input_file_ = parameter_table_[GetParam()].file_name; |
| FrameDropDetector detector; |
| local_file_renderer_ = new ViEToFileRenderer(); |
| remote_file_renderer_ = new FrameDropMonitoringRemoteFileRenderer(&detector); |
| SetUpLocalFileRenderer(local_file_renderer_); |
| SetUpRemoteFileRenderer(remote_file_renderer_); |
| |
| // Set a low bit rate so the encoder budget will be tight, causing it to drop |
| // frames every now and then. |
| const int kBitRateKbps = parameter_table_[GetParam()].bitrate; |
| const NetworkParameters network = parameter_table_[GetParam()].network; |
| int width = parameter_table_[GetParam()].width; |
| int height = parameter_table_[GetParam()].height; |
| ProtectionMethod protection_method = |
| parameter_table_[GetParam()].protection_method; |
| ViETest::Log("Bit rate : %5d kbps", kBitRateKbps); |
| ViETest::Log("Packet loss : %5d %%", network.packet_loss_rate); |
| ViETest::Log("Network delay: mean=%dms std dev=%d ms", |
| network.mean_one_way_delay, network.std_dev_one_way_delay); |
| tests_.TestFullStack(input_file_, width, height, kBitRateKbps, |
| protection_method, network, local_file_renderer_, |
| remote_file_renderer_, &detector); |
| const std::string reference_file = local_file_renderer_->GetFullOutputPath(); |
| const std::string output_file = remote_file_renderer_->GetFullOutputPath(); |
| StopRenderers(); |
| |
| detector.CalculateResults(); |
| detector.PrintReport(parameter_table_[GetParam()].test_label); |
| |
| if (detector.GetNumberOfFramesDroppedAt(FrameDropDetector::kRendered) > |
| detector.GetNumberOfFramesDroppedAt(FrameDropDetector::kDecoded)) { |
| detector.PrintDebugDump(); |
| } |
| |
| ASSERT_GE(detector.GetNumberOfFramesDroppedAt(FrameDropDetector::kRendered), |
| detector.GetNumberOfFramesDroppedAt(FrameDropDetector::kDecoded)) |
| << "The number of dropped frames on the decode and render steps are not " |
| "equal. This may be because we have a major problem in the buffers of " |
| "the ViEToFileRenderer?"; |
| |
| // We may have dropped frames during the processing, which means the output |
| // file does not contain all the frames that are present in the input file. |
| // To make the quality measurement correct, we must adjust the output file to |
| // that by copying the last successful frame into the place where the dropped |
| // frame would be, for all dropped frames. |
| const int frame_length_in_bytes = 3 * width * height / 2; |
| ViETest::Log("Frame length: %d bytes", frame_length_in_bytes); |
| std::vector<Frame*> all_frames = detector.GetAllFrames(); |
| FixOutputFileForComparison(output_file, frame_length_in_bytes, all_frames); |
| |
| // Verify all sent frames are present in the output file. |
| size_t output_file_size = webrtc::test::GetFileSize(output_file); |
| EXPECT_EQ(all_frames.size(), output_file_size / frame_length_in_bytes) |
| << "The output file size is incorrect. It should be equal to the number " |
| "of frames multiplied by the frame size. This will likely affect " |
| "PSNR/SSIM calculations in a bad way."; |
| |
| TearDownFileRenderers(); |
| |
| // We are running on a lower bitrate here so we need to settle for somewhat |
| // lower PSNR and SSIM values. |
| double actual_psnr = 0; |
| double actual_ssim = 0; |
| CompareFiles(reference_file, output_file, &actual_psnr, &actual_ssim); |
| |
| const double kExpectedMinimumPSNR = |
| parameter_table_[GetParam()].avg_psnr_threshold; |
| const double kExpectedMinimumSSIM = |
| parameter_table_[GetParam()].avg_ssim_threshold; |
| |
| EXPECT_GE(actual_psnr, kExpectedMinimumPSNR); |
| EXPECT_GE(actual_ssim, kExpectedMinimumSSIM); |
| |
| std::stringstream ss; |
| ss << std::setprecision(3) << std::fixed << actual_psnr; |
| webrtc::test::PrintResult( |
| "psnr", "", parameter_table_[GetParam()].test_label, |
| ss.str(), "dB", false); |
| |
| ss.str(""); |
| ss << std::setprecision(3) << std::fixed << actual_ssim; |
| webrtc::test::PrintResult( |
| "ssim", "", parameter_table_[GetParam()].test_label, |
| ss.str(), "", false); |
| } |
| |
| INSTANTIATE_TEST_CASE_P(FullStackTests, ParameterizedFullStackTest, |
| ::testing::Range(0, ParameterizedFullStackTest::kNumFullStackInstances)); |
| |
| } // namespace |