Files
AllMusic/core/decoder/flac/SeekTable.java
T

159 lines
6.1 KiB
Java
Raw Normal View History

2025-06-23 13:15:56 +08:00
/*
* 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.ByteArrayInputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* Represents precisely all the fields of a seek table metadata block. Mutable structure,
* not thread-safe. Also has methods for parsing and serializing this structure to/from bytes.
* All fields and objects can be modified freely when no method call is active.
*
* @see FlacDecoder
*/
public final class SeekTable {
/*---- Fields ----*/
/**
* The list of seek points in this seek table. It is okay to replace this
* list as needed (the initially constructed list object is not special).
*/
public List<SeekPoint> points;
/*---- Constructors ----*/
/**
* Constructs a blank seek table with an initially empty
* list of seek points. (Note that the empty state is legal.)
*/
public SeekTable() {
points = new ArrayList<>();
}
/**
* Constructs a seek table by parsing the given byte array representing the metadata block.
* (The array must contain only the metadata payload, without the type or length fields.)
* <p>
* This constructor does not check the validity of the seek points, namely the ordering
* of seek point offsets, so calling {@link#checkValues()} on the freshly constructed object
* can fail. However, this does guarantee that every point's frameSamples field is a uint16.
* </p>
*
* @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 a multiple of 18 (size of each seek point)
*/
public SeekTable(byte[] b) {
this();
Objects.requireNonNull(b);
if (b.length % 18 != 0) throw new IllegalArgumentException("Data contains a partial seek point");
try {
DataInput in = new DataInputStream(new ByteArrayInputStream(b));
for (int i = 0; i < b.length; i += 18) {
SeekPoint p = new SeekPoint();
p.sampleOffset = in.readLong();
p.fileOffset = in.readLong();
p.frameSamples = in.readUnsignedShort();
points.add(p);
}
// Skip closing the in-memory streams
} catch (IOException e) {
throw new AssertionError(e);
}
}
/*---- Methods ----*/
/**
* Checks the state of this object and returns silently if all these criteria pass:
* <ul>
* <li>No object is {@code null}</li>
* <li>The frameSamples field of each point is a uint16 value</li>
* <li>All points with sampleOffset = &minus;1 (i.e. 0xFFF...FFF) are at the end of the list</li>
* <li>All points with sampleOffset &ne; &minus;1 have strictly increasing
* values of sampleOffset and non-decreasing values of fileOffset</li>
* </ul>
*
* @throws NullPointerException if the list or an element is {@code null}
* @throws IllegalStateException if the current list of seek points is contains invalid data
*/
public void checkValues() {
// Check list and each point
Objects.requireNonNull(points);
for (SeekPoint p : points) {
Objects.requireNonNull(p);
if ((p.frameSamples & 0xFFFF) != p.frameSamples)
throw new IllegalStateException("Frame samples outside uint16 range");
}
// Check ordering of points
for (int i = 1; i < points.size(); i++) {
SeekPoint p = points.get(i);
if (p.sampleOffset != -1) {
SeekPoint q = points.get(i - 1);
if (p.sampleOffset <= q.sampleOffset) throw new IllegalStateException("Sample offsets out of order");
if (p.fileOffset < q.fileOffset) throw new IllegalStateException("File offsets out of order");
}
}
}
/*---- Helper structure ----*/
/**
* Represents a seek point entry in a seek table. Mutable structure, not thread-safe.
* This class itself does not check the correctness of data, but other classes might.
* <p>
* A seek point with data (sampleOffset = x, fileOffset = y, frameSamples = z) means
* that at byte position (y + (byte offset of foremost audio frame)) in the file,
* a FLAC frame begins (with the sync sequence), that frame has sample offset x
* (where sample 0 is defined as the start of the audio stream),
* and the frame contains z samples per channel.
*
* @see SeekTable
*/
public static final class SeekPoint {
/**
* The sample offset in the audio stream, a uint64 value.
* A value of -1 (i.e. 0xFFF...FFF) means this is a placeholder point.
*/
public long sampleOffset;
/**
* The byte offset relative to the start of the foremost frame, a uint64 value.
* If sampleOffset is -1, then this value is ignored.
*/
public long fileOffset;
/**
* The number of audio samples in the target block/frame, a uint16 value.
* If sampleOffset is -1, then this value is ignored.
*/
public int frameSamples;
}
}