Converts the project to use a licensed WAVE file reader.

This is easier to distribute.
This commit is contained in:
Markil3
2021-07-24 14:12:42 -07:00
parent 97985044f8
commit adf4cea82c
19 changed files with 850 additions and 379 deletions

View 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]);
}
}
}

View 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));
}
}

View File

@@ -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);
}
}