RTP video playback tool using Call APIs.

Plays back rtpdump files from Wireshark in realtime as well as save the
resulting raw video to file. Unlike the RTP playback tool it doesn't
support faster-than-realtime playback/rendering, but it instead utilizes
the same path as production code and also contains support for playing
back FEC.

BUG=
R=stefan@webrtc.org

Review URL: https://webrtc-codereview.appspot.com/16969004

git-svn-id: http://webrtc.googlecode.com/svn/trunk@6838 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
pbos@webrtc.org 2014-08-06 16:26:56 +00:00
parent 1ccff349ee
commit 4b5625e5ac
19 changed files with 646 additions and 467 deletions

View File

@ -242,10 +242,6 @@
'video_coding/main/source/qm_select_unittest.cc',
'video_coding/main/source/test/stream_generator.cc',
'video_coding/main/source/test/stream_generator.h',
'video_coding/main/test/pcap_file_reader.cc',
'video_coding/main/test/pcap_file_reader_unittest.cc',
'video_coding/main/test/rtp_file_reader.cc',
'video_coding/main/test/rtp_file_reader_unittest.cc',
'video_processing/main/test/unit_test/brightness_detection_test.cc',
'video_processing/main/test/unit_test/color_enhancement_test.cc',
'video_processing/main/test/unit_test/content_metrics_test.cc',

View File

@ -56,8 +56,8 @@
},
'sources': [
'tools/rtp_to_text.cc',
'<(webrtc_root)/modules/video_coding/main/test/rtp_file_reader.cc',
'<(webrtc_root)/modules/video_coding/main/test/rtp_file_reader.h',
'<(webrtc_root)/test/rtp_file_reader.cc',
'<(webrtc_root)/test/rtp_file_reader.h',
], # source
},
{
@ -79,8 +79,8 @@
},
'sources': [
'tools/bwe_rtp_play.cc',
'<(webrtc_root)/modules/video_coding/main/test/rtp_file_reader.cc',
'<(webrtc_root)/modules/video_coding/main/test/rtp_file_reader.h',
'<(webrtc_root)/test/rtp_file_reader.cc',
'<(webrtc_root)/test/rtp_file_reader.h',
], # source
},
], # targets

View File

@ -16,10 +16,7 @@
#include "webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h"
#include "webrtc/modules/rtp_rtcp/interface/rtp_header_parser.h"
#include "webrtc/modules/rtp_rtcp/interface/rtp_payload_registry.h"
#include "webrtc/modules/video_coding/main/test/rtp_file_reader.h"
#include "webrtc/modules/video_coding/main/test/rtp_player.h"
using webrtc::rtpplayer::RtpPacketSourceInterface;
#include "webrtc/test/rtp_file_reader.h"
const int kMinBitrateBps = 30000;
@ -27,11 +24,12 @@ bool ParseArgsAndSetupEstimator(int argc,
char** argv,
webrtc::Clock* clock,
webrtc::RemoteBitrateObserver* observer,
RtpPacketSourceInterface** rtp_reader,
webrtc::test::RtpFileReader** rtp_reader,
webrtc::RtpHeaderParser** parser,
webrtc::RemoteBitrateEstimator** estimator,
std::string* estimator_used) {
*rtp_reader = webrtc::rtpplayer::CreateRtpFileReader(argv[3]);
*rtp_reader = webrtc::test::RtpFileReader::Create(
webrtc::test::RtpFileReader::kRtpDump, argv[3]);
if (!*rtp_reader) {
fprintf(stderr, "Cannot open input file %s\n", argv[3]);
return false;

View File

@ -18,8 +18,8 @@ class Clock;
class RemoteBitrateEstimator;
class RemoteBitrateObserver;
class RtpHeaderParser;
namespace rtpplayer {
class RtpPacketSourceInterface;
namespace test {
class RtpFileReader;
}
}
@ -28,7 +28,7 @@ bool ParseArgsAndSetupEstimator(
char** argv,
webrtc::Clock* clock,
webrtc::RemoteBitrateObserver* observer,
webrtc::rtpplayer::RtpPacketSourceInterface** rtp_reader,
webrtc::test::RtpFileReader** rtp_reader,
webrtc::RtpHeaderParser** parser,
webrtc::RemoteBitrateEstimator** estimator,
std::string* estimator_used);

View File

@ -14,11 +14,8 @@
#include "webrtc/modules/remote_bitrate_estimator/tools/bwe_rtp.h"
#include "webrtc/modules/rtp_rtcp/interface/rtp_header_parser.h"
#include "webrtc/modules/rtp_rtcp/interface/rtp_payload_registry.h"
#include "webrtc/modules/video_coding/main/test/rtp_file_reader.h"
#include "webrtc/modules/video_coding/main/test/rtp_player.h"
#include "webrtc/system_wrappers/interface/scoped_ptr.h"
using webrtc::rtpplayer::RtpPacketSourceInterface;
#include "webrtc/test/rtp_file_reader.h"
class Observer : public webrtc::RemoteBitrateObserver {
public:
@ -49,7 +46,7 @@ int main(int argc, char** argv) {
"<extension id> is the id associated with the extension.\n");
return -1;
}
RtpPacketSourceInterface* reader;
webrtc::test::RtpFileReader* reader;
webrtc::RemoteBitrateEstimator* estimator;
webrtc::RtpHeaderParser* parser;
std::string estimator_used;
@ -59,7 +56,7 @@ int main(int argc, char** argv) {
&parser, &estimator, &estimator_used)) {
return -1;
}
webrtc::scoped_ptr<RtpPacketSourceInterface> rtp_reader(reader);
webrtc::scoped_ptr<webrtc::test::RtpFileReader> rtp_reader(reader);
webrtc::scoped_ptr<webrtc::RtpHeaderParser> rtp_parser(parser);
webrtc::scoped_ptr<webrtc::RemoteBitrateEstimator> rbe(estimator);
@ -68,30 +65,25 @@ int main(int argc, char** argv) {
int64_t next_process_time_ms = 0;
int64_t next_rtp_time_ms = 0;
int64_t first_rtp_time_ms = -1;
const uint32_t kMaxPacketSize = 1500;
uint8_t packet_buffer[kMaxPacketSize];
uint8_t* packet = packet_buffer;
int non_zero_abs_send_time = 0;
int non_zero_ts_offsets = 0;
while (true) {
uint32_t next_rtp_time;
if (next_rtp_time_ms <= clock.TimeInMilliseconds()) {
uint32_t packet_length = kMaxPacketSize;
if (rtp_reader->NextPacket(packet, &packet_length,
&next_rtp_time) == -1) {
webrtc::test::RtpFileReader::Packet packet;
if (!rtp_reader->NextPacket(&packet)) {
break;
}
if (first_rtp_time_ms == -1)
first_rtp_time_ms = next_rtp_time;
next_rtp_time_ms = next_rtp_time - first_rtp_time_ms;
first_rtp_time_ms = packet.time_ms;
packet.time_ms = packet.time_ms - first_rtp_time_ms;
webrtc::RTPHeader header;
parser->Parse(packet, packet_length, &header);
parser->Parse(packet.data, packet.length, &header);
if (header.extension.absoluteSendTime != 0)
++non_zero_abs_send_time;
if (header.extension.transmissionTimeOffset != 0)
++non_zero_ts_offsets;
rbe->IncomingPacket(clock.TimeInMilliseconds(),
packet_length - header.headerLength,
static_cast<int>(packet.length - header.headerLength),
header);
++packet_counter;
}

View File

@ -14,11 +14,8 @@
#include "webrtc/modules/remote_bitrate_estimator/tools/bwe_rtp.h"
#include "webrtc/modules/rtp_rtcp/interface/rtp_header_parser.h"
#include "webrtc/modules/rtp_rtcp/interface/rtp_payload_registry.h"
#include "webrtc/modules/video_coding/main/test/rtp_file_reader.h"
#include "webrtc/modules/video_coding/main/test/rtp_player.h"
#include "webrtc/system_wrappers/interface/scoped_ptr.h"
using webrtc::rtpplayer::RtpPacketSourceInterface;
#include "webrtc/test/rtp_file_reader.h"
int main(int argc, char** argv) {
if (argc < 4) {
@ -32,43 +29,44 @@ int main(int argc, char** argv) {
" output.\n");
return -1;
}
RtpPacketSourceInterface* reader;
webrtc::test::RtpFileReader* reader;
webrtc::RtpHeaderParser* parser;
if (!ParseArgsAndSetupEstimator(argc, argv, NULL, NULL, &reader, &parser,
NULL, NULL)) {
return -1;
}
bool arrival_time_only = (argc >= 5 && strncmp(argv[4], "-t", 2) == 0);
webrtc::scoped_ptr<RtpPacketSourceInterface> rtp_reader(reader);
webrtc::scoped_ptr<webrtc::test::RtpFileReader> rtp_reader(reader);
webrtc::scoped_ptr<webrtc::RtpHeaderParser> rtp_parser(parser);
fprintf(stdout, "seqnum timestamp ts_offset abs_sendtime recvtime "
"markerbit ssrc size\n");
int packet_counter = 0;
static const uint32_t kMaxPacketSize = 1500;
uint8_t packet_buffer[kMaxPacketSize];
uint8_t* packet = packet_buffer;
uint32_t packet_length = kMaxPacketSize;
uint32_t time_ms = 0;
int non_zero_abs_send_time = 0;
int non_zero_ts_offsets = 0;
while (rtp_reader->NextPacket(packet, &packet_length, &time_ms) == 0) {
webrtc::test::RtpFileReader::Packet packet;
while (rtp_reader->NextPacket(&packet)) {
webrtc::RTPHeader header;
parser->Parse(packet, packet_length, &header);
parser->Parse(packet.data, packet.length, &header);
if (header.extension.absoluteSendTime != 0)
++non_zero_abs_send_time;
if (header.extension.transmissionTimeOffset != 0)
++non_zero_ts_offsets;
if (arrival_time_only) {
std::stringstream ss;
ss << static_cast<int64_t>(time_ms) * 1000000;
ss << static_cast<int64_t>(packet.time_ms) * 1000000;
fprintf(stdout, "%s\n", ss.str().c_str());
} else {
fprintf(stdout, "%u %u %d %u %u %d %u %u\n", header.sequenceNumber,
header.timestamp, header.extension.transmissionTimeOffset,
header.extension.absoluteSendTime, time_ms, header.markerBit,
header.ssrc, packet_length);
fprintf(stdout,
"%u %u %d %u %u %d %u %d\n",
header.sequenceNumber,
header.timestamp,
header.extension.transmissionTimeOffset,
header.extension.absoluteSendTime,
packet.time_ms,
header.markerBit,
header.ssrc,
static_cast<int>(packet.length));
}
packet_length = kMaxPacketSize;
++packet_counter;
}
fprintf(stderr, "Parsed %d packets\n", packet_counter);

View File

@ -21,6 +21,7 @@
'<(webrtc_root)/test/metrics.gyp:metrics',
'<(webrtc_root)/common_video/common_video.gyp:common_video',
'<(webrtc_root)/system_wrappers/source/system_wrappers.gyp:field_trial_default',
'<(webrtc_root)/test/webrtc_test_common.gyp:webrtc_test_common',
],
'sources': [
# headers
@ -30,11 +31,9 @@
'../test/media_opt_test.h',
'../test/mt_test_common.h',
'../test/normal_test.h',
'../test/pcap_file_reader.h',
'../test/quality_modes_test.h',
'../test/receiver_tests.h',
'../test/release_test.h',
'../test/rtp_file_reader.h',
'../test/rtp_player.h',
'../test/test_callbacks.h',
'../test/test_util.h',
@ -48,9 +47,7 @@
'../test/mt_test_common.cc',
'../test/mt_rx_tx_test.cc',
'../test/normal_test.cc',
'../test/pcap_file_reader.cc',
'../test/quality_modes_test.cc',
'../test/rtp_file_reader.cc',
'../test/rtp_player.cc',
'../test/test_callbacks.cc',
'../test/test_util.cc',

View File

@ -1,26 +0,0 @@
/*
* Copyright (c) 2013 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_VIDEO_CODING_TEST_PCAP_FILE_READER_H_
#define WEBRTC_MODULES_VIDEO_CODING_TEST_PCAP_FILE_READER_H_
#include <string>
namespace webrtc {
namespace rtpplayer {
class RtpPacketSourceInterface;
RtpPacketSourceInterface* CreatePcapFileReader(const std::string& filename);
} // namespace rtpplayer
} // namespace webrtc
#endif // WEBRTC_MODULES_VIDEO_CODING_TEST_PCAP_FILE_READER_H_

View File

@ -1,166 +0,0 @@
/*
* Copyright (c) 2013 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/video_coding/main/test/rtp_file_reader.h"
#ifdef WIN32
#include <windows.h>
#include <Winsock2.h>
#else
#include <arpa/inet.h>
#endif
#include <assert.h>
#include <stdio.h>
#include "webrtc/modules/video_coding/main/test/rtp_player.h"
#include "webrtc/system_wrappers/interface/scoped_ptr.h"
namespace webrtc {
namespace rtpplayer {
enum {
kResultFail = -1,
kResultSuccess = 0,
kFirstLineLength = 40, // More than needed to read the ID line.
kPacketHeaderSize = 8 // Rtpplay packet header size in bytes.
};
#if 1
# define DEBUG_LOG(text)
# define DEBUG_LOG1(text, arg)
#else
# define DEBUG_LOG(text) (printf(text "\n"))
# define DEBUG_LOG1(text, arg) (printf(text "\n", arg))
#endif
#define TRY(expr) \
do { \
if ((expr) < 0) { \
DEBUG_LOG1("FAIL at " __FILE__ ":%d", __LINE__); \
return kResultFail; \
} \
} while (0)
// Read RTP packets from file in rtpdump format, as documented at:
// http://www.cs.columbia.edu/irt/software/rtptools/
class RtpFileReaderImpl : public RtpPacketSourceInterface {
public:
RtpFileReaderImpl() : file_(NULL) {}
virtual ~RtpFileReaderImpl() {
if (file_ != NULL) {
fclose(file_);
file_ = NULL;
}
}
int Initialize(const std::string& filename) {
file_ = fopen(filename.c_str(), "rb");
if (file_ == NULL) {
printf("ERROR: Can't open file: %s\n", filename.c_str());
return kResultFail;
}
char firstline[kFirstLineLength + 1] = {0};
if (fgets(firstline, kFirstLineLength, file_) == NULL) {
DEBUG_LOG("ERROR: Can't read from file\n");
return kResultFail;
}
if (strncmp(firstline, "#!rtpplay", 9) == 0) {
if (strncmp(firstline, "#!rtpplay1.0", 12) != 0) {
DEBUG_LOG("ERROR: wrong rtpplay version, must be 1.0\n");
return kResultFail;
}
} else if (strncmp(firstline, "#!RTPencode", 11) == 0) {
if (strncmp(firstline, "#!RTPencode1.0", 14) != 0) {
DEBUG_LOG("ERROR: wrong RTPencode version, must be 1.0\n");
return kResultFail;
}
} else {
DEBUG_LOG("ERROR: wrong file format of input file\n");
return kResultFail;
}
uint32_t start_sec;
uint32_t start_usec;
uint32_t source;
uint16_t port;
uint16_t padding;
TRY(Read(&start_sec));
TRY(Read(&start_usec));
TRY(Read(&source));
TRY(Read(&port));
TRY(Read(&padding));
return kResultSuccess;
}
virtual int NextPacket(uint8_t* rtp_data, uint32_t* length,
uint32_t* time_ms) {
assert(rtp_data);
assert(length);
assert(time_ms);
uint16_t len;
uint16_t plen;
uint32_t offset;
TRY(Read(&len));
TRY(Read(&plen));
TRY(Read(&offset));
// Use 'len' here because a 'plen' of 0 specifies rtcp.
len -= kPacketHeaderSize;
if (*length < len) {
return kResultFail;
}
if (fread(rtp_data, 1, len, file_) != len) {
return kResultFail;
}
*length = len;
*time_ms = offset;
return kResultSuccess;
}
private:
int Read(uint32_t* out) {
assert(out);
uint32_t tmp = 0;
if (fread(&tmp, 1, sizeof(uint32_t), file_) != sizeof(uint32_t)) {
return kResultFail;
}
*out = ntohl(tmp);
return kResultSuccess;
}
int Read(uint16_t* out) {
assert(out);
uint16_t tmp = 0;
if (fread(&tmp, 1, sizeof(uint16_t), file_) != sizeof(uint16_t)) {
return kResultFail;
}
*out = ntohs(tmp);
return kResultSuccess;
}
FILE* file_;
DISALLOW_COPY_AND_ASSIGN(RtpFileReaderImpl);
};
RtpPacketSourceInterface* CreateRtpFileReader(const std::string& filename) {
scoped_ptr<RtpFileReaderImpl> impl(new RtpFileReaderImpl());
if (impl->Initialize(filename) != 0) {
return NULL;
}
return impl.release();
}
} // namespace rtpplayer
} // namespace webrtc

View File

@ -1,26 +0,0 @@
/*
* Copyright (c) 2013 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_VIDEO_CODING_MAIN_TEST_RTP_FILE_READER_H_
#define WEBRTC_MODULES_VIDEO_CODING_MAIN_TEST_RTP_FILE_READER_H_
#include <string>
namespace webrtc {
namespace rtpplayer {
class RtpPacketSourceInterface;
RtpPacketSourceInterface* CreateRtpFileReader(const std::string& filename);
} // namespace rtpplayer
} // namespace webrtc
#endif // WEBRTC_MODULES_VIDEO_CODING_MAIN_TEST_RTP_FILE_READER_H_

View File

@ -1,54 +0,0 @@
/*
* Copyright (c) 2013 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 "testing/gtest/include/gtest/gtest.h"
#include "webrtc/modules/rtp_rtcp/source/rtp_utility.h"
#include "webrtc/modules/video_coding/main/test/rtp_file_reader.h"
#include "webrtc/modules/video_coding/main/test/rtp_player.h"
#include "webrtc/system_wrappers/interface/scoped_ptr.h"
#include "webrtc/test/testsupport/fileutils.h"
namespace webrtc {
namespace rtpplayer {
class TestRtpFileReader : public ::testing::Test {
public:
void Init(const std::string& filename) {
std::string filepath =
test::ResourcePath("video_coding/" + filename, "rtp");
rtp_packet_source_.reset(CreateRtpFileReader(filepath));
ASSERT_TRUE(rtp_packet_source_.get() != NULL);
}
int CountRtpPackets() {
const uint32_t kBufferSize = 4096;
uint8_t data[kBufferSize];
uint32_t length = kBufferSize;
uint32_t dummy_time_ms = 0;
int c = 0;
while (rtp_packet_source_->NextPacket(data, &length, &dummy_time_ms) == 0) {
EXPECT_GE(kBufferSize, length);
length = kBufferSize;
c++;
}
return c;
}
private:
scoped_ptr<RtpPacketSourceInterface> rtp_packet_source_;
};
TEST_F(TestRtpFileReader, Test60Packets) {
Init("pltype103");
EXPECT_EQ(60, CountRtpPackets());
}
} // namespace rtpplayer
} // namespace webrtc

View File

@ -19,12 +19,11 @@
#include "webrtc/modules/rtp_rtcp/interface/rtp_receiver.h"
#include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp.h"
#include "webrtc/modules/video_coding/main/source/internal_defines.h"
#include "webrtc/modules/video_coding/main/test/pcap_file_reader.h"
#include "webrtc/modules/video_coding/main/test/rtp_file_reader.h"
#include "webrtc/modules/video_coding/main/test/test_util.h"
#include "webrtc/system_wrappers/interface/clock.h"
#include "webrtc/system_wrappers/interface/critical_section_wrapper.h"
#include "webrtc/system_wrappers/interface/scoped_ptr.h"
#include "webrtc/test/rtp_file_reader.h"
#if 1
# define DEBUG_LOG1(text, arg)
@ -323,7 +322,7 @@ class RtpPlayerImpl : public RtpPlayerInterface {
public:
RtpPlayerImpl(PayloadSinkFactoryInterface* payload_sink_factory,
const PayloadTypes& payload_types, Clock* clock,
scoped_ptr<RtpPacketSourceInterface>* packet_source,
scoped_ptr<test::RtpFileReader>* packet_source,
float loss_rate, uint32_t rtt_ms, bool reordering)
: ssrc_handlers_(payload_sink_factory, payload_types),
clock_(clock),
@ -337,9 +336,7 @@ class RtpPlayerImpl : public RtpPlayerInterface {
no_loss_startup_(100),
end_of_file_(false),
reordering_(false),
reorder_buffer_(),
next_packet_(),
next_packet_length_(0) {
reorder_buffer_() {
assert(clock);
assert(packet_source);
assert(packet_source->get());
@ -368,22 +365,23 @@ class RtpPlayerImpl : public RtpPlayerInterface {
// Send any packets from packet source.
if (!end_of_file_ && (TimeUntilNextPacket() == 0 || first_packet_)) {
if (first_packet_) {
next_packet_length_ = sizeof(next_packet_);
if (packet_source_->NextPacket(next_packet_, &next_packet_length_,
&next_rtp_time_) != 0) {
if (!packet_source_->NextPacket(&next_packet_))
return 0;
}
first_packet_rtp_time_ = next_rtp_time_;
first_packet_rtp_time_ = next_packet_.time_ms;
first_packet_time_ms_ = clock_->TimeInMilliseconds();
first_packet_ = false;
}
if (reordering_ && reorder_buffer_.get() == NULL) {
reorder_buffer_.reset(new RawRtpPacket(next_packet_,
next_packet_length_, 0, 0));
reorder_buffer_.reset(
new RawRtpPacket(next_packet_.data,
static_cast<uint32_t>(next_packet_.length),
0,
0));
return 0;
}
int ret = SendPacket(next_packet_, next_packet_length_);
int ret = SendPacket(next_packet_.data,
static_cast<uint32_t>(next_packet_.length));
if (reorder_buffer_.get()) {
SendPacket(reorder_buffer_->data(), reorder_buffer_->length());
reorder_buffer_.reset(NULL);
@ -392,13 +390,11 @@ class RtpPlayerImpl : public RtpPlayerInterface {
return ret;
}
next_packet_length_ = sizeof(next_packet_);
if (packet_source_->NextPacket(next_packet_, &next_packet_length_,
&next_rtp_time_) != 0) {
if (!packet_source_->NextPacket(&next_packet_)) {
end_of_file_ = true;
return 0;
}
else if (next_packet_length_ == 0) {
else if (next_packet_.length == 0) {
return 0;
}
}
@ -456,7 +452,8 @@ class RtpPlayerImpl : public RtpPlayerInterface {
SsrcHandlers ssrc_handlers_;
Clock* clock_;
scoped_ptr<RtpPacketSourceInterface> packet_source_;
scoped_ptr<test::RtpFileReader> packet_source_;
test::RtpFileReader::Packet next_packet_;
uint32_t next_rtp_time_;
bool first_packet_;
int64_t first_packet_rtp_time_;
@ -468,8 +465,6 @@ class RtpPlayerImpl : public RtpPlayerInterface {
bool end_of_file_;
bool reordering_;
scoped_ptr<RawRtpPacket> reorder_buffer_;
uint8_t next_packet_[kMaxPacketBufferSize];
uint32_t next_packet_length_;
DISALLOW_IMPLICIT_CONSTRUCTORS(RtpPlayerImpl);
};
@ -478,10 +473,11 @@ RtpPlayerInterface* Create(const std::string& input_filename,
PayloadSinkFactoryInterface* payload_sink_factory, Clock* clock,
const PayloadTypes& payload_types, float loss_rate, uint32_t rtt_ms,
bool reordering) {
scoped_ptr<RtpPacketSourceInterface> packet_source(
CreateRtpFileReader(input_filename));
scoped_ptr<test::RtpFileReader> packet_source(test::RtpFileReader::Create(
test::RtpFileReader::kRtpDump, input_filename));
if (packet_source.get() == NULL) {
packet_source.reset(CreatePcapFileReader(input_filename));
packet_source.reset(test::RtpFileReader::Create(test::RtpFileReader::kPcap,
input_filename));
if (packet_source.get() == NULL) {
return NULL;
}

View File

@ -44,20 +44,6 @@ class PayloadCodecTuple {
typedef std::vector<PayloadCodecTuple> PayloadTypes;
typedef std::vector<PayloadCodecTuple>::const_iterator PayloadTypesIterator;
// Implemented by something that can provide RTP packets, for instance a file
// format parser such as the rtp_file_reader or the pcap_file_reader.
class RtpPacketSourceInterface {
public:
virtual ~RtpPacketSourceInterface() {}
// Read next RTP packet into buffer pointed to by rtp_data. On call, 'length'
// field must be filled in with the size of the buffer. The actual size of
// the packet is available in 'length' upon returning. Time in milliseconds
// from start of stream is returned in 'time_ms'.
virtual int NextPacket(uint8_t* rtp_data, uint32_t* length,
uint32_t* time_ms) = 0;
};
// Implemented by RtpPlayer and given to client as a means to retrieve
// information about a specific RTP stream.
class RtpStreamInterface {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
* 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
@ -8,15 +8,8 @@
* be found in the AUTHORS file in the root of the source tree.
*/
#include "webrtc/modules/video_coding/main/test/pcap_file_reader.h"
#include "webrtc/test/rtp_file_reader.h"
#ifdef WIN32
#include <windows.h>
#include <Winsock2.h>
#else
#include <arpa/inet.h>
#endif
#include <assert.h>
#include <stdio.h>
#include <map>
@ -24,11 +17,142 @@
#include <vector>
#include "webrtc/modules/rtp_rtcp/source/rtp_utility.h"
#include "webrtc/modules/video_coding/main/test/rtp_player.h"
#include "webrtc/system_wrappers/interface/scoped_ptr.h"
namespace webrtc {
namespace rtpplayer {
namespace test {
static const size_t kFirstLineLength = 40;
static uint16_t kPacketHeaderSize = 8;
#if 1
# define DEBUG_LOG(text)
# define DEBUG_LOG1(text, arg)
#else
# define DEBUG_LOG(text) (printf(text "\n"))
# define DEBUG_LOG1(text, arg) (printf(text "\n", arg))
#endif
#define TRY(expr) \
do { \
if (!(expr)) { \
DEBUG_LOG1("FAIL at " __FILE__ ":%d", __LINE__); \
return false; \
} \
} while (0)
class RtpFileReaderImpl : public RtpFileReader {
public:
virtual bool Init(const std::string& filename) = 0;
};
// Read RTP packets from file in rtpdump format, as documented at:
// http://www.cs.columbia.edu/irt/software/rtptools/
class RtpDumpReader : public RtpFileReaderImpl {
public:
RtpDumpReader() : file_(NULL) {}
virtual ~RtpDumpReader() {
if (file_ != NULL) {
fclose(file_);
file_ = NULL;
}
}
bool Init(const std::string& filename) {
file_ = fopen(filename.c_str(), "rb");
if (file_ == NULL) {
printf("ERROR: Can't open file: %s\n", filename.c_str());
return false;
}
char firstline[kFirstLineLength + 1] = {0};
if (fgets(firstline, kFirstLineLength, file_) == NULL) {
DEBUG_LOG("ERROR: Can't read from file\n");
return false;
}
if (strncmp(firstline, "#!rtpplay", 9) == 0) {
if (strncmp(firstline, "#!rtpplay1.0", 12) != 0) {
DEBUG_LOG("ERROR: wrong rtpplay version, must be 1.0\n");
return false;
}
} else if (strncmp(firstline, "#!RTPencode", 11) == 0) {
if (strncmp(firstline, "#!RTPencode1.0", 14) != 0) {
DEBUG_LOG("ERROR: wrong RTPencode version, must be 1.0\n");
return false;
}
} else {
DEBUG_LOG("ERROR: wrong file format of input file\n");
return false;
}
uint32_t start_sec;
uint32_t start_usec;
uint32_t source;
uint16_t port;
uint16_t padding;
TRY(Read(&start_sec));
TRY(Read(&start_usec));
TRY(Read(&source));
TRY(Read(&port));
TRY(Read(&padding));
return true;
}
virtual bool NextPacket(Packet* packet) OVERRIDE {
uint8_t* rtp_data = packet->data;
packet->length = Packet::kMaxPacketBufferSize;
uint16_t len;
uint16_t plen;
uint32_t offset;
TRY(Read(&len));
TRY(Read(&plen));
TRY(Read(&offset));
// Use 'len' here because a 'plen' of 0 specifies rtcp.
len -= kPacketHeaderSize;
if (packet->length < len) {
return false;
}
if (fread(rtp_data, 1, len, file_) != len) {
return false;
}
packet->length = len;
packet->time_ms = offset;
return true;
}
private:
bool Read(uint32_t* out) {
*out = 0;
for (size_t i = 0; i < 4; ++i) {
*out <<= 8;
uint8_t tmp;
if (fread(&tmp, 1, sizeof(uint8_t), file_) != sizeof(uint8_t))
return false;
*out |= tmp;
}
return true;
}
bool Read(uint16_t* out) {
*out = 0;
for (size_t i = 0; i < 2; ++i) {
*out <<= 8;
uint8_t tmp;
if (fread(&tmp, 1, sizeof(uint8_t), file_) != sizeof(uint8_t))
return false;
*out |= tmp;
}
return true;
}
FILE* file_;
DISALLOW_COPY_AND_ASSIGN(RtpDumpReader);
};
enum {
kResultFail = -1,
@ -56,50 +180,46 @@ enum {
const uint32_t kPcapBOMSwapOrder = 0xd4c3b2a1UL;
const uint32_t kPcapBOMNoSwapOrder = 0xa1b2c3d4UL;
#if 1
# define DEBUG_LOG(text)
# define DEBUG_LOG1(text, arg)
#else
# define DEBUG_LOG(text) (printf(text "\n"))
# define DEBUG_LOG1(text, arg) (printf(text "\n", arg))
#endif
#define TRY(expr) \
do { \
int r = (expr); \
if (r == kResultFail) { \
DEBUG_LOG1("FAIL at " __FILE__ ":%d", __LINE__); \
return kResultFail; \
} else if (r == kResultSkip) { \
return kResultSkip; \
} \
#define TRY_PCAP(expr) \
do { \
int r = (expr); \
if (r == kResultFail) { \
DEBUG_LOG1("FAIL at " __FILE__ ":%d", __LINE__); \
return kResultFail; \
} else if (r == kResultSkip) { \
return kResultSkip; \
} \
} while (0)
// Read RTP packets from file in tcpdump/libpcap format, as documented at:
// http://wiki.wireshark.org/Development/LibpcapFileFormat
class PcapFileReaderImpl : public RtpPacketSourceInterface {
class PcapReader : public RtpFileReaderImpl {
public:
PcapFileReaderImpl()
PcapReader()
: file_(NULL),
swap_pcap_byte_order_(false),
#ifdef WEBRTC_ARCH_BIG_ENDIAN
swap_network_byte_order_(false),
#else
swap_network_byte_order_(true),
#endif
read_buffer_(),
packets_by_ssrc_(),
packets_(),
next_packet_it_() {
int16_t test = 0x7f00;
if (test != htons(test)) {
swap_network_byte_order_ = true;
}
}
virtual ~PcapFileReaderImpl() {
virtual ~PcapReader() {
if (file_ != NULL) {
fclose(file_);
file_ = NULL;
}
}
bool Init(const std::string& filename) OVERRIDE {
return Initialize(filename) == kResultSuccess;
}
int Initialize(const std::string& filename) {
file_ = fopen(filename.c_str(), "rb");
if (file_ == NULL) {
@ -115,7 +235,7 @@ class PcapFileReaderImpl : public RtpPacketSourceInterface {
uint32_t stream_start_ms = 0;
int32_t next_packet_pos = ftell(file_);
for (;;) {
TRY(fseek(file_, next_packet_pos, SEEK_SET));
TRY_PCAP(fseek(file_, next_packet_pos, SEEK_SET));
int result = ReadPacket(&next_packet_pos, stream_start_ms,
++total_packet_count);
if (result == kResultFail) {
@ -165,7 +285,15 @@ class PcapFileReaderImpl : public RtpPacketSourceInterface {
return kResultSuccess;
}
virtual int NextPacket(uint8_t* data, uint32_t* length, uint32_t* time_ms) {
virtual bool NextPacket(Packet* packet) OVERRIDE {
uint32_t length = Packet::kMaxPacketBufferSize;
if (NextPcap(packet->data, &length, &packet->time_ms) != kResultSuccess)
return false;
packet->length = static_cast<size_t>(length);
return true;
}
virtual int NextPcap(uint8_t* data, uint32_t* length, uint32_t* time_ms) {
assert(data);
assert(length);
assert(time_ms);
@ -176,8 +304,8 @@ class PcapFileReaderImpl : public RtpPacketSourceInterface {
if (*length < next_packet_it_->payload_length) {
return -1;
}
TRY(fseek(file_, next_packet_it_->pos_in_file, SEEK_SET));
TRY(Read(data, next_packet_it_->payload_length));
TRY_PCAP(fseek(file_, next_packet_it_->pos_in_file, SEEK_SET));
TRY_PCAP(Read(data, next_packet_it_->payload_length));
*length = next_packet_it_->payload_length;
*time_ms = next_packet_it_->time_offset_ms;
next_packet_it_++;
@ -205,7 +333,7 @@ class PcapFileReaderImpl : public RtpPacketSourceInterface {
int ReadGlobalHeader() {
uint32_t magic;
TRY(Read(&magic, false));
TRY_PCAP(Read(&magic, false));
if (magic == kPcapBOMSwapOrder) {
swap_pcap_byte_order_ = true;
} else if (magic == kPcapBOMNoSwapOrder) {
@ -216,8 +344,8 @@ class PcapFileReaderImpl : public RtpPacketSourceInterface {
uint16_t version_major;
uint16_t version_minor;
TRY(Read(&version_major, false));
TRY(Read(&version_minor, false));
TRY_PCAP(Read(&version_major, false));
TRY_PCAP(Read(&version_minor, false));
if (version_major != kPcapVersionMajor ||
version_minor != kPcapVersionMinor) {
return kResultFail;
@ -227,10 +355,10 @@ class PcapFileReaderImpl : public RtpPacketSourceInterface {
uint32_t sigfigs; // Accuracy of timestamps.
uint32_t snaplen; // Max length of captured packets, in octets.
uint32_t network; // Data link type.
TRY(Read(&this_zone, false));
TRY(Read(&sigfigs, false));
TRY(Read(&snaplen, false));
TRY(Read(&network, false));
TRY_PCAP(Read(&this_zone, false));
TRY_PCAP(Read(&sigfigs, false));
TRY_PCAP(Read(&snaplen, false));
TRY_PCAP(Read(&network, false));
// Accept only LINKTYPE_NULL and LINKTYPE_ETHERNET.
// See: http://www.tcpdump.org/linktypes.html
@ -249,24 +377,24 @@ class PcapFileReaderImpl : public RtpPacketSourceInterface {
uint32_t ts_usec; // Timestamp microseconds.
uint32_t incl_len; // Number of octets of packet saved in file.
uint32_t orig_len; // Actual length of packet.
TRY(Read(&ts_sec, false));
TRY(Read(&ts_usec, false));
TRY(Read(&incl_len, false));
TRY(Read(&orig_len, false));
TRY_PCAP(Read(&ts_sec, false));
TRY_PCAP(Read(&ts_usec, false));
TRY_PCAP(Read(&incl_len, false));
TRY_PCAP(Read(&orig_len, false));
*next_packet_pos = ftell(file_) + incl_len;
RtpPacketMarker marker = {0};
marker.packet_number = number;
marker.time_offset_ms = CalcTimeDelta(ts_sec, ts_usec, stream_start_ms);
TRY(ReadPacketHeader(&marker));
TRY_PCAP(ReadPacketHeader(&marker));
marker.pos_in_file = ftell(file_);
if (marker.payload_length > sizeof(read_buffer_)) {
printf("Packet too large!\n");
return kResultFail;
}
TRY(Read(read_buffer_, marker.payload_length));
TRY_PCAP(Read(read_buffer_, marker.payload_length));
RtpUtility::RtpHeaderParser rtp_parser(read_buffer_, marker.payload_length);
if (rtp_parser.RTCP()) {
@ -294,7 +422,7 @@ class PcapFileReaderImpl : public RtpPacketSourceInterface {
// the header as such and will likely fail reading the IP header if this is
// something else than null/loopback.
uint32_t protocol;
TRY(Read(&protocol, true));
TRY_PCAP(Read(&protocol, true));
if (protocol == kBsdNullLoopback1 || protocol == kBsdNullLoopback2) {
int result = ReadXxpIpHeader(marker);
DEBUG_LOG("Recognized loopback frame");
@ -303,12 +431,12 @@ class PcapFileReaderImpl : public RtpPacketSourceInterface {
}
}
TRY(fseek(file_, file_pos, SEEK_SET));
TRY_PCAP(fseek(file_, file_pos, SEEK_SET));
// Check for Ethernet II, IP frame header.
uint16_t type;
TRY(Skip(kEthernetIIHeaderMacSkip)); // Source+destination MAC.
TRY(Read(&type, true));
TRY_PCAP(Skip(kEthernetIIHeaderMacSkip)); // Source+destination MAC.
TRY_PCAP(Read(&type, true));
if (type == kEthertypeIp) {
int result = ReadXxpIpHeader(marker);
DEBUG_LOG("Recognized ethernet 2 frame");
@ -341,14 +469,14 @@ class PcapFileReaderImpl : public RtpPacketSourceInterface {
uint16_t fragment;
uint16_t protocol;
uint16_t checksum;
TRY(Read(&version, true));
TRY(Read(&length, true));
TRY(Read(&id, true));
TRY(Read(&fragment, true));
TRY(Read(&protocol, true));
TRY(Read(&checksum, true));
TRY(Read(&marker->source_ip, true));
TRY(Read(&marker->dest_ip, true));
TRY_PCAP(Read(&version, true));
TRY_PCAP(Read(&length, true));
TRY_PCAP(Read(&id, true));
TRY_PCAP(Read(&fragment, true));
TRY_PCAP(Read(&protocol, true));
TRY_PCAP(Read(&checksum, true));
TRY_PCAP(Read(&marker->source_ip, true));
TRY_PCAP(Read(&marker->dest_ip, true));
if (((version >> 12) & 0x000f) != kIpVersion4) {
DEBUG_LOG("IP header is not IPv4");
@ -364,7 +492,7 @@ class PcapFileReaderImpl : public RtpPacketSourceInterface {
// Skip remaining fields of IP header.
uint16_t header_length = (version & 0x0f00) >> (8 - 2);
assert(header_length >= kMinIpHeaderLength);
TRY(Skip(header_length - kMinIpHeaderLength));
TRY_PCAP(Skip(header_length - kMinIpHeaderLength));
protocol = protocol & 0x00ff;
if (protocol == kProtocolTcp) {
@ -373,10 +501,10 @@ class PcapFileReaderImpl : public RtpPacketSourceInterface {
} else if (protocol == kProtocolUdp) {
uint16_t length;
uint16_t checksum;
TRY(Read(&marker->source_port, true));
TRY(Read(&marker->dest_port, true));
TRY(Read(&length, true));
TRY(Read(&checksum, true));
TRY_PCAP(Read(&marker->source_port, true));
TRY_PCAP(Read(&marker->dest_port, true));
TRY_PCAP(Read(&length, true));
TRY_PCAP(Read(&checksum, true));
marker->payload_length = length - kUdpHeaderLength;
} else {
DEBUG_LOG("Unknown transport (expected UDP or TCP)");
@ -443,22 +571,33 @@ class PcapFileReaderImpl : public RtpPacketSourceInterface {
FILE* file_;
bool swap_pcap_byte_order_;
bool swap_network_byte_order_;
const bool swap_network_byte_order_;
uint8_t read_buffer_[kMaxReadBufferSize];
SsrcMap packets_by_ssrc_;
std::vector<RtpPacketMarker> packets_;
PacketIterator next_packet_it_;
DISALLOW_COPY_AND_ASSIGN(PcapFileReaderImpl);
DISALLOW_COPY_AND_ASSIGN(PcapReader);
};
RtpPacketSourceInterface* CreatePcapFileReader(const std::string& filename) {
scoped_ptr<PcapFileReaderImpl> impl(new PcapFileReaderImpl());
if (impl->Initialize(filename) != 0) {
RtpFileReader* RtpFileReader::Create(FileFormat format,
const std::string& filename) {
RtpFileReaderImpl* reader = NULL;
switch (format) {
case kPcap:
reader = new PcapReader();
break;
case kRtpDump:
reader = new RtpDumpReader();
break;
}
if (!reader->Init(filename)) {
delete reader;
return NULL;
}
return impl.release();
return reader;
}
} // namespace rtpplayer
} // namespace test
} // namespace webrtc

View File

@ -0,0 +1,42 @@
/*
* 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_TEST_RTP_FILE_READER_H_
#define WEBRTC_TEST_RTP_FILE_READER_H_
#include <string>
#include "webrtc/common_types.h"
namespace webrtc {
namespace test {
class RtpFileReader {
public:
enum FileFormat {
kPcap,
kRtpDump,
};
struct Packet {
static const size_t kMaxPacketBufferSize = 1500;
uint8_t data[kMaxPacketBufferSize];
size_t length;
uint32_t time_ms;
};
virtual ~RtpFileReader() {}
static RtpFileReader* Create(FileFormat format,
const std::string& filename);
virtual bool NextPacket(Packet* packet) = 0;
};
} // namespace test
} // namespace webrtc
#endif // WEBRTC_TEST_RTP_FILE_READER_H_

View File

@ -12,13 +12,38 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "webrtc/modules/rtp_rtcp/source/rtp_utility.h"
#include "webrtc/modules/video_coding/main/test/pcap_file_reader.h"
#include "webrtc/modules/video_coding/main/test/rtp_player.h"
#include "webrtc/system_wrappers/interface/scoped_ptr.h"
#include "webrtc/test/rtp_file_reader.h"
#include "webrtc/test/testsupport/fileutils.h"
namespace webrtc {
namespace rtpplayer {
class TestRtpFileReader : public ::testing::Test {
public:
void Init(const std::string& filename) {
std::string filepath =
test::ResourcePath("video_coding/" + filename, "rtp");
rtp_packet_source_.reset(
test::RtpFileReader::Create(test::RtpFileReader::kRtpDump, filepath));
ASSERT_TRUE(rtp_packet_source_.get() != NULL);
}
int CountRtpPackets() {
test::RtpFileReader::Packet packet;
int c = 0;
while (rtp_packet_source_->NextPacket(&packet))
c++;
return c;
}
private:
scoped_ptr<test::RtpFileReader> rtp_packet_source_;
};
TEST_F(TestRtpFileReader, Test60Packets) {
Init("pltype103");
EXPECT_EQ(60, CountRtpPackets());
}
typedef std::map<uint32_t, int> PacketsPerSsrc;
@ -27,35 +52,24 @@ class TestPcapFileReader : public ::testing::Test {
void Init(const std::string& filename) {
std::string filepath =
test::ResourcePath("video_coding/" + filename, "pcap");
rtp_packet_source_.reset(CreatePcapFileReader(filepath));
rtp_packet_source_.reset(
test::RtpFileReader::Create(test::RtpFileReader::kPcap, filepath));
ASSERT_TRUE(rtp_packet_source_.get() != NULL);
}
int CountRtpPackets() {
const uint32_t kBufferSize = 4096;
uint8_t data[kBufferSize];
uint32_t length = kBufferSize;
uint32_t dummy_time_ms = 0;
int c = 0;
while (rtp_packet_source_->NextPacket(data, &length, &dummy_time_ms) == 0) {
EXPECT_GE(kBufferSize, length);
length = kBufferSize;
test::RtpFileReader::Packet packet;
while (rtp_packet_source_->NextPacket(&packet))
c++;
}
return c;
}
PacketsPerSsrc CountRtpPacketsPerSsrc() {
const uint32_t kBufferSize = 4096;
uint8_t data[kBufferSize];
uint32_t length = kBufferSize;
uint32_t dummy_time_ms = 0;
PacketsPerSsrc pps;
while (rtp_packet_source_->NextPacket(data, &length, &dummy_time_ms) == 0) {
EXPECT_GE(kBufferSize, length);
length = kBufferSize;
RtpUtility::RtpHeaderParser rtp_header_parser(data, length);
test::RtpFileReader::Packet packet;
while (rtp_packet_source_->NextPacket(&packet)) {
RtpUtility::RtpHeaderParser rtp_header_parser(packet.data, packet.length);
webrtc::RTPHeader header;
if (!rtp_header_parser.RTCP() && rtp_header_parser.Parse(header, NULL)) {
pps[header.ssrc]++;
@ -65,7 +79,7 @@ class TestPcapFileReader : public ::testing::Test {
}
private:
scoped_ptr<RtpPacketSourceInterface> rtp_packet_source_;
scoped_ptr<test::RtpFileReader> rtp_packet_source_;
};
TEST_F(TestPcapFileReader, TestEthernetIIFrame) {
@ -94,6 +108,4 @@ TEST_F(TestPcapFileReader, TestThreeSsrc) {
EXPECT_EQ(113, pps[0x59fe6ef0]);
EXPECT_EQ(61, pps[0xed2bd2ac]);
}
} // namespace rtpplayer
} // namespace webrtc

View File

@ -35,6 +35,8 @@
'mock_transport.h',
'null_transport.cc',
'null_transport.h',
'rtp_file_reader.cc',
'rtp_file_reader.h',
'rtp_rtcp_observer.h',
'run_loop.cc',
'run_loop.h',
@ -184,6 +186,7 @@
],
'sources': [
'fake_network_pipe_unittest.cc',
'rtp_file_reader_unittest.cc',
],
},
], #targets

266
webrtc/video/replay.cc Normal file
View File

@ -0,0 +1,266 @@
/*
* 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 <stdio.h>
#include <map>
#include <sstream>
#include "gflags/gflags.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "webrtc/call.h"
#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
#include "webrtc/modules/rtp_rtcp/interface/rtp_header_parser.h"
#include "webrtc/system_wrappers/interface/clock.h"
#include "webrtc/system_wrappers/interface/scoped_ptr.h"
#include "webrtc/system_wrappers/interface/sleep.h"
#include "webrtc/test/encoder_settings.h"
#include "webrtc/test/null_transport.h"
#include "webrtc/test/rtp_file_reader.h"
#include "webrtc/test/run_loop.h"
#include "webrtc/test/run_test.h"
#include "webrtc/test/video_capturer.h"
#include "webrtc/test/video_renderer.h"
#include "webrtc/typedefs.h"
namespace webrtc {
namespace flags {
// TODO(pbos): Multiple receivers.
// Flag for payload type.
static bool ValidatePayloadType(const char* flagname, int32_t payload_type) {
return payload_type > 0 && payload_type <= 127;
}
DEFINE_int32(payload_type, 0, "Payload type.");
static int PayloadType() { return static_cast<int>(FLAGS_payload_type); }
static const bool payload_dummy =
google::RegisterFlagValidator(&FLAGS_payload_type, &ValidatePayloadType);
// Flag for SSRC.
static bool ValidateSsrc(const char* flagname, uint64_t ssrc) {
return ssrc > 0 && ssrc <= 0xFFFFFFFFu;
}
DEFINE_uint64(ssrc, 0, "Incoming SSRC.");
static uint32_t Ssrc() { return static_cast<uint32_t>(FLAGS_ssrc); }
static const bool ssrc_dummy =
google::RegisterFlagValidator(&FLAGS_ssrc, &ValidateSsrc);
static bool ValidateOptionalPayloadType(const char* flagname,
int32_t payload_type) {
return payload_type == -1 || ValidatePayloadType(flagname, payload_type);
}
// Flag for RED payload type.
DEFINE_int32(red_payload_type, -1, "RED payload type.");
static int RedPayloadType() {
return static_cast<int>(FLAGS_red_payload_type);
}
static const bool red_dummy =
google::RegisterFlagValidator(&FLAGS_red_payload_type,
&ValidateOptionalPayloadType);
// Flag for ULPFEC payload type.
DEFINE_int32(fec_payload_type, -1, "ULPFEC payload type.");
static int FecPayloadType() {
return static_cast<int>(FLAGS_fec_payload_type);
}
static const bool fec_dummy =
google::RegisterFlagValidator(&FLAGS_fec_payload_type,
&ValidateOptionalPayloadType);
// Flag for abs-send-time id.
static bool ValidateRtpHeaderExtensionId(const char* flagname,
int32_t extension_id) {
return extension_id >= -1 || extension_id < 15;
}
DEFINE_int32(abs_send_time_id, -1, "RTP extension ID for abs-send-time.");
static int AbsSendTimeId() { return static_cast<int>(FLAGS_abs_send_time_id); }
static const bool abs_send_time_dummy =
google::RegisterFlagValidator(&FLAGS_abs_send_time_id,
&ValidateRtpHeaderExtensionId);
// Flag for transmission-offset id.
DEFINE_int32(transmission_offset_id,
-1,
"RTP extension ID for transmission-offset.");
static int TransmissionOffsetId() {
return static_cast<int>(FLAGS_transmission_offset_id);
}
static const bool timestamp_offset_dummy =
google::RegisterFlagValidator(&FLAGS_transmission_offset_id,
&ValidateRtpHeaderExtensionId);
// Flag for rtpdump input file.
DEFINE_string(input_file, "", "rtpdump input file.");
static std::string InputFile() {
return static_cast<std::string>(FLAGS_input_file);
}
// Flag for raw output files.
DEFINE_string(out_base, "", "Basename (excluding .yuv) for raw output.");
static std::string OutBase() {
return static_cast<std::string>(FLAGS_out_base);
}
// Flag for video codec.
DEFINE_string(codec, "VP8", "Video codec.");
static std::string Codec() { return static_cast<std::string>(FLAGS_codec); }
} // namespace flags
static const uint32_t kReceiverLocalSsrc = 0x123456;
class FileRenderPassthrough : public VideoRenderer {
public:
FileRenderPassthrough(const std::string& basename, VideoRenderer* renderer)
: basename_(basename),
renderer_(renderer),
file_(NULL),
count_(0),
last_width_(0),
last_height_(0) {}
~FileRenderPassthrough() {
if (file_ != NULL)
fclose(file_);
}
private:
virtual void RenderFrame(const I420VideoFrame& video_frame,
int time_to_render_ms) OVERRIDE {
if (renderer_ != NULL)
renderer_->RenderFrame(video_frame, time_to_render_ms);
if (basename_ == "")
return;
if (last_width_ != video_frame.width() ||
last_height_ != video_frame.height()) {
if (file_ != NULL)
fclose(file_);
std::stringstream filename;
filename << basename_;
if (++count_ > 1)
filename << '-' << count_;
filename << '_' << video_frame.width() << 'x' << video_frame.height()
<< ".yuv";
file_ = fopen(filename.str().c_str(), "wb");
if (file_ == NULL) {
fprintf(stderr,
"Couldn't open file for writing: %s\n",
filename.str().c_str());
}
}
last_width_ = video_frame.width();
last_height_ = video_frame.height();
if (file_ == NULL)
return;
PrintI420VideoFrame(video_frame, file_);
}
const std::string basename_;
VideoRenderer* const renderer_;
FILE* file_;
size_t count_;
int last_width_;
int last_height_;
};
void RtpReplay() {
scoped_ptr<test::VideoRenderer> playback_video(test::VideoRenderer::Create(
"Playback Video", 640, 480));
FileRenderPassthrough file_passthrough(flags::OutBase(),
playback_video.get());
// TODO(pbos): Might be good to have a transport that prints keyframe requests
// etc.
test::NullTransport transport;
Call::Config call_config(&transport);
scoped_ptr<Call> call(Call::Create(call_config));
VideoReceiveStream::Config receive_config;
receive_config.rtp.remote_ssrc = flags::Ssrc();
receive_config.rtp.local_ssrc = kReceiverLocalSsrc;
receive_config.rtp.fec.ulpfec_payload_type = flags::FecPayloadType();
receive_config.rtp.fec.red_payload_type = flags::RedPayloadType();
receive_config.rtp.nack.rtp_history_ms = 1000;
if (flags::TransmissionOffsetId() != -1) {
receive_config.rtp.extensions.push_back(
RtpExtension(RtpExtension::kTOffset, flags::TransmissionOffsetId()));
}
if (flags::AbsSendTimeId() != -1) {
receive_config.rtp.extensions.push_back(
RtpExtension(RtpExtension::kAbsSendTime, flags::AbsSendTimeId()));
}
receive_config.renderer = &file_passthrough;
VideoSendStream::Config::EncoderSettings encoder_settings;
encoder_settings.payload_name = flags::Codec();
encoder_settings.payload_type = flags::PayloadType();
VideoCodec codec = test::CreateDecoderVideoCodec(encoder_settings);
receive_config.codecs.push_back(codec);
VideoReceiveStream* receive_stream =
call->CreateVideoReceiveStream(receive_config);
scoped_ptr<test::RtpFileReader> rtp_reader(test::RtpFileReader::Create(
test::RtpFileReader::kRtpDump, flags::InputFile()));
if (rtp_reader.get() == NULL) {
rtp_reader.reset(test::RtpFileReader::Create(test::RtpFileReader::kPcap,
flags::InputFile()));
if (rtp_reader.get() == NULL) {
fprintf(stderr,
"Couldn't open input file as either a rtpdump or .pcap. Note "
"that .pcapng is not supported.\n");
return;
}
}
receive_stream->Start();
uint32_t last_time_ms = 0;
int num_packets = 0;
while (true) {
test::RtpFileReader::Packet packet;
if (!rtp_reader->NextPacket(&packet))
break;
++num_packets;
switch (call->Receiver()->DeliverPacket(packet.data, packet.length)) {
case PacketReceiver::DELIVERY_OK:
break;
case PacketReceiver::DELIVERY_UNKNOWN_SSRC: {
RTPHeader header;
scoped_ptr<RtpHeaderParser> parser(RtpHeaderParser::Create());
parser->Parse(packet.data, packet.length, &header);
fprintf(stderr, "Unknown SSRC: %u!\n", header.ssrc);
break;
}
case PacketReceiver::DELIVERY_PACKET_ERROR:
fprintf(stderr, "Packet error, corrupt packets or incorrect setup?\n");
break;
}
if (last_time_ms != 0 && last_time_ms != packet.time_ms) {
SleepMs(packet.time_ms - last_time_ms);
}
last_time_ms = packet.time_ms;
}
fprintf(stderr, "num_packets: %d\n", num_packets);
call->DestroyVideoReceiveStream(receive_stream);
}
} // namespace webrtc
int main(int argc, char* argv[]) {
::testing::InitGoogleTest(&argc, argv);
google::ParseCommandLineFlags(&argc, &argv, true);
webrtc::test::RunTest(webrtc::RtpReplay);
return 0;
}

View File

@ -13,6 +13,7 @@
'dependencies': [
'video_engine_tests',
'video_loopback',
'video_replay',
'webrtc_perf_tests',
],
},
@ -41,6 +42,31 @@
'webrtc',
],
},
{
'target_name': 'video_replay',
'type': 'executable',
'sources': [
'test/mac/run_test.mm',
'test/run_test.cc',
'test/run_test.h',
'video/replay.cc',
],
'conditions': [
['OS=="mac"', {
'sources!': [
'test/run_test.cc',
],
}],
],
'dependencies': [
'<(DEPTH)/testing/gtest.gyp:gtest',
'<(DEPTH)/third_party/gflags/gflags.gyp:gflags',
'system_wrappers/source/system_wrappers.gyp:field_trial_default',
'test/webrtc_test_common.gyp:webrtc_test_common',
'test/webrtc_test_common.gyp:webrtc_test_renderer',
'webrtc',
],
},
{
'target_name': 'video_engine_tests',
'type': '<(gtest_target_type)',