diff options
author | sergeyu@chromium.org <sergeyu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-11-17 23:05:15 +0000 |
---|---|---|
committer | sergeyu@chromium.org <sergeyu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-11-17 23:05:15 +0000 |
commit | 5ddaa0ac4c4d01677f469e65a02ff584d3035de2 (patch) | |
tree | a69cd3af68055724bae9ee9d4ddcac69bed8c666 /remoting | |
parent | ee97fb0629b6dc336f9c7d69e8595dd9637b336c (diff) | |
download | chromium_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.cc | 2 | ||||
-rw-r--r-- | remoting/protocol/fake_session.cc | 60 | ||||
-rw-r--r-- | remoting/protocol/fake_session.h | 50 | ||||
-rw-r--r-- | remoting/protocol/rtp_video_reader.cc | 66 | ||||
-rw-r--r-- | remoting/protocol/rtp_video_reader.h | 20 | ||||
-rw-r--r-- | remoting/protocol/rtp_video_reader_unittest.cc | 348 | ||||
-rw-r--r-- | remoting/protocol/rtp_video_writer_unittest.cc | 178 | ||||
-rw-r--r-- | remoting/remoting.gyp | 3 |
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', |