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

Packetizer

public class Packetizer extends BasicCodec

Fields Summary
public static float[]
RATE_TABLE
private static char[]
hexChar
protected static int
SEGMENT_DONE
protected static int
SEGMENT_REPEAT
protected static int
SEGMENT_DONE_BUFFER_FULL
protected static int
PACKET_MAX
private static boolean
debug
private VideoFormat
inputFormat
private VideoFormat
outputFormat
private boolean
inputEOM
private boolean
expectingNewInput
private boolean
expectingNewOutput
private boolean
resetTime
private boolean
resetInProgress
private Vector
outputQueue
private Vector
inputQueue
private Vector
segmentQueue
private byte[]
sequenceHeader
private int
frameWidth
private int
frameHeight
private double
frameRate
private long
picNanos
private long
gopTime
private long
startTime
private long
frameTime
private long
frameCount
private int
sequenceNumber
private byte[]
mpegHeader
Constructors Summary
public Packetizer()


    
    // Initialize default formats.
      
	inputFormats = new Format[] { new VideoFormat(VideoFormat.MPEG) };
	outputFormats = new Format[] { new VideoFormat(VideoFormat.MPEG_RTP) };
    
Methods Summary
public synchronized voidclose()

	reset();
    
private javax.media.BuffercopyInto(javax.media.Buffer src)

	Buffer dest = new Buffer();
	dest.copy(src);
	dest.setFlags(dest.getFlags() | Buffer.FLAG_NO_DROP);
	src.setData(null);
	src.setHeader(null);
	src.setLength(0);
	src.setOffset(0);
	return dest;
    
private intcopySequenceHeader(javax.media.Buffer outBuffer)

	if (sequenceHeader == null)
	    return 0;
	System.arraycopy(sequenceHeader, 0,
					outBuffer.getData(),
					outBuffer.getLength(),
					sequenceHeader.length);
	outBuffer.setLength(outBuffer.getLength() + sequenceHeader.length);
	return sequenceHeader.length;
    
private intdoGOP(com.sun.media.codec.video.mpeg.Packetizer$MPEGSegment gop, javax.media.Buffer outBuffer)

//	byte[] gb = new byte[4];
//	gop.copyData(4, 4, gb, 0);
//	long gtm = ((gb[0] & 0x7c) >> 2) * 60 * 60 * 1000 * 1000000;
//	gtm += ((gb[0] & 0x03) << 4 | (gb[1] & 0xf0) >> 4) * 60 * 1000
//								* 1000000;
//	gtm += ((gb[1] & 0x07) << 3 | (gb[2] & 0xe0) >> 5) * 1000
//								* 1000000;
//	long gpic = (gb[2] & 0x1f) << 1 | (gb[3] & 0x80) >> 7;
//	if (gpic != 0 || gtm != 0) {
//	    gopTime = gtm + (gpic * picNanos);
//	} else if (frameCount == 0) {
//	    gopTime = 1;		// to get past RTPSinkStream
//	}

	if (frameCount == 0) {
	    gopTime = 1 + startTime;		// to get past RTPSinkStream
	} else {
	    gopTime = frameCount * picNanos + startTime;
	}

	// put a sequence header before each GOP to allow RTP clients
	// to connect beginning with any GOP
	copySequenceHeader(outBuffer);
	gop.copyData((byte[]) outBuffer.getData(), outBuffer.getLength());
	outBuffer.setLength(outBuffer.getLength() + gop.getLength());
	return SEGMENT_DONE;
    
private intdoPicture(com.sun.media.codec.video.mpeg.Packetizer$MPEGSegment ph, javax.media.Buffer outBuffer)

	byte[] pic = new byte[ph.getLength()];
	ph.copyData(pic, 0);
	int cnt = (pic[4] & 0xff) << 2 | (pic[5] & 0xc0) >> 6;
	int type = (pic[5] & 0x38) >> 3;
	mpegHeader[0] = (byte) ((cnt >> 8) & 0x02);
	mpegHeader[1] = (byte) cnt;
	mpegHeader[2] = (byte) type;		// ignore N, S, B, E for now
	if (type == 1) {
	    mpegHeader[3] = 0;
	} else {
	    int next = (pic[7] & 0x07) << 1 | (pic[8] & 0x80) >> 7;
	    if (type > 2)
		next |= (pic[8] & 0x78) << 1;
	    mpegHeader[3] = (byte) next;
	}
	resetInProgress = false;
	byte[] outData = (byte[]) outBuffer.getData();
	System.arraycopy(mpegHeader, 0, outData, 0, 4);

	// by the time a picture is encountered,
	// sequence header & GOP should already be copied into buffer
	if (outBuffer.getLength() > 8
					&& outData[4] == 0
					&& outData[5] == 0
					&& outData[6] == 1
					&& (outData[7] & 0xff) == 0xb3) {
	    outData[2] |= 0x20;		// set S (section header present)
	}

	ph.copyData((byte[]) outBuffer.getData(), outBuffer.getLength());
	outBuffer.setLength(outBuffer.getLength() + ph.getLength());
	outBuffer.setFlags(outBuffer.getFlags() | Buffer.FLAG_KEY_FRAME);
	frameCount++;
	frameTime = gopTime + (cnt * picNanos);
	outBuffer.setTimeStamp(frameTime);
	outBuffer.setFormat(outputFormat);
	return SEGMENT_DONE;
    
private intdoProcess(javax.media.Buffer inBuffer, javax.media.Buffer outBuffer)

	if (expectingNewInput) {
	    if (!inputEOM) {
		if (inBuffer.getData() == null) {
		    return OUTPUT_BUFFER_NOT_FILLED;
		}
		if (resetTime) {
		    // get the new position time from a setStartTime
		    startTime = inBuffer.getTimeStamp();
		    if (debug) {
			System.err.println("Packetizer(V): new synctime set: "
						+ startTime);
		    }
		    if (startTime == 0)
			startTime = 1;	// to get past RTPSinkStream
		    resetTime = false;
		}
		inputQueue.addElement(copyInto(inBuffer));
	    }
	    expectingNewInput = false;
	}

	if (expectingNewOutput) {
	    byte[] outData = (byte[]) outBuffer.getData();
	    if (outData == null || outData.length < PACKET_MAX) {
		outData = new byte[PACKET_MAX];
		outBuffer.setData(outData);
	    }
	    System.arraycopy(mpegHeader, 0, outData, 0, 4);
	    outBuffer.setOffset(0);
	    outBuffer.setLength(4);
	    outBuffer.setFlags(0);
	    outBuffer.setHeader(null);
	    outBuffer.setFormat(outputFormat);
	    expectingNewOutput = false;
	}

	if (segmentQueue.isEmpty()) {
	    findFirstStartCode();
	    if (segmentQueue.isEmpty()) {
		expectingNewInput = true;
		return OUTPUT_BUFFER_NOT_FILLED;
	    }
	}
	MPEGSegment mseg = (MPEGSegment) segmentQueue.firstElement();
	while (mseg != null) {
	    if (mseg.getLength() < 0) {
		// need a new input buffer to calculate length for this segment
		expectingNewInput = true;
		return OUTPUT_BUFFER_NOT_FILLED;
	    }
	    int startCode = mseg.startCode;
	    int res = 0;
	    if (startCode == 0xb3) {		// sequence header
		res = doSequenceHeader(mseg, outBuffer);
	    } else if (startCode == 0xb7) {		// sequence end
		res = doSequenceEnd(mseg, outBuffer);
	    } else if (startCode == 0xb8) {		// GOP
		res = doGOP(mseg, outBuffer);
	    } else if (startCode == 0) {		// picture header
		res = doPicture(mseg, outBuffer);
	    } else if (startCode >= 1 && startCode <= 0xaf) {	// slice
		res = doSlice(mseg, outBuffer);
	    } else {
		// unknown start code, simply skip it
		res = SEGMENT_DONE;
	    }
	    if (res == SEGMENT_DONE) {
		segmentQueue.removeElementAt(0);
		if (segmentQueue.isEmpty()) {
		    expectingNewInput = true;
		    if (outBuffer.getLength() > 4) {
			return BUFFER_PROCESSED_OK;
		    } else {
			return OUTPUT_BUFFER_NOT_FILLED;
		    }
		}
		mseg = (MPEGSegment) segmentQueue.firstElement();
		continue;
	    }
	    if (res == SEGMENT_DONE_BUFFER_FULL) {
		segmentQueue.removeElementAt(0);
		// output buffer has data to be sent
		outBuffer.setFlags(outBuffer.getFlags() | Buffer.FLAG_NO_DROP);
		if (expectingNewInput) {
		    return BUFFER_PROCESSED_OK;
		}
		return INPUT_BUFFER_NOT_CONSUMED;
	    }
	    if (res == SEGMENT_REPEAT) {
		// output buffer has data to be sent
		outBuffer.setFlags(outBuffer.getFlags() | Buffer.FLAG_NO_DROP);
		if (expectingNewInput) {
		    return BUFFER_PROCESSED_OK;
		}
		return INPUT_BUFFER_NOT_CONSUMED;
	    }
	}
	return BUFFER_PROCESSED_FAILED;
    
private intdoSequenceEnd(com.sun.media.codec.video.mpeg.Packetizer$MPEGSegment se, javax.media.Buffer outBuffer)

//	se.copyData((byte[]) outBuffer.getData(), outBuffer.getLength());
//	outBuffer.setLength(outBuffer.getLength() + se.getLength());
	return SEGMENT_DONE;
    
private intdoSequenceHeader(com.sun.media.codec.video.mpeg.Packetizer$MPEGSegment sh, javax.media.Buffer outBuffer)

	sequenceHeader = new byte[sh.getLength()];
	sh.copyData(sequenceHeader, 0);
	frameWidth = (sequenceHeader[4] & 0xff) << 4
					| (sequenceHeader[5] & 0xf0) >> 4;
	frameHeight = (sequenceHeader[5] & 0x0f) << 8
					| (sequenceHeader[6] & 0xff);
	int frix = (sequenceHeader[7] & 0x0f);
	if (frix > 0 && frix <= 8)
	    frameRate = RATE_TABLE[frix];
	picNanos = (long)((1000 * 1000000) / frameRate);
//	int bitrate = (sequenceHeader[8] & 0xff) << 10
//					| (sequenceHeader[9] & 0xff) << 2
//					| (sequenceHeader[6] & 0xc0) >> 6;
	return SEGMENT_DONE;
    
private intdoSlice(com.sun.media.codec.video.mpeg.Packetizer$MPEGSegment slice, javax.media.Buffer outBuffer)

	byte[] outData = (byte[]) outBuffer.getData();
	if (slice.getLength() < PACKET_MAX - outBuffer.getLength()) {
	    slice.copyData(outData, outBuffer.getLength());
	    outBuffer.setLength(outBuffer.getLength() + slice.getLength());
	    outBuffer.setTimeStamp(frameTime);
	    outBuffer.setFormat(outputFormat);
	    outData[2] |= 0x18;		// set B, E (begin, end slice)
	    if (segmentQueue.size() > 1) {
		MPEGSegment mse = (MPEGSegment) segmentQueue.elementAt(1);
		if (mse.startCode < 1 || mse.startCode > 0xaf) {
		    outBuffer.setFlags(outBuffer.getFlags()
						| Buffer.FLAG_RTP_MARKER);
		    expectingNewOutput = true;
		    return SEGMENT_DONE_BUFFER_FULL;
		}
	    } else if (inputEOM) {
		outBuffer.setFlags(outBuffer.getFlags()
						| Buffer.FLAG_RTP_MARKER);
		expectingNewOutput = true;
		return SEGMENT_DONE_BUFFER_FULL;
	    }
	    return SEGMENT_DONE;
	}
	if ((outData[2] & 0x18) != 0) {
	    // there are slices in the buffer but this one won't fit
	    // send the buffer as is then come back to this slice
	    expectingNewOutput = true;
	    return SEGMENT_REPEAT;
	}
	int len = PACKET_MAX - outBuffer.getLength();
	slice.copyData(0, len, outData, outBuffer.getLength());
	outBuffer.setLength(outBuffer.getLength() + len);
	outBuffer.setTimeStamp(frameTime);
	outBuffer.setFormat(outputFormat);
	outData[2] |= 0x10;		// set B (begin slice)
	int off = len;
	len = slice.getLength() - len;

	Buffer b = null;
	// Now queue up additional output buffers to complete the slice
	while (len > 0) {
	    b = new Buffer();
	    outData = new byte[PACKET_MAX];
	    b.setData(outData);
	    b.setTimeStamp(frameTime);
	    b.setHeader(null);
	    b.setFormat(outputFormat);
	    b.setFlags(outBuffer.getFlags());
	    b.setOffset(0);
	    System.arraycopy(mpegHeader, 0, outData, 0, 4);
	    int l = len;
	    if (len > PACKET_MAX - 4)
		l = PACKET_MAX - 4;
	    slice.copyData(off, l, (byte[]) b.getData(), 4);
	    b.setLength(l + 4);
	    off += l;
	    len -= l;
	    if (len <= 0)
		outData[2] |= 0x08;		// set E (end slice)
	    outputQueue.addElement(b);
	}
	if (segmentQueue.size() > 1) {
	    MPEGSegment mse = (MPEGSegment) segmentQueue.elementAt(1);
	    if (mse.startCode < 1 || mse.startCode > 0xaf) {
		b.setFlags(b.getFlags() | Buffer.FLAG_RTP_MARKER);
		expectingNewOutput = true;
		return SEGMENT_DONE_BUFFER_FULL;
	    }
	} else if (inputEOM) {
	    b.setFlags(b.getFlags() | Buffer.FLAG_RTP_MARKER);
	    expectingNewOutput = true;
	    return SEGMENT_DONE_BUFFER_FULL;
	}
	expectingNewOutput = true;
	return SEGMENT_DONE_BUFFER_FULL;

    
public voidfinalize()

	close();
    
private voidfindFirstStartCode()

	if (inputQueue.isEmpty())
	    return;
	Buffer inBuffer = (Buffer) inputQueue.firstElement();
	// now the buffer is in the pipe, drop it from inputQueue
	inputQueue.removeElementAt(0);
	byte[] inData = (byte[]) inBuffer.getData();
	int off = inBuffer.getOffset();
	int len = inBuffer.getLength();
	while (len > 4) {
	    if (inData[off] == 0 && inData[off+1] == 0 && inData[off+2] == 1) {
		// treat extension and user_data as part of current header
		if ((inData[off+3] & 0xff) != 0xb5
					&& (inData[off+3] & 0xff) != 0xb2) {
		    if (resetInProgress) {
			// after reset, need a sequence_header_code or GOP
			if ((inData[off+3] & 0xff) == 0xb3
					|| (inData[off+3] & 0xff) == 0xb8) {
			    MPEGSegment ns =
					new MPEGSegment((inData[off+3] & 0xff),
							off, inBuffer);
			    segmentQueue.addElement(ns);
			    return;
			}
		    } else {
			MPEGSegment ns = new MPEGSegment((inData[off+3] & 0xff),
							off, inBuffer);
			segmentQueue.addElement(ns);
			return;
		    }
		}
	    }
	    off++;
	    len--;
	}
	// didn't find anything, try the next buffer
	expectingNewInput = true;
    
protected javax.media.FormatgetInputFormat()

	return inputFormat;
    
public java.lang.StringgetName()

	return "MPEG Video Packetizer";
    
protected javax.media.FormatgetOutputFormat()

	return outputFormat;
    
public javax.media.Format[]getSupportedOutputFormats(javax.media.Format in)

	if (in == null)
	    return outputFormats;

	// Make sure the input is MPEG video format
	if (matches(in, inputFormats) == null)
	    return new Format[0];
        
	Format out [] = new Format[1];
	out[0] = makeMPEGFormat(in);
	return out;
    
private final javax.media.format.VideoFormatmakeMPEGFormat(javax.media.Format in)

	VideoFormat vf = (VideoFormat)in;
	return new VideoFormat(VideoFormat.MPEG_RTP,
			vf.getSize(),
			VideoFormat.NOT_SPECIFIED,
			Format.byteArray,
			vf.getFrameRate());
    
public voidopen()

	if (inputFormat == null || outputFormat == null)
	    throw new ResourceUnavailableException(
				"Incorrect formats set on MPEG converter");
	startTime = 1;		// to get past RTPSinkStream
	frameRate = 0.0;
	picNanos = 0;
	sequenceNumber = 0;
	resetTime = true;
    
public synchronized intprocess(javax.media.Buffer inBuffer, javax.media.Buffer outBuffer)

	if (outputQueue.size() > 0) {
	    Buffer qbuf = (Buffer) outputQueue.firstElement();
	    outputQueue.removeElementAt(0);

	    outBuffer.setData((byte[]) qbuf.getData());
	    outBuffer.setOffset(qbuf.getOffset());
	    outBuffer.setLength(qbuf.getLength());
	    outBuffer.setFlags(qbuf.getFlags());
	    outBuffer.setTimeStamp(qbuf.getTimeStamp());
	    outBuffer.setSequenceNumber(sequenceNumber++);
	    outBuffer.setFormat(outputFormat);
	    expectingNewOutput = true;
	    return INPUT_BUFFER_NOT_CONSUMED;
	}
	if (isEOM(inBuffer)) {
	    inputEOM = true;
	    if (segmentQueue.isEmpty()) {
		propagateEOM(outBuffer);
		outBuffer.setSequenceNumber(sequenceNumber++);
		return BUFFER_PROCESSED_OK;
	    }
	}
	if (inBuffer.isDiscard()) {
	    updateOutput(outBuffer, outputFormat, 0, 0);
	    outBuffer.setDiscard(true);
	    return OUTPUT_BUFFER_NOT_FILLED;
	}

	int retVal = BUFFER_PROCESSED_FAILED;
	try {
	    retVal = doProcess(inBuffer, outBuffer);
	} catch (Exception ex) {
	    ex.printStackTrace();
	    return BUFFER_PROCESSED_FAILED;
	}

	// get the outputFormat from the buffer only if this is the first time.
	if (outputFormat == null) {
	    outputFormat = makeMPEGFormat(inBuffer.getFormat());
	}

	if (retVal != OUTPUT_BUFFER_NOT_FILLED) {
	    outBuffer.setSequenceNumber(sequenceNumber++);
	}
	return retVal;

    
public voidreset()

	// Anything to do?
	super.reset();
	outputQueue.removeAllElements();
	inputQueue.removeAllElements();
	segmentQueue.removeAllElements();
	inputEOM = false;
	expectingNewInput = true;
	expectingNewOutput = true;
	resetInProgress = true;
	resetTime = true;
	sequenceHeader = null;
	frameWidth = 0;
	frameHeight = 0;
	mpegHeader[0] = 0;
	mpegHeader[1] = 0;
	mpegHeader[2] = 0;
	mpegHeader[3] = 0;
	gopTime = 1;		// to get past RTPSinkStream
	frameTime = 0;
	frameCount = 0;
	if (debug) {
	    System.err.println("Packetizer(V): reset completed");
	}

    
public javax.media.FormatsetInputFormat(javax.media.Format input)

	inputFormat = (VideoFormat) input;
	return input;
    
public javax.media.FormatsetOutputFormat(javax.media.Format output)

	if (!(output instanceof VideoFormat)) return null;
	outputFormat = makeMPEGFormat(output);
	return output;
    
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;