Add metric for number of packets discarded by JB due to not being decodable

Also fixes a couple of bugs related to sequence number wrap found while
testing.

BUG=
TEST=

Review URL: http://webrtc-codereview.appspot.com/218001

git-svn-id: http://webrtc.googlecode.com/svn/trunk@732 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
stefan@webrtc.org
2011-10-13 07:35:37 +00:00
parent 77d7d5455e
commit 4c059d87b3
12 changed files with 305 additions and 164 deletions

View File

@@ -793,18 +793,10 @@ VP8Decoder::Decode(const EncodedImage& inputImage,
{ {
return WEBRTC_VIDEO_CODEC_UNINITIALIZED; return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
} }
if (inputImage._buffer == NULL)
{
return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
}
if (_decodeCompleteCallback == NULL) if (_decodeCompleteCallback == NULL)
{ {
return WEBRTC_VIDEO_CODEC_UNINITIALIZED; return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
} }
if (inputImage._length <= 0)
{
return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
}
if (inputImage._completeFrame == false) if (inputImage._completeFrame == false)
{ {
// future improvement // future improvement
@@ -815,6 +807,11 @@ VP8Decoder::Decode(const EncodedImage& inputImage,
} }
// otherwise allow for incomplete frames to be decoded. // otherwise allow for incomplete frames to be decoded.
} }
if (inputImage._buffer == NULL && inputImage._length > 0)
{
return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
}
#ifdef INDEPENDENT_PARTITIONS #ifdef INDEPENDENT_PARTITIONS
if (fragmentation == NULL) if (fragmentation == NULL)
{ {
@@ -844,8 +841,13 @@ VP8Decoder::Decode(const EncodedImage& inputImage,
return WEBRTC_VIDEO_CODEC_ERROR; return WEBRTC_VIDEO_CODEC_ERROR;
} }
#else #else
WebRtc_UWord8* buffer = inputImage._buffer;
if (inputImage._length == 0)
{
buffer = NULL; // Triggers full frame concealment
}
if (vpx_codec_decode(_decoder, if (vpx_codec_decode(_decoder,
inputImage._buffer, buffer,
inputImage._length, inputImage._length,
0, 0,
VPX_DL_REALTIME)) VPX_DL_REALTIME))
@@ -947,11 +949,12 @@ VP8Decoder::DecodePartitions(const EncodedImage& input_image,
partition_length, partition_length,
0, 0,
VPX_DL_REALTIME)) { VPX_DL_REALTIME)) {
return WEBRTC_VIDEO_CODEC_ERROR; return WEBRTC_VIDEO_CODEC_ERROR;
} }
} }
// Signal end of frame data // Signal end of frame data. If there was no frame data this will trigger
// a full frame concealment.
if (vpx_codec_decode(_decoder, NULL, 0, 0, VPX_DL_REALTIME)) if (vpx_codec_decode(_decoder, NULL, 0, 0, VPX_DL_REALTIME))
return WEBRTC_VIDEO_CODEC_ERROR; return WEBRTC_VIDEO_CODEC_ERROR;
return WEBRTC_VIDEO_CODEC_OK; return WEBRTC_VIDEO_CODEC_OK;

View File

@@ -58,19 +58,19 @@ VCMFrameBuffer::SetPreviousFrameLoss()
} }
WebRtc_Word32 WebRtc_Word32
VCMFrameBuffer::GetLowSeqNum() VCMFrameBuffer::GetLowSeqNum() const
{ {
return _sessionInfo.GetLowSeqNum(); return _sessionInfo.GetLowSeqNum();
} }
WebRtc_Word32 WebRtc_Word32
VCMFrameBuffer::GetHighSeqNum() VCMFrameBuffer::GetHighSeqNum() const
{ {
return _sessionInfo.GetHighSeqNum(); return _sessionInfo.GetHighSeqNum();
} }
bool bool
VCMFrameBuffer::IsSessionComplete() VCMFrameBuffer::IsSessionComplete() const
{ {
return _sessionInfo.IsSessionComplete(); return _sessionInfo.IsSessionComplete();
} }
@@ -229,7 +229,7 @@ VCMFrameBuffer::GetNackCount() const
} }
bool bool
VCMFrameBuffer::HaveLastPacket() VCMFrameBuffer::HaveLastPacket() const
{ {
return _sessionInfo.HaveLastPacket(); return _sessionInfo.HaveLastPacket();
} }
@@ -311,9 +311,12 @@ VCMFrameBuffer::SetState(VCMFrameBufferStateEnum state)
break; break;
case kStateDecoding: case kStateDecoding:
// we can go to this state from state kStateComplete kStateIncomplete // A frame migth have received empty packets, or media packets might
// have been removed when making the frame decodable. The frame can
// still be set to decodable since it can be used to inform the
// decoder of a frame loss.
assert(_state == kStateComplete || _state == kStateIncomplete || assert(_state == kStateComplete || _state == kStateIncomplete ||
_state == kStateDecodable); _state == kStateDecodable || _state == kStateEmpty);
// Transfer frame information to EncodedFrame and create any codec // Transfer frame information to EncodedFrame and create any codec
// specific information // specific information
RestructureFrameInformation(); RestructureFrameInformation();
@@ -372,13 +375,17 @@ VCMFrameBuffer::ExtractFromStorage(const EncodedVideoData& frameFromStorage)
return VCM_OK; return VCM_OK;
} }
int VCMFrameBuffer::NotDecodablePackets() const {
return _sessionInfo.NotDecodablePackets();
}
// Set counted status (as counted by JB or not) // Set counted status (as counted by JB or not)
void VCMFrameBuffer::SetCountedFrame(bool frameCounted) void VCMFrameBuffer::SetCountedFrame(bool frameCounted)
{ {
_frameCounted = frameCounted; _frameCounted = frameCounted;
} }
bool VCMFrameBuffer::GetCountedFrame() bool VCMFrameBuffer::GetCountedFrame() const
{ {
return _frameCounted; return _frameCounted;
} }
@@ -399,7 +406,7 @@ VCMFrameBuffer::GetState(WebRtc_UWord32& timeStamp) const
} }
bool bool
VCMFrameBuffer::IsRetransmitted() VCMFrameBuffer::IsRetransmitted() const
{ {
return _sessionInfo.IsRetransmitted(); return _sessionInfo.IsRetransmitted();
} }

View File

@@ -42,22 +42,22 @@ public:
VCMFrameBufferStateEnum GetState(WebRtc_UWord32& timeStamp) const; VCMFrameBufferStateEnum GetState(WebRtc_UWord32& timeStamp) const;
void SetState(VCMFrameBufferStateEnum state); // Set state of frame void SetState(VCMFrameBufferStateEnum state); // Set state of frame
bool IsRetransmitted(); bool IsRetransmitted() const;
bool IsSessionComplete(); bool IsSessionComplete() const;
bool HaveLastPacket(); bool HaveLastPacket() const;
bool ForceSetHaveLastPacket(); bool ForceSetHaveLastPacket();
// Makes sure the session contain a decodable stream. // Makes sure the session contain a decodable stream.
void MakeSessionDecodable(); void MakeSessionDecodable();
// Sequence numbers // Sequence numbers
// Get lowest packet sequence number in frame // Get lowest packet sequence number in frame
WebRtc_Word32 GetLowSeqNum(); WebRtc_Word32 GetLowSeqNum() const;
// Get highest packet sequence number in frame // Get highest packet sequence number in frame
WebRtc_Word32 GetHighSeqNum(); WebRtc_Word32 GetHighSeqNum() const;
// Set counted status (as counted by JB or not) // Set counted status (as counted by JB or not)
void SetCountedFrame(bool frameCounted); void SetCountedFrame(bool frameCounted);
bool GetCountedFrame(); bool GetCountedFrame() const;
// NACK // NACK
// Zero out all entries in list up to and including _lowSeqNum // Zero out all entries in list up to and including _lowSeqNum
@@ -76,6 +76,10 @@ public:
WebRtc_Word32 ExtractFromStorage(const EncodedVideoData& frameFromStorage); WebRtc_Word32 ExtractFromStorage(const EncodedVideoData& frameFromStorage);
// The number of packets discarded because the decoder can't make use of
// them.
int NotDecodablePackets() const;
protected: protected:
void RestructureFrameInformation(); void RestructureFrameInformation();
void PrepareForDecode(); void PrepareForDecode();

View File

@@ -26,7 +26,8 @@ VCMFrameListTimestampOrderAsc::Flush()
while(Erase(First()) != -1) { } while(Erase(First()) != -1) { }
} }
// Inserts frame in timestamp order, with the oldest timestamp first. Takes wrap arounds into account // Inserts frame in timestamp order, with the oldest timestamp first. Takes wrap
// arounds into account
WebRtc_Word32 WebRtc_Word32
VCMFrameListTimestampOrderAsc::Insert(VCMFrameBuffer* frame) VCMFrameListTimestampOrderAsc::Insert(VCMFrameBuffer* frame)
{ {
@@ -40,7 +41,8 @@ VCMFrameListTimestampOrderAsc::Insert(VCMFrameBuffer* frame)
while (item != NULL) while (item != NULL)
{ {
const WebRtc_UWord32 itemTimestamp = item->GetItem()->TimeStamp(); const WebRtc_UWord32 itemTimestamp = item->GetItem()->TimeStamp();
if (VCMJitterBuffer::LatestTimestamp(itemTimestamp, frame->TimeStamp()) == itemTimestamp) if (LatestTimestamp(itemTimestamp, frame->TimeStamp(), NULL) ==
itemTimestamp)
{ {
if (InsertBefore(item, newItem) < 0) if (InsertBefore(item, newItem) < 0)
{ {
@@ -101,7 +103,9 @@ VCMFrameListTimestampOrderAsc::FindFrame(FindFrameCriteria criteria,
const void* compareWith, const void* compareWith,
VCMFrameListItem* startItem) const VCMFrameListItem* startItem) const
{ {
const VCMFrameListItem* frameListItem = FindFrameListItem(criteria, compareWith, startItem); const VCMFrameListItem* frameListItem = FindFrameListItem(criteria,
compareWith,
startItem);
if (frameListItem == NULL) if (frameListItem == NULL)
{ {
return NULL; return NULL;

View File

@@ -71,8 +71,9 @@ VCMJitterBuffer::VCMJitterBuffer(WebRtc_Word32 vcmId, WebRtc_Word32 receiverId,
_maxNumberOfFrames(kStartNumberOfFrames), _maxNumberOfFrames(kStartNumberOfFrames),
_frameBuffers(), _frameBuffers(),
_frameBuffersTSOrder(), _frameBuffersTSOrder(),
_lastDecodedSeqNum(), _lastDecodedSeqNum(-1),
_lastDecodedTimeStamp(-1), _lastDecodedTimeStamp(-1),
_packetsNotDecodable(0),
_receiveStatistics(), _receiveStatistics(),
_incomingFrameRate(0), _incomingFrameRate(0),
_incomingFrameCount(0), _incomingFrameCount(0),
@@ -92,7 +93,6 @@ VCMJitterBuffer::VCMJitterBuffer(WebRtc_Word32 vcmId, WebRtc_Word32 receiverId,
{ {
memset(_frameBuffers, 0, sizeof(_frameBuffers)); memset(_frameBuffers, 0, sizeof(_frameBuffers));
memset(_receiveStatistics, 0, sizeof(_receiveStatistics)); memset(_receiveStatistics, 0, sizeof(_receiveStatistics));
_lastDecodedSeqNum = -1;
memset(_NACKSeqNumInternal, -1, sizeof(_NACKSeqNumInternal)); memset(_NACKSeqNumInternal, -1, sizeof(_NACKSeqNumInternal));
for (int i = 0; i< kStartNumberOfFrames; i++) for (int i = 0; i< kStartNumberOfFrames; i++)
@@ -127,7 +127,6 @@ VCMJitterBuffer::operator=(const VCMJitterBuffer& rhs)
_running = rhs._running; _running = rhs._running;
_master = !rhs._master; _master = !rhs._master;
_maxNumberOfFrames = rhs._maxNumberOfFrames; _maxNumberOfFrames = rhs._maxNumberOfFrames;
_lastDecodedTimeStamp = rhs._lastDecodedTimeStamp;
_incomingFrameRate = rhs._incomingFrameRate; _incomingFrameRate = rhs._incomingFrameRate;
_incomingFrameCount = rhs._incomingFrameCount; _incomingFrameCount = rhs._incomingFrameCount;
_timeLastIncomingFrameCount = rhs._timeLastIncomingFrameCount; _timeLastIncomingFrameCount = rhs._timeLastIncomingFrameCount;
@@ -145,6 +144,8 @@ VCMJitterBuffer::operator=(const VCMJitterBuffer& rhs)
_missingMarkerBits = rhs._missingMarkerBits; _missingMarkerBits = rhs._missingMarkerBits;
_firstPacket = rhs._firstPacket; _firstPacket = rhs._firstPacket;
_lastDecodedSeqNum = rhs._lastDecodedSeqNum; _lastDecodedSeqNum = rhs._lastDecodedSeqNum;
_lastDecodedTimeStamp = rhs._lastDecodedTimeStamp;
_packetsNotDecodable = rhs._packetsNotDecodable;
memcpy(_receiveStatistics, rhs._receiveStatistics, memcpy(_receiveStatistics, rhs._receiveStatistics,
sizeof(_receiveStatistics)); sizeof(_receiveStatistics));
memcpy(_NACKSeqNumInternal, rhs._NACKSeqNumInternal, memcpy(_NACKSeqNumInternal, rhs._NACKSeqNumInternal,
@@ -174,30 +175,6 @@ VCMJitterBuffer::operator=(const VCMJitterBuffer& rhs)
return *this; return *this;
} }
WebRtc_UWord32
VCMJitterBuffer::LatestTimestamp(const WebRtc_UWord32 existingTimestamp,
const WebRtc_UWord32 newTimestamp)
{
bool wrap = (newTimestamp < 0x0000ffff && existingTimestamp > 0xffff0000) ||
(newTimestamp > 0xffff0000 && existingTimestamp < 0x0000ffff);
if (existingTimestamp > newTimestamp && !wrap)
{
return existingTimestamp;
}
else if (existingTimestamp <= newTimestamp && !wrap)
{
return newTimestamp;
}
else if (existingTimestamp < newTimestamp && wrap)
{
return existingTimestamp;
}
else
{
return newTimestamp;
}
}
// Start jitter buffer // Start jitter buffer
void void
VCMJitterBuffer::Start() VCMJitterBuffer::Start()
@@ -223,6 +200,7 @@ VCMJitterBuffer::Start()
_firstPacket = true; _firstPacket = true;
_NACKSeqNumLength = 0; _NACKSeqNumLength = 0;
_rttMs = 0; _rttMs = 0;
_packetsNotDecodable = 0;
WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCoding, VCMId(_vcmId, WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCoding, VCMId(_vcmId,
_receiverId), "JB(0x%x): Jitter buffer: start", this); _receiverId), "JB(0x%x): Jitter buffer: start", this);
@@ -280,6 +258,7 @@ VCMJitterBuffer::FlushInternal()
} }
_lastDecodedSeqNum = -1; _lastDecodedSeqNum = -1;
_lastDecodedTimeStamp = -1; _lastDecodedTimeStamp = -1;
_packetsNotDecodable = 0;
_frameEvent.Reset(); _frameEvent.Reset();
_packetEvent.Reset(); _packetEvent.Reset();
@@ -353,7 +332,7 @@ VCMJitterBuffer::UpdateFrameState(VCMFrameBuffer* frame)
// an old complete frame can arrive too late // an old complete frame can arrive too late
if (_lastDecodedTimeStamp > 0 && if (_lastDecodedTimeStamp > 0 &&
LatestTimestamp(static_cast<WebRtc_UWord32>(_lastDecodedTimeStamp), LatestTimestamp(static_cast<WebRtc_UWord32>(_lastDecodedTimeStamp),
frame->TimeStamp()) == _lastDecodedTimeStamp) frame->TimeStamp(), NULL) == _lastDecodedTimeStamp)
{ {
// Frame is older than the latest decoded frame, drop it. // Frame is older than the latest decoded frame, drop it.
// This will trigger a release in CleanUpSizeZeroFrames later. // This will trigger a release in CleanUpSizeZeroFrames later.
@@ -427,7 +406,6 @@ VCMJitterBuffer::UpdateFrameState(VCMFrameBuffer* frame)
} }
} }
// Get received key and delta frames // Get received key and delta frames
WebRtc_Word32 WebRtc_Word32
VCMJitterBuffer::GetFrameStatistics(WebRtc_UWord32& receivedDeltaFrames, VCMJitterBuffer::GetFrameStatistics(WebRtc_UWord32& receivedDeltaFrames,
@@ -441,6 +419,11 @@ VCMJitterBuffer::GetFrameStatistics(WebRtc_UWord32& receivedDeltaFrames,
return 0; return 0;
} }
WebRtc_UWord32 VCMJitterBuffer::NumNotDecodablePackets() const {
CriticalSectionScoped cs(_critSect);
return _packetsNotDecodable;
}
WebRtc_UWord32 VCMJitterBuffer::DiscardedPackets() const { WebRtc_UWord32 VCMJitterBuffer::DiscardedPackets() const {
CriticalSectionScoped cs(_critSect); CriticalSectionScoped cs(_critSect);
return _discardedPackets; return _discardedPackets;
@@ -458,7 +441,7 @@ VCMJitterBuffer::GetFrame(const VCMPacket& packet, VCMEncodedFrame*& frame)
_critSect.Enter(); _critSect.Enter();
// Make sure that old empty packets are inserted. // Make sure that old empty packets are inserted.
if (LatestTimestamp(static_cast<WebRtc_UWord32>(_lastDecodedTimeStamp), if (LatestTimestamp(static_cast<WebRtc_UWord32>(_lastDecodedTimeStamp),
packet.timestamp) == _lastDecodedTimeStamp packet.timestamp, NULL) == _lastDecodedTimeStamp
&& packet.sizeBytes > 0) && packet.sizeBytes > 0)
{ {
_discardedPackets++; // Only counts discarded media packets _discardedPackets++; // Only counts discarded media packets
@@ -1136,6 +1119,8 @@ VCMJitterBuffer::GetFrameForDecoding()
// Set as decoding. Propagates the missingFrame bit. // Set as decoding. Propagates the missingFrame bit.
oldestFrame->SetState(kStateDecoding); oldestFrame->SetState(kStateDecoding);
_packetsNotDecodable += oldestFrame->NotDecodablePackets();
// Store current seqnum & time // Store current seqnum & time
_lastDecodedSeqNum = oldestFrame->GetHighSeqNum(); _lastDecodedSeqNum = oldestFrame->GetHighSeqNum();
_lastDecodedTimeStamp = oldestFrame->TimeStamp(); _lastDecodedTimeStamp = oldestFrame->TimeStamp();
@@ -1204,8 +1189,9 @@ VCMJitterBuffer::GetFrameForDecodingNACK()
return oldestFrame; return oldestFrame;
} }
// Must be called under the critical section _critSect. Should never be called with // Must be called under the critical section _critSect. Should never be called
// retransmitted frames, they must be filtered out before this function is called. // with retransmitted frames, they must be filtered out before this function is
// called.
void void
VCMJitterBuffer::UpdateJitterAndDelayEstimates(VCMJitterSample& sample, VCMJitterBuffer::UpdateJitterAndDelayEstimates(VCMJitterSample& sample,
bool incompleteFrame) bool incompleteFrame)
@@ -1334,24 +1320,8 @@ VCMJitterBuffer::GetLowHighSequenceNumbers(WebRtc_Word32& lowSeqNum,
(kStateDecoding != state) && (kStateDecoding != state) &&
seqNum != -1) seqNum != -1)
{ {
if (highSeqNum == -1) bool wrap;
{ highSeqNum = LatestSequenceNumber(seqNum, highSeqNum, &wrap);
// first
highSeqNum = seqNum;
}
else if (seqNum < 0x0fff && highSeqNum > 0xf000)
{
// wrap
highSeqNum = seqNum;
}
else if(seqNum > 0xf000 && highSeqNum < 0x0fff)
{
// Do nothing since it is a wrap and this one is older
}
else if (seqNum > highSeqNum)
{
highSeqNum = seqNum;
}
} }
} // for } // for
return 0; return 0;
@@ -1648,18 +1618,20 @@ VCMJitterBuffer::InsertPacket(VCMEncodedFrame* buffer, const VCMPacket& packet)
VCMFrameBufferEnum ret = kSizeError; VCMFrameBufferEnum ret = kSizeError;
VCMFrameBuffer* frame = static_cast<VCMFrameBuffer*>(buffer); VCMFrameBuffer* frame = static_cast<VCMFrameBuffer*>(buffer);
// We are keeping track of the first seq num, the latest seq num and
// the number of wraps to be able to calculate how many packets we expect.
if (_firstPacket)
{
// Now it's time to start estimating jitter
// reset the delay estimate.
_delayEstimate.Reset();
_firstPacket = false;
}
// Empty packets may bias the jitter estimate (lacking size component), // Empty packets may bias the jitter estimate (lacking size component),
// therefore don't let empty packet trigger the following updates: // therefore don't let empty packet trigger the following updates:
if (packet.frameType != kFrameEmpty) if (packet.frameType != kFrameEmpty)
{ {
if (_firstPacket)
{
// Now it's time to start estimating jitter
// reset the delay estimate.
_delayEstimate.Reset();
_firstPacket = false;
}
if (_waitingForCompletion.timestamp == packet.timestamp) if (_waitingForCompletion.timestamp == packet.timestamp)
{ {
// This can get bad if we have a lot of duplicate packets, // This can get bad if we have a lot of duplicate packets,
@@ -1681,24 +1653,19 @@ VCMJitterBuffer::InsertPacket(VCMEncodedFrame* buffer, const VCMPacket& packet)
if (frame != NULL) if (frame != NULL)
{ {
VCMFrameBufferStateEnum state = frame->GetState(); VCMFrameBufferStateEnum state = frame->GetState();
if ((packet.sizeBytes == 0) && if (packet.sizeBytes == 0 && packet.timestamp == _lastDecodedTimeStamp)
((state == kStateDecoding) || {
(state == kStateEmpty &&
_lastDecodedTimeStamp == packet.timestamp)))
{
// Empty packet (sizeBytes = 0), make sure we update the last // Empty packet (sizeBytes = 0), make sure we update the last
// decoded seq num since this packet belongs either to a frame // decoded sequence number
// being decoded (condition 1) or to a frame which was already _lastDecodedSeqNum = LatestSequenceNumber(packet.seqNum,
// decoded and freed (condition 2). A new frame will be created _lastDecodedSeqNum, NULL);
// for the empty packet. That frame will be empty and later on
// cleaned up.
UpdateLastDecodedWithEmpty(packet);
} }
// Insert packet // Insert packet
// check for first packet // Check for first packet
// high sequence number will not be set // High sequence number will be -1 if neither an empty packet nor
bool first = frame->GetHighSeqNum() == -1; // a media packet has been inserted.
bool first = (frame->GetHighSeqNum() == -1);
bufferReturn = frame->InsertPacket(packet, nowMs); bufferReturn = frame->InsertPacket(packet, nowMs);
ret = bufferReturn; ret = bufferReturn;
@@ -1762,26 +1729,13 @@ VCMJitterBuffer::InsertPacket(VCMEncodedFrame* buffer, const VCMPacket& packet)
return ret; return ret;
} }
void
VCMJitterBuffer::UpdateLastDecodedWithEmpty(const VCMPacket& packet)
{
// Empty packet inserted to a frame which
// is already decoding. Update the last decoded seq no.
if (_lastDecodedTimeStamp == packet.timestamp &&
(packet.seqNum > _lastDecodedSeqNum ||
(packet.seqNum < 0x0fff && _lastDecodedSeqNum > 0xf000)))
{
_lastDecodedSeqNum = packet.seqNum;
}
}
// Must be called from within _critSect // Must be called from within _critSect
void void
VCMJitterBuffer::UpdateOldJitterSample(const VCMPacket& packet) VCMJitterBuffer::UpdateOldJitterSample(const VCMPacket& packet)
{ {
if (_waitingForCompletion.timestamp != packet.timestamp && if (_waitingForCompletion.timestamp != packet.timestamp &&
LatestTimestamp(_waitingForCompletion.timestamp, packet.timestamp) == LatestTimestamp(_waitingForCompletion.timestamp, packet.timestamp,
packet.timestamp) NULL) == packet.timestamp)
{ {
// This is a newer frame than the one waiting for completion. // This is a newer frame than the one waiting for completion.
_waitingForCompletion.frameSize = packet.sizeBytes; _waitingForCompletion.frameSize = packet.sizeBytes;
@@ -1904,8 +1858,8 @@ VCMJitterBuffer::CleanUpOldFrames()
// Release the frame if it's older than the last decoded frame. // Release the frame if it's older than the last decoded frame.
if (_lastDecodedTimeStamp > -1 && if (_lastDecodedTimeStamp > -1 &&
LatestTimestamp(static_cast<WebRtc_UWord32>(_lastDecodedTimeStamp), LatestTimestamp(static_cast<WebRtc_UWord32>(_lastDecodedTimeStamp),
frameTimeStamp) frameTimeStamp, NULL) ==
== static_cast<WebRtc_UWord32>(_lastDecodedTimeStamp)) static_cast<WebRtc_UWord32>(_lastDecodedTimeStamp))
{ {
const WebRtc_Word32 frameLowSeqNum = oldestFrame->GetLowSeqNum(); const WebRtc_Word32 frameLowSeqNum = oldestFrame->GetLowSeqNum();
const WebRtc_Word32 frameHighSeqNum = oldestFrame->GetHighSeqNum(); const WebRtc_Word32 frameHighSeqNum = oldestFrame->GetHighSeqNum();
@@ -2027,13 +1981,17 @@ void
VCMJitterBuffer::VerifyAndSetPreviousFrameLost(VCMFrameBuffer& frame) VCMJitterBuffer::VerifyAndSetPreviousFrameLost(VCMFrameBuffer& frame)
{ {
frame.MakeSessionDecodable(); // make sure the session can be decoded. frame.MakeSessionDecodable(); // make sure the session can be decoded.
if (frame.FrameType() == kVideoFrameKey)
return;
WebRtc_UWord16 nextExpectedSeqNum =
static_cast<WebRtc_UWord16>(_lastDecodedSeqNum + 1);
if (_lastDecodedSeqNum == -1) if (_lastDecodedSeqNum == -1)
{ {
// First frame // First frame
frame.SetPreviousFrameLoss(); frame.SetPreviousFrameLoss();
} }
else if ((WebRtc_UWord16)frame.GetLowSeqNum() != else if (static_cast<WebRtc_UWord16>(frame.GetLowSeqNum()) !=
((WebRtc_UWord16)_lastDecodedSeqNum + (WebRtc_UWord16)1)) nextExpectedSeqNum)
{ {
// Frame loss // Frame loss
frame.SetPreviousFrameLoss(); frame.SetPreviousFrameLoss();

View File

@@ -68,6 +68,9 @@ public:
WebRtc_Word32 GetFrameStatistics(WebRtc_UWord32& receivedDeltaFrames, WebRtc_Word32 GetFrameStatistics(WebRtc_UWord32& receivedDeltaFrames,
WebRtc_UWord32& receivedKeyFrames) const; WebRtc_UWord32& receivedKeyFrames) const;
// The number of packets discarded by the jitter buffer because the decoder
// won't be able to decode them.
WebRtc_UWord32 NumNotDecodablePackets() const;
// Get number of packets discarded by the jitter buffer // Get number of packets discarded by the jitter buffer
WebRtc_UWord32 DiscardedPackets() const; WebRtc_UWord32 DiscardedPackets() const;
@@ -124,11 +127,8 @@ public:
bool& listExtended); bool& listExtended);
WebRtc_Word64 LastDecodedTimestamp() const; WebRtc_Word64 LastDecodedTimestamp() const;
static WebRtc_UWord32 LatestTimestamp(const WebRtc_UWord32 existingTimestamp,
const WebRtc_UWord32 newTimestamp);
protected:
private:
// Misc help functions // Misc help functions
// Recycle (release) frame, used if we didn't receive whole frame // Recycle (release) frame, used if we didn't receive whole frame
void RecycleFrame(VCMFrameBuffer* frame); void RecycleFrame(VCMFrameBuffer* frame);
@@ -159,6 +159,7 @@ protected:
void VerifyAndSetPreviousFrameLost(VCMFrameBuffer& frame); void VerifyAndSetPreviousFrameLost(VCMFrameBuffer& frame);
bool IsPacketRetransmitted(const VCMPacket& packet) const; bool IsPacketRetransmitted(const VCMPacket& packet) const;
void UpdateJitterAndDelayEstimates(VCMJitterSample& sample, void UpdateJitterAndDelayEstimates(VCMJitterSample& sample,
bool incompleteFrame); bool incompleteFrame);
void UpdateJitterAndDelayEstimates(VCMFrameBuffer& frame, void UpdateJitterAndDelayEstimates(VCMFrameBuffer& frame,
@@ -176,10 +177,6 @@ protected:
WebRtc_Word32 GetLowHighSequenceNumbers(WebRtc_Word32& lowSeqNum, WebRtc_Word32 GetLowHighSequenceNumbers(WebRtc_Word32& lowSeqNum,
WebRtc_Word32& highSeqNum) const; WebRtc_Word32& highSeqNum) const;
void UpdateLastDecodedWithEmpty(const VCMPacket& packet);
private:
static bool FrameEqualTimestamp(VCMFrameBuffer* frame, static bool FrameEqualTimestamp(VCMFrameBuffer* frame,
const void* timestamp); const void* timestamp);
static bool CompleteDecodableKeyFrameCriteria(VCMFrameBuffer* frame, static bool CompleteDecodableKeyFrameCriteria(VCMFrameBuffer* frame,
@@ -208,6 +205,7 @@ private:
WebRtc_Word32 _lastDecodedSeqNum; WebRtc_Word32 _lastDecodedSeqNum;
// Timestamp of last frame that was given to decoder // Timestamp of last frame that was given to decoder
WebRtc_Word64 _lastDecodedTimeStamp; WebRtc_Word64 _lastDecodedTimeStamp;
WebRtc_UWord32 _packetsNotDecodable;
// Statistics // Statistics
// Frame counter for each type (key, delta, golden, key-delta) // Frame counter for each type (key, delta, golden, key-delta)

View File

@@ -0,0 +1,60 @@
/*
* Copyright (c) 2011 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 "jitter_buffer_common.h"
#include <cstdlib>
namespace webrtc {
WebRtc_UWord32 LatestTimestamp(WebRtc_UWord32 timestamp1,
WebRtc_UWord32 timestamp2,
bool* has_wrapped) {
bool wrap = (timestamp2 < 0x0000ffff && timestamp1 > 0xffff0000) ||
(timestamp2 > 0xffff0000 && timestamp1 < 0x0000ffff);
if (has_wrapped != NULL)
*has_wrapped = wrap;
if (timestamp1 > timestamp2 && !wrap)
return timestamp1;
else if (timestamp1 <= timestamp2 && !wrap)
return timestamp2;
else if (timestamp1 < timestamp2 && wrap)
return timestamp1;
else
return timestamp2;
}
WebRtc_Word32 LatestSequenceNumber(WebRtc_Word32 seq_num1,
WebRtc_Word32 seq_num2,
bool* has_wrapped) {
if (seq_num1 < 0 && seq_num2 < 0)
return -1;
else if (seq_num1 < 0)
return seq_num2;
else if (seq_num2 < 0)
return seq_num1;
bool wrap = (seq_num1 < 0x00ff && seq_num2 > 0xff00) ||
(seq_num1 > 0xff00 && seq_num2 < 0x00ff);
if (has_wrapped != NULL)
*has_wrapped = wrap;
if (seq_num2 > seq_num1 && !wrap)
return seq_num2;
else if (seq_num2 <= seq_num1 && !wrap)
return seq_num1;
else if (seq_num2 < seq_num1 && wrap)
return seq_num2;
else
return seq_num1;
}
} // namespace webrtc

View File

@@ -11,6 +11,8 @@
#ifndef WEBRTC_MODULES_VIDEO_CODING_JITTER_BUFFER_COMMON_H_ #ifndef WEBRTC_MODULES_VIDEO_CODING_JITTER_BUFFER_COMMON_H_
#define WEBRTC_MODULES_VIDEO_CODING_JITTER_BUFFER_COMMON_H_ #define WEBRTC_MODULES_VIDEO_CODING_JITTER_BUFFER_COMMON_H_
#include "typedefs.h"
namespace webrtc namespace webrtc
{ {
@@ -62,6 +64,19 @@ enum VCMNaluCompleteness
kNaluEnd, // Packet is the end of a NALU kNaluEnd, // Packet is the end of a NALU
}; };
// Returns the latest of the two timestamps, compensating for wrap arounds.
// This function assumes that the two timestamps are close in time.
WebRtc_UWord32 LatestTimestamp(WebRtc_UWord32 timestamp1,
WebRtc_UWord32 timestamp2,
bool* has_wrapped);
// Returns the latest of the two sequence numbers, compensating for wrap
// arounds. This function assumes that the two sequence numbers are close in
// time.
WebRtc_Word32 LatestSequenceNumber(WebRtc_Word32 seq_num1,
WebRtc_Word32 seq_num2,
bool* has_wrapped);
} // namespace webrtc } // namespace webrtc
#endif // WEBRTC_MODULES_VIDEO_CODING_JITTER_BUFFER_COMMON_H_ #endif // WEBRTC_MODULES_VIDEO_CODING_JITTER_BUFFER_COMMON_H_

View File

@@ -8,13 +8,9 @@
* be found in the AUTHORS file in the root of the source tree. * be found in the AUTHORS file in the root of the source tree.
*/ */
#include "packet.h"
#include "session_info.h" #include "session_info.h"
#include <string.h> #include "packet.h"
#include <cassert>
#include "internal_defines.h"
namespace webrtc { namespace webrtc {
@@ -29,7 +25,8 @@ VCMSessionInfo::VCMSessionInfo():
_highestPacketIndex(0), _highestPacketIndex(0),
_emptySeqNumLow(-1), _emptySeqNumLow(-1),
_emptySeqNumHigh(-1), _emptySeqNumHigh(-1),
_markerSeqNum(-1) _markerSeqNum(-1),
_packetsNotDecodable(0)
{ {
} }
@@ -55,7 +52,7 @@ VCMSessionInfo::GetLowSeqNum() const
WebRtc_Word32 WebRtc_Word32
VCMSessionInfo::GetHighSeqNum() const VCMSessionInfo::GetHighSeqNum() const
{ {
return VCM_MAX(_emptySeqNumHigh, _highSeqNum); return LatestSequenceNumber(_emptySeqNumHigh, _highSeqNum, NULL);
} }
void void
@@ -73,6 +70,7 @@ VCMSessionInfo::Reset() {
_sessionNACK = false; _sessionNACK = false;
_highestPacketIndex = 0; _highestPacketIndex = 0;
_markerSeqNum = -1; _markerSeqNum = -1;
_packetsNotDecodable = 0;
} }
WebRtc_UWord32 WebRtc_UWord32
@@ -202,7 +200,7 @@ VCMSessionInfo::UpdateCompleteSession()
} }
} }
bool VCMSessionInfo::IsSessionComplete() bool VCMSessionInfo::IsSessionComplete() const
{ {
return _completeSession; return _completeSession;
} }
@@ -287,6 +285,7 @@ VCMSessionInfo::DeletePackets(WebRtc_UWord8* ptrStartOfLayer,
{ {
bytesToDelete += _packets[j].sizeBytes; bytesToDelete += _packets[j].sizeBytes;
_packets[j].Reset(); _packets[j].Reset();
++_packetsNotDecodable;
} }
if (bytesToDelete > 0) if (bytesToDelete > 0)
{ {
@@ -362,7 +361,7 @@ VCMSessionInfo::BuildVP8FragmentationHeader(
return new_length; return new_length;
} }
int VCMSessionInfo::FindNextPartitionBeginning(int packet_index) const { int VCMSessionInfo::FindNextPartitionBeginning(int packet_index) {
while (packet_index <= _highestPacketIndex) { while (packet_index <= _highestPacketIndex) {
if (_packets[packet_index].completeNALU == kNaluUnset) { if (_packets[packet_index].completeNALU == kNaluUnset) {
// Missing packet // Missing packet
@@ -371,8 +370,13 @@ int VCMSessionInfo::FindNextPartitionBeginning(int packet_index) const {
} }
const bool beginning = _packets[packet_index].codecSpecificHeader. const bool beginning = _packets[packet_index].codecSpecificHeader.
codecHeader.VP8.beginningOfPartition; codecHeader.VP8.beginningOfPartition;
if (beginning) if (beginning) {
return packet_index; return packet_index;
} else {
// This packet belongs to a partition with a previous loss and can't
// be decoded.
++_packetsNotDecodable;
}
++packet_index; ++packet_index;
} }
return packet_index; return packet_index;
@@ -433,7 +437,7 @@ VCMSessionInfo::MakeDecodable(WebRtc_UWord8* ptrStartOfLayer)
} }
returnLength += DeletePackets(ptrStartOfLayer, returnLength += DeletePackets(ptrStartOfLayer,
packetIndex, endIndex); packetIndex + 1, endIndex);
packetIndex = endIndex; packetIndex = endIndex;
}// end lost packet }// end lost packet
} }
@@ -636,7 +640,7 @@ VCMSessionInfo::GetHighestPacketIndex()
} }
bool bool
VCMSessionInfo::HaveLastPacket() VCMSessionInfo::HaveLastPacket() const
{ {
return _markerBit; return _markerBit;
} }
@@ -649,25 +653,11 @@ VCMSessionInfo::ForceSetHaveLastPacket()
} }
bool bool
VCMSessionInfo::IsRetransmitted() VCMSessionInfo::IsRetransmitted() const
{ {
return _sessionNACK; return _sessionNACK;
} }
void
VCMSessionInfo::UpdatePacketSize(WebRtc_Word32 packetIndex,
WebRtc_UWord32 length)
{
// sanity
if (packetIndex >= kMaxPacketsInJitterBuffer || packetIndex < 0)
{
// not allowed
assert(!"SessionInfo::UpdatePacketSize Error: invalid packetIndex");
return;
}
_packets[packetIndex].sizeBytes = length;
}
WebRtc_Word64 WebRtc_Word64
VCMSessionInfo::InsertPacket(const VCMPacket& packet, VCMSessionInfo::InsertPacket(const VCMPacket& packet,
WebRtc_UWord8* ptrStartOfLayer) WebRtc_UWord8* ptrStartOfLayer)
@@ -848,6 +838,7 @@ VCMSessionInfo::PrepareForDecode(WebRtc_UWord8* ptrStartOfLayer,
// missing the previous packet. // missing the previous packet.
memset(ptrFirstByte, 0, _packets[i].sizeBytes); memset(ptrFirstByte, 0, _packets[i].sizeBytes);
previousLost = true; previousLost = true;
++_packetsNotDecodable;
} }
else if (_packets[i].sizeBytes > 0) // Ignore if empty packet else if (_packets[i].sizeBytes > 0) // Ignore if empty packet
{ {
@@ -870,6 +861,7 @@ VCMSessionInfo::PrepareForDecode(WebRtc_UWord8* ptrStartOfLayer,
{ {
memset(ptrStartOfLayer, 0, _packets[i].sizeBytes); memset(ptrStartOfLayer, 0, _packets[i].sizeBytes);
previousLost = true; previousLost = true;
++_packetsNotDecodable;
} }
} }
else if (_packets[i].sizeBytes == 0 && codec == kVideoCodecH263) else if (_packets[i].sizeBytes == 0 && codec == kVideoCodecH263)
@@ -899,4 +891,8 @@ VCMSessionInfo::PrepareForDecode(WebRtc_UWord8* ptrStartOfLayer,
return length; return length;
} }
int VCMSessionInfo::NotDecodablePackets() const {
return _packetsNotDecodable;
}
} }

View File

@@ -44,7 +44,7 @@ public:
WebRtc_UWord8* ptrStartOfLayer); WebRtc_UWord8* ptrStartOfLayer);
WebRtc_Word32 InformOfEmptyPacket(const WebRtc_UWord16 seqNum); WebRtc_Word32 InformOfEmptyPacket(const WebRtc_UWord16 seqNum);
virtual bool IsSessionComplete(); virtual bool IsSessionComplete() const;
// Builds fragmentation headers for VP8, each fragment being a decodable // Builds fragmentation headers for VP8, each fragment being a decodable
// VP8 partition. Returns the total number of bytes which are decodable. Is // VP8 partition. Returns the total number of bytes which are decodable. Is
@@ -60,14 +60,12 @@ public:
WebRtc_UWord32 MakeDecodable(WebRtc_UWord8* ptrStartOfLayer); WebRtc_UWord32 MakeDecodable(WebRtc_UWord8* ptrStartOfLayer);
WebRtc_UWord32 GetSessionLength(); WebRtc_UWord32 GetSessionLength();
bool HaveLastPacket(); bool HaveLastPacket() const;
void ForceSetHaveLastPacket(); void ForceSetHaveLastPacket();
bool IsRetransmitted(); bool IsRetransmitted() const;
webrtc::FrameType FrameType() const { return _frameType; } webrtc::FrameType FrameType() const { return _frameType; }
virtual WebRtc_Word32 GetHighestPacketIndex(); virtual WebRtc_Word32 GetHighestPacketIndex();
virtual void UpdatePacketSize(WebRtc_Word32 packetIndex,
WebRtc_UWord32 length);
void SetStartSeqNumber(WebRtc_UWord16 seqNumber); void SetStartSeqNumber(WebRtc_UWord16 seqNumber);
@@ -83,10 +81,17 @@ public:
void SetPreviousFrameLoss() { _previousFrameLoss = true; } void SetPreviousFrameLoss() { _previousFrameLoss = true; }
bool PreviousFrameLoss() const { return _previousFrameLoss; } bool PreviousFrameLoss() const { return _previousFrameLoss; }
protected: // The number of packets discarded because the decoder can't make use of
// Finds the packet index of the next VP8 partition. If none is found // them.
// _highestPacketIndex + 1 is returned. int NotDecodablePackets() const;
int FindNextPartitionBeginning(int packet_index) const;
private:
// Finds the packet index of the beginning of the next VP8 partition. If
// none is found _highestPacketIndex + 1 is returned. packet_index is
// expected to be the index of the last decodable packet of the previous
// partitions + 1, or 0 for the first partition.
int FindNextPartitionBeginning(int packet_index);
// Finds the packet index of the end of the partition with index // Finds the packet index of the end of the partition with index
// partitionIndex. // partitionIndex.
int FindPartitionEnd(int packet_index) const; int FindPartitionEnd(int packet_index) const;
@@ -123,6 +128,8 @@ protected:
WebRtc_Word32 _emptySeqNumHigh; WebRtc_Word32 _emptySeqNumHigh;
// Store the sequence number that marks the last media packet // Store the sequence number that marks the last media packet
WebRtc_Word32 _markerSeqNum; WebRtc_Word32 _markerSeqNum;
// Number of packets discarded because the decoder can't use them.
int _packetsNotDecodable;
}; };
} // namespace webrtc } // namespace webrtc

View File

@@ -50,8 +50,8 @@
'generic_encoder.h', 'generic_encoder.h',
'inter_frame_delay.h', 'inter_frame_delay.h',
'internal_defines.h', 'internal_defines.h',
'jitter_buffer_common.h',
'jitter_buffer.h', 'jitter_buffer.h',
'jitter_buffer_common.h',
'jitter_estimator.h', 'jitter_estimator.h',
'media_opt_util.h', 'media_opt_util.h',
'media_optimization.h', 'media_optimization.h',
@@ -81,6 +81,7 @@
'generic_encoder.cc', 'generic_encoder.cc',
'inter_frame_delay.cc', 'inter_frame_delay.cc',
'jitter_buffer.cc', 'jitter_buffer.cc',
'jitter_buffer_common.cc',
'jitter_estimator.cc', 'jitter_estimator.cc',
'media_opt_util.cc', 'media_opt_util.cc',
'media_optimization.cc', 'media_optimization.cc',

View File

@@ -819,6 +819,94 @@ int JitterBufferTest(CmdArgs& args)
TEST(bitRate > 10000000); TEST(bitRate > 10000000);
jb.Flush();
//
// TEST packet loss. Verify missing packets statistics and not decodable
// packets statistics.
// Insert 10 frames consisting of 4 packets and remove one from all of them.
// The last packet is a empty (non-media) packet
//
// Select a start seqNum which triggers a difficult wrap situation
seqNum = 0xffff - 4;
for (int i=0; i < 10; ++i) {
webrtc::FrameType frametype = kVideoFrameDelta;
if (i == 0)
frametype = kVideoFrameKey;
seqNum++;
timeStamp += 33*90;
packet.frameType = frametype;
if (i == 0)
packet.frameType = frametype;
packet.isFirstPacket = true;
packet.markerBit = false;
packet.seqNum = seqNum;
packet.timestamp = timeStamp;
packet.completeNALU = kNaluStart;
frameIn = jb.GetFrame(packet);
TEST(frameIn != 0);
// Insert a packet into a frame
TEST(kFirstPacket == jb.InsertPacket(frameIn, packet));
// get packet notification
TEST(timeStamp == jb.GetNextTimeStamp(10, incomingFrameType, renderTimeMs));
// check incoming frame type
TEST(incomingFrameType == frametype);
// get the frame
frameOut = jb.GetCompleteFrameForDecoding(10);
// it should not be complete
TEST(frameOut == 0);
seqNum += 2;
packet.isFirstPacket = false;
packet.markerBit = true;
packet.seqNum = seqNum;
packet.completeNALU = kNaluEnd;
frameIn = jb.GetFrame(packet);
TEST(frameIn != 0);
// Insert a packet into a frame
TEST(kIncomplete == jb.InsertPacket(frameIn, packet));
// Insert an empty (non-media) packet
seqNum++;
packet.isFirstPacket = false;
packet.markerBit = false;
packet.seqNum = seqNum;
packet.completeNALU = kNaluEnd;
packet.frameType = kFrameEmpty;
frameIn = jb.GetFrame(packet);
TEST(frameIn != 0);
// Insert a packet into a frame
TEST(kIncomplete == jb.InsertPacket(frameIn, packet));
// get the frame
frameOut = jb.GetFrameForDecoding();
// One of the packets has been discarded by the jitter buffer
CheckOutFrame(frameOut, size, false);
// check the frame type
TEST(frameOut->FrameType() == frametype);
TEST(frameOut->Complete() == false);
TEST(frameOut->MissingFrame() == false);
// Release frame (when done with decoding)
jb.ReleaseFrame(frameOut);
}
TEST(jb.NumNotDecodablePackets() == 10);
// Insert 3 old packets and verify that we have 3 discarded packets // Insert 3 old packets and verify that we have 3 discarded packets
packet.timestamp = timeStamp - 1000; packet.timestamp = timeStamp - 1000;
frameIn = jb.GetFrame(packet); frameIn = jb.GetFrame(packet);