2 * Copyright (c) 2003-2009 jMonkeyEngine
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
9 * * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
12 * * Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17 * may be used to endorse or promote products derived from this software
18 * without specific prior written permission.
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 package com.jmex.audio.stream;
35 import java.io.IOException;
37 import java.nio.ByteBuffer;
38 import java.nio.ByteOrder;
39 import java.util.Iterator;
40 import java.util.logging.Level;
41 import java.util.logging.Logger;
43 import com.jcraft.jogg.Packet;
44 import com.jcraft.jogg.Page;
45 import com.jcraft.jogg.StreamState;
46 import com.jcraft.jogg.SyncState;
47 import com.jcraft.jorbis.Block;
48 import com.jcraft.jorbis.Comment;
49 import com.jcraft.jorbis.DspState;
50 import com.jcraft.jorbis.Info;
51 import com.jmex.audio.filter.Filter;
54 * Decompresses an Ogg file as it streams from a source.
56 * @author Arman Ozcelik
57 * @author Joshua Slack
58 * @version $Id: OggInputStream.java,v 1.4 2007/08/20 10:28:29 rherlitz Exp $
60 public class OggInputStream extends AudioInputStream {
61 private static final Logger logger = Logger.getLogger(OggInputStream.class
65 private float[][][] _pcm = new float[1][][];
69 private boolean eos = false;
71 // sync and verify incoming physical bitstream
72 private SyncState syncState = new SyncState();
74 // take physical pages, weld into a logical stream of packets
75 private StreamState streamState = new StreamState();
77 // one Ogg bitstream page. Vorbis packets are inside
78 private Page page = new Page();
80 // one raw packet of data for decode
81 private Packet packet = new Packet();
83 // struct that stores all the static vorbis bitstream settings
84 private Info info = new Info();
86 // struct that stores all the bitstream user comments
87 private Comment comment = new Comment();
89 // central working state for the packet->PCM decoder
90 private DspState dspState = new DspState();
92 // local working space for packet->PCM decode
93 private Block block = new Block(dspState);
95 /// Conversion buffer size
96 private int convsize = 4096 * 6;
99 private byte[] convbuffer = new byte[convsize];
101 // where we are in the convbuffer
102 private int convbufferOff = 0;
104 // bytes ready in convbuffer.
105 private int convbufferSize = 0;
107 // a dummy used by read() to read 1 byte.
108 private byte readDummy[] = new byte[1];
112 * Creates an OggInputStream that decompressed the specified ogg file.
113 * @throws IOException
115 public OggInputStream(URL resource, float length) throws IOException {
116 super(resource, length);
119 _index = new int[info.channels];
120 } catch (Exception e) {
121 logger.logp(Level.SEVERE, this.getClass().toString(),
122 "OggInputStream(URL resource, float lengt)", "Exception", e);
128 public int getBitRate() {
133 public int getDepth() {
139 * Reads the next byte of data from this input stream. The value byte is
140 * returned as an int in the range 0 to 255. If no byte is available because
141 * the end of the stream has been reached, the value -1 is returned. This
142 * method blocks until input data is available, the end of the stream is
143 * detected, or an exception is thrown.
144 * @return the next byte of data, or -1 if the end of the stream is reached.
146 public int read() throws IOException {
147 int retVal = read(readDummy, 0, 1);
148 return (retVal == -1 ? -1 : readDummy[0]);
153 * Reads up to len bytes of data from the input stream into an array of bytes.
154 * @param b the buffer into which the data is read.
155 * @param off the start offset of the data.
156 * @param len the maximum number of bytes read.
157 * @return the total number of bytes read into the buffer, or -1 if there is
158 * no more data because the end of the stream has been reached.
160 public int read(byte b[], int off, int len) throws IOException {
166 while (!eos && (len > 0)) {
170 int bytesToCopy = Math.min(len, convbufferSize-convbufferOff);
171 System.arraycopy(convbuffer, convbufferOff, b, off, bytesToCopy);
172 convbufferOff += bytesToCopy;
173 bytesRead += bytesToCopy;
184 * Reads up to len bytes of data from the input stream into a ByteBuffer.
185 * @param b the buffer into which the data is read.
186 * @param off the start offset of the data.
187 * @param len the maximum number of bytes read.
188 * @return the total number of bytes read into the buffer, or -1 if there is
189 * no more data because the end of the stream has been reached.
191 public int read(ByteBuffer b, int off, int len) throws IOException {
192 byte[] buffer = new byte[b.capacity()];
193 int bytesRead = read(buffer, off, len);
194 if (bytesRead > 0 && filters.size() > 0) {
195 Iterator<Filter> it = filters.iterator();
196 while (it.hasNext()) {
197 buffer = it.next().filter(buffer);
207 * Helper function. Decodes a packet to the convbuffer if it is empty.
208 * Updates convbufferSize, convbufferOff, and eos.
210 private void fillConvbuffer() throws IOException {
211 if (convbufferOff >= convbufferSize) {
212 convbufferSize = lazyDecodePacket();
214 if (convbufferSize == -1) {
222 * Returns 0 after EOF is reached, otherwise always return 1.
224 * Programs should not count on this method to return the actual number of
225 * bytes that could be read without blocking.
226 * @return 1 before EOF and 0 after EOF is reached.
228 public int available() throws IOException {
229 return (eos ? 0 : 1);
234 * OggInputStream does not support mark and reset. This function does nothing.
236 public void reset() throws IOException {
241 * OggInputStream does not support mark and reset.
244 public boolean markSupported() {
250 * Skips over and discards n bytes of data from the input stream. The skip
251 * method may, for a variety of reasons, end up skipping over some smaller
252 * number of bytes, possibly 0. The actual number of bytes skipped is returned.
253 * @param n the number of bytes to be skipped.
254 * @return the actual number of bytes skipped.
256 public long skip(long n) throws IOException {
258 while (bytesRead < n) {
272 * Initalizes the vorbis stream. Reads the stream until info and comment are read.
274 private void initVorbis() throws Exception {
275 // Now we can read pages
278 // grab some data at the head of the stream. We want the first page
279 // (which is guaranteed to be small and only contain the Vorbis
280 // stream initial header) We need the first page to get the stream
283 // submit a 4k block to libvorbis' Ogg layer
284 int index = syncState.buffer(4096);
285 byte buffer[] = syncState.data;
286 int bytes = in.read(buffer, index, 4096);
287 syncState.wrote(bytes);
289 // Get the first page.
290 if (syncState.pageout(page) != 1) {
291 // have we simply run out of data? If so, we're done.
295 // error case. Must not be Vorbis data
296 throw new Exception("Input does not appear to be an Ogg bitstream.");
299 // Get the serial number and set up the rest of decode.
300 // serialno first; use it to set up a logical stream
301 streamState.init(page.serialno());
303 // extract the initial header from the first page and verify that the
304 // Ogg bitstream is in fact Vorbis data
306 // I handle the initial header first instead of just having the code
307 // read all three Vorbis headers at once because reading the initial
308 // header is an easy way to identify a Vorbis bitstream and it's
309 // useful to see that functionality seperated out.
313 if (streamState.pagein(page) < 0) {
314 // error; stream version mismatch perhaps
315 throw new Exception("Error reading first page of Ogg bitstream data.");
318 if (streamState.packetout(packet) != 1) {
319 // no page? must not be vorbis
320 throw new Exception("Error reading initial header packet.");
323 if (info.synthesis_headerin(comment, packet) < 0) {
324 // error case; not a vorbis header
325 throw new Exception("This Ogg bitstream does not contain Vorbis audio data.");
328 // At this point, we're sure we're Vorbis. We've set up the logical
329 // (Ogg) bitstream decoder. Get the comment and codebook headers and
330 // set up the Vorbis decoder
332 // The next two packets in order are the comment and codebook headers.
333 // They're likely large and may span multiple pages. Thus we read
334 // and submit data until we get our two packets, watching that no
335 // pages are missing. If a page is missing, error out; losing a
336 // header page is the only place where missing data is fatal.
343 int result = syncState.pageout(page);
345 break; // Need more data
346 // Don't complain about missing or corrupt data yet. We'll
347 // catch it at the packet output phase
350 streamState.pagein(page); // we can ignore any errors here
351 // as they'll also become apparent
354 result = streamState.packetout(packet);
360 // Uh oh; data at some point was corrupted or missing!
361 // We can't tolerate that in a header. Die.
362 throw new Exception("Corrupt secondary header. Exiting.");
365 info.synthesis_headerin(comment, packet);
371 // no harm in not checking before adding more
372 index = syncState.buffer(4096);
373 buffer = syncState.data;
374 bytes = in.read(buffer, index, 4096);
376 // NOTE: This is a bugfix. read will return -1 which will mess up syncState.
381 if (bytes == 0 && i < 2) {
382 throw new Exception("End of file before finding all Vorbis headers!");
385 syncState.wrote(bytes);
388 convsize = 4096 / info.channels;
390 // OK, got and parsed all three headers. Initialize the Vorbis
391 // packet->PCM decoder.
392 dspState.synthesis_init(info); // central decode state
393 block.init(dspState); // local state for most of the decode
394 // so multiple block decodes can
395 // proceed in parallel. We could init
396 // multiple vorbis_block structures
404 private int decodePacket(Packet packet) {
405 // check the endianes of the computer.
406 final boolean bigEndian = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN;
408 if (block.synthesis(packet) == 0) {
410 dspState.synthesis_blockin(block);
413 // **pcm is a multichannel float vector. In stereo, for
414 // example, pcm[0] is left, and pcm[1] is right. samples is
415 // the size of each channel. Convert the float values
416 // (-1.<=range<=1.) to whatever PCM format and write it out
419 while ((samples = dspState.synthesis_pcmout(_pcm, _index)) > 0) {
420 //logger.info("while() 4");
421 float[][] pcm = _pcm[0];
422 int bout = (samples < convsize ? samples : convsize);
424 // convert floats to 16 bit signed ints (host order) and interleave
425 for (int i = 0; i < info.channels; i++) {
426 int ptr = (i << 1) + convOff;
429 int mono = _index[i];
431 for (int j = 0; j < bout; j++) {
432 int val = (int) (pcm[i][mono + j] * 32767.);
434 // might as well guard against clipping
435 val = Math.max(-32768, Math.min(32767, val));
436 val |= (val < 0 ? 0x8000 : 0);
438 convbuffer[ptr + 0] = (byte) (bigEndian ? val >>> 8 : val);
439 convbuffer[ptr + 1] = (byte) (bigEndian ? val : val >>> 8);
440 ptr += (info.channels << 1);
444 convOff += 2 * info.channels * bout;
446 // Tell orbis how many samples were consumed
447 dspState.synthesis_read(bout);
455 * Decodes the next packet.
456 * @return bytes read into convbuffer of -1 if end of file
458 private int lazyDecodePacket() throws IOException {
459 int result = getNextPacket(packet);
464 // we have a packet. Decode it
465 return decodePacket(packet);
470 * @param packet where to put the packet.
472 private int getNextPacket(Packet packet) throws IOException {
474 boolean fetchedPacket = false;
475 while (!eos && !fetchedPacket) {
476 int result1 = streamState.packetout(packet);
478 // no more packets in page. Fetch new page.
480 while (!eos && result2 == 0) {
481 result2 = syncState.pageout(page);
487 // return if we have reaced end of file.
488 if ((result2 == 0) && (page.eos() != 0)) {
493 // need more data fetching page..
495 } else if (result2 == -1) {
496 logger.info("syncState.pageout(page) result == -1");
500 streamState.pagein(page);
502 } else if (result1 == -1) {
503 logger.info("streamState.packetout(packet) result == -1");
506 fetchedPacket = true;
515 * Copys data from input stream to syncState.
517 private void fetchData() throws IOException {
519 // copy 4096 bytes from compressed stream to syncState.
520 int index = syncState.buffer(4096);
525 int bytes = in.read(syncState.data, index, 4096);
526 syncState.wrote(bytes);
535 * Gets information on the ogg.
537 public String toString() {
539 s = s + "version " + info.version + "\n";
540 s = s + "channels " + info.channels + "\n";
541 s = s + "rate (hz) " + info.rate ;
546 public int getChannelCount() {
547 return info.channels;
552 public OggInputStream makeNew() throws IOException {
553 return new OggInputStream(getResource(), getLength());