View Javadoc

1   package net.sf.jack4j.util.file.wav;
2   
3   import java.io.File;
4   import java.io.FileOutputStream;
5   import java.io.IOException;
6   import java.nio.ByteBuffer;
7   import java.nio.ByteOrder;
8   import java.nio.channels.FileChannel;
9   
10  import net.sf.jack4j.util.IntSampleConverter;
11  
12  /*
13  Copyright (C) 2008 Ondrej Par
14  
15  This program is free software; you can redistribute it and/or modify
16  it under the terms of the GNU Lesser General Public License as published by
17  the Free Software Foundation; either version 2.1 of the License, or
18  (at your option) any later version.
19  
20  This program is distributed in the hope that it will be useful,
21  but WITHOUT ANY WARRANTY; without even the implied warranty of
22  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23  GNU Lesser General Public License for more details.
24  
25  You should have received a copy of the GNU Lesser General Public License
26  along with this program; if not, write to the Free Software
27  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
28  
29  */
30  
31  /**
32   * Creates WAV file.
33   * 
34   * <p>
35   * This is very simple writer for WAV files. The instance must be closed after
36   * last sample was written.
37   */
38  public class WavFileWriter {
39  
40  	private static final int OPTIMAL_BUFFER_SIZE = 4096;
41  
42  	private FileChannel fileChannel;
43  	private IntSampleConverter sampleConverter;
44  	private int channelCount;
45  	private long totalSizePosition;
46  	private long firstChunkPosition;
47  	private long dataChunkSizePosition;
48  	private int bufferSamples;
49  	private int blockAlign;
50  	private long dataPosition;
51  	private ByteBuffer buffer;
52  
53  	public WavFileWriter(String fileName, int bitsPerSample, int samplesPerSecond, int channelCount) throws IOException {
54  		File file = new File(fileName);
55  		FileOutputStream fileOutputStream = new FileOutputStream(file);
56  		this.fileChannel = fileOutputStream.getChannel();
57  
58  		this.sampleConverter = IntSampleConverter.createIntSampleConverter(bitsPerSample, !(bitsPerSample <= 8), false);
59  
60  		this.channelCount = channelCount;
61  
62  		this.blockAlign = sampleConverter.bytesPerSample() * channelCount;
63  
64  		bufferSamples = OPTIMAL_BUFFER_SIZE / blockAlign;
65  		if (bufferSamples == 0) {
66  			bufferSamples = 1;
67  		}
68  		this.buffer = ByteBuffer.allocateDirect(bufferSamples * blockAlign).order(ByteOrder.LITTLE_ENDIAN);
69  
70  		writeHeaders(bitsPerSample, samplesPerSecond);
71  	}
72  
73  	/**
74  	 * Writes samples from specified buffers to the WAV file.
75  	 * 
76  	 * <p>
77  	 * Buffers in the given array are used for corresponding channels. If the
78  	 * array element at index <code>i</code> is null, the channel
79  	 * <code>i</code> will be filled with 0db samples. If the array has less
80  	 * elements than the number of channels, channels with higher numbers will
81  	 * be all filled with 0db samples. If the array has more elements than the
82  	 * number of channels, extra buffers will be ignored.
83  	 */
84  	public void writeSamples(ByteBuffer[] channelBuffers, int nframes) throws IOException {
85  		int remainsToWrite = nframes;
86  
87  		while (remainsToWrite > 0) {
88  			int bufferRemainingSamples = buffer.remaining() / blockAlign;
89  			int toWrite = (remainsToWrite < bufferRemainingSamples) ? remainsToWrite : bufferRemainingSamples;
90  
91  			for (int sampleIdx = 0; sampleIdx < toWrite; sampleIdx++) {
92  				for (int channelIdx = 0; channelIdx < channelCount; channelIdx++) {
93  					ByteBuffer channelBuffer = (channelIdx < channelBuffers.length) ? channelBuffers[channelIdx] : null;
94  					float sample = (channelBuffer == null) ? 0.0f : channelBuffer.getFloat();
95  
96  					sampleConverter.floatToIntSample(sample, buffer);
97  				}
98  			}
99  
100 			if (buffer.remaining() < blockAlign) {
101 				flushBuffer();
102 			}
103 
104 			remainsToWrite -= toWrite;
105 		}
106 	}
107 
108 	/**
109 	 * Closes the file.
110 	 * 
111 	 * <p>
112 	 * This method must be called after the last sample is written. The instance
113 	 * is unusable afterwards.
114 	 */
115 	public void close() throws IOException {
116 		flushBuffer();
117 
118 		long fileSize = fileChannel.position();
119 
120 		int aiffSize = (int) (fileSize - firstChunkPosition);
121 		int dataSize = (int) (fileSize - dataPosition);
122 
123 		ByteBuffer buffer = ByteBuffer.allocate(Integer.SIZE / 8).order(ByteOrder.LITTLE_ENDIAN);
124 
125 		fileChannel.position(totalSizePosition);
126 		buffer.putInt(aiffSize);
127 		buffer.flip();
128 		fileChannel.write(buffer);
129 
130 		fileChannel.position(dataChunkSizePosition);
131 		buffer.clear();
132 		buffer.putInt(dataSize);
133 		buffer.flip();
134 		fileChannel.write(buffer);
135 
136 		fileChannel.close();
137 	}
138 
139 	private void flushBuffer() throws IOException {
140 		if (buffer.position() > 0) {
141 			buffer.flip();
142 			fileChannel.write(buffer);
143 			buffer.clear();
144 		}
145 	}
146 
147 	private void writeHeaders(int bitsPerSample, int samplesPerSecond) throws IOException {
148 		ByteBuffer buffer = ByteBuffer.allocate(1024).order(ByteOrder.LITTLE_ENDIAN);
149 
150 		buffer.putInt(WavFormat.RIFF_GROUP_ID);
151 		buffer.flip();
152 		fileChannel.write(buffer);
153 		buffer.clear();
154 
155 		this.totalSizePosition = fileChannel.position();
156 
157 		buffer.putInt(0); // will be replaced with data size
158 		buffer.flip();
159 		fileChannel.write(buffer);
160 		buffer.clear();
161 
162 		this.firstChunkPosition = fileChannel.position();
163 
164 		buffer.putInt(WavFormat.WAVE_FILE_TYPE);
165 
166 		buffer.putInt(WavFormat.FMT_CHUNK_TYPE);
167 		buffer.putInt(WavFormat.FORMAT_FIELDS_SIZE);
168 		buffer.putShort((short) WavFormat.REQUIRED_FORMAT_TAG);
169 		buffer.putShort((short) channelCount);
170 		buffer.putInt(samplesPerSecond);
171 		buffer.putInt(blockAlign * samplesPerSecond);
172 		buffer.putShort((short) blockAlign);
173 		buffer.putShort((short) bitsPerSample);
174 
175 		buffer.putInt(WavFormat.DATA_CHUNK_TYPE);
176 		buffer.flip();
177 		fileChannel.write(buffer);
178 		buffer.clear();
179 
180 		this.dataChunkSizePosition = fileChannel.position();
181 
182 		buffer.putInt(0); // will be replaced with chunk size
183 		buffer.flip();
184 		fileChannel.write(buffer);
185 
186 		this.dataPosition = fileChannel.position();
187 	}
188 }