This is accomplished by breaking out RTX and FEC/RED functionality from the RTP module and keeping track of the base payload type, that is the payload type received when not receiving RTX. Enables retransmissions over RTX by default in the loopback test. BUG=1811 TESTS=voe/vie_auto_test --automated and trybots. R=mflodman@webrtc.org, pbos@webrtc.org, xians@webrtc.org Review URL: https://webrtc-codereview.appspot.com/2154004 git-svn-id: http://webrtc.googlecode.com/svn/trunk@4692 4adac7df-926f-26a2-2b94-8c16560cd09d
482 lines
13 KiB
C++
482 lines
13 KiB
C++
/*
|
|
* Copyright (c) 2012 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/test_callbacks.h"
|
|
|
|
#include <math.h>
|
|
|
|
#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
|
|
#include "webrtc/modules/rtp_rtcp/interface/rtp_header_parser.h"
|
|
#include "webrtc/modules/rtp_rtcp/interface/rtp_payload_registry.h"
|
|
#include "webrtc/modules/rtp_rtcp/interface/rtp_receiver.h"
|
|
#include "webrtc/modules/utility/interface/rtp_dump.h"
|
|
#include "webrtc/modules/video_coding/main/test/test_macros.h"
|
|
#include "webrtc/system_wrappers/interface/clock.h"
|
|
|
|
namespace webrtc {
|
|
|
|
/******************************
|
|
* VCMEncodeCompleteCallback
|
|
*****************************/
|
|
// Basic callback implementation
|
|
// passes the encoded frame directly to the encoder
|
|
// Packetization callback implementation
|
|
VCMEncodeCompleteCallback::VCMEncodeCompleteCallback(FILE* encodedFile):
|
|
_encodedFile(encodedFile),
|
|
_encodedBytes(0),
|
|
_VCMReceiver(NULL),
|
|
_seqNo(0),
|
|
_encodeComplete(false),
|
|
_width(0),
|
|
_height(0),
|
|
_codecType(kRtpVideoNone)
|
|
{
|
|
//
|
|
}
|
|
VCMEncodeCompleteCallback::~VCMEncodeCompleteCallback()
|
|
{
|
|
}
|
|
|
|
void
|
|
VCMEncodeCompleteCallback::RegisterTransportCallback(
|
|
VCMPacketizationCallback* transport)
|
|
{
|
|
}
|
|
|
|
int32_t
|
|
VCMEncodeCompleteCallback::SendData(
|
|
const FrameType frameType,
|
|
const uint8_t payloadType,
|
|
const uint32_t timeStamp,
|
|
int64_t capture_time_ms,
|
|
const uint8_t* payloadData,
|
|
const uint32_t payloadSize,
|
|
const RTPFragmentationHeader& fragmentationHeader,
|
|
const RTPVideoHeader* videoHdr)
|
|
{
|
|
// will call the VCMReceiver input packet
|
|
_frameType = frameType;
|
|
// writing encodedData into file
|
|
if (fwrite(payloadData, 1, payloadSize, _encodedFile) != payloadSize) {
|
|
return -1;
|
|
}
|
|
WebRtcRTPHeader rtpInfo;
|
|
rtpInfo.header.markerBit = true; // end of frame
|
|
rtpInfo.type.Video.isFirstPacket = true;
|
|
rtpInfo.type.Video.codec = _codecType;
|
|
rtpInfo.type.Video.height = (uint16_t)_height;
|
|
rtpInfo.type.Video.width = (uint16_t)_width;
|
|
switch (_codecType)
|
|
{
|
|
case webrtc::kRtpVideoVp8:
|
|
rtpInfo.type.Video.codecHeader.VP8.InitRTPVideoHeaderVP8();
|
|
rtpInfo.type.Video.codecHeader.VP8.nonReference =
|
|
videoHdr->codecHeader.VP8.nonReference;
|
|
rtpInfo.type.Video.codecHeader.VP8.pictureId =
|
|
videoHdr->codecHeader.VP8.pictureId;
|
|
break;
|
|
default:
|
|
assert(false);
|
|
return -1;
|
|
}
|
|
|
|
rtpInfo.header.payloadType = payloadType;
|
|
rtpInfo.header.sequenceNumber = _seqNo++;
|
|
rtpInfo.header.ssrc = 0;
|
|
rtpInfo.header.timestamp = timeStamp;
|
|
rtpInfo.frameType = frameType;
|
|
// Size should also be received from that table, since the payload type
|
|
// defines the size.
|
|
|
|
_encodedBytes += payloadSize;
|
|
// directly to receiver
|
|
int ret = _VCMReceiver->IncomingPacket(payloadData, payloadSize, rtpInfo);
|
|
_encodeComplete = true;
|
|
|
|
return ret;
|
|
}
|
|
|
|
float
|
|
VCMEncodeCompleteCallback::EncodedBytes()
|
|
{
|
|
return _encodedBytes;
|
|
}
|
|
|
|
bool
|
|
VCMEncodeCompleteCallback::EncodeComplete()
|
|
{
|
|
if (_encodeComplete)
|
|
{
|
|
_encodeComplete = false;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void
|
|
VCMEncodeCompleteCallback::Initialize()
|
|
{
|
|
_encodeComplete = false;
|
|
_encodedBytes = 0;
|
|
_seqNo = 0;
|
|
return;
|
|
}
|
|
|
|
void
|
|
VCMEncodeCompleteCallback::ResetByteCount()
|
|
{
|
|
_encodedBytes = 0;
|
|
}
|
|
|
|
/***********************************/
|
|
/* VCMRTPEncodeCompleteCallback */
|
|
/***********************************/
|
|
// Encode Complete callback implementation
|
|
// passes the encoded frame via the RTP module to the decoder
|
|
// Packetization callback implementation
|
|
|
|
int32_t
|
|
VCMRTPEncodeCompleteCallback::SendData(
|
|
const FrameType frameType,
|
|
const uint8_t payloadType,
|
|
const uint32_t timeStamp,
|
|
int64_t capture_time_ms,
|
|
const uint8_t* payloadData,
|
|
const uint32_t payloadSize,
|
|
const RTPFragmentationHeader& fragmentationHeader,
|
|
const RTPVideoHeader* videoHdr)
|
|
{
|
|
_frameType = frameType;
|
|
_encodedBytes+= payloadSize;
|
|
_encodeComplete = true;
|
|
return _RTPModule->SendOutgoingData(frameType,
|
|
payloadType,
|
|
timeStamp,
|
|
capture_time_ms,
|
|
payloadData,
|
|
payloadSize,
|
|
&fragmentationHeader,
|
|
videoHdr);
|
|
}
|
|
|
|
float
|
|
VCMRTPEncodeCompleteCallback::EncodedBytes()
|
|
{
|
|
// only good for one call - after which will reset value;
|
|
float tmp = _encodedBytes;
|
|
_encodedBytes = 0;
|
|
return tmp;
|
|
}
|
|
|
|
bool
|
|
VCMRTPEncodeCompleteCallback::EncodeComplete()
|
|
{
|
|
if (_encodeComplete)
|
|
{
|
|
_encodeComplete = false;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Decoded Frame Callback Implementation
|
|
|
|
int32_t
|
|
VCMDecodeCompleteCallback::FrameToRender(I420VideoFrame& videoFrame)
|
|
{
|
|
if (PrintI420VideoFrame(videoFrame, _decodedFile) < 0) {
|
|
return -1;
|
|
}
|
|
_decodedBytes+= CalcBufferSize(kI420, videoFrame.width(),
|
|
videoFrame.height());
|
|
return VCM_OK;
|
|
}
|
|
|
|
int32_t
|
|
VCMDecodeCompleteCallback::DecodedBytes()
|
|
{
|
|
return _decodedBytes;
|
|
}
|
|
|
|
RTPSendCompleteCallback::RTPSendCompleteCallback(Clock* clock,
|
|
const char* filename):
|
|
_clock(clock),
|
|
_sendCount(0),
|
|
rtp_payload_registry_(NULL),
|
|
rtp_receiver_(NULL),
|
|
_rtp(NULL),
|
|
_lossPct(0),
|
|
_burstLength(0),
|
|
_networkDelayMs(0),
|
|
_jitterVar(0),
|
|
_prevLossState(0),
|
|
_totalSentLength(0),
|
|
_rtpPackets(),
|
|
_rtpDump(NULL)
|
|
{
|
|
if (filename != NULL)
|
|
{
|
|
_rtpDump = RtpDump::CreateRtpDump();
|
|
_rtpDump->Start(filename);
|
|
}
|
|
}
|
|
|
|
RTPSendCompleteCallback::~RTPSendCompleteCallback()
|
|
{
|
|
if (_rtpDump != NULL)
|
|
{
|
|
_rtpDump->Stop();
|
|
RtpDump::DestroyRtpDump(_rtpDump);
|
|
}
|
|
// Delete remaining packets
|
|
while (!_rtpPackets.empty())
|
|
{
|
|
// Take first packet in list
|
|
delete _rtpPackets.front();
|
|
_rtpPackets.pop_front();
|
|
}
|
|
}
|
|
|
|
int
|
|
RTPSendCompleteCallback::SendPacket(int channel, const void *data, int len)
|
|
{
|
|
_sendCount++;
|
|
_totalSentLength += len;
|
|
|
|
if (_rtpDump != NULL)
|
|
{
|
|
if (_rtpDump->DumpPacket((const uint8_t*)data, len) != 0)
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
bool transmitPacket = true;
|
|
transmitPacket = PacketLoss();
|
|
|
|
int64_t now = _clock->TimeInMilliseconds();
|
|
// Insert outgoing packet into list
|
|
if (transmitPacket)
|
|
{
|
|
RtpPacket* newPacket = new RtpPacket();
|
|
memcpy(newPacket->data, data, len);
|
|
newPacket->length = len;
|
|
// Simulate receive time = network delay + packet jitter
|
|
// simulated as a Normal distribution random variable with
|
|
// mean = networkDelay and variance = jitterVar
|
|
int32_t
|
|
simulatedDelay = (int32_t)NormalDist(_networkDelayMs,
|
|
sqrt(_jitterVar));
|
|
newPacket->receiveTime = now + simulatedDelay;
|
|
_rtpPackets.push_back(newPacket);
|
|
}
|
|
|
|
// Are we ready to send packets to the receiver?
|
|
RtpPacket* packet = NULL;
|
|
|
|
while (!_rtpPackets.empty())
|
|
{
|
|
// Take first packet in list
|
|
packet = _rtpPackets.front();
|
|
int64_t timeToReceive = packet->receiveTime - now;
|
|
if (timeToReceive > 0)
|
|
{
|
|
// No available packets to send
|
|
break;
|
|
}
|
|
|
|
_rtpPackets.pop_front();
|
|
assert(_rtp); // We must have a configured RTP module for this test.
|
|
// Send to receive side
|
|
RTPHeader header;
|
|
scoped_ptr<RtpHeaderParser> parser(RtpHeaderParser::Create());
|
|
if (!parser->Parse(packet->data, packet->length, &header)) {
|
|
delete packet;
|
|
return -1;
|
|
}
|
|
PayloadUnion payload_specific;
|
|
if (!rtp_payload_registry_->GetPayloadSpecifics(
|
|
header.payloadType, &payload_specific)) {
|
|
return -1;
|
|
}
|
|
if (!rtp_receiver_->IncomingRtpPacket(header, packet->data,
|
|
packet->length, payload_specific,
|
|
true))
|
|
{
|
|
delete packet;
|
|
return -1;
|
|
}
|
|
delete packet;
|
|
packet = NULL;
|
|
}
|
|
return len; // OK
|
|
}
|
|
|
|
int
|
|
RTPSendCompleteCallback::SendRTCPPacket(int channel, const void *data, int len)
|
|
{
|
|
// Incorporate network conditions
|
|
return SendPacket(channel, data, len);
|
|
}
|
|
|
|
void
|
|
RTPSendCompleteCallback::SetLossPct(double lossPct)
|
|
{
|
|
_lossPct = lossPct;
|
|
return;
|
|
}
|
|
|
|
void
|
|
RTPSendCompleteCallback::SetBurstLength(double burstLength)
|
|
{
|
|
_burstLength = burstLength;
|
|
return;
|
|
}
|
|
|
|
bool
|
|
RTPSendCompleteCallback::PacketLoss()
|
|
{
|
|
bool transmitPacket = true;
|
|
if (_burstLength <= 1.0)
|
|
{
|
|
// Random loss: if _burstLength parameter is not set, or <=1
|
|
if (UnifomLoss(_lossPct))
|
|
{
|
|
// drop
|
|
transmitPacket = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Simulate bursty channel (Gilbert model)
|
|
// (1st order) Markov chain model with memory of the previous/last
|
|
// packet state (loss or received)
|
|
|
|
// 0 = received state
|
|
// 1 = loss state
|
|
|
|
// probTrans10: if previous packet is lost, prob. to -> received state
|
|
// probTrans11: if previous packet is lost, prob. to -> loss state
|
|
|
|
// probTrans01: if previous packet is received, prob. to -> loss state
|
|
// probTrans00: if previous packet is received, prob. to -> received
|
|
|
|
// Map the two channel parameters (average loss rate and burst length)
|
|
// to the transition probabilities:
|
|
double probTrans10 = 100 * (1.0 / _burstLength);
|
|
double probTrans11 = (100.0 - probTrans10);
|
|
double probTrans01 = (probTrans10 * ( _lossPct / (100.0 - _lossPct)));
|
|
|
|
// Note: Random loss (Bernoulli) model is a special case where:
|
|
// burstLength = 100.0 / (100.0 - _lossPct) (i.e., p10 + p01 = 100)
|
|
|
|
if (_prevLossState == 0 )
|
|
{
|
|
// previous packet was received
|
|
if (UnifomLoss(probTrans01))
|
|
{
|
|
// drop, update previous state to loss
|
|
_prevLossState = 1;
|
|
transmitPacket = false;
|
|
}
|
|
}
|
|
else if (_prevLossState == 1)
|
|
{
|
|
_prevLossState = 0;
|
|
// previous packet was lost
|
|
if (UnifomLoss(probTrans11))
|
|
{
|
|
// drop, update previous state to loss
|
|
_prevLossState = 1;
|
|
transmitPacket = false;
|
|
}
|
|
}
|
|
}
|
|
return transmitPacket;
|
|
}
|
|
|
|
|
|
bool
|
|
RTPSendCompleteCallback::UnifomLoss(double lossPct)
|
|
{
|
|
double randVal = (std::rand() + 1.0)/(RAND_MAX + 1.0);
|
|
return randVal < lossPct/100;
|
|
}
|
|
|
|
int32_t
|
|
PacketRequester::ResendPackets(const uint16_t* sequenceNumbers,
|
|
uint16_t length)
|
|
{
|
|
return _rtp.SendNACK(sequenceNumbers, length);
|
|
}
|
|
|
|
int32_t
|
|
SendStatsTest::SendStatistics(const uint32_t bitRate,
|
|
const uint32_t frameRate)
|
|
{
|
|
TEST(frameRate <= _framerate);
|
|
TEST(bitRate > _bitrate / 2 && bitRate < 3 * _bitrate / 2);
|
|
printf("VCM 1 sec: Bit rate: %u\tFrame rate: %u\n", bitRate, frameRate);
|
|
return 0;
|
|
}
|
|
|
|
int32_t KeyFrameReqTest::RequestKeyFrame() {
|
|
printf("Key frame requested\n");
|
|
return 0;
|
|
}
|
|
|
|
|
|
VideoProtectionCallback::VideoProtectionCallback():
|
|
delta_fec_params_(),
|
|
key_fec_params_()
|
|
{
|
|
memset(&delta_fec_params_, 0, sizeof(delta_fec_params_));
|
|
memset(&key_fec_params_, 0, sizeof(key_fec_params_));
|
|
}
|
|
|
|
VideoProtectionCallback::~VideoProtectionCallback()
|
|
{
|
|
//
|
|
}
|
|
|
|
int32_t
|
|
VideoProtectionCallback::ProtectionRequest(
|
|
const FecProtectionParams* delta_fec_params,
|
|
const FecProtectionParams* key_fec_params,
|
|
uint32_t* sent_video_rate_bps,
|
|
uint32_t* sent_nack_rate_bps,
|
|
uint32_t* sent_fec_rate_bps)
|
|
{
|
|
key_fec_params_ = *key_fec_params;
|
|
delta_fec_params_ = *delta_fec_params;
|
|
|
|
// Update RTP
|
|
if (_rtp->SetFecParameters(&delta_fec_params_,
|
|
&key_fec_params_) != 0)
|
|
{
|
|
printf("Error in Setting FEC rate\n");
|
|
return -1;
|
|
|
|
}
|
|
return 0;
|
|
|
|
}
|
|
|
|
FecProtectionParams VideoProtectionCallback::DeltaFecParameters() const
|
|
{
|
|
return delta_fec_params_;
|
|
}
|
|
|
|
FecProtectionParams VideoProtectionCallback::KeyFecParameters() const
|
|
{
|
|
return key_fec_params_;
|
|
}
|
|
} // namespace webrtc
|