From 386098336aa778f82d5a079b9371a1c63bd46d94 Mon Sep 17 00:00:00 2001 From: "kxing@chromium.org" Date: Thu, 23 Aug 2012 01:36:10 +0000 Subject: Moved the video encoders/decoders to the codec directory. Review URL: https://chromiumcodereview.appspot.com/10877014 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@152912 0039d316-1c4b-4281-b951-d872f2087c98 --- remoting/base/base_mock_objects.cc | 13 - remoting/base/base_mock_objects.h | 31 -- remoting/base/codec_test.cc | 497 --------------------- remoting/base/codec_test.h | 40 -- remoting/base/decoder.h | 75 ---- remoting/base/decoder_row_based.cc | 225 ---------- remoting/base/decoder_row_based.h | 84 ---- remoting/base/decoder_vp8.cc | 211 --------- remoting/base/decoder_vp8.h | 60 --- remoting/base/decoder_vp8_unittest.cc | 74 --- remoting/base/encode_decode_unittest.cc | 31 -- remoting/base/encoder.h | 48 -- remoting/base/encoder_row_based.cc | 177 -------- remoting/base/encoder_row_based.h | 71 --- remoting/base/encoder_row_based_unittest.cc | 21 - remoting/base/encoder_vp8.cc | 307 ------------- remoting/base/encoder_vp8.h | 64 --- remoting/base/encoder_vp8_unittest.cc | 96 ---- remoting/client/rectangle_update_decoder.cc | 6 +- remoting/client/rectangle_update_decoder.h | 2 +- remoting/codec/codec_test.cc | 496 ++++++++++++++++++++ remoting/codec/codec_test.h | 40 ++ remoting/codec/video_decoder.h | 75 ++++ remoting/codec/video_decoder_row_based.cc | 225 ++++++++++ remoting/codec/video_decoder_row_based.h | 84 ++++ remoting/codec/video_decoder_vp8.cc | 211 +++++++++ remoting/codec/video_decoder_vp8.h | 60 +++ remoting/codec/video_decoder_vp8_unittest.cc | 75 ++++ remoting/codec/video_encode_decode_unittest.cc | 32 ++ remoting/codec/video_encoder.h | 48 ++ remoting/codec/video_encoder_row_based.cc | 177 ++++++++ remoting/codec/video_encoder_row_based.h | 71 +++ remoting/codec/video_encoder_row_based_unittest.cc | 22 + remoting/codec/video_encoder_vp8.cc | 307 +++++++++++++ remoting/codec/video_encoder_vp8.h | 64 +++ remoting/codec/video_encoder_vp8_unittest.cc | 97 ++++ remoting/host/chromoting_host.cc | 6 +- remoting/host/chromoting_host.h | 2 +- remoting/host/screen_recorder.h | 2 +- remoting/host/screen_recorder_unittest.cc | 21 +- remoting/protocol/DEPS | 1 + remoting/protocol/connection_to_client_unittest.cc | 4 +- remoting/remoting.gyp | 34 +- 43 files changed, 2132 insertions(+), 2155 deletions(-) delete mode 100644 remoting/base/base_mock_objects.cc delete mode 100644 remoting/base/base_mock_objects.h delete mode 100644 remoting/base/codec_test.cc delete mode 100644 remoting/base/codec_test.h delete mode 100644 remoting/base/decoder.h delete mode 100644 remoting/base/decoder_row_based.cc delete mode 100644 remoting/base/decoder_row_based.h delete mode 100644 remoting/base/decoder_vp8.cc delete mode 100644 remoting/base/decoder_vp8.h delete mode 100644 remoting/base/decoder_vp8_unittest.cc delete mode 100644 remoting/base/encode_decode_unittest.cc delete mode 100644 remoting/base/encoder.h delete mode 100644 remoting/base/encoder_row_based.cc delete mode 100644 remoting/base/encoder_row_based.h delete mode 100644 remoting/base/encoder_row_based_unittest.cc delete mode 100644 remoting/base/encoder_vp8.cc delete mode 100644 remoting/base/encoder_vp8.h delete mode 100644 remoting/base/encoder_vp8_unittest.cc create mode 100644 remoting/codec/codec_test.cc create mode 100644 remoting/codec/codec_test.h create mode 100644 remoting/codec/video_decoder.h create mode 100644 remoting/codec/video_decoder_row_based.cc create mode 100644 remoting/codec/video_decoder_row_based.h create mode 100644 remoting/codec/video_decoder_vp8.cc create mode 100644 remoting/codec/video_decoder_vp8.h create mode 100644 remoting/codec/video_decoder_vp8_unittest.cc create mode 100644 remoting/codec/video_encode_decode_unittest.cc create mode 100644 remoting/codec/video_encoder.h create mode 100644 remoting/codec/video_encoder_row_based.cc create mode 100644 remoting/codec/video_encoder_row_based.h create mode 100644 remoting/codec/video_encoder_row_based_unittest.cc create mode 100644 remoting/codec/video_encoder_vp8.cc create mode 100644 remoting/codec/video_encoder_vp8.h create mode 100644 remoting/codec/video_encoder_vp8_unittest.cc (limited to 'remoting') diff --git a/remoting/base/base_mock_objects.cc b/remoting/base/base_mock_objects.cc deleted file mode 100644 index d76a965..0000000 --- a/remoting/base/base_mock_objects.cc +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) 2011 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 "remoting/base/base_mock_objects.h" - -namespace remoting { - -MockEncoder::MockEncoder() {} - -MockEncoder::~MockEncoder() {} - -} // namespace remoting diff --git a/remoting/base/base_mock_objects.h b/remoting/base/base_mock_objects.h deleted file mode 100644 index e90530d..0000000 --- a/remoting/base/base_mock_objects.h +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) 2010 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. - -#ifndef REMOTING_BASE_BASE_MOCK_OBJECTS_H_ -#define REMOTING_BASE_BASE_MOCK_OBJECTS_H_ - -#include "remoting/base/capture_data.h" -#include "remoting/base/decoder.h" -#include "remoting/base/encoder.h" -#include "testing/gmock/include/gmock/gmock.h" - -namespace remoting { - -class MockEncoder : public Encoder { - public: - MockEncoder(); - virtual ~MockEncoder(); - - MOCK_METHOD3(Encode, void( - scoped_refptr capture_data, - bool key_frame, - const DataAvailableCallback& data_available_callback)); - - private: - DISALLOW_COPY_AND_ASSIGN(MockEncoder); -}; - -} // namespace remoting - -#endif // REMOTING_BASE_BASE_MOCK_OBJECTS_H_ diff --git a/remoting/base/codec_test.cc b/remoting/base/codec_test.cc deleted file mode 100644 index 6813c66..0000000 --- a/remoting/base/codec_test.cc +++ /dev/null @@ -1,497 +0,0 @@ -// 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 -#include - -#include "base/bind.h" -#include "base/logging.h" -#include "base/memory/scoped_ptr.h" -#include "media/base/video_frame.h" -#include "remoting/base/base_mock_objects.h" -#include "remoting/base/codec_test.h" -#include "remoting/base/decoder.h" -#include "remoting/base/encoder.h" -#include "remoting/base/util.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace { - -const int kBytesPerPixel = 4; - -// Some sample rects for testing. -std::vector > MakeTestRectLists(const SkISize& size) { - std::vector > rect_lists; - std::vector rects; - rects.push_back(SkIRect::MakeXYWH(0, 0, size.width(), size.height())); - rect_lists.push_back(rects); - rects.clear(); - rects.push_back(SkIRect::MakeXYWH(0, 0, size.width() / 2, size.height() / 2)); - rect_lists.push_back(rects); - rects.clear(); - rects.push_back(SkIRect::MakeXYWH(size.width() / 2, size.height() / 2, - size.width() / 2, size.height() / 2)); - rect_lists.push_back(rects); - rects.clear(); - rects.push_back(SkIRect::MakeXYWH(16, 16, 16, 16)); - rects.push_back(SkIRect::MakeXYWH(128, 64, 32, 32)); - rect_lists.push_back(rects); - return rect_lists; -} - -} // namespace - -namespace remoting { - -// A class to test the message output of the encoder. -class EncoderMessageTester { - public: - EncoderMessageTester() - : begin_rect_(0), - rect_data_(0), - end_rect_(0), - added_rects_(0), - state_(kWaitingForBeginRect), - strict_(false) { - } - - ~EncoderMessageTester() { - EXPECT_EQ(begin_rect_, end_rect_); - EXPECT_GT(begin_rect_, 0); - EXPECT_EQ(kWaitingForBeginRect, state_); - if (strict_) { - EXPECT_EQ(added_rects_, begin_rect_); - } - } - - // Test that we received the correct packet. - void ReceivedPacket(VideoPacket* packet) { - if (state_ == kWaitingForBeginRect) { - EXPECT_TRUE((packet->flags() & VideoPacket::FIRST_PACKET) != 0); - state_ = kWaitingForRectData; - ++begin_rect_; - - if (strict_) { - SkIRect rect = rects_.front(); - rects_.pop_front(); - EXPECT_EQ(rect.fLeft, packet->format().x()); - EXPECT_EQ(rect.fTop, packet->format().y()); - EXPECT_EQ(rect.width(), packet->format().width()); - EXPECT_EQ(rect.height(), packet->format().height()); - } - } else { - EXPECT_FALSE((packet->flags() & VideoPacket::FIRST_PACKET) != 0); - } - - if (state_ == kWaitingForRectData) { - if (packet->has_data()) { - ++rect_data_; - } - - if ((packet->flags() & VideoPacket::LAST_PACKET) != 0) { - // Expect that we have received some data. - EXPECT_GT(rect_data_, 0); - rect_data_ = 0; - state_ = kWaitingForBeginRect; - ++end_rect_; - } - - if ((packet->flags() & VideoPacket::LAST_PARTITION) != 0) { - // LAST_PARTITION must always be marked with LAST_PACKET. - EXPECT_TRUE((packet->flags() & VideoPacket::LAST_PACKET) != 0); - } - } - } - - void set_strict(bool strict) { - strict_ = strict; - } - - void AddRects(const SkIRect* rects, int count) { - rects_.insert(rects_.begin() + rects_.size(), rects, rects + count); - added_rects_ += count; - } - - private: - enum State { - kWaitingForBeginRect, - kWaitingForRectData, - }; - - int begin_rect_; - int rect_data_; - int end_rect_; - int added_rects_; - State state_; - bool strict_; - - std::deque rects_; - - DISALLOW_COPY_AND_ASSIGN(EncoderMessageTester); -}; - -class DecoderTester { - public: - DecoderTester(Decoder* decoder, const SkISize& screen_size, - const SkISize& view_size) - : screen_size_(screen_size), - view_size_(view_size), - strict_(false), - decoder_(decoder) { - image_data_.reset(new uint8[ - view_size_.width() * view_size_.height() * kBytesPerPixel]); - EXPECT_TRUE(image_data_.get()); - decoder_->Initialize(screen_size_); - } - - void Reset() { - expected_region_.setEmpty(); - update_region_.setEmpty(); - } - - void ResetRenderedData() { - memset(image_data_.get(), 0, - view_size_.width() * view_size_.height() * kBytesPerPixel); - } - - void ReceivedPacket(VideoPacket* packet) { - Decoder::DecodeResult result = decoder_->DecodePacket(packet); - - ASSERT_NE(Decoder::DECODE_ERROR, result); - - if (result == Decoder::DECODE_DONE) { - RenderFrame(); - } - } - - void RenderFrame() { - decoder_->RenderFrame(view_size_, - SkIRect::MakeSize(view_size_), - image_data_.get(), - view_size_.width() * kBytesPerPixel, - &update_region_); - } - - void ReceivedScopedPacket(scoped_ptr packet) { - ReceivedPacket(packet.get()); - } - - void set_strict(bool strict) { - strict_ = strict; - } - - void set_capture_data(scoped_refptr data) { - capture_data_ = data; - } - - void AddRects(const SkIRect* rects, int count) { - SkRegion new_rects; - new_rects.setRects(rects, count); - AddRegion(new_rects); - } - - void AddRegion(const SkRegion& region) { - expected_region_.op(region, SkRegion::kUnion_Op); - } - - void VerifyResults() { - if (!strict_) - return; - - ASSERT_TRUE(capture_data_.get()); - - // Test the content of the update region. - EXPECT_EQ(expected_region_, update_region_); - for (SkRegion::Iterator i(update_region_); !i.done(); i.next()) { - const int stride = view_size_.width() * kBytesPerPixel; - EXPECT_EQ(stride, capture_data_->data_planes().strides[0]); - const int offset = stride * i.rect().top() + - kBytesPerPixel * i.rect().left(); - const uint8* original = capture_data_->data_planes().data[0] + offset; - const uint8* decoded = image_data_.get() + offset; - const int row_size = kBytesPerPixel * i.rect().width(); - for (int y = 0; y < i.rect().height(); ++y) { - EXPECT_EQ(0, memcmp(original, decoded, row_size)) - << "Row " << y << " is different"; - original += stride; - decoded += stride; - } - } - } - - // The error at each pixel is the root mean square of the errors in - // the R, G, and B components, each normalized to [0, 1]. This routine - // checks that the maximum and mean pixel errors do not exceed given limits. -void VerifyResultsApprox(const uint8* expected_view_data, - double max_error_limit, double mean_error_limit) { - double max_error = 0.0; - double sum_error = 0.0; - int error_num = 0; - for (SkRegion::Iterator i(update_region_); !i.done(); i.next()) { - const int stride = view_size_.width() * kBytesPerPixel; - const int offset = stride * i.rect().top() + - kBytesPerPixel * i.rect().left(); - const uint8* expected = expected_view_data + offset; - const uint8* actual = image_data_.get() + offset; - for (int y = 0; y < i.rect().height(); ++y) { - for (int x = 0; x < i.rect().width(); ++x) { - double error = CalculateError(expected, actual); - max_error = std::max(max_error, error); - sum_error += error; - ++error_num; - expected += 4; - actual += 4; - } - } - } - EXPECT_LE(max_error, max_error_limit); - double mean_error = sum_error / error_num; - EXPECT_LE(mean_error, mean_error_limit); - LOG(INFO) << "Max error: " << max_error; - LOG(INFO) << "Mean error: " << mean_error; - } - - double CalculateError(const uint8* original, const uint8* decoded) { - double error_sum_squares = 0.0; - for (int i = 0; i < 3; i++) { - double error = static_cast(*original++) - - static_cast(*decoded++); - error /= 255.0; - error_sum_squares += error * error; - } - original++; - decoded++; - return sqrt(error_sum_squares / 3.0); - } - - private: - SkISize screen_size_; - SkISize view_size_; - bool strict_; - SkRegion expected_region_; - SkRegion update_region_; - Decoder* decoder_; - scoped_array image_data_; - scoped_refptr capture_data_; - - DISALLOW_COPY_AND_ASSIGN(DecoderTester); -}; - -// The EncoderTester provides a hook for retrieving the data, and passing the -// message to other subprograms for validaton. -class EncoderTester { - public: - EncoderTester(EncoderMessageTester* message_tester) - : message_tester_(message_tester), - decoder_tester_(NULL), - data_available_(0) { - } - - ~EncoderTester() { - EXPECT_GT(data_available_, 0); - } - - void DataAvailable(scoped_ptr packet) { - ++data_available_; - message_tester_->ReceivedPacket(packet.get()); - - // Send the message to the DecoderTester. - if (decoder_tester_) { - decoder_tester_->ReceivedPacket(packet.get()); - } - } - - void AddRects(const SkIRect* rects, int count) { - message_tester_->AddRects(rects, count); - } - - void set_decoder_tester(DecoderTester* decoder_tester) { - decoder_tester_ = decoder_tester; - } - - private: - EncoderMessageTester* message_tester_; - DecoderTester* decoder_tester_; - int data_available_; - - DISALLOW_COPY_AND_ASSIGN(EncoderTester); -}; - -scoped_refptr PrepareEncodeData(const SkISize& size, - media::VideoFrame::Format format, - uint8** memory) { - // TODO(hclam): Support also YUV format. - CHECK_EQ(format, media::VideoFrame::RGB32); - int memory_size = size.width() * size.height() * kBytesPerPixel; - - *memory = new uint8[memory_size]; - srand(0); - for (int i = 0; i < memory_size; ++i) { - (*memory)[i] = rand() % 256; - } - - DataPlanes planes; - memset(planes.data, 0, sizeof(planes.data)); - memset(planes.strides, 0, sizeof(planes.strides)); - planes.data[0] = *memory; - planes.strides[0] = size.width() * kBytesPerPixel; - - scoped_refptr data = - new CaptureData(planes, size, format); - return data; -} - -static void TestEncodingRects(Encoder* encoder, - EncoderTester* tester, - scoped_refptr data, - const SkIRect* rects, int count) { - data->mutable_dirty_region().setEmpty(); - for (int i = 0; i < count; ++i) { - data->mutable_dirty_region().op(rects[i], SkRegion::kUnion_Op); - } - tester->AddRects(rects, count); - - encoder->Encode(data, true, base::Bind( - &EncoderTester::DataAvailable, base::Unretained(tester))); -} - -void TestEncoder(Encoder* encoder, bool strict) { - SkISize kSize = SkISize::Make(320, 240); - - EncoderMessageTester message_tester; - message_tester.set_strict(strict); - - EncoderTester tester(&message_tester); - - uint8* memory; - scoped_refptr data = - PrepareEncodeData(kSize, media::VideoFrame::RGB32, &memory); - scoped_array memory_wrapper(memory); - - std::vector > test_rect_lists = MakeTestRectLists(kSize); - for (size_t i = 0; i < test_rect_lists.size(); ++i) { - const std::vector& test_rects = test_rect_lists[i]; - TestEncodingRects(encoder, &tester, data, - &test_rects[0], test_rects.size()); - } -} - -static void TestEncodeDecodeRects(Encoder* encoder, - EncoderTester* encoder_tester, - DecoderTester* decoder_tester, - scoped_refptr data, - const SkIRect* rects, int count) { - data->mutable_dirty_region().setRects(rects, count); - encoder_tester->AddRects(rects, count); - decoder_tester->AddRects(rects, count); - - // Generate random data for the updated region. - srand(0); - for (int i = 0; i < count; ++i) { - CHECK_EQ(data->pixel_format(), media::VideoFrame::RGB32); - const int bytes_per_pixel = 4; // Because of RGB32 on previous line. - const int row_size = bytes_per_pixel * rects[i].width(); - uint8* memory = data->data_planes().data[0] + - data->data_planes().strides[0] * rects[i].top() + - bytes_per_pixel * rects[i].left(); - for (int y = 0; y < rects[i].height(); ++y) { - for (int x = 0; x < row_size; ++x) - memory[x] = rand() % 256; - memory += data->data_planes().strides[0]; - } - } - - encoder->Encode(data, true, base::Bind(&EncoderTester::DataAvailable, - base::Unretained(encoder_tester))); - decoder_tester->VerifyResults(); - decoder_tester->Reset(); -} - -void TestEncoderDecoder(Encoder* encoder, Decoder* decoder, bool strict) { - SkISize kSize = SkISize::Make(320, 240); - - EncoderMessageTester message_tester; - message_tester.set_strict(strict); - - EncoderTester encoder_tester(&message_tester); - - uint8* memory; - scoped_refptr data = - PrepareEncodeData(kSize, media::VideoFrame::RGB32, &memory); - scoped_array memory_wrapper(memory); - - DecoderTester decoder_tester(decoder, kSize, kSize); - decoder_tester.set_strict(strict); - decoder_tester.set_capture_data(data); - encoder_tester.set_decoder_tester(&decoder_tester); - - std::vector > test_rect_lists = MakeTestRectLists(kSize); - for (size_t i = 0; i < test_rect_lists.size(); ++i) { - const std::vector test_rects = test_rect_lists[i]; - TestEncodeDecodeRects(encoder, &encoder_tester, &decoder_tester, data, - &test_rects[0], test_rects.size()); - } -} - -static void FillWithGradient(uint8* memory, const SkISize& frame_size, - const SkIRect& rect) { - for (int j = rect.top(); j < rect.bottom(); ++j) { - uint8* p = memory + ((j * frame_size.width()) + rect.left()) * 4; - for (int i = rect.left(); i < rect.right(); ++i) { - *p++ = static_cast((255.0 * i) / frame_size.width()); - *p++ = static_cast((164.0 * j) / frame_size.height()); - *p++ = static_cast((82.0 * (i + j)) / - (frame_size.width() + frame_size.height())); - *p++ = 0; - } - } -} - -void TestEncoderDecoderGradient(Encoder* encoder, - Decoder* decoder, - const SkISize& screen_size, - const SkISize& view_size, - double max_error_limit, - double mean_error_limit) { - SkIRect screen_rect = SkIRect::MakeSize(screen_size); - scoped_array screen_data(new uint8[ - screen_size.width() * screen_size.height() * kBytesPerPixel]); - FillWithGradient(screen_data.get(), screen_size, screen_rect); - - SkIRect view_rect = SkIRect::MakeSize(view_size); - scoped_array expected_view_data(new uint8[ - view_size.width() * view_size.height() * kBytesPerPixel]); - FillWithGradient(expected_view_data.get(), view_size, view_rect); - - DataPlanes planes; - memset(planes.data, 0, sizeof(planes.data)); - memset(planes.strides, 0, sizeof(planes.strides)); - planes.data[0] = screen_data.get(); - planes.strides[0] = screen_size.width() * kBytesPerPixel; - - scoped_refptr capture_data = - new CaptureData(planes, screen_size, media::VideoFrame::RGB32); - capture_data->mutable_dirty_region().op(screen_rect, SkRegion::kUnion_Op); - - DecoderTester decoder_tester(decoder, screen_size, view_size); - decoder_tester.set_capture_data(capture_data); - decoder_tester.AddRegion(capture_data->dirty_region()); - - encoder->Encode(capture_data, true, - base::Bind(&DecoderTester::ReceivedScopedPacket, - base::Unretained(&decoder_tester))); - - decoder_tester.VerifyResultsApprox(expected_view_data.get(), - max_error_limit, mean_error_limit); - - // Check that the decoder correctly re-renders the frame if its client - // invalidates the frame. - decoder_tester.ResetRenderedData(); - decoder->Invalidate(view_size, SkRegion(view_rect)); - decoder_tester.RenderFrame(); - decoder_tester.VerifyResultsApprox(expected_view_data.get(), - max_error_limit, mean_error_limit); -} - -} // namespace remoting diff --git a/remoting/base/codec_test.h b/remoting/base/codec_test.h deleted file mode 100644 index ba71dad..0000000 --- a/remoting/base/codec_test.h +++ /dev/null @@ -1,40 +0,0 @@ -// 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. - -#ifndef REMOTING_BASE_CODEC_TEST_H_ -#define REMOTING_BASE_CODEC_TEST_H_ - -#include "base/memory/ref_counted.h" -#include "media/base/video_frame.h" -#include "remoting/base/capture_data.h" - -namespace remoting { - -class Decoder; -class Encoder; - -// Generate test data and test the encoder for a regular encoding sequence. -// This will test encoder test and the sequence of messages sent. -// -// If |strict| is set to true then this routine will make sure the updated -// rects match dirty rects. -void TestEncoder(Encoder* encoder, bool strict); - -// Generate test data and test the encoder and decoder pair. -// -// If |strict| is set to true, this routine will make sure the updated rects -// are correct. -void TestEncoderDecoder(Encoder* encoder, Decoder* decoder, bool strict); - -// Generate a frame containing a gradient, and test the encoder and decoder -// pair. -void TestEncoderDecoderGradient(Encoder* encoder, Decoder* decoder, - const SkISize& screen_size, - const SkISize& view_size, - double max_error_limit, - double mean_error_limit); - -} // namespace remoting - -#endif // REMOTING_BASE_CODEC_TEST_H_ diff --git a/remoting/base/decoder.h b/remoting/base/decoder.h deleted file mode 100644 index 9286ab5..0000000 --- a/remoting/base/decoder.h +++ /dev/null @@ -1,75 +0,0 @@ -// 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. - -#ifndef REMOTING_BASE_DECODER_H_ -#define REMOTING_BASE_DECODER_H_ - -#include "base/basictypes.h" -#include "remoting/proto/video.pb.h" -#include "third_party/skia/include/core/SkRect.h" -#include "third_party/skia/include/core/SkRegion.h" -#include "third_party/skia/include/core/SkSize.h" - -namespace remoting { - -// Interface for a decoder that takes a stream of bytes from the network and -// outputs frames of data. -// -// TODO(ajwong): Beef up this documentation once the API stablizes. -class Decoder { - public: - // DecodeResult is returned from DecodePacket() and indicates current state - // of the decoder. DECODE_DONE means that last packet for the frame was - // processed, and the frame can be displayed now. DECODE_IN_PROGRESS - // indicates that the decoder must receive more data before the frame can be - // displayed. DECODE_ERROR is returned if there was an error in the stream. - enum DecodeResult { - DECODE_ERROR = -1, - DECODE_IN_PROGRESS, - DECODE_DONE, - }; - - Decoder() {} - virtual ~Decoder() {} - - // Initializes the decoder and sets the output dimensions. - // |screen size| must not be empty. - virtual void Initialize(const SkISize& screen_size) = 0; - - // Feeds more data into the decoder. - virtual DecodeResult DecodePacket(const VideoPacket* packet) = 0; - - // Returns true if decoder is ready to accept data via DecodePacket. - virtual bool IsReadyForData() = 0; - - virtual VideoPacketFormat::Encoding Encoding() = 0; - - // Marks the specified |region| of the view for update the next time - // RenderFrame() is called. |region| is expressed in |view_size| coordinates. - // |view_size| must not be empty. - virtual void Invalidate(const SkISize& view_size, - const SkRegion& region) = 0; - - // Copies invalidated pixels within |clip_area| to |image_buffer|. Pixels are - // invalidated either by new data received in DecodePacket(), or by explicit - // calls to Invalidate(). |clip_area| is specified in |view_size| coordinates. - // If |view_size| differs from the source size then the copied pixels will be - // scaled accordingly. |view_size| cannot be empty. - // - // |image_buffer|'s origin must correspond to the top-left of |clip_area|, - // and the buffer must be large enough to hold |clip_area| RGBA32 pixels. - // |image_stride| gives the output buffer's stride in pixels. - // - // On return, |output_region| contains the updated area, in |view_size| - // coordinates. - virtual void RenderFrame(const SkISize& view_size, - const SkIRect& clip_area, - uint8* image_buffer, - int image_stride, - SkRegion* output_region) = 0; -}; - -} // namespace remoting - -#endif // REMOTING_BASE_DECODER_H_ diff --git a/remoting/base/decoder_row_based.cc b/remoting/base/decoder_row_based.cc deleted file mode 100644 index d98a5ca..0000000 --- a/remoting/base/decoder_row_based.cc +++ /dev/null @@ -1,225 +0,0 @@ -// 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 "remoting/base/decoder_row_based.h" - -#include "base/logging.h" -#include "remoting/base/decompressor.h" -#include "remoting/base/decompressor_zlib.h" -#include "remoting/base/decompressor_verbatim.h" -#include "remoting/base/util.h" - -namespace remoting { - -namespace { -// Both input and output data are assumed to be RGBA32. -const int kBytesPerPixel = 4; -} - -DecoderRowBased* DecoderRowBased::CreateZlibDecoder() { - return new DecoderRowBased(new DecompressorZlib(), - VideoPacketFormat::ENCODING_ZLIB); -} - -DecoderRowBased* DecoderRowBased::CreateVerbatimDecoder() { - return new DecoderRowBased(new DecompressorVerbatim(), - VideoPacketFormat::ENCODING_VERBATIM); -} - -DecoderRowBased::DecoderRowBased(Decompressor* decompressor, - VideoPacketFormat::Encoding encoding) - : state_(kUninitialized), - clip_(SkIRect::MakeEmpty()), - decompressor_(decompressor), - encoding_(encoding), - row_pos_(0), - row_y_(0), - screen_size_(SkISize::Make(0, 0)) { -} - -DecoderRowBased::~DecoderRowBased() { -} - -bool DecoderRowBased::IsReadyForData() { - switch (state_) { - case kUninitialized: - case kError: - return false; - case kReady: - case kProcessing: - case kPartitionDone: - case kDone: - return true; - } - NOTREACHED(); - return false; -} - -void DecoderRowBased::Initialize(const SkISize& screen_size) { - decompressor_->Reset(); - updated_region_.setEmpty(); - screen_buffer_.reset(NULL); - - screen_size_ = screen_size; - // Allocate the screen buffer, if necessary. - if (!screen_size_.isEmpty()) { - screen_buffer_.reset(new uint8[ - screen_size_.width() * screen_size_.height() * kBytesPerPixel]); - } - - state_ = kReady; -} - -Decoder::DecodeResult DecoderRowBased::DecodePacket(const VideoPacket* packet) { - UpdateStateForPacket(packet); - - if (state_ == kError) { - return DECODE_ERROR; - } - - const uint8* in = reinterpret_cast(packet->data().data()); - const int in_size = packet->data().size(); - const int row_size = clip_.width() * kBytesPerPixel; - - int out_stride = screen_size_.width() * kBytesPerPixel; - uint8* out = screen_buffer_.get() + out_stride * (clip_.top() + row_y_) + - kBytesPerPixel * clip_.left(); - - // Consume all the data in the message. - bool decompress_again = true; - int used = 0; - while (decompress_again && used < in_size) { - if (row_y_ >= clip_.height()) { - state_ = kError; - LOG(WARNING) << "Too much data is received for the given rectangle."; - return DECODE_ERROR; - } - - int written = 0; - int consumed = 0; - decompress_again = decompressor_->Process( - in + used, in_size - used, out + row_pos_, row_size - row_pos_, - &consumed, &written); - used += consumed; - row_pos_ += written; - - // If this row is completely filled then move onto the next row. - if (row_pos_ == row_size) { - ++row_y_; - row_pos_ = 0; - out += out_stride; - } - } - - if (state_ == kPartitionDone || state_ == kDone) { - if (row_y_ < clip_.height()) { - state_ = kError; - LOG(WARNING) << "Received LAST_PACKET, but didn't get enough data."; - return DECODE_ERROR; - } - - updated_region_.op(clip_, SkRegion::kUnion_Op); - decompressor_->Reset(); - } - - if (state_ == kDone) { - return DECODE_DONE; - } else { - return DECODE_IN_PROGRESS; - } -} - -void DecoderRowBased::UpdateStateForPacket(const VideoPacket* packet) { - if (state_ == kError) { - return; - } - - if (packet->flags() & VideoPacket::FIRST_PACKET) { - if (state_ != kReady && state_ != kDone && state_ != kPartitionDone) { - state_ = kError; - LOG(WARNING) << "Received unexpected FIRST_PACKET."; - return; - } - - // Reset the buffer location status variables on the first packet. - clip_.setXYWH(packet->format().x(), packet->format().y(), - packet->format().width(), packet->format().height()); - if (!SkIRect::MakeSize(screen_size_).contains(clip_)) { - state_ = kError; - LOG(WARNING) << "Invalid clipping area received."; - return; - } - - state_ = kProcessing; - row_pos_ = 0; - row_y_ = 0; - } - - if (state_ != kProcessing) { - state_ = kError; - LOG(WARNING) << "Received unexpected packet."; - return; - } - - if (packet->flags() & VideoPacket::LAST_PACKET) { - if (state_ != kProcessing) { - state_ = kError; - LOG(WARNING) << "Received unexpected LAST_PACKET."; - return; - } - state_ = kPartitionDone; - } - - if (packet->flags() & VideoPacket::LAST_PARTITION) { - if (state_ != kPartitionDone) { - state_ = kError; - LOG(WARNING) << "Received unexpected LAST_PARTITION."; - return; - } - state_ = kDone; - } - - return; -} - -VideoPacketFormat::Encoding DecoderRowBased::Encoding() { - return encoding_; -} - -void DecoderRowBased::Invalidate(const SkISize& view_size, - const SkRegion& region) { - updated_region_.op(region, SkRegion::kUnion_Op); -} - -void DecoderRowBased::RenderFrame(const SkISize& view_size, - const SkIRect& clip_area, - uint8* image_buffer, - int image_stride, - SkRegion* output_region) { - output_region->setEmpty(); - - // TODO(alexeypa): scaling is not implemented. - SkIRect clip_rect = SkIRect::MakeSize(screen_size_); - if (!clip_rect.intersect(clip_area)) - return; - - int screen_stride = screen_size_.width() * kBytesPerPixel; - - for (SkRegion::Iterator i(updated_region_); !i.done(); i.next()) { - SkIRect rect(i.rect()); - if (!rect.intersect(clip_rect)) - continue; - - CopyRGB32Rect(screen_buffer_.get(), screen_stride, - clip_rect, - image_buffer, image_stride, - clip_area, - rect); - output_region->op(rect, SkRegion::kUnion_Op); - } - - updated_region_.setEmpty(); -} - -} // namespace remoting diff --git a/remoting/base/decoder_row_based.h b/remoting/base/decoder_row_based.h deleted file mode 100644 index fc76837..0000000 --- a/remoting/base/decoder_row_based.h +++ /dev/null @@ -1,84 +0,0 @@ -// 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. - -#ifndef REMOTING_BASE_DECODER_ROW_BASED_H_ -#define REMOTING_BASE_DECODER_ROW_BASED_H_ - -#include "base/compiler_specific.h" -#include "base/memory/scoped_ptr.h" -#include "remoting/base/decoder.h" - -namespace remoting { - -class Decompressor; - -class DecoderRowBased : public Decoder { - public: - virtual ~DecoderRowBased(); - - static DecoderRowBased* CreateZlibDecoder(); - static DecoderRowBased* CreateVerbatimDecoder(); - - // Decoder implementation. - virtual bool IsReadyForData() OVERRIDE; - virtual void Initialize(const SkISize& screen_size) OVERRIDE; - virtual DecodeResult DecodePacket(const VideoPacket* packet) OVERRIDE; - virtual VideoPacketFormat::Encoding Encoding() OVERRIDE; - virtual void Invalidate(const SkISize& view_size, - const SkRegion& region) OVERRIDE; - virtual void RenderFrame(const SkISize& view_size, - const SkIRect& clip_area, - uint8* image_buffer, - int image_stride, - SkRegion* output_region) OVERRIDE; - - private: - enum State { - kUninitialized, - kReady, - kProcessing, - kPartitionDone, - kDone, - kError, - }; - - DecoderRowBased(Decompressor* decompressor, - VideoPacketFormat::Encoding encoding); - - // Helper method. Called from DecodePacket to updated state of the decoder. - void UpdateStateForPacket(const VideoPacket* packet); - - // The internal state of the decoder. - State state_; - - // Keeps track of the updating rect. - SkIRect clip_; - - // The compression for the input byte stream. - scoped_ptr decompressor_; - - // The encoding of the incoming stream. - VideoPacketFormat::Encoding encoding_; - - // The position in the row that we are updating. - int row_pos_; - - // The current row in the rect that we are updaing. - int row_y_; - - // The region updated that hasn't been copied to the screen yet. - SkRegion updated_region_; - - // Size of the remote screen. - SkISize screen_size_; - - // The bitmap holding the remote screen bits. - scoped_array screen_buffer_; - - DISALLOW_COPY_AND_ASSIGN(DecoderRowBased); -}; - -} // namespace remoting - -#endif // REMOTING_BASE_DECODER_ROW_BASED_H_ diff --git a/remoting/base/decoder_vp8.cc b/remoting/base/decoder_vp8.cc deleted file mode 100644 index 74fd8a8..0000000 --- a/remoting/base/decoder_vp8.cc +++ /dev/null @@ -1,211 +0,0 @@ -// 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 "remoting/base/decoder_vp8.h" - -#include - -#include "base/logging.h" -#include "media/base/media.h" -#include "media/base/yuv_convert.h" -#include "remoting/base/util.h" - -extern "C" { -#define VPX_CODEC_DISABLE_COMPAT 1 -#include "third_party/libvpx/libvpx.h" -} - -namespace remoting { - -DecoderVp8::DecoderVp8() - : state_(kUninitialized), - codec_(NULL), - last_image_(NULL), - screen_size_(SkISize::Make(0, 0)) { -} - -DecoderVp8::~DecoderVp8() { - if (codec_) { - vpx_codec_err_t ret = vpx_codec_destroy(codec_); - CHECK(ret == VPX_CODEC_OK) << "Failed to destroy codec"; - } - delete codec_; -} - -void DecoderVp8::Initialize(const SkISize& screen_size) { - DCHECK(!screen_size.isEmpty()); - - screen_size_ = screen_size; - state_ = kReady; -} - -Decoder::DecodeResult DecoderVp8::DecodePacket(const VideoPacket* packet) { - DCHECK_EQ(kReady, state_); - - // Initialize the codec as needed. - if (!codec_) { - codec_ = new vpx_codec_ctx_t(); - - // TODO(hclam): Scale the number of threads with number of cores of the - // machine. - vpx_codec_dec_cfg config; - config.w = 0; - config.h = 0; - config.threads = 2; - vpx_codec_err_t ret = - vpx_codec_dec_init( - codec_, vpx_codec_vp8_dx(), &config, 0); - if (ret != VPX_CODEC_OK) { - LOG(INFO) << "Cannot initialize codec."; - delete codec_; - codec_ = NULL; - state_ = kError; - return DECODE_ERROR; - } - } - - // Do the actual decoding. - vpx_codec_err_t ret = vpx_codec_decode( - codec_, reinterpret_cast(packet->data().data()), - packet->data().size(), NULL, 0); - if (ret != VPX_CODEC_OK) { - LOG(INFO) << "Decoding failed:" << vpx_codec_err_to_string(ret) << "\n" - << "Details: " << vpx_codec_error(codec_) << "\n" - << vpx_codec_error_detail(codec_); - return DECODE_ERROR; - } - - // Gets the decoded data. - vpx_codec_iter_t iter = NULL; - vpx_image_t* image = vpx_codec_get_frame(codec_, &iter); - if (!image) { - LOG(INFO) << "No video frame decoded"; - return DECODE_ERROR; - } - last_image_ = image; - - SkRegion region; - for (int i = 0; i < packet->dirty_rects_size(); ++i) { - Rect remoting_rect = packet->dirty_rects(i); - SkIRect rect = SkIRect::MakeXYWH(remoting_rect.x(), - remoting_rect.y(), - remoting_rect.width(), - remoting_rect.height()); - region.op(rect, SkRegion::kUnion_Op); - } - - updated_region_.op(region, SkRegion::kUnion_Op); - return DECODE_DONE; -} - -bool DecoderVp8::IsReadyForData() { - return state_ == kReady; -} - -VideoPacketFormat::Encoding DecoderVp8::Encoding() { - return VideoPacketFormat::ENCODING_VP8; -} - -void DecoderVp8::Invalidate(const SkISize& view_size, - const SkRegion& region) { - DCHECK_EQ(kReady, state_); - DCHECK(!view_size.isEmpty()); - - for (SkRegion::Iterator i(region); !i.done(); i.next()) { - SkIRect rect = i.rect(); - rect = ScaleRect(rect, view_size, screen_size_); - updated_region_.op(rect, SkRegion::kUnion_Op); - } -} - -void DecoderVp8::RenderFrame(const SkISize& view_size, - const SkIRect& clip_area, - uint8* image_buffer, - int image_stride, - SkRegion* output_region) { - DCHECK_EQ(kReady, state_); - DCHECK(!view_size.isEmpty()); - - // Early-return and do nothing if we haven't yet decoded any frames. - if (!last_image_) - return; - - SkIRect source_clip = SkIRect::MakeWH(last_image_->d_w, last_image_->d_h); - - // ScaleYUVToRGB32WithRect does not currently support up-scaling. We won't - // be asked to up-scale except during resizes or if page zoom is >100%, so - // we work-around the limitation by using the slower ScaleYUVToRGB32. - // TODO(wez): Remove this hack if/when ScaleYUVToRGB32WithRect can up-scale. - if (!updated_region_.isEmpty() && - (source_clip.width() < view_size.width() || - source_clip.height() < view_size.height())) { - // We're scaling only |clip_area| into the |image_buffer|, so we need to - // work out which source rectangle that corresponds to. - SkIRect source_rect = ScaleRect(clip_area, view_size, screen_size_); - source_rect = SkIRect::MakeLTRB(RoundToTwosMultiple(source_rect.left()), - RoundToTwosMultiple(source_rect.top()), - source_rect.right(), - source_rect.bottom()); - - // If there were no changes within the clip source area then don't render. - if (!updated_region_.intersects(source_rect)) - return; - - // Scale & convert the entire clip area. - int y_offset = CalculateYOffset(source_rect.x(), - source_rect.y(), - last_image_->stride[0]); - int uv_offset = CalculateUVOffset(source_rect.x(), - source_rect.y(), - last_image_->stride[1]); - ScaleYUVToRGB32(last_image_->planes[0] + y_offset, - last_image_->planes[1] + uv_offset, - last_image_->planes[2] + uv_offset, - image_buffer, - source_rect.width(), - source_rect.height(), - clip_area.width(), - clip_area.height(), - last_image_->stride[0], - last_image_->stride[1], - image_stride, - media::YV12, - media::ROTATE_0, - media::FILTER_BILINEAR); - - output_region->op(clip_area, SkRegion::kUnion_Op); - updated_region_.op(source_rect, SkRegion::kDifference_Op); - return; - } - - for (SkRegion::Iterator i(updated_region_); !i.done(); i.next()) { - // Determine the scaled area affected by this rectangle changing. - SkIRect rect = i.rect(); - if (!rect.intersect(source_clip)) - continue; - rect = ScaleRect(rect, screen_size_, view_size); - if (!rect.intersect(clip_area)) - continue; - - ConvertAndScaleYUVToRGB32Rect(last_image_->planes[0], - last_image_->planes[1], - last_image_->planes[2], - last_image_->stride[0], - last_image_->stride[1], - screen_size_, - source_clip, - image_buffer, - image_stride, - view_size, - clip_area, - rect); - - output_region->op(rect, SkRegion::kUnion_Op); - } - - updated_region_.op(ScaleRect(clip_area, view_size, screen_size_), - SkRegion::kDifference_Op); -} - -} // namespace remoting diff --git a/remoting/base/decoder_vp8.h b/remoting/base/decoder_vp8.h deleted file mode 100644 index 53029e2..0000000 --- a/remoting/base/decoder_vp8.h +++ /dev/null @@ -1,60 +0,0 @@ -// 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. - -#ifndef REMOTING_BASE_DECODER_VP8_H_ -#define REMOTING_BASE_DECODER_VP8_H_ - -#include "base/compiler_specific.h" -#include "remoting/base/decoder.h" - -typedef struct vpx_codec_ctx vpx_codec_ctx_t; -typedef struct vpx_image vpx_image_t; - -namespace remoting { - -class DecoderVp8 : public Decoder { - public: - DecoderVp8(); - virtual ~DecoderVp8(); - - // Decoder implementations. - virtual void Initialize(const SkISize& screen_size) OVERRIDE; - virtual DecodeResult DecodePacket(const VideoPacket* packet) OVERRIDE; - virtual bool IsReadyForData() OVERRIDE; - virtual VideoPacketFormat::Encoding Encoding() OVERRIDE; - virtual void Invalidate(const SkISize& view_size, - const SkRegion& region) OVERRIDE; - virtual void RenderFrame(const SkISize& view_size, - const SkIRect& clip_area, - uint8* image_buffer, - int image_stride, - SkRegion* output_region) OVERRIDE; - - private: - enum State { - kUninitialized, - kReady, - kError, - }; - - // The internal state of the decoder. - State state_; - - vpx_codec_ctx_t* codec_; - - // Pointer to the last decoded image. - vpx_image_t* last_image_; - - // The region updated that hasn't been copied to the screen yet. - SkRegion updated_region_; - - // Output dimensions. - SkISize screen_size_; - - DISALLOW_COPY_AND_ASSIGN(DecoderVp8); -}; - -} // namespace remoting - -#endif // REMOTING_BASE_DECODER_VP8_H_ diff --git a/remoting/base/decoder_vp8_unittest.cc b/remoting/base/decoder_vp8_unittest.cc deleted file mode 100644 index 553d27c..0000000 --- a/remoting/base/decoder_vp8_unittest.cc +++ /dev/null @@ -1,74 +0,0 @@ -// 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 "media/base/video_frame.h" -#include "remoting/base/codec_test.h" -#include "remoting/base/decoder_vp8.h" -#include "remoting/base/encoder_vp8.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace remoting { - -class DecoderVp8Test : public testing::Test { - protected: - EncoderVp8 encoder_; - DecoderVp8 decoder_; - - void TestGradient(int screen_width, int screen_height, - int view_width, int view_height, - double max_error_limit, double mean_error_limit) { - TestEncoderDecoderGradient(&encoder_, &decoder_, - SkISize::Make(screen_width, screen_height), - SkISize::Make(view_width, view_height), - max_error_limit, mean_error_limit); - } -}; - -TEST_F(DecoderVp8Test, EncodeAndDecode) { - TestEncoderDecoder(&encoder_, &decoder_, false); -} - -// Check that encoding and decoding a particular frame doesn't change the -// frame too much. The frame used is a gradient, which does not contain sharp -// transitions, so encoding lossiness should not be too high. -TEST_F(DecoderVp8Test, Gradient) { - TestGradient(320, 240, 320, 240, 0.03, 0.01); -} - -TEST_F(DecoderVp8Test, GradientScaleUpEvenToEven) { - TestGradient(320, 240, 640, 480, 0.04, 0.02); -} - -TEST_F(DecoderVp8Test, GradientScaleUpEvenToOdd) { - TestGradient(320, 240, 641, 481, 0.04, 0.02); -} - -TEST_F(DecoderVp8Test, GradientScaleUpOddToEven) { - TestGradient(321, 241, 640, 480, 0.04, 0.02); -} - -TEST_F(DecoderVp8Test, GradientScaleUpOddToOdd) { - TestGradient(321, 241, 641, 481, 0.04, 0.02); -} - -TEST_F(DecoderVp8Test, GradientScaleDownEvenToEven) { - TestGradient(320, 240, 160, 120, 0.04, 0.02); -} - -TEST_F(DecoderVp8Test, GradientScaleDownEvenToOdd) { - // The maximum error is non-deterministic. The mean error is not too high, - // which suggests that the problem is restricted to a small area of the output - // image. See crbug.com/139437 and crbug.com/139633. - TestGradient(320, 240, 161, 121, 1.0, 0.02); -} - -TEST_F(DecoderVp8Test, GradientScaleDownOddToEven) { - TestGradient(321, 241, 160, 120, 0.04, 0.02); -} - -TEST_F(DecoderVp8Test, GradientScaleDownOddToOdd) { - TestGradient(321, 241, 161, 121, 0.04, 0.02); -} - -} // namespace remoting diff --git a/remoting/base/encode_decode_unittest.cc b/remoting/base/encode_decode_unittest.cc deleted file mode 100644 index 10baf7c..0000000 --- a/remoting/base/encode_decode_unittest.cc +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) 2010 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 "media/base/video_frame.h" -#include "remoting/base/codec_test.h" -#include "remoting/base/decoder_row_based.h" -#include "remoting/base/encoder_row_based.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace remoting { - -TEST(EncodeDecodeTest, EncodeAndDecodeZlib) { - scoped_ptr encoder(EncoderRowBased::CreateZlibEncoder()); - scoped_ptr decoder(DecoderRowBased::CreateZlibDecoder()); - TestEncoderDecoder(encoder.get(), decoder.get(), true); -} - -TEST(EncodeDecodeTest, EncodeAndDecodeSmallOutputBufferZlib) { - scoped_ptr encoder(EncoderRowBased::CreateZlibEncoder(64)); - scoped_ptr decoder(DecoderRowBased::CreateZlibDecoder()); - TestEncoderDecoder(encoder.get(), decoder.get(), true); -} - -TEST(EncodeDecodeTest, EncodeAndDecodeNoneStrictZlib) { - scoped_ptr encoder(EncoderRowBased::CreateZlibEncoder()); - scoped_ptr decoder(DecoderRowBased::CreateZlibDecoder()); - TestEncoderDecoder(encoder.get(), decoder.get(), false); -} - -} // namespace remoting diff --git a/remoting/base/encoder.h b/remoting/base/encoder.h deleted file mode 100644 index ae6fadc..0000000 --- a/remoting/base/encoder.h +++ /dev/null @@ -1,48 +0,0 @@ -// 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. - -#ifndef REMOTING_BASE_ENCODER_H_ -#define REMOTING_BASE_ENCODER_H_ - -#include "base/basictypes.h" -#include "base/callback.h" -#include "media/base/data_buffer.h" - -namespace media { - class DataBuffer; -} - -namespace remoting { - -class CaptureData; -class VideoPacket; - -// A class to perform the task of encoding a continous stream of -// images. -// This class operates asynchronously to enable maximum throughput. -class Encoder { - public: - - // DataAvailableCallback is called as blocks of data are made available - // from the encoder. Data made available by the encoder is in the form - // of HostMessage to reduce the amount of memory copies. - typedef base::Callback)> DataAvailableCallback; - - virtual ~Encoder() {} - - // Encode an image stored in |capture_data|. - // - // If |key_frame| is true, the encoder should not reference - // previous encode and encode the full frame. - // - // When encoded data is available, partial or full |data_available_callback| - // is called. - virtual void Encode(scoped_refptr capture_data, - bool key_frame, - const DataAvailableCallback& data_available_callback) = 0; -}; - -} // namespace remoting - -#endif // REMOTING_BASE_ENCODER_H_ diff --git a/remoting/base/encoder_row_based.cc b/remoting/base/encoder_row_based.cc deleted file mode 100644 index c28c06c..0000000 --- a/remoting/base/encoder_row_based.cc +++ /dev/null @@ -1,177 +0,0 @@ -// 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 "remoting/base/encoder_row_based.h" - -#include "base/logging.h" -#include "remoting/base/capture_data.h" -#include "remoting/base/compressor_verbatim.h" -#include "remoting/base/compressor_zlib.h" -#include "remoting/base/util.h" -#include "remoting/proto/video.pb.h" - -namespace remoting { - -static const int kPacketSize = 1024 * 1024; - -EncoderRowBased* EncoderRowBased::CreateZlibEncoder() { - return new EncoderRowBased(new CompressorZlib(), - VideoPacketFormat::ENCODING_ZLIB); -} - -EncoderRowBased* EncoderRowBased::CreateZlibEncoder(int packet_size) { - return new EncoderRowBased(new CompressorZlib(), - VideoPacketFormat::ENCODING_ZLIB, - packet_size); -} - -EncoderRowBased* EncoderRowBased::CreateVerbatimEncoder() { - return new EncoderRowBased(new CompressorVerbatim(), - VideoPacketFormat::ENCODING_VERBATIM); -} - -EncoderRowBased* EncoderRowBased::CreateVerbatimEncoder(int packet_size) { - return new EncoderRowBased(new CompressorVerbatim(), - VideoPacketFormat::ENCODING_VERBATIM, - packet_size); -} - -EncoderRowBased::EncoderRowBased(Compressor* compressor, - VideoPacketFormat::Encoding encoding) - : encoding_(encoding), - compressor_(compressor), - screen_size_(SkISize::Make(0,0)), - packet_size_(kPacketSize) { -} - -EncoderRowBased::EncoderRowBased(Compressor* compressor, - VideoPacketFormat::Encoding encoding, - int packet_size) - : encoding_(encoding), - compressor_(compressor), - screen_size_(SkISize::Make(0,0)), - packet_size_(packet_size) { -} - -EncoderRowBased::~EncoderRowBased() {} - -void EncoderRowBased::Encode( - scoped_refptr capture_data, - bool key_frame, - const DataAvailableCallback& data_available_callback) { - CHECK(capture_data->pixel_format() == media::VideoFrame::RGB32) - << "RowBased Encoder only works with RGB32. Got " - << capture_data->pixel_format(); - capture_data_ = capture_data; - callback_ = data_available_callback; - - const SkRegion& region = capture_data->dirty_region(); - SkRegion::Iterator iter(region); - while (!iter.done()) { - SkIRect rect = iter.rect(); - iter.next(); - EncodeRect(rect, iter.done()); - } - - capture_data_ = NULL; - callback_.Reset(); -} - -void EncoderRowBased::EncodeRect(const SkIRect& rect, bool last) { - CHECK(capture_data_->data_planes().data[0]); - CHECK_EQ(capture_data_->pixel_format(), media::VideoFrame::RGB32); - const int strides = capture_data_->data_planes().strides[0]; - const int bytes_per_pixel = 4; - const int row_size = bytes_per_pixel * rect.width(); - - compressor_->Reset(); - - scoped_ptr packet(new VideoPacket()); - PrepareUpdateStart(rect, packet.get()); - const uint8* in = capture_data_->data_planes().data[0] + - rect.fTop * strides + rect.fLeft * bytes_per_pixel; - // TODO(hclam): Fill in the sequence number. - uint8* out = GetOutputBuffer(packet.get(), packet_size_); - int filled = 0; - int row_pos = 0; // Position in the current row in bytes. - int row_y = 0; // Current row. - bool compress_again = true; - while (compress_again) { - // Prepare a message for sending out. - if (!packet.get()) { - packet.reset(new VideoPacket()); - out = GetOutputBuffer(packet.get(), packet_size_); - filled = 0; - } - - Compressor::CompressorFlush flush = Compressor::CompressorNoFlush; - if (row_y == rect.height() - 1) { - flush = Compressor::CompressorFinish; - } - - int consumed = 0; - int written = 0; - compress_again = compressor_->Process(in + row_pos, row_size - row_pos, - out + filled, packet_size_ - filled, - flush, &consumed, &written); - row_pos += consumed; - filled += written; - - // We have reached the end of stream. - if (!compress_again) { - packet->set_flags(packet->flags() | VideoPacket::LAST_PACKET); - packet->set_capture_time_ms(capture_data_->capture_time_ms()); - packet->set_client_sequence_number( - capture_data_->client_sequence_number()); - SkIPoint dpi(capture_data_->dpi()); - if (dpi.x()) - packet->mutable_format()->set_x_dpi(dpi.x()); - if (dpi.y()) - packet->mutable_format()->set_y_dpi(dpi.y()); - if (last) - packet->set_flags(packet->flags() | VideoPacket::LAST_PARTITION); - DCHECK(row_pos == row_size); - DCHECK(row_y == rect.height() - 1); - } - - // If we have filled the message or we have reached the end of stream. - if (filled == packet_size_ || !compress_again) { - packet->mutable_data()->resize(filled); - callback_.Run(packet.Pass()); - } - - // Reached the end of input row and we're not at the last row. - if (row_pos == row_size && row_y < rect.height() - 1) { - row_pos = 0; - in += strides; - ++row_y; - } - } -} - -void EncoderRowBased::PrepareUpdateStart(const SkIRect& rect, - VideoPacket* packet) { - packet->set_flags(packet->flags() | VideoPacket::FIRST_PACKET); - - VideoPacketFormat* format = packet->mutable_format(); - format->set_x(rect.fLeft); - format->set_y(rect.fTop); - format->set_width(rect.width()); - format->set_height(rect.height()); - format->set_encoding(encoding_); - if (capture_data_->size() != screen_size_) { - screen_size_ = capture_data_->size(); - format->set_screen_width(screen_size_.width()); - format->set_screen_height(screen_size_.height()); - } -} - -uint8* EncoderRowBased::GetOutputBuffer(VideoPacket* packet, size_t size) { - packet->mutable_data()->resize(size); - // TODO(ajwong): Is there a better way to do this at all??? - return const_cast(reinterpret_cast( - packet->mutable_data()->data())); -} - -} // namespace remoting diff --git a/remoting/base/encoder_row_based.h b/remoting/base/encoder_row_based.h deleted file mode 100644 index 5d8a877..0000000 --- a/remoting/base/encoder_row_based.h +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) 2011 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. - -#ifndef REMOTING_BASE_ENCODER_ROW_BASED_H_ -#define REMOTING_BASE_ENCODER_ROW_BASED_H_ - -#include "remoting/base/encoder.h" -#include "remoting/proto/video.pb.h" -#include "third_party/skia/include/core/SkRect.h" - -namespace remoting { - -class Compressor; - -// EncoderRowBased implements an Encoder using zlib or verbatim -// compression. Zlib-based encoder must be created using -// CreateZlibEncoder(), verbatim encoder is created with -// CreateVerbatimEncoder(). -// -// Compressor is reset before encoding each rectangle, so that each -// rectangle can be decoded independently. -class EncoderRowBased : public Encoder { - public: - static EncoderRowBased* CreateZlibEncoder(); - static EncoderRowBased* CreateZlibEncoder(int packet_size); - static EncoderRowBased* CreateVerbatimEncoder(); - static EncoderRowBased* CreateVerbatimEncoder(int packet_size); - - virtual ~EncoderRowBased(); - - virtual void Encode( - scoped_refptr capture_data, - bool key_frame, - const DataAvailableCallback& data_available_callback) OVERRIDE; - - private: - EncoderRowBased(Compressor* compressor, VideoPacketFormat::Encoding encoding); - EncoderRowBased(Compressor* compressor, VideoPacketFormat::Encoding encoding, - int packet_size); - - // Encode a single dirty rect using compressor. - void EncodeRect(const SkIRect& rect, bool last); - - // Marks a packet as the first in a series of rectangle updates. - void PrepareUpdateStart(const SkIRect& rect, VideoPacket* packet); - - // Retrieves a pointer to the output buffer in |update| used for storing the - // encoded rectangle data. Will resize the buffer to |size|. - uint8* GetOutputBuffer(VideoPacket* packet, size_t size); - - // Submit |message| to |callback_|. - void SubmitMessage(VideoPacket* packet, size_t rect_index); - - // The encoding of the incoming stream. - VideoPacketFormat::Encoding encoding_; - - scoped_ptr compressor_; - - scoped_refptr capture_data_; - DataAvailableCallback callback_; - - // The most recent screen size. - SkISize screen_size_; - - int packet_size_; -}; - -} // namespace remoting - -#endif // REMOTING_BASE_ENCODER_ROW_BASED_H_ diff --git a/remoting/base/encoder_row_based_unittest.cc b/remoting/base/encoder_row_based_unittest.cc deleted file mode 100644 index 735e806..0000000 --- a/remoting/base/encoder_row_based_unittest.cc +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) 2010 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 "remoting/base/codec_test.h" -#include "remoting/base/encoder_row_based.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace remoting { - -TEST(EncoderZlibTest, TestEncoder) { - scoped_ptr encoder(EncoderRowBased::CreateZlibEncoder()); - TestEncoder(encoder.get(), true); -} - -TEST(EncoderZlibTest, TestEncoderSmallOutputBuffer) { - scoped_ptr encoder(EncoderRowBased::CreateZlibEncoder(16)); - TestEncoder(encoder.get(), true); -} - -} // namespace remoting diff --git a/remoting/base/encoder_vp8.cc b/remoting/base/encoder_vp8.cc deleted file mode 100644 index f329948..0000000 --- a/remoting/base/encoder_vp8.cc +++ /dev/null @@ -1,307 +0,0 @@ -// 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 "remoting/base/encoder_vp8.h" - -#include "base/logging.h" -#include "base/sys_info.h" -#include "media/base/yuv_convert.h" -#include "remoting/base/capture_data.h" -#include "remoting/base/util.h" -#include "remoting/proto/video.pb.h" - -extern "C" { -#define VPX_CODEC_DISABLE_COMPAT 1 -#include "third_party/libvpx/libvpx.h" -} - -namespace { - -// Defines the dimension of a macro block. This is used to compute the active -// map for the encoder. -const int kMacroBlockSize = 16; - -} // namespace remoting - -namespace remoting { - -EncoderVp8::EncoderVp8() - : initialized_(false), - codec_(NULL), - image_(NULL), - active_map_width_(0), - active_map_height_(0), - last_timestamp_(0) { -} - -EncoderVp8::~EncoderVp8() { - Destroy(); -} - -void EncoderVp8::Destroy() { - if (initialized_) { - vpx_codec_err_t ret = vpx_codec_destroy(codec_.get()); - DCHECK(ret == VPX_CODEC_OK) << "Failed to destroy codec"; - initialized_ = false; - } -} - -bool EncoderVp8::Init(const SkISize& size) { - Destroy(); - codec_.reset(new vpx_codec_ctx_t()); - image_.reset(new vpx_image_t()); - memset(image_.get(), 0, sizeof(vpx_image_t)); - - image_->fmt = VPX_IMG_FMT_YV12; - - // libvpx seems to require both to be assigned. - image_->d_w = size.width(); - image_->w = size.width(); - image_->d_h = size.height(); - image_->h = size.height(); - - // Initialize active map. - active_map_width_ = (image_->w + kMacroBlockSize - 1) / kMacroBlockSize; - active_map_height_ = (image_->h + kMacroBlockSize - 1) / kMacroBlockSize; - active_map_.reset(new uint8[active_map_width_ * active_map_height_]); - - // YUV image size is 1.5 times of a plane. Multiplication is performed first - // to avoid rounding error. - const int y_plane_size = image_->w * image_->h; - const int uv_width = (image_->w + 1) / 2; - const int uv_height = (image_->w + 1) / 2; - const int uv_plane_size = uv_width * uv_height; - const int yuv_image_size = y_plane_size + uv_plane_size * 2; - - // libvpx may try to access memory after the buffer (it still - // doesn't use it) - it copies the data in 16x16 blocks: - // crbug.com/119633 . Here we workaround that problem by adding - // padding at the end of the buffer. Overreading to U and V buffers - // is safe so the padding is necessary only at the end. - // - // TODO(sergeyu): Remove this padding when the bug is fixed in libvpx. - const int active_map_area = active_map_width_ * kMacroBlockSize * - active_map_height_ * kMacroBlockSize; - const int padding_size = active_map_area - y_plane_size; - const int buffer_size = yuv_image_size + padding_size; - - yuv_image_.reset(new uint8[buffer_size]); - - // Reset image value to 128 so we just need to fill in the y plane. - memset(yuv_image_.get(), 128, yuv_image_size); - - // Fill in the information for |image_|. - unsigned char* image = reinterpret_cast(yuv_image_.get()); - image_->planes[0] = image; - image_->planes[1] = image + y_plane_size; - image_->planes[2] = image + y_plane_size + uv_plane_size; - image_->stride[0] = image_->w; - image_->stride[1] = uv_width; - image_->stride[2] = uv_width; - - // Configure the encoder. - vpx_codec_enc_cfg_t config; - const vpx_codec_iface_t* algo = vpx_codec_vp8_cx(); - CHECK(algo); - vpx_codec_err_t ret = vpx_codec_enc_config_default(algo, &config, 0); - if (ret != VPX_CODEC_OK) - return false; - - config.rc_target_bitrate = image_->w * image_->h * - config.rc_target_bitrate / config.g_w / config.g_h; - config.g_w = image_->w; - config.g_h = image_->h; - config.g_pass = VPX_RC_ONE_PASS; - - // Value of 2 means using the real time profile. This is basically a - // redundant option since we explicitly select real time mode when doing - // encoding. - config.g_profile = 2; - - // Using 2 threads gives a great boost in performance for most systems with - // adequate processing power. NB: Going to multiple threads on low end - // windows systems can really hurt performance. - // http://crbug.com/99179 - config.g_threads = (base::SysInfo::NumberOfProcessors() > 2) ? 2 : 1; - config.rc_min_quantizer = 20; - config.rc_max_quantizer = 30; - config.g_timebase.num = 1; - config.g_timebase.den = 20; - - if (vpx_codec_enc_init(codec_.get(), algo, &config, 0)) - return false; - - // Value of 16 will have the smallest CPU load. This turns off subpixel - // motion search. - if (vpx_codec_control(codec_.get(), VP8E_SET_CPUUSED, 16)) - return false; - - // Use the lowest level of noise sensitivity so as to spend less time - // on motion estimation and inter-prediction mode. - if (vpx_codec_control(codec_.get(), VP8E_SET_NOISE_SENSITIVITY, 0)) - return false; - return true; -} - -void EncoderVp8::PrepareImage(scoped_refptr capture_data, - SkRegion* updated_region) { - // Perform RGB->YUV conversion. - CHECK_EQ(capture_data->pixel_format(), media::VideoFrame::RGB32) - << "Only RGB32 is supported"; - - const SkRegion& region = capture_data->dirty_region(); - if (region.isEmpty()) { - updated_region->setEmpty(); - return; - } - - // Align the region to macroblocks, to avoid encoding artefacts. - // This also ensures that all rectangles have even-aligned top-left, which - // is required for ConvertRGBToYUVWithRect() to work. - std::vector aligned_rects; - for (SkRegion::Iterator r(region); !r.done(); r.next()) { - aligned_rects.push_back(AlignRect(r.rect())); - } - DCHECK(!aligned_rects.empty()); - updated_region->setRects(&aligned_rects[0], aligned_rects.size()); - - // Clip back to the screen dimensions, in case they're not macroblock aligned. - // The conversion routines don't require even width & height, so this is safe - // even if the source dimensions are not even. - updated_region->op(SkIRect::MakeWH(image_->w, image_->h), - SkRegion::kIntersect_Op); - - // Convert the updated region to YUV ready for encoding. - const uint8* rgb_data = capture_data->data_planes().data[0]; - const int rgb_stride = capture_data->data_planes().strides[0]; - const int y_stride = image_->stride[0]; - DCHECK(image_->stride[1] == image_->stride[2]); - const int uv_stride = image_->stride[1]; - uint8* y_data = image_->planes[0]; - uint8* u_data = image_->planes[1]; - uint8* v_data = image_->planes[2]; - for (SkRegion::Iterator r(*updated_region); !r.done(); r.next()) { - const SkIRect& rect = r.rect(); - ConvertRGB32ToYUVWithRect( - rgb_data, y_data, u_data, v_data, - rect.x(), rect.y(), rect.width(), rect.height(), - rgb_stride, y_stride, uv_stride); - } -} - -void EncoderVp8::PrepareActiveMap(const SkRegion& updated_region) { - // Clear active map first. - memset(active_map_.get(), 0, active_map_width_ * active_map_height_); - - // Mark updated areas active. - for (SkRegion::Iterator r(updated_region); !r.done(); r.next()) { - const SkIRect& rect = r.rect(); - int left = rect.left() / kMacroBlockSize; - int right = (rect.right() - 1) / kMacroBlockSize; - int top = rect.top() / kMacroBlockSize; - int bottom = (rect.bottom() - 1) / kMacroBlockSize; - CHECK(right < active_map_width_); - CHECK(bottom < active_map_height_); - - uint8* map = active_map_.get() + top * active_map_width_; - for (int y = top; y <= bottom; ++y) { - for (int x = left; x <= right; ++x) - map[x] = 1; - map += active_map_width_; - } - } -} - -void EncoderVp8::Encode(scoped_refptr capture_data, - bool key_frame, - const DataAvailableCallback& data_available_callback) { - DCHECK_LE(32, capture_data->size().width()); - DCHECK_LE(32, capture_data->size().height()); - - if (!initialized_ || - (capture_data->size() != SkISize::Make(image_->w, image_->h))) { - bool ret = Init(capture_data->size()); - // TODO(hclam): Handle error better. - CHECK(ret) << "Initialization of encoder failed"; - initialized_ = ret; - } - - // Convert the updated capture data ready for encode. - SkRegion updated_region; - PrepareImage(capture_data, &updated_region); - - // Update active map based on updated region. - PrepareActiveMap(updated_region); - - // Apply active map to the encoder. - vpx_active_map_t act_map; - act_map.rows = active_map_height_; - act_map.cols = active_map_width_; - act_map.active_map = active_map_.get(); - if (vpx_codec_control(codec_.get(), VP8E_SET_ACTIVEMAP, &act_map)) { - LOG(ERROR) << "Unable to apply active map"; - } - - // Do the actual encoding. - vpx_codec_err_t ret = vpx_codec_encode(codec_.get(), image_.get(), - last_timestamp_, - 1, 0, VPX_DL_REALTIME); - DCHECK_EQ(ret, VPX_CODEC_OK) - << "Encoding error: " << vpx_codec_err_to_string(ret) << "\n" - << "Details: " << vpx_codec_error(codec_.get()) << "\n" - << vpx_codec_error_detail(codec_.get()); - - // TODO(hclam): Apply the proper timestamp here. - last_timestamp_ += 50; - - // Read the encoded data. - vpx_codec_iter_t iter = NULL; - bool got_data = false; - - // TODO(hclam): Make sure we get exactly one frame from the packet. - // TODO(hclam): We should provide the output buffer to avoid one copy. - scoped_ptr packet(new VideoPacket()); - - while (!got_data) { - const vpx_codec_cx_pkt_t* vpx_packet = vpx_codec_get_cx_data(codec_.get(), - &iter); - if (!vpx_packet) - continue; - - switch (vpx_packet->kind) { - case VPX_CODEC_CX_FRAME_PKT: - got_data = true; - // TODO(sergeyu): Split each frame into multiple partitions. - packet->set_data(vpx_packet->data.frame.buf, vpx_packet->data.frame.sz); - break; - default: - break; - } - } - - // Construct the VideoPacket message. - packet->mutable_format()->set_encoding(VideoPacketFormat::ENCODING_VP8); - packet->set_flags(VideoPacket::FIRST_PACKET | VideoPacket::LAST_PACKET | - VideoPacket::LAST_PARTITION); - packet->mutable_format()->set_screen_width(capture_data->size().width()); - packet->mutable_format()->set_screen_height(capture_data->size().height()); - packet->set_capture_time_ms(capture_data->capture_time_ms()); - packet->set_client_sequence_number(capture_data->client_sequence_number()); - SkIPoint dpi(capture_data->dpi()); - if (dpi.x()) - packet->mutable_format()->set_x_dpi(dpi.x()); - if (dpi.y()) - packet->mutable_format()->set_y_dpi(dpi.y()); - for (SkRegion::Iterator r(updated_region); !r.done(); r.next()) { - Rect* rect = packet->add_dirty_rects(); - rect->set_x(r.rect().x()); - rect->set_y(r.rect().y()); - rect->set_width(r.rect().width()); - rect->set_height(r.rect().height()); - } - - data_available_callback.Run(packet.Pass()); -} - -} // namespace remoting diff --git a/remoting/base/encoder_vp8.h b/remoting/base/encoder_vp8.h deleted file mode 100644 index b9883a5..0000000 --- a/remoting/base/encoder_vp8.h +++ /dev/null @@ -1,64 +0,0 @@ -// 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. - -#ifndef REMOTING_BASE_ENCODER_VP8_H_ -#define REMOTING_BASE_ENCODER_VP8_H_ - -#include "base/gtest_prod_util.h" -#include "remoting/base/encoder.h" -#include "third_party/skia/include/core/SkRegion.h" - -typedef struct vpx_codec_ctx vpx_codec_ctx_t; -typedef struct vpx_image vpx_image_t; - -namespace remoting { - -// A class that uses VP8 to perform encoding. -class EncoderVp8 : public Encoder { - public: - EncoderVp8(); - virtual ~EncoderVp8(); - - virtual void Encode( - scoped_refptr capture_data, - bool key_frame, - const DataAvailableCallback& data_available_callback) OVERRIDE; - - private: - FRIEND_TEST_ALL_PREFIXES(EncoderVp8Test, AlignAndClipRect); - - // Initialize the encoder. Returns true if successful. - bool Init(const SkISize& size); - - // Destroy the encoder. - void Destroy(); - - // Prepare |image_| for encoding. Write updated rectangles into - // |updated_region|. - void PrepareImage(scoped_refptr capture_data, - SkRegion* updated_region); - - // Update the active map according to |updated_region|. Active map is then - // given to the encoder to speed up encoding. - void PrepareActiveMap(const SkRegion& updated_region); - - // True if the encoder is initialized. - bool initialized_; - - scoped_ptr codec_; - scoped_ptr image_; - scoped_array active_map_; - int active_map_width_; - int active_map_height_; - int last_timestamp_; - - // Buffer for storing the yuv image. - scoped_array yuv_image_; - - DISALLOW_COPY_AND_ASSIGN(EncoderVp8); -}; - -} // namespace remoting - -#endif // REMOTING_BASE_ENCODER_VP8_H_ diff --git a/remoting/base/encoder_vp8_unittest.cc b/remoting/base/encoder_vp8_unittest.cc deleted file mode 100644 index 635284b..0000000 --- a/remoting/base/encoder_vp8_unittest.cc +++ /dev/null @@ -1,96 +0,0 @@ -// 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 -#include - -#include "base/bind.h" -#include "base/callback.h" -#include "base/memory/scoped_ptr.h" -#include "remoting/base/capture_data.h" -#include "remoting/base/codec_test.h" -#include "remoting/base/encoder_vp8.h" -#include "remoting/proto/video.pb.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace { - -const int kIntMax = std::numeric_limits::max(); - -} // namespace - -namespace remoting { - -TEST(EncoderVp8Test, TestEncoder) { - EncoderVp8 encoder; - TestEncoder(&encoder, false); -} - -class EncoderCallback { - public: - void DataAvailable(scoped_ptr packet) { - } -}; - -// Test that calling Encode with a differently-sized CaptureData does not -// leak memory. -TEST(EncoderVp8Test, TestSizeChangeNoLeak) { - int height = 1000; - int width = 1000; - const int kBytesPerPixel = 4; - - EncoderVp8 encoder; - EncoderCallback callback; - - std::vector buffer(width * height * kBytesPerPixel); - DataPlanes planes; - planes.data[0] = &buffer.front(); - planes.strides[0] = width; - - scoped_refptr capture_data(new CaptureData( - planes, SkISize::Make(width, height), media::VideoFrame::RGB32)); - encoder.Encode(capture_data, false, - base::Bind(&EncoderCallback::DataAvailable, - base::Unretained(&callback))); - - height /= 2; - capture_data = new CaptureData(planes, SkISize::Make(width, height), - media::VideoFrame::RGB32); - encoder.Encode(capture_data, false, - base::Bind(&EncoderCallback::DataAvailable, - base::Unretained(&callback))); -} - -class EncoderDpiCallback { - public: - void DataAvailable(scoped_ptr packet) { - EXPECT_EQ(packet->format().x_dpi(), 96); - EXPECT_EQ(packet->format().y_dpi(), 97); - } -}; - -// Test that the DPI information is correctly propagated from the CaptureData -// to the VideoPacket. -TEST(EncoderVp8Test, TestDpiPropagation) { - int height = 32; - int width = 32; - const int kBytesPerPixel = 4; - - EncoderVp8 encoder; - EncoderDpiCallback callback; - - std::vector buffer(width * height * kBytesPerPixel); - DataPlanes planes; - planes.data[0] = &buffer.front(); - planes.strides[0] = width; - - scoped_refptr capture_data(new CaptureData( - planes, SkISize::Make(width, height), media::VideoFrame::RGB32)); - capture_data->set_dpi(SkIPoint::Make(96, 97)); - encoder.Encode(capture_data, false, - base::Bind(&EncoderDpiCallback::DataAvailable, - base::Unretained(&callback))); -} - -} // namespace remoting diff --git a/remoting/client/rectangle_update_decoder.cc b/remoting/client/rectangle_update_decoder.cc index 1b4a771..16638ad 100644 --- a/remoting/client/rectangle_update_decoder.cc +++ b/remoting/client/rectangle_update_decoder.cc @@ -11,10 +11,10 @@ #include "base/logging.h" #include "base/message_loop_proxy.h" #include "ppapi/cpp/image_data.h" -#include "remoting/base/decoder.h" -#include "remoting/base/decoder_row_based.h" -#include "remoting/base/decoder_vp8.h" #include "remoting/base/util.h" +#include "remoting/codec/video_decoder.h" +#include "remoting/codec/video_decoder_row_based.h" +#include "remoting/codec/video_decoder_vp8.h" #include "remoting/client/frame_consumer.h" #include "remoting/protocol/session_config.h" diff --git a/remoting/client/rectangle_update_decoder.h b/remoting/client/rectangle_update_decoder.h index fdee520..afe0f28 100644 --- a/remoting/client/rectangle_update_decoder.h +++ b/remoting/client/rectangle_update_decoder.h @@ -10,7 +10,7 @@ #include "base/callback_forward.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" -#include "remoting/base/decoder.h" +#include "remoting/codec/video_decoder.h" #include "remoting/client/frame_consumer_proxy.h" #include "remoting/client/frame_producer.h" diff --git a/remoting/codec/codec_test.cc b/remoting/codec/codec_test.cc new file mode 100644 index 0000000..d811e78 --- /dev/null +++ b/remoting/codec/codec_test.cc @@ -0,0 +1,496 @@ +// 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 +#include + +#include "base/bind.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "media/base/video_frame.h" +#include "remoting/codec/codec_test.h" +#include "remoting/codec/video_decoder.h" +#include "remoting/codec/video_encoder.h" +#include "remoting/base/util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +const int kBytesPerPixel = 4; + +// Some sample rects for testing. +std::vector > MakeTestRectLists(const SkISize& size) { + std::vector > rect_lists; + std::vector rects; + rects.push_back(SkIRect::MakeXYWH(0, 0, size.width(), size.height())); + rect_lists.push_back(rects); + rects.clear(); + rects.push_back(SkIRect::MakeXYWH(0, 0, size.width() / 2, size.height() / 2)); + rect_lists.push_back(rects); + rects.clear(); + rects.push_back(SkIRect::MakeXYWH(size.width() / 2, size.height() / 2, + size.width() / 2, size.height() / 2)); + rect_lists.push_back(rects); + rects.clear(); + rects.push_back(SkIRect::MakeXYWH(16, 16, 16, 16)); + rects.push_back(SkIRect::MakeXYWH(128, 64, 32, 32)); + rect_lists.push_back(rects); + return rect_lists; +} + +} // namespace + +namespace remoting { + +// A class to test the message output of the encoder. +class EncoderMessageTester { + public: + EncoderMessageTester() + : begin_rect_(0), + rect_data_(0), + end_rect_(0), + added_rects_(0), + state_(kWaitingForBeginRect), + strict_(false) { + } + + ~EncoderMessageTester() { + EXPECT_EQ(begin_rect_, end_rect_); + EXPECT_GT(begin_rect_, 0); + EXPECT_EQ(kWaitingForBeginRect, state_); + if (strict_) { + EXPECT_EQ(added_rects_, begin_rect_); + } + } + + // Test that we received the correct packet. + void ReceivedPacket(VideoPacket* packet) { + if (state_ == kWaitingForBeginRect) { + EXPECT_TRUE((packet->flags() & VideoPacket::FIRST_PACKET) != 0); + state_ = kWaitingForRectData; + ++begin_rect_; + + if (strict_) { + SkIRect rect = rects_.front(); + rects_.pop_front(); + EXPECT_EQ(rect.fLeft, packet->format().x()); + EXPECT_EQ(rect.fTop, packet->format().y()); + EXPECT_EQ(rect.width(), packet->format().width()); + EXPECT_EQ(rect.height(), packet->format().height()); + } + } else { + EXPECT_FALSE((packet->flags() & VideoPacket::FIRST_PACKET) != 0); + } + + if (state_ == kWaitingForRectData) { + if (packet->has_data()) { + ++rect_data_; + } + + if ((packet->flags() & VideoPacket::LAST_PACKET) != 0) { + // Expect that we have received some data. + EXPECT_GT(rect_data_, 0); + rect_data_ = 0; + state_ = kWaitingForBeginRect; + ++end_rect_; + } + + if ((packet->flags() & VideoPacket::LAST_PARTITION) != 0) { + // LAST_PARTITION must always be marked with LAST_PACKET. + EXPECT_TRUE((packet->flags() & VideoPacket::LAST_PACKET) != 0); + } + } + } + + void set_strict(bool strict) { + strict_ = strict; + } + + void AddRects(const SkIRect* rects, int count) { + rects_.insert(rects_.begin() + rects_.size(), rects, rects + count); + added_rects_ += count; + } + + private: + enum State { + kWaitingForBeginRect, + kWaitingForRectData, + }; + + int begin_rect_; + int rect_data_; + int end_rect_; + int added_rects_; + State state_; + bool strict_; + + std::deque rects_; + + DISALLOW_COPY_AND_ASSIGN(EncoderMessageTester); +}; + +class DecoderTester { + public: + DecoderTester(Decoder* decoder, const SkISize& screen_size, + const SkISize& view_size) + : screen_size_(screen_size), + view_size_(view_size), + strict_(false), + decoder_(decoder) { + image_data_.reset(new uint8[ + view_size_.width() * view_size_.height() * kBytesPerPixel]); + EXPECT_TRUE(image_data_.get()); + decoder_->Initialize(screen_size_); + } + + void Reset() { + expected_region_.setEmpty(); + update_region_.setEmpty(); + } + + void ResetRenderedData() { + memset(image_data_.get(), 0, + view_size_.width() * view_size_.height() * kBytesPerPixel); + } + + void ReceivedPacket(VideoPacket* packet) { + Decoder::DecodeResult result = decoder_->DecodePacket(packet); + + ASSERT_NE(Decoder::DECODE_ERROR, result); + + if (result == Decoder::DECODE_DONE) { + RenderFrame(); + } + } + + void RenderFrame() { + decoder_->RenderFrame(view_size_, + SkIRect::MakeSize(view_size_), + image_data_.get(), + view_size_.width() * kBytesPerPixel, + &update_region_); + } + + void ReceivedScopedPacket(scoped_ptr packet) { + ReceivedPacket(packet.get()); + } + + void set_strict(bool strict) { + strict_ = strict; + } + + void set_capture_data(scoped_refptr data) { + capture_data_ = data; + } + + void AddRects(const SkIRect* rects, int count) { + SkRegion new_rects; + new_rects.setRects(rects, count); + AddRegion(new_rects); + } + + void AddRegion(const SkRegion& region) { + expected_region_.op(region, SkRegion::kUnion_Op); + } + + void VerifyResults() { + if (!strict_) + return; + + ASSERT_TRUE(capture_data_.get()); + + // Test the content of the update region. + EXPECT_EQ(expected_region_, update_region_); + for (SkRegion::Iterator i(update_region_); !i.done(); i.next()) { + const int stride = view_size_.width() * kBytesPerPixel; + EXPECT_EQ(stride, capture_data_->data_planes().strides[0]); + const int offset = stride * i.rect().top() + + kBytesPerPixel * i.rect().left(); + const uint8* original = capture_data_->data_planes().data[0] + offset; + const uint8* decoded = image_data_.get() + offset; + const int row_size = kBytesPerPixel * i.rect().width(); + for (int y = 0; y < i.rect().height(); ++y) { + EXPECT_EQ(0, memcmp(original, decoded, row_size)) + << "Row " << y << " is different"; + original += stride; + decoded += stride; + } + } + } + + // The error at each pixel is the root mean square of the errors in + // the R, G, and B components, each normalized to [0, 1]. This routine + // checks that the maximum and mean pixel errors do not exceed given limits. +void VerifyResultsApprox(const uint8* expected_view_data, + double max_error_limit, double mean_error_limit) { + double max_error = 0.0; + double sum_error = 0.0; + int error_num = 0; + for (SkRegion::Iterator i(update_region_); !i.done(); i.next()) { + const int stride = view_size_.width() * kBytesPerPixel; + const int offset = stride * i.rect().top() + + kBytesPerPixel * i.rect().left(); + const uint8* expected = expected_view_data + offset; + const uint8* actual = image_data_.get() + offset; + for (int y = 0; y < i.rect().height(); ++y) { + for (int x = 0; x < i.rect().width(); ++x) { + double error = CalculateError(expected, actual); + max_error = std::max(max_error, error); + sum_error += error; + ++error_num; + expected += 4; + actual += 4; + } + } + } + EXPECT_LE(max_error, max_error_limit); + double mean_error = sum_error / error_num; + EXPECT_LE(mean_error, mean_error_limit); + LOG(INFO) << "Max error: " << max_error; + LOG(INFO) << "Mean error: " << mean_error; + } + + double CalculateError(const uint8* original, const uint8* decoded) { + double error_sum_squares = 0.0; + for (int i = 0; i < 3; i++) { + double error = static_cast(*original++) - + static_cast(*decoded++); + error /= 255.0; + error_sum_squares += error * error; + } + original++; + decoded++; + return sqrt(error_sum_squares / 3.0); + } + + private: + SkISize screen_size_; + SkISize view_size_; + bool strict_; + SkRegion expected_region_; + SkRegion update_region_; + Decoder* decoder_; + scoped_array image_data_; + scoped_refptr capture_data_; + + DISALLOW_COPY_AND_ASSIGN(DecoderTester); +}; + +// The EncoderTester provides a hook for retrieving the data, and passing the +// message to other subprograms for validaton. +class EncoderTester { + public: + EncoderTester(EncoderMessageTester* message_tester) + : message_tester_(message_tester), + decoder_tester_(NULL), + data_available_(0) { + } + + ~EncoderTester() { + EXPECT_GT(data_available_, 0); + } + + void DataAvailable(scoped_ptr packet) { + ++data_available_; + message_tester_->ReceivedPacket(packet.get()); + + // Send the message to the DecoderTester. + if (decoder_tester_) { + decoder_tester_->ReceivedPacket(packet.get()); + } + } + + void AddRects(const SkIRect* rects, int count) { + message_tester_->AddRects(rects, count); + } + + void set_decoder_tester(DecoderTester* decoder_tester) { + decoder_tester_ = decoder_tester; + } + + private: + EncoderMessageTester* message_tester_; + DecoderTester* decoder_tester_; + int data_available_; + + DISALLOW_COPY_AND_ASSIGN(EncoderTester); +}; + +scoped_refptr PrepareEncodeData(const SkISize& size, + media::VideoFrame::Format format, + uint8** memory) { + // TODO(hclam): Support also YUV format. + CHECK_EQ(format, media::VideoFrame::RGB32); + int memory_size = size.width() * size.height() * kBytesPerPixel; + + *memory = new uint8[memory_size]; + srand(0); + for (int i = 0; i < memory_size; ++i) { + (*memory)[i] = rand() % 256; + } + + DataPlanes planes; + memset(planes.data, 0, sizeof(planes.data)); + memset(planes.strides, 0, sizeof(planes.strides)); + planes.data[0] = *memory; + planes.strides[0] = size.width() * kBytesPerPixel; + + scoped_refptr data = + new CaptureData(planes, size, format); + return data; +} + +static void TestEncodingRects(Encoder* encoder, + EncoderTester* tester, + scoped_refptr data, + const SkIRect* rects, int count) { + data->mutable_dirty_region().setEmpty(); + for (int i = 0; i < count; ++i) { + data->mutable_dirty_region().op(rects[i], SkRegion::kUnion_Op); + } + tester->AddRects(rects, count); + + encoder->Encode(data, true, base::Bind( + &EncoderTester::DataAvailable, base::Unretained(tester))); +} + +void TestEncoder(Encoder* encoder, bool strict) { + SkISize kSize = SkISize::Make(320, 240); + + EncoderMessageTester message_tester; + message_tester.set_strict(strict); + + EncoderTester tester(&message_tester); + + uint8* memory; + scoped_refptr data = + PrepareEncodeData(kSize, media::VideoFrame::RGB32, &memory); + scoped_array memory_wrapper(memory); + + std::vector > test_rect_lists = MakeTestRectLists(kSize); + for (size_t i = 0; i < test_rect_lists.size(); ++i) { + const std::vector& test_rects = test_rect_lists[i]; + TestEncodingRects(encoder, &tester, data, + &test_rects[0], test_rects.size()); + } +} + +static void TestEncodeDecodeRects(Encoder* encoder, + EncoderTester* encoder_tester, + DecoderTester* decoder_tester, + scoped_refptr data, + const SkIRect* rects, int count) { + data->mutable_dirty_region().setRects(rects, count); + encoder_tester->AddRects(rects, count); + decoder_tester->AddRects(rects, count); + + // Generate random data for the updated region. + srand(0); + for (int i = 0; i < count; ++i) { + CHECK_EQ(data->pixel_format(), media::VideoFrame::RGB32); + const int bytes_per_pixel = 4; // Because of RGB32 on previous line. + const int row_size = bytes_per_pixel * rects[i].width(); + uint8* memory = data->data_planes().data[0] + + data->data_planes().strides[0] * rects[i].top() + + bytes_per_pixel * rects[i].left(); + for (int y = 0; y < rects[i].height(); ++y) { + for (int x = 0; x < row_size; ++x) + memory[x] = rand() % 256; + memory += data->data_planes().strides[0]; + } + } + + encoder->Encode(data, true, base::Bind(&EncoderTester::DataAvailable, + base::Unretained(encoder_tester))); + decoder_tester->VerifyResults(); + decoder_tester->Reset(); +} + +void TestEncoderDecoder(Encoder* encoder, Decoder* decoder, bool strict) { + SkISize kSize = SkISize::Make(320, 240); + + EncoderMessageTester message_tester; + message_tester.set_strict(strict); + + EncoderTester encoder_tester(&message_tester); + + uint8* memory; + scoped_refptr data = + PrepareEncodeData(kSize, media::VideoFrame::RGB32, &memory); + scoped_array memory_wrapper(memory); + + DecoderTester decoder_tester(decoder, kSize, kSize); + decoder_tester.set_strict(strict); + decoder_tester.set_capture_data(data); + encoder_tester.set_decoder_tester(&decoder_tester); + + std::vector > test_rect_lists = MakeTestRectLists(kSize); + for (size_t i = 0; i < test_rect_lists.size(); ++i) { + const std::vector test_rects = test_rect_lists[i]; + TestEncodeDecodeRects(encoder, &encoder_tester, &decoder_tester, data, + &test_rects[0], test_rects.size()); + } +} + +static void FillWithGradient(uint8* memory, const SkISize& frame_size, + const SkIRect& rect) { + for (int j = rect.top(); j < rect.bottom(); ++j) { + uint8* p = memory + ((j * frame_size.width()) + rect.left()) * 4; + for (int i = rect.left(); i < rect.right(); ++i) { + *p++ = static_cast((255.0 * i) / frame_size.width()); + *p++ = static_cast((164.0 * j) / frame_size.height()); + *p++ = static_cast((82.0 * (i + j)) / + (frame_size.width() + frame_size.height())); + *p++ = 0; + } + } +} + +void TestEncoderDecoderGradient(Encoder* encoder, + Decoder* decoder, + const SkISize& screen_size, + const SkISize& view_size, + double max_error_limit, + double mean_error_limit) { + SkIRect screen_rect = SkIRect::MakeSize(screen_size); + scoped_array screen_data(new uint8[ + screen_size.width() * screen_size.height() * kBytesPerPixel]); + FillWithGradient(screen_data.get(), screen_size, screen_rect); + + SkIRect view_rect = SkIRect::MakeSize(view_size); + scoped_array expected_view_data(new uint8[ + view_size.width() * view_size.height() * kBytesPerPixel]); + FillWithGradient(expected_view_data.get(), view_size, view_rect); + + DataPlanes planes; + memset(planes.data, 0, sizeof(planes.data)); + memset(planes.strides, 0, sizeof(planes.strides)); + planes.data[0] = screen_data.get(); + planes.strides[0] = screen_size.width() * kBytesPerPixel; + + scoped_refptr capture_data = + new CaptureData(planes, screen_size, media::VideoFrame::RGB32); + capture_data->mutable_dirty_region().op(screen_rect, SkRegion::kUnion_Op); + + DecoderTester decoder_tester(decoder, screen_size, view_size); + decoder_tester.set_capture_data(capture_data); + decoder_tester.AddRegion(capture_data->dirty_region()); + + encoder->Encode(capture_data, true, + base::Bind(&DecoderTester::ReceivedScopedPacket, + base::Unretained(&decoder_tester))); + + decoder_tester.VerifyResultsApprox(expected_view_data.get(), + max_error_limit, mean_error_limit); + + // Check that the decoder correctly re-renders the frame if its client + // invalidates the frame. + decoder_tester.ResetRenderedData(); + decoder->Invalidate(view_size, SkRegion(view_rect)); + decoder_tester.RenderFrame(); + decoder_tester.VerifyResultsApprox(expected_view_data.get(), + max_error_limit, mean_error_limit); +} + +} // namespace remoting diff --git a/remoting/codec/codec_test.h b/remoting/codec/codec_test.h new file mode 100644 index 0000000..e7168ed --- /dev/null +++ b/remoting/codec/codec_test.h @@ -0,0 +1,40 @@ +// 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. + +#ifndef REMOTING_CODEC_CODEC_TEST_H_ +#define REMOTING_CODEC_CODEC_TEST_H_ + +#include "base/memory/ref_counted.h" +#include "media/base/video_frame.h" +#include "remoting/base/capture_data.h" + +namespace remoting { + +class Decoder; +class Encoder; + +// Generate test data and test the encoder for a regular encoding sequence. +// This will test encoder test and the sequence of messages sent. +// +// If |strict| is set to true then this routine will make sure the updated +// rects match dirty rects. +void TestEncoder(Encoder* encoder, bool strict); + +// Generate test data and test the encoder and decoder pair. +// +// If |strict| is set to true, this routine will make sure the updated rects +// are correct. +void TestEncoderDecoder(Encoder* encoder, Decoder* decoder, bool strict); + +// Generate a frame containing a gradient, and test the encoder and decoder +// pair. +void TestEncoderDecoderGradient(Encoder* encoder, Decoder* decoder, + const SkISize& screen_size, + const SkISize& view_size, + double max_error_limit, + double mean_error_limit); + +} // namespace remoting + +#endif // REMOTING_CODEC_CODEC_TEST_H_ diff --git a/remoting/codec/video_decoder.h b/remoting/codec/video_decoder.h new file mode 100644 index 0000000..70e5723 --- /dev/null +++ b/remoting/codec/video_decoder.h @@ -0,0 +1,75 @@ +// 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. + +#ifndef REMOTING_CODEC_VIDEO_DECODER_H_ +#define REMOTING_CODEC_VIDEO_DECODER_H_ + +#include "base/basictypes.h" +#include "remoting/proto/video.pb.h" +#include "third_party/skia/include/core/SkRect.h" +#include "third_party/skia/include/core/SkRegion.h" +#include "third_party/skia/include/core/SkSize.h" + +namespace remoting { + +// Interface for a decoder that takes a stream of bytes from the network and +// outputs frames of data. +// +// TODO(ajwong): Beef up this documentation once the API stablizes. +class Decoder { + public: + // DecodeResult is returned from DecodePacket() and indicates current state + // of the decoder. DECODE_DONE means that last packet for the frame was + // processed, and the frame can be displayed now. DECODE_IN_PROGRESS + // indicates that the decoder must receive more data before the frame can be + // displayed. DECODE_ERROR is returned if there was an error in the stream. + enum DecodeResult { + DECODE_ERROR = -1, + DECODE_IN_PROGRESS, + DECODE_DONE, + }; + + Decoder() {} + virtual ~Decoder() {} + + // Initializes the decoder and sets the output dimensions. + // |screen size| must not be empty. + virtual void Initialize(const SkISize& screen_size) = 0; + + // Feeds more data into the decoder. + virtual DecodeResult DecodePacket(const VideoPacket* packet) = 0; + + // Returns true if decoder is ready to accept data via DecodePacket. + virtual bool IsReadyForData() = 0; + + virtual VideoPacketFormat::Encoding Encoding() = 0; + + // Marks the specified |region| of the view for update the next time + // RenderFrame() is called. |region| is expressed in |view_size| coordinates. + // |view_size| must not be empty. + virtual void Invalidate(const SkISize& view_size, + const SkRegion& region) = 0; + + // Copies invalidated pixels within |clip_area| to |image_buffer|. Pixels are + // invalidated either by new data received in DecodePacket(), or by explicit + // calls to Invalidate(). |clip_area| is specified in |view_size| coordinates. + // If |view_size| differs from the source size then the copied pixels will be + // scaled accordingly. |view_size| cannot be empty. + // + // |image_buffer|'s origin must correspond to the top-left of |clip_area|, + // and the buffer must be large enough to hold |clip_area| RGBA32 pixels. + // |image_stride| gives the output buffer's stride in pixels. + // + // On return, |output_region| contains the updated area, in |view_size| + // coordinates. + virtual void RenderFrame(const SkISize& view_size, + const SkIRect& clip_area, + uint8* image_buffer, + int image_stride, + SkRegion* output_region) = 0; +}; + +} // namespace remoting + +#endif // REMOTING_CODEC_VIDEO_DECODER_H_ diff --git a/remoting/codec/video_decoder_row_based.cc b/remoting/codec/video_decoder_row_based.cc new file mode 100644 index 0000000..7072a6f --- /dev/null +++ b/remoting/codec/video_decoder_row_based.cc @@ -0,0 +1,225 @@ +// 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 "remoting/codec/video_decoder_row_based.h" + +#include "base/logging.h" +#include "remoting/base/decompressor.h" +#include "remoting/base/decompressor_zlib.h" +#include "remoting/base/decompressor_verbatim.h" +#include "remoting/base/util.h" + +namespace remoting { + +namespace { +// Both input and output data are assumed to be RGBA32. +const int kBytesPerPixel = 4; +} + +DecoderRowBased* DecoderRowBased::CreateZlibDecoder() { + return new DecoderRowBased(new DecompressorZlib(), + VideoPacketFormat::ENCODING_ZLIB); +} + +DecoderRowBased* DecoderRowBased::CreateVerbatimDecoder() { + return new DecoderRowBased(new DecompressorVerbatim(), + VideoPacketFormat::ENCODING_VERBATIM); +} + +DecoderRowBased::DecoderRowBased(Decompressor* decompressor, + VideoPacketFormat::Encoding encoding) + : state_(kUninitialized), + clip_(SkIRect::MakeEmpty()), + decompressor_(decompressor), + encoding_(encoding), + row_pos_(0), + row_y_(0), + screen_size_(SkISize::Make(0, 0)) { +} + +DecoderRowBased::~DecoderRowBased() { +} + +bool DecoderRowBased::IsReadyForData() { + switch (state_) { + case kUninitialized: + case kError: + return false; + case kReady: + case kProcessing: + case kPartitionDone: + case kDone: + return true; + } + NOTREACHED(); + return false; +} + +void DecoderRowBased::Initialize(const SkISize& screen_size) { + decompressor_->Reset(); + updated_region_.setEmpty(); + screen_buffer_.reset(NULL); + + screen_size_ = screen_size; + // Allocate the screen buffer, if necessary. + if (!screen_size_.isEmpty()) { + screen_buffer_.reset(new uint8[ + screen_size_.width() * screen_size_.height() * kBytesPerPixel]); + } + + state_ = kReady; +} + +Decoder::DecodeResult DecoderRowBased::DecodePacket(const VideoPacket* packet) { + UpdateStateForPacket(packet); + + if (state_ == kError) { + return DECODE_ERROR; + } + + const uint8* in = reinterpret_cast(packet->data().data()); + const int in_size = packet->data().size(); + const int row_size = clip_.width() * kBytesPerPixel; + + int out_stride = screen_size_.width() * kBytesPerPixel; + uint8* out = screen_buffer_.get() + out_stride * (clip_.top() + row_y_) + + kBytesPerPixel * clip_.left(); + + // Consume all the data in the message. + bool decompress_again = true; + int used = 0; + while (decompress_again && used < in_size) { + if (row_y_ >= clip_.height()) { + state_ = kError; + LOG(WARNING) << "Too much data is received for the given rectangle."; + return DECODE_ERROR; + } + + int written = 0; + int consumed = 0; + decompress_again = decompressor_->Process( + in + used, in_size - used, out + row_pos_, row_size - row_pos_, + &consumed, &written); + used += consumed; + row_pos_ += written; + + // If this row is completely filled then move onto the next row. + if (row_pos_ == row_size) { + ++row_y_; + row_pos_ = 0; + out += out_stride; + } + } + + if (state_ == kPartitionDone || state_ == kDone) { + if (row_y_ < clip_.height()) { + state_ = kError; + LOG(WARNING) << "Received LAST_PACKET, but didn't get enough data."; + return DECODE_ERROR; + } + + updated_region_.op(clip_, SkRegion::kUnion_Op); + decompressor_->Reset(); + } + + if (state_ == kDone) { + return DECODE_DONE; + } else { + return DECODE_IN_PROGRESS; + } +} + +void DecoderRowBased::UpdateStateForPacket(const VideoPacket* packet) { + if (state_ == kError) { + return; + } + + if (packet->flags() & VideoPacket::FIRST_PACKET) { + if (state_ != kReady && state_ != kDone && state_ != kPartitionDone) { + state_ = kError; + LOG(WARNING) << "Received unexpected FIRST_PACKET."; + return; + } + + // Reset the buffer location status variables on the first packet. + clip_.setXYWH(packet->format().x(), packet->format().y(), + packet->format().width(), packet->format().height()); + if (!SkIRect::MakeSize(screen_size_).contains(clip_)) { + state_ = kError; + LOG(WARNING) << "Invalid clipping area received."; + return; + } + + state_ = kProcessing; + row_pos_ = 0; + row_y_ = 0; + } + + if (state_ != kProcessing) { + state_ = kError; + LOG(WARNING) << "Received unexpected packet."; + return; + } + + if (packet->flags() & VideoPacket::LAST_PACKET) { + if (state_ != kProcessing) { + state_ = kError; + LOG(WARNING) << "Received unexpected LAST_PACKET."; + return; + } + state_ = kPartitionDone; + } + + if (packet->flags() & VideoPacket::LAST_PARTITION) { + if (state_ != kPartitionDone) { + state_ = kError; + LOG(WARNING) << "Received unexpected LAST_PARTITION."; + return; + } + state_ = kDone; + } + + return; +} + +VideoPacketFormat::Encoding DecoderRowBased::Encoding() { + return encoding_; +} + +void DecoderRowBased::Invalidate(const SkISize& view_size, + const SkRegion& region) { + updated_region_.op(region, SkRegion::kUnion_Op); +} + +void DecoderRowBased::RenderFrame(const SkISize& view_size, + const SkIRect& clip_area, + uint8* image_buffer, + int image_stride, + SkRegion* output_region) { + output_region->setEmpty(); + + // TODO(alexeypa): scaling is not implemented. + SkIRect clip_rect = SkIRect::MakeSize(screen_size_); + if (!clip_rect.intersect(clip_area)) + return; + + int screen_stride = screen_size_.width() * kBytesPerPixel; + + for (SkRegion::Iterator i(updated_region_); !i.done(); i.next()) { + SkIRect rect(i.rect()); + if (!rect.intersect(clip_rect)) + continue; + + CopyRGB32Rect(screen_buffer_.get(), screen_stride, + clip_rect, + image_buffer, image_stride, + clip_area, + rect); + output_region->op(rect, SkRegion::kUnion_Op); + } + + updated_region_.setEmpty(); +} + +} // namespace remoting diff --git a/remoting/codec/video_decoder_row_based.h b/remoting/codec/video_decoder_row_based.h new file mode 100644 index 0000000..258809d --- /dev/null +++ b/remoting/codec/video_decoder_row_based.h @@ -0,0 +1,84 @@ +// 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. + +#ifndef REMOTING_CODEC_VIDEO_DECODER_ROW_BASED_H_ +#define REMOTING_CODEC_VIDEO_DECODER_ROW_BASED_H_ + +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "remoting/codec/video_decoder.h" + +namespace remoting { + +class Decompressor; + +class DecoderRowBased : public Decoder { + public: + virtual ~DecoderRowBased(); + + static DecoderRowBased* CreateZlibDecoder(); + static DecoderRowBased* CreateVerbatimDecoder(); + + // Decoder implementation. + virtual bool IsReadyForData() OVERRIDE; + virtual void Initialize(const SkISize& screen_size) OVERRIDE; + virtual DecodeResult DecodePacket(const VideoPacket* packet) OVERRIDE; + virtual VideoPacketFormat::Encoding Encoding() OVERRIDE; + virtual void Invalidate(const SkISize& view_size, + const SkRegion& region) OVERRIDE; + virtual void RenderFrame(const SkISize& view_size, + const SkIRect& clip_area, + uint8* image_buffer, + int image_stride, + SkRegion* output_region) OVERRIDE; + + private: + enum State { + kUninitialized, + kReady, + kProcessing, + kPartitionDone, + kDone, + kError, + }; + + DecoderRowBased(Decompressor* decompressor, + VideoPacketFormat::Encoding encoding); + + // Helper method. Called from DecodePacket to updated state of the decoder. + void UpdateStateForPacket(const VideoPacket* packet); + + // The internal state of the decoder. + State state_; + + // Keeps track of the updating rect. + SkIRect clip_; + + // The compression for the input byte stream. + scoped_ptr decompressor_; + + // The encoding of the incoming stream. + VideoPacketFormat::Encoding encoding_; + + // The position in the row that we are updating. + int row_pos_; + + // The current row in the rect that we are updaing. + int row_y_; + + // The region updated that hasn't been copied to the screen yet. + SkRegion updated_region_; + + // Size of the remote screen. + SkISize screen_size_; + + // The bitmap holding the remote screen bits. + scoped_array screen_buffer_; + + DISALLOW_COPY_AND_ASSIGN(DecoderRowBased); +}; + +} // namespace remoting + +#endif // REMOTING_CODEC_VIDEO_DECODER_ROW_BASED_H_ diff --git a/remoting/codec/video_decoder_vp8.cc b/remoting/codec/video_decoder_vp8.cc new file mode 100644 index 0000000..6a8d7c0 --- /dev/null +++ b/remoting/codec/video_decoder_vp8.cc @@ -0,0 +1,211 @@ +// 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 "remoting/codec/video_decoder_vp8.h" + +#include + +#include "base/logging.h" +#include "media/base/media.h" +#include "media/base/yuv_convert.h" +#include "remoting/base/util.h" + +extern "C" { +#define VPX_CODEC_DISABLE_COMPAT 1 +#include "third_party/libvpx/libvpx.h" +} + +namespace remoting { + +DecoderVp8::DecoderVp8() + : state_(kUninitialized), + codec_(NULL), + last_image_(NULL), + screen_size_(SkISize::Make(0, 0)) { +} + +DecoderVp8::~DecoderVp8() { + if (codec_) { + vpx_codec_err_t ret = vpx_codec_destroy(codec_); + CHECK(ret == VPX_CODEC_OK) << "Failed to destroy codec"; + } + delete codec_; +} + +void DecoderVp8::Initialize(const SkISize& screen_size) { + DCHECK(!screen_size.isEmpty()); + + screen_size_ = screen_size; + state_ = kReady; +} + +Decoder::DecodeResult DecoderVp8::DecodePacket(const VideoPacket* packet) { + DCHECK_EQ(kReady, state_); + + // Initialize the codec as needed. + if (!codec_) { + codec_ = new vpx_codec_ctx_t(); + + // TODO(hclam): Scale the number of threads with number of cores of the + // machine. + vpx_codec_dec_cfg config; + config.w = 0; + config.h = 0; + config.threads = 2; + vpx_codec_err_t ret = + vpx_codec_dec_init( + codec_, vpx_codec_vp8_dx(), &config, 0); + if (ret != VPX_CODEC_OK) { + LOG(INFO) << "Cannot initialize codec."; + delete codec_; + codec_ = NULL; + state_ = kError; + return DECODE_ERROR; + } + } + + // Do the actual decoding. + vpx_codec_err_t ret = vpx_codec_decode( + codec_, reinterpret_cast(packet->data().data()), + packet->data().size(), NULL, 0); + if (ret != VPX_CODEC_OK) { + LOG(INFO) << "Decoding failed:" << vpx_codec_err_to_string(ret) << "\n" + << "Details: " << vpx_codec_error(codec_) << "\n" + << vpx_codec_error_detail(codec_); + return DECODE_ERROR; + } + + // Gets the decoded data. + vpx_codec_iter_t iter = NULL; + vpx_image_t* image = vpx_codec_get_frame(codec_, &iter); + if (!image) { + LOG(INFO) << "No video frame decoded"; + return DECODE_ERROR; + } + last_image_ = image; + + SkRegion region; + for (int i = 0; i < packet->dirty_rects_size(); ++i) { + Rect remoting_rect = packet->dirty_rects(i); + SkIRect rect = SkIRect::MakeXYWH(remoting_rect.x(), + remoting_rect.y(), + remoting_rect.width(), + remoting_rect.height()); + region.op(rect, SkRegion::kUnion_Op); + } + + updated_region_.op(region, SkRegion::kUnion_Op); + return DECODE_DONE; +} + +bool DecoderVp8::IsReadyForData() { + return state_ == kReady; +} + +VideoPacketFormat::Encoding DecoderVp8::Encoding() { + return VideoPacketFormat::ENCODING_VP8; +} + +void DecoderVp8::Invalidate(const SkISize& view_size, + const SkRegion& region) { + DCHECK_EQ(kReady, state_); + DCHECK(!view_size.isEmpty()); + + for (SkRegion::Iterator i(region); !i.done(); i.next()) { + SkIRect rect = i.rect(); + rect = ScaleRect(rect, view_size, screen_size_); + updated_region_.op(rect, SkRegion::kUnion_Op); + } +} + +void DecoderVp8::RenderFrame(const SkISize& view_size, + const SkIRect& clip_area, + uint8* image_buffer, + int image_stride, + SkRegion* output_region) { + DCHECK_EQ(kReady, state_); + DCHECK(!view_size.isEmpty()); + + // Early-return and do nothing if we haven't yet decoded any frames. + if (!last_image_) + return; + + SkIRect source_clip = SkIRect::MakeWH(last_image_->d_w, last_image_->d_h); + + // ScaleYUVToRGB32WithRect does not currently support up-scaling. We won't + // be asked to up-scale except during resizes or if page zoom is >100%, so + // we work-around the limitation by using the slower ScaleYUVToRGB32. + // TODO(wez): Remove this hack if/when ScaleYUVToRGB32WithRect can up-scale. + if (!updated_region_.isEmpty() && + (source_clip.width() < view_size.width() || + source_clip.height() < view_size.height())) { + // We're scaling only |clip_area| into the |image_buffer|, so we need to + // work out which source rectangle that corresponds to. + SkIRect source_rect = ScaleRect(clip_area, view_size, screen_size_); + source_rect = SkIRect::MakeLTRB(RoundToTwosMultiple(source_rect.left()), + RoundToTwosMultiple(source_rect.top()), + source_rect.right(), + source_rect.bottom()); + + // If there were no changes within the clip source area then don't render. + if (!updated_region_.intersects(source_rect)) + return; + + // Scale & convert the entire clip area. + int y_offset = CalculateYOffset(source_rect.x(), + source_rect.y(), + last_image_->stride[0]); + int uv_offset = CalculateUVOffset(source_rect.x(), + source_rect.y(), + last_image_->stride[1]); + ScaleYUVToRGB32(last_image_->planes[0] + y_offset, + last_image_->planes[1] + uv_offset, + last_image_->planes[2] + uv_offset, + image_buffer, + source_rect.width(), + source_rect.height(), + clip_area.width(), + clip_area.height(), + last_image_->stride[0], + last_image_->stride[1], + image_stride, + media::YV12, + media::ROTATE_0, + media::FILTER_BILINEAR); + + output_region->op(clip_area, SkRegion::kUnion_Op); + updated_region_.op(source_rect, SkRegion::kDifference_Op); + return; + } + + for (SkRegion::Iterator i(updated_region_); !i.done(); i.next()) { + // Determine the scaled area affected by this rectangle changing. + SkIRect rect = i.rect(); + if (!rect.intersect(source_clip)) + continue; + rect = ScaleRect(rect, screen_size_, view_size); + if (!rect.intersect(clip_area)) + continue; + + ConvertAndScaleYUVToRGB32Rect(last_image_->planes[0], + last_image_->planes[1], + last_image_->planes[2], + last_image_->stride[0], + last_image_->stride[1], + screen_size_, + source_clip, + image_buffer, + image_stride, + view_size, + clip_area, + rect); + + output_region->op(rect, SkRegion::kUnion_Op); + } + + updated_region_.op(ScaleRect(clip_area, view_size, screen_size_), + SkRegion::kDifference_Op); +} + +} // namespace remoting diff --git a/remoting/codec/video_decoder_vp8.h b/remoting/codec/video_decoder_vp8.h new file mode 100644 index 0000000..2095fbb --- /dev/null +++ b/remoting/codec/video_decoder_vp8.h @@ -0,0 +1,60 @@ +// 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. + +#ifndef REMOTING_CODEC_VIDEO_DECODER_VP8_H_ +#define REMOTING_CODEC_VIDEO_DECODER_VP8_H_ + +#include "base/compiler_specific.h" +#include "remoting/codec/video_decoder.h" + +typedef struct vpx_codec_ctx vpx_codec_ctx_t; +typedef struct vpx_image vpx_image_t; + +namespace remoting { + +class DecoderVp8 : public Decoder { + public: + DecoderVp8(); + virtual ~DecoderVp8(); + + // Decoder implementations. + virtual void Initialize(const SkISize& screen_size) OVERRIDE; + virtual DecodeResult DecodePacket(const VideoPacket* packet) OVERRIDE; + virtual bool IsReadyForData() OVERRIDE; + virtual VideoPacketFormat::Encoding Encoding() OVERRIDE; + virtual void Invalidate(const SkISize& view_size, + const SkRegion& region) OVERRIDE; + virtual void RenderFrame(const SkISize& view_size, + const SkIRect& clip_area, + uint8* image_buffer, + int image_stride, + SkRegion* output_region) OVERRIDE; + + private: + enum State { + kUninitialized, + kReady, + kError, + }; + + // The internal state of the decoder. + State state_; + + vpx_codec_ctx_t* codec_; + + // Pointer to the last decoded image. + vpx_image_t* last_image_; + + // The region updated that hasn't been copied to the screen yet. + SkRegion updated_region_; + + // Output dimensions. + SkISize screen_size_; + + DISALLOW_COPY_AND_ASSIGN(DecoderVp8); +}; + +} // namespace remoting + +#endif // REMOTING_CODEC_VIDEO_DECODER_VP8_H_ diff --git a/remoting/codec/video_decoder_vp8_unittest.cc b/remoting/codec/video_decoder_vp8_unittest.cc new file mode 100644 index 0000000..472dda8 --- /dev/null +++ b/remoting/codec/video_decoder_vp8_unittest.cc @@ -0,0 +1,75 @@ +// 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 "remoting/codec/video_decoder_vp8.h" + +#include "media/base/video_frame.h" +#include "remoting/codec/codec_test.h" +#include "remoting/codec/video_encoder_vp8.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace remoting { + +class DecoderVp8Test : public testing::Test { + protected: + EncoderVp8 encoder_; + DecoderVp8 decoder_; + + void TestGradient(int screen_width, int screen_height, + int view_width, int view_height, + double max_error_limit, double mean_error_limit) { + TestEncoderDecoderGradient(&encoder_, &decoder_, + SkISize::Make(screen_width, screen_height), + SkISize::Make(view_width, view_height), + max_error_limit, mean_error_limit); + } +}; + +TEST_F(DecoderVp8Test, EncodeAndDecode) { + TestEncoderDecoder(&encoder_, &decoder_, false); +} + +// Check that encoding and decoding a particular frame doesn't change the +// frame too much. The frame used is a gradient, which does not contain sharp +// transitions, so encoding lossiness should not be too high. +TEST_F(DecoderVp8Test, Gradient) { + TestGradient(320, 240, 320, 240, 0.03, 0.01); +} + +TEST_F(DecoderVp8Test, GradientScaleUpEvenToEven) { + TestGradient(320, 240, 640, 480, 0.04, 0.02); +} + +TEST_F(DecoderVp8Test, GradientScaleUpEvenToOdd) { + TestGradient(320, 240, 641, 481, 0.04, 0.02); +} + +TEST_F(DecoderVp8Test, GradientScaleUpOddToEven) { + TestGradient(321, 241, 640, 480, 0.04, 0.02); +} + +TEST_F(DecoderVp8Test, GradientScaleUpOddToOdd) { + TestGradient(321, 241, 641, 481, 0.04, 0.02); +} + +TEST_F(DecoderVp8Test, GradientScaleDownEvenToEven) { + TestGradient(320, 240, 160, 120, 0.04, 0.02); +} + +TEST_F(DecoderVp8Test, GradientScaleDownEvenToOdd) { + // The maximum error is non-deterministic. The mean error is not too high, + // which suggests that the problem is restricted to a small area of the output + // image. See crbug.com/139437 and crbug.com/139633. + TestGradient(320, 240, 161, 121, 1.0, 0.02); +} + +TEST_F(DecoderVp8Test, GradientScaleDownOddToEven) { + TestGradient(321, 241, 160, 120, 0.04, 0.02); +} + +TEST_F(DecoderVp8Test, GradientScaleDownOddToOdd) { + TestGradient(321, 241, 161, 121, 0.04, 0.02); +} + +} // namespace remoting diff --git a/remoting/codec/video_encode_decode_unittest.cc b/remoting/codec/video_encode_decode_unittest.cc new file mode 100644 index 0000000..fac0efa --- /dev/null +++ b/remoting/codec/video_encode_decode_unittest.cc @@ -0,0 +1,32 @@ +// Copyright (c) 2010 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 "remoting/codec/video_encoder_row_based.h" + +#include "media/base/video_frame.h" +#include "remoting/codec/codec_test.h" +#include "remoting/codec/video_decoder_row_based.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace remoting { + +TEST(EncodeDecodeTest, EncodeAndDecodeZlib) { + scoped_ptr encoder(EncoderRowBased::CreateZlibEncoder()); + scoped_ptr decoder(DecoderRowBased::CreateZlibDecoder()); + TestEncoderDecoder(encoder.get(), decoder.get(), true); +} + +TEST(EncodeDecodeTest, EncodeAndDecodeSmallOutputBufferZlib) { + scoped_ptr encoder(EncoderRowBased::CreateZlibEncoder(64)); + scoped_ptr decoder(DecoderRowBased::CreateZlibDecoder()); + TestEncoderDecoder(encoder.get(), decoder.get(), true); +} + +TEST(EncodeDecodeTest, EncodeAndDecodeNoneStrictZlib) { + scoped_ptr encoder(EncoderRowBased::CreateZlibEncoder()); + scoped_ptr decoder(DecoderRowBased::CreateZlibDecoder()); + TestEncoderDecoder(encoder.get(), decoder.get(), false); +} + +} // namespace remoting diff --git a/remoting/codec/video_encoder.h b/remoting/codec/video_encoder.h new file mode 100644 index 0000000..83fb308 --- /dev/null +++ b/remoting/codec/video_encoder.h @@ -0,0 +1,48 @@ +// 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. + +#ifndef REMOTING_CODEC_VIDEO_ENCODER_H_ +#define REMOTING_CODEC_VIDEO_ENCODER_H_ + +#include "base/basictypes.h" +#include "base/callback.h" +#include "media/base/data_buffer.h" + +namespace media { + class DataBuffer; +} + +namespace remoting { + +class CaptureData; +class VideoPacket; + +// A class to perform the task of encoding a continous stream of +// images. +// This class operates asynchronously to enable maximum throughput. +class Encoder { + public: + + // DataAvailableCallback is called as blocks of data are made available + // from the encoder. Data made available by the encoder is in the form + // of HostMessage to reduce the amount of memory copies. + typedef base::Callback)> DataAvailableCallback; + + virtual ~Encoder() {} + + // Encode an image stored in |capture_data|. + // + // If |key_frame| is true, the encoder should not reference + // previous encode and encode the full frame. + // + // When encoded data is available, partial or full |data_available_callback| + // is called. + virtual void Encode(scoped_refptr capture_data, + bool key_frame, + const DataAvailableCallback& data_available_callback) = 0; +}; + +} // namespace remoting + +#endif // REMOTING_CODEC_VIDEO_ENCODER_H_ diff --git a/remoting/codec/video_encoder_row_based.cc b/remoting/codec/video_encoder_row_based.cc new file mode 100644 index 0000000..f3b0ef6 --- /dev/null +++ b/remoting/codec/video_encoder_row_based.cc @@ -0,0 +1,177 @@ +// 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 "remoting/codec/video_encoder_row_based.h" + +#include "base/logging.h" +#include "remoting/base/capture_data.h" +#include "remoting/base/compressor_verbatim.h" +#include "remoting/base/compressor_zlib.h" +#include "remoting/base/util.h" +#include "remoting/proto/video.pb.h" + +namespace remoting { + +static const int kPacketSize = 1024 * 1024; + +EncoderRowBased* EncoderRowBased::CreateZlibEncoder() { + return new EncoderRowBased(new CompressorZlib(), + VideoPacketFormat::ENCODING_ZLIB); +} + +EncoderRowBased* EncoderRowBased::CreateZlibEncoder(int packet_size) { + return new EncoderRowBased(new CompressorZlib(), + VideoPacketFormat::ENCODING_ZLIB, + packet_size); +} + +EncoderRowBased* EncoderRowBased::CreateVerbatimEncoder() { + return new EncoderRowBased(new CompressorVerbatim(), + VideoPacketFormat::ENCODING_VERBATIM); +} + +EncoderRowBased* EncoderRowBased::CreateVerbatimEncoder(int packet_size) { + return new EncoderRowBased(new CompressorVerbatim(), + VideoPacketFormat::ENCODING_VERBATIM, + packet_size); +} + +EncoderRowBased::EncoderRowBased(Compressor* compressor, + VideoPacketFormat::Encoding encoding) + : encoding_(encoding), + compressor_(compressor), + screen_size_(SkISize::Make(0,0)), + packet_size_(kPacketSize) { +} + +EncoderRowBased::EncoderRowBased(Compressor* compressor, + VideoPacketFormat::Encoding encoding, + int packet_size) + : encoding_(encoding), + compressor_(compressor), + screen_size_(SkISize::Make(0,0)), + packet_size_(packet_size) { +} + +EncoderRowBased::~EncoderRowBased() {} + +void EncoderRowBased::Encode( + scoped_refptr capture_data, + bool key_frame, + const DataAvailableCallback& data_available_callback) { + CHECK(capture_data->pixel_format() == media::VideoFrame::RGB32) + << "RowBased Encoder only works with RGB32. Got " + << capture_data->pixel_format(); + capture_data_ = capture_data; + callback_ = data_available_callback; + + const SkRegion& region = capture_data->dirty_region(); + SkRegion::Iterator iter(region); + while (!iter.done()) { + SkIRect rect = iter.rect(); + iter.next(); + EncodeRect(rect, iter.done()); + } + + capture_data_ = NULL; + callback_.Reset(); +} + +void EncoderRowBased::EncodeRect(const SkIRect& rect, bool last) { + CHECK(capture_data_->data_planes().data[0]); + CHECK_EQ(capture_data_->pixel_format(), media::VideoFrame::RGB32); + const int strides = capture_data_->data_planes().strides[0]; + const int bytes_per_pixel = 4; + const int row_size = bytes_per_pixel * rect.width(); + + compressor_->Reset(); + + scoped_ptr packet(new VideoPacket()); + PrepareUpdateStart(rect, packet.get()); + const uint8* in = capture_data_->data_planes().data[0] + + rect.fTop * strides + rect.fLeft * bytes_per_pixel; + // TODO(hclam): Fill in the sequence number. + uint8* out = GetOutputBuffer(packet.get(), packet_size_); + int filled = 0; + int row_pos = 0; // Position in the current row in bytes. + int row_y = 0; // Current row. + bool compress_again = true; + while (compress_again) { + // Prepare a message for sending out. + if (!packet.get()) { + packet.reset(new VideoPacket()); + out = GetOutputBuffer(packet.get(), packet_size_); + filled = 0; + } + + Compressor::CompressorFlush flush = Compressor::CompressorNoFlush; + if (row_y == rect.height() - 1) { + flush = Compressor::CompressorFinish; + } + + int consumed = 0; + int written = 0; + compress_again = compressor_->Process(in + row_pos, row_size - row_pos, + out + filled, packet_size_ - filled, + flush, &consumed, &written); + row_pos += consumed; + filled += written; + + // We have reached the end of stream. + if (!compress_again) { + packet->set_flags(packet->flags() | VideoPacket::LAST_PACKET); + packet->set_capture_time_ms(capture_data_->capture_time_ms()); + packet->set_client_sequence_number( + capture_data_->client_sequence_number()); + SkIPoint dpi(capture_data_->dpi()); + if (dpi.x()) + packet->mutable_format()->set_x_dpi(dpi.x()); + if (dpi.y()) + packet->mutable_format()->set_y_dpi(dpi.y()); + if (last) + packet->set_flags(packet->flags() | VideoPacket::LAST_PARTITION); + DCHECK(row_pos == row_size); + DCHECK(row_y == rect.height() - 1); + } + + // If we have filled the message or we have reached the end of stream. + if (filled == packet_size_ || !compress_again) { + packet->mutable_data()->resize(filled); + callback_.Run(packet.Pass()); + } + + // Reached the end of input row and we're not at the last row. + if (row_pos == row_size && row_y < rect.height() - 1) { + row_pos = 0; + in += strides; + ++row_y; + } + } +} + +void EncoderRowBased::PrepareUpdateStart(const SkIRect& rect, + VideoPacket* packet) { + packet->set_flags(packet->flags() | VideoPacket::FIRST_PACKET); + + VideoPacketFormat* format = packet->mutable_format(); + format->set_x(rect.fLeft); + format->set_y(rect.fTop); + format->set_width(rect.width()); + format->set_height(rect.height()); + format->set_encoding(encoding_); + if (capture_data_->size() != screen_size_) { + screen_size_ = capture_data_->size(); + format->set_screen_width(screen_size_.width()); + format->set_screen_height(screen_size_.height()); + } +} + +uint8* EncoderRowBased::GetOutputBuffer(VideoPacket* packet, size_t size) { + packet->mutable_data()->resize(size); + // TODO(ajwong): Is there a better way to do this at all??? + return const_cast(reinterpret_cast( + packet->mutable_data()->data())); +} + +} // namespace remoting diff --git a/remoting/codec/video_encoder_row_based.h b/remoting/codec/video_encoder_row_based.h new file mode 100644 index 0000000..7762f92 --- /dev/null +++ b/remoting/codec/video_encoder_row_based.h @@ -0,0 +1,71 @@ +// Copyright (c) 2011 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. + +#ifndef REMOTING_CODEC_VIDEO_ENCODER_ROW_BASED_H_ +#define REMOTING_CODEC_VIDEO_ENCODER_ROW_BASED_H_ + +#include "remoting/codec/video_encoder.h" +#include "remoting/proto/video.pb.h" +#include "third_party/skia/include/core/SkRect.h" + +namespace remoting { + +class Compressor; + +// EncoderRowBased implements an Encoder using zlib or verbatim +// compression. Zlib-based encoder must be created using +// CreateZlibEncoder(), verbatim encoder is created with +// CreateVerbatimEncoder(). +// +// Compressor is reset before encoding each rectangle, so that each +// rectangle can be decoded independently. +class EncoderRowBased : public Encoder { + public: + static EncoderRowBased* CreateZlibEncoder(); + static EncoderRowBased* CreateZlibEncoder(int packet_size); + static EncoderRowBased* CreateVerbatimEncoder(); + static EncoderRowBased* CreateVerbatimEncoder(int packet_size); + + virtual ~EncoderRowBased(); + + virtual void Encode( + scoped_refptr capture_data, + bool key_frame, + const DataAvailableCallback& data_available_callback) OVERRIDE; + + private: + EncoderRowBased(Compressor* compressor, VideoPacketFormat::Encoding encoding); + EncoderRowBased(Compressor* compressor, VideoPacketFormat::Encoding encoding, + int packet_size); + + // Encode a single dirty rect using compressor. + void EncodeRect(const SkIRect& rect, bool last); + + // Marks a packet as the first in a series of rectangle updates. + void PrepareUpdateStart(const SkIRect& rect, VideoPacket* packet); + + // Retrieves a pointer to the output buffer in |update| used for storing the + // encoded rectangle data. Will resize the buffer to |size|. + uint8* GetOutputBuffer(VideoPacket* packet, size_t size); + + // Submit |message| to |callback_|. + void SubmitMessage(VideoPacket* packet, size_t rect_index); + + // The encoding of the incoming stream. + VideoPacketFormat::Encoding encoding_; + + scoped_ptr compressor_; + + scoped_refptr capture_data_; + DataAvailableCallback callback_; + + // The most recent screen size. + SkISize screen_size_; + + int packet_size_; +}; + +} // namespace remoting + +#endif // REMOTING_CODEC_VIDEO_ENCODER_ROW_BASED_H_ diff --git a/remoting/codec/video_encoder_row_based_unittest.cc b/remoting/codec/video_encoder_row_based_unittest.cc new file mode 100644 index 0000000..76a75b5 --- /dev/null +++ b/remoting/codec/video_encoder_row_based_unittest.cc @@ -0,0 +1,22 @@ +// Copyright (c) 2010 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 "remoting/codec/video_encoder_row_based.h" + +#include "remoting/codec/codec_test.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace remoting { + +TEST(EncoderZlibTest, TestEncoder) { + scoped_ptr encoder(EncoderRowBased::CreateZlibEncoder()); + TestEncoder(encoder.get(), true); +} + +TEST(EncoderZlibTest, TestEncoderSmallOutputBuffer) { + scoped_ptr encoder(EncoderRowBased::CreateZlibEncoder(16)); + TestEncoder(encoder.get(), true); +} + +} // namespace remoting diff --git a/remoting/codec/video_encoder_vp8.cc b/remoting/codec/video_encoder_vp8.cc new file mode 100644 index 0000000..d3a5fca --- /dev/null +++ b/remoting/codec/video_encoder_vp8.cc @@ -0,0 +1,307 @@ +// 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 "remoting/codec/video_encoder_vp8.h" + +#include "base/logging.h" +#include "base/sys_info.h" +#include "media/base/yuv_convert.h" +#include "remoting/base/capture_data.h" +#include "remoting/base/util.h" +#include "remoting/proto/video.pb.h" + +extern "C" { +#define VPX_CODEC_DISABLE_COMPAT 1 +#include "third_party/libvpx/libvpx.h" +} + +namespace { + +// Defines the dimension of a macro block. This is used to compute the active +// map for the encoder. +const int kMacroBlockSize = 16; + +} // namespace remoting + +namespace remoting { + +EncoderVp8::EncoderVp8() + : initialized_(false), + codec_(NULL), + image_(NULL), + active_map_width_(0), + active_map_height_(0), + last_timestamp_(0) { +} + +EncoderVp8::~EncoderVp8() { + Destroy(); +} + +void EncoderVp8::Destroy() { + if (initialized_) { + vpx_codec_err_t ret = vpx_codec_destroy(codec_.get()); + DCHECK(ret == VPX_CODEC_OK) << "Failed to destroy codec"; + initialized_ = false; + } +} + +bool EncoderVp8::Init(const SkISize& size) { + Destroy(); + codec_.reset(new vpx_codec_ctx_t()); + image_.reset(new vpx_image_t()); + memset(image_.get(), 0, sizeof(vpx_image_t)); + + image_->fmt = VPX_IMG_FMT_YV12; + + // libvpx seems to require both to be assigned. + image_->d_w = size.width(); + image_->w = size.width(); + image_->d_h = size.height(); + image_->h = size.height(); + + // Initialize active map. + active_map_width_ = (image_->w + kMacroBlockSize - 1) / kMacroBlockSize; + active_map_height_ = (image_->h + kMacroBlockSize - 1) / kMacroBlockSize; + active_map_.reset(new uint8[active_map_width_ * active_map_height_]); + + // YUV image size is 1.5 times of a plane. Multiplication is performed first + // to avoid rounding error. + const int y_plane_size = image_->w * image_->h; + const int uv_width = (image_->w + 1) / 2; + const int uv_height = (image_->w + 1) / 2; + const int uv_plane_size = uv_width * uv_height; + const int yuv_image_size = y_plane_size + uv_plane_size * 2; + + // libvpx may try to access memory after the buffer (it still + // doesn't use it) - it copies the data in 16x16 blocks: + // crbug.com/119633 . Here we workaround that problem by adding + // padding at the end of the buffer. Overreading to U and V buffers + // is safe so the padding is necessary only at the end. + // + // TODO(sergeyu): Remove this padding when the bug is fixed in libvpx. + const int active_map_area = active_map_width_ * kMacroBlockSize * + active_map_height_ * kMacroBlockSize; + const int padding_size = active_map_area - y_plane_size; + const int buffer_size = yuv_image_size + padding_size; + + yuv_image_.reset(new uint8[buffer_size]); + + // Reset image value to 128 so we just need to fill in the y plane. + memset(yuv_image_.get(), 128, yuv_image_size); + + // Fill in the information for |image_|. + unsigned char* image = reinterpret_cast(yuv_image_.get()); + image_->planes[0] = image; + image_->planes[1] = image + y_plane_size; + image_->planes[2] = image + y_plane_size + uv_plane_size; + image_->stride[0] = image_->w; + image_->stride[1] = uv_width; + image_->stride[2] = uv_width; + + // Configure the encoder. + vpx_codec_enc_cfg_t config; + const vpx_codec_iface_t* algo = vpx_codec_vp8_cx(); + CHECK(algo); + vpx_codec_err_t ret = vpx_codec_enc_config_default(algo, &config, 0); + if (ret != VPX_CODEC_OK) + return false; + + config.rc_target_bitrate = image_->w * image_->h * + config.rc_target_bitrate / config.g_w / config.g_h; + config.g_w = image_->w; + config.g_h = image_->h; + config.g_pass = VPX_RC_ONE_PASS; + + // Value of 2 means using the real time profile. This is basically a + // redundant option since we explicitly select real time mode when doing + // encoding. + config.g_profile = 2; + + // Using 2 threads gives a great boost in performance for most systems with + // adequate processing power. NB: Going to multiple threads on low end + // windows systems can really hurt performance. + // http://crbug.com/99179 + config.g_threads = (base::SysInfo::NumberOfProcessors() > 2) ? 2 : 1; + config.rc_min_quantizer = 20; + config.rc_max_quantizer = 30; + config.g_timebase.num = 1; + config.g_timebase.den = 20; + + if (vpx_codec_enc_init(codec_.get(), algo, &config, 0)) + return false; + + // Value of 16 will have the smallest CPU load. This turns off subpixel + // motion search. + if (vpx_codec_control(codec_.get(), VP8E_SET_CPUUSED, 16)) + return false; + + // Use the lowest level of noise sensitivity so as to spend less time + // on motion estimation and inter-prediction mode. + if (vpx_codec_control(codec_.get(), VP8E_SET_NOISE_SENSITIVITY, 0)) + return false; + return true; +} + +void EncoderVp8::PrepareImage(scoped_refptr capture_data, + SkRegion* updated_region) { + // Perform RGB->YUV conversion. + CHECK_EQ(capture_data->pixel_format(), media::VideoFrame::RGB32) + << "Only RGB32 is supported"; + + const SkRegion& region = capture_data->dirty_region(); + if (region.isEmpty()) { + updated_region->setEmpty(); + return; + } + + // Align the region to macroblocks, to avoid encoding artefacts. + // This also ensures that all rectangles have even-aligned top-left, which + // is required for ConvertRGBToYUVWithRect() to work. + std::vector aligned_rects; + for (SkRegion::Iterator r(region); !r.done(); r.next()) { + aligned_rects.push_back(AlignRect(r.rect())); + } + DCHECK(!aligned_rects.empty()); + updated_region->setRects(&aligned_rects[0], aligned_rects.size()); + + // Clip back to the screen dimensions, in case they're not macroblock aligned. + // The conversion routines don't require even width & height, so this is safe + // even if the source dimensions are not even. + updated_region->op(SkIRect::MakeWH(image_->w, image_->h), + SkRegion::kIntersect_Op); + + // Convert the updated region to YUV ready for encoding. + const uint8* rgb_data = capture_data->data_planes().data[0]; + const int rgb_stride = capture_data->data_planes().strides[0]; + const int y_stride = image_->stride[0]; + DCHECK(image_->stride[1] == image_->stride[2]); + const int uv_stride = image_->stride[1]; + uint8* y_data = image_->planes[0]; + uint8* u_data = image_->planes[1]; + uint8* v_data = image_->planes[2]; + for (SkRegion::Iterator r(*updated_region); !r.done(); r.next()) { + const SkIRect& rect = r.rect(); + ConvertRGB32ToYUVWithRect( + rgb_data, y_data, u_data, v_data, + rect.x(), rect.y(), rect.width(), rect.height(), + rgb_stride, y_stride, uv_stride); + } +} + +void EncoderVp8::PrepareActiveMap(const SkRegion& updated_region) { + // Clear active map first. + memset(active_map_.get(), 0, active_map_width_ * active_map_height_); + + // Mark updated areas active. + for (SkRegion::Iterator r(updated_region); !r.done(); r.next()) { + const SkIRect& rect = r.rect(); + int left = rect.left() / kMacroBlockSize; + int right = (rect.right() - 1) / kMacroBlockSize; + int top = rect.top() / kMacroBlockSize; + int bottom = (rect.bottom() - 1) / kMacroBlockSize; + CHECK(right < active_map_width_); + CHECK(bottom < active_map_height_); + + uint8* map = active_map_.get() + top * active_map_width_; + for (int y = top; y <= bottom; ++y) { + for (int x = left; x <= right; ++x) + map[x] = 1; + map += active_map_width_; + } + } +} + +void EncoderVp8::Encode(scoped_refptr capture_data, + bool key_frame, + const DataAvailableCallback& data_available_callback) { + DCHECK_LE(32, capture_data->size().width()); + DCHECK_LE(32, capture_data->size().height()); + + if (!initialized_ || + (capture_data->size() != SkISize::Make(image_->w, image_->h))) { + bool ret = Init(capture_data->size()); + // TODO(hclam): Handle error better. + CHECK(ret) << "Initialization of encoder failed"; + initialized_ = ret; + } + + // Convert the updated capture data ready for encode. + SkRegion updated_region; + PrepareImage(capture_data, &updated_region); + + // Update active map based on updated region. + PrepareActiveMap(updated_region); + + // Apply active map to the encoder. + vpx_active_map_t act_map; + act_map.rows = active_map_height_; + act_map.cols = active_map_width_; + act_map.active_map = active_map_.get(); + if (vpx_codec_control(codec_.get(), VP8E_SET_ACTIVEMAP, &act_map)) { + LOG(ERROR) << "Unable to apply active map"; + } + + // Do the actual encoding. + vpx_codec_err_t ret = vpx_codec_encode(codec_.get(), image_.get(), + last_timestamp_, + 1, 0, VPX_DL_REALTIME); + DCHECK_EQ(ret, VPX_CODEC_OK) + << "Encoding error: " << vpx_codec_err_to_string(ret) << "\n" + << "Details: " << vpx_codec_error(codec_.get()) << "\n" + << vpx_codec_error_detail(codec_.get()); + + // TODO(hclam): Apply the proper timestamp here. + last_timestamp_ += 50; + + // Read the encoded data. + vpx_codec_iter_t iter = NULL; + bool got_data = false; + + // TODO(hclam): Make sure we get exactly one frame from the packet. + // TODO(hclam): We should provide the output buffer to avoid one copy. + scoped_ptr packet(new VideoPacket()); + + while (!got_data) { + const vpx_codec_cx_pkt_t* vpx_packet = vpx_codec_get_cx_data(codec_.get(), + &iter); + if (!vpx_packet) + continue; + + switch (vpx_packet->kind) { + case VPX_CODEC_CX_FRAME_PKT: + got_data = true; + // TODO(sergeyu): Split each frame into multiple partitions. + packet->set_data(vpx_packet->data.frame.buf, vpx_packet->data.frame.sz); + break; + default: + break; + } + } + + // Construct the VideoPacket message. + packet->mutable_format()->set_encoding(VideoPacketFormat::ENCODING_VP8); + packet->set_flags(VideoPacket::FIRST_PACKET | VideoPacket::LAST_PACKET | + VideoPacket::LAST_PARTITION); + packet->mutable_format()->set_screen_width(capture_data->size().width()); + packet->mutable_format()->set_screen_height(capture_data->size().height()); + packet->set_capture_time_ms(capture_data->capture_time_ms()); + packet->set_client_sequence_number(capture_data->client_sequence_number()); + SkIPoint dpi(capture_data->dpi()); + if (dpi.x()) + packet->mutable_format()->set_x_dpi(dpi.x()); + if (dpi.y()) + packet->mutable_format()->set_y_dpi(dpi.y()); + for (SkRegion::Iterator r(updated_region); !r.done(); r.next()) { + Rect* rect = packet->add_dirty_rects(); + rect->set_x(r.rect().x()); + rect->set_y(r.rect().y()); + rect->set_width(r.rect().width()); + rect->set_height(r.rect().height()); + } + + data_available_callback.Run(packet.Pass()); +} + +} // namespace remoting diff --git a/remoting/codec/video_encoder_vp8.h b/remoting/codec/video_encoder_vp8.h new file mode 100644 index 0000000..05aeea1 --- /dev/null +++ b/remoting/codec/video_encoder_vp8.h @@ -0,0 +1,64 @@ +// 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. + +#ifndef REMOTING_CODEC_VIDEO_ENCODER_VP8_H_ +#define REMOTING_CODEC_VIDEO_ENCODER_VP8_H_ + +#include "base/gtest_prod_util.h" +#include "remoting/codec/video_encoder.h" +#include "third_party/skia/include/core/SkRegion.h" + +typedef struct vpx_codec_ctx vpx_codec_ctx_t; +typedef struct vpx_image vpx_image_t; + +namespace remoting { + +// A class that uses VP8 to perform encoding. +class EncoderVp8 : public Encoder { + public: + EncoderVp8(); + virtual ~EncoderVp8(); + + virtual void Encode( + scoped_refptr capture_data, + bool key_frame, + const DataAvailableCallback& data_available_callback) OVERRIDE; + + private: + FRIEND_TEST_ALL_PREFIXES(EncoderVp8Test, AlignAndClipRect); + + // Initialize the encoder. Returns true if successful. + bool Init(const SkISize& size); + + // Destroy the encoder. + void Destroy(); + + // Prepare |image_| for encoding. Write updated rectangles into + // |updated_region|. + void PrepareImage(scoped_refptr capture_data, + SkRegion* updated_region); + + // Update the active map according to |updated_region|. Active map is then + // given to the encoder to speed up encoding. + void PrepareActiveMap(const SkRegion& updated_region); + + // True if the encoder is initialized. + bool initialized_; + + scoped_ptr codec_; + scoped_ptr image_; + scoped_array active_map_; + int active_map_width_; + int active_map_height_; + int last_timestamp_; + + // Buffer for storing the yuv image. + scoped_array yuv_image_; + + DISALLOW_COPY_AND_ASSIGN(EncoderVp8); +}; + +} // namespace remoting + +#endif // REMOTING_CODEC_VIDEO_ENCODER_VP8_H_ diff --git a/remoting/codec/video_encoder_vp8_unittest.cc b/remoting/codec/video_encoder_vp8_unittest.cc new file mode 100644 index 0000000..8fe3e27 --- /dev/null +++ b/remoting/codec/video_encoder_vp8_unittest.cc @@ -0,0 +1,97 @@ +// 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 "remoting/codec/video_encoder_vp8.h" + +#include +#include + +#include "base/bind.h" +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" +#include "remoting/base/capture_data.h" +#include "remoting/codec/codec_test.h" +#include "remoting/proto/video.pb.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +const int kIntMax = std::numeric_limits::max(); + +} // namespace + +namespace remoting { + +TEST(EncoderVp8Test, TestEncoder) { + EncoderVp8 encoder; + TestEncoder(&encoder, false); +} + +class EncoderCallback { + public: + void DataAvailable(scoped_ptr packet) { + } +}; + +// Test that calling Encode with a differently-sized CaptureData does not +// leak memory. +TEST(EncoderVp8Test, TestSizeChangeNoLeak) { + int height = 1000; + int width = 1000; + const int kBytesPerPixel = 4; + + EncoderVp8 encoder; + EncoderCallback callback; + + std::vector buffer(width * height * kBytesPerPixel); + DataPlanes planes; + planes.data[0] = &buffer.front(); + planes.strides[0] = width; + + scoped_refptr capture_data(new CaptureData( + planes, SkISize::Make(width, height), media::VideoFrame::RGB32)); + encoder.Encode(capture_data, false, + base::Bind(&EncoderCallback::DataAvailable, + base::Unretained(&callback))); + + height /= 2; + capture_data = new CaptureData(planes, SkISize::Make(width, height), + media::VideoFrame::RGB32); + encoder.Encode(capture_data, false, + base::Bind(&EncoderCallback::DataAvailable, + base::Unretained(&callback))); +} + +class EncoderDpiCallback { + public: + void DataAvailable(scoped_ptr packet) { + EXPECT_EQ(packet->format().x_dpi(), 96); + EXPECT_EQ(packet->format().y_dpi(), 97); + } +}; + +// Test that the DPI information is correctly propagated from the CaptureData +// to the VideoPacket. +TEST(EncoderVp8Test, TestDpiPropagation) { + int height = 32; + int width = 32; + const int kBytesPerPixel = 4; + + EncoderVp8 encoder; + EncoderDpiCallback callback; + + std::vector buffer(width * height * kBytesPerPixel); + DataPlanes planes; + planes.data[0] = &buffer.front(); + planes.strides[0] = width; + + scoped_refptr capture_data(new CaptureData( + planes, SkISize::Make(width, height), media::VideoFrame::RGB32)); + capture_data->set_dpi(SkIPoint::Make(96, 97)); + encoder.Encode(capture_data, false, + base::Bind(&EncoderDpiCallback::DataAvailable, + base::Unretained(&callback))); +} + +} // namespace remoting diff --git a/remoting/host/chromoting_host.cc b/remoting/host/chromoting_host.cc index 4ef9de1..d70085fb 100644 --- a/remoting/host/chromoting_host.cc +++ b/remoting/host/chromoting_host.cc @@ -10,12 +10,12 @@ #include "base/message_loop_proxy.h" #include "build/build_config.h" #include "remoting/base/constants.h" -#include "remoting/base/encoder.h" -#include "remoting/base/encoder_row_based.h" -#include "remoting/base/encoder_vp8.h" #include "remoting/codec/audio_encoder.h" #include "remoting/codec/audio_encoder_speex.h" #include "remoting/codec/audio_encoder_verbatim.h" +#include "remoting/codec/video_encoder.h" +#include "remoting/codec/video_encoder_row_based.h" +#include "remoting/codec/video_encoder_vp8.h" #include "remoting/host/audio_scheduler.h" #include "remoting/host/chromoting_host_context.h" #include "remoting/host/desktop_environment.h" diff --git a/remoting/host/chromoting_host.h b/remoting/host/chromoting_host.h index ee745c8..4893347 100644 --- a/remoting/host/chromoting_host.h +++ b/remoting/host/chromoting_host.h @@ -12,7 +12,7 @@ #include "base/observer_list.h" #include "base/threading/thread.h" #include "net/base/backoff_entry.h" -#include "remoting/base/encoder.h" +#include "remoting/codec/video_encoder.h" #include "remoting/host/client_session.h" #include "remoting/host/desktop_environment.h" #include "remoting/host/host_key_pair.h" diff --git a/remoting/host/screen_recorder.h b/remoting/host/screen_recorder.h index 2c343d3..97a811b 100644 --- a/remoting/host/screen_recorder.h +++ b/remoting/host/screen_recorder.h @@ -13,7 +13,7 @@ #include "base/memory/scoped_ptr.h" #include "base/time.h" #include "base/timer.h" -#include "remoting/base/encoder.h" +#include "remoting/codec/video_encoder.h" #include "remoting/host/capture_scheduler.h" #include "remoting/proto/video.pb.h" diff --git a/remoting/host/screen_recorder_unittest.cc b/remoting/host/screen_recorder_unittest.cc index bfd3a50..5642313 100644 --- a/remoting/host/screen_recorder_unittest.cc +++ b/remoting/host/screen_recorder_unittest.cc @@ -6,7 +6,8 @@ #include "base/bind.h" #include "base/message_loop.h" -#include "remoting/base/base_mock_objects.h" +#include "remoting/base/capture_data.h" +#include "remoting/codec/video_encoder.h" #include "remoting/host/host_mock_objects.h" #include "remoting/proto/video.pb.h" #include "remoting/protocol/protocol_mock_objects.h" @@ -68,6 +69,24 @@ static const media::VideoFrame::Format kFormat = media::VideoFrame::RGB32; static const VideoPacketFormat::Encoding kEncoding = VideoPacketFormat::ENCODING_VERBATIM; +class MockEncoder : public Encoder { + public: + MockEncoder(); + virtual ~MockEncoder(); + + MOCK_METHOD3(Encode, void( + scoped_refptr capture_data, + bool key_frame, + const DataAvailableCallback& data_available_callback)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockEncoder); +}; + +MockEncoder::MockEncoder() {} + +MockEncoder::~MockEncoder() {} + class ScreenRecorderTest : public testing::Test { public: ScreenRecorderTest() { diff --git a/remoting/protocol/DEPS b/remoting/protocol/DEPS index 09d966f..8b14bb2 100644 --- a/remoting/protocol/DEPS +++ b/remoting/protocol/DEPS @@ -5,6 +5,7 @@ include_rules = [ "+ppapi/c", "+ppapi/cpp", "+ppapi/utility", + "+remoting/codec", "+remoting/jingle_glue", "+third_party/libjingle", "+third_party/protobuf/src", diff --git a/remoting/protocol/connection_to_client_unittest.cc b/remoting/protocol/connection_to_client_unittest.cc index 69f1879..043ecdd 100644 --- a/remoting/protocol/connection_to_client_unittest.cc +++ b/remoting/protocol/connection_to_client_unittest.cc @@ -2,13 +2,13 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "remoting/protocol/connection_to_client.h" + #include "base/bind.h" #include "base/message_loop.h" #include "base/message_loop_proxy.h" -#include "remoting/base/base_mock_objects.h" #include "remoting/base/constants.h" #include "remoting/protocol/fake_session.h" -#include "remoting/protocol/connection_to_client.h" #include "remoting/protocol/protocol_mock_objects.h" #include "testing/gmock/include/gmock/gmock.h" diff --git a/remoting/remoting.gyp b/remoting/remoting.gyp index 5337bd2..6d4922b 100644 --- a/remoting/remoting.gyp +++ b/remoting/remoting.gyp @@ -1136,21 +1136,11 @@ 'base/compressor_zlib.h', 'base/constants.cc', 'base/constants.h', - 'base/decoder.h', - 'base/decoder_vp8.cc', - 'base/decoder_vp8.h', - 'base/decoder_row_based.cc', - 'base/decoder_row_based.h', 'base/decompressor.h', 'base/decompressor_verbatim.cc', 'base/decompressor_verbatim.h', 'base/decompressor_zlib.cc', 'base/decompressor_zlib.h', - 'base/encoder.h', - 'base/encoder_vp8.cc', - 'base/encoder_vp8.h', - 'base/encoder_row_based.cc', - 'base/encoder_row_based.h', 'base/plugin_thread_task_runner.cc', 'base/plugin_thread_task_runner.h', 'base/rate_counter.cc', @@ -1174,6 +1164,16 @@ 'codec/audio_encoder_speex.h', 'codec/audio_encoder_verbatim.cc', 'codec/audio_encoder_verbatim.h', + 'codec/video_decoder.h', + 'codec/video_decoder_vp8.cc', + 'codec/video_decoder_vp8.h', + 'codec/video_decoder_row_based.cc', + 'codec/video_decoder_row_based.h', + 'codec/video_encoder.h', + 'codec/video_encoder_vp8.cc', + 'codec/video_encoder_vp8.h', + 'codec/video_encoder_row_based.cc', + 'codec/video_encoder_row_based.h', ], }, # end of target 'remoting_base' @@ -1729,21 +1729,19 @@ ], 'sources': [ 'base/auth_token_util_unittest.cc', - 'base/base_mock_objects.cc', - 'base/base_mock_objects.h', 'base/breakpad_win_unittest.cc', - 'base/codec_test.cc', - 'base/codec_test.h', 'base/compound_buffer_unittest.cc', 'base/compressor_zlib_unittest.cc', - 'base/decoder_vp8_unittest.cc', 'base/decompressor_zlib_unittest.cc', - 'base/encode_decode_unittest.cc', - 'base/encoder_vp8_unittest.cc', - 'base/encoder_row_based_unittest.cc', 'base/util_unittest.cc', 'client/key_event_mapper_unittest.cc', 'client/plugin/mac_key_event_processor_unittest.cc', + 'codec/codec_test.cc', + 'codec/codec_test.h', + 'codec/video_decoder_vp8_unittest.cc', + 'codec/video_encode_decode_unittest.cc', + 'codec/video_encoder_row_based_unittest.cc', + 'codec/video_encoder_vp8_unittest.cc', 'host/audio_capturer_win_unittest.cc', 'host/chromoting_host_context_unittest.cc', 'host/chromoting_host_unittest.cc', -- cgit v1.1