Package org.kc7bfi.jflac

Source Code of org.kc7bfi.jflac.FLACDecoder

package org.kc7bfi.jflac;

/**
*  libFLAC - Free Lossless Audio Codec library
* Copyright (C) 2000,2001,2002,2003  Josh Coalson
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA  02111-1307, USA.
*/

import org.kc7bfi.jflac.frame.*;
import org.kc7bfi.jflac.io.BitInputStream;
import org.kc7bfi.jflac.metadata.*;
import org.kc7bfi.jflac.util.ByteData;
import org.kc7bfi.jflac.util.CRC16;

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Vector;

/**
* A Java FLAC decoder.
*
* @author kc7bfi
*/
public class FLACDecoder {
    private static final int FRAME_FOOTER_CRC_LEN = 16; // bits
    private static final byte[] ID3V2_TAG = new byte[]{'I', 'D', '3'};

    private BitInputStream bitStream;
    private ChannelData[] channelData = new ChannelData[Constants.MAX_CHANNELS];
    private int outputCapacity = 0;
    private int outputChannels = 0;
    private int lastFrameNumber;
    private long samplesDecoded = 0;
    private StreamInfo streamInfo;
    private Frame frame = new Frame();
    private byte[] headerWarmup = new byte[2]; // contains the sync code and reserved bits
    //private int state;
    private int channels;
    private int channelAssignment;
    private int bitsPerSample;
    private int sampleRate; // in Hz
    private int blockSize; // in samples (per channel)
    private InputStream inputStream = null;

    private int badFrames;
    private boolean eof = false;

    private FrameListeners frameListeners = new FrameListeners();
    private PCMProcessors pcmProcessors = new PCMProcessors();

    /**
     * The constructor.
     *
     * @param inputStream The input stream to read data from
     */
    public FLACDecoder(InputStream inputStream) {
        this.inputStream = inputStream;
        this.bitStream = new BitInputStream(inputStream);
        lastFrameNumber = 0;
        samplesDecoded = 0;
    }

    /**
     * Return the parsed StreamInfo Metadata record.
     *
     * @return The StreamInfo
     */
    public StreamInfo getStreamInfo() {
        return streamInfo;
    }

    /**
     * Return the ChannelData object.
     *
     * @return The ChannelData object
     */
    public ChannelData[] getChannelData() {
        return channelData;
    }

    /**
     * Return the input but stream.
     *
     * @return The bit stream
     */
    public BitInputStream getBitInputStream() {
        return bitStream;
    }

    /**
     * Add a frame listener.
     *
     * @param listener The frame listener to add
     */
    public void addFrameListener(FrameListener listener) {
        frameListeners.addFrameListener(listener);
    }

    /**
     * Remove a frame listener.
     *
     * @param listener The frame listener to remove
     */
    public void removeFrameListener(FrameListener listener) {
        frameListeners.removeFrameListener(listener);
    }

    /**
     * Add a PCM processor.
     *
     * @param processor The processor listener to add
     */
    public void addPCMProcessor(PCMProcessor processor) {
        pcmProcessors.addPCMProcessor(processor);
    }

    /**
     * Remove a PCM processor.
     *
     * @param processor The processor listener to remove
     */
    public void removePCMProcessor(PCMProcessor processor) {
        pcmProcessors.removePCMProcessor(processor);
    }

    private void callPCMProcessors(Frame frame) {
        ByteData bd = decodeFrame(frame, null);
        pcmProcessors.processPCM(bd);
    }

    /**
     * Fill the given ByteData object with PCM data from the frame.
     *
     * @param frame   the frame to send to the PCM processors
     * @param pcmData the byte data to be filled, or null if it should be allocated
     * @return the ByteData that was filled (may be a new instance from <code>space</code>)
     */
    public ByteData decodeFrame(Frame frame, ByteData pcmData) {
        // required size of the byte buffer
        int byteSize = frame.header.blockSize * channels * ((streamInfo.getBitsPerSample() / 8));
        if (pcmData == null || pcmData.getData().length < byteSize) {
            pcmData = new ByteData(byteSize);
        } else {
            pcmData.setLen(0);
        }
        if (streamInfo.getBitsPerSample() == 8) {
            for (int i = 0; i < frame.header.blockSize; i++) {
                for (int channel = 0; channel < channels; channel++) {
                    pcmData.append((byte) (channelData[channel].getOutput()[i] + 0x80));
                }
            }
        } else if (streamInfo.getBitsPerSample() == 16) {
            for (int i = 0; i < frame.header.blockSize; i++) {
                for (int channel = 0; channel < channels; channel++) {
                    short val = (short) (channelData[channel].getOutput()[i]);
                    pcmData.append((byte) (val & 0xff));
                    pcmData.append((byte) ((val >> 8) & 0xff));
                }
            }
        } else if (streamInfo.getBitsPerSample() == 24) {
            for (int i = 0; i < frame.header.blockSize; i++) {
                for (int channel = 0; channel < channels; channel++) {
                    int val = (channelData[channel].getOutput()[i]);
                    pcmData.append((byte) (val & 0xff));
                    pcmData.append((byte) ((val >> 8) & 0xff));
                    pcmData.append((byte) ((val >> 16) & 0xff));
                }
            }
        }
        return pcmData;
    }

    /**
     * Read the FLAC stream info.
     *
     * @return The FLAC Stream Info record
     * @throws IOException On read error
     */
    public StreamInfo readStreamInfo() throws IOException {
        readStreamSync();
        Metadata metadata = readNextMetadata();
        if (!(metadata instanceof StreamInfo)) throw new IOException("StreamInfo metadata block missing");
        return (StreamInfo) metadata;
    }

    /**
     * Read an array of metadata blocks.
     *
     * @return The array of metadata blocks
     * @throws IOException On read error
     */
    public Metadata[] readMetadata() throws IOException {
        readStreamSync();
        Vector<Metadata> metadataList = new Vector<Metadata>();
        Metadata metadata;
        do {
            metadata = readNextMetadata();
            metadataList.add(metadata);
        } while (!metadata.isLast());
        return metadataList.toArray(new Metadata[metadataList.size()]);
    }

    /**
     * Read an array of metadata blocks.
     *
     * @param streamInfo The StreamInfo metadata block previously read
     * @return The array of metadata blocks
     * @throws IOException On read error
     */
    public Metadata[] readMetadata(StreamInfo streamInfo) throws IOException {
        if (streamInfo.isLast()) return new Metadata[0];
        Vector<Metadata> metadataList = new Vector<Metadata>();
        Metadata metadata;
        do {
            metadata = readNextMetadata();
            metadataList.add(metadata);
        } while (!metadata.isLast());
        return metadataList.toArray(new Metadata[metadataList.size()]);
    }


    /**
     * Decode the data frames.
     *
     * @throws IOException On read error
     */
    public void decodeFrames() throws IOException {
        try {
            while (true) {
                findFrameSync();
                try {
                    readFrame();
                    frameListeners.processFrame(frame);
                    callPCMProcessors(frame);
                } catch (FrameDecodeException e) {
                    badFrames++;
                }
            }
        } catch (EOFException e) {
            eof = true;
        }
    }

    /**
     * Read the next data frame.
     *
     * @return The next frame
     * @throws IOException on read error
     */
    public Frame readNextFrame() throws IOException {
        try {
            while (true) {
                findFrameSync();
                try {
                    readFrame();
                    return frame;
                } catch (FrameDecodeException e) {
                    badFrames++;
                    if (badFrames > 10)
                        return null;
                }
            }
        } catch (EOFException e) {
            eof = true;
        }
        return null;
    }

    /**
     * Bytes read.
     *
     * @return The number of bytes read
     */
    public long getTotalBytesRead() {
        return bitStream.getTotalBytesRead();
    }

    private void allocateOutput(int size, int channels) {
        if (size <= outputCapacity && channels <= outputChannels) return;

        for (int i = 0; i < Constants.MAX_CHANNELS; i++) {
            channelData[i] = null;
        }

        for (int i = 0; i < channels; i++) {
            channelData[i] = new ChannelData(size);
        }

        outputCapacity = size;
        outputChannels = channels;
    }

    /**
     * Read the stream sync string.
     *
     * @throws IOException On read error
     */
    private void readStreamSync() throws IOException {
        int id = 0;
        for (int i = 0; i < 4;) {
            int x = bitStream.readRawUInt(8);
            if (x == Constants.STREAM_SYNC_STRING[i]) {
                i++;
                id = 0;
            } else if (x == ID3V2_TAG[id]) {
                id++;
                i = 0;
                if (id == 3) {
                    skipID3v2Tag();
                    id = 0;
                }
            } else {
                throw new IOException("Could not find Stream Sync");
            }
        }
    }

    /**
     * Read a single metadata record.
     *
     * @return The next metadata record
     * @throws IOException on read error
     */
    public Metadata readNextMetadata() throws IOException {
        Metadata metadata;

        boolean isLast = (bitStream.readRawUInt(Metadata.STREAM_METADATA_IS_LAST_LEN) != 0);
        int type = bitStream.readRawUInt(Metadata.STREAM_METADATA_TYPE_LEN);
        int length = bitStream.readRawUInt(Metadata.STREAM_METADATA_LENGTH_LEN);

        if (type == Metadata.METADATA_TYPE_STREAMINFO) {
            streamInfo = new StreamInfo(bitStream, length, isLast);
            metadata = streamInfo;
            pcmProcessors.processStreamInfo((StreamInfo) metadata);
        } else if (type == Metadata.METADATA_TYPE_SEEKTABLE) {
            metadata = new SeekTable(bitStream, length, isLast);
        } else if (type == Metadata.METADATA_TYPE_APPLICATION) {
            metadata = new Application(bitStream, length, isLast);
        } else if (type == Metadata.METADATA_TYPE_PADDING) {
            metadata = new Padding(bitStream, length, isLast);
        } else if (type == Metadata.METADATA_TYPE_VORBIS_COMMENT) {
            metadata = new VorbisComment(bitStream, length, isLast);
        } else if (type == Metadata.METADATA_TYPE_CUESHEET) {
            metadata = new CueSheet(bitStream, length, isLast);
        } else if (type == Metadata.METADATA_TYPE_PICTURE) {
            metadata = new Picture(bitStream, length, isLast);
        } else {
            metadata = new Unknown(bitStream, length, isLast);
        }
        frameListeners.processMetadata(metadata);
        //if (isLast) state = DECODER_SEARCH_FOR_FRAME_SYNC;
        return metadata;
    }


    private void skipID3v2Tag() throws IOException {

        // skip the version and flags bytes
        int verMajor = bitStream.readRawInt(8);
        int verMinor = bitStream.readRawInt(8);
        int flags = bitStream.readRawInt(8);

        // get the size (in bytes) to skip
        int skip = 0;
        for (int i = 0; i < 4; i++) {
            int x = bitStream.readRawUInt(8);
            skip <<= 7;
            skip |= (x & 0x7f);
        }

        // skip the rest of the tag
        bitStream.readByteBlockAlignedNoCRC(null, skip);
    }

    private void findFrameSync() throws IOException {
        boolean first = true;
        //int cnt=0;

        // If we know the total number of samples in the stream, stop if we've read that many.
        // This will stop us, for example, from wasting time trying to sync on an ID3V1 tag.
        if (streamInfo != null && (streamInfo.getTotalSamples() != 0)) {
            if (samplesDecoded >= streamInfo.getTotalSamples()) {
                //state = DECODER_END_OF_STREAM;
                return;
            }
        }

        // make sure we're byte aligned
        if (!bitStream.isConsumedByteAligned()) {
            bitStream.readRawUInt(bitStream.bitsLeftForByteAlignment());
        }

        int x;
        try {
            while (true) {
                x = bitStream.readRawUInt(8);
                if (x == 0xff) { // MAGIC NUMBER for the first 8 frame sync bits
                    headerWarmup[0] = (byte) x;
                    x = bitStream.peekRawUInt(8);

                    /* we have to check if we just read two 0xff's in a row; the second may actually be the beginning of the sync code */
                    /* else we have to check if the second byte is the end of a sync code */
                    if (x >> 2 == 0x3e) { /* MAGIC NUMBER for the last 6 sync bits */
                        headerWarmup[1] = (byte) bitStream.readRawUInt(8);
                        //state = DECODER_READ_FRAME;
                        return;
                    }
                }
                if (first) {
                    frameListeners.processError("FindSync LOST_SYNC: " + Integer.toHexString((x & 0xff)));
                    first = false;
                }
            }
        } catch (EOFException e) {
            if (!first) frameListeners.processError("FindSync LOST_SYNC: Left over data in file");
            //state = DECODER_END_OF_STREAM;
        }
    }

    /**
     * Read the next data frame.
     *
     * @throws IOException          On read error
     * @throws FrameDecodeException On frame decoding error
     */
    public void readFrame() throws IOException, FrameDecodeException {
        boolean gotAFrame = false;
        int channel;
        int i;
        int mid, side, left, right;
        short frameCRC; /* the one we calculate from the input stream */
        //int x;

        /* init the CRC */
        frameCRC = 0;
        frameCRC = CRC16.update(headerWarmup[0], frameCRC);
        frameCRC = CRC16.update(headerWarmup[1], frameCRC);
        bitStream.resetReadCRC16(frameCRC);

        try {
            frame.header = new Header(bitStream, headerWarmup, streamInfo);
        } catch (BadHeaderException e) {
            frameListeners.processError("Found bad header: " + e);
            throw new FrameDecodeException("Bad Frame Header: " + e);
        }
        //if (state == DECODER_SEARCH_FOR_FRAME_SYNC) return false;
        allocateOutput(frame.header.blockSize, frame.header.channels);
        for (channel = 0; channel < frame.header.channels; channel++) {
            // first figure the correct bits-per-sample of the subframe
            int bps = frame.header.bitsPerSample;
            switch (frame.header.channelAssignment) {
                case Constants.CHANNEL_ASSIGNMENT_INDEPENDENT:
                    /* no adjustment needed */
                    break;
                case Constants.CHANNEL_ASSIGNMENT_LEFT_SIDE:
                    if (channel == 1)
                        bps++;
                    break;
                case Constants.CHANNEL_ASSIGNMENT_RIGHT_SIDE:
                    if (channel == 0)
                        bps++;
                    break;
                case Constants.CHANNEL_ASSIGNMENT_MID_SIDE:
                    if (channel == 1)
                        bps++;
                    break;
                default:
            }
            // now read it
            try {
                readSubframe(channel, bps);
            } catch (IOException e) {
                frameListeners.processError("ReadSubframe: " + e);
                throw e;
            }
        }
        readZeroPadding();

        // Read the frame CRC-16 from the footer and check
        frameCRC = bitStream.getReadCRC16();
        frame.setCRC((short) bitStream.readRawUInt(FRAME_FOOTER_CRC_LEN));
        if (frameCRC == frame.getCRC()) {
            /* Undo any special channel coding */
            switch (frame.header.channelAssignment) {
                case Constants.CHANNEL_ASSIGNMENT_INDEPENDENT:
                    /* do nothing */
                    break;
                case Constants.CHANNEL_ASSIGNMENT_LEFT_SIDE:
                    for (i = 0; i < frame.header.blockSize; i++)
                        channelData[1].getOutput()[i] = channelData[0].getOutput()[i] - channelData[1].getOutput()[i];
                    break;
                case Constants.CHANNEL_ASSIGNMENT_RIGHT_SIDE:
                    for (i = 0; i < frame.header.blockSize; i++)
                        channelData[0].getOutput()[i] += channelData[1].getOutput()[i];
                    break;
                case Constants.CHANNEL_ASSIGNMENT_MID_SIDE:
                    for (i = 0; i < frame.header.blockSize; i++) {
                        mid = channelData[0].getOutput()[i];
                        side = channelData[1].getOutput()[i];
                        mid <<= 1;
                        if ((side & 1) != 0) // i.e. if 'side' is odd...
                            mid++;
                        left = mid + side;
                        right = mid - side;
                        channelData[0].getOutput()[i] = left >> 1;
                        channelData[1].getOutput()[i] = right >> 1;
                    }
                    //System.exit(1);
                    break;
                default:
                    break;
            }

            gotAFrame = true;
        } else {
            // Bad frame, emit error and zero the output signal
            frameListeners.processError("CRC Error: " + Integer.toHexString((frameCRC & 0xffff)) + " vs " + Integer.toHexString((frame.getCRC() & 0xffff)));
            for (channel = 0; channel < frame.header.channels; channel++) {
                for (int j = 0; j < frame.header.blockSize; j++)
                    channelData[channel].getOutput()[j] = 0;
            }
        }

        // put the latest values into the public section of the decoder instance
        channels = frame.header.channels;
        channelAssignment = frame.header.channelAssignment;
        bitsPerSample = frame.header.bitsPerSample;
        sampleRate = frame.header.sampleRate;
        blockSize = frame.header.blockSize;

        samplesDecoded += frame.header.blockSize;
    }

    private void readSubframe(int channel, int bps) throws IOException, FrameDecodeException {
        int x;

        x = bitStream.readRawUInt(8); /* MAGIC NUMBER */

        boolean haveWastedBits = ((x & 1) != 0);
        x &= 0xfe;

        int wastedBits = 0;
        if (haveWastedBits) {
            wastedBits = bitStream.readUnaryUnsigned() + 1;
            bps -= wastedBits;
        }

        // Lots of magic numbers here
        if ((x & 0x80) != 0) {
            frameListeners.processError("ReadSubframe LOST_SYNC: " + Integer.toHexString(x & 0xff));
            //state = DECODER_SEARCH_FOR_FRAME_SYNC;
            throw new FrameDecodeException("ReadSubframe LOST_SYNC: " + Integer.toHexString(x & 0xff));
            //return true;
        } else if (x == 0) {
            frame.subframes[channel] = new ChannelConstant(bitStream, frame.header, channelData[channel], bps, wastedBits);
        } else if (x == 2) {
            frame.subframes[channel] = new ChannelVerbatim(bitStream, frame.header, channelData[channel], bps, wastedBits);
        } else if (x < 16) {
            //state = DECODER_UNPARSEABLE_STREAM;
            throw new FrameDecodeException("ReadSubframe Bad Subframe Type: " + Integer.toHexString(x & 0xff));
        } else if (x <= 24) {
            //FLACSubframe_Fixed subframe = read_subframe_fixed_(channel, bps, (x >> 1) & 7);
            frame.subframes[channel] = new ChannelFixed(bitStream, frame.header, channelData[channel], bps, wastedBits, (x >> 1) & 7);
        } else if (x < 64) {
            //state = DECODER_UNPARSEABLE_STREAM;
            throw new FrameDecodeException("ReadSubframe Bad Subframe Type: " + Integer.toHexString(x & 0xff));
        } else {
            frame.subframes[channel] = new ChannelLPC(bitStream, frame.header, channelData[channel], bps, wastedBits, ((x >> 1) & 31) + 1);
        }
        if (haveWastedBits) {
            int i;
            x = frame.subframes[channel].getWastedBits();
            for (i = 0; i < frame.header.blockSize; i++)
                channelData[channel].getOutput()[i] <<= x;
        }
    }

    private void readZeroPadding() throws IOException, FrameDecodeException {
        if (!bitStream.isConsumedByteAligned()) {
            int zero = bitStream.readRawUInt(bitStream.bitsLeftForByteAlignment());
            if (zero != 0) {
                frameListeners.processError("ZeroPaddingError: " + Integer.toHexString(zero));
                //state = DECODER_SEARCH_FOR_FRAME_SYNC;
                throw new FrameDecodeException("ZeroPaddingError: " + Integer.toHexString(zero));
            }
        }
    }

    /**
     * Get the number of samples decoded.
     *
     * @return Returns the samples Decoded.
     */
    public long getSamplesDecoded() {
        return samplesDecoded;
    }

    /**
     * @return Returns the number of bad frames decoded.
     */
    public int getBadFrames() {
        return badFrames;
    }

    /**
     * @return Returns true if end-of-file.
     */
    public boolean isEOF() {
        return eof;
    }

    public void flush() {
        samplesDecoded = 0;
    }
}
TOP

Related Classes of org.kc7bfi.jflac.FLACDecoder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.