Initial code check in continued

This commit is contained in:
dennis.sheirer
2014-04-26 19:48:24 +00:00
parent 960a6f9952
commit 3dfedcf8d1
474 changed files with 76613 additions and 0 deletions

32
src/util/Angle.java Normal file
View File

@@ -0,0 +1,32 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package util;
public class Angle
{
public static double degreesFromRadians( double radians )
{
return radians * ( 180 / Math.PI );
}
public static double radiansFromDegrees( double degrees )
{
return degrees * ( Math.PI / 180 );
}
}

262
src/util/ArcTangent.java Normal file
View File

@@ -0,0 +1,262 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
* ----------------------------------------------
* Adapted from GNU Radio's gr_fast_atan2f() method, retrieved Jan 2013 from
* https://raw.github.com/gnuradio/gnuradio/master/gnuradio-core/src/lib/general/gr_fast_atan2f.cc
*
* Copyright 2005 Free Software Foundation, Inc.
*
* This file is part of GNU Radio
*
* GNU Radio is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3, or (at your option)
* any later version.
*
* GNU Radio 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GNU Radio; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
package util;
import sample.complex.ComplexSample;
public class ArcTangent
{
public static final double sTANGENT_MAP_RESOLUTION = 0.003921549;
public static final double sRADIANS_PER_DEGREE = 0.017453293;
public static final int sTANGENT_MAP_SIZE = 256;
public static final double sHALF_PI = Math.PI / 2;
//ArcTangents from 0 to pi/4 radians
private static double[] sLOOKUP_TABLE = new double[] {
0.000000e+00, 3.921549e-03, 7.842976e-03, 1.176416e-02,
1.568499e-02, 1.960533e-02, 2.352507e-02, 2.744409e-02,
3.136226e-02, 3.527947e-02, 3.919560e-02, 4.311053e-02,
4.702413e-02, 5.093629e-02, 5.484690e-02, 5.875582e-02,
6.266295e-02, 6.656816e-02, 7.047134e-02, 7.437238e-02,
7.827114e-02, 8.216752e-02, 8.606141e-02, 8.995267e-02,
9.384121e-02, 9.772691e-02, 1.016096e-01, 1.054893e-01,
1.093658e-01, 1.132390e-01, 1.171087e-01, 1.209750e-01,
1.248376e-01, 1.286965e-01, 1.325515e-01, 1.364026e-01,
1.402496e-01, 1.440924e-01, 1.479310e-01, 1.517652e-01,
1.555948e-01, 1.594199e-01, 1.632403e-01, 1.670559e-01,
1.708665e-01, 1.746722e-01, 1.784728e-01, 1.822681e-01,
1.860582e-01, 1.898428e-01, 1.936220e-01, 1.973956e-01,
2.011634e-01, 2.049255e-01, 2.086818e-01, 2.124320e-01,
2.161762e-01, 2.199143e-01, 2.236461e-01, 2.273716e-01,
2.310907e-01, 2.348033e-01, 2.385093e-01, 2.422086e-01,
2.459012e-01, 2.495869e-01, 2.532658e-01, 2.569376e-01,
2.606024e-01, 2.642600e-01, 2.679104e-01, 2.715535e-01,
2.751892e-01, 2.788175e-01, 2.824383e-01, 2.860514e-01,
2.896569e-01, 2.932547e-01, 2.968447e-01, 3.004268e-01,
3.040009e-01, 3.075671e-01, 3.111252e-01, 3.146752e-01,
3.182170e-01, 3.217506e-01, 3.252758e-01, 3.287927e-01,
3.323012e-01, 3.358012e-01, 3.392926e-01, 3.427755e-01,
3.462497e-01, 3.497153e-01, 3.531721e-01, 3.566201e-01,
3.600593e-01, 3.634896e-01, 3.669110e-01, 3.703234e-01,
3.737268e-01, 3.771211e-01, 3.805064e-01, 3.838825e-01,
3.872494e-01, 3.906070e-01, 3.939555e-01, 3.972946e-01,
4.006244e-01, 4.039448e-01, 4.072558e-01, 4.105574e-01,
4.138496e-01, 4.171322e-01, 4.204054e-01, 4.236689e-01,
4.269229e-01, 4.301673e-01, 4.334021e-01, 4.366272e-01,
4.398426e-01, 4.430483e-01, 4.462443e-01, 4.494306e-01,
4.526070e-01, 4.557738e-01, 4.589307e-01, 4.620778e-01,
4.652150e-01, 4.683424e-01, 4.714600e-01, 4.745676e-01,
4.776654e-01, 4.807532e-01, 4.838312e-01, 4.868992e-01,
4.899573e-01, 4.930055e-01, 4.960437e-01, 4.990719e-01,
5.020902e-01, 5.050985e-01, 5.080968e-01, 5.110852e-01,
5.140636e-01, 5.170320e-01, 5.199904e-01, 5.229388e-01,
5.258772e-01, 5.288056e-01, 5.317241e-01, 5.346325e-01,
5.375310e-01, 5.404195e-01, 5.432980e-01, 5.461666e-01,
5.490251e-01, 5.518738e-01, 5.547124e-01, 5.575411e-01,
5.603599e-01, 5.631687e-01, 5.659676e-01, 5.687566e-01,
5.715357e-01, 5.743048e-01, 5.770641e-01, 5.798135e-01,
5.825531e-01, 5.852828e-01, 5.880026e-01, 5.907126e-01,
5.934128e-01, 5.961032e-01, 5.987839e-01, 6.014547e-01,
6.041158e-01, 6.067672e-01, 6.094088e-01, 6.120407e-01,
6.146630e-01, 6.172755e-01, 6.198784e-01, 6.224717e-01,
6.250554e-01, 6.276294e-01, 6.301939e-01, 6.327488e-01,
6.352942e-01, 6.378301e-01, 6.403565e-01, 6.428734e-01,
6.453808e-01, 6.478788e-01, 6.503674e-01, 6.528466e-01,
6.553165e-01, 6.577770e-01, 6.602282e-01, 6.626701e-01,
6.651027e-01, 6.675261e-01, 6.699402e-01, 6.723452e-01,
6.747409e-01, 6.771276e-01, 6.795051e-01, 6.818735e-01,
6.842328e-01, 6.865831e-01, 6.889244e-01, 6.912567e-01,
6.935800e-01, 6.958943e-01, 6.981998e-01, 7.004964e-01,
7.027841e-01, 7.050630e-01, 7.073330e-01, 7.095943e-01,
7.118469e-01, 7.140907e-01, 7.163258e-01, 7.185523e-01,
7.207701e-01, 7.229794e-01, 7.251800e-01, 7.273721e-01,
7.295557e-01, 7.317307e-01, 7.338974e-01, 7.360555e-01,
7.382053e-01, 7.403467e-01, 7.424797e-01, 7.446045e-01,
7.467209e-01, 7.488291e-01, 7.509291e-01, 7.530208e-01,
7.551044e-01, 7.571798e-01, 7.592472e-01, 7.613064e-01,
7.633576e-01, 7.654008e-01, 7.674360e-01, 7.694633e-01,
7.714826e-01, 7.734940e-01, 7.754975e-01, 7.774932e-01,
7.794811e-01, 7.814612e-01, 7.834335e-01, 7.853983e-01,
7.853983e-01 };
/**
* Lookup radian angle from polar coordinates represented by the
* ComplexSample
*
* @param sample representing polar coordinates
* @return - angle in radians
*/
public static double getAngle( ComplexSample sample )
{
return getAngle( ( (double) sample.inphase() ),
( (double) sample.quadrature() ) );
}
/**
* Lookup radian angle from polar coordinates x and y
* @param x - x polar coordinate
* @param y - y polar coordinate
* @return - angle in radians
*/
public static double getAngle( double x, double y )
{
/**
* Calculates the angle of the vector (x,y) based on a table lookup and
* linear interpolation. The table uses a 256 point table covering
* -45 to +45 degrees and uses symmetry to determine the final angle
* value in the range of -180 to 180 degrees. Note that this function
* uses the small angle approximation for values close to zero. This
* routine calculates the arc tangent with an average error of
* +/- 0.045 degrees.
*/
double alpha;
double angle;
double base_angle;
int index;
//Check for divide by zero
if( x == 0.0 && y == 0.0 )
{
return 0.0;
}
//Normalize to +/- 45 degree range
double x_abs = Math.abs( x );
double y_abs = Math.abs( y );
//Calculate the ratio ( opposite/adjacent )
double z;
if( x_abs < y_abs )
{
z = x_abs / y_abs;
}
else
{
z = y_abs / x_abs;
}
//When the ratio approaches table resolution, the angle is best
//approximated with the argument itself
if( z < sTANGENT_MAP_RESOLUTION )
{
base_angle = z;
}
else
{
alpha = z * (double)( sTANGENT_MAP_SIZE - .5 );
index = (int)alpha;
alpha -= (double)index;
//Determine base angle based on quadrant and add or subtract table
//value from base angle, based on quadrant
base_angle = sLOOKUP_TABLE[ index ];
base_angle += ( sLOOKUP_TABLE[ index + 1 ] -
sLOOKUP_TABLE[ index ] ) * alpha;
}
//Map the angle to the correct quadrant
if( x_abs > y_abs ) // -45 to 45, or 135 to -135
{
if( x >= 0.0 ) // -45 to 45
{
if( y >= 0.0 )
{
angle = base_angle; // 0 to 45
}
else
{
angle = -base_angle; // -45 to 0
}
}
else // 135 to 180 or -180 to -135
{
angle = Math.PI;
if( y >= 0.0 )
{
angle -= base_angle; // 135 to 180
}
else
{
angle = base_angle - angle; // 180 to -135
}
}
}
else // 45 to 135, or -135 to -45
{
if( y >= 0.0 ) //45 to 135
{
angle = sHALF_PI;
if( x >= 0.0 )
{
angle -= base_angle; // 45 to 90
}
else
{
angle += base_angle; // 90 to 135
}
}
else
{
angle = -sHALF_PI;
if( x >= 0.0 )
{
angle += base_angle; //-90 to -45
}
else
{
angle -= base_angle; //-135 to -90
}
}
}
// //Check for negative radian angle value
//
// if( angle < 0 )
// {
// angle += 2 * Math.PI;
// }
return angle;
}
}

60
src/util/ColorIcon.java Normal file
View File

@@ -0,0 +1,60 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package util;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import javax.swing.Icon;
public class ColorIcon implements Icon
{
private static final int sSIZE = 12;
private static final int sARC_SIZE = 3;
private Color mColor;
public ColorIcon( Color color )
{
mColor = color;
}
@Override
public void paintIcon(Component c, Graphics g, int x, int y)
{
Color temp = g.getColor();
g.setColor( mColor );
g.fillRoundRect( x, y, sSIZE, sSIZE, sARC_SIZE, sARC_SIZE );
g.setColor( temp );
}
@Override
public int getIconWidth()
{
return sSIZE;
}
@Override
public int getIconHeight()
{
return sSIZE;
}
}

97
src/util/Oscillator.java Normal file
View File

@@ -0,0 +1,97 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package util;
import sample.complex.ComplexSample;
public class Oscillator
{
private double mFrequency;
private double mSampleRate;
private ComplexSample mAnglePerSample;
private ComplexSample mCurrentAngle = new ComplexSample( 0.0f, -1.0f );
/**
* Oscillator produces complex or float samples corresponding to a sine wave
* oscillating at the specified frequency and sample rate
*
* @param frequency - positive or negative frequency in hertz
* @param sampleRate - in hertz
*/
public Oscillator( int frequency, int sampleRate )
{
mSampleRate = (double)sampleRate;
mFrequency = (double)frequency;
update();
}
private void update()
{
float anglePerSample =
(float)( 2.0d * Math.PI * mFrequency / mSampleRate );
mAnglePerSample = ComplexSample.fromAngle( anglePerSample );
}
/**
* Sets or changes the frequency of this oscillator
*/
public void setFrequency( int frequency )
{
mFrequency = (double)frequency;
update();
}
/**
* Sets or changes the sample rate of this oscillator
*/
public void setSampleRate( int sampleRate )
{
mSampleRate = (double)sampleRate;
update();
}
/**
* Steps the current angle by the angle per sample amount
*/
private void rotate()
{
mCurrentAngle.multiply( mAnglePerSample );
mCurrentAngle.fastNormalize();
}
/**
* Get next complex sample
*/
public ComplexSample nextComplex()
{
rotate();
return mCurrentAngle;
}
/**
* Get the next float sample
*/
public float nextFloat()
{
rotate();
return mCurrentAngle.real();
}
}

84
src/util/TimeStamp.java Normal file
View File

@@ -0,0 +1,84 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package util;
import java.text.SimpleDateFormat;
import java.util.Date;
public class TimeStamp
{
private static SimpleDateFormat mSDFDate = new SimpleDateFormat( "yyyyMMdd" );
private static SimpleDateFormat mSDFTime = new SimpleDateFormat( "HHmmss" );
/**
* Returns the current system date formatted as yyyy-MM-dd
*/
public static String getFormattedDate()
{
return getFormattedDate( System.currentTimeMillis() );
}
/**
* Returns the timestamp formatted as a date of yyyy-MM-dd
*/
public static String getFormattedDate( long timestamp )
{
return mSDFDate.format( new Date( timestamp ) );
}
/**
* Returns the current system time formatted as HH:mm:ss
*/
public static String getFormattedTime()
{
return getFormattedTime( System.currentTimeMillis() );
}
/**
* Returns the timestamp formatted as a time of HH:mm:ss
*/
public static String getFormattedTime( long timestamp )
{
return mSDFTime.format( new Date( timestamp ) );
}
/**
* Returns current system time formatted as yyyy-MM-dd*HH:mm:ss
* with the * representing the separator attribute
*/
public static String getTimeStamp( String separator )
{
return getTimeStamp( System.currentTimeMillis(), separator );
}
/**
* Returns timestamp formatted as yyyy-MM-dd*HH:mm:ss
* with the * representing the separator attribute
*/
public static String getTimeStamp( long timestamp, String separator )
{
StringBuilder sb = new StringBuilder();
sb.append( getFormattedDate( timestamp ) );
sb.append( separator );
sb.append( getFormattedTime( timestamp ) );
return sb.toString();
}
}

View File

@@ -0,0 +1,864 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package util.waveaudio;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
public class WavFile
{
private enum IOState {READING, WRITING, CLOSED};
private final static int BUFFER_SIZE = 4096;
private final static int FMT_CHUNK_ID = 0x20746D66;
private final static int DATA_CHUNK_ID = 0x61746164;
private final static int RIFF_CHUNK_ID = 0x46464952;
private final static int RIFF_TYPE_ID = 0x45564157;
private File file; // File that will be read from or written to
private IOState ioState; // Specifies the IO State of the Wav File (used for snaity checking)
private int bytesPerSample; // Number of bytes required to store a single sample
private long numFrames; // Number of frames within the data section
private FileOutputStream oStream; // Output stream used for writting data
private FileInputStream iStream; // Input stream used for reading data
private double floatScale; // Scaling factor used for int <-> float conversion
private double floatOffset; // Offset factor used for int <-> float conversion
private boolean wordAlignAdjust; // Specify if an extra byte at the end of the data chunk is required for word alignment
// Wav Header
private int numChannels; // 2 bytes unsigned, 0x0001 (1) to 0xFFFF (65,535)
private long sampleRate; // 4 bytes unsigned, 0x00000001 (1) to 0xFFFFFFFF (4,294,967,295)
// Although a java int is 4 bytes, it is signed, so need to use a long
private int blockAlign; // 2 bytes unsigned, 0x0001 (1) to 0xFFFF (65,535)
private int validBits; // 2 bytes unsigned, 0x0002 (2) to 0xFFFF (65,535)
// Buffering
private byte[] buffer; // Local buffer used for IO
private int bufferPointer; // Points to the current position in local buffer
private int bytesRead; // Bytes read after last read into local buffer
private long frameCounter; // Current number of frames read or written
// Cannot instantiate WavFile directly, must either use newWavFile() or openWavFile()
private WavFile()
{
buffer = new byte[BUFFER_SIZE];
}
public int getNumChannels()
{
return numChannels;
}
public long getNumFrames()
{
return numFrames;
}
public long getFramesRemaining()
{
return numFrames - frameCounter;
}
public long getSampleRate()
{
return sampleRate;
}
public int getValidBits()
{
return validBits;
}
public static WavFile newWavFile(File file, int numChannels, long numFrames, int validBits, long sampleRate) throws IOException, WavFileException
{
// Instantiate new Wavfile and initialise
WavFile wavFile = new WavFile();
wavFile.file = file;
wavFile.numChannels = numChannels;
wavFile.numFrames = numFrames;
wavFile.sampleRate = sampleRate;
wavFile.bytesPerSample = (validBits + 7) / 8;
wavFile.blockAlign = wavFile.bytesPerSample * numChannels;
wavFile.validBits = validBits;
// Sanity check arguments
if (numChannels < 1 || numChannels > 65535) throw new WavFileException("Illegal number of channels, valid range 1 to 65536");
if (numFrames < 0) throw new WavFileException("Number of frames must be positive");
if (validBits < 2 || validBits > 65535) throw new WavFileException("Illegal number of valid bits, valid range 2 to 65536");
if (sampleRate < 0) throw new WavFileException("Sample rate must be positive");
// Create output stream for writing data
wavFile.oStream = new FileOutputStream(file);
// Calculate the chunk sizes
long dataChunkSize = wavFile.blockAlign * numFrames;
long mainChunkSize = 4 + // Riff Type
8 + // Format ID and size
16 + // Format data
8 + // Data ID and size
dataChunkSize;
// Chunks must be word aligned, so if odd number of audio data bytes
// adjust the main chunk size
if (dataChunkSize % 2 == 1) {
mainChunkSize += 1;
wavFile.wordAlignAdjust = true;
}
else {
wavFile.wordAlignAdjust = false;
}
// Set the main chunk size
putLE(RIFF_CHUNK_ID, wavFile.buffer, 0, 4);
putLE(mainChunkSize, wavFile.buffer, 4, 4);
putLE(RIFF_TYPE_ID, wavFile.buffer, 8, 4);
// Write out the header
wavFile.oStream.write(wavFile.buffer, 0, 12);
// Put format data in buffer
long averageBytesPerSecond = sampleRate * wavFile.blockAlign;
putLE(FMT_CHUNK_ID, wavFile.buffer, 0, 4); // Chunk ID
putLE(16, wavFile.buffer, 4, 4); // Chunk Data Size
putLE(1, wavFile.buffer, 8, 2); // Compression Code (Uncompressed)
putLE(numChannels, wavFile.buffer, 10, 2); // Number of channels
putLE(sampleRate, wavFile.buffer, 12, 4); // Sample Rate
putLE(averageBytesPerSecond, wavFile.buffer, 16, 4); // Average Bytes Per Second
putLE(wavFile.blockAlign, wavFile.buffer, 20, 2); // Block Align
putLE(validBits, wavFile.buffer, 22, 2); // Valid Bits
// Write Format Chunk
wavFile.oStream.write(wavFile.buffer, 0, 24);
// Start Data Chunk
putLE(DATA_CHUNK_ID, wavFile.buffer, 0, 4); // Chunk ID
putLE(dataChunkSize, wavFile.buffer, 4, 4); // Chunk Data Size
// Write Format Chunk
wavFile.oStream.write(wavFile.buffer, 0, 8);
// Calculate the scaling factor for converting to a normalised double
if (wavFile.validBits > 8)
{
// If more than 8 validBits, data is signed
// Conversion required multiplying by magnitude of max positive value
wavFile.floatOffset = 0;
wavFile.floatScale = Long.MAX_VALUE >> (64 - wavFile.validBits);
}
else
{
// Else if 8 or less validBits, data is unsigned
// Conversion required dividing by max positive value
wavFile.floatOffset = 1;
wavFile.floatScale = 0.5 * ((1 << wavFile.validBits) - 1);
}
// Finally, set the IO State
wavFile.bufferPointer = 0;
wavFile.bytesRead = 0;
wavFile.frameCounter = 0;
wavFile.ioState = IOState.WRITING;
return wavFile;
}
public static WavFile openWavFile(File file) throws IOException, WavFileException
{
// Instantiate new Wavfile and store the file reference
WavFile wavFile = new WavFile();
wavFile.file = file;
// Create a new file input stream for reading file data
wavFile.iStream = new FileInputStream(file);
// Read the first 12 bytes of the file
int bytesRead = wavFile.iStream.read(wavFile.buffer, 0, 12);
if (bytesRead != 12) throw new WavFileException("Not enough wav file bytes for header");
// Extract parts from the header
long riffChunkID = getLE(wavFile.buffer, 0, 4);
long chunkSize = getLE(wavFile.buffer, 4, 4);
long riffTypeID = getLE(wavFile.buffer, 8, 4);
// Check the header bytes contains the correct signature
if (riffChunkID != RIFF_CHUNK_ID) throw new WavFileException("Invalid Wav Header data, incorrect riff chunk ID");
if (riffTypeID != RIFF_TYPE_ID) throw new WavFileException("Invalid Wav Header data, incorrect riff type ID");
// Check that the file size matches the number of bytes listed in header
if (file.length() != chunkSize+8) {
throw new WavFileException("Header chunk size (" + chunkSize + ") does not match file size (" + file.length() + ")");
}
boolean foundFormat = false;
boolean foundData = false;
// Search for the Format and Data Chunks
while (true)
{
// Read the first 8 bytes of the chunk (ID and chunk size)
bytesRead = wavFile.iStream.read(wavFile.buffer, 0, 8);
if (bytesRead == -1) throw new WavFileException("Reached end of file without finding format chunk");
if (bytesRead != 8) throw new WavFileException("Could not read chunk header");
// Extract the chunk ID and Size
long chunkID = getLE(wavFile.buffer, 0, 4);
chunkSize = getLE(wavFile.buffer, 4, 4);
// Word align the chunk size
// chunkSize specifies the number of bytes holding data. However,
// the data should be word aligned (2 bytes) so we need to calculate
// the actual number of bytes in the chunk
long numChunkBytes = (chunkSize%2 == 1) ? chunkSize+1 : chunkSize;
if (chunkID == FMT_CHUNK_ID)
{
// Flag that the format chunk has been found
foundFormat = true;
// Read in the header info
bytesRead = wavFile.iStream.read(wavFile.buffer, 0, 16);
// Check this is uncompressed data
int compressionCode = (int) getLE(wavFile.buffer, 0, 2);
if (compressionCode != 1) throw new WavFileException("Compression Code " + compressionCode + " not supported");
// Extract the format information
wavFile.numChannels = (int) getLE(wavFile.buffer, 2, 2);
wavFile.sampleRate = getLE(wavFile.buffer, 4, 4);
wavFile.blockAlign = (int) getLE(wavFile.buffer, 12, 2);
wavFile.validBits = (int) getLE(wavFile.buffer, 14, 2);
if (wavFile.numChannels == 0) throw new WavFileException("Number of channels specified in header is equal to zero");
if (wavFile.blockAlign == 0) throw new WavFileException("Block Align specified in header is equal to zero");
if (wavFile.validBits < 2) throw new WavFileException("Valid Bits specified in header is less than 2");
if (wavFile.validBits > 64) throw new WavFileException("Valid Bits specified in header is greater than 64, this is greater than a long can hold");
// Calculate the number of bytes required to hold 1 sample
wavFile.bytesPerSample = (wavFile.validBits + 7) / 8;
if (wavFile.bytesPerSample * wavFile.numChannels != wavFile.blockAlign)
throw new WavFileException("Block Align does not agree with bytes required for validBits and number of channels");
// Account for number of format bytes and then skip over
// any extra format bytes
numChunkBytes -= 16;
if (numChunkBytes > 0) wavFile.iStream.skip(numChunkBytes);
}
else if (chunkID == DATA_CHUNK_ID)
{
// Check if we've found the format chunk,
// If not, throw an exception as we need the format information
// before we can read the data chunk
if (foundFormat == false) throw new WavFileException("Data chunk found before Format chunk");
// Check that the chunkSize (wav data length) is a multiple of the
// block align (bytes per frame)
if (chunkSize % wavFile.blockAlign != 0) throw new WavFileException("Data Chunk size is not multiple of Block Align");
// Calculate the number of frames
wavFile.numFrames = chunkSize / wavFile.blockAlign;
// Flag that we've found the wave data chunk
foundData = true;
break;
}
else
{
// If an unknown chunk ID is found, just skip over the chunk data
wavFile.iStream.skip(numChunkBytes);
}
}
// Throw an exception if no data chunk has been found
if (foundData == false) throw new WavFileException("Did not find a data chunk");
// Calculate the scaling factor for converting to a normalised double
if (wavFile.validBits > 8)
{
// If more than 8 validBits, data is signed
// Conversion required dividing by magnitude of max negative value
wavFile.floatOffset = 0;
wavFile.floatScale = 1 << (wavFile.validBits - 1);
}
else
{
// Else if 8 or less validBits, data is unsigned
// Conversion required dividing by max positive value
wavFile.floatOffset = -1;
wavFile.floatScale = 0.5 * ((1 << wavFile.validBits) - 1);
}
wavFile.bufferPointer = 0;
wavFile.bytesRead = 0;
wavFile.frameCounter = 0;
wavFile.ioState = IOState.READING;
return wavFile;
}
// Get and Put little endian data from local buffer
// ------------------------------------------------
private static long getLE(byte[] buffer, int pos, int numBytes)
{
numBytes --;
pos += numBytes;
long val = buffer[pos] & 0xFF;
for (int b=0 ; b<numBytes ; b++) val = (val << 8) + (buffer[--pos] & 0xFF);
return val;
}
private static void putLE(long val, byte[] buffer, int pos, int numBytes)
{
for (int b=0 ; b<numBytes ; b++)
{
buffer[pos] = (byte) (val & 0xFF);
val >>= 8;
pos ++;
}
}
// Sample Writing and Reading
// --------------------------
private void writeSample(long val) throws IOException
{
for (int b=0 ; b<bytesPerSample ; b++)
{
if (bufferPointer == BUFFER_SIZE)
{
oStream.write(buffer, 0, BUFFER_SIZE);
bufferPointer = 0;
}
buffer[bufferPointer] = (byte) (val & 0xFF);
val >>= 8;
bufferPointer ++;
}
}
private long readSample() throws IOException, WavFileException
{
long val = 0;
for (int b=0 ; b<bytesPerSample ; b++)
{
if (bufferPointer == bytesRead)
{
int read = iStream.read(buffer, 0, BUFFER_SIZE);
if (read == -1) throw new WavFileException("Not enough data available");
bytesRead = read;
bufferPointer = 0;
}
int v = buffer[bufferPointer];
if (b < bytesPerSample-1 || bytesPerSample == 1) v &= 0xFF;
val += v << (b * 8);
bufferPointer ++;
}
return val;
}
// Short
public int readFrames(short[] sampleBuffer, int numFramesToRead) throws IOException, WavFileException
{
return readFrames(sampleBuffer, 0, numFramesToRead);
}
public int readFrames(short[] sampleBuffer, int offset, int numFramesToRead) throws IOException, WavFileException
{
if (ioState != IOState.READING) throw new IOException("Cannot read from WavFile instance");
for (int f=0 ; f<numFramesToRead ; f++)
{
if (frameCounter == numFrames) return f;
for (int c=0 ; c<numChannels ; c++)
{
sampleBuffer[offset] = (short) readSample();
offset ++;
}
frameCounter ++;
}
return numFramesToRead;
}
public int readFrames(short[][] sampleBuffer, int numFramesToRead) throws IOException, WavFileException
{
return readFrames(sampleBuffer, 0, numFramesToRead);
}
public int readFrames(short[][] sampleBuffer, int offset, int numFramesToRead) throws IOException, WavFileException
{
if (ioState != IOState.READING) throw new IOException("Cannot read from WavFile instance");
for (int f=0 ; f<numFramesToRead ; f++)
{
if (frameCounter == numFrames) return f;
for (int c=0 ; c<numChannels ; c++) sampleBuffer[c][offset] = (short) readSample();
offset ++;
frameCounter ++;
}
return numFramesToRead;
}
public int writeFrames(short[] sampleBuffer, int numFramesToWrite) throws IOException, WavFileException
{
return writeFrames(sampleBuffer, 0, numFramesToWrite);
}
public int writeFrames(short[] sampleBuffer, int offset, int numFramesToWrite) throws IOException, WavFileException
{
if (ioState != IOState.WRITING) throw new IOException("Cannot write to WavFile instance");
for (int f=0 ; f<numFramesToWrite ; f++)
{
if (frameCounter == numFrames) return f;
for (int c=0 ; c<numChannels ; c++)
{
writeSample(sampleBuffer[offset]);
offset ++;
}
frameCounter ++;
}
return numFramesToWrite;
}
public int writeFrames(short[][] sampleBuffer, int numFramesToWrite) throws IOException, WavFileException
{
return writeFrames(sampleBuffer, 0, numFramesToWrite);
}
public int writeFrames(short[][] sampleBuffer, int offset, int numFramesToWrite) throws IOException, WavFileException
{
if (ioState != IOState.WRITING) throw new IOException("Cannot write to WavFile instance");
for (int f=0 ; f<numFramesToWrite ; f++)
{
if (frameCounter == numFrames) return f;
for (int c=0 ; c<numChannels ; c++) writeSample(sampleBuffer[c][offset]);
offset ++;
frameCounter ++;
}
return numFramesToWrite;
}
// Integer
// -------
public int readFrames(int[] sampleBuffer, int numFramesToRead) throws IOException, WavFileException
{
return readFrames(sampleBuffer, 0, numFramesToRead);
}
public int readFrames(int[] sampleBuffer, int offset, int numFramesToRead) throws IOException, WavFileException
{
if (ioState != IOState.READING) throw new IOException("Cannot read from WavFile instance");
for (int f=0 ; f<numFramesToRead ; f++)
{
if (frameCounter == numFrames) return f;
for (int c=0 ; c<numChannels ; c++)
{
sampleBuffer[offset] = (int) readSample();
offset ++;
}
frameCounter ++;
}
return numFramesToRead;
}
public int readFrames(int[][] sampleBuffer, int numFramesToRead) throws IOException, WavFileException
{
return readFrames(sampleBuffer, 0, numFramesToRead);
}
public int readFrames(int[][] sampleBuffer, int offset, int numFramesToRead) throws IOException, WavFileException
{
if (ioState != IOState.READING) throw new IOException("Cannot read from WavFile instance");
for (int f=0 ; f<numFramesToRead ; f++)
{
if (frameCounter == numFrames) return f;
for (int c=0 ; c<numChannels ; c++) sampleBuffer[c][offset] = (int) readSample();
offset ++;
frameCounter ++;
}
return numFramesToRead;
}
public int writeFrames(int[] sampleBuffer, int numFramesToWrite) throws IOException, WavFileException
{
return writeFrames(sampleBuffer, 0, numFramesToWrite);
}
public int writeFrames(int[] sampleBuffer, int offset, int numFramesToWrite) throws IOException, WavFileException
{
if (ioState != IOState.WRITING) throw new IOException("Cannot write to WavFile instance");
for (int f=0 ; f<numFramesToWrite ; f++)
{
if (frameCounter == numFrames) return f;
for (int c=0 ; c<numChannels ; c++)
{
writeSample(sampleBuffer[offset]);
offset ++;
}
frameCounter ++;
}
return numFramesToWrite;
}
public int writeFrames(int[][] sampleBuffer, int numFramesToWrite) throws IOException, WavFileException
{
return writeFrames(sampleBuffer, 0, numFramesToWrite);
}
public int writeFrames(int[][] sampleBuffer, int offset, int numFramesToWrite) throws IOException, WavFileException
{
if (ioState != IOState.WRITING) throw new IOException("Cannot write to WavFile instance");
for (int f=0 ; f<numFramesToWrite ; f++)
{
if (frameCounter == numFrames) return f;
for (int c=0 ; c<numChannels ; c++) writeSample(sampleBuffer[c][offset]);
offset ++;
frameCounter ++;
}
return numFramesToWrite;
}
// Long
// ----
public int readFrames(long[] sampleBuffer, int numFramesToRead) throws IOException, WavFileException
{
return readFrames(sampleBuffer, 0, numFramesToRead);
}
public int readFrames(long[] sampleBuffer, int offset, int numFramesToRead) throws IOException, WavFileException
{
if (ioState != IOState.READING) throw new IOException("Cannot read from WavFile instance");
for (int f=0 ; f<numFramesToRead ; f++)
{
if (frameCounter == numFrames) return f;
for (int c=0 ; c<numChannels ; c++)
{
sampleBuffer[offset] = readSample();
offset ++;
}
frameCounter ++;
}
return numFramesToRead;
}
public int readFrames(long[][] sampleBuffer, int numFramesToRead) throws IOException, WavFileException
{
return readFrames(sampleBuffer, 0, numFramesToRead);
}
public int readFrames(long[][] sampleBuffer, int offset, int numFramesToRead) throws IOException, WavFileException
{
if (ioState != IOState.READING) throw new IOException("Cannot read from WavFile instance");
for (int f=0 ; f<numFramesToRead ; f++)
{
if (frameCounter == numFrames) return f;
for (int c=0 ; c<numChannels ; c++) sampleBuffer[c][offset] = readSample();
offset ++;
frameCounter ++;
}
return numFramesToRead;
}
public int writeFrames(long[] sampleBuffer, int numFramesToWrite) throws IOException, WavFileException
{
return writeFrames(sampleBuffer, 0, numFramesToWrite);
}
public int writeFrames(long[] sampleBuffer, int offset, int numFramesToWrite) throws IOException, WavFileException
{
if (ioState != IOState.WRITING) throw new IOException("Cannot write to WavFile instance");
for (int f=0 ; f<numFramesToWrite ; f++)
{
if (frameCounter == numFrames) return f;
for (int c=0 ; c<numChannels ; c++)
{
writeSample(sampleBuffer[offset]);
offset ++;
}
frameCounter ++;
}
return numFramesToWrite;
}
public int writeFrames(long[][] sampleBuffer, int numFramesToWrite) throws IOException, WavFileException
{
return writeFrames(sampleBuffer, 0, numFramesToWrite);
}
public int writeFrames(long[][] sampleBuffer, int offset, int numFramesToWrite) throws IOException, WavFileException
{
if (ioState != IOState.WRITING) throw new IOException("Cannot write to WavFile instance");
for (int f=0 ; f<numFramesToWrite ; f++)
{
if (frameCounter == numFrames) return f;
for (int c=0 ; c<numChannels ; c++) writeSample(sampleBuffer[c][offset]);
offset ++;
frameCounter ++;
}
return numFramesToWrite;
}
// Double
// ------
public int readFrames(double[] sampleBuffer, int numFramesToRead) throws IOException, WavFileException
{
return readFrames(sampleBuffer, 0, numFramesToRead);
}
public int readFrames(double[] sampleBuffer, int offset, int numFramesToRead) throws IOException, WavFileException
{
if (ioState != IOState.READING) throw new IOException("Cannot read from WavFile instance");
for (int f=0 ; f<numFramesToRead ; f++)
{
if (frameCounter == numFrames) return f;
for (int c=0 ; c<numChannels ; c++)
{
sampleBuffer[offset] = floatOffset + (double) readSample() / floatScale;
offset ++;
}
frameCounter ++;
}
return numFramesToRead;
}
public int readFrames(double[][] sampleBuffer, int numFramesToRead) throws IOException, WavFileException
{
return readFrames(sampleBuffer, 0, numFramesToRead);
}
public int readFrames(double[][] sampleBuffer, int offset, int numFramesToRead) throws IOException, WavFileException
{
if (ioState != IOState.READING) throw new IOException("Cannot read from WavFile instance");
for (int f=0 ; f<numFramesToRead ; f++)
{
if (frameCounter == numFrames) return f;
for (int c=0 ; c<numChannels ; c++) sampleBuffer[c][offset] = floatOffset + (double) readSample() / floatScale;
offset ++;
frameCounter ++;
}
return numFramesToRead;
}
public int writeFrames(double[] sampleBuffer, int numFramesToWrite) throws IOException, WavFileException
{
return writeFrames(sampleBuffer, 0, numFramesToWrite);
}
public int writeFrames(double[] sampleBuffer, int offset, int numFramesToWrite) throws IOException, WavFileException
{
if (ioState != IOState.WRITING) throw new IOException("Cannot write to WavFile instance");
for (int f=0 ; f<numFramesToWrite ; f++)
{
if (frameCounter == numFrames) return f;
for (int c=0 ; c<numChannels ; c++)
{
writeSample((long) (floatScale * (floatOffset + sampleBuffer[offset])));
offset ++;
}
frameCounter ++;
}
return numFramesToWrite;
}
public int writeFrames(double[][] sampleBuffer, int numFramesToWrite) throws IOException, WavFileException
{
return writeFrames(sampleBuffer, 0, numFramesToWrite);
}
public int writeFrames(double[][] sampleBuffer, int offset, int numFramesToWrite) throws IOException, WavFileException
{
if (ioState != IOState.WRITING) throw new IOException("Cannot write to WavFile instance");
for (int f=0 ; f<numFramesToWrite ; f++)
{
if (frameCounter == numFrames) return f;
for (int c=0 ; c<numChannels ; c++) writeSample((long) (floatScale * (floatOffset + sampleBuffer[c][offset])));
offset ++;
frameCounter ++;
}
return numFramesToWrite;
}
public void close() throws IOException
{
// Close the input stream and set to null
if (iStream != null)
{
iStream.close();
iStream = null;
}
if (oStream != null)
{
// Write out anything still in the local buffer
if (bufferPointer > 0) oStream.write(buffer, 0, bufferPointer);
// If an extra byte is required for word alignment, add it to the end
if (wordAlignAdjust) oStream.write(0);
// Close the stream and set to null
oStream.close();
oStream = null;
}
// Flag that the stream is closed
ioState = IOState.CLOSED;
}
public void display()
{
display(System.out);
}
public void display(PrintStream out)
{
out.printf("File: %s\n", file);
out.printf("Channels: %d, Frames: %d\n", numChannels, numFrames);
out.printf("IO State: %s\n", ioState);
out.printf("Sample Rate: %d, Block Align: %d\n", sampleRate, blockAlign);
out.printf("Valid Bits: %d, Bytes per sample: %d\n", validBits, bytesPerSample);
}
public static void main(String[] args)
{
if (args.length < 1)
{
System.err.println("Must supply filename");
System.exit(1);
}
try
{
for (String filename : args)
{
WavFile readWavFile = openWavFile(new File(filename));
readWavFile.display();
long numFrames = readWavFile.getNumFrames();
int numChannels = readWavFile.getNumChannels();
int validBits = readWavFile.getValidBits();
long sampleRate = readWavFile.getSampleRate();
WavFile writeWavFile = newWavFile(new File("out.wav"), numChannels, numFrames, validBits, sampleRate);
final int BUF_SIZE = 5001;
// int[] buffer = new int[BUF_SIZE * numChannels];
// long[] buffer = new long[BUF_SIZE * numChannels];
double[] buffer = new double[BUF_SIZE * numChannels];
int framesRead = 0;
int framesWritten = 0;
do
{
framesRead = readWavFile.readFrames(buffer, BUF_SIZE);
framesWritten = writeWavFile.writeFrames(buffer, BUF_SIZE);
System.out.printf("%d %d\n", framesRead, framesWritten);
}
while (framesRead != 0);
readWavFile.close();
writeWavFile.close();
}
WavFile writeWavFile = newWavFile(new File("out2.wav"), 1, 10, 23, 44100);
double[] buffer = new double[10];
writeWavFile.writeFrames(buffer, 10);
writeWavFile.close();
}
catch (Exception e)
{
System.err.println(e);
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,43 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package util.waveaudio;
public class WavFileException extends Exception
{
public WavFileException()
{
super();
}
public WavFileException(String message)
{
super(message);
}
public WavFileException(String message, Throwable cause)
{
super(message, cause);
}
public WavFileException(Throwable cause)
{
super(cause);
}
}

View File

@@ -0,0 +1,149 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package util.waveaudio;
public class WaveUtils
{
/**
* Returns a byte array representing a wave file RIFF chunk descriptor,
* for mono channel, signed 16-bit audio sample recording.
*
* @param sampleRate - audio sample rate
*
* @param duration - recording duration in seconds. Note: this is a
* temporary value, since you won't know the exact duration until after the
* recording is complete. Your code should overwrite this initial value
* with the correct value, upon completion of the recording
*/
public static byte[] getRIFFChunkDescriptor( float sampleRate, int duration )
{
//Byte array large enough to hold the descriptor
byte[] buffer = new byte[ 12 ];
//Load "RIFF" into the descriptor (4-bytes)
loadDouble( buffer, 0x46464952, 0, 4 );
//Initial total size value ... will be overwritten later (4-bytes)
//Note: assumes that sample rate is an integer value
int totalSize = 36 + ( (int)sampleRate * duration );
loadDouble( buffer, totalSize, 4, 4);
//Load "WAVE" into the descriptor (4-bytes)
loadDouble( buffer, 0x45564157, 8, 4);
return buffer;
}
/**
* WAVE format descriptor
* @param sampleRate
*/
public static byte[] getWAVEFormatDescriptor( float sampleRate,
int numberOfChannels,
int bitsPerSample )
{
int bytesPerSample = bitsPerSample / 8;
//Byte array large enough to hold the descriptor
byte[] buffer = new byte[ 24 ];
//SubChunk1ID = "fmt "
loadDouble( buffer, 0x20746D66, 0, 4);
//SubChunk1Size = 16
loadDouble( buffer, 16, 4, 4);
//AudioFormat = PCM ( 1 = not compressed )
loadDouble( buffer, 1, 8, 2 );
//NumberOfChannels
loadDouble( buffer, numberOfChannels, 10, 2);
//SampleRate
//Note: assumes sample rate is an integer value
loadDouble( buffer, (int)sampleRate, 12, 4 );
//ByteRate ( sampleRate * numberOfChannels * bytesPerSample )
loadDouble( buffer,
( (int)sampleRate * numberOfChannels * bytesPerSample ),
16,
4 );
//BlockAlign ( numberOfChannels * bytesPerSample )
loadDouble( buffer, ( numberOfChannels * bytesPerSample ), 20, 2 );
//BitsPerSample
loadDouble( buffer, bitsPerSample, 22, 2 );
return buffer;
}
/**
* Returns formatted data chunk header, based on the following values:
*
* @param numberSamples - number of samples that will be in the chunk
* @param numberChannels - number of channels
* @param bitsPerSample - bits per sample
* @return
*/
public static byte[] getDataChunkHeader( int numberBytes,
int numberChannels )
{
//Byte array large enough to hold the descriptor
byte[] buffer = new byte[ 24 ];
//SubChunk2ID = "data"
loadDouble( buffer, 0x61746164, 0, 4 );
//SubChunk2Size
loadDouble( buffer, ( numberBytes * numberChannels ), 4, 4 );
return buffer;
}
/**
* Loads valueToLoad into the byte buffer at start location
* @param buffer - byte array
* @param valueToLoad
* @param start
* @param numberOfBytes
*/
protected static void loadDouble ( byte[] buffer,
long valueToLoad,
int start,
int numberOfBytes )
{
if( !( ( start + numberOfBytes ) <= buffer.length ) )
{
throw new IllegalArgumentException( "Byte array buffer is not "
+ "large enough to load value:" + valueToLoad );
}
for ( int x = 0; x < numberOfBytes; x++ )
{
buffer[ start ] = (byte) ( valueToLoad & 0xFF);
valueToLoad >>= 8;
start++;
}
}
}

View File

@@ -0,0 +1,170 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package util.waveaudio;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import javax.sound.sampled.AudioFormat;
import log.Log;
public class WaveWriter
{
private static final int sTOTAL_BYTE_LIMIT = Integer.MAX_VALUE;
private BufferedOutputStream mOutputStream;
private boolean mRunning = false;
private AudioFormat mAudioFormat;
private String mFileName;
private int mTotalByteCount = 0;
private int mFileRolloverCounter = 0;
public WaveWriter( String fileName, AudioFormat format )
{
mAudioFormat = format;
mFileName = fileName;
}
public void start() throws IOException
{
if( !mRunning )
{
openFile();
mRunning = true;
}
}
public void stop() throws IOException
{
if( mRunning )
{
closeFile();
//Increment file rollover count, in case we re-start the recorder
mFileRolloverCounter++;
mRunning = false;
}
}
public void write( ByteBuffer buffer ) throws IOException
{
checkFileRollover( buffer.capacity() );
if( mRunning && mOutputStream != null )
{
//Write the data
mOutputStream.write( buffer.array() );
mTotalByteCount += buffer.capacity();
}
}
protected void checkFileRollover( int byteCount ) throws IOException
{
if( mTotalByteCount + byteCount >= sTOTAL_BYTE_LIMIT )
{
closeFile();
mFileRolloverCounter++;
openFile();
}
}
private void closeFile() throws IOException
{
mOutputStream.flush();
mOutputStream.close();
String filename = mFileName;
//If this is a rollover file, append the rollover count to the filename
if( mFileRolloverCounter > 0 )
{
filename += mFileRolloverCounter;
}
//Correct the total count. Subtract 8 bytes from the total, to account
//for the ChunkID and ChunkSize bytes, at the beginning of the file
correctWAVEFileByteCount( filename, ( mTotalByteCount ) );
}
private void correctWAVEFileByteCount( String fileName, int newByteCount ) throws IOException
{
RandomAccessFile raf = new RandomAccessFile( fileName, "rw" );
//Get byte array for newByteCount minus header (8 bytes)
byte[] newTotalSize = new byte[ 4 ];
WaveUtils.loadDouble( newTotalSize, ( newByteCount - 8 ), 0, 4 );
//Seek to byte position 4 and write the 4 byte length value
raf.seek( 4 );
raf.write( newTotalSize );
//Get byte array for newByteCount minus all headers (44 bytes)
byte[] newDataSize = new byte[ 4 ];
WaveUtils.loadDouble( newDataSize, ( newByteCount - 44 ), 0, 4 );
//Seek to byte position 4 and write the 4 byte length value
raf.seek( 40 );
raf.write( newDataSize );
//debug
Log.info( "Closing recording [" + mFileName + "] amending file byte size to:" + newByteCount );
raf.close();
}
private void openFile() throws IOException
{
mTotalByteCount = 0;
String filename = mFileName;
if( mFileRolloverCounter > 0 )
{
filename += mFileRolloverCounter;
}
mOutputStream = new BufferedOutputStream( new FileOutputStream( filename ) );
//Write the RIFF descriptor
byte[] riff = WaveUtils.getRIFFChunkDescriptor(
mAudioFormat.getSampleRate(), 10 );
mOutputStream.write( riff );
mTotalByteCount += riff.length;
//Write the WAVE descriptor
byte[] wave = WaveUtils.getWAVEFormatDescriptor(
mAudioFormat.getSampleRate(),
mAudioFormat.getChannels(),
mAudioFormat.getSampleSizeInBits() );
mOutputStream.write( wave );
mTotalByteCount += wave.length;
//Write the data chunk header - for 10 seconds of audio
byte[] header = WaveUtils.getDataChunkHeader(
(int)(10 * mAudioFormat.getSampleRate() ), mAudioFormat.getChannels() );
mOutputStream.write( header );
mTotalByteCount += header.length;
}
}