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