📄 javaoggparser.java
字号:
package net.sf.fmj.theora_java;import java.awt.Dimension;import java.awt.image.BufferedImage;import java.io.File;import java.io.FileInputStream;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.PullDataSource;import javax.media.protocol.PullSourceStream;import javax.media.util.ImageToBuffer;import net.sf.fmj.media.AbstractDemultiplexer;import net.sf.fmj.media.AbstractTrack;import net.sf.fmj.utility.LoggerSingleton;import net.sf.fmj.utility.URLUtils;import net.sf.theora_java.jheora.utils.YUVConverter;import com.fluendo.jheora.Colorspace;import com.fluendo.jheora.YUVBuffer;import com.jcraft.jogg.Packet;import com.jcraft.jogg.Page;import com.jcraft.jogg.StreamState;import com.jcraft.jogg.SyncState;/** * Uses jheora,jogg,jorbis to parse Ogg files, and decode vorbis and theora data within them. * Adapted from theora-java's jheora net.sf.theora_java.jheora.example.PlayerExample, which is adapted from player_example.c. * @author Ken Larson * */public class JavaOggParser extends AbstractDemultiplexer { private static final Logger logger = LoggerSingleton.logger; private static final boolean ENABLE_VIDEO = true; private static final boolean ENABLE_AUDIO = true; /* never forget that globals are a one-way ticket to Hell */ /* Ogg and codec state for demux/decode */ private final SyncState oy = new SyncState(); private final Page og = new Page(); private StreamState vo = new StreamState(); private StreamState to = new StreamState(); private final com.fluendo.jheora.Info ti = new com.fluendo.jheora.Info(); private final com.fluendo.jheora.Comment tc = new com.fluendo.jheora.Comment(); private final com.fluendo.jheora.State td = new com.fluendo.jheora.State(); private final com.jcraft.jorbis.Info vi = new com.jcraft.jorbis.Info(); private final com.jcraft.jorbis.DspState vd = new com.jcraft.jorbis.DspState(); private final com.jcraft.jorbis.Block vb = new com.jcraft.jorbis.Block(vd); private com.jcraft.jorbis.Comment vc = new com.jcraft.jorbis.Comment(); private int theora_p=0; private int vorbis_p=0; private int stateflag=0; /* single frame video buffering */ private int videobuf_ready=0; private long /*ogg_int64_t*/ videobuf_granulepos=-1; private double videobuf_time=0; // in seconds /* single audio fragment audio buffering */ private int audiobuf_fill=0; private int audiobuf_ready=0; private short []audiobuf; private long /*ogg_int64_t*/ audiobuf_granulepos=0; /* time position of last sample */ /** In bytes. */ private int audiofd_fragsize; /* read and write only complete fragments so that SNDCTL_DSP_GETOSPACE is accurate immediately after a bank switch */ private final Packet op = new Packet(); // 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 = false; private ContentDescriptor[] supportedInputContentDescriptors = new ContentDescriptor[] { new ContentDescriptor("video.ogg"), new ContentDescriptor("audio.ogg"), new ContentDescriptor("application.ogg"), new ContentDescriptor("application.x_ogg"), // TODO: content descriptors are problematic, because an .ogg file will be interpreted as an audio file, and // another handler may try it. // See http://wiki.xiph.org/index.php/MIME_Types_and_File_Extensions // for mime type info. }; // also, once this parser is able to simply extract the tracks, rather than just decode the data, // information on the container-less mime types is available at http://wiki.xiph.org/index.php/MIME_Types_and_File_Extensions public JavaOggParser() { super(); } private static final Object OGG_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"))) throw new IncompatibleSourceException(); } else { if (!(source instanceof PullDataSource)) throw new IncompatibleSourceException(); } this.source = (PullDataSource) source; } private FileInputStream infile; private PullSourceStream instream; private int buffer_data(SyncState oy) throws IOException { if (USE_DATASOURCE_URL_ONLY) return buffer_data(infile, oy); else return buffer_data(instream, oy); } private int buffer_data(FileInputStream in, SyncState oy) throws IOException { final int BUFSIZE = 4096; int fill = oy.buffer(BUFSIZE); byte[] buffer2 = oy.data; int bytes = in.read(buffer2, fill, BUFSIZE); if (bytes < 0) return bytes; // EOF oy.wrote(bytes); return (bytes); } private int buffer_data(PullSourceStream in, SyncState oy) throws IOException { final int BUFSIZE = 4096; int fill = oy.buffer(BUFSIZE); byte[] buffer2 = oy.data; int bytes = in.read(buffer2, fill, BUFSIZE); if (bytes < 0) return bytes; // EOF oy.wrote(bytes); return (bytes); } /** dump the theora (or vorbis) comment header */ private static int dump_comments(com.fluendo.jheora.Comment tc) { int i, len; logger.info("Encoded by " + tc.vendor); if (tc.user_comments != null && tc.user_comments.length > 0) { logger.info("theora comment header:"); for (i = 0; i < tc.user_comments.length; i++) { if (tc.user_comments[i] != null) { final String value = tc.user_comments[i]; logger.info("\t" + value); } } } return (0); } /** * Report the encoder-specified colorspace for the video, if any. We * don't actually make use of the information in this example; a real * player should attempt to perform color correction for whatever * display device it supports. */ private static void report_colorspace(com.fluendo.jheora.Info ti) { if (ti.colorspace == Colorspace.UNSPECIFIED) { /* nothing to report */ } else if (ti.colorspace == Colorspace.ITU_REC_470M) { logger.info(" encoder specified ITU Rec 470M (NTSC) color."); } else if (ti.colorspace == Colorspace.ITU_REC_470BG) { logger.info(" encoder specified ITU Rec 470BG (PAL) color."); } else { logger.warning("warning: encoder specified unknown colorspace (" + ti.colorspace + ")"); } } /** helper: push a page into the appropriate steam * this can be done blindly; a stream won't accept a page that doesn't * belong to it */ private int queue_page(Page page) { if (theora_p != 0) to.pagein(og); if (vorbis_p != 0) vo.pagein(og); return 0; } private void resetVars() { theora_p=0; vorbis_p=0; stateflag=0; /* single frame video buffering */ videobuf_ready=0; videobuf_granulepos=-1; videobuf_time=0; // in seconds /* single audio fragment audio buffering */ audiobuf_fill=0; audiobuf_ready=0; audiobuf=null; audiobuf_granulepos=0; /* time position of last sample */ // TODO: we might want to re-init others to be sure. } // @Override @Override public void open() throws ResourceUnavailableException { System.out.println("OPEN!"); synchronized (OGG_SYNC_OBJ) { resetVars(); try { final String urlStr; if (USE_DATASOURCE_URL_ONLY) { // just use the file URL from the datasource final File f = new File(URLUtils.extractValidPathFromFileUrl(source.getLocator().toExternalForm())); infile = new FileInputStream(f); } else { source.connect(); source.start(); // TODO: stop/disconnect on stop/close. instream = source.getStreams()[0]; } /* start up Ogg stream synchronization layer */ oy.init(); /* init supporting Vorbis structures needed in header parsing */ vi.init(); vc.init(); /* init supporting Theora structures needed in header parsing */ //tc.init(); //ti.init(); //System.out.println("Parsing headers..."); /* Ogg file open; parse the headers */ /* Only interested in Vorbis/Theora streams */ while (stateflag == 0) { int ret = buffer_data(oy); if (ret <= 0) break; while (oy.pageout(og) > 0) { StreamState test = new StreamState(); /* is this a mandated initial header? If not, stop parsing */ if (og.bos() == 0) { /* don't leak the page; get it into the appropriate stream */ queue_page(og); stateflag = 1; break; } test.init(og.serialno()); test.pagein(og); test.packetout(op); /* identify the codec: try theora */ if (ENABLE_VIDEO && theora_p == 0 && ti.decodeHeader(tc, op) >= 0) { /* it is theora */ to = test; theora_p = 1; } else if (ENABLE_AUDIO && vorbis_p == 0 && vi.synthesis_headerin(vc, op) >= 0) { /* it is vorbis */ vo = test; //memcpy(&vo,&test,sizeof(test)); vorbis_p = 1; } else { /* whatever it is, we don't care about it */ test.clear(); } } /* fall through to non-bos page parsing */ } /* we're expecting more header packets. */ while ((theora_p != 0 && theora_p < 3) || (vorbis_p != 0 && vorbis_p < 3)) { int ret; /* look for further theora headers */ while (theora_p != 0 && (theora_p < 3) && ((ret = to.packetout(op))) != 0) { if (ret < 0) { throw new ResourceUnavailableException("Error parsing Theora stream headers; corrupt stream?"); } if (ti.decodeHeader(tc, op) != 0) { throw new ResourceUnavailableException("Error parsing Theora stream headers; corrupt stream?"); } theora_p++; if (theora_p == 3) break; } /* look for more vorbis header packets */ while (vorbis_p != 0 && (vorbis_p < 3) && ((ret = vo.packetout(op))) != 0) { if (ret < 0) { throw new ResourceUnavailableException("Error parsing Vorbis stream headers; corrupt stream?"); } if (vi.synthesis_headerin(vc, op) != 0) { throw new ResourceUnavailableException("Error parsing Vorbis stream headers; corrupt stream?"); } vorbis_p++; if (vorbis_p == 3) break; } /* The header pages/packets will arrive before anything else we care about, or the stream is not obeying spec */ if (oy.pageout(og) > 0) { queue_page(og); /* demux into the appropriate stream */ } else { final int ret2 = buffer_data(oy); /* someone needs more data */ if (ret2 <= 0) { throw new ResourceUnavailableException("End of file while searching for codec headers."); } } } //System.out.println("Initializing decoders..."); /* and now we have it all. initialize decoders */ if (theora_p != 0) { td.decodeInit(ti); final double fps = (double) ti.fps_numerator / (double) ti.fps_denominator; logger.info("Ogg logical stream " + Integer.toHexString(getSerialNo(to)) + " is Theora " + ti.width + "x" + ti.height + " " + fps + " fps"); // TODO: jheora doesn't have pixelformat as a field of ti: // switch(ti.pixelformat){ // case TheoraLibrary.OC_PF_420: System.out.printf(" 4:2:0 video\n"); break; // case TheoraLibrary.OC_PF_422: System.out.printf(" 4:2:2 video\n"); break; // case TheoraLibrary.OC_PF_444: System.out.printf(" 4:4:4 video\n"); break; // case TheoraLibrary.OC_PF_RSVD: // default: // System.out.printf(" video\n (UNKNOWN Chroma sampling!)\n"); // break; // } if (ti.width != ti.frame_width || ti.height != ti.frame_height) { logger.warning(" Frame content is " + ti.frame_width + "x" + ti.frame_height + " with offset (" + ti.offset_x + "," + ti.offset_y + ")."); // TODO: we need to handle cropping properly. } report_colorspace(ti); dump_comments(tc); } else { /* tear down the partial theora setup */ ti.clear(); // tc.clear(); } if (vorbis_p != 0) { vd.synthesis_init(vi); vb.init(vd); logger.info("Ogg logical stream " + Integer.toHexString(getSerialNo(vo)) + " is Vorbis " + vi.channels + " channel " + vi.rate + " Hz audio."); } else { /* tear down the partial vorbis setup */ vi.clear(); // vc.clear(); } stateflag = 0; /* playback has not begun */ VideoTrack videoTrack = null; AudioTrack audioTrack = null; if (theora_p != 0) { videoTrack = new VideoTrack(); } if (vorbis_p != 0) { audioTrack = new AudioTrack(); } 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 (IOException e) { logger.log(Level.WARNING, "" + e, e); throw new ResourceUnavailableException("" + e); } } super.open(); } @Override public void close() { synchronized (OGG_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; } if (vorbis_p != 0) { vo.clear(); vb.clear(); vd.clear(); // vc.clear(); vi.clear(); } if (theora_p != 0) { to.clear(); td.clear(); //tc.clear(); ti.clear(); } oy.clear(); try { if (infile != null) infile.close(); } catch (IOException e) { logger.log(Level.WARNING, "" + e, e); } resetVars(); // this will prevent vi.clear from being called twice, which can cause an NPE } super.close(); } // @Override @Override public void start() throws IOException { } // TODO: should we stop data source in stop?// // @Override// public void stop()// {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -