mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-13 05:40:42 +01:00
Add Y4mFileReader
Encapsulate logic for reading .y4m video files in a single class. We currently have spread out logic for opening .y4m files with partial parsing. This CL consolidates this logic into a single class with a well defined interface. Change-Id: Id61673b3c95a0053b30e95b4cf382e1c6b05fc30 Bug: webrtc:9642 Reviewed-on: https://webrtc-review.googlesource.com/94772 Reviewed-by: Patrik Höglund <phoglund@webrtc.org> Reviewed-by: Paulina Hensman <phensman@webrtc.org> Commit-Queue: Magnus Jedvert <magjed@webrtc.org> Cr-Commit-Position: refs/heads/master@{#24398}
This commit is contained in:
parent
63af828a1b
commit
404be7f302
13 changed files with 543 additions and 382 deletions
|
@ -1 +0,0 @@
|
||||||
4d1ac894f1743af8059e8d8ae2465f6eaa1790b0
|
|
|
@ -17,6 +17,7 @@ group("rtc_tools") {
|
||||||
":command_line_parser",
|
":command_line_parser",
|
||||||
":frame_analyzer",
|
":frame_analyzer",
|
||||||
":video_quality_analysis",
|
":video_quality_analysis",
|
||||||
|
":y4m_file_reader",
|
||||||
]
|
]
|
||||||
if (!build_with_chromium) {
|
if (!build_with_chromium) {
|
||||||
deps += [
|
deps += [
|
||||||
|
@ -59,12 +60,28 @@ rtc_static_library("command_line_parser") {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rtc_static_library("y4m_file_reader") {
|
||||||
|
sources = [
|
||||||
|
"y4m_file_reader.cc",
|
||||||
|
"y4m_file_reader.h",
|
||||||
|
]
|
||||||
|
deps = [
|
||||||
|
"../api/video:video_frame",
|
||||||
|
"../api/video:video_frame_i420",
|
||||||
|
"../rtc_base:rtc_base_approved",
|
||||||
|
"../rtc_base:sequenced_task_checker",
|
||||||
|
"//third_party/abseil-cpp/absl/types:optional",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
rtc_static_library("video_quality_analysis") {
|
rtc_static_library("video_quality_analysis") {
|
||||||
sources = [
|
sources = [
|
||||||
"frame_analyzer/video_quality_analysis.cc",
|
"frame_analyzer/video_quality_analysis.cc",
|
||||||
"frame_analyzer/video_quality_analysis.h",
|
"frame_analyzer/video_quality_analysis.h",
|
||||||
]
|
]
|
||||||
deps = [
|
deps = [
|
||||||
|
":y4m_file_reader",
|
||||||
|
"../api/video:video_frame_i420",
|
||||||
"../common_video",
|
"../common_video",
|
||||||
"../test:perf_test",
|
"../test:perf_test",
|
||||||
"//third_party/libyuv",
|
"//third_party/libyuv",
|
||||||
|
@ -80,6 +97,7 @@ rtc_executable("frame_analyzer") {
|
||||||
deps = [
|
deps = [
|
||||||
":command_line_parser",
|
":command_line_parser",
|
||||||
":video_quality_analysis",
|
":video_quality_analysis",
|
||||||
|
":y4m_file_reader",
|
||||||
"../test:perf_test",
|
"../test:perf_test",
|
||||||
"//build/win:default_exe_manifest",
|
"//build/win:default_exe_manifest",
|
||||||
]
|
]
|
||||||
|
@ -96,6 +114,7 @@ if (!build_with_chromium) {
|
||||||
deps = [
|
deps = [
|
||||||
":command_line_parser",
|
":command_line_parser",
|
||||||
":video_quality_analysis",
|
":video_quality_analysis",
|
||||||
|
":y4m_file_reader",
|
||||||
"//build/win:default_exe_manifest",
|
"//build/win:default_exe_manifest",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -108,6 +127,7 @@ if (!build_with_chromium) {
|
||||||
|
|
||||||
deps = [
|
deps = [
|
||||||
":video_quality_analysis",
|
":video_quality_analysis",
|
||||||
|
":y4m_file_reader",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -288,7 +308,6 @@ if (rtc_include_tests) {
|
||||||
tools_unittests_resources = [
|
tools_unittests_resources = [
|
||||||
"../resources/foreman_cif.yuv",
|
"../resources/foreman_cif.yuv",
|
||||||
"../resources/reference_less_video_test_file.y4m",
|
"../resources/reference_less_video_test_file.y4m",
|
||||||
"../resources/video_quality_analysis_frame.txt",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
if (is_ios) {
|
if (is_ios) {
|
||||||
|
@ -310,6 +329,7 @@ if (rtc_include_tests) {
|
||||||
"frame_editing/frame_editing_unittest.cc",
|
"frame_editing/frame_editing_unittest.cc",
|
||||||
"sanitizers_unittest.cc",
|
"sanitizers_unittest.cc",
|
||||||
"simple_command_line_parser_unittest.cc",
|
"simple_command_line_parser_unittest.cc",
|
||||||
|
"y4m_file_reader_unittest.cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
if (!build_with_chromium && is_clang) {
|
if (!build_with_chromium && is_clang) {
|
||||||
|
@ -322,6 +342,7 @@ if (rtc_include_tests) {
|
||||||
":frame_editing_lib",
|
":frame_editing_lib",
|
||||||
":reference_less_video_analysis_lib",
|
":reference_less_video_analysis_lib",
|
||||||
":video_quality_analysis",
|
":video_quality_analysis",
|
||||||
|
":y4m_file_reader",
|
||||||
"../common_video:common_video",
|
"../common_video:common_video",
|
||||||
"../rtc_base",
|
"../rtc_base",
|
||||||
"../rtc_base:checks",
|
"../rtc_base:checks",
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
#include "rtc_tools/frame_analyzer/video_quality_analysis.h"
|
#include "rtc_tools/frame_analyzer/video_quality_analysis.h"
|
||||||
#include "rtc_tools/simple_command_line_parser.h"
|
#include "rtc_tools/simple_command_line_parser.h"
|
||||||
|
#include "rtc_tools/y4m_file_reader.h"
|
||||||
#include "test/testsupport/perf_test.h"
|
#include "test/testsupport/perf_test.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -101,11 +102,19 @@ int main(int argc, char* argv[]) {
|
||||||
|
|
||||||
webrtc::test::ResultsContainer results;
|
webrtc::test::ResultsContainer results;
|
||||||
|
|
||||||
webrtc::test::RunAnalysis(parser.GetFlag("reference_file").c_str(),
|
rtc::scoped_refptr<webrtc::test::Y4mFile> reference_video =
|
||||||
parser.GetFlag("test_file").c_str(),
|
webrtc::test::Y4mFile::Open(parser.GetFlag("reference_file"));
|
||||||
parser.GetFlag("stats_file_ref").c_str(),
|
rtc::scoped_refptr<webrtc::test::Y4mFile> test_video =
|
||||||
parser.GetFlag("stats_file_test").c_str(), width,
|
webrtc::test::Y4mFile::Open(parser.GetFlag("test_file"));
|
||||||
height, &results);
|
|
||||||
|
if (!reference_video || !test_video) {
|
||||||
|
fprintf(stderr, "Error opening video files\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
webrtc::test::RunAnalysis(
|
||||||
|
reference_video, test_video, parser.GetFlag("stats_file_ref").c_str(),
|
||||||
|
parser.GetFlag("stats_file_test").c_str(), width, height, &results);
|
||||||
webrtc::test::GetMaxRepeatedAndSkippedFrames(
|
webrtc::test::GetMaxRepeatedAndSkippedFrames(
|
||||||
parser.GetFlag("stats_file_ref"), parser.GetFlag("stats_file_test"),
|
parser.GetFlag("stats_file_ref"), parser.GetFlag("stats_file_test"),
|
||||||
&results);
|
&results);
|
||||||
|
|
|
@ -25,36 +25,6 @@
|
||||||
#define strtok_r strtok_s
|
#define strtok_r strtok_s
|
||||||
#endif
|
#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,
|
bool frozen_frame(std::vector<double> psnr_per_frame,
|
||||||
std::vector<double> ssim_per_frame,
|
std::vector<double> ssim_per_frame,
|
||||||
size_t frame) {
|
size_t frame) {
|
||||||
|
@ -135,60 +105,29 @@ void print_freezing_metrics(const std::vector<double>& psnr_per_frame,
|
||||||
printf("\n");
|
printf("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
void compute_metrics(const std::string& video_file_name,
|
void compute_metrics(const rtc::scoped_refptr<webrtc::test::Y4mFile>& video,
|
||||||
std::vector<double>* psnr_per_frame,
|
std::vector<double>* psnr_per_frame,
|
||||||
std::vector<double>* ssim_per_frame) {
|
std::vector<double>* ssim_per_frame) {
|
||||||
int height = 0, width = 0, fps = 0;
|
for (size_t i = 0; i < video->number_of_frames() - 1; ++i) {
|
||||||
get_height_width_fps(&height, &width, &fps, video_file_name);
|
const rtc::scoped_refptr<webrtc::I420BufferInterface> current_frame =
|
||||||
|
video->GetFrame(i);
|
||||||
int no_of_frames = 0;
|
const rtc::scoped_refptr<webrtc::I420BufferInterface> next_frame =
|
||||||
int size = webrtc::test::GetI420FrameSize(width, height);
|
video->GetFrame(i + 1);
|
||||||
|
double result_psnr = webrtc::test::Psnr(current_frame, next_frame);
|
||||||
// Allocate buffers for test and reference frames.
|
double result_ssim = webrtc::test::Ssim(current_frame, next_frame);
|
||||||
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);
|
psnr_per_frame->push_back(result_psnr);
|
||||||
ssim_per_frame->push_back(result_ssim);
|
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) {
|
int run_analysis(const std::string& video_file) {
|
||||||
std::vector<double> psnr_per_frame;
|
std::vector<double> psnr_per_frame;
|
||||||
std::vector<double> ssim_per_frame;
|
std::vector<double> ssim_per_frame;
|
||||||
if (check_file_extension(video_file)) {
|
rtc::scoped_refptr<webrtc::test::Y4mFile> video =
|
||||||
compute_metrics(video_file, &psnr_per_frame, &ssim_per_frame);
|
webrtc::test::Y4mFile::Open(video_file);
|
||||||
|
if (video) {
|
||||||
|
compute_metrics(video, &psnr_per_frame, &ssim_per_frame);
|
||||||
} else {
|
} else {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,12 +14,7 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
// Parse the file header to extract height, width and fps
|
#include "rtc_tools/y4m_file_reader.h"
|
||||||
// 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
|
// Returns true if the frame is frozen based on psnr and ssim freezing
|
||||||
// threshold values.
|
// threshold values.
|
||||||
|
@ -39,13 +34,10 @@ void print_freezing_metrics(const std::vector<double>& psnr_per_frame,
|
||||||
|
|
||||||
// Compute the metrics like freezing score based on PSNR and SSIM values for a
|
// Compute the metrics like freezing score based on PSNR and SSIM values for a
|
||||||
// given video file.
|
// given video file.
|
||||||
void compute_metrics(const std::string& video_file_name,
|
void compute_metrics(const rtc::scoped_refptr<webrtc::test::Y4mFile>& video,
|
||||||
std::vector<double>* psnr_per_frame,
|
std::vector<double>* psnr_per_frame,
|
||||||
std::vector<double>* ssim_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
|
// Compute freezing score metrics and prints the metrics
|
||||||
// for a list of video files.
|
// for a list of video files.
|
||||||
int run_analysis(const std::string& video_file);
|
int run_analysis(const std::string& video_file);
|
||||||
|
|
|
@ -20,16 +20,18 @@
|
||||||
class ReferenceLessVideoAnalysisTest : public ::testing::Test {
|
class ReferenceLessVideoAnalysisTest : public ::testing::Test {
|
||||||
public:
|
public:
|
||||||
void SetUp() override {
|
void SetUp() override {
|
||||||
video_file =
|
video = webrtc::test::Y4mFile::Open(
|
||||||
webrtc::test::ResourcePath("reference_less_video_test_file", "y4m");
|
webrtc::test::ResourcePath("reference_less_video_test_file", "y4m"));
|
||||||
|
ASSERT_TRUE(video);
|
||||||
}
|
}
|
||||||
std::string video_file;
|
|
||||||
|
rtc::scoped_refptr<webrtc::test::Y4mFile> video;
|
||||||
std::vector<double> psnr_per_frame;
|
std::vector<double> psnr_per_frame;
|
||||||
std::vector<double> ssim_per_frame;
|
std::vector<double> ssim_per_frame;
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST_F(ReferenceLessVideoAnalysisTest, MatchComputedMetrics) {
|
TEST_F(ReferenceLessVideoAnalysisTest, MatchComputedMetrics) {
|
||||||
compute_metrics(video_file, &psnr_per_frame, &ssim_per_frame);
|
compute_metrics(video, &psnr_per_frame, &ssim_per_frame);
|
||||||
EXPECT_EQ(74, (int)psnr_per_frame.size());
|
EXPECT_EQ(74, (int)psnr_per_frame.size());
|
||||||
|
|
||||||
ASSERT_NEAR(27.2f, psnr_per_frame[1], 0.1f);
|
ASSERT_NEAR(27.2f, psnr_per_frame[1], 0.1f);
|
||||||
|
@ -39,26 +41,11 @@ TEST_F(ReferenceLessVideoAnalysisTest, MatchComputedMetrics) {
|
||||||
ASSERT_NEAR(0.9f, ssim_per_frame[5], 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) {
|
TEST_F(ReferenceLessVideoAnalysisTest, MatchIdenticalFrameClusters) {
|
||||||
compute_metrics(video_file, &psnr_per_frame, &ssim_per_frame);
|
compute_metrics(video, &psnr_per_frame, &ssim_per_frame);
|
||||||
std::vector<int> identical_frame_clusters =
|
std::vector<int> identical_frame_clusters =
|
||||||
find_frame_clusters(psnr_per_frame, ssim_per_frame);
|
find_frame_clusters(psnr_per_frame, ssim_per_frame);
|
||||||
EXPECT_EQ(5, (int)identical_frame_clusters.size());
|
EXPECT_EQ(5, (int)identical_frame_clusters.size());
|
||||||
EXPECT_EQ(1, identical_frame_clusters[0]);
|
EXPECT_EQ(1, identical_frame_clusters[0]);
|
||||||
EXPECT_EQ(1, identical_frame_clusters[4]);
|
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));
|
|
||||||
}
|
|
||||||
|
|
|
@ -19,11 +19,10 @@
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "test/testsupport/perf_test.h"
|
#include "test/testsupport/perf_test.h"
|
||||||
|
#include "third_party/libyuv/include/libyuv/compare.h"
|
||||||
|
#include "third_party/libyuv/include/libyuv/convert.h"
|
||||||
|
|
||||||
#define STATS_LINE_LENGTH 32
|
#define STATS_LINE_LENGTH 32
|
||||||
#define Y4M_FILE_HEADER_MAX_SIZE 200
|
|
||||||
#define Y4M_FRAME_DELIMITER "FRAME"
|
|
||||||
#define Y4M_FRAME_HEADER_SIZE 6
|
|
||||||
|
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
namespace test {
|
namespace test {
|
||||||
|
@ -92,160 +91,47 @@ bool GetNextStatsLine(FILE* stats_file, char* line) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ExtractFrameFromYuvFile(const char* i420_file_name,
|
template <typename FrameMetricFunction>
|
||||||
int width,
|
static double CalculateMetric(
|
||||||
int height,
|
const FrameMetricFunction& frame_metric_function,
|
||||||
int frame_number,
|
const rtc::scoped_refptr<I420BufferInterface>& ref_buffer,
|
||||||
uint8_t* result_frame) {
|
const rtc::scoped_refptr<I420BufferInterface>& test_buffer) {
|
||||||
int frame_size = GetI420FrameSize(width, height);
|
RTC_CHECK_EQ(ref_buffer->width(), test_buffer->width());
|
||||||
int offset = frame_number * frame_size; // Calculate offset for the frame.
|
RTC_CHECK_EQ(ref_buffer->height(), test_buffer->height());
|
||||||
bool errors = false;
|
return frame_metric_function(
|
||||||
|
ref_buffer->DataY(), ref_buffer->StrideY(), ref_buffer->DataU(),
|
||||||
FILE* input_file = fopen(i420_file_name, "rb");
|
ref_buffer->StrideU(), ref_buffer->DataV(), ref_buffer->StrideV(),
|
||||||
if (input_file == NULL) {
|
test_buffer->DataY(), test_buffer->StrideY(), test_buffer->DataU(),
|
||||||
fprintf(stderr, "Couldn't open input file for reading: %s\n",
|
test_buffer->StrideU(), test_buffer->DataV(), test_buffer->StrideV(),
|
||||||
i420_file_name);
|
test_buffer->width(), test_buffer->height());
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Change stream pointer to new offset.
|
|
||||||
fseek(input_file, offset, SEEK_SET);
|
|
||||||
|
|
||||||
size_t bytes_read = fread(result_frame, 1, frame_size, input_file);
|
|
||||||
if (bytes_read != static_cast<size_t>(frame_size) && ferror(input_file)) {
|
|
||||||
fprintf(stdout, "Error while reading frame no %d from file %s\n",
|
|
||||||
frame_number, i420_file_name);
|
|
||||||
errors = true;
|
|
||||||
}
|
|
||||||
fclose(input_file);
|
|
||||||
return !errors;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ExtractFrameFromY4mFile(const char* y4m_file_name,
|
double Psnr(const rtc::scoped_refptr<I420BufferInterface>& ref_buffer,
|
||||||
int width,
|
const rtc::scoped_refptr<I420BufferInterface>& test_buffer) {
|
||||||
int height,
|
// LibYuv sets the max psnr value to 128, we restrict it to 48.
|
||||||
int frame_number,
|
// In case of 0 mse in one frame, 128 can skew the results significantly.
|
||||||
uint8_t* result_frame) {
|
return std::min(48.0,
|
||||||
int frame_size = GetI420FrameSize(width, height);
|
CalculateMetric(&libyuv::I420Psnr, ref_buffer, test_buffer));
|
||||||
int inital_offset = frame_number * (frame_size + Y4M_FRAME_HEADER_SIZE);
|
|
||||||
int frame_offset = 0;
|
|
||||||
|
|
||||||
FILE* input_file = fopen(y4m_file_name, "rb");
|
|
||||||
if (input_file == NULL) {
|
|
||||||
fprintf(stderr, "Couldn't open input file for reading: %s\n",
|
|
||||||
y4m_file_name);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// YUV4MPEG2, a.k.a. Y4M File format has a file header and a frame header. The
|
|
||||||
// file header has the aspect: "YUV4MPEG2 C420 W640 H360 Ip F30:1 A1:1".
|
|
||||||
char frame_header[Y4M_FILE_HEADER_MAX_SIZE];
|
|
||||||
size_t bytes_read =
|
|
||||||
fread(frame_header, 1, Y4M_FILE_HEADER_MAX_SIZE - 1, input_file);
|
|
||||||
if (bytes_read != static_cast<size_t>(frame_size) && ferror(input_file)) {
|
|
||||||
fprintf(stdout, "Error while reading frame from file %s\n", y4m_file_name);
|
|
||||||
fclose(input_file);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
frame_header[bytes_read] = '\0';
|
|
||||||
std::string header_contents(frame_header);
|
|
||||||
std::size_t found = header_contents.find(Y4M_FRAME_DELIMITER);
|
|
||||||
if (found == std::string::npos) {
|
|
||||||
fprintf(stdout, "Corrupted Y4M header, could not find \"FRAME\" in %s\n",
|
|
||||||
header_contents.c_str());
|
|
||||||
fclose(input_file);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
frame_offset = static_cast<int>(found);
|
|
||||||
|
|
||||||
// Change stream pointer to new offset, skipping the frame header as well.
|
|
||||||
fseek(input_file, inital_offset + frame_offset + Y4M_FRAME_HEADER_SIZE,
|
|
||||||
SEEK_SET);
|
|
||||||
|
|
||||||
bytes_read = fread(result_frame, 1, frame_size, input_file);
|
|
||||||
if (feof(input_file)) {
|
|
||||||
fclose(input_file);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (bytes_read != static_cast<size_t>(frame_size) && ferror(input_file)) {
|
|
||||||
fprintf(stdout, "Error while reading frame no %d from file %s\n",
|
|
||||||
frame_number, y4m_file_name);
|
|
||||||
fclose(input_file);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
fclose(input_file);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
double CalculateMetrics(VideoAnalysisMetricsType video_metrics_type,
|
double Ssim(const rtc::scoped_refptr<I420BufferInterface>& ref_buffer,
|
||||||
const uint8_t* ref_frame,
|
const rtc::scoped_refptr<I420BufferInterface>& test_buffer) {
|
||||||
const uint8_t* test_frame,
|
return CalculateMetric(&libyuv::I420Ssim, ref_buffer, test_buffer);
|
||||||
int width,
|
|
||||||
int height) {
|
|
||||||
if (!ref_frame || !test_frame)
|
|
||||||
return -1;
|
|
||||||
else if (height < 0 || width < 0)
|
|
||||||
return -1;
|
|
||||||
int half_width = (width + 1) >> 1;
|
|
||||||
int half_height = (height + 1) >> 1;
|
|
||||||
const uint8_t* src_y_a = ref_frame;
|
|
||||||
const uint8_t* src_u_a = src_y_a + width * height;
|
|
||||||
const uint8_t* src_v_a = src_u_a + half_width * half_height;
|
|
||||||
const uint8_t* src_y_b = test_frame;
|
|
||||||
const uint8_t* src_u_b = src_y_b + width * height;
|
|
||||||
const uint8_t* src_v_b = src_u_b + half_width * half_height;
|
|
||||||
|
|
||||||
int stride_y = width;
|
|
||||||
int stride_uv = half_width;
|
|
||||||
|
|
||||||
double result = 0.0;
|
|
||||||
|
|
||||||
switch (video_metrics_type) {
|
|
||||||
case kPSNR:
|
|
||||||
// In the following: stride is determined by width.
|
|
||||||
result = libyuv::I420Psnr(src_y_a, width, src_u_a, half_width, src_v_a,
|
|
||||||
half_width, src_y_b, width, src_u_b, half_width,
|
|
||||||
src_v_b, half_width, width, height);
|
|
||||||
// LibYuv sets the max psnr value to 128, we restrict it to 48.
|
|
||||||
// In case of 0 mse in one frame, 128 can skew the results significantly.
|
|
||||||
result = (result > 48.0) ? 48.0 : result;
|
|
||||||
break;
|
|
||||||
case kSSIM:
|
|
||||||
result = libyuv::I420Ssim(src_y_a, stride_y, src_u_a, stride_uv, src_v_a,
|
|
||||||
stride_uv, src_y_b, stride_y, src_u_b,
|
|
||||||
stride_uv, src_v_b, stride_uv, width, height);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
assert(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RunAnalysis(const char* reference_file_name,
|
void RunAnalysis(const rtc::scoped_refptr<webrtc::test::Video>& reference_video,
|
||||||
const char* test_file_name,
|
const rtc::scoped_refptr<webrtc::test::Video>& test_video,
|
||||||
const char* stats_file_reference_name,
|
const char* stats_file_reference_name,
|
||||||
const char* stats_file_test_name,
|
const char* stats_file_test_name,
|
||||||
int width,
|
int width,
|
||||||
int height,
|
int height,
|
||||||
ResultsContainer* results) {
|
ResultsContainer* results) {
|
||||||
// Check if the reference_file_name ends with "y4m".
|
|
||||||
bool y4m_mode = false;
|
|
||||||
if (std::string(reference_file_name).find("y4m") != std::string::npos) {
|
|
||||||
y4m_mode = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int size = GetI420FrameSize(width, height);
|
|
||||||
FILE* stats_file_ref = fopen(stats_file_reference_name, "r");
|
FILE* stats_file_ref = fopen(stats_file_reference_name, "r");
|
||||||
FILE* stats_file_test = fopen(stats_file_test_name, "r");
|
FILE* stats_file_test = fopen(stats_file_test_name, "r");
|
||||||
|
|
||||||
// String buffer for the lines in the stats file.
|
// String buffer for the lines in the stats file.
|
||||||
char line[STATS_LINE_LENGTH];
|
char line[STATS_LINE_LENGTH];
|
||||||
|
|
||||||
// Allocate buffers for test and reference frames.
|
|
||||||
uint8_t* test_frame = new uint8_t[size];
|
|
||||||
uint8_t* reference_frame = new uint8_t[size];
|
|
||||||
int previous_frame_number = -1;
|
int previous_frame_number = -1;
|
||||||
|
|
||||||
// Maps barcode id to the frame id for the reference video.
|
// Maps barcode id to the frame id for the reference video.
|
||||||
|
@ -282,21 +168,14 @@ void RunAnalysis(const char* reference_file_name,
|
||||||
assert(extracted_test_frame != -1);
|
assert(extracted_test_frame != -1);
|
||||||
assert(decoded_frame_number != -1);
|
assert(decoded_frame_number != -1);
|
||||||
|
|
||||||
ExtractFrameFromYuvFile(test_file_name, width, height, extracted_test_frame,
|
const rtc::scoped_refptr<webrtc::I420BufferInterface> test_frame =
|
||||||
test_frame);
|
test_video->GetFrame(extracted_test_frame);
|
||||||
if (y4m_mode) {
|
const rtc::scoped_refptr<webrtc::I420BufferInterface> reference_frame =
|
||||||
ExtractFrameFromY4mFile(reference_file_name, width, height,
|
reference_video->GetFrame(extracted_ref_frame);
|
||||||
extracted_ref_frame, reference_frame);
|
|
||||||
} else {
|
|
||||||
ExtractFrameFromYuvFile(reference_file_name, width, height,
|
|
||||||
extracted_ref_frame, reference_frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate the PSNR and SSIM.
|
// Calculate the PSNR and SSIM.
|
||||||
double result_psnr =
|
double result_psnr = Psnr(reference_frame, test_frame);
|
||||||
CalculateMetrics(kPSNR, reference_frame, test_frame, width, height);
|
double result_ssim = Ssim(reference_frame, test_frame);
|
||||||
double result_ssim =
|
|
||||||
CalculateMetrics(kSSIM, reference_frame, test_frame, width, height);
|
|
||||||
|
|
||||||
previous_frame_number = decoded_frame_number;
|
previous_frame_number = decoded_frame_number;
|
||||||
|
|
||||||
|
@ -312,8 +191,6 @@ void RunAnalysis(const char* reference_file_name,
|
||||||
// Cleanup.
|
// Cleanup.
|
||||||
fclose(stats_file_ref);
|
fclose(stats_file_ref);
|
||||||
fclose(stats_file_test);
|
fclose(stats_file_test);
|
||||||
delete[] test_frame;
|
|
||||||
delete[] reference_frame;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::pair<int, int> > CalculateFrameClusters(
|
std::vector<std::pair<int, int> > CalculateFrameClusters(
|
||||||
|
|
|
@ -15,8 +15,8 @@
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "third_party/libyuv/include/libyuv/compare.h"
|
#include "api/video/i420_buffer.h"
|
||||||
#include "third_party/libyuv/include/libyuv/convert.h"
|
#include "rtc_tools/y4m_file_reader.h"
|
||||||
|
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
namespace test {
|
namespace test {
|
||||||
|
@ -44,8 +44,6 @@ struct ResultsContainer {
|
||||||
int decode_errors_test;
|
int decode_errors_test;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum VideoAnalysisMetricsType { kPSNR, kSSIM };
|
|
||||||
|
|
||||||
// A function to run the PSNR and SSIM analysis on the test file. The test file
|
// A function to run the PSNR and SSIM analysis on the test file. The test file
|
||||||
// comprises the frames that were captured during the quality measurement test.
|
// comprises the frames that were captured during the quality measurement test.
|
||||||
// There may be missing or duplicate frames. Also the frames start at a random
|
// There may be missing or duplicate frames. Also the frames start at a random
|
||||||
|
@ -61,23 +59,23 @@ enum VideoAnalysisMetricsType { kPSNR, kSSIM };
|
||||||
// problem with the decoding there would be 'Barcode error' instead of yyyy.
|
// problem with the decoding there would be 'Barcode error' instead of yyyy.
|
||||||
// The stat files are used to compare the right frames with each other and
|
// The stat files are used to compare the right frames with each other and
|
||||||
// to calculate statistics.
|
// to calculate statistics.
|
||||||
void RunAnalysis(const char* reference_file_name,
|
void RunAnalysis(const rtc::scoped_refptr<webrtc::test::Video>& reference_video,
|
||||||
const char* test_file_name,
|
const rtc::scoped_refptr<webrtc::test::Video>& test_video,
|
||||||
const char* stats_file_reference_name,
|
const char* stats_file_reference_name,
|
||||||
const char* stats_file_test_name,
|
const char* stats_file_test_name,
|
||||||
int width,
|
int width,
|
||||||
int height,
|
int height,
|
||||||
ResultsContainer* results);
|
ResultsContainer* results);
|
||||||
|
|
||||||
// Compute PSNR or SSIM for an I420 frame (all planes). When we are calculating
|
// Compute PSNR for an I420 buffer (all planes). The max return value (in the
|
||||||
// PSNR values, the max return value (in the case where the test and reference
|
// case where the test and reference frames are exactly the same) will be 48.
|
||||||
// frames are exactly the same) will be 48. In the case of SSIM the max return
|
double Psnr(const rtc::scoped_refptr<I420BufferInterface>& ref_buffer,
|
||||||
// value will be 1.
|
const rtc::scoped_refptr<I420BufferInterface>& test_buffer);
|
||||||
double CalculateMetrics(VideoAnalysisMetricsType video_metrics_type,
|
|
||||||
const uint8_t* ref_frame,
|
// Compute SSIM for an I420 buffer (all planes). The max return value (in the
|
||||||
const uint8_t* test_frame,
|
// case where the test and reference frames are exactly the same) will be 1.
|
||||||
int width,
|
double Ssim(const rtc::scoped_refptr<I420BufferInterface>& ref_buffer,
|
||||||
int height);
|
const rtc::scoped_refptr<I420BufferInterface>& test_buffer);
|
||||||
|
|
||||||
// Prints the result from the analysis in Chromium performance
|
// Prints the result from the analysis in Chromium performance
|
||||||
// numbers compatible format to stdout. If the results object contains no frames
|
// numbers compatible format to stdout. If the results object contains no frames
|
||||||
|
@ -129,21 +127,6 @@ bool IsThereBarcodeError(std::string line);
|
||||||
// frame_0023 0284, we will get 284.
|
// frame_0023 0284, we will get 284.
|
||||||
int ExtractDecodedFrameNumber(std::string line);
|
int ExtractDecodedFrameNumber(std::string line);
|
||||||
|
|
||||||
// Extracts an I420 frame at position frame_number from the raw YUV file.
|
|
||||||
bool ExtractFrameFromYuvFile(const char* i420_file_name,
|
|
||||||
int width,
|
|
||||||
int height,
|
|
||||||
int frame_number,
|
|
||||||
uint8_t* result_frame);
|
|
||||||
|
|
||||||
// Extracts an I420 frame at position frame_number from the Y4M file. The first
|
|
||||||
// frame has corresponded |frame_number| 0.
|
|
||||||
bool ExtractFrameFromY4mFile(const char* i420_file_name,
|
|
||||||
int width,
|
|
||||||
int height,
|
|
||||||
int frame_number,
|
|
||||||
uint8_t* result_frame);
|
|
||||||
|
|
||||||
} // namespace test
|
} // namespace test
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
|
||||||
|
|
|
@ -41,31 +41,6 @@ class VideoQualityAnalysisTest : public ::testing::Test {
|
||||||
std::string stats_filename_;
|
std::string stats_filename_;
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST_F(VideoQualityAnalysisTest, MatchExtractedY4mFrame) {
|
|
||||||
std::string video_file =
|
|
||||||
webrtc::test::ResourcePath("reference_less_video_test_file", "y4m");
|
|
||||||
|
|
||||||
std::string extracted_frame_from_video_file =
|
|
||||||
webrtc::test::ResourcePath("video_quality_analysis_frame", "txt");
|
|
||||||
|
|
||||||
int frame_height = 720, frame_width = 1280;
|
|
||||||
int frame_number = 2;
|
|
||||||
int size = GetI420FrameSize(frame_width, frame_height);
|
|
||||||
uint8_t* result_frame = new uint8_t[size];
|
|
||||||
uint8_t* expected_frame = new uint8_t[size];
|
|
||||||
|
|
||||||
FILE* input_file = fopen(extracted_frame_from_video_file.c_str(), "rb");
|
|
||||||
fread(expected_frame, 1, size, input_file);
|
|
||||||
|
|
||||||
ExtractFrameFromY4mFile(video_file.c_str(), frame_width, frame_height,
|
|
||||||
frame_number, result_frame);
|
|
||||||
|
|
||||||
EXPECT_EQ(*expected_frame, *result_frame);
|
|
||||||
fclose(input_file);
|
|
||||||
delete[] result_frame;
|
|
||||||
delete[] expected_frame;
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(VideoQualityAnalysisTest, PrintAnalysisResultsEmpty) {
|
TEST_F(VideoQualityAnalysisTest, PrintAnalysisResultsEmpty) {
|
||||||
ResultsContainer result;
|
ResultsContainer result;
|
||||||
PrintAnalysisResults(logfile_, "Empty", &result);
|
PrintAnalysisResults(logfile_, "Empty", &result);
|
||||||
|
|
|
@ -8,70 +8,42 @@
|
||||||
* be found in the AUTHORS file in the root of the source tree.
|
* be found in the AUTHORS file in the root of the source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <limits.h>
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
#include <map>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "rtc_tools/frame_analyzer/video_quality_analysis.h"
|
#include "rtc_tools/frame_analyzer/video_quality_analysis.h"
|
||||||
#include "rtc_tools/simple_command_line_parser.h"
|
#include "rtc_tools/simple_command_line_parser.h"
|
||||||
|
#include "rtc_tools/y4m_file_reader.h"
|
||||||
|
|
||||||
#define MAX_NUM_FRAMES_PER_FILE INT_MAX
|
void CompareFiles(
|
||||||
|
const rtc::scoped_refptr<webrtc::test::Video>& reference_video,
|
||||||
void CompareFiles(const char* reference_file_name,
|
const rtc::scoped_refptr<webrtc::test::Video>& test_video,
|
||||||
const char* test_file_name,
|
const char* results_file_name) {
|
||||||
const char* results_file_name,
|
|
||||||
int width,
|
|
||||||
int height) {
|
|
||||||
// Check if the reference_file_name ends with "y4m".
|
|
||||||
bool y4m_mode = false;
|
|
||||||
if (std::string(reference_file_name).find("y4m") != std::string::npos) {
|
|
||||||
y4m_mode = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
FILE* results_file = fopen(results_file_name, "w");
|
FILE* results_file = fopen(results_file_name, "w");
|
||||||
|
|
||||||
int size = webrtc::test::GetI420FrameSize(width, height);
|
const size_t num_frames = std::min(reference_video->number_of_frames(),
|
||||||
|
test_video->number_of_frames());
|
||||||
// Allocate buffers for test and reference frames.
|
for (size_t i = 0; i < num_frames; ++i) {
|
||||||
uint8_t* test_frame = new uint8_t[size];
|
const rtc::scoped_refptr<webrtc::I420BufferInterface> ref_buffer =
|
||||||
uint8_t* ref_frame = new uint8_t[size];
|
reference_video->GetFrame(i);
|
||||||
|
const rtc::scoped_refptr<webrtc::I420BufferInterface> test_buffer =
|
||||||
bool read_result = true;
|
test_video->GetFrame(i);
|
||||||
for (int frame_counter = 0; frame_counter < MAX_NUM_FRAMES_PER_FILE;
|
|
||||||
++frame_counter) {
|
|
||||||
read_result &=
|
|
||||||
(y4m_mode)
|
|
||||||
? webrtc::test::ExtractFrameFromY4mFile(
|
|
||||||
reference_file_name, width, height, frame_counter, ref_frame)
|
|
||||||
: webrtc::test::ExtractFrameFromYuvFile(
|
|
||||||
reference_file_name, width, height, frame_counter, ref_frame);
|
|
||||||
read_result &= webrtc::test::ExtractFrameFromYuvFile(
|
|
||||||
test_file_name, width, height, frame_counter, test_frame);
|
|
||||||
|
|
||||||
if (!read_result)
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Calculate the PSNR and SSIM.
|
// Calculate the PSNR and SSIM.
|
||||||
double result_psnr = webrtc::test::CalculateMetrics(
|
double result_psnr = webrtc::test::Psnr(ref_buffer, test_buffer);
|
||||||
webrtc::test::kPSNR, ref_frame, test_frame, width, height);
|
double result_ssim = webrtc::test::Ssim(ref_buffer, test_buffer);
|
||||||
double result_ssim = webrtc::test::CalculateMetrics(
|
fprintf(results_file, "Frame: %zu, PSNR: %f, SSIM: %f\n", i, result_psnr,
|
||||||
webrtc::test::kSSIM, ref_frame, test_frame, width, height);
|
result_ssim);
|
||||||
fprintf(results_file, "Frame: %d, PSNR: %f, SSIM: %f\n", frame_counter,
|
|
||||||
result_psnr, result_ssim);
|
|
||||||
}
|
}
|
||||||
delete[] test_frame;
|
|
||||||
delete[] ref_frame;
|
|
||||||
|
|
||||||
fclose(results_file);
|
fclose(results_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A tool running PSNR and SSIM analysis on two videos - a reference video and a
|
* A tool running PSNR and SSIM analysis on two videos - a reference video and a
|
||||||
* test video. The two videos should be I420 YUV videos.
|
* test video. The two videos should be I420 Y4M videos.
|
||||||
* The tool just runs PSNR and SSIM on the corresponding frames in the test and
|
* The tool just runs PSNR and SSIM on the corresponding frames in the test and
|
||||||
* the reference videos until either the first or the second video runs out of
|
* the reference videos until either the first or the second video runs out of
|
||||||
* frames. The result is written in a results text file in the format:
|
* frames. The result is written in a results text file in the format:
|
||||||
|
@ -82,8 +54,7 @@ void CompareFiles(const char* reference_file_name,
|
||||||
*
|
*
|
||||||
* Usage:
|
* Usage:
|
||||||
* psnr_ssim_analyzer --reference_file=<name_of_file> --test_file=<name_of_file>
|
* psnr_ssim_analyzer --reference_file=<name_of_file> --test_file=<name_of_file>
|
||||||
* --results_file=<name_of_file> --width=<width_of_frames>
|
* --results_file=<name_of_file>
|
||||||
* --height=<height_of_frames>
|
|
||||||
*/
|
*/
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
std::string program_name = argv[0];
|
std::string program_name = argv[0];
|
||||||
|
@ -93,12 +64,8 @@ int main(int argc, char* argv[]) {
|
||||||
"Example usage:\n" +
|
"Example usage:\n" +
|
||||||
program_name +
|
program_name +
|
||||||
" --reference_file=ref.yuv "
|
" --reference_file=ref.yuv "
|
||||||
"--test_file=test.yuv --results_file=results.txt --width=320 "
|
"--test_file=test.yuv --results_file=results.txt\n"
|
||||||
"--height=240\n"
|
|
||||||
"Command line flags:\n"
|
"Command line flags:\n"
|
||||||
" - width(int): The width of the reference and test files. Default: -1\n"
|
|
||||||
" - height(int): The height of the reference and test files. "
|
|
||||||
" Default: -1\n"
|
|
||||||
" - reference_file(string): The reference YUV file to compare against."
|
" - reference_file(string): The reference YUV file to compare against."
|
||||||
" Default: ref.yuv\n"
|
" Default: ref.yuv\n"
|
||||||
" - test_file(string): The test YUV file to run the analysis for."
|
" - test_file(string): The test YUV file to run the analysis for."
|
||||||
|
@ -112,8 +79,6 @@ int main(int argc, char* argv[]) {
|
||||||
parser.Init(argc, argv);
|
parser.Init(argc, argv);
|
||||||
parser.SetUsageMessage(usage);
|
parser.SetUsageMessage(usage);
|
||||||
|
|
||||||
parser.SetFlag("width", "-1");
|
|
||||||
parser.SetFlag("height", "-1");
|
|
||||||
parser.SetFlag("results_file", "results.txt");
|
parser.SetFlag("results_file", "results.txt");
|
||||||
parser.SetFlag("reference_file", "ref.yuv");
|
parser.SetFlag("reference_file", "ref.yuv");
|
||||||
parser.SetFlag("test_file", "test.yuv");
|
parser.SetFlag("test_file", "test.yuv");
|
||||||
|
@ -127,16 +92,26 @@ int main(int argc, char* argv[]) {
|
||||||
}
|
}
|
||||||
parser.PrintEnteredFlags();
|
parser.PrintEnteredFlags();
|
||||||
|
|
||||||
int width = strtol((parser.GetFlag("width")).c_str(), NULL, 10);
|
rtc::scoped_refptr<webrtc::test::Y4mFile> reference_video =
|
||||||
int height = strtol((parser.GetFlag("height")).c_str(), NULL, 10);
|
webrtc::test::Y4mFile::Open(parser.GetFlag("reference_file"));
|
||||||
|
rtc::scoped_refptr<webrtc::test::Y4mFile> test_video =
|
||||||
|
webrtc::test::Y4mFile::Open(parser.GetFlag("test_file"));
|
||||||
|
|
||||||
if (width <= 0 || height <= 0) {
|
if (!reference_video || !test_video) {
|
||||||
fprintf(stderr, "Error: width or height cannot be <= 0!\n");
|
fprintf(stderr, "Error opening video files\n");
|
||||||
return -1;
|
return 0;
|
||||||
|
}
|
||||||
|
if (reference_video->width() != test_video->width() ||
|
||||||
|
reference_video->height() != test_video->height()) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"Reference and test video files do not have same size: %dx%d "
|
||||||
|
"versus %dx%d\n",
|
||||||
|
reference_video->width(), reference_video->height(),
|
||||||
|
test_video->width(), test_video->height());
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
CompareFiles(parser.GetFlag("reference_file").c_str(),
|
CompareFiles(reference_video, test_video,
|
||||||
parser.GetFlag("test_file").c_str(),
|
parser.GetFlag("results_file").c_str());
|
||||||
parser.GetFlag("results_file").c_str(), width, height);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
234
rtc_tools/y4m_file_reader.cc
Normal file
234
rtc_tools/y4m_file_reader.cc
Normal file
|
@ -0,0 +1,234 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018 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 "rtc_tools/y4m_file_reader.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "absl/types/optional.h"
|
||||||
|
#include "api/video/i420_buffer.h"
|
||||||
|
#include "rtc_base/logging.h"
|
||||||
|
#include "rtc_base/refcountedobject.h"
|
||||||
|
#include "rtc_base/sequenced_task_checker.h"
|
||||||
|
#include "rtc_base/string_to_number.h"
|
||||||
|
#include "rtc_base/stringencode.h"
|
||||||
|
#include "rtc_base/stringutils.h"
|
||||||
|
|
||||||
|
namespace webrtc {
|
||||||
|
namespace test {
|
||||||
|
|
||||||
|
Video::Iterator::Iterator(const rtc::scoped_refptr<const Video>& video,
|
||||||
|
size_t index)
|
||||||
|
: video_(video), index_(index) {}
|
||||||
|
|
||||||
|
Video::Iterator::Iterator(const Video::Iterator& other) = default;
|
||||||
|
Video::Iterator::Iterator(Video::Iterator&& other) = default;
|
||||||
|
Video::Iterator& Video::Iterator::operator=(Video::Iterator&&) = default;
|
||||||
|
Video::Iterator& Video::Iterator::operator=(const Video::Iterator&) = default;
|
||||||
|
Video::Iterator::~Iterator() = default;
|
||||||
|
|
||||||
|
rtc::scoped_refptr<I420BufferInterface> Video::Iterator::operator*() const {
|
||||||
|
return video_->GetFrame(index_);
|
||||||
|
}
|
||||||
|
bool Video::Iterator::operator==(const Video::Iterator& other) const {
|
||||||
|
return index_ == other.index_;
|
||||||
|
}
|
||||||
|
bool Video::Iterator::operator!=(const Video::Iterator& other) const {
|
||||||
|
return !(*this == other);
|
||||||
|
}
|
||||||
|
|
||||||
|
Video::Iterator Video::Iterator::operator++(int) {
|
||||||
|
const Iterator copy = *this;
|
||||||
|
++*this;
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
Video::Iterator& Video::Iterator::operator++() {
|
||||||
|
++index_;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Video::Iterator Video::begin() const {
|
||||||
|
return Iterator(this, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Video::Iterator Video::end() const {
|
||||||
|
return Iterator(this, number_of_frames());
|
||||||
|
}
|
||||||
|
|
||||||
|
rtc::scoped_refptr<Y4mFile> Y4mFile::Open(const std::string& file_name) {
|
||||||
|
FILE* file = fopen(file_name.c_str(), "rb");
|
||||||
|
if (file == nullptr) {
|
||||||
|
RTC_LOG(LS_ERROR) << "Could not open input file for reading: " << file_name;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
int parse_file_header_result = -1;
|
||||||
|
fscanf(file, "YUV4MPEG2 %n", &parse_file_header_result);
|
||||||
|
if (parse_file_header_result == -1) {
|
||||||
|
RTC_LOG(LS_ERROR) << "File " << file_name
|
||||||
|
<< " does not start with YUV4MPEG2 header";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string header_line;
|
||||||
|
while (true) {
|
||||||
|
const int c = fgetc(file);
|
||||||
|
if (c == EOF) {
|
||||||
|
RTC_LOG(LS_ERROR) << "Could not read header line";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
if (c == '\n')
|
||||||
|
break;
|
||||||
|
header_line.push_back(static_cast<char>(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::optional<int> width;
|
||||||
|
absl::optional<int> height;
|
||||||
|
absl::optional<float> fps;
|
||||||
|
|
||||||
|
std::vector<std::string> fields;
|
||||||
|
rtc::tokenize(header_line, ' ', &fields);
|
||||||
|
for (const std::string& field : fields) {
|
||||||
|
const char prefix = field.front();
|
||||||
|
const std::string suffix = field.substr(1);
|
||||||
|
switch (prefix) {
|
||||||
|
case 'W':
|
||||||
|
width = rtc::StringToNumber<int>(suffix);
|
||||||
|
break;
|
||||||
|
case 'H':
|
||||||
|
height = rtc::StringToNumber<int>(suffix);
|
||||||
|
break;
|
||||||
|
case 'C':
|
||||||
|
if (suffix != "420" && suffix != "420mpeg2") {
|
||||||
|
RTC_LOG(LS_ERROR)
|
||||||
|
<< "Does not support any other color space than I420 or "
|
||||||
|
"420mpeg2, but was: "
|
||||||
|
<< suffix;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'F': {
|
||||||
|
std::vector<std::string> fraction;
|
||||||
|
rtc::tokenize(suffix, ':', &fraction);
|
||||||
|
if (fraction.size() == 2) {
|
||||||
|
const absl::optional<int> numerator =
|
||||||
|
rtc::StringToNumber<int>(fraction[0]);
|
||||||
|
const absl::optional<int> denominator =
|
||||||
|
rtc::StringToNumber<int>(fraction[1]);
|
||||||
|
if (numerator && denominator && *denominator != 0)
|
||||||
|
fps = *numerator / static_cast<float>(*denominator);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!width || !height) {
|
||||||
|
RTC_LOG(LS_ERROR) << "Could not find width and height in file header";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
if (!fps) {
|
||||||
|
RTC_LOG(LS_ERROR) << "Could not find fps in file header";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
RTC_LOG(LS_INFO) << "Video has resolution: " << *width << "x" << *height
|
||||||
|
<< " " << *fps << " fps";
|
||||||
|
if (*width % 2 != 0 || *height % 2 != 0) {
|
||||||
|
RTC_LOG(LS_ERROR)
|
||||||
|
<< "Only supports even width/height so that chroma size is a "
|
||||||
|
"whole number.";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int i420_frame_size = 3 * *width * *height / 2;
|
||||||
|
std::vector<fpos_t> frame_positions;
|
||||||
|
while (true) {
|
||||||
|
int parse_frame_header_result = -1;
|
||||||
|
fscanf(file, "FRAME\n%n", &parse_frame_header_result);
|
||||||
|
if (parse_frame_header_result == -1) {
|
||||||
|
if (!feof(file)) {
|
||||||
|
RTC_LOG(LS_ERROR) << "Did not find FRAME header, ignoring rest of file";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
fpos_t pos;
|
||||||
|
fgetpos(file, &pos);
|
||||||
|
frame_positions.push_back(pos);
|
||||||
|
// Skip over YUV pixel data.
|
||||||
|
fseek(file, i420_frame_size, SEEK_CUR);
|
||||||
|
}
|
||||||
|
if (frame_positions.empty()) {
|
||||||
|
RTC_LOG(LS_ERROR) << "Could not find any frames in the file";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
RTC_LOG(LS_INFO) << "Video has " << frame_positions.size() << " frames";
|
||||||
|
|
||||||
|
return new rtc::RefCountedObject<Y4mFile>(*width, *height, *fps,
|
||||||
|
frame_positions, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Y4mFile::number_of_frames() const {
|
||||||
|
return frame_positions_.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
rtc::scoped_refptr<I420BufferInterface> Y4mFile::GetFrame(
|
||||||
|
size_t frame_index) const {
|
||||||
|
RTC_DCHECK_CALLED_SEQUENTIALLY(&thread_checker_);
|
||||||
|
RTC_CHECK_LT(frame_index, frame_positions_.size());
|
||||||
|
|
||||||
|
fsetpos(file_, &frame_positions_[frame_index]);
|
||||||
|
rtc::scoped_refptr<I420Buffer> buffer = I420Buffer::Create(width_, height_);
|
||||||
|
fread(reinterpret_cast<char*>(buffer->MutableDataY()), /* size= */ 1,
|
||||||
|
width_ * height_, file_);
|
||||||
|
fread(reinterpret_cast<char*>(buffer->MutableDataU()), /* size= */ 1,
|
||||||
|
buffer->ChromaWidth() * buffer->ChromaHeight(), file_);
|
||||||
|
fread(reinterpret_cast<char*>(buffer->MutableDataV()), /* size= */ 1,
|
||||||
|
buffer->ChromaWidth() * buffer->ChromaHeight(), file_);
|
||||||
|
|
||||||
|
if (ferror(file_) != 0) {
|
||||||
|
RTC_LOG(LS_ERROR) << "Could not read YUV data for frame " << frame_index;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Y4mFile::width() const {
|
||||||
|
return width_;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Y4mFile::height() const {
|
||||||
|
return height_;
|
||||||
|
}
|
||||||
|
|
||||||
|
float Y4mFile::fps() const {
|
||||||
|
return fps_;
|
||||||
|
}
|
||||||
|
|
||||||
|
Y4mFile::Y4mFile(int width,
|
||||||
|
int height,
|
||||||
|
float fps,
|
||||||
|
const std::vector<fpos_t>& frame_positions,
|
||||||
|
FILE* file)
|
||||||
|
: width_(width),
|
||||||
|
height_(height),
|
||||||
|
fps_(fps),
|
||||||
|
frame_positions_(frame_positions),
|
||||||
|
file_(file) {}
|
||||||
|
|
||||||
|
Y4mFile::~Y4mFile() {
|
||||||
|
fclose(file_);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace test
|
||||||
|
} // namespace webrtc
|
99
rtc_tools/y4m_file_reader.h
Normal file
99
rtc_tools/y4m_file_reader.h
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018 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 RTC_TOOLS_Y4M_FILE_READER_H_
|
||||||
|
#define RTC_TOOLS_Y4M_FILE_READER_H_
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <iterator>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "api/video/video_frame.h"
|
||||||
|
#include "rtc_base/refcount.h"
|
||||||
|
#include "rtc_base/sequenced_task_checker.h"
|
||||||
|
|
||||||
|
namespace webrtc {
|
||||||
|
namespace test {
|
||||||
|
|
||||||
|
// Iterable class representing a sequence of I420 buffers. This class is not
|
||||||
|
// thread safe because it is expected to be backed by a file.
|
||||||
|
class Video : public rtc::RefCountInterface {
|
||||||
|
public:
|
||||||
|
class Iterator {
|
||||||
|
public:
|
||||||
|
typedef int value_type;
|
||||||
|
typedef std::ptrdiff_t difference_type;
|
||||||
|
typedef int* pointer;
|
||||||
|
typedef int& reference;
|
||||||
|
typedef std::input_iterator_tag iterator_category;
|
||||||
|
|
||||||
|
Iterator(const rtc::scoped_refptr<const Video>& video, size_t index);
|
||||||
|
Iterator(const Iterator& other);
|
||||||
|
Iterator(Iterator&& other);
|
||||||
|
Iterator& operator=(Iterator&&);
|
||||||
|
Iterator& operator=(const Iterator&);
|
||||||
|
~Iterator();
|
||||||
|
|
||||||
|
rtc::scoped_refptr<I420BufferInterface> operator*() const;
|
||||||
|
bool operator==(const Iterator& other) const;
|
||||||
|
bool operator!=(const Iterator& other) const;
|
||||||
|
|
||||||
|
Iterator operator++(int);
|
||||||
|
Iterator& operator++();
|
||||||
|
|
||||||
|
private:
|
||||||
|
rtc::scoped_refptr<const Video> video_;
|
||||||
|
size_t index_;
|
||||||
|
};
|
||||||
|
|
||||||
|
Iterator begin() const;
|
||||||
|
Iterator end() const;
|
||||||
|
|
||||||
|
virtual size_t number_of_frames() const = 0;
|
||||||
|
virtual rtc::scoped_refptr<I420BufferInterface> GetFrame(
|
||||||
|
size_t index) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Y4mFile : public Video {
|
||||||
|
public:
|
||||||
|
// This function opens the file and reads it as an .y4m file. It returns null
|
||||||
|
// on failure. The file will be closed when the returned object is destroyed.
|
||||||
|
static rtc::scoped_refptr<Y4mFile> Open(const std::string& file_name);
|
||||||
|
|
||||||
|
size_t number_of_frames() const override;
|
||||||
|
|
||||||
|
rtc::scoped_refptr<I420BufferInterface> GetFrame(
|
||||||
|
size_t frame_index) const override;
|
||||||
|
|
||||||
|
int width() const;
|
||||||
|
int height() const;
|
||||||
|
float fps() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Y4mFile(int width,
|
||||||
|
int height,
|
||||||
|
float fps,
|
||||||
|
const std::vector<fpos_t>& frame_positions,
|
||||||
|
FILE* file);
|
||||||
|
~Y4mFile() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const int width_;
|
||||||
|
const int height_;
|
||||||
|
const float fps_;
|
||||||
|
const std::vector<fpos_t> frame_positions_;
|
||||||
|
const rtc::SequencedTaskChecker thread_checker_;
|
||||||
|
FILE* const file_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace test
|
||||||
|
} // namespace webrtc
|
||||||
|
|
||||||
|
#endif // RTC_TOOLS_Y4M_FILE_READER_H_
|
71
rtc_tools/y4m_file_reader_unittest.cc
Normal file
71
rtc_tools/y4m_file_reader_unittest.cc
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018 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 "rtc_tools/y4m_file_reader.h"
|
||||||
|
#include "test/gtest.h"
|
||||||
|
#include "test/testsupport/fileutils.h"
|
||||||
|
|
||||||
|
namespace webrtc {
|
||||||
|
namespace test {
|
||||||
|
|
||||||
|
class Y4mFileReaderTest : public ::testing::Test {
|
||||||
|
public:
|
||||||
|
void SetUp() override {
|
||||||
|
const std::string filename =
|
||||||
|
TempFilename(webrtc::test::OutputPath(), "test_video_file.y4m");
|
||||||
|
|
||||||
|
// Create simple test video of size 6x4.
|
||||||
|
FILE* file = fopen(filename.c_str(), "wb");
|
||||||
|
ASSERT_TRUE(file != nullptr);
|
||||||
|
fprintf(file, "YUV4MPEG2 W6 H4 F57:10 C420 dummyParam\n");
|
||||||
|
fprintf(file, "FRAME\n");
|
||||||
|
|
||||||
|
const int width = 6;
|
||||||
|
const int height = 4;
|
||||||
|
const int i40_size = width * height * 3 / 2;
|
||||||
|
for (int i = 0; i < i40_size; ++i)
|
||||||
|
fputc(static_cast<char>(i), file);
|
||||||
|
fprintf(file, "FRAME\n");
|
||||||
|
for (int i = 0; i < i40_size; ++i)
|
||||||
|
fputc(static_cast<char>(i + i40_size), file);
|
||||||
|
fclose(file);
|
||||||
|
|
||||||
|
// Open the newly created file.
|
||||||
|
video = webrtc::test::Y4mFile::Open(filename);
|
||||||
|
ASSERT_TRUE(video);
|
||||||
|
}
|
||||||
|
|
||||||
|
rtc::scoped_refptr<webrtc::test::Y4mFile> video;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(Y4mFileReaderTest, TestParsingFileHeader) {
|
||||||
|
EXPECT_EQ(6, video->width());
|
||||||
|
EXPECT_EQ(4, video->height());
|
||||||
|
EXPECT_EQ(5.7f, video->fps());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(Y4mFileReaderTest, TestParsingNumberOfFrames) {
|
||||||
|
EXPECT_EQ(2u, video->number_of_frames());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(Y4mFileReaderTest, TestPixelContent) {
|
||||||
|
int cnt = 0;
|
||||||
|
for (const rtc::scoped_refptr<I420BufferInterface> frame : *video) {
|
||||||
|
for (int i = 0; i < 6 * 4; ++i, ++cnt)
|
||||||
|
EXPECT_EQ(cnt, frame->DataY()[i]);
|
||||||
|
for (int i = 0; i < 3 * 2; ++i, ++cnt)
|
||||||
|
EXPECT_EQ(cnt, frame->DataU()[i]);
|
||||||
|
for (int i = 0; i < 3 * 2; ++i, ++cnt)
|
||||||
|
EXPECT_EQ(cnt, frame->DataV()[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace test
|
||||||
|
} // namespace webrtc
|
Loading…
Reference in a new issue