mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-10 15:47:25 -05:00
259 lines
7.5 KiB
Java
259 lines
7.5 KiB
Java
/*******************************************************************************
|
|
* sdrtrunk
|
|
* Copyright (C) 2014-2016 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 record.wave;
|
|
|
|
import org.apache.commons.lang3.Validate;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
import sample.ConversionUtils;
|
|
import sample.Listener;
|
|
import sample.real.RealBuffer;
|
|
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.ByteOrder;
|
|
import java.nio.ShortBuffer;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
import java.nio.file.Paths;
|
|
import java.nio.file.StandardOpenOption;
|
|
import java.util.Arrays;
|
|
import java.util.regex.Pattern;
|
|
|
|
public class MonoWaveReader implements AutoCloseable
|
|
{
|
|
private final static Logger mLog = LoggerFactory.getLogger( MonoWaveReader.class);
|
|
|
|
private static final Pattern FILENAME_PATTERN = Pattern.compile( "(.*_)(\\d+)(\\.wav)" );
|
|
|
|
private Path mPath;
|
|
private boolean mRealTime;
|
|
private InputStream mInputStream;
|
|
private int mDataByteSize = 0;
|
|
private Listener<RealBuffer> mListener;
|
|
private byte[] mDataBuffer = new byte[8000];
|
|
|
|
/**
|
|
* Wave file reader for PCM 8kHz, 16-bit little-endian format.
|
|
*
|
|
* @param path of the wave file
|
|
* @param realTime to force replay to real-time. Note: you should run this reader on a separate thread if you
|
|
* choose real time since this reader will sleep the calling thread as needed to effect real-time playback.
|
|
*/
|
|
public MonoWaveReader(Path path, boolean realTime) throws IOException
|
|
{
|
|
Validate.isTrue(path != null);
|
|
mPath = path;
|
|
mRealTime = realTime;
|
|
|
|
open();
|
|
}
|
|
|
|
/**
|
|
* Opens the file
|
|
*/
|
|
private void open() throws IOException
|
|
{
|
|
if(!Files.exists(mPath))
|
|
{
|
|
throw new IOException("File not found");
|
|
}
|
|
|
|
mInputStream = Files.newInputStream(mPath, StandardOpenOption.READ);
|
|
|
|
//Check for RIFF header
|
|
byte[] buffer = new byte[4];
|
|
mInputStream.read(buffer);
|
|
|
|
if(!Arrays.equals(buffer, WaveUtils.RIFF_CHUNK))
|
|
{
|
|
throw new IOException("File is not .wav format - missing RIFF chunk");
|
|
}
|
|
|
|
//Get file size
|
|
mInputStream.read(buffer);
|
|
int fileSize = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().get();
|
|
|
|
//Check for WAVE format
|
|
mInputStream.read(buffer);
|
|
|
|
if(!Arrays.equals(buffer, WaveUtils.WAV_FORMAT))
|
|
{
|
|
throw new IOException("File is not .wav format - missing WAVE format");
|
|
}
|
|
|
|
//Check for format chunk
|
|
mInputStream.read(buffer);
|
|
|
|
if(!Arrays.equals(buffer, WaveUtils.CHUNK_FORMAT))
|
|
{
|
|
throw new IOException("File is not .wav format - missing format chunk");
|
|
}
|
|
|
|
//Get chunk size
|
|
mInputStream.read(buffer);
|
|
int chunkSize = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().get();
|
|
|
|
//Get format
|
|
mInputStream.read(buffer);
|
|
|
|
ShortBuffer shortBuffer = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
|
|
short format = shortBuffer.get();
|
|
if(format != WaveUtils.PCM_FORMAT)
|
|
{
|
|
throw new IOException("File format not supported - expecting PCM format");
|
|
}
|
|
|
|
//Get number of channels
|
|
short channels = shortBuffer.get();
|
|
if(channels != 1)
|
|
{
|
|
throw new IOException("Unsupported channel count - mono audio only");
|
|
}
|
|
|
|
//Get samples per second
|
|
mInputStream.read(buffer);
|
|
int sampleRate = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().get();
|
|
|
|
//Get bytes per second
|
|
mInputStream.read(buffer);
|
|
int bytesPerSecond = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().get();
|
|
|
|
mInputStream.read(buffer);
|
|
|
|
//Get frame size
|
|
shortBuffer = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
|
|
short frameSize = shortBuffer.get();
|
|
if(frameSize != 2)
|
|
{
|
|
throw new IOException("PCM frame size not supported - expecting 2 bytes per frame");
|
|
}
|
|
|
|
//Get bits per sample
|
|
short bitsPerSample = shortBuffer.get();
|
|
if(bitsPerSample != 16)
|
|
{
|
|
throw new IOException("PCM sample size not supported - expecting 16 bits per sample");
|
|
}
|
|
|
|
mInputStream.read(buffer);
|
|
|
|
if(!Arrays.equals(buffer, WaveUtils.CHUNK_DATA))
|
|
{
|
|
throw new IOException("Unexpected chunk - expecting data chunk");
|
|
}
|
|
|
|
//Get data chunk size
|
|
mInputStream.read(buffer);
|
|
mDataByteSize = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().get();
|
|
}
|
|
|
|
public void read() throws IOException
|
|
{
|
|
long start = System.currentTimeMillis();
|
|
long samplesRead = 0;
|
|
|
|
int totalBytesRead = 0;
|
|
|
|
while(totalBytesRead < mDataByteSize)
|
|
{
|
|
int bytesRead = mInputStream.read(mDataBuffer);
|
|
|
|
totalBytesRead += bytesRead;
|
|
|
|
float[] samples = ConversionUtils.convertFromSigned16BitSamples(mDataBuffer);
|
|
|
|
if(bytesRead != mDataBuffer.length)
|
|
{
|
|
samples = Arrays.copyOf(samples, bytesRead / 2);
|
|
}
|
|
|
|
if(mRealTime)
|
|
{
|
|
samplesRead += samples.length;
|
|
|
|
long actualElapsed = System.currentTimeMillis() - start;
|
|
|
|
long audioElapsed = (long)(((double)samplesRead / 8000.0) * 1000.0);
|
|
|
|
if(audioElapsed > actualElapsed)
|
|
{
|
|
try
|
|
{
|
|
Thread.sleep(audioElapsed - actualElapsed);
|
|
}
|
|
catch (InterruptedException e)
|
|
{
|
|
}
|
|
}
|
|
}
|
|
|
|
if(mListener != null)
|
|
{
|
|
mListener.receive(new RealBuffer(samples));
|
|
}
|
|
}
|
|
|
|
close();
|
|
}
|
|
|
|
public void setListener(Listener<RealBuffer> listener)
|
|
{
|
|
mListener = listener;
|
|
}
|
|
|
|
/**
|
|
* Closes the file
|
|
*/
|
|
public void close() throws IOException
|
|
{
|
|
if(mInputStream != null)
|
|
{
|
|
mInputStream.close();
|
|
}
|
|
}
|
|
|
|
public static void main(String[] args)
|
|
{
|
|
Path path = Paths.get("/home/denny/Music/PCM.wav");
|
|
mLog.debug("Opening: " + path.toString());
|
|
|
|
try
|
|
{
|
|
MonoWaveReader reader = new MonoWaveReader(path, true);
|
|
reader.setListener(new Listener<RealBuffer>()
|
|
{
|
|
@Override
|
|
public void receive(RealBuffer realBuffer)
|
|
{
|
|
mLog.debug("Received buffer");
|
|
}
|
|
});
|
|
reader.read();
|
|
}
|
|
catch(IOException e)
|
|
{
|
|
mLog.error("Error", e);
|
|
}
|
|
|
|
mLog.debug("Finished");
|
|
}
|
|
}
|