Chromium Code Reviews| 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 // RGB Color triplets, and it can be converted to and from RGBA32. | |
| 23 struct RGBTriplets { | |
| 24 RGBTriplets(int red, int green, int blue) | |
| 25 : red_(red), green_(green), blue_(blue) {} | |
| 26 int red_; | |
|
Sergey Ulanov
2015/07/10 01:21:56
don't need _ suffix for struct fields. It's only f
liaoyuke
2015/07/10 16:22:30
Done.
| |
| 27 int green_; | |
| 28 int blue_; | |
| 29 }; | |
| 30 | |
| 31 namespace { | |
| 32 // Used to account for video frame resizing and lossy encoding. | |
| 33 const int kRectFuzzyThreshold = 2; | |
|
Sergey Ulanov
2015/07/10 01:21:56
Maybe call it something like kMaxPositionError? Do
liaoyuke
2015/07/10 16:22:30
Done.
| |
| 34 const double kColorFuzzyThreshold = 0.05; | |
|
Sergey Ulanov
2015/07/10 01:21:55
kMaxColorError?
Also document units.
liaoyuke
2015/07/10 16:22:30
Done.
| |
| 35 } // namespace | |
| 36 | |
| 18 namespace remoting { | 37 namespace remoting { |
| 19 namespace test { | 38 namespace test { |
| 20 | 39 |
|
liaoyuke
2015/07/10 16:22:30
Per discussion with Sergey, the dirty rects are no
| |
| 21 // Implements video decoding functionality. | 40 // Implements video decoding functionality. |
| 22 class TestVideoRenderer::Core { | 41 class TestVideoRenderer::Core { |
| 23 public: | 42 public: |
| 24 Core(); | 43 Core(); |
| 25 ~Core(); | 44 ~Core(); |
| 26 | 45 |
| 27 // Initializes the internal structures of the class. | 46 // Initializes the internal structures of the class. |
| 28 void Initialize(); | 47 void Initialize(); |
| 29 | 48 |
| 30 // Used to decode video packets. | 49 // Used to decode video packets. |
| 31 void ProcessVideoPacket(scoped_ptr<VideoPacket> packet, | 50 void ProcessVideoPacket(scoped_ptr<VideoPacket> packet, |
| 32 const base::Closure& done); | 51 const base::Closure& done); |
| 33 | 52 |
| 34 // Initialize a decoder to decode video packets. | 53 // Initialize a decoder to decode video packets. |
| 35 void SetCodecForDecoding(const protocol::ChannelConfig::Codec codec); | 54 void SetCodecForDecoding(const protocol::ChannelConfig::Codec codec); |
| 36 | 55 |
| 37 // Returns a copy of the current buffer. | 56 // Returns a copy of the current buffer. |
| 38 scoped_ptr<webrtc::DesktopFrame> GetBufferForTest() const; | 57 scoped_ptr<webrtc::DesktopFrame> GetBufferForTest() const; |
| 39 | 58 |
| 40 // Set expected image pattern for comparison and the callback will be called | 59 // Set expected image pattern for comparison and the callback will be called |
| 41 // when the pattern is matched. | 60 // when the pattern is matched. |
| 42 void SetImagePatternAndMatchedCallback( | 61 void SetImagePatternAndMatchedCallback( |
| 43 const webrtc::DesktopRect& expected_rect, | 62 const webrtc::DesktopRect& expected_rect, |
| 44 const RgbaColor& expected_color, | 63 const RGBA32& expected_avg_color, |
| 45 const base::Closure& image_pattern_matched_callback); | 64 const base::Closure& image_pattern_matched_callback); |
| 46 | 65 |
| 66 // Convert a RGB triplets to 32 bit RGBA. | |
| 67 static RGBA32 ConvertTripletsToRGBA32(const RGBTriplets& rgb_triplets); | |
| 68 | |
| 69 // Convert a 32 bit RGBA to a RGB triplets. | |
| 70 static RGBTriplets ConvertRGBA32ToTriplets(RGBA32 color); | |
| 71 | |
| 72 // Returns true if |candidate_rect| falls within |template_rect|, allowing for | |
| 73 // certain amount of error specified by a fuzzy threshold. | |
| 74 static bool ContainsRectWithErrorMargin( | |
| 75 const webrtc::DesktopRect& template_rect, | |
| 76 const webrtc::DesktopRect& candidate_rect, | |
| 77 int error_margin); | |
| 78 | |
| 47 private: | 79 private: |
| 80 // Expand the |accumulating_rect_| to a minimum rectangle that contains both | |
| 81 // |rect| and itself. | |
| 82 void MergeRectToAccumulatingRect(const webrtc::DesktopRect& rect); | |
| 83 | |
| 84 // Returns the average color value of pixels fall within |rect|. | |
| 85 RGBTriplets CalculateAverageColorTriplets( | |
| 86 const webrtc::DesktopRect& rect) const; | |
| 87 | |
| 88 // Compares |candidate_avg_triplets| to |expected_avg_color_|. | |
| 89 // Returns true if the root mean square of the errors in the R, G and B | |
| 90 // components does not exceed a fuzzy threshold. | |
| 91 bool ExpectedColorIsMatched(const RGBTriplets& candidate_avg_triplets) const; | |
| 92 | |
| 93 // Returns true if |expected_rect_| and |accumulating_rect_| contains each | |
| 94 // other and expected color is matched. | |
| 95 bool ExpectedImagePatternIsMatched( | |
| 96 const RGBTriplets& candidate_avg_triplets) const; | |
| 97 | |
| 48 // Used to ensure Core methods are called on the same thread. | 98 // Used to ensure Core methods are called on the same thread. |
| 49 base::ThreadChecker thread_checker_; | 99 base::ThreadChecker thread_checker_; |
| 50 | 100 |
| 51 // Used to decode video packets. | 101 // Used to decode video packets. |
| 52 scoped_ptr<VideoDecoder> decoder_; | 102 scoped_ptr<VideoDecoder> decoder_; |
| 53 | 103 |
| 54 // Updated region of the current desktop frame compared to previous one. | 104 // Updated region of the current desktop frame compared to previous one. |
| 55 webrtc::DesktopRegion updated_region_; | 105 webrtc::DesktopRegion updated_region_; |
| 56 | 106 |
| 57 // Screen size of the remote host. | 107 // Screen size of the remote host. |
| 58 webrtc::DesktopSize screen_size_; | 108 webrtc::DesktopSize screen_size_; |
| 59 | 109 |
| 60 // Used to post tasks back to main thread. | 110 // Used to post tasks back to main thread. |
| 61 scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_; | 111 scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_; |
| 62 | 112 |
| 63 // Used to store decoded video frame. | 113 // Used to store decoded video frame. |
| 64 scoped_ptr<webrtc::DesktopFrame> buffer_; | 114 scoped_ptr<webrtc::DesktopFrame> buffer_; |
| 65 | 115 |
| 66 // Protects access to |buffer_|. | 116 // Protects access to |buffer_|. |
| 67 mutable base::Lock lock_; | 117 mutable base::Lock lock_; |
| 68 | 118 |
| 69 // Used to store the expected image pattern. | 119 // Used to store the expected image pattern. |
| 70 webrtc::DesktopRect expected_rect_; | 120 webrtc::DesktopRect expected_rect_; |
| 71 RgbaColor expected_color_; | 121 RGBA32 expected_avg_color_; |
| 72 | 122 |
| 73 // Maintains accumulating image pattern. | 123 // Maintains accumulating image pattern. |
| 74 webrtc::DesktopRect accumulating_rect_; | 124 webrtc::DesktopRect accumulating_rect_; |
| 75 RgbaColor accumulating_color_; | |
| 76 | 125 |
| 77 // Used to store the callback when expected pattern is matched. | 126 // Used to store the callback when expected pattern is matched. |
| 78 base::Closure image_pattern_matched_callback_; | 127 base::Closure image_pattern_matched_callback_; |
| 79 | 128 |
| 80 DISALLOW_COPY_AND_ASSIGN(Core); | 129 DISALLOW_COPY_AND_ASSIGN(Core); |
| 81 }; | 130 }; |
| 82 | 131 |
| 83 TestVideoRenderer::Core::Core() | 132 TestVideoRenderer::Core::Core() |
| 84 : main_task_runner_(base::ThreadTaskRunnerHandle::Get()) { | 133 : main_task_runner_(base::ThreadTaskRunnerHandle::Get()) { |
| 85 thread_checker_.DetachFromThread(); | 134 thread_checker_.DetachFromThread(); |
| 86 } | 135 } |
| 87 | 136 |
| 88 TestVideoRenderer::Core::~Core() { | 137 TestVideoRenderer::Core::~Core() { |
| 89 DCHECK(thread_checker_.CalledOnValidThread()); | 138 DCHECK(thread_checker_.CalledOnValidThread()); |
| 90 } | 139 } |
| 91 | 140 |
| 92 void TestVideoRenderer::Core::Initialize() { | 141 void TestVideoRenderer::Core::Initialize() { |
| 93 DCHECK(thread_checker_.CalledOnValidThread()); | 142 DCHECK(thread_checker_.CalledOnValidThread()); |
| 143 | |
| 144 accumulating_rect_ = webrtc::DesktopRect(); | |
| 94 } | 145 } |
| 95 | 146 |
| 96 void TestVideoRenderer::Core::SetCodecForDecoding( | 147 void TestVideoRenderer::Core::SetCodecForDecoding( |
| 97 const protocol::ChannelConfig::Codec codec) { | 148 const protocol::ChannelConfig::Codec codec) { |
| 98 DCHECK(thread_checker_.CalledOnValidThread()); | 149 DCHECK(thread_checker_.CalledOnValidThread()); |
| 99 | 150 |
| 100 if (decoder_) { | 151 if (decoder_) { |
| 101 LOG(WARNING) << "Decoder is set more than once"; | 152 LOG(WARNING) << "Decoder is set more than once"; |
| 102 } | 153 } |
| 103 | 154 |
| (...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 | 216 // Note that the |updated_region_| maintains the changed regions compared to |
| 166 // previous video frame. | 217 // previous video frame. |
| 167 decoder_->RenderFrame(screen_size_, | 218 decoder_->RenderFrame(screen_size_, |
| 168 webrtc::DesktopRect::MakeWH(screen_size_.width(), | 219 webrtc::DesktopRect::MakeWH(screen_size_.width(), |
| 169 screen_size_.height()), buffer_->data(), | 220 screen_size_.height()), buffer_->data(), |
| 170 buffer_->stride(), &updated_region_); | 221 buffer_->stride(), &updated_region_); |
| 171 } | 222 } |
| 172 | 223 |
| 173 main_task_runner_->PostTask(FROM_HERE, done); | 224 main_task_runner_->PostTask(FROM_HERE, done); |
| 174 | 225 |
| 175 // TODO(liaoyuke): Update |accumulating_rect_| and |accumulating_color_|, then | 226 // 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 | 227 if (image_pattern_matched_callback_.is_null()) { |
| 177 // matched or not and update |image_pattern_matched| accordingly. | 228 return; |
| 229 } | |
| 230 | |
| 231 for (webrtc::DesktopRegion::Iterator dirty_region(updated_region_); | |
| 232 !dirty_region.IsAtEnd(); dirty_region.Advance()) { | |
| 233 webrtc::DesktopRect dirty_rect = dirty_region.rect(); | |
| 234 | |
| 235 // Dirty rects not falling within |expected_rect_| doesn't affect the | |
| 236 // result. | |
| 237 if (ContainsRectWithErrorMargin(expected_rect_, dirty_rect, | |
| 238 kRectFuzzyThreshold)) { | |
| 239 MergeRectToAccumulatingRect(dirty_rect); | |
| 240 } | |
| 241 } | |
| 242 | |
| 243 if (accumulating_rect_.is_empty()) { | |
| 244 return; | |
| 245 } | |
| 246 | |
| 247 RGBTriplets accumulating_avg_triplets = | |
| 248 CalculateAverageColorTriplets(accumulating_rect_); | |
| 249 if (ExpectedImagePatternIsMatched(accumulating_avg_triplets)) { | |
| 250 base::ResetAndReturn(&image_pattern_matched_callback_).Run(); | |
| 251 accumulating_rect_ = webrtc::DesktopRect(); | |
| 252 } | |
| 178 } | 253 } |
| 179 | 254 |
| 180 void TestVideoRenderer::Core::SetImagePatternAndMatchedCallback( | 255 void TestVideoRenderer::Core::SetImagePatternAndMatchedCallback( |
| 181 const webrtc::DesktopRect& expected_rect, | 256 const webrtc::DesktopRect& expected_rect, |
| 182 const RgbaColor& expected_color, | 257 const RGBA32& expected_avg_color, |
| 183 const base::Closure& image_pattern_matched_callback) { | 258 const base::Closure& image_pattern_matched_callback) { |
| 184 DCHECK(thread_checker_.CalledOnValidThread()); | 259 DCHECK(thread_checker_.CalledOnValidThread()); |
| 185 | 260 |
| 186 expected_rect_ = expected_rect; | 261 expected_rect_ = expected_rect; |
| 187 expected_color_ = expected_color; | 262 expected_avg_color_ = expected_avg_color; |
| 188 image_pattern_matched_callback_ = image_pattern_matched_callback; | 263 image_pattern_matched_callback_ = image_pattern_matched_callback; |
| 189 } | 264 } |
| 190 | 265 |
| 266 RGBA32 TestVideoRenderer::Core::ConvertTripletsToRGBA32( | |
| 267 const RGBTriplets& rgb_triplets) { | |
| 268 return 0xFF000000 | (rgb_triplets.red_ << 16) | (rgb_triplets.green_ << 8) | | |
| 269 rgb_triplets.blue_; | |
| 270 } | |
| 271 | |
| 272 RGBTriplets TestVideoRenderer::Core::ConvertRGBA32ToTriplets(RGBA32 color) { | |
| 273 RGBTriplets rgb_triplets((color >> 16) & 0xFF, (color >> 8) & 0xFF, | |
| 274 color & 0xFF); | |
| 275 return rgb_triplets; | |
| 276 } | |
| 277 | |
| 278 bool TestVideoRenderer::Core::ContainsRectWithErrorMargin( | |
| 279 const webrtc::DesktopRect& template_rect, | |
| 280 const webrtc::DesktopRect& candidate_rect, | |
| 281 int error_margin) { | |
| 282 return template_rect.ContainsRect(webrtc::DesktopRect::MakeLTRB( | |
| 283 candidate_rect.left() + error_margin, candidate_rect.top() + error_margin, | |
| 284 candidate_rect.right() - error_margin, | |
| 285 candidate_rect.bottom() - error_margin)); | |
| 286 } | |
| 287 | |
| 288 void TestVideoRenderer::Core::MergeRectToAccumulatingRect( | |
| 289 const webrtc::DesktopRect& rect) { | |
| 290 // If |rect| is the first dirty rect that falls within |expected_rect_|, | |
| 291 // simply assign |rect| to |accumulating_rect_|. | |
| 292 if (accumulating_rect_.is_empty()) { | |
| 293 accumulating_rect_ = rect; | |
| 294 } else { | |
| 295 // Merge |rect| to obtain a larger |accumulating_rect_|. | |
| 296 accumulating_rect_ = webrtc::DesktopRect::MakeLTRB( | |
| 297 std::min(accumulating_rect_.left(), rect.left()), | |
| 298 std::min(accumulating_rect_.top(), rect.top()), | |
| 299 std::max(accumulating_rect_.right(), rect.right()), | |
| 300 std::max(accumulating_rect_.bottom(), rect.bottom())); | |
| 301 } | |
| 302 } | |
| 303 | |
| 304 RGBTriplets TestVideoRenderer::Core::CalculateAverageColorTriplets( | |
| 305 const webrtc::DesktopRect& rect) const { | |
| 306 int red_sum = 0; | |
| 307 int green_sum = 0; | |
| 308 int blue_sum = 0; | |
| 309 | |
| 310 // Loop through pixels that fall within |accumulating_rect_| to obtain the | |
| 311 // average color triplets. | |
| 312 for (int y = rect.top(); y < rect.bottom(); ++y) { | |
| 313 uint8_t* ptr = | |
|
Sergey Ulanov
2015/07/10 01:21:56
|ptr| is not the best name. https://google-stylegu
liaoyuke
2015/07/10 16:22:30
Done.
| |
| 314 buffer_->data() + (y * buffer_->stride() + | |
| 315 rect.left() * webrtc::DesktopFrame::kBytesPerPixel); | |
| 316 | |
| 317 // Pixels of decoded video frame are presented in ARGB format. | |
| 318 for (int x = 0; x < rect.width(); ++x) { | |
| 319 red_sum += *(ptr + 2); | |
|
Sergey Ulanov
2015/07/10 01:21:55
These can be ptr[2], ptr[1] and ptr[0]
liaoyuke
2015/07/10 16:22:30
Done.
| |
| 320 green_sum += *(ptr + 1); | |
| 321 blue_sum += *(ptr + 0); | |
| 322 ptr += 4; | |
| 323 } | |
| 324 } | |
| 325 | |
| 326 int area = screen_size_.width() * screen_size_.height(); | |
| 327 RGBTriplets rgb_triplets(red_sum / area, green_sum / area, blue_sum / area); | |
| 328 return rgb_triplets; | |
| 329 } | |
| 330 | |
| 331 bool TestVideoRenderer::Core::ExpectedColorIsMatched( | |
| 332 const RGBTriplets& candidate_avg_triplets) const { | |
| 333 RGBTriplets expected_avg_triplets = | |
| 334 ConvertRGBA32ToTriplets(expected_avg_color_); | |
| 335 double error_sum_squares = 0; | |
| 336 double red_error = expected_avg_triplets.red_ - candidate_avg_triplets.red_; | |
| 337 double green_error = | |
| 338 expected_avg_triplets.green_ - candidate_avg_triplets.green_; | |
| 339 double blue_error = | |
| 340 expected_avg_triplets.blue_ - candidate_avg_triplets.blue_; | |
| 341 error_sum_squares = red_error * red_error + green_error * green_error + | |
| 342 blue_error * blue_error; | |
| 343 error_sum_squares /= (255.0 * 255.0); | |
| 344 | |
| 345 return sqrt(error_sum_squares / 3) < kColorFuzzyThreshold; | |
| 346 } | |
| 347 | |
| 348 bool TestVideoRenderer::Core::ExpectedImagePatternIsMatched( | |
| 349 const RGBTriplets& candidate_avg_triplets) const { | |
| 350 // |accumulating_rect_| must be valid in order to be matched. | |
| 351 return accumulating_rect_.width() && accumulating_rect_.height() && | |
| 352 ContainsRectWithErrorMargin(accumulating_rect_, expected_rect_, | |
| 353 kRectFuzzyThreshold) && | |
| 354 ContainsRectWithErrorMargin(expected_rect_, accumulating_rect_, | |
| 355 kRectFuzzyThreshold) && | |
| 356 ExpectedColorIsMatched(candidate_avg_triplets); | |
| 357 } | |
| 358 | |
| 191 TestVideoRenderer::TestVideoRenderer() | 359 TestVideoRenderer::TestVideoRenderer() |
| 192 : video_decode_thread_( | 360 : video_decode_thread_( |
| 193 new base::Thread("TestVideoRendererVideoDecodingThread")), | 361 new base::Thread("TestVideoRendererVideoDecodingThread")), |
| 194 weak_factory_(this) { | 362 weak_factory_(this) { |
| 195 DCHECK(thread_checker_.CalledOnValidThread()); | 363 DCHECK(thread_checker_.CalledOnValidThread()); |
| 196 | 364 |
| 197 core_.reset(new Core()); | 365 core_.reset(new Core()); |
| 198 if (!video_decode_thread_->Start()) { | 366 if (!video_decode_thread_->Start()) { |
| 199 LOG(ERROR) << "Cannot start TestVideoRenderer"; | 367 LOG(ERROR) << "Cannot start TestVideoRenderer"; |
| 200 } else { | 368 } else { |
| (...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 268 } | 436 } |
| 269 | 437 |
| 270 scoped_ptr<webrtc::DesktopFrame> TestVideoRenderer::GetBufferForTest() const { | 438 scoped_ptr<webrtc::DesktopFrame> TestVideoRenderer::GetBufferForTest() const { |
| 271 DCHECK(thread_checker_.CalledOnValidThread()); | 439 DCHECK(thread_checker_.CalledOnValidThread()); |
| 272 | 440 |
| 273 return core_->GetBufferForTest(); | 441 return core_->GetBufferForTest(); |
| 274 } | 442 } |
| 275 | 443 |
| 276 void TestVideoRenderer::SetImagePatternAndMatchedCallback( | 444 void TestVideoRenderer::SetImagePatternAndMatchedCallback( |
| 277 const webrtc::DesktopRect& expected_rect, | 445 const webrtc::DesktopRect& expected_rect, |
| 278 const RgbaColor& expected_color, | 446 const RGBA32& expected_avg_color, |
| 279 const base::Closure& image_pattern_matched_callback) { | 447 const base::Closure& image_pattern_matched_callback) { |
| 280 DCHECK(thread_checker_.CalledOnValidThread()); | 448 DCHECK(thread_checker_.CalledOnValidThread()); |
| 281 | 449 |
| 282 DVLOG(2) << "TestVideoRenderer::SetImagePatternAndMatchedCallback() Called"; | 450 DVLOG(2) << "TestVideoRenderer::SetImagePatternAndMatchedCallback() Called"; |
| 283 video_decode_task_runner_->PostTask( | 451 video_decode_task_runner_->PostTask( |
| 284 FROM_HERE, base::Bind(&Core::SetImagePatternAndMatchedCallback, | 452 FROM_HERE, |
| 285 base::Unretained(core_.get()), expected_rect, | 453 base::Bind(&Core::SetImagePatternAndMatchedCallback, |
| 286 expected_color, image_pattern_matched_callback)); | 454 base::Unretained(core_.get()), expected_rect, |
| 455 expected_avg_color, image_pattern_matched_callback)); | |
| 287 } | 456 } |
| 288 | 457 |
| 289 } // namespace test | 458 } // namespace test |
| 290 } // namespace remoting | 459 } // namespace remoting |
| OLD | NEW |