Add support for multiple streams to RtpPlayer:

- Tests video_rtp_play.cc, video_rtp_play_mt.cc, decode_from_storage.cc rewritten
 - rtp_player.cc/.h rewritten; added interfaces for externally setting up sinks
 - Support for reading .rtp files pulled out into rtp_file_reader namespace
 - Added support for reading .pcap (libpcap/wireshark/tcpdump) files, see pcap_file_reader

BUG=
TEST=trybots

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

git-svn-id: http://webrtc.googlecode.com/svn/trunk@3856 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
solenberg@webrtc.org 2013-04-16 10:31:56 +00:00
parent 885cd13356
commit 56b5f77a2b
26 changed files with 2079 additions and 1171 deletions

2
DEPS
View File

@ -14,7 +14,7 @@ vars = {
# External resources like video and audio files used for testing purposes.
# Downloaded on demand when needed.
"webrtc_resources_revision": "15",
"webrtc_resources_revision": "16",
}
# NOTE: Prefer revision numbers to tags for svn deps. Use http rather than

View File

@ -38,7 +38,7 @@ namespace webrtc {
#define VCM_RED_PAYLOAD_TYPE 96
#define VCM_ULPFEC_PAYLOAD_TYPE 97
#define VCM_VP8_PAYLOAD_TYPE 120
#define VCM_VP8_PAYLOAD_TYPE 100
#define VCM_I420_PAYLOAD_TYPE 124
enum VCMVideoProtection {

View File

@ -37,12 +37,15 @@
'../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',
'../test/vcm_payload_sink_factory.h',
'../test/video_source.h',
# sources
@ -54,12 +57,15 @@
'../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/receiver_timing_tests.cc',
'../test/rtp_file_reader.cc',
'../test/rtp_player.cc',
'../test/test_callbacks.cc',
'../test/test_util.cc',
'../test/tester_main.cc',
'../test/vcm_payload_sink_factory.cc',
'../test/video_rtp_play_mt.cc',
'../test/video_rtp_play.cc',
'../test/video_source.cc',
@ -83,6 +89,7 @@
'target_name': 'video_coding_unittests',
'type': 'executable',
'dependencies': [
'rtp_rtcp',
'video_codecs_test_framework',
'webrtc_video_coding',
'<(webrtc_root)/test/test.gyp:test_support_main',
@ -102,6 +109,10 @@
'video_coding_robustness_unittest.cc',
'video_coding_impl_unittest.cc',
'qm_select_unittest.cc',
'../test/pcap_file_reader.cc',
'../test/pcap_file_reader_unittest.cc',
'../test/rtp_file_reader.cc',
'../test/rtp_file_reader_unittest.cc',
'../../codecs/test/packet_manipulator_unittest.cc',
'../../codecs/test/stats_unittest.cc',
'../../codecs/test/videoprocessor_unittest.cc',

View File

@ -8,167 +8,76 @@
* be found in the AUTHORS file in the root of the source tree.
*/
#include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp.h"
#include "webrtc/modules/video_coding/main/interface/video_coding.h"
#include "webrtc/modules/video_coding/main/test/receiver_tests.h"
#include "webrtc/modules/video_coding/main/test/rtp_player.h"
#include "webrtc/system_wrappers/interface/clock.h"
#include "webrtc/modules/video_coding/main/test/vcm_payload_sink_factory.h"
#include "webrtc/system_wrappers/interface/trace.h"
#include "webrtc/test/testsupport/fileutils.h"
using namespace webrtc;
namespace {
class FrameStorageCallback : public VCMFrameStorageCallback
{
public:
FrameStorageCallback(VideoCodingModule* vcm) : _vcm(vcm) {}
const bool kConfigProtectionEnabled = false;
const webrtc::VCMVideoProtection kConfigProtectionMethod =
webrtc::kProtectionNack;
const float kConfigLossRate = 0.0f;
const bool kConfigReordering = false;
const uint32_t kConfigRttMs = 100;
const uint32_t kConfigRenderDelayMs = 0;
const uint32_t kConfigMinPlayoutDelayMs = 0;
const int64_t kConfigMaxRuntimeMs = -1;
int32_t StoreReceivedFrame(const EncodedVideoData& frameToStore)
{
_vcm->DecodeFromStorage(frameToStore);
return VCM_OK;
} // namespace
int DecodeFromStorageTest(const CmdArgs& args) {
std::string trace_file = webrtc::test::OutputPath() +
"decodeFromStorageTestTrace.txt";
webrtc::Trace::CreateTrace();
webrtc::Trace::SetTraceFile(trace_file.c_str());
webrtc::Trace::SetLevelFilter(webrtc::kTraceAll);
webrtc::rtpplayer::PayloadTypes payload_types;
payload_types.push_back(webrtc::rtpplayer::PayloadCodecTuple(
VCM_VP8_PAYLOAD_TYPE, "VP8", webrtc::kVideoCodecVP8));
std::string output_file = args.outputFile;
if (output_file == "") {
output_file = webrtc::test::OutputPath() + "DecodeFromStorage.yuv";
}
webrtc::SimulatedClock clock(0);
webrtc::rtpplayer::VcmPayloadSinkFactory factory(output_file, &clock,
kConfigProtectionEnabled, kConfigProtectionMethod, kConfigRttMs,
kConfigRenderDelayMs, kConfigMinPlayoutDelayMs, true);
webrtc::scoped_ptr<webrtc::rtpplayer::RtpPlayerInterface> rtp_player(
webrtc::rtpplayer::Create(args.inputFile, &factory, &clock, payload_types,
kConfigLossRate, kConfigRttMs, kConfigReordering));
if (rtp_player.get() == NULL) {
return -1;
}
int ret = 0;
while ((ret = rtp_player->NextPacket(clock.TimeInMilliseconds())) == 0) {
ret = factory.DecodeAndProcessAll(false);
if (ret < 0 || (kConfigMaxRuntimeMs > -1 &&
clock.TimeInMilliseconds() >= kConfigMaxRuntimeMs)) {
break;
}
clock.AdvanceTimeMilliseconds(1);
}
private:
VideoCodingModule* _vcm;
};
rtp_player->Print();
int DecodeFromStorageTest(CmdArgs& args)
{
// BEGIN Settings
bool protectionEnabled = false;
VCMVideoProtection protectionMethod = kProtectionNack;
uint32_t rttMS = 100;
float lossRate = 0.00f;
bool reordering = false;
uint32_t renderDelayMs = 0;
uint32_t minPlayoutDelayMs = 0;
const int64_t MAX_RUNTIME_MS = -1;
std::string rtpFilename = args.inputFile;
std::string outFilename = args.outputFile;
if (outFilename == "")
outFilename = test::OutputPath() + "DecodeFromStorage.yuv";
FrameReceiveCallback receiveCallback(outFilename.c_str());
// END Settings
Trace::CreateTrace();
Trace::SetTraceFile(
(test::OutputPath() + "decodeFromStorageTestTrace.txt").c_str());
Trace::SetLevelFilter(webrtc::kTraceAll);
SimulatedClock clock(0);
NullEventFactory event_factory;
// TODO(hlundin): This test was not verified after changing to FakeTickTime.
VideoCodingModule* vcm = VideoCodingModule::Create(1, &clock,
&event_factory);
VideoCodingModule* vcmPlayback = VideoCodingModule::Create(2, &clock,
&event_factory);
FrameStorageCallback storageCallback(vcmPlayback);
RtpDataCallback dataCallback(vcm);
int32_t ret = vcm->InitializeReceiver();
if (ret < 0)
{
return -1;
}
ret = vcmPlayback->InitializeReceiver();
if (ret < 0)
{
return -1;
}
vcm->RegisterFrameStorageCallback(&storageCallback);
vcmPlayback->RegisterReceiveCallback(&receiveCallback);
RTPPlayer rtpStream(rtpFilename.c_str(), &dataCallback, &clock);
PayloadTypeList payloadTypes;
payloadTypes.push_front(new PayloadCodecTuple(VCM_VP8_PAYLOAD_TYPE, "VP8",
kVideoCodecVP8));
// Register receive codecs in VCM
for (PayloadTypeList::iterator it = payloadTypes.begin();
it != payloadTypes.end(); ++it) {
PayloadCodecTuple* payloadType = *it;
if (payloadType != NULL)
{
VideoCodec codec;
memset(&codec, 0, sizeof(codec));
strncpy(codec.plName, payloadType->name.c_str(), payloadType->name.length());
codec.plName[payloadType->name.length()] = '\0';
codec.plType = payloadType->payloadType;
codec.codecType = payloadType->codecType;
if (vcm->RegisterReceiveCodec(&codec, 1) < 0)
{
return -1;
}
if (vcmPlayback->RegisterReceiveCodec(&codec, 1) < 0)
{
return -1;
}
}
}
if (rtpStream.Initialize(&payloadTypes) < 0)
{
return -1;
}
bool nackEnabled = protectionEnabled && (protectionMethod == kProtectionNack ||
protectionMethod == kProtectionDualDecoder);
rtpStream.SimulatePacketLoss(lossRate, nackEnabled, rttMS);
rtpStream.SetReordering(reordering);
vcm->SetChannelParameters(0, 0, rttMS);
vcm->SetVideoProtection(protectionMethod, protectionEnabled);
vcm->SetRenderDelay(renderDelayMs);
vcm->SetMinimumPlayoutDelay(minPlayoutDelayMs);
ret = 0;
// RTP stream main loop
while ((ret = rtpStream.NextPacket(clock.TimeInMilliseconds())) == 0)
{
if (clock.TimeInMilliseconds() % 5 == 0)
{
ret = vcm->Decode();
if (ret < 0)
{
return -1;
}
}
if (vcm->TimeUntilNextProcess() <= 0)
{
vcm->Process();
}
if (MAX_RUNTIME_MS > -1 && clock.TimeInMilliseconds() >= MAX_RUNTIME_MS)
{
break;
}
clock.AdvanceTimeMilliseconds(1);
}
switch (ret)
{
switch (ret) {
case 1:
printf("Success\n");
break;
printf("Success\n");
break;
case -1:
printf("Failed\n");
break;
printf("Failed\n");
break;
case 0:
printf("Timeout\n");
break;
}
printf("Timeout\n");
break;
}
rtpStream.Print();
// Tear down
while (!payloadTypes.empty())
{
delete payloadTypes.front();
payloadTypes.pop_front();
}
VideoCodingModule::Destroy(vcm);
vcm = NULL;
VideoCodingModule::Destroy(vcmPlayback);
vcmPlayback = NULL;
Trace::ReturnTrace();
return 0;
webrtc::Trace::ReturnTrace();
return 0;
}

View File

@ -18,6 +18,7 @@
#include "webrtc/modules/video_coding/main/interface/video_coding.h"
#include "webrtc/modules/video_coding/main/test/test_macros.h"
#include "webrtc/system_wrappers/interface/clock.h"
#include "webrtc/test/testsupport/fileutils.h"
using namespace webrtc;

View File

@ -21,6 +21,7 @@
#include "webrtc/modules/video_coding/main/interface/video_coding.h"
#include "webrtc/modules/video_coding/main/test/test_macros.h"
#include "webrtc/modules/video_coding/main/test/test_util.h"
#include "webrtc/test/testsupport/fileutils.h"
#include "webrtc/test/testsupport/metrics/video_metrics.h"
using namespace webrtc;

View File

@ -24,6 +24,7 @@
#include "webrtc/modules/video_coding/main/test/test_macros.h"
#include "webrtc/modules/video_coding/main/test/test_util.h"
#include "webrtc/system_wrappers/interface/thread_wrapper.h"
#include "webrtc/test/testsupport/fileutils.h"
using namespace webrtc;

View File

@ -21,9 +21,10 @@
#include "webrtc/modules/video_coding/main/test/test_callbacks.h"
#include "webrtc/modules/video_coding/main/test/test_macros.h"
#include "webrtc/modules/video_coding/main/test/test_util.h"
#include "webrtc/test/testsupport/metrics/video_metrics.h"
#include "webrtc/system_wrappers/interface/clock.h"
#include "webrtc/system_wrappers/interface/trace.h"
#include "webrtc/test/testsupport/fileutils.h"
#include "webrtc/test/testsupport/metrics/video_metrics.h"
using namespace webrtc;

View File

@ -14,6 +14,7 @@
#include "video_coding.h"
#include "test_util.h"
#include <fstream>
#include <map>
class NormalTest;

View File

@ -0,0 +1,463 @@
/*
* 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/pcap_file_reader.h"
#ifdef WIN32
#include <windows.h>
#include <Winsock2.h>
#else
#include <arpa/inet.h>
#endif
#include <cassert>
#include <cstdio>
#include <map>
#include <string>
#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 {
enum {
kResultFail = -1,
kResultSuccess = 0,
kResultSkip = 1,
kPcapVersionMajor = 2,
kPcapVersionMinor = 4,
kLinktypeNull = 0,
kLinktypeEthernet = 1,
kBsdNullLoopback1 = 0x00000002,
kBsdNullLoopback2 = 0x02000000,
kEthernetIIHeaderMacSkip = 12,
kEthertypeIp = 0x0800,
kIpVersion4 = 4,
kMinIpHeaderLength = 20,
kFragmentOffsetClear = 0x0000,
kFragmentOffsetDoNotFragment = 0x4000,
kProtocolTcp = 0x06,
kProtocolUdp = 0x11,
kUdpHeaderLength = 8,
kMaxReadBufferSize = 4096
};
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; \
} \
} while (0)
// Read RTP packets from file in tcpdump/libpcap format, as documented at:
// http://wiki.wireshark.org/Development/LibpcapFileFormat
class PcapFileReaderImpl : public RtpPacketSourceInterface {
public:
PcapFileReaderImpl()
: file_(NULL),
swap_pcap_byte_order_(false),
swap_network_byte_order_(false),
read_buffer_(),
packets_by_ssrc_(),
packets_(),
next_packet_it_() {
int16_t test = 0x7f00;
if (test != htons(test)) {
swap_network_byte_order_ = true;
}
}
virtual ~PcapFileReaderImpl() {
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;
}
if (ReadGlobalHeader() < 0) {
return kResultFail;
}
int total_packet_count = 0;
uint32_t stream_start_ms = 0;
int32_t next_packet_pos = ftell(file_);
for (;;) {
++total_packet_count;
TRY(fseek(file_, next_packet_pos, SEEK_SET));
int result = ReadPacket(&next_packet_pos, stream_start_ms,
total_packet_count);
if (result == kResultFail) {
break;
} else if (result == kResultSuccess && packets_.size() == 1) {
assert(stream_start_ms == 0);
PacketIterator it = packets_.begin();
stream_start_ms = it->time_offset_ms;
it->time_offset_ms = 0;
}
}
if (feof(file_) == 0) {
printf("Failed reading file!\n");
return kResultFail;
}
printf("Total packets in file: %d\n", total_packet_count);
printf("Total RTP/RTCP packets: %d\n", static_cast<int>(packets_.size()));
for (SsrcMapIterator mit = packets_by_ssrc_.begin();
mit != packets_by_ssrc_.end(); ++mit) {
uint32_t ssrc = mit->first;
const std::vector<uint32_t>& packet_numbers = mit->second;
printf("SSRC: %08x, %d packets\n", ssrc,
static_cast<int>(packet_numbers.size()));
}
// TODO(solenberg): Better validation of identified SSRC streams.
//
// Since we're dealing with raw network data here, we will wrongly identify
// some packets as RTP. When these packets are consumed by RtpPlayer, they
// are unlikely to cause issues as they will ultimately be filtered out by
// the RtpRtcp module. However, we should really do better filtering here,
// which we can accomplish in a number of ways, e.g.:
//
// - Verify that the time stamps and sequence numbers for RTP packets are
// both increasing/decreasing. If they move in different directions, the
// SSRC is likely bogus and can be dropped. (Normally they should be inc-
// reasing but we must allow packet reordering).
// - If RTP sequence number is not changing, drop the stream.
// - Can also use srcip:port->dstip:port pairs, assuming few SSRC collisions
// for up/down streams.
next_packet_it_ = packets_.begin();
return kResultSuccess;
}
virtual int NextPacket(uint8_t* data, uint32_t* length, uint32_t* time_ms) {
assert(data);
assert(length);
assert(time_ms);
if (next_packet_it_ == packets_.end()) {
return -1;
}
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));
*length = next_packet_it_->payload_length;
*time_ms = next_packet_it_->time_offset_ms;
next_packet_it_++;
return 0;
}
private:
// A marker of an RTP packet within the file.
struct RtpPacketMarker {
uint32_t packet_number;
uint32_t time_offset_ms;
uint32_t source_ip;
uint32_t dest_ip;
uint16_t source_port;
uint16_t dest_port;
WebRtcRTPHeader rtp_header;
int32_t pos_in_file;
uint32_t payload_length;
};
typedef std::vector<RtpPacketMarker>::iterator PacketIterator;
typedef std::map<uint32_t, std::vector<uint32_t> > SsrcMap;
typedef std::map<uint32_t, std::vector<uint32_t> >::iterator SsrcMapIterator;
int ReadGlobalHeader() {
uint32_t magic;
TRY(Read(&magic, false));
if (magic == kPcapBOMSwapOrder) {
swap_pcap_byte_order_ = true;
} else if (magic == kPcapBOMNoSwapOrder) {
swap_pcap_byte_order_ = false;
} else {
return kResultFail;
}
uint16_t version_major;
uint16_t version_minor;
TRY(Read(&version_major, false));
TRY(Read(&version_minor, false));
if (version_major != kPcapVersionMajor ||
version_minor != kPcapVersionMinor) {
return kResultFail;
}
int32_t this_zone; // GMT to local correction.
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));
// Accept only LINKTYPE_NULL and LINKTYPE_ETHERNET.
// See: http://www.tcpdump.org/linktypes.html
if (network != kLinktypeNull && network != kLinktypeEthernet) {
return kResultFail;
}
return kResultSuccess;
}
int ReadPacket(int32_t* next_packet_pos, uint32_t stream_start_ms,
uint32_t number) {
assert(next_packet_pos);
uint32_t ts_sec; // Timestamp seconds.
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));
*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));
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));
ModuleRTPUtility::RTPHeaderParser rtp_parser(read_buffer_,
marker.payload_length);
if (rtp_parser.RTCP()) {
packets_.push_back(marker);
} else {
if (!rtp_parser.Parse(marker.rtp_header, NULL)) {
DEBUG_LOG("Not recognized as RTP/RTCP");
return kResultSkip;
}
uint32_t ssrc = marker.rtp_header.header.ssrc;
packets_by_ssrc_[ssrc].push_back(marker.packet_number);
packets_.push_back(marker);
}
return kResultSuccess;
}
int ReadPacketHeader(RtpPacketMarker* marker) {
int32_t file_pos = ftell(file_);
// Check for BSD null/loopback frame header. The header is just 4 bytes in
// native byte order, so we check for both versions as we don't care about
// 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));
if (protocol == kBsdNullLoopback1 || protocol == kBsdNullLoopback2) {
int result = ReadXxpIpHeader(marker);
DEBUG_LOG("Recognized loopback frame");
if (result != kResultSkip) {
return result;
}
}
TRY(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));
if (type == kEthertypeIp) {
int result = ReadXxpIpHeader(marker);
DEBUG_LOG("Recognized ethernet 2 frame");
if (result != kResultSkip) {
return result;
}
}
return kResultSkip;
}
uint32_t CalcTimeDelta(uint32_t ts_sec, uint32_t ts_usec, uint32_t start_ms) {
// Round to nearest ms.
uint64_t t2_ms = ((static_cast<uint64_t>(ts_sec) * 1000000) + ts_usec +
500) / 1000;
uint64_t t1_ms = static_cast<uint64_t>(start_ms);
if (t2_ms < t1_ms) {
return 0;
} else {
return t2_ms - t1_ms;
}
}
int ReadXxpIpHeader(RtpPacketMarker* marker) {
assert(marker);
uint16_t version;
uint16_t length;
uint16_t id;
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));
if (((version >> 12) & 0x000f) != kIpVersion4) {
DEBUG_LOG("IP header is not IPv4");
return kResultSkip;
}
if (fragment != kFragmentOffsetClear &&
fragment != kFragmentOffsetDoNotFragment) {
DEBUG_LOG("IP fragments cannot be handled");
return kResultSkip;
}
// Skip remaining fields of IP header.
uint16_t header_length = (version & 0x0f00) >> (8 - 2);
assert(header_length >= kMinIpHeaderLength);
TRY(Skip(header_length - kMinIpHeaderLength));
protocol = protocol & 0x00ff;
if (protocol == kProtocolTcp) {
DEBUG_LOG("TCP packets are not handled");
return kResultSkip;
} 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));
marker->payload_length = length - kUdpHeaderLength;
} else {
DEBUG_LOG("Unknown transport (expected UDP or TCP)");
return kResultSkip;
}
return kResultSuccess;
}
int Read(uint32_t* out, bool expect_network_order) {
uint32_t tmp = 0;
if (fread(&tmp, 1, sizeof(uint32_t), file_) != sizeof(uint32_t)) {
return kResultFail;
}
if ((!expect_network_order && swap_pcap_byte_order_) ||
(expect_network_order && swap_network_byte_order_)) {
tmp = ((tmp >> 24) & 0x000000ff) | (tmp << 24) |
((tmp >> 8) & 0x0000ff00) | ((tmp << 8) & 0x00ff0000);
}
*out = tmp;
return kResultSuccess;
}
int Read(uint16_t* out, bool expect_network_order) {
uint16_t tmp = 0;
if (fread(&tmp, 1, sizeof(uint16_t), file_) != sizeof(uint16_t)) {
return kResultFail;
}
if ((!expect_network_order && swap_pcap_byte_order_) ||
(expect_network_order && swap_network_byte_order_)) {
tmp = ((tmp >> 8) & 0x00ff) | (tmp << 8);
}
*out = tmp;
return kResultSuccess;
}
int Read(uint8_t* out, uint32_t count) {
if (fread(out, 1, count, file_) != count) {
return kResultFail;
}
return kResultSuccess;
}
int Read(int32_t* out, bool expect_network_order) {
int32_t tmp = 0;
if (fread(&tmp, 1, sizeof(uint32_t), file_) != sizeof(uint32_t)) {
return kResultFail;
}
if ((!expect_network_order && swap_pcap_byte_order_) ||
(expect_network_order && swap_network_byte_order_)) {
tmp = ((tmp >> 24) & 0x000000ff) | (tmp << 24) |
((tmp >> 8) & 0x0000ff00) | ((tmp << 8) & 0x00ff0000);
}
*out = tmp;
return kResultSuccess;
}
int Skip(uint32_t length) {
if (fseek(file_, length, SEEK_CUR) != 0) {
return kResultFail;
}
return kResultSuccess;
}
FILE* file_;
bool swap_pcap_byte_order_;
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);
};
RtpPacketSourceInterface* CreatePcapFileReader(const std::string& filename) {
scoped_ptr<PcapFileReaderImpl> impl(new PcapFileReaderImpl());
if (impl->Initialize(filename) != 0) {
return NULL;
}
return impl.release();
}
} // namespace rtpplayer
} // namespace webrtc

View File

@ -0,0 +1,26 @@
/*
* 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

@ -0,0 +1,99 @@
/*
* 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 <map>
#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/testsupport/fileutils.h"
namespace webrtc {
namespace rtpplayer {
typedef std::map<uint32_t, int> PacketsPerSsrc;
class TestPcapFileReader : public ::testing::Test {
public:
void Init(const std::string& filename) {
std::string filepath =
test::ResourcePath("video_coding/" + filename, "pcap");
rtp_packet_source_.reset(CreatePcapFileReader(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;
}
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;
ModuleRTPUtility::RTPHeaderParser rtp_header_parser(data, length);
webrtc::WebRtcRTPHeader header;
if (!rtp_header_parser.RTCP() && rtp_header_parser.Parse(header, NULL)) {
pps[header.header.ssrc]++;
}
}
return pps;
}
private:
scoped_ptr<RtpPacketSourceInterface> rtp_packet_source_;
};
TEST_F(TestPcapFileReader, TestEthernetIIFrame) {
Init("frame-ethernet-ii");
EXPECT_EQ(368, CountRtpPackets());
}
TEST_F(TestPcapFileReader, TestLoopbackFrame) {
Init("frame-loopback");
EXPECT_EQ(491, CountRtpPackets());
}
TEST_F(TestPcapFileReader, TestTwoSsrc) {
Init("ssrcs-2");
PacketsPerSsrc pps = CountRtpPacketsPerSsrc();
EXPECT_EQ(2UL, pps.size());
EXPECT_EQ(370, pps[0x78d48f61]);
EXPECT_EQ(60, pps[0xae94130b]);
}
TEST_F(TestPcapFileReader, TestThreeSsrc) {
Init("ssrcs-3");
PacketsPerSsrc pps = CountRtpPacketsPerSsrc();
EXPECT_EQ(3UL, pps.size());
EXPECT_EQ(162, pps[0x938c5eaa]);
EXPECT_EQ(113, pps[0x59fe6ef0]);
EXPECT_EQ(61, pps[0xed2bd2ac]);
}
} // namespace rtpplayer
} // namespace webrtc

View File

@ -23,6 +23,7 @@
#include "webrtc/system_wrappers/interface/clock.h"
#include "webrtc/system_wrappers/interface/data_log.h"
#include "webrtc/system_wrappers/interface/data_log.h"
#include "webrtc/test/testsupport/fileutils.h"
#include "webrtc/test/testsupport/metrics/video_metrics.h"
using namespace webrtc;

View File

@ -16,73 +16,32 @@
#include "common_types.h"
#include "rtp_rtcp.h"
#include "typedefs.h"
#include "rtp_player.h"
#include "test_util.h"
#include <string>
#include <stdio.h>
class RtpDataCallback : public webrtc::RtpData
{
public:
RtpDataCallback(webrtc::VideoCodingModule* vcm)
: _vcm(vcm) {};
class RtpDataCallback : public webrtc::RtpData {
public:
RtpDataCallback(webrtc::VideoCodingModule* vcm) : vcm_(vcm) {}
virtual ~RtpDataCallback() {}
virtual int32_t OnReceivedPayloadData(const uint8_t* payloadData,
const uint16_t payloadSize,
const webrtc::WebRtcRTPHeader* rtpHeader);
private:
webrtc::VideoCodingModule* _vcm;
virtual WebRtc_Word32 OnReceivedPayloadData(
const WebRtc_UWord8* payload_data,
const WebRtc_UWord16 payload_size,
const webrtc::WebRtcRTPHeader* rtp_header) {
return vcm_->IncomingPacket(payload_data, payload_size, *rtp_header);
}
private:
webrtc::VideoCodingModule* vcm_;
};
class FrameReceiveCallback : public webrtc::VCMReceiveCallback
{
public:
FrameReceiveCallback(std::string outFilename) :
_outFilename(outFilename),
_outFile(NULL),
_timingFile(NULL),
width_(0),
height_(0),
count_(0) {}
virtual ~FrameReceiveCallback();
int32_t FrameToRender(webrtc::I420VideoFrame& videoFrame);
private:
static void SplitFilename(std::string filename, std::string* basename,
std::string* ending);
static std::string AppendWidthHeightAndCount(std::string basename,
unsigned int width,
unsigned int height,
int count);
std::string _outFilename;
FILE* _outFile;
FILE* _timingFile;
unsigned int width_;
unsigned int height_;
int count_;
};
class SharedState
{
public:
SharedState(webrtc::VideoCodingModule& vcm, RTPPlayer& rtpPlayer) :
_vcm(vcm),
_rtpPlayer(rtpPlayer) {}
webrtc::VideoCodingModule& _vcm;
RTPPlayer& _rtpPlayer;
};
int RtpPlay(CmdArgs& args);
int RtpPlayMT(CmdArgs& args,
int releaseTest = 0,
webrtc::VideoCodecType releaseTestVideoType = webrtc::kVideoCodecVP8);
int RtpPlay(const CmdArgs& args);
int RtpPlayMT(const CmdArgs& args);
int ReceiverTimingTests(CmdArgs& args);
int JitterBufferTest(CmdArgs& args);
int DecodeFromStorageTest(CmdArgs& args);
int DecodeFromStorageTest(const CmdArgs& args);
// Thread functions:
bool ProcessingThread(void* obj);

View File

@ -19,6 +19,7 @@
#include "webrtc/modules/video_coding/main/test/test_macros.h"
#include "webrtc/modules/video_coding/main/test/test_util.h"
#include "webrtc/system_wrappers/interface/trace.h"
#include "webrtc/test/testsupport/fileutils.h"
using namespace webrtc;

View File

@ -0,0 +1,166 @@
/*
* 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 <cassert>
#include <cstdio>
#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

@ -0,0 +1,26 @@
/*
* 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

@ -0,0 +1,54 @@
/*
* 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

@ -10,435 +10,479 @@
#include "webrtc/modules/video_coding/main/test/rtp_player.h"
#include <cstdlib>
#ifdef WIN32
#include <windows.h>
#include <Winsock2.h>
#else
#include <arpa/inet.h>
#endif
#include <cstdio>
#include <map>
#include "testing/gtest/include/gtest/gtest.h"
#include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp.h"
#include "webrtc/modules/rtp_rtcp/source/rtp_utility.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"
using namespace webrtc;
#if 1
# define DEBUG_LOG1(text, arg)
#else
# define DEBUG_LOG1(text, arg) (printf(text "\n", arg))
#endif
RawRtpPacket::RawRtpPacket(uint8_t* rtp_data, uint16_t rtp_length)
: data(rtp_data),
length(rtp_length),
resend_time_ms(-1) {
data = new uint8_t[length];
memcpy(data, rtp_data, length);
}
namespace webrtc {
namespace rtpplayer {
RawRtpPacket::~RawRtpPacket() {
delete [] data;
}
enum {
kMaxPacketBufferSize = 4096,
kDefaultTransmissionTimeOffsetExtensionId = 2
};
LostPackets::LostPackets()
: crit_sect_(CriticalSectionWrapper::CreateCriticalSection()),
loss_count_(0),
debug_file_(NULL),
packets_() {
debug_file_ = fopen("PacketLossDebug.txt", "w");
}
class RawRtpPacket {
public:
RawRtpPacket(const uint8_t* data, uint32_t length, uint32_t ssrc,
uint16_t seq_num)
: data_(new uint8_t[length]),
length_(length),
resend_time_ms_(-1),
ssrc_(ssrc),
seq_num_(seq_num) {
assert(data);
memcpy(data_.get(), data, length_);
}
LostPackets::~LostPackets() {
if (debug_file_) {
const uint8_t* data() const { return data_.get(); }
uint32_t length() const { return length_; }
int64_t resend_time_ms() const { return resend_time_ms_; }
void set_resend_time_ms(int64_t timeMs) { resend_time_ms_ = timeMs; }
uint32_t ssrc() const { return ssrc_; }
uint16_t seq_num() const { return seq_num_; }
private:
scoped_array<uint8_t> data_;
uint32_t length_;
int64_t resend_time_ms_;
uint32_t ssrc_;
uint16_t seq_num_;
DISALLOW_IMPLICIT_CONSTRUCTORS(RawRtpPacket);
};
class LostPackets {
public:
LostPackets(Clock* clock, uint32_t rtt_ms)
: crit_sect_(CriticalSectionWrapper::CreateCriticalSection()),
debug_file_(fopen("PacketLossDebug.txt", "w")),
loss_count_(0),
packets_(),
clock_(clock),
rtt_ms_(rtt_ms) {
assert(clock);
}
~LostPackets() {
if (debug_file_) {
fclose(debug_file_);
debug_file_ = NULL;
}
while (!packets_.empty()) {
delete packets_.back();
packets_.pop_back();
}
}
while (!packets_.empty()) {
delete packets_.front();
packets_.pop_front();
}
delete crit_sect_;
}
void LostPackets::AddPacket(RawRtpPacket* packet) {
CriticalSectionScoped cs(crit_sect_);
packets_.push_back(packet);
uint16_t seq_num = (packet->data[2] << 8) + packet->data[3];
if (debug_file_ != NULL) {
fprintf(debug_file_, "%u Lost packet: %u\n", loss_count_, seq_num);
void AddPacket(RawRtpPacket* packet) {
assert(packet);
printf("Throw: %08x:%u\n", packet->ssrc(), packet->seq_num());
CriticalSectionScoped cs(crit_sect_.get());
if (debug_file_) {
fprintf(debug_file_, "%u Lost packet: %u\n", loss_count_,
packet->seq_num());
}
packets_.push_back(packet);
loss_count_++;
}
++loss_count_;
}
void LostPackets::SetResendTime(uint16_t resend_seq_num,
int64_t resend_time_ms,
int64_t now_ms) {
CriticalSectionScoped cs(crit_sect_);
for (RtpPacketIterator it = packets_.begin(); it != packets_.end(); ++it) {
const uint16_t seq_num = ((*it)->data[2] << 8) +
(*it)->data[3];
if (resend_seq_num == seq_num) {
if ((*it)->resend_time_ms + 10 < now_ms) {
if (debug_file_ != NULL) {
fprintf(debug_file_, "Resend %u at %u\n", seq_num,
void SetResendTime(uint32_t ssrc, int16_t resendSeqNum) {
int64_t resend_time_ms = clock_->TimeInMilliseconds() + rtt_ms_;
int64_t now_ms = clock_->TimeInMilliseconds();
CriticalSectionScoped cs(crit_sect_.get());
for (RtpPacketIterator it = packets_.begin(); it != packets_.end(); ++it) {
RawRtpPacket* packet = *it;
if (ssrc == packet->ssrc() && resendSeqNum == packet->seq_num() &&
packet->resend_time_ms() + 10 < now_ms) {
if (debug_file_) {
fprintf(debug_file_, "Resend %u at %u\n", packet->seq_num(),
MaskWord64ToUWord32(resend_time_ms));
}
(*it)->resend_time_ms = resend_time_ms;
packet->set_resend_time_ms(resend_time_ms);
return;
}
return;
}
// We may get here since the captured stream may itself be missing packets.
}
assert(false);
}
RawRtpPacket* LostPackets::NextPacketToResend(int64_t timeNow) {
CriticalSectionScoped cs(crit_sect_);
for (RtpPacketIterator it = packets_.begin(); it != packets_.end(); ++it) {
if (timeNow >= (*it)->resend_time_ms && (*it)->resend_time_ms != -1) {
RawRtpPacket* NextPacketToResend(int64_t time_now) {
CriticalSectionScoped cs(crit_sect_.get());
for (RtpPacketIterator it = packets_.begin(); it != packets_.end(); ++it) {
RawRtpPacket* packet = *it;
it = packets_.erase(it);
return packet;
if (time_now >= packet->resend_time_ms() &&
packet->resend_time_ms() != -1) {
packets_.erase(it);
return packet;
}
}
return NULL;
}
return NULL;
}
int LostPackets::NumberOfPacketsToResend() const {
CriticalSectionScoped cs(crit_sect_);
int count = 0;
for (ConstRtpPacketIterator it = packets_.begin(); it != packets_.end();
++it) {
if ((*it)->resend_time_ms >= 0) {
int NumberOfPacketsToResend() const {
CriticalSectionScoped cs(crit_sect_.get());
int count = 0;
for (ConstRtpPacketIterator it = packets_.begin(); it != packets_.end();
++it) {
if ((*it)->resend_time_ms() >= 0) {
count++;
}
}
return count;
}
void LogPacketResent(RawRtpPacket* packet) {
int64_t now_ms = clock_->TimeInMilliseconds();
CriticalSectionScoped cs(crit_sect_.get());
if (debug_file_) {
fprintf(debug_file_, "Resent %u at %u\n", packet->seq_num(),
MaskWord64ToUWord32(now_ms));
}
}
return count;
}
void LostPackets::SetPacketResent(uint16_t seq_num, int64_t now_ms) {
CriticalSectionScoped cs(crit_sect_);
if (debug_file_ != NULL) {
fprintf(debug_file_, "Resent %u at %u\n", seq_num,
MaskWord64ToUWord32(now_ms));
}
}
void LostPackets::Print() const {
CriticalSectionScoped cs(crit_sect_);
printf("Lost packets: %u\n", loss_count_);
printf("Packets waiting to be resent: %u\n",
NumberOfPacketsToResend());
printf("Packets still lost: %u\n",
static_cast<unsigned int>(packets_.size()));
printf("Sequence numbers:\n");
for (ConstRtpPacketIterator it = packets_.begin(); it != packets_.end();
++it) {
uint16_t seq_num = ((*it)->data[2] << 8) + (*it)->data[3];
printf("%u, ", seq_num);
}
printf("\n");
}
RTPPlayer::RTPPlayer(const char* filename,
RtpData* callback,
Clock* clock)
:
_clock(clock),
_rtpModule(NULL),
_nextRtpTime(0),
_dataCallback(callback),
_firstPacket(true),
_lossRate(0.0f),
_nackEnabled(false),
_resendPacketCount(0),
_noLossStartup(100),
_endOfFile(false),
_rttMs(0),
_firstPacketRtpTime(0),
_firstPacketTimeMs(0),
_reorderBuffer(NULL),
_reordering(false),
_nextPacket(),
_nextPacketLength(0),
_randVec(),
_randVecPos(0)
{
_rtpFile = fopen(filename, "rb");
memset(_nextPacket, 0, sizeof(_nextPacket));
}
RTPPlayer::~RTPPlayer()
{
delete _rtpModule;
if (_rtpFile != NULL)
{
fclose(_rtpFile);
void Print() const {
CriticalSectionScoped cs(crit_sect_.get());
printf("Lost packets: %u\n", loss_count_);
printf("Packets waiting to be resent: %d\n", NumberOfPacketsToResend());
printf("Packets still lost: %zd\n", packets_.size());
printf("Sequence numbers:\n");
for (ConstRtpPacketIterator it = packets_.begin(); it != packets_.end();
++it) {
printf("%u, ", (*it)->seq_num());
}
printf("\n");
}
private:
typedef std::vector<RawRtpPacket*> RtpPacketList;
typedef RtpPacketList::iterator RtpPacketIterator;
typedef RtpPacketList::const_iterator ConstRtpPacketIterator;
scoped_ptr<CriticalSectionWrapper> crit_sect_;
FILE* debug_file_;
int loss_count_;
RtpPacketList packets_;
Clock* clock_;
uint32_t rtt_ms_;
DISALLOW_IMPLICIT_CONSTRUCTORS(LostPackets);
};
class SsrcHandlers {
public:
SsrcHandlers(PayloadSinkFactoryInterface* payload_sink_factory,
const PayloadTypes& payload_types)
: payload_sink_factory_(payload_sink_factory),
payload_types_(payload_types),
handlers_() {
assert(payload_sink_factory);
}
~SsrcHandlers() {
while (!handlers_.empty()) {
delete handlers_.begin()->second;
handlers_.erase(handlers_.begin());
}
}
int RegisterSsrc(uint32_t ssrc, LostPackets* lost_packets) {
if (handlers_.count(ssrc) > 0) {
return 0;
}
DEBUG_LOG1("Registering handler for ssrc=%08x", ssrc);
scoped_ptr<Handler> handler(
new Handler(ssrc, payload_types_, lost_packets));
handler->payload_sink_.reset(payload_sink_factory_->Create(handler.get()));
if (handler->payload_sink_.get() == NULL) {
return -1;
}
if (_reorderBuffer != NULL)
{
delete _reorderBuffer;
_reorderBuffer = NULL;
}
}
int32_t RTPPlayer::Initialize(const PayloadTypeList* payloadList)
{
RtpRtcp::Configuration configuration;
configuration.id = 1;
configuration.audio = false;
configuration.incoming_data = _dataCallback;
_rtpModule = RtpRtcp::CreateRtpRtcp(configuration);
configuration.incoming_data = handler->payload_sink_.get();
handler->rtp_module_.reset(RtpRtcp::CreateRtpRtcp(configuration));
if (handler->rtp_module_.get() == NULL) {
return -1;
}
std::srand(321);
for (int i=0; i < RAND_VEC_LENGTH; i++)
{
_randVec[i] = rand();
if (handler->rtp_module_->SetNACKStatus(kNackOff,
kMaxPacketAgeToNack) < 0) {
return -1;
}
_randVecPos = 0;
int32_t ret = _rtpModule->SetNACKStatus(kNackOff,
kMaxPacketAgeToNack);
if (ret < 0)
{
return -1;
}
_rtpModule->SetRTCPStatus(kRtcpNonCompound);
_rtpModule->SetTMMBRStatus(true);
handler->rtp_module_->SetRTCPStatus(kRtcpNonCompound);
handler->rtp_module_->SetREMBStatus(true);
handler->rtp_module_->SetSSRCFilter(true, ssrc);
handler->rtp_module_->RegisterReceiveRtpHeaderExtension(
kRtpExtensionTransmissionTimeOffset,
kDefaultTransmissionTimeOffsetExtensionId);
if (ret < 0)
{
for (PayloadTypesIterator it = payload_types_.begin();
it != payload_types_.end(); ++it) {
VideoCodec codec;
memset(&codec, 0, sizeof(codec));
strncpy(codec.plName, it->name().c_str(), sizeof(codec.plName)-1);
codec.plType = it->payload_type();
codec.codecType = it->codec_type();
if (handler->rtp_module_->RegisterReceivePayload(codec) < 0) {
return -1;
}
}
// Register payload types
for (PayloadTypeList::const_iterator it = payloadList->begin();
it != payloadList->end(); ++it) {
PayloadCodecTuple* payloadType = *it;
if (payloadType != NULL)
{
VideoCodec videoCodec;
strncpy(videoCodec.plName, payloadType->name.c_str(), 32);
videoCodec.plType = payloadType->payloadType;
if (_rtpModule->RegisterReceivePayload(videoCodec) < 0)
{
return -1;
}
}
}
if (ReadHeader() < 0)
{
return -1;
}
memset(_nextPacket, 0, sizeof(_nextPacket));
_nextPacketLength = ReadPacket(_nextPacket, &_nextRtpTime);
handlers_[ssrc] = handler.release();
return 0;
}
}
int32_t RTPPlayer::ReadHeader()
{
char firstline[FIRSTLINELEN];
if (_rtpFile == NULL)
{
return -1;
void Process() {
for (HandlerMapIt it = handlers_.begin(); it != handlers_.end(); ++it) {
it->second->rtp_module_->Process();
}
EXPECT_TRUE(fgets(firstline, FIRSTLINELEN, _rtpFile) != NULL);
if(strncmp(firstline,"#!rtpplay",9) == 0) {
if(strncmp(firstline,"#!rtpplay1.0",12) != 0){
printf("ERROR: wrong rtpplay version, must be 1.0\n");
return -1;
}
}
void IncomingPacket(const uint8_t* data, uint32_t length) {
for (HandlerMapIt it = handlers_.begin(); it != handlers_.end(); ++it) {
it->second->rtp_module_->IncomingPacket(data, length);
}
else if (strncmp(firstline,"#!RTPencode",11) == 0) {
if(strncmp(firstline,"#!RTPencode1.0",14) != 0){
printf("ERROR: wrong RTPencode version, must be 1.0\n");
return -1;
}
}
private:
class Handler : public RtpStreamInterface {
public:
Handler(uint32_t ssrc, const PayloadTypes& payload_types,
LostPackets* lost_packets)
: rtp_module_(),
payload_sink_(),
ssrc_(ssrc),
payload_types_(payload_types),
lost_packets_(lost_packets) {
assert(lost_packets);
}
else {
printf("ERROR: wrong file format of input file\n");
return -1;
virtual ~Handler() {}
virtual void ResendPackets(const uint16_t* sequence_numbers,
uint16_t length) {
assert(sequence_numbers);
for (uint16_t i = 0; i < length; i++) {
lost_packets_->SetResendTime(ssrc_, sequence_numbers[i]);
}
}
uint32_t start_sec;
uint32_t start_usec;
uint32_t source;
uint16_t port;
uint16_t padding;
EXPECT_GT(fread(&start_sec, 4, 1, _rtpFile), 0u);
start_sec=ntohl(start_sec);
EXPECT_GT(fread(&start_usec, 4, 1, _rtpFile), 0u);
start_usec=ntohl(start_usec);
EXPECT_GT(fread(&source, 4, 1, _rtpFile), 0u);
source=ntohl(source);
EXPECT_GT(fread(&port, 2, 1, _rtpFile), 0u);
port=ntohs(port);
EXPECT_GT(fread(&padding, 2, 1, _rtpFile), 0u);
padding=ntohs(padding);
return 0;
}
uint32_t RTPPlayer::TimeUntilNextPacket() const
{
int64_t timeLeft = (_nextRtpTime - _firstPacketRtpTime) -
(_clock->TimeInMilliseconds() - _firstPacketTimeMs);
if (timeLeft < 0)
{
return 0;
virtual uint32_t ssrc() const { return ssrc_; }
virtual const PayloadTypes& payload_types() const {
return payload_types_;
}
return static_cast<uint32_t>(timeLeft);
}
int32_t RTPPlayer::NextPacket(const int64_t timeNow)
{
// Send any packets ready to be resent,
RawRtpPacket* resend_packet = _lostPackets.NextPacketToResend(timeNow);
while (resend_packet != NULL) {
const uint16_t seqNo = (resend_packet->data[2] << 8) +
resend_packet->data[3];
printf("Resend: %u\n", seqNo);
int ret = SendPacket(resend_packet->data, resend_packet->length);
delete resend_packet;
_resendPacketCount++;
scoped_ptr<RtpRtcp> rtp_module_;
scoped_ptr<PayloadSinkInterface> payload_sink_;
private:
uint32_t ssrc_;
const PayloadTypes& payload_types_;
LostPackets* lost_packets_;
DISALLOW_COPY_AND_ASSIGN(Handler);
};
typedef std::map<uint32_t, Handler*> HandlerMap;
typedef std::map<uint32_t, Handler*>::iterator HandlerMapIt;
PayloadSinkFactoryInterface* payload_sink_factory_;
PayloadTypes payload_types_;
HandlerMap handlers_;
DISALLOW_IMPLICIT_CONSTRUCTORS(SsrcHandlers);
};
class RtpPlayerImpl : public RtpPlayerInterface {
public:
RtpPlayerImpl(PayloadSinkFactoryInterface* payload_sink_factory,
const PayloadTypes& payload_types, Clock* clock,
scoped_ptr<RtpPacketSourceInterface>* packet_source,
float loss_rate, uint32_t rtt_ms, bool reordering)
: ssrc_handlers_(payload_sink_factory, payload_types),
clock_(clock),
packet_source_(NULL),
next_rtp_time_(0),
first_packet_(true),
first_packet_rtp_time_(0),
first_packet_time_ms_(0),
loss_rate_(loss_rate),
lost_packets_(clock, rtt_ms),
resend_packet_count_(0),
no_loss_startup_(100),
end_of_file_(false),
reordering_(false),
reorder_buffer_(),
next_packet_(),
next_packet_length_(0) {
assert(clock);
assert(packet_source);
assert(packet_source->get());
packet_source_.swap(*packet_source);
srand(321);
}
virtual ~RtpPlayerImpl() {}
virtual int NextPacket(int64_t time_now) {
// Send any packets ready to be resent.
RawRtpPacket* packet;
while ((packet = lost_packets_.NextPacketToResend(time_now))) {
int ret = SendPacket(packet->data(), packet->length());
if (ret > 0) {
_lostPackets.SetPacketResent(seqNo, _clock->TimeInMilliseconds());
} else if (ret < 0) {
printf("Resend: %08x:%u\n", packet->ssrc(), packet->seq_num());
lost_packets_.LogPacketResent(packet);
resend_packet_count_++;
}
delete packet;
if (ret < 0) {
return ret;
}
resend_packet = _lostPackets.NextPacketToResend(timeNow);
}
// Send any packets from rtp file
if (!_endOfFile && (TimeUntilNextPacket() == 0 || _firstPacket))
{
_rtpModule->Process();
if (_firstPacket)
{
_firstPacketRtpTime = static_cast<int64_t>(_nextRtpTime);
_firstPacketTimeMs = _clock->TimeInMilliseconds();
// Send any packets from packet source.
if (!end_of_file_ && (TimeUntilNextPacket() == 0 || first_packet_)) {
ssrc_handlers_.Process();
if (first_packet_) {
next_packet_length_ = sizeof(next_packet_);
if (packet_source_->NextPacket(next_packet_, &next_packet_length_,
&next_rtp_time_) != 0) {
return 0;
}
if (_reordering && _reorderBuffer == NULL)
{
_reorderBuffer = new RawRtpPacket(reinterpret_cast<uint8_t*>(_nextPacket), static_cast<uint16_t>(_nextPacketLength));
return 0;
}
int32_t ret = SendPacket(reinterpret_cast<uint8_t*>(_nextPacket), static_cast<uint16_t>(_nextPacketLength));
if (_reordering && _reorderBuffer != NULL)
{
RawRtpPacket* rtpPacket = _reorderBuffer;
_reorderBuffer = NULL;
SendPacket(rtpPacket->data, rtpPacket->length);
delete rtpPacket;
}
_firstPacket = false;
if (ret < 0)
{
return ret;
}
_nextPacketLength = ReadPacket(_nextPacket, &_nextRtpTime);
if (_nextPacketLength < 0)
{
_endOfFile = true;
return 0;
}
else if (_nextPacketLength == 0)
{
return 0;
}
}
if (_endOfFile && _lostPackets.NumberOfPacketsToResend() == 0)
{
return 1;
}
return 0;
}
first_packet_rtp_time_ = next_rtp_time_;
first_packet_time_ms_ = clock_->TimeInMilliseconds();
first_packet_ = false;
}
int32_t RTPPlayer::SendPacket(uint8_t* rtpData, uint16_t rtpLen)
{
if ((_randVec[(_randVecPos++) % RAND_VEC_LENGTH] + 1.0)/(RAND_MAX + 1.0) < _lossRate &&
_noLossStartup < 0)
{
if (_nackEnabled)
{
const uint16_t seqNo = (rtpData[2] << 8) + rtpData[3];
printf("Throw: %u\n", seqNo);
_lostPackets.AddPacket(new RawRtpPacket(rtpData, rtpLen));
return 0;
}
}
else if (rtpLen > 0)
{
int32_t ret = _rtpModule->IncomingPacket(rtpData, rtpLen);
if (ret < 0)
{
return -1;
}
}
if (_noLossStartup >= 0)
{
_noLossStartup--;
}
return 1;
}
int32_t RTPPlayer::ReadPacket(int16_t* rtpdata, uint32_t* offset)
{
uint16_t length, plen;
if (fread(&length,2,1,_rtpFile)==0)
return(-1);
length=ntohs(length);
if (fread(&plen,2,1,_rtpFile)==0)
return(-1);
plen=ntohs(plen);
if (fread(offset,4,1,_rtpFile)==0)
return(-1);
*offset=ntohl(*offset);
// Use length here because a plen of 0 specifies rtcp
length = (uint16_t) (length - HDR_SIZE);
if (fread((unsigned short *) rtpdata,1,length,_rtpFile) != length)
return(-1);
#ifdef JUNK_DATA
// destroy the RTP payload with random data
if (plen > 12) { // ensure that we have more than just a header
for ( int ix = 12; ix < plen; ix=ix+2 ) {
rtpdata[ix>>1] = (short) (rtpdata[ix>>1] + (short) rand());
}
}
#endif
return plen;
}
int32_t RTPPlayer::SimulatePacketLoss(float lossRate, bool enableNack, uint32_t rttMs)
{
_nackEnabled = enableNack;
_lossRate = lossRate;
_rttMs = rttMs;
return 0;
}
int32_t RTPPlayer::SetReordering(bool enabled)
{
_reordering = enabled;
return 0;
}
int32_t RTPPlayer::ResendPackets(const uint16_t* sequenceNumbers, uint16_t length)
{
if (sequenceNumbers == NULL)
{
if (reordering_ && reorder_buffer_.get() == NULL) {
reorder_buffer_.reset(new RawRtpPacket(next_packet_,
next_packet_length_, 0, 0));
return 0;
}
int ret = SendPacket(next_packet_, next_packet_length_);
if (reorder_buffer_.get()) {
SendPacket(reorder_buffer_->data(), reorder_buffer_->length());
reorder_buffer_.reset(NULL);
}
if (ret < 0) {
return ret;
}
next_packet_length_ = sizeof(next_packet_);
if (packet_source_->NextPacket(next_packet_, &next_packet_length_,
&next_rtp_time_) != 0) {
end_of_file_ = true;
return 0;
}
else if (next_packet_length_ == 0) {
return 0;
}
}
for (int i=0; i < length; i++)
{
_lostPackets.SetResendTime(sequenceNumbers[i],
_clock->TimeInMilliseconds() + _rttMs,
_clock->TimeInMilliseconds());
if (end_of_file_ && lost_packets_.NumberOfPacketsToResend() == 0) {
return 1;
}
return 0;
}
}
void RTPPlayer::Print() const
{
printf("Resent packets: %u\n", _resendPacketCount);
_lostPackets.Print();
virtual uint32_t TimeUntilNextPacket() const {
int64_t time_left = (next_rtp_time_ - first_packet_rtp_time_) -
(clock_->TimeInMilliseconds() - first_packet_time_ms_);
if (time_left < 0) {
return 0;
}
return static_cast<uint32_t>(time_left);
}
virtual void Print() const {
printf("Resent packets: %u\n", resend_packet_count_);
lost_packets_.Print();
}
private:
int SendPacket(const uint8_t* data, uint32_t length) {
assert(data);
assert(length > 0);
ModuleRTPUtility::RTPHeaderParser rtp_header_parser(data, length);
if (!rtp_header_parser.RTCP()) {
WebRtcRTPHeader header;
if (!rtp_header_parser.Parse(header, NULL)) {
return -1;
}
uint32_t ssrc = header.header.ssrc;
if (ssrc_handlers_.RegisterSsrc(ssrc, &lost_packets_) < 0) {
DEBUG_LOG1("Unable to register ssrc: %d", ssrc);
return -1;
}
if (no_loss_startup_ > 0) {
no_loss_startup_--;
} else if ((rand() + 1.0)/(RAND_MAX + 1.0) < loss_rate_) {
uint16_t seq_num = header.header.sequenceNumber;
lost_packets_.AddPacket(new RawRtpPacket(data, length, ssrc, seq_num));
DEBUG_LOG1("Dropped packet: %d!", header.header.sequenceNumber);
return 0;
}
}
ssrc_handlers_.IncomingPacket(data, length);
return 1;
}
SsrcHandlers ssrc_handlers_;
Clock* clock_;
scoped_ptr<RtpPacketSourceInterface> packet_source_;
uint32_t next_rtp_time_;
bool first_packet_;
int64_t first_packet_rtp_time_;
int64_t first_packet_time_ms_;
float loss_rate_;
LostPackets lost_packets_;
uint32_t resend_packet_count_;
uint32_t no_loss_startup_;
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);
};
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));
if (packet_source.get() == NULL) {
packet_source.reset(CreatePcapFileReader(input_filename));
if (packet_source.get() == NULL) {
return NULL;
}
}
scoped_ptr<RtpPlayerImpl> impl(new RtpPlayerImpl(payload_sink_factory,
payload_types, clock, &packet_source, loss_rate, rtt_ms, reordering));
return impl.release();
}
} // namespace rtpplayer
} // namespace webrtc

View File

@ -11,109 +11,101 @@
#ifndef WEBRTC_MODULES_VIDEO_CODING_TEST_RTP_PLAYER_H_
#define WEBRTC_MODULES_VIDEO_CODING_TEST_RTP_PLAYER_H_
#include "typedefs.h"
#include "rtp_rtcp.h"
#include "critical_section_wrapper.h"
#include "video_coding_defines.h"
#include "webrtc/system_wrappers/interface/clock.h"
#include <stdio.h>
#include <list>
#include <string>
#include <vector>
#define HDR_SIZE 8 // rtpplay packet header size in bytes
#define FIRSTLINELEN 40
#define RAND_VEC_LENGTH 4096
#include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h"
#include "webrtc/modules/video_coding/main/interface/video_coding_defines.h"
struct PayloadCodecTuple;
namespace webrtc {
class Clock;
struct RawRtpPacket
{
public:
RawRtpPacket(uint8_t* rtp_data, uint16_t rtp_length);
~RawRtpPacket();
namespace rtpplayer {
uint8_t* data;
uint16_t length;
int64_t resend_time_ms;
};
typedef std::list<PayloadCodecTuple*> PayloadTypeList;
typedef std::list<RawRtpPacket*> RtpPacketList;
typedef RtpPacketList::iterator RtpPacketIterator;
typedef RtpPacketList::const_iterator ConstRtpPacketIterator;
class LostPackets {
class PayloadCodecTuple {
public:
LostPackets();
~LostPackets();
PayloadCodecTuple(uint8_t payload_type, const std::string& codec_name,
VideoCodecType codec_type)
: name_(codec_name),
payload_type_(payload_type),
codec_type_(codec_type) {
}
void AddPacket(RawRtpPacket* packet);
void SetResendTime(uint16_t sequenceNumber,
int64_t resendTime,
int64_t nowMs);
RawRtpPacket* NextPacketToResend(int64_t timeNow);
int NumberOfPacketsToResend() const;
void SetPacketResent(uint16_t seqNo, int64_t nowMs);
void Print() const;
const std::string& name() const { return name_; }
uint8_t payload_type() const { return payload_type_; }
VideoCodecType codec_type() const { return codec_type_; }
private:
webrtc::CriticalSectionWrapper* crit_sect_;
int loss_count_;
FILE* debug_file_;
RtpPacketList packets_;
std::string name_;
uint8_t payload_type_;
VideoCodecType codec_type_;
};
struct PayloadCodecTuple
{
PayloadCodecTuple(uint8_t plType, std::string codecName, webrtc::VideoCodecType type) :
name(codecName), payloadType(plType), codecType(type) {};
const std::string name;
const uint8_t payloadType;
const webrtc::VideoCodecType codecType;
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;
};
class RTPPlayer : public webrtc::VCMPacketRequestCallback
{
public:
RTPPlayer(const char* filename,
webrtc::RtpData* callback,
webrtc::Clock* clock);
virtual ~RTPPlayer();
// Implemented by RtpPlayer and given to client as a means to retrieve
// information about a specific RTP stream.
class RtpStreamInterface {
public:
virtual ~RtpStreamInterface() {}
int32_t Initialize(const PayloadTypeList* payloadList);
int32_t NextPacket(const int64_t timeNow);
uint32_t TimeUntilNextPacket() const;
int32_t SimulatePacketLoss(float lossRate, bool enableNack = false, uint32_t rttMs = 0);
int32_t SetReordering(bool enabled);
int32_t ResendPackets(const uint16_t* sequenceNumbers, uint16_t length);
void Print() const;
// Ask for missing packets to be resent.
virtual void ResendPackets(const uint16_t* sequence_numbers,
uint16_t length) = 0;
private:
int32_t SendPacket(uint8_t* rtpData, uint16_t rtpLen);
int32_t ReadPacket(int16_t* rtpdata, uint32_t* offset);
int32_t ReadHeader();
webrtc::Clock* _clock;
FILE* _rtpFile;
webrtc::RtpRtcp* _rtpModule;
uint32_t _nextRtpTime;
webrtc::RtpData* _dataCallback;
bool _firstPacket;
float _lossRate;
bool _nackEnabled;
LostPackets _lostPackets;
uint32_t _resendPacketCount;
int32_t _noLossStartup;
bool _endOfFile;
uint32_t _rttMs;
int64_t _firstPacketRtpTime;
int64_t _firstPacketTimeMs;
RawRtpPacket* _reorderBuffer;
bool _reordering;
int16_t _nextPacket[8000];
int32_t _nextPacketLength;
int _randVec[RAND_VEC_LENGTH];
int _randVecPos;
virtual uint32_t ssrc() const = 0;
virtual const PayloadTypes& payload_types() const = 0;
};
// Implemented by a sink. Wraps RtpData because its d-tor is protected.
class PayloadSinkInterface : public RtpData {
public:
virtual ~PayloadSinkInterface() {}
};
// Implemented to provide a sink for RTP data, such as hooking up a VCM to
// the incoming RTP stream.
class PayloadSinkFactoryInterface {
public:
virtual ~PayloadSinkFactoryInterface() {}
// Return NULL if failed to create sink. 'stream' is guaranteed to be
// around for as long as the RtpData. The returned object is owned by
// the caller (RtpPlayer).
virtual PayloadSinkInterface* Create(RtpStreamInterface* stream) = 0;
};
// The client's view of an RtpPlayer.
class RtpPlayerInterface {
public:
virtual ~RtpPlayerInterface() {}
virtual int NextPacket(int64_t timeNow) = 0;
virtual uint32_t TimeUntilNextPacket() const = 0;
virtual void Print() const = 0;
};
RtpPlayerInterface* Create(const std::string& inputFilename,
PayloadSinkFactoryInterface* payloadSinkFactory, Clock* clock,
const PayloadTypes& payload_types, float lossRate, uint32_t rttMs,
bool reordering);
} // namespace rtpplayer
} // namespace webrtc
#endif // WEBRTC_MODULES_VIDEO_CODING_TEST_RTP_PLAYER_H_

View File

@ -8,39 +8,150 @@
* be found in the AUTHORS file in the root of the source tree.
*/
#include "test_util.h"
#include "test_macros.h"
#include "rtp_dump.h"
#include <cmath>
#include "webrtc/modules/video_coding/main/test/test_util.h"
using namespace webrtc;
#include <cassert>
#include <cmath>
#include <iomanip>
#include <sstream>
#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
#include "webrtc/modules/video_coding/main/source/internal_defines.h"
#include "webrtc/test/testsupport/fileutils.h"
CmdArgs::CmdArgs()
: codecName("VP8"),
codecType(webrtc::kVideoCodecVP8),
width(352),
height(288),
bitRate(500),
frameRate(30),
packetLoss(0),
rtt(0),
protectionMode(0),
camaEnable(0),
inputFile(webrtc::test::ProjectRootPath() + "/resources/foreman_cif.yuv"),
outputFile(webrtc::test::OutputPath() +
"video_coding_test_output_352x288.yuv"),
fv_outputfile(webrtc::test::OutputPath() + "features.txt"),
testNum(0) {
}
// Normal Distribution
#define PI 3.14159265
double
NormalDist(double mean, double stdDev)
double NormalDist(double mean, double stdDev)
{
// Creating a Normal distribution variable from two independent uniform
// variables based on the Box-Muller transform
double uniform1 = (std::rand() + 1.0) / (RAND_MAX + 1.0);
double uniform2 = (std::rand() + 1.0) / (RAND_MAX + 1.0);
double uniform1 = (rand() + 1.0) / (RAND_MAX + 1.0);
double uniform2 = (rand() + 1.0) / (RAND_MAX + 1.0);
return (mean + stdDev * sqrt(-2 * log(uniform1)) * cos(2 * PI * uniform2));
}
RTPVideoCodecTypes
ConvertCodecType(const char* plname)
{
if (strncmp(plname,"VP8" , 3) == 0)
{
return kRTPVideoVP8;
}
else if (strncmp(plname,"I420" , 5) == 0)
{
return kRTPVideoI420;
}
else
{
return kRTPVideoNoVideo; // Default value
}
namespace {
void SplitFilename(const std::string& filename, std::string* basename,
std::string* extension) {
assert(basename);
assert(extension);
std::string::size_type idx;
idx = filename.rfind('.');
if(idx != std::string::npos) {
*basename = filename.substr(0, idx);
*extension = filename.substr(idx + 1);
} else {
*basename = filename;
*extension = "";
}
}
std::string AppendWidthHeightCount(const std::string& filename, int width,
int height, int count) {
std::string basename;
std::string extension;
SplitFilename(filename, &basename, &extension);
std::stringstream ss;
ss << basename << "_" << count << "." << width << "_" << height << "." <<
extension;
return ss.str();
}
} // namespace
FileOutputFrameReceiver::FileOutputFrameReceiver(
const std::string& base_out_filename, uint32_t ssrc)
: out_filename_(),
out_file_(NULL),
timing_file_(NULL),
width_(0),
height_(0),
count_(0) {
std::string basename;
std::string extension;
if (base_out_filename == "") {
basename = webrtc::test::OutputPath() + "rtp_decoded";
extension = "yuv";
} else {
SplitFilename(base_out_filename, &basename, &extension);
}
std::stringstream ss;
ss << basename << "_" << std::hex << std::setw(8) << std::setfill('0') <<
ssrc << "." << extension;
out_filename_ = ss.str();
}
FileOutputFrameReceiver::~FileOutputFrameReceiver() {
if (timing_file_ != NULL) {
fclose(timing_file_);
}
if (out_file_ != NULL) {
fclose(out_file_);
}
}
WebRtc_Word32 FileOutputFrameReceiver::FrameToRender(
webrtc::I420VideoFrame& video_frame) {
if (timing_file_ == NULL) {
std::string basename;
std::string extension;
SplitFilename(out_filename_, &basename, &extension);
timing_file_ = fopen((basename + "_renderTiming.txt").c_str(), "w");
if (timing_file_ == NULL) {
return -1;
}
}
if (out_file_ == NULL || video_frame.width() != width_ ||
video_frame.height() != height_) {
if (out_file_) {
fclose(out_file_);
}
printf("New size: %dx%d\n", video_frame.width(), video_frame.height());
width_ = video_frame.width();
height_ = video_frame.height();
std::string filename_with_width_height = AppendWidthHeightCount(
out_filename_, width_, height_, count_);
++count_;
out_file_ = fopen(filename_with_width_height.c_str(), "wb");
if (out_file_ == NULL) {
return -1;
}
}
fprintf(timing_file_, "%u, %u\n", video_frame.timestamp(),
webrtc::MaskWord64ToUWord32(video_frame.render_time_ms()));
if (PrintI420VideoFrame(video_frame, out_file_) < 0) {
return -1;
}
return 0;
}
webrtc::RTPVideoCodecTypes ConvertCodecType(const char* plname) {
if (strncmp(plname,"VP8" , 3) == 0) {
return webrtc::kRTPVideoVP8;
} else if (strncmp(plname,"I420" , 5) == 0) {
return webrtc::kRTPVideoI420;
} else {
return webrtc::kRTPVideoNoVideo; // Default value
}
}

View File

@ -15,56 +15,37 @@
* General declarations used through out VCM offline tests.
*/
#include <string.h>
#include <fstream>
#include <cstdlib>
#include <string>
#include "webrtc/modules/interface/module_common_types.h"
#include "webrtc/modules/video_coding/main/interface/video_coding.h"
#include "webrtc/system_wrappers/interface/event_wrapper.h"
#include "webrtc/test/testsupport/fileutils.h"
#include "webrtc/system_wrappers/interface/constructor_magic.h"
enum { kMaxNackListSize = 250 };
enum { kMaxPacketAgeToNack = 450 };
// Class used for passing command line arguments to tests
class CmdArgs
{
class CmdArgs {
public:
CmdArgs()
: codecName("VP8"),
codecType(webrtc::kVideoCodecVP8),
width(352),
height(288),
bitRate(500),
frameRate(30),
packetLoss(0),
rtt(0),
protectionMode(0),
camaEnable(0),
inputFile(webrtc::test::ProjectRootPath() +
"/resources/foreman_cif.yuv"),
outputFile(webrtc::test::OutputPath() +
"video_coding_test_output_352x288.yuv"),
fv_outputfile(webrtc::test::OutputPath() + "features.txt"),
testNum(0) {}
std::string codecName;
webrtc::VideoCodecType codecType;
int width;
int height;
int bitRate;
int frameRate;
int packetLoss;
int rtt;
int protectionMode;
int camaEnable;
std::string inputFile;
std::string outputFile;
std::string fv_outputfile;
int testNum;
CmdArgs();
std::string codecName;
webrtc::VideoCodecType codecType;
int width;
int height;
int bitRate;
int frameRate;
int packetLoss;
int rtt;
int protectionMode;
int camaEnable;
std::string inputFile;
std::string outputFile;
std::string fv_outputfile;
int testNum;
};
// forward declaration
int MTRxTxTest(CmdArgs& args);
double NormalDist(double mean, double stdDev);
@ -100,8 +81,27 @@ class NullEventFactory : public webrtc::EventFactory {
}
};
class FileOutputFrameReceiver : public webrtc::VCMReceiveCallback {
public:
FileOutputFrameReceiver(const std::string& base_out_filename, uint32_t ssrc);
virtual ~FileOutputFrameReceiver();
// VCMReceiveCallback
virtual WebRtc_Word32 FrameToRender(webrtc::I420VideoFrame& video_frame);
private:
std::string out_filename_;
uint32_t ssrc_;
FILE* out_file_;
FILE* timing_file_;
int width_;
int height_;
int count_;
DISALLOW_IMPLICIT_CONSTRUCTORS(FileOutputFrameReceiver);
};
// Codec type conversion
webrtc::RTPVideoCodecTypes
ConvertCodecType(const char* plname);
webrtc::RTPVideoCodecTypes ConvertCodecType(const char* plname);
#endif

View File

@ -0,0 +1,249 @@
/*
* 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/vcm_payload_sink_factory.h"
#include <algorithm>
#include <cassert>
#include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp.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"
namespace webrtc {
namespace rtpplayer {
class VcmPayloadSinkFactory::VcmPayloadSink
: public PayloadSinkInterface,
public VCMPacketRequestCallback,
public VCMFrameStorageCallback {
public:
VcmPayloadSink(VcmPayloadSinkFactory* factory,
RtpStreamInterface* stream,
scoped_ptr<VideoCodingModule>* vcm,
scoped_ptr<VideoCodingModule>* vcm_playback,
scoped_ptr<FileOutputFrameReceiver>* frame_receiver)
: factory_(factory),
stream_(stream),
vcm_(),
vcm_playback_(),
frame_receiver_() {
assert(factory);
assert(stream);
assert(vcm);
assert(vcm->get());
assert(vcm_playback);
assert(frame_receiver);
assert(frame_receiver->get());
vcm_.swap(*vcm);
vcm_playback_.swap(*vcm_playback);
frame_receiver_.swap(*frame_receiver);
vcm_->RegisterPacketRequestCallback(this);
if (vcm_playback_.get() == NULL) {
vcm_->RegisterReceiveCallback(frame_receiver_.get());
} else {
vcm_->RegisterFrameStorageCallback(this);
vcm_playback_->RegisterReceiveCallback(frame_receiver_.get());
}
}
virtual ~VcmPayloadSink() {
factory_->Remove(this);
}
// PayloadSinkInterface
virtual WebRtc_Word32 OnReceivedPayloadData(const WebRtc_UWord8* payload_data,
const WebRtc_UWord16 payload_size,
const WebRtcRTPHeader* rtp_header) {
return vcm_->IncomingPacket(payload_data, payload_size, *rtp_header);
}
// VCMPacketRequestCallback
virtual WebRtc_Word32 ResendPackets(const WebRtc_UWord16* sequence_numbers,
WebRtc_UWord16 length) {
stream_->ResendPackets(sequence_numbers, length);
return 0;
}
// VCMFrameStorageCallback
virtual WebRtc_Word32 StoreReceivedFrame(
const EncodedVideoData& frame_to_store) {
vcm_playback_->DecodeFromStorage(frame_to_store);
return VCM_OK;
}
int DecodeAndProcess(bool should_decode, bool decode_dual_frame) {
if (should_decode) {
if (vcm_->Decode() < 0) {
return -1;
}
}
while (decode_dual_frame && vcm_->DecodeDualFrame(0) == 1) {
}
return Process() ? 0 : -1;
}
bool Process() {
if (vcm_->TimeUntilNextProcess() <= 0) {
if (vcm_->Process() < 0) {
return false;
}
}
return true;
}
bool Decode() {
vcm_->Decode(10000);
while (vcm_->DecodeDualFrame(0) == 1) {
}
return true;
}
private:
VcmPayloadSinkFactory* factory_;
RtpStreamInterface* stream_;
scoped_ptr<VideoCodingModule> vcm_;
scoped_ptr<VideoCodingModule> vcm_playback_;
scoped_ptr<FileOutputFrameReceiver> frame_receiver_;
DISALLOW_IMPLICIT_CONSTRUCTORS(VcmPayloadSink);
};
VcmPayloadSinkFactory::VcmPayloadSinkFactory(
const std::string& base_out_filename, Clock* clock, bool protection_enabled,
VCMVideoProtection protection_method, uint32_t rtt_ms,
uint32_t render_delay_ms, uint32_t min_playout_delay_ms,
bool use_frame_storage)
: base_out_filename_(base_out_filename),
clock_(clock),
protection_enabled_(protection_enabled),
protection_method_(protection_method),
rtt_ms_(rtt_ms),
render_delay_ms_(render_delay_ms),
min_playout_delay_ms_(min_playout_delay_ms),
use_frame_storage_(use_frame_storage),
null_event_factory_(new NullEventFactory()),
crit_sect_(CriticalSectionWrapper::CreateCriticalSection()),
sinks_(),
next_id_(1) {
assert(clock);
assert(crit_sect_.get());
}
VcmPayloadSinkFactory::~VcmPayloadSinkFactory() {
assert(sinks_.empty());
}
PayloadSinkInterface* VcmPayloadSinkFactory::Create(
RtpStreamInterface* stream) {
assert(stream);
CriticalSectionScoped cs(crit_sect_.get());
scoped_ptr<VideoCodingModule> vcm(
VideoCodingModule::Create(next_id_++, clock_, null_event_factory_.get()));
if (vcm.get() == NULL) {
return NULL;
}
if (vcm->InitializeReceiver() < 0) {
return NULL;
}
scoped_ptr<VideoCodingModule> vcm_playback;
if (use_frame_storage_) {
vcm_playback.reset(
VideoCodingModule::Create(next_id_++, clock_,
null_event_factory_.get()));
if (vcm_playback.get() == NULL) {
return NULL;
}
if (vcm_playback->InitializeReceiver() < 0) {
return NULL;
}
}
const PayloadTypes& plt = stream->payload_types();
for (PayloadTypesIterator it = plt.begin(); it != plt.end();
++it) {
if (it->codec_type() != kVideoCodecULPFEC &&
it->codec_type() != kVideoCodecRED) {
VideoCodec codec;
if (VideoCodingModule::Codec(it->codec_type(), &codec) < 0) {
return NULL;
}
codec.plType = it->payload_type();
if (vcm->RegisterReceiveCodec(&codec, 1) < 0) {
return NULL;
}
if (use_frame_storage_) {
if (vcm_playback->RegisterReceiveCodec(&codec, 1) < 0) {
return NULL;
}
}
}
}
vcm->SetChannelParameters(0, 0, rtt_ms_);
vcm->SetVideoProtection(protection_method_, protection_enabled_);
vcm->SetRenderDelay(render_delay_ms_);
vcm->SetMinimumPlayoutDelay(min_playout_delay_ms_);
vcm->SetNackSettings(kMaxNackListSize, kMaxPacketAgeToNack);
scoped_ptr<FileOutputFrameReceiver> frame_receiver(
new FileOutputFrameReceiver(base_out_filename_, stream->ssrc()));
scoped_ptr<VcmPayloadSink> sink(
new VcmPayloadSink(this, stream, &vcm, &vcm_playback, &frame_receiver));
sinks_.push_back(sink.get());
return sink.release();
}
int VcmPayloadSinkFactory::DecodeAndProcessAll(bool decode_dual_frame) {
CriticalSectionScoped cs(crit_sect_.get());
assert(clock_);
bool should_decode = (clock_->TimeInMilliseconds() % 5) == 0;
for (Sinks::iterator it = sinks_.begin(); it != sinks_.end(); ++it) {
if ((*it)->DecodeAndProcess(should_decode, decode_dual_frame) < 0) {
return -1;
}
}
return 0;
}
bool VcmPayloadSinkFactory::ProcessAll() {
CriticalSectionScoped cs(crit_sect_.get());
for (Sinks::iterator it = sinks_.begin(); it != sinks_.end(); ++it) {
if (!(*it)->Process()) {
return false;
}
}
return true;
}
bool VcmPayloadSinkFactory::DecodeAll() {
CriticalSectionScoped cs(crit_sect_.get());
for (Sinks::iterator it = sinks_.begin(); it != sinks_.end(); ++it) {
if (!(*it)->Decode()) {
return false;
}
}
return true;
}
void VcmPayloadSinkFactory::Remove(VcmPayloadSink* sink) {
assert(sink);
CriticalSectionScoped cs(crit_sect_.get());
Sinks::iterator it = std::find(sinks_.begin(), sinks_.end(), sink);
assert(it != sinks_.end());
sinks_.erase(it);
}
} // namespace rtpplayer
} // namespace webrtc

View File

@ -0,0 +1,66 @@
/*
* 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 <string>
#include <vector>
#include "webrtc/modules/video_coding/main/interface/video_coding_defines.h"
#include "webrtc/modules/video_coding/main/test/rtp_player.h"
#include "webrtc/system_wrappers/interface/constructor_magic.h"
#include "webrtc/system_wrappers/interface/scoped_ptr.h"
class NullEventFactory;
namespace webrtc {
class Clock;
class CriticalSectionWrapper;
namespace rtpplayer {
class VcmPayloadSinkFactory : public PayloadSinkFactoryInterface {
public:
VcmPayloadSinkFactory(const std::string& base_out_filename,
Clock* clock, bool protection_enabled,
VCMVideoProtection protection_method,
uint32_t rtt_ms, uint32_t render_delay_ms,
uint32_t min_playout_delay_ms,
bool use_frame_storage);
virtual ~VcmPayloadSinkFactory();
// PayloadSinkFactoryInterface
virtual PayloadSinkInterface* Create(RtpStreamInterface* stream);
int DecodeAndProcessAll(bool decode_dual_frame);
bool ProcessAll();
bool DecodeAll();
private:
class VcmPayloadSink;
friend class VcmPayloadSink;
typedef std::vector<VcmPayloadSink*> Sinks;
void Remove(VcmPayloadSink* sink);
std::string base_out_filename_;
Clock* clock_;
bool protection_enabled_;
VCMVideoProtection protection_method_;
uint32_t rtt_ms_;
uint32_t render_delay_ms_;
uint32_t min_playout_delay_ms_;
bool use_frame_storage_;
scoped_ptr<NullEventFactory> null_event_factory_;
scoped_ptr<CriticalSectionWrapper> crit_sect_;
Sinks sinks_;
int next_id_;
DISALLOW_IMPLICIT_CONSTRUCTORS(VcmPayloadSinkFactory);
};
} // namespace rtpplayer
} // namespace webrtc

View File

@ -8,239 +8,79 @@
* be found in the AUTHORS file in the root of the source tree.
*/
#include <stdio.h>
#include <string.h>
#include <sstream>
#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
#include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp.h"
#include "webrtc/modules/video_coding/main/interface/video_coding.h"
#include "webrtc/modules/video_coding/main/source/internal_defines.h"
#include "webrtc/modules/video_coding/main/test/receiver_tests.h"
#include "webrtc/modules/video_coding/main/test/test_macros.h"
#include "webrtc/modules/video_coding/main/test/rtp_player.h"
#include "webrtc/system_wrappers/interface/clock.h"
#include "webrtc/modules/video_coding/main/test/vcm_payload_sink_factory.h"
#include "webrtc/system_wrappers/interface/trace.h"
#include "webrtc/test/testsupport/fileutils.h"
using namespace webrtc;
namespace {
int32_t
RtpDataCallback::OnReceivedPayloadData(const uint8_t* payloadData,
const uint16_t payloadSize,
const WebRtcRTPHeader* rtpHeader)
{
return _vcm->IncomingPacket(payloadData, payloadSize, *rtpHeader);
}
const bool kConfigProtectionEnabled = true;
const webrtc::VCMVideoProtection kConfigProtectionMethod =
webrtc::kProtectionNack;
const float kConfigLossRate = 0.0f;
const bool kConfigReordering = false;
const uint32_t kConfigRttMs = 0;
const uint32_t kConfigRenderDelayMs = 0;
const uint32_t kConfigMinPlayoutDelayMs = 0;
const int64_t kConfigMaxRuntimeMs = -1;
FrameReceiveCallback::~FrameReceiveCallback()
{
if (_timingFile != NULL)
{
fclose(_timingFile);
}
if (_outFile != NULL)
{
fclose(_outFile);
}
}
} // namespace
int32_t
FrameReceiveCallback::FrameToRender(I420VideoFrame& videoFrame)
{
if (_timingFile == NULL)
{
_timingFile = fopen((test::OutputPath() + "renderTiming.txt").c_str(),
"w");
if (_timingFile == NULL)
{
return -1;
}
}
if (_outFile == NULL ||
videoFrame.width() != static_cast<int>(width_) ||
videoFrame.height() != static_cast<int>(height_))
{
if (_outFile) {
fclose(_outFile);
}
printf("New size: %ux%u\n", videoFrame.width(), videoFrame.height());
width_ = videoFrame.width();
height_ = videoFrame.height();
std::string filename_with_width_height = AppendWidthHeightAndCount(
_outFilename, width_, height_, count_);
++count_;
_outFile = fopen(filename_with_width_height.c_str(), "wb");
if (_outFile == NULL)
{
return -1;
}
}
fprintf(_timingFile, "%u, %u\n",
videoFrame.timestamp(),
MaskWord64ToUWord32(videoFrame.render_time_ms()));
if (PrintI420VideoFrame(videoFrame, _outFile) < 0) {
return -1;
}
return 0;
}
int RtpPlay(const CmdArgs& args) {
std::string trace_file = webrtc::test::OutputPath() + "receiverTestTrace.txt";
webrtc::Trace::CreateTrace();
webrtc::Trace::SetTraceFile(trace_file.c_str());
webrtc::Trace::SetLevelFilter(webrtc::kTraceAll);
void FrameReceiveCallback::SplitFilename(std::string filename,
std::string* basename,
std::string* ending) {
std::string::size_type idx;
idx = filename.rfind('.');
webrtc::rtpplayer::PayloadTypes payload_types;
payload_types.push_back(webrtc::rtpplayer::PayloadCodecTuple(
VCM_ULPFEC_PAYLOAD_TYPE, "ULPFEC", webrtc::kVideoCodecULPFEC));
payload_types.push_back(webrtc::rtpplayer::PayloadCodecTuple(
VCM_RED_PAYLOAD_TYPE, "RED", webrtc::kVideoCodecRED));
payload_types.push_back(webrtc::rtpplayer::PayloadCodecTuple(
VCM_VP8_PAYLOAD_TYPE, "VP8", webrtc::kVideoCodecVP8));
if(idx != std::string::npos) {
*ending = filename.substr(idx + 1);
*basename = filename.substr(0, idx);
} else {
*basename = filename;
*ending = "";
std::string output_file = args.outputFile;
if (output_file == "") {
output_file = webrtc::test::OutputPath() + "RtpPlay_decoded.yuv";
}
}
std::string FrameReceiveCallback::AppendWidthHeightAndCount(
std::string filename, unsigned int width, unsigned int height, int count) {
std::string basename;
std::string ending;
SplitFilename(filename, &basename, &ending);
std::stringstream ss;
ss << basename << "_" << count << "." << width << "_" << height << "." <<
ending;
return ss.str();
}
int RtpPlay(CmdArgs& args)
{
// BEGIN Settings
bool protectionEnabled = true;
VCMVideoProtection protectionMethod = kProtectionNack;
uint32_t rttMS = 0;
float lossRate = 0.0f;
bool reordering = false;
uint32_t renderDelayMs = 0;
uint32_t minPlayoutDelayMs = 0;
const int64_t MAX_RUNTIME_MS = -1;
std::string outFile = args.outputFile;
if (outFile == "")
outFile = test::OutputPath() + "RtpPlay_decoded.yuv";
FrameReceiveCallback receiveCallback(outFile);
SimulatedClock clock(0);
NullEventFactory event_factory;
VideoCodingModule* vcm = VideoCodingModule::Create(1, &clock,
&event_factory);
RtpDataCallback dataCallback(vcm);
RTPPlayer rtpStream(args.inputFile.c_str(), &dataCallback, &clock);
webrtc::SimulatedClock clock(0);
webrtc::rtpplayer::VcmPayloadSinkFactory factory(output_file, &clock,
kConfigProtectionEnabled, kConfigProtectionMethod, kConfigRttMs,
kConfigRenderDelayMs, kConfigMinPlayoutDelayMs, false);
webrtc::scoped_ptr<webrtc::rtpplayer::RtpPlayerInterface> rtp_player(
webrtc::rtpplayer::Create(args.inputFile, &factory, &clock, payload_types,
kConfigLossRate, kConfigRttMs, kConfigReordering));
if (rtp_player.get() == NULL) {
return -1;
}
PayloadTypeList payloadTypes;
payloadTypes.push_front(new PayloadCodecTuple(VCM_VP8_PAYLOAD_TYPE, "VP8",
kVideoCodecVP8));
payloadTypes.push_front(new PayloadCodecTuple(VCM_RED_PAYLOAD_TYPE, "RED",
kVideoCodecRED));
payloadTypes.push_front(new PayloadCodecTuple(VCM_ULPFEC_PAYLOAD_TYPE,
"ULPFEC", kVideoCodecULPFEC));
Trace::CreateTrace();
Trace::SetTraceFile((test::OutputPath() + "receiverTestTrace.txt").c_str());
Trace::SetLevelFilter(webrtc::kTraceAll);
// END Settings
// Set up
int32_t ret = vcm->InitializeReceiver();
if (ret < 0)
{
return -1;
int ret = 0;
while ((ret = rtp_player->NextPacket(clock.TimeInMilliseconds())) == 0) {
ret = factory.DecodeAndProcessAll(true);
if (ret < 0 || (kConfigMaxRuntimeMs > -1 &&
clock.TimeInMilliseconds() >= kConfigMaxRuntimeMs)) {
break;
}
vcm->RegisterReceiveCallback(&receiveCallback);
vcm->RegisterPacketRequestCallback(&rtpStream);
clock.AdvanceTimeMilliseconds(1);
}
// Register receive codecs in VCM
for (PayloadTypeList::iterator it = payloadTypes.begin();
it != payloadTypes.end(); ++it) {
PayloadCodecTuple* payloadType = *it;
if (payloadType != NULL)
{
VideoCodec codec;
if (payloadType->codecType != kVideoCodecULPFEC &&
payloadType->codecType != kVideoCodecRED) {
if (VideoCodingModule::Codec(payloadType->codecType, &codec) < 0)
{
return -1;
}
codec.plType = payloadType->payloadType;
if (vcm->RegisterReceiveCodec(&codec, 1) < 0)
{
return -1;
}
}
}
}
rtp_player->Print();
if (rtpStream.Initialize(&payloadTypes) < 0)
{
return -1;
}
bool nackEnabled = protectionEnabled &&
(protectionMethod == kProtectionNack ||
protectionMethod == kProtectionDualDecoder);
rtpStream.SimulatePacketLoss(lossRate, nackEnabled, rttMS);
rtpStream.SetReordering(reordering);
vcm->SetChannelParameters(0, 0, rttMS);
vcm->SetVideoProtection(protectionMethod, protectionEnabled);
vcm->SetRenderDelay(renderDelayMs);
vcm->SetMinimumPlayoutDelay(minPlayoutDelayMs);
vcm->SetNackSettings(kMaxNackListSize, kMaxPacketAgeToNack);
ret = 0;
// RTP stream main loop
while ((ret = rtpStream.NextPacket(clock.TimeInMilliseconds())) == 0)
{
if (clock.TimeInMilliseconds() % 5 == 0)
{
ret = vcm->Decode();
if (ret < 0)
{
return -1;
}
}
while (vcm->DecodeDualFrame(0) == 1) {
}
if (vcm->TimeUntilNextProcess() <= 0)
{
vcm->Process();
}
if (MAX_RUNTIME_MS > -1 && clock.TimeInMilliseconds() >=
MAX_RUNTIME_MS)
{
break;
}
clock.AdvanceTimeMilliseconds(1);
}
// Tear down
while (!payloadTypes.empty())
{
delete payloadTypes.front();
payloadTypes.pop_front();
}
delete vcm;
vcm = NULL;
rtpStream.Print();
Trace::ReturnTrace();
switch (ret)
{
switch (ret) {
case 1:
printf("Success\n");
return 0;
printf("Success\n");
return 0;
case -1:
printf("Failed\n");
return -1;
printf("Failed\n");
return -1;
case 0:
printf("Timeout\n");
return -1;
}
return 0;
printf("Timeout\n");
return -1;
}
webrtc::Trace::ReturnTrace();
return 0;
}

View File

@ -8,250 +8,136 @@
* be found in the AUTHORS file in the root of the source tree.
*/
#include <string.h>
#include <cassert>
#include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp.h"
#include "webrtc/modules/video_coding/main/interface/video_coding.h"
#include "webrtc/modules/video_coding/main/test/receiver_tests.h"
#include "webrtc/modules/video_coding/main/test/rtp_player.h"
#include "webrtc/modules/video_coding/main/test/test_macros.h"
#include "webrtc/system_wrappers/interface/clock.h"
#include "webrtc/modules/video_coding/main/test/vcm_payload_sink_factory.h"
#include "webrtc/system_wrappers/interface/event_wrapper.h"
#include "webrtc/system_wrappers/interface/thread_wrapper.h"
#include "webrtc/system_wrappers/interface/trace.h"
#include "webrtc/test/testsupport/fileutils.h"
using namespace webrtc;
using webrtc::rtpplayer::RtpPlayerInterface;
using webrtc::rtpplayer::VcmPayloadSinkFactory;
bool ProcessingThread(void* obj)
{
SharedState* state = static_cast<SharedState*>(obj);
if (state->_vcm.TimeUntilNextProcess() <= 0)
{
if (state->_vcm.Process() < 0)
{
return false;
}
}
return true;
namespace {
const bool kConfigProtectionEnabled = true;
const webrtc::VCMVideoProtection kConfigProtectionMethod =
webrtc::kProtectionDualDecoder;
const float kConfigLossRate = 0.05f;
const uint32_t kConfigRttMs = 50;
const bool kConfigReordering = false;
const uint32_t kConfigRenderDelayMs = 0;
const uint32_t kConfigMinPlayoutDelayMs = 0;
const int64_t kConfigMaxRuntimeMs = 10000;
} // namespace
bool PlayerThread(void* obj) {
assert(obj);
RtpPlayerInterface* rtp_player = static_cast<RtpPlayerInterface*>(obj);
webrtc::scoped_ptr<webrtc::EventWrapper> wait_event(
webrtc::EventWrapper::Create());
if (wait_event.get() == NULL) {
return false;
}
webrtc::Clock* clock = webrtc::Clock::GetRealTimeClock();
if (rtp_player->NextPacket(clock->TimeInMilliseconds()) < 0) {
return false;
}
wait_event->Wait(rtp_player->TimeUntilNextPacket());
return true;
}
bool RtpReaderThread(void* obj)
{
SharedState* state = static_cast<SharedState*>(obj);
EventWrapper& waitEvent = *EventWrapper::Create();
// RTP stream main loop
Clock* clock = Clock::GetRealTimeClock();
if (state->_rtpPlayer.NextPacket(clock->TimeInMilliseconds()) < 0)
{
return false;
}
waitEvent.Wait(state->_rtpPlayer.TimeUntilNextPacket());
delete &waitEvent;
return true;
bool ProcessingThread(void* obj) {
assert(obj);
return static_cast<VcmPayloadSinkFactory*>(obj)->ProcessAll();
}
bool DecodeThread(void* obj)
{
SharedState* state = static_cast<SharedState*>(obj);
state->_vcm.Decode(10000);
while (state->_vcm.DecodeDualFrame(0) == 1) {
}
return true;
bool DecodeThread(void* obj) {
assert(obj);
return static_cast<VcmPayloadSinkFactory*>(obj)->DecodeAll();
}
int RtpPlayMT(CmdArgs& args, int releaseTestNo, webrtc::VideoCodecType releaseTestVideoType)
{
// BEGIN Settings
bool protectionEnabled = true;
VCMVideoProtection protection = kProtectionDualDecoder;
uint8_t rttMS = 50;
float lossRate = 0.05f;
uint32_t renderDelayMs = 0;
uint32_t minPlayoutDelayMs = 0;
const int64_t MAX_RUNTIME_MS = 10000;
std::string outFilename = args.outputFile;
if (outFilename == "")
outFilename = test::OutputPath() + "RtpPlayMT_decoded.yuv";
int RtpPlayMT(const CmdArgs& args) {
std::string trace_file = webrtc::test::OutputPath() + "receiverTestTrace.txt";
webrtc::Trace::CreateTrace();
webrtc::Trace::SetTraceFile(trace_file.c_str());
webrtc::Trace::SetLevelFilter(webrtc::kTraceAll);
bool nackEnabled = (protectionEnabled &&
(protection == kProtectionDualDecoder ||
protection == kProtectionNack ||
kProtectionNackFEC));
VideoCodingModule* vcm = VideoCodingModule::Create(1);
RtpDataCallback dataCallback(vcm);
std::string rtpFilename;
rtpFilename = args.inputFile;
if (releaseTestNo > 0)
{
// Setup a release test
switch (releaseTestVideoType)
{
case webrtc::kVideoCodecVP8:
rtpFilename = args.inputFile;
outFilename = test::OutputPath() + "MTReceiveTest_VP8";
break;
default:
return -1;
}
switch (releaseTestNo)
{
case 1:
// Normal execution
protectionEnabled = false;
nackEnabled = false;
rttMS = 0;
lossRate = 0.0f;
outFilename += "_Normal.yuv";
break;
case 2:
// Packet loss
protectionEnabled = false;
nackEnabled = false;
rttMS = 0;
lossRate = 0.05f;
outFilename += "_0.05.yuv";
break;
case 3:
// Packet loss and NACK
protection = kProtectionNack;
nackEnabled = true;
protectionEnabled = true;
rttMS = 100;
lossRate = 0.05f;
outFilename += "_0.05_NACK_100ms.yuv";
break;
case 4:
// Packet loss and dual decoder
// Not implemented
return 0;
break;
default:
return -1;
}
printf("Watch %s to verify that the output is reasonable\n", outFilename.c_str());
}
RTPPlayer rtpStream(rtpFilename.c_str(), &dataCallback,
Clock::GetRealTimeClock());
PayloadTypeList payloadTypes;
payloadTypes.push_front(new PayloadCodecTuple(VCM_VP8_PAYLOAD_TYPE, "VP8",
kVideoCodecVP8));
Trace::CreateTrace();
Trace::SetTraceFile("receiverTestTrace.txt");
Trace::SetLevelFilter(webrtc::kTraceAll);
webrtc::rtpplayer::PayloadTypes payload_types;
payload_types.push_back(webrtc::rtpplayer::PayloadCodecTuple(
VCM_VP8_PAYLOAD_TYPE, "VP8", webrtc::kVideoCodecVP8));
// END Settings
std::string output_file = args.outputFile;
if (output_file == "") {
output_file = webrtc::test::OutputPath() + "RtpPlayMT_decoded.yuv";
}
// Set up
webrtc::Clock* clock = webrtc::Clock::GetRealTimeClock();
VcmPayloadSinkFactory factory(output_file, clock, kConfigProtectionEnabled,
kConfigProtectionMethod, kConfigRttMs, kConfigRenderDelayMs,
kConfigMinPlayoutDelayMs, false);
webrtc::scoped_ptr<RtpPlayerInterface> rtp_player(webrtc::rtpplayer::Create(
args.inputFile, &factory, clock, payload_types, kConfigLossRate,
kConfigRttMs, kConfigReordering));
if (rtp_player.get() == NULL) {
return -1;
}
SharedState mtState(*vcm, rtpStream);
if (rtpStream.Initialize(&payloadTypes) < 0)
{
return -1;
}
rtpStream.SimulatePacketLoss(lossRate, nackEnabled, rttMS);
int32_t ret = vcm->InitializeReceiver();
if (ret < 0)
{
return -1;
{
webrtc::scoped_ptr<webrtc::ThreadWrapper> player_thread(
webrtc::ThreadWrapper::CreateThread(PlayerThread, rtp_player.get(),
webrtc::kNormalPriority, "PlayerThread"));
if (player_thread.get() == NULL) {
printf("Unable to start RTP reader thread\n");
return -1;
}
// Create and start all threads
ThreadWrapper* processingThread = ThreadWrapper::CreateThread(
ProcessingThread, &mtState, kNormalPriority, "ProcessingThread");
ThreadWrapper* rtpReaderThread = ThreadWrapper::CreateThread(
RtpReaderThread, &mtState, kNormalPriority, "RtpReaderThread");
ThreadWrapper* decodeThread = ThreadWrapper::CreateThread(DecodeThread,
&mtState, kNormalPriority, "DecodeThread");
// Register receive codecs in VCM
for (PayloadTypeList::iterator it = payloadTypes.begin();
it != payloadTypes.end(); ++it) {
PayloadCodecTuple* payloadType = *it;
if (payloadType != NULL)
{
VideoCodec codec;
VideoCodingModule::Codec(payloadType->codecType, &codec);
codec.plType = payloadType->payloadType;
if (vcm->RegisterReceiveCodec(&codec, 1) < 0)
{
return -1;
}
}
webrtc::scoped_ptr<webrtc::ThreadWrapper> processing_thread(
webrtc::ThreadWrapper::CreateThread(ProcessingThread, &factory,
webrtc::kNormalPriority, "ProcessingThread"));
if (processing_thread.get() == NULL) {
printf("Unable to start processing thread\n");
return -1;
}
if (processingThread != NULL)
{
unsigned int tid;
processingThread->Start(tid);
}
else
{
printf("Unable to start processing thread\n");
return -1;
}
if (rtpReaderThread != NULL)
{
unsigned int tid;
rtpReaderThread->Start(tid);
}
else
{
printf("Unable to start RTP reader thread\n");
return -1;
}
if (decodeThread != NULL)
{
unsigned int tid;
decodeThread->Start(tid);
}
else
{
printf("Unable to start decode thread\n");
return -1;
webrtc::scoped_ptr<webrtc::ThreadWrapper> decode_thread(
webrtc::ThreadWrapper::CreateThread(DecodeThread, &factory,
webrtc::kNormalPriority, "DecodeThread"));
if (decode_thread.get() == NULL) {
printf("Unable to start decode thread\n");
return -1;
}
FrameReceiveCallback receiveCallback(outFilename);
vcm->RegisterReceiveCallback(&receiveCallback);
vcm->RegisterPacketRequestCallback(&rtpStream);
vcm->SetChannelParameters(0, 0, rttMS);
vcm->SetVideoProtection(protection, protectionEnabled);
vcm->SetRenderDelay(renderDelayMs);
vcm->SetMinimumPlayoutDelay(minPlayoutDelayMs);
vcm->SetNackSettings(kMaxNackListSize, kMaxPacketAgeToNack);
EventWrapper& waitEvent = *EventWrapper::Create();
// Decode for 10 seconds and then tear down and exit.
waitEvent.Wait(MAX_RUNTIME_MS);
// Tear down
while (!payloadTypes.empty())
{
delete payloadTypes.front();
payloadTypes.pop_front();
webrtc::scoped_ptr<webrtc::EventWrapper> wait_event(
webrtc::EventWrapper::Create());
if (wait_event.get() == NULL) {
printf("Unable to create wait event\n");
return -1;
}
while (!processingThread->Stop())
{
;
unsigned int dummy_thread_id;
player_thread->Start(dummy_thread_id);
processing_thread->Start(dummy_thread_id);
decode_thread->Start(dummy_thread_id);
wait_event->Wait(kConfigMaxRuntimeMs);
while (!player_thread->Stop()) {
}
while (!rtpReaderThread->Stop())
{
;
while (!processing_thread->Stop()) {
}
while (!decodeThread->Stop())
{
;
while (!decode_thread->Stop()) {
}
VideoCodingModule::Destroy(vcm);
vcm = NULL;
delete &waitEvent;
delete processingThread;
delete decodeThread;
delete rtpReaderThread;
rtpStream.Print();
Trace::ReturnTrace();
return 0;
}
rtp_player->Print();
webrtc::Trace::ReturnTrace();
return 0;
}