diff options
author | mikhal@google.com <mikhal@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-09-04 02:28:01 +0000 |
---|---|---|
committer | mikhal@google.com <mikhal@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-09-04 02:28:01 +0000 |
commit | c72e1a037f0d3dc4a61cb1a26395064a7fcc2ed4 (patch) | |
tree | ed7fee2f4351eddb1a6ea4b5d41a81b113fb36a5 /media | |
parent | d363d80ee017cba160a5aec02a830e6bf39fc9d3 (diff) | |
download | chromium_src-c72e1a037f0d3dc4a61cb1a26395064a7fcc2ed4.zip chromium_src-c72e1a037f0d3dc4a61cb1a26395064a7fcc2ed4.tar.gz chromium_src-c72e1a037f0d3dc4a61cb1a26395064a7fcc2ed4.tar.bz2 |
Adding rtp receiver to cast
Review URL: https://chromiumcodereview.appspot.com/23718002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@221120 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r-- | media/cast/cast.gyp | 3 | ||||
-rw-r--r-- | media/cast/cast_receiver.gyp | 1 | ||||
-rw-r--r-- | media/cast/rtp_receiver/receiver_stats.cc | 120 | ||||
-rw-r--r-- | media/cast/rtp_receiver/receiver_stats.h | 53 | ||||
-rw-r--r-- | media/cast/rtp_receiver/receiver_stats_unittest.cc | 157 | ||||
-rw-r--r-- | media/cast/rtp_receiver/rtp_parser/include/mock/mock_rtp_feedback.h | 37 | ||||
-rw-r--r-- | media/cast/rtp_receiver/rtp_parser/rtp_parser.cc | 107 | ||||
-rw-r--r-- | media/cast/rtp_receiver/rtp_parser/rtp_parser.gypi | 25 | ||||
-rw-r--r-- | media/cast/rtp_receiver/rtp_parser/rtp_parser.h | 53 | ||||
-rw-r--r-- | media/cast/rtp_receiver/rtp_parser/rtp_parser_unittest.cc | 201 | ||||
-rw-r--r-- | media/cast/rtp_receiver/rtp_parser/test/rtp_packet_builder.cc | 104 | ||||
-rw-r--r-- | media/cast/rtp_receiver/rtp_parser/test/rtp_packet_builder.h | 51 | ||||
-rw-r--r-- | media/cast/rtp_receiver/rtp_receiver.cc | 57 | ||||
-rw-r--r-- | media/cast/rtp_receiver/rtp_receiver.gyp | 27 | ||||
-rw-r--r-- | media/cast/rtp_receiver/rtp_receiver.h | 53 |
15 files changed, 1049 insertions, 0 deletions
diff --git a/media/cast/cast.gyp b/media/cast/cast.gyp index 776bb46..33f6afd 100644 --- a/media/cast/cast.gyp +++ b/media/cast/cast.gyp @@ -60,6 +60,9 @@ 'framer/cast_message_builder_unittest.cc', 'framer/frame_buffer_unittest.cc', 'framer/framer_unittest.cc', + 'rtp_receiver/receiver_stats_unittest.cc', + 'rtp_receiver/rtp_parser/test/rtp_packet_builder.cc', + 'rtp_receiver/rtp_parser/rtp_parser_unittest.cc', 'rtp_sender/packet_storage/packet_storage_unittest.cc', 'rtp_sender/rtp_packetizer/rtp_packetizer_unittest.cc', 'rtp_sender/rtp_packetizer/test/rtp_header_parser.cc', diff --git a/media/cast/cast_receiver.gyp b/media/cast/cast_receiver.gyp index 0fb5e38..32bc5c7 100644 --- a/media/cast/cast_receiver.gyp +++ b/media/cast/cast_receiver.gyp @@ -17,6 +17,7 @@ # 'cast_receiver_impl.h', ], # source 'dependencies': [ + 'rtp_receiver/rtp_receiver.gyp:*', # 'audio_receiver', # 'video_receiver', 'framer/framer.gyp:cast_framer', diff --git a/media/cast/rtp_receiver/receiver_stats.cc b/media/cast/rtp_receiver/receiver_stats.cc new file mode 100644 index 0000000..44a9b81 --- /dev/null +++ b/media/cast/rtp_receiver/receiver_stats.cc @@ -0,0 +1,120 @@ +// Copyright 2013 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 "media/cast/rtp_receiver/receiver_stats.h" + +#include "base/logging.h" +#include "media/cast/rtp_common/rtp_defines.h" + +namespace media { +namespace cast { + +static const uint32 kMaxSequenceNumber = 65536; + +ReceiverStats::ReceiverStats(uint32 ssrc) + : ssrc_(ssrc), + min_sequence_number_(0), + max_sequence_number_(0), + total_number_packets_(0), + sequence_number_cycles_(0), + interval_min_sequence_number_(0), + interval_number_packets_(0), + interval_wrap_count_(0), + default_tick_clock_(), + clock_(&default_tick_clock_) {} + +ReceiverStats::~ReceiverStats() {} + +void ReceiverStats::GetStatistics(uint8* fraction_lost, + uint32* cumulative_lost, + uint32* extended_high_sequence_number, + uint32* jitter) { + // Compute losses. + if (interval_number_packets_ == 0) { + *fraction_lost = 0; + } else { + int diff = 0; + if (interval_wrap_count_ == 0) { + diff = max_sequence_number_ - interval_min_sequence_number_ + 1; + } else { + diff = kMaxSequenceNumber * (interval_wrap_count_ - 1) + + (max_sequence_number_ - interval_min_sequence_number_ + + kMaxSequenceNumber + 1); + } + + if (diff < 1) { + *fraction_lost = 0; + } else { + *fraction_lost = static_cast<uint8>((256 * (1 - + static_cast<float>(interval_number_packets_) / abs(diff)))); + } + } + + int expected_packets_num = max_sequence_number_ - min_sequence_number_ + 1; + if (total_number_packets_ == 0) { + *cumulative_lost = 0; + } else if (sequence_number_cycles_ == 0) { + *cumulative_lost = expected_packets_num - total_number_packets_; + } else { + *cumulative_lost = kMaxSequenceNumber * (sequence_number_cycles_ - 1) + + (expected_packets_num - total_number_packets_ + kMaxSequenceNumber); + } + + // Extended high sequence number consists of the highest seq number and the + // number of cycles (wrap). + *extended_high_sequence_number = (sequence_number_cycles_ << 16) + + max_sequence_number_; + + *jitter = static_cast<uint32>(abs(jitter_.InMilliseconds())); + + // Reset interval values. + interval_min_sequence_number_ = 0; + interval_number_packets_ = 0; + interval_wrap_count_ = 0; +} + +void ReceiverStats::UpdateStatistics(const RtpCastHeader& header) { + if (ssrc_ != header.webrtc.header.ssrc) return; + + uint16 new_seq_num = header.webrtc.header.sequenceNumber; + + if (interval_number_packets_ == 0) { + // First packet in the interval. + interval_min_sequence_number_ = new_seq_num; + } + if (total_number_packets_ == 0) { + // First incoming packet. + min_sequence_number_ = new_seq_num; + max_sequence_number_ = new_seq_num; + } + + if (IsNewerSequenceNumber(new_seq_num, max_sequence_number_)) { + // Check wrap. + if (new_seq_num < max_sequence_number_) { + ++sequence_number_cycles_; + ++interval_wrap_count_; + } + max_sequence_number_ = new_seq_num; + } + + // Compute Jitter. + base::TimeTicks now = clock_->NowTicks(); + base::TimeDelta delta_new_timestamp = + base::TimeDelta::FromMilliseconds(header.webrtc.header.timestamp); + if (total_number_packets_ > 0) { + // Update jitter. + base::TimeDelta delta = (now - last_received_packet_time_) - + ((delta_new_timestamp - last_received_timestamp_) / 90000); + jitter_ += (delta - jitter_) / 16; + } + last_received_timestamp_ = delta_new_timestamp; + last_received_packet_time_ = now; + + // Increment counters. + ++total_number_packets_; + ++interval_number_packets_; +} + +} // namespace cast +} // namespace media diff --git a/media/cast/rtp_receiver/receiver_stats.h b/media/cast/rtp_receiver/receiver_stats.h new file mode 100644 index 0000000..610f515 --- /dev/null +++ b/media/cast/rtp_receiver/receiver_stats.h @@ -0,0 +1,53 @@ +// Copyright 2013 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 MEDIA_CAST_RTP_RECEIVER_RECEIVER_STATS_H_ +#define MEDIA_CAST_RTP_RECEIVER_RECEIVER_STATS_H_ + +#include "base/time/default_tick_clock.h" +#include "base/time/tick_clock.h" +#include "base/time/time.h" +#include "media/cast/rtp_common/rtp_defines.h" + +namespace media { +namespace cast { + +class ReceiverStats { + public: + explicit ReceiverStats(uint32 ssrc); + ~ReceiverStats(); + void GetStatistics(uint8* fraction_lost, + uint32* cumulative_lost, // 24 bits valid. + uint32* extended_high_sequence_number, + uint32* jitter); + void UpdateStatistics(const RtpCastHeader& header); + + void set_clock(base::TickClock* clock) { + clock_ = clock; + } + + private: + const uint32 ssrc_; + + // Global metrics. + uint16 min_sequence_number_; + uint16 max_sequence_number_; + uint32 total_number_packets_; + uint16 sequence_number_cycles_; + base::TimeDelta last_received_timestamp_; + base::TimeTicks last_received_packet_time_; + base::TimeDelta jitter_; + + // Intermediate metrics - between RTCP reports. + int interval_min_sequence_number_; + int interval_number_packets_; + int interval_wrap_count_; + base::DefaultTickClock default_tick_clock_; + base::TickClock* clock_; +}; + +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_RTP_RECEIVER_RECEIVER_STATS_H_ diff --git a/media/cast/rtp_receiver/receiver_stats_unittest.cc b/media/cast/rtp_receiver/receiver_stats_unittest.cc new file mode 100644 index 0000000..c6cf91a --- /dev/null +++ b/media/cast/rtp_receiver/receiver_stats_unittest.cc @@ -0,0 +1,157 @@ +// Copyright 2013 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 <gtest/gtest.h> + +#include "base/test/simple_test_tick_clock.h" +#include "base/time/time.h" +#include "media/cast/rtp_common/rtp_defines.h" +#include "media/cast/rtp_receiver/receiver_stats.h" + +namespace media { +namespace cast { + +static const int64 kStartMillisecond = 123456789; +static const uint32 kStdTimeIncrementMs = 33; +static const uint32 kSsrc = 0x1234; + +class ReceiverStatsTest : public ::testing::Test { + protected: + ReceiverStatsTest() + : stats_(kSsrc), + rtp_header_(), + fraction_lost_(0), + cumulative_lost_(0), + extended_high_sequence_number_(0), + jitter_(0) { + testing_clock_.Advance( + base::TimeDelta::FromMilliseconds(kStartMillisecond)); + start_time_ = testing_clock_.NowTicks(); + delta_increments_ = base::TimeDelta::FromMilliseconds(kStdTimeIncrementMs); + } + ~ReceiverStatsTest() {} + + virtual void SetUp() { + rtp_header_.webrtc.header.sequenceNumber = 0; + rtp_header_.webrtc.header.timestamp = 0; + rtp_header_.webrtc.header.ssrc = kSsrc; + } + + uint32 ExpectedJitter(uint32 const_interval, int num_packets) { + float jitter = 0; + // Assume timestamps have a constant kStdTimeIncrementMs interval. + float float_interval = + static_cast<float>(const_interval - kStdTimeIncrementMs); + for (int i = 0; i < num_packets; ++i) { + jitter += (float_interval - jitter) / 16; + } + return static_cast<uint32>(jitter + 0.5f); + } + + uint32 Timestamp() { + base::TimeDelta delta = testing_clock_.NowTicks() - start_time_; + return static_cast<uint32>(delta.InMilliseconds() * 90); + } + + ReceiverStats stats_; + RtpCastHeader rtp_header_; + uint8 fraction_lost_; + uint32 cumulative_lost_; + uint32 extended_high_sequence_number_; + uint32 jitter_; + base::SimpleTestTickClock testing_clock_; + base::TimeTicks start_time_; + base::TimeDelta delta_increments_; +}; + +TEST_F(ReceiverStatsTest, ResetState) { + stats_.GetStatistics(&fraction_lost_, &cumulative_lost_, + &extended_high_sequence_number_, &jitter_); + EXPECT_EQ(0u, fraction_lost_); + EXPECT_EQ(0u, cumulative_lost_); + EXPECT_EQ(0u, extended_high_sequence_number_); + EXPECT_EQ(0u, jitter_); +} + +TEST_F(ReceiverStatsTest, LossCount) { + for (int i = 0; i < 300; ++i) { + if (i % 4) + stats_.UpdateStatistics(rtp_header_); + if (i % 3) { + rtp_header_.webrtc.header.timestamp = Timestamp(); + } + ++rtp_header_.webrtc.header.sequenceNumber; + testing_clock_.Advance(delta_increments_); + } + stats_.GetStatistics(&fraction_lost_, &cumulative_lost_, + &extended_high_sequence_number_, &jitter_); + EXPECT_EQ(63u, fraction_lost_); + EXPECT_EQ(74u, cumulative_lost_); + // Build extended sequence number. + uint32 extended_seq_num = rtp_header_.webrtc.header.sequenceNumber - 1; + EXPECT_EQ(extended_seq_num, extended_high_sequence_number_); +} + +TEST_F(ReceiverStatsTest, NoLossWrap) { + rtp_header_.webrtc.header.sequenceNumber = 65500; + for (int i = 0; i < 300; ++i) { + stats_.UpdateStatistics(rtp_header_); + if (i % 3) { + rtp_header_.webrtc.header.timestamp = Timestamp(); + } + ++rtp_header_.webrtc.header.sequenceNumber; + testing_clock_.Advance(delta_increments_); + } + stats_.GetStatistics(&fraction_lost_, &cumulative_lost_, + &extended_high_sequence_number_, &jitter_); + EXPECT_EQ(0u, fraction_lost_); + EXPECT_EQ(0u, cumulative_lost_); + // Build extended sequence number (one wrap cycle). + uint32 extended_seq_num = (1 << 16) + + rtp_header_.webrtc.header.sequenceNumber - 1; + EXPECT_EQ(extended_seq_num, extended_high_sequence_number_); +} + +TEST_F(ReceiverStatsTest, LossCountWrap) { + const uint32 start_sequence_number = 65500; + rtp_header_.webrtc.header.sequenceNumber = start_sequence_number; + for (int i = 0; i < 300; ++i) { + if (i % 4) + stats_.UpdateStatistics(rtp_header_); + if (i % 3) + // Update timestamp. + ++rtp_header_.webrtc.header.timestamp; + ++rtp_header_.webrtc.header.sequenceNumber; + testing_clock_.Advance(delta_increments_); + } + stats_.GetStatistics(&fraction_lost_, &cumulative_lost_, + &extended_high_sequence_number_, &jitter_); + EXPECT_EQ(63u, fraction_lost_); + EXPECT_EQ(74u, cumulative_lost_); + // Build extended sequence number (one wrap cycle). + uint32 extended_seq_num = (1 << 16) + + rtp_header_.webrtc.header.sequenceNumber - 1; + EXPECT_EQ(extended_seq_num, extended_high_sequence_number_); +} + +TEST_F(ReceiverStatsTest, Jitter) { + rtp_header_.webrtc.header.timestamp = Timestamp(); + for (int i = 0; i < 300; ++i) { + stats_.UpdateStatistics(rtp_header_); + ++rtp_header_.webrtc.header.sequenceNumber; + rtp_header_.webrtc.header.timestamp += 33 * 90; + testing_clock_.Advance(delta_increments_); + } + stats_.GetStatistics(&fraction_lost_, &cumulative_lost_, + &extended_high_sequence_number_, &jitter_); + EXPECT_FALSE(fraction_lost_); + EXPECT_FALSE(cumulative_lost_); + // Build extended sequence number (one wrap cycle). + uint32 extended_seq_num = rtp_header_.webrtc.header.sequenceNumber - 1; + EXPECT_EQ(extended_seq_num, extended_high_sequence_number_); + EXPECT_EQ(ExpectedJitter(kStdTimeIncrementMs, 300), jitter_); +} + +} // namespace cast +} // namespace media diff --git a/media/cast/rtp_receiver/rtp_parser/include/mock/mock_rtp_feedback.h b/media/cast/rtp_receiver/rtp_parser/include/mock/mock_rtp_feedback.h new file mode 100644 index 0000000..d39bc2a --- /dev/null +++ b/media/cast/rtp_receiver/rtp_parser/include/mock/mock_rtp_feedback.h @@ -0,0 +1,37 @@ +// Copyright 2013 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 MEDIA_CAST_RTP_INCLUDE_MOCK_RTP_FEEDBACK_H_ +#define MEDIA_CAST_RTP_INCLUDE_MOCK_RTP_FEEDBACK_H_ + +#include "media/cast/rtp_receiver/rtp_parser/rtp_feedback.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace media { +namespace cast { + +class MockRtpFeedback : public RtpFeedback { + public: + MOCK_METHOD4(OnInitializeDecoder, + int32(const int8 payloadType, + const int frequency, + const uint8 channels, + const uint32 rate)); + + MOCK_METHOD1(OnPacketTimeout, + void(const int32 id)); + MOCK_METHOD2(OnReceivedPacket, + void(const int32 id, const RtpRtcpPacketType packet_type)); + MOCK_METHOD2(OnPeriodicDeadOrAlive, + void(const int32 id, const RTPAliveType alive)); + MOCK_METHOD2(OnIncomingSSRCChanged, + void(const int32 id, const uint32 ssrc)); + MOCK_METHOD3(OnIncomingCSRCChanged, + void(const int32 id, const uint32 csrc, const bool added)); +}; + +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_RTP_INCLUDE_MOCK_RTP_FEEDBACK_H_
\ No newline at end of file diff --git a/media/cast/rtp_receiver/rtp_parser/rtp_parser.cc b/media/cast/rtp_receiver/rtp_parser/rtp_parser.cc new file mode 100644 index 0000000..0eb691b --- /dev/null +++ b/media/cast/rtp_receiver/rtp_parser/rtp_parser.cc @@ -0,0 +1,107 @@ +// Copyright 2013 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 "media/cast/rtp_receiver/rtp_parser/rtp_parser.h" + +#include "base/logging.h" +#include "media/cast/cast_defines.h" +#include "media/cast/rtp_receiver/rtp_receiver.h" +#include "net/base/big_endian.h" + +namespace media { +namespace cast { + +static const int kRtpCommonHeaderLength = 12; +static const int kRtpCastHeaderLength = 7; +static const uint8 kCastKeyFrameBitMask = 0x80; +static const uint8 kCastReferenceFrameIdBitMask = 0x40; + +RtpParser::RtpParser(RtpData* incoming_payload_callback, + const RtpParserConfig parser_config) + : data_callback_(incoming_payload_callback), + parser_config_(parser_config) { +} + +RtpParser::~RtpParser() { +} + +bool RtpParser::ParsePacket(const uint8* packet, int length, + RtpCastHeader* rtp_header) { + if (length == 0) return false; + // Get RTP general header. + if (!ParseCommon(packet, length, rtp_header)) return false; + if (rtp_header->webrtc.header.payloadType == parser_config_.payload_type && + rtp_header->webrtc.header.ssrc == parser_config_.ssrc) { + return ParseCast(packet + kRtpCommonHeaderLength, + length - kRtpCommonHeaderLength, rtp_header); + } + // Not a valid payload type / ssrc combination. + return false; +} + +bool RtpParser::ParseCommon(const uint8* packet, + int length, + RtpCastHeader* rtp_header) { + if (length < kRtpCommonHeaderLength) return false; + uint8 version = packet[0] >> 6; + if (version != 2) return false; + uint8 cc = packet[0] & 0x0f; + bool marker = ((packet[1] & 0x80) != 0); + int payload_type = packet[1] & 0x7f; + + uint16 sequence_number; + uint32 rtp_timestamp, ssrc; + net::BigEndianReader big_endian_reader(packet + 2, 80); + big_endian_reader.ReadU16(&sequence_number); + big_endian_reader.ReadU32(&rtp_timestamp); + big_endian_reader.ReadU32(&ssrc); + + rtp_header->webrtc.header.markerBit = marker; + rtp_header->webrtc.header.payloadType = payload_type; + rtp_header->webrtc.header.sequenceNumber = sequence_number; + rtp_header->webrtc.header.timestamp = rtp_timestamp; + rtp_header->webrtc.header.ssrc = ssrc; + rtp_header->webrtc.header.numCSRCs = cc; + + uint8 csrc_octs = cc * 4; + rtp_header->webrtc.type.Audio.numEnergy = rtp_header->webrtc.header.numCSRCs; + rtp_header->webrtc.header.headerLength = kRtpCommonHeaderLength + csrc_octs; + rtp_header->webrtc.type.Audio.isCNG = false; + rtp_header->webrtc.type.Audio.channel = parser_config_.audio_channels; + return true; +} + +bool RtpParser::ParseCast(const uint8* packet, + int length, + RtpCastHeader* rtp_header) { + if (length < kRtpCastHeaderLength) return false; + // Extract header. + const uint8* data_ptr = packet; + int data_length = length; + rtp_header->is_key_frame = (data_ptr[0] & kCastKeyFrameBitMask); + rtp_header->is_reference = (data_ptr[0] & kCastReferenceFrameIdBitMask); + rtp_header->frame_id = data_ptr[1]; + + net::BigEndianReader big_endian_reader(data_ptr + 2, 32); + big_endian_reader.ReadU16(&rtp_header->packet_id); + big_endian_reader.ReadU16(&rtp_header->max_packet_id); + + if (rtp_header->is_reference) { + rtp_header->reference_frame_id = data_ptr[6]; + data_ptr += kRtpCastHeaderLength; + data_length -= kRtpCastHeaderLength; + } else { + data_ptr += kRtpCastHeaderLength - 1; + data_length -= kRtpCastHeaderLength - 1; + } + + if (rtp_header->max_packet_id < rtp_header->packet_id) { + return false; + } + data_callback_->OnReceivedPayloadData(data_ptr, data_length, rtp_header); + return true; +} + +} // namespace cast +} // namespace media diff --git a/media/cast/rtp_receiver/rtp_parser/rtp_parser.gypi b/media/cast/rtp_receiver/rtp_parser/rtp_parser.gypi new file mode 100644 index 0000000..0814e55 --- /dev/null +++ b/media/cast/rtp_receiver/rtp_parser/rtp_parser.gypi @@ -0,0 +1,25 @@ +# Copyright 2013 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. + +{ + 'targets': [ + { + 'target_name': 'cast_rtp_parser', + 'type': 'static_library', + 'include_dirs': [ + '<(DEPTH)/', + '<(DEPTH)/third_party/', + ], + 'sources': [ + 'rtp_parser_config.h', + 'rtp_parser.cc', + 'rtp_parser.h', + ], # source + 'dependencies': [ + '<(DEPTH)/base/base.gyp:base', + '<(DEPTH)/base/base.gyp:test_support_base', + ], + }, + ], +} diff --git a/media/cast/rtp_receiver/rtp_parser/rtp_parser.h b/media/cast/rtp_receiver/rtp_parser/rtp_parser.h new file mode 100644 index 0000000..7f85609 --- /dev/null +++ b/media/cast/rtp_receiver/rtp_parser/rtp_parser.h @@ -0,0 +1,53 @@ +// Copyright 2013 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 MEDIA_CAST_RTP_RECEIVER_RTP_PARSER_RTP_PARSER_H_ +#define MEDIA_CAST_RTP_RECEIVER_RTP_PARSER_RTP_PARSER_H_ + +#include "media/cast/rtp_common/rtp_defines.h" + +namespace media { +namespace cast { + +class RtpData; + +struct RtpParserConfig { + RtpParserConfig() { + ssrc = 0; + payload_type = 0; + audio_channels = 0; + } + + uint32 ssrc; + int payload_type; + AudioCodec audio_codec; + VideoCodec video_codec; + int audio_channels; +}; + +class RtpParser { + public: + RtpParser(RtpData* incoming_payload_callback, + const RtpParserConfig parser_config); + + ~RtpParser(); + + bool ParsePacket(const uint8* packet, int length, + RtpCastHeader* rtp_header); + + private: + bool ParseCommon(const uint8* packet, int length, + RtpCastHeader* rtp_header); + + bool ParseCast(const uint8* packet, int length, + RtpCastHeader* rtp_header); + + RtpData* data_callback_; + RtpParserConfig parser_config_; +}; + +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_RTP_RECEIVER_RTP_PARSER_RTP_PARSER_H_ diff --git a/media/cast/rtp_receiver/rtp_parser/rtp_parser_unittest.cc b/media/cast/rtp_receiver/rtp_parser/rtp_parser_unittest.cc new file mode 100644 index 0000000..71e6f50 --- /dev/null +++ b/media/cast/rtp_receiver/rtp_parser/rtp_parser_unittest.cc @@ -0,0 +1,201 @@ +// Copyright 2013 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 <gtest/gtest.h> + +#include "base/memory/scoped_ptr.h" +#include "media/cast/rtp_common/rtp_defines.h" +#include "media/cast/rtp_receiver/rtp_parser/rtp_parser.h" +#include "media/cast/rtp_receiver/rtp_parser/test/rtp_packet_builder.h" +#include "media/cast/rtp_receiver/rtp_receiver.h" + +namespace media { +namespace cast { + +static const int kPacketLength = 1500; +static const int kCastRtpLength = 7; +static const int kTestPayloadType = 127; +static const uint32 kTestSsrc = 1234; +static const uint32 kTestTimestamp = 111111; +static const uint16 kTestSeqNum = 4321; +static const uint8 kRefFrameId = 17; + +class RtpDataTest : public RtpData { + public: + RtpDataTest() { + expected_header_.reset(new RtpCastHeader()); + } + + ~RtpDataTest() {} + + void SetExpectedHeader(const RtpCastHeader& cast_header) { + memcpy(expected_header_.get(), &cast_header, sizeof(RtpCastHeader)); + } + + void OnReceivedPayloadData(const uint8* payloadData, + int payloadSize, + const RtpCastHeader* rtpHeader) { + VerifyCommonHeader(*rtpHeader); + VerifyCastHeader(*rtpHeader); + // TODO(mikhal): Add data verification. + } + + void VerifyCommonHeader(const RtpCastHeader& parsed_header) { + EXPECT_EQ(expected_header_->packet_id == expected_header_->max_packet_id, + parsed_header.webrtc.header.markerBit); + EXPECT_EQ(kTestPayloadType, parsed_header.webrtc.header.payloadType); + EXPECT_EQ(kTestSsrc, parsed_header.webrtc.header.ssrc); + EXPECT_EQ(0, parsed_header.webrtc.header.numCSRCs); + } + + void VerifyCastHeader(const RtpCastHeader& parsed_header) { + EXPECT_EQ(expected_header_->is_key_frame, parsed_header.is_key_frame); + EXPECT_EQ(expected_header_->frame_id, parsed_header.frame_id); + EXPECT_EQ(expected_header_->packet_id, parsed_header.packet_id); + EXPECT_EQ(expected_header_->max_packet_id, parsed_header.max_packet_id); + EXPECT_EQ(expected_header_->is_reference, parsed_header.is_reference); + } + + private: + scoped_ptr<RtpCastHeader> expected_header_; +}; + +class RtpParserTest : public ::testing::Test { + protected: + RtpParserTest() { + PopulateConfig(); + rtp_data_.reset(new RtpDataTest()); + rtp_parser_.reset(new RtpParser(rtp_data_.get(), config_)); + } + + ~RtpParserTest() {} + + virtual void SetUp() { + cast_header_.InitRTPVideoHeaderCast(); + cast_header_.is_reference = true; + cast_header_.reference_frame_id = kRefFrameId; + packet_builder_.SetSsrc(kTestSsrc); + packet_builder_.SetReferenceFrameId(kRefFrameId, true); + packet_builder_.SetSequenceNumber(kTestSeqNum); + packet_builder_.SetTimestamp(kTestTimestamp); + packet_builder_.SetPayloadType(kTestPayloadType); + packet_builder_.SetMarkerBit(true); // Only one packet. + } + + void PopulateConfig() { + config_.payload_type = kTestPayloadType; + config_.ssrc = kTestSsrc; + } + + scoped_ptr<RtpDataTest> rtp_data_; + RtpPacketBuilder packet_builder_; + scoped_ptr<RtpParser> rtp_parser_; + RtpParserConfig config_; + RtpCastHeader cast_header_; +}; + +TEST_F(RtpParserTest, ParseDefaultCastPacket) { + // Build generic data packet. + uint8 packet[kPacketLength]; + packet_builder_.BuildHeader(packet, kPacketLength); + // Parse packet as is. + RtpCastHeader rtp_header; + rtp_data_->SetExpectedHeader(cast_header_); + EXPECT_TRUE(rtp_parser_->ParsePacket(packet, kPacketLength, &rtp_header)); +} + +TEST_F(RtpParserTest, ParseNonDefaultCastPacket) { + // Build generic data packet. + uint8 packet[kPacketLength]; + packet_builder_.SetKeyFrame(true); + packet_builder_.SetFrameId(10); + packet_builder_.SetPacketId(5); + packet_builder_.SetMaxPacketId(15); + packet_builder_.SetMarkerBit(false); + packet_builder_.BuildHeader(packet, kPacketLength); + cast_header_.is_key_frame = true; + cast_header_.frame_id = 10; + cast_header_.packet_id = 5; + cast_header_.max_packet_id = 15; + rtp_data_->SetExpectedHeader(cast_header_); + // Parse packet as is. + RtpCastHeader rtp_header; + EXPECT_TRUE(rtp_parser_->ParsePacket(packet, kPacketLength, &rtp_header)); +} + +TEST_F(RtpParserTest, TooBigPacketId) { + // Build generic data packet. + uint8 packet[kPacketLength]; + packet_builder_.SetKeyFrame(true); + packet_builder_.SetFrameId(10); + packet_builder_.SetPacketId(15); + packet_builder_.SetMaxPacketId(5); + packet_builder_.BuildHeader(packet, kPacketLength); + // Parse packet as is. + RtpCastHeader rtp_header; + EXPECT_FALSE(rtp_parser_->ParsePacket(packet, kPacketLength, &rtp_header)); +} + +TEST_F(RtpParserTest, MaxPacketId) { + // Build generic data packet. + uint8 packet[kPacketLength]; + packet_builder_.SetKeyFrame(true); + packet_builder_.SetFrameId(10); + packet_builder_.SetPacketId(65535); + packet_builder_.SetMaxPacketId(65535); + packet_builder_.BuildHeader(packet, kPacketLength); + cast_header_.is_key_frame = true; + cast_header_.frame_id = 10; + cast_header_.packet_id = 65535; + cast_header_.max_packet_id = 65535; + rtp_data_->SetExpectedHeader(cast_header_); + // Parse packet as is. + RtpCastHeader rtp_header; + EXPECT_TRUE(rtp_parser_->ParsePacket(packet, kPacketLength, &rtp_header)); +} + +TEST_F(RtpParserTest, InvalidPayloadType) { + // Build generic data packet. + uint8 packet[kPacketLength]; + packet_builder_.SetKeyFrame(true); + packet_builder_.SetFrameId(10); + packet_builder_.SetPacketId(65535); + packet_builder_.SetMaxPacketId(65535); + packet_builder_.SetPayloadType(kTestPayloadType - 1); + packet_builder_.BuildHeader(packet, kPacketLength); + // Parse packet as is. + RtpCastHeader rtp_header; + EXPECT_FALSE(rtp_parser_->ParsePacket(packet, kPacketLength, &rtp_header)); +} + +TEST_F(RtpParserTest, InvalidSsrc) { + // Build generic data packet. + uint8 packet[kPacketLength]; + packet_builder_.SetKeyFrame(true); + packet_builder_.SetFrameId(10); + packet_builder_.SetPacketId(65535); + packet_builder_.SetMaxPacketId(65535); + packet_builder_.SetSsrc(kTestSsrc - 1); + packet_builder_.BuildHeader(packet, kPacketLength); + // Parse packet as is. + RtpCastHeader rtp_header; + EXPECT_FALSE(rtp_parser_->ParsePacket(packet, kPacketLength, &rtp_header)); +} + +TEST_F(RtpParserTest, ParseCastPacketWithoutReference) { + cast_header_.is_reference = false; + cast_header_.reference_frame_id = 0; + packet_builder_.SetReferenceFrameId(kRefFrameId, false); + + // Build generic data packet. + uint8 packet[kPacketLength]; + packet_builder_.BuildHeader(packet, kPacketLength); + // Parse packet as is. + RtpCastHeader rtp_header; + rtp_data_->SetExpectedHeader(cast_header_); + EXPECT_TRUE(rtp_parser_->ParsePacket(packet, kPacketLength, &rtp_header)); +} + +} // namespace cast +} // namespace media diff --git a/media/cast/rtp_receiver/rtp_parser/test/rtp_packet_builder.cc b/media/cast/rtp_receiver/rtp_parser/test/rtp_packet_builder.cc new file mode 100644 index 0000000..9f61d9b --- /dev/null +++ b/media/cast/rtp_receiver/rtp_parser/test/rtp_packet_builder.cc @@ -0,0 +1,104 @@ +// Copyright 2013 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 "media/cast/rtp_receiver/rtp_parser/test/rtp_packet_builder.h" + +#include "base/logging.h" +#include "net/base/big_endian.h" + +namespace media { +namespace cast { + +const int kCastRtpHeaderLength = 7; +const int kGenericRtpHeaderLength = 12; + +RtpPacketBuilder::RtpPacketBuilder() + : is_key_(false), + frame_id_(0), + packet_id_(0), + max_packet_id_(0), + reference_frame_id_(0), + is_reference_set_(false), + timestamp_(0), + sequence_number_(0), + marker_(false), + payload_type_(0), + ssrc_(0) {} + +void RtpPacketBuilder::SetKeyFrame(bool is_key) { + is_key_ = is_key; +} + +void RtpPacketBuilder::SetFrameId(uint8 frame_id) { + frame_id_ = frame_id; +} + +void RtpPacketBuilder::SetPacketId(uint16 packet_id) { + packet_id_ = packet_id; +} + +void RtpPacketBuilder::SetMaxPacketId(uint16 max_packet_id) { + max_packet_id_ = max_packet_id; +} + +void RtpPacketBuilder::SetReferenceFrameId(uint8 reference_frame_id, + bool is_set) { + is_reference_set_ = is_set; + if (is_set) + reference_frame_id_ = reference_frame_id; +} +void RtpPacketBuilder::SetTimestamp(uint32 timestamp) { + timestamp_ = timestamp; +} + +void RtpPacketBuilder::SetSequenceNumber(uint16 sequence_number) { + sequence_number_ = sequence_number; +} + +void RtpPacketBuilder::SetMarkerBit(bool marker) { + marker_ = marker; +} + +void RtpPacketBuilder::SetPayloadType(int payload_type) { + payload_type_ = payload_type; +} + +void RtpPacketBuilder::SetSsrc(uint32 ssrc) { + ssrc_ = ssrc; +} + +void RtpPacketBuilder::BuildHeader(uint8* data, uint32 data_length) { + BuildCommonHeader(data, data_length); + BuildCastHeader(data + kGenericRtpHeaderLength, + data_length - kGenericRtpHeaderLength); +} + +void RtpPacketBuilder::BuildCastHeader(uint8* data, uint32 data_length) { + // Build header. + DCHECK_LE(kCastRtpHeaderLength, data_length); + // Set the first 7 bytes to 0. + memset(data, 0, kCastRtpHeaderLength); + net::BigEndianWriter big_endian_writer(data, 56); + big_endian_writer.WriteU8( + (is_key_ ? 0x80 : 0) | (is_reference_set_ ? 0x40 : 0)); + big_endian_writer.WriteU8(frame_id_); + big_endian_writer.WriteU16(packet_id_); + big_endian_writer.WriteU16(max_packet_id_); + if (is_reference_set_) { + big_endian_writer.WriteU8(reference_frame_id_); + } +} + +void RtpPacketBuilder::BuildCommonHeader(uint8* data, uint32 data_length) { + DCHECK_LE(kGenericRtpHeaderLength, data_length); + net::BigEndianWriter big_endian_writer(data, 96); + big_endian_writer.WriteU8(0x80); + big_endian_writer.WriteU8(payload_type_ | (marker_ ? kRtpMarkerBitMask : 0)); + big_endian_writer.WriteU16(sequence_number_); + big_endian_writer.WriteU32(timestamp_); + big_endian_writer.WriteU32(ssrc_); +} + +} // namespace cast +} // namespace media diff --git a/media/cast/rtp_receiver/rtp_parser/test/rtp_packet_builder.h b/media/cast/rtp_receiver/rtp_parser/test/rtp_packet_builder.h new file mode 100644 index 0000000..70f520e --- /dev/null +++ b/media/cast/rtp_receiver/rtp_parser/test/rtp_packet_builder.h @@ -0,0 +1,51 @@ +// Copyright 2013 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. + +// Test helper class that builds rtp packets. + +#ifndef MEDIA_CAST_RTP_RECEIVER_RTP_PARSER_TEST_RTP_PACKET_BUILDER_H_ +#define MEDIA_CAST_RTP_RECEIVER_RTP_PARSER_TEST_RTP_PACKET_BUILDER_H_ + + +#include "media/cast/rtp_common/rtp_defines.h" + +namespace media { +namespace cast { + +class RtpPacketBuilder { + public: + RtpPacketBuilder(); + void SetKeyFrame(bool is_key); + void SetFrameId(uint8 frame_id); + void SetPacketId(uint16 packet_id); + void SetMaxPacketId(uint16 max_packet_id); + void SetReferenceFrameId(uint8 reference_frame_id, bool is_set); + void SetTimestamp(uint32 timestamp); + void SetSequenceNumber(uint16 sequence_number); + void SetMarkerBit(bool marker); + void SetPayloadType(int payload_type); + void SetSsrc(uint32 ssrc); + void BuildHeader(uint8* data, uint32 data_length); + + private: + bool is_key_; + uint8 frame_id_; + uint16 packet_id_; + uint16 max_packet_id_; + uint8 reference_frame_id_; + bool is_reference_set_; + uint32 timestamp_; + uint16 sequence_number_; + bool marker_; + int payload_type_; + uint32 ssrc_; + + void BuildCastHeader(uint8* data, uint32 data_length); + void BuildCommonHeader(uint8* data, uint32 data_length); +}; + +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_RTP_RECEIVER_RTP_PARSER_TEST_RTP_PACKET_BUILDER_H_ diff --git a/media/cast/rtp_receiver/rtp_receiver.cc b/media/cast/rtp_receiver/rtp_receiver.cc new file mode 100644 index 0000000..97e9b03 --- /dev/null +++ b/media/cast/rtp_receiver/rtp_receiver.cc @@ -0,0 +1,57 @@ +// Copyright 2013 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 "media/cast/rtp_receiver/rtp_receiver.h" + +#include "base/logging.h" +#include "media/cast/rtp_common/rtp_defines.h" +#include "media/cast/rtp_receiver/receiver_stats.h" +#include "media/cast/rtp_receiver/rtp_parser/rtp_parser.h" + +namespace media { +namespace cast { + +RtpReceiver::RtpReceiver(const AudioReceiverConfig* audio_config, + const VideoReceiverConfig* video_config, + RtpData* incoming_payload_callback) { + DCHECK(incoming_payload_callback) << "Invalid argument"; + DCHECK(audio_config || video_config) << "Invalid argument"; + // Configure parser. + RtpParserConfig config; + if (audio_config) { + config.ssrc = audio_config->incoming_ssrc; + config.payload_type = audio_config->rtp_payload_type; + config.audio_codec = audio_config->codec; + config.audio_channels = audio_config->channels; + } else { + config.ssrc = video_config->incoming_ssrc; + config.payload_type = video_config->rtp_payload_type; + config.video_codec = video_config->codec; + } + stats_.reset(new ReceiverStats(config.ssrc)); + parser_.reset(new RtpParser(incoming_payload_callback, config)); +} + +RtpReceiver::~RtpReceiver() {} + +bool RtpReceiver::ReceivedPacket(const uint8* packet, int length) { + RtpCastHeader rtp_header; + if (!parser_->ParsePacket(packet, length, &rtp_header)) return false; + + stats_->UpdateStatistics(rtp_header); + return true; +} + +void RtpReceiver::GetStatistics(uint8* fraction_lost, + uint32* cumulative_lost, + uint32* extended_high_sequence_number, + uint32* jitter) { + stats_->GetStatistics(fraction_lost, + cumulative_lost, + extended_high_sequence_number, + jitter); +} + +} // namespace cast +} // namespace media diff --git a/media/cast/rtp_receiver/rtp_receiver.gyp b/media/cast/rtp_receiver/rtp_receiver.gyp new file mode 100644 index 0000000..c1d4d5a --- /dev/null +++ b/media/cast/rtp_receiver/rtp_receiver.gyp @@ -0,0 +1,27 @@ +# Copyright 2013 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. + +{ + 'targets': [ + { + 'target_name': 'cast_rtp_receiver', + 'type': 'static_library', + 'include_dirs': [ + '<(DEPTH)/', + '<(DEPTH)/third_party/', + ], + 'sources': [ + 'receiver_stats.cc', + 'receiver_stats.h', + 'rtp_receiver.cc', + 'rtp_receiver.h', + ], # source + 'dependencies': [ + '<(DEPTH)/base/base.gyp:base', + '<(DEPTH)/base/base.gyp:test_support_base', + 'rtp_parser/rtp_parser.gypi:*', + ], + }, + ], +} diff --git a/media/cast/rtp_receiver/rtp_receiver.h b/media/cast/rtp_receiver/rtp_receiver.h new file mode 100644 index 0000000..6cac8ca --- /dev/null +++ b/media/cast/rtp_receiver/rtp_receiver.h @@ -0,0 +1,53 @@ +// Copyright 2013 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. + +// Interface to the rtp receiver. + +#ifndef MEDIA_CAST_RTP_RECEIVER_RTP_RECEIVER_H_ +#define MEDIA_CAST_RTP_RECEIVER_RTP_RECEIVER_H_ + +#include "base/memory/scoped_ptr.h" +#include "media/cast/cast_config.h" +#include "media/cast/rtcp/rtcp.h" +#include "media/cast/rtp_common/rtp_defines.h" + +namespace media { +namespace cast { + +class RtpData { + public: + virtual void OnReceivedPayloadData(const uint8* payload_data, + int payload_size, + const RtpCastHeader* rtp_header) = 0; + + protected: + virtual ~RtpData() {} +}; + +class ReceiverStats; +class RtpParser; + +class RtpReceiver { + public: + RtpReceiver(const AudioReceiverConfig* audio_config, + const VideoReceiverConfig* video_config, + RtpData* incoming_payload_callback); + ~RtpReceiver(); + + bool ReceivedPacket(const uint8* packet, int length); + + void GetStatistics(uint8* fraction_lost, + uint32* cumulative_lost, // 24 bits valid. + uint32* extended_high_sequence_number, + uint32* jitter); + + private: + scoped_ptr<ReceiverStats> stats_; + scoped_ptr<RtpParser> parser_; +}; + +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_RTP_RECEIVER_RTP_RECEIVER_H_ |