📄 qtparser.java
字号:
package net.sf.fmj.qt;import java.awt.Dimension;import java.awt.image.BufferedImage;import java.io.File;import java.io.IOException;import java.util.logging.Level;import java.util.logging.Logger;import javax.media.BadHeaderException;import javax.media.Buffer;import javax.media.Duration;import javax.media.Format;import javax.media.IncompatibleSourceException;import javax.media.ResourceUnavailableException;import javax.media.Time;import javax.media.Track;import javax.media.format.AudioFormat;import javax.media.format.RGBFormat;import javax.media.format.VideoFormat;import javax.media.protocol.ContentDescriptor;import javax.media.protocol.DataSource;import javax.media.protocol.FileTypeDescriptor;import javax.media.protocol.PullDataSource;import javax.media.util.ImageToBuffer;import quicktime.QTException;import net.sf.fmj.media.AbstractDemultiplexer;import net.sf.fmj.media.AbstractTrack;import net.sf.fmj.utility.LoggerSingleton;import net.sf.fmj.utility.URLUtils;/** * * @author Ken Larson * */public class QTParser extends AbstractDemultiplexer { private static final Logger logger = LoggerSingleton.logger; private static final boolean ENABLE_AUDIO = false; // if USE_DATASOURCE_URL_ONLY is true, this is a bit of a hack - we don't really use the DataSource, we just grab its URL. So arbitrary data sources won't work. private final boolean USE_DATASOURCE_URL_ONLY = true; private ContentDescriptor[] supportedInputContentDescriptors = new ContentDescriptor[] { new ContentDescriptor(FileTypeDescriptor.MSVIDEO), // .avi new ContentDescriptor(FileTypeDescriptor.QUICKTIME), // .mov // TODO: others // TODO: query dynamically. }; private QTSnapper qtSnapper; public QTParser() { } private static final Object QT_SYNC_OBJ = new Boolean(true); // synchronize on this before using the libraries, to prevent threading problems. private PullDataSource source; private PullSourceStreamTrack[] tracks; @Override public ContentDescriptor[] getSupportedInputContentDescriptors() { return supportedInputContentDescriptors; } @Override public Track[] getTracks() throws IOException, BadHeaderException { return tracks; } @Override public void setSource(DataSource source) throws IOException, IncompatibleSourceException { final String protocol = source.getLocator().getProtocol(); if (USE_DATASOURCE_URL_ONLY) { if (!(protocol.equals("file")) && !(protocol.equals("http"))) throw new IncompatibleSourceException(); } else { if (!(source instanceof PullDataSource)) throw new IncompatibleSourceException(); } this.source = (PullDataSource) source; } // @Override @Override public void open() throws ResourceUnavailableException { synchronized (QT_SYNC_OBJ) { try { final String urlStr; if (USE_DATASOURCE_URL_ONLY) { // just use the file URL from the datasource // FMJ supports relative file URLs, but not sure if qt does. So we'll rewrite the URL here: // TODO: perhaps we should only do this if qt has a problem. if (source.getLocator().getProtocol().equals("file")) urlStr = URLUtils.createUrlStr(new File(URLUtils.extractValidPathFromFileUrl(source.getLocator().toExternalForm()))); else urlStr = source.getLocator().toExternalForm(); qtSnapper = new QTSnapper(urlStr); } else { throw new RuntimeException(); } VideoTrack videoTrack = null; AudioTrack audioTrack = null; videoTrack = new VideoTrack(); if (audioTrack == null && videoTrack == null) throw new ResourceUnavailableException("No audio or video track found"); else if (audioTrack != null && videoTrack != null) tracks = new PullSourceStreamTrack[] { videoTrack, audioTrack }; else if (audioTrack != null) tracks = new PullSourceStreamTrack[] { audioTrack }; else tracks = new PullSourceStreamTrack[] { videoTrack }; } catch (Exception e) { logger.log(Level.WARNING, "" + e, e); throw new ResourceUnavailableException("" + e); } } super.open(); } @Override public void close() { synchronized (QT_SYNC_OBJ) { if (tracks != null) { for (int i = 0; i < tracks.length; ++i) { if (tracks[i] != null) { tracks[i].deallocate(); tracks[i] = null; } } tracks = null; } } super.close(); } // @Override @Override public void start() throws IOException { } // TODO: should we stop data source in stop?// // @Override// public void stop()// {// try // {// source.stop();// } catch (IOException e) // {// logger.log(Level.WARNING, "" + e, e);// }// } @Override public boolean isPositionable() { return false; // TODO } // @Override// public Time setPosition(Time where, int rounding)// {// synchronized (QT_SYNC_OBJ)// { // // }// } @Override public boolean isRandomAccess() { return super.isRandomAccess(); // TODO: can we determine this from the data source? } public static VideoFormat convertCodecPixelFormat(QTSnapper qtSnapper) { // resulting format based on what QTSnapper will return. Depends on a bit of internal // knowledge of how QTSnapper and ImageToBuffer work. // TODO: we are ignoring any cropping here. //final Dimension size = new Dimension(128, 128); final Dimension size = new Dimension(qtSnapper.getImageWidth(), qtSnapper.getImageHeight()); final int maxDataLength; final Class dataType; final int bitsPerPixel; final float frameRate = -1.f; // TODO final int red; final int green; final int blue; // QTSnapper returns BufferedImage.TYPE_INT_RGB final int bufferedImageType = BufferedImage.TYPE_INT_RGB; if (bufferedImageType == BufferedImage.TYPE_3BYTE_BGR) { maxDataLength = qtSnapper.getImageWidth() * qtSnapper.getImageHeight() * 3; dataType = Format.byteArray; bitsPerPixel = 24; red = 1; green = 2; blue = 3; } else if (bufferedImageType == BufferedImage.TYPE_INT_BGR) { maxDataLength = qtSnapper.getImageWidth() * qtSnapper.getImageHeight(); dataType = Format.intArray; bitsPerPixel = 32; // TODO: test red = 0xFF; green = 0xFF00; blue = 0xFF0000; } else if (bufferedImageType == BufferedImage.TYPE_INT_RGB) { maxDataLength = qtSnapper.getImageWidth() * qtSnapper.getImageHeight(); dataType = Format.intArray; bitsPerPixel = 32; red = 0xFF0000; green = 0xFF00; blue = 0xFF; } else if (bufferedImageType == BufferedImage.TYPE_INT_ARGB) { maxDataLength = qtSnapper.getImageWidth() * qtSnapper.getImageHeight(); dataType = Format.intArray; bitsPerPixel = 32; red = 0xFF0000; green = 0xFF00; blue = 0xFF; // just ignore alpha } else throw new IllegalArgumentException("Unsupported buffered image type: " + bufferedImageType); return new RGBFormat(size, maxDataLength, dataType, frameRate, bitsPerPixel, red, green, blue); } private abstract class PullSourceStreamTrack extends AbstractTrack { public abstract void deallocate(); } private class VideoTrack extends PullSourceStreamTrack { // TODO: track listener private final VideoFormat format; public VideoTrack() throws ResourceUnavailableException { super(); synchronized (QT_SYNC_OBJ) { // set format format = convertCodecPixelFormat(qtSnapper); } } @Override public void deallocate() { } /** * * @return nanos skipped, 0 if unable to skip. * @throws IOException */ public long skipNanos(long nanos) throws IOException { return 0; // TODO } public boolean canSkipNanos() { return false; } @Override public Format getFormat() { return format; }// TODO: from JAVADOC:// This method might block if the data for a complete frame is not available. It might also block if the stream contains intervening data for a different interleaved Track. Once the other Track is read by a readFrame call from a different thread, this method can read the frame. If the intervening Track has been disabled, data for that Track is read and discarded.//// Note: This scenario is necessary only if a PullDataSource Demultiplexer implementation wants to avoid buffering data locally and copying the data to the Buffer passed in as a parameter. Implementations might decide to buffer data and not block (if possible) and incur data copy overhead. @Override public void readFrame(Buffer buffer) { synchronized (QT_SYNC_OBJ) { BufferedImage bi; try { qtSnapper.next(); bi = qtSnapper.getFrame(); } catch (QTException e) { throw new RuntimeException(e); // TODO: how to handle. } if (bi != null) { final Buffer b = ImageToBuffer.createBuffer(bi, format.getFrameRate()); buffer.setData(b.getData()); buffer.setLength(b.getLength()); buffer.setOffset(b.getOffset()); buffer.setEOM(false); buffer.setDiscard(false); buffer.setTimeStamp((qtSnapper.getFrameTime() * 1000000000L) / qtSnapper.getTimeScale()); } else { buffer.setEOM(true); buffer.setLength(0); } } } @Override public Time mapFrameToTime(int frameNumber) { return TIME_UNKNOWN; } @Override public int mapTimeToFrame(Time t) { return FRAME_UNKNOWN; } @Override public Time getDuration() { synchronized (QT_SYNC_OBJ) { if (qtSnapper.getDuration() <= 0 || qtSnapper.getTimeScale() <= 0) return Duration.DURATION_UNKNOWN; return new Time((qtSnapper.getDuration() * 1000000000L) / qtSnapper.getTimeScale()); } } } private class AudioTrack extends PullSourceStreamTrack { // TODO: track listener private final AudioFormat format; public AudioTrack() throws ResourceUnavailableException { super(); synchronized (QT_SYNC_OBJ) { format = null; // TODO } } @Override public void deallocate() { } // TODO: implement seeking using av_seek_frame /** * * @return nanos skipped, 0 if unable to skip. * @throws IOException */ public long skipNanos(long nanos) throws IOException { return 0; } public boolean canSkipNanos() { return false; } @Override public Format getFormat() { return format; }// TODO: from JAVADOC:// This method might block if the data for a complete frame is not available. It might also block if the stream contains intervening data for a different interleaved Track. Once the other Track is read by a readFrame call from a different thread, this method can read the frame. If the intervening Track has been disabled, data for that Track is read and discarded.//// Note: This scenario is necessary only if a PullDataSource Demultiplexer implementation wants to avoid buffering data locally and copying the data to the Buffer passed in as a parameter. Implementations might decide to buffer data and not block (if possible) and incur data copy overhead. @Override public void readFrame(Buffer buffer) { synchronized (QT_SYNC_OBJ) { throw new UnsupportedOperationException(); } } @Override public Time mapFrameToTime(int frameNumber) { return TIME_UNKNOWN; } @Override public int mapTimeToFrame(Time t) { return FRAME_UNKNOWN; } @Override public Time getDuration() { return Duration.DURATION_UNKNOWN; // TODO } } // private static final double secondsToNanos(double secs)// { return secs * 1000000000.0;// }}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -