diff --git a/webrtc/test/BUILD.gn b/webrtc/test/BUILD.gn index 199fd63ef8..f5c0afde41 100644 --- a/webrtc/test/BUILD.gn +++ b/webrtc/test/BUILD.gn @@ -207,6 +207,7 @@ if (!build_with_chromium) { ":test_support", ":video_test_common", "..:webrtc_common", + "../api:video_frame_api", "../common_video", "../rtc_base:rtc_base_approved", "../system_wrappers", @@ -215,6 +216,11 @@ if (!build_with_chromium) { "//third_party/gflags", ] + if (!is_ios) { + deps += [ "//third_party:jpeg" ] + sources += [ "testsupport/jpeg_frame_writer.cc" ] + } + public_deps = [ ":fileutils", ] diff --git a/webrtc/test/DEPS b/webrtc/test/DEPS index 1d44a0742e..9d4e6afdf0 100644 --- a/webrtc/test/DEPS +++ b/webrtc/test/DEPS @@ -1,4 +1,6 @@ include_rules = [ + "+third_party/libjpeg", + "+third_party/libjpeg_turbo", "+webrtc/base", "+webrtc/call", "+webrtc/common_audio", diff --git a/webrtc/test/testsupport/frame_writer.h b/webrtc/test/testsupport/frame_writer.h index 76298498ac..148f23338c 100644 --- a/webrtc/test/testsupport/frame_writer.h +++ b/webrtc/test/testsupport/frame_writer.h @@ -15,6 +15,7 @@ #include +#include "webrtc/api/video/video_frame.h" #include "webrtc/typedefs.h" namespace webrtc { @@ -82,6 +83,20 @@ class Y4mFrameWriterImpl : public YuvFrameWriterImpl { const int frame_rate_; }; +// LibJpeg is not available on iOS +#if !defined(is_ios) +class JpegFrameWriter { + public: + JpegFrameWriter(const std::string &output_filename); + bool WriteFrame(const VideoFrame& input_frame, int quality); + + private: + bool frame_written_; + const std::string output_filename_; + FILE* output_file_; +}; +#endif + } // namespace test } // namespace webrtc diff --git a/webrtc/test/testsupport/jpeg_frame_writer.cc b/webrtc/test/testsupport/jpeg_frame_writer.cc new file mode 100644 index 0000000000..483a2e02c5 --- /dev/null +++ b/webrtc/test/testsupport/jpeg_frame_writer.cc @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2017 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 + + +#include "webrtc/common_types.h" +#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h" +#include "webrtc/rtc_base/checks.h" +#include "webrtc/rtc_base/logging.h" +#include "webrtc/test/testsupport/frame_writer.h" + +extern "C" { +#if defined(USE_SYSTEM_LIBJPEG) +#include +#else +// Include directory supplied by gn +#include "jpeglib.h" // NOLINT +#endif +} + +namespace webrtc { +namespace test { + +JpegFrameWriter::JpegFrameWriter(const std::string &output_filename) + : frame_written_(false), + output_filename_(output_filename), + output_file_(nullptr) {} + +bool JpegFrameWriter::WriteFrame(const VideoFrame& input_frame, int quality) { + RTC_CHECK(!frame_written_) << "Only a single frame can be saved to Jpeg."; + const int kColorPlanes = 3; // R, G and B. + size_t rgb_len = input_frame.height() * input_frame.width() * kColorPlanes; + std::unique_ptr rgb_buf(new uint8_t[rgb_len]); + + // kRGB24 actually corresponds to FourCC 24BG which is 24-bit BGR. + if (ConvertFromI420(input_frame, VideoType::kRGB24, 0, rgb_buf.get()) < 0) { + LOG(LS_ERROR) << "Could not convert input frame to RGB."; + return false; + } + output_file_ = fopen(output_filename_.c_str(), "wb"); + if (!output_file_) { + LOG(LS_ERROR) << "Couldn't open file to write jpeg frame to:" << + output_filename_; + return false; + } + + // Invoking LIBJPEG + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + JSAMPROW row_pointer[1]; + cinfo.err = jpeg_std_error(&jerr); + jpeg_create_compress(&cinfo); + + jpeg_stdio_dest(&cinfo, output_file_); + + cinfo.image_width = input_frame.width(); + cinfo.image_height = input_frame.height(); + cinfo.input_components = kColorPlanes; + cinfo.in_color_space = JCS_EXT_BGR; + jpeg_set_defaults(&cinfo); + jpeg_set_quality(&cinfo, quality, TRUE); + + jpeg_start_compress(&cinfo, TRUE); + int row_stride = input_frame.width() * kColorPlanes; + while (cinfo.next_scanline < cinfo.image_height) { + row_pointer[0] = &rgb_buf.get()[cinfo.next_scanline * row_stride]; + jpeg_write_scanlines(&cinfo, row_pointer, 1); + } + + jpeg_finish_compress(&cinfo); + jpeg_destroy_compress(&cinfo); + fclose(output_file_); + + frame_written_ = true; + return true; +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/video/BUILD.gn b/webrtc/video/BUILD.gn index 6ad7fd199d..980145b460 100644 --- a/webrtc/video/BUILD.gn +++ b/webrtc/video/BUILD.gn @@ -111,11 +111,13 @@ if (rtc_include_tests) { "../test:test_renderer", "../test:test_renderer", "../test:test_support", + "../test:test_support_test_output", "../test:video_test_common", "../test:video_test_common", "../test:video_test_support", "../voice_engine", "//testing/gtest", + "//third_party/gflags", ] if (!build_with_chromium && is_clang) { # Suppress warnings from the Chromium Clang plugin (bugs.webrtc.org/163). diff --git a/webrtc/video/video_quality_test.cc b/webrtc/video/video_quality_test.cc index 16d7356053..7cacf297ef 100644 --- a/webrtc/video/video_quality_test.cc +++ b/webrtc/video/video_quality_test.cc @@ -18,6 +18,7 @@ #include #include +#include "gflags/gflags.h" #include "webrtc/call/call.h" #include "webrtc/common_video/libyuv/include/webrtc_libyuv.h" #include "webrtc/logging/rtc_event_log/rtc_event_log.h" @@ -34,8 +35,10 @@ #include "webrtc/rtc_base/cpu_time.h" #include "webrtc/rtc_base/event.h" #include "webrtc/rtc_base/format_macros.h" +#include "webrtc/rtc_base/logging.h" #include "webrtc/rtc_base/memory_usage.h" #include "webrtc/rtc_base/optional.h" +#include "webrtc/rtc_base/pathutils.h" #include "webrtc/rtc_base/platform_file.h" #include "webrtc/rtc_base/ptr_util.h" #include "webrtc/rtc_base/timeutils.h" @@ -47,12 +50,18 @@ #include "webrtc/test/statistics.h" #include "webrtc/test/testsupport/fileutils.h" #include "webrtc/test/testsupport/frame_writer.h" +#include "webrtc/test/testsupport/test_output.h" #include "webrtc/test/vcm_capturer.h" #include "webrtc/test/video_renderer.h" #include "webrtc/voice_engine/include/voe_base.h" #include "webrtc/test/rtp_file_writer.h" +DEFINE_bool(save_worst_frame, + false, + "Enable saving a frame with the lowest PSNR to a jpeg file in the " + "test_output_dir"); + namespace { constexpr int kSendStatsPollingIntervalMs = 1000; @@ -825,27 +834,21 @@ class VideoAnalyzer : public PacketReceiver, // will be flaky. PrintResult("memory_usage", memory_usage_, " bytes"); #endif - // TODO(ilnik): enable frame writing for android, once jpeg frame writer - // is implemented. -#if !defined(WEBRTC_ANDROID) - if (worst_frame_) { - test::Y4mFrameWriterImpl frame_writer(test_label_ + ".y4m", - worst_frame_->frame.width(), - worst_frame_->frame.height(), 1); - bool res = frame_writer.Init(); - RTC_DCHECK(res); - size_t length = - CalcBufferSize(VideoType::kI420, worst_frame_->frame.width(), - worst_frame_->frame.height()); - rtc::Buffer extracted_buffer(length); - size_t extracted_length = - ExtractBuffer(worst_frame_->frame.video_frame_buffer()->ToI420(), - length, extracted_buffer.data()); - RTC_DCHECK_EQ(extracted_length, frame_writer.FrameLength()); - res = frame_writer.WriteFrame(extracted_buffer.data()); - RTC_DCHECK(res); - frame_writer.Close(); + // LibJpeg is not available on iOS. +#if !defined(WEBRTC_IOS) + // Saving only the worst frame for manual analysis. Intention here is to + // only detect video corruptions and not to track picture quality. Thus, + // jpeg is used here. + if (FLAGS_save_worst_frame && worst_frame_) { + std::string output_dir; + test::GetTestOutputDir(&output_dir); + std::string output_path = + rtc::Pathname(output_dir, test_label_ + ".jpg").pathname(); + LOG(LS_INFO) << "Saving worst frame to " << output_path; + test::JpegFrameWriter frame_writer(output_path); + RTC_CHECK(frame_writer.WriteFrame(worst_frame_->frame, + 100 /*best quality*/)); } #endif