📄 nativeoggparser.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.jna.OggLibrary;import net.sf.theora_java.jna.TheoraLibrary;import net.sf.theora_java.jna.VorbisLibrary;import net.sf.theora_java.jna.OggLibrary.ogg_page;import net.sf.theora_java.jna.OggLibrary.ogg_stream_state;import net.sf.theora_java.jna.OggLibrary.ogg_sync_state;import net.sf.theora_java.jna.TheoraLibrary.theora_comment;import net.sf.theora_java.jna.TheoraLibrary.theora_info;import net.sf.theora_java.jna.TheoraLibrary.theora_state;import net.sf.theora_java.jna.TheoraLibrary.yuv_buffer;import net.sf.theora_java.jna.VorbisLibrary.vorbis_block;import net.sf.theora_java.jna.VorbisLibrary.vorbis_comment;import net.sf.theora_java.jna.VorbisLibrary.vorbis_dsp_state;import net.sf.theora_java.jna.VorbisLibrary.vorbis_info;import net.sf.theora_java.jna.XiphLibrary.ogg_packet;import net.sf.theora_java.jna.utils.YUVConverter;import com.sun.jna.NativeLong;import com.sun.jna.Pointer;import com.sun.jna.ptr.PointerByReference;/** * Uses theora-jna to parse Ogg files, and decode vorbis and theora data within them. * Adapted from theora-jna's PlayerExample, which is adapted from player_example.c. * @author Ken Larson * */public class NativeOggParser extends AbstractDemultiplexer { private static final Logger logger = LoggerSingleton.logger; private final TheoraLibrary THEORA; private final OggLibrary OGG; private final VorbisLibrary VORBIS; 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 ogg_sync_state oy = new ogg_sync_state(); private final ogg_page og = new ogg_page(); private ogg_stream_state vo = new ogg_stream_state(); private ogg_stream_state to = new ogg_stream_state(); private final theora_info ti = new theora_info(); private final theora_comment tc = new theora_comment(); private final theora_state td = new theora_state(); private final vorbis_info vi = new vorbis_info(); private final vorbis_dsp_state vd = new vorbis_dsp_state(); private final vorbis_block vb = new vorbis_block(); private vorbis_comment vc = new vorbis_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 ogg_packet op = new ogg_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 NativeOggParser() { try { THEORA = TheoraLibrary.INSTANCE; OGG = OggLibrary.INSTANCE; VORBIS = VorbisLibrary.INSTANCE; } catch (Throwable t) { logger.log(Level.WARNING, "Unable to initialize ffmpeg libraries: " + t); throw new RuntimeException(t); } } 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(ogg_sync_state 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, ogg_sync_state oy) throws IOException { final int BUFSIZE = 4096; Pointer buffer = OGG.ogg_sync_buffer(oy, new NativeLong(BUFSIZE)); byte[] buffer2 = new byte[BUFSIZE]; // TODO: this is inefficient. int bytes = in.read(buffer2, 0, BUFSIZE); if (bytes < 0) return bytes; // EOF buffer.write(0, buffer2, 0, bytes); OGG.ogg_sync_wrote(oy, new NativeLong(bytes)); return (bytes); } private int buffer_data(PullSourceStream in, ogg_sync_state oy) throws IOException { final int BUFSIZE = 4096; Pointer buffer = OGG.ogg_sync_buffer(oy, new NativeLong(BUFSIZE)); byte[] buffer2 = new byte[BUFSIZE]; // TODO: this is inefficient. int bytes = in.read(buffer2, 0, BUFSIZE); if (bytes < 0) return bytes; // EOF buffer.write(0, buffer2, 0, bytes); OGG.ogg_sync_wrote(oy, new NativeLong(bytes)); return (bytes); } /** dump the theora (or vorbis) comment header */ int dump_comments(theora_comment tc) { int i, len; logger.info("Encoded by " + tc.vendor.getString(0)); if (tc.comments > 0) { logger.info("theora comment header:"); for (i = 0; i < tc.comments; i++) { final Pointer[] user_comments = tc.user_comments.getPointerArray(0, tc.comments); final int[] comment_lengths = tc.comment_lengths.getIntArray(0, tc.comments); if (user_comments[i] != null) { len = comment_lengths[i]; final String value = new String(user_comments[i].getByteArray(0, len)); 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. */ static void report_colorspace(theora_info ti) { switch (ti.colorspace) { case TheoraLibrary.OC_CS_UNSPECIFIED: /* nothing to report */ break; case TheoraLibrary.OC_CS_ITU_REC_470M: logger.info(" encoder specified ITU Rec 470M (NTSC) color."); break; case TheoraLibrary.OC_CS_ITU_REC_470BG: logger.info(" encoder specified ITU Rec 470BG (PAL) color."); break; default: logger.warning("warning: encoder specified unknown colorspace (" + ti.colorspace + ")"); break; } } /** 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 */ int queue_page(ogg_page page) { if (theora_p != 0) OGG.ogg_stream_pagein(to, og); if (vorbis_p != 0) OGG.ogg_stream_pagein(vo, og); return 0; } // @Override @Override public void open() throws ResourceUnavailableException { synchronized (OGG_SYNC_OBJ) { 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 */ OGG.ogg_sync_init(oy); /* init supporting Vorbis structures needed in header parsing */ VORBIS.vorbis_info_init(vi); VORBIS.vorbis_comment_init(vc); /* init supporting Theora structures needed in header parsing */ THEORA.theora_comment_init(tc); THEORA.theora_info_init(ti); // 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 (OGG.ogg_sync_pageout(oy, og) > 0) { ogg_stream_state test = new ogg_stream_state(); /* * is this a mandated initial header? If not, stop * parsing */ if (OGG.ogg_page_bos(og) == 0) { /* * don't leak the page; get it into the appropriate * stream */ queue_page(og); stateflag = 1; break; } OGG.ogg_stream_init(test, OGG.ogg_page_serialno(og)); OGG.ogg_stream_pagein(test, og); OGG.ogg_stream_packetout(test, op); /* identify the codec: try theora */ if (ENABLE_VIDEO && theora_p == 0 && THEORA.theora_decode_header(ti, tc, op) >= 0) { /* it is theora */ to = test; theora_p = 1; } else if (ENABLE_AUDIO && vorbis_p == 0 && VORBIS.vorbis_synthesis_headerin(vi, vc, op) >= 0) { /* it is vorbis */ vo = test; vorbis_p = 1; } else { /* whatever it is, we don't care about it */ OGG.ogg_stream_clear(test); } } /* 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 = OGG.ogg_stream_packetout(to, op))) != 0) { if (ret < 0) { throw new ResourceUnavailableException("Error parsing Theora stream headers; corrupt stream?"); } if (THEORA.theora_decode_header(ti, 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 = OGG.ogg_stream_packetout(vo, op))) != 0) { if (ret < 0) { throw new ResourceUnavailableException("Error parsing Vorbis stream headers; corrupt stream?"); } if (VORBIS.vorbis_synthesis_headerin(vi, 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 (OGG.ogg_sync_pageout(oy, 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."); } } } /* and now we have it all. initialize decoders */ if (theora_p != 0) { THEORA.theora_decode_init(td, ti); final double fps = (double) ti.fps_numerator / (double) ti.fps_denominator; logger.info("Ogg logical stream " + Integer.toHexString( to.serialno.intValue()) + " is Theora " + ti.width + "x" + ti.height + " " + fps + " fps"); switch (ti.pixelformat) { case TheoraLibrary.OC_PF_420: logger.info(" 4:2:0 video"); break; case TheoraLibrary.OC_PF_422: logger.info(" 4:2:2 video"); break; case TheoraLibrary.OC_PF_444: logger.info(" 4:4:4 video"); break; case TheoraLibrary.OC_PF_RSVD: default: logger.info(" video\n (UNKNOWN Chroma sampling!)"); 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 */ THEORA.theora_info_clear(ti); THEORA.theora_comment_clear(tc); } if (vorbis_p != 0) { VORBIS.vorbis_synthesis_init(vd, vi); VORBIS.vorbis_block_init(vd, vb); logger.info("Ogg logical stream " + Integer.toHexString(vo.serialno.intValue()) + " is Vorbis " + vi.channels + " channel " + vi.rate.intValue() + " Hz audio."); } else { /* tear down the partial vorbis setup */ VORBIS.vorbis_info_clear(vi); VORBIS.vorbis_comment_clear(vc); } 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) { OGG.ogg_stream_clear(vo); VORBIS.vorbis_block_clear(vb); VORBIS.vorbis_dsp_clear(vd); VORBIS.vorbis_comment_clear(vc); VORBIS.vorbis_info_clear(vi); } if (theora_p != 0) { OGG.ogg_stream_clear(to); THEORA.theora_clear(td); THEORA.theora_comment_clear(tc); THEORA.theora_info_clear(ti); } OGG.ogg_sync_clear(oy); try { if (infile != null) infile.close(); } catch (IOException e) { logger.log(Level.WARNING, "" + e, e); } } super.close(); } // @Override @Override public void start() throws IOException { } // TODO: should we stop data source in stop?
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -