• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.cts.verifier.audio.audiolib;
18 
19 import java.io.BufferedOutputStream;
20 import java.io.File;
21 import java.io.FileNotFoundException;
22 import java.io.FileOutputStream;
23 import java.io.IOException;
24 import java.io.OutputStream;
25 import java.io.RandomAccessFile;
26 
27 
28 /**
29  * Write audio data to a WAV file.
30  *
31  * <pre>
32  * <code>
33  * WaveFileWriter writer = new WaveFileWriter(file);
34  * writer.setFrameRate(22050);
35  * writer.setBitsPerSample(24);
36  * writer.write(floatArray);
37  * writer.close();
38  * </code>
39  * </pre>
40  *
41  * This was borrowed with Phil Burk's permission from JSyn at:
42  * https://github.com/philburk/jsyn/blob/master/src/main/java/com/jsyn/util/WaveFileWriter.java
43  */
44 public class WaveFileWriter {
45 
46     private static final short WAVE_FORMAT_PCM = 1;
47     private static final int PCM24_MIN = -(1 << 23);
48     private static final int PCM24_MAX = (1 << 23) - 1;
49 
50     private final OutputStream mOutputStream;
51     private long mRiffSizePosition = 0;
52     private long mDataSizePosition = 0;
53     private int mFrameRate = 44100;
54     private int mSamplesPerFrame = 1;
55     private int mBitsPerSample = 16;
56     private int mBytesWritten;
57     private final File mOutputFile;
58     private boolean mHeaderWritten = false;
59 
60     /**
61      * Create a writer that will write to the specified file.
62      */
WaveFileWriter(File outputFile)63     public WaveFileWriter(File outputFile) throws FileNotFoundException {
64         mOutputFile = outputFile;
65         FileOutputStream fileOut = new FileOutputStream(outputFile);
66         mOutputStream = new BufferedOutputStream(fileOut);
67     }
68 
69     /**
70      * @param frameRate default is 44100
71      */
setFrameRate(int frameRate)72     public void setFrameRate(int frameRate) {
73         mFrameRate = frameRate;
74     }
75 
getFrameRate()76     public int getFrameRate() {
77         return mFrameRate;
78     }
79 
80     /**
81      * For stereo, set this to 2. Default is 1.
82      */
setSamplesPerFrame(int samplesPerFrame)83     public void setSamplesPerFrame(int samplesPerFrame) {
84         mSamplesPerFrame = samplesPerFrame;
85     }
86 
getSamplesPerFrame()87     public int getSamplesPerFrame() {
88         return mSamplesPerFrame;
89     }
90 
91     /**
92      * Only 16 or 24 bit samples supported at the moment. Default is 16.
93      */
setBitsPerSample(int bits)94     public void setBitsPerSample(int bits) {
95         if ((bits != 16) && (bits != 24)) {
96             throw new IllegalArgumentException(
97                     "Only 16 or 24 bits per sample allowed. Not " + bits);
98         }
99         mBitsPerSample = bits;
100     }
101 
getBitsPerSample()102     public int getBitsPerSample() {
103         return mBitsPerSample;
104     }
105 
106     /**
107      * Close the stream and fix the chunk sizes.
108      * @throws IOException if the close fails
109      */
close()110     public void close() throws IOException {
111         mOutputStream.close();
112         fixSizes();
113     }
114 
115     /**
116      * Write entire buffer of audio samples to the WAV file.
117      */
write(float[] buffer)118     public void write(float[] buffer) throws IOException {
119         write(buffer, 0, buffer.length);
120     }
121 
122     /**
123      * Write single audio data value to the WAV file.
124      */
write(float value)125     public void write(float value) throws IOException {
126         if (!mHeaderWritten) {
127             writeHeader();
128         }
129 
130         if (mBitsPerSample == 24) {
131             writePCM24(value);
132         } else {
133             writePCM16(value);
134         }
135     }
136 
writePCM24(float value)137     private void writePCM24(float value) throws IOException {
138         // Offset before casting so that we can avoid using floor().
139         // Also round by adding 0.5 so that very small signals go to zero.
140         float temp = (PCM24_MAX * value) + 0.5f - PCM24_MIN;
141         int sample = ((int) temp) + PCM24_MIN;
142         // clip to 24-bit range
143         if (sample > PCM24_MAX) {
144             sample = PCM24_MAX;
145         } else if (sample < PCM24_MIN) {
146             sample = PCM24_MIN;
147         }
148         // encode as little-endian
149         writeByte(sample); // little end
150         writeByte(sample >> 8); // middle
151         writeByte(sample >> 16); // big end
152     }
153 
writePCM16(float value)154     private void writePCM16(float value) throws IOException {
155         // Offset before casting so that we can avoid using floor().
156         // Also round by adding 0.5 so that very small signals go to zero.
157         float temp = (Short.MAX_VALUE * value) + 0.5f - Short.MIN_VALUE;
158         int sample = ((int) temp) + Short.MIN_VALUE;
159         if (sample > Short.MAX_VALUE) {
160             sample = Short.MAX_VALUE;
161         } else if (sample < Short.MIN_VALUE) {
162             sample = Short.MIN_VALUE;
163         }
164         writeByte(sample); // little end
165         writeByte(sample >> 8); // big end
166     }
167 
168     /**
169      * Write audio to the WAV file.
170      */
write(float[] buffer, int start, int count)171     public void write(float[] buffer, int start, int count) throws IOException {
172         for (int i = 0; i < count; i++) {
173             write(buffer[start + i]);
174         }
175     }
176 
177     // Write lower 8 bits. Upper bits ignored.
writeByte(int b)178     private void writeByte(int b) throws IOException {
179         mOutputStream.write(b);
180         mBytesWritten += 1;
181     }
182 
183     /**
184      * Write a 32 bit integer to the stream in Little Endian format.
185      */
writeIntLittle(int n)186     public void writeIntLittle(int n) throws IOException {
187         writeByte(n);
188         writeByte(n >> 8);
189         writeByte(n >> 16);
190         writeByte(n >> 24);
191     }
192 
193     /**
194      * Write a 16 bit integer to the stream in Little Endian format.
195      */
writeShortLittle(short n)196     public void writeShortLittle(short n) throws IOException {
197         writeByte(n);
198         writeByte(n >> 8);
199     }
200 
201     /**
202      * Write a simple WAV header for PCM data.
203      */
writeHeader()204     private void writeHeader() throws IOException {
205         writeRiffHeader();
206         writeFormatChunk();
207         writeDataChunkHeader();
208         mOutputStream.flush();
209         mHeaderWritten = true;
210     }
211 
212     /**
213      * Write a 'RIFF' file header and a 'WAVE' ID to the WAV file.
214      */
writeRiffHeader()215     private void writeRiffHeader() throws IOException {
216         writeByte('R');
217         writeByte('I');
218         writeByte('F');
219         writeByte('F');
220         mRiffSizePosition = mBytesWritten;
221         // This will be overwritten by fixSizes() when the writer is closed.
222         writeIntLittle(Integer.MAX_VALUE);
223         writeByte('W');
224         writeByte('A');
225         writeByte('V');
226         writeByte('E');
227     }
228 
229     /**
230      * Write an 'fmt ' chunk to the WAV file containing the given information.
231      */
writeFormatChunk()232     public void writeFormatChunk() throws IOException {
233         int bytesPerSample = (mBitsPerSample + 7) / 8;
234 
235         writeByte('f');
236         writeByte('m');
237         writeByte('t');
238         writeByte(' ');
239         writeIntLittle(16); // chunk size
240         writeShortLittle(WAVE_FORMAT_PCM);
241         writeShortLittle((short) mSamplesPerFrame);
242         writeIntLittle(mFrameRate);
243         // bytes/second
244         writeIntLittle(mFrameRate * mSamplesPerFrame * bytesPerSample);
245         // block align
246         writeShortLittle((short) (mSamplesPerFrame * bytesPerSample));
247         writeShortLittle((short) mBitsPerSample);
248     }
249 
250     /**
251      * Write a 'data' chunk header to the WAV file. This should be followed by call to
252      * writeShortLittle() to write the data to the chunk.
253      */
writeDataChunkHeader()254     public void writeDataChunkHeader() throws IOException {
255         writeByte('d');
256         writeByte('a');
257         writeByte('t');
258         writeByte('a');
259         mDataSizePosition = mBytesWritten;
260         // This will be overwritten by fixSizes() when the writer is closed.
261         writeIntLittle(Integer.MAX_VALUE); // size
262     }
263 
264     /**
265      * Fix RIFF and data chunk sizes based on final size. Assume data chunk is the last chunk.
266      */
fixSizes()267     private void fixSizes() throws IOException {
268         RandomAccessFile randomFile = new RandomAccessFile(mOutputFile, "rw");
269         try {
270             // adjust RIFF size
271             long end = mBytesWritten;
272             int riffSize = (int) (end - mRiffSizePosition) - 4;
273             randomFile.seek(mRiffSizePosition);
274             writeRandomIntLittle(randomFile, riffSize);
275             // adjust data size
276             int dataSize = (int) (end - mDataSizePosition) - 4;
277             randomFile.seek(mDataSizePosition);
278             writeRandomIntLittle(randomFile, dataSize);
279         } finally {
280             randomFile.close();
281         }
282     }
283 
writeRandomIntLittle(RandomAccessFile randomFile, int n)284     private void writeRandomIntLittle(RandomAccessFile randomFile, int n) throws IOException {
285         byte[] buffer = new byte[4];
286         buffer[0] = (byte) n;
287         buffer[1] = (byte) (n >> 8);
288         buffer[2] = (byte) (n >> 16);
289         buffer[3] = (byte) (n >> 24);
290         randomFile.write(buffer);
291     }
292 
293 }
294