1 /*
2  * Copyright (C) 2009 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.speechrecorder;
18 
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.io.OutputStream;
22 
23 /**
24  * This class represents the header of a WAVE format audio file, which usually
25  * have a .wav suffix.  The following integer valued fields are contained:
26  * <ul>
27  * <li> format - usually PCM, ALAW or ULAW.
28  * <li> numChannels - 1 for mono, 2 for stereo.
29  * <li> sampleRate - usually 8000, 11025, 16000, 22050, or 44100 hz.
30  * <li> bitsPerSample - usually 16 for PCM, 8 for ALAW, or 8 for ULAW.
31  * <li> numBytes - size of audio data after this header, in bytes.
32  * </ul>
33  *
34  * Not yet ready to be supported, so
35  * @hide
36  */
37 public class WaveHeader {
38 
39     // follows WAVE format in http://ccrma.stanford.edu/courses/422/projects/WaveFormat
40 
41     private static final String TAG = "WaveHeader";
42 
43     private static final int HEADER_LENGTH = 44;
44 
45     /** Indicates PCM format. */
46     public static final short FORMAT_PCM = 1;
47     /** Indicates ALAW format. */
48     public static final short FORMAT_ALAW = 6;
49     /** Indicates ULAW format. */
50     public static final short FORMAT_ULAW = 7;
51 
52     private short mFormat;
53     private short mNumChannels;
54     private int mSampleRate;
55     private short mBitsPerSample;
56     private int mNumBytes;
57 
58     /**
59      * Construct a WaveHeader, with all fields defaulting to zero.
60      */
WaveHeader()61     public WaveHeader() {
62     }
63 
64     /**
65      * Construct a WaveHeader, with fields initialized.
66      * @param format format of audio data,
67      * one of {@link #FORMAT_PCM}, {@link #FORMAT_ULAW}, or {@link #FORMAT_ALAW}.
68      * @param numChannels 1 for mono, 2 for stereo.
69      * @param sampleRate typically 8000, 11025, 16000, 22050, or 44100 hz.
70      * @param bitsPerSample usually 16 for PCM, 8 for ULAW or 8 for ALAW.
71      * @param numBytes size of audio data after this header, in bytes.
72      */
WaveHeader(short format, short numChannels, int sampleRate, short bitsPerSample, int numBytes)73     public WaveHeader(short format, short numChannels, int sampleRate, short bitsPerSample, int numBytes) {
74         mFormat = format;
75         mSampleRate = sampleRate;
76         mNumChannels = numChannels;
77         mBitsPerSample = bitsPerSample;
78         mNumBytes = numBytes;
79     }
80 
81     /**
82      * Get the format field.
83      * @return format field,
84      * one of {@link #FORMAT_PCM}, {@link #FORMAT_ULAW}, or {@link #FORMAT_ALAW}.
85      */
getFormat()86     public short getFormat() {
87         return mFormat;
88     }
89 
90     /**
91      * Set the format field.
92      * @param format
93      * one of {@link #FORMAT_PCM}, {@link #FORMAT_ULAW}, or {@link #FORMAT_ALAW}.
94      * @return reference to this WaveHeader instance.
95      */
setFormat(short format)96     public WaveHeader setFormat(short format) {
97         mFormat = format;
98         return this;
99     }
100 
101     /**
102      * Get the number of channels.
103      * @return number of channels, 1 for mono, 2 for stereo.
104      */
getNumChannels()105     public short getNumChannels() {
106         return mNumChannels;
107     }
108 
109     /**
110      * Set the number of channels.
111      * @param numChannels 1 for mono, 2 for stereo.
112      * @return reference to this WaveHeader instance.
113      */
setNumChannels(short numChannels)114     public WaveHeader setNumChannels(short numChannels) {
115         mNumChannels = numChannels;
116         return this;
117     }
118 
119     /**
120      * Get the sample rate.
121      * @return sample rate, typically 8000, 11025, 16000, 22050, or 44100 hz.
122      */
getSampleRate()123     public int getSampleRate() {
124         return mSampleRate;
125     }
126 
127     /**
128      * Set the sample rate.
129      * @param sampleRate sample rate, typically 8000, 11025, 16000, 22050, or 44100 hz.
130      * @return reference to this WaveHeader instance.
131      */
setSampleRate(int sampleRate)132     public WaveHeader setSampleRate(int sampleRate) {
133         mSampleRate = sampleRate;
134         return this;
135     }
136 
137     /**
138      * Get the number of bits per sample.
139      * @return number of bits per sample,
140      * usually 16 for PCM, 8 for ULAW or 8 for ALAW.
141      */
getBitsPerSample()142     public short getBitsPerSample() {
143         return mBitsPerSample;
144     }
145 
146     /**
147      * Set the number of bits per sample.
148      * @param bitsPerSample number of bits per sample,
149      * usually 16 for PCM, 8 for ULAW or 8 for ALAW.
150      * @return reference to this WaveHeader instance.
151      */
setBitsPerSample(short bitsPerSample)152     public WaveHeader setBitsPerSample(short bitsPerSample) {
153         mBitsPerSample = bitsPerSample;
154         return this;
155     }
156 
157     /**
158      * Get the size of audio data after this header, in bytes.
159      * @return size of audio data after this header, in bytes.
160      */
getNumBytes()161     public int getNumBytes() {
162         return mNumBytes;
163     }
164 
165     /**
166      * Set the size of audio data after this header, in bytes.
167      * @param numBytes size of audio data after this header, in bytes.
168      * @return reference to this WaveHeader instance.
169      */
setNumBytes(int numBytes)170     public WaveHeader setNumBytes(int numBytes) {
171         mNumBytes = numBytes;
172         return this;
173     }
174 
175     /**
176      * Read and initialize a WaveHeader.
177      * @param in {@link java.io.InputStream} to read from.
178      * @return number of bytes consumed.
179      * @throws IOException
180      */
read(InputStream in)181     public int read(InputStream in) throws IOException {
182         /* RIFF header */
183         readId(in, "RIFF");
184         int numBytes = readInt(in) - 36;
185         readId(in, "WAVE");
186 
187         /* fmt chunk */
188         readId(in, "fmt ");
189         if (16 != readInt(in)) throw new IOException("fmt chunk length not 16");
190         mFormat = readShort(in);
191         mNumChannels = readShort(in);
192         mSampleRate = readInt(in);
193         int byteRate = readInt(in);
194         short blockAlign = readShort(in);
195         mBitsPerSample = readShort(in);
196         if (byteRate != mNumChannels * mSampleRate * mBitsPerSample / 8) {
197             throw new IOException("fmt.ByteRate field inconsistent");
198         }
199         if (blockAlign != mNumChannels * mBitsPerSample / 8) {
200             throw new IOException("fmt.BlockAlign field inconsistent");
201         }
202 
203         /* data chunk */
204         readId(in, "data");
205         mNumBytes = readInt(in);
206 
207         return HEADER_LENGTH;
208     }
209 
readId(InputStream in, String id)210     private static void readId(InputStream in, String id) throws IOException {
211         for (int i = 0; i < id.length(); i++) {
212             if (id.charAt(i) != in.read()) throw new IOException( id + " tag not present");
213         }
214     }
215 
readInt(InputStream in)216     private static int readInt(InputStream in) throws IOException {
217         return in.read() | (in.read() << 8) | (in.read() << 16) | (in.read() << 24);
218     }
219 
readShort(InputStream in)220     private static short readShort(InputStream in) throws IOException {
221         return (short)(in.read() | (in.read() << 8));
222     }
223 
224     /**
225      * Write a WAVE file header.
226      * @param out {@link java.io.OutputStream} to receive the header.
227      * @return number of bytes written.
228      * @throws IOException
229      */
write(OutputStream out)230     public int write(OutputStream out) throws IOException {
231         /* RIFF header */
232         writeId(out, "RIFF");
233         writeInt(out, 36 + mNumBytes);
234         writeId(out, "WAVE");
235 
236         /* fmt chunk */
237         writeId(out, "fmt ");
238         writeInt(out, 16);
239         writeShort(out, mFormat);
240         writeShort(out, mNumChannels);
241         writeInt(out, mSampleRate);
242         writeInt(out, mNumChannels * mSampleRate * mBitsPerSample / 8);
243         writeShort(out, (short)(mNumChannels * mBitsPerSample / 8));
244         writeShort(out, mBitsPerSample);
245 
246         /* data chunk */
247         writeId(out, "data");
248         writeInt(out, mNumBytes);
249 
250         return HEADER_LENGTH;
251     }
252 
writeId(OutputStream out, String id)253     private static void writeId(OutputStream out, String id) throws IOException {
254         for (int i = 0; i < id.length(); i++) out.write(id.charAt(i));
255     }
256 
writeInt(OutputStream out, int val)257     private static void writeInt(OutputStream out, int val) throws IOException {
258         out.write(val >> 0);
259         out.write(val >> 8);
260         out.write(val >> 16);
261         out.write(val >> 24);
262     }
263 
writeShort(OutputStream out, short val)264     private static void writeShort(OutputStream out, short val) throws IOException {
265         out.write(val >> 0);
266         out.write(val >> 8);
267     }
268 
269     @Override
toString()270     public String toString() {
271         return String.format(
272                 "WaveHeader format=%d numChannels=%d sampleRate=%d bitsPerSample=%d numBytes=%d",
273                 mFormat, mNumChannels, mSampleRate, mBitsPerSample, mNumBytes);
274     }
275 
276 }
277