OLD | NEW |
1 /* | 1 /* |
2 * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. | 2 * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. |
3 * | 3 * |
4 * Use of this source code is governed by a BSD-style license | 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 | 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 | 6 * tree. An additional intellectual property rights grant can be found |
7 * in the file PATENTS. All contributing project authors may | 7 * in the file PATENTS. All contributing project authors may |
8 * be found in the AUTHORS file in the root of the source tree. | 8 * be found in the AUTHORS file in the root of the source tree. |
9 */ | 9 */ |
10 | 10 |
11 #include "webrtc/modules/video_coding/packet_buffer.h" | 11 #include "webrtc/modules/video_coding/packet_buffer.h" |
12 | 12 |
13 #include <algorithm> | 13 #include <algorithm> |
14 #include <limits> | 14 #include <limits> |
15 | 15 |
16 #include "webrtc/base/checks.h" | 16 #include "webrtc/base/checks.h" |
| 17 #include "webrtc/base/logging.h" |
17 #include "webrtc/modules/video_coding/frame_object.h" | 18 #include "webrtc/modules/video_coding/frame_object.h" |
18 | 19 |
19 namespace webrtc { | 20 namespace webrtc { |
20 namespace video_coding { | 21 namespace video_coding { |
21 | 22 |
22 PacketBuffer::PacketBuffer(size_t start_buffer_size, | 23 PacketBuffer::PacketBuffer(size_t start_buffer_size, |
23 size_t max_buffer_size, | 24 size_t max_buffer_size, |
24 OnCompleteFrameCallback* frame_callback) | 25 OnCompleteFrameCallback* frame_callback) |
25 : size_(start_buffer_size), | 26 : size_(start_buffer_size), |
26 max_size_(max_buffer_size), | 27 max_size_(max_buffer_size), |
27 first_seq_num_(0), | 28 first_seq_num_(0), |
28 last_seq_num_(0), | 29 last_seq_num_(0), |
29 first_packet_received_(false), | 30 first_packet_received_(false), |
30 data_buffer_(start_buffer_size), | 31 data_buffer_(start_buffer_size), |
31 sequence_buffer_(start_buffer_size), | 32 sequence_buffer_(start_buffer_size), |
32 frame_callback_(frame_callback), | 33 frame_callback_(frame_callback), |
33 last_picture_id_(-1), | 34 last_picture_id_(-1), |
34 last_unwrap_(-1) { | 35 last_unwrap_(-1), |
| 36 current_ss_idx_(0) { |
35 RTC_DCHECK_LE(start_buffer_size, max_buffer_size); | 37 RTC_DCHECK_LE(start_buffer_size, max_buffer_size); |
36 // Buffer size must always be a power of 2. | 38 // Buffer size must always be a power of 2. |
37 RTC_DCHECK((start_buffer_size & (start_buffer_size - 1)) == 0); | 39 RTC_DCHECK((start_buffer_size & (start_buffer_size - 1)) == 0); |
38 RTC_DCHECK((max_buffer_size & (max_buffer_size - 1)) == 0); | 40 RTC_DCHECK((max_buffer_size & (max_buffer_size - 1)) == 0); |
39 } | 41 } |
40 | 42 |
41 bool PacketBuffer::InsertPacket(const VCMPacket& packet) { | 43 bool PacketBuffer::InsertPacket(const VCMPacket& packet) { |
42 rtc::CritScope lock(&crit_); | 44 rtc::CritScope lock(&crit_); |
43 uint16_t seq_num = packet.seqNum; | 45 uint16_t seq_num = packet.seqNum; |
44 size_t index = seq_num % size_; | 46 size_t index = seq_num % size_; |
(...skipping 164 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
209 switch (codec_type) { | 211 switch (codec_type) { |
210 case kVideoCodecULPFEC: | 212 case kVideoCodecULPFEC: |
211 case kVideoCodecRED: | 213 case kVideoCodecRED: |
212 case kVideoCodecUnknown: | 214 case kVideoCodecUnknown: |
213 RTC_NOTREACHED(); | 215 RTC_NOTREACHED(); |
214 break; | 216 break; |
215 case kVideoCodecVP8: | 217 case kVideoCodecVP8: |
216 ManageFrameVp8(std::move(frame)); | 218 ManageFrameVp8(std::move(frame)); |
217 break; | 219 break; |
218 case kVideoCodecVP9: | 220 case kVideoCodecVP9: |
219 // TODO(philipel): ManageFrameVp9(std::move(frame)); | 221 ManageFrameVp9(std::move(frame)); |
220 break; | 222 break; |
221 case kVideoCodecH264: | 223 case kVideoCodecH264: |
222 case kVideoCodecI420: | 224 case kVideoCodecI420: |
223 case kVideoCodecGeneric: | 225 case kVideoCodecGeneric: |
224 ManageFrameGeneric(std::move(frame)); | 226 ManageFrameGeneric(std::move(frame)); |
225 break; | 227 break; |
226 } | 228 } |
227 } | 229 } |
228 | 230 |
229 void PacketBuffer::RetryStashedFrames() { | 231 void PacketBuffer::RetryStashedFrames() { |
(...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
309 frame->picture_id = codec_header.pictureId % kPicIdLength; | 311 frame->picture_id = codec_header.pictureId % kPicIdLength; |
310 | 312 |
311 if (last_unwrap_ == -1) | 313 if (last_unwrap_ == -1) |
312 last_unwrap_ = codec_header.pictureId; | 314 last_unwrap_ = codec_header.pictureId; |
313 | 315 |
314 if (last_picture_id_ == -1) | 316 if (last_picture_id_ == -1) |
315 last_picture_id_ = frame->picture_id; | 317 last_picture_id_ = frame->picture_id; |
316 | 318 |
317 // Find if there has been a gap in fully received frames and save the picture | 319 // Find if there has been a gap in fully received frames and save the picture |
318 // id of those frames in |not_yet_received_frames_|. | 320 // id of those frames in |not_yet_received_frames_|. |
319 if (AheadOf<uint8_t, kPicIdLength>(frame->picture_id, last_picture_id_)) { | 321 if (AheadOf<uint16_t, kPicIdLength>(frame->picture_id, last_picture_id_)) { |
320 last_picture_id_ = Add<kPicIdLength>(last_picture_id_, 1); | 322 last_picture_id_ = Add<kPicIdLength>(last_picture_id_, 1); |
321 while (last_picture_id_ != frame->picture_id) { | 323 while (last_picture_id_ != frame->picture_id) { |
322 not_yet_received_frames_.insert(last_picture_id_); | 324 not_yet_received_frames_.insert(last_picture_id_); |
323 last_picture_id_ = Add<kPicIdLength>(last_picture_id_, 1); | 325 last_picture_id_ = Add<kPicIdLength>(last_picture_id_, 1); |
324 } | 326 } |
325 } | 327 } |
326 | 328 |
327 // Clean up info for base layers that are too old. | 329 // Clean up info for base layers that are too old. |
328 uint8_t old_tl0_pic_idx = codec_header.tl0PicIdx - kMaxLayerInfo; | 330 uint8_t old_tl0_pic_idx = codec_header.tl0PicIdx - kMaxLayerInfo; |
329 auto clean_layer_info_to = layer_info_.lower_bound(old_tl0_pic_idx); | 331 auto clean_layer_info_to = layer_info_.lower_bound(old_tl0_pic_idx); |
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
379 // Find all references for this frame. | 381 // Find all references for this frame. |
380 frame->num_references = 0; | 382 frame->num_references = 0; |
381 for (uint8_t layer = 0; layer <= codec_header.temporalIdx; ++layer) { | 383 for (uint8_t layer = 0; layer <= codec_header.temporalIdx; ++layer) { |
382 RTC_DCHECK_NE(-1, layer_info_it->second[layer]); | 384 RTC_DCHECK_NE(-1, layer_info_it->second[layer]); |
383 | 385 |
384 // If we have not yet received a frame between this frame and the referenced | 386 // If we have not yet received a frame between this frame and the referenced |
385 // frame then we have to wait for that frame to be completed first. | 387 // frame then we have to wait for that frame to be completed first. |
386 auto not_received_frame_it = | 388 auto not_received_frame_it = |
387 not_yet_received_frames_.upper_bound(layer_info_it->second[layer]); | 389 not_yet_received_frames_.upper_bound(layer_info_it->second[layer]); |
388 if (not_received_frame_it != not_yet_received_frames_.end() && | 390 if (not_received_frame_it != not_yet_received_frames_.end() && |
389 AheadOf<uint8_t, kPicIdLength>(frame->picture_id, | 391 AheadOf<uint16_t, kPicIdLength>(frame->picture_id, |
390 *not_received_frame_it)) { | 392 *not_received_frame_it)) { |
391 stashed_frames_.emplace(std::move(frame)); | 393 stashed_frames_.emplace(std::move(frame)); |
392 return; | 394 return; |
393 } | 395 } |
394 | 396 |
395 ++frame->num_references; | 397 ++frame->num_references; |
396 frame->references[layer] = layer_info_it->second[layer]; | 398 frame->references[layer] = layer_info_it->second[layer]; |
397 } | 399 } |
398 | 400 |
399 CompletedFrameVp8(std::move(frame)); | 401 CompletedFrameVp8(std::move(frame)); |
400 } | 402 } |
401 | 403 |
402 void PacketBuffer::CompletedFrameVp8(std::unique_ptr<RtpFrameObject> frame) { | 404 void PacketBuffer::CompletedFrameVp8(std::unique_ptr<RtpFrameObject> frame) { |
(...skipping 15 matching lines...) Expand all Loading... |
418 // update. | 420 // update. |
419 break; | 421 break; |
420 } | 422 } |
421 | 423 |
422 layer_info_it->second[codec_header.temporalIdx] = frame->picture_id; | 424 layer_info_it->second[codec_header.temporalIdx] = frame->picture_id; |
423 ++tl0_pic_idx; | 425 ++tl0_pic_idx; |
424 layer_info_it = layer_info_.find(tl0_pic_idx); | 426 layer_info_it = layer_info_.find(tl0_pic_idx); |
425 } | 427 } |
426 not_yet_received_frames_.erase(frame->picture_id); | 428 not_yet_received_frames_.erase(frame->picture_id); |
427 | 429 |
428 for (size_t r = 0; r < frame->num_references; ++r) | 430 for (size_t i = 0; i < frame->num_references; ++i) |
429 frame->references[r] = UnwrapPictureId(frame->references[r]); | 431 frame->references[i] = UnwrapPictureId(frame->references[i]); |
430 frame->picture_id = UnwrapPictureId(frame->picture_id); | 432 frame->picture_id = UnwrapPictureId(frame->picture_id); |
431 | 433 |
432 frame_callback_->OnCompleteFrame(std::move(frame)); | 434 frame_callback_->OnCompleteFrame(std::move(frame)); |
433 RetryStashedFrames(); | 435 RetryStashedFrames(); |
434 } | 436 } |
435 | 437 |
| 438 void PacketBuffer::ManageFrameVp9(std::unique_ptr<RtpFrameObject> frame) { |
| 439 size_t index = frame->first_seq_num() % size_; |
| 440 const VCMPacket& packet = data_buffer_[index]; |
| 441 const RTPVideoHeaderVP9& codec_header = |
| 442 packet.codecSpecificHeader.codecHeader.VP9; |
| 443 |
| 444 if (codec_header.picture_id == kNoPictureId) { |
| 445 ManageFrameGeneric(std::move(frame)); |
| 446 return; |
| 447 } |
| 448 |
| 449 frame->spatial_layer = codec_header.spatial_idx; |
| 450 frame->inter_layer_predicted = codec_header.inter_layer_predicted; |
| 451 frame->picture_id = codec_header.picture_id % kPicIdLength; |
| 452 |
| 453 if (last_unwrap_ == -1) |
| 454 last_unwrap_ = codec_header.picture_id; |
| 455 |
| 456 if (last_picture_id_ == -1) |
| 457 last_picture_id_ = frame->picture_id; |
| 458 |
| 459 if (codec_header.flexible_mode) { |
| 460 frame->num_references = codec_header.num_ref_pics; |
| 461 for (size_t i = 0; i < frame->num_references; ++i) { |
| 462 frame->references[i] = |
| 463 Subtract<1 << 16>(frame->picture_id, codec_header.pid_diff[i]); |
| 464 } |
| 465 |
| 466 CompletedFrameVp9(std::move(frame)); |
| 467 return; |
| 468 } |
| 469 |
| 470 if (codec_header.ss_data_available) { |
| 471 // Scalability structures can only be sent with tl0 frames. |
| 472 if (codec_header.temporal_idx != 0) { |
| 473 LOG(LS_WARNING) << "Received scalability structure on a non base layer" |
| 474 " frame. Scalability structure ignored."; |
| 475 } else { |
| 476 current_ss_idx_ = Add<kMaxGofSaved>(current_ss_idx_, 1); |
| 477 scalability_structures_[current_ss_idx_] = codec_header.gof; |
| 478 scalability_structures_[current_ss_idx_].pid_start = frame->picture_id; |
| 479 |
| 480 auto pid_and_gof = std::make_pair( |
| 481 frame->picture_id, &scalability_structures_[current_ss_idx_]); |
| 482 gof_info_.insert(std::make_pair(codec_header.tl0_pic_idx, pid_and_gof)); |
| 483 } |
| 484 } |
| 485 |
| 486 // Clean up info for base layers that are too old. |
| 487 uint8_t old_tl0_pic_idx = codec_header.tl0_pic_idx - kMaxGofSaved; |
| 488 auto clean_gof_info_to = gof_info_.lower_bound(old_tl0_pic_idx); |
| 489 gof_info_.erase(gof_info_.begin(), clean_gof_info_to); |
| 490 |
| 491 if (packet.frameType == kVideoFrameKey) { |
| 492 // When using GOF all keyframes must include the scalability structure. |
| 493 if (!codec_header.ss_data_available) |
| 494 LOG(LS_WARNING) << "Received keyframe without scalability structure"; |
| 495 |
| 496 frame->num_references = 0; |
| 497 GofInfoVP9* gof = gof_info_.find(codec_header.tl0_pic_idx)->second.second; |
| 498 FrameReceivedVp9(frame->picture_id, *gof); |
| 499 CompletedFrameVp9(std::move(frame)); |
| 500 return; |
| 501 } |
| 502 |
| 503 auto gof_info_it = gof_info_.find( |
| 504 (codec_header.temporal_idx == 0 && !codec_header.ss_data_available) |
| 505 ? codec_header.tl0_pic_idx - 1 |
| 506 : codec_header.tl0_pic_idx); |
| 507 |
| 508 // Gof info for this frame is not available yet, stash this frame. |
| 509 if (gof_info_it == gof_info_.end()) { |
| 510 stashed_frames_.emplace(std::move(frame)); |
| 511 return; |
| 512 } |
| 513 |
| 514 GofInfoVP9* gof = gof_info_it->second.second; |
| 515 uint16_t picture_id_tl0 = gof_info_it->second.first; |
| 516 |
| 517 FrameReceivedVp9(frame->picture_id, *gof); |
| 518 |
| 519 // Make sure we don't miss any frame that could potentially have the |
| 520 // up switch flag set. |
| 521 if (MissingRequiredFrameVp9(frame->picture_id, *gof)) { |
| 522 stashed_frames_.emplace(std::move(frame)); |
| 523 return; |
| 524 } |
| 525 |
| 526 if (codec_header.temporal_up_switch) { |
| 527 auto pid_tidx = |
| 528 std::make_pair(frame->picture_id, codec_header.temporal_idx); |
| 529 up_switch_.insert(pid_tidx); |
| 530 } |
| 531 |
| 532 // If this is a base layer frame that contains a scalability structure |
| 533 // then gof info has already been inserted earlier, so we only want to |
| 534 // insert if we haven't done so already. |
| 535 if (codec_header.temporal_idx == 0 && !codec_header.ss_data_available) { |
| 536 auto pid_and_gof = std::make_pair(frame->picture_id, gof); |
| 537 gof_info_.insert(std::make_pair(codec_header.tl0_pic_idx, pid_and_gof)); |
| 538 } |
| 539 |
| 540 // Clean out old info about up switch frames. |
| 541 uint16_t old_picture_id = Subtract<kPicIdLength>(last_picture_id_, 50); |
| 542 auto up_switch_erase_to = up_switch_.lower_bound(old_picture_id); |
| 543 up_switch_.erase(up_switch_.begin(), up_switch_erase_to); |
| 544 |
| 545 RTC_DCHECK( |
| 546 (AheadOrAt<uint16_t, kPicIdLength>(frame->picture_id, picture_id_tl0))); |
| 547 |
| 548 size_t diff = |
| 549 ForwardDiff<uint16_t, kPicIdLength>(gof->pid_start, frame->picture_id); |
| 550 size_t gof_idx = diff % gof->num_frames_in_gof; |
| 551 |
| 552 // Populate references according to the scalability structure. |
| 553 frame->num_references = gof->num_ref_pics[gof_idx]; |
| 554 for (size_t i = 0; i < frame->num_references; ++i) { |
| 555 frame->references[i] = |
| 556 Subtract<kPicIdLength>(frame->picture_id, gof->pid_diff[gof_idx][i]); |
| 557 |
| 558 // If this is a reference to a frame earlier than the last up switch point, |
| 559 // then ignore this reference. |
| 560 if (UpSwitchInIntervalVp9(frame->picture_id, codec_header.temporal_idx, |
| 561 frame->references[i])) { |
| 562 --frame->num_references; |
| 563 } |
| 564 } |
| 565 |
| 566 CompletedFrameVp9(std::move(frame)); |
| 567 } |
| 568 |
| 569 bool PacketBuffer::MissingRequiredFrameVp9(uint16_t picture_id, |
| 570 const GofInfoVP9& gof) { |
| 571 size_t diff = ForwardDiff<uint16_t, kPicIdLength>(gof.pid_start, picture_id); |
| 572 size_t gof_idx = diff % gof.num_frames_in_gof; |
| 573 size_t temporal_idx = gof.temporal_idx[gof_idx]; |
| 574 |
| 575 // For every reference this frame has, check if there is a frame missing in |
| 576 // the interval (|ref_pid|, |picture_id|) in any of the lower temporal |
| 577 // layers. If so, we are missing a required frame. |
| 578 uint8_t num_references = gof.num_ref_pics[gof_idx]; |
| 579 for (size_t i = 0; i < num_references; ++i) { |
| 580 uint16_t ref_pid = |
| 581 Subtract<kPicIdLength>(picture_id, gof.pid_diff[gof_idx][i]); |
| 582 for (size_t l = 0; l < temporal_idx; ++l) { |
| 583 auto missing_frame_it = missing_frames_for_layer_[l].lower_bound(ref_pid); |
| 584 if (missing_frame_it != missing_frames_for_layer_[l].end() && |
| 585 AheadOf<uint16_t, kPicIdLength>(picture_id, *missing_frame_it)) { |
| 586 return true; |
| 587 } |
| 588 } |
| 589 } |
| 590 return false; |
| 591 } |
| 592 |
| 593 void PacketBuffer::FrameReceivedVp9(uint16_t picture_id, |
| 594 const GofInfoVP9& gof) { |
| 595 RTC_DCHECK_NE(-1, last_picture_id_); |
| 596 |
| 597 // If there is a gap, find which temporal layer the missing frames |
| 598 // belong to and add the frame as missing for that temporal layer. |
| 599 // Otherwise, remove this frame from the set of missing frames. |
| 600 if (AheadOf<uint16_t, kPicIdLength>(picture_id, last_picture_id_)) { |
| 601 size_t diff = |
| 602 ForwardDiff<uint16_t, kPicIdLength>(gof.pid_start, last_picture_id_); |
| 603 size_t gof_idx = diff % gof.num_frames_in_gof; |
| 604 |
| 605 last_picture_id_ = Add<kPicIdLength>(last_picture_id_, 1); |
| 606 while (last_picture_id_ != picture_id) { |
| 607 ++gof_idx; |
| 608 RTC_DCHECK_NE(0ul, gof_idx % gof.num_frames_in_gof); |
| 609 size_t temporal_idx = gof.temporal_idx[gof_idx]; |
| 610 missing_frames_for_layer_[temporal_idx].insert(last_picture_id_); |
| 611 last_picture_id_ = Add<kPicIdLength>(last_picture_id_, 1); |
| 612 } |
| 613 } else { |
| 614 size_t diff = |
| 615 ForwardDiff<uint16_t, kPicIdLength>(gof.pid_start, picture_id); |
| 616 size_t gof_idx = diff % gof.num_frames_in_gof; |
| 617 size_t temporal_idx = gof.temporal_idx[gof_idx]; |
| 618 missing_frames_for_layer_[temporal_idx].erase(picture_id); |
| 619 } |
| 620 } |
| 621 |
| 622 bool PacketBuffer::UpSwitchInIntervalVp9(uint16_t picture_id, |
| 623 uint8_t temporal_idx, |
| 624 uint16_t pid_ref) { |
| 625 for (auto up_switch_it = up_switch_.upper_bound(pid_ref); |
| 626 up_switch_it != up_switch_.end() && |
| 627 AheadOf<uint16_t, kPicIdLength>(picture_id, up_switch_it->first); |
| 628 ++up_switch_it) { |
| 629 if (up_switch_it->second < temporal_idx) |
| 630 return true; |
| 631 } |
| 632 |
| 633 return false; |
| 634 } |
| 635 |
| 636 void PacketBuffer::CompletedFrameVp9(std::unique_ptr<RtpFrameObject> frame) { |
| 637 for (size_t i = 0; i < frame->num_references; ++i) |
| 638 frame->references[i] = UnwrapPictureId(frame->references[i]); |
| 639 frame->picture_id = UnwrapPictureId(frame->picture_id); |
| 640 |
| 641 frame_callback_->OnCompleteFrame(std::move(frame)); |
| 642 RetryStashedFrames(); |
| 643 } |
| 644 |
436 uint16_t PacketBuffer::UnwrapPictureId(uint16_t picture_id) { | 645 uint16_t PacketBuffer::UnwrapPictureId(uint16_t picture_id) { |
437 if (last_unwrap_ == -1) | 646 RTC_DCHECK_NE(-1, last_unwrap_); |
438 last_unwrap_ = picture_id; | |
439 | 647 |
440 uint16_t unwrap_truncated = last_unwrap_ % kPicIdLength; | 648 uint16_t unwrap_truncated = last_unwrap_ % kPicIdLength; |
441 uint16_t diff = MinDiff<uint8_t, kPicIdLength>(unwrap_truncated, picture_id); | 649 uint16_t diff = MinDiff<uint16_t, kPicIdLength>(unwrap_truncated, picture_id); |
442 | 650 |
443 if (AheadOf<uint8_t, kPicIdLength>(picture_id, unwrap_truncated)) | 651 if (AheadOf<uint16_t, kPicIdLength>(picture_id, unwrap_truncated)) |
444 last_unwrap_ = Add<1 << 16>(last_unwrap_, diff); | 652 last_unwrap_ = Add<1 << 16>(last_unwrap_, diff); |
445 else | 653 else |
446 last_unwrap_ = Subtract<1 << 16>(last_unwrap_, diff); | 654 last_unwrap_ = Subtract<1 << 16>(last_unwrap_, diff); |
447 | 655 |
448 return last_unwrap_; | 656 return last_unwrap_; |
449 } | 657 } |
450 | 658 |
451 void PacketBuffer::Flush() { | 659 void PacketBuffer::Flush() { |
452 rtc::CritScope lock(&crit_); | 660 rtc::CritScope lock(&crit_); |
453 for (size_t i = 0; i < size_; ++i) | 661 for (size_t i = 0; i < size_; ++i) |
454 sequence_buffer_[i].used = false; | 662 sequence_buffer_[i].used = false; |
455 | 663 |
456 last_seq_num_gop_.clear(); | 664 last_seq_num_gop_.clear(); |
457 while (!stashed_frames_.empty()) | 665 while (!stashed_frames_.empty()) |
458 stashed_frames_.pop(); | 666 stashed_frames_.pop(); |
459 not_yet_received_frames_.clear(); | 667 not_yet_received_frames_.clear(); |
460 | 668 |
461 first_packet_received_ = false; | 669 first_packet_received_ = false; |
462 } | 670 } |
463 | 671 |
464 } // namespace video_coding | 672 } // namespace video_coding |
465 } // namespace webrtc | 673 } // namespace webrtc |
OLD | NEW |