blob: 62b986077f2118bd0c1c429c82dc3d3f8b56f3d8 [file] [log] [blame]
// 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 <deque>
#include <stdlib.h>
#include "gfx/rect.h"
#include "media/base/video_frame.h"
#include "remoting/base/codec_test.h"
#include "remoting/base/decoder.h"
#include "remoting/base/encoder.h"
#include "remoting/base/mock_objects.h"
#include "remoting/base/util.h"
#include "testing/gtest/include/gtest/gtest.h"
static const int kWidth = 320;
static const int kHeight = 240;
static const int kBytesPerPixel = 4;
// Some sample rects for testing.
static const gfx::Rect kTestRects[] = {
gfx::Rect(0, 0, kWidth, kHeight),
gfx::Rect(0, 0, kWidth / 2, kHeight / 2),
gfx::Rect(kWidth / 2, kHeight / 2, kWidth / 2, kHeight / 2),
gfx::Rect(16, 16, 16, 16),
gfx::Rect(128, 64, 32, 32),
};
namespace remoting {
// A class to test that the state transition of the Encoder is correct.
class EncoderStateTester {
public:
EncoderStateTester()
: next_state_(Encoder::EncodingStarting) {
}
~EncoderStateTester() {
EXPECT_EQ(Encoder::EncodingStarting, next_state_);
}
// Set the state output of the Encoder.
void ReceivedState(Encoder::EncodingState state) {
if (state & Encoder::EncodingStarting) {
EXPECT_EQ(Encoder::EncodingStarting, next_state_);
next_state_ = Encoder::EncodingInProgress | Encoder::EncodingEnded;
} else {
EXPECT_FALSE(next_state_ & Encoder::EncodingStarting);
}
if (state & Encoder::EncodingInProgress) {
EXPECT_TRUE(next_state_ & Encoder::EncodingInProgress);
}
if (state & Encoder::EncodingEnded) {
EXPECT_TRUE(next_state_ & Encoder::EncodingEnded);
next_state_ = Encoder::EncodingStarting;
}
}
private:
Encoder::EncodingState next_state_;
DISALLOW_COPY_AND_ASSIGN(EncoderStateTester);
};
// 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 message.
void ReceivedMessage(ChromotingHostMessage* message) {
EXPECT_TRUE(message->has_update_stream_packet());
if (state_ == kWaitingForBeginRect) {
EXPECT_TRUE(message->update_stream_packet().has_begin_rect());
state_ = kWaitingForRectData;
++begin_rect_;
if (strict_) {
gfx::Rect rect = rects_.front();
rects_.pop_front();
EXPECT_EQ(rect.x(), message->update_stream_packet().begin_rect().x());
EXPECT_EQ(rect.y(), message->update_stream_packet().begin_rect().y());
EXPECT_EQ(rect.width(),
message->update_stream_packet().begin_rect().width());
EXPECT_EQ(rect.height(),
message->update_stream_packet().begin_rect().height());
}
} else {
EXPECT_FALSE(message->update_stream_packet().has_begin_rect());
}
if (state_ == kWaitingForRectData) {
if (message->update_stream_packet().has_rect_data()) {
++rect_data_;
}
if (message->update_stream_packet().has_end_rect()) {
// Expect that we have received some data.
EXPECT_GT(rect_data_, 0);
rect_data_ = 0;
state_ = kWaitingForBeginRect;
++end_rect_;
}
}
}
void set_strict(bool strict) {
strict_ = strict;
}
void AddRects(const gfx::Rect* 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<gfx::Rect> rects_;
DISALLOW_COPY_AND_ASSIGN(EncoderMessageTester);
};
class DecoderTester {
public:
DecoderTester(Decoder* decoder)
: strict_(false),
decoder_(decoder),
decode_done_(false) {
media::VideoFrame::CreateFrame(media::VideoFrame::RGB32,
kWidth, kHeight,
base::TimeDelta(),
base::TimeDelta(), &frame_);
EXPECT_TRUE(frame_.get());
}
void ReceivedMessage(ChromotingHostMessage* message) {
if (message->has_update_stream_packet()) {
EXPECT_TRUE(decoder_->PartialDecode(message));
return;
}
if (message->has_begin_update_stream()) {
EXPECT_TRUE(decoder_->BeginDecode(
frame_, &update_rects_,
NewRunnableMethod(this, &DecoderTester::OnPartialDecodeDone),
NewRunnableMethod(this, &DecoderTester::OnDecodeDone)));
}
if (message->has_end_update_stream()) {
decoder_->EndDecode();
}
delete message;
}
void set_strict(bool strict) {
strict_ = strict;
}
void set_capture_data(scoped_refptr<CaptureData> data) {
capture_data_ = data;
}
void AddRects(const gfx::Rect* rects, int count) {
rects_.insert(rects_.begin() + rects_.size(), rects, rects + count);
}
bool decode_done() const { return decode_done_; }
void reset_decode_done() { decode_done_ = false; }
private:
void OnPartialDecodeDone() {
if (!strict_)
return;
// Test the content of the update rect.
for (size_t i = 0; i < update_rects_.size(); ++i) {
EXPECT_FALSE(rects_.empty());
gfx::Rect rect = rects_.front();
rects_.pop_front();
EXPECT_EQ(rect, update_rects_[i]);
EXPECT_EQ(frame_->stride(0), capture_data_->data_planes().strides[0]);
const int stride = frame_->stride(0);
const int offset = stride * update_rects_[i].y() +
kBytesPerPixel * update_rects_[i].x();
const uint8* original = capture_data_->data_planes().data[0] + offset;
const uint8* decoded = frame_->data(0) + offset;
const int row_size = kBytesPerPixel * update_rects_[i].width();
for (int y = 0; y < update_rects_[i].height(); ++y) {
EXPECT_EQ(0, memcmp(original, decoded, row_size))
<< "Row " << y << " is different";
original += stride;
decoded += stride;
}
}
}
void OnDecodeDone() {
decode_done_ = true;
if (!strict_)
return;
EXPECT_TRUE(capture_data_.get());
for (int i = 0; i < DataPlanes::kPlaneCount; ++i) {
if (!frame_->data(i) || !capture_data_->data_planes().data[i])
continue;
// TODO(hclam): HAndle YUV.
int size = capture_data_->data_planes().strides[i] * kHeight;
EXPECT_EQ(0, memcmp(capture_data_->data_planes().data[i],
frame_->data(i), size));
}
}
bool strict_;
std::deque<gfx::Rect> rects_;
UpdatedRects update_rects_;
Decoder* decoder_;
scoped_refptr<media::VideoFrame> frame_;
scoped_refptr<CaptureData> capture_data_;
bool decode_done_;
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,
EncoderStateTester* state_tester)
: message_tester_(message_tester),
state_tester_(state_tester),
decoder_tester_(NULL),
data_available_(0) {
}
~EncoderTester() {
EXPECT_GT(data_available_, 0);
}
void DataAvailable(ChromotingHostMessage* message,
Encoder::EncodingState state) {
++data_available_;
message_tester_->ReceivedMessage(message);
state_tester_->ReceivedState(state);
// Send the message to the DecoderTester.
if (decoder_tester_) {
if (state & Encoder::EncodingStarting) {
ChromotingHostMessage* begin_update = new ChromotingHostMessage();
begin_update->mutable_begin_update_stream();
decoder_tester_->ReceivedMessage(begin_update);
}
if (state & Encoder::EncodingInProgress) {
decoder_tester_->ReceivedMessage(message);
}
if (state & Encoder::EncodingEnded) {
ChromotingHostMessage* end_update = new ChromotingHostMessage();
end_update->mutable_end_update_stream();
decoder_tester_->ReceivedMessage(end_update);
}
} else {
delete message;
}
}
void AddRects(const gfx::Rect* rects, int count) {
message_tester_->AddRects(rects, count);
}
void set_decoder_tester(DecoderTester* decoder_tester) {
decoder_tester_ = decoder_tester;
}
private:
EncoderMessageTester* message_tester_;
EncoderStateTester* state_tester_;
DecoderTester* decoder_tester_;
int data_available_;
DISALLOW_COPY_AND_ASSIGN(EncoderTester);
};
scoped_refptr<CaptureData> PrepareEncodeData(PixelFormat format,
uint8** memory) {
// TODO(hclam): Support also YUV format.
CHECK(format == PixelFormatRgb32);
int size = kWidth * kHeight * kBytesPerPixel;
*memory = new uint8[size];
srand(0);
for (int i = 0; i < 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] = kWidth * kBytesPerPixel;
scoped_refptr<CaptureData> data =
new CaptureData(planes, kWidth, kHeight, format);
return data;
}
static void TestEncodingRects(Encoder* encoder,
EncoderTester* tester,
scoped_refptr<CaptureData> data,
const gfx::Rect* rects, int count) {
data->mutable_dirty_rects().clear();
for (int i = 0; i < count; ++i) {
data->mutable_dirty_rects().insert(rects[i]);
}
tester->AddRects(rects, count);
encoder->Encode(data, true,
NewCallback(tester, &EncoderTester::DataAvailable));
}
void TestEncoder(Encoder* encoder, bool strict) {
EncoderMessageTester message_tester;
message_tester.set_strict(strict);
EncoderStateTester state_tester;
EncoderTester tester(&message_tester, &state_tester);
uint8* memory;
scoped_refptr<CaptureData> data =
PrepareEncodeData(PixelFormatRgb32, &memory);
TestEncodingRects(encoder, &tester, data, kTestRects, 1);
TestEncodingRects(encoder, &tester, data, kTestRects + 1, 1);
TestEncodingRects(encoder, &tester, data, kTestRects + 2, 1);
TestEncodingRects(encoder, &tester, data, kTestRects + 3, 2);
delete [] memory;
}
static void TestEncodingRects(Encoder* encoder,
EncoderTester* encoder_tester,
DecoderTester* decoder_tester,
scoped_refptr<CaptureData> data,
const gfx::Rect* rects, int count) {
data->mutable_dirty_rects().clear();
for (int i = 0; i < count; ++i) {
data->mutable_dirty_rects().insert(rects[i]);
}
encoder_tester->AddRects(rects, count);
decoder_tester->AddRects(rects, count);
decoder_tester->reset_decode_done();
// Generate random data for the updated rects.
srand(0);
for (int i = 0; i < count; ++i) {
const gfx::Rect rect = rects[i];
const int bytes_per_pixel = GetBytesPerPixel(data->pixel_format());
const int row_size = bytes_per_pixel * rect.width();
uint8* memory = data->data_planes().data[0] +
data->data_planes().strides[0] * rect.y() +
bytes_per_pixel * rect.x();
for (int y = 0; y < rect.height(); ++y) {
for (int x = 0; x < row_size; ++x)
memory[x] = rand() % 256;
memory += data->data_planes().strides[0];
}
}
encoder->Encode(data, true,
NewCallback(encoder_tester, &EncoderTester::DataAvailable));
EXPECT_TRUE(decoder_tester->decode_done());
}
void TestEncoderDecoder(Encoder* encoder, Decoder* decoder, bool strict) {
EncoderMessageTester message_tester;
message_tester.set_strict(strict);
EncoderStateTester state_tester;
EncoderTester encoder_tester(&message_tester, &state_tester);
uint8* memory;
scoped_refptr<CaptureData> data =
PrepareEncodeData(PixelFormatRgb32, &memory);
DecoderTester decoder_tester(decoder);
decoder_tester.set_strict(strict);
decoder_tester.set_capture_data(data);
encoder_tester.set_decoder_tester(&decoder_tester);
TestEncodingRects(encoder, &encoder_tester, &decoder_tester, data,
kTestRects, 1);
TestEncodingRects(encoder, &encoder_tester, &decoder_tester, data,
kTestRects + 1, 1);
TestEncodingRects(encoder, &encoder_tester, &decoder_tester, data,
kTestRects + 2, 1);
TestEncodingRects(encoder, &encoder_tester, &decoder_tester, data,
kTestRects + 3, 2);
delete [] memory;
}
} // namespace remoting
DISABLE_RUNNABLE_METHOD_REFCOUNT(remoting::DecoderTester);