Files
UniversalMusicPlayer/libwave/src/com/intervigil/wave/WaveReader.java
Markil3 adf4cea82c Converts the project to use a licensed WAVE file reader.
This is easier to distribute.
2021-07-24 14:12:42 -07:00

344 lines
11 KiB
Java

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