| /* |
| * 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/frame_analyzer/video_geometry_aligner.h" |
| |
| #include <map> |
| |
| #include "api/make_ref_counted.h" |
| #include "api/video/i420_buffer.h" |
| #include "rtc_base/checks.h" |
| #include "rtc_tools/frame_analyzer/video_quality_analysis.h" |
| #include "third_party/libyuv/include/libyuv/scale.h" |
| |
| namespace webrtc { |
| namespace test { |
| |
| namespace { |
| |
| bool IsValidRegion(const CropRegion& region, |
| const rtc::scoped_refptr<I420BufferInterface>& frame) { |
| return region.left >= 0 && region.right >= 0 && region.top >= 0 && |
| region.bottom >= 0 && region.left + region.right < frame->width() && |
| region.top + region.bottom < frame->height(); |
| } |
| |
| } // namespace |
| |
| rtc::scoped_refptr<I420BufferInterface> CropAndZoom( |
| const CropRegion& crop_region, |
| const rtc::scoped_refptr<I420BufferInterface>& frame) { |
| RTC_CHECK(IsValidRegion(crop_region, frame)); |
| |
| const int uv_crop_left = crop_region.left / 2; |
| const int uv_crop_top = crop_region.top / 2; |
| |
| const int cropped_width = |
| frame->width() - crop_region.left - crop_region.right; |
| const int cropped_height = |
| frame->height() - crop_region.top - crop_region.bottom; |
| |
| // Crop by only adjusting pointers. |
| const uint8_t* y_plane = |
| frame->DataY() + frame->StrideY() * crop_region.top + crop_region.left; |
| const uint8_t* u_plane = |
| frame->DataU() + frame->StrideU() * uv_crop_top + uv_crop_left; |
| const uint8_t* v_plane = |
| frame->DataV() + frame->StrideV() * uv_crop_top + uv_crop_left; |
| |
| // Stretch the cropped frame to the original size using libyuv. |
| rtc::scoped_refptr<I420Buffer> adjusted_frame = |
| I420Buffer::Create(frame->width(), frame->height()); |
| libyuv::I420Scale(y_plane, frame->StrideY(), u_plane, frame->StrideU(), |
| v_plane, frame->StrideV(), cropped_width, cropped_height, |
| adjusted_frame->MutableDataY(), adjusted_frame->StrideY(), |
| adjusted_frame->MutableDataU(), adjusted_frame->StrideU(), |
| adjusted_frame->MutableDataV(), adjusted_frame->StrideV(), |
| frame->width(), frame->height(), libyuv::kFilterBox); |
| |
| return adjusted_frame; |
| } |
| |
| CropRegion CalculateCropRegion( |
| const rtc::scoped_refptr<I420BufferInterface>& reference_frame, |
| const rtc::scoped_refptr<I420BufferInterface>& test_frame) { |
| RTC_CHECK_EQ(reference_frame->width(), test_frame->width()); |
| RTC_CHECK_EQ(reference_frame->height(), test_frame->height()); |
| |
| CropRegion best_region; |
| double best_ssim = Ssim(reference_frame, test_frame); |
| |
| typedef int CropRegion::*CropParameter; |
| CropParameter crop_parameters[4] = {&CropRegion::left, &CropRegion::top, |
| &CropRegion::right, &CropRegion::bottom}; |
| |
| while (true) { |
| // Find the parameter in which direction SSIM improves the most. |
| CropParameter best_parameter = nullptr; |
| const CropRegion prev_best_region = best_region; |
| |
| for (CropParameter crop_parameter : crop_parameters) { |
| CropRegion test_region = prev_best_region; |
| ++(test_region.*crop_parameter); |
| |
| if (!IsValidRegion(test_region, reference_frame)) |
| continue; |
| |
| const double ssim = |
| Ssim(CropAndZoom(test_region, reference_frame), test_frame); |
| |
| if (ssim > best_ssim) { |
| best_ssim = ssim; |
| best_parameter = crop_parameter; |
| best_region = test_region; |
| } |
| } |
| |
| // No improvement among any direction, stop iteration. |
| if (best_parameter == nullptr) |
| break; |
| |
| // Iterate in the best direction as long as it improves SSIM. |
| for (CropRegion test_region = best_region; |
| IsValidRegion(test_region, reference_frame); |
| ++(test_region.*best_parameter)) { |
| const double ssim = |
| Ssim(CropAndZoom(test_region, reference_frame), test_frame); |
| if (ssim <= best_ssim) |
| break; |
| |
| best_ssim = ssim; |
| best_region = test_region; |
| } |
| } |
| |
| return best_region; |
| } |
| |
| rtc::scoped_refptr<I420BufferInterface> AdjustCropping( |
| const rtc::scoped_refptr<I420BufferInterface>& reference_frame, |
| const rtc::scoped_refptr<I420BufferInterface>& test_frame) { |
| return CropAndZoom(CalculateCropRegion(reference_frame, test_frame), |
| reference_frame); |
| } |
| |
| rtc::scoped_refptr<Video> AdjustCropping( |
| const rtc::scoped_refptr<Video>& reference_video, |
| const rtc::scoped_refptr<Video>& test_video) { |
| class CroppedVideo : public Video { |
| public: |
| CroppedVideo(const rtc::scoped_refptr<Video>& reference_video, |
| const rtc::scoped_refptr<Video>& test_video) |
| : reference_video_(reference_video), test_video_(test_video) { |
| RTC_CHECK_EQ(reference_video->number_of_frames(), |
| test_video->number_of_frames()); |
| RTC_CHECK_EQ(reference_video->width(), test_video->width()); |
| RTC_CHECK_EQ(reference_video->height(), test_video->height()); |
| } |
| |
| int width() const override { return test_video_->width(); } |
| int height() const override { return test_video_->height(); } |
| size_t number_of_frames() const override { |
| return test_video_->number_of_frames(); |
| } |
| |
| rtc::scoped_refptr<I420BufferInterface> GetFrame( |
| size_t index) const override { |
| const rtc::scoped_refptr<I420BufferInterface> reference_frame = |
| reference_video_->GetFrame(index); |
| |
| // Only calculate cropping region once per frame since it's expensive. |
| if (!crop_regions_.count(index)) { |
| crop_regions_[index] = |
| CalculateCropRegion(reference_frame, test_video_->GetFrame(index)); |
| } |
| |
| return CropAndZoom(crop_regions_[index], reference_frame); |
| } |
| |
| private: |
| const rtc::scoped_refptr<Video> reference_video_; |
| const rtc::scoped_refptr<Video> test_video_; |
| // Mutable since this is a cache that affects performance and not logical |
| // behavior. |
| mutable std::map<size_t, CropRegion> crop_regions_; |
| }; |
| |
| return rtc::make_ref_counted<CroppedVideo>(reference_video, test_video); |
| } |
| |
| } // namespace test |
| } // namespace webrtc |