1 /* 2 * Copyright @ 2019 8x8, Inc 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package org.jitsi.videobridge.cc.vp8; 17 18 import org.jetbrains.annotations.*; 19 import org.jitsi.nlj.*; 20 import org.jitsi.nlj.codec.vp8.*; 21 import org.jitsi.nlj.format.*; 22 import org.jitsi.nlj.rtp.codec.vp8.*; 23 import org.jitsi.rtp.rtcp.*; 24 import org.jitsi.rtp.util.*; 25 import org.jitsi.utils.logging.*; 26 import org.jitsi.utils.logging2.Logger; 27 import org.jitsi.videobridge.cc.*; 28 import org.json.simple.*; 29 30 import java.util.*; 31 32 /** 33 * This class represents a projection of a VP8 RTP stream in the RFC 7667 sense 34 * and it is the main entry point for VP8 simulcast/svc RTP/RTCP rewriting. Read 35 * svc.md for implementation details. Instances of this class are thread-safe. 36 * 37 * @author George Politis 38 */ 39 public class VP8AdaptiveTrackProjectionContext 40 implements AdaptiveTrackProjectionContext 41 { 42 private final Logger logger; 43 44 /** 45 * A map that stores the per-encoding VP8 frame maps. 46 */ 47 private final Map<Long, VP8FrameMap> 48 vp8FrameMaps = new HashMap<>(); 49 50 /** 51 * The {@link VP8QualityFilter} instance that does quality filtering on the 52 * incoming frames. 53 */ 54 private final VP8QualityFilter vp8QualityFilter; 55 56 /** 57 * The diagnostic context of this instance. 58 */ 59 private final DiagnosticContext diagnosticContext; 60 61 /** 62 * The "last" {@link VP8FrameProjection} that this instance has accepted. 63 * In this context, last here means with the highest sequence number 64 * and not, for example, the last one received by the bridge. 65 */ 66 private VP8FrameProjection lastVP8FrameProjection; 67 68 /** 69 * The VP8 media format. No essential functionality relies on this field, 70 * it's only used as a cache of the {@link PayloadType} instance for VP8 in 71 * case we have to do a context switch (see {@link AdaptiveTrackProjection}), 72 * in order to avoid having to resolve the format. 73 */ 74 private final PayloadType payloadType; 75 76 /** 77 * Ctor. 78 * 79 * @param payloadType the VP8 media format. 80 * @param rtpState the RTP state to begin with. 81 */ VP8AdaptiveTrackProjectionContext( @otNull DiagnosticContext diagnosticContext, @NotNull PayloadType payloadType, @NotNull RtpState rtpState, @NotNull Logger parentLogger)82 public VP8AdaptiveTrackProjectionContext( 83 @NotNull DiagnosticContext diagnosticContext, 84 @NotNull PayloadType payloadType, 85 @NotNull RtpState rtpState, 86 @NotNull Logger parentLogger) 87 { 88 this.diagnosticContext = diagnosticContext; 89 this.logger = parentLogger.createChildLogger(VP8AdaptiveTrackProjectionContext.class.getName()); 90 this.payloadType = payloadType; 91 this.vp8QualityFilter = new VP8QualityFilter(parentLogger); 92 93 lastVP8FrameProjection = new VP8FrameProjection(diagnosticContext, 94 rtpState.ssrc, rtpState.maxSequenceNumber, rtpState.maxTimestamp); 95 } 96 97 /** Lookup a Vp8Frame for a packet. */ lookupVP8Frame(@otNull Vp8Packet vp8Packet)98 private VP8Frame lookupVP8Frame(@NotNull Vp8Packet vp8Packet) 99 { 100 VP8FrameMap frameMap = vp8FrameMaps.get(vp8Packet.getSsrc()); 101 if (frameMap == null) 102 return null; 103 104 return frameMap.findFrame(vp8Packet); 105 } 106 107 /** 108 * Insert a packet in the appropriate Vp8FrameMap. 109 */ insertPacketInMap( @otNull Vp8Packet vp8Packet)110 private VP8FrameMap.FrameInsertionResult insertPacketInMap( 111 @NotNull Vp8Packet vp8Packet) 112 { 113 VP8FrameMap frameMap = vp8FrameMaps.computeIfAbsent(vp8Packet.getSsrc(), 114 ssrc -> new VP8FrameMap(logger)); 115 /* TODO: add more context (ssrc?) to frame map's logger? */ 116 117 return frameMap.insertPacket(vp8Packet); 118 } 119 120 /** 121 * Find the previous frame before the given one. 122 */ 123 @Nullable prevFrame(@otNull VP8Frame frame)124 private synchronized VP8Frame prevFrame(@NotNull VP8Frame frame) 125 { 126 VP8FrameMap frameMap = vp8FrameMaps.get(frame.getSsrc()); 127 if (frameMap == null) 128 { 129 return null; 130 } 131 132 return frameMap.prevFrame(frame); 133 } 134 135 /** 136 * Find the next frame after the given one. 137 */ 138 @Nullable nextFrame(@otNull VP8Frame frame)139 private synchronized VP8Frame nextFrame(@NotNull VP8Frame frame) 140 { 141 VP8FrameMap frameMap = vp8FrameMaps.get(frame.getSsrc()); 142 if (frameMap == null) 143 { 144 return null; 145 } 146 147 return frameMap.nextFrame(frame); 148 } 149 150 /** 151 * Find the previous accepted frame before the given one. 152 */ 153 @Nullable findPrevAcceptedFrame(@otNull VP8Frame frame)154 private VP8Frame findPrevAcceptedFrame(@NotNull VP8Frame frame) 155 { 156 VP8FrameMap frameMap = vp8FrameMaps.get(frame.getSsrc()); 157 if (frameMap == null) 158 { 159 return null; 160 } 161 162 return frameMap.findPrevAcceptedFrame(frame); 163 } 164 165 /** 166 * Find the next accepted frame after the given one. 167 */ 168 @Nullable findNextAcceptedFrame(@otNull VP8Frame frame)169 private VP8Frame findNextAcceptedFrame(@NotNull VP8Frame frame) 170 { 171 VP8FrameMap frameMap = vp8FrameMaps.get(frame.getSsrc()); 172 if (frameMap == null) 173 { 174 return null; 175 } 176 177 return frameMap.findNextAcceptedFrame(frame); 178 } 179 180 /** 181 * Find a subsequent Tid==0 frame after the given frame 182 * @param frame The frame to query 183 * @return A subsequent TL0 frame, or null 184 */ 185 @Nullable findNextTl0(@otNull VP8Frame frame)186 private VP8Frame findNextTl0(@NotNull VP8Frame frame) 187 { 188 VP8FrameMap frameMap = vp8FrameMaps.get(frame.getSsrc()); 189 if (frameMap == null) 190 { 191 return null; 192 } 193 194 return frameMap.findNextTl0(frame); 195 } 196 197 /** 198 * Calculate the projected sequence number gap between two frames (of the same encoding), 199 * allowing collapsing for unaccepted frames. 200 */ seqGap(@otNull VP8Frame frame1, @NotNull VP8Frame frame2)201 private int seqGap(@NotNull VP8Frame frame1, @NotNull VP8Frame frame2) 202 { 203 int seqGap = RtpUtils.getSequenceNumberDelta(frame2.getEarliestKnownSequenceNumber(), frame1.getLatestKnownSequenceNumber()); 204 205 if (!frame1.isAccepted() && !frame2.isAccepted() && 206 frame2.isImmediatelyAfter(frame1)) 207 { 208 /* If neither frame is being projected, and they have consecutive 209 picture IDs, we don't need to leave any gap. */ 210 seqGap = 0; 211 } 212 else 213 { 214 /* If the earlier frame wasn't projected, and we haven't seen its 215 * final packet, we know it has to consume at least one more sequence number. */ 216 if (!frame1.isAccepted() && !frame1.hasSeenEndOfFrame() && seqGap > 1) 217 { 218 seqGap--; 219 } 220 /* Similarly, if the later frame wasn't projected and we haven't seen 221 * its first packet. */ 222 if (!frame2.isAccepted() && !frame2.hasSeenStartOfFrame() && seqGap > 1) 223 { 224 seqGap--; 225 } 226 if (!frame1.isAccepted() && seqGap > 0) 227 { 228 seqGap--; 229 } 230 } 231 232 return seqGap; 233 } 234 235 /** 236 * Calculate the projected picture ID gap between two frames (of the same encoding), 237 * allowing collapsing for unaccepted frames. 238 */ picGap(@otNull VP8Frame frame1, @NotNull VP8Frame frame2)239 private int picGap(@NotNull VP8Frame frame1, @NotNull VP8Frame frame2) 240 { 241 int picGap = Vp8Utils.getExtendedPictureIdDelta(frame2.getPictureId(), frame1.getPictureId()); 242 243 if (!frame1.isAccepted() && picGap > 0) 244 { 245 picGap--; 246 } 247 248 return picGap; 249 } 250 251 frameIsNewSsrc(VP8Frame frame)252 private boolean frameIsNewSsrc(VP8Frame frame) 253 { 254 return lastVP8FrameProjection.getVP8Frame() == null || 255 !frame.matchesSSRC(lastVP8FrameProjection.getVP8Frame()); 256 } 257 258 /** 259 * Determines whether a packet should be accepted or not. 260 * 261 * @param packetInfo the RTP packet to determine whether to project or not. 262 * @param incomingIndex the quality index of the incoming RTP packet 263 * @param targetIndex the target quality index we want to achieve 264 * @return true if the packet should be accepted, false otherwise. 265 */ 266 @Override accept( @otNull PacketInfo packetInfo, int incomingIndex, int targetIndex)267 public synchronized boolean accept( 268 @NotNull PacketInfo packetInfo, int incomingIndex, int targetIndex) 269 { 270 if (!(packetInfo.getPacket() instanceof Vp8Packet)) 271 { 272 logger.warn("Packet is not VP8 packet"); 273 return false; 274 } 275 Vp8Packet vp8Packet = packetInfo.packetAs(); 276 277 VP8FrameMap.FrameInsertionResult result = insertPacketInMap(vp8Packet); 278 279 if (result == null) 280 { 281 /* Very old frame, more than Vp8FrameMap.FRAME_MAP_SIZE old, 282 or something wrong with the stream. */ 283 return false; 284 } 285 286 VP8Frame frame = result.getFrame(); 287 288 if (result.isNewFrame()) 289 { 290 if (vp8Packet.isKeyframe() && frameIsNewSsrc(frame)) 291 { 292 /* If we're not currently projecting this SSRC, check if we've 293 already decided to drop a subsequent TL0 frame of this SSRC. 294 If we have, we can't turn on the encoding starting from this 295 packet, so treat this frame as though it weren't a keyframe. 296 */ 297 VP8Frame f = findNextTl0(frame); 298 if (f != null && !f.isAccepted()) 299 { 300 frame.setKeyframe(false); 301 } 302 } 303 304 long receivedMs = packetInfo.getReceivedTime(); 305 boolean accepted = vp8QualityFilter 306 .acceptFrame(frame, incomingIndex, targetIndex, receivedMs); 307 308 if (accepted) 309 { 310 accepted = checkDecodability(frame); 311 } 312 313 frame.setAccepted(accepted); 314 315 if (accepted) 316 { 317 VP8FrameProjection projection; 318 try 319 { 320 projection = createProjection(frame, vp8Packet, result.isReset(), 321 receivedMs); 322 } 323 catch (Exception e) 324 { 325 logger.warn("Failed to create frame projection", e); 326 /* Make sure we don't have an accepted frame without a projection in the map. */ 327 frame.setAccepted(false); 328 return false; 329 } 330 frame.setProjection(projection); 331 332 if (RtpUtils.isNewerSequenceNumberThan(projection.getEarliestProjectedSequence(), 333 lastVP8FrameProjection.getLatestProjectedSequence())) 334 { 335 lastVP8FrameProjection = projection; 336 } 337 } 338 } 339 340 return frame.isAccepted() && frame.getProjection().accept(vp8Packet); 341 } 342 343 /** 344 * For a frame that's been accepted by the quality filter, verify that 345 * it's decodable given the projection decisions about previous frames 346 * (in case the targetIndex has changed). 347 */ checkDecodability(@otNull VP8Frame frame)348 private boolean checkDecodability(@NotNull VP8Frame frame) 349 { 350 if (frame.isKeyframe() || frame.getTemporalLayer() <= 0) 351 { 352 /* We'll always project all TL0 or unknown-TL frames, and TL0PICIDX lets the 353 * decoder know if it's missed something, so no need to check. 354 */ 355 return true; 356 } 357 358 VP8Frame f = frame, prev; 359 360 while ((prev = prevFrame(f)) != null) 361 { 362 if (!f.isImmediatelyAfter(prev)) 363 { 364 /* If we have a gap in the projection history, we don't know 365 * what will be sent. Default to assuming it'll be decodable. 366 */ 367 return true; 368 } 369 if (prev.isKeyframe() || prev.getTemporalLayer() <= frame.getTemporalLayer()) 370 { 371 /* Assume temporal nesting - if the previous frame of a lower 372 * or equal layer was accepted, this frame is decodable, otherwise 373 * it probably isn't. 374 */ 375 return prev.isAccepted(); 376 } 377 378 f = prev; 379 } 380 /* If we ran off the beginning of our history, we don't know what was 381 * sent before; assume it'll be decodable. */ 382 return true; 383 } 384 385 /** 386 * Create a projection for this frame. 387 */ 388 @NotNull createProjection(@otNull VP8Frame frame, @NotNull Vp8Packet initialPacket, boolean isReset, long receivedMs)389 private VP8FrameProjection createProjection(@NotNull VP8Frame frame, @NotNull Vp8Packet initialPacket, boolean isReset, long receivedMs) 390 { 391 if (frameIsNewSsrc(frame)) 392 { 393 return createLayerSwitchProjection(frame, initialPacket, receivedMs); 394 } 395 396 else if (isReset) 397 { 398 return createResetProjection(frame, initialPacket, receivedMs); 399 } 400 401 return createInLayerProjection(frame, initialPacket, receivedMs); 402 } 403 404 /** 405 * Create a projection for this frame. It is the first frame sent for a layer. 406 */ 407 @NotNull createLayerSwitchProjection(@otNull VP8Frame frame, @NotNull Vp8Packet initialPacket, long receivedMs)408 private VP8FrameProjection createLayerSwitchProjection(@NotNull VP8Frame frame, @NotNull Vp8Packet initialPacket, long receivedMs) 409 { 410 assert(frame.isKeyframe()); 411 assert(initialPacket.isStartOfFrame()); 412 413 int projectedSeqGap = 1; 414 415 if (lastVP8FrameProjection.getVP8Frame() != null && 416 !lastVP8FrameProjection.getVP8Frame().hasSeenEndOfFrame()) 417 { 418 /* Leave a gap to signal to the decoder that the previously routed 419 frame was incomplete. */ 420 projectedSeqGap++; 421 422 /* Make sure subsequent packets of the previous projection won't 423 overlap the new one. (This means the gap, above, will never be 424 filled in.) 425 */ 426 lastVP8FrameProjection.close(); 427 } 428 429 int projectedSeq = RtpUtils.applySequenceNumberDelta(lastVP8FrameProjection.getLatestProjectedSequence(), projectedSeqGap); 430 431 // this is a simulcast switch. The typical incremental value = 432 // 90kHz / 30 = 90,000Hz / 30 = 3000 per frame or per 33ms 433 long tsDelta; 434 if (lastVP8FrameProjection.getCreatedMs() != 0) 435 { 436 tsDelta = 3000 * Math.max(1, (receivedMs - lastVP8FrameProjection.getCreatedMs()) / 33); 437 } 438 else 439 { 440 tsDelta = 3000; 441 } 442 long projectedTs = RtpUtils.applyTimestampDelta(lastVP8FrameProjection.getTimestamp(), tsDelta); 443 444 int picId; 445 int tl0PicIdx; 446 if (lastVP8FrameProjection.getVP8Frame() != null) 447 { 448 picId = Vp8Utils.applyExtendedPictureIdDelta(lastVP8FrameProjection.getPictureId(), 449 1); 450 tl0PicIdx = Vp8Utils.applyTl0PicIdxDelta(lastVP8FrameProjection.getTl0PICIDX(), 451 1); 452 } 453 else { 454 picId = frame.getPictureId(); 455 tl0PicIdx = frame.getTl0PICIDX(); 456 } 457 458 VP8FrameProjection projection = 459 new VP8FrameProjection(diagnosticContext, 460 frame, lastVP8FrameProjection.getSSRC(), projectedTs, 461 RtpUtils.getSequenceNumberDelta(projectedSeq, initialPacket.getSequenceNumber()), 462 picId, tl0PicIdx, receivedMs 463 ); 464 465 return projection; 466 } 467 468 /** 469 * Create a projection for this frame. It follows a large gap in the stream's projected frames. 470 */ 471 @NotNull createResetProjection(@otNull VP8Frame frame, @NotNull Vp8Packet initialPacket, long receivedMs)472 private VP8FrameProjection createResetProjection(@NotNull VP8Frame frame, 473 @NotNull Vp8Packet initialPacket, long receivedMs) 474 { 475 VP8Frame lastFrame = lastVP8FrameProjection.getVP8Frame(); 476 477 /* Apply the latest projected frame's projections out, linearly. */ 478 int seqDelta = RtpUtils.getSequenceNumberDelta(lastVP8FrameProjection.getLatestProjectedSequence(), 479 lastFrame.getLatestKnownSequenceNumber()); 480 long tsDelta = RtpUtils.getTimestampDiff(lastVP8FrameProjection.getTimestamp(), 481 lastFrame.getTimestamp()); 482 int picIdDelta = Vp8Utils.getExtendedPictureIdDelta(lastVP8FrameProjection.getPictureId(), 483 lastFrame.getPictureId()); 484 int tl0PicIdxDelta = Vp8Utils.getTl0PicIdxDelta(lastVP8FrameProjection.getTl0PICIDX(), 485 lastFrame.getTl0PICIDX()); 486 487 long projectedTs = RtpUtils.applyTimestampDelta(frame.getTimestamp(), tsDelta); 488 int projectedPicId = Vp8Utils.applyExtendedPictureIdDelta(frame.getPictureId(), picIdDelta); 489 int projectedTl0PicIdx = Vp8Utils.applyTl0PicIdxDelta(frame.getTl0PICIDX(), tl0PicIdxDelta); 490 491 VP8FrameProjection projection = 492 new VP8FrameProjection(diagnosticContext, 493 frame, lastVP8FrameProjection.getSSRC(), projectedTs, 494 seqDelta, 495 projectedPicId, projectedTl0PicIdx, receivedMs 496 ); 497 498 return projection; 499 } 500 501 /** 502 * Create a projection for this frame. It is being sent subsequent to other projected frames 503 * of this layer. 504 */ 505 @NotNull createInLayerProjection(@otNull VP8Frame frame, @NotNull VP8Frame refFrame, @NotNull Vp8Packet initialPacket, long receivedMs)506 private VP8FrameProjection createInLayerProjection(@NotNull VP8Frame frame, 507 @NotNull VP8Frame refFrame, @NotNull Vp8Packet initialPacket, 508 long receivedMs) 509 { 510 long tsGap = RtpUtils.getTimestampDiff(frame.getTimestamp(), refFrame.getTimestamp()); 511 int tl0Gap = Vp8Utils.getTl0PicIdxDelta(frame.getTl0PICIDX(), refFrame.getTl0PICIDX()); 512 int seqGap = 0; 513 int picGap = 0; 514 515 VP8Frame f1 = refFrame, f2; 516 int refSeq; 517 int picIdDelta = Vp8Utils.getExtendedPictureIdDelta(refFrame.getPictureId(), frame.getPictureId()); 518 if (picIdDelta < 0) 519 { 520 do 521 { 522 f2 = nextFrame(f1); 523 if (f2 == null) 524 { 525 throw new IllegalStateException("No next frame found after frame with picId " + f1.getPictureId() + 526 ", even though refFrame " + refFrame.getPictureId() + " is before frame " + frame.getPictureId() + "!"); 527 } 528 seqGap += seqGap(f1, f2); 529 picGap += picGap(f1, f2); 530 f1 = f2; 531 } 532 while (f2 != frame); 533 refSeq = refFrame.getProjection().getLatestProjectedSequence(); 534 } 535 else 536 { 537 do 538 { 539 f2 = prevFrame(f1); 540 if (f2 == null) 541 { 542 throw new IllegalStateException("No previous frame found before frame with picId " + f1.getPictureId() + 543 ", even though refFrame " + refFrame.getPictureId() + " is after frame " + frame.getPictureId() + "!"); 544 } 545 seqGap += -seqGap(f2, f1); 546 picGap += -picGap(f2, f1); 547 f1 = f2; 548 } 549 while (f2 != frame); 550 refSeq = refFrame.getProjection().getEarliestProjectedSequence(); 551 } 552 553 int projectedSeq = RtpUtils.applySequenceNumberDelta(refSeq, seqGap); 554 long projectedTs = RtpUtils.applyTimestampDelta(refFrame.getProjection().getTimestamp(), tsGap); 555 int projectedPicId = Vp8Utils.applyExtendedPictureIdDelta(refFrame.getProjection().getPictureId(), picGap); 556 int projectedTl0PicIdx = Vp8Utils.applyTl0PicIdxDelta(refFrame.getProjection().getTl0PICIDX(), tl0Gap); 557 558 VP8FrameProjection projection = 559 new VP8FrameProjection(diagnosticContext, 560 frame, lastVP8FrameProjection.getSSRC(), projectedTs, 561 RtpUtils.getSequenceNumberDelta(projectedSeq, initialPacket.getSequenceNumber()), 562 projectedPicId, projectedTl0PicIdx, receivedMs 563 ); 564 565 return projection; 566 } 567 568 /** 569 * Create a projection for this frame. It is being sent subsequent to other projected frames 570 * of this layer. 571 */ 572 @NotNull createInLayerProjection(@otNull VP8Frame frame, @NotNull Vp8Packet initialPacket, long receivedMs)573 private VP8FrameProjection createInLayerProjection(@NotNull VP8Frame frame, @NotNull Vp8Packet initialPacket, long receivedMs) 574 { 575 VP8Frame prevFrame = findPrevAcceptedFrame(frame); 576 if (prevFrame != null) 577 { 578 return createInLayerProjection(frame, prevFrame, initialPacket, receivedMs); 579 } 580 /* prev frame has rolled off beginning of frame map, try next frame */ 581 VP8Frame nextFrame = findNextAcceptedFrame(frame); 582 if (nextFrame != null) 583 { 584 return createInLayerProjection(frame, nextFrame, initialPacket, receivedMs); 585 } 586 587 /* Neither previous or next is found. Very big frame? Use previous projected. 588 (This must be valid because we don't execute this function unless 589 frameIsNewSsrc has returned false.) 590 */ 591 return createInLayerProjection(frame, lastVP8FrameProjection.getVP8Frame(), 592 initialPacket, receivedMs); 593 } 594 595 @Override needsKeyframe()596 public boolean needsKeyframe() 597 { 598 if (vp8QualityFilter.needsKeyframe()) 599 { 600 return true; 601 } 602 603 if (lastVP8FrameProjection.getVP8Frame() == null) 604 { 605 /* Never sent anything */ 606 return true; 607 } 608 return false; 609 } 610 611 /** 612 * Rewrites the RTCP packet that is specified as an argument. 613 * 614 * @param rtcpSrPacket the RTCP packet to transform. 615 * @return true if the RTCP packet is accepted, false otherwise, in which 616 * case it needs to be dropped. 617 */ 618 @Override rewriteRtcp(@otNull RtcpSrPacket rtcpSrPacket)619 public boolean rewriteRtcp(@NotNull RtcpSrPacket rtcpSrPacket) 620 { 621 VP8FrameProjection lastVP8FrameProjectionCopy = lastVP8FrameProjection; 622 if (lastVP8FrameProjectionCopy.getVP8Frame() == null 623 || rtcpSrPacket.getSenderSsrc() != lastVP8FrameProjectionCopy.getVP8Frame().getSsrc()) 624 { 625 return false; 626 } 627 628 rtcpSrPacket.setSenderSsrc(lastVP8FrameProjectionCopy.getSSRC()); 629 630 long srcTs = rtcpSrPacket.getSenderInfo().getRtpTimestamp(); 631 long delta = RtpUtils.getTimestampDiff( 632 lastVP8FrameProjectionCopy.getTimestamp(), 633 lastVP8FrameProjectionCopy.getVP8Frame().getTimestamp()); 634 635 long dstTs = RtpUtils.applyTimestampDelta(srcTs, delta); 636 637 if (srcTs != dstTs) 638 { 639 rtcpSrPacket.getSenderInfo().setRtpTimestamp(dstTs); 640 } 641 642 return true; 643 } 644 645 @Override getRtpState()646 public RtpState getRtpState() 647 { 648 return new RtpState( 649 lastVP8FrameProjection.getSSRC(), 650 lastVP8FrameProjection.getLatestProjectedSequence(), 651 lastVP8FrameProjection.getTimestamp()); 652 } 653 654 @Override getPayloadType()655 public PayloadType getPayloadType() 656 { 657 return payloadType; 658 } 659 660 /** 661 * Rewrites the RTP packet that is specified as an argument. 662 * 663 * @param packetInfo the packet info for the RTP packet to rewrite. 664 * @throws RewriteException if a VP8 frame projection is not found 665 * for the RTP packet that is specified as a parameter. 666 */ 667 @Override rewriteRtp( @otNull PacketInfo packetInfo)668 public void rewriteRtp( 669 @NotNull PacketInfo packetInfo) 670 throws RewriteException 671 { 672 if (!(packetInfo.getPacket() instanceof Vp8Packet)) 673 { 674 logger.info("Got a non-VP8 packet."); 675 throw new RewriteException("Non-VP8 packet in VP8 track projection"); 676 } 677 Vp8Packet vp8Packet = packetInfo.packetAs(); 678 679 if (vp8Packet.getPictureId() == -1) 680 { 681 /* Should have been rejected in accept(). */ 682 logger.info("VP8 packet does not have picture ID, cannot track in frame map."); 683 throw new RewriteException("VP8 packet without picture ID in VP8 track projeciton"); 684 } 685 686 VP8Frame vp8Frame = lookupVP8Frame(vp8Packet); 687 if (vp8Frame == null) 688 { 689 // This packet does not belong to an accepted frame. 690 // Possibly it aged off the frame map since accept was called? 691 throw new RewriteException("Frame not in tracker (aged off?)"); 692 } 693 694 if (vp8Frame.getProjection() == null) { 695 /* Shouldn't happen for an accepted packet whose frame is still known? */ 696 throw new RewriteException("Frame does not have projection?"); 697 } 698 699 vp8Frame.getProjection().rewriteRtp(vp8Packet); 700 } 701 702 /** 703 * {@inheritDoc} 704 */ 705 @Override 706 @SuppressWarnings("unchecked") getDebugState()707 public synchronized JSONObject getDebugState() 708 { 709 JSONObject debugState = new JSONObject(); 710 debugState.put( 711 "class", 712 VP8AdaptiveTrackProjectionContext.class.getSimpleName()); 713 714 JSONArray mapSizes = new JSONArray(); 715 for (Map.Entry<Long, VP8FrameMap> entry: vp8FrameMaps.entrySet()) 716 { 717 JSONObject sizeInfo = new JSONObject(); 718 sizeInfo.put("ssrc", entry.getKey()); 719 sizeInfo.put("size", entry.getValue().size()); 720 mapSizes.add(sizeInfo); 721 } 722 debugState.put( 723 "vp8FrameMaps", mapSizes); 724 debugState.put("vp8QualityFilter", vp8QualityFilter.getDebugState()); 725 debugState.put("payloadType", payloadType.toString()); 726 727 return debugState; 728 } 729 } 730