View Javadoc

1   package net.sf.jack4j.util.file.wav;
2   
3   /*
4   Copyright (C) 2008 Ondrej Par
5   
6   This program is free software; you can redistribute it and/or modify
7   it under the terms of the GNU Lesser General Public License as published by
8   the Free Software Foundation; either version 2.1 of the License, or
9   (at your option) any later version.
10  
11  This program is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  GNU Lesser General Public License for more details.
15  
16  You should have received a copy of the GNU Lesser General Public License
17  along with this program; if not, write to the Free Software
18  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19  
20  */
21  
22  import java.io.IOException;
23  import java.nio.ByteBuffer;
24  import java.nio.ByteOrder;
25  import java.nio.channels.FileChannel;
26  
27  import net.sf.jack4j.util.IntSampleConverter;
28  
29  /**
30   * Allows read-only access to WAV file.
31   * 
32   * <p>
33   * The class uses dedicated {@link FileChannel} instance that must be passed to
34   * the constructor and may not be used by anyone else.
35   * 
36   * @author repa
37   * 
38   */
39  public class WavFileReader {
40  
41  	private static final int OPTIMAL_BUFFER_SIZE = 4096;
42  
43  	private FileChannel fileChannel;
44  	private IntSampleConverter sampleConverter;
45  	private int bitsPerSample;
46  	private int samplesPerSecond;
47  	private int channelCount;
48  	private int blockAlign;
49  	private int avgBytesPerSecond;
50  	private long dataPosition;
51  	private long dataSize;
52  	private ByteBuffer buffer;
53  	private int bufferSamples;
54  	private long currentSample;
55  	private long totalSamples;
56  
57  	public WavFileReader(FileChannel fileChannel) throws IOException {
58  		this.fileChannel = fileChannel;
59  
60  		parseHeaders();
61  
62  		bufferSamples = OPTIMAL_BUFFER_SIZE / blockAlign;
63  		if (bufferSamples == 0) {
64  			bufferSamples = 1;
65  		}
66  		this.buffer = ByteBuffer.allocateDirect(bufferSamples * blockAlign);
67  
68  		initialSeek();
69  	}
70  
71  	/**
72  	 * Reads samples from WAV file, starting at current position, and writes
73  	 * them to specified buffers.
74  	 * 
75  	 * <p>
76  	 * Buffers in the given array are used for corresponding channels. If the
77  	 * array element at index <code>i</code> is null, the channel
78  	 * <code>i</code> will not be processed. If the array has less elements
79  	 * than the number of channels, channels with higher numbers will not be
80  	 * processed. If the array has more elements than the number of channels,
81  	 * zero samples (0.0f) will be put into unused buffers.
82  	 * 
83  	 * @return number of samples read
84  	 */
85  	public int readSamples(ByteBuffer[] channelBuffers, int nframes) throws IOException {
86  		int totalRead = 0;
87  		int remainsToRead = nframes;
88  
89  		while (remainsToRead > 0) {
90  			long remainingSamples = totalSamples - currentSample;
91  			int toRead = (remainsToRead < remainingSamples) ? remainsToRead : (int) remainingSamples;
92  			if (toRead == 0) {
93  				break;
94  			} else if (toRead > bufferSamples) {
95  				toRead = bufferSamples;
96  			}
97  
98  			buffer.clear();
99  			buffer.limit(toRead * blockAlign);
100 			int bytesRead = fileChannel.read(buffer);
101 			if (bytesRead == -1) {
102 				break;
103 			}
104 			buffer.flip();
105 			int samplesRead = bytesRead / blockAlign;
106 
107 			for (int sampleIdx = 0; sampleIdx < samplesRead; sampleIdx++) {
108 				for (int channelIdx = 0; channelIdx < channelCount; channelIdx++) {
109 					float sample = sampleConverter.intSampleToFloat(buffer);
110 
111 					if (channelIdx >= channelBuffers.length) {
112 						continue;
113 					}
114 					ByteBuffer channelBuffer = channelBuffers[channelIdx];
115 					if (channelBuffer == null) {
116 						continue;
117 					}
118 
119 					channelBuffer.putFloat(sample);
120 				}
121 				for (int channelIdx = channelCount; channelIdx < channelBuffers.length; channelIdx++) {
122 					ByteBuffer channelBuffer = channelBuffers[channelIdx];
123 					if (channelBuffer == null) {
124 						continue;
125 					}
126 					channelBuffer.putFloat(0.0f);
127 				}
128 			}
129 
130 			totalRead += samplesRead;
131 			currentSample += samplesRead;
132 			remainsToRead -= samplesRead;
133 		}
134 
135 		return totalRead;
136 	}
137 
138 	private void parseHeaders() throws IOException {
139 		ByteBuffer buffer = ByteBuffer.allocate(1024).order(ByteOrder.LITTLE_ENDIAN);
140 
141 		fileChannel.position(0);
142 		readBytesToBuffer(buffer, WavFormat.AIFF_HEADER_LENGTH);
143 		buffer.flip();
144 
145 		if (buffer.getInt() != WavFormat.RIFF_GROUP_ID) {
146 			throw new IOException("Wrong AIFF groupId");
147 		}
148 
149 		long totalDataSize = 0xffffffffL & (buffer.getInt());
150 		long maxPosition = buffer.position() + totalDataSize;
151 
152 		if (buffer.getInt() != WavFormat.WAVE_FILE_TYPE) {
153 			throw new IOException("Wrong RIFF file type");
154 		}
155 
156 		while (fileChannel.position() < maxPosition) {
157 			readBytesToBuffer(buffer, WavFormat.CHUNK_HEADER_LENGTH);
158 			buffer.flip();
159 
160 			int chunkType = buffer.getInt();
161 			long chunkSize = 0xffffffffL & (buffer.getInt());
162 			long paddedChunkSize = (chunkSize % 2 == 0) ? chunkSize : chunkSize + 1;
163 
164 			long chunkStartPosition = fileChannel.position();
165 
166 			if (chunkType == WavFormat.FMT_CHUNK_TYPE) {
167 				readBytesToBuffer(buffer, WavFormat.FORMAT_FIELDS_SIZE);
168 				buffer.flip();
169 
170 				int formatTag = buffer.getShort();
171 				if (formatTag != WavFormat.REQUIRED_FORMAT_TAG) {
172 					throw new UnsupportedOperationException("Unsupported WAV format #" + formatTag);
173 				}
174 				channelCount = buffer.getShort();
175 				samplesPerSecond = buffer.getInt();
176 				avgBytesPerSecond = buffer.getInt();
177 				blockAlign = buffer.getShort();
178 				bitsPerSample = buffer.getShort();
179 
180 				sampleConverter = IntSampleConverter.createIntSampleConverter(bitsPerSample,
181 				        !(bitsPerSample <= 8),
182 				        false);
183 			} else if (chunkType == WavFormat.DATA_CHUNK_TYPE) {
184 				dataPosition = chunkStartPosition;
185 				dataSize = chunkSize;
186 			}
187 
188 			fileChannel.position(chunkStartPosition + paddedChunkSize);
189 		}
190 	}
191 
192 	private void initialSeek() throws IOException {
193 		fileChannel.position(dataPosition);
194 		currentSample = 0;
195 		totalSamples = dataSize / blockAlign;
196 	}
197 
198 	private void readBytesToBuffer(ByteBuffer buffer, int size) throws IOException {
199 		buffer.clear();
200 		buffer.limit(size);
201 		if (fileChannel.read(buffer) != size) {
202 			throw new IOException("Can't read " + size + " bytes from WAV file");
203 		}
204 	}
205 
206 	/**
207 	 * @return the bitsPerSample
208 	 */
209 	public int getBitsPerSample() {
210 		return bitsPerSample;
211 	}
212 
213 	/**
214 	 * @return the samplesPerSecond
215 	 */
216 	public int getSamplesPerSecond() {
217 		return samplesPerSecond;
218 	}
219 
220 	/**
221 	 * @return the channelCount
222 	 */
223 	public int getChannelCount() {
224 		return channelCount;
225 	}
226 
227 	/**
228 	 * @return the blockAlign
229 	 */
230 	public int getBlockAlign() {
231 		return blockAlign;
232 	}
233 
234 	/**
235 	 * @return the avgBytesPerSecond
236 	 */
237 	public int getAvgBytesPerSecond() {
238 		return avgBytesPerSecond;
239 	}
240 }