diff options
-rw-r--r-- | media/base/yuv_convert.cc | 5 | ||||
-rw-r--r-- | remoting/base/decoder_vp8.cc | 102 | ||||
-rw-r--r-- | remoting/base/decoder_vp8.h | 13 | ||||
-rw-r--r-- | remoting/base/util.cc | 175 | ||||
-rw-r--r-- | remoting/base/util.h | 43 | ||||
-rw-r--r-- | remoting/base/util_unittest.cc | 187 | ||||
-rw-r--r-- | remoting/remoting.gyp | 1 |
7 files changed, 399 insertions, 127 deletions
diff --git a/media/base/yuv_convert.cc b/media/base/yuv_convert.cc index 8d5073b..ed9bed4 100644 --- a/media/base/yuv_convert.cc +++ b/media/base/yuv_convert.cc @@ -335,8 +335,9 @@ void ScaleYUVToRGB32WithRect(const uint8* y_buf, // Determine the parts of the Y, U and V buffers to interpolate. int source_y_left = source_left >> kFractionBits; - int source_y_right = (source_right >> kFractionBits) + 2; - DCHECK(source_y_right <= source_width); + int source_y_right = std::min( + (source_right >> kFractionBits) + 2, + source_width + 1); int source_uv_left = source_y_left / 2; int source_uv_right = std::min( diff --git a/remoting/base/decoder_vp8.cc b/remoting/base/decoder_vp8.cc index 40746913..fc6670f 100644 --- a/remoting/base/decoder_vp8.cc +++ b/remoting/base/decoder_vp8.cc @@ -142,71 +142,17 @@ void DecoderVp8::RefreshRegion(const SkRegion& region) { if (output_size_.height() > static_cast<int>(frame_->height())) output_size_.set(output_size_.width(), frame_->height()); - if (!DoScaling()) { - ConvertRegion(region, &updated_region_); - } else { - ScaleAndConvertRegion(region, &updated_region_); - } -} - -bool DecoderVp8::DoScaling() const { - DCHECK(last_image_); - return !output_size_.equals(last_image_->d_w, last_image_->d_h); -} - -void DecoderVp8::ConvertRegion(const SkRegion& input_region, - SkRegion* output_region) { if (!last_image_) return; - output_region->setEmpty(); + updated_region_.setEmpty(); // Clip based on both the output dimensions and Pepper clip rect. - // ConvertYUVToRGB32WithRect() requires even X and Y coordinates, so we align - // |clip_rect| to prevent clipping from breaking alignment. We then clamp it - // to the image dimensions, which may lead to odd width & height, which we - // can cope with. + // ConvertAndScaleYUVToRGB32Rect() requires even X and Y coordinates, so we + // align |clip_rect| to prevent clipping from breaking alignment. We then + // clamp it to the image dimensions, which may lead to odd width & height, + // which we can cope with. SkIRect clip_rect = AlignRect(clip_rect_); - if (!clip_rect.intersect(SkIRect::MakeWH(last_image_->d_w, last_image_->d_h))) - return; - - uint8* output_rgb_buf = frame_->data(media::VideoFrame::kRGBPlane); - const int output_stride = frame_->stride(media::VideoFrame::kRGBPlane); - - for (SkRegion::Iterator i(input_region); !i.done(); i.next()) { - // Align the rectangle so the top-left coordinates are even, for - // ConvertYUVToRGB32WithRect(). - SkIRect dest_rect(AlignRect(i.rect())); - - // Clip the rectangle, preserving alignment since |clip_rect| is aligned. - if (!dest_rect.intersect(clip_rect)) - continue; - - ConvertYUVToRGB32WithRect(last_image_->planes[0], - last_image_->planes[1], - last_image_->planes[2], - output_rgb_buf, - dest_rect, - last_image_->stride[0], - last_image_->stride[1], - output_stride); - - output_region->op(dest_rect, SkRegion::kUnion_Op); - } -} - -void DecoderVp8::ScaleAndConvertRegion(const SkRegion& input_region, - SkRegion* output_region) { - if (!last_image_) - return; - - DCHECK(output_size_.width() <= static_cast<int>(frame_->width())); - DCHECK(output_size_.height() <= static_cast<int>(frame_->height())); - - output_region->setEmpty(); - - // Clip based on both the output dimensions and Pepper clip rect. - SkIRect clip_rect = clip_rect_; if (!clip_rect.intersect(SkIRect::MakeSize(output_size_))) return; @@ -214,30 +160,30 @@ void DecoderVp8::ScaleAndConvertRegion(const SkRegion& input_region, uint8* output_rgb_buf = frame_->data(media::VideoFrame::kRGBPlane); const int output_stride = frame_->stride(media::VideoFrame::kRGBPlane); - for (SkRegion::Iterator i(input_region); !i.done(); i.next()) { + for (SkRegion::Iterator i(region); !i.done(); i.next()) { // Determine the scaled area affected by this rectangle changing. - SkIRect output_rect = ScaleRect(i.rect(), image_size, output_size_); + // Align the rectangle so the top-left coordinates are even, for + // ConvertAndScaleYUVToRGB32Rect(). + SkIRect output_rect = ScaleRect(AlignRect(i.rect()), + image_size, output_size_); if (!output_rect.intersect(clip_rect)) continue; // The scaler will not to read outside the input dimensions. - media::ScaleYUVToRGB32WithRect(last_image_->planes[0], - last_image_->planes[1], - last_image_->planes[2], - output_rgb_buf, - image_size.width(), - image_size.height(), - output_size_.width(), - output_size_.height(), - output_rect.x(), - output_rect.y(), - output_rect.right(), - output_rect.bottom(), - last_image_->stride[0], - last_image_->stride[1], - output_stride); - - output_region->op(output_rect, SkRegion::kUnion_Op); + ConvertAndScaleYUVToRGB32Rect(last_image_->planes[0], + last_image_->planes[1], + last_image_->planes[2], + last_image_->stride[0], + last_image_->stride[1], + image_size, + SkIRect::MakeSize(image_size), + output_rgb_buf, + output_stride, + output_size_, + SkIRect::MakeSize(output_size_), + output_rect); + + updated_region_.op(output_rect, SkRegion::kUnion_Op); } } diff --git a/remoting/base/decoder_vp8.h b/remoting/base/decoder_vp8.h index 76fd547..0549f03 100644 --- a/remoting/base/decoder_vp8.h +++ b/remoting/base/decoder_vp8.h @@ -35,19 +35,6 @@ class DecoderVp8 : public Decoder { kError, }; - // Return true if scaling is enabled - bool DoScaling() const; - - // Perform color space conversion on the specified region. - // Writes the updated region to |output_region|. - void ConvertRegion(const SkRegion& region, - SkRegion* output_region); - - // Perform scaling and color space conversion on the specified - // region. Writes the updated rectangles to |output_region|. - void ScaleAndConvertRegion(const SkRegion& region, - SkRegion* output_region); - // The internal state of the decoder. State state_; diff --git a/remoting/base/util.cc b/remoting/base/util.cc index 486b6a3..30691ea 100644 --- a/remoting/base/util.cc +++ b/remoting/base/util.cc @@ -11,6 +11,7 @@ #include "base/time.h" #include "media/base/video_frame.h" #include "media/base/yuv_convert.h" +#include "third_party/skia/include/core/SkRegion.h" using media::VideoFrame; @@ -48,38 +49,15 @@ static int CalculateRGBOffset(int x, int y, int stride) { } static int CalculateYOffset(int x, int y, int stride) { + DCHECK(((x & 1) == 0) && ((y & 1) == 0)); return stride * y + x; } static int CalculateUVOffset(int x, int y, int stride) { + DCHECK(((x & 1) == 0) && ((y & 1) == 0)); return stride * y / 2 + x / 2; } -void ConvertYUVToRGB32WithRect(const uint8* y_plane, - const uint8* u_plane, - const uint8* v_plane, - uint8* rgb_plane, - const SkIRect& rect, - int y_stride, - int uv_stride, - int rgb_stride) { - DCHECK((rect.x() & 1) == 0 && (rect.y() & 1) == 0); - int rgb_offset = CalculateRGBOffset(rect.left(), rect.top(), rgb_stride); - int y_offset = CalculateYOffset(rect.left(), rect.top(), y_stride); - int uv_offset = CalculateUVOffset(rect.left(), rect.top(), uv_stride); - - media::ConvertYUVToRGB32(y_plane + y_offset, - u_plane + uv_offset, - v_plane + uv_offset, - rgb_plane + rgb_offset, - rect.width(), - rect.height(), - y_stride, - uv_stride, - rgb_stride, - media::YV12); -} - void ConvertRGB32ToYUVWithRect(const uint8* rgb_plane, uint8* y_plane, uint8* u_plane, @@ -106,6 +84,126 @@ void ConvertRGB32ToYUVWithRect(const uint8* rgb_plane, uv_stride); } +void ConvertAndScaleYUVToRGB32Rect(const uint8* source_yplane, + const uint8* source_uplane, + const uint8* source_vplane, + int source_ystride, + int source_uvstride, + const SkISize& source_size, + const SkIRect& source_buffer_rect, + uint8* dest_buffer, + int dest_stride, + const SkISize& dest_size, + const SkIRect& dest_buffer_rect, + const SkIRect& dest_rect) { + // N.B. It is caller's responsibility to check if strides are large enough. We + // cannot do it here anyway. + DCHECK(SkIRect::MakeSize(source_size).contains(source_buffer_rect)); + DCHECK(SkIRect::MakeSize(dest_size).contains(dest_buffer_rect)); + DCHECK(dest_buffer_rect.contains(dest_rect)); + DCHECK(ScaleRect(source_buffer_rect, source_size, dest_size). + contains(dest_rect)); + + // If the source and/or destination buffers don't start at (0, 0) + // offset the pointers to pretend we have complete buffers. + int y_offset = - CalculateYOffset(source_buffer_rect.x(), + source_buffer_rect.y(), + source_ystride); + int uv_offset = - CalculateUVOffset(source_buffer_rect.x(), + source_buffer_rect.y(), + source_uvstride); + int rgb_offset = - CalculateRGBOffset(dest_buffer_rect.x(), + dest_buffer_rect.y(), + dest_stride); + + // See if scaling is needed. + if (source_size == dest_size) { + // Calculate the inner rectangle that can be copied by the optimized + // ConvertYUVToRGB32(). + SkIRect inner_rect = + SkIRect::MakeLTRB(RoundToTwosMultiple(dest_rect.left() + 1), + RoundToTwosMultiple(dest_rect.top() + 1), + dest_rect.right(), + dest_rect.bottom()); + + // Offset pointers to point to the top left corner of the inner rectangle. + y_offset += CalculateYOffset(inner_rect.x(), inner_rect.y(), + source_ystride); + uv_offset += CalculateUVOffset(inner_rect.x(), inner_rect.y(), + source_uvstride); + rgb_offset += CalculateRGBOffset(inner_rect.x(), inner_rect.y(), + dest_stride); + + media::ConvertYUVToRGB32(source_yplane + y_offset, + source_uplane + uv_offset, + source_vplane + uv_offset, + dest_buffer + rgb_offset, + inner_rect.width(), + inner_rect.height(), + source_ystride, + source_uvstride, + dest_stride, + media::YV12); + + // Now see if some pixels weren't copied due to alignment. + if (dest_rect != inner_rect) { + SkIRect outer_rect = + SkIRect::MakeLTRB(RoundToTwosMultiple(dest_rect.left()), + RoundToTwosMultiple(dest_rect.top()), + dest_rect.right(), + dest_rect.bottom()); + + SkIPoint offset = SkIPoint::Make(outer_rect.x() - inner_rect.x(), + outer_rect.y() - inner_rect.y()); + + // Offset the pointers to point to the top left corner of the outer + // rectangle. + y_offset += CalculateYOffset(offset.x(), offset.y(), source_ystride); + uv_offset += CalculateUVOffset(offset.x(), offset.y(), source_uvstride); + rgb_offset += CalculateRGBOffset(offset.x(), offset.y(), dest_stride); + + // Draw unaligned edges. + SkRegion edges(dest_rect); + edges.op(inner_rect, SkRegion::kDifference_Op); + for (SkRegion::Iterator i(edges); !i.done(); i.next()) { + SkIRect rect(i.rect()); + rect.offset(- outer_rect.left(), - outer_rect.top()); + media::ScaleYUVToRGB32WithRect(source_yplane + y_offset, + source_uplane + uv_offset, + source_vplane + uv_offset, + dest_buffer + rgb_offset, + source_size.width(), + source_size.height(), + dest_size.width(), + dest_size.height(), + rect.left(), + rect.top(), + rect.right(), + rect.bottom(), + source_ystride, + source_uvstride, + dest_stride); + } + } + } else { + media::ScaleYUVToRGB32WithRect(source_yplane + y_offset, + source_uplane + uv_offset, + source_vplane + uv_offset, + dest_buffer + rgb_offset, + source_size.width(), + source_size.height(), + dest_size.width(), + dest_size.height(), + dest_rect.left(), + dest_rect.top(), + dest_rect.right(), + dest_rect.bottom(), + source_ystride, + source_uvstride, + dest_stride); + } +} + int RoundToTwosMultiple(int x) { return x & (~1); } @@ -153,4 +251,31 @@ void CopyRect(const uint8* src_plane, } } +void CopyRGB32Rect(const uint8* source_buffer, + int source_stride, + const SkIRect& source_buffer_rect, + uint8* dest_buffer, + int dest_stride, + const SkIRect& dest_buffer_rect, + const SkIRect& dest_rect) { + DCHECK(dest_buffer_rect.contains(dest_rect)); + DCHECK(source_buffer_rect.contains(dest_rect)); + + // Get the address of the starting point. + int source_offset = CalculateRGBOffset(dest_rect.x() - source_buffer_rect.x(), + dest_rect.y() - source_buffer_rect.y(), + source_stride); + int dest_offset = CalculateRGBOffset(dest_rect.x() - dest_buffer_rect.x(), + dest_rect.y() - dest_buffer_rect.y(), + source_stride); + + // Copy bits. + CopyRect(source_buffer + source_offset, + source_stride, + dest_buffer + dest_offset, + dest_stride, + GetBytesPerPixel(media::VideoFrame::RGB32), + SkIRect::MakeWH(dest_rect.width(), dest_rect.height())); +} + } // namespace remoting diff --git a/remoting/base/util.h b/remoting/base/util.h index c05ef2f..c4621cc 100644 --- a/remoting/base/util.h +++ b/remoting/base/util.h @@ -18,16 +18,33 @@ std::string GetTimestampString(); // TODO(sergeyu): Move these methods to media. int GetBytesPerPixel(media::VideoFrame::Format format); -// Convert YUV to RGB32 on a specific rectangle. -void ConvertYUVToRGB32WithRect(const uint8* y_plane, - const uint8* u_plane, - const uint8* v_plane, - uint8* rgb_plane, - const SkIRect& rect, - int y_stride, - int uv_stride, - int rgb_stride); +// Convert and scale YUV to RGB32 on a specific rectangle. The source and +// destination buffers are assumed to contain only |source_buffer_rect| and +// |dest_buffer_rect| areas correspondingly. The scaling factor is determined +// as ratio between |dest_size| and |source_size|. The target rectangle +// |dect_rect| is specified in the destination coordinates. +// +// |source_buffer_rect| and |dest_buffer_rect| must fall entirely within +// the source and destination dimensions, respectively. |dest_rect| must be +// completely contained within the source and destinations buffers boundaries +// including the case when scaling is requested. +// +// N.B. The top left corner coordinates of YUV buffer should have even X and Y +// coordinates. +void ConvertAndScaleYUVToRGB32Rect(const uint8* source_yplane, + const uint8* source_uplane, + const uint8* source_vplane, + int source_ystride, + int source_uvstride, + const SkISize& source_size, + const SkIRect& source_buffer_rect, + uint8* dest_buffer, + int dest_stride, + const SkISize& dest_size, + const SkIRect& dest_buffer_rect, + const SkIRect& dest_rect); +// Convert RGB32 to YUV on a specific rectangle. void ConvertRGB32ToYUVWithRect(const uint8* rgb_plane, uint8* y_plane, uint8* u_plane, @@ -60,6 +77,14 @@ void CopyRect(const uint8* src_plane, int bytes_per_pixel, const SkIRect& rect); +void CopyRGB32Rect(const uint8* source_buffer, + int source_stride, + const SkIRect& source_buffer_rect, + uint8* dest_buffer, + int dest_stride, + const SkIRect& dest_buffer_rect, + const SkIRect& dest_rect); + } // namespace remoting #endif // REMOTING_BASE_UTIL_H_ diff --git a/remoting/base/util_unittest.cc b/remoting/base/util_unittest.cc new file mode 100644 index 0000000..24e628d --- /dev/null +++ b/remoting/base/util_unittest.cc @@ -0,0 +1,187 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <algorithm> + +#include "remoting/base/util.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkRect.h" +#include "third_party/skia/include/core/SkSize.h" + +static const int kWidth = 32 ; +static const int kHeight = 24 ; +static const int kBytesPerPixel = 4; +static const int kYStride = kWidth; +static const int kUvStride = kWidth / 2; +static const int kRgbStride = kWidth * kBytesPerPixel; +static const uint32 kFillColor = 0xffffff; + +namespace remoting { + +class YuvToRgbTester { + public: + YuvToRgbTester() { + yuv_buffer_size_ = (kYStride + kUvStride) * kHeight; + yuv_buffer_.reset(new uint8[yuv_buffer_size_]); + yplane_ = yuv_buffer_.get(); + uplane_ = yplane_ + (kYStride * kHeight); + vplane_ = uplane_ + (kUvStride * kHeight / 2); + + rgb_buffer_size_ = kWidth * kHeight * kBytesPerPixel; + rgb_buffer_.reset(new uint8[rgb_buffer_size_]); + + ResetYuvBuffer(); + ResetRgbBuffer(); + } + + ~YuvToRgbTester() {} + + void ResetYuvBuffer() { + memset(yuv_buffer_.get(), 0, yuv_buffer_size_); + } + + void ResetRgbBuffer() { + memset(rgb_buffer_.get(), 0, rgb_buffer_size_); + } + + void FillRgbBuffer(const SkIRect& rect) { + uint32* ptr = reinterpret_cast<uint32*>( + rgb_buffer_.get() + (rect.top() * kRgbStride) + + (rect.left() * kBytesPerPixel)); + int width = rect.width(); + for (int height = rect.height(); height > 0; --height) { + std::fill(ptr, ptr + width, kFillColor); + ptr += kRgbStride / kBytesPerPixel; + } + } + + // Check the the desination buffer is filled within expected bounds. + void CheckRgbBuffer(const SkIRect& rect) { + uint32* ptr = reinterpret_cast<uint32*>(rgb_buffer_.get()); + for (int y = 0; y < kHeight; ++y) { + if (y < rect.top() || rect.bottom() <= y) { + // The whole line should be intact. + EXPECT_EQ((ptrdiff_t)kWidth, + std::count(ptr, ptr + kWidth, 0u)); + } else { + // The space before the painted rectangle should be intact. + EXPECT_EQ((ptrdiff_t)rect.left(), + std::count(ptr, ptr + rect.left(), 0u)); + + // All pixels of the target rectangle should be touched. + EXPECT_EQ(ptr + rect.right(), + std::find(ptr + rect.left(), ptr + rect.right(), 0u)); + + // The space after the painted rectangle should be intact. + EXPECT_EQ((ptrdiff_t)kWidth - rect.right(), + std::count(ptr + rect.right(), ptr + kWidth, 0u)); + } + ptr += kRgbStride / kBytesPerPixel; + } + } + + void RunTest(const SkISize dest_size, const SkIRect& rect) { + ASSERT_TRUE(SkIRect::MakeSize(dest_size).contains(rect)); + + // Reset buffers. + ResetYuvBuffer(); + ResetRgbBuffer(); + FillRgbBuffer(rect); + + // RGB -> YUV + ConvertRGB32ToYUVWithRect(rgb_buffer_.get(), + yplane_, + uplane_, + vplane_, + 0, + 0, + kWidth, + kHeight, + kRgbStride, + kYStride, + kUvStride); + + // Reset RGB buffer and do opposite conversion. + ResetRgbBuffer(); + ConvertAndScaleYUVToRGB32Rect(yplane_, + uplane_, + vplane_, + kYStride, + kUvStride, + SkISize::Make(kWidth, kHeight), + SkIRect::MakeWH(kWidth, kHeight), + rgb_buffer_.get(), + kRgbStride, + dest_size, + SkIRect::MakeSize(dest_size), + rect); + + // Check if it worked out. + CheckRgbBuffer(rect); + } + + void TestBasicConversion() { + // Whole buffer. + RunTest(SkISize::Make(kWidth, kHeight), SkIRect::MakeWH(kWidth, kHeight)); + } + + private: + size_t yuv_buffer_size_; + scoped_array<uint8> yuv_buffer_; + uint8* yplane_; + uint8* uplane_; + uint8* vplane_; + + size_t rgb_buffer_size_; + scoped_array<uint8> rgb_buffer_; + + DISALLOW_COPY_AND_ASSIGN(YuvToRgbTester); +}; + +TEST(YuvToRgbTest, BasicConversion) { + YuvToRgbTester tester; + tester.TestBasicConversion(); +} + +TEST(YuvToRgbTest, Clipping) { + YuvToRgbTester tester; + + SkISize dest_size = SkISize::Make(kWidth, kHeight); + SkIRect rect = SkIRect::MakeLTRB(0, 0, kWidth - 1, kHeight - 1); + for (int i = 0; i < 16; ++i) { + SkIRect dest_rect = rect; + if ((i & 1) != 0) + dest_rect.fLeft += 1; + if ((i & 2) != 0) + dest_rect.fTop += 1; + if ((i & 4) != 0) + dest_rect.fRight += 1; + if ((i & 8) != 0) + dest_rect.fBottom += 1; + + tester.RunTest(dest_size, dest_rect); + } +} + +TEST(YuvToRgbTest, ClippingAndScaling) { + YuvToRgbTester tester; + + SkISize dest_size = SkISize::Make(kWidth - 10, kHeight - 10); + SkIRect rect = SkIRect::MakeLTRB(5, 5, kWidth - 11, kHeight - 11); + for (int i = 0; i < 16; ++i) { + SkIRect dest_rect = rect; + if ((i & 1) != 0) + dest_rect.fLeft += 1; + if ((i & 2) != 0) + dest_rect.fTop += 1; + if ((i & 4) != 0) + dest_rect.fRight += 1; + if ((i & 8) != 0) + dest_rect.fBottom += 1; + + tester.RunTest(dest_size, dest_rect); + } +} + +} // namespace remoting diff --git a/remoting/remoting.gyp b/remoting/remoting.gyp index 4b86ba2..5c69bc2 100644 --- a/remoting/remoting.gyp +++ b/remoting/remoting.gyp @@ -922,6 +922,7 @@ 'base/encoder_row_based_unittest.cc', 'base/base_mock_objects.cc', 'base/base_mock_objects.h', + 'base/util_unittest.cc', 'client/mouse_input_filter_unittest.cc', 'host/capturer_linux_unittest.cc', 'host/capturer_mac_unittest.cc', |