From b78bc75e8c459d02b84dbadd2c5a7d0c831c81f7 Mon Sep 17 00:00:00 2001 From: brandtr Date: Wed, 22 Feb 2017 01:26:59 -0800 Subject: [PATCH] Reland of Add optional visualization file writers to VideoProcessor tests. (patchset #1 id:1 of https://codereview.webrtc.org/2708103002/ ) Reason for revert: Necessary calls were "protected" by RTC_DCHECKs, that were optimized away in some release builds. Replacing the RTC_DCHECKs with EXPECTs. Original issue's description: > Revert of Add optional visualization file writers to VideoProcessor tests. (patchset #4 id:220001 of https://codereview.webrtc.org/2700493006/ ) > > Reason for revert: > Breaks downstream project. > > Original issue's description: > > Add optional visualization file writers to VideoProcessor tests. > > > > The purpose of this visualization CL is to add the ability to record > > video at the source, after encode, and after decode, in the VideoProcessor > > tests. These output files can then be replayed and used as a subjective > > complement to the objective metric plots given by the existing Python > > plotting script. > > > > BUG=webrtc:6634 > > > > Review-Url: https://codereview.webrtc.org/2700493006 > > Cr-Commit-Position: refs/heads/master@{#16738} > > Committed: https://chromium.googlesource.com/external/webrtc/+/872104ac41d7764f8676c9ea55555210bea4605c > > TBR=asapersson@webrtc.org,sprang@webrtc.org,kjellander@webrtc.org > # Skipping CQ checks because original CL landed less than 1 days ago. > NOPRESUBMIT=true > NOTREECHECKS=true > NOTRY=true > BUG=webrtc:6634 > > Review-Url: https://codereview.webrtc.org/2708103002 > Cr-Commit-Position: refs/heads/master@{#16745} > Committed: https://chromium.googlesource.com/external/webrtc/+/2a8135a1741761bd6de52163c0dc35f6eff7c8eb TBR=asapersson@webrtc.org,sprang@webrtc.org,kjellander@webrtc.org # Skipping CQ checks because original CL landed less than 1 days ago. NOPRESUBMIT=true BUG=webrtc:6634 Review-Url: https://codereview.webrtc.org/2706123003 Cr-Commit-Position: refs/heads/master@{#16769} --- webrtc/modules/video_coding/BUILD.gn | 1 + .../plot_videoprocessor_integrationtest.cc | 9 +- .../codecs/test/videoprocessor.cc | 109 +++++++++----- .../video_coding/codecs/test/videoprocessor.h | 26 +++- .../test/videoprocessor_integrationtest.cc | 30 ++-- .../test/videoprocessor_integrationtest.h | 134 +++++++++++++----- .../codecs/test/videoprocessor_unittest.cc | 8 +- .../codecs/tools/video_quality_measurement.cc | 19 +-- webrtc/test/BUILD.gn | 10 +- webrtc/test/testsupport/frame_reader.h | 12 +- .../test/testsupport/frame_reader_unittest.cc | 70 --------- webrtc/test/testsupport/frame_writer.cc | 70 --------- webrtc/test/testsupport/frame_writer.h | 32 ++++- .../test/testsupport/frame_writer_unittest.cc | 63 -------- webrtc/test/testsupport/y4m_frame_writer.cc | 56 ++++++++ .../testsupport/y4m_frame_writer_unittest.cc | 77 ++++++++++ .../{frame_reader.cc => yuv_frame_reader.cc} | 61 ++++---- .../testsupport/yuv_frame_reader_unittest.cc | 83 +++++++++++ webrtc/test/testsupport/yuv_frame_writer.cc | 77 ++++++++++ .../testsupport/yuv_frame_writer_unittest.cc | 68 +++++++++ 20 files changed, 667 insertions(+), 348 deletions(-) delete mode 100644 webrtc/test/testsupport/frame_reader_unittest.cc delete mode 100644 webrtc/test/testsupport/frame_writer.cc delete mode 100644 webrtc/test/testsupport/frame_writer_unittest.cc create mode 100644 webrtc/test/testsupport/y4m_frame_writer.cc create mode 100644 webrtc/test/testsupport/y4m_frame_writer_unittest.cc rename webrtc/test/testsupport/{frame_reader.cc => yuv_frame_reader.cc} (60%) create mode 100644 webrtc/test/testsupport/yuv_frame_reader_unittest.cc create mode 100644 webrtc/test/testsupport/yuv_frame_writer.cc create mode 100644 webrtc/test/testsupport/yuv_frame_writer_unittest.cc diff --git a/webrtc/modules/video_coding/BUILD.gn b/webrtc/modules/video_coding/BUILD.gn index e8ac6719ab..da6343e717 100644 --- a/webrtc/modules/video_coding/BUILD.gn +++ b/webrtc/modules/video_coding/BUILD.gn @@ -351,6 +351,7 @@ if (rtc_include_tests) { deps = [ ":video_codecs_test_framework", ":video_coding", + ":video_coding_utility", ":webrtc_h264", ":webrtc_vp8", ":webrtc_vp9", diff --git a/webrtc/modules/video_coding/codecs/test/plot_videoprocessor_integrationtest.cc b/webrtc/modules/video_coding/codecs/test/plot_videoprocessor_integrationtest.cc index ffc22c438a..d82169b6c1 100644 --- a/webrtc/modules/video_coding/codecs/test/plot_videoprocessor_integrationtest.cc +++ b/webrtc/modules/video_coding/codecs/test/plot_videoprocessor_integrationtest.cc @@ -12,6 +12,7 @@ namespace webrtc { namespace test { + namespace { // Codec settings. const int kBitrates[] = {30, 50, 100, 200, 300, 500, 1000}; @@ -27,6 +28,12 @@ const bool kUseSingleCore = true; // Packet loss probability [0.0, 1.0]. const float kPacketLoss = 0.0f; +const VisualizationParams kVisualizationParams = { + false, // save_source_y4m + false, // save_encoded_ivf + false, // save_decoded_y4m +}; + const bool kVerboseLogging = true; } // namespace @@ -75,7 +82,7 @@ class PlotVideoProcessorIntegrationTest 0, // num_spatial_resizes 1); // num_key_frames ProcessFramesAndVerify(quality_metrics, rate_profile, process_settings, - rc_metrics); + rc_metrics, &kVisualizationParams); } const int bitrate_; const int framerate_; diff --git a/webrtc/modules/video_coding/codecs/test/videoprocessor.cc b/webrtc/modules/video_coding/codecs/test/videoprocessor.cc index 99a34fc60e..c7c152d161 100644 --- a/webrtc/modules/video_coding/codecs/test/videoprocessor.cc +++ b/webrtc/modules/video_coding/codecs/test/videoprocessor.cc @@ -29,6 +29,10 @@ namespace webrtc { namespace test { +namespace { +const int k90khzTimestampFrameDiff = 3000; // Assuming 30 fps. +} // namespace + const char* ExcludeFrameTypesToStr(ExcludeFrameTypes e) { switch (e) { case kExcludeOnlyFirstKeyFrame: @@ -60,18 +64,24 @@ TestConfig::~TestConfig() {} VideoProcessorImpl::VideoProcessorImpl(webrtc::VideoEncoder* encoder, webrtc::VideoDecoder* decoder, - FrameReader* frame_reader, - FrameWriter* frame_writer, + FrameReader* analysis_frame_reader, + FrameWriter* analysis_frame_writer, PacketManipulator* packet_manipulator, const TestConfig& config, - Stats* stats) + Stats* stats, + FrameWriter* source_frame_writer, + IvfFileWriter* encoded_frame_writer, + FrameWriter* decoded_frame_writer) : encoder_(encoder), decoder_(decoder), - frame_reader_(frame_reader), - frame_writer_(frame_writer), + analysis_frame_reader_(analysis_frame_reader), + analysis_frame_writer_(analysis_frame_writer), packet_manipulator_(packet_manipulator), config_(config), stats_(stats), + source_frame_writer_(source_frame_writer), + encoded_frame_writer_(encoded_frame_writer), + decoded_frame_writer_(decoded_frame_writer), first_key_frame_has_been_excluded_(false), last_frame_missing_(false), initialized_(false), @@ -94,8 +104,8 @@ VideoProcessorImpl::VideoProcessorImpl(webrtc::VideoEncoder* encoder, *config.codec_settings, std::move(tl_factory)); RTC_DCHECK(encoder); RTC_DCHECK(decoder); - RTC_DCHECK(frame_reader); - RTC_DCHECK(frame_writer); + RTC_DCHECK(analysis_frame_reader); + RTC_DCHECK(analysis_frame_writer); RTC_DCHECK(packet_manipulator); RTC_DCHECK(stats); } @@ -105,7 +115,7 @@ bool VideoProcessorImpl::Init() { bit_rate_factor_ = config_.codec_settings->maxFramerate * 0.001 * 8; // bits // Initialize data structures used by the encoder/decoder APIs. - size_t frame_length_in_bytes = frame_reader_->FrameLength(); + size_t frame_length_in_bytes = analysis_frame_reader_->FrameLength(); last_successful_frame_buffer_.reset(new uint8_t[frame_length_in_bytes]); // Set fixed properties common for all frames. @@ -139,7 +149,8 @@ bool VideoProcessorImpl::Init() { if (config_.verbose) { printf("Video Processor:\n"); printf(" #CPU cores used : %d\n", num_cores); - printf(" Total # of frames: %d\n", frame_reader_->NumberOfFrames()); + printf(" Total # of frames: %d\n", + analysis_frame_reader_->NumberOfFrames()); printf(" Codec settings:\n"); printf(" Start bitrate : %d kbps\n", config_.codec_settings->startBitrate); @@ -202,18 +213,29 @@ int VideoProcessorImpl::NumberSpatialResizes() { } bool VideoProcessorImpl::ProcessFrame(int frame_number) { - RTC_DCHECK_GE(frame_number, 0); + RTC_CHECK_GE(frame_number, 0); RTC_CHECK(initialized_) << "Attempting to use uninitialized VideoProcessor"; - // |prev_time_stamp_| is used for getting number of dropped frames. - if (frame_number == 0) { - prev_time_stamp_ = -1; - } - - rtc::scoped_refptr buffer(frame_reader_->ReadFrame()); + rtc::scoped_refptr buffer( + analysis_frame_reader_->ReadFrame()); if (buffer) { - // Use the frame number as "timestamp" to identify frames. - VideoFrame source_frame(buffer, frame_number, 0, webrtc::kVideoRotation_0); + if (source_frame_writer_) { + // TODO(brandtr): Introduce temp buffer as data member, to avoid + // allocating for every frame. + size_t length = CalcBufferSize(kI420, buffer->width(), buffer->height()); + std::unique_ptr extracted_buffer(new uint8_t[length]); + int extracted_length = + ExtractBuffer(buffer, length, extracted_buffer.get()); + RTC_CHECK_EQ(extracted_length, source_frame_writer_->FrameLength()); + source_frame_writer_->WriteFrame(extracted_buffer.get()); + } + + // Use the frame number as basis for timestamp to identify frames. Let the + // first timestamp be non-zero, to not make the IvfFileWriter believe that + // we want to use capture timestamps in the IVF files. + VideoFrame source_frame(buffer, + (frame_number + 1) * k90khzTimestampFrameDiff, 0, + webrtc::kVideoRotation_0); // Ensure we have a new statistics data object we can fill. FrameStatistic& stat = stats_->NewFrame(frame_number); @@ -256,30 +278,41 @@ void VideoProcessorImpl::FrameEncoded( // time recordings should wrap the Encode call as tightly as possible. int64_t encode_stop_ns = rtc::TimeNanos(); - // Timestamp is frame number, so this gives us #dropped frames. + if (encoded_frame_writer_) { + RTC_CHECK(encoded_frame_writer_->WriteFrame(encoded_image, codec)); + } + + // Timestamp is proportional to frame number, so this gives us number of + // dropped frames. int num_dropped_from_prev_encode = - encoded_image._timeStamp - prev_time_stamp_ - 1; + (encoded_image._timeStamp - prev_time_stamp_) / k90khzTimestampFrameDiff - + 1; num_dropped_frames_ += num_dropped_from_prev_encode; prev_time_stamp_ = encoded_image._timeStamp; if (num_dropped_from_prev_encode > 0) { // For dropped frames, we write out the last decoded frame to avoid getting // out of sync for the computation of PSNR and SSIM. for (int i = 0; i < num_dropped_from_prev_encode; i++) { - frame_writer_->WriteFrame(last_successful_frame_buffer_.get()); + RTC_CHECK(analysis_frame_writer_->WriteFrame( + last_successful_frame_buffer_.get())); + if (decoded_frame_writer_) { + RTC_CHECK(decoded_frame_writer_->WriteFrame( + last_successful_frame_buffer_.get())); + } } } + // Frame is not dropped, so update the encoded frame size // (encoder callback is only called for non-zero length frames). encoded_frame_size_ = encoded_image._length; encoded_frame_type_ = encoded_image._frameType; - int frame_number = encoded_image._timeStamp; - + int frame_number = encoded_image._timeStamp / k90khzTimestampFrameDiff - 1; FrameStatistic& stat = stats_->stats_[frame_number]; stat.encode_time_in_us = GetElapsedTimeMicroseconds(encode_start_ns_, encode_stop_ns); stat.encoding_successful = true; stat.encoded_frame_length_in_bytes = encoded_image._length; - stat.frame_number = encoded_image._timeStamp; + stat.frame_number = frame_number; stat.frame_type = encoded_image._frameType; stat.bit_rate_in_kbps = encoded_image._length * bit_rate_factor_; stat.total_packets = @@ -336,7 +369,12 @@ void VideoProcessorImpl::FrameEncoded( if (decode_result != WEBRTC_VIDEO_CODEC_OK) { // Write the last successful frame the output file to avoid getting it out // of sync with the source file for SSIM and PSNR comparisons. - frame_writer_->WriteFrame(last_successful_frame_buffer_.get()); + RTC_CHECK(analysis_frame_writer_->WriteFrame( + last_successful_frame_buffer_.get())); + if (decoded_frame_writer_) { + RTC_CHECK(decoded_frame_writer_->WriteFrame( + last_successful_frame_buffer_.get())); + } } // Save status for losses so we can inform the decoder for the next frame. @@ -349,7 +387,7 @@ void VideoProcessorImpl::FrameDecoded(const VideoFrame& image) { int64_t decode_stop_ns = rtc::TimeNanos(); // Report stats. - int frame_number = image.timestamp(); + int frame_number = image.timestamp() / k90khzTimestampFrameDiff - 1; FrameStatistic& stat = stats_->stats_[frame_number]; stat.decode_time_in_us = GetElapsedTimeMicroseconds(decode_start_ns_, decode_stop_ns); @@ -383,14 +421,14 @@ void VideoProcessorImpl::FrameDecoded(const VideoFrame& image) { CalcBufferSize(kI420, up_image->width(), up_image->height()); std::unique_ptr image_buffer(new uint8_t[length]); int extracted_length = ExtractBuffer(up_image, length, image_buffer.get()); - RTC_DCHECK_GT(extracted_length, 0); + RTC_CHECK_GT(extracted_length, 0); // Update our copy of the last successful frame. memcpy(last_successful_frame_buffer_.get(), image_buffer.get(), extracted_length); - bool write_success = frame_writer_->WriteFrame(image_buffer.get()); - RTC_DCHECK(write_success); - if (!write_success) { - fprintf(stderr, "Failed to write frame %d to disk!", frame_number); + + RTC_CHECK(analysis_frame_writer_->WriteFrame(image_buffer.get())); + if (decoded_frame_writer_) { + RTC_CHECK(decoded_frame_writer_->WriteFrame(image_buffer.get())); } } else { // No resize. // Update our copy of the last successful frame. @@ -406,14 +444,13 @@ void VideoProcessorImpl::FrameDecoded(const VideoFrame& image) { extracted_length = ExtractBuffer(image.video_frame_buffer(), length, image_buffer.get()); } - RTC_DCHECK_GT(extracted_length, 0); + RTC_CHECK_GT(extracted_length, 0); memcpy(last_successful_frame_buffer_.get(), image_buffer.get(), extracted_length); - bool write_success = frame_writer_->WriteFrame(image_buffer.get()); - RTC_DCHECK(write_success); - if (!write_success) { - fprintf(stderr, "Failed to write frame %d to disk!", frame_number); + RTC_CHECK(analysis_frame_writer_->WriteFrame(image_buffer.get())); + if (decoded_frame_writer_) { + RTC_CHECK(decoded_frame_writer_->WriteFrame(image_buffer.get())); } } } diff --git a/webrtc/modules/video_coding/codecs/test/videoprocessor.h b/webrtc/modules/video_coding/codecs/test/videoprocessor.h index de20020bdc..c2e26b3f09 100644 --- a/webrtc/modules/video_coding/codecs/test/videoprocessor.h +++ b/webrtc/modules/video_coding/codecs/test/videoprocessor.h @@ -20,6 +20,7 @@ #include "webrtc/modules/video_coding/include/video_codec_interface.h" #include "webrtc/modules/video_coding/codecs/test/packet_manipulator.h" #include "webrtc/modules/video_coding/codecs/test/stats.h" +#include "webrtc/modules/video_coding/utility/ivf_file_writer.h" #include "webrtc/test/testsupport/frame_reader.h" #include "webrtc/test/testsupport/frame_writer.h" @@ -161,11 +162,14 @@ class VideoProcessorImpl : public VideoProcessor { public: VideoProcessorImpl(webrtc::VideoEncoder* encoder, webrtc::VideoDecoder* decoder, - FrameReader* frame_reader, - FrameWriter* frame_writer, + FrameReader* analysis_frame_reader, + FrameWriter* analysis_frame_writer, PacketManipulator* packet_manipulator, const TestConfig& config, - Stats* stats); + Stats* stats, + FrameWriter* source_frame_writer, + IvfFileWriter* encoded_frame_writer, + FrameWriter* decoded_frame_writer); virtual ~VideoProcessorImpl(); bool Init() override; bool ProcessFrame(int frame_number) override; @@ -248,12 +252,24 @@ class VideoProcessorImpl : public VideoProcessor { webrtc::VideoEncoder* const encoder_; webrtc::VideoDecoder* const decoder_; std::unique_ptr bitrate_allocator_; - FrameReader* const frame_reader_; - FrameWriter* const frame_writer_; + // These (mandatory) file manipulators are used for, e.g., objective PSNR and + // SSIM calculations at the end of a test run. + FrameReader* const analysis_frame_reader_; + FrameWriter* const analysis_frame_writer_; PacketManipulator* const packet_manipulator_; const TestConfig& config_; Stats* stats_; + // These (optional) file writers are used for persistently storing the output + // of the coding pipeline at different stages: pre encode (source), post + // encode (encoded), and post decode (decoded). The purpose is to give the + // experimenter an option to subjectively evaluate the quality of the + // encoding, given the test settings. Each frame writer is enabled by being + // non-null. + FrameWriter* const source_frame_writer_; + IvfFileWriter* const encoded_frame_writer_; + FrameWriter* const decoded_frame_writer_; + // Adapters for the codec callbacks. std::unique_ptr encode_callback_; std::unique_ptr decode_callback_; diff --git a/webrtc/modules/video_coding/codecs/test/videoprocessor_integrationtest.cc b/webrtc/modules/video_coding/codecs/test/videoprocessor_integrationtest.cc index a3718a00f4..b818fa50d2 100644 --- a/webrtc/modules/video_coding/codecs/test/videoprocessor_integrationtest.cc +++ b/webrtc/modules/video_coding/codecs/test/videoprocessor_integrationtest.cc @@ -46,10 +46,8 @@ TEST_F(VideoProcessorIntegrationTest, Process0PercentPacketLossH264) { // Metrics for rate control. RateControlMetrics rc_metrics[1]; SetRateControlMetrics(rc_metrics, 0, 2, 60, 20, 10, 20, 0, 1); - ProcessFramesAndVerify(quality_metrics, - rate_profile, - process_settings, - rc_metrics); + ProcessFramesAndVerify(quality_metrics, rate_profile, process_settings, + rc_metrics, nullptr /* visualization_params */); } #endif // defined(WEBRTC_VIDEOPROCESSOR_H264_TESTS) @@ -78,7 +76,7 @@ TEST_F(VideoProcessorIntegrationTest, Process0PercentPacketLossVP9) { RateControlMetrics rc_metrics[1]; SetRateControlMetrics(rc_metrics, 0, 0, 40, 20, 10, 20, 0, 1); ProcessFramesAndVerify(quality_metrics, rate_profile, process_settings, - rc_metrics); + rc_metrics, nullptr /* visualization_params */); } // VP9: Run with 5% packet loss and fixed bitrate. Quality should be a bit @@ -100,7 +98,7 @@ TEST_F(VideoProcessorIntegrationTest, Process5PercentPacketLossVP9) { RateControlMetrics rc_metrics[1]; SetRateControlMetrics(rc_metrics, 0, 0, 40, 20, 10, 20, 0, 1); ProcessFramesAndVerify(quality_metrics, rate_profile, process_settings, - rc_metrics); + rc_metrics, nullptr /* visualization_params */); } // VP9: Run with no packet loss, with varying bitrate (3 rate updates): @@ -128,7 +126,7 @@ TEST_F(VideoProcessorIntegrationTest, ProcessNoLossChangeBitRateVP9) { SetRateControlMetrics(rc_metrics, 1, 2, 0, 20, 20, 60, 0, 0); SetRateControlMetrics(rc_metrics, 2, 0, 0, 25, 20, 40, 0, 0); ProcessFramesAndVerify(quality_metrics, rate_profile, process_settings, - rc_metrics); + rc_metrics, nullptr /* visualization_params */); } // VP9: Run with no packet loss, with an update (decrease) in frame rate. @@ -161,7 +159,7 @@ TEST_F(VideoProcessorIntegrationTest, SetRateControlMetrics(rc_metrics, 1, 10, 0, 40, 10, 30, 0, 0); SetRateControlMetrics(rc_metrics, 2, 5, 0, 30, 5, 20, 0, 0); ProcessFramesAndVerify(quality_metrics, rate_profile, process_settings, - rc_metrics); + rc_metrics, nullptr /* visualization_params */); } // VP9: Run with no packet loss and denoiser on. One key frame (first frame). @@ -182,7 +180,7 @@ TEST_F(VideoProcessorIntegrationTest, ProcessNoLossDenoiserOnVP9) { RateControlMetrics rc_metrics[1]; SetRateControlMetrics(rc_metrics, 0, 0, 40, 20, 10, 20, 0, 1); ProcessFramesAndVerify(quality_metrics, rate_profile, process_settings, - rc_metrics); + rc_metrics, nullptr /* visualization_params */); } // Run with no packet loss, at low bitrate. @@ -207,7 +205,7 @@ TEST_F(VideoProcessorIntegrationTest, RateControlMetrics rc_metrics[1]; SetRateControlMetrics(rc_metrics, 0, 228, 70, 160, 15, 80, 1, 1); ProcessFramesAndVerify(quality_metrics, rate_profile, process_settings, - rc_metrics); + rc_metrics, nullptr /* visualization_params */); } // TODO(marpan): Add temporal layer test for VP9, once changes are in @@ -235,7 +233,7 @@ TEST_F(VideoProcessorIntegrationTest, ProcessZeroPacketLoss) { RateControlMetrics rc_metrics[1]; SetRateControlMetrics(rc_metrics, 0, 0, 40, 20, 10, 15, 0, 1); ProcessFramesAndVerify(quality_metrics, rate_profile, process_settings, - rc_metrics); + rc_metrics, nullptr /* visualization_params */); } // VP8: Run with 5% packet loss and fixed bitrate. Quality should be a bit @@ -257,7 +255,7 @@ TEST_F(VideoProcessorIntegrationTest, Process5PercentPacketLoss) { RateControlMetrics rc_metrics[1]; SetRateControlMetrics(rc_metrics, 0, 0, 40, 20, 10, 15, 0, 1); ProcessFramesAndVerify(quality_metrics, rate_profile, process_settings, - rc_metrics); + rc_metrics, nullptr /* visualization_params */); } // VP8: Run with 10% packet loss and fixed bitrate. Quality should be lower. @@ -279,7 +277,7 @@ TEST_F(VideoProcessorIntegrationTest, Process10PercentPacketLoss) { RateControlMetrics rc_metrics[1]; SetRateControlMetrics(rc_metrics, 0, 0, 40, 20, 10, 15, 0, 1); ProcessFramesAndVerify(quality_metrics, rate_profile, process_settings, - rc_metrics); + rc_metrics, nullptr /* visualization_params */); } #endif // !defined(WEBRTC_IOS) @@ -325,7 +323,7 @@ TEST_F(VideoProcessorIntegrationTest, MAYBE_ProcessNoLossChangeBitRateVP8) { SetRateControlMetrics(rc_metrics, 1, 0, 0, 25, 20, 10, 0, 0); SetRateControlMetrics(rc_metrics, 2, 0, 0, 25, 15, 10, 0, 0); ProcessFramesAndVerify(quality_metrics, rate_profile, process_settings, - rc_metrics); + rc_metrics, nullptr /* visualization_params */); } // VP8: Run with no packet loss, with an update (decrease) in frame rate. @@ -366,7 +364,7 @@ TEST_F(VideoProcessorIntegrationTest, SetRateControlMetrics(rc_metrics, 1, 10, 0, 25, 10, 35, 0, 0); SetRateControlMetrics(rc_metrics, 2, 0, 0, 20, 10, 15, 0, 0); ProcessFramesAndVerify(quality_metrics, rate_profile, process_settings, - rc_metrics); + rc_metrics, nullptr /* visualization_params */); } // VP8: Run with no packet loss, with 3 temporal layers, with a rate update in @@ -401,7 +399,7 @@ TEST_F(VideoProcessorIntegrationTest, MAYBE_ProcessNoLossTemporalLayersVP8) { SetRateControlMetrics(rc_metrics, 0, 0, 20, 30, 10, 10, 0, 1); SetRateControlMetrics(rc_metrics, 1, 0, 0, 30, 15, 10, 0, 0); ProcessFramesAndVerify(quality_metrics, rate_profile, process_settings, - rc_metrics); + rc_metrics, nullptr /* visualization_params */); } } // namespace test } // namespace webrtc diff --git a/webrtc/modules/video_coding/codecs/test/videoprocessor_integrationtest.h b/webrtc/modules/video_coding/codecs/test/videoprocessor_integrationtest.h index 669ae3012b..ddd343836b 100644 --- a/webrtc/modules/video_coding/codecs/test/videoprocessor_integrationtest.h +++ b/webrtc/modules/video_coding/codecs/test/videoprocessor_integrationtest.h @@ -15,6 +15,7 @@ #include #include +#include #if defined(WEBRTC_ANDROID) #include "webrtc/modules/video_coding/codecs/test/android_test_initializer.h" @@ -26,6 +27,7 @@ #endif #include "webrtc/base/checks.h" +#include "webrtc/base/file.h" #include "webrtc/media/engine/webrtcvideodecoderfactory.h" #include "webrtc/media/engine/webrtcvideoencoderfactory.h" #include "webrtc/modules/video_coding/codecs/h264/include/h264.h" @@ -33,10 +35,10 @@ #include "webrtc/modules/video_coding/codecs/test/videoprocessor.h" #include "webrtc/modules/video_coding/codecs/vp8/include/vp8.h" #include "webrtc/modules/video_coding/codecs/vp8/include/vp8_common_types.h" -#include "webrtc/modules/video_coding/codecs/vp8/temporal_layers.h" #include "webrtc/modules/video_coding/codecs/vp9/include/vp9.h" #include "webrtc/modules/video_coding/include/video_codec_interface.h" #include "webrtc/modules/video_coding/include/video_coding.h" +#include "webrtc/modules/video_coding/utility/ivf_file_writer.h" #include "webrtc/test/gtest.h" #include "webrtc/test/testsupport/fileutils.h" #include "webrtc/test/testsupport/frame_reader.h" @@ -110,6 +112,13 @@ struct RateControlMetrics { int num_key_frames; }; +// Should video files be saved persistently to disk for post-run visualization? +struct VisualizationParams { + bool save_source_y4m; + bool save_encoded_ivf; + bool save_decoded_y4m; +}; + #if !defined(WEBRTC_IOS) const int kNumFramesShort = 100; #endif @@ -143,7 +152,8 @@ class VideoProcessorIntegrationTest : public testing::Test { } virtual ~VideoProcessorIntegrationTest() = default; - void SetUpCodecConfig(const CodecConfigPars& process) { + void SetUpCodecConfig(const CodecConfigPars& process, + const VisualizationParams* visualization_params) { if (process.hw_codec) { #if defined(WEBRTC_VIDEOPROCESSOR_INTEGRATIONTEST_HW_CODECS_ENABLED) #if defined(WEBRTC_ANDROID) @@ -175,7 +185,7 @@ class VideoProcessorIntegrationTest : public testing::Test { break; } #elif defined(WEBRTC_IOS) - RTC_DCHECK_EQ(kVideoCodecH264, process.codec_type) + ASSERT_EQ(kVideoCodecH264, process.codec_type) << "iOS HW codecs only support H264."; encoder_.reset(new H264VideoToolboxEncoder( cricket::VideoCodec(cricket::kH264CodecName))); @@ -184,8 +194,8 @@ class VideoProcessorIntegrationTest : public testing::Test { RTC_NOTREACHED() << "Only support HW codecs on Android and iOS."; #endif #endif // WEBRTC_VIDEOPROCESSOR_INTEGRATIONTEST_HW_CODECS_ENABLED - RTC_DCHECK(encoder_) << "HW encoder not successfully created."; - RTC_DCHECK(decoder_) << "HW decoder not successfully created."; + RTC_CHECK(encoder_) << "HW encoder not successfully created."; + RTC_CHECK(decoder_) << "HW decoder not successfully created."; } else { // SW codecs. switch (process.codec_type) { @@ -265,19 +275,54 @@ class VideoProcessorIntegrationTest : public testing::Test { RTC_NOTREACHED(); break; } - frame_reader_.reset(new test::FrameReaderImpl( + + // Create file objects for quality analysis. + analysis_frame_reader_.reset(new test::YuvFrameReaderImpl( config_.input_filename, config_.codec_settings->width, config_.codec_settings->height)); - frame_writer_.reset(new test::FrameWriterImpl( - config_.output_filename, config_.frame_length_in_bytes)); - RTC_CHECK(frame_reader_->Init()); - RTC_CHECK(frame_writer_->Init()); + analysis_frame_writer_.reset(new test::YuvFrameWriterImpl( + config_.output_filename, config_.codec_settings->width, + config_.codec_settings->height)); + RTC_CHECK(analysis_frame_reader_->Init()); + RTC_CHECK(analysis_frame_writer_->Init()); + + if (visualization_params) { + // clang-format off + const std::string output_filename_base = + test::OutputPath() + process.filename + + "_cd-" + CodecTypeToPayloadName(process.codec_type).value_or("") + + "_hw-" + std::to_string(process.hw_codec) + + "_fr-" + std::to_string(start_frame_rate_) + + "_br-" + std::to_string(static_cast(start_bitrate_)); + // clang-format on + if (visualization_params->save_source_y4m) { + source_frame_writer_.reset(new test::Y4mFrameWriterImpl( + output_filename_base + "_source.y4m", config_.codec_settings->width, + config_.codec_settings->height, start_frame_rate_)); + RTC_CHECK(source_frame_writer_->Init()); + } + if (visualization_params->save_encoded_ivf) { + rtc::File post_encode_file = + rtc::File::Create(output_filename_base + "_encoded.ivf"); + encoded_frame_writer_ = + IvfFileWriter::Wrap(std::move(post_encode_file), 0); + } + if (visualization_params->save_decoded_y4m) { + decoded_frame_writer_.reset(new test::Y4mFrameWriterImpl( + output_filename_base + "_decoded.y4m", + config_.codec_settings->width, config_.codec_settings->height, + start_frame_rate_)); + RTC_CHECK(decoded_frame_writer_->Init()); + } + } packet_manipulator_.reset(new test::PacketManipulatorImpl( &packet_reader_, config_.networking_config, config_.verbose)); processor_.reset(new test::VideoProcessorImpl( - encoder_.get(), decoder_.get(), frame_reader_.get(), - frame_writer_.get(), packet_manipulator_.get(), config_, &stats_)); + encoder_.get(), decoder_.get(), analysis_frame_reader_.get(), + analysis_frame_writer_.get(), packet_manipulator_.get(), config_, + &stats_, source_frame_writer_.get(), encoded_frame_writer_.get(), + decoded_frame_writer_.get())); RTC_CHECK(processor_->Init()); } @@ -463,12 +508,14 @@ class VideoProcessorIntegrationTest : public testing::Test { void ProcessFramesAndVerify(QualityMetrics quality_metrics, RateProfile rate_profile, CodecConfigPars process, - RateControlMetrics* rc_metrics) { + RateControlMetrics* rc_metrics, + const VisualizationParams* visualization_params) { // Codec/config settings. start_bitrate_ = rate_profile.target_bit_rate[0]; + start_frame_rate_ = rate_profile.input_frame_rate[0]; packet_loss_ = process.packet_loss; num_temporal_layers_ = process.num_temporal_layers; - SetUpCodecConfig(process); + SetUpCodecConfig(process, visualization_params); // Update the layers and the codec with the initial rates. bit_rate_ = rate_profile.target_bit_rate[0]; frame_rate_ = rate_profile.input_frame_rate[0]; @@ -534,11 +581,22 @@ class VideoProcessorIntegrationTest : public testing::Test { EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, encoder_->Release()); EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, decoder_->Release()); - // Close the files before we start using them for SSIM/PSNR calculations. - frame_reader_->Close(); - frame_writer_->Close(); + // Close the analysis files before we use them for SSIM/PSNR calculations. + analysis_frame_reader_->Close(); + analysis_frame_writer_->Close(); - // TODO(marpan): should compute these quality metrics per SetRates update. + // Close visualization files. + if (source_frame_writer_) { + source_frame_writer_->Close(); + } + if (encoded_frame_writer_) { + encoded_frame_writer_->Close(); + } + if (decoded_frame_writer_) { + decoded_frame_writer_->Close(); + } + + // TODO(marpan): Should compute these quality metrics per SetRates update. test::QualityMetricsResult psnr_result, ssim_result; EXPECT_EQ(0, test::I420MetricsFromFiles(config_.input_filename.c_str(), config_.output_filename.c_str(), @@ -553,22 +611,13 @@ class VideoProcessorIntegrationTest : public testing::Test { EXPECT_GT(psnr_result.min, quality_metrics.minimum_min_psnr); EXPECT_GT(ssim_result.average, quality_metrics.minimum_avg_ssim); EXPECT_GT(ssim_result.min, quality_metrics.minimum_min_ssim); + + // Remove analysis file. if (remove(config_.output_filename.c_str()) < 0) { fprintf(stderr, "Failed to remove temporary file!\n"); } } - static void SetRateProfilePars(RateProfile* rate_profile, - int update_index, - int bit_rate, - int frame_rate, - int frame_index_rate_update) { - rate_profile->target_bit_rate[update_index] = bit_rate; - rate_profile->input_frame_rate[update_index] = frame_rate; - rate_profile->frame_index_rate_update[update_index] = - frame_index_rate_update; - } - static void SetCodecParameters(CodecConfigPars* process_settings, VideoCodecType codec_type, bool hw_codec, @@ -629,6 +678,17 @@ class VideoProcessorIntegrationTest : public testing::Test { quality_metrics->minimum_min_ssim = minimum_min_ssim; } + static void SetRateProfilePars(RateProfile* rate_profile, + int update_index, + int bit_rate, + int frame_rate, + int frame_index_rate_update) { + rate_profile->target_bit_rate[update_index] = bit_rate; + rate_profile->input_frame_rate[update_index] = frame_rate; + rate_profile->frame_index_rate_update[update_index] = + frame_index_rate_update; + } + static void SetRateControlMetrics(RateControlMetrics* rc_metrics, int update_index, int max_num_dropped_frames, @@ -650,20 +710,27 @@ class VideoProcessorIntegrationTest : public testing::Test { rc_metrics[update_index].num_key_frames = num_key_frames; } + // Codecs. std::unique_ptr encoder_; std::unique_ptr external_encoder_factory_; std::unique_ptr decoder_; std::unique_ptr external_decoder_factory_; - std::unique_ptr frame_reader_; - std::unique_ptr frame_writer_; + VideoCodec codec_settings_; + + // Helper objects. + std::unique_ptr analysis_frame_reader_; + std::unique_ptr analysis_frame_writer_; test::PacketReader packet_reader_; std::unique_ptr packet_manipulator_; test::Stats stats_; test::TestConfig config_; - VideoCodec codec_settings_; // Must be destroyed before |encoder_| and |decoder_|. std::unique_ptr processor_; - TemporalLayersFactory tl_factory_; + + // Visualization objects. + std::unique_ptr source_frame_writer_; + std::unique_ptr encoded_frame_writer_; + std::unique_ptr decoded_frame_writer_; // Quantities defined/updated for every encoder rate update. // Some quantities defined per temporal layer (at most 3 layers in this test). @@ -688,6 +755,7 @@ class VideoProcessorIntegrationTest : public testing::Test { float sum_key_frame_size_mismatch_; int num_key_frames_; float start_bitrate_; + int start_frame_rate_; // Codec and network settings. float packet_loss_; diff --git a/webrtc/modules/video_coding/codecs/test/videoprocessor_unittest.cc b/webrtc/modules/video_coding/codecs/test/videoprocessor_unittest.cc index 9846fbcfde..efc60c10d7 100644 --- a/webrtc/modules/video_coding/codecs/test/videoprocessor_unittest.cc +++ b/webrtc/modules/video_coding/codecs/test/videoprocessor_unittest.cc @@ -69,7 +69,9 @@ TEST_F(VideoProcessorTest, Init) { ExpectInit(); VideoProcessorImpl video_processor( &encoder_mock_, &decoder_mock_, &frame_reader_mock_, &frame_writer_mock_, - &packet_manipulator_mock_, config_, &stats_); + &packet_manipulator_mock_, config_, &stats_, + nullptr /* source_frame_writer */, nullptr /* encoded_frame_writer */, + nullptr /* decoded_frame_writer */); ASSERT_TRUE(video_processor.Init()); } @@ -82,7 +84,9 @@ TEST_F(VideoProcessorTest, ProcessFrame) { // be more than initialized... VideoProcessorImpl video_processor( &encoder_mock_, &decoder_mock_, &frame_reader_mock_, &frame_writer_mock_, - &packet_manipulator_mock_, config_, &stats_); + &packet_manipulator_mock_, config_, &stats_, + nullptr /* source_frame_writer */, nullptr /* encoded_frame_writer */, + nullptr /* decoded_frame_writer */); ASSERT_TRUE(video_processor.Init()); video_processor.ProcessFrame(0); } diff --git a/webrtc/modules/video_coding/codecs/tools/video_quality_measurement.cc b/webrtc/modules/video_coding/codecs/tools/video_quality_measurement.cc index f1b580719c..5dd3ce734d 100644 --- a/webrtc/modules/video_coding/codecs/tools/video_quality_measurement.cc +++ b/webrtc/modules/video_coding/codecs/tools/video_quality_measurement.cc @@ -490,11 +490,12 @@ int main(int argc, char* argv[]) { webrtc::VP8Encoder* encoder = webrtc::VP8Encoder::Create(); webrtc::VP8Decoder* decoder = webrtc::VP8Decoder::Create(); webrtc::test::Stats stats; - webrtc::test::FrameReaderImpl frame_reader(config.input_filename, - config.codec_settings->width, - config.codec_settings->height); - webrtc::test::FrameWriterImpl frame_writer(config.output_filename, - config.frame_length_in_bytes); + webrtc::test::YuvFrameReaderImpl frame_reader(config.input_filename, + config.codec_settings->width, + config.codec_settings->height); + webrtc::test::YuvFrameWriterImpl frame_writer(config.output_filename, + config.codec_settings->width, + config.codec_settings->height); frame_reader.Init(); frame_writer.Init(); webrtc::test::PacketReader packet_reader; @@ -507,9 +508,11 @@ int main(int argc, char* argv[]) { packet_manipulator.InitializeRandomSeed(time(NULL)); } webrtc::test::VideoProcessor* processor = - new webrtc::test::VideoProcessorImpl(encoder, decoder, &frame_reader, - &frame_writer, &packet_manipulator, - config, &stats); + new webrtc::test::VideoProcessorImpl( + encoder, decoder, &frame_reader, &frame_writer, &packet_manipulator, + config, &stats, nullptr /* source_frame_writer */, + nullptr /* encoded_frame_writer */, + nullptr /* decoded_frame_writer */); processor->Init(); int frame_number = 0; diff --git a/webrtc/test/BUILD.gn b/webrtc/test/BUILD.gn index a4c018524f..07ffbee412 100644 --- a/webrtc/test/BUILD.gn +++ b/webrtc/test/BUILD.gn @@ -129,14 +129,15 @@ rtc_source_set("video_test_support") { testonly = true sources = [ - "testsupport/frame_reader.cc", "testsupport/frame_reader.h", - "testsupport/frame_writer.cc", "testsupport/frame_writer.h", "testsupport/metrics/video_metrics.cc", "testsupport/metrics/video_metrics.h", "testsupport/mock/mock_frame_reader.h", "testsupport/mock/mock_frame_writer.h", + "testsupport/y4m_frame_writer.cc", + "testsupport/yuv_frame_reader.cc", + "testsupport/yuv_frame_writer.cc", ] deps = [ @@ -259,12 +260,13 @@ rtc_test("test_support_unittests") { "rtp_file_reader_unittest.cc", "rtp_file_writer_unittest.cc", "testsupport/always_passing_unittest.cc", - "testsupport/frame_reader_unittest.cc", - "testsupport/frame_writer_unittest.cc", "testsupport/isolated_output_unittest.cc", "testsupport/metrics/video_metrics_unittest.cc", "testsupport/packet_reader_unittest.cc", "testsupport/perf_test_unittest.cc", + "testsupport/y4m_frame_writer_unittest.cc", + "testsupport/yuv_frame_reader_unittest.cc", + "testsupport/yuv_frame_writer_unittest.cc", ] # TODO(jschuh): Bug 1348: fix this warning. diff --git a/webrtc/test/testsupport/frame_reader.h b/webrtc/test/testsupport/frame_reader.h index 13800cdc6f..94dd78bbda 100644 --- a/webrtc/test/testsupport/frame_reader.h +++ b/webrtc/test/testsupport/frame_reader.h @@ -46,14 +46,14 @@ class FrameReader { virtual int NumberOfFrames() = 0; }; -class FrameReaderImpl : public FrameReader { +class YuvFrameReaderImpl : public FrameReader { public: // Creates a file handler. The input file is assumed to exist and be readable. // Parameters: // input_filename The file to read from. // width, height Size of each frame to read. - FrameReaderImpl(std::string input_filename, int width, int height); - ~FrameReaderImpl() override; + YuvFrameReaderImpl(std::string input_filename, int width, int height); + ~YuvFrameReaderImpl() override; bool Init() override; rtc::scoped_refptr ReadFrame() override; void Close() override; @@ -61,10 +61,10 @@ class FrameReaderImpl : public FrameReader { int NumberOfFrames() override; private: - std::string input_filename_; + const std::string input_filename_; size_t frame_length_in_bytes_; - int width_; - int height_; + const int width_; + const int height_; int number_of_frames_; FILE* input_file_; }; diff --git a/webrtc/test/testsupport/frame_reader_unittest.cc b/webrtc/test/testsupport/frame_reader_unittest.cc deleted file mode 100644 index 58a3245fd4..0000000000 --- a/webrtc/test/testsupport/frame_reader_unittest.cc +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2011 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 "webrtc/test/testsupport/frame_reader.h" - -#include "webrtc/api/video/i420_buffer.h" -#include "webrtc/test/gtest.h" -#include "webrtc/test/testsupport/fileutils.h" - -namespace webrtc { -namespace test { - -const std::string kInputFileContents = "baz"; -const size_t kFrameLength = 3; - -class FrameReaderTest: public testing::Test { - protected: - FrameReaderTest() {} - virtual ~FrameReaderTest() {} - void SetUp() { - // Create a dummy input file. - temp_filename_ = webrtc::test::TempFilename(webrtc::test::OutputPath(), - "frame_reader_unittest"); - FILE* dummy = fopen(temp_filename_.c_str(), "wb"); - fprintf(dummy, "%s", kInputFileContents.c_str()); - fclose(dummy); - - frame_reader_ = new FrameReaderImpl(temp_filename_, 1, 1); - ASSERT_TRUE(frame_reader_->Init()); - } - void TearDown() { - delete frame_reader_; - // Cleanup the dummy input file. - remove(temp_filename_.c_str()); - } - FrameReader* frame_reader_; - std::string temp_filename_; -}; - -TEST_F(FrameReaderTest, InitSuccess) { - FrameReaderImpl frame_reader(temp_filename_, 1, 1); - ASSERT_TRUE(frame_reader.Init()); - ASSERT_EQ(kFrameLength, frame_reader.FrameLength()); - ASSERT_EQ(1, frame_reader.NumberOfFrames()); -} - -TEST_F(FrameReaderTest, ReadFrame) { - rtc::scoped_refptr buffer; - buffer = frame_reader_->ReadFrame(); - ASSERT_TRUE(buffer); - ASSERT_EQ(kInputFileContents[0], buffer->DataY()[0]); - ASSERT_EQ(kInputFileContents[1], buffer->DataU()[0]); - ASSERT_EQ(kInputFileContents[2], buffer->DataV()[0]); - ASSERT_FALSE(frame_reader_->ReadFrame()); // End of file -} - -TEST_F(FrameReaderTest, ReadFrameUninitialized) { - FrameReaderImpl file_reader(temp_filename_, 1, 1); - ASSERT_FALSE(file_reader.ReadFrame()); -} - -} // namespace test -} // namespace webrtc diff --git a/webrtc/test/testsupport/frame_writer.cc b/webrtc/test/testsupport/frame_writer.cc deleted file mode 100644 index 1b9e8a82ef..0000000000 --- a/webrtc/test/testsupport/frame_writer.cc +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2011 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 "webrtc/test/testsupport/frame_writer.h" - -#include - -namespace webrtc { -namespace test { - -FrameWriterImpl::FrameWriterImpl(std::string output_filename, - size_t frame_length_in_bytes) - : output_filename_(output_filename), - frame_length_in_bytes_(frame_length_in_bytes), - output_file_(NULL) { -} - -FrameWriterImpl::~FrameWriterImpl() { - Close(); -} - -bool FrameWriterImpl::Init() { - if (frame_length_in_bytes_ <= 0) { - fprintf(stderr, "Frame length must be >0, was %zu\n", - frame_length_in_bytes_); - return false; - } - output_file_ = fopen(output_filename_.c_str(), "wb"); - if (output_file_ == NULL) { - fprintf(stderr, "Couldn't open output file for writing: %s\n", - output_filename_.c_str()); - return false; - } - return true; -} - -void FrameWriterImpl::Close() { - if (output_file_ != NULL) { - fclose(output_file_); - output_file_ = NULL; - } -} - -size_t FrameWriterImpl::FrameLength() { return frame_length_in_bytes_; } - -bool FrameWriterImpl::WriteFrame(uint8_t* frame_buffer) { - assert(frame_buffer); - if (output_file_ == NULL) { - fprintf(stderr, "FrameWriter is not initialized (output file is NULL)\n"); - return false; - } - size_t bytes_written = fwrite(frame_buffer, 1, frame_length_in_bytes_, - output_file_); - if (bytes_written != frame_length_in_bytes_) { - fprintf(stderr, "Failed to write %zu bytes to file %s\n", - frame_length_in_bytes_, output_filename_.c_str()); - return false; - } - return true; -} - -} // namespace test -} // namespace webrtc diff --git a/webrtc/test/testsupport/frame_writer.h b/webrtc/test/testsupport/frame_writer.h index 8a6b1c2152..76298498ac 100644 --- a/webrtc/test/testsupport/frame_writer.h +++ b/webrtc/test/testsupport/frame_writer.h @@ -42,28 +42,46 @@ class FrameWriter { virtual size_t FrameLength() = 0; }; -class FrameWriterImpl : public FrameWriter { +// Writes raw I420 frames in sequence. +class YuvFrameWriterImpl : public FrameWriter { public: // Creates a file handler. The input file is assumed to exist and be readable // and the output file must be writable. // Parameters: // output_filename The file to write. Will be overwritten if already // existing. - // frame_length_in_bytes The size of each frame. - // For YUV: 3*width*height/2 - FrameWriterImpl(std::string output_filename, size_t frame_length_in_bytes); - ~FrameWriterImpl() override; + // width, height Size of each frame to read. + YuvFrameWriterImpl(std::string output_filename, int width, int height); + ~YuvFrameWriterImpl() override; bool Init() override; bool WriteFrame(uint8_t* frame_buffer) override; void Close() override; size_t FrameLength() override; - private: - std::string output_filename_; + protected: + const std::string output_filename_; size_t frame_length_in_bytes_; + const int width_; + const int height_; FILE* output_file_; }; +// Writes raw I420 frames in sequence, but with Y4M file and frame headers for +// more convenient playback in external media players. +class Y4mFrameWriterImpl : public YuvFrameWriterImpl { + public: + Y4mFrameWriterImpl(std::string output_filename, + int width, + int height, + int frame_rate); + ~Y4mFrameWriterImpl() override; + bool Init() override; + bool WriteFrame(uint8_t* frame_buffer) override; + + private: + const int frame_rate_; +}; + } // namespace test } // namespace webrtc diff --git a/webrtc/test/testsupport/frame_writer_unittest.cc b/webrtc/test/testsupport/frame_writer_unittest.cc deleted file mode 100644 index 59173bdfaa..0000000000 --- a/webrtc/test/testsupport/frame_writer_unittest.cc +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2011 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 "webrtc/test/testsupport/frame_writer.h" - -#include "webrtc/test/gtest.h" -#include "webrtc/test/testsupport/fileutils.h" - -namespace webrtc { -namespace test { - -const size_t kFrameLength = 1000; - -class FrameWriterTest: public testing::Test { - protected: - FrameWriterTest() {} - virtual ~FrameWriterTest() {} - void SetUp() { - temp_filename_ = webrtc::test::TempFilename(webrtc::test::OutputPath(), - "frame_writer_unittest"); - frame_writer_ = new FrameWriterImpl(temp_filename_, kFrameLength); - ASSERT_TRUE(frame_writer_->Init()); - } - void TearDown() { - delete frame_writer_; - // Cleanup the temporary file. - remove(temp_filename_.c_str()); - } - FrameWriter* frame_writer_; - std::string temp_filename_; -}; - -TEST_F(FrameWriterTest, InitSuccess) { - FrameWriterImpl frame_writer(temp_filename_, kFrameLength); - ASSERT_TRUE(frame_writer.Init()); - ASSERT_EQ(kFrameLength, frame_writer.FrameLength()); -} - -TEST_F(FrameWriterTest, WriteFrame) { - uint8_t buffer[kFrameLength]; - memset(buffer, 9, kFrameLength); // Write lots of 9s to the buffer - bool result = frame_writer_->WriteFrame(buffer); - ASSERT_TRUE(result); // success - // Close the file and verify the size. - frame_writer_->Close(); - ASSERT_EQ(kFrameLength, GetFileSize(temp_filename_)); -} - -TEST_F(FrameWriterTest, WriteFrameUninitialized) { - uint8_t buffer[3]; - FrameWriterImpl frame_writer(temp_filename_, kFrameLength); - ASSERT_FALSE(frame_writer.WriteFrame(buffer)); -} - -} // namespace test -} // namespace webrtc diff --git a/webrtc/test/testsupport/y4m_frame_writer.cc b/webrtc/test/testsupport/y4m_frame_writer.cc new file mode 100644 index 0000000000..28fb4b06db --- /dev/null +++ b/webrtc/test/testsupport/y4m_frame_writer.cc @@ -0,0 +1,56 @@ +/* + * 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 "webrtc/base/checks.h" +#include "webrtc/test/testsupport/frame_writer.h" + +namespace webrtc { +namespace test { + +Y4mFrameWriterImpl::Y4mFrameWriterImpl(std::string output_filename, + int width, + int height, + int frame_rate) + : YuvFrameWriterImpl(output_filename, width, height), + frame_rate_(frame_rate) {} + +Y4mFrameWriterImpl::~Y4mFrameWriterImpl() = default; + +bool Y4mFrameWriterImpl::Init() { + if (!YuvFrameWriterImpl::Init()) { + return false; + } + int bytes_written = fprintf(output_file_, "YUV4MPEG2 W%d H%d F%d:1 C420\n", + width_, height_, frame_rate_); + if (bytes_written < 0) { + fprintf(stderr, "Failed to write Y4M file header to file %s\n", + output_filename_.c_str()); + return false; + } + return true; +} + +bool Y4mFrameWriterImpl::WriteFrame(uint8_t* frame_buffer) { + if (output_file_ == nullptr) { + fprintf(stderr, + "Y4mFrameWriterImpl is not initialized (output file is NULL)\n"); + return false; + } + int bytes_written = fprintf(output_file_, "FRAME\n"); + if (bytes_written < 0) { + fprintf(stderr, "Failed to write Y4M frame header to file %s\n", + output_filename_.c_str()); + return false; + } + return YuvFrameWriterImpl::WriteFrame(frame_buffer); +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/testsupport/y4m_frame_writer_unittest.cc b/webrtc/test/testsupport/y4m_frame_writer_unittest.cc new file mode 100644 index 0000000000..a4e417228f --- /dev/null +++ b/webrtc/test/testsupport/y4m_frame_writer_unittest.cc @@ -0,0 +1,77 @@ +/* + * 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 + +#include "webrtc/test/gtest.h" +#include "webrtc/test/testsupport/fileutils.h" +#include "webrtc/test/testsupport/frame_writer.h" + +namespace webrtc { +namespace test { + +namespace { +const size_t kFrameWidth = 50; +const size_t kFrameHeight = 20; +const size_t kFrameLength = 3 * kFrameWidth * kFrameHeight / 2; // I420. +const size_t kFrameRate = 30; + +const std::string kFileHeader = "YUV4MPEG2 W50 H20 F30:1 C420\n"; +const std::string kFrameHeader = "FRAME\n"; +} // namespace + +class Y4mFrameWriterTest : public testing::Test { + protected: + Y4mFrameWriterTest() = default; + ~Y4mFrameWriterTest() override = default; + + void SetUp() override { + temp_filename_ = webrtc::test::TempFilename(webrtc::test::OutputPath(), + "y4m_frame_writer_unittest"); + frame_writer_.reset(new Y4mFrameWriterImpl(temp_filename_, kFrameWidth, + kFrameHeight, kFrameRate)); + ASSERT_TRUE(frame_writer_->Init()); + } + + void TearDown() override { remove(temp_filename_.c_str()); } + + std::unique_ptr frame_writer_; + std::string temp_filename_; +}; + +TEST_F(Y4mFrameWriterTest, InitSuccess) {} + +TEST_F(Y4mFrameWriterTest, FrameLength) { + EXPECT_EQ(kFrameLength, frame_writer_->FrameLength()); +} + +TEST_F(Y4mFrameWriterTest, WriteFrame) { + uint8_t buffer[kFrameLength]; + memset(buffer, 9, kFrameLength); // Write lots of 9s to the buffer. + bool result = frame_writer_->WriteFrame(buffer); + ASSERT_TRUE(result); + result = frame_writer_->WriteFrame(buffer); + ASSERT_TRUE(result); + + frame_writer_->Close(); + EXPECT_EQ(kFileHeader.size() + 2 * kFrameHeader.size() + 2 * kFrameLength, + GetFileSize(temp_filename_)); +} + +TEST_F(Y4mFrameWriterTest, WriteFrameUninitialized) { + uint8_t buffer[kFrameLength]; + Y4mFrameWriterImpl frame_writer(temp_filename_, kFrameWidth, kFrameHeight, + kFrameRate); + EXPECT_FALSE(frame_writer.WriteFrame(buffer)); +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/testsupport/frame_reader.cc b/webrtc/test/testsupport/yuv_frame_reader.cc similarity index 60% rename from webrtc/test/testsupport/frame_reader.cc rename to webrtc/test/testsupport/yuv_frame_reader.cc index 2593afd8c8..3fe481ee1e 100644 --- a/webrtc/test/testsupport/frame_reader.cc +++ b/webrtc/test/testsupport/yuv_frame_reader.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * 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 @@ -10,8 +10,6 @@ #include "webrtc/test/testsupport/frame_reader.h" -#include - #include "webrtc/api/video/i420_buffer.h" #include "webrtc/test/frame_utils.h" #include "webrtc/test/testsupport/fileutils.h" @@ -19,28 +17,31 @@ namespace webrtc { namespace test { -FrameReaderImpl::FrameReaderImpl(std::string input_filename, - int width, int height) +YuvFrameReaderImpl::YuvFrameReaderImpl(std::string input_filename, + int width, + int height) : input_filename_(input_filename), - width_(width), height_(height), - input_file_(NULL) { -} + frame_length_in_bytes_(0), + width_(width), + height_(height), + number_of_frames_(-1), + input_file_(nullptr) {} -FrameReaderImpl::~FrameReaderImpl() { +YuvFrameReaderImpl::~YuvFrameReaderImpl() { Close(); } -bool FrameReaderImpl::Init() { +bool YuvFrameReaderImpl::Init() { if (width_ <= 0 || height_ <= 0) { - fprintf(stderr, "Frame width and height must be >0, was %d x %d\n", - width_, height_); + fprintf(stderr, "Frame width and height must be >0, was %d x %d\n", width_, + height_); return false; } frame_length_in_bytes_ = width_ * height_ + 2 * ((width_ + 1) / 2) * ((height_ + 1) / 2); input_file_ = fopen(input_filename_.c_str(), "rb"); - if (input_file_ == NULL) { + if (input_file_ == nullptr) { fprintf(stderr, "Couldn't open input file for reading: %s\n", input_filename_.c_str()); return false; @@ -51,21 +52,15 @@ bool FrameReaderImpl::Init() { fprintf(stderr, "Found empty file: %s\n", input_filename_.c_str()); return false; } - number_of_frames_ = static_cast(source_file_size / - frame_length_in_bytes_); + number_of_frames_ = + static_cast(source_file_size / frame_length_in_bytes_); return true; } -void FrameReaderImpl::Close() { - if (input_file_ != NULL) { - fclose(input_file_); - input_file_ = NULL; - } -} - -rtc::scoped_refptr FrameReaderImpl::ReadFrame() { - if (input_file_ == NULL) { - fprintf(stderr, "FrameReader is not initialized (input file is NULL)\n"); +rtc::scoped_refptr YuvFrameReaderImpl::ReadFrame() { + if (input_file_ == nullptr) { + fprintf(stderr, + "YuvFrameReaderImpl is not initialized (input file is NULL)\n"); return nullptr; } rtc::scoped_refptr buffer( @@ -77,8 +72,20 @@ rtc::scoped_refptr FrameReaderImpl::ReadFrame() { return buffer; } -size_t FrameReaderImpl::FrameLength() { return frame_length_in_bytes_; } -int FrameReaderImpl::NumberOfFrames() { return number_of_frames_; } +void YuvFrameReaderImpl::Close() { + if (input_file_ != nullptr) { + fclose(input_file_); + input_file_ = nullptr; + } +} + +size_t YuvFrameReaderImpl::FrameLength() { + return frame_length_in_bytes_; +} + +int YuvFrameReaderImpl::NumberOfFrames() { + return number_of_frames_; +} } // namespace test } // namespace webrtc diff --git a/webrtc/test/testsupport/yuv_frame_reader_unittest.cc b/webrtc/test/testsupport/yuv_frame_reader_unittest.cc new file mode 100644 index 0000000000..95908142c9 --- /dev/null +++ b/webrtc/test/testsupport/yuv_frame_reader_unittest.cc @@ -0,0 +1,83 @@ +/* + * 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 + +#include "webrtc/api/video/i420_buffer.h" +#include "webrtc/test/gtest.h" +#include "webrtc/test/testsupport/fileutils.h" +#include "webrtc/test/testsupport/frame_reader.h" + +namespace webrtc { +namespace test { + +namespace { +const std::string kInputFileContents = "bazouk"; + +const size_t kFrameWidth = 2; +const size_t kFrameHeight = 2; +const size_t kFrameLength = 3 * kFrameWidth * kFrameHeight / 2; // I420. +} // namespace + +class YuvFrameReaderTest : public testing::Test { + protected: + YuvFrameReaderTest() = default; + ~YuvFrameReaderTest() override = default; + + void SetUp() override { + temp_filename_ = webrtc::test::TempFilename(webrtc::test::OutputPath(), + "yuv_frame_reader_unittest"); + FILE* dummy = fopen(temp_filename_.c_str(), "wb"); + fprintf(dummy, "%s", kInputFileContents.c_str()); + fclose(dummy); + + frame_reader_.reset( + new YuvFrameReaderImpl(temp_filename_, kFrameWidth, kFrameHeight)); + ASSERT_TRUE(frame_reader_->Init()); + } + + void TearDown() override { remove(temp_filename_.c_str()); } + + std::unique_ptr frame_reader_; + std::string temp_filename_; +}; + +TEST_F(YuvFrameReaderTest, InitSuccess) {} + +TEST_F(YuvFrameReaderTest, FrameLength) { + EXPECT_EQ(kFrameLength, frame_reader_->FrameLength()); +} + +TEST_F(YuvFrameReaderTest, NumberOfFrames) { + EXPECT_EQ(1, frame_reader_->NumberOfFrames()); +} + +TEST_F(YuvFrameReaderTest, ReadFrame) { + rtc::scoped_refptr buffer; + buffer = frame_reader_->ReadFrame(); + ASSERT_TRUE(buffer); + // Expect I420 packed as YUV. + EXPECT_EQ(kInputFileContents[0], buffer->DataY()[0]); + EXPECT_EQ(kInputFileContents[1], buffer->DataY()[1]); + EXPECT_EQ(kInputFileContents[2], buffer->DataY()[2]); + EXPECT_EQ(kInputFileContents[3], buffer->DataY()[3]); + EXPECT_EQ(kInputFileContents[4], buffer->DataU()[0]); + EXPECT_EQ(kInputFileContents[5], buffer->DataV()[0]); + EXPECT_FALSE(frame_reader_->ReadFrame()); // End of file. +} + +TEST_F(YuvFrameReaderTest, ReadFrameUninitialized) { + YuvFrameReaderImpl file_reader(temp_filename_, kFrameWidth, kFrameHeight); + EXPECT_FALSE(file_reader.ReadFrame()); +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/testsupport/yuv_frame_writer.cc b/webrtc/test/testsupport/yuv_frame_writer.cc new file mode 100644 index 0000000000..3c0076152e --- /dev/null +++ b/webrtc/test/testsupport/yuv_frame_writer.cc @@ -0,0 +1,77 @@ +/* + * 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 "webrtc/base/checks.h" +#include "webrtc/test/testsupport/frame_writer.h" + +namespace webrtc { +namespace test { + +YuvFrameWriterImpl::YuvFrameWriterImpl(std::string output_filename, + int width, + int height) + : output_filename_(output_filename), + frame_length_in_bytes_(0), + width_(width), + height_(height), + output_file_(nullptr) {} + +YuvFrameWriterImpl::~YuvFrameWriterImpl() { + Close(); +} + +bool YuvFrameWriterImpl::Init() { + if (width_ <= 0 || height_ <= 0) { + fprintf(stderr, "Frame width and height must be >0, was %d x %d\n", width_, + height_); + return false; + } + frame_length_in_bytes_ = + width_ * height_ + 2 * ((width_ + 1) / 2) * ((height_ + 1) / 2); + + output_file_ = fopen(output_filename_.c_str(), "wb"); + if (output_file_ == nullptr) { + fprintf(stderr, "Couldn't open output file for writing: %s\n", + output_filename_.c_str()); + return false; + } + return true; +} + +bool YuvFrameWriterImpl::WriteFrame(uint8_t* frame_buffer) { + RTC_DCHECK(frame_buffer); + if (output_file_ == nullptr) { + fprintf(stderr, + "YuvFrameWriterImpl is not initialized (output file is NULL)\n"); + return false; + } + size_t bytes_written = + fwrite(frame_buffer, 1, frame_length_in_bytes_, output_file_); + if (bytes_written != frame_length_in_bytes_) { + fprintf(stderr, "Failed to write %zu bytes to file %s\n", + frame_length_in_bytes_, output_filename_.c_str()); + return false; + } + return true; +} + +void YuvFrameWriterImpl::Close() { + if (output_file_ != nullptr) { + fclose(output_file_); + output_file_ = nullptr; + } +} + +size_t YuvFrameWriterImpl::FrameLength() { + return frame_length_in_bytes_; +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/testsupport/yuv_frame_writer_unittest.cc b/webrtc/test/testsupport/yuv_frame_writer_unittest.cc new file mode 100644 index 0000000000..5e3cc5c9c9 --- /dev/null +++ b/webrtc/test/testsupport/yuv_frame_writer_unittest.cc @@ -0,0 +1,68 @@ +/* + * 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/test/gtest.h" +#include "webrtc/test/testsupport/fileutils.h" +#include "webrtc/test/testsupport/frame_writer.h" + +namespace webrtc { +namespace test { + +namespace { +const size_t kFrameWidth = 50; +const size_t kFrameHeight = 20; +const size_t kFrameLength = 3 * kFrameWidth * kFrameHeight / 2; // I420. +} // namespace + +class YuvFrameWriterTest : public testing::Test { + protected: + YuvFrameWriterTest() = default; + ~YuvFrameWriterTest() override = default; + + void SetUp() override { + temp_filename_ = webrtc::test::TempFilename(webrtc::test::OutputPath(), + "yuv_frame_writer_unittest"); + frame_writer_.reset( + new YuvFrameWriterImpl(temp_filename_, kFrameWidth, kFrameHeight)); + ASSERT_TRUE(frame_writer_->Init()); + } + + void TearDown() override { remove(temp_filename_.c_str()); } + + std::unique_ptr frame_writer_; + std::string temp_filename_; +}; + +TEST_F(YuvFrameWriterTest, InitSuccess) {} + +TEST_F(YuvFrameWriterTest, FrameLength) { + EXPECT_EQ(kFrameLength, frame_writer_->FrameLength()); +} + +TEST_F(YuvFrameWriterTest, WriteFrame) { + uint8_t buffer[kFrameLength]; + memset(buffer, 9, kFrameLength); // Write lots of 9s to the buffer. + bool result = frame_writer_->WriteFrame(buffer); + ASSERT_TRUE(result); + + frame_writer_->Close(); + EXPECT_EQ(kFrameLength, GetFileSize(temp_filename_)); +} + +TEST_F(YuvFrameWriterTest, WriteFrameUninitialized) { + uint8_t buffer[kFrameLength]; + YuvFrameWriterImpl frame_writer(temp_filename_, kFrameWidth, kFrameHeight); + EXPECT_FALSE(frame_writer.WriteFrame(buffer)); +} + +} // namespace test +} // namespace webrtc