Converts the project to use a licensed WAVE file reader.
This is easier to distribute.
This commit is contained in:
344
libwave/src/com/intervigil/wave/WaveReader.java
Normal file
344
libwave/src/com/intervigil/wave/WaveReader.java
Normal file
@@ -0,0 +1,344 @@
|
||||
/* WaveReader.java
|
||||
|
||||
Copyright (c) 2011 Ethan Chen
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
package com.intervigil.wave;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import com.intervigil.wave.exception.InvalidWaveException;
|
||||
|
||||
public class WaveReader {
|
||||
private static final int WAV_HEADER_CHUNK_ID = 0x52494646; // "RIFF"
|
||||
private static final int WAV_FORMAT = 0x57415645; // "WAVE"
|
||||
private static final int WAV_FORMAT_CHUNK_ID = 0x666d7420; // "fmt "
|
||||
private static final int WAV_DATA_CHUNK_ID = 0x64617461; // "data"
|
||||
private static final int STREAM_BUFFER_SIZE = 4096;
|
||||
|
||||
private File mInFile;
|
||||
private BufferedInputStream mInStream;
|
||||
|
||||
private int mSampleRate;
|
||||
private int mChannels;
|
||||
private int mSampleBits;
|
||||
private int mFileSize;
|
||||
private int mDataSize;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor; initializes WaveReader to read from given file
|
||||
*
|
||||
* @param path path to input file
|
||||
* @param name name of input file
|
||||
*/
|
||||
public WaveReader(String path, String name) {
|
||||
this.mInFile = new File(path + File.separator + name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor; initializes WaveReader to read from given file
|
||||
*
|
||||
* @param file handle to input file
|
||||
*/
|
||||
public WaveReader(File file) {
|
||||
this.mInFile = file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor; initializes WaveReader to read from given file
|
||||
*
|
||||
* @param file handle to input file
|
||||
* @author William Hubbard, 7/24/2021
|
||||
*/
|
||||
public WaveReader(InputStream stream) {
|
||||
this.mInFile = null;
|
||||
this.mInStream = new BufferedInputStream(stream, STREAM_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open WAV file for reading
|
||||
*
|
||||
* @throws FileNotFoundException if input file does not exist
|
||||
* @throws InvalidWaveException if input file is not a valid WAVE file
|
||||
* @throws IOException if I/O error occurred during file read
|
||||
*/
|
||||
public void openWave() throws FileNotFoundException, InvalidWaveException, IOException {
|
||||
/*
|
||||
* Only initializes the stream if needed ~ Change made by William Hubbard on 7/24/2021
|
||||
*/
|
||||
if (this.mInStream == null) {
|
||||
FileInputStream fileStream = new FileInputStream(mInFile);
|
||||
mInStream = new BufferedInputStream(fileStream, STREAM_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
int headerId = readUnsignedInt(mInStream); // should be "RIFF"
|
||||
if (headerId != WAV_HEADER_CHUNK_ID) {
|
||||
throw new InvalidWaveException(String.format("Invalid WAVE header chunk ID: %d", headerId));
|
||||
}
|
||||
mFileSize = readUnsignedIntLE(mInStream); // length of header
|
||||
int format = readUnsignedInt(mInStream); // should be "WAVE"
|
||||
if (format != WAV_FORMAT) {
|
||||
throw new InvalidWaveException("Invalid WAVE format");
|
||||
}
|
||||
|
||||
int formatId = readUnsignedInt(mInStream); // should be "fmt "
|
||||
if (formatId != WAV_FORMAT_CHUNK_ID) {
|
||||
throw new InvalidWaveException("Invalid WAVE format chunk ID");
|
||||
}
|
||||
int formatSize = readUnsignedIntLE(mInStream);
|
||||
if (formatSize != 16) {
|
||||
|
||||
}
|
||||
int audioFormat = readUnsignedShortLE(mInStream);
|
||||
if (audioFormat != 1) {
|
||||
throw new InvalidWaveException("Not PCM WAVE format");
|
||||
}
|
||||
mChannels = readUnsignedShortLE(mInStream);
|
||||
mSampleRate = readUnsignedIntLE(mInStream);
|
||||
int byteRate = readUnsignedIntLE(mInStream);
|
||||
int blockAlign = readUnsignedShortLE(mInStream);
|
||||
mSampleBits = readUnsignedShortLE(mInStream);
|
||||
|
||||
int dataId = readUnsignedInt(mInStream);
|
||||
if (dataId != WAV_DATA_CHUNK_ID) {
|
||||
throw new InvalidWaveException("Invalid WAVE data chunk ID");
|
||||
}
|
||||
mDataSize = readUnsignedIntLE(mInStream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a short to a {@link ByteArrayOutputStream}.
|
||||
* @param stream - The stream to write to.
|
||||
* @param num - The number to write.
|
||||
* @throws IOException
|
||||
* @author William Hubbard, 7/24/2021
|
||||
* @author java.io.DataOutputStream#writeShort(int)
|
||||
*/
|
||||
private void writeShort(ByteArrayOutputStream stream, short num) throws IOException {
|
||||
byte[] buf = new byte[2];
|
||||
buf[0] = (byte)(num >>> 8);
|
||||
buf[1] = (byte)(num >>> 0);
|
||||
stream.write(buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restructures the original wave header for the file as a byte array.
|
||||
* @return The original wave header
|
||||
* @throws IOException
|
||||
* @author William Hubbard, 7/24/2021
|
||||
* @author Ethan Chen, com.intervigil.wave.WaveWriter#writeWaveHeader()
|
||||
*/
|
||||
public byte[] getHeaderBytes() throws IOException {
|
||||
// rewind to beginning of the file
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream(44);
|
||||
|
||||
int bytesPerSec = (mSampleBits + 7) / 8;
|
||||
|
||||
stream.write("RIFF".getBytes()); // WAV chunk header
|
||||
stream.write(Integer.reverseBytes(36)); // WAV chunk size
|
||||
stream.write("WAVE".getBytes()); // WAV format
|
||||
|
||||
stream.write("fmt ".getBytes()); // format subchunk header
|
||||
stream.write(Integer.reverseBytes(16)); // format subchunk size
|
||||
writeShort(stream, Short.reverseBytes((short) 1)); // audio format
|
||||
writeShort(stream, Short.reverseBytes((short) mChannels)); // number of channels
|
||||
stream.write(Integer.reverseBytes(mSampleRate)); // sample rate
|
||||
stream.write(Integer.reverseBytes(mSampleRate * mChannels * bytesPerSec)); // byte rate
|
||||
writeShort(stream, Short.reverseBytes((short) (mChannels * bytesPerSec))); // block align
|
||||
writeShort(stream, Short.reverseBytes((short) mSampleBits)); // bits per sample
|
||||
|
||||
stream.write("data".getBytes()); // data subchunk header
|
||||
stream.write(Integer.reverseBytes(0)); // data subchunk size
|
||||
|
||||
stream.close();
|
||||
|
||||
return stream.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sample rate
|
||||
*
|
||||
* @return input file's sample rate
|
||||
*/
|
||||
public int getSampleRate() {
|
||||
return mSampleRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get number of channels
|
||||
*
|
||||
* @return number of channels in input file
|
||||
*/
|
||||
public int getChannels() {
|
||||
return mChannels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get PCM format, S16LE or S8LE
|
||||
*
|
||||
* @return number of bits per sample
|
||||
*/
|
||||
public int getPcmFormat() {
|
||||
return mSampleBits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file size
|
||||
*
|
||||
* @return total input file size in bytes
|
||||
*/
|
||||
public int getFileSize() {
|
||||
return mFileSize + 8;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get input file's audio data size
|
||||
* Basically file size without headers included
|
||||
*
|
||||
* @return audio data size in bytes
|
||||
*/
|
||||
public int getDataSize() {
|
||||
return mDataSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get input file length
|
||||
*
|
||||
* @return length of file in seconds
|
||||
*/
|
||||
public int getLength() {
|
||||
if (mSampleRate == 0 || mChannels == 0 || (mSampleBits + 7) / 8 == 0) {
|
||||
return 0;
|
||||
} else {
|
||||
return mDataSize / (mSampleRate * mChannels * ((mSampleBits + 7) / 8));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read audio data from input file (mono)
|
||||
*
|
||||
* @param dst mono audio data output buffer
|
||||
* @param numSamples number of samples to read
|
||||
*
|
||||
* @return number of samples read
|
||||
*
|
||||
* @throws IOException if file I/O error occurs
|
||||
*/
|
||||
public int read(short[] dst, int numSamples) throws IOException {
|
||||
if (mChannels != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
byte[] buf = new byte[numSamples * 2];
|
||||
int index = 0;
|
||||
int bytesRead = mInStream.read(buf, 0, numSamples * 2);
|
||||
|
||||
for (int i = 0; i < bytesRead; i+=2) {
|
||||
dst[index] = byteToShortLE(buf[i], buf[i+1]);
|
||||
index++;
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read audio data from input file (stereo)
|
||||
*
|
||||
* @param left left channel audio output buffer
|
||||
* @param right right channel audio output buffer
|
||||
* @param numSamples number of samples to read
|
||||
*
|
||||
* @return number of samples read
|
||||
*
|
||||
* @throws IOException if file I/O error occurs
|
||||
*/
|
||||
public int read(short[] left, short[] right, int numSamples) throws IOException {
|
||||
if (mChannels != 2) {
|
||||
return -1;
|
||||
}
|
||||
byte[] buf = new byte[numSamples * 4];
|
||||
int index = 0;
|
||||
int bytesRead = mInStream.read(buf, 0, numSamples * 4);
|
||||
|
||||
for (int i = 0; i < bytesRead; i+=2) {
|
||||
short val = byteToShortLE(buf[0], buf[i+1]);
|
||||
if (i % 4 == 0) {
|
||||
left[index] = val;
|
||||
} else {
|
||||
right[index] = val;
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close WAV file. WaveReader object cannot be used again following this call.
|
||||
*
|
||||
* @throws IOException if I/O error occurred closing filestream
|
||||
*/
|
||||
public void closeWaveFile() throws IOException {
|
||||
if (mInStream != null) {
|
||||
mInStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
private static short byteToShortLE(byte b1, byte b2) {
|
||||
return (short) (b1 & 0xFF | ((b2 & 0xFF) << 8));
|
||||
}
|
||||
|
||||
private static int readUnsignedInt(BufferedInputStream in) throws IOException {
|
||||
int ret;
|
||||
byte[] buf = new byte[4];
|
||||
ret = in.read(buf);
|
||||
if (ret == -1) {
|
||||
return -1;
|
||||
} else {
|
||||
return (((buf[0] & 0xFF) << 24)
|
||||
| ((buf[1] & 0xFF) << 16)
|
||||
| ((buf[2] & 0xFF) << 8)
|
||||
| (buf[3] & 0xFF));
|
||||
}
|
||||
}
|
||||
|
||||
private static int readUnsignedIntLE(BufferedInputStream in) throws IOException {
|
||||
int ret;
|
||||
byte[] buf = new byte[4];
|
||||
ret = in.read(buf);
|
||||
if (ret == -1) {
|
||||
return -1;
|
||||
} else {
|
||||
return (buf[0] & 0xFF
|
||||
| ((buf[1] & 0xFF) << 8)
|
||||
| ((buf[2] & 0xFF) << 16)
|
||||
| ((buf[3] & 0xFF) << 24));
|
||||
}
|
||||
}
|
||||
|
||||
private static short readUnsignedShortLE(BufferedInputStream in) throws IOException {
|
||||
int ret;
|
||||
byte[] buf = new byte[2];
|
||||
ret = in.read(buf, 0, 2);
|
||||
if (ret == -1) {
|
||||
return -1;
|
||||
} else {
|
||||
return byteToShortLE(buf[0], buf[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user