diff options
Diffstat (limited to 'src/java/demos/devmaster/lesson8')
-rw-r--r-- | src/java/demos/devmaster/lesson8/Credits.txt | 12 | ||||
-rw-r--r-- | src/java/demos/devmaster/lesson8/OggDecoder.java | 276 | ||||
-rw-r--r-- | src/java/demos/devmaster/lesson8/OggStreamer.java | 262 |
3 files changed, 550 insertions, 0 deletions
diff --git a/src/java/demos/devmaster/lesson8/Credits.txt b/src/java/demos/devmaster/lesson8/Credits.txt new file mode 100644 index 0000000..36e7188 --- /dev/null +++ b/src/java/demos/devmaster/lesson8/Credits.txt @@ -0,0 +1,12 @@ + +1. This tutorial is an attempt at translating the OggVorbis streaming + tutorial at http://www.devmaster.net/articles/openal-tutorials/lesson8.php + +2. demos/lib/j-ogg-all.jar + + This software is based on or using the J-Ogg library available from + http://www.j-ogg.de and copyrighted by Tor-Einar Jarnbjo. + +3. demos/data/crickets.ogg + + This sample file is from the Freesound project at http://freesound.iua.upf.edu diff --git a/src/java/demos/devmaster/lesson8/OggDecoder.java b/src/java/demos/devmaster/lesson8/OggDecoder.java new file mode 100644 index 0000000..e1a2a5c --- /dev/null +++ b/src/java/demos/devmaster/lesson8/OggDecoder.java @@ -0,0 +1,276 @@ +/** + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * -Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * -Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS + * LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A + * RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. + * IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT + * OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR + * PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, + * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS + * BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for use in the + * design, construction, operation or maintenance of any nuclear facility. + * + */ + +package demos.devmaster.lesson8; + +import de.jarnbjo.ogg.CachedUrlStream; +import de.jarnbjo.ogg.EndOfOggStreamException; +import de.jarnbjo.ogg.LogicalOggStream; +import de.jarnbjo.vorbis.IdentificationHeader; +import de.jarnbjo.vorbis.VorbisStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.ByteBuffer; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.DataLine; +import javax.sound.sampled.SourceDataLine; + +public class OggDecoder { + + private static int BLOCK_SIZE = 4096*64; + + private VorbisStream vStream; + private LogicalOggStream loStream; + private AudioInputStream ais; + private IdentificationHeader vStreamHdr; + + private AudioFormat audioFormat; + + private URL url; + private boolean swap = false; + private boolean endOfStream = false; + + public OggDecoder(URL url) { + this.url = url; + } + + public boolean initialize() { + try { + CachedUrlStream os = new CachedUrlStream(url); + + loStream = (LogicalOggStream)os.getLogicalStreams().iterator().next(); + vStream = new VorbisStream(loStream); + vStreamHdr = vStream.getIdentificationHeader(); + + audioFormat = new AudioFormat( + (float)vStreamHdr.getSampleRate(), + 16, + vStreamHdr.getChannels(), + true, true); + + ais = new AudioInputStream( + new VorbisInputStream(vStream), audioFormat, -1); + + } catch (Exception e) { + e.printStackTrace(); + return false; + } + + return true; + } + + public int numChannels() { + return vStreamHdr.getChannels(); + } + + public int sampleRate() { + return vStreamHdr.getSampleRate(); + } + + public void setSwap(boolean swap) { + this.swap = swap; + } + + /** + * Swaps bytes. + * @throws ArrayOutOfBoundsException if len is not a multiple of 2. + */ + public static void swapBytes(byte[] b) { + swapBytes(b, 0, b.length); + } + + public static void swapBytes(byte[] b, int off, int len) { + + byte tempByte; + for (int i = off; i < (off+len); i+=2) { + + tempByte = b[i]; + b[i] = b[i+1]; + b[i+1] = tempByte; + } + } + + // play using JavaSound + public void play() { + if (!initialize()) + return; + + dump(); + + try { + DataLine.Info dataLineInfo = + new DataLine.Info(SourceDataLine.class, audioFormat); + + SourceDataLine sourceDataLine = + (SourceDataLine)AudioSystem.getLine(dataLineInfo); + + sourceDataLine.open(audioFormat); + sourceDataLine.start(); + + byte[] buffer = new byte[BLOCK_SIZE]; + int bytesRead; + + while (true) { + if ((bytesRead = read(buffer)) > 0) + sourceDataLine.write(buffer, 0, bytesRead); + + if (bytesRead < buffer.length) + break; + } + + sourceDataLine.drain(); + sourceDataLine.close(); + } catch(Exception e) { + e.printStackTrace(); + } + } + + // play using JavaSound + public void toraw(String fileName) { + if (!initialize()) + return; + + setSwap(true); + dump(); + + try { + byte[] buffer = new byte[BLOCK_SIZE]; + int bytesRead; + + FileOutputStream fos = new FileOutputStream(fileName); + + while (true) { + if ((bytesRead = read(buffer)) > 0) + fos.write(buffer, 0, bytesRead); + + if (bytesRead < buffer.length) + break; + } + + fos.close(); + } catch(Exception e) { + e.printStackTrace(); + } + } + + public int read(byte[] buffer) throws IOException { + + if (endOfStream) + return -1; + + int bytesRead = 0, cnt = 0; + + while (bytesRead < buffer.length) { + if ((cnt = ais.read(buffer, bytesRead, buffer.length-bytesRead)) <= 0) { + endOfStream = true; + break; + } + bytesRead += cnt; + } + + if (swap) + swapBytes(buffer, 0, bytesRead); + + return bytesRead; + } + + public void dump() { + System.err.println("#Channels: " + vStreamHdr.getChannels()); + System.err.println("Sample rate: " + vStreamHdr.getSampleRate()); + System.err.println("Bitrate: nominal=" + + vStreamHdr.getNominalBitrate() + + ", max=" + vStreamHdr.getMaximumBitrate() + + ", min=" + vStreamHdr.getMinimumBitrate()); + } + + public static class VorbisInputStream extends InputStream { + + private VorbisStream source; + + public VorbisInputStream(VorbisStream source) { + this.source = source; + } + + public int read() throws IOException { + return 0; + } + + public int read(byte[] buffer) throws IOException { + return read(buffer, 0, buffer.length); + } + + public int read(byte[] buffer, int offset, int length) throws IOException { + try { + return source.readPcm(buffer, offset, length); + } catch(EndOfOggStreamException e) { + return -1; + } + } + } + + public static void main(String args[]) { + URL url; + int i = 0; + String rawname = null; + + try { + if (args.length == 0) { + url = OggDecoder.class.getClassLoader().getResource("demos/data/crickets.ogg"); + (new OggDecoder(url)).play(); + } + + for (; i < args.length; i++) { + if (args[i].equals("-r")) { + rawname = args[++i]; + continue; + } + + System.err.println("Playing: " + args[i]); + + url = ((new File(args[i])).exists()) ? + new URL("file:" + args[i]) : new URL(args[i]); + + if (rawname != null) (new OggDecoder(url)).toraw(rawname); + else (new OggDecoder(url)).play(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/src/java/demos/devmaster/lesson8/OggStreamer.java b/src/java/demos/devmaster/lesson8/OggStreamer.java new file mode 100644 index 0000000..4858f23 --- /dev/null +++ b/src/java/demos/devmaster/lesson8/OggStreamer.java @@ -0,0 +1,262 @@ +/** + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * -Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * -Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS + * LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A + * RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. + * IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT + * OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR + * PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, + * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS + * BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for use in the + * design, construction, operation or maintenance of any nuclear facility. + * + */ + +package demos.devmaster.lesson8; + +import java.io.File; +import java.net.URL; +import java.nio.ByteBuffer; +import java.util.Arrays; +import net.java.games.joal.AL; +import net.java.games.joal.ALException; +import net.java.games.joal.ALFactory; +import net.java.games.joal.util.ALut; +/** + * + * This is a translation of the OggVorbis streamer OpenAL tutorial + * at http://www.devmaster.net/articles/openal-tutorials/lesson8.php + * + * It uses the Java Ogg library from http://www.j-ogg.de to do the Ogg + * file decoding... + * + * @author Krishna K Gadepalli + */ +public class OggStreamer { + + OggDecoder oggDecoder; + + static AL al = null; + static int BUFFER_SIZE = 4096*4; + + static { + // Initialize OpenAL and clear the error bit. + try { + ALut.alutInit(); + al = ALFactory.getAL(); + al.alGetError(); + } catch (ALException e) { + System.err.println("Error initializing OpenAL"); + e.printStackTrace(); + } + } + + // Buffers hold sound data. + private int[] buffers = new int[2]; + + // Sources are points emitting sound. + private int[] source = new int[1]; + + // Position, Velocity, Direction of the source sound. + private float[] sourcePos = { 0.0f, 0.0f, 0.0f }; + private float[] sourceVel = { 0.0f, 0.0f, 0.0f }; + private float[] sourceDir = { 0.0f, 0.0f, 0.0f }; + + private int format; // OpenAL data format + private int rate; // sample rate + + /** Creates a new instance of OggStreamer */ + public OggStreamer(URL url) { + this.oggDecoder = new OggDecoder(url); + } + + public boolean open() { + if (!oggDecoder.initialize()) { + System.err.println("Error initializing ogg stream..."); + return false; + } + + // TODO: I am not if this is the right way to fix the endian + // problems I am having... but this seems to fix it on Linux + oggDecoder.setSwap(true); + + switch (oggDecoder.numChannels()) { + case 1: format = AL.AL_FORMAT_MONO16; break; + case 2: format = AL.AL_FORMAT_STEREO16; break; + default: + System.err.println("Incorrect number of channels.."); + return false; + } + + rate = oggDecoder.sampleRate(); + + al.alGenBuffers(2, buffers, 0); check(); + al.alGenSources(1, source, 0); check(); + + System.err.println("format = 0x" + Integer.toString(format, 16)); + // System.err.println("buffers = " + Arrays.toString(buffers)); + // System.err.println("source = " + Arrays.toString(source )); + + al.alSourcefv(source[0], AL.AL_POSITION , sourcePos, 0); + al.alSourcefv(source[0], AL.AL_VELOCITY , sourceVel, 0); + al.alSourcefv(source[0], AL.AL_DIRECTION, sourceDir, 0); + + al.alSourcef(source[0], AL.AL_ROLLOFF_FACTOR, 0.0f ); + al.alSourcei(source[0], AL.AL_SOURCE_RELATIVE, AL.AL_TRUE); + + return true; + } + + public void release() { + al.alSourceStop(source[0]); + empty(); + + al.alDeleteSources(1, source, 0); check(); + al.alDeleteBuffers(2, buffers, 0); check(); + + // ov_clear(&oggStream); + } + + public boolean playback() { + if (playing()) + return true; + + if (!stream(buffers[0])) + return false; + + if(!stream(buffers[1])) + return false; + + al.alSourceQueueBuffers(source[0], 2, buffers, 0); + al.alSourcePlay(source[0]); + + return true; + } + + public boolean playing() { + int[] state = new int[1]; + + al.alGetSourcei(source[0], AL.AL_SOURCE_STATE, state, 0); + + return (state[0] == AL.AL_PLAYING); + } + + public boolean update() { + int[] processed = new int[1]; + boolean active = true; + + al.alGetSourcei(source[0], AL.AL_BUFFERS_PROCESSED, processed, 0); + + while (processed[0] > 0) + { + int[] buffer = new int[1]; + + al.alSourceUnqueueBuffers(source[0], 1, buffer, 0); check(); + + active = stream(buffer[0]); + + al.alSourceQueueBuffers(source[0], 1, buffer, 0); check(); + + processed[0]--; + } + + return active; + } + + public boolean stream(int buffer) { + byte[] pcm = new byte[BUFFER_SIZE]; + int size = 0; + + try { + if ((size = oggDecoder.read(pcm)) <= 0) + return false; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + + ByteBuffer data = ByteBuffer.wrap(pcm, 0, size); + al.alBufferData(buffer, format, data, size, rate); + check(); + + return true; + } + + public void empty() { + } + + private void check() { + if (al.alGetError() != AL.AL_NO_ERROR) + throw new ALException("OpenAL error raised..."); + } + + public boolean play() { + if (!open()) + return false; + + oggDecoder.dump(); + + if (!playback()) + return false; + + while (update()) { + if (playing()) continue; + + if (!playback()) + return false; + } + + return true; + } + + /** + * @param args the command line arguments + */ + public static void main(String[] args) { + if (al == null) + return; + + URL url; + + try { + if (args.length == 0) { + url = OggStreamer.class.getClassLoader().getResource("demos/data/crickets.ogg"); + (new OggStreamer(url)).play(); + } + + for (int i = 0; i < args.length; i++) { + System.err.println("Playing Ogg stream : " + args[i]); + + url = ((new File(args[i])).exists()) ? + new URL("file:" + args[i]) : new URL(args[i]); + + if ((new OggStreamer(url)).play()) continue; + + System.err.println("ERROR!!"); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + +} |