Converts the project to use a licensed WAVE file reader.
This is easier to distribute.
This commit is contained in:
@@ -1 +0,0 @@
|
|||||||
These files were originally posted by Andrii Tkachenko on GitHub in 2016. They have been refactored to function in a Gradle environment.
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
apply plugin: "java"
|
|
||||||
apply plugin: "application"
|
|
||||||
|
|
||||||
sourceCompatibility = 1.8
|
|
||||||
targetCompatibility = 1.8
|
|
||||||
|
|
||||||
mainClassName = "MetaReader"
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import wave.WavHeader;
|
|
||||||
import wave.WavHeaderReader;
|
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Console utility for reading meta data of the wave files
|
|
||||||
*/
|
|
||||||
public class MetaReader {
|
|
||||||
public static void main(String[] args) {
|
|
||||||
if (args.length == 0) {
|
|
||||||
System.out.println("Illegal command line arguments.\n" +
|
|
||||||
"Usage MetaReader\n\t MetaReader <source file path>");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
WavHeader wavHeader;
|
|
||||||
try {
|
|
||||||
WavHeaderReader wavHeaderReader = new WavHeaderReader(args[0]);
|
|
||||||
wavHeader = wavHeaderReader.read();
|
|
||||||
System.out.println(wavHeader.toString());
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
System.out.println("Error: File " + args[0] + " not found!");
|
|
||||||
} catch (IOException e) {
|
|
||||||
System.out.println("Error: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2016. Andrii Tkachenko. https://github.com/tkaczenko/WavReader
|
|
||||||
*/
|
|
||||||
|
|
||||||
package wave;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class contains a structure of WAVE file.
|
|
||||||
*
|
|
||||||
* @see <a href =
|
|
||||||
* "https://github.com/tkacz-/WavReader/blob/master/wav-sound-format.gif">
|
|
||||||
* Structure of wave format
|
|
||||||
* </a>
|
|
||||||
*/
|
|
||||||
public class WavHeader {
|
|
||||||
private byte[] chunkID = new byte[4];
|
|
||||||
private int chunkSize;
|
|
||||||
private byte[] format = new byte[4];
|
|
||||||
private byte[] subChunk1ID = new byte[4];
|
|
||||||
private int subChunk1Size;
|
|
||||||
private short audioFormat;
|
|
||||||
private short numChannels;
|
|
||||||
private int sampleRate;
|
|
||||||
private int byteRate;
|
|
||||||
private short blockAlign;
|
|
||||||
private short bitsPerSample;
|
|
||||||
private byte[] subChunk2ID = new byte[4];
|
|
||||||
private int subChunk2Size;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "The RIFF chunk desriptor: " + new String(this.getChunkID()) + "\n" +
|
|
||||||
"Size of this chunk: " + this.getChunkSize() + "\n" +
|
|
||||||
"Format: " + new String(this.getFormat()) + "\n" + "\n" +
|
|
||||||
"fmt subchunk: " + new String(this.getSubChunk1ID()) + "\n" +
|
|
||||||
"Size of this chunk: " + this.getSubChunk1Size() + "\n" +
|
|
||||||
"Audio format: " + this.getAudioFormat() + "\n" +
|
|
||||||
"Number of channels: " + this.getNumChannels() + "\n" +
|
|
||||||
"Sample rate: " + this.getSampleRate() + "\n" +
|
|
||||||
"Byte rate: " + this.getByteRate() + "\n" +
|
|
||||||
"Block align: " + this.getBlockAlign() + "\n" +
|
|
||||||
"Bits per sample: " + this.getBitsPerSample() + "\n" + "\n" +
|
|
||||||
"data subchunk: " + new String(this.getSubChunk2ID()) + "\n" +
|
|
||||||
"Size of this chunk: " + this.getSubChunk2Size();
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getChunkID() {
|
|
||||||
return chunkID;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setChunkID(byte[] chunkID) {
|
|
||||||
this.chunkID = chunkID;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getChunkSize() {
|
|
||||||
return chunkSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setChunkSize(int chunkSize) {
|
|
||||||
this.chunkSize = chunkSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getFormat() {
|
|
||||||
return format;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFormat(byte[] format) {
|
|
||||||
this.format = format;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getSubChunk1ID() {
|
|
||||||
return subChunk1ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSubChunk1ID(byte[] subChunk1ID) {
|
|
||||||
this.subChunk1ID = subChunk1ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSubChunk1Size() {
|
|
||||||
return subChunk1Size;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSubChunk1Size(int subChunk1Size) {
|
|
||||||
this.subChunk1Size = subChunk1Size;
|
|
||||||
}
|
|
||||||
|
|
||||||
public short getAudioFormat() {
|
|
||||||
return audioFormat;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAudioFormat(short audioFormat) {
|
|
||||||
this.audioFormat = audioFormat;
|
|
||||||
}
|
|
||||||
|
|
||||||
public short getNumChannels() {
|
|
||||||
return numChannels;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setNumChannels(short numChannels) {
|
|
||||||
this.numChannels = numChannels;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSampleRate() {
|
|
||||||
return sampleRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSampleRate(int sampleRate) {
|
|
||||||
this.sampleRate = sampleRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getByteRate() {
|
|
||||||
return byteRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setByteRate(int byteRate) {
|
|
||||||
this.byteRate = byteRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public short getBlockAlign() {
|
|
||||||
return blockAlign;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBlockAlign(short blockAlign) {
|
|
||||||
this.blockAlign = blockAlign;
|
|
||||||
}
|
|
||||||
|
|
||||||
public short getBitsPerSample() {
|
|
||||||
return bitsPerSample;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBitsPerSample(short bitsPerSample) {
|
|
||||||
this.bitsPerSample = bitsPerSample;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getSubChunk2ID() {
|
|
||||||
return subChunk2ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSubChunk2ID(byte[] subChunk2ID) {
|
|
||||||
this.subChunk2ID = subChunk2ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSubChunk2Size() {
|
|
||||||
return subChunk2Size;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSubChunk2Size(int subChunk2Size) {
|
|
||||||
this.subChunk2Size = subChunk2Size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,159 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2016. Andrii Tkachenko. https://github.com/tkaczenko/WavReader
|
|
||||||
*/
|
|
||||||
|
|
||||||
package wave;
|
|
||||||
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom wave header reader which use class
|
|
||||||
*
|
|
||||||
* @see WavHeader
|
|
||||||
*/
|
|
||||||
public class WavHeaderReader {
|
|
||||||
/**
|
|
||||||
* Size for the wave header
|
|
||||||
*/
|
|
||||||
private static final int HEADER_SIZE = 44;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Buffer which contain bytes of the wave header
|
|
||||||
*/
|
|
||||||
private byte[] buf = new byte[HEADER_SIZE];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wave header
|
|
||||||
*/
|
|
||||||
private WavHeader header = new WavHeader();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* InputStream to the wave file
|
|
||||||
*/
|
|
||||||
private InputStream inputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Empty constructor
|
|
||||||
*/
|
|
||||||
public WavHeaderReader() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor which create own FileInputStream
|
|
||||||
*
|
|
||||||
* @param source absolute path to the file
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public WavHeaderReader(String source) throws IOException {
|
|
||||||
inputStream = new FileInputStream(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor which use created InputStream
|
|
||||||
*
|
|
||||||
* @param inputStream InputStream to the wave file
|
|
||||||
*/
|
|
||||||
public WavHeaderReader(InputStream inputStream) {
|
|
||||||
this.inputStream = inputStream;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read wave header of the file
|
|
||||||
*
|
|
||||||
* @return wave.WavHeader object
|
|
||||||
* @throws IOException
|
|
||||||
* @see WavHeader
|
|
||||||
*/
|
|
||||||
public WavHeader read() throws IOException {
|
|
||||||
int res = inputStream.read(buf);
|
|
||||||
if (res != HEADER_SIZE) {
|
|
||||||
throw new IOException("Could not read header.");
|
|
||||||
}
|
|
||||||
header.setChunkID(Arrays.copyOfRange(buf, 0, 4));
|
|
||||||
if (new String(header.getChunkID()).compareTo("RIFF") != 0) {
|
|
||||||
throw new IOException("Illegal format.");
|
|
||||||
}
|
|
||||||
header.setChunkSize(toInt(4, false));
|
|
||||||
header.setFormat(Arrays.copyOfRange(buf, 8, 12));
|
|
||||||
header.setSubChunk1ID(Arrays.copyOfRange(buf, 12, 16));
|
|
||||||
header.setSubChunk1Size(toInt(16, false));
|
|
||||||
header.setAudioFormat(toShort(20, false));
|
|
||||||
header.setNumChannels(toShort(22, false));
|
|
||||||
header.setSampleRate(toInt(24, false));
|
|
||||||
header.setByteRate(toInt(28, false));
|
|
||||||
header.setBlockAlign(toShort(32, false));
|
|
||||||
header.setBitsPerSample(toShort(34, false));
|
|
||||||
header.setSubChunk2ID(Arrays.copyOfRange(buf, 36, 40));
|
|
||||||
header.setSubChunk2Size(toInt(40, false));
|
|
||||||
return header;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert byte[] array to int number
|
|
||||||
*
|
|
||||||
* @param start start position of the buffer
|
|
||||||
* @param endian <code>true</code> for big-endian
|
|
||||||
* <code>false</code> for little-endian
|
|
||||||
* @return converted number
|
|
||||||
*/
|
|
||||||
private int toInt(int start, boolean endian) {
|
|
||||||
int k = (endian) ? 1 : -1;
|
|
||||||
if (!endian) {
|
|
||||||
start += 3;
|
|
||||||
}
|
|
||||||
return (buf[start] << 24) + (buf[start + k * 1] << 16) +
|
|
||||||
(buf[start + k * 2] << 8) + buf[start + k * 3];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert byte[] array to short number
|
|
||||||
*
|
|
||||||
* @param start start position of the buffer
|
|
||||||
* @param endian <code>true</code> for big-endian
|
|
||||||
* <code>false</code> for little-endian
|
|
||||||
* @return converted number
|
|
||||||
*/
|
|
||||||
private short toShort(int start, boolean endian) {
|
|
||||||
short k = (endian) ? (short) 1 : -1;
|
|
||||||
if (!endian) {
|
|
||||||
start++;
|
|
||||||
}
|
|
||||||
return (short) ((buf[start] << 8) + (buf[start + k * 1]));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the buffer which contain wave hader
|
|
||||||
*
|
|
||||||
* @return buffer
|
|
||||||
*/
|
|
||||||
public byte[] getBuf() {
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return wave.WavHeader object which contain wave header
|
|
||||||
*
|
|
||||||
* @return wave.WavHeader object
|
|
||||||
*/
|
|
||||||
public WavHeader getHeader() {
|
|
||||||
return header;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return input stream to the wave file
|
|
||||||
*
|
|
||||||
* @return Input steam to the wave file
|
|
||||||
*/
|
|
||||||
public InputStream getInputStream() {
|
|
||||||
return inputStream;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setInputStream(InputStream inputStream) {
|
|
||||||
this.inputStream = inputStream;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -17,7 +17,9 @@ dependencies {
|
|||||||
// The production code uses the SLF4J logging API at compile time
|
// The production code uses the SLF4J logging API at compile time
|
||||||
implementation 'org.slf4j:slf4j-api:1.7.25'
|
implementation 'org.slf4j:slf4j-api:1.7.25'
|
||||||
implementation 'com.google.code.gson:gson:2.8.7'
|
implementation 'com.google.code.gson:gson:2.8.7'
|
||||||
implementation project(":WavReader")
|
implementation 'com.googlecode.soundlibs:jlayer:1.0.1.4'
|
||||||
|
implementation 'com.mpatric:mp3agic:0.9.1'
|
||||||
|
implementation project(":libwave")
|
||||||
|
|
||||||
// Declare the dependency for your favourite test framework you want to use in your tests.
|
// Declare the dependency for your favourite test framework you want to use in your tests.
|
||||||
// TestNG is also supported by the Gradle Test task. Just change the
|
// TestNG is also supported by the Gradle Test task. Just change the
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ package edu.regis.universeplayer.localPlayer;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
import wave.WavHeader;
|
import com.intervigil.wave.WaveReader;
|
||||||
import wave.WavHeaderReader;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains information on a WAV audio file, along with a reference to the stream of raw data.
|
* Contains information on a WAV audio file, along with a reference to the stream of raw data.
|
||||||
@@ -20,7 +19,7 @@ public class AudioFile extends InputStream
|
|||||||
/**
|
/**
|
||||||
* Contains header information.
|
* Contains header information.
|
||||||
*/
|
*/
|
||||||
private final WavHeaderReader header;
|
private final WaveReader header;
|
||||||
/**
|
/**
|
||||||
* Contains a link to the process reading the audio file.
|
* Contains a link to the process reading the audio file.
|
||||||
*/
|
*/
|
||||||
@@ -40,13 +39,13 @@ public class AudioFile extends InputStream
|
|||||||
{
|
{
|
||||||
this.process = stream;
|
this.process = stream;
|
||||||
this.stream = stream.getInputStream();
|
this.stream = stream.getInputStream();
|
||||||
this.header = new WavHeaderReader(this.stream);
|
this.header = new WaveReader(this.stream);
|
||||||
this.header.read();
|
this.header.openWave();
|
||||||
}
|
}
|
||||||
|
|
||||||
public WavHeader getHeader()
|
public WaveReader getHeader()
|
||||||
{
|
{
|
||||||
return this.header.getHeader();
|
return this.header;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -55,7 +54,11 @@ public class AudioFile extends InputStream
|
|||||||
*/
|
*/
|
||||||
public byte[] getByteStream()
|
public byte[] getByteStream()
|
||||||
{
|
{
|
||||||
return this.header.getBuf();
|
try {
|
||||||
|
return this.header.getHeaderBytes();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Could not recreate header");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -69,4 +72,10 @@ public class AudioFile extends InputStream
|
|||||||
{
|
{
|
||||||
return this.process.isAlive() ? Integer.MAX_VALUE : super.available();
|
return this.process.isAlive() ? Integer.MAX_VALUE : super.available();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
super.close();
|
||||||
|
this.header.closeWaveFile();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
*/
|
*/
|
||||||
package edu.regis.universeplayer.localPlayer;
|
package edu.regis.universeplayer.localPlayer;
|
||||||
|
|
||||||
|
import com.intervigil.wave.WaveReader;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
@@ -10,8 +12,6 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
|
||||||
import wave.WavHeader;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This allows for the control of playback of files on the local file system
|
* This allows for the control of playback of files on the local file system
|
||||||
*
|
*
|
||||||
@@ -36,11 +36,11 @@ public class Player
|
|||||||
*/
|
*/
|
||||||
public void setCurrentFile(AudioFile file)
|
public void setCurrentFile(AudioFile file)
|
||||||
{
|
{
|
||||||
WavHeader header = file.getHeader();
|
WaveReader header = file.getHeader();
|
||||||
this.currentFile = file;
|
this.currentFile = file;
|
||||||
// TODO - Ensure that bytes per sample and bits per sample don't bother things
|
// TODO - Ensure that bytes per sample and bits per sample don't bother things
|
||||||
this.currentId = this.setCurrentFile(this.currentFile, header.getNumChannels(), header
|
this.currentId = this.setCurrentFile(this.currentFile, (short) header.getChannels(), (short) header
|
||||||
.getBitsPerSample(), header.getSampleRate());
|
.getPcmFormat(), header.getSampleRate());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
0
interface/src/main/resources/gui/icons/defaultart.png
Executable file → Normal file
0
interface/src/main/resources/gui/icons/defaultart.png
Executable file → Normal file
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
17
libwave/LICENSE
Normal file
17
libwave/LICENSE
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
libwave is licensed under LGPL
|
||||||
|
|
||||||
|
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
|
||||||
21
libwave/build.gradle
Normal file
21
libwave/build.gradle
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
jcenter()
|
||||||
|
google()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id 'java-library'
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
java.srcDirs = ['src']
|
||||||
|
resources.srcDirs = ['src']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,17 +16,20 @@ library {
|
|||||||
def compileTask = binary.compileTask.get()
|
def compileTask = binary.compileTask.get()
|
||||||
compileTask.includes.from("${Jvm.current().javaHome}/include")
|
compileTask.includes.from("${Jvm.current().javaHome}/include")
|
||||||
|
|
||||||
|
compileTask.source.from fileTree(dir: "src/main/c", include: "player.c")
|
||||||
|
|
||||||
def osFamily = binary.targetPlatform.targetMachine.operatingSystemFamily
|
def osFamily = binary.targetPlatform.targetMachine.operatingSystemFamily
|
||||||
if (osFamily.macOs) {
|
if (osFamily.macOs) {
|
||||||
compileTask.includes.from("${Jvm.current().javaHome}/include/darwin")
|
compileTask.includes.from("${Jvm.current().javaHome}/include/darwin")
|
||||||
|
compileTask.source.from fileTree(dir: "src/main/c", include: "player_mac.c")
|
||||||
} else if (osFamily.linux) {
|
} else if (osFamily.linux) {
|
||||||
compileTask.includes.from("${Jvm.current().javaHome}/include/linux")
|
compileTask.includes.from("${Jvm.current().javaHome}/include/linux")
|
||||||
|
compileTask.source.from fileTree(dir: "src/main/c", include: "player_alsa.c")
|
||||||
} else if (osFamily.windows) {
|
} else if (osFamily.windows) {
|
||||||
compileTask.includes.from("${Jvm.current().javaHome}/include/win32")
|
compileTask.includes.from("${Jvm.current().javaHome}/include/win32")
|
||||||
|
compileTask.source.from fileTree(dir: "src/main/c", include: "player_win.c")
|
||||||
}
|
}
|
||||||
|
|
||||||
compileTask.source.from fileTree(dir: "src/main/c", include: "**/*.c")
|
|
||||||
|
|
||||||
def toolChain = binary.toolChain
|
def toolChain = binary.toolChain
|
||||||
if (toolChain instanceof VisualCpp) {
|
if (toolChain instanceof VisualCpp) {
|
||||||
compileTask.compilerArgs.addAll(["/TC"])
|
compileTask.compilerArgs.addAll(["/TC"])
|
||||||
|
|||||||
@@ -4,14 +4,35 @@
|
|||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
short numChannels;
|
||||||
|
short bytesPerChannel;
|
||||||
|
int sampleRate;
|
||||||
|
} HeaderData;
|
||||||
|
|
||||||
static jclass InputStream;
|
static jclass InputStream;
|
||||||
static jclass AudioStream;
|
static jclass AudioStream;
|
||||||
|
static jclass Header;
|
||||||
static jmethodID AudioStream_getHeader;
|
static jmethodID AudioStream_getHeader;
|
||||||
static jmethodID AudioStream_getByteStream;
|
static jmethodID AudioStream_getByteStream;
|
||||||
static jmethodID AudioStream_readInt;
|
static jmethodID AudioStream_readInt;
|
||||||
static jmethodID InputStream_readBuffer;
|
static jmethodID InputStream_readBuffer;
|
||||||
|
static jmethodID Header_getChunkId;
|
||||||
|
static jmethodID Header_getChunkSize;
|
||||||
|
static jmethodID Header_getFormat;
|
||||||
|
static jmethodID Header_getSubChunk1ID;
|
||||||
|
static jmethodID Header_getSubChunk1Size;
|
||||||
|
static jmethodID Header_getAudioFormat;
|
||||||
|
static jmethodID Header_getNumChannels;
|
||||||
|
static jmethodID Header_getSampleRate;
|
||||||
|
static jmethodID Header_getByteRate;
|
||||||
|
static jmethodID Header_getBlockAlign;
|
||||||
|
static jmethodID Header_getBitsPerSample;
|
||||||
|
static jmethodID Header_getSubChunk2ID;
|
||||||
|
static jmethodID Header_getSubChunk2Size;
|
||||||
|
|
||||||
static jobject currentFile = NULL;
|
static jobject currentFile = NULL;
|
||||||
|
static HeaderData currentHeader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function is used to initialize the static reference to java.io.InputStream class.
|
* This function is used to initialize the static reference to java.io.InputStream class.
|
||||||
@@ -27,7 +48,7 @@ jclass getInputStreamClass(JNIEnv *env)
|
|||||||
jclass tempClass = (*env)->FindClass(env, "java/io/InputStream");
|
jclass tempClass = (*env)->FindClass(env, "java/io/InputStream");
|
||||||
if (tempClass == 0)
|
if (tempClass == 0)
|
||||||
{
|
{
|
||||||
printf("Could not find class java/io/InputStream");
|
fprintf(stderr, "Could not find class java/io/InputStream");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
InputStream = (*env)->NewGlobalRef(env, tempClass);
|
InputStream = (*env)->NewGlobalRef(env, tempClass);
|
||||||
@@ -49,7 +70,7 @@ jclass getAudioStreamClass(JNIEnv *env, jobject audio)
|
|||||||
jclass tempClass = (*env)->GetObjectClass(env, audio);
|
jclass tempClass = (*env)->GetObjectClass(env, audio);
|
||||||
if (tempClass == 0)
|
if (tempClass == 0)
|
||||||
{
|
{
|
||||||
printf("Could not find class edu/regis/universeplayer_localPlayer/AudioFile");
|
fprintf(stderr, "Could not find class edu/regis/universeplayer_localPlayer/AudioFile");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
AudioStream = (*env)->NewGlobalRef(env, tempClass);
|
AudioStream = (*env)->NewGlobalRef(env, tempClass);
|
||||||
@@ -57,6 +78,21 @@ jclass getAudioStreamClass(JNIEnv *env, jobject audio)
|
|||||||
return AudioStream;
|
return AudioStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jclass getHeaderClass(JNIEnv *env)
|
||||||
|
{
|
||||||
|
if (Header == 0)
|
||||||
|
{
|
||||||
|
jclass tempClass = (*env)->FindClass(env, "wave/WavHeader");
|
||||||
|
if (tempClass == 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Could not find wave/WavHeader");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
Header = (*env)->NewGlobalRef(env, tempClass);
|
||||||
|
}
|
||||||
|
return Header;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function is used to initialize the static reference to AudioStream's getHeader method.
|
* This function is used to initialize the static reference to AudioStream's getHeader method.
|
||||||
*
|
*
|
||||||
@@ -114,7 +150,7 @@ jmethodID getReadMethod(JNIEnv *env, jobject audio)
|
|||||||
tempClass = getAudioStreamClass(env, audio);
|
tempClass = getAudioStreamClass(env, audio);
|
||||||
if (tempClass == 0)
|
if (tempClass == 0)
|
||||||
{
|
{
|
||||||
printf("Could not find AudioStream class");
|
fprintf(stderr, "Could not find AudioStream class");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -127,7 +163,7 @@ jmethodID getReadMethod(JNIEnv *env, jobject audio)
|
|||||||
tempClass = getInputStreamClass(env);
|
tempClass = getInputStreamClass(env);
|
||||||
if (tempClass == 0)
|
if (tempClass == 0)
|
||||||
{
|
{
|
||||||
printf("Could not find InputStream class");
|
fprintf(stderr, "Could not find InputStream class");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -138,6 +174,124 @@ jmethodID getReadMethod(JNIEnv *env, jobject audio)
|
|||||||
return AudioStream_readInt;
|
return AudioStream_readInt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is used to initialize the static reference to WavHeader's various methods.
|
||||||
|
*
|
||||||
|
* @param env - A reference to the JVM.
|
||||||
|
*/
|
||||||
|
void getHeaderMethods(JNIEnv *env)
|
||||||
|
{
|
||||||
|
jclass tempClass = getHeaderClass(env);
|
||||||
|
if (tempClass == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Header_getChunkId == 0)
|
||||||
|
{
|
||||||
|
Header_getChunkId = (*env)->GetMethodID(env, tempClass, "getChunkID", "()[B");
|
||||||
|
if (Header_getChunkId == 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Could not find wave/WavHeader#getChunkID\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Header_getChunkSize == 0)
|
||||||
|
{
|
||||||
|
Header_getChunkSize = (*env)->GetMethodID(env, tempClass, "getChunkSize", "()I");
|
||||||
|
if (Header_getChunkSize == 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Could not find wave/WavHeader#getChunkSize\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Header_getFormat == 0)
|
||||||
|
{
|
||||||
|
Header_getFormat = (*env)->GetMethodID(env, tempClass, "getFormat", "()[B");
|
||||||
|
if (Header_getFormat == 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Could not find wave/WavHeader#getFormat\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Header_getSubChunk1ID == 0)
|
||||||
|
{
|
||||||
|
Header_getSubChunk1ID = (*env)->GetMethodID(env, tempClass, "getSubChunk1ID", "()[B");
|
||||||
|
if (Header_getSubChunk1ID == 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Could not find wave/WavHeader#getSubChunk1ID\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Header_getSubChunk1Size == 0)
|
||||||
|
{
|
||||||
|
Header_getSubChunk1Size = (*env)->GetMethodID(env, tempClass, "getSubChunk1Size", "()I");
|
||||||
|
if (Header_getSubChunk1Size == 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Could not find wave/WavHeader#getSubChunk1Size\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Header_getAudioFormat == 0)
|
||||||
|
{
|
||||||
|
Header_getAudioFormat = (*env)->GetMethodID(env, tempClass, "getAudioFormat", "()S");
|
||||||
|
if (Header_getAudioFormat == 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Could not find wave/WavHeader#getAudioFormat\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Header_getNumChannels == 0)
|
||||||
|
{
|
||||||
|
Header_getNumChannels = (*env)->GetMethodID(env, tempClass, "getNumChannels", "()S");
|
||||||
|
if (Header_getNumChannels == 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Could not find wave/WavHeader#getNumChannels\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Header_getSampleRate == 0)
|
||||||
|
{
|
||||||
|
Header_getSampleRate = (*env)->GetMethodID(env, tempClass, "getSampleRate", "()I");
|
||||||
|
if (Header_getSampleRate == 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Could not find wave/WavHeader#getSampleRate\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Header_getByteRate == 0)
|
||||||
|
{
|
||||||
|
Header_getByteRate = (*env)->GetMethodID(env, tempClass, "getByteRate", "()I");
|
||||||
|
if (Header_getByteRate == 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Could not find wave/WavHeader#getByteRate\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Header_getBlockAlign == 0)
|
||||||
|
{
|
||||||
|
Header_getBlockAlign = (*env)->GetMethodID(env, tempClass, "getBlockAlign", "()S");
|
||||||
|
if (Header_getBlockAlign == 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Could not find wave/WavHeader#getBlockAlign\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Header_getBitsPerSample == 0)
|
||||||
|
{
|
||||||
|
Header_getBitsPerSample = (*env)->GetMethodID(env, tempClass, "getBitsPerSample", "()S");
|
||||||
|
if (Header_getBitsPerSample == 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Could not find wave/WavHeader#getBitsPerSample\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Header_getSubChunk2ID == 0)
|
||||||
|
{
|
||||||
|
Header_getSubChunk2ID = (*env)->GetMethodID(env, tempClass, "getSubChunk2ID", "()[B");
|
||||||
|
if (Header_getSubChunk2ID == 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Could not find wave/WavHeader#getSubChunk2ID\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Header_getSubChunk2Size == 0)
|
||||||
|
{
|
||||||
|
Header_getSubChunk2Size = (*env)->GetMethodID(env, tempClass, "getSubChunk2Size", "()I");
|
||||||
|
if (Header_getSubChunk2Size == 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Could not find wave/WavHeader#getSubChunk2Size\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtains an audio stream's header.
|
* Obtains an audio stream's header.
|
||||||
* @param env - A reference to the JVM.
|
* @param env - A reference to the JVM.
|
||||||
@@ -213,12 +367,12 @@ int readBuffer(JNIEnv *env, jobject stream, char* buffer, int bufferSize)
|
|||||||
getReadMethod(env, stream);
|
getReadMethod(env, stream);
|
||||||
if (InputStream_readBuffer == 0)
|
if (InputStream_readBuffer == 0)
|
||||||
{
|
{
|
||||||
printf("readBuffer method not found\n");
|
fprintf(stderr, "readBuffer method not found\n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if (AudioStream_readInt == 0)
|
if (AudioStream_readInt == 0)
|
||||||
{
|
{
|
||||||
printf("readInt method not found\n");
|
fprintf(stderr, "readInt method not found\n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -234,24 +388,49 @@ int readBuffer(JNIEnv *env, jobject stream, char* buffer, int bufferSize)
|
|||||||
return returnValue;
|
return returnValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a WavHeader into something more easily accessible by native code.
|
||||||
|
*
|
||||||
|
* @param env - The JVM.
|
||||||
|
* @param header - The header to read.
|
||||||
|
* @param headerStruct - The header to write the data to.
|
||||||
|
*/
|
||||||
|
HeaderData readHeader(JNIEnv *env, jobject header, HeaderData *headerStruct)
|
||||||
|
{
|
||||||
|
if (Header == 0)
|
||||||
|
{
|
||||||
|
getHeaderMethods(env);
|
||||||
|
}
|
||||||
|
headerStruct->numChannels = (short) (*env)->CallShortMethod(env, header, Header_getNumChannels);
|
||||||
|
headerStruct->bytesPerChannel = (short) (*env)->CallShortMethod(env, header, Header_getBitsPerSample);
|
||||||
|
headerStruct->sampleRate = (int) (*env)->CallIntMethod(env, header, Header_getSampleRate);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the file currently being used
|
* Sets the file currently being used
|
||||||
*/
|
*/
|
||||||
JNIEXPORT jint JNICALL Java_edu_regis_universeplayer_localPlayer_Player_setCurrentFile(JNIEnv *env, jobject obj, jobject stream, jshort numChannels, jshort bitsPerSample, jint sampleRate)
|
JNIEXPORT jint JNICALL Java_edu_regis_universeplayer_localPlayer_Player_setCurrentFile(JNIEnv *env, jobject obj, jobject stream, jshort numChannels, jshort bitsPerSample, jint sampleRate)
|
||||||
{
|
{
|
||||||
|
jobject header;
|
||||||
if (currentFile != NULL)
|
if (currentFile != NULL)
|
||||||
{
|
{
|
||||||
(*env)->DeleteLocalRef(env, currentFile);
|
(*env)->DeleteLocalRef(env, currentFile);
|
||||||
}
|
}
|
||||||
currentFile = (*env)->NewGlobalRef(env, stream);
|
currentFile = (*env)->NewGlobalRef(env, stream);
|
||||||
printf("Setting File\n");
|
header = getHeader(env, stream);
|
||||||
return 0;
|
readHeader(env, header, ¤tHeader);
|
||||||
|
|
||||||
|
return updatePlayer(env, obj, stream, numChannels, bitsPerSample, sampleRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL Java_edu_regis_universeplayer_localPlayer_Player_save(JNIEnv *env, jobject obj, jstring location)
|
JNIEXPORT void JNICALL Java_edu_regis_universeplayer_localPlayer_Player_save(JNIEnv *env, jobject obj, jstring location)
|
||||||
{
|
{
|
||||||
int BUFFER_SIZE = 256;
|
const int BUFFER_SIZE = 256;
|
||||||
char buffer[BUFFER_SIZE];
|
/*
|
||||||
|
* Windows doesn't seem to like using even a constant for determining array
|
||||||
|
* size.
|
||||||
|
*/
|
||||||
|
char buffer[256];
|
||||||
int read;
|
int read;
|
||||||
|
|
||||||
FILE *output;
|
FILE *output;
|
||||||
@@ -262,7 +441,7 @@ JNIEXPORT void JNICALL Java_edu_regis_universeplayer_localPlayer_Player_save(JNI
|
|||||||
|
|
||||||
if (output == NULL)
|
if (output == NULL)
|
||||||
{
|
{
|
||||||
printf("File %s can't be opened\n", locationPath);
|
fprintf(stderr, "File %s can't be opened\n", locationPath);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,7 +449,7 @@ JNIEXPORT void JNICALL Java_edu_regis_universeplayer_localPlayer_Player_save(JNI
|
|||||||
jbyteArray headerData = getByteStream(env, currentFile);
|
jbyteArray headerData = getByteStream(env, currentFile);
|
||||||
jsize headerLength = (*env)->GetArrayLength(env, headerData);
|
jsize headerLength = (*env)->GetArrayLength(env, headerData);
|
||||||
jbyte* header = (*env)->GetByteArrayElements(env, headerData, 0);
|
jbyte* header = (*env)->GetByteArrayElements(env, headerData, 0);
|
||||||
char headerBuff[headerLength];
|
char headerBuff[256];
|
||||||
for (int i = 0; i < headerLength; i++)
|
for (int i = 0; i < headerLength; i++)
|
||||||
{
|
{
|
||||||
headerBuff[i] = header[i];
|
headerBuff[i] = header[i];
|
||||||
|
|||||||
11
player/src/main/c/player_alsa.c
Normal file
11
player/src/main/c/player_alsa.c
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
#include <jni.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int updatePlayer(JNIEnv *env, jobject obj, jobject stream, jshort numChannels, jshort bitsPerSample, jint sampleRate)
|
||||||
|
{
|
||||||
|
printf("Updating File\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -23,4 +23,4 @@ rootProject.name = 'UniversalMusicPlayer'
|
|||||||
include ':interface'
|
include ':interface'
|
||||||
include ':add-on'
|
include ':add-on'
|
||||||
include ':player'
|
include ':player'
|
||||||
include ':WavReader'
|
include ':libwave'
|
||||||
|
|||||||
Reference in New Issue
Block a user