/* * @(#)RawSyncBufferMux.java 1.40 02/08/21 * * Copyright (c) 1996-2002 Sun Microsystems, Inc. All rights reserved. */ package com.sun.media.multiplexer; import com.sun.media.*; import javax.media.*; import javax.media.protocol.*; import javax.media.format.*; import javax.media.format.AudioFormat; import java.io.IOException; import com.sun.media.controls.MonitorAdapter; import com.sun.media.util.MediaThread; import com.sun.media.util.ElapseTime; public class RawSyncBufferMux extends RawBufferMux { // used for MPEG video synchronization. boolean mpegBFrame = false; boolean mpegPFrame = false; // If this flag is on, the timestamp of the buffers will be // monotonically increasing. This is used for RTP in particular. protected boolean monoIncrTime = false; private long monoStartTime = 0; // in nanoseconds. private long monoTime = 0; private Object waitLock = new Object(); private boolean resetted = false; private boolean masterTrackEnded = false; // For comparing formats. static AudioFormat mpegAudio = new AudioFormat(AudioFormat.MPEG_RTP); static VideoFormat mpegVideo = new VideoFormat(VideoFormat.MPEG_RTP); // Constructor public RawSyncBufferMux(){ super(); timeBase = new RawMuxTimeBase(); // Allow data to be dropped. allowDrop = true; clock = new BasicClock(); try{ clock.setTimeBase(timeBase); } catch (Exception e){ } } public boolean initializeTracks(Format[] trackFormats){ if (!super.initializeTracks(trackFormats)) return false; masterTrackID = 0; for (int i = 0; i < trackFormats.length; i++) { if (trackFormats[i] instanceof AudioFormat) masterTrackID = i; } return true; } public void reset(){ super.reset(); mpegBFrame = false; mpegPFrame = false; synchronized (waitLock) { resetted = true; waitLock.notify(); } } /** * Returns a descriptive name for the plug-in. * This is a user readable string. */ public String getName(){ return "Raw Sync Buffer Multiplexer"; } /** * Process the buffer and multiplex it with data from other * tracks. The multiplexed output is sent to the output * <code>DataSource</code>. * @param buffer the input buffer * @param trackID the index identifying the track where the input buffer * belongs. * @return BUFFER_PROCESSED_OK if the processing is successful. Other * possible return codes are defined in PlugIn. * @see PlugIn */ public int process(Buffer buffer, int trackID) { // If the processor starts out having RTP times, before the // data comes out of this processor, we should reset the // RTP flag and sets it to RELATIVE time. Otherwise, the // next guy in the processing chain may compute the time // incorrectly. if ((buffer.getFlags() & Buffer.FLAG_RTP_TIME) != 0) { buffer.setFlags((buffer.getFlags() & ~Buffer.FLAG_RTP_TIME) | Buffer.FLAG_RELATIVE_TIME); } // If the monitor is enabled, we'll send the data to the monitor. if (mc[trackID] != null && mc[trackID].isEnabled()) mc[trackID].process(buffer); if ((streams == null) || (buffer == null) || (trackID >= streams.length)){ return PlugIn.BUFFER_PROCESSED_FAILED; } if (buffer.isDiscard()) return BUFFER_PROCESSED_OK; // // Unless the NO_WAIT flag is on, we'll need to wait for // the presentation time. // if ((buffer.getFlags() & Buffer.FLAG_NO_WAIT) == 0) { if (buffer.getFormat() instanceof AudioFormat) { // Regular audio requires that we wait for the last // bit of audio to be done. But MPEG's timestamp is // at the beginning of the chunk. if (mpegAudio.matches(buffer.getFormat())) waitForPT(buffer.getTimeStamp(), trackID); else waitForPT(mediaTime[trackID], trackID); } else if (buffer.getTimeStamp() >= 0) { if (mpegVideo.matches(buffer.getFormat()) && (buffer.getFlags() & Buffer.FLAG_RTP_MARKER) != 0) { byte[] payload = (byte[])buffer.getData(); int offset = buffer.getOffset(); int ptype = payload[offset+2] & 0x07; if (ptype > 2) { // found a B frame mpegBFrame = true; } else if (ptype == 2) { // found a P frame mpegPFrame = true; } /* * For MPEG the timestamp is the time of the frame but * frames come out of order. Since a stream may not * have all types of frames, have to allow for all * mixes. If B frames are present, only wait on them. * If no B frames, wait on P frames. If no B or P frames * wait on I frames. */ if (ptype > 2 || (ptype == 2 && !mpegBFrame) || (ptype == 1 && !(mpegBFrame | mpegPFrame))) { waitForPT(buffer.getTimeStamp(), trackID); } } else { waitForPT(buffer.getTimeStamp(), trackID); } } } updateTime(buffer, trackID); // We are doing the synchronization here so the down stream // will not need to. buffer.setFlags(buffer.getFlags() | Buffer.FLAG_NO_SYNC); if (!(buffer.getFormat() instanceof AudioFormat) || mpegAudio.matches(buffer.getFormat())) { // Convert the timestamps to be monotically increasing. if (monoIncrTime) { monoTime = monoStartTime + buffer.getTimeStamp() - mediaStartTime * 1000000; /* if ((buffer.getFlags() & Buffer.FLAG_RTP_MARKER) != 0) System.err.println("monoStartTime = " + monoStartTime + " mediaStartTime = " + mediaStartTime + " TS = " + buffer.getTimeStamp() + " mono TS = " + monoTime); */ buffer.setTimeStamp(monoTime); } } if (buffer.isEOM() && trackID == masterTrackID) masterTrackEnded = true; buffer.setHeader(new Long(System.currentTimeMillis())); return streams[trackID].process(buffer); } public void syncStart(Time at) { masterTrackEnded = false; super.syncStart(at); } public void setMediaTime(Time now) { super.setMediaTime(now); monoStartTime = monoTime + 10; // This is so the next frame time // will not be exactly the same. } /** * Update the media time per track. */ protected void updateTime(Buffer buf, int trackID) { if (buf.getFormat() instanceof AudioFormat) { if (mpegAudio.matches(buf.getFormat())) { if (buf.getTimeStamp() < 0) { if (systemStartTime >= 0) mediaTime[trackID] = (mediaStartTime + System.currentTimeMillis() - systemStartTime) * 1000000; } else mediaTime[trackID] = buf.getTimeStamp(); } else { long t = ((AudioFormat)buf.getFormat()).computeDuration(buf.getLength()); if (t >= 0) mediaTime[trackID] += t; else mediaTime[trackID] = buf.getTimeStamp(); } } else { if (buf.getTimeStamp() < 0 && systemStartTime >= 0) mediaTime[trackID] = (mediaStartTime + System.currentTimeMillis() - systemStartTime) * 1000000; else mediaTime[trackID] = buf.getTimeStamp(); } //System.err.println("mediaTime = " + mediaTime[trackID]); timeBase.update(); } static int THRESHOLD = 80; static int LEEWAY = 5; private void waitForPT(long pt, int trackID) { long delay; pt = pt / 1000000; // To bring it to millisecs range. //System.err.println("MT = " + mediaStartTime + // " ST = " + systemStartTime + // " pt = " + pt + // " st = " + System.currentTimeMillis()); if (masterTrackID == -1 || trackID == masterTrackID) { if (systemStartTime < 0) delay = 0; else delay = (pt-mediaStartTime) - (System.currentTimeMillis()-systemStartTime); } else { delay = pt - mediaTime[masterTrackID]/1000000; } //System.err.println("delay = " + delay); // This is a workaround for now. The video capture pipeline // is not fully flushed causing wrong values in the timestamps. if (delay > 2000) return; while (delay > LEEWAY && !masterTrackEnded) { if (delay > THRESHOLD) delay = THRESHOLD; synchronized (waitLock) { try { waitLock.wait(delay); } catch (Exception e) { break; } if (resetted) { resetted = false; break; } } if (masterTrackID == -1 || trackID == masterTrackID) delay = (pt-mediaStartTime) - (System.currentTimeMillis()-systemStartTime); else delay = pt - mediaTime[masterTrackID]/1000000; } } }// end of class RawBufferMultiplexer