├── src └── io │ └── nayuki │ └── flac │ ├── decode │ ├── DataFormatException.java │ ├── SeekableFileFlacInput.java │ ├── ByteArrayFlacInput.java │ ├── FlacLowLevelInput.java │ ├── FlacDecoder.java │ ├── AbstractFlacLowLevelInput.java │ └── FrameDecoder.java │ ├── encode │ ├── SizeEstimate.java │ ├── RandomAccessFileOutputStream.java │ ├── VerbatimEncoder.java │ ├── FlacEncoder.java │ ├── FixedPredictionEncoder.java │ ├── ConstantEncoder.java │ ├── FastDotProduct.java │ ├── AdvancedFlacEncoder.java │ ├── BitOutputStream.java │ ├── FrameEncoder.java │ ├── RiceEncoder.java │ ├── LinearPredictiveEncoder.java │ └── SubframeEncoder.java │ ├── app │ ├── DecodeFlacToWav.java │ ├── EncodeWavToFlac.java │ ├── SeekableFlacPlayerGui.java │ └── ShowFlacFileStats.java │ └── common │ ├── SeekTable.java │ ├── StreamInfo.java │ └── FrameInfo.java └── COPYING.LESSER.txt /src/io/nayuki/flac/decode/DataFormatException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * FLAC library (Java) 3 | * 4 | * Copyright (c) Project Nayuki 5 | * https://www.nayuki.io/page/flac-library-java 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with this program (see COPYING.txt and COPYING.LESSER.txt). 19 | * If not, see . 20 | */ 21 | 22 | package io.nayuki.flac.decode; 23 | 24 | 25 | /** 26 | * Thrown when data being read violates the FLAC file format. 27 | */ 28 | @SuppressWarnings("serial") 29 | public class DataFormatException extends RuntimeException { 30 | 31 | /*---- Constructors ----*/ 32 | 33 | public DataFormatException() { 34 | super(); 35 | } 36 | 37 | 38 | public DataFormatException(String msg) { 39 | super(msg); 40 | } 41 | 42 | 43 | public DataFormatException(String msg, Throwable cause) { 44 | super(msg, cause); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/io/nayuki/flac/encode/SizeEstimate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * FLAC library (Java) 3 | * 4 | * Copyright (c) Project Nayuki 5 | * https://www.nayuki.io/page/flac-library-java 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with this program (see COPYING.txt and COPYING.LESSER.txt). 19 | * If not, see . 20 | */ 21 | 22 | package io.nayuki.flac.encode; 23 | 24 | import java.util.Objects; 25 | 26 | 27 | /* 28 | * Pairs an integer with an arbitrary object. Immutable structure. 29 | */ 30 | final class SizeEstimate { 31 | 32 | /*---- Fields ----*/ 33 | 34 | public final long sizeEstimate; // Non-negative 35 | public final E encoder; // Not null 36 | 37 | 38 | 39 | /*---- Constructors ----*/ 40 | 41 | public SizeEstimate(long size, E enc) { 42 | if (size < 0) 43 | throw new IllegalArgumentException(); 44 | sizeEstimate = size; 45 | encoder = Objects.requireNonNull(enc); 46 | } 47 | 48 | 49 | 50 | /*---- Methods ----*/ 51 | 52 | // Returns this object if the size is less than or equal to the other object, otherwise returns other. 53 | public SizeEstimate minimum(SizeEstimate other) { 54 | Objects.requireNonNull(other); 55 | if (sizeEstimate <= other.sizeEstimate) 56 | return this; 57 | else 58 | return other; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/io/nayuki/flac/decode/SeekableFileFlacInput.java: -------------------------------------------------------------------------------- 1 | /* 2 | * FLAC library (Java) 3 | * 4 | * Copyright (c) Project Nayuki 5 | * https://www.nayuki.io/page/flac-library-java 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with this program (see COPYING.txt and COPYING.LESSER.txt). 19 | * If not, see . 20 | */ 21 | 22 | package io.nayuki.flac.decode; 23 | 24 | import java.io.File; 25 | import java.io.IOException; 26 | import java.io.RandomAccessFile; 27 | import java.util.Objects; 28 | 29 | 30 | /** 31 | * A FLAC input stream based on a {@link RandomAccessFile}. 32 | */ 33 | public final class SeekableFileFlacInput extends AbstractFlacLowLevelInput { 34 | 35 | /*---- Fields ----*/ 36 | 37 | // The underlying byte-based input stream to read from. 38 | private RandomAccessFile raf; 39 | 40 | 41 | 42 | /*---- Constructors ----*/ 43 | 44 | public SeekableFileFlacInput(File file) throws IOException { 45 | super(); 46 | Objects.requireNonNull(file); 47 | this.raf = new RandomAccessFile(file, "r"); 48 | } 49 | 50 | 51 | 52 | /*---- Methods ----*/ 53 | 54 | public long getLength() { 55 | try { 56 | return raf.length(); 57 | } catch (IOException e) { 58 | throw new RuntimeException(e); 59 | } 60 | } 61 | 62 | 63 | public void seekTo(long pos) throws IOException { 64 | raf.seek(pos); 65 | positionChanged(pos); 66 | } 67 | 68 | 69 | protected int readUnderlying(byte[] buf, int off, int len) throws IOException { 70 | return raf.read(buf, off, len); 71 | } 72 | 73 | 74 | // Closes the underlying RandomAccessFile stream (very important). 75 | public void close() throws IOException { 76 | if (raf != null) { 77 | raf.close(); 78 | raf = null; 79 | super.close(); 80 | } 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/io/nayuki/flac/encode/RandomAccessFileOutputStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * FLAC library (Java) 3 | * 4 | * Copyright (c) Project Nayuki 5 | * https://www.nayuki.io/page/flac-library-java 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with this program (see COPYING.txt and COPYING.LESSER.txt). 19 | * If not, see . 20 | */ 21 | 22 | package io.nayuki.flac.encode; 23 | 24 | import java.io.IOException; 25 | import java.io.OutputStream; 26 | import java.io.RandomAccessFile; 27 | import java.util.Objects; 28 | 29 | 30 | /* 31 | * An adapter from RandomAccessFile to OutputStream. These objects have no buffer, so seek() 32 | * and write() can be safely interleaved. Also, objects of this class have no direct 33 | * native resources - so it is safe to discard a RandomAccessFileOutputStream object without 34 | * closing it, as long as other code will close() the underlying RandomAccessFile object. 35 | */ 36 | public final class RandomAccessFileOutputStream extends OutputStream { 37 | 38 | /*---- Fields ----*/ 39 | 40 | private RandomAccessFile out; 41 | 42 | 43 | 44 | /*---- Constructors ----*/ 45 | 46 | public RandomAccessFileOutputStream(RandomAccessFile raf) { 47 | this.out = Objects.requireNonNull(raf); 48 | } 49 | 50 | 51 | 52 | /*---- Methods ----*/ 53 | 54 | public long getPosition() throws IOException { 55 | return out.getFilePointer(); 56 | } 57 | 58 | 59 | public void seek(long pos) throws IOException { 60 | out.seek(pos); 61 | } 62 | 63 | 64 | public void write(int b) throws IOException { 65 | out.write(b); 66 | } 67 | 68 | 69 | public void write(byte[] b, int off, int len) throws IOException { 70 | out.write(b, off, len); 71 | } 72 | 73 | 74 | public void close() throws IOException { 75 | if (out != null) { 76 | out.close(); 77 | out = null; 78 | } 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/io/nayuki/flac/decode/ByteArrayFlacInput.java: -------------------------------------------------------------------------------- 1 | /* 2 | * FLAC library (Java) 3 | * 4 | * Copyright (c) Project Nayuki 5 | * https://www.nayuki.io/page/flac-library-java 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with this program (see COPYING.txt and COPYING.LESSER.txt). 19 | * If not, see . 20 | */ 21 | 22 | package io.nayuki.flac.decode; 23 | 24 | import java.io.IOException; 25 | import java.util.Objects; 26 | 27 | 28 | /** 29 | * A FLAC input stream based on a fixed byte array. 30 | */ 31 | public final class ByteArrayFlacInput extends AbstractFlacLowLevelInput { 32 | 33 | /*---- Fields ----*/ 34 | 35 | // The underlying byte array to read from. 36 | private byte[] data; 37 | private int offset; 38 | 39 | 40 | 41 | /*---- Constructors ----*/ 42 | 43 | public ByteArrayFlacInput(byte[] b) { 44 | super(); 45 | data = Objects.requireNonNull(b); 46 | offset = 0; 47 | } 48 | 49 | 50 | 51 | /*---- Methods ----*/ 52 | 53 | public long getLength() { 54 | return data.length; 55 | } 56 | 57 | 58 | public void seekTo(long pos) { 59 | offset = (int)pos; 60 | positionChanged(pos); 61 | } 62 | 63 | 64 | protected int readUnderlying(byte[] buf, int off, int len) { 65 | if (off < 0 || off > buf.length || len < 0 || len > buf.length - off) 66 | throw new ArrayIndexOutOfBoundsException(); 67 | int n = Math.min(data.length - offset, len); 68 | if (n == 0) 69 | return -1; 70 | System.arraycopy(data, offset, buf, off, n); 71 | offset += n; 72 | return n; 73 | } 74 | 75 | 76 | // Discards data buffers and invalidates this stream. Because this class and its superclass 77 | // only use memory and have no native resources, it's okay to simply let a ByteArrayFlacInput 78 | // be garbage-collected without calling close(). 79 | public void close() throws IOException { 80 | if (data != null) { 81 | data = null; 82 | super.close(); 83 | } 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/io/nayuki/flac/encode/VerbatimEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * FLAC library (Java) 3 | * 4 | * Copyright (c) Project Nayuki 5 | * https://www.nayuki.io/page/flac-library-java 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with this program (see COPYING.txt and COPYING.LESSER.txt). 19 | * If not, see . 20 | */ 21 | 22 | package io.nayuki.flac.encode; 23 | 24 | import java.io.IOException; 25 | 26 | 27 | /* 28 | * Under the verbatim coding mode, this provides size calculations on and bitstream encoding of audio sample data. 29 | * Note that the size depends on the data length, shift, and bit depth, but not on the data contents. 30 | */ 31 | final class VerbatimEncoder extends SubframeEncoder { 32 | 33 | // Computes the best way to encode the given values under the verbatim coding mode, 34 | // returning an exact size plus a new encoder object associated with the input arguments. 35 | public static SizeEstimate computeBest(long[] samples, int shift, int depth) { 36 | VerbatimEncoder enc = new VerbatimEncoder(samples, shift, depth); 37 | long size = 1 + 6 + 1 + shift + samples.length * depth; 38 | return new SizeEstimate(size, enc); 39 | } 40 | 41 | 42 | // Constructs a constant encoder for the given data, right shift, and sample depth. 43 | public VerbatimEncoder(long[] samples, int shift, int depth) { 44 | super(shift, depth); 45 | } 46 | 47 | 48 | // Encodes the given vector of audio sample data to the given bit output stream using 49 | // the this encoding method (and the superclass fields sampleShift and sampleDepth). 50 | // This requires the data array to have the same values (but not necessarily 51 | // the same object reference) as the array that was passed to the constructor. 52 | public void encode(long[] samples, BitOutputStream out) throws IOException { 53 | writeTypeAndShift(1, out); 54 | for (long val : samples) 55 | writeRawSample(val >> sampleShift, out); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/io/nayuki/flac/encode/FlacEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * FLAC library (Java) 3 | * 4 | * Copyright (c) Project Nayuki 5 | * https://www.nayuki.io/page/flac-library-java 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with this program (see COPYING.txt and COPYING.LESSER.txt). 19 | * If not, see . 20 | */ 21 | 22 | package io.nayuki.flac.encode; 23 | 24 | import java.io.IOException; 25 | import io.nayuki.flac.common.StreamInfo; 26 | 27 | 28 | public final class FlacEncoder { 29 | 30 | public FlacEncoder(StreamInfo info, int[][] samples, int blockSize, SubframeEncoder.SearchOptions opt, BitOutputStream out) throws IOException { 31 | info.minBlockSize = blockSize; 32 | info.maxBlockSize = blockSize; 33 | info.minFrameSize = 0; 34 | info.maxFrameSize = 0; 35 | 36 | for (int i = 0, pos = 0; pos < samples[0].length; i++) { 37 | System.err.printf("frame=%d position=%d %.2f%%%n", i, pos, 100.0 * pos / samples[0].length); 38 | int n = Math.min(samples[0].length - pos, blockSize); 39 | long[][] subsamples = getRange(samples, pos, n); 40 | FrameEncoder enc = FrameEncoder.computeBest(pos, subsamples, info.sampleDepth, info.sampleRate, opt).encoder; 41 | long startByte = out.getByteCount(); 42 | enc.encode(subsamples, out); 43 | long frameSize = out.getByteCount() - startByte; 44 | if (frameSize < 0 || (int)frameSize != frameSize) 45 | throw new AssertionError(); 46 | if (info.minFrameSize == 0 || frameSize < info.minFrameSize) 47 | info.minFrameSize = (int)frameSize; 48 | if (frameSize > info.maxFrameSize) 49 | info.maxFrameSize = (int)frameSize; 50 | pos += n; 51 | } 52 | } 53 | 54 | 55 | // Returns the subrange array[ : ][off : off + len] upcasted to long. 56 | private static long[][] getRange(int[][] array, int off, int len) { 57 | long[][] result = new long[array.length][len]; 58 | for (int i = 0; i < array.length; i++) { 59 | int[] src = array[i]; 60 | long[] dest = result[i]; 61 | for (int j = 0; j < len; j++) 62 | dest[j] = src[off + j]; 63 | } 64 | return result; 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/io/nayuki/flac/encode/FixedPredictionEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * FLAC library (Java) 3 | * 4 | * Copyright (c) Project Nayuki 5 | * https://www.nayuki.io/page/flac-library-java 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with this program (see COPYING.txt and COPYING.LESSER.txt). 19 | * If not, see . 20 | */ 21 | 22 | package io.nayuki.flac.encode; 23 | 24 | import java.io.IOException; 25 | import java.util.Objects; 26 | 27 | 28 | /* 29 | * Under the fixed prediction coding mode of some order, this provides size calculations on and bitstream encoding of audio sample data. 30 | */ 31 | final class FixedPredictionEncoder extends SubframeEncoder { 32 | 33 | // Computes the best way to encode the given values under the fixed prediction coding mode of the given order, 34 | // returning a size plus a new encoder object associated with the input arguments. The maxRiceOrder argument 35 | // is used by the Rice encoder to estimate the size of coding the residual signal. 36 | public static SizeEstimate computeBest(long[] samples, int shift, int depth, int order, int maxRiceOrder) { 37 | FixedPredictionEncoder enc = new FixedPredictionEncoder(samples, shift, depth, order); 38 | samples = LinearPredictiveEncoder.shiftRight(samples, shift); 39 | LinearPredictiveEncoder.applyLpc(samples, COEFFICIENTS[order], 0); 40 | long temp = RiceEncoder.computeBestSizeAndOrder(samples, order, maxRiceOrder); 41 | enc.riceOrder = (int)(temp & 0xF); 42 | long size = 1 + 6 + 1 + shift + order * depth + (temp >>> 4); 43 | return new SizeEstimate(size, enc); 44 | } 45 | 46 | 47 | 48 | private final int order; 49 | public int riceOrder; 50 | 51 | 52 | public FixedPredictionEncoder(long[] samples, int shift, int depth, int order) { 53 | super(shift, depth); 54 | if (order < 0 || order >= COEFFICIENTS.length || samples.length < order) 55 | throw new IllegalArgumentException(); 56 | this.order = order; 57 | } 58 | 59 | 60 | public void encode(long[] samples, BitOutputStream out) throws IOException { 61 | Objects.requireNonNull(samples); 62 | Objects.requireNonNull(out); 63 | if (samples.length < order) 64 | throw new IllegalArgumentException(); 65 | 66 | writeTypeAndShift(8 + order, out); 67 | samples = LinearPredictiveEncoder.shiftRight(samples, sampleShift); 68 | 69 | for (int i = 0; i < order; i++) // Warmup 70 | writeRawSample(samples[i], out); 71 | LinearPredictiveEncoder.applyLpc(samples, COEFFICIENTS[order], 0); 72 | RiceEncoder.encode(samples, order, riceOrder, out); 73 | } 74 | 75 | 76 | // The linear predictive coding (LPC) coefficients for fixed prediction of orders 0 to 4 (inclusive). 77 | private static final int[][] COEFFICIENTS = { 78 | {}, 79 | {1}, 80 | {2, -1}, 81 | {3, -3, 1}, 82 | {4, -6, 4, -1}, 83 | }; 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/io/nayuki/flac/encode/ConstantEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * FLAC library (Java) 3 | * 4 | * Copyright (c) Project Nayuki 5 | * https://www.nayuki.io/page/flac-library-java 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with this program (see COPYING.txt and COPYING.LESSER.txt). 19 | * If not, see . 20 | */ 21 | 22 | package io.nayuki.flac.encode; 23 | 24 | import java.io.IOException; 25 | 26 | 27 | /* 28 | * Under the constant coding mode, this provides size calculations on and bitstream encoding of audio sample data. 29 | * Note that unlike the other subframe encoders which are fully general, not all data can be encoded using constant mode. 30 | * The encoded size depends on the shift and bit depth, but not on the data length or contents. 31 | */ 32 | final class ConstantEncoder extends SubframeEncoder { 33 | 34 | // Computes the best way to encode the given values under the constant coding mode, 35 | // returning an exact size plus a new encoder object associated with the input arguments. 36 | // However if the sample data is non-constant then null is returned instead, 37 | // to indicate that the data is impossible to represent in this mode. 38 | public static SizeEstimate computeBest(long[] samples, int shift, int depth) { 39 | if (!isConstant(samples)) 40 | return null; 41 | ConstantEncoder enc = new ConstantEncoder(samples, shift, depth); 42 | long size = 1 + 6 + 1 + shift + depth; 43 | return new SizeEstimate(size, enc); 44 | } 45 | 46 | 47 | // Constructs a constant encoder for the given data, right shift, and sample depth. 48 | public ConstantEncoder(long[] samples, int shift, int depth) { 49 | super(shift, depth); 50 | } 51 | 52 | 53 | // Encodes the given vector of audio sample data to the given bit output stream using 54 | // the this encoding method (and the superclass fields sampleShift and sampleDepth). 55 | // This requires the data array to have the same values (but not necessarily 56 | // the same object reference) as the array that was passed to the constructor. 57 | public void encode(long[] samples, BitOutputStream out) throws IOException { 58 | if (!isConstant(samples)) 59 | throw new IllegalArgumentException("Data is not constant-valued"); 60 | if ((samples[0] >> sampleShift) << sampleShift != samples[0]) 61 | throw new IllegalArgumentException("Invalid shift value for data"); 62 | writeTypeAndShift(0, out); 63 | writeRawSample(samples[0] >> sampleShift, out); 64 | } 65 | 66 | 67 | // Returns true iff the set of unique values in the array has size exactly 1. Pure function. 68 | private static boolean isConstant(long[] data) { 69 | if (data.length == 0) 70 | return false; 71 | long val = data[0]; 72 | for (long x : data) { 73 | if (x != val) 74 | return false; 75 | } 76 | return true; 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/io/nayuki/flac/encode/FastDotProduct.java: -------------------------------------------------------------------------------- 1 | /* 2 | * FLAC library (Java) 3 | * 4 | * Copyright (c) Project Nayuki 5 | * https://www.nayuki.io/page/flac-library-java 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with this program (see COPYING.txt and COPYING.LESSER.txt). 19 | * If not, see . 20 | */ 21 | 22 | package io.nayuki.flac.encode; 23 | 24 | import java.util.Objects; 25 | 26 | 27 | /* 28 | * Speeds up computations of a signal vector's autocorrelation by avoiding redundant 29 | * arithmetic operations. Acts as a helper class for LinearPredictiveEncoder. 30 | * Objects of this class are intended to be immutable, but can't enforce it because 31 | * they store a reference to a caller-controlled array without making a private copy. 32 | */ 33 | final class FastDotProduct { 34 | 35 | /*---- Fields ----*/ 36 | 37 | // Not null, and precomputed.length <= data.length. 38 | private long[] data; 39 | 40 | // precomputed[i] = dotProduct(0, i, data.length - i). In other words, it is the sum 41 | // the of products of all unordered pairs of elements whose indices differ by i. 42 | private double[] precomputed; 43 | 44 | 45 | 46 | /*---- Constructors ----*/ 47 | 48 | // Constructs a fast dot product calculator over the given array, with the given maximum difference in indexes. 49 | // This pre-calculates some dot products and caches them so that future queries can be answered faster. 50 | // To avoid the cost of copying the entire vector, a reference to the array is saved into this object. 51 | // The values from data array are still needed when dotProduct() is called, thus no other code is allowed to modify the values. 52 | public FastDotProduct(long[] data, int maxDelta) { 53 | // Check arguments 54 | this.data = Objects.requireNonNull(data); 55 | if (maxDelta < 0 || maxDelta >= data.length) 56 | throw new IllegalArgumentException(); 57 | 58 | // Precompute some dot products 59 | precomputed = new double[maxDelta + 1]; 60 | for (int i = 0; i < precomputed.length; i++) { 61 | double sum = 0; 62 | for (int j = 0; i + j < data.length; j++) 63 | sum += (double)data[j] * data[i + j]; 64 | precomputed[i] = sum; 65 | } 66 | } 67 | 68 | 69 | 70 | /*---- Methods ----*/ 71 | 72 | // Returns the dot product of data[off0 : off0 + len] with data[off1 : off1 + len], 73 | // i.e. data[off0]*data[off1] + data[off0+1]*data[off1+1] + ... + data[off0+len-1]*data[off1+len-1], 74 | // with potential rounding error. Note that all the endpoints must lie within the bounds 75 | // of the data array. Also, this method requires abs(off0 - off1) <= maxDelta. 76 | public double dotProduct(int off0, int off1, int len) { 77 | if (off0 > off1) // Symmetric case 78 | return dotProduct(off1, off0, len); 79 | 80 | // Check arguments 81 | if (off0 < 0 || off1 < 0 || len < 0 || data.length - len < off1) 82 | throw new IndexOutOfBoundsException(); 83 | assert off0 <= off1; 84 | int delta = off1 - off0; 85 | if (delta > precomputed.length) 86 | throw new IllegalArgumentException(); 87 | 88 | // Add up a small number of products to remove from the precomputed sum 89 | double removal = 0; 90 | for (int i = 0; i < off0; i++) 91 | removal += (double)data[i] * data[i + delta]; 92 | for (int i = off1 + len; i < data.length; i++) 93 | removal += (double)data[i] * data[i - delta]; 94 | return precomputed[delta] - removal; 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src/io/nayuki/flac/encode/AdvancedFlacEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * FLAC library (Java) 3 | * 4 | * Copyright (c) Project Nayuki 5 | * https://www.nayuki.io/page/flac-library-java 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with this program (see COPYING.txt and COPYING.LESSER.txt). 19 | * If not, see . 20 | */ 21 | 22 | package io.nayuki.flac.encode; 23 | 24 | import java.io.IOException; 25 | import java.util.ArrayList; 26 | import java.util.Arrays; 27 | import java.util.List; 28 | import io.nayuki.flac.common.StreamInfo; 29 | 30 | 31 | public final class AdvancedFlacEncoder { 32 | 33 | public AdvancedFlacEncoder(StreamInfo info, int[][] samples, int baseSize, int[] sizeMultiples, SubframeEncoder.SearchOptions opts, BitOutputStream out) throws IOException { 34 | int numSamples = samples[0].length; 35 | 36 | // Calculate compressed sizes for many block positions and sizes 37 | @SuppressWarnings("unchecked") 38 | SizeEstimate[][] encoderInfo = new SizeEstimate[sizeMultiples.length][(numSamples + baseSize - 1) / baseSize]; 39 | long startTime = System.currentTimeMillis(); 40 | for (int i = 0; i < encoderInfo[0].length; i++) { 41 | double progress = (double)i / encoderInfo[0].length; 42 | double timeRemain = (System.currentTimeMillis() - startTime) / 1000.0 / progress * (1 - progress); 43 | System.err.printf("\rprogress=%.2f%% timeRemain=%ds", progress * 100, Math.round(timeRemain)); 44 | 45 | int pos = i * baseSize; 46 | for (int j = 0; j < encoderInfo.length; j++) { 47 | int n = Math.min(sizeMultiples[j] * baseSize, numSamples - pos); 48 | long[][] subsamples = getRange(samples, pos, n); 49 | encoderInfo[j][i] = FrameEncoder.computeBest(pos, subsamples, info.sampleDepth, info.sampleRate, opts); 50 | } 51 | } 52 | System.err.println(); 53 | 54 | // Initialize arrays to prepare for dynamic programming 55 | FrameEncoder[] bestEncoders = new FrameEncoder[encoderInfo[0].length]; 56 | long[] bestSizes = new long[bestEncoders.length]; 57 | Arrays.fill(bestSizes, Long.MAX_VALUE); 58 | 59 | // Use dynamic programming to calculate optimum block size switching 60 | for (int i = 0; i < encoderInfo.length; i++) { 61 | for (int j = bestSizes.length - 1; j >= 0; j--) { 62 | long size = encoderInfo[i][j].sizeEstimate; 63 | if (j + sizeMultiples[i] < bestSizes.length) 64 | size += bestSizes[j + sizeMultiples[i]]; 65 | if (size < bestSizes[j]) { 66 | bestSizes[j] = size; 67 | bestEncoders[j] = encoderInfo[i][j].encoder; 68 | } 69 | } 70 | } 71 | 72 | // Do the actual encoding and writing 73 | info.minBlockSize = 0; 74 | info.maxBlockSize = 0; 75 | info.minFrameSize = 0; 76 | info.maxFrameSize = 0; 77 | List blockSizes = new ArrayList<>(); 78 | for (int i = 0; i < bestEncoders.length; ) { 79 | FrameEncoder enc = bestEncoders[i]; 80 | int pos = i * baseSize; 81 | int n = Math.min(enc.metadata.blockSize, numSamples - pos); 82 | blockSizes.add(n); 83 | if (info.minBlockSize == 0 || n < info.minBlockSize) 84 | info.minBlockSize = Math.max(n, 16); 85 | info.maxBlockSize = Math.max(n, info.maxBlockSize); 86 | 87 | long[][] subsamples = getRange(samples, pos, n); 88 | long startByte = out.getByteCount(); 89 | bestEncoders[i].encode(subsamples, out); 90 | i += (n + baseSize - 1) / baseSize; 91 | 92 | long frameSize = out.getByteCount() - startByte; 93 | if (frameSize < 0 || (int)frameSize != frameSize) 94 | throw new AssertionError(); 95 | if (info.minFrameSize == 0 || frameSize < info.minFrameSize) 96 | info.minFrameSize = (int)frameSize; 97 | if (frameSize > info.maxFrameSize) 98 | info.maxFrameSize = (int)frameSize; 99 | } 100 | } 101 | 102 | 103 | // Returns the subrange array[ : ][off : off + len] upcasted to long. 104 | private static long[][] getRange(int[][] array, int off, int len) { 105 | long[][] result = new long[array.length][len]; 106 | for (int i = 0; i < array.length; i++) { 107 | int[] src = array[i]; 108 | long[] dest = result[i]; 109 | for (int j = 0; j < len; j++) 110 | dest[j] = src[off + j]; 111 | } 112 | return result; 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /src/io/nayuki/flac/app/DecodeFlacToWav.java: -------------------------------------------------------------------------------- 1 | /* 2 | * FLAC library (Java) 3 | * 4 | * Copyright (c) Project Nayuki 5 | * https://www.nayuki.io/page/flac-library-java 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with this program (see COPYING.txt and COPYING.LESSER.txt). 19 | * If not, see . 20 | */ 21 | 22 | package io.nayuki.flac.app; 23 | 24 | import java.io.BufferedOutputStream; 25 | import java.io.DataOutputStream; 26 | import java.io.File; 27 | import java.io.FileOutputStream; 28 | import java.io.IOException; 29 | import java.util.Arrays; 30 | import io.nayuki.flac.common.StreamInfo; 31 | import io.nayuki.flac.decode.DataFormatException; 32 | import io.nayuki.flac.decode.FlacDecoder; 33 | 34 | 35 | /** 36 | * Decodes a FLAC file to an uncompressed PCM WAV file. Overwrites output file if already exists. 37 | * Runs silently if successful, otherwise prints error messages to standard error. 38 | *

Usage: java DecodeFlacToWav InFile.flac OutFile.wav

39 | *

Requirements on the FLAC file:

40 | *
    41 | *
  • Sample depth is 8, 16, 24, or 32 bits (not 4, 17, 23, etc.)
  • 42 | *
  • Contains no ID3v1 or ID3v2 tags, or other data unrecognized by the FLAC format
  • 43 | *
  • Correct total number of samples (not zero) is stored in stream info block
  • 44 | *
  • Every frame has a correct header, subframes do not overflow the sample depth, 45 | * and other strict checks enforced by this decoder library
  • 46 | *
47 | */ 48 | public final class DecodeFlacToWav { 49 | 50 | public static void main(String[] args) throws IOException { 51 | // Handle command line arguments 52 | if (args.length != 2) { 53 | System.err.println("Usage: java DecodeFlacToWav InFile.flac OutFile.wav"); 54 | System.exit(1); 55 | return; 56 | } 57 | File inFile = new File(args[0]); 58 | File outFile = new File(args[1]); 59 | 60 | // Decode input FLAC file 61 | StreamInfo streamInfo; 62 | int[][] samples; 63 | try (FlacDecoder dec = new FlacDecoder(inFile)) { 64 | 65 | // Handle metadata header blocks 66 | while (dec.readAndHandleMetadataBlock() != null); 67 | streamInfo = dec.streamInfo; 68 | if (streamInfo.sampleDepth % 8 != 0) 69 | throw new UnsupportedOperationException("Only whole-byte sample depth supported"); 70 | 71 | // Decode every block 72 | samples = new int[streamInfo.numChannels][(int)streamInfo.numSamples]; 73 | for (int off = 0; ;) { 74 | int len = dec.readAudioBlock(samples, off); 75 | if (len == 0) 76 | break; 77 | off += len; 78 | } 79 | } 80 | 81 | // Check audio MD5 hash 82 | byte[] expectHash = streamInfo.md5Hash; 83 | if (Arrays.equals(expectHash, new byte[16])) 84 | System.err.println("Warning: MD5 hash field was blank"); 85 | else if (!Arrays.equals(StreamInfo.getMd5Hash(samples, streamInfo.sampleDepth), expectHash)) 86 | throw new DataFormatException("MD5 hash check failed"); 87 | // Else the hash check passed 88 | 89 | // Start writing WAV output file 90 | int bytesPerSample = streamInfo.sampleDepth / 8; 91 | try (DataOutputStream out = new DataOutputStream( 92 | new BufferedOutputStream(new FileOutputStream(outFile)))) { 93 | DecodeFlacToWav.out = out; 94 | 95 | // Header chunk 96 | int sampleDataLen = samples[0].length * streamInfo.numChannels * bytesPerSample; 97 | out.writeInt(0x52494646); // "RIFF" 98 | writeLittleInt32(sampleDataLen + 36); 99 | out.writeInt(0x57415645); // "WAVE" 100 | 101 | // Metadata chunk 102 | out.writeInt(0x666D7420); // "fmt " 103 | writeLittleInt32(16); 104 | writeLittleInt16(0x0001); 105 | writeLittleInt16(streamInfo.numChannels); 106 | writeLittleInt32(streamInfo.sampleRate); 107 | writeLittleInt32(streamInfo.sampleRate * streamInfo.numChannels * bytesPerSample); 108 | writeLittleInt16(streamInfo.numChannels * bytesPerSample); 109 | writeLittleInt16(streamInfo.sampleDepth); 110 | 111 | // Audio data chunk ("data") 112 | out.writeInt(0x64617461); // "data" 113 | writeLittleInt32(sampleDataLen); 114 | for (int i = 0; i < samples[0].length; i++) { 115 | for (int j = 0; j < samples.length; j++) { 116 | int val = samples[j][i]; 117 | if (bytesPerSample == 1) 118 | out.write(val + 128); // Convert to unsigned, as per WAV PCM conventions 119 | else { // 2 <= bytesPerSample <= 4 120 | for (int k = 0; k < bytesPerSample; k++) 121 | out.write(val >>> (k * 8)); // Little endian 122 | } 123 | } 124 | } 125 | } 126 | } 127 | 128 | 129 | // Helper members for writing WAV files 130 | 131 | private static DataOutputStream out; 132 | 133 | private static void writeLittleInt16(int x) throws IOException { 134 | out.writeShort(Integer.reverseBytes(x) >>> 16); 135 | } 136 | 137 | private static void writeLittleInt32(int x) throws IOException { 138 | out.writeInt(Integer.reverseBytes(x)); 139 | } 140 | 141 | } 142 | -------------------------------------------------------------------------------- /src/io/nayuki/flac/encode/BitOutputStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * FLAC library (Java) 3 | * 4 | * Copyright (c) Project Nayuki 5 | * https://www.nayuki.io/page/flac-library-java 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with this program (see COPYING.txt and COPYING.LESSER.txt). 19 | * If not, see . 20 | */ 21 | 22 | package io.nayuki.flac.encode; 23 | 24 | import java.io.IOException; 25 | import java.io.OutputStream; 26 | import java.util.Objects; 27 | 28 | 29 | /* 30 | * A bit-oriented output stream, with other methods tailored for FLAC usage (such as CRC calculation). 31 | */ 32 | public final class BitOutputStream implements AutoCloseable { 33 | 34 | /*---- Fields ----*/ 35 | 36 | private OutputStream out; // The underlying byte-based output stream to write to. 37 | private long bitBuffer; // Only the bottom bitBufferLen bits are valid; the top bits are garbage. 38 | private int bitBufferLen; // Always in the range [0, 64]. 39 | private long byteCount; // Number of bytes written since the start of stream. 40 | 41 | // Current state of the CRC calculations. 42 | private int crc8; // Always a uint8 value. 43 | private int crc16; // Always a uint16 value. 44 | 45 | 46 | 47 | /*---- Constructors ----*/ 48 | 49 | // Constructs a FLAC-oriented bit output stream from the given byte-based output stream. 50 | public BitOutputStream(OutputStream out) throws IOException { 51 | this.out = Objects.requireNonNull(out); 52 | bitBuffer = 0; 53 | bitBufferLen = 0; 54 | byteCount = 0; 55 | resetCrcs(); 56 | } 57 | 58 | 59 | 60 | /*---- Methods ----*/ 61 | 62 | /*-- Bit position --*/ 63 | 64 | // Writes between 0 to 7 zero bits, to align the current bit position to a byte boundary. 65 | public void alignToByte() throws IOException { 66 | writeInt((64 - bitBufferLen) % 8, 0); 67 | } 68 | 69 | 70 | // Either returns silently or throws an exception. 71 | private void checkByteAligned() { 72 | if (bitBufferLen % 8 != 0) 73 | throw new IllegalStateException("Not at a byte boundary"); 74 | } 75 | 76 | 77 | /*-- Writing bitwise integers --*/ 78 | 79 | // Writes the lowest n bits of the given value to this bit output stream. 80 | // This doesn't care whether val represents a signed or unsigned integer. 81 | public void writeInt(int n, int val) throws IOException { 82 | if (n < 0 || n > 32) 83 | throw new IllegalArgumentException(); 84 | 85 | if (bitBufferLen + n > 64) { 86 | flush(); 87 | assert bitBufferLen + n <= 64; 88 | } 89 | bitBuffer <<= n; 90 | bitBuffer |= val & ((1L << n) - 1); 91 | bitBufferLen += n; 92 | assert 0 <= bitBufferLen && bitBufferLen <= 64; 93 | } 94 | 95 | 96 | // Writes out whole bytes from the bit buffer to the underlying stream. After this is done, 97 | // only 0 to 7 bits remain in the bit buffer. Also updates the CRCs on each byte written. 98 | public void flush() throws IOException { 99 | while (bitBufferLen >= 8) { 100 | bitBufferLen -= 8; 101 | int b = (int)(bitBuffer >>> bitBufferLen) & 0xFF; 102 | out.write(b); 103 | byteCount++; 104 | crc8 ^= b; 105 | crc16 ^= b << 8; 106 | for (int i = 0; i < 8; i++) { 107 | crc8 <<= 1; 108 | crc16 <<= 1; 109 | crc8 ^= (crc8 >>> 8) * 0x107; 110 | crc16 ^= (crc16 >>> 16) * 0x18005; 111 | assert (crc8 >>> 8) == 0; 112 | assert (crc16 >>> 16) == 0; 113 | } 114 | } 115 | assert 0 <= bitBufferLen && bitBufferLen <= 64; 116 | out.flush(); 117 | } 118 | 119 | 120 | /*-- CRC calculations --*/ 121 | 122 | // Marks the current position (which must be byte-aligned) as the start of both CRC calculations. 123 | public void resetCrcs() throws IOException { 124 | flush(); 125 | crc8 = 0; 126 | crc16 = 0; 127 | } 128 | 129 | 130 | // Returns the CRC-8 hash of all the bytes written since the last call to resetCrcs() 131 | // (or from the beginning of stream if reset was never called). 132 | public int getCrc8() throws IOException { 133 | checkByteAligned(); 134 | flush(); 135 | if ((crc8 >>> 8) != 0) 136 | throw new AssertionError(); 137 | return crc8; 138 | } 139 | 140 | 141 | // Returns the CRC-16 hash of all the bytes written since the last call to resetCrcs() 142 | // (or from the beginning of stream if reset was never called). 143 | public int getCrc16() throws IOException { 144 | checkByteAligned(); 145 | flush(); 146 | if ((crc16 >>> 16) != 0) 147 | throw new AssertionError(); 148 | return crc16; 149 | } 150 | 151 | 152 | /*-- Miscellaneous --*/ 153 | 154 | // Returns the number of bytes written since the start of the stream. 155 | public long getByteCount() { 156 | return byteCount + bitBufferLen / 8; 157 | } 158 | 159 | 160 | // Writes out any internally buffered bit data, closes the underlying output stream, and invalidates this 161 | // bit output stream object for any future operation. Note that a BitOutputStream only uses memory but 162 | // does not have native resources. It is okay to flush() the pending data and simply let a BitOutputStream 163 | // be garbage collected without calling close(), but the parent is still responsible for calling close() 164 | // on the underlying output stream if it uses native resources (such as FileOutputStream or SocketOutputStream). 165 | public void close() throws IOException { 166 | if (out != null) { 167 | checkByteAligned(); 168 | flush(); 169 | out.close(); 170 | out = null; 171 | } 172 | } 173 | 174 | } 175 | -------------------------------------------------------------------------------- /src/io/nayuki/flac/decode/FlacLowLevelInput.java: -------------------------------------------------------------------------------- 1 | /* 2 | * FLAC library (Java) 3 | * 4 | * Copyright (c) Project Nayuki 5 | * https://www.nayuki.io/page/flac-library-java 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with this program (see COPYING.txt and COPYING.LESSER.txt). 19 | * If not, see . 20 | */ 21 | 22 | package io.nayuki.flac.decode; 23 | 24 | import java.io.IOException; 25 | 26 | 27 | /** 28 | * A low-level input stream tailored to the needs of FLAC decoding. An overview of methods includes 29 | * bit reading, CRC calculation, Rice decoding, and positioning and seeking (partly optional). 30 | * @see SeekableFileFlacInput 31 | * @see FrameDecoder 32 | */ 33 | public interface FlacLowLevelInput extends AutoCloseable { 34 | 35 | /*---- Stream position ----*/ 36 | 37 | // Returns the total number of bytes in the FLAC file represented by this input stream. 38 | // This number should not change for the lifetime of this object. Implementing this is optional; 39 | // it's intended to support blind seeking without the use of seek tables, such as binary searching 40 | // the whole file. A class may choose to throw UnsupportedOperationException instead, 41 | // such as for a non-seekable network input stream of unknown length. 42 | public long getLength(); 43 | 44 | 45 | // Returns the current byte position in the stream, a non-negative value. 46 | // This increments after every 8 bits read, and a partially read byte is treated as unread. 47 | // This value is 0 initially, is set directly by seekTo(), and potentially increases 48 | // after every call to a read*() method. Other methods do not affect this value. 49 | public long getPosition(); 50 | 51 | 52 | // Returns the current number of consumed bits in the current byte. This starts at 0, 53 | // increments for each bit consumed, maxes out at 7, then resets to 0 and repeats. 54 | public int getBitPosition(); 55 | 56 | 57 | // Changes the position of the next read to the given byte offset from the start of the stream. 58 | // This also resets CRCs and sets the bit position to 0. 59 | // Implementing this is optional; it is intended to support playback seeking. 60 | // A class may choose to throw UnsupportedOperationException instead. 61 | public void seekTo(long pos) throws IOException; 62 | 63 | 64 | 65 | /*---- Reading bitwise integers ----*/ 66 | 67 | // Reads the next given number of bits (0 <= n <= 32) as an unsigned integer (i.e. zero-extended to int32). 68 | // However in the case of n = 32, the result will be a signed integer that represents a uint32. 69 | public int readUint(int n) throws IOException; 70 | 71 | 72 | // Reads the next given number of bits (0 <= n <= 32) as an signed integer (i.e. sign-extended to int32). 73 | public int readSignedInt(int n) throws IOException; 74 | 75 | 76 | // Reads and decodes the next batch of Rice-coded signed integers. Note that any Rice-coded integer might read a large 77 | // number of bits from the underlying stream (but not in practice because it would be a very inefficient encoding). 78 | // Every new value stored into the array is guaranteed to fit into a signed int53 - see FrameDecoder.restoreLpc() 79 | // for an explanation of why this is a necessary (but not sufficient) bound on the range of decoded values. 80 | public void readRiceSignedInts(int param, long[] result, int start, int end) throws IOException; 81 | 82 | 83 | 84 | /*---- Reading bytes ----*/ 85 | 86 | // Returns the next unsigned byte value (in the range [0, 255]) or -1 for EOF. 87 | // Must be called at a byte boundary (i.e. getBitPosition() == 0), otherwise IllegalStateException is thrown. 88 | public int readByte() throws IOException; 89 | 90 | 91 | // Discards any partial bits, then reads the given array fully or throws EOFException. 92 | // Must be called at a byte boundary (i.e. getBitPosition() == 0), otherwise IllegalStateException is thrown. 93 | public void readFully(byte[] b) throws IOException; 94 | 95 | 96 | 97 | /*---- CRC calculations ----*/ 98 | 99 | // Marks the current byte position as the start of both CRC calculations. 100 | // The effect of resetCrcs() is implied at the beginning of stream and when seekTo() is called. 101 | // Must be called at a byte boundary (i.e. getBitPosition() == 0), otherwise IllegalStateException is thrown. 102 | public void resetCrcs(); 103 | 104 | 105 | // Returns the CRC-8 hash of all the bytes read since the most recent time one of these 106 | // events occurred: a call to resetCrcs(), a call to seekTo(), the beginning of stream. 107 | // Must be called at a byte boundary (i.e. getBitPosition() == 0), otherwise IllegalStateException is thrown. 108 | public int getCrc8(); 109 | 110 | 111 | // Returns the CRC-16 hash of all the bytes read since the most recent time one of these 112 | // events occurred: a call to resetCrcs(), a call to seekTo(), the beginning of stream. 113 | // Must be called at a byte boundary (i.e. getBitPosition() == 0), otherwise IllegalStateException is thrown. 114 | public int getCrc16(); 115 | 116 | 117 | 118 | /*---- Miscellaneous ----*/ 119 | 120 | // Closes underlying objects / native resources, and possibly discards memory buffers. 121 | // Generally speaking, this operation invalidates this input stream, so calling methods 122 | // (other than close()) or accessing fields thereafter should be forbidden. 123 | // The close() method must be idempotent and safe when called more than once. 124 | // If an implementation does not have native or time-sensitive resources, it is okay for the class user 125 | // to skip calling close() and simply let the object be garbage-collected. But out of good habit, it is 126 | // recommended to always close a FlacLowLevelInput stream so that the logic works correctly on all types. 127 | public void close() throws IOException; 128 | 129 | } 130 | -------------------------------------------------------------------------------- /src/io/nayuki/flac/encode/FrameEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * FLAC library (Java) 3 | * 4 | * Copyright (c) Project Nayuki 5 | * https://www.nayuki.io/page/flac-library-java 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with this program (see COPYING.txt and COPYING.LESSER.txt). 19 | * If not, see . 20 | */ 21 | 22 | package io.nayuki.flac.encode; 23 | 24 | import java.io.ByteArrayOutputStream; 25 | import java.io.IOException; 26 | import java.util.Objects; 27 | import io.nayuki.flac.common.FrameInfo; 28 | 29 | 30 | /* 31 | * Calculates/estimates the encoded size of a frame of audio sample data 32 | * (including the frame header), and also performs the encoding to an output stream. 33 | */ 34 | final class FrameEncoder { 35 | 36 | /*---- Static functions ----*/ 37 | 38 | public static SizeEstimate computeBest(int sampleOffset, long[][] samples, int sampleDepth, int sampleRate, SubframeEncoder.SearchOptions opt) { 39 | FrameEncoder enc = new FrameEncoder(sampleOffset, samples, sampleDepth, sampleRate); 40 | int numChannels = samples.length; 41 | @SuppressWarnings("unchecked") 42 | SizeEstimate[] encoderInfo = new SizeEstimate[numChannels]; 43 | if (numChannels != 2) { 44 | enc.metadata.channelAssignment = numChannels - 1; 45 | for (int i = 0; i < encoderInfo.length; i++) 46 | encoderInfo[i] = SubframeEncoder.computeBest(samples[i], sampleDepth, opt); 47 | } else { // Explore the 4 stereo encoding modes 48 | long[] left = samples[0]; 49 | long[] right = samples[1]; 50 | long[] mid = new long[samples[0].length]; 51 | long[] side = new long[mid.length]; 52 | for (int i = 0; i < mid.length; i++) { 53 | mid[i] = (left[i] + right[i]) >> 1; 54 | side[i] = left[i] - right[i]; 55 | } 56 | SizeEstimate leftInfo = SubframeEncoder.computeBest(left , sampleDepth, opt); 57 | SizeEstimate rightInfo = SubframeEncoder.computeBest(right, sampleDepth, opt); 58 | SizeEstimate midInfo = SubframeEncoder.computeBest(mid , sampleDepth, opt); 59 | SizeEstimate sideInfo = SubframeEncoder.computeBest(side , sampleDepth + 1, opt); 60 | long mode1Size = leftInfo.sizeEstimate + rightInfo.sizeEstimate; 61 | long mode8Size = leftInfo.sizeEstimate + sideInfo.sizeEstimate; 62 | long mode9Size = rightInfo.sizeEstimate + sideInfo.sizeEstimate; 63 | long mode10Size = midInfo.sizeEstimate + sideInfo.sizeEstimate; 64 | long minimum = Math.min(Math.min(mode1Size, mode8Size), Math.min(mode9Size, mode10Size)); 65 | if (mode1Size == minimum) { 66 | enc.metadata.channelAssignment = 1; 67 | encoderInfo[0] = leftInfo; 68 | encoderInfo[1] = rightInfo; 69 | } else if (mode8Size == minimum) { 70 | enc.metadata.channelAssignment = 8; 71 | encoderInfo[0] = leftInfo; 72 | encoderInfo[1] = sideInfo; 73 | } else if (mode9Size == minimum) { 74 | enc.metadata.channelAssignment = 9; 75 | encoderInfo[0] = sideInfo; 76 | encoderInfo[1] = rightInfo; 77 | } else if (mode10Size == minimum) { 78 | enc.metadata.channelAssignment = 10; 79 | encoderInfo[0] = midInfo; 80 | encoderInfo[1] = sideInfo; 81 | } else 82 | throw new AssertionError(); 83 | } 84 | 85 | // Add up subframe sizes 86 | long size = 0; 87 | enc.subEncoders = new SubframeEncoder[encoderInfo.length]; 88 | for (int i = 0; i < enc.subEncoders.length; i++) { 89 | size += encoderInfo[i].sizeEstimate; 90 | enc.subEncoders[i] = encoderInfo[i].encoder; 91 | } 92 | 93 | // Count length of header (always in whole bytes) 94 | try { 95 | ByteArrayOutputStream bout = new ByteArrayOutputStream(); 96 | try (BitOutputStream bitout = new BitOutputStream(bout)) { 97 | enc.metadata.writeHeader(bitout); 98 | } 99 | bout.close(); 100 | size += bout.toByteArray().length * 8; 101 | } catch (IOException e) { 102 | throw new AssertionError(e); 103 | } 104 | 105 | // Count padding and footer 106 | size = (size + 7) / 8; // Round up to nearest byte 107 | size += 2; // CRC-16 108 | return new SizeEstimate<>(size, enc); 109 | } 110 | 111 | 112 | 113 | /*---- Fields ----*/ 114 | 115 | public FrameInfo metadata; 116 | private SubframeEncoder[] subEncoders; 117 | 118 | 119 | 120 | /*---- Constructors ----*/ 121 | 122 | public FrameEncoder(int sampleOffset, long[][] samples, int sampleDepth, int sampleRate) { 123 | metadata = new FrameInfo(); 124 | metadata.sampleOffset = sampleOffset; 125 | metadata.sampleDepth = sampleDepth; 126 | metadata.sampleRate = sampleRate; 127 | metadata.blockSize = samples[0].length; 128 | metadata.channelAssignment = samples.length - 1; 129 | } 130 | 131 | 132 | 133 | /*---- Public methods ----*/ 134 | 135 | public void encode(long[][] samples, BitOutputStream out) throws IOException { 136 | // Check arguments 137 | Objects.requireNonNull(samples); 138 | Objects.requireNonNull(out); 139 | if (samples[0].length != metadata.blockSize) 140 | throw new IllegalArgumentException(); 141 | 142 | metadata.writeHeader(out); 143 | 144 | int chanAsgn = metadata.channelAssignment; 145 | if (0 <= chanAsgn && chanAsgn <= 7) { 146 | for (int i = 0; i < samples.length; i++) 147 | subEncoders[i].encode(samples[i], out); 148 | } else if (8 <= chanAsgn || chanAsgn <= 10) { 149 | long[] left = samples[0]; 150 | long[] right = samples[1]; 151 | long[] mid = new long[metadata.blockSize]; 152 | long[] side = new long[metadata.blockSize]; 153 | for (int i = 0; i < metadata.blockSize; i++) { 154 | mid[i] = (left[i] + right[i]) >> 1; 155 | side[i] = left[i] - right[i]; 156 | } 157 | if (chanAsgn == 8) { 158 | subEncoders[0].encode(left, out); 159 | subEncoders[1].encode(side, out); 160 | } else if (chanAsgn == 9) { 161 | subEncoders[0].encode(side, out); 162 | subEncoders[1].encode(right, out); 163 | } else if (chanAsgn == 10) { 164 | subEncoders[0].encode(mid, out); 165 | subEncoders[1].encode(side, out); 166 | } else 167 | throw new AssertionError(); 168 | } else 169 | throw new AssertionError(); 170 | out.alignToByte(); 171 | out.writeInt(16, out.getCrc16()); 172 | } 173 | 174 | } 175 | -------------------------------------------------------------------------------- /src/io/nayuki/flac/app/EncodeWavToFlac.java: -------------------------------------------------------------------------------- 1 | /* 2 | * FLAC library (Java) 3 | * 4 | * Copyright (c) Project Nayuki 5 | * https://www.nayuki.io/page/flac-library-java 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with this program (see COPYING.txt and COPYING.LESSER.txt). 19 | * If not, see . 20 | */ 21 | 22 | package io.nayuki.flac.app; 23 | 24 | import java.io.BufferedInputStream; 25 | import java.io.BufferedOutputStream; 26 | import java.io.EOFException; 27 | import java.io.File; 28 | import java.io.FileInputStream; 29 | import java.io.IOException; 30 | import java.io.InputStream; 31 | import java.io.RandomAccessFile; 32 | import java.nio.charset.StandardCharsets; 33 | import io.nayuki.flac.common.StreamInfo; 34 | import io.nayuki.flac.decode.DataFormatException; 35 | import io.nayuki.flac.encode.BitOutputStream; 36 | import io.nayuki.flac.encode.FlacEncoder; 37 | import io.nayuki.flac.encode.RandomAccessFileOutputStream; 38 | import io.nayuki.flac.encode.SubframeEncoder; 39 | 40 | 41 | /** 42 | * Encodes an uncompressed PCM WAV file to a FLAC file. 43 | * Overwrites the output file if it already exists. 44 | *

Usage: java EncodeWavToFlac InFile.wav OutFile.flac

45 | *

Requirements on the WAV file:

46 | *
    47 | *
  • Sample depth is 8, 16, 24, or 32 bits (not 4, 17, 23, etc.)
  • 48 | *
  • Number of channels is between 1 to 8 inclusive
  • 49 | *
  • Sample rate is less than 220 hertz
  • 50 | *
51 | */ 52 | public final class EncodeWavToFlac { 53 | 54 | public static void main(String[] args) throws IOException { 55 | // Handle command line arguments 56 | if (args.length != 2) { 57 | System.err.println("Usage: java EncodeWavToFlac InFile.wav OutFile.flac"); 58 | System.exit(1); 59 | return; 60 | } 61 | File inFile = new File(args[0]); 62 | File outFile = new File(args[1]); 63 | 64 | // Read WAV file headers and audio sample data 65 | int[][] samples; 66 | int sampleRate; 67 | int sampleDepth; 68 | try (InputStream in = new BufferedInputStream(new FileInputStream(inFile))) { 69 | // Parse and check WAV header 70 | if (!readString(in, 4).equals("RIFF")) 71 | throw new DataFormatException("Invalid RIFF file header"); 72 | readLittleUint(in, 4); // Remaining data length 73 | if (!readString(in, 4).equals("WAVE")) 74 | throw new DataFormatException("Invalid WAV file header"); 75 | 76 | // Handle the format chunk 77 | if (!readString(in, 4).equals("fmt ")) 78 | throw new DataFormatException("Unrecognized WAV file chunk"); 79 | if (readLittleUint(in, 4) != 16) 80 | throw new DataFormatException("Unsupported WAV file type"); 81 | if (readLittleUint(in, 2) != 0x0001) 82 | throw new DataFormatException("Unsupported WAV file codec"); 83 | int numChannels = readLittleUint(in, 2); 84 | if (numChannels < 0 || numChannels > 8) 85 | throw new RuntimeException("Too many (or few) audio channels"); 86 | sampleRate = readLittleUint(in, 4); 87 | if (sampleRate <= 0 || sampleRate >= (1 << 20)) 88 | throw new RuntimeException("Sample rate too large or invalid"); 89 | int byteRate = readLittleUint(in, 4); 90 | int blockAlign = readLittleUint(in, 2); 91 | sampleDepth = readLittleUint(in, 2); 92 | if (sampleDepth == 0 || sampleDepth > 32 || sampleDepth % 8 != 0) 93 | throw new RuntimeException("Unsupported sample depth"); 94 | int bytesPerSample = sampleDepth / 8; 95 | if (bytesPerSample * numChannels != blockAlign) 96 | throw new RuntimeException("Invalid block align value"); 97 | if (bytesPerSample * numChannels * sampleRate != byteRate) 98 | throw new RuntimeException("Invalid byte rate value"); 99 | 100 | // Handle the data chunk 101 | if (!readString(in, 4).equals("data")) 102 | throw new DataFormatException("Unrecognized WAV file chunk"); 103 | int sampleDataLen = readLittleUint(in, 4); 104 | if (sampleDataLen <= 0 || sampleDataLen % (numChannels * bytesPerSample) != 0) 105 | throw new DataFormatException("Invalid length of audio sample data"); 106 | int numSamples = sampleDataLen / (numChannels * bytesPerSample); 107 | samples = new int[numChannels][numSamples]; 108 | for (int i = 0; i < numSamples; i++) { 109 | for (int ch = 0; ch < numChannels; ch++) { 110 | int val = readLittleUint(in, bytesPerSample); 111 | if (sampleDepth == 8) 112 | val -= 128; 113 | else 114 | val = (val << (32 - sampleDepth)) >> (32 - sampleDepth); 115 | samples[ch][i] = val; 116 | } 117 | } 118 | // Note: There might be chunks after "data", but they can be ignored 119 | } 120 | 121 | // Open output file and encode samples to FLAC 122 | try (RandomAccessFile raf = new RandomAccessFile(outFile, "rw")) { 123 | raf.setLength(0); // Truncate an existing file 124 | BitOutputStream out = new BitOutputStream( 125 | new BufferedOutputStream(new RandomAccessFileOutputStream(raf))); 126 | out.writeInt(32, 0x664C6143); 127 | 128 | // Populate and write the stream info structure 129 | StreamInfo info = new StreamInfo(); 130 | info.sampleRate = sampleRate; 131 | info.numChannels = samples.length; 132 | info.sampleDepth = sampleDepth; 133 | info.numSamples = samples[0].length; 134 | info.md5Hash = StreamInfo.getMd5Hash(samples, sampleDepth); 135 | info.write(true, out); 136 | 137 | // Encode all frames 138 | new FlacEncoder(info, samples, 4096, SubframeEncoder.SearchOptions.SUBSET_BEST, out); 139 | out.flush(); 140 | 141 | // Rewrite the stream info metadata block, which is 142 | // located at a fixed offset in the file by definition 143 | raf.seek(4); 144 | info.write(true, out); 145 | out.flush(); 146 | } 147 | } 148 | 149 | 150 | // Reads len bytes from the given stream and interprets them as a UTF-8 string. 151 | private static String readString(InputStream in, int len) throws IOException { 152 | byte[] temp = new byte[len]; 153 | for (int i = 0; i < temp.length; i++) { 154 | int b = in.read(); 155 | if (b == -1) 156 | throw new EOFException(); 157 | temp[i] = (byte)b; 158 | } 159 | return new String(temp, StandardCharsets.UTF_8); 160 | } 161 | 162 | 163 | // Reads n bytes (0 <= n <= 4) from the given stream, interpreting 164 | // them as an unsigned integer encoded in little endian. 165 | private static int readLittleUint(InputStream in, int n) throws IOException { 166 | int result = 0; 167 | for (int i = 0; i < n; i++) { 168 | int b = in.read(); 169 | if (b == -1) 170 | throw new EOFException(); 171 | result |= b << (i * 8); 172 | } 173 | return result; 174 | } 175 | 176 | } 177 | -------------------------------------------------------------------------------- /src/io/nayuki/flac/common/SeekTable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * FLAC library (Java) 3 | * 4 | * Copyright (c) Project Nayuki 5 | * https://www.nayuki.io/page/flac-library-java 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with this program (see COPYING.txt and COPYING.LESSER.txt). 19 | * If not, see . 20 | */ 21 | 22 | package io.nayuki.flac.common; 23 | 24 | import java.io.ByteArrayInputStream; 25 | import java.io.DataInput; 26 | import java.io.DataInputStream; 27 | import java.io.IOException; 28 | import java.util.ArrayList; 29 | import java.util.List; 30 | import java.util.Objects; 31 | import io.nayuki.flac.decode.FlacDecoder; 32 | import io.nayuki.flac.encode.BitOutputStream; 33 | 34 | 35 | /** 36 | * Represents precisely all the fields of a seek table metadata block. Mutable structure, 37 | * not thread-safe. Also has methods for parsing and serializing this structure to/from bytes. 38 | * All fields and objects can be modified freely when no method call is active. 39 | * @see FlacDecoder 40 | */ 41 | public final class SeekTable { 42 | 43 | /*---- Fields ----*/ 44 | 45 | /** 46 | * The list of seek points in this seek table. It is okay to replace this 47 | * list as needed (the initially constructed list object is not special). 48 | */ 49 | public List points; 50 | 51 | 52 | 53 | /*---- Constructors ----*/ 54 | 55 | /** 56 | * Constructs a blank seek table with an initially empty 57 | * list of seek points. (Note that the empty state is legal.) 58 | */ 59 | public SeekTable() { 60 | points = new ArrayList<>(); 61 | } 62 | 63 | 64 | /** 65 | * Constructs a seek table by parsing the given byte array representing the metadata block. 66 | * (The array must contain only the metadata payload, without the type or length fields.) 67 | *

This constructor does not check the validity of the seek points, namely the ordering 68 | * of seek point offsets, so calling {@link#checkValues()} on the freshly constructed object 69 | * can fail. However, this does guarantee that every point's frameSamples field is a uint16.

70 | * @param b the metadata block's payload data to parse (not {@code null}) 71 | * @throws NullPointerException if the array is {@code null} 72 | * @throws IllegalArgumentException if the array length 73 | * is not a multiple of 18 (size of each seek point) 74 | */ 75 | public SeekTable(byte[] b) { 76 | this(); 77 | Objects.requireNonNull(b); 78 | if (b.length % 18 != 0) 79 | throw new IllegalArgumentException("Data contains a partial seek point"); 80 | try { 81 | DataInput in = new DataInputStream(new ByteArrayInputStream(b)); 82 | for (int i = 0; i < b.length; i += 18) { 83 | SeekPoint p = new SeekPoint(); 84 | p.sampleOffset = in.readLong(); 85 | p.fileOffset = in.readLong(); 86 | p.frameSamples = in.readUnsignedShort(); 87 | points.add(p); 88 | } 89 | // Skip closing the in-memory streams 90 | } catch (IOException e) { 91 | throw new AssertionError(e); 92 | } 93 | } 94 | 95 | 96 | 97 | /*---- Methods ----*/ 98 | 99 | /** 100 | * Checks the state of this object and returns silently if all these criteria pass: 101 | *
    102 | *
  • No object is {@code null}
  • 103 | *
  • The frameSamples field of each point is a uint16 value
  • 104 | *
  • All points with sampleOffset = −1 (i.e. 0xFFF...FFF) are at the end of the list
  • 105 | *
  • All points with sampleOffset ≠ −1 have strictly increasing 106 | * values of sampleOffset and non-decreasing values of fileOffset
  • 107 | *
108 | * @throws NullPointerException if the list or an element is {@code null} 109 | * @throws IllegalStateException if the current list of seek points is contains invalid data 110 | */ 111 | public void checkValues() { 112 | // Check list and each point 113 | Objects.requireNonNull(points); 114 | for (SeekPoint p : points) { 115 | Objects.requireNonNull(p); 116 | if ((p.frameSamples & 0xFFFF) != p.frameSamples) 117 | throw new IllegalStateException("Frame samples outside uint16 range"); 118 | } 119 | 120 | // Check ordering of points 121 | for (int i = 1; i < points.size(); i++) { 122 | SeekPoint p = points.get(i); 123 | if (p.sampleOffset != -1) { 124 | SeekPoint q = points.get(i - 1); 125 | if (p.sampleOffset <= q.sampleOffset) 126 | throw new IllegalStateException("Sample offsets out of order"); 127 | if (p.fileOffset < q.fileOffset) 128 | throw new IllegalStateException("File offsets out of order"); 129 | } 130 | } 131 | } 132 | 133 | 134 | /** 135 | * Writes all the points of this seek table as a metadata block to the specified output stream, 136 | * also indicating whether it is the last metadata block. (This does write the type and length 137 | * fields for the metadata block, unlike the constructor which takes an array without those fields.) 138 | * @param last whether the metadata block is the final one in the FLAC file 139 | * @param out the output stream to write to (not {@code null}) 140 | * @throws NullPointerException if the output stream is {@code null} 141 | * @throws IllegalStateException if there are too many 142 | * @throws IOException if an I/O exception occurred 143 | * seek points (> 932067) or {@link#checkValues()} fails 144 | */ 145 | public void write(boolean last, BitOutputStream out) throws IOException { 146 | // Check arguments and state 147 | Objects.requireNonNull(out); 148 | Objects.requireNonNull(points); 149 | if (points.size() > ((1 << 24) - 1) / 18) 150 | throw new IllegalStateException("Too many seek points"); 151 | checkValues(); 152 | 153 | // Write metadata block header 154 | out.writeInt(1, last ? 1 : 0); 155 | out.writeInt(7, 3); 156 | out.writeInt(24, points.size() * 18); 157 | 158 | // Write each seek point 159 | for (SeekPoint p : points) { 160 | out.writeInt(32, (int)(p.sampleOffset >>> 32)); 161 | out.writeInt(32, (int)(p.sampleOffset >>> 0)); 162 | out.writeInt(32, (int)(p.fileOffset >>> 32)); 163 | out.writeInt(32, (int)(p.fileOffset >>> 0)); 164 | out.writeInt(16, p.frameSamples); 165 | } 166 | } 167 | 168 | 169 | 170 | /*---- Helper structure ----*/ 171 | 172 | /** 173 | * Represents a seek point entry in a seek table. Mutable structure, not thread-safe. 174 | * This class itself does not check the correctness of data, but other classes might. 175 | *

A seek point with data (sampleOffset = x, fileOffset = y, frameSamples = z) means 176 | * that at byte position (y + (byte offset of foremost audio frame)) in the file, 177 | * a FLAC frame begins (with the sync sequence), that frame has sample offset x 178 | * (where sample 0 is defined as the start of the audio stream), 179 | * and the frame contains z samples per channel. 180 | * @see SeekTable 181 | */ 182 | public static final class SeekPoint { 183 | 184 | /** 185 | * The sample offset in the audio stream, a uint64 value. 186 | * A value of -1 (i.e. 0xFFF...FFF) means this is a placeholder point. 187 | */ 188 | public long sampleOffset; 189 | 190 | /** 191 | * The byte offset relative to the start of the foremost frame, a uint64 value. 192 | * If sampleOffset is -1, then this value is ignored. 193 | */ 194 | public long fileOffset; 195 | 196 | /** 197 | * The number of audio samples in the target block/frame, a uint16 value. 198 | * If sampleOffset is -1, then this value is ignored. 199 | */ 200 | public int frameSamples; 201 | 202 | } 203 | 204 | } 205 | -------------------------------------------------------------------------------- /src/io/nayuki/flac/encode/RiceEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * FLAC library (Java) 3 | * 4 | * Copyright (c) Project Nayuki 5 | * https://www.nayuki.io/page/flac-library-java 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with this program (see COPYING.txt and COPYING.LESSER.txt). 19 | * If not, see . 20 | */ 21 | 22 | package io.nayuki.flac.encode; 23 | 24 | import java.io.IOException; 25 | import java.util.Objects; 26 | 27 | 28 | /* 29 | * Calculates/estimates the encoded size of a vector of residuals, and also performs the encoding to an output stream. 30 | */ 31 | final class RiceEncoder { 32 | 33 | /*---- Functions for size calculation ---*/ 34 | 35 | // Calculates the best number of bits and partition order needed to encode the values data[warmup : data.length]. 36 | // Each value in that subrange of data must fit in a signed 53-bit integer. The result is packed in the form 37 | // ((bestSize << 4) | bestOrder), where bestSize is an unsigned integer and bestOrder is a uint4. 38 | // Note that the partition orders searched, and hence the resulting bestOrder, are in the range [0, maxPartOrder]. 39 | public static long computeBestSizeAndOrder(long[] data, int warmup, int maxPartOrder) { 40 | // Check arguments strictly 41 | Objects.requireNonNull(data); 42 | if (warmup < 0 || warmup > data.length) 43 | throw new IllegalArgumentException(); 44 | if (maxPartOrder < 0 || maxPartOrder > 15) 45 | throw new IllegalArgumentException(); 46 | for (long x : data) { 47 | x >>= 52; 48 | if (x != 0 && x != -1) // Check that it fits in a signed int53 49 | throw new IllegalArgumentException(); 50 | } 51 | 52 | long bestSize = Integer.MAX_VALUE; 53 | int bestOrder = -1; 54 | 55 | int[] escapeBits = null; 56 | int[] bitsAtParam = null; 57 | for (int order = maxPartOrder; order >= 0; order--) { 58 | int partSize = data.length >>> order; 59 | if ((partSize << order) != data.length || partSize < warmup) 60 | continue; 61 | int numPartitions = 1 << order; 62 | 63 | if (escapeBits == null) { // And bitsAtParam == null 64 | escapeBits = new int[numPartitions]; 65 | bitsAtParam = new int[numPartitions * 16]; 66 | for (int i = warmup; i < data.length; i++) { 67 | int j = i / partSize; 68 | long val = data[i]; 69 | escapeBits[j] = Math.max(65 - Long.numberOfLeadingZeros(val ^ (val >> 63)), escapeBits[j]); 70 | val = (val >= 0) ? (val << 1) : (((-val) << 1) - 1); 71 | for (int param = 0; param < 15; param++, val >>>= 1) 72 | bitsAtParam[param + j * 16] += val + 1 + param; 73 | } 74 | } else { // Both arrays are non-null 75 | // Logically halve the size of both arrays (but without reallocating to the true new size) 76 | for (int i = 0; i < numPartitions; i++) { 77 | int j = i << 1; 78 | escapeBits[i] = Math.max(escapeBits[j], escapeBits[j + 1]); 79 | for (int param = 0; param < 15; param++) 80 | bitsAtParam[param + i * 16] = bitsAtParam[param + j * 16] + bitsAtParam[param + (j + 1) * 16]; 81 | } 82 | } 83 | 84 | long size = 4 + (4 << order); 85 | for (int i = 0; i < numPartitions; i++) { 86 | int min = Integer.MAX_VALUE; 87 | if (escapeBits[i] <= 31) 88 | min = 5 + escapeBits[i] * (partSize - (i == 0 ? warmup : 0)); 89 | for (int param = 0; param < 15; param++) 90 | min = Math.min(bitsAtParam[param + i * 16], min); 91 | size += min; 92 | } 93 | if (size < bestSize) { 94 | bestSize = size; 95 | bestOrder = order; 96 | } 97 | } 98 | 99 | if (bestSize == Integer.MAX_VALUE || (bestOrder >>> 4) != 0) 100 | throw new AssertionError(); 101 | return bestSize << 4 | bestOrder; 102 | } 103 | 104 | 105 | // Calculates the number of bits needed to encode the sequence of values 106 | // data[start : end] with an optimally chosen Rice parameter. 107 | private static long computeBestSizeAndParam(long[] data, int start, int end) { 108 | assert data != null && 0 <= start && start <= end && end <= data.length; 109 | 110 | // Use escape code 111 | int bestParam; 112 | long bestSize; 113 | { 114 | long accumulator = 0; 115 | for (int i = start; i < end; i++) { 116 | long val = data[i]; 117 | accumulator |= val ^ (val >> 63); 118 | } 119 | int numBits = 65 - Long.numberOfLeadingZeros(accumulator); 120 | assert 1 <= numBits && numBits <= 65; 121 | if (numBits <= 31) { 122 | bestSize = 4 + 5 + (end - start) * numBits; 123 | bestParam = 16 + numBits; 124 | if ((bestParam >>> 6) != 0) 125 | throw new AssertionError(); 126 | } else { 127 | bestSize = Long.MAX_VALUE; 128 | bestParam = 0; 129 | } 130 | } 131 | 132 | // Use Rice coding 133 | for (int param = 0; param <= 14; param++) { 134 | long size = 4; 135 | for (int i = start; i < end; i++) { 136 | long val = data[i]; 137 | if (val >= 0) 138 | val <<= 1; 139 | else 140 | val = ((-val) << 1) - 1; 141 | size += (val >>> param) + 1 + param; 142 | } 143 | if (size < bestSize) { 144 | bestSize = size; 145 | bestParam = param; 146 | } 147 | } 148 | return bestSize << 6 | bestParam; 149 | } 150 | 151 | 152 | 153 | /*---- Functions for encoding data ---*/ 154 | 155 | // Encodes the sequence of values data[warmup : data.length] with an appropriately chosen order and Rice parameters. 156 | // Each value in data must fit in a signed 53-bit integer. 157 | public static void encode(long[] data, int warmup, int order, BitOutputStream out) throws IOException { 158 | // Check arguments strictly 159 | Objects.requireNonNull(data); 160 | Objects.requireNonNull(out); 161 | if (warmup < 0 || warmup > data.length) 162 | throw new IllegalArgumentException(); 163 | if (order < 0 || order > 15) 164 | throw new IllegalArgumentException(); 165 | for (long x : data) { 166 | x >>= 52; 167 | if (x != 0 && x != -1) // Check that it fits in a signed int53 168 | throw new IllegalArgumentException(); 169 | } 170 | 171 | out.writeInt(2, 0); 172 | out.writeInt(4, order); 173 | int numPartitions = 1 << order; 174 | int start = warmup; 175 | int end = data.length >>> order; 176 | for (int i = 0; i < numPartitions; i++) { 177 | int param = (int)computeBestSizeAndParam(data, start, end) & 0x3F; 178 | encode(data, start, end, param, out); 179 | start = end; 180 | end += data.length >>> order; 181 | } 182 | } 183 | 184 | 185 | // Encodes the sequence of values data[start : end] with the given Rice parameter. 186 | private static void encode(long[] data, int start, int end, int param, BitOutputStream out) throws IOException { 187 | assert 0 <= param && param <= 31 && data != null && out != null; 188 | assert 0 <= start && start <= end && end <= data.length; 189 | 190 | if (param < 15) { 191 | out.writeInt(4, param); 192 | for (int j = start; j < end; j++) 193 | writeRiceSignedInt(data[j], param, out); 194 | } else { 195 | out.writeInt(4, 15); 196 | int numBits = param - 16; 197 | out.writeInt(5, numBits); 198 | for (int j = start; j < end; j++) 199 | out.writeInt(numBits, (int)data[j]); 200 | } 201 | } 202 | 203 | 204 | private static void writeRiceSignedInt(long val, int param, BitOutputStream out) throws IOException { 205 | assert 0 <= param && param <= 31 && out != null; 206 | assert (val >> 52) == 0 || (val >> 52) == -1; // Fits in a signed int53 207 | 208 | long unsigned = val >= 0 ? val << 1 : ((-val) << 1) - 1; 209 | int unary = (int)(unsigned >>> param); 210 | for (int i = 0; i < unary; i++) 211 | out.writeInt(1, 0); 212 | out.writeInt(1, 1); 213 | out.writeInt(param, (int)unsigned); 214 | } 215 | 216 | } 217 | -------------------------------------------------------------------------------- /COPYING.LESSER.txt: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /src/io/nayuki/flac/app/SeekableFlacPlayerGui.java: -------------------------------------------------------------------------------- 1 | /* 2 | * FLAC library (Java) 3 | * 4 | * Copyright (c) Project Nayuki 5 | * https://www.nayuki.io/page/flac-library-java 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with this program (see COPYING.txt and COPYING.LESSER.txt). 19 | * If not, see . 20 | */ 21 | 22 | package io.nayuki.flac.app; 23 | 24 | import java.awt.Dimension; 25 | import java.awt.event.MouseAdapter; 26 | import java.awt.event.MouseEvent; 27 | import java.awt.event.MouseMotionAdapter; 28 | import java.awt.event.WindowAdapter; 29 | import java.awt.event.WindowEvent; 30 | import java.io.File; 31 | import java.io.IOException; 32 | import javax.sound.sampled.AudioFormat; 33 | import javax.sound.sampled.AudioSystem; 34 | import javax.sound.sampled.DataLine; 35 | import javax.sound.sampled.LineUnavailableException; 36 | import javax.sound.sampled.SourceDataLine; 37 | import javax.swing.JFrame; 38 | import javax.swing.JSlider; 39 | import javax.swing.SwingConstants; 40 | import javax.swing.SwingUtilities; 41 | import javax.swing.plaf.basic.BasicSliderUI; 42 | import javax.swing.plaf.metal.MetalSliderUI; 43 | import io.nayuki.flac.common.StreamInfo; 44 | import io.nayuki.flac.decode.FlacDecoder; 45 | 46 | 47 | /** 48 | * Plays a single FLAC file to the system audio output, showing a GUI window with a seek bar. 49 | * The file to play is specified as a command line argument. The seek bar is responsible for both 50 | * displaying the current playback position, and allowing the user to click to seek to new positions. 51 | *

Usage: java SeekableFlacPlayerGui InFile.flac

52 | */ 53 | public final class SeekableFlacPlayerGui { 54 | 55 | public static void main(String[] args) throws 56 | LineUnavailableException, IOException, InterruptedException { 57 | 58 | /*-- Initialization code --*/ 59 | 60 | // Handle command line arguments 61 | if (args.length != 1) { 62 | System.err.println("Usage: java SeekableFlacPlayerGui InFile.flac"); 63 | System.exit(1); 64 | return; 65 | } 66 | File inFile = new File(args[0]); 67 | 68 | // Process header metadata blocks 69 | FlacDecoder decoder = new FlacDecoder(inFile); 70 | while (decoder.readAndHandleMetadataBlock() != null); 71 | StreamInfo streamInfo = decoder.streamInfo; 72 | if (streamInfo.numSamples == 0) 73 | throw new IllegalArgumentException("Unknown audio length"); 74 | 75 | // Start Java sound output API 76 | AudioFormat format = new AudioFormat(streamInfo.sampleRate, 77 | streamInfo.sampleDepth, streamInfo.numChannels, true, false); 78 | DataLine.Info info = new DataLine.Info(SourceDataLine.class, format); 79 | SourceDataLine line = (SourceDataLine)AudioSystem.getLine(info); 80 | line.open(format); 81 | line.start(); 82 | 83 | // Create GUI object, event handler, communication object 84 | final double[] seekRequest = {-1}; 85 | AudioPlayerGui gui = new AudioPlayerGui("FLAC Player"); 86 | gui.listener = new AudioPlayerGui.Listener() { 87 | public void seekRequested(double t) { 88 | synchronized(seekRequest) { 89 | seekRequest[0] = t; 90 | seekRequest.notify(); 91 | } 92 | } 93 | public void windowClosing() { 94 | System.exit(0); 95 | } 96 | }; 97 | 98 | /*-- Audio player loop --*/ 99 | 100 | // Decode and write audio data, handle seek requests, wait for seek when end of stream reached 101 | int bytesPerSample = streamInfo.sampleDepth / 8; 102 | long startTime = line.getMicrosecondPosition(); 103 | 104 | // Buffers for data created and discarded within each loop iteration, but allocated outside the loop 105 | int[][] samples = new int[streamInfo.numChannels][65536]; 106 | byte[] sampleBytes = new byte[65536 * streamInfo.numChannels * bytesPerSample]; 107 | while (true) { 108 | 109 | // Get and clear seek request, if any 110 | double seekReq; 111 | synchronized(seekRequest) { 112 | seekReq = seekRequest[0]; 113 | seekRequest[0] = -1; 114 | } 115 | 116 | // Decode next audio block, or seek and decode 117 | int blockSamples; 118 | if (seekReq == -1) 119 | blockSamples = decoder.readAudioBlock(samples, 0); 120 | else { 121 | long samplePos = Math.round(seekReq * streamInfo.numSamples); 122 | seekReq = -1; 123 | blockSamples = decoder.seekAndReadAudioBlock(samplePos, samples, 0); 124 | line.flush(); 125 | startTime = line.getMicrosecondPosition() - Math.round(samplePos * 1e6 / streamInfo.sampleRate); 126 | } 127 | 128 | // Set display position 129 | double timePos = (line.getMicrosecondPosition() - startTime) / 1e6; 130 | gui.setPosition(timePos * streamInfo.sampleRate / streamInfo.numSamples); 131 | 132 | // Wait when end of stream reached 133 | if (blockSamples == 0) { 134 | synchronized(seekRequest) { 135 | while (seekRequest[0] == -1) 136 | seekRequest.wait(); 137 | } 138 | continue; 139 | } 140 | 141 | // Convert samples to channel-interleaved bytes in little endian 142 | int sampleBytesLen = 0; 143 | for (int i = 0; i < blockSamples; i++) { 144 | for (int ch = 0; ch < streamInfo.numChannels; ch++) { 145 | int val = samples[ch][i]; 146 | for (int j = 0; j < bytesPerSample; j++, sampleBytesLen++) 147 | sampleBytes[sampleBytesLen] = (byte)(val >>> (j << 3)); 148 | } 149 | } 150 | line.write(sampleBytes, 0, sampleBytesLen); 151 | } 152 | } 153 | 154 | 155 | 156 | /*---- User interface classes ----*/ 157 | 158 | private static final class AudioPlayerGui { 159 | 160 | /*-- Fields --*/ 161 | 162 | public Listener listener; 163 | private JSlider slider; 164 | private BasicSliderUI sliderUi; 165 | 166 | 167 | /*-- Constructor --*/ 168 | 169 | public AudioPlayerGui(String windowTitle) { 170 | // Create and configure slider 171 | slider = new JSlider(SwingConstants.HORIZONTAL, 0, 10000, 0); 172 | sliderUi = new MetalSliderUI(); 173 | slider.setUI(sliderUi); 174 | slider.setPreferredSize(new Dimension(800, 50)); 175 | slider.addMouseListener(new MouseAdapter() { 176 | public void mousePressed(MouseEvent ev) { 177 | moveSlider(ev); 178 | } 179 | public void mouseReleased(MouseEvent ev) { 180 | moveSlider(ev); 181 | listener.seekRequested((double)slider.getValue() / slider.getMaximum()); 182 | } 183 | }); 184 | slider.addMouseMotionListener(new MouseMotionAdapter() { 185 | public void mouseDragged(MouseEvent ev) { 186 | moveSlider(ev); 187 | } 188 | }); 189 | 190 | // Create and configure frame (window) 191 | JFrame frame = new JFrame(windowTitle); 192 | frame.add(slider); 193 | frame.pack(); 194 | frame.addWindowListener(new WindowAdapter() { 195 | public void windowClosing(WindowEvent ev) { 196 | listener.windowClosing(); 197 | } 198 | }); 199 | frame.setResizable(false); 200 | frame.setVisible(true); 201 | } 202 | 203 | 204 | /*-- Methods --*/ 205 | 206 | public void setPosition(double t) { 207 | if (Double.isNaN(t)) 208 | return; 209 | final double val = Math.max(Math.min(t, 1), 0); 210 | SwingUtilities.invokeLater(new Runnable() { 211 | public void run() { 212 | if (!slider.getValueIsAdjusting()) 213 | slider.setValue((int)Math.round(val * slider.getMaximum())); 214 | } 215 | }); 216 | } 217 | 218 | 219 | private void moveSlider(MouseEvent ev) { 220 | slider.setValue(sliderUi.valueForXPosition(ev.getX())); 221 | } 222 | 223 | 224 | /*-- Helper interface --*/ 225 | 226 | public interface Listener { 227 | 228 | public void seekRequested(double t); // 0.0 <= t <= 1.0 229 | 230 | public void windowClosing(); 231 | 232 | } 233 | 234 | } 235 | 236 | } 237 | -------------------------------------------------------------------------------- /src/io/nayuki/flac/encode/LinearPredictiveEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * FLAC library (Java) 3 | * 4 | * Copyright (c) Project Nayuki 5 | * https://www.nayuki.io/page/flac-library-java 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with this program (see COPYING.txt and COPYING.LESSER.txt). 19 | * If not, see . 20 | */ 21 | 22 | package io.nayuki.flac.encode; 23 | 24 | import java.io.IOException; 25 | import java.util.Arrays; 26 | import java.util.Comparator; 27 | import java.util.Objects; 28 | 29 | 30 | /* 31 | * Under the linear predictive coding (LPC) mode of some order, this provides size estimates on and bitstream encoding of audio sample data. 32 | */ 33 | final class LinearPredictiveEncoder extends SubframeEncoder { 34 | 35 | // Computes a good way to encode the given values under the linear predictive coding (LPC) mode of the given order, 36 | // returning a size plus a new encoder object associated with the input arguments. This process of minimizing the size 37 | // has an enormous search space, and it is impossible to guarantee the absolute optimal solution. The maxRiceOrder argument 38 | // is used by the Rice encoder to estimate the size of coding the residual signal. The roundVars argument controls 39 | // how many different coefficients are tested rounding both up and down, resulting in exponential time behavior. 40 | public static SizeEstimate computeBest(long[] samples, int shift, int depth, int order, int roundVars, FastDotProduct fdp, int maxRiceOrder) { 41 | // Check arguments 42 | if (order < 1 || order > 32) 43 | throw new IllegalArgumentException(); 44 | if (roundVars < 0 || roundVars > order || roundVars > 30) 45 | throw new IllegalArgumentException(); 46 | 47 | LinearPredictiveEncoder enc = new LinearPredictiveEncoder(samples, shift, depth, order, fdp); 48 | samples = shiftRight(samples, shift); 49 | 50 | final double[] residues; 51 | Integer[] indices = null; 52 | int scaler = 1 << enc.coefShift; 53 | if (roundVars > 0) { 54 | residues = new double[order]; 55 | indices = new Integer[order]; 56 | for (int i = 0; i < order; i++) { 57 | residues[i] = Math.abs(Math.round(enc.realCoefs[i] * scaler) - enc.realCoefs[i] * scaler); 58 | indices[i] = i; 59 | } 60 | Arrays.sort(indices, new Comparator() { 61 | public int compare(Integer x, Integer y) { 62 | return Double.compare(residues[y], residues[x]); 63 | } 64 | }); 65 | } else 66 | residues = null; 67 | 68 | long bestSize = Long.MAX_VALUE; 69 | int[] bestCoefs = enc.coefficients.clone(); 70 | for (int i = 0; i < (1 << roundVars); i++) { 71 | for (int j = 0; j < roundVars; j++) { 72 | int k = indices[j]; 73 | double coef = enc.realCoefs[k]; 74 | int val; 75 | if (((i >>> j) & 1) == 0) 76 | val = (int)Math.floor(coef * scaler); 77 | else 78 | val = (int)Math.ceil(coef * scaler); 79 | enc.coefficients[order - 1 - k] = Math.max(Math.min(val, (1 << (enc.coefDepth - 1)) - 1), -(1 << (enc.coefDepth - 1))); 80 | } 81 | 82 | long[] newData = roundVars > 0 ? samples.clone() : samples; 83 | applyLpc(newData, enc.coefficients, enc.coefShift); 84 | long temp = RiceEncoder.computeBestSizeAndOrder(newData, order, maxRiceOrder); 85 | long size = 1 + 6 + 1 + shift + order * depth + (temp >>> 4); 86 | if (size < bestSize) { 87 | bestSize = size; 88 | bestCoefs = enc.coefficients.clone(); 89 | enc.riceOrder = (int)(temp & 0xF); 90 | } 91 | } 92 | enc.coefficients = bestCoefs; 93 | return new SizeEstimate(bestSize, enc); 94 | } 95 | 96 | 97 | 98 | private final int order; 99 | private final double[] realCoefs; 100 | private int[] coefficients; 101 | private final int coefDepth; 102 | private final int coefShift; 103 | public int riceOrder; 104 | 105 | 106 | public LinearPredictiveEncoder(long[] samples, int shift, int depth, int order, FastDotProduct fdp) { 107 | super(shift, depth); 108 | int numSamples = samples.length; 109 | if (order < 1 || order > 32 || numSamples < order) 110 | throw new IllegalArgumentException(); 111 | this.order = order; 112 | 113 | // Set up matrix to solve linear least squares problem 114 | double[][] matrix = new double[order][order + 1]; 115 | for (int r = 0; r < matrix.length; r++) { 116 | for (int c = 0; c < matrix[r].length; c++) { 117 | double val; 118 | if (c >= r) 119 | val = fdp.dotProduct(r, c, samples.length - order); 120 | else 121 | val = matrix[c][r]; 122 | matrix[r][c] = val; 123 | } 124 | } 125 | 126 | // Solve matrix, then examine range of coefficients 127 | realCoefs = solveMatrix(matrix); 128 | double maxCoef = 0; 129 | for (double x : realCoefs) 130 | maxCoef = Math.max(Math.abs(x), maxCoef); 131 | int wholeBits = maxCoef >= 1 ? (int)(Math.log(maxCoef) / Math.log(2)) + 1 : 0; 132 | 133 | // Quantize and store the coefficients 134 | coefficients = new int[order]; 135 | coefDepth = 15; // The maximum possible 136 | coefShift = coefDepth - 1 - wholeBits; 137 | for (int i = 0; i < realCoefs.length; i++) { 138 | double coef = realCoefs[realCoefs.length - 1 - i]; 139 | int val = (int)Math.round(coef * (1 << coefShift)); 140 | coefficients[i] = Math.max(Math.min(val, (1 << (coefDepth - 1)) - 1), -(1 << (coefDepth - 1))); 141 | } 142 | } 143 | 144 | 145 | // Solves an n * (n+1) augmented matrix (which modifies its values as a side effect), 146 | // returning a new solution vector of length n. 147 | private static double[] solveMatrix(double[][] mat) { 148 | // Gauss-Jordan elimination algorithm 149 | int rows = mat.length; 150 | int cols = mat[0].length; 151 | if (rows + 1 != cols) 152 | throw new IllegalArgumentException(); 153 | 154 | // Forward elimination 155 | int numPivots = 0; 156 | for (int j = 0; j < rows && numPivots < rows; j++) { 157 | int pivotRow = rows; 158 | double pivotMag = 0; 159 | for (int i = numPivots; i < rows; i++) { 160 | if (Math.abs(mat[i][j]) > pivotMag) { 161 | pivotMag = Math.abs(mat[i][j]); 162 | pivotRow = i; 163 | } 164 | } 165 | if (pivotRow == rows) 166 | continue; 167 | 168 | double[] temp = mat[numPivots]; 169 | mat[numPivots] = mat[pivotRow]; 170 | mat[pivotRow] = temp; 171 | pivotRow = numPivots; 172 | numPivots++; 173 | 174 | double factor = mat[pivotRow][j]; 175 | for (int k = 0; k < cols; k++) 176 | mat[pivotRow][k] /= factor; 177 | mat[pivotRow][j] = 1; 178 | 179 | for (int i = pivotRow + 1; i < rows; i++) { 180 | factor = mat[i][j]; 181 | for (int k = 0; k < cols; k++) 182 | mat[i][k] -= mat[pivotRow][k] * factor; 183 | mat[i][j] = 0; 184 | } 185 | } 186 | 187 | // Back substitution 188 | double[] result = new double[rows]; 189 | for (int i = numPivots - 1; i >= 0; i--) { 190 | int pivotCol = 0; 191 | while (pivotCol < cols && mat[i][pivotCol] == 0) 192 | pivotCol++; 193 | if (pivotCol == cols) 194 | continue; 195 | result[pivotCol] = mat[i][cols - 1]; 196 | 197 | for (int j = i - 1; j >= 0; j--) { 198 | double factor = mat[j][pivotCol]; 199 | for (int k = 0; k < cols; k++) 200 | mat[j][k] -= mat[i][k] * factor; 201 | mat[j][pivotCol] = 0; 202 | } 203 | } 204 | return result; 205 | } 206 | 207 | 208 | public void encode(long[] samples, BitOutputStream out) throws IOException { 209 | Objects.requireNonNull(samples); 210 | Objects.requireNonNull(out); 211 | if (samples.length < order) 212 | throw new IllegalArgumentException(); 213 | 214 | writeTypeAndShift(32 + order - 1, out); 215 | samples = shiftRight(samples, sampleShift); 216 | 217 | for (int i = 0; i < order; i++) // Warmup 218 | writeRawSample(samples[i], out); 219 | out.writeInt(4, coefDepth - 1); 220 | out.writeInt(5, coefShift); 221 | for (int x : coefficients) 222 | out.writeInt(coefDepth, x); 223 | applyLpc(samples, coefficients, coefShift); 224 | RiceEncoder.encode(samples, order, riceOrder, out); 225 | } 226 | 227 | 228 | 229 | /*---- Static helper functions ----*/ 230 | 231 | // Applies linear prediction to data[coefs.length : data.length] so that newdata[i] = 232 | // data[i] - ((data[i-1]*coefs[0] + data[i-2]*coefs[1] + ... + data[i-coefs.length]*coefs[coefs.length]) >> shift). 233 | // By FLAC parameters, each data[i] must fit in a signed 33-bit integer, each coef must fit in signed int15, and coefs.length <= 32. 234 | // When these preconditions are met, they guarantee the lack of arithmetic overflow in the computation and results, 235 | // and each value written back to the data array fits in a signed int53. 236 | static void applyLpc(long[] data, int[] coefs, int shift) { 237 | // Check arguments and arrays strictly 238 | Objects.requireNonNull(data); 239 | Objects.requireNonNull(coefs); 240 | if (coefs.length > 32 || shift < 0 || shift > 63) 241 | throw new IllegalArgumentException(); 242 | for (long x : data) { 243 | x >>= 32; 244 | if (x != 0 && x != -1) // Check if it fits in signed int33 245 | throw new IllegalArgumentException(); 246 | } 247 | for (int x : coefs) { 248 | x >>= 14; 249 | if (x != 0 && x != -1) // Check if it fits in signed int15 250 | throw new IllegalArgumentException(); 251 | } 252 | 253 | // Perform the LPC convolution/FIR 254 | for (int i = data.length - 1; i >= coefs.length; i--) { 255 | long sum = 0; 256 | for (int j = 0; j < coefs.length; j++) 257 | sum += data[i - 1 - j] * coefs[j]; 258 | long val = data[i] - (sum >> shift); 259 | if ((val >> 52) != 0 && (val >> 52) != -1) // Check if it fits in signed int53 260 | throw new AssertionError(); 261 | data[i] = val; 262 | } 263 | } 264 | 265 | 266 | // Returns a new array where each result[i] = data[i] >> shift. 267 | static long[] shiftRight(long[] data, int shift) { 268 | Objects.requireNonNull(data); 269 | if (shift < 0 || shift > 63) 270 | throw new IllegalArgumentException(); 271 | long[] result = new long[data.length]; 272 | for (int i = 0; i < data.length; i++) 273 | result[i] = data[i] >> shift; 274 | return result; 275 | } 276 | 277 | } 278 | -------------------------------------------------------------------------------- /src/io/nayuki/flac/decode/FlacDecoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * FLAC library (Java) 3 | * 4 | * Copyright (c) Project Nayuki 5 | * https://www.nayuki.io/page/flac-library-java 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with this program (see COPYING.txt and COPYING.LESSER.txt). 19 | * If not, see . 20 | */ 21 | 22 | package io.nayuki.flac.decode; 23 | 24 | import java.io.File; 25 | import java.io.IOException; 26 | import java.util.Objects; 27 | import io.nayuki.flac.common.FrameInfo; 28 | import io.nayuki.flac.common.SeekTable; 29 | import io.nayuki.flac.common.StreamInfo; 30 | 31 | 32 | /** 33 | * Handles high-level decoding and seeking in FLAC files. Also returns metadata blocks. 34 | * Every object is stateful, not thread-safe, and needs to be closed. Sample usage: 35 | *
// Create a decoder
 36 |  *FlacDecoder dec = new FlacDecoder(...);
 37 |  *
 38 |  *// Make the decoder process all metadata blocks internally.
 39 |  *// We could capture the returned data for extra processing.
 40 |  *// We must read all metadata before reading audio data.
 41 |  *while (dec.readAndHandleMetadataBlock() != null);
 42 |  *
 43 |  *// Read audio samples starting from beginning
 44 |  *int[][] samples = (...);
 45 |  *dec.readAudioBlock(samples, ...);
 46 |  *dec.readAudioBlock(samples, ...);
 47 |  *dec.readAudioBlock(samples, ...);
 48 |  *
 49 |  *// Seek to some position and continue reading
 50 |  *dec.seekAndReadAudioBlock(..., samples, ...);
 51 |  *dec.readAudioBlock(samples, ...);
 52 |  *dec.readAudioBlock(samples, ...);
 53 |  *
 54 |  *// Close underlying file stream
 55 |  *dec.close();
56 | *@see FrameDecoder 57 | *@see FlacLowLevelInput 58 | */ 59 | public final class FlacDecoder implements AutoCloseable { 60 | 61 | /*---- Fields ----*/ 62 | 63 | public StreamInfo streamInfo; 64 | public SeekTable seekTable; 65 | 66 | private FlacLowLevelInput input; 67 | 68 | private long metadataEndPos; 69 | 70 | private FrameDecoder frameDec; 71 | 72 | 73 | 74 | /*---- Constructors ----*/ 75 | 76 | // Constructs a new FLAC decoder to read the given file. 77 | // This immediately reads the basic header but not metadata blocks. 78 | public FlacDecoder(File file) throws IOException { 79 | // Initialize streams 80 | Objects.requireNonNull(file); 81 | input = new SeekableFileFlacInput(file); 82 | 83 | // Read basic header 84 | if (input.readUint(32) != 0x664C6143) // Magic string "fLaC" 85 | throw new DataFormatException("Invalid magic string"); 86 | metadataEndPos = -1; 87 | } 88 | 89 | 90 | 91 | /*---- Methods ----*/ 92 | 93 | // Reads, handles, and returns the next metadata block. Returns a pair (Integer type, byte[] data) if the 94 | // next metadata block exists, otherwise returns null if the final metadata block was previously read. 95 | // In addition to reading and returning data, this method also updates the internal state 96 | // of this object to reflect the new data seen, and throws exceptions for situations such as 97 | // not starting with a stream info metadata block or encountering duplicates of certain blocks. 98 | public Object[] readAndHandleMetadataBlock() throws IOException { 99 | if (metadataEndPos != -1) 100 | return null; // All metadata already consumed 101 | 102 | // Read entire block 103 | boolean last = input.readUint(1) != 0; 104 | int type = input.readUint(7); 105 | int length = input.readUint(24); 106 | byte[] data = new byte[length]; 107 | input.readFully(data); 108 | 109 | // Handle recognized block 110 | if (type == 0) { 111 | if (streamInfo != null) 112 | throw new DataFormatException("Duplicate stream info metadata block"); 113 | streamInfo = new StreamInfo(data); 114 | } else { 115 | if (streamInfo == null) 116 | throw new DataFormatException("Expected stream info metadata block"); 117 | if (type == 3) { 118 | if (seekTable != null) 119 | throw new DataFormatException("Duplicate seek table metadata block"); 120 | seekTable = new SeekTable(data); 121 | } 122 | } 123 | 124 | if (last) { 125 | metadataEndPos = input.getPosition(); 126 | frameDec = new FrameDecoder(input, streamInfo.sampleDepth); 127 | } 128 | return new Object[]{type, data}; 129 | } 130 | 131 | 132 | // Reads and decodes the next block of audio samples into the given buffer, 133 | // returning the number of samples in the block. The return value is 0 if the read 134 | // started at the end of stream, or a number in the range [1, 65536] for a valid block. 135 | // All metadata blocks must be read before starting to read audio blocks. 136 | public int readAudioBlock(int[][] samples, int off) throws IOException { 137 | if (frameDec == null) 138 | throw new IllegalStateException("Metadata blocks not fully consumed yet"); 139 | FrameInfo frame = frameDec.readFrame(samples, off); 140 | if (frame == null) 141 | return 0; 142 | else 143 | return frame.blockSize; // In the range [1, 65536] 144 | } 145 | 146 | 147 | // Seeks to the given sample position and reads audio samples into the given buffer, 148 | // returning the number of samples filled. If audio data is available then the return value 149 | // is at least 1; otherwise 0 is returned to indicate the end of stream. Note that the 150 | // sample position can land in the middle of a FLAC block and will still behave correctly. 151 | // In theory this method subsumes the functionality of readAudioBlock(), but seeking can be 152 | // an expensive operation so readAudioBlock() should be used for ordinary contiguous streaming. 153 | public int seekAndReadAudioBlock(long pos, int[][] samples, int off) throws IOException { 154 | if (frameDec == null) 155 | throw new IllegalStateException("Metadata blocks not fully consumed yet"); 156 | 157 | long[] sampleAndFilePos = getBestSeekPoint(pos); 158 | if (pos - sampleAndFilePos[0] > 300000) { 159 | sampleAndFilePos = seekBySyncAndDecode(pos); 160 | sampleAndFilePos[1] -= metadataEndPos; 161 | } 162 | input.seekTo(sampleAndFilePos[1] + metadataEndPos); 163 | 164 | long curPos = sampleAndFilePos[0]; 165 | int[][] smpl = new int[streamInfo.numChannels][65536]; 166 | while (true) { 167 | FrameInfo frame = frameDec.readFrame(smpl, 0); 168 | if (frame == null) 169 | return 0; 170 | long nextPos = curPos + frame.blockSize; 171 | if (nextPos > pos) { 172 | for (int ch = 0; ch < smpl.length; ch++) 173 | System.arraycopy(smpl[ch], (int)(pos - curPos), samples[ch], off, (int)(nextPos - pos)); 174 | return (int)(nextPos - pos); 175 | } 176 | curPos = nextPos; 177 | } 178 | } 179 | 180 | 181 | private long[] getBestSeekPoint(long pos) { 182 | long samplePos = 0; 183 | long filePos = 0; 184 | if (seekTable != null) { 185 | for (SeekTable.SeekPoint p : seekTable.points) { 186 | if (p.sampleOffset <= pos) { 187 | samplePos = p.sampleOffset; 188 | filePos = p.fileOffset; 189 | } else 190 | break; 191 | } 192 | } 193 | return new long[]{samplePos, filePos}; 194 | } 195 | 196 | 197 | // Returns a pair (sample offset, file position) such sampleOffset <= pos and abs(sampleOffset - pos) 198 | // is a relatively small number compared to the total number of samples in the audio file. 199 | // This method works by skipping to arbitrary places in the file, finding a sync sequence, 200 | // decoding the frame header, examining the audio position stored in the frame, and possibly deciding 201 | // to skip to other places and retrying. This changes the state of the input streams as a side effect. 202 | // There is a small chance of finding a valid-looking frame header but causing erroneous decoding later. 203 | private long[] seekBySyncAndDecode(long pos) throws IOException { 204 | long start = metadataEndPos; 205 | long end = input.getLength(); 206 | while (end - start > 100000) { // Binary search 207 | long mid = (start + end) >>> 1; 208 | long[] offsets = getNextFrameOffsets(mid); 209 | if (offsets == null || offsets[0] > pos) 210 | end = mid; 211 | else 212 | start = offsets[1]; 213 | } 214 | return getNextFrameOffsets(start); 215 | } 216 | 217 | 218 | // Returns a pair (sample offset, file position) describing the next frame found starting 219 | // at the given file offset, or null if no frame is found before the end of stream. 220 | // This changes the state of the input streams as a side effect. 221 | private long[] getNextFrameOffsets(long filePos) throws IOException { 222 | if (filePos < metadataEndPos || filePos > input.getLength()) 223 | throw new IllegalArgumentException("File position out of bounds"); 224 | 225 | // Repeatedly search for a sync 226 | while (true) { 227 | input.seekTo(filePos); 228 | 229 | // Finite state machine to match the 2-byte sync sequence 230 | int state = 0; 231 | while (true) { 232 | int b = input.readByte(); 233 | if (b == -1) 234 | return null; 235 | else if (b == 0xFF) 236 | state = 1; 237 | else if (state == 1 && (b & 0xFE) == 0xF8) 238 | break; 239 | else 240 | state = 0; 241 | } 242 | 243 | // Sync found, rewind 2 bytes, try to decode frame header 244 | filePos = input.getPosition() - 2; 245 | input.seekTo(filePos); 246 | try { 247 | FrameInfo frame = FrameInfo.readFrame(input); 248 | return new long[]{getSampleOffset(frame), filePos}; 249 | } catch (DataFormatException e) { 250 | // Advance past the sync and search again 251 | filePos += 2; 252 | } 253 | } 254 | } 255 | 256 | 257 | // Calculates the sample offset of the given frame, automatically handling the constant-block-size case. 258 | private long getSampleOffset(FrameInfo frame) { 259 | Objects.requireNonNull(frame); 260 | if (frame.sampleOffset != -1) 261 | return frame.sampleOffset; 262 | else if (frame.frameIndex != -1) 263 | return frame.frameIndex * streamInfo.maxBlockSize; 264 | else 265 | throw new AssertionError(); 266 | } 267 | 268 | 269 | // Closes the underlying input streams and discards object data. 270 | // This decoder object becomes invalid for any method calls or field usages. 271 | public void close() throws IOException { 272 | if (input != null) { 273 | streamInfo = null; 274 | seekTable = null; 275 | frameDec = null; 276 | input.close(); 277 | input = null; 278 | } 279 | } 280 | 281 | } 282 | -------------------------------------------------------------------------------- /src/io/nayuki/flac/app/ShowFlacFileStats.java: -------------------------------------------------------------------------------- 1 | /* 2 | * FLAC library (Java) 3 | * 4 | * Copyright (c) Project Nayuki 5 | * https://www.nayuki.io/page/flac-library-java 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with this program (see COPYING.txt and COPYING.LESSER.txt). 19 | * If not, see . 20 | */ 21 | 22 | package io.nayuki.flac.app; 23 | 24 | import java.io.File; 25 | import java.io.IOException; 26 | import java.util.ArrayList; 27 | import java.util.Arrays; 28 | import java.util.List; 29 | import java.util.Map; 30 | import java.util.Objects; 31 | import java.util.SortedMap; 32 | import java.util.TreeMap; 33 | import io.nayuki.flac.common.FrameInfo; 34 | import io.nayuki.flac.common.StreamInfo; 35 | import io.nayuki.flac.decode.DataFormatException; 36 | import io.nayuki.flac.decode.FlacLowLevelInput; 37 | import io.nayuki.flac.decode.FrameDecoder; 38 | import io.nayuki.flac.decode.SeekableFileFlacInput; 39 | 40 | 41 | /** 42 | * Reads a FLAC file, collects various statistics, and 43 | * prints human-formatted information to standard output. 44 | *

Usage: java ShowFlacFileStats InFile.flac

45 | *

Example output from this program (abbreviated):

46 | *
===== Block sizes (samples) =====
 47 |  * 4096: * (11)
 48 |  * 5120: ***** (56)
 49 |  * 6144: *********** (116)
 50 |  * 7168: ************* (134)
 51 |  * 8192: ***************** (177)
 52 |  * 9216: ***************** (182)
 53 |  *10240: ***************** (179)
 54 |  *11264: ****************************** (318)
 55 |  *12288: ****************** (194)
 56 |  *
 57 |  *===== Frame sizes (bytes) =====
 58 |  *12000: ****** (20)
 59 |  *13000: ******* (24)
 60 |  *14000: ********** (34)
 61 |  *15000: **************** (51)
 62 |  *16000: ********************* (68)
 63 |  *17000: ******************* (63)
 64 |  *18000: ******************* (63)
 65 |  *19000: ************************ (77)
 66 |  *20000: ********************* (70)
 67 |  *21000: ****************** (60)
 68 |  *22000: ************************* (82)
 69 |  *23000: ********************* (69)
 70 |  *24000: *************************** (87)
 71 |  *25000: *************************** (88)
 72 |  *26000: ********************** (73)
 73 |  *27000: ************************** (84)
 74 |  *28000: ****************************** (98)
 75 |  *29000: ********************** (73)
 76 |  *30000: *********************** (75)
 77 |  *31000: ************ (39)
 78 |  *
 79 |  *===== Average compression ratio at block sizes =====
 80 |  * 4096: ********************** (0.7470)
 81 |  * 5120: ******************** (0.6815)
 82 |  * 6144: ******************** (0.6695)
 83 |  * 7168: ******************* (0.6438)
 84 |  * 8192: ******************* (0.6379)
 85 |  * 9216: ****************** (0.6107)
 86 |  *10240: ****************** (0.6022)
 87 |  *11264: ***************** (0.5628)
 88 |  *12288: ***************** (0.5724)
 89 |  *
 90 |  *===== Stereo coding modes =====
 91 |  *Independent: **** (83)
 92 |  *Left-side  :  (3)
 93 |  *Right-side : ************************ (574)
 94 |  *Mid-side   : ****************************** (708)
95 | */ 96 | public final class ShowFlacFileStats { 97 | 98 | /*---- Main application function ----*/ 99 | 100 | public static void main(String[] args) throws IOException { 101 | // Handle command line arguments 102 | if (args.length != 1) { 103 | System.err.println("Usage: java ShowFlacFileStats InFile.flac"); 104 | System.exit(1); 105 | return; 106 | } 107 | File inFile = new File(args[0]); 108 | 109 | // Data structures to hold statistics 110 | List blockSizes = new ArrayList<>(); 111 | List frameSizes = new ArrayList<>(); 112 | List channelAssignments = new ArrayList<>(); 113 | 114 | // Read input file 115 | StreamInfo streamInfo = null; 116 | try (FlacLowLevelInput input = new SeekableFileFlacInput(inFile)) { 117 | // Magic string "fLaC" 118 | if (input.readUint(32) != 0x664C6143) 119 | throw new DataFormatException("Invalid magic string"); 120 | 121 | // Handle metadata blocks 122 | for (boolean last = false; !last; ) { 123 | last = input.readUint(1) != 0; 124 | int type = input.readUint(7); 125 | int length = input.readUint(24); 126 | byte[] data = new byte[length]; 127 | input.readFully(data); 128 | if (type == 0) 129 | streamInfo = new StreamInfo(data); 130 | } 131 | 132 | // Decode every frame 133 | FrameDecoder dec = new FrameDecoder(input, streamInfo.sampleDepth); 134 | int[][] blockSamples = new int[8][65536]; 135 | while (true) { 136 | FrameInfo meta = dec.readFrame(blockSamples, 0); 137 | if (meta == null) 138 | break; 139 | blockSizes.add(meta.blockSize); 140 | frameSizes.add(meta.frameSize); 141 | channelAssignments.add(meta.channelAssignment); 142 | } 143 | } 144 | 145 | // Build and print graphs 146 | printBlockSizeHistogram(blockSizes); 147 | printFrameSizeHistogram(frameSizes); 148 | printCompressionRatioGraph(streamInfo, blockSizes, frameSizes); 149 | if (streamInfo.numChannels == 2) 150 | printStereoModeGraph(channelAssignments); 151 | } 152 | 153 | 154 | 155 | /*---- Statistics-processing functions ----*/ 156 | 157 | private static void printBlockSizeHistogram(List blockSizes) { 158 | Map blockSizeCounts = new TreeMap<>(); 159 | for (int bs : blockSizes) { 160 | if (!blockSizeCounts.containsKey(bs)) 161 | blockSizeCounts.put(bs, 0); 162 | int count = blockSizeCounts.get(bs) + 1; 163 | blockSizeCounts.put(bs, count); 164 | } 165 | List blockSizeLabels = new ArrayList<>(); 166 | List blockSizeValues = new ArrayList<>(); 167 | for (Map.Entry entry : blockSizeCounts.entrySet()) { 168 | blockSizeLabels.add(String.format("%5d", entry.getKey())); 169 | blockSizeValues.add((double)entry.getValue()); 170 | } 171 | printNormalizedBarGraph("Block sizes (samples)", blockSizeLabels, blockSizeValues); 172 | } 173 | 174 | 175 | private static void printFrameSizeHistogram(List frameSizes) { 176 | final int step = 1000; 177 | SortedMap frameSizeCounts = new TreeMap<>(); 178 | int maxKeyLen = 0; 179 | for (int fs : frameSizes) { 180 | int key = (int)Math.round((double)fs / step); 181 | maxKeyLen = Math.max(Integer.toString(key * step).length(), maxKeyLen); 182 | if (!frameSizeCounts.containsKey(key)) 183 | frameSizeCounts.put(key, 0); 184 | frameSizeCounts.put(key, frameSizeCounts.get(key) + 1); 185 | } 186 | for (int i = frameSizeCounts.firstKey(); i < frameSizeCounts.lastKey(); i++) { 187 | if (!frameSizeCounts.containsKey(i)) 188 | frameSizeCounts.put(i, 0); 189 | } 190 | List frameSizeLabels = new ArrayList<>(); 191 | List frameSizeValues = new ArrayList<>(); 192 | for (Map.Entry entry : frameSizeCounts.entrySet()) { 193 | frameSizeLabels.add(String.format("%" + maxKeyLen + "d", entry.getKey() * step)); 194 | frameSizeValues.add((double)entry.getValue()); 195 | } 196 | printNormalizedBarGraph("Frame sizes (bytes)", frameSizeLabels, frameSizeValues); 197 | } 198 | 199 | 200 | private static void printCompressionRatioGraph(StreamInfo streamInfo, List blockSizes, List frameSizes) { 201 | Map blockSizeCounts = new TreeMap<>(); 202 | Map blockSizeBytes = new TreeMap<>(); 203 | for (int i = 0; i < blockSizes.size(); i++) { 204 | int bs = blockSizes.get(i); 205 | if (!blockSizeCounts.containsKey(bs)) { 206 | blockSizeCounts.put(bs, 0); 207 | blockSizeBytes.put(bs, 0L); 208 | } 209 | blockSizeCounts.put(bs, blockSizeCounts.get(bs) + 1); 210 | blockSizeBytes.put(bs, blockSizeBytes.get(bs) + frameSizes.get(i)); 211 | } 212 | List blockRatioLabels = new ArrayList<>(); 213 | List blockRatioValues = new ArrayList<>(); 214 | for (Map.Entry entry : blockSizeCounts.entrySet()) { 215 | blockRatioLabels.add(String.format("%5d", entry.getKey())); 216 | blockRatioValues.add(blockSizeBytes.get(entry.getKey()) / ((double)entry.getValue() * entry.getKey() * streamInfo.numChannels * streamInfo.sampleDepth / 8)); 217 | } 218 | printNormalizedBarGraph("Average compression ratio at block sizes", blockRatioLabels, blockRatioValues); 219 | } 220 | 221 | 222 | private static void printStereoModeGraph(List channelAssignments) { 223 | List stereoModeLabels = Arrays.asList("Independent", "Left-side", "Right-side", "Mid-side"); 224 | List stereoModeValues = new ArrayList<>(); 225 | for (int i = 0; i < 4; i++) 226 | stereoModeValues.add(0.0); 227 | for (int mode : channelAssignments) { 228 | int index = switch (mode) { 229 | case 1 -> 0; 230 | case 8 -> 1; 231 | case 9 -> 2; 232 | case 10 -> 3; 233 | default -> throw new DataFormatException("Invalid mode in stereo stream"); 234 | }; 235 | stereoModeValues.set(index, stereoModeValues.get(index) + 1); 236 | } 237 | printNormalizedBarGraph("Stereo coding modes", stereoModeLabels, stereoModeValues); 238 | } 239 | 240 | 241 | 242 | /*---- Utility functions ----*/ 243 | 244 | private static void printNormalizedBarGraph(String heading, List labels, List values) { 245 | Objects.requireNonNull(heading); 246 | Objects.requireNonNull(labels); 247 | Objects.requireNonNull(values); 248 | if (labels.size() != values.size()) 249 | throw new IllegalArgumentException(); 250 | 251 | final int maxBarWidth = 100; 252 | System.out.printf("==================== %s ====================%n", heading); 253 | System.out.println(); 254 | 255 | int maxLabelLen = 0; 256 | for (String s : labels) 257 | maxLabelLen = Math.max(s.length(), maxLabelLen); 258 | String spaces = new String(new char[maxLabelLen]).replace((char)0, ' '); 259 | 260 | double maxValue = 1; // This avoids division by zero 261 | for (double val : values) 262 | maxValue = Math.max(val, maxValue); 263 | 264 | for (int i = 0; i < labels.size(); i++) { 265 | String label = labels.get(i); 266 | double value = values.get(i); 267 | int barWidth = (int)Math.round(value / maxValue * maxBarWidth); 268 | String bar = new String(new char[barWidth]).replace((char)0, '*'); 269 | System.out.printf("%s%s: %s (%s)%n", label, spaces.substring(label.length()), 270 | bar, (long)value == value ? Long.toString((long)value) : Double.toString(value)); 271 | } 272 | System.out.println(); 273 | System.out.println(); 274 | } 275 | 276 | } 277 | -------------------------------------------------------------------------------- /src/io/nayuki/flac/encode/SubframeEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * FLAC library (Java) 3 | * 4 | * Copyright (c) Project Nayuki 5 | * https://www.nayuki.io/page/flac-library-java 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with this program (see COPYING.txt and COPYING.LESSER.txt). 19 | * If not, see . 20 | */ 21 | 22 | package io.nayuki.flac.encode; 23 | 24 | import java.io.IOException; 25 | import java.util.Objects; 26 | 27 | 28 | /* 29 | * Calculates/estimates the encoded size of a subframe of audio sample data, and also performs the encoding to an output stream. 30 | */ 31 | public abstract class SubframeEncoder { 32 | 33 | /*---- Static functions ----*/ 34 | 35 | // Computes/estimates the best way to encode the given vector of audio sample data at the given sample depth under 36 | // the given search criteria, returning a size estimate plus a new encoder object associated with that size. 37 | public static SizeEstimate computeBest(long[] samples, int sampleDepth, SearchOptions opt) { 38 | // Check arguments 39 | Objects.requireNonNull(samples); 40 | if (sampleDepth < 1 || sampleDepth > 33) 41 | throw new IllegalArgumentException(); 42 | Objects.requireNonNull(opt); 43 | for (long x : samples) { 44 | x >>= sampleDepth - 1; 45 | if (x != 0 && x != -1) // Check that the input actually fits the indicated sample depth 46 | throw new IllegalArgumentException(); 47 | } 48 | 49 | // Encode with constant if possible 50 | SizeEstimate result = ConstantEncoder.computeBest(samples, 0, sampleDepth); 51 | if (result != null) 52 | return result; 53 | 54 | // Detect number of trailing zero bits 55 | int shift = computeWastedBits(samples); 56 | 57 | // Start with verbatim as fallback 58 | result = VerbatimEncoder.computeBest(samples, shift, sampleDepth); 59 | 60 | // Try fixed prediction encoding 61 | for (int order = opt.minFixedOrder; 0 <= order && order <= opt.maxFixedOrder; order++) { 62 | SizeEstimate temp = FixedPredictionEncoder.computeBest( 63 | samples, shift, sampleDepth, order, opt.maxRiceOrder); 64 | result = result.minimum(temp); 65 | } 66 | 67 | // Try linear predictive coding 68 | FastDotProduct fdp = new FastDotProduct(samples, Math.max(opt.maxLpcOrder, 0)); 69 | for (int order = opt.minLpcOrder; 0 <= order && order <= opt.maxLpcOrder; order++) { 70 | SizeEstimate temp = LinearPredictiveEncoder.computeBest( 71 | samples, shift, sampleDepth, order, Math.min(opt.lpcRoundVariables, order), fdp, opt.maxRiceOrder); 72 | result = result.minimum(temp); 73 | } 74 | 75 | // Return the encoder found with the lowest bit length 76 | return result; 77 | } 78 | 79 | 80 | // Looks at each value in the array and computes the minimum number of trailing binary zeros 81 | // among all the elements. For example, computedwastedBits({0b10, 0b10010, 0b1100}) = 1. 82 | // If there are no elements or every value is zero (the former actually implies the latter), then 83 | // the return value is 0. This is because every zero value has an infinite number of trailing zeros. 84 | private static int computeWastedBits(long[] data) { 85 | Objects.requireNonNull(data); 86 | long accumulator = 0; 87 | for (long x : data) 88 | accumulator |= x; 89 | if (accumulator == 0) 90 | return 0; 91 | else { 92 | int result = Long.numberOfTrailingZeros(accumulator); 93 | assert 0 <= result && result <= 63; 94 | return result; 95 | } 96 | } 97 | 98 | 99 | 100 | /*---- Instance members ----*/ 101 | 102 | protected final int sampleShift; // Number of bits to shift each sample right by. In the range [0, sampleDepth]. 103 | protected final int sampleDepth; // Stipulate that each audio sample fits in a signed integer of this width. In the range [1, 33]. 104 | 105 | 106 | // Constructs a subframe encoder on some data array with the given right shift (wasted bits) and sample depth. 107 | // Note that every element of the array must fit in a signed depth-bit integer and have at least 'shift' trailing binary zeros. 108 | // After the encoder object is created and when encode() is called, it must receive the same array length and values (but the object reference can be different). 109 | // Subframe encoders should not retain a reference to the sample data array because the higher-level encoder may request and 110 | // keep many size estimates coupled with encoder objects, but only utilize a small number of encoder objects in the end. 111 | protected SubframeEncoder(int shift, int depth) { 112 | if (depth < 1 || depth > 33 || shift < 0 || shift > depth) 113 | throw new IllegalArgumentException(); 114 | sampleShift = shift; 115 | sampleDepth = depth; 116 | } 117 | 118 | 119 | // Encodes the given vector of audio sample data to the given bit output stream 120 | // using the current encoding method (dictated by subclasses and field values). 121 | // This requires the data array to have the same values (but not necessarily be the same object reference) 122 | // as the array that was passed to the constructor when this encoder object was created. 123 | public abstract void encode(long[] samples, BitOutputStream out) throws IOException; 124 | 125 | 126 | // Writes the subframe header to the given output stream, based on the given 127 | // type code (uint6) and this object's sampleShift field (a.k.a. wasted bits per sample). 128 | protected final void writeTypeAndShift(int type, BitOutputStream out) throws IOException { 129 | // Check arguments 130 | if ((type >>> 6) != 0) 131 | throw new IllegalArgumentException(); 132 | Objects.requireNonNull(out); 133 | 134 | // Write some fields 135 | out.writeInt(1, 0); 136 | out.writeInt(6, type); 137 | 138 | // Write shift value in quasi-unary 139 | if (sampleShift == 0) 140 | out.writeInt(1, 0); 141 | else { 142 | out.writeInt(1, 1); 143 | for (int i = 0; i < sampleShift - 1; i++) 144 | out.writeInt(1, 0); 145 | out.writeInt(1, 1); 146 | } 147 | } 148 | 149 | 150 | // Writes the given value to the output stream as a signed (sampleDepth-sampleShift) bit integer. 151 | // Note that the value to being written is equal to the raw sample value shifted right by sampleShift. 152 | protected final void writeRawSample(long val, BitOutputStream out) throws IOException { 153 | int width = sampleDepth - sampleShift; 154 | if (width < 1 || width > 33) 155 | throw new IllegalStateException(); 156 | long temp = val >> (width - 1); 157 | if (temp != 0 && temp != -1) 158 | throw new IllegalArgumentException(); 159 | if (width <= 32) 160 | out.writeInt(width, (int)val); 161 | else { // width == 33 162 | out.writeInt(1, (int)(val >>> 32)); 163 | out.writeInt(32, (int)val); 164 | } 165 | } 166 | 167 | 168 | 169 | /*---- Helper structure ----*/ 170 | 171 | // Represents options for how to search the encoding parameters for a subframe. It is used directly by 172 | // SubframeEncoder.computeBest() and indirectly by its sub-calls. Objects of this class are immutable. 173 | public static final class SearchOptions { 174 | 175 | /*-- Fields --*/ 176 | 177 | // The range of orders to test for fixed prediction mode, possibly none. 178 | // The values satisfy (minFixedOrder = maxFixedOrder = -1) || (0 <= minFixedOrder <= maxFixedOrder <= 4). 179 | public final int minFixedOrder; 180 | public final int maxFixedOrder; 181 | 182 | // The range of orders to test for linear predictive coding (LPC) mode, possibly none. 183 | // The values satisfy (minLpcOrder = maxLpcOrder = -1) || (1 <= minLpcOrder <= maxLpcOrder <= 32). 184 | // Note that the FLAC subset format requires maxLpcOrder <= 12 when sampleRate <= 48000. 185 | public final int minLpcOrder; 186 | public final int maxLpcOrder; 187 | 188 | // How many LPC coefficient variables to try rounding both up and down. 189 | // In the range [0, 30]. Note that each increase by one will double the search time! 190 | public final int lpcRoundVariables; 191 | 192 | // The maximum partition order used in Rice coding. The minimum is not configurable and always 0. 193 | // In the range [0, 15]. Note that the FLAC subset format requires maxRiceOrder <= 8. 194 | public final int maxRiceOrder; 195 | 196 | 197 | /*-- Constructors --*/ 198 | 199 | // Constructs a search options object based on the given values, 200 | // throwing an IllegalArgumentException if and only if they are nonsensical. 201 | public SearchOptions(int minFixedOrder, int maxFixedOrder, int minLpcOrder, int maxLpcOrder, int lpcRoundVars, int maxRiceOrder) { 202 | // Check argument ranges 203 | if ((minFixedOrder != -1 || maxFixedOrder != -1) && 204 | !(0 <= minFixedOrder && minFixedOrder <= maxFixedOrder && maxFixedOrder <= 4)) 205 | throw new IllegalArgumentException(); 206 | if ((minLpcOrder != -1 || maxLpcOrder != -1) && 207 | !(1 <= minLpcOrder && minLpcOrder <= maxLpcOrder && maxLpcOrder <= 32)) 208 | throw new IllegalArgumentException(); 209 | if (lpcRoundVars < 0 || lpcRoundVars > 30) 210 | throw new IllegalArgumentException(); 211 | if (maxRiceOrder < 0 || maxRiceOrder > 15) 212 | throw new IllegalArgumentException(); 213 | 214 | // Copy arguments to fields 215 | this.minFixedOrder = minFixedOrder; 216 | this.maxFixedOrder = maxFixedOrder; 217 | this.minLpcOrder = minLpcOrder; 218 | this.maxLpcOrder = maxLpcOrder; 219 | this.lpcRoundVariables = lpcRoundVars; 220 | this.maxRiceOrder = maxRiceOrder; 221 | } 222 | 223 | 224 | /*-- Constants for recommended defaults --*/ 225 | 226 | // Note that these constants are for convenience only, and offer little promises in terms of API stability. 227 | // For example, there is no expectation that the set of search option names as a whole, 228 | // or the values of each search option will remain the same from version to version. 229 | // Even if a search option retains the same value across code versions, the underlying encoder implementation 230 | // can change in such a way that the encoded output is not bit-identical or size-identical across versions. 231 | // Therefore, treat these search options as suggestions that strongly influence the encoded FLAC output, 232 | // but *not* as firm guarantees that the same audio data with the same options will forever produce the same result. 233 | 234 | // These search ranges conform to the FLAC subset format. 235 | public static final SearchOptions SUBSET_ONLY_FIXED = new SearchOptions(0, 4, -1, -1, 0, 8); 236 | public static final SearchOptions SUBSET_MEDIUM = new SearchOptions(0, 1, 2, 8, 0, 5); 237 | public static final SearchOptions SUBSET_BEST = new SearchOptions(0, 1, 2, 12, 0, 8); 238 | public static final SearchOptions SUBSET_INSANE = new SearchOptions(0, 4, 1, 12, 4, 8); 239 | 240 | // These cannot guarantee that an encoded file conforms to the FLAC subset (i.e. they are lax). 241 | public static final SearchOptions LAX_MEDIUM = new SearchOptions(0, 1, 2, 22, 0, 15); 242 | public static final SearchOptions LAX_BEST = new SearchOptions(0, 1, 2, 32, 0, 15); 243 | public static final SearchOptions LAX_INSANE = new SearchOptions(0, 1, 2, 32, 4, 15); 244 | 245 | } 246 | 247 | } 248 | -------------------------------------------------------------------------------- /src/io/nayuki/flac/decode/AbstractFlacLowLevelInput.java: -------------------------------------------------------------------------------- 1 | /* 2 | * FLAC library (Java) 3 | * 4 | * Copyright (c) Project Nayuki 5 | * https://www.nayuki.io/page/flac-library-java 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with this program (see COPYING.txt and COPYING.LESSER.txt). 19 | * If not, see . 20 | */ 21 | 22 | package io.nayuki.flac.decode; 23 | 24 | import java.io.EOFException; 25 | import java.io.IOException; 26 | import java.util.Arrays; 27 | import java.util.Objects; 28 | 29 | 30 | /** 31 | * A basic implementation of most functionality required by FlacLowLevelInpuut. 32 | */ 33 | public abstract class AbstractFlacLowLevelInput implements FlacLowLevelInput { 34 | 35 | /*---- Fields ----*/ 36 | 37 | // Data from the underlying stream is first stored into this byte buffer before further processing. 38 | private long byteBufferStartPos; 39 | private byte[] byteBuffer; 40 | private int byteBufferLen; 41 | private int byteBufferIndex; 42 | 43 | // The buffer of next bits to return to a reader. Note that byteBufferIndex is incremented when byte 44 | // values are put into the bit buffer, but they might not have been consumed by the ultimate reader yet. 45 | private long bitBuffer; // Only the bottom bitBufferLen bits are valid; the top bits are garbage. 46 | private int bitBufferLen; // Always in the range [0, 64]. 47 | 48 | // Current state of the CRC calculations. 49 | private int crc8; // Always a uint8 value. 50 | private int crc16; // Always a uint16 value. 51 | private int crcStartIndex; // In the range [0, byteBufferLen], unless byteBufferLen = -1. 52 | 53 | 54 | 55 | /*---- Constructors ----*/ 56 | 57 | public AbstractFlacLowLevelInput() { 58 | byteBuffer = new byte[4096]; 59 | positionChanged(0); 60 | } 61 | 62 | 63 | 64 | /*---- Methods ----*/ 65 | 66 | /*-- Stream position --*/ 67 | 68 | public long getPosition() { 69 | return byteBufferStartPos + byteBufferIndex - (bitBufferLen + 7) / 8; 70 | } 71 | 72 | 73 | public int getBitPosition() { 74 | return (-bitBufferLen) & 7; 75 | } 76 | 77 | 78 | // When a subclass handles seekTo() and didn't throw UnsupportedOperationException, 79 | // it must call this method to flush the buffers of upcoming data. 80 | protected void positionChanged(long pos) { 81 | byteBufferStartPos = pos; 82 | Arrays.fill(byteBuffer, (byte)0); // Defensive clearing, should have no visible effect outside of debugging 83 | byteBufferLen = 0; 84 | byteBufferIndex = 0; 85 | bitBuffer = 0; // Defensive clearing, should have no visible effect outside of debugging 86 | bitBufferLen = 0; 87 | resetCrcs(); 88 | } 89 | 90 | 91 | // Either returns silently or throws an exception. 92 | private void checkByteAligned() { 93 | if (bitBufferLen % 8 != 0) 94 | throw new IllegalStateException("Not at a byte boundary"); 95 | } 96 | 97 | 98 | /*-- Reading bitwise integers --*/ 99 | 100 | public int readUint(int n) throws IOException { 101 | if (n < 0 || n > 32) 102 | throw new IllegalArgumentException(); 103 | while (bitBufferLen < n) { 104 | int b = readUnderlying(); 105 | if (b == -1) 106 | throw new EOFException(); 107 | bitBuffer = (bitBuffer << 8) | b; 108 | bitBufferLen += 8; 109 | assert 0 <= bitBufferLen && bitBufferLen <= 64; 110 | } 111 | int result = (int)(bitBuffer >>> (bitBufferLen - n)); 112 | if (n != 32) { 113 | result &= (1 << n) - 1; 114 | assert (result >>> n) == 0; 115 | } 116 | bitBufferLen -= n; 117 | assert 0 <= bitBufferLen && bitBufferLen <= 64; 118 | return result; 119 | } 120 | 121 | 122 | public int readSignedInt(int n) throws IOException { 123 | int shift = 32 - n; 124 | return (readUint(n) << shift) >> shift; 125 | } 126 | 127 | 128 | public void readRiceSignedInts(int param, long[] result, int start, int end) throws IOException { 129 | if (param < 0 || param > 31) 130 | throw new IllegalArgumentException(); 131 | long unaryLimit = 1L << (53 - param); 132 | 133 | byte[] consumeTable = RICE_DECODING_CONSUMED_TABLES[param]; 134 | int[] valueTable = RICE_DECODING_VALUE_TABLES[param]; 135 | while (true) { 136 | middle: 137 | while (start <= end - RICE_DECODING_CHUNK) { 138 | if (bitBufferLen < RICE_DECODING_CHUNK * RICE_DECODING_TABLE_BITS) { 139 | if (byteBufferIndex <= byteBufferLen - 8) { 140 | fillBitBuffer(); 141 | } else 142 | break; 143 | } 144 | for (int i = 0; i < RICE_DECODING_CHUNK; i++, start++) { 145 | // Fast decoder 146 | int extractedBits = (int)(bitBuffer >>> (bitBufferLen - RICE_DECODING_TABLE_BITS)) & RICE_DECODING_TABLE_MASK; 147 | int consumed = consumeTable[extractedBits]; 148 | if (consumed == 0) 149 | break middle; 150 | bitBufferLen -= consumed; 151 | result[start] = valueTable[extractedBits]; 152 | } 153 | } 154 | 155 | // Slow decoder 156 | if (start >= end) 157 | break; 158 | long val = 0; 159 | while (readUint(1) == 0) { 160 | if (val >= unaryLimit) { 161 | // At this point, the final decoded value would be so large that the result of the 162 | // downstream restoreLpc() calculation would not fit in the output sample's bit depth - 163 | // hence why we stop early and throw an exception. However, this check is conservative 164 | // and doesn't catch all the cases where the post-LPC result wouldn't fit. 165 | throw new DataFormatException("Residual value too large"); 166 | } 167 | val++; 168 | } 169 | val = (val << param) | readUint(param); // Note: Long masking unnecessary because param <= 31 170 | assert (val >>> 53) == 0; // Must fit a uint53 by design due to unaryLimit 171 | val = (val >>> 1) ^ -(val & 1); // Transform uint53 to int53 according to Rice coding of signed numbers 172 | assert (val >> 52) == 0 || (val >> 52) == -1; // Must fit a signed int53 by design 173 | result[start] = val; 174 | start++; 175 | } 176 | } 177 | 178 | 179 | // Appends at least 8 bits to the bit buffer, or throws EOFException. 180 | private void fillBitBuffer() throws IOException { 181 | int i = byteBufferIndex; 182 | int n = Math.min((64 - bitBufferLen) >>> 3, byteBufferLen - i); 183 | byte[] b = byteBuffer; 184 | if (n > 0) { 185 | for (int j = 0; j < n; j++, i++) 186 | bitBuffer = (bitBuffer << 8) | (b[i] & 0xFF); 187 | bitBufferLen += n << 3; 188 | } else if (bitBufferLen <= 56) { 189 | int temp = readUnderlying(); 190 | if (temp == -1) 191 | throw new EOFException(); 192 | bitBuffer = (bitBuffer << 8) | temp; 193 | bitBufferLen += 8; 194 | } 195 | assert 8 <= bitBufferLen && bitBufferLen <= 64; 196 | byteBufferIndex += n; 197 | } 198 | 199 | 200 | /*-- Reading bytes --*/ 201 | 202 | public int readByte() throws IOException { 203 | checkByteAligned(); 204 | if (bitBufferLen >= 8) 205 | return readUint(8); 206 | else { 207 | assert bitBufferLen == 0; 208 | return readUnderlying(); 209 | } 210 | } 211 | 212 | 213 | public void readFully(byte[] b) throws IOException { 214 | Objects.requireNonNull(b); 215 | checkByteAligned(); 216 | for (int i = 0; i < b.length; i++) 217 | b[i] = (byte)readUint(8); 218 | } 219 | 220 | 221 | // Reads a byte from the byte buffer (if available) or from the underlying stream, returning either a uint8 or -1. 222 | private int readUnderlying() throws IOException { 223 | if (byteBufferIndex >= byteBufferLen) { 224 | if (byteBufferLen == -1) 225 | return -1; 226 | byteBufferStartPos += byteBufferLen; 227 | updateCrcs(0); 228 | byteBufferLen = readUnderlying(byteBuffer, 0, byteBuffer.length); 229 | crcStartIndex = 0; 230 | if (byteBufferLen <= 0) 231 | return -1; 232 | byteBufferIndex = 0; 233 | } 234 | assert byteBufferIndex < byteBufferLen; 235 | int temp = byteBuffer[byteBufferIndex] & 0xFF; 236 | byteBufferIndex++; 237 | return temp; 238 | } 239 | 240 | 241 | // Reads up to 'len' bytes from the underlying byte-based input stream into the given array subrange. 242 | // Returns a value in the range [0, len] for a successful read, or -1 if the end of stream was reached. 243 | protected abstract int readUnderlying(byte[] buf, int off, int len) throws IOException; 244 | 245 | 246 | /*-- CRC calculations --*/ 247 | 248 | public void resetCrcs() { 249 | checkByteAligned(); 250 | crcStartIndex = byteBufferIndex - bitBufferLen / 8; 251 | crc8 = 0; 252 | crc16 = 0; 253 | } 254 | 255 | 256 | public int getCrc8() { 257 | checkByteAligned(); 258 | updateCrcs(bitBufferLen / 8); 259 | if ((crc8 >>> 8) != 0) 260 | throw new AssertionError(); 261 | return crc8; 262 | } 263 | 264 | 265 | public int getCrc16() { 266 | checkByteAligned(); 267 | updateCrcs(bitBufferLen / 8); 268 | if ((crc16 >>> 16) != 0) 269 | throw new AssertionError(); 270 | return crc16; 271 | } 272 | 273 | 274 | // Updates the two CRC values with data in byteBuffer[crcStartIndex : byteBufferIndex - unusedTrailingBytes]. 275 | private void updateCrcs(int unusedTrailingBytes) { 276 | int end = byteBufferIndex - unusedTrailingBytes; 277 | for (int i = crcStartIndex; i < end; i++) { 278 | int b = byteBuffer[i] & 0xFF; 279 | crc8 = CRC8_TABLE[crc8 ^ b] & 0xFF; 280 | crc16 = CRC16_TABLE[(crc16 >>> 8) ^ b] ^ ((crc16 & 0xFF) << 8); 281 | assert (crc8 >>> 8) == 0; 282 | assert (crc16 >>> 16) == 0; 283 | } 284 | crcStartIndex = end; 285 | } 286 | 287 | 288 | /*-- Miscellaneous --*/ 289 | 290 | // Note: This class only uses memory and has no native resources. It's not strictly necessary to 291 | // call the implementation of AbstractFlacLowLevelInput.close() here, but it's a good habit anyway. 292 | public void close() throws IOException { 293 | byteBuffer = null; 294 | byteBufferLen = -1; 295 | byteBufferIndex = -1; 296 | bitBuffer = 0; 297 | bitBufferLen = -1; 298 | crc8 = -1; 299 | crc16 = -1; 300 | crcStartIndex = -1; 301 | } 302 | 303 | 304 | 305 | /*---- Tables of constants ----*/ 306 | 307 | // For Rice decoding 308 | 309 | private static final int RICE_DECODING_TABLE_BITS = 13; // Configurable, must be positive 310 | private static final int RICE_DECODING_TABLE_MASK = (1 << RICE_DECODING_TABLE_BITS) - 1; 311 | private static final byte[][] RICE_DECODING_CONSUMED_TABLES = new byte[31][1 << RICE_DECODING_TABLE_BITS]; 312 | private static final int[][] RICE_DECODING_VALUE_TABLES = new int[31][1 << RICE_DECODING_TABLE_BITS]; 313 | private static final int RICE_DECODING_CHUNK = 4; // Configurable, must be positive, and RICE_DECODING_CHUNK * RICE_DECODING_TABLE_BITS <= 64 314 | 315 | static { 316 | for (int param = 0; param < RICE_DECODING_CONSUMED_TABLES.length; param++) { 317 | byte[] consumed = RICE_DECODING_CONSUMED_TABLES[param]; 318 | int[] values = RICE_DECODING_VALUE_TABLES[param]; 319 | for (int i = 0; ; i++) { 320 | int numBits = (i >>> param) + 1 + param; 321 | if (numBits > RICE_DECODING_TABLE_BITS) 322 | break; 323 | int bits = ((1 << param) | (i & ((1 << param) - 1))); 324 | int shift = RICE_DECODING_TABLE_BITS - numBits; 325 | for (int j = 0; j < (1 << shift); j++) { 326 | consumed[(bits << shift) | j] = (byte)numBits; 327 | values[(bits << shift) | j] = (i >>> 1) ^ -(i & 1); 328 | } 329 | } 330 | if (consumed[0] != 0) 331 | throw new AssertionError(); 332 | } 333 | } 334 | 335 | 336 | // For CRC calculations 337 | 338 | private static byte[] CRC8_TABLE = new byte[256]; 339 | private static char[] CRC16_TABLE = new char[256]; 340 | 341 | static { 342 | for (int i = 0; i < CRC8_TABLE.length; i++) { 343 | int temp8 = i; 344 | int temp16 = i << 8; 345 | for (int j = 0; j < 8; j++) { 346 | temp8 = (temp8 << 1) ^ ((temp8 >>> 7) * 0x107); 347 | temp16 = (temp16 << 1) ^ ((temp16 >>> 15) * 0x18005); 348 | } 349 | CRC8_TABLE[i] = (byte)temp8; 350 | CRC16_TABLE[i] = (char)temp16; 351 | } 352 | } 353 | 354 | } 355 | -------------------------------------------------------------------------------- /src/io/nayuki/flac/common/StreamInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * FLAC library (Java) 3 | * 4 | * Copyright (c) Project Nayuki 5 | * https://www.nayuki.io/page/flac-library-java 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with this program (see COPYING.txt and COPYING.LESSER.txt). 19 | * If not, see . 20 | */ 21 | 22 | package io.nayuki.flac.common; 23 | 24 | import java.io.IOException; 25 | import java.security.MessageDigest; 26 | import java.security.NoSuchAlgorithmException; 27 | import java.util.Objects; 28 | import io.nayuki.flac.decode.ByteArrayFlacInput; 29 | import io.nayuki.flac.decode.DataFormatException; 30 | import io.nayuki.flac.decode.FlacDecoder; 31 | import io.nayuki.flac.decode.FlacLowLevelInput; 32 | import io.nayuki.flac.encode.BitOutputStream; 33 | 34 | 35 | /** 36 | * Represents precisely all the fields of a stream info metadata block. Mutable structure, 37 | * not thread-safe. Also has methods for parsing and serializing this structure to/from bytes. 38 | * All fields can be modified freely when no method call is active. 39 | * @see FrameInfo 40 | * @see FlacDecoder 41 | */ 42 | public final class StreamInfo { 43 | 44 | /*---- Fields about block and frame sizes ----*/ 45 | 46 | /** 47 | * Minimum block size (in samples per channel) among the whole stream, a uint16 value. 48 | * However when minBlockSize = maxBlockSize (constant block size encoding style), 49 | * the final block is allowed to be smaller than minBlockSize. 50 | */ 51 | public int minBlockSize; 52 | 53 | /** 54 | * Maximum block size (in samples per channel) among the whole stream, a uint16 value. 55 | */ 56 | public int maxBlockSize; 57 | 58 | /** 59 | * Minimum frame size (in bytes) among the whole stream, a uint24 value. 60 | * However, a value of 0 signifies that the value is unknown. 61 | */ 62 | public int minFrameSize; 63 | 64 | /** 65 | * Maximum frame size (in bytes) among the whole stream, a uint24 value. 66 | * However, a value of 0 signifies that the value is unknown. 67 | */ 68 | public int maxFrameSize; 69 | 70 | 71 | /*---- Fields about stream properties ----*/ 72 | 73 | /** 74 | * The sample rate of the audio stream (in hertz (Hz)), a positive uint20 value. 75 | * Note that 0 is an invalid value. 76 | */ 77 | public int sampleRate; 78 | 79 | /** 80 | * The number of channels in the audio stream, between 1 and 8 inclusive. 81 | * 1 means mono, 2 means stereo, et cetera. 82 | */ 83 | public int numChannels; 84 | 85 | /** 86 | * The bits per sample in the audio stream, in the range 4 to 32 inclusive. 87 | */ 88 | public int sampleDepth; 89 | 90 | /** 91 | * The total number of samples per channel in the whole stream, a uint36 value. 92 | * The special value of 0 signifies that the value is unknown (not empty zero-length stream). 93 | */ 94 | public long numSamples; 95 | 96 | /** 97 | * The 16-byte MD5 hash of the raw uncompressed audio data serialized in little endian with 98 | * channel interleaving (not planar). It can be all zeros to signify that the hash was not computed. 99 | * It is okay to replace this array as needed (the initially constructed array object is not special). 100 | */ 101 | public byte[] md5Hash; 102 | 103 | 104 | 105 | /*---- Constructors ----*/ 106 | 107 | /** 108 | * Constructs a blank stream info structure with certain default values. 109 | */ 110 | public StreamInfo() { 111 | // Set these fields to legal unknown values 112 | minFrameSize = 0; 113 | maxFrameSize = 0; 114 | numSamples = 0; 115 | md5Hash = new byte[16]; 116 | 117 | // Set these fields to invalid (not reserved) values 118 | minBlockSize = 0; 119 | maxBlockSize = 0; 120 | sampleRate = 0; 121 | } 122 | 123 | 124 | /** 125 | * Constructs a stream info structure by parsing the specified 34-byte metadata block. 126 | * (The array must contain only the metadata payload, without the type or length fields.) 127 | * @param b the metadata block's payload data to parse (not {@code null}) 128 | * @throws NullPointerException if the array is {@code null} 129 | * @throws IllegalArgumentException if the array length is not 34 130 | * @throws DataFormatException if the data contains invalid values 131 | */ 132 | public StreamInfo(byte[] b) { 133 | Objects.requireNonNull(b); 134 | if (b.length != 34) 135 | throw new IllegalArgumentException("Invalid data length"); 136 | try { 137 | FlacLowLevelInput in = new ByteArrayFlacInput(b); 138 | minBlockSize = in.readUint(16); 139 | maxBlockSize = in.readUint(16); 140 | minFrameSize = in.readUint(24); 141 | maxFrameSize = in.readUint(24); 142 | if (minBlockSize < 16) 143 | throw new DataFormatException("Minimum block size less than 16"); 144 | if (maxBlockSize > 65535) 145 | throw new DataFormatException("Maximum block size greater than 65535"); 146 | if (maxBlockSize < minBlockSize) 147 | throw new DataFormatException("Maximum block size less than minimum block size"); 148 | if (minFrameSize != 0 && maxFrameSize != 0 && maxFrameSize < minFrameSize) 149 | throw new DataFormatException("Maximum frame size less than minimum frame size"); 150 | sampleRate = in.readUint(20); 151 | if (sampleRate == 0 || sampleRate > 655350) 152 | throw new DataFormatException("Invalid sample rate"); 153 | numChannels = in.readUint(3) + 1; 154 | sampleDepth = in.readUint(5) + 1; 155 | numSamples = (long)in.readUint(18) << 18 | in.readUint(18); // uint36 156 | md5Hash = new byte[16]; 157 | in.readFully(md5Hash); 158 | // Skip closing the in-memory stream 159 | } catch (IOException e) { 160 | throw new AssertionError(e); 161 | } 162 | } 163 | 164 | 165 | 166 | /*---- Methods ----*/ 167 | 168 | /** 169 | * Checks the state of this object, and either returns silently or throws an exception. 170 | * @throws NullPointerException if the MD5 hash array is {@code null} 171 | * @throws IllegalStateException if any field has an invalid value 172 | */ 173 | public void checkValues() { 174 | if ((minBlockSize >>> 16) != 0) 175 | throw new IllegalStateException("Invalid minimum block size"); 176 | if ((maxBlockSize >>> 16) != 0) 177 | throw new IllegalStateException("Invalid maximum block size"); 178 | if ((minFrameSize >>> 24) != 0) 179 | throw new IllegalStateException("Invalid minimum frame size"); 180 | if ((maxFrameSize >>> 24) != 0) 181 | throw new IllegalStateException("Invalid maximum frame size"); 182 | if (sampleRate == 0 || (sampleRate >>> 20) != 0) 183 | throw new IllegalStateException("Invalid sample rate"); 184 | if (numChannels < 1 || numChannels > 8) 185 | throw new IllegalStateException("Invalid number of channels"); 186 | if (sampleDepth < 4 || sampleDepth > 32) 187 | throw new IllegalStateException("Invalid sample depth"); 188 | if ((numSamples >>> 36) != 0) 189 | throw new IllegalStateException("Invalid number of samples"); 190 | Objects.requireNonNull(md5Hash); 191 | if (md5Hash.length != 16) 192 | throw new IllegalStateException("Invalid MD5 hash length"); 193 | } 194 | 195 | 196 | /** 197 | * Checks whether the specified frame information is consistent with values in 198 | * this stream info object, either returning silently or throwing an exception. 199 | * @param meta the frame info object to check (not {@code null}) 200 | * @throws NullPointerException if the frame info is {@code null} 201 | * @throws DataFormatException if the frame info contains bad values 202 | */ 203 | public void checkFrame(FrameInfo meta) { 204 | if (meta.numChannels != numChannels) 205 | throw new DataFormatException("Channel count mismatch"); 206 | if (meta.sampleRate != -1 && meta.sampleRate != sampleRate) 207 | throw new DataFormatException("Sample rate mismatch"); 208 | if (meta.sampleDepth != -1 && meta.sampleDepth != sampleDepth) 209 | throw new DataFormatException("Sample depth mismatch"); 210 | if (numSamples != 0 && meta.blockSize > numSamples) 211 | throw new DataFormatException("Block size exceeds total number of samples"); 212 | 213 | if (meta.blockSize > maxBlockSize) 214 | throw new DataFormatException("Block size exceeds maximum"); 215 | // Note: If minBlockSize == maxBlockSize, then the final block 216 | // in the stream is allowed to be smaller than minBlockSize 217 | 218 | if (minFrameSize != 0 && meta.frameSize < minFrameSize) 219 | throw new DataFormatException("Frame size less than minimum"); 220 | if (maxFrameSize != 0 && meta.frameSize > maxFrameSize) 221 | throw new DataFormatException("Frame size exceeds maximum"); 222 | } 223 | 224 | 225 | /** 226 | * Writes this stream info metadata block to the specified output stream, including the 227 | * metadata block header, writing exactly 38 bytes. (This is unlike the constructor, 228 | * which takes an array without the type and length fields.) The output stream must 229 | * initially be aligned to a byte boundary, and will finish at a byte boundary. 230 | * @param last whether the metadata block is the final one in the FLAC file 231 | * @param out the output stream to write to (not {@code null}) 232 | * @throws NullPointerException if the output stream is {@code null} 233 | * @throws IOException if an I/O exception occurred 234 | */ 235 | public void write(boolean last, BitOutputStream out) throws IOException { 236 | // Check arguments and state 237 | Objects.requireNonNull(out); 238 | checkValues(); 239 | 240 | // Write metadata block header 241 | out.writeInt(1, last ? 1 : 0); 242 | out.writeInt(7, 0); // Type 243 | out.writeInt(24, 34); // Length 244 | 245 | // Write stream info block fields 246 | out.writeInt(16, minBlockSize); 247 | out.writeInt(16, maxBlockSize); 248 | out.writeInt(24, minFrameSize); 249 | out.writeInt(24, maxFrameSize); 250 | out.writeInt(20, sampleRate); 251 | out.writeInt(3, numChannels - 1); 252 | out.writeInt(5, sampleDepth - 1); 253 | out.writeInt(18, (int)(numSamples >>> 18)); 254 | out.writeInt(18, (int)(numSamples >>> 0)); 255 | for (byte b : md5Hash) 256 | out.writeInt(8, b); 257 | } 258 | 259 | 260 | 261 | /*---- Static functions ----*/ 262 | 263 | /** 264 | * Computes and returns the MD5 hash of the specified raw audio sample data at the specified 265 | * bit depth. Currently, the bit depth must be a multiple of 8, between 8 and 32 inclusive. 266 | * The returned array is a new object of length 16. 267 | * @param samples the audio samples to hash, where 268 | * each subarray is a channel (all not {@code null}) 269 | * @param depth the bit depth of the audio samples 270 | * (i.e. each sample value is a signed 'depth'-bit integer) 271 | * @return a new 16-byte array representing the MD5 hash of the audio data 272 | * @throws NullPointerException if the array or any subarray is {@code null} 273 | * @throws IllegalArgumentException if the bit depth is unsupported 274 | */ 275 | public static byte[] getMd5Hash(int[][] samples, int depth) { 276 | // Check arguments 277 | Objects.requireNonNull(samples); 278 | for (int[] chanSamples : samples) 279 | Objects.requireNonNull(chanSamples); 280 | if (depth < 0 || depth > 32 || depth % 8 != 0) 281 | throw new IllegalArgumentException("Unsupported bit depth"); 282 | 283 | // Create hasher 284 | MessageDigest hasher; 285 | try { // Guaranteed available by the Java Cryptography Architecture 286 | hasher = MessageDigest.getInstance("MD5"); 287 | } catch (NoSuchAlgorithmException e) { 288 | throw new AssertionError(e); 289 | } 290 | 291 | // Convert samples to a stream of bytes, compute hash 292 | int numChannels = samples.length; 293 | int numSamples = samples[0].length; 294 | int numBytes = depth / 8; 295 | byte[] buf = new byte[numChannels * numBytes * Math.min(numSamples, 2048)]; 296 | for (int i = 0, l = 0; i < numSamples; i++) { 297 | for (int j = 0; j < numChannels; j++) { 298 | int val = samples[j][i]; 299 | for (int k = 0; k < numBytes; k++, l++) 300 | buf[l] = (byte)(val >>> (k << 3)); 301 | } 302 | if (l == buf.length || i == numSamples - 1) { 303 | hasher.update(buf, 0, l); 304 | l = 0; 305 | } 306 | } 307 | return hasher.digest(); 308 | } 309 | 310 | } 311 | -------------------------------------------------------------------------------- /src/io/nayuki/flac/common/FrameInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * FLAC library (Java) 3 | * 4 | * Copyright (c) Project Nayuki 5 | * https://www.nayuki.io/page/flac-library-java 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with this program (see COPYING.txt and COPYING.LESSER.txt). 19 | * If not, see . 20 | */ 21 | 22 | package io.nayuki.flac.common; 23 | 24 | import java.io.IOException; 25 | import java.util.Objects; 26 | import io.nayuki.flac.decode.DataFormatException; 27 | import io.nayuki.flac.decode.FlacLowLevelInput; 28 | import io.nayuki.flac.decode.FrameDecoder; 29 | import io.nayuki.flac.encode.BitOutputStream; 30 | 31 | 32 | /** 33 | * Represents most fields in a frame header, in decoded (not raw) form. Mutable structure, 34 | * not thread safe. Also has methods for parsing and serializing this structure to/from bytes. 35 | * All fields can be modified freely when no method call is active. 36 | * @see FrameDecoder 37 | * @see StreamInfo#checkFrame(FrameInfo) 38 | */ 39 | public final class FrameInfo { 40 | 41 | /*---- Fields ----*/ 42 | 43 | // Exactly one of these following two fields equals -1. 44 | 45 | /** 46 | * The index of this frame, where the foremost frame has index 0 and each subsequent frame 47 | * increments it. This is either a uint31 value or −1 if unused. Exactly one of the fields 48 | * frameIndex and sampleOffse is equal to −1 (not both nor neither). This value can only 49 | * be used if the stream info's minBlockSize = maxBlockSize (constant block size encoding style). 50 | */ 51 | public int frameIndex; 52 | 53 | /** 54 | * The offset of the first sample in this frame with respect to the beginning of the 55 | * audio stream. This is either a uint36 value or −1 if unused. Exactly one of 56 | * the fields frameIndex and sampleOffse is equal to −1 (not both nor neither). 57 | */ 58 | public long sampleOffset; 59 | 60 | 61 | /** 62 | * The number of audio channels in this frame, in the range 1 to 8 inclusive. 63 | * This value is fully determined by the channelAssignment field. 64 | */ 65 | public int numChannels; 66 | 67 | /** 68 | * The raw channel assignment value of this frame, which is a uint4 value. 69 | * This indicates the number of channels, but also tells the stereo coding mode. 70 | */ 71 | public int channelAssignment; 72 | 73 | /** 74 | * The number of samples per channel in this frame, in the range 1 to 65536 inclusive. 75 | */ 76 | public int blockSize; 77 | 78 | /** 79 | * The sample rate of this frame in hertz (Hz), in the range 1 to 655360 inclusive, 80 | * or −1 if unavailable (i.e. the stream info should be consulted). 81 | */ 82 | public int sampleRate; 83 | 84 | /** 85 | * The sample depth of this frame in bits, in the range 8 to 24 inclusive, 86 | * or −1 if unavailable (i.e. the stream info should be consulted). 87 | */ 88 | public int sampleDepth; 89 | 90 | /** 91 | * The size of this frame in bytes, from the start of the sync sequence to the end 92 | * of the trailing CRC-16 checksum. A valid value is at least 10, or −1 93 | * if unavailable (e.g. the frame header was parsed but not the entire frame). 94 | */ 95 | public int frameSize; 96 | 97 | 98 | 99 | /*---- Constructors ----*/ 100 | 101 | /** 102 | * Constructs a blank frame metadata structure, setting all fields to unknown or invalid values. 103 | */ 104 | public FrameInfo() { 105 | frameIndex = -1; 106 | sampleOffset = -1; 107 | numChannels = -1; 108 | channelAssignment = -1; 109 | blockSize = -1; 110 | sampleRate = -1; 111 | sampleDepth = -1; 112 | frameSize = -1; 113 | } 114 | 115 | 116 | 117 | /*---- Functions to read FrameInfo from stream ----*/ 118 | 119 | /** 120 | * Reads the next FLAC frame header from the specified input stream, either returning 121 | * a new frame info object or {@code null}. The stream must be aligned to a byte 122 | * boundary and start at a sync sequence. If EOF is immediately encountered before 123 | * any bytes were read, then this returns {@code null}. 124 | *

Otherwise this reads between 6 to 16 bytes from the stream – starting 125 | * from the sync code, and ending after the CRC-8 value is read (but before reading 126 | * any subframes). It tries to parse the frame header data. After the values are 127 | * successfully decoded, a new frame info object is created, almost all fields are 128 | * set to the parsed values, and it is returned. (This doesn't read to the end 129 | * of the frame, so the frameSize field is set to -1.)

130 | * @param in the input stream to read from (not {@code null}) 131 | * @return a new frame info object or {@code null} 132 | * @throws NullPointerException if the input stream is {@code null} 133 | * @throws DataFormatException if the input data contains invalid values 134 | * @throws IOException if an I/O exception occurred 135 | */ 136 | public static FrameInfo readFrame(FlacLowLevelInput in) throws IOException { 137 | // Preliminaries 138 | in.resetCrcs(); 139 | int temp = in.readByte(); 140 | if (temp == -1) 141 | return null; 142 | FrameInfo result = new FrameInfo(); 143 | result.frameSize = -1; 144 | 145 | // Read sync bits 146 | int sync = temp << 6 | in.readUint(6); // Uint14 147 | if (sync != 0x3FFE) 148 | throw new DataFormatException("Sync code expected"); 149 | 150 | // Read various simple fields 151 | if (in.readUint(1) != 0) 152 | throw new DataFormatException("Reserved bit"); 153 | int blockStrategy = in.readUint(1); 154 | int blockSizeCode = in.readUint(4); 155 | int sampleRateCode = in.readUint(4); 156 | int chanAsgn = in.readUint(4); 157 | result.channelAssignment = chanAsgn; 158 | if (chanAsgn < 8) 159 | result.numChannels = chanAsgn + 1; 160 | else if (8 <= chanAsgn && chanAsgn <= 10) 161 | result.numChannels = 2; 162 | else 163 | throw new DataFormatException("Reserved channel assignment"); 164 | result.sampleDepth = decodeSampleDepth(in.readUint(3)); 165 | if (in.readUint(1) != 0) 166 | throw new DataFormatException("Reserved bit"); 167 | 168 | // Read and check the frame/sample position field 169 | long position = readUtf8Integer(in); // Reads 1 to 7 bytes 170 | if (blockStrategy == 0) { 171 | if ((position >>> 31) != 0) 172 | throw new DataFormatException("Frame index too large"); 173 | result.frameIndex = (int)position; 174 | result.sampleOffset = -1; 175 | } else if (blockStrategy == 1) { 176 | result.sampleOffset = position; 177 | result.frameIndex = -1; 178 | } else 179 | throw new AssertionError(); 180 | 181 | // Read variable-length data for some fields 182 | result.blockSize = decodeBlockSize(blockSizeCode, in); // Reads 0 to 2 bytes 183 | result.sampleRate = decodeSampleRate(sampleRateCode, in); // Reads 0 to 2 bytes 184 | int computedCrc8 = in.getCrc8(); 185 | if (in.readUint(8) != computedCrc8) 186 | throw new DataFormatException("CRC-8 mismatch"); 187 | return result; 188 | } 189 | 190 | 191 | // Reads 1 to 7 whole bytes from the input stream. Return value is a uint36. 192 | // See: https://hydrogenaud.io/index.php/topic,112831.msg929128.html#msg929128 193 | private static long readUtf8Integer(FlacLowLevelInput in) throws IOException { 194 | int head = in.readUint(8); 195 | int n = Integer.numberOfLeadingZeros(~(head << 24)); // Number of leading 1s in the byte 196 | assert 0 <= n && n <= 8; 197 | if (n == 0) 198 | return head; 199 | else if (n == 1 || n == 8) 200 | throw new DataFormatException("Invalid UTF-8 coded number"); 201 | else { 202 | long result = head & (0x7F >>> n); 203 | for (int i = 0; i < n - 1; i++) { 204 | int temp = in.readUint(8); 205 | if ((temp & 0xC0) != 0x80) 206 | throw new DataFormatException("Invalid UTF-8 coded number"); 207 | result = (result << 6) | (temp & 0x3F); 208 | } 209 | if ((result >>> 36) != 0) 210 | throw new AssertionError(); 211 | return result; 212 | } 213 | } 214 | 215 | 216 | // Argument is a uint4 value. Reads 0 to 2 bytes from the input stream. 217 | // Return value is in the range [1, 65536]. 218 | private static int decodeBlockSize(int code, FlacLowLevelInput in) throws IOException { 219 | if ((code >>> 4) != 0) 220 | throw new IllegalArgumentException(); 221 | return switch (code) { 222 | case 0 -> throw new DataFormatException("Reserved block size"); 223 | case 6 -> in.readUint(8) + 1; 224 | case 7 -> in.readUint(16) + 1; 225 | default -> { 226 | int result = searchSecond(BLOCK_SIZE_CODES, code); 227 | if (result < 1 || result > 65536) 228 | throw new AssertionError(); 229 | yield result; 230 | } 231 | }; 232 | } 233 | 234 | 235 | // Argument is a uint4 value. Reads 0 to 2 bytes from the input stream. 236 | // Return value is in the range [-1, 655350]. 237 | private static int decodeSampleRate(int code, FlacLowLevelInput in) throws IOException { 238 | if ((code >>> 4) != 0) 239 | throw new IllegalArgumentException(); 240 | return switch (code) { 241 | case 0 -> -1; // Caller should obtain value from stream info metadata block 242 | case 12 -> in.readUint(8); 243 | case 13 -> in.readUint(16); 244 | case 14 -> in.readUint(16) * 10; 245 | case 15 -> throw new DataFormatException("Invalid sample rate"); 246 | default -> { 247 | int result = searchSecond(SAMPLE_RATE_CODES, code); 248 | if (result < 1 || result > 655350) 249 | throw new AssertionError(); 250 | yield result; 251 | } 252 | }; 253 | } 254 | 255 | 256 | // Argument is a uint3 value. Pure function and performs no I/O. Return value is in the range [-1, 24]. 257 | private static int decodeSampleDepth(int code) { 258 | if ((code >>> 3) != 0) 259 | throw new IllegalArgumentException(); 260 | else if (code == 0) 261 | return -1; // Caller should obtain value from stream info metadata block 262 | else { 263 | int result = searchSecond(SAMPLE_DEPTH_CODES, code); 264 | if (result == -1) 265 | throw new DataFormatException("Reserved bit depth"); 266 | if (result < 1 || result > 32) 267 | throw new AssertionError(); 268 | return result; 269 | } 270 | } 271 | 272 | 273 | 274 | /*---- Functions to write FrameInfo to stream ----*/ 275 | 276 | /** 277 | * Writes the current state of this object as a frame header to the specified 278 | * output stream, from the sync field through to the CRC-8 field (inclusive). 279 | * This does not write the data of subframes, the bit padding, nor the CRC-16 field.

280 | *

The stream must be byte-aligned before this method is called, and will be aligned 281 | * upon returning (i.e. it writes a whole number of bytes). This method initially resets 282 | * the stream's CRC computations, which is useful behavior for the caller because 283 | * it will need to write the CRC-16 at the end of the frame.

284 | * @param out the output stream to write to (not {@code null}) 285 | * @throws NullPointerException if the output stream is {@code null} 286 | * @throws IOException if an I/O exception occurred 287 | */ 288 | public void writeHeader(BitOutputStream out) throws IOException { 289 | Objects.requireNonNull(out); 290 | out.resetCrcs(); 291 | out.writeInt(14, 0x3FFE); // Sync 292 | out.writeInt(1, 0); // Reserved 293 | out.writeInt(1, 1); // Blocking strategy 294 | 295 | int blockSizeCode = getBlockSizeCode(blockSize); 296 | out.writeInt(4, blockSizeCode); 297 | int sampleRateCode = getSampleRateCode(sampleRate); 298 | out.writeInt(4, sampleRateCode); 299 | 300 | out.writeInt(4, channelAssignment); 301 | out.writeInt(3, getSampleDepthCode(sampleDepth)); 302 | out.writeInt(1, 0); // Reserved 303 | 304 | // Variable-length: 1 to 7 bytes 305 | if (frameIndex != -1 && sampleOffset == -1) 306 | writeUtf8Integer(sampleOffset, out); 307 | else if (sampleOffset != -1 && frameIndex == -1) 308 | writeUtf8Integer(sampleOffset, out); 309 | else 310 | throw new IllegalStateException(); 311 | 312 | // Variable-length: 0 to 2 bytes 313 | if (blockSizeCode == 6) 314 | out.writeInt(8, blockSize - 1); 315 | else if (blockSizeCode == 7) 316 | out.writeInt(16, blockSize - 1); 317 | 318 | // Variable-length: 0 to 2 bytes 319 | if (sampleRateCode == 12) 320 | out.writeInt(8, sampleRate); 321 | else if (sampleRateCode == 13) 322 | out.writeInt(16, sampleRate); 323 | else if (sampleRateCode == 14) 324 | out.writeInt(16, sampleRate / 10); 325 | 326 | out.writeInt(8, out.getCrc8()); 327 | } 328 | 329 | 330 | // Given a uint36 value, this writes 1 to 7 whole bytes to the given output stream. 331 | private static void writeUtf8Integer(long val, BitOutputStream out) throws IOException { 332 | if ((val >>> 36) != 0) 333 | throw new IllegalArgumentException(); 334 | int bitLen = 64 - Long.numberOfLeadingZeros(val); 335 | if (bitLen <= 7) 336 | out.writeInt(8, (int)val); 337 | else { 338 | int n = (bitLen - 2) / 5; 339 | out.writeInt(8, (0xFF80 >>> n) | (int)(val >>> (n * 6))); 340 | for (int i = n - 1; i >= 0; i--) 341 | out.writeInt(8, 0x80 | ((int)(val >>> (i * 6)) & 0x3F)); 342 | } 343 | } 344 | 345 | 346 | // Returns a uint4 value representing the given block size. Pure function. 347 | private static int getBlockSizeCode(int blockSize) { 348 | int result = searchFirst(BLOCK_SIZE_CODES, blockSize); 349 | if (result != -1); // Already done 350 | else if (1 <= blockSize && blockSize <= 256) 351 | result = 6; 352 | else if (1 <= blockSize && blockSize <= 65536) 353 | result = 7; 354 | else // blockSize < 1 || blockSize > 65536 355 | throw new IllegalArgumentException(); 356 | 357 | if ((result >>> 4) != 0) 358 | throw new AssertionError(); 359 | return result; 360 | } 361 | 362 | 363 | // Returns a uint4 value representing the given sample rate. Pure function. 364 | private static int getSampleRateCode(int sampleRate) { 365 | if (sampleRate == 0 || sampleRate < -1) 366 | throw new IllegalArgumentException(); 367 | int result = searchFirst(SAMPLE_RATE_CODES, sampleRate); 368 | if (result != -1); // Already done 369 | else if (0 <= sampleRate && sampleRate < 256) 370 | result = 12; 371 | else if (0 <= sampleRate && sampleRate < 65536) 372 | result = 13; 373 | else if (0 <= sampleRate && sampleRate < 655360 && sampleRate % 10 == 0) 374 | result = 14; 375 | else 376 | result = 0; 377 | 378 | if ((result >>> 4) != 0) 379 | throw new AssertionError(); 380 | return result; 381 | } 382 | 383 | 384 | // Returns a uint3 value representing the given sample depth. Pure function. 385 | private static int getSampleDepthCode(int sampleDepth) { 386 | if (sampleDepth != -1 && (sampleDepth < 1 || sampleDepth > 32)) 387 | throw new IllegalArgumentException(); 388 | int result = searchFirst(SAMPLE_DEPTH_CODES, sampleDepth); 389 | if (result == -1) 390 | result = 0; 391 | if ((result >>> 3) != 0) 392 | throw new AssertionError(); 393 | return result; 394 | } 395 | 396 | 397 | 398 | /*---- Tables of constants and search functions ----*/ 399 | 400 | private static final int searchFirst(int[][] table, int key) { 401 | for (int[] pair : table) { 402 | if (pair[0] == key) 403 | return pair[1]; 404 | } 405 | return -1; 406 | } 407 | 408 | 409 | private static final int searchSecond(int[][] table, int key) { 410 | for (int[] pair : table) { 411 | if (pair[1] == key) 412 | return pair[0]; 413 | } 414 | return -1; 415 | } 416 | 417 | 418 | private static final int[][] BLOCK_SIZE_CODES = { 419 | { 192, 1}, 420 | { 576, 2}, 421 | { 1152, 3}, 422 | { 2304, 4}, 423 | { 4608, 5}, 424 | { 256, 8}, 425 | { 512, 9}, 426 | { 1024, 10}, 427 | { 2048, 11}, 428 | { 4096, 12}, 429 | { 8192, 13}, 430 | {16384, 14}, 431 | {32768, 15}, 432 | }; 433 | 434 | 435 | private static final int[][] SAMPLE_DEPTH_CODES = { 436 | { 8, 1}, 437 | {12, 2}, 438 | {16, 4}, 439 | {20, 5}, 440 | {24, 6}, 441 | }; 442 | 443 | 444 | private static final int[][] SAMPLE_RATE_CODES = { 445 | { 88200, 1}, 446 | {176400, 2}, 447 | {192000, 3}, 448 | { 8000, 4}, 449 | { 16000, 5}, 450 | { 22050, 6}, 451 | { 24000, 7}, 452 | { 32000, 8}, 453 | { 44100, 9}, 454 | { 48000, 10}, 455 | { 96000, 11}, 456 | }; 457 | 458 | } 459 | -------------------------------------------------------------------------------- /src/io/nayuki/flac/decode/FrameDecoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * FLAC library (Java) 3 | * 4 | * Copyright (c) Project Nayuki 5 | * https://www.nayuki.io/page/flac-library-java 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with this program (see COPYING.txt and COPYING.LESSER.txt). 19 | * If not, see . 20 | */ 21 | 22 | package io.nayuki.flac.decode; 23 | 24 | import java.io.IOException; 25 | import java.util.Arrays; 26 | import java.util.Objects; 27 | import io.nayuki.flac.common.FrameInfo; 28 | 29 | 30 | /** 31 | * Decodes a FLAC frame from an input stream into raw audio samples. Note that these objects are 32 | * stateful and not thread-safe, due to the bit input stream field, private temporary arrays, etc. 33 | *

This class only uses memory and has no native resources; however, the 34 | * code that uses this class is responsible for cleaning up the input stream.

35 | * @see FlacDecoder 36 | * @see FlacLowLevelInput 37 | */ 38 | public final class FrameDecoder { 39 | 40 | /*---- Fields ----*/ 41 | 42 | // Can be changed when there is no active call of readFrame(). 43 | // Must be not null when readFrame() is called. 44 | public FlacLowLevelInput in; 45 | 46 | // Can be changed when there is no active call of readFrame(). 47 | // Must be in the range [4, 32]. 48 | public int expectedSampleDepth; 49 | 50 | // Temporary arrays to hold two decoded audio channels (a.k.a. subframes). They have int64 range 51 | // because the worst case of 32-bit audio encoded in stereo side mode uses signed 33 bits. 52 | // The maximum possible block size is either 65536 samples per channel from the 53 | // frame header logic, or 65535 from a strict reading of the FLAC specification. 54 | // Two buffers are needed for stereo coding modes, but not more than two because 55 | // all other multi-channel audio is processed independently per channel. 56 | private long[] temp0; 57 | private long[] temp1; 58 | 59 | // The number of samples (per channel) in the current block/frame being processed. 60 | // This value is only valid while the method readFrame() is on the call stack. 61 | // When readFrame() is active, this value is in the range [1, 65536]. 62 | private int currentBlockSize; 63 | 64 | 65 | 66 | /*---- Constructors ----*/ 67 | 68 | // Constructs a frame decoder that initially uses the given stream. 69 | // The caller is responsible for cleaning up the input stream. 70 | public FrameDecoder(FlacLowLevelInput in, int expectDepth) { 71 | this.in = in; 72 | expectedSampleDepth = expectDepth; 73 | temp0 = new long[65536]; 74 | temp1 = new long[65536]; 75 | currentBlockSize = -1; 76 | } 77 | 78 | 79 | 80 | /*---- Methods ----*/ 81 | 82 | // Reads the next frame of FLAC data from the current bit input stream, decodes it, 83 | // and stores output samples into the given array, and returns a new metadata object. 84 | // The bit input stream must be initially aligned at a byte boundary. If EOF is encountered before 85 | // any actual bytes were read, then this returns null. Otherwise this function either successfully 86 | // decodes a frame and returns a new metadata object, or throws an appropriate exception. A frame 87 | // may have up to 8 channels and 65536 samples, so the output arrays need to be sized appropriately. 88 | public FrameInfo readFrame(int[][] outSamples, int outOffset) throws IOException { 89 | // Check field states 90 | Objects.requireNonNull(in); 91 | if (currentBlockSize != -1) 92 | throw new IllegalStateException("Concurrent call"); 93 | 94 | // Parse the frame header to see if one is available 95 | long startByte = in.getPosition(); 96 | FrameInfo meta = FrameInfo.readFrame(in); 97 | if (meta == null) // EOF occurred cleanly 98 | return null; 99 | if (meta.sampleDepth != -1 && meta.sampleDepth != expectedSampleDepth) 100 | throw new DataFormatException("Sample depth mismatch"); 101 | 102 | // Check arguments and read frame header 103 | currentBlockSize = meta.blockSize; 104 | Objects.requireNonNull(outSamples); 105 | if (outOffset < 0 || outOffset > outSamples[0].length) 106 | throw new IndexOutOfBoundsException(); 107 | if (outSamples.length < meta.numChannels) 108 | throw new IllegalArgumentException("Output array too small for number of channels"); 109 | if (outOffset > outSamples[0].length - currentBlockSize) 110 | throw new IndexOutOfBoundsException(); 111 | 112 | // Do the hard work 113 | decodeSubframes(expectedSampleDepth, meta.channelAssignment, outSamples, outOffset); 114 | 115 | // Read padding and footer 116 | if (in.readUint((8 - in.getBitPosition()) % 8) != 0) 117 | throw new DataFormatException("Invalid padding bits"); 118 | int computedCrc16 = in.getCrc16(); 119 | if (in.readUint(16) != computedCrc16) 120 | throw new DataFormatException("CRC-16 mismatch"); 121 | 122 | // Handle frame size and miscellaneous 123 | long frameSize = in.getPosition() - startByte; 124 | if (frameSize < 10) 125 | throw new AssertionError(); 126 | if ((int)frameSize != frameSize) 127 | throw new DataFormatException("Frame size too large"); 128 | meta.frameSize = (int)frameSize; 129 | currentBlockSize = -1; 130 | return meta; 131 | } 132 | 133 | 134 | // Based on the current bit input stream and the two given arguments, this method reads and decodes 135 | // each subframe, performs stereo decoding if applicable, and writes the final uncompressed audio data 136 | // to the array range outSamples[0 : numChannels][outOffset : outOffset + currentBlockSize]. 137 | // Note that this method uses the private temporary arrays and passes them into sub-method calls. 138 | private void decodeSubframes(int sampleDepth, int chanAsgn, int[][] outSamples, int outOffset) throws IOException { 139 | // Check arguments 140 | if (sampleDepth < 1 || sampleDepth > 32) 141 | throw new IllegalArgumentException(); 142 | if ((chanAsgn >>> 4) != 0) 143 | throw new IllegalArgumentException(); 144 | 145 | if (0 <= chanAsgn && chanAsgn <= 7) { 146 | // Handle 1 to 8 independently coded channels 147 | int numChannels = chanAsgn + 1; 148 | for (int ch = 0; ch < numChannels; ch++) { 149 | decodeSubframe(sampleDepth, temp0); 150 | int[] outChan = outSamples[ch]; 151 | for (int i = 0; i < currentBlockSize; i++) 152 | outChan[outOffset + i] = checkBitDepth(temp0[i], sampleDepth); 153 | } 154 | 155 | } else if (8 <= chanAsgn && chanAsgn <= 10) { 156 | // Handle one of the side-coded stereo methods 157 | decodeSubframe(sampleDepth + (chanAsgn == 9 ? 1 : 0), temp0); 158 | decodeSubframe(sampleDepth + (chanAsgn == 9 ? 0 : 1), temp1); 159 | 160 | if (chanAsgn == 8) { // Left-side stereo 161 | for (int i = 0; i < currentBlockSize; i++) 162 | temp1[i] = temp0[i] - temp1[i]; 163 | } else if (chanAsgn == 9) { // Side-right stereo 164 | for (int i = 0; i < currentBlockSize; i++) 165 | temp0[i] += temp1[i]; 166 | } else if (chanAsgn == 10) { // Mid-side stereo 167 | for (int i = 0; i < currentBlockSize; i++) { 168 | long side = temp1[i]; 169 | long right = temp0[i] - (side >> 1); 170 | temp1[i] = right; 171 | temp0[i] = right + side; 172 | } 173 | } else 174 | throw new AssertionError(); 175 | 176 | // Copy data from temporary to output arrays, and convert from long to int 177 | int[] outLeft = outSamples[0]; 178 | int[] outRight = outSamples[1]; 179 | for (int i = 0; i < currentBlockSize; i++) { 180 | outLeft [outOffset + i] = checkBitDepth(temp0[i], sampleDepth); 181 | outRight[outOffset + i] = checkBitDepth(temp1[i], sampleDepth); 182 | } 183 | } else // 11 <= channelAssignment <= 15 184 | throw new DataFormatException("Reserved channel assignment"); 185 | } 186 | 187 | 188 | // Checks that 'val' is a signed 'depth'-bit integer, and either returns the 189 | // value downcasted to an int or throws an exception if it's out of range. 190 | // Note that depth must be in the range [1, 32] because the return value is an int. 191 | // For example when depth = 16, the range of valid values is [-32768, 32767]. 192 | private static int checkBitDepth(long val, int depth) { 193 | assert 1 <= depth && depth <= 32; 194 | // Equivalent check: (val >> (depth - 1)) == 0 || (val >> (depth - 1)) == -1 195 | if (val >> (depth - 1) == val >> depth) 196 | return (int)val; 197 | else 198 | throw new IllegalArgumentException(val + " is not a signed " + depth + "-bit value"); 199 | } 200 | 201 | 202 | // Reads one subframe from the bit input stream, decodes it, and writes to result[0 : currentBlockSize]. 203 | private void decodeSubframe(int sampleDepth, long[] result) throws IOException { 204 | // Check arguments 205 | Objects.requireNonNull(result); 206 | if (sampleDepth < 1 || sampleDepth > 33) 207 | throw new IllegalArgumentException(); 208 | if (result.length < currentBlockSize) 209 | throw new IllegalArgumentException(); 210 | 211 | // Read header fields 212 | if (in.readUint(1) != 0) 213 | throw new DataFormatException("Invalid padding bit"); 214 | int type = in.readUint(6); 215 | int shift = in.readUint(1); // Also known as "wasted bits-per-sample" 216 | if (shift == 1) { 217 | while (in.readUint(1) == 0) { // Unary coding 218 | if (shift >= sampleDepth) 219 | throw new DataFormatException("Waste-bits-per-sample exceeds sample depth"); 220 | shift++; 221 | } 222 | } 223 | if (!(0 <= shift && shift <= sampleDepth)) 224 | throw new AssertionError(); 225 | sampleDepth -= shift; 226 | 227 | // Read sample data based on type 228 | if (type == 0) // Constant coding 229 | Arrays.fill(result, 0, currentBlockSize, in.readSignedInt(sampleDepth)); 230 | else if (type == 1) { // Verbatim coding 231 | for (int i = 0; i < currentBlockSize; i++) 232 | result[i] = in.readSignedInt(sampleDepth); 233 | } else if (8 <= type && type <= 12) 234 | decodeFixedPredictionSubframe(type - 8, sampleDepth, result); 235 | else if (32 <= type && type <= 63) 236 | decodeLinearPredictiveCodingSubframe(type - 31, sampleDepth, result); 237 | else 238 | throw new DataFormatException("Reserved subframe type"); 239 | 240 | // Add trailing zeros to each sample 241 | if (shift > 0) { 242 | for (int i = 0; i < currentBlockSize; i++) 243 | result[i] <<= shift; 244 | } 245 | } 246 | 247 | 248 | // Reads from the input stream, performs computation, and writes to result[0 : currentBlockSize]. 249 | private void decodeFixedPredictionSubframe(int predOrder, int sampleDepth, long[] result) throws IOException { 250 | // Check arguments 251 | Objects.requireNonNull(result); 252 | if (sampleDepth < 1 || sampleDepth > 33) 253 | throw new IllegalArgumentException(); 254 | if (predOrder < 0 || predOrder >= FIXED_PREDICTION_COEFFICIENTS.length) 255 | throw new IllegalArgumentException(); 256 | if (predOrder > currentBlockSize) 257 | throw new DataFormatException("Fixed prediction order exceeds block size"); 258 | if (result.length < currentBlockSize) 259 | throw new IllegalArgumentException(); 260 | 261 | // Read and compute various values 262 | for (int i = 0; i < predOrder; i++) // Non-Rice-coded warm-up samples 263 | result[i] = in.readSignedInt(sampleDepth); 264 | readResiduals(predOrder, result); 265 | restoreLpc(result, FIXED_PREDICTION_COEFFICIENTS[predOrder], sampleDepth, 0); 266 | } 267 | 268 | private static final int[][] FIXED_PREDICTION_COEFFICIENTS = { 269 | {}, 270 | {1}, 271 | {2, -1}, 272 | {3, -3, 1}, 273 | {4, -6, 4, -1}, 274 | }; 275 | 276 | 277 | // Reads from the input stream, performs computation, and writes to result[0 : currentBlockSize]. 278 | private void decodeLinearPredictiveCodingSubframe(int lpcOrder, int sampleDepth, long[] result) throws IOException { 279 | // Check arguments 280 | Objects.requireNonNull(result); 281 | if (sampleDepth < 1 || sampleDepth > 33) 282 | throw new IllegalArgumentException(); 283 | if (lpcOrder < 1 || lpcOrder > 32) 284 | throw new IllegalArgumentException(); 285 | if (lpcOrder > currentBlockSize) 286 | throw new DataFormatException("LPC order exceeds block size"); 287 | if (result.length < currentBlockSize) 288 | throw new IllegalArgumentException(); 289 | 290 | // Read non-Rice-coded warm-up samples 291 | for (int i = 0; i < lpcOrder; i++) 292 | result[i] = in.readSignedInt(sampleDepth); 293 | 294 | // Read parameters for the LPC coefficients 295 | int precision = in.readUint(4) + 1; 296 | if (precision == 16) 297 | throw new DataFormatException("Invalid LPC precision"); 298 | int shift = in.readSignedInt(5); 299 | if (shift < 0) 300 | throw new DataFormatException("Invalid LPC shift"); 301 | 302 | // Read the coefficients themselves 303 | int[] coefs = new int[lpcOrder]; 304 | for (int i = 0; i < coefs.length; i++) 305 | coefs[i] = in.readSignedInt(precision); 306 | 307 | // Perform the main LPC decoding 308 | readResiduals(lpcOrder, result); 309 | restoreLpc(result, coefs, sampleDepth, shift); 310 | } 311 | 312 | 313 | // Updates the values of result[coefs.length : currentBlockSize] according to linear predictive coding. 314 | // This method reads all the arguments and the field currentBlockSize, only writes to result, and has no other side effects. 315 | // After this method returns, every value in result must fit in a signed sampleDepth-bit integer. 316 | // The largest allowed sample depth is 33, hence the largest absolute value allowed in the result is 2^32. 317 | // During the LPC restoration process, the prefix of result before index i consists of entirely int33 values. 318 | // Because coefs.length <= 32 and each coefficient fits in a signed int15 (both according to the FLAC specification), 319 | // the maximum (worst-case) absolute value of 'sum' is 2^32 * 2^14 * 32 = 2^51, which fits in a signed int53. 320 | // And because of this, the maximum possible absolute value of a residual before LPC restoration is applied, 321 | // such that the post-LPC result fits in a signed int33, is 2^51 + 2^32 which also fits in a signed int53. 322 | // Therefore a residue that is larger than a signed int53 will necessarily not fit in the int33 result and is wrong. 323 | private void restoreLpc(long[] result, int[] coefs, int sampleDepth, int shift) { 324 | // Check and handle arguments 325 | Objects.requireNonNull(result); 326 | Objects.requireNonNull(coefs); 327 | if (result.length < currentBlockSize) 328 | throw new IllegalArgumentException(); 329 | if (sampleDepth < 1 || sampleDepth > 33) 330 | throw new IllegalArgumentException(); 331 | if (shift < 0 || shift > 63) 332 | throw new IllegalArgumentException(); 333 | long lowerBound = (-1) << (sampleDepth - 1); 334 | long upperBound = -(lowerBound + 1); 335 | 336 | for (int i = coefs.length; i < currentBlockSize; i++) { 337 | long sum = 0; 338 | for (int j = 0; j < coefs.length; j++) 339 | sum += result[i - 1 - j] * coefs[j]; 340 | assert (sum >> 53) == 0 || (sum >> 53) == -1; // Fits in signed int54 341 | sum = result[i] + (sum >> shift); 342 | // Check that sum fits in a sampleDepth-bit signed integer, 343 | // i.e. -(2^(sampleDepth-1)) <= sum < 2^(sampleDepth-1) 344 | if (sum < lowerBound || sum > upperBound) 345 | throw new DataFormatException("Post-LPC result exceeds bit depth"); 346 | result[i] = sum; 347 | } 348 | } 349 | 350 | 351 | // Reads metadata and Rice-coded numbers from the input stream, storing them in result[warmup : currentBlockSize]. 352 | // The stored numbers are guaranteed to fit in a signed int53 - see the explanation in restoreLpc(). 353 | private void readResiduals(int warmup, long[] result) throws IOException { 354 | // Check and handle arguments 355 | Objects.requireNonNull(result); 356 | if (warmup < 0 || warmup > currentBlockSize) 357 | throw new IllegalArgumentException(); 358 | if (result.length < currentBlockSize) 359 | throw new IllegalArgumentException(); 360 | 361 | int method = in.readUint(2); 362 | if (method >= 2) 363 | throw new DataFormatException("Reserved residual coding method"); 364 | assert method == 0 || method == 1; 365 | int paramBits = method == 0 ? 4 : 5; 366 | int escapeParam = method == 0 ? 0xF : 0x1F; 367 | 368 | int partitionOrder = in.readUint(4); 369 | int numPartitions = 1 << partitionOrder; 370 | if (currentBlockSize % numPartitions != 0) 371 | throw new DataFormatException("Block size not divisible by number of Rice partitions"); 372 | for (int inc = currentBlockSize >>> partitionOrder, partEnd = inc, resultIndex = warmup; 373 | partEnd <= currentBlockSize; partEnd += inc) { 374 | 375 | int param = in.readUint(paramBits); 376 | if (param == escapeParam) { 377 | int numBits = in.readUint(5); 378 | for (; resultIndex < partEnd; resultIndex++) 379 | result[resultIndex] = in.readSignedInt(numBits); 380 | } else { 381 | in.readRiceSignedInts(param, result, resultIndex, partEnd); 382 | resultIndex = partEnd; 383 | } 384 | } 385 | } 386 | 387 | } 388 | --------------------------------------------------------------------------------