|  | /* | 
|  | *  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 |