FileDocCategorySizeDatePackage
BasicMux.javaAPI DocJMF 2.1.1e24420Mon May 12 12:20:58 BST 2003com.sun.media.multiplexer

BasicMux

public abstract class BasicMux extends BasicPlugIn implements Multiplexer, Clock

Fields Summary
protected Format[]
supportedInputs
Variables and Constants
protected ContentDescriptor[]
supportedOutputs
protected int
numTracks
protected Format[]
inputs
protected BasicMuxDataSource
source
protected BasicMuxPushStream
stream
protected ContentDescriptor
outputCD
protected boolean
flushing
protected Integer
sourceLock
protected boolean
eos
protected boolean
firstBuffer
protected int
fileSize
protected int
filePointer
protected long
fileSizeLimit
protected boolean
streamSizeLimitSupported
protected boolean
fileSizeLimitReached
protected SourceTransferHandler
sth
protected boolean
isLiveData
protected StreamWriterControl
swc
protected MonitorAdapter[]
mc
protected BasicMuxTimeBase
timeBase
Object
startup
boolean
readyToStart
long[]
mediaTime
boolean[]
ready
protected BasicClock
clock
int
master
boolean
mClosed
boolean
dataReady
boolean
startCompensated
Object
dataLock
Buffer[]
firstBuffers
boolean[]
firstBuffersDone
int[]
nonKeyCount
long
masterTime
VideoFormat
jpegFmt
VideoFormat
mjpgFmt
VideoFormat
rgbFmt
VideoFormat
yuvFmt
protected int
maxBufSize
protected byte[]
buf
protected int
bufOffset
protected int
bufLength
Object
timeSetSync
Clock methods
boolean
started
long
systemStartTime
Constructors Summary
public BasicMux()
Multiplexer methods


           

      
	timeBase = new BasicMuxTimeBase();
	clock = new BasicClock();
	try{
	    clock.setTimeBase(timeBase);
	} catch (Exception e){ }

	swc = new SWC(this);
	controls = new Control[] { swc };
    
Methods Summary
protected voidbufClear()

    
       
	bufOffset = 0;
	bufLength = 0;
    
protected voidbufFlush()

	filePointer -= bufLength;  // It is going to be incremented in write()
	write(buf, 0, bufLength);
    
protected voidbufSkip(int size)

	bufOffset += size;
	bufLength += size;
	filePointer += size;
    
protected voidbufWriteByte(byte value)

	buf[bufOffset] = value;
	bufOffset++;
	bufLength++;
	filePointer++;
    
protected voidbufWriteBytes(java.lang.String s)

	byte [] bytes = s.getBytes();
	bufWriteBytes(bytes);
    
protected voidbufWriteBytes(byte[] bytes)

	System.arraycopy(bytes, 0,
			 buf, bufOffset, bytes.length);
	bufOffset += bytes.length;
	bufLength += bytes.length;
	filePointer += bytes.length;
    
protected voidbufWriteInt(int value)

	buf[bufOffset + 0] = (byte)((value >> 24) & 0xFF);
	buf[bufOffset + 1] = (byte)((value >> 16) & 0xFF);
	buf[bufOffset + 2] = (byte)((value >>  8) & 0xFF);
	buf[bufOffset + 3] = (byte)((value >>  0) & 0xFF);
	bufOffset += 4;
	bufLength += 4;
	filePointer += 4;
    
protected voidbufWriteIntLittleEndian(int value)

	buf[bufOffset + 3] = (byte)((value >>> 24) & 0xFF);
	buf[bufOffset + 2] = (byte)((value >>> 16) & 0xFF);
	buf[bufOffset + 1] = (byte)((value >>>  8) & 0xFF);
	buf[bufOffset + 0] = (byte)((value >>>  0) & 0xFF);
	bufOffset += 4;
	bufLength += 4;
	filePointer += 4;
    
protected voidbufWriteShort(short value)

	buf[bufOffset + 0] = (byte)((value >> 8) & 0xFF);
	buf[bufOffset + 1] = (byte)((value >> 0) & 0xFF);
	bufOffset += 2;
	bufLength += 2;
	filePointer += 2;
    
protected voidbufWriteShortLittleEndian(short value)

	buf[bufOffset + 1] = (byte)((value >> 8) & 0xFF);
	buf[bufOffset + 0] = (byte)((value >> 0) & 0xFF);
	bufOffset += 2;
	bufLength += 2;
	filePointer += 2;
    
private booleancheckReady()

	if (readyToStart)
	    return true;
	for (int i = 0; i < ready.length; i++) {
	    if (!ready[i])
		return false;
	}
	readyToStart = true;
	return true;
    
public voidclose()

	if (sth != null) {
	    writeFooter();
	    write(null, 0, -1);
	}

	for (int i = 0; i < mc.length; i++) {
	    if (mc[i] != null)
		mc[i].close();
	}

	synchronized (dataLock) {
	    mClosed = true;
	    dataLock.notifyAll();
	}
    
private booleancompensateStart(javax.media.Buffer buffer, int trackID)


          

      synchronized (dataLock) {

	// This is the case when all the data have arrived, but
	// some buffers should have been dropped.  The following
	// code throw away the buffers that are behind.  For video,
	// key frames are carefully considered.
	if (dataReady) {
	    if (!firstBuffersDone[trackID]) {
		if (buffer.getTimeStamp() < masterTime) {
		    // Drop this frame.
		    return false;
		} else {
		    if (buffer.getFormat() instanceof VideoFormat) {
			Format fmt = buffer.getFormat();
			boolean isKey = (jpegFmt.matches(fmt) ||
					 mjpgFmt.matches(fmt) ||
					 rgbFmt.matches(fmt) ||
					 yuvFmt.matches(fmt));
			if (isKey ||
			    (buffer.getFlags() & Buffer.FLAG_KEY_FRAME) != 0 ||
			    nonKeyCount[trackID]++ > 30) {
			    buffer.setTimeStamp(masterTime);
			    firstBuffersDone[trackID] = true;
			} else
			    return false;
		    } else {
			// For everything else, the media time has exceeded
			// the master time, we reset the timestamps.
			buffer.setTimeStamp(masterTime);
			firstBuffersDone[trackID] = true;
		    }

		    // Check to see if all the first buffers are being
		    // compensated.
		    for (int i = 0; i < firstBuffersDone.length; i++) {
			if (!firstBuffersDone[i])
			    return true;
		    }
		    startCompensated = true;
		    return true;
		}
	    }
	    return true;
	}

	if (buffer.getTimeStamp() < 0) {

	    // At least one of the tracks have undefined timestamps,
	    // synchronization is deem to fail.  We won't attempt any
	    // compensation.
	    startCompensated = true;
	    dataReady = true;
	    dataLock.notifyAll();
	    return true;

	}

	firstBuffers[trackID] = buffer;

	// Check to see if all the buffers have arrived.
	boolean done = true;

	for (int i = 0; i < firstBuffers.length; i++) {
	    if (firstBuffers[i] == null)
		done = false;
	}

	if (!done) {

	    // If not, we'll wait here until all the buffers have arrived.
	    while (!dataReady && !mClosed) {
		try {
		   dataLock.wait();
		} catch (Exception e) {}
	    }

	    if (mClosed || firstBuffers[trackID] == null) {
		// We'll drop this buffer after being compensated for.
		return false;
	    }
	    return true;
	}

	// The first buffers have all arrived.

	// Find the master time.  If audio is there, we
	// use it.  Otherwise, choose the smallest time to
	// be the master time.

	masterTime = firstBuffers[0].getTimeStamp();

	for (int i = 0; i < firstBuffers.length; i++) {
	    if (firstBuffers[i].getFormat() instanceof AudioFormat) {
		masterTime = firstBuffers[i].getTimeStamp();
		break;
	    }
	    if (firstBuffers[i].getTimeStamp() < masterTime)
		masterTime = firstBuffers[i].getTimeStamp();
	}

	// For times bigger than master time, sets it to the
	// master time.  If not, we need to drop the frame.

	startCompensated = true;
	for (int i = 0; i < firstBuffers.length; i++) {

	    if (firstBuffers[i].getTimeStamp() >= masterTime) {
		firstBuffers[i].setTimeStamp(masterTime);
		firstBuffersDone[i] = true;
	    } else {
		firstBuffers[i] = null;
		startCompensated = false;
	    }
	}

	// Release the lock and buffer waiting to be processed since
	// all the initial buffers have arrived.
	synchronized (dataLock) {
	    dataReady = true;
	    dataLock.notifyAll();
	}

	return (firstBuffers[trackID] != null);

      } // dataLock.
    
protected intdoProcess(javax.media.Buffer buffer, int trackID)
Local methods

	// Simple mux - just write the contents of the buffer
	byte [] data = (byte[]) buffer.getData();
	int dataLen = buffer.getLength();
	if (!buffer.isEOM())
	    write(data, buffer.getOffset(), dataLen);
	return BUFFER_PROCESSED_OK;
    
public javax.media.protocol.DataSourcegetDataOutput()

	if (source == null) {
	    source = new BasicMuxDataSource(this, outputCD);
	    synchronized (sourceLock) {
		sourceLock.notifyAll();
	    }
	}
	return source;
    
private longgetDuration(javax.media.Buffer buffer)

	javax.media.format.AudioFormat format =
	  (javax.media.format.AudioFormat)buffer.getFormat();

	long duration = format.computeDuration(buffer.getLength());

	if (duration < 0)
	   return 0;

	return duration;
    
public longgetMediaNanoseconds()

	return clock.getMediaNanoseconds();
    
public javax.media.TimegetMediaTime()

	return clock.getMediaTime();
	
    
public floatgetRate()

	return clock.getRate();
    
public javax.media.TimegetStopTime()

	return clock.getStopTime();
    
longgetStreamSize()

	return fileSize;
    
public javax.media.Format[]getSupportedInputFormats()

	return supportedInputs;
    
public javax.media.protocol.ContentDescriptor[]getSupportedOutputContentDescriptors(javax.media.Format[] inputs)

	return supportedOutputs;
    
public javax.media.TimegetSyncTime()

	return clock.getSyncTime();
	
    
public javax.media.TimeBasegetTimeBase()

	return clock.getTimeBase();
    
booleanisEOS()

	return eos;
    
public javax.media.TimemapToTimeBase(javax.media.Time t)

	return clock.mapToTimeBase(t);
    
booleanneedsSeekable()

	return false;
    
public voidopen()

	int i;
	firstBuffer = true;
	firstBuffers = new Buffer[inputs.length];
	firstBuffersDone = new boolean[inputs.length];
	nonKeyCount = new int[inputs.length];
	mediaTime = new long[inputs.length];

	for (i = 0; i < inputs.length; i++) {
	    firstBuffers[i] = null;
	    firstBuffersDone[i] = false;
	    nonKeyCount[i] = 0;
	    mediaTime[i] = 0;
	}

	ready = new boolean[inputs.length];
	resetReady();

	int len = 0;
	mc = new MonitorAdapter[inputs.length];

	for (i = 0; i < inputs.length; i++) {
	    if (inputs[i] instanceof VideoFormat ||
		 inputs[i] instanceof AudioFormat) {
		mc[i] = new MonitorAdapter(inputs[i], this);
		if (mc[i] != null)
		    len++;
	    }
	}

	int j = 0;
	controls = new Control[len + 1];
	for (i = 0; i < mc.length; i++) {
	    if (mc[i] != null)
	        controls[j++] = mc[i];
	}
	controls[j] = swc;
    
public intprocess(javax.media.Buffer buffer, int trackID)

	if (buffer.isDiscard())
	    return BUFFER_PROCESSED_OK;
	if (!isLiveData && (buffer.getFlags() & Buffer.FLAG_LIVE_DATA) > 0) {
	    isLiveData = true;
	}
	// Wait until the datasource is created, connected and started
	while (source == null || !source.isConnected() || !source.isStarted()) {
	    synchronized (sourceLock) {
		try {
		    sourceLock.wait(500);
		} catch (InterruptedException ie) {
		}
		if (flushing) {
		    flushing = false;
		    buffer.setLength(0); // flush the buffer and dont process it
		    return BUFFER_PROCESSED_OK;
		}
	    }
	}

	synchronized (this) {
	    if (firstBuffer) {
		writeHeader();
		firstBuffer = false;
	    }
	}
	
	if (numTracks > 1) {
	    
	    // For buffers with RTP time stamps, we'll skip until
	    // we reach the buffers with non-zero time.  That's
	    // when synchronization is possible.
	    if ((buffer.getFlags() & Buffer.FLAG_RTP_TIME) != 0) {
		if (buffer.getTimeStamp() <= 0)
		    return BUFFER_PROCESSED_OK;
	    }
	    
	    if (!startCompensated) {
		if (!compensateStart(buffer, trackID)) {
		    // Drop the buffer.
		    return BUFFER_PROCESSED_OK;
		}
	    } 
	}
	
	updateClock(buffer, trackID);
	if (mc[trackID] != null && mc[trackID].isEnabled())
	    mc[trackID].process(buffer);
	int processResult = doProcess(buffer, trackID);
	if (fileSizeLimitReached)
	    processResult |= PLUGIN_TERMINATED;
	return processResult;
	
    
public booleanrequireTwoPass()
sub classes should override this method and return true if two passes are required to create the media file. Two passes may be required for example to rearrange the media file so that the resultant file is Streamable

	return false;
    
public voidreset()

	//firstBuffer = true;
	for (int i = 0; i < mediaTime.length; i++) {
	    mediaTime[i] = 0;
	    if (mc[i] != null)
		mc[i].reset();
	}
	timeBase.update();
	resetReady();
	synchronized (sourceLock) {
	    flushing = true;
	    sourceLock.notifyAll();
	}
    
private voidresetReady()

	for (int i = 0; i < ready.length; i++)
	    ready[i] = false;
	readyToStart = false;
	synchronized (startup) {
	    startup.notifyAll();
	}
    
protected intseek(int location)

	if (source == null || !source.isConnected())
	    return location;
	filePointer = stream.seek(location);
	return filePointer;
    
public javax.media.protocol.ContentDescriptorsetContentDescriptor(javax.media.protocol.ContentDescriptor outputCD)

	if (matches(outputCD, supportedOutputs) == null)
	    return null;

	// create the datasource and set its output
	// contentdescriptor
	this.outputCD = outputCD;
	return outputCD;
    
public javax.media.FormatsetInputFormat(javax.media.Format format, int trackID)

	inputs[trackID] = format;
	return format;
    
public voidsetMediaTime(javax.media.Time now)

	synchronized (timeSetSync) {
	    clock.setMediaTime(now);
	    for (int i = 0; i < mediaTime.length; i++)
		mediaTime[i] = now.getNanoseconds();
	    timeBase.update();
	}
    
public intsetNumTracks(int numTracks)

	this.numTracks = numTracks;

	if (inputs == null)
	    inputs = new Format[numTracks];
	else {
	    Format [] newInputs = new Format[numTracks];
	    for (int i=0; i < inputs.length; i++) {
		newInputs[i] = inputs[i];
	    }
	    inputs = newInputs;
	}
	
	return numTracks;
    
public floatsetRate(float factor)

	if (factor == clock.getRate())
	    return factor;
	return clock.setRate(1.0f);
    
public voidsetStopTime(javax.media.Time stopTime)

	clock.setStopTime(stopTime);
    
voidsetStream(com.sun.media.multiplexer.BasicMux$BasicMuxPushStream ps)

	stream = ps;
    
public voidsetTimeBase(javax.media.TimeBase master)

    
          
	if (master != timeBase)
	    throw new IncompatibleTimeBaseException();
    
public voidstop()

	synchronized (timeSetSync){
	    if (!started) return;
	    started = false;
	    clock.stop();
	    timeBase.mediaStopped();
	}
    
public voidsyncStart(javax.media.Time at)

	synchronized (timeSetSync){
	    if (started) return;
	    started = true;
	    clock.syncStart(at);
	    timeBase.mediaStarted();
	    systemStartTime = System.currentTimeMillis() * 1000000;
	}
    
private voidupdateClock(javax.media.Buffer buffer, int trackID)

	// Initially (after reset), block until all the streams 
	// have arrived.
	if (!readyToStart && numTracks > 1) {
	    synchronized (startup) {
		ready[trackID] = true;
		if (checkReady()) {
		    startup.notifyAll();
		} else {
		    try {
			// wait at most for 1 seconds.
			while (!readyToStart)
			    startup.wait(1000);
	    	    } catch (Exception e) { }
		}
	    }
	}

	// get the timestamp on the incoming buffer
	long timestamp = buffer.getTimeStamp();

	if (timestamp <= 0 && buffer.getFormat() instanceof AudioFormat) {
	    // If it's audio data and the time stamp is undefined,
	    // we'll compute from the audio duration.
	    timestamp = mediaTime[trackID];
	    mediaTime[trackID] += getDuration(buffer);
	} else if (timestamp <= 0) {
	    // This is video with TIME_UNKNOWN.
	    mediaTime[trackID] = System.currentTimeMillis() * 1000000 -
				systemStartTime;
	} else
	    mediaTime[trackID] = timestamp;

	timeBase.update();
    
protected intwrite(byte[] data, int offset, int length)

	if (source == null || !source.isConnected())
	    return length;
	if (length > 0) {
	    filePointer += length;
	    if (filePointer > fileSize)
		fileSize = filePointer;
	    if (fileSizeLimit > 0 && fileSize >= fileSizeLimit)
		fileSizeLimitReached = true;
	}
	return stream.write(data, offset, length);
    
protected voidwriteFooter()

    
protected voidwriteHeader()