summaryrefslogtreecommitdiffstats
path: root/remoting
diff options
context:
space:
mode:
authorsergeyu@chromium.org <sergeyu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-11-17 23:05:15 +0000
committersergeyu@chromium.org <sergeyu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-11-17 23:05:15 +0000
commit5ddaa0ac4c4d01677f469e65a02ff584d3035de2 (patch)
treea69cd3af68055724bae9ee9d4ddcac69bed8c666 /remoting
parentee97fb0629b6dc336f9c7d69e8595dd9637b336c (diff)
downloadchromium_src-5ddaa0ac4c4d01677f469e65a02ff584d3035de2.zip
chromium_src-5ddaa0ac4c4d01677f469e65a02ff584d3035de2.tar.gz
chromium_src-5ddaa0ac4c4d01677f469e65a02ff584d3035de2.tar.bz2
Unittests for RTP packetizer and depacketizer.
BUG=None TEST=Unittests Review URL: http://codereview.chromium.org/4946001 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@66536 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'remoting')
-rw-r--r--remoting/protocol/buffered_socket_writer.cc2
-rw-r--r--remoting/protocol/fake_session.cc60
-rw-r--r--remoting/protocol/fake_session.h50
-rw-r--r--remoting/protocol/rtp_video_reader.cc66
-rw-r--r--remoting/protocol/rtp_video_reader.h20
-rw-r--r--remoting/protocol/rtp_video_reader_unittest.cc348
-rw-r--r--remoting/protocol/rtp_video_writer_unittest.cc178
-rw-r--r--remoting/remoting.gyp3
8 files changed, 690 insertions, 37 deletions
diff --git a/remoting/protocol/buffered_socket_writer.cc b/remoting/protocol/buffered_socket_writer.cc
index bbb0324..555d4e6 100644
--- a/remoting/protocol/buffered_socket_writer.cc
+++ b/remoting/protocol/buffered_socket_writer.cc
@@ -23,7 +23,7 @@ BufferedSocketWriterBase::BufferedSocketWriterBase()
BufferedSocketWriterBase::~BufferedSocketWriterBase() { }
void BufferedSocketWriterBase::Init(net::Socket* socket,
- WriteFailedCallback* callback) {
+ WriteFailedCallback* callback) {
AutoLock auto_lock(lock_);
message_loop_ = MessageLoop::current();
socket_ = socket;
diff --git a/remoting/protocol/fake_session.cc b/remoting/protocol/fake_session.cc
index 4b3ff51..bba1d98 100644
--- a/remoting/protocol/fake_session.cc
+++ b/remoting/protocol/fake_session.cc
@@ -70,6 +70,62 @@ bool FakeSocket::SetSendBufferSize(int32 size) {
return false;
}
+FakeUdpSocket::FakeUdpSocket()
+ : read_pending_(false),
+ input_pos_(0) {
+}
+
+FakeUdpSocket::~FakeUdpSocket() {
+}
+
+void FakeUdpSocket::AppendInputPacket(char* data, int data_size) {
+ input_packets_.push_back(std::string());
+ input_packets_.back().assign(data, data + data_size);
+
+ // Complete pending read if any.
+ if (read_pending_) {
+ read_pending_ = false;
+ int result = std::min(data_size, read_buffer_size_);
+ memcpy(read_buffer_->data(), data, result);
+ input_pos_ = input_packets_.size();
+ read_callback_->Run(result);
+ read_buffer_ = NULL;
+ }
+}
+
+int FakeUdpSocket::Read(net::IOBuffer* buf, int buf_len,
+ net::CompletionCallback* callback) {
+ if (input_pos_ < static_cast<int>(input_packets_.size())) {
+ int result = std::min(
+ buf_len, static_cast<int>(input_packets_[input_pos_].size()));
+ memcpy(buf->data(), &(*input_packets_[input_pos_].begin()), result);
+ ++input_pos_;
+ return result;
+ } else {
+ read_pending_ = true;
+ read_buffer_ = buf;
+ read_buffer_size_ = buf_len;
+ read_callback_ = callback;
+ return net::ERR_IO_PENDING;
+ }
+}
+
+int FakeUdpSocket::Write(net::IOBuffer* buf, int buf_len,
+ net::CompletionCallback* callback) {
+ written_packets_.push_back(std::string());
+ written_packets_.back().assign(buf->data(), buf->data() + buf_len);
+ return buf_len;
+}
+
+bool FakeUdpSocket::SetReceiveBufferSize(int32 size) {
+ NOTIMPLEMENTED();
+ return false;
+}
+bool FakeUdpSocket::SetSendBufferSize(int32 size) {
+ NOTIMPLEMENTED();
+ return false;
+}
+
FakeSession::FakeSession()
: candidate_config_(CandidateSessionConfig::CreateDefault()),
config_(SessionConfig::CreateDefault()),
@@ -96,11 +152,11 @@ FakeSocket* FakeSession::video_channel() {
return &video_channel_;
}
-FakeSocket* FakeSession::video_rtp_channel() {
+FakeUdpSocket* FakeSession::video_rtp_channel() {
return &video_rtp_channel_;
}
-FakeSocket* FakeSession::video_rtcp_channel() {
+FakeUdpSocket* FakeSession::video_rtcp_channel() {
return &video_rtcp_channel_;
}
diff --git a/remoting/protocol/fake_session.h b/remoting/protocol/fake_session.h
index 9b216fe..357ef69 100644
--- a/remoting/protocol/fake_session.h
+++ b/remoting/protocol/fake_session.h
@@ -5,6 +5,7 @@
#ifndef REMOTING_PROTOCOL_FAKE_SESSION_H_
#define REMOTING_PROTOCOL_FAKE_SESSION_H_
+#include <string>
#include <vector>
#include "base/scoped_ptr.h"
@@ -26,7 +27,7 @@ class FakeSocket : public net::Socket {
FakeSocket();
virtual ~FakeSocket();
- const std::vector<char>& written_data() { return written_data_; }
+ const std::string& written_data() { return written_data_; }
void AppendInputData(char* data, int data_size);
int input_pos() { return input_pos_; }
@@ -46,8 +47,43 @@ class FakeSocket : public net::Socket {
int read_buffer_size_;
net::CompletionCallback* read_callback_;
- std::vector<char> written_data_;
- std::vector<char> input_data_;
+ std::string written_data_;
+ std::string input_data_;
+ int input_pos_;
+};
+
+// FakeUdpSocket is similar to FakeSocket but behaves as UDP socket. All written
+// packets are stored separetely in written_packets(). AppendInputPacket() adds
+// one packet that will be returned by Read().
+class FakeUdpSocket : public net::Socket {
+ public:
+ FakeUdpSocket();
+ virtual ~FakeUdpSocket();
+
+ const std::vector<std::string>& written_packets() {
+ return written_packets_;
+ }
+
+ void AppendInputPacket(char* data, int data_size);
+ int input_pos() { return input_pos_; }
+
+ // net::Socket interface.
+ virtual int Read(net::IOBuffer* buf, int buf_len,
+ net::CompletionCallback* callback);
+ virtual int Write(net::IOBuffer* buf, int buf_len,
+ net::CompletionCallback* callback);
+
+ virtual bool SetReceiveBufferSize(int32 size);
+ virtual bool SetSendBufferSize(int32 size);
+
+ private:
+ bool read_pending_;
+ scoped_refptr<net::IOBuffer> read_buffer_;
+ int read_buffer_size_;
+ net::CompletionCallback* read_callback_;
+
+ std::vector<std::string> written_packets_;
+ std::vector<std::string> input_packets_;
int input_pos_;
};
@@ -72,8 +108,8 @@ class FakeSession : public Session {
virtual FakeSocket* event_channel();
virtual FakeSocket* video_channel();
- virtual FakeSocket* video_rtp_channel();
- virtual FakeSocket* video_rtcp_channel();
+ virtual FakeUdpSocket* video_rtp_channel();
+ virtual FakeUdpSocket* video_rtcp_channel();
virtual const std::string& jid();
@@ -92,8 +128,8 @@ class FakeSession : public Session {
FakeSocket control_channel_;
FakeSocket event_channel_;
FakeSocket video_channel_;
- FakeSocket video_rtp_channel_;
- FakeSocket video_rtcp_channel_;
+ FakeUdpSocket video_rtp_channel_;
+ FakeUdpSocket video_rtcp_channel_;
std::string jid_;
bool closed_;
};
diff --git a/remoting/protocol/rtp_video_reader.cc b/remoting/protocol/rtp_video_reader.cc
index 3a73f10..07f57c4 100644
--- a/remoting/protocol/rtp_video_reader.cc
+++ b/remoting/protocol/rtp_video_reader.cc
@@ -15,6 +15,11 @@ namespace {
const int kMaxPacketsInQueue = 1024;
} // namespace
+RtpVideoReader::PacketsQueueEntry::PacketsQueueEntry()
+ : received(false),
+ packet(NULL) {
+}
+
RtpVideoReader::RtpVideoReader()
: last_sequence_number_(0) {
}
@@ -36,26 +41,31 @@ void RtpVideoReader::Close() {
void RtpVideoReader::ResetQueue() {
for (PacketsQueue::iterator it = packets_queue_.begin();
it != packets_queue_.end(); ++it) {
- delete *it;
+ delete it->packet;
}
- packets_queue_.clear();
+ packets_queue_.assign(kMaxPacketsInQueue, PacketsQueueEntry());
}
void RtpVideoReader::OnRtpPacket(const RtpPacket* rtp_packet) {
- uint32 sequence_number = rtp_packet->header().sequence_number;
- int32 relative_number = sequence_number - last_sequence_number_;
+ uint16 sequence_number = rtp_packet->header().sequence_number;
+ int16 relative_number = sequence_number - last_sequence_number_;
int packet_index;
- if (relative_number > 0) {
+
+ if (packets_queue_.empty()) {
+ // This is the first packet we've received. Setup the queue.
+ ResetQueue();
+ last_sequence_number_ = sequence_number;
+ packet_index = packets_queue_.size() - 1;
+ } else if (relative_number > 0) {
if (relative_number > kMaxPacketsInQueue) {
// Sequence number jumped too much for some reason. Reset the queue.
ResetQueue();
- packets_queue_.resize(1);
} else {
packets_queue_.resize(packets_queue_.size() + relative_number);
// Cleanup old packets, so that we don't have more than
// |kMaxPacketsInQueue| packets.
while (static_cast<int>(packets_queue_.size()) > kMaxPacketsInQueue) {
- delete packets_queue_.front();
+ delete packets_queue_.front().packet;
packets_queue_.pop_front();
}
}
@@ -71,18 +81,22 @@ void RtpVideoReader::OnRtpPacket(const RtpPacket* rtp_packet) {
}
CHECK_LT(packet_index, static_cast<int>(packets_queue_.size()));
- if (packets_queue_[packet_index]) {
- LOG(WARNING) << "Received duplicate packet with sequence number "
- << sequence_number;
- delete packets_queue_[packet_index];
+
+ if (packets_queue_[packet_index].received) {
+ VLOG(1) << "Received duplicate packet with sequence number "
+ << sequence_number;
+ delete rtp_packet;
+ return;
}
- packets_queue_[packet_index] = rtp_packet;
+
+ packets_queue_[packet_index].packet = rtp_packet;
+ packets_queue_[packet_index].received = true;
CheckFullPacket(packets_queue_.begin() + packet_index);
}
void RtpVideoReader::CheckFullPacket(PacketsQueue::iterator pos) {
- if ((*pos)->vp8_descriptor().fragmentation_info ==
+ if (pos->packet->vp8_descriptor().fragmentation_info ==
Vp8Descriptor::NOT_FRAGMENTED) {
// The packet is not fragmented.
RebuildVideoPacket(pos, pos);
@@ -90,24 +104,24 @@ void RtpVideoReader::CheckFullPacket(PacketsQueue::iterator pos) {
}
PacketsQueue::iterator first = pos;
- while (first > packets_queue_.begin() && (*first) &&
- (*first)->vp8_descriptor().fragmentation_info !=
+ while (first > packets_queue_.begin() && first->packet &&
+ first->packet->vp8_descriptor().fragmentation_info !=
Vp8Descriptor::FIRST_FRAGMENT) {
first--;
}
- if (!(*first) || (*first)->vp8_descriptor().fragmentation_info !=
+ if (!first->packet || first->packet->vp8_descriptor().fragmentation_info !=
Vp8Descriptor::FIRST_FRAGMENT) {
// We don't have first fragment.
return;
}
PacketsQueue::iterator last = pos;
- while (last < (packets_queue_.end() - 1) && (*last) &&
- (*last)->vp8_descriptor().fragmentation_info !=
+ while (last < (packets_queue_.end() - 1) && last->packet &&
+ last->packet->vp8_descriptor().fragmentation_info !=
Vp8Descriptor::LAST_FRAGMENT) {
last++;
}
- if (!(*last) || (*last)->vp8_descriptor().fragmentation_info !=
+ if (!last->packet || last->packet->vp8_descriptor().fragmentation_info !=
Vp8Descriptor::LAST_FRAGMENT) {
// We don't have last fragment.
return;
@@ -123,22 +137,26 @@ void RtpVideoReader::RebuildVideoPacket(PacketsQueue::iterator first,
VideoPacket* packet = new VideoPacket();
// Set flags.
- if ((*first)->vp8_descriptor().frame_beginning)
+ if (first->packet->vp8_descriptor().frame_beginning)
packet->set_flags(packet->flags() | VideoPacket::FIRST_PACKET);
- if ((*last)->header().marker)
+ if (last->packet->header().marker)
packet->set_flags(packet->flags() | VideoPacket::LAST_PACKET);
+ packet->set_timestamp(first->packet->header().timestamp);
+
// Rebuild packet content from the fragments.
// TODO(sergeyu): Use CompoundBuffer inside of VideoPacket, so that we don't
// need to memcopy any data.
CompoundBuffer content;
for (PacketsQueue::iterator it = first; it <= last; ++it) {
- content.Append((*it)->payload());
+ content.Append(it->packet->payload());
// Delete packet because we don't need it anymore.
- delete *it;
- *it = NULL;
+ delete it->packet;
+ it->packet = NULL;
+ // Here we keep |received| flag set to true, so that duplicate RTP
+ // packets will be ignored.
}
packet->mutable_data()->resize(content.total_bytes());
diff --git a/remoting/protocol/rtp_video_reader.h b/remoting/protocol/rtp_video_reader.h
index 9a0ae89..412a4f0 100644
--- a/remoting/protocol/rtp_video_reader.h
+++ b/remoting/protocol/rtp_video_reader.h
@@ -25,7 +25,23 @@ class RtpVideoReader : public VideoReader {
private:
friend class RtpVideoReaderTest;
- typedef std::deque<const RtpPacket*> PacketsQueue;
+ // Following struct is used to store pending packets in |packets_queue_|.
+ // Each entry may be in three different states:
+ // |received| == false, |packet| == NULL - packet with the corresponding
+ // sequence number hasn't been received.
+ // |received| == true, |packet| != NULL - packet with the corresponding
+ // sequence number has been received, but hasn't been processed, still
+ // waiting for other fragments.
+ // |received| == true, |packet| == NULL - packet with the corresponding
+ // sequence number has been received and processed. Ignore any additional
+ // packet with the same sequence number.
+ struct PacketsQueueEntry {
+ PacketsQueueEntry();
+ bool received;
+ const RtpPacket* packet;
+ };
+
+ typedef std::deque<PacketsQueueEntry> PacketsQueue;
void OnRtpPacket(const RtpPacket* rtp_packet);
void CheckFullPacket(PacketsQueue::iterator pos);
@@ -36,7 +52,7 @@ class RtpVideoReader : public VideoReader {
RtpReader rtp_reader_;
PacketsQueue packets_queue_;
- uint32 last_sequence_number_;
+ uint16 last_sequence_number_;
// The stub that processes all received packets.
VideoStub* video_stub_;
diff --git a/remoting/protocol/rtp_video_reader_unittest.cc b/remoting/protocol/rtp_video_reader_unittest.cc
new file mode 100644
index 0000000..81a8e17
--- /dev/null
+++ b/remoting/protocol/rtp_video_reader_unittest.cc
@@ -0,0 +1,348 @@
+// 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 <vector>
+
+#include "base/message_loop.h"
+#include "base/string_number_conversions.h"
+#include "net/base/io_buffer.h"
+#include "remoting/proto/video.pb.h"
+#include "remoting/protocol/fake_session.h"
+#include "remoting/protocol/rtp_utils.h"
+#include "remoting/protocol/rtp_video_reader.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using net::IOBuffer;
+using std::vector;
+
+namespace remoting {
+namespace protocol {
+
+class RtpVideoReaderTest : public testing::Test,
+ public VideoStub {
+ public:
+ virtual void ProcessVideoPacket(const VideoPacket* video_packet,
+ Task* done) {
+ received_packets_.push_back(VideoPacket());
+ received_packets_.back() = *video_packet;
+ done->Run();
+ delete done;
+ }
+
+ protected:
+ struct FragmentInfo {
+ int sequence_number;
+ int timestamp;
+ bool first;
+ bool last;
+ Vp8Descriptor::FragmentationInfo fragmentation_info;
+ int start;
+ int end;
+ };
+
+ struct ExpectedPacket {
+ int timestamp;
+ int flags;
+ int start;
+ int end;
+ };
+
+ virtual void SetUp() {
+ Reset();
+ InitData(100);
+ }
+
+ void Reset() {
+ session_ = new FakeSession();
+ reader_.reset(new RtpVideoReader());
+ reader_->Init(session_, this);
+ received_packets_.clear();
+ }
+
+ void InitData(int size) {
+ data_.resize(size);
+ for (int i = 0; i < size; ++i) {
+ data_[i] = static_cast<char>(i);
+ }
+ }
+
+ bool CompareData(const CompoundBuffer& buffer, char* data, int size) {
+ scoped_refptr<IOBuffer> buffer_data = buffer.ToIOBufferWithSize();
+ return buffer.total_bytes() == size &&
+ memcmp(buffer_data->data(), data, size) == 0;
+ }
+
+ void SplitAndSend(const FragmentInfo fragments[], int count) {
+ for (int i = 0; i < count; ++i) {
+ RtpPacket* packet = new RtpPacket();
+
+ packet->mutable_header()->sequence_number = fragments[i].sequence_number;
+ packet->mutable_header()->marker = fragments[i].last;
+ packet->mutable_header()->timestamp = fragments[i].timestamp;
+
+ packet->mutable_vp8_descriptor()->frame_beginning = fragments[i].first;
+ packet->mutable_vp8_descriptor()->fragmentation_info =
+ fragments[i].fragmentation_info;
+ packet->mutable_payload()->AppendCopyOf(
+ &*data_.begin() + fragments[i].start,
+ fragments[i].end - fragments[i].start);
+ reader_->OnRtpPacket(packet);
+ }
+ }
+
+ void CheckResults(const ExpectedPacket expected[], int count) {
+ ASSERT_EQ(count, static_cast<int>(received_packets_.size()));
+ for (int i = 0; i < count; ++i) {
+ SCOPED_TRACE("Packet " + base::IntToString(i));
+
+ int expected_size = expected[i].end - expected[i].start;
+ EXPECT_EQ(expected_size,
+ static_cast<int>(received_packets_[i].data().size()));
+ EXPECT_EQ(0, memcmp(&*received_packets_[i].data().data(),
+ &*data_.begin() + expected[i].start, expected_size));
+ EXPECT_EQ(expected[i].flags, received_packets_[i].flags());
+ EXPECT_EQ(expected[i].timestamp, received_packets_[i].timestamp());
+ }
+ }
+
+ scoped_refptr<FakeSession> session_;
+ scoped_ptr<RtpVideoReader> reader_;
+
+ MessageLoop message_loop_;
+ vector<char> data_;
+ vector<VideoPacket> received_packets_;
+};
+
+// One non-fragmented packet marked as first.
+TEST_F(RtpVideoReaderTest, NotFragmented_FirstPacket) {
+ FragmentInfo fragments[] = {
+ { 300, 123, true, false, Vp8Descriptor::NOT_FRAGMENTED, 0, 100 },
+ };
+ SplitAndSend(fragments, arraysize(fragments));
+
+ ExpectedPacket expected[] = {
+ { 123, VideoPacket::FIRST_PACKET, 0, 100 },
+ };
+ CheckResults(expected, arraysize(expected));
+}
+
+// One non-fragmented packet marked as last.
+TEST_F(RtpVideoReaderTest, NotFragmented_LastPacket) {
+ FragmentInfo fragments[] = {
+ { 3000, 123, false, true, Vp8Descriptor::NOT_FRAGMENTED, 0, 100 },
+ };
+ SplitAndSend(fragments, arraysize(fragments));
+
+ ExpectedPacket expected[] = {
+ { 123, VideoPacket::LAST_PACKET, 0, 100 },
+ };
+ CheckResults(expected, arraysize(expected));
+}
+
+// Duplicated non-fragmented packet. Must be processed only once.
+TEST_F(RtpVideoReaderTest, NotFragmented_Duplicate) {
+ FragmentInfo fragments[] = {
+ { 300, 123, true, false, Vp8Descriptor::NOT_FRAGMENTED, 0, 100 },
+ { 300, 123, true, false, Vp8Descriptor::NOT_FRAGMENTED, 0, 100 },
+ };
+ SplitAndSend(fragments, arraysize(fragments));
+
+ ExpectedPacket expected[] = {
+ { 123, VideoPacket::FIRST_PACKET, 0, 100 },
+ };
+ CheckResults(expected, arraysize(expected));
+}
+
+// First packet split into two fragments.
+TEST_F(RtpVideoReaderTest, TwoFragments_FirstPacket) {
+ FragmentInfo fragments[] = {
+ { 300, 321, true, false, Vp8Descriptor::FIRST_FRAGMENT, 0, 50 },
+ { 301, 321, false, false, Vp8Descriptor::LAST_FRAGMENT, 50, 100 },
+ };
+ SplitAndSend(fragments, arraysize(fragments));
+
+ ExpectedPacket expected[] = {
+ { 321, VideoPacket::FIRST_PACKET, 0, 100 },
+ };
+ CheckResults(expected, arraysize(expected));
+}
+
+// Last packet split into two fragments.
+TEST_F(RtpVideoReaderTest, TwoFragments_LastPacket) {
+ FragmentInfo fragments[] = {
+ { 3000, 400, false, false, Vp8Descriptor::FIRST_FRAGMENT, 0, 50 },
+ { 3001, 400, false, true, Vp8Descriptor::LAST_FRAGMENT, 50, 100 },
+ };
+ SplitAndSend(fragments, arraysize(fragments));
+
+ ExpectedPacket expected[] = {
+ { 400, VideoPacket::LAST_PACKET, 0, 100 },
+ };
+ CheckResults(expected, arraysize(expected));
+}
+
+// Duplicated second fragment.
+TEST_F(RtpVideoReaderTest, TwoFragments_WithDuplicate) {
+ FragmentInfo fragments[] = {
+ { 3000, 400, false, false, Vp8Descriptor::FIRST_FRAGMENT, 0, 50 },
+ { 3001, 400, false, true, Vp8Descriptor::LAST_FRAGMENT, 50, 100 },
+ { 3001, 400, false, true, Vp8Descriptor::LAST_FRAGMENT, 50, 100 },
+ };
+ SplitAndSend(fragments, arraysize(fragments));
+
+ ExpectedPacket expected[] = {
+ { 400, VideoPacket::LAST_PACKET, 0, 100 },
+ };
+ CheckResults(expected, arraysize(expected));
+}
+
+// Packet split into three fragments.
+TEST_F(RtpVideoReaderTest, ThreeFragments_Ordered) {
+ FragmentInfo fragments[] = {
+ { 300, 400, true, false, Vp8Descriptor::FIRST_FRAGMENT, 0, 50 },
+ { 301, 400, false, false, Vp8Descriptor::MIDDLE_FRAGMENT, 50, 90 },
+ { 302, 400, false, false, Vp8Descriptor::LAST_FRAGMENT, 90, 100 },
+ };
+ SplitAndSend(fragments, arraysize(fragments));
+
+ ExpectedPacket expected[] = {
+ { 400, VideoPacket::FIRST_PACKET, 0, 100 },
+ };
+ CheckResults(expected, arraysize(expected));
+}
+
+// Packet split into three fragments received in reverse order.
+TEST_F(RtpVideoReaderTest, ThreeFragments_ReverseOrder) {
+ FragmentInfo fragments[] = {
+ { 302, 400, false, false, Vp8Descriptor::LAST_FRAGMENT, 90, 100 },
+ { 301, 400, false, false, Vp8Descriptor::MIDDLE_FRAGMENT, 50, 90 },
+ { 300, 400, true, false, Vp8Descriptor::FIRST_FRAGMENT, 0, 50 },
+ };
+ SplitAndSend(fragments, arraysize(fragments));
+
+ ExpectedPacket expected[] = {
+ { 400, VideoPacket::FIRST_PACKET, 0, 100 },
+ };
+ CheckResults(expected, arraysize(expected));
+}
+
+// Two fragmented packets.
+TEST_F(RtpVideoReaderTest, TwoPackets) {
+ FragmentInfo fragments[] = {
+ { 300, 100, true, false, Vp8Descriptor::FIRST_FRAGMENT, 0, 10 },
+ { 301, 100, false, false, Vp8Descriptor::MIDDLE_FRAGMENT, 10, 20 },
+ { 302, 100, false, false, Vp8Descriptor::LAST_FRAGMENT, 20, 40 },
+
+ { 303, 200, false, false, Vp8Descriptor::FIRST_FRAGMENT, 40, 70 },
+ { 304, 200, false, true, Vp8Descriptor::LAST_FRAGMENT, 70, 100 },
+ };
+ SplitAndSend(fragments, arraysize(fragments));
+
+ ExpectedPacket expected[] = {
+ { 100, VideoPacket::FIRST_PACKET, 0, 40 },
+ { 200, VideoPacket::LAST_PACKET, 40, 100 },
+ };
+ CheckResults(expected, arraysize(expected));
+}
+
+// Sequence of three packets, with one lost fragment lost in the second packet.
+TEST_F(RtpVideoReaderTest, LostFragment) {
+ FragmentInfo fragments[] = {
+ { 300, 100, true, false, Vp8Descriptor::FIRST_FRAGMENT, 0, 10 },
+ { 301, 100, false, false, Vp8Descriptor::MIDDLE_FRAGMENT, 10, 20 },
+ { 302, 100, false, false, Vp8Descriptor::LAST_FRAGMENT, 20, 30 },
+
+ // Lost: { 303, 200, false, false, Vp8Descriptor::FIRST_FRAGMENT, 40, 50 },
+ { 304, 200, false, true, Vp8Descriptor::LAST_FRAGMENT, 30, 40 },
+
+ { 305, 300, true, false, Vp8Descriptor::FIRST_FRAGMENT, 50, 70 },
+ { 306, 300, false, true, Vp8Descriptor::LAST_FRAGMENT, 70, 100 },
+ };
+ SplitAndSend(fragments, arraysize(fragments));
+
+ ExpectedPacket expected[] = {
+ { 100, VideoPacket::FIRST_PACKET, 0, 30 },
+ { 300, VideoPacket::FIRST_PACKET | VideoPacket::LAST_PACKET, 50, 100 },
+ };
+ CheckResults(expected, arraysize(expected));
+}
+
+// Sequence of four packets, with two lost fragments.
+TEST_F(RtpVideoReaderTest, TwoLostFragments) {
+ // Fragments 303 and 306 should not be combined because they belong to
+ // different Vp8 partitions.
+ FragmentInfo fragments[] = {
+ { 300, 100, true, false, Vp8Descriptor::FIRST_FRAGMENT, 0, 10 },
+ { 301, 100, false, false, Vp8Descriptor::MIDDLE_FRAGMENT, 10, 20 },
+ { 302, 100, false, false, Vp8Descriptor::LAST_FRAGMENT, 20, 30 },
+
+ { 303, 200, false, false, Vp8Descriptor::FIRST_FRAGMENT, 40, 50 },
+ // Lost: { 304, 200, false, true, Vp8Descriptor::LAST_FRAGMENT, 30, 40 },
+
+ // Lost: { 305, 300, true, false, Vp8Descriptor::FIRST_FRAGMENT, 50, 60 },
+ { 306, 300, false, true, Vp8Descriptor::LAST_FRAGMENT, 60, 70 },
+
+ { 307, 400, true, false, Vp8Descriptor::FIRST_FRAGMENT, 70, 80 },
+ { 308, 400, false, true, Vp8Descriptor::LAST_FRAGMENT, 80, 100 },
+ };
+ SplitAndSend(fragments, arraysize(fragments));
+
+ ExpectedPacket expected[] = {
+ { 100, VideoPacket::FIRST_PACKET, 0, 30 },
+ { 400, VideoPacket::FIRST_PACKET | VideoPacket::LAST_PACKET, 70, 100 },
+ };
+ CheckResults(expected, arraysize(expected));
+}
+
+// Sequence number wrapping.
+TEST_F(RtpVideoReaderTest, SequenceNumberWrapping) {
+ FragmentInfo fragments[] = {
+ { 65534, 400, true, false, Vp8Descriptor::FIRST_FRAGMENT, 0, 50 },
+ { 65535, 400, false, false, Vp8Descriptor::MIDDLE_FRAGMENT, 50, 90 },
+ { 0, 400, false, false, Vp8Descriptor::LAST_FRAGMENT, 90, 100 },
+ };
+ SplitAndSend(fragments, arraysize(fragments));
+
+ ExpectedPacket expected[] = {
+ { 400, VideoPacket::FIRST_PACKET, 0, 100 },
+ };
+ CheckResults(expected, arraysize(expected));
+}
+
+// Sequence number wrapping for fragments received out of order.
+TEST_F(RtpVideoReaderTest, SequenceNumberWrappingReordered) {
+ FragmentInfo fragments[] = {
+ { 0, 400, false, false, Vp8Descriptor::LAST_FRAGMENT, 90, 100 },
+ { 65534, 400, true, false, Vp8Descriptor::FIRST_FRAGMENT, 0, 50 },
+ { 65535, 400, false, false, Vp8Descriptor::MIDDLE_FRAGMENT, 50, 90 },
+ };
+ SplitAndSend(fragments, arraysize(fragments));
+
+ ExpectedPacket expected[] = {
+ { 400, VideoPacket::FIRST_PACKET, 0, 100 },
+ };
+ CheckResults(expected, arraysize(expected));
+}
+
+// An old packet with invalid sequence number.
+TEST_F(RtpVideoReaderTest, OldPacket) {
+ FragmentInfo fragments[] = {
+ { 32000, 123, true, true, Vp8Descriptor::NOT_FRAGMENTED, 0, 30 },
+
+ // Should be ignored.
+ { 10000, 532, true, true, Vp8Descriptor::NOT_FRAGMENTED, 30, 40 },
+
+ { 32001, 223, true, true, Vp8Descriptor::NOT_FRAGMENTED, 40, 50 },
+ };
+ SplitAndSend(fragments, arraysize(fragments));
+
+ ExpectedPacket expected[] = {
+ { 123, VideoPacket::FIRST_PACKET | VideoPacket::LAST_PACKET, 0, 30 },
+ { 223, VideoPacket::FIRST_PACKET | VideoPacket::LAST_PACKET, 40, 50 },
+ };
+ CheckResults(expected, arraysize(expected));
+}
+
+} // namespace protocol
+} // namespace remoting
diff --git a/remoting/protocol/rtp_video_writer_unittest.cc b/remoting/protocol/rtp_video_writer_unittest.cc
new file mode 100644
index 0000000..ca8a8f4
--- /dev/null
+++ b/remoting/protocol/rtp_video_writer_unittest.cc
@@ -0,0 +1,178 @@
+// 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 <string>
+#include <vector>
+
+#include "base/message_loop.h"
+#include "base/string_number_conversions.h"
+#include "remoting/proto/video.pb.h"
+#include "remoting/protocol/fake_session.h"
+#include "remoting/protocol/rtp_reader.h"
+#include "remoting/protocol/rtp_utils.h"
+#include "remoting/protocol/rtp_video_writer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using net::IOBuffer;
+using std::string;
+using std::vector;
+
+namespace remoting {
+namespace protocol {
+
+namespace {
+
+bool ParsePacket(const string& data, RtpPacket* packet) {
+ int header_size = UnpackRtpHeader(
+ reinterpret_cast<const uint8*>(&*data.begin()),
+ data.size(), packet->mutable_header());
+ if (header_size < 0) {
+ return false;
+ }
+
+ int descriptor_size = UnpackVp8Descriptor(
+ reinterpret_cast<const uint8*>(&*data.begin()) + header_size,
+ data.size() - header_size, packet->mutable_vp8_descriptor());
+ if (descriptor_size < 0) {
+ return false;
+ }
+
+ packet->mutable_payload()->AppendCopyOf(
+ &*data.begin() + header_size + descriptor_size,
+ data.size() - header_size - descriptor_size);
+
+ return true;
+}
+
+} // namespace
+
+class RtpVideoWriterTest : public testing::Test {
+ protected:
+ struct ExpectedPacket {
+ bool first;
+ Vp8Descriptor::FragmentationInfo fragmentation_info;
+ bool last;
+ };
+
+ virtual void SetUp() {
+ session_ = new FakeSession();
+ writer_.Init(session_);
+ }
+
+ void InitData(int size) {
+ data_.resize(size);
+ for (int i = 0; i < size; ++i) {
+ data_[i] = static_cast<char>(i);
+ }
+ }
+
+ void InitPacket(int size, bool first, bool last) {
+ InitData(size);
+
+ packet_.reset(new VideoPacket());
+ packet_->mutable_format()->set_encoding(VideoPacketFormat::ENCODING_VP8);
+ if (first)
+ packet_->set_flags(packet_->flags() | VideoPacket::FIRST_PACKET);
+ if (last)
+ packet_->set_flags(packet_->flags() | VideoPacket::LAST_PACKET);
+ packet_->mutable_data()->assign(data_.begin(), data_.end());
+ }
+
+ bool CompareData(const CompoundBuffer& buffer, char* data, int size) {
+ scoped_refptr<IOBuffer> buffer_data = buffer.ToIOBufferWithSize();
+ return buffer.total_bytes() == size &&
+ memcmp(buffer_data->data(), data, size) == 0;
+ }
+
+ void VerifyResult(const ExpectedPacket expected[],
+ int count) {
+ const vector<string>& rtp_packets =
+ session_->video_rtp_channel()->written_packets();
+ ASSERT_EQ(count, static_cast<int>(rtp_packets.size()));
+ int pos = 0;
+ for (int i = 0; i < count; ++i) {
+ SCOPED_TRACE("Packet " + base::IntToString(i));
+
+ RtpPacket packet;
+ ASSERT_TRUE(ParsePacket(rtp_packets[i], &packet));
+ EXPECT_EQ(expected[i].first, packet.vp8_descriptor().frame_beginning);
+ EXPECT_EQ(expected[i].last, packet.header().marker);
+ EXPECT_EQ(expected[i].fragmentation_info,
+ packet.vp8_descriptor().fragmentation_info);
+ EXPECT_TRUE(CompareData(packet.payload(), &*data_.begin() + pos,
+ packet.payload().total_bytes()));
+ pos += packet.payload().total_bytes();
+ }
+ EXPECT_EQ(pos, static_cast<int>(data_.size()));
+ }
+
+ scoped_refptr<FakeSession> session_;
+ RtpVideoWriter writer_;
+
+ MessageLoop message_loop_;
+ vector<char> data_;
+ scoped_ptr<VideoPacket> packet_;
+};
+
+TEST_F(RtpVideoWriterTest, NotFragmented_FirstPacket) {
+ InitPacket(1024, true, false);
+ writer_.SendPacket(*packet_);
+ message_loop_.RunAllPending();
+
+ ExpectedPacket expected[] = {
+ { true, Vp8Descriptor::NOT_FRAGMENTED, false }
+ };
+ VerifyResult(expected, arraysize(expected));
+}
+
+TEST_F(RtpVideoWriterTest, NotFragmented_LastPackes) {
+ InitPacket(1024, false, true);
+ writer_.SendPacket(*packet_);
+ message_loop_.RunAllPending();
+
+ ExpectedPacket expected[] = {
+ { false, Vp8Descriptor::NOT_FRAGMENTED, true }
+ };
+ VerifyResult(expected, arraysize(expected));
+}
+
+TEST_F(RtpVideoWriterTest, TwoFragments_FirstPacket) {
+ InitPacket(2000, true, false);
+ writer_.SendPacket(*packet_);
+ message_loop_.RunAllPending();
+
+ ExpectedPacket expected[] = {
+ { true, Vp8Descriptor::FIRST_FRAGMENT, false },
+ { false, Vp8Descriptor::LAST_FRAGMENT, false },
+ };
+ VerifyResult(expected, arraysize(expected));
+}
+
+TEST_F(RtpVideoWriterTest, TwoFragments_LastPacket) {
+ InitPacket(2000, false, true);
+ writer_.SendPacket(*packet_);
+ message_loop_.RunAllPending();
+
+ ExpectedPacket expected[] = {
+ { false, Vp8Descriptor::FIRST_FRAGMENT, false },
+ { false, Vp8Descriptor::LAST_FRAGMENT, true },
+ };
+ VerifyResult(expected, arraysize(expected));
+}
+
+TEST_F(RtpVideoWriterTest, ThreeFragments) {
+ InitPacket(3000, true, true);
+ writer_.SendPacket(*packet_);
+ message_loop_.RunAllPending();
+
+ ExpectedPacket expected[] = {
+ { true, Vp8Descriptor::FIRST_FRAGMENT, false },
+ { false, Vp8Descriptor::MIDDLE_FRAGMENT, false },
+ { false, Vp8Descriptor::LAST_FRAGMENT, true },
+ };
+ VerifyResult(expected, arraysize(expected));
+}
+
+} // namespace protocol
+} // namespace remoting
diff --git a/remoting/remoting.gyp b/remoting/remoting.gyp
index 915ae65..e2c3ece 100644
--- a/remoting/remoting.gyp
+++ b/remoting/remoting.gyp
@@ -481,8 +481,9 @@
'protocol/fake_session.h',
'protocol/jingle_session_unittest.cc',
'protocol/message_decoder_unittest.cc',
- 'protocol/message_decoder_unittest.cc',
'protocol/mock_objects.h',
+ 'protocol/rtp_video_reader_unittest.cc',
+ 'protocol/rtp_video_writer_unittest.cc',
'protocol/session_manager_pair.cc',
'protocol/session_manager_pair.h',
'run_all_unittests.cc',