/*
* FLAC library (Java)
* Copyright (c) Project Nayuki
* https://www.nayuki.io/page/flac-library-java
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License
* along with this program (see COPYING.txt and COPYING.LESSER.txt).
* If not, see
* Otherwise this reads between 6 to 16 bytes from the stream – starting * from the sync code, and ending after the CRC-8 value is read (but before reading * any subframes). It tries to parse the frame header data. After the values are * successfully decoded, a new frame info object is created, almost all fields are * set to the parsed values, and it is returned. (This doesn't read to the end * of the frame, so the frameSize field is set to -1.) *
* * @param in the input stream to read from (not {@code null}) * @return a new frame info object or {@code null} * @throws NullPointerException if the input stream is {@code null} * @throws DataFormatException if the input data contains invalid values * @throws IOException if an I/O exception occurred */ public static FrameInfo readFrame(FlacLowLevelInput in) throws IOException { // Preliminaries in.resetCrcs(); int temp = in.readByte(); if (temp == -1) return null; FrameInfo result = new FrameInfo(); result.frameSize = -1; // Read sync bits int sync = temp << 6 | in.readUint(6); // Uint14 if (sync != 0x3FFE) throw new DataFormatException("Sync code expected"); // Read various simple fields if (in.readUint(1) != 0) throw new DataFormatException("Reserved bit"); int blockStrategy = in.readUint(1); int blockSizeCode = in.readUint(4); int sampleRateCode = in.readUint(4); int chanAsgn = in.readUint(4); result.channelAssignment = chanAsgn; if (chanAsgn < 8) result.numChannels = chanAsgn + 1; else if (8 <= chanAsgn && chanAsgn <= 10) result.numChannels = 2; else throw new DataFormatException("Reserved channel assignment"); result.sampleDepth = decodeSampleDepth(in.readUint(3)); if (in.readUint(1) != 0) throw new DataFormatException("Reserved bit"); // Read and check the frame/sample position field long position = readUtf8Integer(in); // Reads 1 to 7 bytes if (blockStrategy == 0) { if ((position >>> 31) != 0) throw new DataFormatException("Frame index too large"); result.frameIndex = (int) position; result.sampleOffset = -1; } else if (blockStrategy == 1) { result.sampleOffset = position; result.frameIndex = -1; } else throw new AssertionError(); // Read variable-length data for some fields result.blockSize = decodeBlockSize(blockSizeCode, in); // Reads 0 to 2 bytes result.sampleRate = decodeSampleRate(sampleRateCode, in); // Reads 0 to 2 bytes int computedCrc8 = in.getCrc8(); if (in.readUint(8) != computedCrc8) throw new DataFormatException("CRC-8 mismatch"); return result; } // Reads 1 to 7 whole bytes from the input stream. Return value is a uint36. // See: https://hydrogenaud.io/index.php/topic,112831.msg929128.html#msg929128 private static long readUtf8Integer(FlacLowLevelInput in) throws IOException { int head = in.readUint(8); int n = Integer.numberOfLeadingZeros(~(head << 24)); // Number of leading 1s in the byte assert 0 <= n && n <= 8; if (n == 0) return head; else if (n == 1 || n == 8) throw new DataFormatException("Invalid UTF-8 coded number"); else { long result = head & (0x7F >>> n); for (int i = 0; i < n - 1; i++) { int temp = in.readUint(8); if ((temp & 0xC0) != 0x80) throw new DataFormatException("Invalid UTF-8 coded number"); result = (result << 6) | (temp & 0x3F); } if ((result >>> 36) != 0) throw new AssertionError(); return result; } } // Argument is a uint4 value. Reads 0 to 2 bytes from the input stream. // Return value is in the range [1, 65536]. private static int decodeBlockSize(int code, FlacLowLevelInput in) throws IOException { if ((code >>> 4) != 0) throw new IllegalArgumentException(); switch (code) { case 0: throw new DataFormatException("Reserved block size"); case 6: return in.readUint(8) + 1; case 7: return in.readUint(16) + 1; default: int result = searchSecond(BLOCK_SIZE_CODES, code); if (result < 1 || result > 65536) throw new AssertionError(); return result; } } // Argument is a uint4 value. Reads 0 to 2 bytes from the input stream. // Return value is in the range [-1, 655350]. private static int decodeSampleRate(int code, FlacLowLevelInput in) throws IOException { if ((code >>> 4) != 0) throw new IllegalArgumentException(); switch (code) { case 0: return -1; // Caller should obtain value from stream info metadata block case 12: return in.readUint(8); case 13: return in.readUint(16); case 14: return in.readUint(16) * 10; case 15: throw new DataFormatException("Invalid sample rate"); default: int result = searchSecond(SAMPLE_RATE_CODES, code); if (result < 1 || result > 655350) throw new AssertionError(); return result; } } // Argument is a uint3 value. Pure function and performs no I/O. Return value is in the range [-1, 24]. private static int decodeSampleDepth(int code) { if ((code >>> 3) != 0) throw new IllegalArgumentException(); else if (code == 0) return -1; // Caller should obtain value from stream info metadata block else { int result = searchSecond(SAMPLE_DEPTH_CODES, code); if (result == -1) throw new DataFormatException("Reserved bit depth"); if (result < 1 || result > 32) throw new AssertionError(); return result; } } /*---- Tables of constants and search functions ----*/ // Returns a uint4 value representing the given block size. Pure function. private static int getBlockSizeCode(int blockSize) { int result = searchFirst(BLOCK_SIZE_CODES, blockSize); if (result != -1) ; // Already done else if (1 <= blockSize && blockSize <= 256) result = 6; else if (1 <= blockSize && blockSize <= 65536) result = 7; else // blockSize < 1 || blockSize > 65536 throw new IllegalArgumentException(); if ((result >>> 4) != 0) throw new AssertionError(); return result; } // Returns a uint4 value representing the given sample rate. Pure function. private static int getSampleRateCode(int sampleRate) { if (sampleRate == 0 || sampleRate < -1) throw new IllegalArgumentException(); int result = searchFirst(SAMPLE_RATE_CODES, sampleRate); if (result != -1) ; // Already done else if (0 <= sampleRate && sampleRate < 256) result = 12; else if (0 <= sampleRate && sampleRate < 65536) result = 13; else if (0 <= sampleRate && sampleRate < 655360 && sampleRate % 10 == 0) result = 14; else result = 0; if ((result >>> 4) != 0) throw new AssertionError(); return result; } // Returns a uint3 value representing the given sample depth. Pure function. private static int getSampleDepthCode(int sampleDepth) { if (sampleDepth != -1 && (sampleDepth < 1 || sampleDepth > 32)) throw new IllegalArgumentException(); int result = searchFirst(SAMPLE_DEPTH_CODES, sampleDepth); if (result == -1) result = 0; if ((result >>> 3) != 0) throw new AssertionError(); return result; } private static final int searchFirst(int[][] table, int key) { for (int[] pair : table) { if (pair[0] == key) return pair[1]; } return -1; } private static final int searchSecond(int[][] table, int key) { for (int[] pair : table) { if (pair[1] == key) return pair[0]; } return -1; } }