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:
parent
885cd13356
commit
56b5f77a2b
2
DEPS
2
DEPS
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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',
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "video_coding.h"
|
||||
#include "test_util.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
|
||||
class NormalTest;
|
||||
|
463
webrtc/modules/video_coding/main/test/pcap_file_reader.cc
Normal file
463
webrtc/modules/video_coding/main/test/pcap_file_reader.cc
Normal 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
|
26
webrtc/modules/video_coding/main/test/pcap_file_reader.h
Normal file
26
webrtc/modules/video_coding/main/test/pcap_file_reader.h
Normal 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_
|
@ -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
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
166
webrtc/modules/video_coding/main/test/rtp_file_reader.cc
Normal file
166
webrtc/modules/video_coding/main/test/rtp_file_reader.cc
Normal 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
|
26
webrtc/modules/video_coding/main/test/rtp_file_reader.h
Normal file
26
webrtc/modules/video_coding/main/test/rtp_file_reader.h
Normal 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_
|
@ -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
|
@ -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
|
||||
|
@ -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_
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user