OLD | NEW |
---|---|
1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "remoting/test/test_video_renderer.h" | 5 #include "remoting/test/test_video_renderer.h" |
6 | 6 |
7 #include <algorithm> | |
8 #include <cmath> | |
9 | |
7 #include "base/bind.h" | 10 #include "base/bind.h" |
11 #include "base/callback_helpers.h" | |
8 #include "base/logging.h" | 12 #include "base/logging.h" |
9 #include "base/synchronization/lock.h" | 13 #include "base/synchronization/lock.h" |
10 #include "base/thread_task_runner_handle.h" | 14 #include "base/thread_task_runner_handle.h" |
11 #include "base/threading/thread.h" | 15 #include "base/threading/thread.h" |
12 #include "remoting/codec/video_decoder.h" | 16 #include "remoting/codec/video_decoder.h" |
13 #include "remoting/codec/video_decoder_verbatim.h" | 17 #include "remoting/codec/video_decoder_verbatim.h" |
14 #include "remoting/codec/video_decoder_vpx.h" | 18 #include "remoting/codec/video_decoder_vpx.h" |
15 #include "remoting/proto/video.pb.h" | 19 #include "remoting/proto/video.pb.h" |
16 #include "third_party/webrtc/modules/desktop_capture/desktop_frame.h" | 20 #include "third_party/webrtc/modules/desktop_capture/desktop_frame.h" |
17 | 21 |
22 namespace { | |
23 const int kBytesPerPixel = 4; | |
Sergey Ulanov
2015/07/09 19:43:14
use webrtc::DesktopFrame::kBytesPerPixel.
liaoyuke
2015/07/09 23:21:01
Done.
| |
24 | |
25 // Used to account for video frame resizing and lossy encoding. | |
26 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
| |
27 } // namespace | |
28 | |
18 namespace remoting { | 29 namespace remoting { |
19 namespace test { | 30 namespace test { |
20 | 31 |
21 // Implements video decoding functionality. | 32 // Implements video decoding functionality. |
22 class TestVideoRenderer::Core { | 33 class TestVideoRenderer::Core { |
23 public: | 34 public: |
24 Core(); | 35 Core(); |
25 ~Core(); | 36 ~Core(); |
26 | 37 |
27 // Initializes the internal structures of the class. | 38 // Initializes the internal structures of the class. |
28 void Initialize(); | 39 void Initialize(); |
29 | 40 |
30 // Used to decode video packets. | 41 // Used to decode video packets. |
31 void ProcessVideoPacket(scoped_ptr<VideoPacket> packet, | 42 void ProcessVideoPacket(scoped_ptr<VideoPacket> packet, |
32 const base::Closure& done); | 43 const base::Closure& done); |
33 | 44 |
34 // Initialize a decoder to decode video packets. | 45 // Initialize a decoder to decode video packets. |
35 void SetCodecForDecoding(const protocol::ChannelConfig::Codec codec); | 46 void SetCodecForDecoding(const protocol::ChannelConfig::Codec codec); |
36 | 47 |
37 // Returns a copy of the current buffer. | 48 // Returns a copy of the current buffer. |
38 scoped_ptr<webrtc::DesktopFrame> GetBufferForTest() const; | 49 scoped_ptr<webrtc::DesktopFrame> GetBufferForTest() const; |
39 | 50 |
40 // Set expected image pattern for comparison and the callback will be called | 51 // Set expected image pattern for comparison and the callback will be called |
41 // when the pattern is matched. | 52 // when the pattern is matched. |
42 void SetImagePatternAndMatchedCallback( | 53 void SetImagePatternAndMatchedCallback( |
43 const webrtc::DesktopRect& expected_rect, | 54 const webrtc::DesktopRect& expected_rect, |
44 const RgbaColor& expected_color, | 55 const RGBA32& expected_color, |
45 const base::Closure& image_pattern_matched_callback); | 56 const base::Closure& image_pattern_matched_callback); |
46 | 57 |
47 private: | 58 private: |
59 // Returns true if |rect2| falls within |rect1|, allowing for certain amount | |
liaoyuke
2015/07/09 23:21:01
Update comments in next patch.
| |
60 // of error specified by a fuzzy threshold. | |
61 bool ContainsRect(const webrtc::DesktopRect& template_rect, | |
62 const webrtc::DesktopRect& candidate_rect) const; | |
63 | |
64 // Expand the |accumulating_rect_| to a minimum rectangle that contains both | |
65 // |rect| and itself. | |
66 void MergeRectToAccumulatingRect(const webrtc::DesktopRect& rect); | |
67 | |
68 // Update |accumulating_color_| to reflect the average color value of pixels | |
69 // fall within |accumulating_rect_|. | |
70 void UpdateAccumulatingColor(); | |
71 | |
72 // Compares |accumulating_color_| to |expected_color_|. | |
73 // Returns true if the root mean square of the errors in the R, G and B | |
74 // components does not exceed a fuzzy threshold. | |
75 bool ExpectedColorIsMatched() const; | |
76 | |
77 // Returns true if |expected_rect_| and |accumulating_rect_| contains each | |
78 // other and expected color is matched. | |
79 bool ExpectedImagePatternIsMatched() const; | |
80 | |
48 // Used to ensure Core methods are called on the same thread. | 81 // Used to ensure Core methods are called on the same thread. |
49 base::ThreadChecker thread_checker_; | 82 base::ThreadChecker thread_checker_; |
50 | 83 |
51 // Used to decode video packets. | 84 // Used to decode video packets. |
52 scoped_ptr<VideoDecoder> decoder_; | 85 scoped_ptr<VideoDecoder> decoder_; |
53 | 86 |
54 // Updated region of the current desktop frame compared to previous one. | 87 // Updated region of the current desktop frame compared to previous one. |
55 webrtc::DesktopRegion updated_region_; | 88 webrtc::DesktopRegion updated_region_; |
56 | 89 |
57 // Screen size of the remote host. | 90 // Screen size of the remote host. |
58 webrtc::DesktopSize screen_size_; | 91 webrtc::DesktopSize screen_size_; |
59 | 92 |
60 // Used to post tasks back to main thread. | 93 // Used to post tasks back to main thread. |
61 scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_; | 94 scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_; |
62 | 95 |
63 // Used to store decoded video frame. | 96 // Used to store decoded video frame. |
64 scoped_ptr<webrtc::DesktopFrame> buffer_; | 97 scoped_ptr<webrtc::DesktopFrame> buffer_; |
Sergey Ulanov
2015/07/09 19:43:13
should this be called frame_?
| |
65 | 98 |
66 // Protects access to |buffer_|. | 99 // Protects access to |buffer_|. |
67 mutable base::Lock lock_; | 100 mutable base::Lock lock_; |
68 | 101 |
69 // Used to store the expected image pattern. | 102 // Used to store the expected image pattern. |
70 webrtc::DesktopRect expected_rect_; | 103 webrtc::DesktopRect expected_rect_; |
71 RgbaColor expected_color_; | 104 RGBColor expected_color_; |
72 | 105 |
73 // Maintains accumulating image pattern. | 106 // Maintains accumulating image pattern. |
74 webrtc::DesktopRect accumulating_rect_; | 107 webrtc::DesktopRect accumulating_rect_; |
75 RgbaColor accumulating_color_; | 108 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.
| |
76 | 109 |
77 // Used to store the callback when expected pattern is matched. | 110 // Used to store the callback when expected pattern is matched. |
78 base::Closure image_pattern_matched_callback_; | 111 base::Closure image_pattern_matched_callback_; |
79 | 112 |
80 DISALLOW_COPY_AND_ASSIGN(Core); | 113 DISALLOW_COPY_AND_ASSIGN(Core); |
81 }; | 114 }; |
82 | 115 |
83 TestVideoRenderer::Core::Core() | 116 TestVideoRenderer::Core::Core() |
84 : main_task_runner_(base::ThreadTaskRunnerHandle::Get()) { | 117 : main_task_runner_(base::ThreadTaskRunnerHandle::Get()) { |
85 thread_checker_.DetachFromThread(); | 118 thread_checker_.DetachFromThread(); |
86 } | 119 } |
87 | 120 |
88 TestVideoRenderer::Core::~Core() { | 121 TestVideoRenderer::Core::~Core() { |
89 DCHECK(thread_checker_.CalledOnValidThread()); | 122 DCHECK(thread_checker_.CalledOnValidThread()); |
90 } | 123 } |
91 | 124 |
92 void TestVideoRenderer::Core::Initialize() { | 125 void TestVideoRenderer::Core::Initialize() { |
93 DCHECK(thread_checker_.CalledOnValidThread()); | 126 DCHECK(thread_checker_.CalledOnValidThread()); |
127 | |
128 accumulating_rect_ = webrtc::DesktopRect::MakeLTRB(0, 0, 0, 0); | |
94 } | 129 } |
95 | 130 |
96 void TestVideoRenderer::Core::SetCodecForDecoding( | 131 void TestVideoRenderer::Core::SetCodecForDecoding( |
97 const protocol::ChannelConfig::Codec codec) { | 132 const protocol::ChannelConfig::Codec codec) { |
98 DCHECK(thread_checker_.CalledOnValidThread()); | 133 DCHECK(thread_checker_.CalledOnValidThread()); |
99 | 134 |
100 if (decoder_) { | 135 if (decoder_) { |
101 LOG(WARNING) << "Decoder is set more than once"; | 136 LOG(WARNING) << "Decoder is set more than once"; |
102 } | 137 } |
103 | 138 |
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
165 // Note that the |updated_region_| maintains the changed regions compared to | 200 // Note that the |updated_region_| maintains the changed regions compared to |
166 // previous video frame. | 201 // previous video frame. |
167 decoder_->RenderFrame(screen_size_, | 202 decoder_->RenderFrame(screen_size_, |
168 webrtc::DesktopRect::MakeWH(screen_size_.width(), | 203 webrtc::DesktopRect::MakeWH(screen_size_.width(), |
169 screen_size_.height()), buffer_->data(), | 204 screen_size_.height()), buffer_->data(), |
170 buffer_->stride(), &updated_region_); | 205 buffer_->stride(), &updated_region_); |
171 } | 206 } |
172 | 207 |
173 main_task_runner_->PostTask(FROM_HERE, done); | 208 main_task_runner_->PostTask(FROM_HERE, done); |
174 | 209 |
175 // TODO(liaoyuke): Update |accumulating_rect_| and |accumulating_color_|, then | 210 // Check to see if a image pattern matched reply is passed in. |
176 // compare to the expected image pattern to check whether the pattern is | 211 if (image_pattern_matched_callback_.is_null()) { |
177 // matched or not and update |image_pattern_matched| accordingly. | 212 return; |
213 } | |
214 | |
215 for (webrtc::DesktopRegion::Iterator dirty_region(updated_region_); | |
216 !dirty_region.IsAtEnd(); dirty_region.Advance()) { | |
217 webrtc::DesktopRect dirty_rect = dirty_region.rect(); | |
218 | |
219 // Dirty rects not falling within |expected_rect_| doesn't affect the | |
220 // result. | |
221 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
| |
222 MergeRectToAccumulatingRect(dirty_rect); | |
223 } | |
224 } | |
225 | |
226 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!
| |
227 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
| |
228 base::ResetAndReturn(&image_pattern_matched_callback_).Run(); | |
229 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.
| |
230 } | |
178 } | 231 } |
179 | 232 |
180 void TestVideoRenderer::Core::SetImagePatternAndMatchedCallback( | 233 void TestVideoRenderer::Core::SetImagePatternAndMatchedCallback( |
181 const webrtc::DesktopRect& expected_rect, | 234 const webrtc::DesktopRect& expected_rect, |
182 const RgbaColor& expected_color, | 235 const RGBA32& expected_color, |
183 const base::Closure& image_pattern_matched_callback) { | 236 const base::Closure& image_pattern_matched_callback) { |
184 DCHECK(thread_checker_.CalledOnValidThread()); | 237 DCHECK(thread_checker_.CalledOnValidThread()); |
185 | 238 |
186 expected_rect_ = expected_rect; | 239 expected_rect_ = expected_rect; |
187 expected_color_ = expected_color; | 240 expected_color_.SetRGB(expected_color); |
188 image_pattern_matched_callback_ = image_pattern_matched_callback; | 241 image_pattern_matched_callback_ = image_pattern_matched_callback; |
189 } | 242 } |
190 | 243 |
244 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.
| |
245 const webrtc::DesktopRect& template_rect, | |
246 const webrtc::DesktopRect& candidate_rect) const { | |
247 DCHECK(thread_checker_.CalledOnValidThread()); | |
248 | |
249 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
| |
250 double fuzzy_limit_height = fuzzy_threshold_ * template_rect.height(); | |
251 | |
252 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!
| |
253 candidate_rect.top() >= template_rect.top() - fuzzy_limit_height && | |
254 candidate_rect.right() <= template_rect.right() + fuzzy_limit_width && | |
255 candidate_rect.bottom() <= template_rect.bottom() + fuzzy_limit_height; | |
256 } | |
257 | |
258 void TestVideoRenderer::Core::MergeRectToAccumulatingRect( | |
259 const webrtc::DesktopRect& rect) { | |
260 // If |rect| is the first dirty rect that falls within |expected_rect_|, | |
261 // simply assign |rect| to |accumulating_rect_|. | |
262 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.
| |
263 accumulating_rect_ = rect; | |
264 } else { | |
265 // Merge |rect| to obtain a larger |accumulating_rect_|. | |
266 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
| |
267 std::min(accumulating_rect_.left(), rect.left()), | |
268 std::min(accumulating_rect_.top(), rect.top()), | |
269 std::max(accumulating_rect_.right(), rect.right()), | |
270 std::max(accumulating_rect_.bottom(), rect.bottom())); | |
271 } | |
272 } | |
273 | |
274 void TestVideoRenderer::Core::UpdateAccumulatingColor() { | |
275 int red_sum = 0; | |
276 int green_sum = 0; | |
277 int blue_sum = 0; | |
278 | |
279 // Loop through pixels that fall within |accumulating_rect_| to obtain the | |
280 // average color. | |
281 for (int y = accumulating_rect_.top(); y < accumulating_rect_.bottom(); ++y) { | |
282 for (int x = accumulating_rect_.left(); x < accumulating_rect_.right(); | |
283 ++x) { | |
284 int offset = | |
285 (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.
| |
286 | |
287 // Pixels of decoded video frame are presented in ARGB format. | |
288 red_sum += buffer_->data()[offset + 2]; | |
289 green_sum += buffer_->data()[offset + 1]; | |
290 blue_sum += buffer_->data()[offset + 0]; | |
291 } | |
292 } | |
293 | |
294 int area = screen_size_.width() * screen_size_.height(); | |
295 accumulating_color_.SetRGB(red_sum / area, green_sum / area, blue_sum / area); | |
296 } | |
297 | |
298 bool TestVideoRenderer::Core::ExpectedColorIsMatched() const { | |
299 double error_sum_squares = 0; | |
300 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
| |
301 accumulating_color_.GetRed()); | |
302 double green_error = static_cast<double>(expected_color_.GetGreen() - | |
303 accumulating_color_.GetGreen()); | |
304 double blue_error = static_cast<double>(expected_color_.GetBlue() - | |
305 accumulating_color_.GetBlue()); | |
306 error_sum_squares = red_error * red_error + green_error * green_error + | |
307 blue_error * blue_error; | |
308 error_sum_squares /= (255.0 * 255.0); | |
309 | |
310 return sqrt(error_sum_squares / 3) < fuzzy_threshold_; | |
311 } | |
312 | |
313 bool TestVideoRenderer::Core::ExpectedImagePatternIsMatched() const { | |
314 // 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.
| |
315 return accumulating_rect_.width() && accumulating_rect_.height() && | |
316 ContainsRect(accumulating_rect_, expected_rect_) && | |
317 ContainsRect(expected_rect_, accumulating_rect_) && | |
318 ExpectedColorIsMatched(); | |
319 } | |
320 | |
191 TestVideoRenderer::TestVideoRenderer() | 321 TestVideoRenderer::TestVideoRenderer() |
192 : video_decode_thread_( | 322 : video_decode_thread_( |
193 new base::Thread("TestVideoRendererVideoDecodingThread")), | 323 new base::Thread("TestVideoRendererVideoDecodingThread")), |
194 weak_factory_(this) { | 324 weak_factory_(this) { |
195 DCHECK(thread_checker_.CalledOnValidThread()); | 325 DCHECK(thread_checker_.CalledOnValidThread()); |
196 | 326 |
197 core_.reset(new Core()); | 327 core_.reset(new Core()); |
198 if (!video_decode_thread_->Start()) { | 328 if (!video_decode_thread_->Start()) { |
199 LOG(ERROR) << "Cannot start TestVideoRenderer"; | 329 LOG(ERROR) << "Cannot start TestVideoRenderer"; |
200 } else { | 330 } else { |
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
268 } | 398 } |
269 | 399 |
270 scoped_ptr<webrtc::DesktopFrame> TestVideoRenderer::GetBufferForTest() const { | 400 scoped_ptr<webrtc::DesktopFrame> TestVideoRenderer::GetBufferForTest() const { |
271 DCHECK(thread_checker_.CalledOnValidThread()); | 401 DCHECK(thread_checker_.CalledOnValidThread()); |
272 | 402 |
273 return core_->GetBufferForTest(); | 403 return core_->GetBufferForTest(); |
274 } | 404 } |
275 | 405 |
276 void TestVideoRenderer::SetImagePatternAndMatchedCallback( | 406 void TestVideoRenderer::SetImagePatternAndMatchedCallback( |
277 const webrtc::DesktopRect& expected_rect, | 407 const webrtc::DesktopRect& expected_rect, |
278 const RgbaColor& expected_color, | 408 const RGBA32& expected_color, |
279 const base::Closure& image_pattern_matched_callback) { | 409 const base::Closure& image_pattern_matched_callback) { |
280 DCHECK(thread_checker_.CalledOnValidThread()); | 410 DCHECK(thread_checker_.CalledOnValidThread()); |
281 | 411 |
282 DVLOG(2) << "TestVideoRenderer::SetImagePatternAndMatchedCallback() Called"; | 412 DVLOG(2) << "TestVideoRenderer::SetImagePatternAndMatchedCallback() Called"; |
283 video_decode_task_runner_->PostTask( | 413 video_decode_task_runner_->PostTask( |
284 FROM_HERE, base::Bind(&Core::SetImagePatternAndMatchedCallback, | 414 FROM_HERE, base::Bind(&Core::SetImagePatternAndMatchedCallback, |
285 base::Unretained(core_.get()), expected_rect, | 415 base::Unretained(core_.get()), expected_rect, |
286 expected_color, image_pattern_matched_callback)); | 416 expected_color, image_pattern_matched_callback)); |
287 } | 417 } |
288 | 418 |
419 RGBColor::RGBColor(int red, int green, int blue) { | |
420 SetRGB(red, green, blue); | |
421 } | |
422 | |
423 int RGBColor::GetRed() const { | |
424 return (color_ >> 16) & 0xFF; | |
425 } | |
426 | |
427 int RGBColor::GetGreen() const { | |
428 return (color_ >> 8) & 0xFF; | |
429 } | |
430 | |
431 int RGBColor::GetBlue() const { | |
432 return color_ & 0xFF; | |
433 } | |
434 | |
435 void RGBColor::SetRGB(int red, int green, int blue) { | |
436 // if any of the channels is out of range [0, 255], simply take it as 0 or 255 | |
437 // accordingly. | |
438 color_ = 0xFF000000 | (std::max(0, std::min(red, 255)) << 16) | | |
439 (std::max(0, std::min(green, 255)) << 8) | | |
440 (std::max(0, std::min(blue, 255))); | |
441 } | |
442 | |
289 } // namespace test | 443 } // namespace test |
290 } // namespace remoting | 444 } // namespace remoting |
OLD | NEW |