FileDocCategorySizeDatePackage
RTPDePacketizer.javaAPI DocJMF 2.1.1e23578Mon May 12 12:20:46 BST 2003com.sun.media.codec.video.mpeg

RTPDePacketizer

public class RTPDePacketizer extends Object
This codec is a MPEG RTP depacketizer. It receives individual RTP buffers with a MPEG_RTP format. These buffers will be used to reconstruct a complete frame in the MPEG format. Once a frame is constructed, it is sent over to the next node i.e. a node capable of handling the MPEG format. This codec currently only supports MPEG 1 RTP headers, it doesn't deal with the MPEG 2 extension headers.

Fields Summary
public static float[]
RATE_TABLE
private static char[]
hexChar
private long
discardtimestamp
private MPEGFrame
currentframe
private boolean
newframe
private boolean
gotSequenceHeader
private boolean
sequenceSent
private byte[]
sequenceHeader
private boolean
gopset
private int
closedGop
private int
ref_pic_temp
private int
dep_pic_temp
private int
sequenceNumber
private int
width
private int
height
private float
frameRate
private VideoFormat
outFormat
private boolean
allowHeadless
private boolean
fullFrameOnly
private boolean
droppedPFrame
private boolean
droppedIFrame
private boolean
capture
private OutputStream
captureFile
private static final boolean
debug
private static int
MAX_SEQ
Constructors Summary
RTPDePacketizer()


     
	if (capture) {
	    try {
		captureFile = new BufferedOutputStream(
				new FileOutputStream("/tmp/rtpstream.mpg"));
	    } catch(IOException ioe) {
		System.err.println("RTPDePacketizer: unable to open file "
									+ ioe);
		capture = false;
	    }
	}
    
Methods Summary
private intaddToFrame(javax.media.Buffer inBuffer, javax.media.Buffer outBuffer)


      Buffer b = copyInto(inBuffer);

      currentframe.add(b);

	if ((b.getFlags() & Buffer.FLAG_RTP_MARKER) != 0) {
	    // copy all elements of the vector into one big buffer.
	    if (constructFrame(outBuffer)) {
		return PlugIn.BUFFER_PROCESSED_OK;
	    }
	}
	return PlugIn.OUTPUT_BUFFER_NOT_FILLED;
    
private intcompareSequenceNumbers(long p, long c)


    /*
     * the RTP sequence number is unsigned 16 bit counter that
     * wraps around. Allow for the case where it has wrapped.
     * @param p sequence number of the suspected previous buffer
     * @param c sequence number of the current (or next) buffer
     * @return int difference in sequence numbers
     */
           
	if (c > p)
	    return (int) (c - p);
	if (c == p)
	    return 0;
	if (p > MAX_SEQ - 100 && c < 100) {
	    // Allow for the case where sequence number has wrapped.
	    return (int) ((MAX_SEQ - p) + c + 1);
	}
	return -1;
    
private booleanconstructFrame(javax.media.Buffer outBuffer)

	Buffer src = (Buffer)currentframe.data.lastElement();
	int type = ((byte[])src.getData())[src.getOffset() + 2] & 0x07;
	if (fullFrameOnly) {
	    if (type >= 2 && (droppedIFrame || droppedPFrame)) {
		if (debug) {
		    System.err.print("Previously dropped I / P frame ");
		}
		dropFrame();
		return false;
	    } else if (type == 1) {
		droppedIFrame = false;	// found an I frame, reset all
		droppedPFrame = false;
	    }

	    // validate the buffers received

	    // does frame end with RTP mark
	    if ((src.getFlags() & Buffer.FLAG_RTP_MARKER) == 0) {
		if (debug) {
		    System.err.print("No RTP marker ");
		}
		dropFrame();
		return false;
	    }
	    // are all sequence numbers present
	    for (int i = currentframe.data.size() - 2; i >= 0; i--) {
		Buffer prev = (Buffer)currentframe.data.elementAt(i);
		if (compareSequenceNumbers(prev.getSequenceNumber(),
						src.getSequenceNumber()) != 1) {
		    if (debug) {
			System.err.print("Missing sequence # ");
		    }
		    dropFrame();
		    return false;
		}
		src = prev;
	    }

	}

	boolean noslices = true;
	byte[] dest = (byte[]) outBuffer.getData();
	if (dest == null || dest.length < currentframe.datalength
						+ sequenceHeader.length + 16)
	    dest = new byte[currentframe.datalength + sequenceHeader.length+16];
	//outBuffer.setFlags(0);
	outBuffer.setData(dest);
	outBuffer.setOffset(0);
	outBuffer.setLength(0);

	// make sure there are sufficient headers
	constructHeaders(outBuffer);
	if (!sequenceSent) {
	    if (debug) {
		System.err.print("!sequence sent ");
	    }
	    dropFrame();
	    return false;
	}

	int outoffset = outBuffer.getLength();

	// copy all elements of the vector into one big buffer.
	bufloop: for (int i = 0; i < currentframe.data.size(); i++) {
	    src = (Buffer)currentframe.data.elementAt(i);
	    byte[] payload = (byte[])src.getData();
	    int offset = src.getOffset();
	    if ((payload[offset+2] & 0x10) != 0x10)
		continue bufloop;	// doesn't contain beginning of slice
	    if ((payload[offset+2] & 0x08) == 0x08) {
		// buffer contains complete slices, copy it
		System.arraycopy(payload,
				 offset + 4,	// 4 byte MPEG header
				 dest,
				 outoffset,
				 src.getLength() - 4);
		outoffset+= src.getLength() - 4;
		noslices = false;
		continue bufloop;
	    }

	    // Have beginning of slice, now need to find the end of the
	    // slice and ALL packets in between
	    long seq = src.getSequenceNumber();
	    int j;
	    for (j = i+1; j < currentframe.data.size(); j++) {
		Buffer next = (Buffer)currentframe.data.elementAt(j);
		if (compareSequenceNumbers(seq,
					next.getSequenceNumber()) != 1) {
		    if (i == 0) {
			// if this is the first packet of the frame need
			// to get sequence header, GOP and picture start
			// if they are present. Take everything up to
			// the first slice start.
			offset += 4;	// skip MPEG RTP header
			int len = src.getLength() - 4;
			int off = offset;
			while (len > 4) {
			    if (payload[off+0] == 0
					&& payload[off+1] == 0
					&& payload[off+2] == 1
					&& (payload[off+3] & 0xff) > 0
					&& (payload[off+3] & 0xff) <= 0xaf)
				break;	// hit beginning of slice
			    off++;
			    len--;
			}
			if (off == offset)
				continue bufloop;	// none present
			System.arraycopy(payload,
					 offset,
					 dest,
					 outoffset,
					 off - offset);
			outoffset+= off - offset;
		    }
		    continue bufloop;
		}
		seq = next.getSequenceNumber();
		if (( ((byte[]) next.getData())[next.getOffset()+2]
							    & 0x08) == 0x08) {
		    break;				// found end of slice
		}
	    }
	    if (j == currentframe.data.size()) {
		break bufloop; // ran past end of array, drop last slice
	    }
	    for (int k = i; k <= j; k++) {
		src = (Buffer)currentframe.data.elementAt(k);
		System.arraycopy((byte[])src.getData(),
				 src.getOffset() + 4,	// 4 byte MPEG header
				 dest,
				 outoffset,
				 src.getLength() - 4);
		outoffset+= src.getLength() - 4;
	    }
	    noslices = false;
	    i = j;
	}
	if (outFormat == null || outFormat.getSize().width != width
				|| outFormat.getSize().height != height
				|| outFormat.getFrameRate() != frameRate) {
	    java.awt.Dimension d = new java.awt.Dimension(width, height);
	    outFormat = new VideoFormat(VideoFormat.MPEG,
				   d,
				   javax.media.format.VideoFormat.NOT_SPECIFIED,
				   Format.byteArray,
				   frameRate
				   );
	}
	    
	outBuffer.setLength(outoffset);
	outBuffer.setFormat(outFormat);

	if (noslices) {
	    outBuffer.setFlags(outBuffer.FLAG_DISCARD);
	} else {
	    //outBuffer.setFlags(outBuffer.getFlags() | outBuffer.FLAG_NO_SYNC);
	}
	    
	outBuffer.setTimeStamp(currentframe.rtptimestamp);
	outBuffer.setSequenceNumber(sequenceNumber++);

	newframe = true;
	currentframe = null;
	if (noslices)
	    return false;
	if (capture) {
	    try {
		captureFile.write((byte[]) outBuffer.getData(),
				outBuffer.getOffset(), outBuffer.getLength());
	    } catch(IOException ioe) {
		System.err.println(
			"RTPDePacketizer: write error for sequence number "
			+ outBuffer.getSequenceNumber() + " : " + ioe);
		capture = false;
	    }
	}
	return true;
    
private voidconstructGop(javax.media.Buffer outBuffer)

	byte[] dest = (byte[])outBuffer.getData();
	int outoffset = outBuffer.getLength();
	if (sequenceHeader != null) {
	    System.arraycopy(sequenceHeader, 0, dest, outoffset,
							 sequenceHeader.length);
	    outBuffer.setLength(outBuffer.getLength() + sequenceHeader.length);
	    outoffset += sequenceHeader.length;
	    sequenceSent = true;
	}
	dest[outoffset] = 0;
	dest[outoffset+1] = 0;
	dest[outoffset+2] = 1;
	dest[outoffset+3] = (byte) 0xb8;

	// drop_frame_flag + time_code_hours + 2 bits time_code_minutes
	dest[outoffset+4] = (byte) 0x80;
	// 4 bits time_code_minute + marker_bit + 3 bits time_code_seconds
	dest[outoffset+5] = (byte) 0x08;
	// 3 bits time_code_seconds + 5 bits time_code_picture
	dest[outoffset+6] = 0;
	// 3 bits time_code_picture + closed_gop + broken_link
	dest[outoffset+7] = (byte) (closedGop | 0x20);

	outBuffer.setLength(outBuffer.getLength() + 8);
	ref_pic_temp = 0;	// don't know the correct value...
	dep_pic_temp = -1;

    
private voidconstructHeaders(javax.media.Buffer outBuffer)


	boolean havePicture = false;

	int outoffset = 0;
	byte[] dest = (byte[]) outBuffer.getData();
	Buffer src = (Buffer)currentframe.data.elementAt(0);
	byte[] payload = (byte[])src.getData();
	int offset = src.getOffset();
	int tr = (payload[offset] & 0x03) << 8 | (payload[offset+1] & 0xff);
	int type = payload[offset+2] & 0x07;

	// first determine what headers are present
	if (src.getLength() >= 8	&& (payload[offset+2] & 0x10) == 0x10
					&& payload[offset+4] == 0
					&& payload[offset+5] == 0
					&& payload[offset+6] == 1) {
	    int startCode = payload[offset+7] & 0xff;
	    if (startCode == 0xb3) {
		// Found sequence start, just reset counters
		sequenceSent = true;
		ref_pic_temp = tr;
		dep_pic_temp = -1;
		return;
	    } else if (startCode == 0xb8) {
		// Found sequence GOP, insert sequence start and reset counters
		if (sequenceHeader != null) {
		    System.arraycopy(sequenceHeader, 0, dest,
							outBuffer.getLength(),
							sequenceHeader.length);
		    outBuffer.setLength(outBuffer.getLength()
							+sequenceHeader.length);
		    sequenceSent = true;
		}
		ref_pic_temp = tr;
		dep_pic_temp = -1;
		return;
	    } else if (startCode == 0)
		havePicture = true;
	}
	ref_pic_temp++;
	dep_pic_temp++;
	if (type < 3) {
	    // it's either a I or P picture
	    if (tr < ref_pic_temp) {
		constructGop(outBuffer);
	    }
	    ref_pic_temp = tr;	// in case we missed some
	} else {
	    // it's a B picture
	    if (tr < dep_pic_temp) {
		constructGop(outBuffer);
	    }
	    dep_pic_temp = tr;	// in case we missed some
	}
	if (!havePicture)
	    constructPicture(src, outBuffer);
    
private voidconstructPicture(javax.media.Buffer inBuffer, javax.media.Buffer outBuffer)

	byte[] payload = (byte[])inBuffer.getData();
	int offset = inBuffer.getOffset();
	byte[] dest = (byte[])outBuffer.getData();
	int outoffset = outBuffer.getLength();
	int next = 0;
	dest[outoffset] = 0;
	dest[outoffset+1] = 0;
	dest[outoffset+2] = 1;
	dest[outoffset+3] = 0;

	// set 8 of 10 bits of temporal reference
	dest[outoffset+4] = (byte) ((payload[offset] & 0x03) << 6
					| (payload[offset+1] & 0xfc) >> 2);

	int ptype = payload[offset+2] & 0x07;
	int back = (payload[offset+3] & 0xf0) >> 4;
	int fwd = payload[offset+3] & 0x0f;

	// set last 2 bits of temporal reference plus 3 bits picture type
	// leave vbv_delay 0 (only first 3 bits of vbv_delay in this byte)
	dest[outoffset+5] = (byte) ((payload[offset+1] & 0x02) << 6
								| ptype << 3);
	dest[outoffset+6] = 0;	// next 8 bits of vbv_delay
	if (ptype == 1) {
	    dest[outoffset+7] = 0;	// last 5 bits of vbv_delay
	    outBuffer.setLength(outBuffer.getLength() + 8);
	} else {
	    // last 5 bits vbv_delay + full_pel_forward_vector
	    //				+ first 2 bits forward_f_code
	    next = fwd >> 1;
	    dest[outoffset+7] = (byte) next;
	    // last 1 bit forward_f_code
	    next = (fwd & 0x01) << 7;
	    if (ptype > 2) {
		// last 1 bit forward_f_code + full_pel_backward_vector
		//					+ backward_f_code
		next |= back << 3;
	    }
	    dest[outoffset+8] = (byte) next;
	    outBuffer.setLength(outBuffer.getLength() + 9);
	}
    
private javax.media.BuffercopyInto(javax.media.Buffer src)

	Buffer dest = new Buffer();
	dest.copy(src);
	src.setData(null);
	src.setHeader(null);
	src.setLength(0);
	src.setOffset(0);
	return dest;
    
private com.sun.media.codec.video.mpeg.RTPDePacketizer$MPEGFramecreateNewFrame(javax.media.Buffer inBuffer)

	Buffer b = copyInto(inBuffer);
	
	MPEGFrame newframe = new MPEGFrame(b);
	newframe.add(b);
	return newframe;
    
private voiddropBufferFrame(javax.media.Buffer src)

	int type = ((byte[])src.getData())[src.getOffset() + 2] & 0x07;
	if (type == 1)
	    droppedIFrame = true;
	else if (type == 2)
	    droppedPFrame = true;
	if (debug) {
	    System.err.println("Dropping " + ((type == 1) ? "I"
					: ((type == 2) ? "P"
					: ((type == 3) ? "B" : "D")))
				+ " frame");
	}
	newframe = true;
	if (debug && (type <= 0 || type > 3)) {
	    System.err.println("Invalid type " + type + " header "
			+ toHex((byte[])src.getData(), src.getOffset()));
	    System.err.println("Buffer length " + src.getLength());
	}
	currentframe = null;
	return;
    
private voiddropFrame()

	Buffer src = (Buffer)currentframe.data.firstElement();
	dropBufferFrame(src);
    
public voidfinalize()

	if (capture) {
	    try {
		captureFile.flush();
		captureFile.close();
		System.err.println("RTPDePacketizer: closed file");
	    } catch(IOException ioe) {
		System.err.println("RTPDePacketizer: unable to close file "
									+ ioe);
		capture = false;
	    }
	}
    
private booleanfirstPacket(javax.media.Buffer inBuffer)

	if (inBuffer == null)
	    return false;
	byte[] payload = (byte[])inBuffer.getData();
	if (payload == null)
	  return false;
	int offset = inBuffer.getOffset();
	int len = inBuffer.getLength();
	if (len < 12)
	    return false;

	// First, must find a sequence header to indicate start of movie
	if (!gotSequenceHeader) {
	    // Look for a sequence header. Until one is found discard frames.
	    if ((payload[offset+2] & 0x20) == 0x20
					&& payload[offset+4] == 0
					&& payload[offset+5] == 0
					&& payload[offset+6] == 1
					&& (payload[offset+7] & 0xff) == 0xb3) {
		// Found a sequence header, get width, height, frame rate

		// width is 12 bits at sequence header + 4
		width = (payload[offset + 8] & 0xff) << 4
					    | (payload[offset + 9] & 0xf0) >> 4;
		// height is 12 bits at sequence header + 5.5
		height = (payload[offset + 9] & 0x0f) << 8
					    | (payload[offset + 10] & 0xff);
		// frameRate index is 4 bits at sequence header + 6.5
		// int frix = (payload[offset + 11] & 0x0f);
		// if (frix > 0 && frix <= 8)
		//     frameRate = RATE_TABLE[frix];
		gotSequenceHeader = true;
		offset += 4;
		len -= 4;
		int off = offset;
		while (len > 8) {
		  if (payload[off+0] == 0 && payload[off+1] == 0
						&& payload[off+2] == 1) {
		      if ((payload[off+3] & 0xff) == 0xb8) {
			gopset = true;
			closedGop = payload[off+7] & 0x40;
			// force broken_link bit on
			payload[off+7] = (byte) (payload[off+7] & 0x20);
			sequenceHeader = new byte[off - offset];
			System.arraycopy(payload,
					 offset,
					 sequenceHeader,
					 0,
					 sequenceHeader.length);
			return true;		// hit group of pictures (GOP)
		      }
		  }
		  off++;
		  len--;
		}
		return true;
	    }
	    return false;
	}

	// check for a start of picture in the MPEG header and
	// return true if found
	if ((payload[offset+2] & 0x10) != 0x10)
	    return false;	// doesn't contain beginning of slice
	offset += 4;		// skip MPEG RTP header
	len -= 4;
	while (len > 8) {
	  if (payload[offset+0] == 0 && payload[offset+1] == 0
						&& payload[offset+2] == 1) {
	      if (payload[offset+3] == 0)
		return true;		// hit start of picture
	      if ((payload[offset+3] & 0xff) == 0xb8) {
		gopset = true;
		closedGop = payload[offset+7] & 0x40;
		// force broken_link bit on
		payload[offset+7] = (byte) (payload[offset+7] | 0x20);
		return true;		// hit group of pictures (GOP)
	      }
	      if ((payload[offset+3] & 0xff) <= 0xaf)
		return false;		// hit beginning of slice
	  }
	  offset++;
	  len--;
	}
	return false;
    
public intprocess(javax.media.Buffer inBuffer, javax.media.Buffer outBuffer)
This method will reconstruct a MPEG frame from individual RTP packets. The reconstruction process waits till all the packtes of a frame are received and send this over to the decoder only if all the frames were received. If a the first packet of a frame is not received, all other packets belonging to this frame are discarded. If an out of order packet is received, it is kept around until the last packet of the frame is received and which time the depacketizer will attempt to reorder the frames.

	// if this timestamp of this inBuffer belongs to a frame we
	// have marked as a to-be discarded frame, discard this packet.
	// We don't want the outData to be sent to the decoder.
	// Allow -1 through -- the timestamp isn't being set.
	if ((inBuffer.getTimeStamp() == discardtimestamp) &&
						(discardtimestamp != -1)) {
		return PlugIn.OUTPUT_BUFFER_NOT_FILLED; 
	}

	// if the newframe is not set and we have received a packet
	// with a RTP timestamp different from the RTPtimestamp of the
	// current frame, we have lost the last packet(s) of the
	// current frame. Discard the current frame
	if ( (!newframe) && (currentframe != null) &&
	    (inBuffer.getTimeStamp() != currentframe.rtptimestamp)) {
	    if (allowHeadless || firstPacket(inBuffer)) {
		boolean haveframe = false;
		if (fullFrameOnly) {
		    if (debug) {
			System.err.print(
			    "!newframe & timestamp mismatch & firstPacket ");
		    }
		    dropFrame();
		} else {
		    haveframe = constructFrame(outBuffer);
		}
		currentframe = createNewFrame(inBuffer);
		if (haveframe) {
		    return PlugIn.BUFFER_PROCESSED_OK;
		} else {
		    // if the whole frame is a single packet
		    if ((currentframe.getFirst().getFlags()
					& Buffer.FLAG_RTP_MARKER) != 0) {
			// copy all elements of the vector into one big buffer.
			if (constructFrame(outBuffer)) {
			    return PlugIn.BUFFER_PROCESSED_OK;
			}
		    }
		    return PlugIn.OUTPUT_BUFFER_NOT_FILLED;
		}
	    }
	    else {
		discardtimestamp = inBuffer.getTimeStamp();
		if (fullFrameOnly) {
		    if (debug) {
			System.err.print("!newframe & timestamp mismatch ");
		    }
		    dropFrame();
		    dropBufferFrame(inBuffer);
		} else if (compareSequenceNumbers(currentframe.seqno,
					inBuffer.getSequenceNumber()) > 0) {
		    // this is a new packet, complete the previous frame
		    if (constructFrame(outBuffer)) {
			return PlugIn.BUFFER_PROCESSED_OK; //return prev frame
		    }
		}
		// this is an old packet, don't complete the frame
		return PlugIn.OUTPUT_BUFFER_NOT_FILLED;
	    }
	}
	// if we are ready for a newframe and receive the first packet
	// of this frame, create a new frame buffer, else mark this
	// frame as a discard frame.
	if (newframe) {
	    if (firstPacket(inBuffer)) {
		newframe = false;
		currentframe = createNewFrame(inBuffer);
		// if the whole frame is a single packet
		if ((currentframe.getFirst().getFlags()
					& Buffer.FLAG_RTP_MARKER) != 0) {
		    // copy all elements of the vector into one big buffer.
		    if (constructFrame(outBuffer)) {
			return PlugIn.BUFFER_PROCESSED_OK;
		    }
		}
		return PlugIn.OUTPUT_BUFFER_NOT_FILLED;
	    }
	    if (fullFrameOnly) {
		if (debug) {
		    System.err.print("newframe & not firstPacket ");
		}
		dropBufferFrame(inBuffer);
	    }
	    discardtimestamp = inBuffer.getTimeStamp();
	    newframe = true;
	    return PlugIn.OUTPUT_BUFFER_NOT_FILLED;
	}
	// add the packet to the queue
	int ret = addToFrame(inBuffer, outBuffer);
	return ret;
    
protected java.lang.StringtoHex(byte[] inData, int inOffset)

	String hex = new String();
	for (int i = 0; i < 4; i++) {
	    hex += hexChar[(inData[inOffset + i] >> 4) & 0x0f];
	    hex += hexChar[inData[inOffset + i] & 0x0f];
	}
	return hex;