/*
* @(#)RawBufferParser.java 1.37 02/08/21
*
* Copyright (c) 1996-2002 Sun Microsystems, Inc. All rights reserved.
*/
package com.sun.media.parser;
import java.io.IOException;
import java.awt.Dimension;
import java.util.Vector;
import javax.media.*;
import javax.media.Buffer;
import javax.media.protocol.*;
import javax.media.format.*;
import com.sun.media.*;
import com.sun.media.rtp.Depacketizer;
/**
* Parser for a raw stream of buffers from a PushBufferDataSource.
*/
public class RawBufferParser extends RawStreamParser {
static final String NAMEBUFFER = "Raw video/audio buffer stream parser";
private boolean started = false;
// For comparing formats.
static AudioFormat mpegAudio = new AudioFormat(AudioFormat.MPEG_RTP);
static VideoFormat mpegVideo = new VideoFormat(VideoFormat.MPEG_RTP);
static VideoFormat jpegVideo = new VideoFormat(VideoFormat.JPEG_RTP);
static VideoFormat h261Video = new VideoFormat(VideoFormat.H261_RTP);
static VideoFormat h263Video = new VideoFormat(VideoFormat.H263_RTP);
static VideoFormat h263_1998Video = new VideoFormat(VideoFormat.H263_1998_RTP);
public String getName() {
return NAMEBUFFER;
}
public void setSource(DataSource source)
throws IOException, IncompatibleSourceException {
if (!(source instanceof PushBufferDataSource)) {
throw new IncompatibleSourceException("DataSource not supported: " + source);
} else {
streams = ((PushBufferDataSource) source).getStreams();
}
if ( streams == null) {
throw new IOException("Got a null stream from the DataSource");
}
if (streams.length == 0) {
throw new IOException("Got a empty stream array from the DataSource");
}
if (!supports(streams))
throw new IncompatibleSourceException("DataSource not supported: " + source);
this.source = source;
this.streams = streams;
}
/**
* Override this if the Parser has additional requirements
* from the PushSourceStream
*/
protected boolean supports(SourceStream[] streams) {
return ( (streams[0] != null) &&
(streams[0] instanceof PushBufferStream) );
}
/**
* Opens the plug-in software or hardware component and acquires
* necessary resources. If all the needed resources could not be
* acquired, it throws a ResourceUnavailableException. Data should not
* be passed into the plug-in without first calling this method.
*/
public void open() {
if (tracks != null)
return;
tracks = new Track[streams.length];
for (int i = 0; i < streams.length; i++) {
tracks[i] = new FrameTrack(this, (PushBufferStream)streams[i], 1);
}
}
public void close() {
if (source != null) {
try {
source.stop();
// stop every tracks, so that readFrame() can be released.
// close every tracks to unblock the transfer handlers.
for (int i = 0; i < tracks.length; i++) {
((FrameTrack)tracks[i]).stop();
((FrameTrack)tracks[i]).close();
}
source.disconnect();
} catch (Exception e) {
// Internal error?
}
source = null;
}
started = false;
}
/**
*/
public Track [] getTracks() {
for (int i = 0; i < tracks.length; i++)
((FrameTrack)tracks[i]).parse();
return tracks;
}
/**
* Start the parser.
*/
public void start() throws IOException {
for (int i = 0; i < tracks.length; i++)
((FrameTrack)tracks[i]).start();
source.start();
started = true;
}
/**
* Stop the parser.
*/
public void stop() {
try {
source.stop();
// stop each of the tracks, so that readFrame can be released
for (int i = 0; i < tracks.length; i++)
((FrameTrack)tracks[i]).stop();
} catch (Exception e) {
// Internal errors?
}
started = false;
}
/**
* Resets the state of the plug-in. Typically at end of media or when media
* is repositioned.
*/
public void reset() {
for (int i = 0; i < tracks.length; i++)
((FrameTrack)tracks[i]).reset();
}
boolean isRTPFormat(Format fmt) {
return fmt != null &&
fmt.getEncoding() != null &&
(fmt.getEncoding().endsWith("rtp") ||
fmt.getEncoding().endsWith("RTP"));
}
/****************************************************************
* Track class
****************************************************************/
final int [] h261Widths = {176, 352};
final int [] h261Heights = {144, 288};
final int [] h263Widths = {0, 128, 176, 352, 704, 1408,0,0};
final int [] h263Heights = {0, 96, 144, 288, 576, 1152,0,0};
final float MPEGRateTbl[] = {
0.0f, 23.976f, 24.f, 25.f, 29.97f,
30.f, 50.f, 59.94f, 60.f
};
public static int[][] MPASampleTbl = {
{ 22050, 24000, 16000, 0 }, // MPEG 2
{ 44100, 48000, 32000, 0 } // MPEG 1
};
class FrameTrack implements Track, BufferTransferHandler {
Demultiplexer parser;
PushBufferStream pbs;
boolean enabled = true;
CircularBuffer bufferQ;
Format format = null;
TrackListener listener;
boolean stopped = true;
boolean closed = false;
boolean keyFrameFound = false;
boolean checkDepacketizer = false;
Depacketizer depacketizer = null;
Object keyFrameLock = new Object();
public FrameTrack(Demultiplexer parser, PushBufferStream pbs, int numOfBufs) {
this.pbs = pbs;
format = pbs.getFormat();
if (source instanceof com.sun.media.protocol.DelegateDataSource ||
!isRTPFormat(format)) {
keyFrameFound = true;
}
bufferQ = new CircularBuffer(numOfBufs);
pbs.setTransferHandler(this);
//System.err.println("Track format is " + format);
}
public Format getFormat() {
return format;
}
public void setEnabled(boolean t) {
if (t)
pbs.setTransferHandler(this);
else
pbs.setTransferHandler(null);
enabled = t;
}
public boolean isEnabled() {
return enabled;
}
public Time getDuration() {
return parser.getDuration();
}
public Time getStartTime() {
return new Time(0);
}
public void setTrackListener(TrackListener l) {
listener = l;
}
/**
* Peek into the data stream to parse the data format.
*/
public void parse() {
try {
synchronized (keyFrameLock) {
while (!keyFrameFound)
keyFrameLock.wait();
}
} catch (Exception e) {
}
}
private Depacketizer findDepacketizer(String name, Format input) {
Class cls;
Object obj;
try {
cls = BasicPlugIn.getClassForName(name);
obj = cls.newInstance();
if (!(obj instanceof Depacketizer))
return null;
Depacketizer dpktizer = (Depacketizer)obj;
if (dpktizer.setInputFormat(input) == null)
return null;
dpktizer.open();
return dpktizer;
} catch (Exception e) {
} catch (Error e) { }
return null;
}
private boolean findKeyFrame(Buffer buf) {
if (!checkDepacketizer) {
// Check to see if there's a depacketizer associated
// with the format. If so, we'll use it for parsing
// the input.
Vector pnames = PlugInManager.getPlugInList(buf.getFormat(),
null, Depacketizer.DEPACKETIZER);
if (pnames.size() != 0) {
depacketizer = findDepacketizer((String)pnames.elementAt(0),
buf.getFormat());
}
checkDepacketizer = true;
}
Format fmt = buf.getFormat();
if (fmt == null)
return false;
if (fmt.getEncoding() == null) {
synchronized (keyFrameLock) {
keyFrameFound = true;
keyFrameLock.notifyAll();
}
return true;
}
boolean rtn = true;
if (jpegVideo.matches(fmt))
rtn = findJPEGKey(buf);
else if (h261Video.matches(fmt))
rtn = findH261Key(buf);
else if (h263Video.matches(fmt))
rtn = findH263Key(buf);
else if (h263_1998Video.matches(fmt))
rtn = findH263_1998Key(buf);
else if (mpegVideo.matches(fmt))
rtn = findMPEGKey(buf);
else if (mpegAudio.matches(fmt))
rtn = findMPAKey(buf);
else if (depacketizer != null) {
fmt = depacketizer.parse(buf);
if (fmt != null) {
// Found the format. We are done with
// the depacketizer.
format = fmt;
buf.setFormat(format);
depacketizer.close();
depacketizer = null;
} else
rtn = false;
}
if (rtn) {
synchronized (keyFrameLock) {
keyFrameFound = true;
keyFrameLock.notifyAll();
}
}
return keyFrameFound;
}
/**
* Parse the RTP/JPEG stream
* Code taken from com.sun.media.codec.video.jpeg.RTPDePacketizer.
*/
public boolean findJPEGKey(Buffer b) {
if ((b.getFlags() & Buffer.FLAG_RTP_MARKER) == 0)
return false;
int width, height;
byte data[];
data = (byte[])b.getData();
width = (data[b.getOffset() + 6] & 0xff) * 8;
height = (data[b.getOffset() + 7] & 0xff) * 8;
format = new VideoFormat(VideoFormat.JPEG_RTP,
new Dimension(width, height),
((VideoFormat)format).getMaxDataLength(),
((VideoFormat)format).getDataType(),
((VideoFormat)format).getFrameRate());
b.setFormat(format);
return true;
}
/**
* Parse the RTP/H261 stream
* Code taken from com.sun.media.codec.video.h261.NativeDecoder.
*/
public boolean findH261Key(Buffer b) {
int width, height, payloadLen, offset, skipBytes;
byte data[];
if ((data = (byte[])b.getData()) == null)
return false;
offset = b.getOffset();
// Get to the actual h261 compressed payload
skipBytes = 4;
if ( (data[offset+skipBytes] != 0) ||
(data[offset+skipBytes+1] != 1) ||
((data[offset+skipBytes+2] & 0xfc) != 0) ) {
return false;
}
int s = (data[offset+skipBytes+3]>>3)&0x01;
width = h261Widths[s];
height = h261Heights[s];
format = new VideoFormat(VideoFormat.H261_RTP,
new Dimension(width, height),
((VideoFormat)format).getMaxDataLength(),
((VideoFormat)format).getDataType(),
((VideoFormat)format).getFrameRate());
b.setFormat(format);
return true;
}
/**
* Parse the RTP/H263 stream
* Code taken from com.ibm.media.codec.video.h263.JavaDecoder.
*/
public boolean findH263Key(Buffer b) {
int width, height, payloadLen, offset;
byte data[];
if ((data = (byte[])b.getData()) == null)
return false;
payloadLen = getH263PayloadHeaderLength(data, b.getOffset());
offset = b.getOffset();
if ((data[offset+payloadLen] != 0) ||
(data[offset+payloadLen+1] != 0) ||
((data[offset+payloadLen+2] & 0xfc) != 0x80))
return false;
int s = (data[offset+payloadLen+4] >> 2) & 0x7;
width = h263Widths[s];
height = h263Heights[s];
format = new VideoFormat(VideoFormat.H263_RTP,
new Dimension(width, height),
((VideoFormat)format).getMaxDataLength(),
((VideoFormat)format).getDataType(),
((VideoFormat)format).getFrameRate());
b.setFormat(format);
return true;
}
int getH263PayloadHeaderLength(byte[] input,int offset) {
int l = 0;
byte b = input[offset];
if ( (b & 0x80) != 0) { //mode B or C
if ((b & 0x40) != 0) //mode C
l = 12;
else //mode B
l = 8;
} else { //mode A
l = 4;
}
return l;
}
/**
* Parse the RTP/H263-1998 stream
* Code taken from com.sun.media.codec.video.h263.NativeDecoder.
*/
public boolean findH263_1998Key(Buffer b) {
int width, height, payloadLen, offset;
byte data[];
int s = -1;
int picOffset = -1;
if ((data = (byte[])b.getData()) == null)
return false;
offset = b.getOffset();
// 2 bytes for H263-1998 header + pLen from header
payloadLen = 2 + (((data[offset] & 0x01) << 5)
| ((data[offset+1] & 0xf8) >> 3));
if ( (data[offset] & 0x02) != 0) { // Video Redundancy present
payloadLen++;
}
picOffset = -1;
if (payloadLen > 5) {
// Use PIC header in payload header
if ( ((data[offset] & 0x02) == 0x02)
&& ((data[offset+3] & 0xfc) == 0x80)) {
picOffset = offset + 3;
} else if ((data[offset+2] & 0xfc) == 0x80) {
picOffset = offset + 2;
}
} else if ( ((data[offset] & 0x04) == 0x04)
&& ((data[offset+payloadLen] & 0xfc) == 0x80)) {
picOffset = offset + payloadLen;
}
if (picOffset < 0)
return false;
s = (data[picOffset+2] >> 2) & 0x7;
if (s == 7) {
// Extended PTYPE, picture size is in the extension
// if UFEP = 001
if (((data[picOffset+3] >> 1) & 0x07) == 1) {
s = ((data[picOffset+3] << 2) & 0x04) |
((data[picOffset+4] >> 6) & 0x03);
} else {
return false; // picture type not present
}
}
if (s < 0)
return false;
width = h263Widths[s];
height = h263Heights[s];
format = new VideoFormat(VideoFormat.H263_1998_RTP,
new Dimension(width, height),
((VideoFormat)format).getMaxDataLength(),
((VideoFormat)format).getDataType(),
((VideoFormat)format).getFrameRate());
b.setFormat(format);
return true;
}
/**
* Parse the RTP/MPEG video stream
* Code taken from com.sun.media.codec.video.mpeg.DePacketizer.
*/
public boolean findMPEGKey(Buffer b) {
int width, height;
float frameRate;
byte data[];
if ((data = (byte[])b.getData()) == null) {
return false;
}
int off = b.getOffset();
if (b.getLength() < 12) {
return false; // can't contain MPEG sequence header
}
if ((data[off+2] & 0x20) != 0x20) {
return false; // doesn't contain MPEG sequence header
}
if (data[off+4] != 0
|| data[off+5] != 0
|| data[off+6] != 1
|| (data[off+7] & 0xff) != 0xb3) {
return false; // doesn't start with MPEG sequence header
}
int frix = (data[off+11] & 0x0f);
if (frix == 0 || frix > 8) {
return false; // not a valid frame rate
}
width = ((data[off+8] & 0xff) << 4) |
((data[off+9] & 0xf0) >> 4);
height = ((data[off+9] & 0x0f) << 8) | (data[off+10] & 0xff);
frameRate = MPEGRateTbl[frix];
format = new VideoFormat(VideoFormat.MPEG_RTP,
new Dimension(width, height),
((VideoFormat)format).getMaxDataLength(),
((VideoFormat)format).getDataType(),
frameRate);
b.setFormat(format);
return true;
}
/**
* Parse the RTP/MPEG audio stream
* Code taken from com.sun.media.codec.audio.mpa.DePacketizer.
*/
public boolean findMPAKey(Buffer b) {
int channels;
double sampleRate;
byte data[];
if ((data = (byte[])b.getData()) == null)
return false;
int off = b.getOffset();
if (b.getLength() < 8)
return false; // doesn't contain MPA header
if (data[off+2] != 0 || data[off+3] != 0)
return false; // frame continuation
off += 4; // skip RTP header to get to MPA header
if ((data[off] & 0xff) != 0xff
|| (data[off+1] & 0xf6) <= 0xf0
|| (data[off+2] & 0xf0) == 0xf0
|| (data[off+2] & 0x0c) == 0x0c
|| (data[off+3] & 0x03) == 0x02)
return false; // doesn't start with a valid MPA header
int id = (data[off+1] >> 3) & 1; // MPEG1 or MPEG2
int six = (data[off+2] >> 2) & 3;
channels = (((data[off+3] >> 6) & 3) == 3) ? 1 : 2;
sampleRate = MPASampleTbl[id][six];
format = new AudioFormat(AudioFormat.MPEG_RTP,
sampleRate, 16, channels,
AudioFormat.LITTLE_ENDIAN,
AudioFormat.SIGNED);
b.setFormat(format);
return true;
}
public void stop(){
// we basically need to ensure that readFrame will return
// immediately.and also make sure that if it is called in
// the stopped state, it returns w/o blocking.
synchronized (bufferQ){
stopped = true;
bufferQ.notifyAll();
}
}
public void start(){
// we need to ensure that readFrame is returned to its
// original state and does not return w/o blocking.
synchronized (bufferQ){
stopped = false;
if (source instanceof CaptureDevice) {
// Flush the buffer Q.
while (bufferQ.canRead()) {
bufferQ.read();
bufferQ.readReport();
}
}
bufferQ.notifyAll();
}
}
public void close() {
// Unblock the transfer handlers.
setEnabled(false);
synchronized (bufferQ) {
closed = true;
bufferQ.notifyAll();
}
}
public void reset() {
}
public void readFrame(Buffer buffer) {
// Retrieve a filled buffer.
if (stopped) {
buffer.setDiscard(true);
buffer.setFormat(format);
return;
}
Buffer filled;
synchronized (bufferQ) {
while (!bufferQ.canRead()) {
try {
bufferQ.wait();
if (stopped){
buffer.setDiscard(true);
buffer.setFormat(format);
return;
}
} catch (Exception e) {}
}
filled = bufferQ.read();
}
// Copy all the attributes from filled to buffer.
Object hdr = buffer.getHeader();
buffer.copy(filled, true);
filled.setHeader(hdr);
//if ((buffer.getFlags() & Buffer.FLAG_RTP_MARKER) != 0)
//System.err.println("RBP: TS: " + buffer.getTimeStamp());
// Update the saved format.
format = filled.getFormat();
synchronized (bufferQ) {
bufferQ.readReport();
bufferQ.notifyAll();
}
}
public int mapTimeToFrame(Time t) {
return -1;
}
public Time mapFrameToTime(int frameNumber) {
return new Time(0);
}
public void transferData(PushBufferStream pbs) {
// Retrieve an empty buffer for the PSS to write into.
Buffer buffer;
synchronized (bufferQ) {
while (!bufferQ.canWrite() && !closed) {
try {
bufferQ.wait();
} catch (Exception e) {}
}
// If source is null, the data source has been closed.
if (closed)
return;
buffer = bufferQ.getEmptyBuffer();
}
try {
pbs.read(buffer);
} catch (IOException e) {
buffer.setDiscard(true);
}
// Until we find the first key frame, we won't
// add it to the buffer queue.
if (!keyFrameFound && !findKeyFrame(buffer)) {
synchronized (bufferQ) {
// Discard that buffer.
bufferQ.writeReport();
bufferQ.read();
bufferQ.readReport();
}
return;
}
// Put the filled buffer back to the queue for consumption.
synchronized (bufferQ) {
bufferQ.writeReport();
bufferQ.notifyAll();
}
}
}
}
|