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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
196
libwave/src/com/intervigil/wave/WaveWriter.java
Normal file
196
libwave/src/com/intervigil/wave/WaveWriter.java
Normal file
@@ -0,0 +1,196 @@
|
||||
/* WaveWriter.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.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
|
||||
public class WaveWriter {
|
||||
private static final int OUTPUT_STREAM_BUFFER = 16384;
|
||||
|
||||
private File mOutFile;
|
||||
private BufferedOutputStream mOutStream;
|
||||
|
||||
private int mSampleRate;
|
||||
private int mChannels;
|
||||
private int mSampleBits;
|
||||
|
||||
private int mBytesWritten;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor; initializes WaveWriter with file name and path
|
||||
*
|
||||
* @param path output file path
|
||||
* @param name output file name
|
||||
* @param sampleRate output sample rate
|
||||
* @param channels number of channels
|
||||
* @param sampleBits number of bits per sample (S8LE, S16LE)
|
||||
*/
|
||||
public WaveWriter(String path, String name, int sampleRate, int channels,
|
||||
int sampleBits) {
|
||||
this.mOutFile = new File(path + File.separator + name);
|
||||
|
||||
this.mSampleRate = sampleRate;
|
||||
this.mChannels = channels;
|
||||
this.mSampleBits = sampleBits;
|
||||
|
||||
this.mBytesWritten = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor; initializes WaveWriter with file name and path
|
||||
*
|
||||
* @param file output file handle
|
||||
* @param sampleRate output sample rate
|
||||
* @param channels number of channels
|
||||
* @param sampleBits number of bits per sample (S8LE, S16LE)
|
||||
*/
|
||||
public WaveWriter(File file, int sampleRate, int channels, int sampleBits) {
|
||||
this.mOutFile = file;
|
||||
|
||||
this.mSampleRate = sampleRate;
|
||||
this.mChannels = channels;
|
||||
this.mSampleBits = sampleBits;
|
||||
|
||||
this.mBytesWritten = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create output WAV file
|
||||
*
|
||||
* @return whether file creation succeeded
|
||||
*
|
||||
* @throws IOException if file I/O error occurs allocating header
|
||||
*/
|
||||
public boolean createWaveFile() throws IOException {
|
||||
if (mOutFile.exists()) {
|
||||
mOutFile.delete();
|
||||
}
|
||||
|
||||
if (mOutFile.createNewFile()) {
|
||||
FileOutputStream fileStream = new FileOutputStream(mOutFile);
|
||||
mOutStream = new BufferedOutputStream(fileStream, OUTPUT_STREAM_BUFFER);
|
||||
// write 44 bytes of space for the header
|
||||
mOutStream.write(new byte[44]);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write audio data to output file (mono). Does
|
||||
* nothing if output file is not mono channel.
|
||||
*
|
||||
* @param src mono audio data input buffer
|
||||
* @param offset offset into src buffer
|
||||
* @param length buffer size in number of samples
|
||||
*
|
||||
* @throws IOException if file I/O error occurs
|
||||
*/
|
||||
public void write(short[] src, int offset, int length) throws IOException {
|
||||
if (mChannels != 1) {
|
||||
return;
|
||||
}
|
||||
if (offset > length) {
|
||||
throw new IndexOutOfBoundsException(String.format("offset %d is greater than length %d", offset, length));
|
||||
}
|
||||
for (int i = offset; i < length; i++) {
|
||||
writeUnsignedShortLE(mOutStream, src[i]);
|
||||
mBytesWritten += 2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write audio data to output file (stereo). Does
|
||||
* nothing if output file is not stereo channel.
|
||||
*
|
||||
* @param left left channel audio data buffer
|
||||
* @param right right channel audio data buffer
|
||||
* @param offset offset into left/right buffers
|
||||
* @param length buffer size in number of samples
|
||||
*
|
||||
* @throws IOException if file I/O error occurs
|
||||
*/
|
||||
public void write(short[] left, short[] right, int offset, int length) throws IOException {
|
||||
if (mChannels != 2) {
|
||||
return;
|
||||
}
|
||||
if (offset > length) {
|
||||
throw new IndexOutOfBoundsException(String.format("offset %d is greater than length %d", offset, length));
|
||||
}
|
||||
for (int i = offset; i < length; i++) {
|
||||
writeUnsignedShortLE(mOutStream, left[i]);
|
||||
writeUnsignedShortLE(mOutStream, right[i]);
|
||||
mBytesWritten += 4;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close output WAV file and write WAV header. WaveWriter
|
||||
* cannot be used again following this call.
|
||||
*
|
||||
* @throws IOException if file I/O error occurs writing WAV header
|
||||
*/
|
||||
public void closeWaveFile() throws IOException {
|
||||
if (mOutStream != null) {
|
||||
this.mOutStream.flush();
|
||||
this.mOutStream.close();
|
||||
}
|
||||
writeWaveHeader();
|
||||
}
|
||||
|
||||
private void writeWaveHeader() throws IOException {
|
||||
// rewind to beginning of the file
|
||||
RandomAccessFile file = new RandomAccessFile(this.mOutFile, "rw");
|
||||
file.seek(0);
|
||||
|
||||
int bytesPerSec = (mSampleBits + 7) / 8;
|
||||
|
||||
file.writeBytes("RIFF"); // WAV chunk header
|
||||
file.writeInt(Integer.reverseBytes(mBytesWritten + 36)); // WAV chunk size
|
||||
file.writeBytes("WAVE"); // WAV format
|
||||
|
||||
file.writeBytes("fmt "); // format subchunk header
|
||||
file.writeInt(Integer.reverseBytes(16)); // format subchunk size
|
||||
file.writeShort(Short.reverseBytes((short) 1)); // audio format
|
||||
file.writeShort(Short.reverseBytes((short) mChannels)); // number of channels
|
||||
file.writeInt(Integer.reverseBytes(mSampleRate)); // sample rate
|
||||
file.writeInt(Integer.reverseBytes(mSampleRate * mChannels * bytesPerSec)); // byte rate
|
||||
file.writeShort(Short.reverseBytes((short) (mChannels * bytesPerSec))); // block align
|
||||
file.writeShort(Short.reverseBytes((short) mSampleBits)); // bits per sample
|
||||
|
||||
file.writeBytes("data"); // data subchunk header
|
||||
file.writeInt(Integer.reverseBytes(mBytesWritten)); // data subchunk size
|
||||
|
||||
file.close();
|
||||
file = null;
|
||||
}
|
||||
|
||||
private static void writeUnsignedShortLE(BufferedOutputStream stream, short sample)
|
||||
throws IOException {
|
||||
// write already writes the lower order byte of this short
|
||||
stream.write(sample);
|
||||
stream.write((sample >> 8));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/* InvalidWaveException.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.exception;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class InvalidWaveException extends IOException {
|
||||
|
||||
/**
|
||||
* Generated serialVersionUID
|
||||
*/
|
||||
private static final long serialVersionUID = -8229742633848759378L;
|
||||
|
||||
public InvalidWaveException() {
|
||||
|
||||
}
|
||||
|
||||
public InvalidWaveException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user