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  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
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  
75  
76  
77  
78  
79  
80  
81  
82  
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 
110 
111 
112 
113 
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); 
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); 
183 		buffer.flip();
184 		fileChannel.write(buffer);
185 
186 		this.dataPosition = fileChannel.position();
187 	}
188 }