summaryrefslogtreecommitdiffstats
path: root/remoting/codec
diff options
context:
space:
mode:
authorkxing@chromium.org <kxing@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-08-23 01:36:10 +0000
committerkxing@chromium.org <kxing@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-08-23 01:36:10 +0000
commit386098336aa778f82d5a079b9371a1c63bd46d94 (patch)
tree3f238db944ed2ac50f2224b62feb5a36193123fd /remoting/codec
parente7e5bc86181faa3076671af080f809944594b60d (diff)
downloadchromium_src-386098336aa778f82d5a079b9371a1c63bd46d94.zip
chromium_src-386098336aa778f82d5a079b9371a1c63bd46d94.tar.gz
chromium_src-386098336aa778f82d5a079b9371a1c63bd46d94.tar.bz2
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
Diffstat (limited to 'remoting/codec')
-rw-r--r--remoting/codec/codec_test.cc496
-rw-r--r--remoting/codec/codec_test.h40
-rw-r--r--remoting/codec/video_decoder.h75
-rw-r--r--remoting/codec/video_decoder_row_based.cc225
-rw-r--r--remoting/codec/video_decoder_row_based.h84
-rw-r--r--remoting/codec/video_decoder_vp8.cc211
-rw-r--r--remoting/codec/video_decoder_vp8.h60
-rw-r--r--remoting/codec/video_decoder_vp8_unittest.cc75
-rw-r--r--remoting/codec/video_encode_decode_unittest.cc32
-rw-r--r--remoting/codec/video_encoder.h48
-rw-r--r--remoting/codec/video_encoder_row_based.cc177
-rw-r--r--remoting/codec/video_encoder_row_based.h71
-rw-r--r--remoting/codec/video_encoder_row_based_unittest.cc22
-rw-r--r--remoting/codec/video_encoder_vp8.cc307
-rw-r--r--remoting/codec/video_encoder_vp8.h64
-rw-r--r--remoting/codec/video_encoder_vp8_unittest.cc97
16 files changed, 2084 insertions, 0 deletions
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 <deque>
+#include <stdlib.h>
+
+#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<std::vector<SkIRect> > MakeTestRectLists(const SkISize& size) {
+ std::vector<std::vector<SkIRect> > rect_lists;
+ std::vector<SkIRect> 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<SkIRect> 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<VideoPacket> packet) {
+ ReceivedPacket(packet.get());
+ }
+
+ void set_strict(bool strict) {
+ strict_ = strict;
+ }
+
+ void set_capture_data(scoped_refptr<CaptureData> 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<double>(*original++) -
+ static_cast<double>(*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<uint8> image_data_;
+ scoped_refptr<CaptureData> 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<VideoPacket> 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<CaptureData> 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<CaptureData> data =
+ new CaptureData(planes, size, format);
+ return data;
+}
+
+static void TestEncodingRects(Encoder* encoder,
+ EncoderTester* tester,
+ scoped_refptr<CaptureData> 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<CaptureData> data =
+ PrepareEncodeData(kSize, media::VideoFrame::RGB32, &memory);
+ scoped_array<uint8> memory_wrapper(memory);
+
+ std::vector<std::vector<SkIRect> > test_rect_lists = MakeTestRectLists(kSize);
+ for (size_t i = 0; i < test_rect_lists.size(); ++i) {
+ const std::vector<SkIRect>& 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<CaptureData> 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<CaptureData> data =
+ PrepareEncodeData(kSize, media::VideoFrame::RGB32, &memory);
+ scoped_array<uint8> 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<std::vector<SkIRect> > test_rect_lists = MakeTestRectLists(kSize);
+ for (size_t i = 0; i < test_rect_lists.size(); ++i) {
+ const std::vector<SkIRect> 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<uint8>((255.0 * i) / frame_size.width());
+ *p++ = static_cast<uint8>((164.0 * j) / frame_size.height());
+ *p++ = static_cast<uint8>((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<uint8> 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<uint8> 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<CaptureData> 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<const uint8*>(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> 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<uint8> 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 <math.h>
+
+#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<const uint8*>(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<EncoderRowBased> encoder(EncoderRowBased::CreateZlibEncoder());
+ scoped_ptr<DecoderRowBased> decoder(DecoderRowBased::CreateZlibDecoder());
+ TestEncoderDecoder(encoder.get(), decoder.get(), true);
+}
+
+TEST(EncodeDecodeTest, EncodeAndDecodeSmallOutputBufferZlib) {
+ scoped_ptr<EncoderRowBased> encoder(EncoderRowBased::CreateZlibEncoder(64));
+ scoped_ptr<DecoderRowBased> decoder(DecoderRowBased::CreateZlibDecoder());
+ TestEncoderDecoder(encoder.get(), decoder.get(), true);
+}
+
+TEST(EncodeDecodeTest, EncodeAndDecodeNoneStrictZlib) {
+ scoped_ptr<EncoderRowBased> encoder(EncoderRowBased::CreateZlibEncoder());
+ scoped_ptr<DecoderRowBased> 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<void(scoped_ptr<VideoPacket>)> 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<CaptureData> 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<CaptureData> 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<VideoPacket> 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<uint8*>(reinterpret_cast<const uint8*>(
+ 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<CaptureData> 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> compressor_;
+
+ scoped_refptr<CaptureData> 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<EncoderRowBased> encoder(EncoderRowBased::CreateZlibEncoder());
+ TestEncoder(encoder.get(), true);
+}
+
+TEST(EncoderZlibTest, TestEncoderSmallOutputBuffer) {
+ scoped_ptr<EncoderRowBased> 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<unsigned char*>(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<CaptureData> 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<SkIRect> 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<CaptureData> 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<VideoPacket> 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<CaptureData> 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<CaptureData> 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<vpx_codec_ctx_t> codec_;
+ scoped_ptr<vpx_image_t> image_;
+ scoped_array<uint8> active_map_;
+ int active_map_width_;
+ int active_map_height_;
+ int last_timestamp_;
+
+ // Buffer for storing the yuv image.
+ scoped_array<uint8> 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 <limits>
+#include <vector>
+
+#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<int>::max();
+
+} // namespace
+
+namespace remoting {
+
+TEST(EncoderVp8Test, TestEncoder) {
+ EncoderVp8 encoder;
+ TestEncoder(&encoder, false);
+}
+
+class EncoderCallback {
+ public:
+ void DataAvailable(scoped_ptr<VideoPacket> 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<uint8> buffer(width * height * kBytesPerPixel);
+ DataPlanes planes;
+ planes.data[0] = &buffer.front();
+ planes.strides[0] = width;
+
+ scoped_refptr<CaptureData> 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<VideoPacket> 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<uint8> buffer(width * height * kBytesPerPixel);
+ DataPlanes planes;
+ planes.data[0] = &buffer.front();
+ planes.strides[0] = width;
+
+ scoped_refptr<CaptureData> 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