/** {@inheritDoc} */ @Override protected int doProcess(Buffer inBuffer, Buffer outBuffer) { byte[] inData = (byte[]) inBuffer.getData(); int inOffset = inBuffer.getOffset(); if (!VP8PayloadDescriptor.isValid(inData, inOffset)) { logger.warn("Invalid RTP/VP8 packet discarded."); outBuffer.setDiscard(true); return BUFFER_PROCESSED_FAILED; // XXX: FAILED or OK? } long inSeq = inBuffer.getSequenceNumber(); long inRtpTimestamp = inBuffer.getRtpTimeStamp(); int inPictureId = VP8PayloadDescriptor.getPictureId(inData, inOffset); boolean inMarker = (inBuffer.getFlags() & Buffer.FLAG_RTP_MARKER) != 0; boolean inIsStartOfFrame = VP8PayloadDescriptor.isStartOfFrame(inData, inOffset); int inLength = inBuffer.getLength(); int inPdSize = VP8PayloadDescriptor.getSize(inData, inOffset); int inPayloadLength = inLength - inPdSize; if (empty && lastSentSeq != -1 && seqNumComparator.compare(inSeq, lastSentSeq) != 1) { if (logger.isInfoEnabled()) logger.info("Discarding old packet (while empty) " + inSeq); outBuffer.setDiscard(true); return BUFFER_PROCESSED_OK; } if (!empty) { // if the incoming packet has a different PictureID or timestamp // than those of the current frame, then it belongs to a different // frame. if ((inPictureId != -1 && pictureId != -1 && inPictureId != pictureId) | (timestamp != -1 && inRtpTimestamp != -1 && inRtpTimestamp != timestamp)) { if (seqNumComparator.compare(inSeq, firstSeq) != 1) // inSeq <= firstSeq { // the packet belongs to a previous frame. discard it if (logger.isInfoEnabled()) logger.info("Discarding old packet " + inSeq); outBuffer.setDiscard(true); return BUFFER_PROCESSED_OK; } else // inSeq > firstSeq (and also presumably isSeq > lastSeq) { // the packet belongs to a subsequent frame (to the one // currently being held). Drop the current frame. if (logger.isInfoEnabled()) logger.info( "Discarding saved packets on arrival of" + " a packet for a subsequent frame: " + inSeq); // TODO: this would be the place to complain about the // not-well-received PictureID by sending a RTCP SLI or NACK. reinit(); } } } // a whole frame in a single packet. avoid the extra copy to // this.data and output it immediately. if (empty && inMarker && inIsStartOfFrame) { byte[] outData = validateByteArraySize(outBuffer, inPayloadLength, false); System.arraycopy(inData, inOffset + inPdSize, outData, 0, inPayloadLength); outBuffer.setOffset(0); outBuffer.setLength(inPayloadLength); outBuffer.setRtpTimeStamp(inBuffer.getRtpTimeStamp()); if (TRACE) logger.trace("Out PictureID=" + inPictureId); lastSentSeq = inSeq; return BUFFER_PROCESSED_OK; } // add to this.data Container container = free.poll(); if (container == null) container = new Container(); if (container.buf == null || container.buf.length < inPayloadLength) container.buf = new byte[inPayloadLength]; if (data.get(inSeq) != null) { if (logger.isInfoEnabled()) logger.info("(Probable) duplicate packet detected, discarding " + inSeq); outBuffer.setDiscard(true); return BUFFER_PROCESSED_OK; } System.arraycopy(inData, inOffset + inPdSize, container.buf, 0, inPayloadLength); container.len = inPayloadLength; data.put(inSeq, container); // update fields frameLength += inPayloadLength; if (firstSeq == -1 || (seqNumComparator.compare(firstSeq, inSeq) == 1)) firstSeq = inSeq; if (lastSeq == -1 || (seqNumComparator.compare(inSeq, lastSeq) == 1)) lastSeq = inSeq; if (empty) { // the first received packet for the current frame was just added empty = false; timestamp = inRtpTimestamp; pictureId = inPictureId; } if (inMarker) haveEnd = true; if (inIsStartOfFrame) haveStart = true; // check if we have a full frame if (frameComplete()) { byte[] outData = validateByteArraySize(outBuffer, frameLength, false); int ptr = 0; Container b; for (Map.Entry<Long, Container> entry : data.entrySet()) { b = entry.getValue(); System.arraycopy(b.buf, 0, outData, ptr, b.len); ptr += b.len; } outBuffer.setOffset(0); outBuffer.setLength(frameLength); outBuffer.setRtpTimeStamp(inBuffer.getRtpTimeStamp()); if (TRACE) logger.trace("Out PictureID=" + inPictureId); lastSentSeq = lastSeq; // prepare for the next frame reinit(); return BUFFER_PROCESSED_OK; } else { // frame not complete yet outBuffer.setDiscard(true); return OUTPUT_BUFFER_NOT_FILLED; } }
/** * Notifies this instance that a <tt>DatagramPacket</tt> packet received on the data * <tt>DatagramSocket</tt> of this <tt>Channel</tt> has been accepted for further processing * within Jitsi Videobridge. * * @param pkt the accepted <tt>RawPacket</tt>. */ public void accepted(RawPacket pkt) { // With native simulcast we don't have a notification when a stream // has started/stopped. The simulcast manager implements a timeout // for the high quality stream and it needs to be notified when // the channel has accepted a datagram packet for the timeout to // function correctly. if (!hasLayers() || pkt == null) { return; } // Find the layer that corresponds to this packet. int acceptedSSRC = pkt.getSSRC(); SimulcastLayer[] layers = getSimulcastLayers(); SimulcastLayer acceptedLayer = null; for (SimulcastLayer layer : layers) { // We only care about the primary SSRC and not the RTX ssrc (or // future FEC ssrc). if ((int) layer.getPrimarySSRC() == acceptedSSRC) { acceptedLayer = layer; break; } } // If this is not an RTP packet or if we can't find an accepted // layer, log and return as it makes no sense to continue in this // situation. if (acceptedLayer == null) { return; } // There are sequences of packets with increasing timestamps but without // the marker bit set. Supposedly, they are probes to detect whether the // bandwidth may increase. We think that they should cause neither the // start nor the stop of any SimulcastLayer. // XXX There's RawPacket#getPayloadLength() but the implementation // includes pkt.paddingSize at the time of this writing and we do not // know whether that's going to stay that way. int pktPayloadLength = pkt.getLength() - pkt.getHeaderLength(); int pktPaddingSize = pkt.getPaddingSize(); if (pktPayloadLength <= pktPaddingSize) { if (logger.isTraceEnabled()) { logger.trace( "pkt.payloadLength= " + pktPayloadLength + " <= pkt.paddingSize= " + pktPaddingSize + "(" + pkt.getSequenceNumber() + ")"); } return; } // NOTE(gp) we expect the base layer to be always on, so we never touch // it or starve it. // XXX Refer to the implementation of // SimulcastLayer#touch(boolean, RawPacket) for an explanation of why we // chose to use a return value. boolean frameStarted = acceptedLayer.touch(pkt); if (frameStarted) simulcastLayerFrameStarted(acceptedLayer, pkt, layers); }