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

Packetizer.java

/*
 * @(#)Packetizer.java	1.14  02/08/21 SMI
 *
 * Copyright 1999 by Sun Microsystems, Inc.,
 * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information
 * of Sun Microsystems, Inc. ("Confidential Information").  You
 * shall not disclose such Confidential Information and shall use
 * it only in accordance with the terms of the license agreement
 * you entered into with Sun.
 */

package com.sun.media.codec.video.mpeg;

import javax.media.*;
import javax.media.Format;
import javax.media.format.VideoFormat;

import java.awt.Dimension;
import java.util.Vector;

import com.sun.media.*;

public class Packetizer extends BasicCodec {

    public static float RATE_TABLE[] = {
			0.0f, 23.976f, 24.f, 25.f, 29.97f,
			30.f, 50.f, 59.94f, 60.f
    };

    private static char[] hexChar = {
			'0', '1', '2', '3', '4', '5', '6', '7',
			'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
    };

    // following values returned by do<segment> routines
    protected static int SEGMENT_DONE = 1;
    protected static int SEGMENT_REPEAT = 2;
    protected static int SEGMENT_DONE_BUFFER_FULL = 3;

    // maximum packet length
    protected static int PACKET_MAX = 1456;

    private static boolean debug = false;

    private VideoFormat inputFormat = null;
    private VideoFormat outputFormat = null;

    private boolean inputEOM = false;
    private boolean expectingNewInput = true;
    private boolean expectingNewOutput = true;
    private boolean resetTime = true;
    private boolean resetInProgress = true;

    // all output buffers -- used for a slice that spans packets
    // type: Buffer
    private Vector outputQueue = new Vector();

    // all input buffers -- used for a segment that spans buffers
    // should never exceed 2 buffers
    private Vector inputQueue = new Vector();

    // known MPEG segments
    // type: MPEGSegments
    private Vector segmentQueue = new Vector();

    // holds last sequence header encountered
    private byte[] sequenceHeader = null;

    private int frameWidth = 0;
    private int frameHeight = 0;
    private double frameRate = 0.0;

    // nanoseconds per picture (frame) based on frameRate
    private long picNanos = 0;

    // time in nanoseconds from last GOP
    private long gopTime = 0;

    // time in nanoseconds from last position time
    private long startTime = 1;

    // time in nanoseconds from last picture header
    //  == gopTime + (picture count * picNanos)
    private long frameTime = 0;
    private long frameCount = 0;

    // sequence number for next packet
    private int sequenceNumber = 0;

    // RTP MPEG header without N, S, B, E set
    private byte[] mpegHeader = { 0, 0, 0, 0 };

    
    // Initialize default formats.
    public Packetizer() {
	inputFormats = new Format[] { new VideoFormat(VideoFormat.MPEG) };
	outputFormats = new Format[] { new VideoFormat(VideoFormat.MPEG_RTP) };
    }
    
    protected Format getInputFormat() {
	return inputFormat;
    }

    protected Format getOutputFormat() {
	return outputFormat;
    }

    // Return supported output formats
    public Format [] getSupportedOutputFormats(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;
    }

    public Format setInputFormat(Format input) {
	inputFormat = (VideoFormat) input;
	return input;
    }

    public Format setOutputFormat(Format output) {
	if (!(output instanceof VideoFormat)) return null;
	outputFormat = makeMPEGFormat(output);
	return output;
    }

    private final VideoFormat makeMPEGFormat(Format in) {
	VideoFormat vf = (VideoFormat)in;
	return new VideoFormat(VideoFormat.MPEG_RTP,
			vf.getSize(),
			VideoFormat.NOT_SPECIFIED,
			Format.byteArray,
			vf.getFrameRate());
    }
    
    public void open() throws ResourceUnavailableException {
	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 void close() {
	reset();
    }

    public void reset() {
	// 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 synchronized int process(Buffer inBuffer, 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 String getName() {
	return "MPEG Video Packetizer";
    }

    public void finalize() {
	close();
    }
 
    private int doProcess(Buffer inBuffer, 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 Buffer copyInto(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;
    }

    protected  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;
    }

    private int doSequenceHeader(MPEGSegment sh, 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 int copySequenceHeader(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 int doSequenceEnd(MPEGSegment se, Buffer outBuffer) {
//	se.copyData((byte[]) outBuffer.getData(), outBuffer.getLength());
//	outBuffer.setLength(outBuffer.getLength() + se.getLength());
	return SEGMENT_DONE;
    }

    private int doGOP(MPEGSegment gop, 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 int doPicture(MPEGSegment ph, 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 int doSlice(MPEGSegment slice, 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;

    }

    private void findFirstStartCode() {
	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;
    }

    /*
     * If length == -1, the end of segment hasn't been determined.
     * offset is absolute in buffer (no need to add buffer's offset).
     * The most common case is a segment is contained in a single
     * buffer so endBuffer is null.
     * Assumption: a segment will never span more than two buffers.
     */
    class MPEGSegment {
	int startCode = -1;
	int offset = -1;	// offset in startBuffer for the start code
	int length = -1;
	Buffer startBuffer = null;
	Buffer endBuffer = null;

	MPEGSegment(int code, int off, Buffer buf) {
	    startCode = code;
	    offset = off;
	    startBuffer = buf;
	}

	// off is relative to start of segment
	void copyData(byte[] dest, int outoffset) {
	    copyData(0, length, dest, outoffset);
	}

	// off is relative to start of segment
	void copyData(int off, byte[] dest, int outoffset) {
	    copyData(off, length - off, dest, outoffset);
	}

	// off is relative to start of segment
	void copyData(int off, int len, byte[] dest, int outoffset) {
	    if (off + len > length) {
		len = length - off;
	    }
	    if (endBuffer == null) {
		// completely contained in single buffer
		System.arraycopy(startBuffer.getData(), offset + off,
					dest, outoffset, len);
		return;
	    }
	    // len1 = portion of segment in startBuffer
	    // len2 = portion of segment in endBuffer
	    int len1 = startBuffer.getLength()
					- (offset - startBuffer.getOffset());
	    int len2 = length - len1;
	    if (off + len <= len1) {
		// still only copying from first buffer
		System.arraycopy(startBuffer.getData(), offset + off,
					dest, outoffset, len);
		return;
	    }
	    if (off >= len1) {
		// only copying from second buffer
		off -= len1;
		System.arraycopy(endBuffer.getData(),
					endBuffer.getOffset() + off,
					dest, outoffset, len);
		return;
	    }
	    // worst case, part of first buffer plus part of second buffer
	    int l = len1 - off;
	    System.arraycopy(startBuffer.getData(), offset + off,
					dest, outoffset, l);
	    len -= l;	// remaining length to copy from second buffer
	    System.arraycopy(endBuffer.getData(), endBuffer.getOffset(),
					dest, outoffset + l, len);
	}

	int getLength() {
	    if (length < 0)
		calculateLength();
	    return length;
	}

	private void calculateLength() {
	    if (length > 0)
		return;
	    int off = findNextStart();	// try in same buffer
	    if (off > offset) {
		length = off - offset;
		return;
	    }
	    if (inputEOM) {
		// at EOM and no more start codes, consume remainder of buffer
		length = startBuffer.getLength()
					- (offset - startBuffer.getOffset());
		return;
	    }
	    if (endBuffer == null) {
		if (inputQueue.isEmpty())
		    return;			// need to get another buffer
		endBuffer = (Buffer) inputQueue.firstElement();
		inputQueue.removeElementAt(0);
	    }
	    // handle case where start code straddles buffers
	    off = findNextStartBetweenBuffers();
	    if (off > offset) {
		length = off - offset;
		return;
	    }
	    off = findNextStartInEndBuffer();
	    length = startBuffer.getLength()
					- (offset - startBuffer.getOffset());
	    length += off - endBuffer.getOffset();
	}

	private int findNextStart() {
	    byte[] inData = (byte[]) startBuffer.getData();
	    int off = offset + 4;
	    int len = startBuffer.getLength()
				- ((offset + 4) - startBuffer.getOffset());
	    while (len > 3) {
		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) {
			MPEGSegment ns = new MPEGSegment((inData[off+3] & 0xff),
							off, startBuffer);
			segmentQueue.addElement(ns);
			return off;
		    }
		}
		off++;
		len--;
	    }
	    return -1;
	}

	// ugly brute force check for start code beginning in last 3 bytes
	// of startBuffer
	private int findNextStartBetweenBuffers() {
	    byte[] inData = (byte[]) startBuffer.getData();
	    byte[] inData2 = (byte[]) endBuffer.getData();
	    int off = startBuffer.getOffset() + startBuffer.getLength() - 3;
	    if (off <= offset)
		return -1;	// already spanning buffer, look beyond here
	    int off2 = endBuffer.getOffset();
	    if (inData[off] == 0 && inData[off+1] == 0 && inData[off+2] == 1) {
		// treat extension and user_data as part of current header
		if ((inData2[off2] & 0xff) != 0xb5
					&& (inData[off2] & 0xff) != 0xb2) {
		    MPEGSegment ns = new MPEGSegment((inData2[off2] & 0xff),
							off, startBuffer);
		    ns.endBuffer = endBuffer;
		    segmentQueue.addElement(ns);
		    endBuffer = null;	// not needed for this segment
		    return off;
		}
	    }
	    if (inData[off+1] == 0 && inData[off+2] == 0
						&& inData2[off2] == 1) {
		// treat extension and user_data as part of current header
		if ((inData2[off2+1] & 0xff) != 0xb5
					&& (inData[off2+1] & 0xff) != 0xb2) {
		    MPEGSegment ns = new MPEGSegment((inData2[off2+1] & 0xff),
							off+1, startBuffer);
		    ns.endBuffer = endBuffer;
		    segmentQueue.addElement(ns);
		    endBuffer = null;	// not needed for this segment
		    return off+1;
		}
	    }
	    if (inData[off+2] == 0 && inData2[off2] == 0
						&& inData2[off2+1] == 1) {
		// treat extension and user_data as part of current header
		if ((inData2[off2+2] & 0xff) != 0xb5
					&& (inData[off2+2] & 0xff) != 0xb2) {
		    MPEGSegment ns = new MPEGSegment((inData2[off2+2] & 0xff),
							off+2, startBuffer);
		    ns.endBuffer = endBuffer;
		    segmentQueue.addElement(ns);
		    endBuffer = null;	// not needed for this segment
		    return off+2;
		}
	    }
	    return -1;
	}

	private int findNextStartInEndBuffer() {
	    byte[] inData = (byte[]) endBuffer.getData();
	    int off = endBuffer.getOffset();
	    int len = endBuffer.getLength();
	    while (len > 3) {
		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) {
			MPEGSegment ns = new MPEGSegment((inData[off+3] & 0xff),
							off, endBuffer);
			segmentQueue.addElement(ns);
			return off;
		    }
		}
		off++;
		len--;
	    }
	    return -1;
	}

    }


}