record.wave.MonoWaveReader.java Source code

Java tutorial

Introduction

Here is the source code for record.wave.MonoWaveReader.java

Source

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