// Copyright 2014-2025 Jesper Larsson
//
// This file is part of Klipspringer, <https://klipspringer.avadeaux.net/>
//
// Klipspringer is free software: you can redistribute it and/or modify it under the terms of the
// GNU General Public License as published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// Klipspringer 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
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with Klipspringer. If
// not, see <https://www.gnu.org/licenses/>.
package net.avadeaux.klipspringer.codec;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
/** Common base class for decoders that invoke native libraries. */
public abstract class ExternalDecoder implements AudioDecoder {
static { Library.init(); }
private enum State { NEW, INITIALIZED, METADATA_DECODED, SEEK, SEEK_ABORT, SEEK_OK, AUDIO_DECODED, CLOSED }
private final Target target;
private final long handle;
private State state = State.NEW;
/** Called from constructor to throw runtime exception if file type not supported. */
abstract void checkSupported();
/** Initiates decoder, including opening the file. */
ExternalDecoder(String fileName, Target target) throws IOException {
checkSupported();
this.target = target;
byte[] fnamUtf8 = fileName.getBytes(StandardCharsets.UTF_8);
try {
handle = create(fileName, Arrays.copyOf(fnamUtf8, fnamUtf8.length+1));
} catch (FileNotFoundException ex) { throw new FileNotFoundException(fileName+": "+ex.getMessage()); }
if (handle == 0) {
// Should be impossible, create should have thrown, but just in case.
throw new IOException("Invalid FLAC decoder handle");
}
state = State.INITIALIZED;
}
/** Always returns true, indicating that decodeAll can be called more than once, each time
* resetting the underlying input.
*/
public boolean reusable() { return true; }
public synchronized void close() throws IOException {
if (state != State.CLOSED) {
free(handle);
state = State.CLOSED;
target.close();
}
}
// Rethrows exception after closing.
private void fail(RuntimeException ex) throws IOException {
try { close(); } catch (Throwable th) {
if (System.getProperty("klipspringer.debug") != null) { th.printStackTrace(); }
}
throw ex;
}
// Rethrows exception after closing.
private void fail(IOException ex) throws IOException {
try { close(); } catch (Throwable th) {
if (System.getProperty("klipspringer.debug") != null) { th.printStackTrace(); }
}
throw ex;
}
public synchronized void decodeMetadata() throws IOException {
try {
if (state != State.INITIALIZED) {
throw new IllegalStateException("Attempted decodeMetadata in state "+state);
}
nativeDecodeMetadata(handle);
} catch (IOException ex) { fail(ex); } catch (RuntimeException ex) { fail(ex); }
}
public synchronized boolean decodeAll(long fromFrame) throws IOException {
try {
if (state == State.INITIALIZED) {
decodeMetadata();
} else if (state == State.AUDIO_DECODED) {
rewind(handle);
state = State.METADATA_DECODED;
} else if (state != State.METADATA_DECODED) {
throw new IllegalStateException("Attempted decodeAll in state "+state);
}
if (fromFrame != 0) {
state = State.SEEK;
nativeSeek(handle, fromFrame);
}
boolean r = state == State.SEEK_ABORT ? false : nativeDecodeAll(handle);
state = State.AUDIO_DECODED;
return r;
} catch (IOException ex) { fail(ex); } catch (RuntimeException ex) { fail(ex); }
throw new IllegalStateException(); // unreachable
}
// Callback function.
private void metadata(int rate, int bips, int ss, int channels, ByteOrder bo, long totalFrames)
throws IOException
{
target.metadata(new PcmFormat(rate, bips, channels, true, channels*ss, bo, bips), totalFrames);
state = State.METADATA_DECODED;
}
// Callback function. Handles write callback from seek specially to prevent seek failure.
private boolean write(ByteBuffer buffer) throws IOException {
boolean r = target.write(buffer);
if (state == State.SEEK) {
state = r ? State.SEEK_OK : State.SEEK_ABORT;
return true;
}
return r;
}
// Abstract methods that invoke external API.
abstract long create(String fnam, byte[] bfnam) throws IOException;
abstract void rewind(long handle) throws IOException;
abstract void free(long handle);
abstract void nativeSeek(long handle, long samplePos) throws IOException;
abstract void nativeDecodeMetadata(long handle) throws IOException;
abstract boolean nativeDecodeAll(long handle) throws IOException;
}
Version: v4.3.2.2 (2026-05-16T17:03:34+02:00)
Raw file
Source code overview
Klipspringer home