From 076fa6e674f0feb2cf3393c44ed845aef8b52f06 Mon Sep 17 00:00:00 2001 From: "stefan@webrtc.org" Date: Tue, 13 Dec 2011 07:54:56 +0000 Subject: [PATCH] The second step towards a list based SessionInfo. Added unittests for most of public functions of SessionInfo. BUG= TEST= Review URL: http://webrtc-codereview.appspot.com/301014 git-svn-id: http://webrtc.googlecode.com/svn/trunk@1166 4adac7df-926f-26a2-2b94-8c16560cd09d --- .../video_coding/main/source/frame_buffer.cc | 39 +- .../main/source/jitter_buffer_common.h | 2 +- .../video_coding/main/source/session_info.cc | 1289 +++++++---------- .../video_coding/main/source/session_info.h | 215 ++- .../main/source/session_info_unittest.cc | 573 +++++++- .../main/test/jitter_buffer_test.cc | 2 +- 6 files changed, 1139 insertions(+), 981 deletions(-) diff --git a/src/modules/video_coding/main/source/frame_buffer.cc b/src/modules/video_coding/main/source/frame_buffer.cc index 153c63576..4fa0c971e 100644 --- a/src/modules/video_coding/main/source/frame_buffer.cc +++ b/src/modules/video_coding/main/source/frame_buffer.cc @@ -59,13 +59,13 @@ VCMFrameBuffer::SetPreviousFrameLoss() WebRtc_Word32 VCMFrameBuffer::GetLowSeqNum() const { - return _sessionInfo.GetLowSeqNum(); + return _sessionInfo.LowSequenceNumber(); } WebRtc_Word32 VCMFrameBuffer::GetHighSeqNum() const { - return _sessionInfo.GetHighSeqNum(); + return _sessionInfo.HighSequenceNumber(); } int VCMFrameBuffer::PictureId() const { @@ -87,7 +87,7 @@ bool VCMFrameBuffer::NonReference() const { bool VCMFrameBuffer::IsSessionComplete() const { - return _sessionInfo.IsSessionComplete(); + return _sessionInfo.complete(); } // Insert packet @@ -124,11 +124,6 @@ VCMFrameBuffer::InsertPacket(const VCMPacket& packet, WebRtc_Word64 timeInMs, { return kSizeError; } - if ((packet.frameType != kFrameEmpty) && - (!_sessionInfo.HaveStartSeqNumber())) - { - _sessionInfo.SetStartSeqNumber(packet.seqNum); - } if (packet.dataPtr != NULL) { _payloadType = packet.payloadType; @@ -166,14 +161,14 @@ VCMFrameBuffer::InsertPacket(const VCMPacket& packet, WebRtc_Word64 timeInMs, { return kSizeError; } - _sessionInfo.UpdateDataPointers(_buffer, prevBuffer); + _sessionInfo.UpdateDataPointers(_buffer - prevBuffer); } CopyCodecSpecific(&packet.codecSpecificHeader); - WebRtc_Word64 retVal = _sessionInfo.InsertPacket(packet, _buffer, - enableDecodableState, - rttMS); + int retVal = _sessionInfo.InsertPacket(packet, _buffer, + enableDecodableState, + rttMS); if (retVal == -1) { return kSizeError; @@ -187,9 +182,9 @@ VCMFrameBuffer::InsertPacket(const VCMPacket& packet, WebRtc_Word64 timeInMs, _latestPacketTimeMs = timeInMs; - if (_sessionInfo.IsSessionComplete()) { + if (_sessionInfo.complete()) { return kCompleteSession; - } else if (_sessionInfo.IsSessionDecodable()) { + } else if (_sessionInfo.decodable()) { SetState(kStateDecodable); return kDecodableSession; } else { @@ -272,11 +267,11 @@ VCMFrameBuffer::MakeSessionDecodable() WebRtc_UWord32 retVal; #ifdef INDEPENDENT_PARTITIONS if (_codec != kVideoCodecVP8) { - retVal = _sessionInfo.MakeDecodable(_buffer); + retVal = _sessionInfo.MakeDecodable(); _length -= retVal; } #else - retVal = _sessionInfo.MakeDecodable(_buffer); + retVal = _sessionInfo.MakeDecodable(); _length -= retVal; #endif } @@ -350,7 +345,7 @@ VCMFrameBuffer::RestructureFrameInformation() { PrepareForDecode(); _frameType = ConvertFrameType(_sessionInfo.FrameType()); - _completeFrame = _sessionInfo.IsSessionComplete(); + _completeFrame = _sessionInfo.complete(); _missingFrame = _sessionInfo.PreviousFrameLoss(); } @@ -371,14 +366,14 @@ VCMFrameBuffer::ExtractFromStorage(const EncodedVideoData& frameFromStorage) { return VCM_MEMORY; } - _sessionInfo.UpdateDataPointers(_buffer, prevBuffer); + _sessionInfo.UpdateDataPointers(_buffer - prevBuffer); memcpy(_buffer, frameFromStorage.payloadData, frameFromStorage.payloadSize); _length = frameFromStorage.payloadSize; return VCM_OK; } int VCMFrameBuffer::NotDecodablePackets() const { - return _sessionInfo.NotDecodablePackets(); + return _sessionInfo.packets_not_decodable(); } // Set counted status (as counted by JB or not) @@ -410,7 +405,7 @@ VCMFrameBuffer::GetState(WebRtc_UWord32& timeStamp) const bool VCMFrameBuffer::IsRetransmitted() const { - return _sessionInfo.IsRetransmitted(); + return _sessionInfo.session_nack(); } void @@ -425,10 +420,10 @@ VCMFrameBuffer::PrepareForDecode() } else { - _length = _sessionInfo.PrepareForDecode(_buffer, _codec); + _length = _sessionInfo.PrepareForDecode(_buffer); } #else - _length = _sessionInfo.PrepareForDecode(_buffer, _codec); + _length = _sessionInfo.PrepareForDecode(_buffer); #endif } diff --git a/src/modules/video_coding/main/source/jitter_buffer_common.h b/src/modules/video_coding/main/source/jitter_buffer_common.h index cbcf1e306..38cea423d 100644 --- a/src/modules/video_coding/main/source/jitter_buffer_common.h +++ b/src/modules/video_coding/main/source/jitter_buffer_common.h @@ -25,7 +25,7 @@ enum VCMJitterBufferEnum { kMaxConsecutiveOldFrames = 60, kMaxConsecutiveOldPackets = 300, - kMaxPacketsInJitterBuffer = 800, + kMaxPacketsInSession = 800, kBufferIncStepSizeBytes = 30000, // >20 packets kMaxJBFrameSizeBytes = 4000000 // sanity don't go above 4Mbyte }; diff --git a/src/modules/video_coding/main/source/session_info.cc b/src/modules/video_coding/main/source/session_info.cc index be6ef6636..173f0fefe 100644 --- a/src/modules/video_coding/main/source/session_info.cc +++ b/src/modules/video_coding/main/source/session_info.cc @@ -8,373 +8,245 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "session_info.h" +#include "modules/video_coding/main/source/session_info.h" -#include "packet.h" +#include "modules/video_coding/main/source/packet.h" namespace webrtc { -VCMSessionInfo::VCMSessionInfo(): - _markerBit(false), - _sessionNACK(false), - _completeSession(false), - _decodableSession(false), - _frameType(kVideoFrameDelta), - _previousFrameLoss(false), - _lowSeqNum(-1), - _highSeqNum(-1), - _highestPacketIndex(0), - _emptySeqNumLow(-1), - _emptySeqNumHigh(-1), - _markerSeqNum(-1), - _packetsNotDecodable(0), - _pictureId(kNoPictureId) -{ +VCMSessionInfo::VCMSessionInfo() + : session_nack_(false), + complete_(false), + decodable_(false), + frame_type_(kVideoFrameDelta), + previous_frame_loss_(false), + packets_(), + empty_seq_num_low_(-1), + empty_seq_num_high_(-1), + packets_not_decodable_(0) { } -VCMSessionInfo::~VCMSessionInfo() -{ +void VCMSessionInfo::UpdateDataPointers(ptrdiff_t address_delta) { + for (PacketIterator it = packets_.begin(); it != packets_.end(); ++it) + if ((*it).dataPtr != NULL) + (*it).dataPtr = (*it).dataPtr + address_delta; } -void -VCMSessionInfo::UpdateDataPointers(const WebRtc_UWord8* frame_buffer, - const WebRtc_UWord8* prev_buffer_address) { - for (int i = 0; i <= _highestPacketIndex; ++i) - if (_packets[i].dataPtr != NULL) - _packets[i].dataPtr = frame_buffer + (_packets[i].dataPtr - - prev_buffer_address); +int VCMSessionInfo::LowSequenceNumber() const { + if (packets_.empty()) + return empty_seq_num_low_; + return packets_.front().seqNum; } -WebRtc_Word32 -VCMSessionInfo::GetLowSeqNum() const -{ - return _lowSeqNum; -} - -WebRtc_Word32 -VCMSessionInfo::GetHighSeqNum() const -{ - return LatestSequenceNumber(_emptySeqNumHigh, _highSeqNum, NULL); +int VCMSessionInfo::HighSequenceNumber() const { + if (packets_.empty()) + return empty_seq_num_high_; + return LatestSequenceNumber(packets_.back().seqNum, empty_seq_num_high_, + NULL); } int VCMSessionInfo::PictureId() const { - return _pictureId; + if (packets_.empty() || + packets_.front().codecSpecificHeader.codec != kRTPVideoVP8) + return kNoPictureId; + return packets_.front().codecSpecificHeader.codecHeader.VP8.pictureId; } int VCMSessionInfo::TemporalId() const { - if (_lowSeqNum == -1 || _packets[0].codecSpecificHeader.codec != kRTPVideoVP8) + if (packets_.empty() || + packets_.front().codecSpecificHeader.codec != kRTPVideoVP8) return kNoTemporalIdx; - return _packets[0].codecSpecificHeader.codecHeader.VP8.temporalIdx; + return packets_.front().codecSpecificHeader.codecHeader.VP8.temporalIdx; } int VCMSessionInfo::Tl0PicId() const { - if (_lowSeqNum == -1 || _packets[0].codecSpecificHeader.codec != kRTPVideoVP8) + if (packets_.empty() || + packets_.front().codecSpecificHeader.codec != kRTPVideoVP8) return kNoTl0PicIdx; - return _packets[0].codecSpecificHeader.codecHeader.VP8.tl0PicIdx; + return packets_.front().codecSpecificHeader.codecHeader.VP8.tl0PicIdx; } bool VCMSessionInfo::NonReference() const { - if (_lowSeqNum == -1 || _packets[0].codecSpecificHeader.codec != kRTPVideoVP8) + if (packets_.empty() || + packets_.front().codecSpecificHeader.codec != kRTPVideoVP8) return false; - return _packets[0].codecSpecificHeader.codecHeader.VP8.nonReference; + return packets_.front().codecSpecificHeader.codecHeader.VP8.nonReference; } -void -VCMSessionInfo::Reset() { - for (int i = 0; i <= _highestPacketIndex; ++i) - _packets[i].Reset(); - _lowSeqNum = -1; - _highSeqNum = -1; - _emptySeqNumLow = -1; - _emptySeqNumHigh = -1; - _markerBit = false; - _completeSession = false; - _decodableSession = false; - _frameType = kVideoFrameDelta; - _previousFrameLoss = false; - _sessionNACK = false; - _highestPacketIndex = 0; - _markerSeqNum = -1; - _packetsNotDecodable = 0; - _pictureId = kNoPictureId; +void VCMSessionInfo::Reset() { + session_nack_ = false; + complete_ = false; + decodable_ = false; + frame_type_ = kVideoFrameDelta; + previous_frame_loss_ = false; + packets_.clear(); + empty_seq_num_low_ = -1; + empty_seq_num_high_ = -1; + packets_not_decodable_ = 0; } -WebRtc_UWord32 -VCMSessionInfo::GetSessionLength() -{ - WebRtc_UWord32 length = 0; - for (WebRtc_Word32 i = 0; i <= _highestPacketIndex; ++i) - { - length += _packets[i].sizeBytes; - } - return length; +int VCMSessionInfo::SessionLength() const { + int length = 0; + for (PacketIteratorConst it = packets_.begin(); it != packets_.end(); ++it) + length += (*it).sizeBytes; + return length; } -void -VCMSessionInfo::SetStartSeqNumber(WebRtc_UWord16 seqNumber) -{ - _lowSeqNum = seqNumber; - _highSeqNum = seqNumber; +int VCMSessionInfo::InsertBuffer(uint8_t* frame_buffer, + PacketIterator packet_it) { + VCMPacket& packet = *packet_it; + PacketIterator it; + + int packet_size = packet.sizeBytes; + if (!packet.bits) + packet_size += (packet.insertStartCode ? kH264StartCodeLengthBytes : 0); + + // Calculate the offset into the frame buffer for this packet. + int offset = 0; + for (it = packets_.begin(); it != packet_it; ++it) + offset += (*it).sizeBytes; + + // Set the data pointer to pointing to the start of this packet in the + // frame buffer. + const uint8_t* data = packet.dataPtr; + packet.dataPtr = frame_buffer + offset; + packet.sizeBytes = packet_size; + + ShiftSubsequentPackets(packet_it, packet_size); + + const unsigned char startCode[] = {0, 0, 0, 1}; + if (packet.insertStartCode) { + memcpy(const_cast(packet.dataPtr), startCode, + kH264StartCodeLengthBytes); + } + memcpy(const_cast(packet.dataPtr + + (packet.insertStartCode ? kH264StartCodeLengthBytes : 0)), + data, + packet.sizeBytes); + + return packet_size; } -bool -VCMSessionInfo::HaveStartSeqNumber() -{ - if (_lowSeqNum == -1 || _highSeqNum == -1) - { - return false; - } - return true; -} - -WebRtc_UWord32 -VCMSessionInfo::InsertBuffer(WebRtc_UWord8* ptrStartOfLayer, - WebRtc_Word32 packetIndex, - const VCMPacket& packet) -{ - WebRtc_UWord32 moveLength = 0; - WebRtc_UWord32 returnLength = 0; - int i = 0; - - // need to calc offset before updating _packetSizeBytes - WebRtc_UWord32 offset = 0; - WebRtc_UWord32 packetSize = 0; - - // Shallow copy without overwriting the dataPtr and the sizeBytes - const WebRtc_UWord8* dataPtr = _packets[packetIndex].dataPtr; - const WebRtc_UWord32 sizeBytes = _packets[packetIndex].sizeBytes; - _packets[packetIndex] = packet; - _packets[packetIndex].dataPtr = dataPtr; - _packets[packetIndex].sizeBytes = sizeBytes; - - // Store this packet length. Add length since we could have data present - // already (e.g. multicall case). - packetSize = packet.sizeBytes; - if (!packet.bits) - { - packetSize += (packet.insertStartCode ? kH264StartCodeLengthBytes : 0); - } - - // count only the one in our layer - for (i = 0; i < packetIndex; ++i) - { - offset += _packets[i].sizeBytes; - } - - // Set the data pointer to pointing to the first part of this packet. - if (_packets[packetIndex].dataPtr == NULL) - _packets[packetIndex].dataPtr = ptrStartOfLayer + offset; - - _packets[packetIndex].sizeBytes += packetSize; - - // Calculate the total move length and move the data pointers in advance. - for (i = packetIndex + 1; i <= _highestPacketIndex; ++i) - { - moveLength += _packets[i].sizeBytes; - if (_packets[i].dataPtr != NULL) - _packets[i].dataPtr += packetSize; - } - if (moveLength > 0) - { - memmove((void*)(_packets[packetIndex].dataPtr + packetSize), - _packets[packetIndex].dataPtr, moveLength); - } - - if (packet.dataPtr != NULL) - { - const unsigned char startCode[] = {0, 0, 0, 1}; - if (packet.insertStartCode) - { - memcpy((void*)(_packets[packetIndex].dataPtr), startCode, - kH264StartCodeLengthBytes); - } - memcpy((void*)(_packets[packetIndex].dataPtr - + (packet.insertStartCode ? kH264StartCodeLengthBytes : 0)), - packet.dataPtr, - packet.sizeBytes); - } - returnLength = packetSize; - - if (packet.codecSpecificHeader.codec == kRTPVideoVP8) - { - _pictureId = packet.codecSpecificHeader.codecHeader.VP8.pictureId; - } - - if (packet.markerBit) - { - _markerBit = true; - _markerSeqNum = packet.seqNum; - } - - return returnLength; +void VCMSessionInfo::ShiftSubsequentPackets(PacketIterator it, + int steps_to_shift) { + ++it; + if (it == packets_.end()) + return; + uint8_t* first_packet_ptr = const_cast((*it).dataPtr); + int shift_length = 0; + // Calculate the total move length and move the data pointers in advance. + for (; it != packets_.end(); ++it) { + shift_length += (*it).sizeBytes; + if ((*it).dataPtr != NULL) + (*it).dataPtr += steps_to_shift; + } + memmove(first_packet_ptr + steps_to_shift, first_packet_ptr, shift_length); } void VCMSessionInfo::UpdateCompleteSession() { - if (_packets[0].isFirstPacket && _markerBit) { + if (packets_.front().isFirstPacket && packets_.back().markerBit) { // Do we have all the packets in this session? - bool completeSession = true; - - for (int i = 0; i <= _highestPacketIndex; ++i) { - if (_packets[i].completeNALU == kNaluUnset) { - completeSession = false; + bool complete_session = true; + PacketIterator it = packets_.begin(); + PacketIterator prev_it = it; + ++it; + for (; it != packets_.end(); ++it) { + if (!InSequence(it, prev_it)) { + complete_session = false; break; } + prev_it = it; } - _completeSession = completeSession; + complete_ = complete_session; } } -void VCMSessionInfo::UpdateDecodableSession(WebRtc_UWord32 rttMs) { +void VCMSessionInfo::UpdateDecodableSession(int rttMs) { // Irrelevant if session is already complete or decodable - if (_completeSession || _decodableSession) + if (complete_ || decodable_) return; // First iteration - do nothing } -bool VCMSessionInfo::IsSessionComplete() const { - return _completeSession; +bool VCMSessionInfo::complete() const { + return complete_; } -bool VCMSessionInfo::IsSessionDecodable() const { - return _decodableSession; +bool VCMSessionInfo::decodable() const { + return decodable_; } -// Find the start and end index of packetIndex packet. -// startIndex -1 if start not found endIndex = -1 if end index not found -void -VCMSessionInfo::FindNaluBorder(WebRtc_Word32 packetIndex, - WebRtc_Word32& startIndex, - WebRtc_Word32& endIndex) -{ - if (_packets[packetIndex].completeNALU == kNaluStart || - _packets[packetIndex].completeNALU == kNaluComplete) - { - startIndex = packetIndex; - } - else // Need to find the start - { - for (startIndex = packetIndex - 1; startIndex >= 0; --startIndex) - { - - if ((_packets[startIndex].completeNALU == kNaluComplete && - _packets[startIndex].sizeBytes > 0) || - // Found previous NALU. - (_packets[startIndex].completeNALU == kNaluEnd && - startIndex > 0)) - { - startIndex++; - break; - } - // This is where the NALU start. - if (_packets[startIndex].completeNALU == kNaluStart) - { - break; - } - } - } - - if (_packets[packetIndex].completeNALU == kNaluEnd || - _packets[packetIndex].completeNALU == kNaluComplete) - { - endIndex = packetIndex; - } - else - { - // Find the next NALU - for (endIndex = packetIndex + 1; endIndex <= _highestPacketIndex; - ++endIndex) - { - if ((_packets[endIndex].completeNALU == kNaluComplete && - _packets[endIndex].completeNALU > 0) || - // Found next NALU. - _packets[endIndex].completeNALU == kNaluStart) - { - endIndex--; - break; - } - if (_packets[endIndex].completeNALU == kNaluEnd) - { - // This is where the NALU end. - break; - } - } - if (endIndex > _highestPacketIndex) - { - endIndex = -1; - } - } +// Find the end of the NAL unit which the packet pointed to by |packet_it| +// belongs to. Returns an iterator to the last packet of the frame if the end +// of the NAL unit wasn't found. +VCMSessionInfo::PacketIterator VCMSessionInfo::FindNaluEnd( + PacketIterator packet_it) const { + if ((*packet_it).completeNALU == kNaluEnd || + (*packet_it).completeNALU == kNaluComplete) { + return packet_it; + } + // Find the end of the NAL unit. + for (; packet_it != packets_.end(); ++packet_it) { + if (((*packet_it).completeNALU == kNaluComplete && + (*packet_it).sizeBytes > 0) || + // Found next NALU. + (*packet_it).completeNALU == kNaluStart) + return --packet_it; + if ((*packet_it).completeNALU == kNaluEnd) + return packet_it; + } + // The end wasn't found. + return --packet_it; } -// Deletes all packets between startIndex and endIndex -WebRtc_UWord32 -VCMSessionInfo::DeletePackets(WebRtc_UWord8* ptrStartOfLayer, - WebRtc_Word32 startIndex, - WebRtc_Word32 endIndex) -{ +int VCMSessionInfo::DeletePacketData(PacketIterator start, + PacketIterator end) { + int bytes_to_delete = 0; // The number of bytes to delete. + PacketIterator packet_after_end = end; + ++packet_after_end; - //Get the number of bytes to delete. - //Clear the size of these packets. - WebRtc_UWord32 bytesToDelete = 0; /// The number of bytes to delete. - for (int j = startIndex;j <= endIndex; ++j) - { - bytesToDelete += _packets[j].sizeBytes; - _packets[j].Reset(); - ++_packetsNotDecodable; - } - if (bytesToDelete > 0) - { - // Get the offset we want to move to. - int destOffset = 0; - for (int j = 0;j < startIndex;j++) - { - destOffset += _packets[j].sizeBytes; - } - - // Get the number of bytes to move and move the data pointers in advance - WebRtc_UWord32 numberOfBytesToMove = 0; - for (int j = endIndex + 1; j <= _highestPacketIndex; ++j) - { - if (_packets[j].dataPtr != NULL) - _packets[j].dataPtr -= bytesToDelete; - numberOfBytesToMove += _packets[j].sizeBytes; - } - memmove((void*)(ptrStartOfLayer + destOffset),(void*)(ptrStartOfLayer + - destOffset + bytesToDelete), numberOfBytesToMove); - - } - - return bytesToDelete; + // Get the number of bytes to delete. + // Clear the size of these packets. + for (PacketIterator it = start; it != packet_after_end; ++it) { + bytes_to_delete += (*it).sizeBytes; + (*it).sizeBytes = 0; + (*it).dataPtr = NULL; + ++packets_not_decodable_; + } + if (bytes_to_delete > 0) + ShiftSubsequentPackets(end, -bytes_to_delete); + return bytes_to_delete; } -int -VCMSessionInfo::BuildVP8FragmentationHeader( - WebRtc_UWord8* frame_buffer, - int frame_buffer_length, - RTPFragmentationHeader* fragmentation) { +int VCMSessionInfo::BuildVP8FragmentationHeader( + uint8_t* frame_buffer, + int frame_buffer_length, + RTPFragmentationHeader* fragmentation) { int new_length = 0; // Allocate space for max number of partitions fragmentation->VerifyAndAllocateFragmentationHeader(kMaxVP8Partitions); fragmentation->fragmentationVectorSize = 0; memset(fragmentation->fragmentationLength, 0, kMaxVP8Partitions * sizeof(WebRtc_UWord32)); - if (_lowSeqNum < 0) + if (packets_.empty()) return new_length; - int i = FindNextPartitionBeginning(0); - while (i <= _highestPacketIndex) { + PacketIterator it = FindNextPartitionBeginning(packets_.begin(), + &packets_not_decodable_); + while (it != packets_.end()) { const int partition_id = - _packets[i].codecSpecificHeader.codecHeader.VP8.partitionId; - const int partition_end = FindPartitionEnd(i); + (*it).codecSpecificHeader.codecHeader.VP8.partitionId; + PacketIterator partition_end = FindPartitionEnd(it); fragmentation->fragmentationOffset[partition_id] = - _packets[i].dataPtr - frame_buffer; + (*it).dataPtr - frame_buffer; assert(fragmentation->fragmentationOffset[partition_id] < static_cast(frame_buffer_length)); fragmentation->fragmentationLength[partition_id] = - _packets[partition_end].dataPtr + _packets[partition_end].sizeBytes - - _packets[i].dataPtr; + (*partition_end).dataPtr + (*partition_end).sizeBytes - (*it).dataPtr; assert(fragmentation->fragmentationLength[partition_id] <= static_cast(frame_buffer_length)); new_length += fragmentation->fragmentationLength[partition_id]; - i = FindNextPartitionBeginning(partition_end + 1); + ++partition_end; + it = FindNextPartitionBeginning(partition_end, &packets_not_decodable_); if (partition_id + 1 > fragmentation->fragmentationVectorSize) fragmentation->fragmentationVectorSize = partition_id + 1; } @@ -382,7 +254,7 @@ VCMSessionInfo::BuildVP8FragmentationHeader( // and have zero length. if (fragmentation->fragmentationLength[0] == 0) fragmentation->fragmentationOffset[0] = 0; - for (i = 1; i < fragmentation->fragmentationVectorSize; ++i) { + for (int i = 1; i < fragmentation->fragmentationVectorSize; ++i) { if (fragmentation->fragmentationLength[i] == 0) fragmentation->fragmentationOffset[i] = fragmentation->fragmentationOffset[i - 1] + @@ -395,535 +267,376 @@ VCMSessionInfo::BuildVP8FragmentationHeader( return new_length; } -int VCMSessionInfo::FindNextPartitionBeginning(int packet_index) { - while (packet_index <= _highestPacketIndex) { - if (_packets[packet_index].completeNALU == kNaluUnset) { - // Missing packet - ++packet_index; - continue; - } - const bool beginning = _packets[packet_index].codecSpecificHeader. - codecHeader.VP8.beginningOfPartition; - if (beginning) { - return packet_index; - } else { +VCMSessionInfo::PacketIterator VCMSessionInfo::FindNextPartitionBeginning( + PacketIterator it, int* packets_skipped) const { + while (it != packets_.end()) { + if ((*it).codecSpecificHeader.codecHeader.VP8.beginningOfPartition) { + return it; + } else if (packets_skipped != NULL) { // This packet belongs to a partition with a previous loss and can't // be decoded. - ++_packetsNotDecodable; + ++(*packets_skipped); } - ++packet_index; + ++it; } - return packet_index; + return it; } -int VCMSessionInfo::FindPartitionEnd(int packet_index) const { - const int partition_id = _packets[packet_index].codecSpecificHeader. - codecHeader.VP8.partitionId; - while (packet_index <= _highestPacketIndex) { - const bool beginning = _packets[packet_index].codecSpecificHeader. - codecHeader.VP8.beginningOfPartition; - const bool packet_loss_found = - (_packets[packet_index].completeNALU == kNaluUnset || (!beginning && - !InSequence(_packets[packet_index].seqNum, - _packets[packet_index - 1].seqNum))); - const int current_partition_id = _packets[packet_index].codecSpecificHeader. - codecHeader.VP8.partitionId; - if (packet_loss_found || (beginning && - current_partition_id != partition_id)) { +VCMSessionInfo::PacketIterator VCMSessionInfo::FindPartitionEnd( + PacketIterator it) const { + assert((*it).codec == kVideoCodecVP8); + PacketIterator prev_it = it; + const int partition_id = + (*it).codecSpecificHeader.codecHeader.VP8.partitionId; + while (it != packets_.end()) { + bool beginning = + (*it).codecSpecificHeader.codecHeader.VP8.beginningOfPartition; + int current_partition_id = + (*it).codecSpecificHeader.codecHeader.VP8.partitionId; + bool packet_loss_found = (!beginning && !InSequence(it, prev_it)); + if (packet_loss_found || + (beginning && current_partition_id != partition_id)) { // Missing packet, the previous packet was the last in sequence. - return packet_index - 1; + return prev_it; } - ++packet_index; + prev_it = it; + ++it; } - return packet_index - 1; + return prev_it; } -bool VCMSessionInfo::InSequence(WebRtc_UWord16 seqNum, - WebRtc_UWord16 prevSeqNum) { - // prevSeqNum is allowed to wrap around here - return (static_cast(prevSeqNum + 1) == seqNum); +bool VCMSessionInfo::InSequence(const PacketIterator& packet_it, + const PacketIterator& prev_packet_it) { + // If the two iterators are pointing to the same packet they are considered + // to be in sequence. + return (packet_it == prev_packet_it || + (static_cast((*prev_packet_it).seqNum + 1) == + (*packet_it).seqNum)); } -WebRtc_UWord32 -VCMSessionInfo::MakeDecodable(WebRtc_UWord8* ptrStartOfLayer) -{ - if (_lowSeqNum < 0) // No packets in this session - { - return 0; +int VCMSessionInfo::MakeDecodable() { + int return_length = 0; + PacketIterator it = packets_.begin(); + // Make sure we remove the first NAL unit if it's not decodable. + if ((*it).completeNALU == kNaluIncomplete || + (*it).completeNALU == kNaluEnd) { + PacketIterator nalu_end = FindNaluEnd(it); + return_length += DeletePacketData(it, nalu_end); + it = nalu_end; + } + PacketIterator prev_it = it; + // Take care of the rest of the NAL units. + for (; it != packets_.end(); ++it) { + bool start_of_nalu = ((*it).completeNALU == kNaluStart || + (*it).completeNALU == kNaluComplete); + if (!start_of_nalu && !InSequence(it, prev_it)) { + // Found a sequence number gap due to packet loss. + PacketIterator nalu_end = FindNaluEnd(it); + return_length += DeletePacketData(it, nalu_end); + it = nalu_end; } - - WebRtc_Word32 startIndex = 0; - WebRtc_Word32 endIndex = 0; - int packetIndex = 0; - WebRtc_UWord32 returnLength = 0; - for (packetIndex = 0; packetIndex <= _highestPacketIndex; ++packetIndex) - { - if (_packets[packetIndex].completeNALU == kNaluUnset) // Found a lost packet - { - FindNaluBorder(packetIndex, startIndex, endIndex); - if (startIndex == -1) - { - startIndex = 0; - } - if (endIndex == -1) - { - endIndex = _highestPacketIndex; - } - - returnLength += DeletePackets(ptrStartOfLayer, - packetIndex + 1, endIndex); - packetIndex = endIndex; - }// end lost packet - } - - // Make sure the first packet is decodable (Either complete nalu or start - // of NALU) - if (_packets[0].sizeBytes > 0) - { - switch (_packets[0].completeNALU) - { - case kNaluComplete: // Packet can be decoded as is. - break; - - case kNaluStart: - // Packet contain beginning of NALU- No need to do anything. - break; - case kNaluIncomplete: //Packet is not beginning or end of NALU - // Need to find the end of this NALU and delete all packets. - FindNaluBorder(0, startIndex, endIndex); - if (endIndex == -1) // No end found. Delete - { - endIndex = _highestPacketIndex; - } - // Delete this NALU. - returnLength += DeletePackets(ptrStartOfLayer, 0, endIndex); - break; - case kNaluEnd: // Packet is the end of a NALU - // Delete this NALU - returnLength += DeletePackets(ptrStartOfLayer, 0, 0); - break; - default: - assert(false); - } - } - - return returnLength; + prev_it = it; + } + return return_length; } -WebRtc_Word32 -VCMSessionInfo::ZeroOutSeqNum(WebRtc_Word32* list, - WebRtc_Word32 numberOfSeqNum) -{ - if (NULL == list || numberOfSeqNum < 1) - { - return -1; - } - if (_lowSeqNum == -1) - { - // no packets in this frame - return 0; - } - - // Find end point (index of entry that equals _lowSeqNum) - int index = 0; - for (; index < numberOfSeqNum; index++) - { - if (list[index] == _lowSeqNum) - { - list[index] = -1; - break; - } - } - - // Zero out between first entry and end point - int i = 0; - while ( i <= _highestPacketIndex && index < numberOfSeqNum) - { - if (_packets[i].completeNALU != kNaluUnset) - { - list[index] = -1; - } - else - { - _sessionNACK = true; - } - i++; - index++; - } - if (!_packets[0].isFirstPacket) - { - _sessionNACK = true; - } +int VCMSessionInfo::ZeroOutSeqNum(int* seq_num_list, + int seq_num_list_length) { + if (NULL == seq_num_list || seq_num_list_length < 1) { + return -1; + } + if (packets_.empty()) { return 0; + } + + // Find end point (index of entry equals the sequence number of the first + // packet). + int index = 0; + for (; index < seq_num_list_length; ++index) { + if (seq_num_list[index] == packets_.front().seqNum) { + seq_num_list[index] = -1; + ++index; + break; + } + } + + // Zero out between the first entry and the end point. + PacketIterator it = packets_.begin(); + PacketIterator prev_it = it; + ++it; + while (it != packets_.end() && index < seq_num_list_length) { + if (!InSequence(it, prev_it)) { + // Found a sequence number gap due to packet loss. + index += PacketsMissing(it, prev_it); + session_nack_ = true; + } + seq_num_list[index] = -1; + ++index; + prev_it = it; + ++it; + } + if (!packets_.front().isFirstPacket) + session_nack_ = true; + return 0; } -WebRtc_Word32 -VCMSessionInfo::ZeroOutSeqNumHybrid(WebRtc_Word32* list, - WebRtc_Word32 numberOfSeqNum, - WebRtc_UWord32 rttMs) -{ - if (NULL == list || numberOfSeqNum < 1) - { - return -1; - } - - WebRtc_Word32 index = 0; - // Find end point (index of entry that equals _lowSeqNum) - for (; index < numberOfSeqNum; index++) - { - if (list[index] == _lowSeqNum) - { - list[index] = -1; - break; - } - } - - // TODO(mikhal): 1. update score based on RTT value 2. add partition data - // use the previous available - bool isBaseAvailable = false; - if ((index > 0) && (list[index] == -1)) - { - // found first packet, for now let's go only one back - if ((list[index - 1] == -1) || (list[index - 1] == -2)) - { - // This is indeed the first packet, as previous packet was populated - isBaseAvailable = true; - } - } - bool allowNack = false; - if (!_packets[0].isFirstPacket || !isBaseAvailable) - { - allowNack = true; - } - - // Zero out between first entry and end point - WebRtc_Word32 i = 0; - // Score place holder - based on RTT and partition (when available). - const float nackScoreTh = 0.25f; - - WebRtc_Word32 highMediaPacket; - if (_markerSeqNum != -1) - { - highMediaPacket = _markerSeqNum; - } - else - { - // Estimation - highMediaPacket = _emptySeqNumLow - 1 > _highSeqNum ? - _emptySeqNumLow - 1: _highSeqNum; - } - - // Place holder - int rttScore = 1.0f; - - while (list[index] <= highMediaPacket && index < numberOfSeqNum) - { - if (_packets[i].completeNALU != kNaluUnset) - { - list[index] = -1; - } - else - { - // compute score of the packet - float score = 1.0f; - // multiply internal score (importance) by external score (RTT) - score *= rttScore; - if (score > nackScoreTh) - { - allowNack = true; - } - else - { - list[index] = -1; - } - } - i++; - index++; - } - // Empty packets follow the data packets, and therefore have a higher - // sequence number. We do not want to NACK empty packets. - - if ((_emptySeqNumLow != -1) && (_emptySeqNumHigh != -1) && - (index < numberOfSeqNum)) - { - // first make sure that we are at least at the minimum value - // (if not we are missing last packet(s)) - while (list[index] < _emptySeqNumLow && index < numberOfSeqNum) - { - index++; - } - - // mark empty packets - while (list[index] <= _emptySeqNumHigh && index < numberOfSeqNum) - { - list[index] = -2; - index++; - } - } - - _sessionNACK = allowNack; +int VCMSessionInfo::ZeroOutSeqNumHybrid(int* seq_num_list, + int seq_num_list_length, + int rtt_ms) { + if (NULL == seq_num_list || seq_num_list_length < 1) { + return -1; + } + if (packets_.empty() && empty_seq_num_low_ == -1) { return 0; + } + + WebRtc_Word32 index = 0; + // Find end point (index of entry equals the sequence number of the first + // packet). + for (; index < seq_num_list_length; ++index) { + if (seq_num_list[index] == packets_.front().seqNum) { + seq_num_list[index] = -1; + break; + } + } + + // TODO(mikhal): 1. Update score based on RTT value 2. Add partition data. + // Use the previous available. + bool base_available = false; + if ((index > 0) && (seq_num_list[index] == -1)) { + // found first packet, for now let's go only one back + if ((seq_num_list[index - 1] == -1) || (seq_num_list[index - 1] == -2)) { + // This is indeed the first packet, as previous packet was populated + base_available = true; + } + } + bool allow_nack = (!packets_.front().isFirstPacket || !base_available); + + // Zero out between first entry and end point. + // Score place holder - based on RTT and partition (when available). + const float nack_score_threshold = 0.25f; + + WebRtc_Word32 media_high_seq_num; + if (HaveLastPacket()) { + media_high_seq_num = packets_.back().seqNum; + } else { + // Estimation. + media_high_seq_num = empty_seq_num_low_ - 1 > packets_.back().seqNum ? + empty_seq_num_low_ - 1: packets_.back().seqNum; + } + + // Place holder. + int rtt_score = 1.0f; + // Zero out between first entry and end point. + PacketIterator it = packets_.begin(); + PacketIterator prev_it = it; + ++index; + ++it; + // TODO(holmer): Rewrite this in a way which better makes use of the list. + while (it != packets_.end() && index < seq_num_list_length) { + // Only process media packet sequence numbers. + if (LatestSequenceNumber((*it).seqNum, media_high_seq_num, NULL) == + (*it).seqNum && (*it).seqNum != media_high_seq_num) + break; + if (!InSequence(it, prev_it)) { + // Found a sequence number gap due to packet loss. + int num_lost = PacketsMissing(it, prev_it); + for (int i = 0 ; i < num_lost; ++i) { + // Compute score of the packet. + float score = 1.0f; + // Multiply internal score (importance) by external score (RTT). + score *= rtt_score; + if (score > nack_score_threshold) { + allow_nack = true; + } else { + seq_num_list[index] = -1; + } + ++index; + } + } + seq_num_list[index] = -1; + ++index; + prev_it = it; + ++it; + } + + // Empty packets follow the data packets, and therefore have a higher + // sequence number. We do not want to NACK empty packets. + if ((empty_seq_num_low_ != -1) && (empty_seq_num_high_ != -1) && + (index < seq_num_list_length)) { + // First make sure that we are at least at the minimum value (if not we are + // missing last packet(s)). + while (seq_num_list[index] < empty_seq_num_low_ && + index < seq_num_list_length) { + ++index; + } + + // Mark empty packets. + while (seq_num_list[index] <= empty_seq_num_high_ && + index < seq_num_list_length) { + seq_num_list[index] = -2; + ++index; + } + } + + session_nack_ = allow_nack; + return 0; } -WebRtc_Word32 -VCMSessionInfo::GetHighestPacketIndex() -{ - return _highestPacketIndex; +int VCMSessionInfo::PacketsMissing(const PacketIterator& packet_it, + const PacketIterator& prev_packet_it) { + if (packet_it == prev_packet_it) + return 0; + if ((*prev_packet_it).seqNum > (*packet_it).seqNum) // Wrap. + return static_cast( + static_cast((*packet_it).seqNum + 0x10000) - + (*prev_packet_it).seqNum) - 1; + else + return (*packet_it).seqNum - (*prev_packet_it).seqNum - 1; } bool -VCMSessionInfo::HaveLastPacket() const -{ - return _markerBit; +VCMSessionInfo::HaveLastPacket() const { + return (!packets_.empty() && packets_.back().markerBit); } bool -VCMSessionInfo::IsRetransmitted() const -{ - return _sessionNACK; +VCMSessionInfo::session_nack() const { + return session_nack_; } -WebRtc_Word64 -VCMSessionInfo::InsertPacket(const VCMPacket& packet, - WebRtc_UWord8* ptrStartOfLayer, - bool enableDecodableState, - WebRtc_UWord32 rttMs) -{ - // not allowed - assert(!packet.insertStartCode || !packet.bits); - // Check if this is first packet (only valid for some codecs) - if (packet.isFirstPacket) - { - // the first packet in the frame always signals the frametype - _frameType = packet.frameType; - } - else if (_frameType == kFrameEmpty && packet.frameType != kFrameEmpty) - { - // Update the frame type with the first media packet - _frameType = packet.frameType; - } - if (packet.frameType == kFrameEmpty) - { - // Update seq number as an empty packet - return InformOfEmptyPacket(packet.seqNum); - } - - // Check sequence number and update highest and lowest sequence numbers - // received. Move data if this seq num is lower than previously lowest. - - if (packet.seqNum > _highSeqNum) - { - // This packet's seq num is higher than previously highest seq num; - // normal case if we have a wrap, only update with wrapped values - if (!(_highSeqNum < 0x00ff && packet.seqNum > 0xff00)) - { - _highSeqNum = packet.seqNum; - } - } - else if (_highSeqNum > 0xff00 && packet.seqNum < 0x00ff) - { - // wrap - _highSeqNum = packet.seqNum; - } - int packetIndex = packet.seqNum - (WebRtc_UWord16)_lowSeqNum; - if (_lowSeqNum < 0x00ff && packet.seqNum > 0xff00) - { - // negative wrap - packetIndex = packet.seqNum - 0x10000 - _lowSeqNum; - } - if (packetIndex < 0) - { - if (_lowSeqNum > 0xff00 && packet.seqNum < 0x00ff) - { - // we have a false detect due to the wrap - packetIndex = (0xffff - (WebRtc_UWord16)_lowSeqNum) + packet.seqNum - + (WebRtc_UWord16)1; - } else - { - // This packet's seq num is lower than previously lowest seq num, - // but no wrap We need to move the data in all arrays indexed by - // packetIndex and insert the new packet's info - // How many packets should we leave room for (positions to shift)? - // Example - this seq num is 3 lower than previously lowest seq num - // Before: |--prev packet with lowest seq num--|--|...| - // After: |--new lowest seq num--|--|--|--prev packet with - // lowest seq num--|--|...| - - WebRtc_UWord16 positionsToShift = (WebRtc_UWord16)_lowSeqNum - - packet.seqNum; - WebRtc_UWord16 numOfPacketsToMove = _highestPacketIndex + 1; - - // sanity, do we have room for the shift? - if ((positionsToShift + numOfPacketsToMove) > - kMaxPacketsInJitterBuffer) - { - return -1; - } - - // Shift _packetSizeBytes array - memmove(&_packets[positionsToShift], - &_packets[0], numOfPacketsToMove * sizeof(VCMPacket)); - for (int i = 0; i < positionsToShift; ++i) - _packets[i].Reset(); - - _highestPacketIndex += positionsToShift; - _lowSeqNum = packet.seqNum; - packetIndex = 0; // (seqNum - _lowSeqNum) = 0 - } - } // if (_lowSeqNum > seqNum) - - // sanity - if (packetIndex >= kMaxPacketsInJitterBuffer ) - { - return -1; - } - if (packetIndex < 0 ) - { - return -1; - } - - // Check for duplicate packets - if (_packets[packetIndex].sizeBytes != 0) - { - // We have already received a packet with this seq number, ignore it. - return -2; - } - - // update highest packet index - _highestPacketIndex = packetIndex > _highestPacketIndex ? - packetIndex :_highestPacketIndex; - - int returnLength = InsertBuffer(ptrStartOfLayer, packetIndex, packet); - UpdateCompleteSession(); - if (enableDecodableState) - UpdateDecodableSession(rttMs); - return returnLength; -} - - -WebRtc_Word32 -VCMSessionInfo::InformOfEmptyPacket(const WebRtc_UWord16 seqNum) -{ - // Empty packets may be FEC or filler packets. They are sequential and - // follow the data packets, therefore, we should only keep track of the high - // and low sequence numbers and may assume that the packets in between are - // empty packets belonging to the same frame (timestamp). - - if (_emptySeqNumLow == -1 && _emptySeqNumHigh == -1) - { - _emptySeqNumLow = seqNum; - _emptySeqNumHigh = seqNum; - } - else - { - if (seqNum > _emptySeqNumHigh) - { - // This packet's seq num is higher than previously highest seq num; - // normal case if we have a wrap, only update with wrapped values - if (!(_emptySeqNumHigh < 0x00ff && seqNum > 0xff00)) - { - _emptySeqNumHigh = seqNum; - } - } - else if (_emptySeqNumHigh > 0xff00 && seqNum < 0x00ff) - { - // wrap - _emptySeqNumHigh = seqNum; - } - if (_emptySeqNumLow < 0x00ff && seqNum > 0xff00) - { - // negative wrap - if (seqNum - 0x10000 - _emptySeqNumLow < 0) - { - _emptySeqNumLow = seqNum; - } - } - } +int VCMSessionInfo::InsertPacket(const VCMPacket& packet, + uint8_t* frame_buffer, + bool enable_decodable_state, + int rtt_ms) { + assert(!packet.insertStartCode || !packet.bits); + // Check if this is first packet (only valid for some codecs) + if (packet.isFirstPacket) { + // The first packet in a frame signals the frame type. + frame_type_ = packet.frameType; + } else if (frame_type_ == kFrameEmpty && packet.frameType != kFrameEmpty) { + // Update the frame type with the first media packet. + frame_type_ = packet.frameType; + } + if (packet.frameType == kFrameEmpty) { + // Update sequence number of an empty packet. + // Only media packets are inserted into the packet list. + InformOfEmptyPacket(packet.seqNum); return 0; + } + + if (packets_.size() == kMaxPacketsInSession) + return -1; + + // Find the position of this packet in the packet list in sequence number + // order and insert it. Loop over the list in reverse order. + ReversePacketIterator rit = packets_.rbegin(); + for (; rit != packets_.rend(); ++rit) + if (LatestSequenceNumber((*rit).seqNum, packet.seqNum, NULL) == + packet.seqNum) + break; + + // Check for duplicate packets. + if (rit != packets_.rend() && + (*rit).seqNum == packet.seqNum && (*rit).sizeBytes > 0) + return -2; + + // The insert operation invalidates the iterator |rit|. + PacketIterator packet_list_it = packets_.insert(rit.base(), packet); + + int returnLength = InsertBuffer(frame_buffer, packet_list_it); + UpdateCompleteSession(); + if (enable_decodable_state) + UpdateDecodableSession(rtt_ms); + return returnLength; } -WebRtc_UWord32 -VCMSessionInfo::PrepareForDecode(WebRtc_UWord8* ptrStartOfLayer, - VideoCodecType codec) -{ - WebRtc_UWord32 currentPacketOffset = 0; - WebRtc_UWord32 length = GetSessionLength(); - WebRtc_UWord32 realDataBytes = 0; - if (length == 0) - { - return length; - } - bool previousLost = false; - for (int i = 0; i <= _highestPacketIndex; i++) - { - if (_packets[i].bits) - { - if (currentPacketOffset > 0) - { - WebRtc_UWord8* ptrFirstByte = ptrStartOfLayer + - currentPacketOffset; - if (_packets[i - 1].sizeBytes == 0 || previousLost) - { - // It is be better to throw away this packet if we are - // missing the previous packet. - memset(ptrFirstByte, 0, _packets[i].sizeBytes); - previousLost = true; - ++_packetsNotDecodable; - } - else if (_packets[i].sizeBytes > 0) // Ignore if empty packet - { - // Glue with previous byte - // Move everything from [this packet start + 1, - // end of buffer] one byte to the left - WebRtc_UWord8* ptrPrevByte = ptrFirstByte - 1; - *ptrPrevByte = (*ptrPrevByte) | (*ptrFirstByte); - WebRtc_UWord32 lengthToEnd = length - - (currentPacketOffset + 1); - memmove((void*)ptrFirstByte, (void*)(ptrFirstByte + 1), - lengthToEnd); - _packets[i].sizeBytes--; - length--; - previousLost = false; - realDataBytes += _packets[i].sizeBytes; - } - } - else - { - memset(ptrStartOfLayer, 0, _packets[i].sizeBytes); - previousLost = true; - ++_packetsNotDecodable; - } +void VCMSessionInfo::InformOfEmptyPacket(uint16_t seq_num) { + // Empty packets may be FEC or filler packets. They are sequential and + // follow the data packets, therefore, we should only keep track of the high + // and low sequence numbers and may assume that the packets in between are + // empty packets belonging to the same frame (timestamp). + empty_seq_num_high_ = LatestSequenceNumber(seq_num, empty_seq_num_high_, + NULL); + if (LatestSequenceNumber(seq_num, empty_seq_num_low_, NULL) == + empty_seq_num_low_) + empty_seq_num_low_ = seq_num; +} + +int VCMSessionInfo::PrepareForDecode(uint8_t* frame_buffer) { + int length = SessionLength(); + int real_data_bytes = 0; + if (length == 0) + return length; + bool previous_lost = false; + PacketIterator it = packets_.begin(); + PacketIterator prev_it = it; + for (; it != packets_.end(); ++it) { + bool packet_loss = ((*prev_it).sizeBytes == 0 || + !InSequence(it, prev_it)); + if ((*it).bits) { + if (prev_it != it) { // Not the first packet. + uint8_t* ptr_first_byte = + const_cast((*it).dataPtr); + + if (packet_loss) { + // It is be better to throw away this packet if we are + // missing the previous packet. + memset(ptr_first_byte, 0, (*it).sizeBytes); + previous_lost = true; + ++packets_not_decodable_; + } else if ((*it).sizeBytes > 0) { + // Glue with previous byte. + // Move everything from [this packet start + 1, end of buffer] one + // byte to the left. + uint8_t* ptr_prev_byte = + const_cast((*prev_it).dataPtr) + + (*prev_it).sizeBytes - 1; + *ptr_prev_byte = (*ptr_prev_byte) | (*ptr_first_byte); + memmove(const_cast((*it).dataPtr), + (*it).dataPtr + 1, (*it).sizeBytes - 1); + ShiftSubsequentPackets(it, -1); + (*it).sizeBytes--; + length--; + previous_lost = false; + real_data_bytes += (*it).sizeBytes; } - else if (_packets[i].sizeBytes == 0 && codec == kVideoCodecH263) - { - WebRtc_UWord8* ptrFirstByte = ptrStartOfLayer + currentPacketOffset; - memmove(ptrFirstByte + 10, ptrFirstByte, - length - currentPacketOffset); - memset(ptrFirstByte, 0, 10); - _packets[i].sizeBytes = 10; - length += _packets[i].sizeBytes; - previousLost = true; - } - else - { - realDataBytes += _packets[i].sizeBytes; - previousLost = false; - } - currentPacketOffset += _packets[i].sizeBytes; + } else { + memset(const_cast((*it).dataPtr), 0, + (*it).sizeBytes); + previous_lost = true; + ++packets_not_decodable_; + } + } else if (packet_loss && + (*it).codecSpecificHeader.codec == kRTPVideoH263) { + // Pad H.263 packet losses with 10 zeros to make it easier + // for the decoder. + const int kPaddingLength = 10; + WebRtc_UWord8 padding_data[kPaddingLength] = {0}; + // Make a copy of the previous packet. + VCMPacket padding_packet(*it); + ++padding_packet.seqNum; + padding_packet.dataPtr = padding_data; + padding_packet.sizeBytes = kPaddingLength; + length += InsertPacket(padding_packet, frame_buffer, false, 0); + previous_lost = true; + } else { + real_data_bytes += (*it).sizeBytes; + previous_lost = false; } - if (realDataBytes == 0) - { - // Drop the frame since all it contains are zeros - length = 0; - for (int i = 0; i <= _highestPacketIndex; ++i) - _packets[i].Reset(); - } - return length; + prev_it = it; + } + if (real_data_bytes == 0) { + // Drop the frame since all it contains are zeros. + length = 0; + packets_.clear(); + } + return length; } -int VCMSessionInfo::NotDecodablePackets() const { - return _packetsNotDecodable; +int VCMSessionInfo::packets_not_decodable() const { + return packets_not_decodable_; } -} +} // namespace webrtc diff --git a/src/modules/video_coding/main/source/session_info.h b/src/modules/video_coding/main/source/session_info.h index 05bdcc6aa..6e645b40e 100644 --- a/src/modules/video_coding/main/source/session_info.h +++ b/src/modules/video_coding/main/source/session_info.h @@ -11,138 +11,121 @@ #ifndef WEBRTC_MODULES_VIDEO_CODING_SESSION_INFO_H_ #define WEBRTC_MODULES_VIDEO_CODING_SESSION_INFO_H_ -#include "typedefs.h" -#include "module_common_types.h" -#include "packet.h" +#include +#include -namespace webrtc -{ +#include "modules/interface/module_common_types.h" +#include "modules/video_coding/main/source/packet.h" +#include "typedefs.h" // NOLINT(build/include) -enum { kMaxVP8Partitions = 9 }; +namespace webrtc { -class VCMSessionInfo -{ -public: - VCMSessionInfo(); - virtual ~VCMSessionInfo(); +class VCMSessionInfo { + public: + VCMSessionInfo(); - VCMSessionInfo(const VCMSessionInfo& rhs); + void UpdateDataPointers(ptrdiff_t address_delta); + int ZeroOutSeqNum(int* seq_num_list, + int seq_num_list_length); - void UpdateDataPointers(const WebRtc_UWord8* frame_buffer, - const WebRtc_UWord8* prev_buffer_address); + // Hybrid version: Zero out seq num for NACK list + // Selectively NACK packets. + int ZeroOutSeqNumHybrid(int* seq_num_list, + int seq_num_list_length, + int rtt_ms); + void Reset(); + int InsertPacket(const VCMPacket& packet, + uint8_t* frame_buffer, + bool enable_decodable_state, + int rtt_ms); + bool complete() const; + bool decodable() const; - WebRtc_Word32 ZeroOutSeqNum(WebRtc_Word32* list, - WebRtc_Word32 numberOfSeqNum); - // Hybrid version: Zero out seq num for NACK list - // Selectively NACK packets. - WebRtc_Word32 ZeroOutSeqNumHybrid(WebRtc_Word32* list, - WebRtc_Word32 numberOfSeqNum, - WebRtc_UWord32 rttMs); - virtual void Reset(); + // Builds fragmentation headers for VP8, each fragment being a decodable + // VP8 partition. Returns the total number of bytes which are decodable. Is + // used instead of MakeDecodable for VP8. + int BuildVP8FragmentationHeader(uint8_t* frame_buffer, + int frame_buffer_length, + RTPFragmentationHeader* fragmentation); - WebRtc_Word64 InsertPacket(const VCMPacket& packet, - WebRtc_UWord8* ptrStartOfLayer, - bool enableDecodableState, - WebRtc_UWord32 rttMs); - WebRtc_Word32 InformOfEmptyPacket(const WebRtc_UWord16 seqNum); + // Makes the frame decodable. I.e., only contain decodable NALUs. All + // non-decodable NALUs will be deleted and packets will be moved to in + // memory to remove any empty space. + // Returns the number of bytes deleted from the session. + int MakeDecodable(); + int SessionLength() const; + bool HaveLastPacket() const; + bool session_nack() const; + webrtc::FrameType FrameType() const { return frame_type_; } + int LowSequenceNumber() const; - virtual bool IsSessionComplete() const; - virtual bool IsSessionDecodable() const; + // Returns highest sequence number, media or empty. + int HighSequenceNumber() const; + int PictureId() const; + int TemporalId() const; + int Tl0PicId() const; + bool NonReference() const; + int PrepareForDecode(uint8_t* frame_buffer); + void SetPreviousFrameLoss() { previous_frame_loss_ = true; } + bool PreviousFrameLoss() const { return previous_frame_loss_; } - // Builds fragmentation headers for VP8, each fragment being a decodable - // VP8 partition. Returns the total number of bytes which are decodable. Is - // used instead of MakeDecodable for VP8. - int BuildVP8FragmentationHeader(WebRtc_UWord8* frame_buffer, - int frame_buffer_length, - RTPFragmentationHeader* fragmentation); + // The number of packets discarded because the decoder can't make use of + // them. + int packets_not_decodable() const; - // Makes the frame decodable. I.e., only contain decodable NALUs. All - // non-decodable NALUs will be deleted and packets will be moved to in - // memory to remove any empty space. - // Returns the number of bytes deleted from the session. - WebRtc_UWord32 MakeDecodable(WebRtc_UWord8* ptrStartOfLayer); + private: + enum { kMaxVP8Partitions = 9 }; - WebRtc_UWord32 GetSessionLength(); - bool HaveLastPacket() const; - bool IsRetransmitted() const; - webrtc::FrameType FrameType() const { return _frameType; } + typedef std::list PacketList; + typedef PacketList::iterator PacketIterator; + typedef PacketList::const_iterator PacketIteratorConst; + typedef PacketList::reverse_iterator ReversePacketIterator; - virtual WebRtc_Word32 GetHighestPacketIndex(); + void InformOfEmptyPacket(uint16_t seq_num); - void SetStartSeqNumber(WebRtc_UWord16 seqNumber); + // Finds the packet of the beginning of the next VP8 partition. If + // none is found the returned iterator points to |packets_.end()|. + // |it| is expected to point to the last packet of the previous partition, + // or to the first packet of the frame. |packets_skipped| is incremented + // for each packet found which doesn't have the beginning bit set. + PacketIterator FindNextPartitionBeginning(PacketIterator it, + int* packets_skipped) const; - bool HaveStartSeqNumber(); + // Returns an iterator pointing to the last packet of the partition pointed to + // by |it|. + PacketIterator FindPartitionEnd(PacketIterator it) const; + static bool InSequence(const PacketIterator& it, + const PacketIterator& prev_it); + static int PacketsMissing(const PacketIterator& packet_it, + const PacketIterator& prev_packet_it); + int InsertBuffer(uint8_t* frame_buffer, + PacketIterator packetIterator); + void ShiftSubsequentPackets(PacketIterator it, int steps_to_shift); + PacketIterator FindNaluEnd(PacketIterator packet_iter) const; + // Deletes the data of all packets between |start| and |end|, inclusively. + // Note that this function doesn't delete the actual packets. + int DeletePacketData(PacketIterator start, + PacketIterator end); + void UpdateCompleteSession(); - WebRtc_Word32 GetLowSeqNum() const; - // returns highest seqNum, media or empty - WebRtc_Word32 GetHighSeqNum() const; - int PictureId() const; - int TemporalId() const; - int Tl0PicId() const; - bool NonReference() const; + // When enabled, determine if session is decodable, i.e. incomplete but + // would be sent to the decoder. + void UpdateDecodableSession(int rtt_ms); - WebRtc_UWord32 PrepareForDecode(WebRtc_UWord8* ptrStartOfLayer, - VideoCodecType codec); - - void SetPreviousFrameLoss() { _previousFrameLoss = true; } - bool PreviousFrameLoss() const { return _previousFrameLoss; } - - // The number of packets discarded because the decoder can't make use of - // them. - int NotDecodablePackets() 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 - // partitionIndex. - int FindPartitionEnd(int packet_index) const; - static bool InSequence(WebRtc_UWord16 seqNum, WebRtc_UWord16 prevSeqNum); - WebRtc_UWord32 InsertBuffer(WebRtc_UWord8* ptrStartOfLayer, - WebRtc_Word32 packetIndex, - const VCMPacket& packet); - void FindNaluBorder(WebRtc_Word32 packetIndex, - WebRtc_Word32& startIndex, - WebRtc_Word32& endIndex); - WebRtc_UWord32 DeletePackets(WebRtc_UWord8* ptrStartOfLayer, - WebRtc_Word32 startIndex, - WebRtc_Word32 endIndex); - void UpdateCompleteSession(); - // When enabled, determine if session is decodable, i.e. incomplete but - // would be sent to the decoder. - void UpdateDecodableSession(WebRtc_UWord32 rttMs); - // If we have inserted a packet with markerbit into this frame - bool _markerBit; - // If this session has been NACKed by JB - bool _sessionNACK; - bool _completeSession; - bool _decodableSession; - webrtc::FrameType _frameType; - bool _previousFrameLoss; - // Lowest/Highest packet sequence number in a session - WebRtc_Word32 _lowSeqNum; - WebRtc_Word32 _highSeqNum; - - // Highest packet index in this frame - WebRtc_UWord16 _highestPacketIndex; - // Packets in this frame. - // TODO(holmer): Replace this with a std::list. Doing that will - // make it possible to get rid of _markerBit, _lowSeqNum, - // _highSeqNum, _highestPacketIndex, etc. - VCMPacket _packets[kMaxPacketsInJitterBuffer]; - WebRtc_Word32 _emptySeqNumLow; - WebRtc_Word32 _emptySeqNumHigh; - // Store the sequence number that marks the last media packet - WebRtc_Word32 _markerSeqNum; - // Number of packets discarded because the decoder can't use them. - int _packetsNotDecodable; - int _pictureId; + // If this session has been NACKed by the jitter buffer. + bool session_nack_; + bool complete_; + bool decodable_; + webrtc::FrameType frame_type_; + bool previous_frame_loss_; + // Packets in this frame. + PacketList packets_; + int empty_seq_num_low_; + int empty_seq_num_high_; + // Number of packets discarded because the decoder can't use them. + int packets_not_decodable_; }; -} // namespace webrtc +} // namespace webrtc -#endif // WEBRTC_MODULES_VIDEO_CODING_SESSION_INFO_H_ +#endif // WEBRTC_MODULES_VIDEO_CODING_SESSION_INFO_H_ diff --git a/src/modules/video_coding/main/source/session_info_unittest.cc b/src/modules/video_coding/main/source/session_info_unittest.cc index 40492e638..dab46b740 100644 --- a/src/modules/video_coding/main/source/session_info_unittest.cc +++ b/src/modules/video_coding/main/source/session_info_unittest.cc @@ -11,63 +11,196 @@ #include #include "gtest/gtest.h" -#include "module_common_types.h" -#include "packet.h" -#include "session_info.h" +#include "modules/interface/module_common_types.h" +#include "modules/video_coding/main/source/packet.h" +#include "modules/video_coding/main/source/session_info.h" -using webrtc::RTPFragmentationHeader; -using webrtc::RTPVideoHeaderVP8; -using webrtc::VCMPacket; -using webrtc::VCMSessionInfo; -using webrtc::WebRtcRTPHeader; +namespace webrtc { -enum { kPacketBufferSize = 10 }; -enum { kFrameBufferSize = 10*kPacketBufferSize }; - -class TestVP8MakeDecodable : public ::testing::Test { +class TestSessionInfo : public ::testing::Test { protected: + enum { kPacketBufferSize = 10 }; + enum { kFrameBufferSize = 10 * kPacketBufferSize }; + virtual void SetUp() { memset(packet_buffer_, 0, kPacketBufferSize); memset(frame_buffer_, 0, kFrameBufferSize); - vp8_header_ = &packet_header_.type.Video.codecHeader.VP8; - packet_header_.frameType = webrtc::kVideoFrameDelta; - packet_header_.type.Video.codec = webrtc::kRTPVideoVP8; - vp8_header_->InitRTPVideoHeaderVP8(); - fragmentation_.VerifyAndAllocateFragmentationHeader( - webrtc::kMaxVP8Partitions); + session_.Reset(); + packet_.Reset(); + packet_.frameType = kVideoFrameDelta; + packet_.sizeBytes = kPacketBufferSize; + packet_.dataPtr = packet_buffer_; + packet_.seqNum = 0; + packet_.timestamp = 0; + packet_.bits = false; } - void FillPacket(WebRtc_UWord8 start_value) { + void FillPacket(uint8_t start_value) { for (int i = 0; i < kPacketBufferSize; ++i) packet_buffer_[i] = start_value + i; } + void VerifyPacket(uint8_t* start_ptr, uint8_t start_value) { + for (int j = 0; j < kPacketBufferSize; ++j) { + ASSERT_EQ(start_value + j, start_ptr[j]); + } + } + + uint8_t packet_buffer_[kPacketBufferSize]; + uint8_t frame_buffer_[kFrameBufferSize]; + VCMSessionInfo session_; + VCMPacket packet_; +}; + +class TestVP8Partitions : public TestSessionInfo { + protected: + enum { kMaxVP8Partitions = 9 }; + + virtual void SetUp() { + TestSessionInfo::SetUp(); + vp8_header_ = &packet_header_.type.Video.codecHeader.VP8; + packet_header_.frameType = kVideoFrameDelta; + packet_header_.type.Video.codec = kRTPVideoVP8; + vp8_header_->InitRTPVideoHeaderVP8(); + fragmentation_.VerifyAndAllocateFragmentationHeader(kMaxVP8Partitions); + } + bool VerifyPartition(int partition_id, int packets_expected, int start_value) { - EXPECT_EQ(static_cast(packets_expected * kPacketBufferSize), + EXPECT_EQ(static_cast(packets_expected * kPacketBufferSize), fragmentation_.fragmentationLength[partition_id]); for (int i = 0; i < packets_expected; ++i) { int packet_index = fragmentation_.fragmentationOffset[partition_id] + i * kPacketBufferSize; - for (int j = 0; j < kPacketBufferSize; ++j) { - if (packet_index + j > kFrameBufferSize) - return false; - EXPECT_EQ(start_value + i + j, frame_buffer_[packet_index + j]); - } + if (packet_index + kPacketBufferSize > kFrameBufferSize) + return false; + VerifyPacket(frame_buffer_ + packet_index, start_value + i); } return true; } - WebRtc_UWord8 packet_buffer_[kPacketBufferSize]; - WebRtc_UWord8 frame_buffer_[kFrameBufferSize]; - WebRtcRTPHeader packet_header_; - VCMSessionInfo session_; - RTPVideoHeaderVP8* vp8_header_; - RTPFragmentationHeader fragmentation_; + WebRtcRTPHeader packet_header_; + RTPVideoHeaderVP8* vp8_header_; + RTPFragmentationHeader fragmentation_; }; -TEST_F(TestVP8MakeDecodable, TwoPartitionsOneLoss) { +class TestNalUnits : public TestSessionInfo { + protected: + virtual void SetUp() { + TestSessionInfo::SetUp(); + packet_.codec = kVideoCodecH264; + } + + bool VerifyNalu(int offset, int packets_expected, int start_value) { + EXPECT_GE(session_.SessionLength(), + packets_expected * kPacketBufferSize); + for (int i = 0; i < packets_expected; ++i) { + int packet_index = offset * kPacketBufferSize + i * kPacketBufferSize; + VerifyPacket(frame_buffer_ + packet_index, start_value + i); + } + return true; + } +}; + +class TestZeroOutSeqNum : public TestSessionInfo { + protected: + enum { kMaxSeqNumListLength = 30 }; + + virtual void SetUp() { + TestSessionInfo::SetUp(); + seq_num_list_length_ = 0; + memset(seq_num_list_, 0, sizeof(seq_num_list_)); + } + + void BuildSeqNumList(uint16_t low, + uint16_t high) { + int i = 0; + while (low != high + 1) { + EXPECT_LT(i, kMaxSeqNumListLength); + if (i >= kMaxSeqNumListLength) { + seq_num_list_length_ = kMaxSeqNumListLength; + return; + } + seq_num_list_[i] = low; + low++; + i++; + } + seq_num_list_length_ = i; + } + + void VerifyAll(int value) { + for (int i = 0; i < seq_num_list_length_; ++i) + EXPECT_EQ(seq_num_list_[i], value); + } + + int seq_num_list_[kMaxSeqNumListLength]; + int seq_num_list_length_; +}; + +TEST_F(TestSessionInfo, TestSimpleAPIs) { + packet_.isFirstPacket = true; + packet_.seqNum = 0xFFFE; + packet_.sizeBytes = kPacketBufferSize; + packet_.frameType = kVideoFrameKey; + FillPacket(0); + ASSERT_EQ(kPacketBufferSize, + session_.InsertPacket(packet_, frame_buffer_, false, 0)); + EXPECT_EQ(false, session_.HaveLastPacket()); + EXPECT_EQ(kVideoFrameKey, session_.FrameType()); + + packet_.isFirstPacket = false; + packet_.markerBit = true; + packet_.seqNum += 1; + ASSERT_EQ(kPacketBufferSize, + session_.InsertPacket(packet_, frame_buffer_, false, 0)); + EXPECT_EQ(true, session_.HaveLastPacket()); + EXPECT_EQ(packet_.seqNum, session_.HighSequenceNumber()); + EXPECT_EQ(0xFFFE, session_.LowSequenceNumber()); + + // Insert empty packet which will be the new high sequence number. + // To make things more difficult we will make sure to have a wrap here. + packet_.isFirstPacket = false; + packet_.markerBit = true; + packet_.seqNum = 2; + packet_.sizeBytes = 0; + packet_.frameType = kFrameEmpty; + ASSERT_EQ(0, + session_.InsertPacket(packet_, frame_buffer_, false, 0)); + EXPECT_EQ(packet_.seqNum, session_.HighSequenceNumber()); +} + +TEST_F(TestSessionInfo, NormalOperation) { + packet_.seqNum = 0xFFFF; + packet_.isFirstPacket = true; + packet_.markerBit = false; + FillPacket(0); + ASSERT_EQ(session_.InsertPacket(packet_, frame_buffer_, false, 0), + kPacketBufferSize); + + packet_.isFirstPacket = false; + for (int i = 1; i < 9; ++i) { + packet_.seqNum += 1; + FillPacket(i); + ASSERT_EQ(session_.InsertPacket(packet_, frame_buffer_, false, 0), + kPacketBufferSize); + } + + packet_.seqNum += 1; + packet_.markerBit = true; + FillPacket(9); + ASSERT_EQ(session_.InsertPacket(packet_, frame_buffer_, false, 0), + kPacketBufferSize); + + EXPECT_EQ(0, session_.packets_not_decodable()); + EXPECT_EQ(10 * kPacketBufferSize, session_.SessionLength()); + for (int i = 0; i < 10; ++i) { + SCOPED_TRACE("Calling VerifyPacket"); + VerifyPacket(frame_buffer_ + i * kPacketBufferSize, i); + } +} + +TEST_F(TestVP8Partitions, TwoPartitionsOneLoss) { // Partition 0 | Partition 1 // [ 0 ] [ 2 ] | [ 3 ] packet_header_.type.Video.isFirstPacket = true; @@ -78,7 +211,6 @@ TEST_F(TestVP8MakeDecodable, TwoPartitionsOneLoss) { FillPacket(0); VCMPacket* packet = new VCMPacket(packet_buffer_, kPacketBufferSize, packet_header_); - session_.SetStartSeqNumber(packet_header_.header.sequenceNumber); ASSERT_EQ(session_.InsertPacket(*packet, frame_buffer_, false, 0), kPacketBufferSize); delete packet; @@ -106,7 +238,7 @@ TEST_F(TestVP8MakeDecodable, TwoPartitionsOneLoss) { delete packet; // One packet should be removed (end of partition 0). - ASSERT_EQ(session_.BuildVP8FragmentationHeader(frame_buffer_, + EXPECT_EQ(session_.BuildVP8FragmentationHeader(frame_buffer_, kFrameBufferSize, &fragmentation_), 2*kPacketBufferSize); @@ -116,7 +248,7 @@ TEST_F(TestVP8MakeDecodable, TwoPartitionsOneLoss) { EXPECT_TRUE(VerifyPartition(1, 1, 3)); } -TEST_F(TestVP8MakeDecodable, TwoPartitionsOneLoss2) { +TEST_F(TestVP8Partitions, TwoPartitionsOneLoss2) { // Partition 0 | Partition 1 // [ 1 ] [ 2 ] | [ 3 ] [ 5 ] packet_header_.type.Video.isFirstPacket = true; @@ -127,7 +259,6 @@ TEST_F(TestVP8MakeDecodable, TwoPartitionsOneLoss2) { FillPacket(1); VCMPacket* packet = new VCMPacket(packet_buffer_, kPacketBufferSize, packet_header_); - session_.SetStartSeqNumber(packet_header_.header.sequenceNumber); ASSERT_EQ(session_.InsertPacket(*packet, frame_buffer_, false, 0) , kPacketBufferSize); delete packet; @@ -166,7 +297,7 @@ TEST_F(TestVP8MakeDecodable, TwoPartitionsOneLoss2) { delete packet; // One packet should be removed (end of partition 2), 3 left. - ASSERT_EQ(session_.BuildVP8FragmentationHeader(frame_buffer_, + EXPECT_EQ(session_.BuildVP8FragmentationHeader(frame_buffer_, kFrameBufferSize, &fragmentation_), 3*kPacketBufferSize); @@ -174,9 +305,10 @@ TEST_F(TestVP8MakeDecodable, TwoPartitionsOneLoss2) { EXPECT_TRUE(VerifyPartition(0, 2, 1)); SCOPED_TRACE("Calling VerifyPartition"); EXPECT_TRUE(VerifyPartition(1, 1, 3)); + EXPECT_EQ(1, session_.packets_not_decodable()); } -TEST_F(TestVP8MakeDecodable, TwoPartitionsNoLossWrap) { +TEST_F(TestVP8Partitions, TwoPartitionsNoLossWrap) { // Partition 0 | Partition 1 // [ fffd ] [ fffe ] | [ ffff ] [ 0 ] packet_header_.type.Video.isFirstPacket = true; @@ -187,7 +319,6 @@ TEST_F(TestVP8MakeDecodable, TwoPartitionsNoLossWrap) { FillPacket(0); VCMPacket* packet = new VCMPacket(packet_buffer_, kPacketBufferSize, packet_header_); - session_.SetStartSeqNumber(packet_header_.header.sequenceNumber); ASSERT_EQ(session_.InsertPacket(*packet, frame_buffer_, false, 0), kPacketBufferSize); delete packet; @@ -226,7 +357,7 @@ TEST_F(TestVP8MakeDecodable, TwoPartitionsNoLossWrap) { delete packet; // No packet should be removed. - ASSERT_EQ(session_.BuildVP8FragmentationHeader(frame_buffer_, + EXPECT_EQ(session_.BuildVP8FragmentationHeader(frame_buffer_, kFrameBufferSize, &fragmentation_), 4*kPacketBufferSize); @@ -234,9 +365,10 @@ TEST_F(TestVP8MakeDecodable, TwoPartitionsNoLossWrap) { EXPECT_TRUE(VerifyPartition(0, 2, 0)); SCOPED_TRACE("Calling VerifyPartition"); EXPECT_TRUE(VerifyPartition(1, 2, 2)); + EXPECT_EQ(0, session_.packets_not_decodable()); } -TEST_F(TestVP8MakeDecodable, TwoPartitionsLossWrap) { +TEST_F(TestVP8Partitions, TwoPartitionsLossWrap) { // Partition 0 | Partition 1 // [ fffd ] [ fffe ] | [ ffff ] [ 1 ] packet_header_.type.Video.isFirstPacket = true; @@ -247,7 +379,6 @@ TEST_F(TestVP8MakeDecodable, TwoPartitionsLossWrap) { FillPacket(0); VCMPacket* packet = new VCMPacket(packet_buffer_, kPacketBufferSize, packet_header_); - session_.SetStartSeqNumber(packet_header_.header.sequenceNumber); ASSERT_EQ(session_.InsertPacket(*packet, frame_buffer_, false, 0), kPacketBufferSize); delete packet; @@ -286,7 +417,7 @@ TEST_F(TestVP8MakeDecodable, TwoPartitionsLossWrap) { delete packet; // One packet should be removed from the last partition - ASSERT_EQ(session_.BuildVP8FragmentationHeader(frame_buffer_, + EXPECT_EQ(session_.BuildVP8FragmentationHeader(frame_buffer_, kFrameBufferSize, &fragmentation_), 3*kPacketBufferSize); @@ -294,10 +425,11 @@ TEST_F(TestVP8MakeDecodable, TwoPartitionsLossWrap) { EXPECT_TRUE(VerifyPartition(0, 2, 0)); SCOPED_TRACE("Calling VerifyPartition"); EXPECT_TRUE(VerifyPartition(1, 1, 2)); + EXPECT_EQ(1, session_.packets_not_decodable()); } -TEST_F(TestVP8MakeDecodable, ThreePartitionsOneMissing) { +TEST_F(TestVP8Partitions, ThreePartitionsOneMissing) { // Partition 1 |Partition 2 | Partition 3 // [ 1 ] [ 2 ] | | [ 5 ] | [ 6 ] packet_header_.type.Video.isFirstPacket = true; @@ -308,7 +440,6 @@ TEST_F(TestVP8MakeDecodable, ThreePartitionsOneMissing) { FillPacket(1); VCMPacket* packet = new VCMPacket(packet_buffer_, kPacketBufferSize, packet_header_); - session_.SetStartSeqNumber(packet_header_.header.sequenceNumber); ASSERT_EQ(session_.InsertPacket(*packet, frame_buffer_, false, 0), kPacketBufferSize); delete packet; @@ -347,7 +478,7 @@ TEST_F(TestVP8MakeDecodable, ThreePartitionsOneMissing) { delete packet; // No packet should be removed. - ASSERT_EQ(session_.BuildVP8FragmentationHeader(frame_buffer_, + EXPECT_EQ(session_.BuildVP8FragmentationHeader(frame_buffer_, kFrameBufferSize, &fragmentation_), 4*kPacketBufferSize); @@ -355,9 +486,10 @@ TEST_F(TestVP8MakeDecodable, ThreePartitionsOneMissing) { EXPECT_TRUE(VerifyPartition(0, 2, 1)); SCOPED_TRACE("Calling VerifyPartition"); EXPECT_TRUE(VerifyPartition(2, 2, 5)); + EXPECT_EQ(0, session_.packets_not_decodable()); } -TEST_F(TestVP8MakeDecodable, ThreePartitionsLossInSecond) { +TEST_F(TestVP8Partitions, ThreePartitionsLossInSecond) { // Partition 0 |Partition 1 | Partition 2 // [ 1 ] [ 2 ] | [ 4 ] [ 5 ] | [ 6 ] [ 7 ] packet_header_.type.Video.isFirstPacket = true; @@ -368,7 +500,6 @@ TEST_F(TestVP8MakeDecodable, ThreePartitionsLossInSecond) { FillPacket(1); VCMPacket* packet = new VCMPacket(packet_buffer_, kPacketBufferSize, packet_header_); - session_.SetStartSeqNumber(packet_header_.header.sequenceNumber); ASSERT_EQ(session_.InsertPacket(*packet, frame_buffer_, false, 0), kPacketBufferSize); delete packet; @@ -429,7 +560,7 @@ TEST_F(TestVP8MakeDecodable, ThreePartitionsLossInSecond) { delete packet; // 2 partitions left. 2 packets removed from second partition - ASSERT_EQ(session_.BuildVP8FragmentationHeader(frame_buffer_, + EXPECT_EQ(session_.BuildVP8FragmentationHeader(frame_buffer_, kFrameBufferSize, &fragmentation_), 4*kPacketBufferSize); @@ -437,9 +568,10 @@ TEST_F(TestVP8MakeDecodable, ThreePartitionsLossInSecond) { EXPECT_TRUE(VerifyPartition(0, 2, 1)); SCOPED_TRACE("Calling VerifyPartition"); EXPECT_TRUE(VerifyPartition(2, 2, 6)); + EXPECT_EQ(2, session_.packets_not_decodable()); } -TEST_F(TestVP8MakeDecodable, AggregationOverTwoPackets) { +TEST_F(TestVP8Partitions, AggregationOverTwoPackets) { // Partition 0 | Partition 1 | Partition 2 // [ 0 | ] [ 1 ] | [ 2 ] packet_header_.type.Video.isFirstPacket = true; @@ -450,7 +582,6 @@ TEST_F(TestVP8MakeDecodable, AggregationOverTwoPackets) { FillPacket(0); VCMPacket* packet = new VCMPacket(packet_buffer_, kPacketBufferSize, packet_header_); - session_.SetStartSeqNumber(packet_header_.header.sequenceNumber); ASSERT_EQ(session_.InsertPacket(*packet, frame_buffer_, false, 0), kPacketBufferSize); delete packet; @@ -477,11 +608,12 @@ TEST_F(TestVP8MakeDecodable, AggregationOverTwoPackets) { kPacketBufferSize); delete packet; - // One packet should be removed (end of partition 0). - ASSERT_EQ(session_.BuildVP8FragmentationHeader(frame_buffer_, + // No packets removed. + EXPECT_EQ(session_.BuildVP8FragmentationHeader(frame_buffer_, kFrameBufferSize, &fragmentation_), 3*kPacketBufferSize); + EXPECT_EQ(0, session_.packets_not_decodable()); SCOPED_TRACE("Calling VerifyPartition"); EXPECT_TRUE(VerifyPartition(0, 2, 0)); // This partition is aggregated in partition 0 @@ -490,3 +622,338 @@ TEST_F(TestVP8MakeDecodable, AggregationOverTwoPackets) { SCOPED_TRACE("Calling VerifyPartition"); EXPECT_TRUE(VerifyPartition(2, 1, 2)); } + +TEST_F(TestNalUnits, OneIsolatedNaluLoss) { + packet_.isFirstPacket = true; + packet_.completeNALU = kNaluComplete; + packet_.seqNum = 0; + packet_.markerBit = false; + FillPacket(0); + ASSERT_EQ(session_.InsertPacket(packet_, frame_buffer_, false, 0), + kPacketBufferSize); + + packet_.isFirstPacket = false; + packet_.completeNALU = kNaluComplete; + packet_.seqNum += 2; + packet_.markerBit = true; + FillPacket(2); + ASSERT_EQ(session_.InsertPacket(packet_, frame_buffer_, false, 0), + kPacketBufferSize); + + EXPECT_EQ(0, session_.MakeDecodable()); + EXPECT_EQ(2 * kPacketBufferSize, session_.SessionLength()); + EXPECT_EQ(0, session_.packets_not_decodable()); + SCOPED_TRACE("Calling VerifyNalu"); + EXPECT_TRUE(VerifyNalu(0, 1, 0)); + SCOPED_TRACE("Calling VerifyNalu"); + EXPECT_TRUE(VerifyNalu(1, 1, 2)); +} + +TEST_F(TestNalUnits, LossInMiddleOfNalu) { + packet_.isFirstPacket = true; + packet_.completeNALU = kNaluComplete; + packet_.seqNum = 0; + packet_.markerBit = false; + FillPacket(0); + ASSERT_EQ(session_.InsertPacket(packet_, frame_buffer_, false, 0), + kPacketBufferSize); + + packet_.isFirstPacket = false; + packet_.completeNALU = kNaluEnd; + packet_.seqNum += 2; + packet_.markerBit = true; + FillPacket(2); + ASSERT_EQ(session_.InsertPacket(packet_, frame_buffer_, false, 0), + kPacketBufferSize); + + EXPECT_EQ(kPacketBufferSize, session_.MakeDecodable()); + EXPECT_EQ(kPacketBufferSize, session_.SessionLength()); + EXPECT_EQ(1, session_.packets_not_decodable()); + SCOPED_TRACE("Calling VerifyNalu"); + EXPECT_TRUE(VerifyNalu(0, 1, 0)); +} + +TEST_F(TestNalUnits, StartAndEndOfLastNalUnitLost) { + packet_.isFirstPacket = true; + packet_.completeNALU = kNaluComplete; + packet_.seqNum = 0; + packet_.markerBit = false; + FillPacket(0); + ASSERT_EQ(session_.InsertPacket(packet_, frame_buffer_, false, 0), + kPacketBufferSize); + + packet_.isFirstPacket = false; + packet_.completeNALU = kNaluIncomplete; + packet_.seqNum += 2; + packet_.markerBit = false; + FillPacket(1); + ASSERT_EQ(session_.InsertPacket(packet_, frame_buffer_, false, 0), + kPacketBufferSize); + + EXPECT_EQ(kPacketBufferSize, session_.MakeDecodable()); + EXPECT_EQ(kPacketBufferSize, session_.SessionLength()); + EXPECT_EQ(1, session_.packets_not_decodable()); + SCOPED_TRACE("Calling VerifyNalu"); + EXPECT_TRUE(VerifyNalu(0, 1, 0)); +} + +TEST_F(TestNalUnits, ReorderWrapNoLoss) { + packet_.seqNum = 0xFFFF; + packet_.isFirstPacket = false; + packet_.completeNALU = kNaluIncomplete; + packet_.seqNum += 1; + packet_.markerBit = false; + FillPacket(1); + ASSERT_EQ(session_.InsertPacket(packet_, frame_buffer_, false, 0), + kPacketBufferSize); + + packet_.isFirstPacket = true; + packet_.completeNALU = kNaluComplete; + packet_.seqNum -= 1; + packet_.markerBit = false; + FillPacket(0); + ASSERT_EQ(session_.InsertPacket(packet_, frame_buffer_, false, 0), + kPacketBufferSize); + + packet_.isFirstPacket = false; + packet_.completeNALU = kNaluEnd; + packet_.seqNum += 2; + packet_.markerBit = true; + FillPacket(2); + ASSERT_EQ(session_.InsertPacket(packet_, frame_buffer_, false, 0), + kPacketBufferSize); + + EXPECT_EQ(0, session_.MakeDecodable()); + EXPECT_EQ(0, session_.packets_not_decodable()); + EXPECT_EQ(3*kPacketBufferSize, session_.SessionLength()); + SCOPED_TRACE("Calling VerifyNalu"); + EXPECT_TRUE(VerifyNalu(0, 1, 0)); +} + +TEST_F(TestNalUnits, WrapLosses) { + packet_.seqNum = 0xFFFF; + packet_.isFirstPacket = false; + packet_.completeNALU = kNaluIncomplete; + packet_.markerBit = false; + FillPacket(1); + ASSERT_EQ(session_.InsertPacket(packet_, frame_buffer_, false, 0), + kPacketBufferSize); + + packet_.isFirstPacket = false; + packet_.completeNALU = kNaluEnd; + packet_.seqNum += 2; + packet_.markerBit = true; + FillPacket(2); + ASSERT_EQ(session_.InsertPacket(packet_, frame_buffer_, false, 0), + kPacketBufferSize); + + EXPECT_EQ(2 * kPacketBufferSize, session_.MakeDecodable()); + EXPECT_EQ(0, session_.SessionLength()); + EXPECT_EQ(2, session_.packets_not_decodable()); +} + +TEST_F(TestNalUnits, ReorderWrapLosses) { + packet_.seqNum = 0xFFFF; + + packet_.isFirstPacket = false; + packet_.completeNALU = kNaluEnd; + packet_.seqNum += 2; + packet_.markerBit = true; + FillPacket(2); + ASSERT_EQ(session_.InsertPacket(packet_, frame_buffer_, false, 0), + kPacketBufferSize); + + packet_.seqNum -= 2; + packet_.isFirstPacket = false; + packet_.completeNALU = kNaluIncomplete; + packet_.markerBit = false; + FillPacket(1); + ASSERT_EQ(session_.InsertPacket(packet_, frame_buffer_, false, 0), + kPacketBufferSize); + + EXPECT_EQ(2 * kPacketBufferSize, session_.MakeDecodable()); + EXPECT_EQ(0, session_.SessionLength()); + EXPECT_EQ(2, session_.packets_not_decodable()); +} + +TEST_F(TestZeroOutSeqNum, NoLosses) { + uint16_t low = 0xFFFF - 5; + + packet_.seqNum = low; + packet_.isFirstPacket = true; + packet_.markerBit = false; + FillPacket(0); + ASSERT_EQ(session_.InsertPacket(packet_, frame_buffer_, false, 0), + kPacketBufferSize); + + for (int i = 1; i < 9; ++i) { + packet_.seqNum += 1; + packet_.isFirstPacket = false; + packet_.markerBit = false; + FillPacket(i + 1); + ASSERT_EQ(session_.InsertPacket(packet_, frame_buffer_, false, 0), + kPacketBufferSize); + } + + packet_.seqNum += 1; + packet_.isFirstPacket = false; + packet_.markerBit = true; + FillPacket(10); + ASSERT_EQ(session_.InsertPacket(packet_, frame_buffer_, false, 0), + kPacketBufferSize); + + EXPECT_EQ(10 * kPacketBufferSize, session_.SessionLength()); + BuildSeqNumList(low, packet_.seqNum); + EXPECT_EQ(0, session_.ZeroOutSeqNum(seq_num_list_, seq_num_list_length_)); + EXPECT_EQ(false, session_.session_nack()); + SCOPED_TRACE("Calling VerifyAll"); + VerifyAll(-1); + + BuildSeqNumList(low, packet_.seqNum); + EXPECT_EQ(0, session_.ZeroOutSeqNumHybrid(seq_num_list_, + seq_num_list_length_, + 60)); + SCOPED_TRACE("Calling VerifyAll"); + VerifyAll(-1); +} + +TEST_F(TestZeroOutSeqNum, FiveLossesSpreadOut) { + uint16_t low = 0xFFFF - 5; + + packet_.seqNum = low; + packet_.isFirstPacket = false; + packet_.markerBit = true; + FillPacket(0); + ASSERT_EQ(session_.InsertPacket(packet_, frame_buffer_, false, 0), + kPacketBufferSize); + + for (int i = 1; i < 9; ++i) { + packet_.seqNum += 1; + packet_.isFirstPacket = false; + packet_.markerBit = false; + FillPacket(i); + if ((i + 1) % 2) + ASSERT_EQ(session_.InsertPacket(packet_, frame_buffer_, false, 0), + kPacketBufferSize); + } + + packet_.seqNum++; // Simulate loss of last packet. + + EXPECT_EQ(5 * kPacketBufferSize, session_.SessionLength()); + BuildSeqNumList(low, packet_.seqNum); + EXPECT_EQ(0, session_.ZeroOutSeqNum(seq_num_list_, seq_num_list_length_)); + for (int i = 0; i < seq_num_list_length_; ++i) { + if (i % 2) + EXPECT_EQ(static_cast(low + i), seq_num_list_[i]); + else + EXPECT_EQ(-1, seq_num_list_[i]); + } + + BuildSeqNumList(low, packet_.seqNum); + EXPECT_EQ(0, session_.ZeroOutSeqNumHybrid(seq_num_list_, + seq_num_list_length_, + 60)); + EXPECT_EQ(true, session_.session_nack()); + for (int i = 0; i < seq_num_list_length_; ++i) { + if (i % 2) + EXPECT_EQ(static_cast(low + i), seq_num_list_[i]); + else + EXPECT_EQ(-1, seq_num_list_[i]); + } +} + +TEST_F(TestZeroOutSeqNum, FirstAndLastLost) { + uint16_t low = 0xFFFF; + + packet_.seqNum = low + 1; + packet_.isFirstPacket = false; + packet_.markerBit = false; + FillPacket(0); + ASSERT_EQ(session_.InsertPacket(packet_, frame_buffer_, false, 0), + kPacketBufferSize); + + EXPECT_EQ(kPacketBufferSize, session_.SessionLength()); + BuildSeqNumList(low, packet_.seqNum + 1); + EXPECT_EQ(0, session_.ZeroOutSeqNum(seq_num_list_, seq_num_list_length_)); + EXPECT_EQ(0xFFFF, seq_num_list_[0]); + EXPECT_EQ(-1, seq_num_list_[1]); + EXPECT_EQ(1, seq_num_list_[2]); + + BuildSeqNumList(low, packet_.seqNum + 1); + EXPECT_EQ(0, session_.ZeroOutSeqNumHybrid(seq_num_list_, + seq_num_list_length_, + 60)); + EXPECT_EQ(true, session_.session_nack()); + EXPECT_EQ(0xFFFF, seq_num_list_[0]); + EXPECT_EQ(-1, seq_num_list_[1]); + EXPECT_EQ(1, seq_num_list_[2]); +} + +TEST_F(TestSessionInfo, PacketPriorBitsPacketLost) { + packet_.seqNum = 0; + packet_.codecSpecificHeader.codec = kRTPVideoH263; + packet_.bits = true; + packet_.isFirstPacket = false; + packet_.markerBit = true; + FillPacket(1); + ASSERT_EQ(session_.InsertPacket(packet_, frame_buffer_, false, 0), + kPacketBufferSize); + + EXPECT_EQ(0, session_.PrepareForDecode(frame_buffer_)); + EXPECT_EQ(0, session_.SessionLength()); + EXPECT_EQ(1, session_.packets_not_decodable()); +} + +TEST_F(TestSessionInfo, MiddlePacketPriorBitsPacketLost) { + packet_.codecSpecificHeader.codec = kRTPVideoH263; + packet_.bits = false; + packet_.isFirstPacket = true; + packet_.seqNum = 0; + packet_.markerBit = false; + FillPacket(2); + ASSERT_EQ(session_.InsertPacket(packet_, frame_buffer_, false, 0), + kPacketBufferSize); + + packet_.bits = true; + packet_.isFirstPacket = false; + packet_.seqNum += 2; + packet_.markerBit = true; + FillPacket(2); + ASSERT_EQ(session_.InsertPacket(packet_, frame_buffer_, false, 0), + kPacketBufferSize); + + EXPECT_EQ(2 * kPacketBufferSize, session_.PrepareForDecode(frame_buffer_)); + EXPECT_EQ(2 * kPacketBufferSize, session_.SessionLength()); + EXPECT_EQ(1, session_.packets_not_decodable()); +} + +TEST_F(TestSessionInfo, ORingReorderedBitsPackets) { + const uint8_t kEndByte = 0x07; + const uint8_t kStartByte = 0xF8; + packet_.codecSpecificHeader.codec = kRTPVideoH263; + packet_.bits = true; + packet_.isFirstPacket = false; + packet_.seqNum = 1; + packet_.markerBit = true; + FillPacket(2); + packet_buffer_[0] = kStartByte; + ASSERT_EQ(session_.InsertPacket(packet_, frame_buffer_, false, 0), + kPacketBufferSize); + + packet_.bits = false; + packet_.isFirstPacket = true; + packet_.seqNum = 0; + packet_.markerBit = false; + FillPacket(1); + packet_buffer_[kPacketBufferSize - 1] = kEndByte; + ASSERT_EQ(session_.InsertPacket(packet_, frame_buffer_, false, 0), + kPacketBufferSize); + + EXPECT_EQ(2 * kPacketBufferSize - 1, + session_.PrepareForDecode(frame_buffer_)); + EXPECT_EQ(2 * kPacketBufferSize - 1, session_.SessionLength()); + EXPECT_EQ(kStartByte | kEndByte, frame_buffer_[kPacketBufferSize - 1]); + EXPECT_EQ(0, session_.packets_not_decodable()); +} + +} // namespace webrtc diff --git a/src/modules/video_coding/main/test/jitter_buffer_test.cc b/src/modules/video_coding/main/test/jitter_buffer_test.cc index 601302e48..49365d46f 100644 --- a/src/modules/video_coding/main/test/jitter_buffer_test.cc +++ b/src/modules/video_coding/main/test/jitter_buffer_test.cc @@ -1868,7 +1868,7 @@ int JitterBufferTest(CmdArgs& args) TEST(incomingFrameType == kVideoFrameDelta); loop++; - } while (loop < kMaxPacketsInJitterBuffer); + } while (loop < kMaxPacketsInSession); // Max number of packets inserted