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
17package android.speech.srec;
18
19import java.io.IOException;
20import java.io.InputStream;
21import 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 */
37public 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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
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
216    private static int readInt(InputStream in) throws IOException {
217        return in.read() | (in.read() << 8) | (in.read() << 16) | (in.read() << 24);
218    }
219
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     */
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
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
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
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
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