diff --git a/webrtc/modules/audio_coding/neteq4/neteq.gypi b/webrtc/modules/audio_coding/neteq4/neteq.gypi index 3e7ede4e66..b041694143 100644 --- a/webrtc/modules/audio_coding/neteq4/neteq.gypi +++ b/webrtc/modules/audio_coding/neteq4/neteq.gypi @@ -171,6 +171,7 @@ '<(DEPTH)/testing/gmock.gyp:gmock', '<(DEPTH)/testing/gtest.gyp:gtest', 'PCM16B', # Needed by neteq_performance_test. + 'rtp_rtcp', ], 'direct_dependent_settings': { 'include_dirs': [ @@ -187,6 +188,11 @@ 'tools/input_audio_file.h', 'tools/neteq_performance_test.cc', 'tools/neteq_performance_test.h', + 'tools/packet.cc', + 'tools/packet.h', + 'tools/packet_source.h', + 'tools/rtp_file_source.cc', + 'tools/rtp_file_source.h', 'tools/rtp_generator.cc', 'tools/rtp_generator.h', 'tools/neteq_quality_test.cc', diff --git a/webrtc/modules/audio_coding/neteq4/neteq_tests.gypi b/webrtc/modules/audio_coding/neteq4/neteq_tests.gypi index dda2a1ce8c..9751a1ce3a 100644 --- a/webrtc/modules/audio_coding/neteq4/neteq_tests.gypi +++ b/webrtc/modules/audio_coding/neteq4/neteq_tests.gypi @@ -85,7 +85,7 @@ 'target_name': 'rtp_analyze', 'type': 'executable', 'dependencies': [ - 'NetEq4TestTools', + 'neteq_unittest_tools', '<(DEPTH)/testing/gtest.gyp:gtest', '<(DEPTH)/third_party/gflags/gflags.gyp:gflags', ], diff --git a/webrtc/modules/audio_coding/neteq4/tools/packet.cc b/webrtc/modules/audio_coding/neteq4/tools/packet.cc new file mode 100644 index 0000000000..6e0778b387 --- /dev/null +++ b/webrtc/modules/audio_coding/neteq4/tools/packet.cc @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/audio_coding/neteq4/tools/packet.h" +#include "webrtc/modules/rtp_rtcp/interface/rtp_header_parser.h" + +namespace webrtc { +namespace test { + +Packet::Packet(uint8_t* packet_memory, + size_t allocated_bytes, + double time_ms, + const RtpHeaderParser& parser) + : payload_memory_(packet_memory), + payload_(NULL), + packet_length_bytes_(allocated_bytes), + payload_length_bytes_(0), + virtual_packet_length_bytes_(allocated_bytes), + virtual_payload_length_bytes_(0), + time_ms_(time_ms) { + valid_header_ = ParseHeader(parser); +} + +Packet::Packet(uint8_t* packet_memory, + size_t allocated_bytes, + size_t virtual_packet_length_bytes, + double time_ms, + const RtpHeaderParser& parser) + : payload_memory_(packet_memory), + payload_(NULL), + packet_length_bytes_(allocated_bytes), + payload_length_bytes_(0), + virtual_packet_length_bytes_(virtual_packet_length_bytes), + virtual_payload_length_bytes_(0), + time_ms_(time_ms) { + valid_header_ = ParseHeader(parser); +} + +Packet::Packet(uint8_t* packet_memory, size_t allocated_bytes, double time_ms) + : payload_memory_(packet_memory), + payload_(NULL), + packet_length_bytes_(allocated_bytes), + payload_length_bytes_(0), + virtual_packet_length_bytes_(allocated_bytes), + virtual_payload_length_bytes_(0), + time_ms_(time_ms) { + scoped_ptr parser(RtpHeaderParser::Create()); + valid_header_ = ParseHeader(*parser); +} + +Packet::Packet(uint8_t* packet_memory, + size_t allocated_bytes, + size_t virtual_packet_length_bytes, + double time_ms) + : payload_memory_(packet_memory), + payload_(NULL), + packet_length_bytes_(allocated_bytes), + payload_length_bytes_(0), + virtual_packet_length_bytes_(virtual_packet_length_bytes), + virtual_payload_length_bytes_(0), + time_ms_(time_ms) { + scoped_ptr parser(RtpHeaderParser::Create()); + valid_header_ = ParseHeader(*parser); +} + +bool Packet::ExtractRedHeaders(std::list* headers) const { + // + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // |1| block PT | timestamp offset | block length | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // |1| ... | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // |0| block PT | + // +-+-+-+-+-+-+-+-+ + // + + assert(payload_); + const uint8_t* payload_ptr = payload_; + const uint8_t* payload_end_ptr = payload_ptr + payload_length_bytes_; + + // Find all RED headers with the extension bit set to 1. That is, all headers + // but the last one. + while ((payload_ptr < payload_end_ptr) && (*payload_ptr & 0x80)) { + RTPHeader* header = new RTPHeader; + CopyToHeader(header); + header->payloadType = payload_ptr[0] & 0x7F; + uint32_t offset = (payload_ptr[1] << 6) + ((payload_ptr[2] & 0xFC) >> 2); + header->timestamp -= offset; + headers->push_front(header); + payload_ptr += 4; + } + // Last header. + assert(payload_ptr < payload_end_ptr); + if (payload_ptr >= payload_end_ptr) { + return false; // Payload too short. + } + RTPHeader* header = new RTPHeader; + CopyToHeader(header); + header->payloadType = payload_ptr[0] & 0x7F; + headers->push_front(header); + return true; +} + +void Packet::DeleteRedHeaders(std::list* headers) { + while (!headers->empty()) { + delete headers->front(); + headers->pop_front(); + } +} + +bool Packet::ParseHeader(const RtpHeaderParser& parser) { + bool valid_header = parser.Parse( + payload_memory_.get(), static_cast(packet_length_bytes_), &header_); + assert(valid_header); + if (!valid_header) { + return false; + } + assert(header_.headerLength <= packet_length_bytes_); + payload_ = &payload_memory_[header_.headerLength]; + assert(packet_length_bytes_ >= header_.headerLength); + payload_length_bytes_ = packet_length_bytes_ - header_.headerLength; + assert(virtual_packet_length_bytes_ >= header_.headerLength); + virtual_payload_length_bytes_ = + virtual_packet_length_bytes_ - header_.headerLength; + return true; +} + +void Packet::CopyToHeader(RTPHeader* destination) const { + destination->markerBit = header_.markerBit; + destination->payloadType = header_.payloadType; + destination->sequenceNumber = header_.sequenceNumber; + destination->timestamp = header_.timestamp; + destination->ssrc = header_.ssrc; + destination->numCSRCs = header_.numCSRCs; + destination->paddingLength = header_.paddingLength; + destination->headerLength = header_.headerLength; + destination->payload_type_frequency = header_.payload_type_frequency; + memcpy(&destination->arrOfCSRCs, + &header_.arrOfCSRCs, + sizeof(header_.arrOfCSRCs)); + memcpy( + &destination->extension, &header_.extension, sizeof(header_.extension)); +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/modules/audio_coding/neteq4/tools/packet.h b/webrtc/modules/audio_coding/neteq4/tools/packet.h new file mode 100644 index 0000000000..af549ffed7 --- /dev/null +++ b/webrtc/modules/audio_coding/neteq4/tools/packet.h @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_TOOLS_PACKET_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_TOOLS_PACKET_H_ + +#include + +#include "webrtc/common_types.h" +#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/system_wrappers/interface/scoped_ptr.h" +#include "webrtc/typedefs.h" + +namespace webrtc { + +class RtpHeaderParser; + +namespace test { + +// Class for handling RTP packets in test applications. +class Packet { + public: + // Creates a packet, with the packet payload (including header bytes) in + // |packet_memory|. The length of |packet_memory| is |allocated_bytes|. + // The new object assumes ownership of |packet_memory| and will delete it + // when the Packet object is deleted. The |time_ms| is an extra time + // associated with this packet, typically used to denote arrival time. + // The first bytes in |packet_memory| will be parsed using |parser|. + Packet(uint8_t* packet_memory, + size_t allocated_bytes, + double time_ms, + const RtpHeaderParser& parser); + + // Same as above, but with the extra argument |virtual_packet_length_bytes|. + // This is typically used when reading RTP dump files that only contain the + // RTP headers, and no payload (a.k.a RTP dummy files or RTP light). The + // |virtual_packet_length_bytes| tells what size the packet had on wire, + // including the now discarded payload, whereas |allocated_bytes| is the + // length of the remaining payload (typically only the RTP header). + Packet(uint8_t* packet_memory, + size_t allocated_bytes, + size_t virtual_packet_length_bytes, + double time_ms, + const RtpHeaderParser& parser); + + // The following two constructors are the same as above, but without a + // parser. Note that when the object is constructed using any of these + // methods, the header will be parsed using a default RtpHeaderParser object. + // In particular, RTP header extensions won't be parsed. + Packet(uint8_t* packet_memory, size_t allocated_bytes, double time_ms); + + Packet(uint8_t* packet_memory, + size_t allocated_bytes, + size_t virtual_packet_length_bytes, + double time_ms); + + virtual ~Packet() {} + + // Parses the first bytes of the RTP payload, interpreting them as RED headers + // according to RFC 2198. The headers will be inserted into |headers|. The + // caller of the method assumes ownership of the objects in the list, and + // must delete them properly. + bool ExtractRedHeaders(std::list* headers) const; + + // Deletes all RTPHeader objects in |headers|, but does not delete |headers| + // itself. + static void DeleteRedHeaders(std::list* headers); + + const uint8_t* payload() const { return payload_; } + + size_t packet_length_bytes() const { return packet_length_bytes_; } + + size_t payload_length_bytes() const { return payload_length_bytes_; } + + size_t virtual_packet_length_bytes() const { + return virtual_packet_length_bytes_; + } + + size_t virtual_payload_length_bytes() const { + return virtual_payload_length_bytes_; + } + + const RTPHeader& header() const { return header_; } + + void set_time_ms(double time) { time_ms_ = time; } + double time_ms() const { return time_ms_; } + bool valid_header() const { return valid_header_; } + + private: + bool ParseHeader(const RtpHeaderParser& parser); + void CopyToHeader(RTPHeader* destination) const; + + RTPHeader header_; + scoped_ptr payload_memory_; + const uint8_t* payload_; // First byte after header. + const size_t packet_length_bytes_; // Total length of packet. + size_t payload_length_bytes_; // Length of the payload, after RTP header. + // Zero for dummy RTP packets. + // Virtual lengths are used when parsing RTP header files (dummy RTP files). + const size_t virtual_packet_length_bytes_; + size_t virtual_payload_length_bytes_; + double time_ms_; // Used to denote a packet's arrival time. + bool valid_header_; // Set by the RtpHeaderParser. + + DISALLOW_COPY_AND_ASSIGN(Packet); +}; + +} // namespace test +} // namespace webrtc +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_TOOLS_PACKET_H_ diff --git a/webrtc/modules/audio_coding/neteq4/tools/packet_source.h b/webrtc/modules/audio_coding/neteq4/tools/packet_source.h new file mode 100644 index 0000000000..1e1997f5a9 --- /dev/null +++ b/webrtc/modules/audio_coding/neteq4/tools/packet_source.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_TOOLS_PACKET_SOURCE_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_TOOLS_PACKET_SOURCE_H_ + +#include "webrtc/system_wrappers/interface/constructor_magic.h" + +namespace webrtc { +namespace test { + +class Packet; + +// Interface class for an object delivering RTP packets to test applications. +class PacketSource { + public: + PacketSource() {} + virtual ~PacketSource() {} + + // Returns a pointer to the next packet. + virtual Packet* NextPacket() = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(PacketSource); +}; + +} // namespace test +} // namespace webrtc +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_TOOLS_PACKET_SOURCE_H_ diff --git a/webrtc/modules/audio_coding/neteq4/tools/packet_unittest.cc b/webrtc/modules/audio_coding/neteq4/tools/packet_unittest.cc new file mode 100644 index 0000000000..b6c0713d1f --- /dev/null +++ b/webrtc/modules/audio_coding/neteq4/tools/packet_unittest.cc @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// Unit tests for test Packet class. + +#include "webrtc/modules/audio_coding/neteq4/tools/packet.h" + +#include "gtest/gtest.h" + +namespace webrtc { +namespace test { + +namespace { +const int kHeaderLengthBytes = 12; + +void MakeRtpHeader(int payload_type, + int seq_number, + uint32_t timestamp, + uint32_t ssrc, + uint8_t* rtp_data) { + rtp_data[0] = 0x80; + rtp_data[1] = payload_type & 0xFF; + rtp_data[2] = (seq_number >> 8) & 0xFF; + rtp_data[3] = (seq_number) & 0xFF; + rtp_data[4] = (timestamp >> 24) & 0xFF; + rtp_data[5] = (timestamp >> 16) & 0xFF; + rtp_data[6] = (timestamp >> 8) & 0xFF; + rtp_data[7] = timestamp & 0xFF; + rtp_data[8] = (ssrc >> 24) & 0xFF; + rtp_data[9] = (ssrc >> 16) & 0xFF; + rtp_data[10] = (ssrc >> 8) & 0xFF; + rtp_data[11] = ssrc & 0xFF; +} +} // namespace + +TEST(TestPacket, RegularPacket) { + const size_t kPacketLengthBytes = 100; + uint8_t* packet_memory = new uint8_t[kPacketLengthBytes]; + const uint8_t kPayloadType = 17; + const uint16_t kSequenceNumber = 4711; + const uint32_t kTimestamp = 47114711; + const uint32_t kSsrc = 0x12345678; + MakeRtpHeader( + kPayloadType, kSequenceNumber, kTimestamp, kSsrc, packet_memory); + const double kPacketTime = 1.0; + // Hand over ownership of |packet_memory| to |packet|. + Packet packet(packet_memory, kPacketLengthBytes, kPacketTime); + ASSERT_TRUE(packet.valid_header()); + EXPECT_EQ(kPayloadType, packet.header().payloadType); + EXPECT_EQ(kSequenceNumber, packet.header().sequenceNumber); + EXPECT_EQ(kTimestamp, packet.header().timestamp); + EXPECT_EQ(kSsrc, packet.header().ssrc); + EXPECT_EQ(0, packet.header().numCSRCs); + EXPECT_EQ(kPacketLengthBytes, packet.packet_length_bytes()); + EXPECT_EQ(kPacketLengthBytes - kHeaderLengthBytes, + packet.payload_length_bytes()); + EXPECT_EQ(kPacketLengthBytes, packet.virtual_packet_length_bytes()); + EXPECT_EQ(kPacketLengthBytes - kHeaderLengthBytes, + packet.virtual_payload_length_bytes()); + EXPECT_EQ(kPacketTime, packet.time_ms()); +} + +TEST(TestPacket, DummyPacket) { + const size_t kPacketLengthBytes = kHeaderLengthBytes; // Only RTP header. + const size_t kVirtualPacketLengthBytes = 100; + uint8_t* packet_memory = new uint8_t[kPacketLengthBytes]; + const uint8_t kPayloadType = 17; + const uint16_t kSequenceNumber = 4711; + const uint32_t kTimestamp = 47114711; + const uint32_t kSsrc = 0x12345678; + MakeRtpHeader( + kPayloadType, kSequenceNumber, kTimestamp, kSsrc, packet_memory); + const double kPacketTime = 1.0; + // Hand over ownership of |packet_memory| to |packet|. + Packet packet(packet_memory, + kPacketLengthBytes, + kVirtualPacketLengthBytes, + kPacketTime); + ASSERT_TRUE(packet.valid_header()); + EXPECT_EQ(kPayloadType, packet.header().payloadType); + EXPECT_EQ(kSequenceNumber, packet.header().sequenceNumber); + EXPECT_EQ(kTimestamp, packet.header().timestamp); + EXPECT_EQ(kSsrc, packet.header().ssrc); + EXPECT_EQ(0, packet.header().numCSRCs); + EXPECT_EQ(kPacketLengthBytes, packet.packet_length_bytes()); + EXPECT_EQ(kPacketLengthBytes - kHeaderLengthBytes, + packet.payload_length_bytes()); + EXPECT_EQ(kVirtualPacketLengthBytes, packet.virtual_packet_length_bytes()); + EXPECT_EQ(kVirtualPacketLengthBytes - kHeaderLengthBytes, + packet.virtual_payload_length_bytes()); + EXPECT_EQ(kPacketTime, packet.time_ms()); +} + +namespace { +// Writes one RED block header starting at |rtp_data|, according to RFC 2198. +// returns the number of bytes written (1 or 4). +// +// Format if |last_payoad| is false: +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |1| block PT | timestamp offset | block length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// Format if |last_payoad| is true: +// 0 1 2 3 4 5 6 7 +// +-+-+-+-+-+-+-+-+ +// |0| Block PT | +// +-+-+-+-+-+-+-+-+ + +int MakeRedHeader(int payload_type, + uint32_t timestamp_offset, + int block_length, + bool last_payload, + uint8_t* rtp_data) { + rtp_data[0] = 0x80 | (payload_type & 0x7F); // Set the first bit to 1. + if (last_payload) { + rtp_data[0] &= 0x7F; // Reset the first but to 0 to indicate last block. + return 1; + } + rtp_data[1] = timestamp_offset >> 6; + rtp_data[2] = (timestamp_offset & 0x3F) << 2; + rtp_data[2] |= block_length >> 8; + rtp_data[3] = block_length & 0xFF; + return 4; +} +} // namespace + +TEST(TestPacket, RED) { + const size_t kPacketLengthBytes = 100; + uint8_t* packet_memory = new uint8_t[kPacketLengthBytes]; + const uint8_t kRedPayloadType = 17; + const uint16_t kSequenceNumber = 4711; + const uint32_t kTimestamp = 47114711; + const uint32_t kSsrc = 0x12345678; + MakeRtpHeader( + kRedPayloadType, kSequenceNumber, kTimestamp, kSsrc, packet_memory); + // Create four RED headers. + // Payload types are just the same as the block index the offset is 100 times + // the block index. + const int kRedBlocks = 4; + uint8_t* payload_ptr = + &packet_memory[kHeaderLengthBytes]; // First byte after header. + for (int i = 0; i < kRedBlocks; ++i) { + int payload_type = i; + // Offset value is not used for the last block. + uint32_t timestamp_offset = 100 * i; + int block_length = 10 * i; + bool last_block = (i == kRedBlocks - 1) ? true : false; + payload_ptr += MakeRedHeader( + payload_type, timestamp_offset, block_length, last_block, payload_ptr); + } + const double kPacketTime = 1.0; + // Hand over ownership of |packet_memory| to |packet|. + Packet packet(packet_memory, kPacketLengthBytes, kPacketTime); + ASSERT_TRUE(packet.valid_header()); + EXPECT_EQ(kRedPayloadType, packet.header().payloadType); + EXPECT_EQ(kSequenceNumber, packet.header().sequenceNumber); + EXPECT_EQ(kTimestamp, packet.header().timestamp); + EXPECT_EQ(kSsrc, packet.header().ssrc); + EXPECT_EQ(0, packet.header().numCSRCs); + EXPECT_EQ(kPacketLengthBytes, packet.packet_length_bytes()); + EXPECT_EQ(kPacketLengthBytes - kHeaderLengthBytes, + packet.payload_length_bytes()); + EXPECT_EQ(kPacketLengthBytes, packet.virtual_packet_length_bytes()); + EXPECT_EQ(kPacketLengthBytes - kHeaderLengthBytes, + packet.virtual_payload_length_bytes()); + EXPECT_EQ(kPacketTime, packet.time_ms()); + std::list red_headers; + EXPECT_TRUE(packet.ExtractRedHeaders(&red_headers)); + EXPECT_EQ(kRedBlocks, static_cast(red_headers.size())); + int block_index = 0; + for (std::list::reverse_iterator it = red_headers.rbegin(); + it != red_headers.rend(); + ++it) { + // Reading list from the back, since the extraction puts the main payload + // (which is the last one on wire) first. + RTPHeader* red_block = *it; + EXPECT_EQ(block_index, red_block->payloadType); + EXPECT_EQ(kSequenceNumber, red_block->sequenceNumber); + if (block_index == kRedBlocks - 1) { + // Last block has zero offset per definition. + EXPECT_EQ(kTimestamp, red_block->timestamp); + } else { + EXPECT_EQ(kTimestamp - 100 * block_index, red_block->timestamp); + } + EXPECT_EQ(kSsrc, red_block->ssrc); + EXPECT_EQ(0, red_block->numCSRCs); + ++block_index; + } + Packet::DeleteRedHeaders(&red_headers); +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/modules/audio_coding/neteq4/tools/rtp_analyze.cc b/webrtc/modules/audio_coding/neteq4/tools/rtp_analyze.cc index 63786ec56c..71a6c3c6c3 100644 --- a/webrtc/modules/audio_coding/neteq4/tools/rtp_analyze.cc +++ b/webrtc/modules/audio_coding/neteq4/tools/rtp_analyze.cc @@ -13,8 +13,9 @@ #include #include "google/gflags.h" -#include "webrtc/modules/audio_coding/neteq4/test/NETEQTEST_DummyRTPpacket.h" -#include "webrtc/modules/audio_coding/neteq4/test/NETEQTEST_RTPpacket.h" +#include "webrtc/modules/audio_coding/neteq4/tools/packet.h" +#include "webrtc/modules/audio_coding/neteq4/tools/rtp_file_source.h" +#include "webrtc/system_wrappers/interface/scoped_ptr.h" // Flag validator. static bool ValidatePayloadType(const char* flagname, int32_t value) { @@ -23,11 +24,20 @@ static bool ValidatePayloadType(const char* flagname, int32_t value) { printf("Invalid value for --%s: %d\n", flagname, static_cast(value)); return false; } +static bool ValidateExtensionId(const char* flagname, int32_t value) { + if (value > 0 && value <= 255) // Value is ok. + return true; + printf("Invalid value for --%s: %d\n", flagname, static_cast(value)); + return false; +} // Define command line flags. DEFINE_int32(red, 117, "RTP payload type for RED"); -static const bool pcmu_dummy = +static const bool red_dummy = google::RegisterFlagValidator(&FLAGS_red, &ValidatePayloadType); +DEFINE_int32(audio_level, 1, "Extension ID for audio level (RFC 6464)"); +static const bool audio_level_dummy = + google::RegisterFlagValidator(&FLAGS_audio_level, &ValidateExtensionId); int main(int argc, char* argv[]) { std::string program_name = argv[0]; @@ -55,6 +65,16 @@ int main(int argc, char* argv[]) { return -1; } printf("Input file: %s\n", argv[1]); + webrtc::scoped_ptr file_source( + webrtc::test::RtpFileSource::Create(argv[1])); + assert(file_source.get()); + // Set RTP extension ID. + bool print_audio_level = false; + if (!google::GetCommandLineFlagInfoOrDie("audio_level").is_default) { + print_audio_level = true; + file_source->RegisterRtpHeaderExtension(webrtc::kRtpExtensionAudioLevel, + FLAGS_audio_level); + } FILE* out_file; if (argc == 3) { @@ -69,37 +89,54 @@ int main(int argc, char* argv[]) { } // Print file header. - fprintf(out_file, "SeqNo TimeStamp SendTime Size PT M SSRC\n"); + fprintf(out_file, "SeqNo TimeStamp SendTime Size PT M SSRC"); + if (print_audio_level) { + fprintf(out_file, " AuLvl (V)"); + } + fprintf(out_file, "\n"); - // Read file header. - NETEQTEST_RTPpacket::skipFileHeader(in_file); - NETEQTEST_RTPpacket packet; - - while (packet.readFromFile(in_file) >= 0) { + webrtc::scoped_ptr packet; + while (!file_source->EndOfFile()) { + packet.reset(file_source->NextPacket()); + if (!packet.get()) { + // This is probably an RTCP packet. Move on to the next one. + continue; + } + assert(packet.get()); // Write packet data to file. fprintf(out_file, - "%5u %10u %10u %5i %5i %2i %#08X\n", - packet.sequenceNumber(), - packet.timeStamp(), - packet.time(), - packet.dataLen(), - packet.payloadType(), - packet.markerBit(), - packet.SSRC()); - if (packet.payloadType() == FLAGS_red) { - webrtc::WebRtcRTPHeader red_header; - int len; - int red_index = 0; - while ((len = packet.extractRED(red_index++, red_header)) >= 0) { + "%5u %10u %10u %5i %5i %2i %#08X", + packet->header().sequenceNumber, + packet->header().timestamp, + static_cast(packet->time_ms()), + static_cast(packet->packet_length_bytes()), + packet->header().payloadType, + packet->header().markerBit, + packet->header().ssrc); + if (print_audio_level && packet->header().extension.hasAudioLevel) { + // |audioLevel| consists of one bit for "V" and then 7 bits level. + fprintf(out_file, + " %5u (%1i)", + packet->header().extension.audioLevel & 0x7F, + (packet->header().extension.audioLevel & 0x80) == 0 ? 0 : 1); + } + fprintf(out_file, "\n"); + + if (packet->header().payloadType == FLAGS_red) { + std::list red_headers; + packet->ExtractRedHeaders(&red_headers); + while (!red_headers.empty()) { + webrtc::RTPHeader* red = red_headers.front(); + assert(red); fprintf(out_file, - "* %5u %10u %10u %5i %5i\n", - red_header.header.sequenceNumber, - red_header.header.timestamp, - packet.time(), - len, - red_header.header.payloadType); + "* %5u %10u %10u %5i\n", + red->sequenceNumber, + red->timestamp, + static_cast(packet->time_ms()), + red->payloadType); + red_headers.pop_front(); + delete red; } - assert(red_index > 1); // We must get at least one payload. } } diff --git a/webrtc/modules/audio_coding/neteq4/tools/rtp_file_source.cc b/webrtc/modules/audio_coding/neteq4/tools/rtp_file_source.cc new file mode 100644 index 0000000000..ceedde01c6 --- /dev/null +++ b/webrtc/modules/audio_coding/neteq4/tools/rtp_file_source.cc @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/audio_coding/neteq4/tools/rtp_file_source.h" + +#include +#include +#ifdef WIN32 +#include +#else +#include +#endif + +#include "webrtc/modules/audio_coding/neteq4/tools/packet.h" +#include "webrtc/modules/rtp_rtcp/interface/rtp_header_parser.h" + +namespace webrtc { +namespace test { + +RtpFileSource* RtpFileSource::Create(const std::string& file_name) { + RtpFileSource* source = new RtpFileSource; + assert(source); + if (!source->OpenFile(file_name) || !source->SkipFileHeader()) { + assert(false); + delete source; + return NULL; + } + return source; +} + +RtpFileSource::~RtpFileSource() { + if (in_file_) + fclose(in_file_); +} + +bool RtpFileSource::RegisterRtpHeaderExtension(RTPExtensionType type, + uint8_t id) { + assert(parser_.get()); + return parser_->RegisterRtpHeaderExtension(type, id); +} + +Packet* RtpFileSource::NextPacket() { + uint16_t length; + if (fread(&length, sizeof(uint16_t), 1, in_file_) == 0) { + assert(false); + return NULL; + } + length = ntohs(length); + + uint16_t plen; + if (fread(&plen, sizeof(uint16_t), 1, in_file_) == 0) { + assert(false); + return NULL; + } + plen = ntohs(plen); + + uint32_t offset; + if (fread(&offset, sizeof(uint32_t), 1, in_file_) == 0) { + assert(false); + return NULL; + } + + // Use length here because a plen of 0 specifies RTCP. + size_t packet_size_bytes = length - kPacketHeaderSize; + if (packet_size_bytes <= 0) { + // May be an RTCP packet. + return NULL; + } + uint8_t* packet_memory = new uint8_t[packet_size_bytes]; + if (fread(packet_memory, 1, packet_size_bytes, in_file_) != + packet_size_bytes) { + assert(false); + delete[] packet_memory; + return NULL; + } + Packet* packet = new Packet( + packet_memory, packet_size_bytes, plen, ntohl(offset), *parser_.get()); + if (!packet->valid_header()) { + assert(false); + delete packet; + return NULL; + } + return packet; +} + +bool RtpFileSource::EndOfFile() const { + assert(in_file_); + return ftell(in_file_) >= file_end_; +} + +RtpFileSource::RtpFileSource() + : PacketSource(), + in_file_(NULL), + file_end_(-1), + parser_(RtpHeaderParser::Create()) {} + +bool RtpFileSource::OpenFile(const std::string& file_name) { + in_file_ = fopen(file_name.c_str(), "rb"); + assert(in_file_); + if (in_file_ == NULL) { + return false; + } + + // Find out how long the file is. + fseek(in_file_, 0, SEEK_END); + file_end_ = ftell(in_file_); + rewind(in_file_); + return true; +} + +bool RtpFileSource::SkipFileHeader() { + char firstline[kFirstLineLength]; + assert(in_file_); + if (fgets(firstline, kFirstLineLength, in_file_) == NULL) { + assert(false); + return false; + } + // Check that the first line is ok. + if ((strncmp(firstline, "#!rtpplay1.0", 12) != 0) && + (strncmp(firstline, "#!RTPencode1.0", 14) != 0)) { + assert(false); + return false; + } + // Skip the file header. + if (fseek(in_file_, kRtpFileHeaderSize, SEEK_CUR) != 0) { + assert(false); + return false; + } + return true; +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/modules/audio_coding/neteq4/tools/rtp_file_source.h b/webrtc/modules/audio_coding/neteq4/tools/rtp_file_source.h new file mode 100644 index 0000000000..6a0be2a2d8 --- /dev/null +++ b/webrtc/modules/audio_coding/neteq4/tools/rtp_file_source.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ4_TOOLS_RTP_FILE_SOURCE_H_ +#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_TOOLS_RTP_FILE_SOURCE_H_ + +#include +#include + +#include "webrtc/common_types.h" +#include "webrtc/modules/audio_coding/neteq4/tools/packet_source.h" +#include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h" +#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/system_wrappers/interface/scoped_ptr.h" + +namespace webrtc { + +class RtpHeaderParser; + +namespace test { + +class RtpFileSource : public PacketSource { + public: + // Creates an RtpFileSource reading from |file_name|. If the file cannot be + // opened, or has the wrong format, NULL will be returned. + static RtpFileSource* Create(const std::string& file_name); + + virtual ~RtpFileSource(); + + // Registers an RTP header extension and binds it to |id|. + virtual bool RegisterRtpHeaderExtension(RTPExtensionType type, uint8_t id); + + // Returns a pointer to the next packet. + virtual Packet* NextPacket(); + + // Returns true if the end of file has been reached. + virtual bool EndOfFile() const; + + private: + static const int kFirstLineLength = 40; + static const int kRtpFileHeaderSize = 4 + 4 + 4 + 2 + 2; + static const size_t kPacketHeaderSize = 8; + + RtpFileSource(); + + bool OpenFile(const std::string& file_name); + + bool SkipFileHeader(); + + FILE* in_file_; + int64_t file_end_; + scoped_ptr parser_; + + DISALLOW_COPY_AND_ASSIGN(RtpFileSource); +}; + +} // namespace test +} // namespace webrtc +#endif // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_TOOLS_RTP_FILE_SOURCE_H_ diff --git a/webrtc/modules/modules.gyp b/webrtc/modules/modules.gyp index 096c04e1ed..dafe6bde8b 100644 --- a/webrtc/modules/modules.gyp +++ b/webrtc/modules/modules.gyp @@ -152,6 +152,7 @@ 'audio_coding/neteq4/mock/mock_external_decoder_pcm16b.h', 'audio_coding/neteq4/mock/mock_packet_buffer.h', 'audio_coding/neteq4/mock/mock_payload_splitter.h', + 'audio_coding/neteq4/tools/packet_unittest.cc', 'audio_processing/aec/system_delay_unittest.cc', 'audio_processing/aec/echo_cancellation_unittest.cc', 'audio_processing/echo_cancellation_impl_unittest.cc',