package org.kc7bfi.jflac.frame; /** * libFLAC - Free Lossless Audio Codec library * Copyright (C) 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.Constants; import org.kc7bfi.jflac.io.BitInputStream; import org.kc7bfi.jflac.metadata.StreamInfo; import org.kc7bfi.jflac.util.ByteData; import org.kc7bfi.jflac.util.CRC8; import java.io.IOException; /** * Frame header class. * * @author kc7bfi */ public class Header { /** * The number of samples per subframe. */ public int blockSize; /** * The sample rate in Hz. */ public int sampleRate; /** * The number of channels (== number of subframes). */ public int channels; /** * The channel assignment for the frame. */ public int channelAssignment; /** * The sample resolution. */ public int bitsPerSample; /** * The frame number or sample number of first sample in frame. * use the number_type value to determine which to use. */ public int frameNumber = -1; /** * The sample number for the first sample in the frame. */ public long sampleNumber = -1; /** * CRC-8 (polynomial = x^8 + x^2 + x^1 + x^0, initialized with 0). * of the raw frame header bytes, meaning everything before the CRC byte * including the sync code. */ protected byte crc; /** * The constructor. * * @param is The InputBitStream * @param headerWarmup The header warm-up bytes * @param streamInfo The FLAC Stream Info * @throws IOException Thrown on error reading InputBitStream * @throws BadHeaderException Thrown if header is bad */ public Header(BitInputStream is, byte[] headerWarmup, StreamInfo streamInfo) throws IOException, BadHeaderException { int blocksizeHint = 0; int sampleRateHint = 0; ByteData rawHeader = new ByteData(16); // MAGIC NUMBER based on the maximum frame header size, including CRC boolean isKnownVariableBlockSizeStream = (streamInfo != null && streamInfo.getMinBlockSize() != streamInfo.getMaxBlockSize()); boolean isKnownFixedBlockSizeStream = (streamInfo != null && streamInfo.getMinBlockSize() == streamInfo.getMaxBlockSize()); // init the raw header with the saved bits from synchronization rawHeader.append(headerWarmup[0]); rawHeader.append(headerWarmup[1]); // check to make sure that the reserved bits are 0 if ((rawHeader.getData(1) & 0x03) != 0) { // MAGIC NUMBER throw new BadHeaderException("Bad Magic Number: " + (rawHeader.getData(1) & 0xff)); } // Note that along the way as we read the header, we look for a sync // code inside. If we find one it would indicate that our original // sync was bad since there cannot be a sync code in a valid header. // read in the raw header as bytes so we can CRC it, and parse it on the way for (int i = 0; i < 2; i++) { if (is.peekRawUInt(8) == 0xff) { // MAGIC NUMBER for the first 8 frame sync bits throw new BadHeaderException("Found sync byte"); } rawHeader.append((byte) is.readRawUInt(8)); } int bsType = (rawHeader.getData(2) >> 4) & 0x0f; switch (bsType) { case 0: if (!isKnownFixedBlockSizeStream) throw new BadHeaderException("Unknown Block Size (0)"); blockSize = streamInfo.getMinBlockSize(); break; case 1: blockSize = 192; break; case 2: case 3: case 4: case 5: blockSize = 576 << (bsType - 2); break; case 6: case 7: blocksizeHint = bsType; break; case 8: case 9: case 10: case 11: case 12: case 13: case 14: case 15: blockSize = 256 << (bsType - 8); break; default: break; } //System.out.println("BSType="+bsType+" BS="+blockSize); int srType = rawHeader.getData(2) & 0x0f; switch (srType) { case 0: if (streamInfo == null) throw new BadHeaderException("Bad Sample Rate (0)"); sampleRate = streamInfo.getSampleRate(); break; case 1: case 2: case 3: throw new BadHeaderException("Bad Sample Rate (" + srType + ")"); case 4: sampleRate = 8000; break; case 5: sampleRate = 16000; break; case 6: sampleRate = 22050; break; case 7: sampleRate = 24000; break; case 8: sampleRate = 32000; break; case 9: sampleRate = 44100; break; case 10: sampleRate = 48000; break; case 11: sampleRate = 96000; break; case 12: case 13: case 14: sampleRateHint = srType; break; case 15: throw new BadHeaderException("Bad Sample Rate (" + srType + ")"); default: } int asgnType = (int) ((rawHeader.getData(3) >> 4) & 0x0f); //System.out.println("AsgnType="+asgnType+" "+(rawHeader.space[3] >> 4)); if ((asgnType & 8) != 0) { channels = 2; switch (asgnType & 7) { case 0: channelAssignment = Constants.CHANNEL_ASSIGNMENT_LEFT_SIDE; break; case 1: channelAssignment = Constants.CHANNEL_ASSIGNMENT_RIGHT_SIDE; break; case 2: channelAssignment = Constants.CHANNEL_ASSIGNMENT_MID_SIDE; break; default: throw new BadHeaderException("Bad Channel Assignment (" + asgnType + ")"); } } else { channels = (int) asgnType + 1; channelAssignment = Constants.CHANNEL_ASSIGNMENT_INDEPENDENT; } int bpsType = (int) (rawHeader.getData(3) & 0x0e) >> 1; switch (bpsType) { case 0: if (streamInfo != null) bitsPerSample = streamInfo.getBitsPerSample(); else throw new BadHeaderException("Bad BPS (" + bpsType + ")"); break; case 1: bitsPerSample = 8; break; case 2: bitsPerSample = 12; break; case 4: bitsPerSample = 16; break; case 5: bitsPerSample = 20; break; case 6: bitsPerSample = 24; break; case 3: case 7: throw new BadHeaderException("Bad BPS (" + bpsType + ")"); default: break; } if ((rawHeader.getData(3) & 0x01) != 0) { // this should be a zero padding bit throw new BadHeaderException("this should be a zero padding bit"); } if ((blocksizeHint != 0) && isKnownVariableBlockSizeStream) { sampleNumber = is.readUTF8Long(rawHeader); if (sampleNumber == 0xffffffffffffffffL) { // i.e. non-UTF8 code... throw new BadHeaderException("Bad Sample Number"); } } else { int lastFrameNumber = is.readUTF8Int(rawHeader); if (lastFrameNumber == 0xffffffff) { // i.e. non-UTF8 code... throw new BadHeaderException("Bad Last Frame"); } sampleNumber = (long) streamInfo.getMinBlockSize() * (long) lastFrameNumber; } if (blocksizeHint != 0) { int blockSizeCode = is.readRawUInt(8); rawHeader.append((byte) blockSizeCode); if (blocksizeHint == 7) { int blockSizeCode2 = is.readRawUInt(8); rawHeader.append((byte) blockSizeCode2); blockSizeCode = (blockSizeCode << 8) | blockSizeCode2; } blockSize = blockSizeCode + 1; } if (sampleRateHint != 0) { int sampleRateCode = is.readRawUInt(8); rawHeader.append((byte) sampleRateCode); if (sampleRateHint != 12) { int sampleRateCode2 = is.readRawUInt(8); rawHeader.append((byte) sampleRateCode2); sampleRateCode = (sampleRateCode << 8) | sampleRateCode2; } if (sampleRateHint == 12) sampleRate = sampleRateCode * 1000; else if (sampleRateHint == 13) sampleRate = sampleRateCode; else sampleRate = sampleRateCode * 10; } // read the CRC-8 byte byte crc8 = (byte) is.readRawUInt(8); if (CRC8.calc(rawHeader.getData(), rawHeader.getLen()) != crc8) { throw new BadHeaderException("STREAM_DECODER_ERROR_STATUS_BAD_HEADER"); } } /** * Return a descriptive string for this object. * * @return the string description * @see java.lang.Object#toString() */ public String toString() { return "FrameHeader:" + " BlockSize=" + blockSize + " SampleRate=" + sampleRate + " Channels=" + channels + " ChannelAssignment=" + channelAssignment + " BPS=" + bitsPerSample + " SampleNumber=" + sampleNumber; } }