/* * Copyright (c) 2009-2012 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions 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 'jMonkeyEngine' nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jme3.audio.plugins; import com.jme3.asset.AssetInfo; import com.jme3.asset.AssetLoader; import com.jme3.audio.AudioBuffer; import com.jme3.audio.AudioData; import com.jme3.audio.AudioKey; import com.jme3.audio.AudioStream; import com.jme3.audio.SeekableStream; import com.jme3.util.BufferUtils; import com.jme3.util.LittleEndien; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.util.logging.Level; import java.util.logging.Logger; public class WAVLoader implements AssetLoader { private static final Logger logger = Logger.getLogger(WAVLoader.class.getName()); // all these are in big endian private static final int i_RIFF = 0x46464952; private static final int i_WAVE = 0x45564157; private static final int i_fmt = 0x20746D66; private static final int i_data = 0x61746164; private boolean readStream = false; private AudioBuffer audioBuffer; private AudioStream audioStream; private AudioData audioData; private int bytesPerSec; private float duration; private ResettableInputStream in; private int inOffset = 0; private static class ResettableInputStream extends LittleEndien implements SeekableStream { private AssetInfo info; private int resetOffset = 0; public ResettableInputStream(AssetInfo info, InputStream in) { super(in); this.info = info; } public void setResetOffset(int resetOffset) { this.resetOffset = resetOffset; } public void setTime(float time) { if (time != 0f) { throw new UnsupportedOperationException("Seeking WAV files not supported"); } InputStream newStream = info.openStream(); try { newStream.skip(resetOffset); this.in = new BufferedInputStream(newStream); } catch (IOException ex) { // Resource could have gotten lost, etc. try { newStream.close(); } catch (IOException ex2) { } throw new RuntimeException(ex); } } } private void readFormatChunk(int size) throws IOException{ // if other compressions are supported, size doesn't have to be 16 // if (size != 16) // logger.warning("Expected size of format chunk to be 16"); int compression = in.readShort(); if (compression != 1){ throw new IOException("WAV Loader only supports PCM wave files"); } int channels = in.readShort(); int sampleRate = in.readInt(); bytesPerSec = in.readInt(); // used to calculate duration int bytesPerSample = in.readShort(); int bitsPerSample = in.readShort(); int expectedBytesPerSec = (bitsPerSample * channels * sampleRate) / 8; if (expectedBytesPerSec != bytesPerSec){ logger.log(Level.WARNING, "Expected {0} bytes per second, got {1}", new Object[]{expectedBytesPerSec, bytesPerSec}); } if (bitsPerSample != 8 && bitsPerSample != 16) throw new IOException("Only 8 and 16 bits per sample are supported!"); if ( (bitsPerSample / 8) * channels != bytesPerSample) throw new IOException("Invalid bytes per sample value"); if (bytesPerSample * sampleRate != bytesPerSec) throw new IOException("Invalid bytes per second value"); audioData.setupFormat(channels, bitsPerSample, sampleRate); int remaining = size - 16; if (remaining > 0){ in.skipBytes(remaining); } } private void readDataChunkForBuffer(int len) throws IOException { ByteBuffer data = BufferUtils.createByteBuffer(len); byte[] buf = new byte[512]; int read = 0; while ( (read = in.read(buf)) > 0){ data.put(buf, 0, Math.min(read, data.remaining()) ); } data.flip(); audioBuffer.updateData(data); in.close(); } private void readDataChunkForStream(int offset, int len) throws IOException { in.setResetOffset(offset); audioStream.updateData(in, duration); } private AudioData load(AssetInfo info, InputStream inputStream, boolean stream) throws IOException{ this.in = new ResettableInputStream(info, inputStream); inOffset = 0; int sig = in.readInt(); if (sig != i_RIFF) throw new IOException("File is not a WAVE file"); // skip size in.readInt(); if (in.readInt() != i_WAVE) throw new IOException("WAVE File does not contain audio"); inOffset += 4 * 3; readStream = stream; if (readStream){ audioStream = new AudioStream(); audioData = audioStream; }else{ audioBuffer = new AudioBuffer(); audioData = audioBuffer; } while (true) { int type = in.readInt(); int len = in.readInt(); inOffset += 4 * 2; switch (type) { case i_fmt: readFormatChunk(len); inOffset += len; break; case i_data: // Compute duration based on data chunk size duration = (float)(len / bytesPerSec); if (readStream) { readDataChunkForStream(inOffset, len); } else { readDataChunkForBuffer(len); } return audioData; default: int skipped = in.skipBytes(len); if (skipped <= 0) { return null; } inOffset += skipped; break; } } } public Object load(AssetInfo info) throws IOException { AudioData data; InputStream inputStream = null; try { inputStream = info.openStream(); data = load(info, inputStream, ((AudioKey)info.getKey()).isStream()); if (data instanceof AudioStream){ inputStream = null; } return data; } finally { if (inputStream != null){ inputStream.close(); } } } }