/** * Creates a new <tt>RawPacket</tt> from a specific <tt>DatagramPacket</tt> in order to have this * instance receive its packet data through its {@link #read(byte[], int, int)} method. Returns an * array of <tt>RawPacket</tt> with the created packet as its first element (and <tt>null</tt> for * the other elements). * * <p>Allows extenders to intercept the packet data and possibly filter and/or modify it. * * @param datagramPacket the <tt>DatagramPacket</tt> containing the packet data * @return an array of <tt>RawPacket</tt> containing the <tt>RawPacket</tt> which contains the * packet data of the specified <tt>DatagramPacket</tt> as its first element. */ protected RawPacket[] createRawPacket(DatagramPacket datagramPacket) { RawPacket[] pkts = rawPacketArrayPool.poll(); if (pkts == null) pkts = new RawPacket[1]; RawPacket pkt = rawPacketPool.poll(); if (pkt == null) pkt = new RawPacket(); pkt.setBuffer(datagramPacket.getData()); pkt.setFlags(0); pkt.setLength(datagramPacket.getLength()); pkt.setOffset(datagramPacket.getOffset()); pkts[0] = pkt; return pkts; }
/** * Pools the specified <tt>RawPacket</tt> in order to avoid future allocations and to reduce the * effects of garbage collection. * * @param pkt the <tt>RawPacket</tt> to be offered to {@link #rawPacketPool} */ private void poolRawPacket(RawPacket pkt) { pkt.setBuffer(null); pkt.setFlags(0); pkt.setLength(0); pkt.setOffset(0); rawPacketPool.offer(pkt); }
/** * Re-initializes the fields which store information about the currently held data. Empties * <tt>data</tt>. */ private void reinit() { firstSeq = lastSeq = timestamp = -1; pictureId = -1; empty = true; haveEnd = haveStart = false; frameLength = 0; Iterator<Map.Entry<Long, Container>> it = data.entrySet().iterator(); Map.Entry<Long, Container> e; while (it.hasNext()) { e = it.next(); free.offer(e.getValue()); it.remove(); } }
/** {@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; } }
/** * Listens for incoming datagrams, stores them for reading by the <tt>read</tt> method and * notifies the local <tt>transferHandler</tt> that there's data to be read. */ public void run() { DatagramPacket p = new DatagramPacket(buffer, 0, PACKET_RECEIVE_BUFFER_LENGTH); while (!closed) { try { // http://code.google.com/p/android/issues/detail?id=24765 if (OSUtils.IS_ANDROID) p.setLength(PACKET_RECEIVE_BUFFER_LENGTH); receivePacket(p); } catch (IOException e) { ioError = true; break; } /* * Do the DatagramPacketFilters accept the received DatagramPacket? */ DatagramPacketFilter[] datagramPacketFilters = getDatagramPacketFilters(); boolean accept; if (!enabled) accept = false; else if (datagramPacketFilters == null) accept = true; else { accept = true; for (int i = 0; i < datagramPacketFilters.length; i++) { try { if (!datagramPacketFilters[i].accept(p)) { accept = false; break; } } catch (Throwable t) { if (t instanceof ThreadDeath) throw (ThreadDeath) t; } } } if (accept) { RawPacket pkts[] = createRawPacket(p); for (int i = 0; i < pkts.length; i++) { RawPacket pkt = pkts[i]; pkts[i] = null; if (pkt != null) { if (pkt.isInvalid()) { /* * Return pkt to the pool because it is invalid and, * consequently, will not be made available to * reading. */ poolRawPacket(pkt); } else { RawPacket oldPkt; synchronized (pktSyncRoot) { oldPkt = this.pkt; this.pkt = pkt; } if (oldPkt != null) { /* * Return oldPkt to the pool because it was made * available to reading and it was not read. */ poolRawPacket(oldPkt); } if (videoRecorder != null) videoRecorder.recordData(pkt); if ((transferHandler != null) && !closed) { try { transferHandler.transferData(this); } catch (Throwable t) { /* * XXX We cannot allow transferHandler to * kill us. */ if (t instanceof ThreadDeath) { throw (ThreadDeath) t; } else { logger.warn("An RTP packet may have not been" + " fully handled.", t); } } } } } } rawPacketArrayPool.offer(pkts); } } }