├── 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 |
--------------------------------------------------------------------------------