RTPDePacketizerpublic 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 int | addToFrame(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 int | compareSequenceNumbers(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 boolean | constructFrame(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 void | constructGop(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 void | constructHeaders(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 void | constructPicture(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.Buffer | copyInto(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$MPEGFrame | createNewFrame(javax.media.Buffer inBuffer)
Buffer b = copyInto(inBuffer);
MPEGFrame newframe = new MPEGFrame(b);
newframe.add(b);
return newframe;
| private void | dropBufferFrame(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 void | dropFrame()
Buffer src = (Buffer)currentframe.data.firstElement();
dropBufferFrame(src);
| public void | finalize()
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 boolean | firstPacket(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 int | process(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.String | toHex(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;
|
|