diff options
author | kxing@chromium.org <kxing@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-08-23 01:36:10 +0000 |
---|---|---|
committer | kxing@chromium.org <kxing@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-08-23 01:36:10 +0000 |
commit | 386098336aa778f82d5a079b9371a1c63bd46d94 (patch) | |
tree | 3f238db944ed2ac50f2224b62feb5a36193123fd /remoting/codec | |
parent | e7e5bc86181faa3076671af080f809944594b60d (diff) | |
download | chromium_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.cc | 496 | ||||
-rw-r--r-- | remoting/codec/codec_test.h | 40 | ||||
-rw-r--r-- | remoting/codec/video_decoder.h | 75 | ||||
-rw-r--r-- | remoting/codec/video_decoder_row_based.cc | 225 | ||||
-rw-r--r-- | remoting/codec/video_decoder_row_based.h | 84 | ||||
-rw-r--r-- | remoting/codec/video_decoder_vp8.cc | 211 | ||||
-rw-r--r-- | remoting/codec/video_decoder_vp8.h | 60 | ||||
-rw-r--r-- | remoting/codec/video_decoder_vp8_unittest.cc | 75 | ||||
-rw-r--r-- | remoting/codec/video_encode_decode_unittest.cc | 32 | ||||
-rw-r--r-- | remoting/codec/video_encoder.h | 48 | ||||
-rw-r--r-- | remoting/codec/video_encoder_row_based.cc | 177 | ||||
-rw-r--r-- | remoting/codec/video_encoder_row_based.h | 71 | ||||
-rw-r--r-- | remoting/codec/video_encoder_row_based_unittest.cc | 22 | ||||
-rw-r--r-- | remoting/codec/video_encoder_vp8.cc | 307 | ||||
-rw-r--r-- | remoting/codec/video_encoder_vp8.h | 64 | ||||
-rw-r--r-- | remoting/codec/video_encoder_vp8_unittest.cc | 97 |
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 |