Index: remoting/test/test_video_renderer.cc |
diff --git a/remoting/test/test_video_renderer.cc b/remoting/test/test_video_renderer.cc |
index 75dd2c9511ba614425c8230dcebcd5e82bbcb23f..e0690303953abdb93b2538f07df5dd85e61cba48 100644 |
--- a/remoting/test/test_video_renderer.cc |
+++ b/remoting/test/test_video_renderer.cc |
@@ -4,7 +4,11 @@ |
#include "remoting/test/test_video_renderer.h" |
+#include <algorithm> |
+#include <cmath> |
+ |
#include "base/bind.h" |
+#include "base/callback_helpers.h" |
#include "base/logging.h" |
#include "base/synchronization/lock.h" |
#include "base/thread_task_runner_handle.h" |
@@ -15,6 +19,13 @@ |
#include "remoting/proto/video.pb.h" |
#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h" |
+namespace { |
+const int kBytesPerPixel = 4; |
Sergey Ulanov
2015/07/09 19:43:14
use webrtc::DesktopFrame::kBytesPerPixel.
liaoyuke
2015/07/09 23:21:01
Done.
|
+ |
+// Used to account for video frame resizing and lossy encoding. |
+const double fuzzy_threshold_ = 0.05; |
Sergey Ulanov
2015/07/09 19:43:14
Not a proper name for a const.
What units is it m
liaoyuke
2015/07/09 23:21:01
I think so, have two separate values looks cleaner
|
+} // namespace |
+ |
namespace remoting { |
namespace test { |
@@ -41,10 +52,32 @@ class TestVideoRenderer::Core { |
// when the pattern is matched. |
void SetImagePatternAndMatchedCallback( |
const webrtc::DesktopRect& expected_rect, |
- const RgbaColor& expected_color, |
+ const RGBA32& expected_color, |
const base::Closure& image_pattern_matched_callback); |
private: |
+ // Returns true if |rect2| falls within |rect1|, allowing for certain amount |
liaoyuke
2015/07/09 23:21:01
Update comments in next patch.
|
+ // of error specified by a fuzzy threshold. |
+ bool ContainsRect(const webrtc::DesktopRect& template_rect, |
+ const webrtc::DesktopRect& candidate_rect) const; |
+ |
+ // Expand the |accumulating_rect_| to a minimum rectangle that contains both |
+ // |rect| and itself. |
+ void MergeRectToAccumulatingRect(const webrtc::DesktopRect& rect); |
+ |
+ // Update |accumulating_color_| to reflect the average color value of pixels |
+ // fall within |accumulating_rect_|. |
+ void UpdateAccumulatingColor(); |
+ |
+ // Compares |accumulating_color_| to |expected_color_|. |
+ // Returns true if the root mean square of the errors in the R, G and B |
+ // components does not exceed a fuzzy threshold. |
+ bool ExpectedColorIsMatched() const; |
+ |
+ // Returns true if |expected_rect_| and |accumulating_rect_| contains each |
+ // other and expected color is matched. |
+ bool ExpectedImagePatternIsMatched() const; |
+ |
// Used to ensure Core methods are called on the same thread. |
base::ThreadChecker thread_checker_; |
@@ -68,11 +101,11 @@ class TestVideoRenderer::Core { |
// Used to store the expected image pattern. |
webrtc::DesktopRect expected_rect_; |
- RgbaColor expected_color_; |
+ RGBColor expected_color_; |
// Maintains accumulating image pattern. |
webrtc::DesktopRect accumulating_rect_; |
- RgbaColor accumulating_color_; |
+ RGBColor accumulating_color_; |
Sergey Ulanov
2015/07/09 19:43:14
I'm not sure why it's called "accumulating" given
liaoyuke
2015/07/09 23:21:01
Done.
|
// Used to store the callback when expected pattern is matched. |
base::Closure image_pattern_matched_callback_; |
@@ -91,6 +124,8 @@ TestVideoRenderer::Core::~Core() { |
void TestVideoRenderer::Core::Initialize() { |
DCHECK(thread_checker_.CalledOnValidThread()); |
+ |
+ accumulating_rect_ = webrtc::DesktopRect::MakeLTRB(0, 0, 0, 0); |
} |
void TestVideoRenderer::Core::SetCodecForDecoding( |
@@ -172,22 +207,117 @@ void TestVideoRenderer::Core::ProcessVideoPacket( |
main_task_runner_->PostTask(FROM_HERE, done); |
- // TODO(liaoyuke): Update |accumulating_rect_| and |accumulating_color_|, then |
- // compare to the expected image pattern to check whether the pattern is |
- // matched or not and update |image_pattern_matched| accordingly. |
+ // Check to see if a image pattern matched reply is passed in. |
+ if (image_pattern_matched_callback_.is_null()) { |
+ return; |
+ } |
+ |
+ for (webrtc::DesktopRegion::Iterator dirty_region(updated_region_); |
+ !dirty_region.IsAtEnd(); dirty_region.Advance()) { |
+ webrtc::DesktopRect dirty_rect = dirty_region.rect(); |
+ |
+ // Dirty rects not falling within |expected_rect_| doesn't affect the |
+ // result. |
+ if (ContainsRect(expected_rect_, dirty_rect)) { |
Sergey Ulanov
2015/07/09 19:43:14
I don't get this part. What if dirty_rect is parti
liaoyuke
2015/07/09 23:21:01
Yes, it will be ignored in both case, and we only
Sergey Ulanov
2015/07/10 01:13:07
I still don't see how this can work correctly. Let
|
+ MergeRectToAccumulatingRect(dirty_rect); |
+ } |
+ } |
+ |
+ UpdateAccumulatingColor(); |
Sergey Ulanov
2015/07/09 19:43:14
would it be better to return "accumulated_color" f
liaoyuke
2015/07/09 23:21:01
Yes, I think that's a good idea!
|
+ if (ExpectedImagePatternIsMatched()) { |
Sergey Ulanov
2015/07/09 19:43:14
I don't get the logic here. UpdateAccumulatedColor
liaoyuke
2015/07/09 23:21:01
Actually, ExpectedImagePatternIsMatched() not only
Sergey Ulanov
2015/07/10 01:13:07
Missed that sorry. But I still think it isn't corr
|
+ base::ResetAndReturn(&image_pattern_matched_callback_).Run(); |
+ accumulating_rect_ = webrtc::DesktopRect::MakeLTRB(0, 0, 0, 0); |
Sergey Ulanov
2015/07/09 19:43:14
Default constructor creates an empty rect, i.e. th
liaoyuke
2015/07/09 23:21:01
Done.
|
+ } |
} |
void TestVideoRenderer::Core::SetImagePatternAndMatchedCallback( |
const webrtc::DesktopRect& expected_rect, |
- const RgbaColor& expected_color, |
+ const RGBA32& expected_color, |
const base::Closure& image_pattern_matched_callback) { |
DCHECK(thread_checker_.CalledOnValidThread()); |
expected_rect_ = expected_rect; |
- expected_color_ = expected_color; |
+ expected_color_.SetRGB(expected_color); |
image_pattern_matched_callback_ = image_pattern_matched_callback; |
} |
+bool TestVideoRenderer::Core::ContainsRect( |
Sergey Ulanov
2015/07/09 19:43:14
This can be static function, doesn't need to be a
liaoyuke
2015/07/09 23:21:01
Done.
|
+ const webrtc::DesktopRect& template_rect, |
+ const webrtc::DesktopRect& candidate_rect) const { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ |
+ double fuzzy_limit_width = fuzzy_threshold_ * template_rect.width(); |
Sergey Ulanov
2015/07/09 19:43:14
Why does this need to be relative to the rect size
liaoyuke
2015/07/09 23:21:01
I think you are right, since the lossy encoding wi
|
+ double fuzzy_limit_height = fuzzy_threshold_ * template_rect.height(); |
+ |
+ return candidate_rect.left() >= template_rect.left() - fuzzy_limit_width && |
Sergey Ulanov
2015/07/09 19:43:14
I think you can use DesktopRect::ContainsRect(), j
liaoyuke
2015/07/09 23:21:01
Good point!
|
+ candidate_rect.top() >= template_rect.top() - fuzzy_limit_height && |
+ candidate_rect.right() <= template_rect.right() + fuzzy_limit_width && |
+ candidate_rect.bottom() <= template_rect.bottom() + fuzzy_limit_height; |
+} |
+ |
+void TestVideoRenderer::Core::MergeRectToAccumulatingRect( |
+ const webrtc::DesktopRect& rect) { |
+ // If |rect| is the first dirty rect that falls within |expected_rect_|, |
+ // simply assign |rect| to |accumulating_rect_|. |
+ if (!accumulating_rect_.width() && !accumulating_rect_.height()) { |
Sergey Ulanov
2015/07/09 19:43:15
accumulating_rect_.is_empty()
liaoyuke
2015/07/09 23:21:01
Done.
|
+ accumulating_rect_ = rect; |
+ } else { |
+ // Merge |rect| to obtain a larger |accumulating_rect_|. |
+ accumulating_rect_ = webrtc::DesktopRect::MakeLTRB( |
Sergey Ulanov
2015/07/09 19:43:14
would it be better to store region instead of rect
liaoyuke
2015/07/09 23:21:01
Yes, I think so, using a single rectangle would lo
Sergey Ulanov
2015/07/10 01:13:07
See my comment above for my suggestions
|
+ std::min(accumulating_rect_.left(), rect.left()), |
+ std::min(accumulating_rect_.top(), rect.top()), |
+ std::max(accumulating_rect_.right(), rect.right()), |
+ std::max(accumulating_rect_.bottom(), rect.bottom())); |
+ } |
+} |
+ |
+void TestVideoRenderer::Core::UpdateAccumulatingColor() { |
+ int red_sum = 0; |
+ int green_sum = 0; |
+ int blue_sum = 0; |
+ |
+ // Loop through pixels that fall within |accumulating_rect_| to obtain the |
+ // average color. |
+ for (int y = accumulating_rect_.top(); y < accumulating_rect_.bottom(); ++y) { |
+ for (int x = accumulating_rect_.left(); x < accumulating_rect_.right(); |
+ ++x) { |
+ int offset = |
+ (y * kBytesPerPixel * screen_size_.width()) + (x * kBytesPerPixel); |
Sergey Ulanov
2015/07/09 19:43:14
You can make this code slightly faster by incremen
Sergey Ulanov
2015/07/09 19:43:14
use buffer_->stride() instead of screen_size_.widt
Sergey Ulanov
2015/07/09 19:43:15
nit: extra parentheses don't add much here
liaoyuke
2015/07/09 23:21:01
Done.
liaoyuke
2015/07/09 23:21:01
Done.
liaoyuke
2015/07/09 23:21:01
Done.
|
+ |
+ // Pixels of decoded video frame are presented in ARGB format. |
+ red_sum += buffer_->data()[offset + 2]; |
+ green_sum += buffer_->data()[offset + 1]; |
+ blue_sum += buffer_->data()[offset + 0]; |
+ } |
+ } |
+ |
+ int area = screen_size_.width() * screen_size_.height(); |
+ accumulating_color_.SetRGB(red_sum / area, green_sum / area, blue_sum / area); |
+} |
+ |
+bool TestVideoRenderer::Core::ExpectedColorIsMatched() const { |
+ double error_sum_squares = 0; |
+ double red_error = static_cast<double>(expected_color_.GetRed() - |
Sergey Ulanov
2015/07/09 19:43:14
I don't think you need explicit cast here
Sergey Ulanov
2015/07/09 19:43:14
Also I don't think that comparing averages is the
liaoyuke
2015/07/09 23:21:01
Yes, you are right, there are some potential probl
liaoyuke
2015/07/09 23:21:01
Done.
Sergey Ulanov
2015/07/10 01:13:07
I don't think you would need to change the interfa
|
+ accumulating_color_.GetRed()); |
+ double green_error = static_cast<double>(expected_color_.GetGreen() - |
+ accumulating_color_.GetGreen()); |
+ double blue_error = static_cast<double>(expected_color_.GetBlue() - |
+ accumulating_color_.GetBlue()); |
+ error_sum_squares = red_error * red_error + green_error * green_error + |
+ blue_error * blue_error; |
+ error_sum_squares /= (255.0 * 255.0); |
+ |
+ return sqrt(error_sum_squares / 3) < fuzzy_threshold_; |
+} |
+ |
+bool TestVideoRenderer::Core::ExpectedImagePatternIsMatched() const { |
+ // accumulating_rect_ must be valid in order to be matched. |
Sergey Ulanov
2015/07/09 19:43:14
nit: |accumulating_rect_|
liaoyuke
2015/07/09 23:21:01
Done.
|
+ return accumulating_rect_.width() && accumulating_rect_.height() && |
+ ContainsRect(accumulating_rect_, expected_rect_) && |
+ ContainsRect(expected_rect_, accumulating_rect_) && |
+ ExpectedColorIsMatched(); |
+} |
+ |
TestVideoRenderer::TestVideoRenderer() |
: video_decode_thread_( |
new base::Thread("TestVideoRendererVideoDecodingThread")), |
@@ -275,7 +405,7 @@ scoped_ptr<webrtc::DesktopFrame> TestVideoRenderer::GetBufferForTest() const { |
void TestVideoRenderer::SetImagePatternAndMatchedCallback( |
const webrtc::DesktopRect& expected_rect, |
- const RgbaColor& expected_color, |
+ const RGBA32& expected_color, |
const base::Closure& image_pattern_matched_callback) { |
DCHECK(thread_checker_.CalledOnValidThread()); |
@@ -286,5 +416,29 @@ void TestVideoRenderer::SetImagePatternAndMatchedCallback( |
expected_color, image_pattern_matched_callback)); |
} |
+RGBColor::RGBColor(int red, int green, int blue) { |
+ SetRGB(red, green, blue); |
+} |
+ |
+int RGBColor::GetRed() const { |
+ return (color_ >> 16) & 0xFF; |
+} |
+ |
+int RGBColor::GetGreen() const { |
+ return (color_ >> 8) & 0xFF; |
+} |
+ |
+int RGBColor::GetBlue() const { |
+ return color_ & 0xFF; |
+} |
+ |
+void RGBColor::SetRGB(int red, int green, int blue) { |
+ // if any of the channels is out of range [0, 255], simply take it as 0 or 255 |
+ // accordingly. |
+ color_ = 0xFF000000 | (std::max(0, std::min(red, 255)) << 16) | |
+ (std::max(0, std::min(green, 255)) << 8) | |
+ (std::max(0, std::min(blue, 255))); |
+} |
+ |
} // namespace test |
} // namespace remoting |