1 /*
2 * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11 #include "modules/video_coding/session_info.h"
12
13 #include <assert.h>
14 #include <string.h>
15
16 #include <vector>
17
18 #include "absl/types/variant.h"
19 #include "modules/include/module_common_types.h"
20 #include "modules/include/module_common_types_public.h"
21 #include "modules/video_coding/codecs/interface/common_constants.h"
22 #include "modules/video_coding/codecs/vp8/include/vp8_globals.h"
23 #include "modules/video_coding/jitter_buffer_common.h"
24 #include "modules/video_coding/packet.h"
25 #include "rtc_base/logging.h"
26
27 namespace webrtc {
28
29 namespace {
30
BufferToUWord16(const uint8_t * dataBuffer)31 uint16_t BufferToUWord16(const uint8_t* dataBuffer) {
32 return (dataBuffer[0] << 8) | dataBuffer[1];
33 }
34
35 } // namespace
36
VCMSessionInfo()37 VCMSessionInfo::VCMSessionInfo()
38 : complete_(false),
39 frame_type_(VideoFrameType::kVideoFrameDelta),
40 packets_(),
41 empty_seq_num_low_(-1),
42 empty_seq_num_high_(-1),
43 first_packet_seq_num_(-1),
44 last_packet_seq_num_(-1) {}
45
~VCMSessionInfo()46 VCMSessionInfo::~VCMSessionInfo() {}
47
UpdateDataPointers(const uint8_t * old_base_ptr,const uint8_t * new_base_ptr)48 void VCMSessionInfo::UpdateDataPointers(const uint8_t* old_base_ptr,
49 const uint8_t* new_base_ptr) {
50 for (PacketIterator it = packets_.begin(); it != packets_.end(); ++it)
51 if ((*it).dataPtr != NULL) {
52 assert(old_base_ptr != NULL && new_base_ptr != NULL);
53 (*it).dataPtr = new_base_ptr + ((*it).dataPtr - old_base_ptr);
54 }
55 }
56
LowSequenceNumber() const57 int VCMSessionInfo::LowSequenceNumber() const {
58 if (packets_.empty())
59 return empty_seq_num_low_;
60 return packets_.front().seqNum;
61 }
62
HighSequenceNumber() const63 int VCMSessionInfo::HighSequenceNumber() const {
64 if (packets_.empty())
65 return empty_seq_num_high_;
66 if (empty_seq_num_high_ == -1)
67 return packets_.back().seqNum;
68 return LatestSequenceNumber(packets_.back().seqNum, empty_seq_num_high_);
69 }
70
PictureId() const71 int VCMSessionInfo::PictureId() const {
72 if (packets_.empty())
73 return kNoPictureId;
74 if (packets_.front().video_header.codec == kVideoCodecVP8) {
75 return absl::get<RTPVideoHeaderVP8>(
76 packets_.front().video_header.video_type_header)
77 .pictureId;
78 } else if (packets_.front().video_header.codec == kVideoCodecVP9) {
79 return absl::get<RTPVideoHeaderVP9>(
80 packets_.front().video_header.video_type_header)
81 .picture_id;
82 } else {
83 return kNoPictureId;
84 }
85 }
86
TemporalId() const87 int VCMSessionInfo::TemporalId() const {
88 if (packets_.empty())
89 return kNoTemporalIdx;
90 if (packets_.front().video_header.codec == kVideoCodecVP8) {
91 return absl::get<RTPVideoHeaderVP8>(
92 packets_.front().video_header.video_type_header)
93 .temporalIdx;
94 } else if (packets_.front().video_header.codec == kVideoCodecVP9) {
95 return absl::get<RTPVideoHeaderVP9>(
96 packets_.front().video_header.video_type_header)
97 .temporal_idx;
98 } else {
99 return kNoTemporalIdx;
100 }
101 }
102
LayerSync() const103 bool VCMSessionInfo::LayerSync() const {
104 if (packets_.empty())
105 return false;
106 if (packets_.front().video_header.codec == kVideoCodecVP8) {
107 return absl::get<RTPVideoHeaderVP8>(
108 packets_.front().video_header.video_type_header)
109 .layerSync;
110 } else if (packets_.front().video_header.codec == kVideoCodecVP9) {
111 return absl::get<RTPVideoHeaderVP9>(
112 packets_.front().video_header.video_type_header)
113 .temporal_up_switch;
114 } else {
115 return false;
116 }
117 }
118
Tl0PicId() const119 int VCMSessionInfo::Tl0PicId() const {
120 if (packets_.empty())
121 return kNoTl0PicIdx;
122 if (packets_.front().video_header.codec == kVideoCodecVP8) {
123 return absl::get<RTPVideoHeaderVP8>(
124 packets_.front().video_header.video_type_header)
125 .tl0PicIdx;
126 } else if (packets_.front().video_header.codec == kVideoCodecVP9) {
127 return absl::get<RTPVideoHeaderVP9>(
128 packets_.front().video_header.video_type_header)
129 .tl0_pic_idx;
130 } else {
131 return kNoTl0PicIdx;
132 }
133 }
134
GetNaluInfos() const135 std::vector<NaluInfo> VCMSessionInfo::GetNaluInfos() const {
136 if (packets_.empty() ||
137 packets_.front().video_header.codec != kVideoCodecH264)
138 return std::vector<NaluInfo>();
139 std::vector<NaluInfo> nalu_infos;
140 for (const VCMPacket& packet : packets_) {
141 const auto& h264 =
142 absl::get<RTPVideoHeaderH264>(packet.video_header.video_type_header);
143 for (size_t i = 0; i < h264.nalus_length; ++i) {
144 nalu_infos.push_back(h264.nalus[i]);
145 }
146 }
147 return nalu_infos;
148 }
149 #ifndef DISABLE_H265
GetH265NaluInfos() const150 std::vector<H265NaluInfo> VCMSessionInfo::GetH265NaluInfos() const {
151 if (packets_.empty() || packets_.front().video_header.codec != kVideoCodecH265)
152 return std::vector<H265NaluInfo>();
153 std::vector<H265NaluInfo> nalu_infos;
154 for (const VCMPacket& packet : packets_) {
155 const auto& h265 =
156 absl::get<RTPVideoHeaderH265>(packet.video_header.video_type_header);
157 for (size_t i = 0; i < h265.nalus_length; ++i) {
158 nalu_infos.push_back(h265.nalus[i]);
159 }
160 }
161 return nalu_infos;
162 }
163 #endif
164
SetGofInfo(const GofInfoVP9 & gof_info,size_t idx)165 void VCMSessionInfo::SetGofInfo(const GofInfoVP9& gof_info, size_t idx) {
166 if (packets_.empty())
167 return;
168
169 auto* vp9_header = absl::get_if<RTPVideoHeaderVP9>(
170 &packets_.front().video_header.video_type_header);
171 if (!vp9_header || vp9_header->flexible_mode)
172 return;
173
174 vp9_header->temporal_idx = gof_info.temporal_idx[idx];
175 vp9_header->temporal_up_switch = gof_info.temporal_up_switch[idx];
176 vp9_header->num_ref_pics = gof_info.num_ref_pics[idx];
177 for (uint8_t i = 0; i < gof_info.num_ref_pics[idx]; ++i) {
178 vp9_header->pid_diff[i] = gof_info.pid_diff[idx][i];
179 }
180 }
181
Reset()182 void VCMSessionInfo::Reset() {
183 complete_ = false;
184 frame_type_ = VideoFrameType::kVideoFrameDelta;
185 packets_.clear();
186 empty_seq_num_low_ = -1;
187 empty_seq_num_high_ = -1;
188 first_packet_seq_num_ = -1;
189 last_packet_seq_num_ = -1;
190 }
191
SessionLength() const192 size_t VCMSessionInfo::SessionLength() const {
193 size_t length = 0;
194 for (PacketIteratorConst it = packets_.begin(); it != packets_.end(); ++it)
195 length += (*it).sizeBytes;
196 return length;
197 }
198
NumPackets() const199 int VCMSessionInfo::NumPackets() const {
200 return packets_.size();
201 }
202
InsertBuffer(uint8_t * frame_buffer,PacketIterator packet_it)203 size_t VCMSessionInfo::InsertBuffer(uint8_t* frame_buffer,
204 PacketIterator packet_it) {
205 VCMPacket& packet = *packet_it;
206 PacketIterator it;
207
208 // Calculate the offset into the frame buffer for this packet.
209 size_t offset = 0;
210 for (it = packets_.begin(); it != packet_it; ++it)
211 offset += (*it).sizeBytes;
212
213 // Set the data pointer to pointing to the start of this packet in the
214 // frame buffer.
215 const uint8_t* packet_buffer = packet.dataPtr;
216 packet.dataPtr = frame_buffer + offset;
217
218 // We handle H.264 STAP-A packets in a special way as we need to remove the
219 // two length bytes between each NAL unit, and potentially add start codes.
220 // TODO(pbos): Remove H264 parsing from this step and use a fragmentation
221 // header supplied by the H264 depacketizer.
222 const size_t kH264NALHeaderLengthInBytes = 1;
223 #ifndef DISABLE_H265
224 const size_t kH265NALHeaderLengthInBytes = 2;
225 const auto* h265 =
226 absl::get_if<RTPVideoHeaderH265>(&packet.video_header.video_type_header);
227 #endif
228 const size_t kLengthFieldLength = 2;
229 const auto* h264 =
230 absl::get_if<RTPVideoHeaderH264>(&packet.video_header.video_type_header);
231 if (h264 && h264->packetization_type == kH264StapA) {
232 size_t required_length = 0;
233 const uint8_t* nalu_ptr = packet_buffer + kH264NALHeaderLengthInBytes;
234 while (nalu_ptr < packet_buffer + packet.sizeBytes) {
235 size_t length = BufferToUWord16(nalu_ptr);
236 required_length +=
237 length + (packet.insertStartCode ? kH264StartCodeLengthBytes : 0);
238 nalu_ptr += kLengthFieldLength + length;
239 }
240 ShiftSubsequentPackets(packet_it, required_length);
241 nalu_ptr = packet_buffer + kH264NALHeaderLengthInBytes;
242 uint8_t* frame_buffer_ptr = frame_buffer + offset;
243 while (nalu_ptr < packet_buffer + packet.sizeBytes) {
244 size_t length = BufferToUWord16(nalu_ptr);
245 nalu_ptr += kLengthFieldLength;
246 frame_buffer_ptr += Insert(nalu_ptr, length, packet.insertStartCode,
247 const_cast<uint8_t*>(frame_buffer_ptr));
248 nalu_ptr += length;
249 }
250 packet.sizeBytes = required_length;
251 return packet.sizeBytes;
252 }
253 #ifndef DISABLE_H265
254 else if (h265 && h265->packetization_type == kH265AP) {
255 // Similar to H264, for H265 aggregation packets, we rely on jitter buffer
256 // to remove the two length bytes between each NAL unit, and potentially add
257 // start codes.
258 size_t required_length = 0;
259 const uint8_t* nalu_ptr =
260 packet_buffer + kH265NALHeaderLengthInBytes; // skip payloadhdr
261 while (nalu_ptr < packet_buffer + packet.sizeBytes) {
262 size_t length = BufferToUWord16(nalu_ptr);
263 required_length +=
264 length + (packet.insertStartCode ? kH265StartCodeLengthBytes : 0);
265 nalu_ptr += kLengthFieldLength + length;
266 }
267 ShiftSubsequentPackets(packet_it, required_length);
268 nalu_ptr = packet_buffer + kH265NALHeaderLengthInBytes;
269 uint8_t* frame_buffer_ptr = frame_buffer + offset;
270 while (nalu_ptr < packet_buffer + packet.sizeBytes) {
271 size_t length = BufferToUWord16(nalu_ptr);
272 nalu_ptr += kLengthFieldLength;
273 // since H265 shares the same start code as H264, use the same Insert
274 // function to handle start code.
275 frame_buffer_ptr += Insert(nalu_ptr, length, packet.insertStartCode,
276 const_cast<uint8_t*>(frame_buffer_ptr));
277 nalu_ptr += length;
278 }
279 packet.sizeBytes = required_length;
280 return packet.sizeBytes;
281 }
282 #endif
283 ShiftSubsequentPackets(
284 packet_it, packet.sizeBytes +
285 (packet.insertStartCode ? kH264StartCodeLengthBytes : 0));
286
287 packet.sizeBytes =
288 Insert(packet_buffer, packet.sizeBytes, packet.insertStartCode,
289 const_cast<uint8_t*>(packet.dataPtr));
290 return packet.sizeBytes;
291 }
292
Insert(const uint8_t * buffer,size_t length,bool insert_start_code,uint8_t * frame_buffer)293 size_t VCMSessionInfo::Insert(const uint8_t* buffer,
294 size_t length,
295 bool insert_start_code,
296 uint8_t* frame_buffer) {
297 if (insert_start_code) {
298 const unsigned char startCode[] = {0, 0, 0, 1};
299 memcpy(frame_buffer, startCode, kH264StartCodeLengthBytes);
300 }
301 memcpy(frame_buffer + (insert_start_code ? kH264StartCodeLengthBytes : 0),
302 buffer, length);
303 length += (insert_start_code ? kH264StartCodeLengthBytes : 0);
304
305 return length;
306 }
307
ShiftSubsequentPackets(PacketIterator it,int steps_to_shift)308 void VCMSessionInfo::ShiftSubsequentPackets(PacketIterator it,
309 int steps_to_shift) {
310 ++it;
311 if (it == packets_.end())
312 return;
313 uint8_t* first_packet_ptr = const_cast<uint8_t*>((*it).dataPtr);
314 int shift_length = 0;
315 // Calculate the total move length and move the data pointers in advance.
316 for (; it != packets_.end(); ++it) {
317 shift_length += (*it).sizeBytes;
318 if ((*it).dataPtr != NULL)
319 (*it).dataPtr += steps_to_shift;
320 }
321 memmove(first_packet_ptr + steps_to_shift, first_packet_ptr, shift_length);
322 }
323
UpdateCompleteSession()324 void VCMSessionInfo::UpdateCompleteSession() {
325 if (HaveFirstPacket() && HaveLastPacket()) {
326 // Do we have all the packets in this session?
327 bool complete_session = true;
328 PacketIterator it = packets_.begin();
329 PacketIterator prev_it = it;
330 ++it;
331 for (; it != packets_.end(); ++it) {
332 if (!InSequence(it, prev_it)) {
333 complete_session = false;
334 break;
335 }
336 prev_it = it;
337 }
338 complete_ = complete_session;
339 }
340 }
341
complete() const342 bool VCMSessionInfo::complete() const {
343 return complete_;
344 }
345
346 // Find the end of the NAL unit which the packet pointed to by |packet_it|
347 // belongs to. Returns an iterator to the last packet of the frame if the end
348 // of the NAL unit wasn't found.
FindNaluEnd(PacketIterator packet_it) const349 VCMSessionInfo::PacketIterator VCMSessionInfo::FindNaluEnd(
350 PacketIterator packet_it) const {
351 if ((*packet_it).completeNALU == kNaluEnd ||
352 (*packet_it).completeNALU == kNaluComplete) {
353 return packet_it;
354 }
355 // Find the end of the NAL unit.
356 for (; packet_it != packets_.end(); ++packet_it) {
357 if (((*packet_it).completeNALU == kNaluComplete &&
358 (*packet_it).sizeBytes > 0) ||
359 // Found next NALU.
360 (*packet_it).completeNALU == kNaluStart)
361 return --packet_it;
362 if ((*packet_it).completeNALU == kNaluEnd)
363 return packet_it;
364 }
365 // The end wasn't found.
366 return --packet_it;
367 }
368
DeletePacketData(PacketIterator start,PacketIterator end)369 size_t VCMSessionInfo::DeletePacketData(PacketIterator start,
370 PacketIterator end) {
371 size_t bytes_to_delete = 0; // The number of bytes to delete.
372 PacketIterator packet_after_end = end;
373 ++packet_after_end;
374
375 // Get the number of bytes to delete.
376 // Clear the size of these packets.
377 for (PacketIterator it = start; it != packet_after_end; ++it) {
378 bytes_to_delete += (*it).sizeBytes;
379 (*it).sizeBytes = 0;
380 (*it).dataPtr = NULL;
381 }
382 if (bytes_to_delete > 0)
383 ShiftSubsequentPackets(end, -static_cast<int>(bytes_to_delete));
384 return bytes_to_delete;
385 }
386
FindNextPartitionBeginning(PacketIterator it) const387 VCMSessionInfo::PacketIterator VCMSessionInfo::FindNextPartitionBeginning(
388 PacketIterator it) const {
389 while (it != packets_.end()) {
390 if (absl::get<RTPVideoHeaderVP8>((*it).video_header.video_type_header)
391 .beginningOfPartition) {
392 return it;
393 }
394 ++it;
395 }
396 return it;
397 }
398
FindPartitionEnd(PacketIterator it) const399 VCMSessionInfo::PacketIterator VCMSessionInfo::FindPartitionEnd(
400 PacketIterator it) const {
401 assert((*it).codec() == kVideoCodecVP8);
402 PacketIterator prev_it = it;
403 const int partition_id =
404 absl::get<RTPVideoHeaderVP8>((*it).video_header.video_type_header)
405 .partitionId;
406 while (it != packets_.end()) {
407 bool beginning =
408 absl::get<RTPVideoHeaderVP8>((*it).video_header.video_type_header)
409 .beginningOfPartition;
410 int current_partition_id =
411 absl::get<RTPVideoHeaderVP8>((*it).video_header.video_type_header)
412 .partitionId;
413 bool packet_loss_found = (!beginning && !InSequence(it, prev_it));
414 if (packet_loss_found ||
415 (beginning && current_partition_id != partition_id)) {
416 // Missing packet, the previous packet was the last in sequence.
417 return prev_it;
418 }
419 prev_it = it;
420 ++it;
421 }
422 return prev_it;
423 }
424
InSequence(const PacketIterator & packet_it,const PacketIterator & prev_packet_it)425 bool VCMSessionInfo::InSequence(const PacketIterator& packet_it,
426 const PacketIterator& prev_packet_it) {
427 // If the two iterators are pointing to the same packet they are considered
428 // to be in sequence.
429 return (packet_it == prev_packet_it ||
430 (static_cast<uint16_t>((*prev_packet_it).seqNum + 1) ==
431 (*packet_it).seqNum));
432 }
433
MakeDecodable()434 size_t VCMSessionInfo::MakeDecodable() {
435 size_t return_length = 0;
436 if (packets_.empty()) {
437 return 0;
438 }
439 PacketIterator it = packets_.begin();
440 // Make sure we remove the first NAL unit if it's not decodable.
441 if ((*it).completeNALU == kNaluIncomplete || (*it).completeNALU == kNaluEnd) {
442 PacketIterator nalu_end = FindNaluEnd(it);
443 return_length += DeletePacketData(it, nalu_end);
444 it = nalu_end;
445 }
446 PacketIterator prev_it = it;
447 // Take care of the rest of the NAL units.
448 for (; it != packets_.end(); ++it) {
449 bool start_of_nalu = ((*it).completeNALU == kNaluStart ||
450 (*it).completeNALU == kNaluComplete);
451 if (!start_of_nalu && !InSequence(it, prev_it)) {
452 // Found a sequence number gap due to packet loss.
453 PacketIterator nalu_end = FindNaluEnd(it);
454 return_length += DeletePacketData(it, nalu_end);
455 it = nalu_end;
456 }
457 prev_it = it;
458 }
459 return return_length;
460 }
461
HaveFirstPacket() const462 bool VCMSessionInfo::HaveFirstPacket() const {
463 return !packets_.empty() && (first_packet_seq_num_ != -1);
464 }
465
HaveLastPacket() const466 bool VCMSessionInfo::HaveLastPacket() const {
467 return !packets_.empty() && (last_packet_seq_num_ != -1);
468 }
469
InsertPacket(const VCMPacket & packet,uint8_t * frame_buffer,const FrameData & frame_data)470 int VCMSessionInfo::InsertPacket(const VCMPacket& packet,
471 uint8_t* frame_buffer,
472 const FrameData& frame_data) {
473 if (packet.video_header.frame_type == VideoFrameType::kEmptyFrame) {
474 // Update sequence number of an empty packet.
475 // Only media packets are inserted into the packet list.
476 InformOfEmptyPacket(packet.seqNum);
477 return 0;
478 }
479
480 if (packets_.size() == kMaxPacketsInSession) {
481 RTC_LOG(LS_ERROR) << "Max number of packets per frame has been reached.";
482 return -1;
483 }
484
485 // Find the position of this packet in the packet list in sequence number
486 // order and insert it. Loop over the list in reverse order.
487 ReversePacketIterator rit = packets_.rbegin();
488 for (; rit != packets_.rend(); ++rit)
489 if (LatestSequenceNumber(packet.seqNum, (*rit).seqNum) == packet.seqNum)
490 break;
491
492 // Check for duplicate packets.
493 if (rit != packets_.rend() && (*rit).seqNum == packet.seqNum &&
494 (*rit).sizeBytes > 0)
495 return -2;
496
497 if (packet.codec() == kVideoCodecH264) {
498 frame_type_ = packet.video_header.frame_type;
499 if (packet.is_first_packet_in_frame() &&
500 (first_packet_seq_num_ == -1 ||
501 IsNewerSequenceNumber(first_packet_seq_num_, packet.seqNum))) {
502 first_packet_seq_num_ = packet.seqNum;
503 }
504 if (packet.markerBit &&
505 (last_packet_seq_num_ == -1 ||
506 IsNewerSequenceNumber(packet.seqNum, last_packet_seq_num_))) {
507 last_packet_seq_num_ = packet.seqNum;
508 }
509 #ifndef DISABLE_H265
510 } else if (packet.codec() == kVideoCodecH265) {
511 frame_type_ = packet.video_header.frame_type;
512 if (packet.is_first_packet_in_frame() &&
513 (first_packet_seq_num_ == -1 ||
514 IsNewerSequenceNumber(first_packet_seq_num_, packet.seqNum))) {
515 first_packet_seq_num_ = packet.seqNum;
516 }
517 if (packet.markerBit &&
518 (last_packet_seq_num_ == -1 ||
519 IsNewerSequenceNumber(packet.seqNum, last_packet_seq_num_))) {
520 last_packet_seq_num_ = packet.seqNum;
521 }
522 #else
523 } else {
524 #endif
525 // Only insert media packets between first and last packets (when
526 // available).
527 // Placing check here, as to properly account for duplicate packets.
528 // Check if this is first packet (only valid for some codecs)
529 // Should only be set for one packet per session.
530 if (packet.is_first_packet_in_frame() && first_packet_seq_num_ == -1) {
531 // The first packet in a frame signals the frame type.
532 frame_type_ = packet.video_header.frame_type;
533 // Store the sequence number for the first packet.
534 first_packet_seq_num_ = static_cast<int>(packet.seqNum);
535 } else if (first_packet_seq_num_ != -1 &&
536 IsNewerSequenceNumber(first_packet_seq_num_, packet.seqNum)) {
537 RTC_LOG(LS_WARNING)
538 << "Received packet with a sequence number which is out "
539 "of frame boundaries";
540 return -3;
541 } else if (frame_type_ == VideoFrameType::kEmptyFrame &&
542 packet.video_header.frame_type != VideoFrameType::kEmptyFrame) {
543 // Update the frame type with the type of the first media packet.
544 // TODO(mikhal): Can this trigger?
545 frame_type_ = packet.video_header.frame_type;
546 }
547
548 // Track the marker bit, should only be set for one packet per session.
549 if (packet.markerBit && last_packet_seq_num_ == -1) {
550 last_packet_seq_num_ = static_cast<int>(packet.seqNum);
551 } else if (last_packet_seq_num_ != -1 &&
552 IsNewerSequenceNumber(packet.seqNum, last_packet_seq_num_)) {
553 RTC_LOG(LS_WARNING)
554 << "Received packet with a sequence number which is out "
555 "of frame boundaries";
556 return -3;
557 }
558 }
559
560 // The insert operation invalidates the iterator |rit|.
561 PacketIterator packet_list_it = packets_.insert(rit.base(), packet);
562
563 size_t returnLength = InsertBuffer(frame_buffer, packet_list_it);
564 UpdateCompleteSession();
565
566 return static_cast<int>(returnLength);
567 }
568
InformOfEmptyPacket(uint16_t seq_num)569 void VCMSessionInfo::InformOfEmptyPacket(uint16_t seq_num) {
570 // Empty packets may be FEC or filler packets. They are sequential and
571 // follow the data packets, therefore, we should only keep track of the high
572 // and low sequence numbers and may assume that the packets in between are
573 // empty packets belonging to the same frame (timestamp).
574 if (empty_seq_num_high_ == -1)
575 empty_seq_num_high_ = seq_num;
576 else
577 empty_seq_num_high_ = LatestSequenceNumber(seq_num, empty_seq_num_high_);
578 if (empty_seq_num_low_ == -1 ||
579 IsNewerSequenceNumber(empty_seq_num_low_, seq_num))
580 empty_seq_num_low_ = seq_num;
581 }
582
583 } // namespace webrtc
584