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 
readFormatChunk(int size)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 
readDataChunkForBuffer(int len)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 
readDataChunkForStream(int len)122     private void readDataChunkForStream(int len) throws IOException {
123         audioStream.updateData(in, duration);
124     }
125 
load(InputStream inputStream, boolean stream)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 
load(AssetInfo info)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