Added tool for reference less video analysis (go/refless-video-analysis)
This tool takes list of video file names as input and calculates freezing metrics score for the video files without having reference to original video by comparing the PSNR and SSIM values of current and previous frame.
BUG=webrtc:6759
Review-Url: https://codereview.webrtc.org/2515253004
Cr-Commit-Position: refs/heads/master@{#15386}
diff --git a/webrtc/tools/BUILD.gn b/webrtc/tools/BUILD.gn
index 5353ba8..c8604f1 100644
--- a/webrtc/tools/BUILD.gn
+++ b/webrtc/tools/BUILD.gn
@@ -87,6 +87,29 @@
]
}
+rtc_static_library("reference_less_video_analysis_lib") {
+ sources = [
+ "frame_analyzer/reference_less_video_analysis_lib.cc",
+ "frame_analyzer/reference_less_video_analysis_lib.h",
+ ]
+
+ deps = [
+ ":video_quality_analysis",
+ ]
+}
+
+rtc_executable("reference_less_video_analysis") {
+ sources = [
+ "frame_analyzer/reference_less_video_analysis.cc",
+ ]
+
+ deps = [
+ ":command_line_parser",
+ ":reference_less_video_analysis_lib",
+ "//build/win:default_exe_manifest",
+ ]
+}
+
rtc_executable("rgba_to_i420_converter") {
sources = [
"converter/converter.cc",
@@ -254,6 +277,7 @@
testonly = true
sources = [
+ "frame_analyzer/reference_less_video_analysis_unittest.cc",
"frame_analyzer/video_quality_analysis_unittest.cc",
"frame_editing/frame_editing_unittest.cc",
"simple_command_line_parser_unittest.cc",
@@ -270,6 +294,7 @@
deps = [
":command_line_parser",
":frame_editing_lib",
+ ":reference_less_video_analysis_lib",
":video_quality_analysis",
"../test:test_main",
"//testing/gtest",
diff --git a/webrtc/tools/frame_analyzer/reference_less_video_analysis.cc b/webrtc/tools/frame_analyzer/reference_less_video_analysis.cc
new file mode 100644
index 0000000..019564e
--- /dev/null
+++ b/webrtc/tools/frame_analyzer/reference_less_video_analysis.cc
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2016 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "webrtc/tools/frame_analyzer/reference_less_video_analysis_lib.h"
+#include "webrtc/tools/simple_command_line_parser.h"
+
+int main(int argc, char** argv) {
+ // This captures the freezing metrics for reference less video analysis.
+ std::string program_name = argv[0];
+ std::string usage = "Outputs the freezing score by comparing current frame "
+ "with the previous frame.\nExample usage:\n" + program_name +
+ " --video_file=video_file.y4m\n"
+ "Command line flags:\n"
+ " - video_file(string): Path of the video "
+ "file to be analyzed. Only y4m file format is supported.\n";
+
+ webrtc::test::CommandLineParser parser;
+
+ // Init the parser and set the usage message.
+ parser.Init(argc, argv);
+ parser.SetUsageMessage(usage);
+
+ parser.SetFlag("video_file", "");
+ parser.ProcessFlags();
+ if (parser.GetFlag("video_file").empty()) {
+ parser.PrintUsageMessage();
+ exit(EXIT_SUCCESS);
+ }
+ std::string video_file = parser.GetFlag("video_file");
+
+ return run_analysis(video_file);
+}
diff --git a/webrtc/tools/frame_analyzer/reference_less_video_analysis_lib.cc b/webrtc/tools/frame_analyzer/reference_less_video_analysis_lib.cc
new file mode 100644
index 0000000..ab484e9
--- /dev/null
+++ b/webrtc/tools/frame_analyzer/reference_less_video_analysis_lib.cc
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) 2016 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <iostream>
+#include <numeric>
+#include <vector>
+
+#include "webrtc/tools/frame_analyzer/reference_less_video_analysis_lib.h"
+#include "webrtc/tools/frame_analyzer/video_quality_analysis.h"
+
+#define STATS_LINE_LENGTH 28
+#define PSNR_FREEZE_THRESHOLD 47
+#define SSIM_FREEZE_THRESHOLD .999
+
+#if defined(_WIN32) || defined(_WIN64)
+#define strtok_r strtok_s
+#endif
+
+void get_height_width_fps(int *height, int *width, int *fps,
+ const std::string& video_file) {
+ // File header looks like :
+ // YUV4MPEG2 W1280 H720 F25:1 Ip A0:0 C420mpeg2 XYSCSS=420MPEG2.
+ char frame_header[STATS_LINE_LENGTH];
+ FILE* input_file = fopen(video_file.c_str(), "rb");
+
+ size_t bytes_read = fread(frame_header, 1, STATS_LINE_LENGTH - 1, input_file);
+
+ frame_header[bytes_read] = '\0';
+ std::string file_header_stats[5];
+ int no_of_stats = 0;
+ char *save_ptr;
+ char *token = strtok_r(frame_header, " ", &save_ptr);
+
+ while (token != NULL) {
+ file_header_stats[no_of_stats++] = token;
+ token = strtok_r(NULL, " ", &save_ptr);
+ }
+
+ *width = std::stoi(file_header_stats[1].erase(0, 1));
+ *height = std::stoi(file_header_stats[2].erase(0, 1));
+ *fps = std::stoi(file_header_stats[3].erase(0, 1));
+
+ printf("Height: %d Width: %d fps:%d \n", *height, *width, *fps);
+ fclose(input_file);
+}
+
+bool frozen_frame(std::vector<double> psnr_per_frame,
+ std::vector<double> ssim_per_frame, size_t frame) {
+ if (psnr_per_frame[frame] >= PSNR_FREEZE_THRESHOLD ||
+ ssim_per_frame[frame] >= SSIM_FREEZE_THRESHOLD)
+ return true;
+ return false;
+}
+
+std::vector<int> find_frame_clusters(const std::vector<double>& psnr_per_frame,
+ const std::vector<double>& ssim_per_frame) {
+ std::vector<int> identical_frame_clusters;
+ int num_frozen = 0;
+ size_t total_no_of_frames = psnr_per_frame.size();
+
+ for (size_t each_frame = 0; each_frame < total_no_of_frames; each_frame++) {
+ if (frozen_frame(psnr_per_frame, ssim_per_frame, each_frame)) {
+ num_frozen++;
+ } else if (num_frozen > 0) {
+ // Not frozen anymore.
+ identical_frame_clusters.push_back(num_frozen);
+ num_frozen = 0;
+ }
+ }
+ return identical_frame_clusters;
+}
+
+void print_freezing_metrics(const std::vector<double>& psnr_per_frame,
+ const std::vector<double>& ssim_per_frame) {
+ /*
+ * Prints the different metrics mainly:
+ * 1) Identical frame number, PSNR and SSIM values.
+ * 2) Length of continuous frozen frames.
+ * 3) Max length of continuous freezed frames.
+ * 4) No of unique frames found.
+ * 5) Total different identical frames found.
+ *
+ * Sample output:
+ * Printing metrics for file: /src/webrtc/tools/test_3.y4m
+ =============================
+ Total number of frames received: 74
+ Total identical frames: 5
+ Number of unique frames: 69
+ Printing Identical Frames:
+ Frame Number: 29 PSNR: 48.000000 SSIM: 0.999618
+ Frame Number: 30 PSNR: 48.000000 SSIM: 0.999898
+ Frame Number: 60 PSNR: 48.000000 SSIM: 0.999564
+ Frame Number: 64 PSNR: 48.000000 SSIM: 0.999651
+ Frame Number: 69 PSNR: 48.000000 SSIM: 0.999684
+ Print identical frame which appears in clusters :
+ 2 1 1 1
+ *
+ */
+ size_t total_no_of_frames = psnr_per_frame.size();
+ std::vector<int> identical_frame_clusters = find_frame_clusters(
+ psnr_per_frame, ssim_per_frame);
+ int total_identical_frames = std::accumulate(
+ identical_frame_clusters.begin(), identical_frame_clusters.end(), 0);
+ size_t unique_frames = total_no_of_frames - total_identical_frames;
+
+ printf("Total number of frames received: %zu\n", total_no_of_frames);
+ printf("Total identical frames: %d\n", total_identical_frames);
+ printf("Number of unique frames: %zu\n", unique_frames);
+
+ printf("Printing Identical Frames: \n");
+ for (size_t frame = 0; frame < total_no_of_frames; frame++) {
+ if (frozen_frame(psnr_per_frame, ssim_per_frame, frame)) {
+ printf(" Frame Number: %zu PSNR: %f SSIM: %f \n", frame,
+ psnr_per_frame[frame], ssim_per_frame[frame]);
+ }
+ }
+
+ printf("Print identical frame which appears in clusters : \n");
+ for (int cluster = 0;
+ cluster < static_cast<int>(identical_frame_clusters.size()); cluster++)
+ printf("%d ", identical_frame_clusters[cluster]);
+ printf("\n");
+}
+
+void compute_metrics(const std::string& video_file_name,
+ std::vector<double>* psnr_per_frame,
+ std::vector<double>* ssim_per_frame) {
+ int height = 0, width = 0, fps = 0;
+ get_height_width_fps(&height, &width, &fps, video_file_name);
+
+ int no_of_frames = 0;
+ int size = webrtc::test::GetI420FrameSize(width, height);
+
+ // Allocate buffers for test and reference frames.
+ uint8_t* current_frame = new uint8_t[size];
+ uint8_t* next_frame = new uint8_t[size];
+
+ while (true) {
+ if (!(webrtc::test::ExtractFrameFromY4mFile (video_file_name.c_str(),
+ width, height,
+ no_of_frames,
+ current_frame))) {
+ break;
+ }
+
+ if (!(webrtc::test::ExtractFrameFromY4mFile (video_file_name.c_str(),
+ width, height,
+ no_of_frames + 1,
+ next_frame))) {
+ break;
+ }
+
+ double result_psnr = webrtc::test::CalculateMetrics(webrtc::test::kPSNR,
+ current_frame,
+ next_frame,
+ width, height);
+ double result_ssim = webrtc::test::CalculateMetrics(webrtc::test::kSSIM,
+ current_frame,
+ next_frame,
+ width, height);
+
+ psnr_per_frame->push_back(result_psnr);
+ ssim_per_frame->push_back(result_ssim);
+ no_of_frames++;
+ }
+ // Cleanup.
+ delete[] current_frame;
+ delete[] next_frame;
+}
+
+bool check_file_extension(const std::string& video_file_name) {
+ if (video_file_name.substr(video_file_name.length()-3, 3) != "y4m") {
+ printf("Only y4m video file format is supported. Given: %s\n",
+ video_file_name.c_str());
+ return false;
+ }
+ return true;
+}
+
+int run_analysis(const std::string& video_file) {
+ std::vector<double> psnr_per_frame;
+ std::vector<double> ssim_per_frame;
+ if (check_file_extension(video_file)) {
+ compute_metrics(video_file, &psnr_per_frame, &ssim_per_frame);
+ } else {
+ return -1;
+ }
+ printf("=============================\n");
+ printf("Printing metrics for file: %s\n", video_file.c_str());
+ printf("=============================\n");
+ print_freezing_metrics(psnr_per_frame, ssim_per_frame);
+ return 0;
+}
diff --git a/webrtc/tools/frame_analyzer/reference_less_video_analysis_lib.h b/webrtc/tools/frame_analyzer/reference_less_video_analysis_lib.h
new file mode 100644
index 0000000..2fa0898
--- /dev/null
+++ b/webrtc/tools/frame_analyzer/reference_less_video_analysis_lib.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2016 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.
+ */
+
+#ifndef WEBRTC_TOOLS_FRAME_ANALYZER_REFERENCE_LESS_VIDEO_ANALYSIS_LIB_H_
+#define WEBRTC_TOOLS_FRAME_ANALYZER_REFERENCE_LESS_VIDEO_ANALYSIS_LIB_H_
+
+#include <string>
+#include <vector>
+
+// Parse the file header to extract height, width and fps
+// for a given video file.
+void get_height_width_fps(int *height, int *width, int *fps,
+ const std::string& video_file);
+
+// Returns true if the frame is frozen based on psnr and ssim freezing
+// threshold values.
+bool frozen_frame(std::vector<double> psnr_per_frame,
+ std::vector<double> ssim_per_frame, size_t frame);
+
+// Returns the vector of identical cluster of frames that are frozen
+// and appears continuously.
+std::vector<int> find_frame_clusters(const std::vector<double>& psnr_per_frame,
+ const std::vector<double>& ssim_per_frame);
+
+// Prints various freezing metrics like identical frames,
+// total unique frames etc.
+void print_freezing_metrics(const std::vector<double>& psnr_per_frame,
+ const std::vector<double>& ssim_per_frame);
+
+// Compute the metrics like freezing score based on PSNR and SSIM values for a
+// given video file.
+void compute_metrics(const std::string& video_file_name,
+ std::vector<double>* psnr_per_frame,
+ std::vector<double>* ssim_per_frame);
+
+// Checks the file extension and return true if it is y4m.
+bool check_file_extension(const std::string& video_file_name);
+
+// Compute freezing score metrics and prints the metrics
+// for a list of video files.
+int run_analysis(const std::string& video_file);
+
+#endif // WEBRTC_TOOLS_FRAME_ANALYZER_REFERENCE_LESS_VIDEO_ANALYSIS_LIB_H_
diff --git a/webrtc/tools/frame_analyzer/reference_less_video_analysis_unittest.cc b/webrtc/tools/frame_analyzer/reference_less_video_analysis_unittest.cc
new file mode 100644
index 0000000..b8e1c55
--- /dev/null
+++ b/webrtc/tools/frame_analyzer/reference_less_video_analysis_unittest.cc
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2016 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 <string.h>
+#include <stdio.h>
+#include <cstring>
+#include <iostream>
+#include <vector>
+
+#include "webrtc/test/gtest.h"
+#include "webrtc/test/testsupport/fileutils.h"
+#include "webrtc/tools/frame_analyzer/reference_less_video_analysis_lib.h"
+
+class ReferenceLessVideoAnalysisTest : public ::testing::Test {
+ public:
+ void SetUp() override {
+ video_file =
+ webrtc::test::ResourcePath("reference_less_video_test_file", "y4m");
+ }
+ std::string video_file;
+ std::vector<double> psnr_per_frame;
+ std::vector<double> ssim_per_frame;
+};
+
+TEST_F(ReferenceLessVideoAnalysisTest, MatchComputedMetrics) {
+ compute_metrics(video_file, &psnr_per_frame, &ssim_per_frame);
+ EXPECT_EQ(74, (int)psnr_per_frame.size());
+
+ ASSERT_NEAR(27.2f, psnr_per_frame[1], 0.1f);
+ ASSERT_NEAR(24.9f, psnr_per_frame[5], 0.1f);
+
+ ASSERT_NEAR(0.9f, ssim_per_frame[1], 0.1f);
+ ASSERT_NEAR(0.9f, ssim_per_frame[5], 0.1f);
+}
+
+TEST_F(ReferenceLessVideoAnalysisTest, MatchHeightWidthFps) {
+ int height = 0, width = 0, fps = 0;
+ get_height_width_fps(&height, &width, &fps, video_file.c_str());
+ EXPECT_EQ(height, 720);
+ EXPECT_EQ(width, 1280);
+ EXPECT_EQ(fps, 25);
+}
+
+TEST_F(ReferenceLessVideoAnalysisTest, MatchIdenticalFrameClusters) {
+ compute_metrics(video_file, &psnr_per_frame, &ssim_per_frame);
+ std::vector<int> identical_frame_clusters =
+ find_frame_clusters(psnr_per_frame, ssim_per_frame);
+ EXPECT_EQ(5, (int)identical_frame_clusters.size());
+ EXPECT_EQ(1, identical_frame_clusters[0]);
+ EXPECT_EQ(1, identical_frame_clusters[4]);
+}
+
+TEST_F(ReferenceLessVideoAnalysisTest, CheckFileExtension) {
+ EXPECT_TRUE(check_file_extension(video_file));
+ std::string txt_file =
+ webrtc::test::ResourcePath("video_quality_analysis_frame", "txt");
+ EXPECT_FALSE(check_file_extension(txt_file));
+}
+
+
+
+