1 /* 2 * Copyright (c) 2009-2010 jMonkeyEngine 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 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. 15 * 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. 19 * 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. 31 */ 32 33 package com.jme3.audio.plugins; 34 35 import com.jme3.asset.AssetInfo; 36 import com.jme3.asset.AssetLoader; 37 import com.jme3.audio.AudioBuffer; 38 import com.jme3.audio.AudioData; 39 import com.jme3.audio.AudioKey; 40 import com.jme3.audio.AudioStream; 41 import com.jme3.util.BufferUtils; 42 import com.jme3.util.LittleEndien; 43 import java.io.IOException; 44 import java.io.InputStream; 45 import java.nio.ByteBuffer; 46 import java.util.logging.Level; 47 import java.util.logging.Logger; 48 49 public class WAVLoader implements AssetLoader { 50 51 private static final Logger logger = Logger.getLogger(WAVLoader.class.getName()); 52 53 // all these are in big endian 54 private static final int i_RIFF = 0x46464952; 55 private static final int i_WAVE = 0x45564157; 56 private static final int i_fmt = 0x20746D66; 57 private static final int i_data = 0x61746164; 58 59 private boolean readStream = false; 60 61 private AudioBuffer audioBuffer; 62 private AudioStream audioStream; 63 private AudioData audioData; 64 private int bytesPerSec; 65 private float duration; 66 67 private LittleEndien in; 68 69 private void readFormatChunk(int size) throws IOException{ 70 // if other compressions are supported, size doesn't have to be 16 71 // if (size != 16) 72 // logger.warning("Expected size of format chunk to be 16"); 73 74 int compression = in.readShort(); 75 if (compression != 1){ 76 throw new IOException("WAV Loader only supports PCM wave files"); 77 } 78 79 int channels = in.readShort(); 80 int sampleRate = in.readInt(); 81 82 bytesPerSec = in.readInt(); // used to calculate duration 83 84 int bytesPerSample = in.readShort(); 85 int bitsPerSample = in.readShort(); 86 87 int expectedBytesPerSec = (bitsPerSample * channels * sampleRate) / 8; 88 if (expectedBytesPerSec != bytesPerSec){ 89 logger.log(Level.WARNING, "Expected {0} bytes per second, got {1}", 90 new Object[]{expectedBytesPerSec, bytesPerSec}); 91 } 92 93 if (bitsPerSample != 8 && bitsPerSample != 16) 94 throw new IOException("Only 8 and 16 bits per sample are supported!"); 95 96 if ( (bitsPerSample / 8) * channels != bytesPerSample) 97 throw new IOException("Invalid bytes per sample value"); 98 99 if (bytesPerSample * sampleRate != bytesPerSec) 100 throw new IOException("Invalid bytes per second value"); 101 102 audioData.setupFormat(channels, bitsPerSample, sampleRate); 103 104 int remaining = size - 16; 105 if (remaining > 0){ 106 in.skipBytes(remaining); 107 } 108 } 109 110 private void readDataChunkForBuffer(int len) throws IOException { 111 ByteBuffer data = BufferUtils.createByteBuffer(len); 112 byte[] buf = new byte[512]; 113 int read = 0; 114 while ( (read = in.read(buf)) > 0){ 115 data.put(buf, 0, Math.min(read, data.remaining()) ); 116 } 117 data.flip(); 118 audioBuffer.updateData(data); 119 in.close(); 120 } 121 122 private void readDataChunkForStream(int len) throws IOException { 123 audioStream.updateData(in, duration); 124 } 125 126 private AudioData load(InputStream inputStream, boolean stream) throws IOException{ 127 this.in = new LittleEndien(inputStream); 128 129 int sig = in.readInt(); 130 if (sig != i_RIFF) 131 throw new IOException("File is not a WAVE file"); 132 133 // skip size 134 in.readInt(); 135 if (in.readInt() != i_WAVE) 136 throw new IOException("WAVE File does not contain audio"); 137 138 readStream = stream; 139 if (readStream){ 140 audioStream = new AudioStream(); 141 audioData = audioStream; 142 }else{ 143 audioBuffer = new AudioBuffer(); 144 audioData = audioBuffer; 145 } 146 147 while (true) { 148 int type = in.readInt(); 149 int len = in.readInt(); 150 151 switch (type) { 152 case i_fmt: 153 readFormatChunk(len); 154 break; 155 case i_data: 156 // Compute duration based on data chunk size 157 duration = len / bytesPerSec; 158 159 if (readStream) { 160 readDataChunkForStream(len); 161 } else { 162 readDataChunkForBuffer(len); 163 } 164 return audioData; 165 default: 166 int skipped = in.skipBytes(len); 167 if (skipped <= 0) { 168 return null; 169 } 170 break; 171 } 172 } 173 } 174 175 public Object load(AssetInfo info) throws IOException { 176 AudioData data; 177 InputStream inputStream = null; 178 try { 179 inputStream = info.openStream(); 180 data = load(inputStream, ((AudioKey)info.getKey()).isStream()); 181 if (data instanceof AudioStream){ 182 inputStream = null; 183 } 184 return data; 185 } finally { 186 if (inputStream != null){ 187 inputStream.close(); 188 } 189 } 190 } 191 } 192