mirror of
https://github.com/Coloryr/AllMusic.git
synced 2026-11-13 01:57:38 +00:00
243 lines
10 KiB
Java
243 lines
10 KiB
Java
/*
|
|||
|
|
* FLAC library (Java)
|
||
|
|
* Copyright (c) Project Nayuki
|
||
|
|
* https://www.nayuki.io/page/flac-library-java
|
||
|
|
* This program is free software: you can redistribute it and/or modify
|
||
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
||
|
|
* the Free Software Foundation, either version 3 of the License, or
|
||
|
|
* (at your option) any later version.
|
||
|
|
* This program is distributed in the hope that it will be useful,
|
||
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
|
* GNU Lesser General Public License for more details.
|
||
|
|
* You should have received a copy of the GNU Lesser General Public License
|
||
|
|
* along with this program (see COPYING.txt and COPYING.LESSER.txt).
|
||
|
|
* If not, see <http://www.gnu.org/licenses/>.
|
||
|
|
*/
|
||
|
|
|
||
|
|
package com.coloryr.allmusic.server.core.decoder.flac;
|
||
|
|
|
||
|
|
import java.io.IOException;
|
||
|
|
import java.security.MessageDigest;
|
||
|
|
import java.security.NoSuchAlgorithmException;
|
||
|
|
import java.util.Objects;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Represents precisely all the fields of a stream info metadata block. Mutable structure,
|
||
|
|
* not thread-safe. Also has methods for parsing and serializing this structure to/from bytes.
|
||
|
|
* All fields can be modified freely when no method call is active.
|
||
|
|
*
|
||
|
|
* @see FrameInfo
|
||
|
|
* @see FlacDecoder
|
||
|
|
*/
|
||
|
|
public final class StreamInfo {
|
||
|
|
|
||
|
|
/*---- Fields about block and frame sizes ----*/
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Minimum block size (in samples per channel) among the whole stream, a uint16 value.
|
||
|
|
* However when minBlockSize = maxBlockSize (constant block size encoding style),
|
||
|
|
* the final block is allowed to be smaller than minBlockSize.
|
||
|
|
*/
|
||
|
|
public int minBlockSize;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Maximum block size (in samples per channel) among the whole stream, a uint16 value.
|
||
|
|
*/
|
||
|
|
public int maxBlockSize;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Minimum frame size (in bytes) among the whole stream, a uint24 value.
|
||
|
|
* However, a value of 0 signifies that the value is unknown.
|
||
|
|
*/
|
||
|
|
public int minFrameSize;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Maximum frame size (in bytes) among the whole stream, a uint24 value.
|
||
|
|
* However, a value of 0 signifies that the value is unknown.
|
||
|
|
*/
|
||
|
|
public int maxFrameSize;
|
||
|
|
|
||
|
|
/*---- Fields about stream properties ----*/
|
||
|
|
|
||
|
|
/**
|
||
|
|
* The sample rate of the audio stream (in hertz (Hz)), a positive uint20 value.
|
||
|
|
* Note that 0 is an invalid value.
|
||
|
|
*/
|
||
|
|
public int sampleRate;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* The number of channels in the audio stream, between 1 and 8 inclusive.
|
||
|
|
* 1 means mono, 2 means stereo, et cetera.
|
||
|
|
*/
|
||
|
|
public int numChannels;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* The bits per sample in the audio stream, in the range 4 to 32 inclusive.
|
||
|
|
*/
|
||
|
|
public int sampleDepth;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* The total number of samples per channel in the whole stream, a uint36 value.
|
||
|
|
* The special value of 0 signifies that the value is unknown (not empty zero-length stream).
|
||
|
|
*/
|
||
|
|
public long numSamples;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* The 16-byte MD5 hash of the raw uncompressed audio data serialized in little endian with
|
||
|
|
* channel interleaving (not planar). It can be all zeros to signify that the hash was not computed.
|
||
|
|
* It is okay to replace this array as needed (the initially constructed array object is not special).
|
||
|
|
*/
|
||
|
|
public byte[] md5Hash;
|
||
|
|
|
||
|
|
/*---- Constructors ----*/
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Constructs a blank stream info structure with certain default values.
|
||
|
|
*/
|
||
|
|
public StreamInfo() {
|
||
|
|
// Set these fields to legal unknown values
|
||
|
|
minFrameSize = 0;
|
||
|
|
maxFrameSize = 0;
|
||
|
|
numSamples = 0;
|
||
|
|
md5Hash = new byte[16];
|
||
|
|
|
||
|
|
// Set these fields to invalid (not reserved) values
|
||
|
|
minBlockSize = 0;
|
||
|
|
maxBlockSize = 0;
|
||
|
|
sampleRate = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Constructs a stream info structure by parsing the specified 34-byte metadata block.
|
||
|
|
* (The array must contain only the metadata payload, without the type or length fields.)
|
||
|
|
*
|
||
|
|
* @param b the metadata block's payload data to parse (not {@code null})
|
||
|
|
* @throws NullPointerException if the array is {@code null}
|
||
|
|
* @throws IllegalArgumentException if the array length is not 34
|
||
|
|
* @throws DataFormatException if the data contains invalid values
|
||
|
|
*/
|
||
|
|
public StreamInfo(byte[] b) {
|
||
|
|
Objects.requireNonNull(b);
|
||
|
|
if (b.length != 34) throw new IllegalArgumentException("Invalid data length");
|
||
|
|
try {
|
||
|
|
FlacLowLevelInput in = new ByteArrayFlacInput(b);
|
||
|
|
minBlockSize = in.readUint(16);
|
||
|
|
maxBlockSize = in.readUint(16);
|
||
|
|
minFrameSize = in.readUint(24);
|
||
|
|
maxFrameSize = in.readUint(24);
|
||
|
|
if (minBlockSize < 16) throw new DataFormatException("Minimum block size less than 16");
|
||
|
|
if (maxBlockSize > 65535) throw new DataFormatException("Maximum block size greater than 65535");
|
||
|
|
if (maxBlockSize < minBlockSize)
|
||
|
|
throw new DataFormatException("Maximum block size less than minimum block size");
|
||
|
|
if (minFrameSize != 0 && maxFrameSize != 0 && maxFrameSize < minFrameSize)
|
||
|
|
throw new DataFormatException("Maximum frame size less than minimum frame size");
|
||
|
|
sampleRate = in.readUint(20);
|
||
|
|
if (sampleRate == 0 || sampleRate > 655350) throw new DataFormatException("Invalid sample rate");
|
||
|
|
numChannels = in.readUint(3) + 1;
|
||
|
|
sampleDepth = in.readUint(5) + 1;
|
||
|
|
numSamples = (long) in.readUint(18) << 18 | in.readUint(18); // uint36
|
||
|
|
md5Hash = new byte[16];
|
||
|
|
in.readFully(md5Hash);
|
||
|
|
// Skip closing the in-memory stream
|
||
|
|
} catch (IOException e) {
|
||
|
|
throw new AssertionError(e);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/*---- Methods ----*/
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Computes and returns the MD5 hash of the specified raw audio sample data at the specified
|
||
|
|
* bit depth. Currently, the bit depth must be a multiple of 8, between 8 and 32 inclusive.
|
||
|
|
* The returned array is a new object of length 16.
|
||
|
|
*
|
||
|
|
* @param samples the audio samples to hash, where
|
||
|
|
* each subarray is a channel (all not {@code null})
|
||
|
|
* @param depth the bit depth of the audio samples
|
||
|
|
* (i.e. each sample value is a signed 'depth'-bit integer)
|
||
|
|
* @return a new 16-byte array representing the MD5 hash of the audio data
|
||
|
|
* @throws NullPointerException if the array or any subarray is {@code null}
|
||
|
|
* @throws IllegalArgumentException if the bit depth is unsupported
|
||
|
|
*/
|
||
|
|
public static byte[] getMd5Hash(int[][] samples, int depth) {
|
||
|
|
// Check arguments
|
||
|
|
Objects.requireNonNull(samples);
|
||
|
|
for (int[] chanSamples : samples) Objects.requireNonNull(chanSamples);
|
||
|
|
if (depth < 0 || depth > 32 || depth % 8 != 0) throw new IllegalArgumentException("Unsupported bit depth");
|
||
|
|
|
||
|
|
// Create hasher
|
||
|
|
MessageDigest hasher;
|
||
|
|
try { // Guaranteed available by the Java Cryptography Architecture
|
||
|
|
hasher = MessageDigest.getInstance("MD5");
|
||
|
|
} catch (NoSuchAlgorithmException e) {
|
||
|
|
throw new AssertionError(e);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Convert samples to a stream of bytes, compute hash
|
||
|
|
int numChannels = samples.length;
|
||
|
|
int numSamples = samples[0].length;
|
||
|
|
int numBytes = depth / 8;
|
||
|
|
byte[] buf = new byte[numChannels * numBytes * Math.min(numSamples, 2048)];
|
||
|
|
for (int i = 0, l = 0; i < numSamples; i++) {
|
||
|
|
for (int j = 0; j < numChannels; j++) {
|
||
|
|
int val = samples[j][i];
|
||
|
|
for (int k = 0; k < numBytes; k++, l++) buf[l] = (byte) (val >>> (k << 3));
|
||
|
|
}
|
||
|
|
if (l == buf.length || i == numSamples - 1) {
|
||
|
|
hasher.update(buf, 0, l);
|
||
|
|
l = 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return hasher.digest();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Checks the state of this object, and either returns silently or throws an exception.
|
||
|
|
*
|
||
|
|
* @throws NullPointerException if the MD5 hash array is {@code null}
|
||
|
|
* @throws IllegalStateException if any field has an invalid value
|
||
|
|
*/
|
||
|
|
public void checkValues() {
|
||
|
|
if ((minBlockSize >>> 16) != 0) throw new IllegalStateException("Invalid minimum block size");
|
||
|
|
if ((maxBlockSize >>> 16) != 0) throw new IllegalStateException("Invalid maximum block size");
|
||
|
|
if ((minFrameSize >>> 24) != 0) throw new IllegalStateException("Invalid minimum frame size");
|
||
|
|
if ((maxFrameSize >>> 24) != 0) throw new IllegalStateException("Invalid maximum frame size");
|
||
|
|
if (sampleRate == 0 || (sampleRate >>> 20) != 0) throw new IllegalStateException("Invalid sample rate");
|
||
|
|
if (numChannels < 1 || numChannels > 8) throw new IllegalStateException("Invalid number of channels");
|
||
|
|
if (sampleDepth < 4 || sampleDepth > 32) throw new IllegalStateException("Invalid sample depth");
|
||
|
|
if ((numSamples >>> 36) != 0) throw new IllegalStateException("Invalid number of samples");
|
||
|
|
Objects.requireNonNull(md5Hash);
|
||
|
|
if (md5Hash.length != 16) throw new IllegalStateException("Invalid MD5 hash length");
|
||
|
|
}
|
||
|
|
|
||
|
|
/*---- Static functions ----*/
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Checks whether the specified frame information is consistent with values in
|
||
|
|
* this stream info object, either returning silently or throwing an exception.
|
||
|
|
*
|
||
|
|
* @param meta the frame info object to check (not {@code null})
|
||
|
|
* @throws NullPointerException if the frame info is {@code null}
|
||
|
|
* @throws DataFormatException if the frame info contains bad values
|
||
|
|
*/
|
||
|
|
public void checkFrame(FrameInfo meta) {
|
||
|
|
if (meta.numChannels != numChannels) throw new DataFormatException("Channel count mismatch");
|
||
|
|
if (meta.sampleRate != -1 && meta.sampleRate != sampleRate)
|
||
|
|
throw new DataFormatException("Sample rate mismatch");
|
||
|
|
if (meta.sampleDepth != -1 && meta.sampleDepth != sampleDepth)
|
||
|
|
throw new DataFormatException("Sample depth mismatch");
|
||
|
|
if (numSamples != 0 && meta.blockSize > numSamples)
|
||
|
|
throw new DataFormatException("Block size exceeds total number of samples");
|
||
|
|
|
||
|
|
if (meta.blockSize > maxBlockSize) throw new DataFormatException("Block size exceeds maximum");
|
||
|
|
// Note: If minBlockSize == maxBlockSize, then the final block
|
||
|
|
// in the stream is allowed to be smaller than minBlockSize
|
||
|
|
|
||
|
|
if (minFrameSize != 0 && meta.frameSize < minFrameSize)
|
||
|
|
throw new DataFormatException("Frame size less than minimum");
|
||
|
|
if (maxFrameSize != 0 && meta.frameSize > maxFrameSize)
|
||
|
|
throw new DataFormatException("Frame size exceeds maximum");
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|