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 }