// 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/>.
#include <stdlib.h>
#include <string.h>
#include <sys/errno.h>
#include <sys/stat.h>
#include "FLAC/stream_decoder.h"
#include "Library.h"
#include "flac_error.h"
#include "net_avadeaux_klipspringer_codec_FlacDecoder.h" // generated by javac -h
// A pointer to this struct is cast to an integer and used for interaction with the Java side. Also
// passed to FLAC callbacks.
typedef struct {
// Members set in create.
FLAC__StreamDecoder *decoder; // FLAC interface
jobject this; // FlacDecoder object
jmethodID metadataMid; // this metadata callback method
jmethodID writeMid; // this write callback method
FILE *file; // set in create, kept open until delete
bool ogg; // true if Ogg FLAC
void *restrict buffer; // allocated in first metadata_cb
JNIEnv *env; // for passing handle through to FLAC callbacks
} DecoderRecord;
// -------------------------------------------------------------------------------------------------
// Helper functions used on errors and by free. The policy is that drec is freed on error in create,
// but in other cases the caller should catch IOException and call free.
// Closes and frees everything.
static void
free_drec(JNIEnv *env, DecoderRecord *drec) {
if (drec != NULL) {
if (drec->decoder) { FLAC__stream_decoder_delete(drec->decoder); }
if (drec->this) { (*env)->DeleteGlobalRef(env, drec->this); }
if (drec->file) { fclose(drec->file); }
if (drec->buffer) { free(drec->buffer); }
free(drec);
}
}
// Throws Error unless already thrown, frees drec, and returns 0.
static jlong
fail(JNIEnv *env, const char *message, DecoderRecord *drec) {
raiseError(env, message);
free_drec(env, drec);
return 0;
}
// Calculates frame size from number of bits per sample. There is a choice for what to do when 16 <
// bips < 25, either use 3 bytes or pad to 4 bytes. Both have advantages, but we have to pick one.
static unsigned
calc_ss(unsigned bips) {
return (bips+7)/8; // 3 byte samples are ok
// return 1 << (bips-1)/8 - bips/25; // pad 3 to 4 bytes
}
// -------------------------------------------------------------------------------------------------
// FLAC callback functions
// Allocates buffer and sends metadata to Java side, unless this has already been done.
static void
metadata_cb(const FLAC__StreamDecoder *decoder,
const FLAC__StreamMetadata *metadata,
void *handle)
{
DecoderRecord *drec = (DecoderRecord *) handle;
JNIEnv *env = drec->env;
if ((*env)->ExceptionCheck(env)) { return; }
if (metadata->type == FLAC__METADATA_TYPE_STREAMINFO) {
uint32_t rate = metadata->data.stream_info.sample_rate;
uint32_t bips = metadata->data.stream_info.bits_per_sample;
uint32_t channels = metadata->data.stream_info.channels;
FLAC__uint64 samples = metadata->data.stream_info.total_samples;
if (drec->buffer == NULL) { // first time?
unsigned ss = calc_ss(bips);
drec->buffer = malloc(metadata->data.stream_info.max_blocksize * channels * ss);
if (drec->buffer == NULL) {
raiseError(env, "Failed to allocate PCM data buffer");
return;
}
// Send metadata to Java.
(*env)->CallVoidMethod(env, drec->this, drec->metadataMid, rate, bips, ss, channels, platform_order(), samples);
}
} else if (metadata->type == FLAC__METADATA_TYPE_PICTURE) {
picture(env, drec->this,
metadata->data.picture.mime_type,
FLAC__StreamMetadata_Picture_TypeString[metadata->data.picture.type],
(char *) metadata->data.picture.description,
metadata->data.picture.data, metadata->data.picture.data_length);
}
}
// Sends block to Java side as a ByteBuffer.
static FLAC__StreamDecoderWriteStatus
write_cb(const FLAC__StreamDecoder *decoder,
const FLAC__Frame *frame,
const FLAC__int32 * const buffer[],
void *handle)
{
DecoderRecord *drec = (DecoderRecord *) handle;
JNIEnv *env = drec->env;
if ((*env)->ExceptionCheck(env)) { return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; }
unsigned ss = calc_ss(frame->header.bits_per_sample);
unsigned channels = frame->header.channels;
unsigned blocksz = frame->header.blocksize;
// Intermix the channels in the buffer, make compiler optimize for important cases.
int8_t *w = drec->buffer;
# define COPY_LOOP { \
unsigned bo = platform_bigend() ? 4-ss : 0; \
for (int i = 0; i < blocksz; i++) { \
for (int j = 0; j < channels; j++) { \
memcpy(w, (int8_t *) (buffer[j]+i) + bo, ss); \
w += ss; \
} \
} \
}
# define SS_CASES \
switch (ss) { \
case 1: COPY_LOOP break; \
case 2: COPY_LOOP break; \
case 4: COPY_LOOP break; \
default: COPY_LOOP; \
}
switch (channels) {
case 1: SS_CASES break;
case 2: SS_CASES break;
default: SS_CASES;
}
// Wrap the buffer as a DirectByteBuffer.
jobject bb = wrapByteBuffer(env, drec->buffer, blocksz*channels*ss, platform_bigend());
if (bb == NULL) { return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; }
// Call Java side.
return (*env)->CallBooleanMethod(env, drec->this, drec->writeMid, bb) ?
FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE :
FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
}
// Sets exception to be thrown.
static void
error_cb(const FLAC__StreamDecoder *decoder,
FLAC__StreamDecoderErrorStatus status,
void *handle)
{
raiseErrorCodeException(((DecoderRecord *) handle)->env, ERRORSTAT_OFF+status, (char *) FLAC__StreamDecoderErrorStatusString[status]);
}
// Standard callback function more or less copied from FLAC docs.
static FLAC__StreamDecoderReadStatus
read_cb(const FLAC__StreamDecoder *decoder,
FLAC__byte buffer[],
size_t *bytes,
void *handle)
{
DecoderRecord *drec = (DecoderRecord *) handle;
JNIEnv *env = drec->env;
if ((*env)->ExceptionCheck(env)) { return FLAC__STREAM_DECODER_READ_STATUS_ABORT; }
if (*bytes == 0) { return FLAC__STREAM_DECODER_READ_STATUS_ABORT; } // example in docs does this
*bytes = fread(buffer, sizeof(FLAC__byte), *bytes, drec->file);
if (ferror(drec->file)) {
raiseIOException(env, "Error reading FLAC file");
return FLAC__STREAM_DECODER_READ_STATUS_ABORT;
}
return *bytes == 0 ?
FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM :
FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
}
// Standard callback function more or less copied from FLAC docs.
static FLAC__StreamDecoderSeekStatus
seek_cb(const FLAC__StreamDecoder *decoder,
FLAC__uint64 absolute_byte_offset,
void *handle)
{
DecoderRecord *drec = (DecoderRecord *) handle;
JNIEnv *env = drec->env;
if ((*env)->ExceptionCheck(env)) { return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR; }
if (fseeko(drec->file, (off_t) absolute_byte_offset, SEEK_SET) < 0) {
raiseErrorCodeException(env, ERRNO_OFF+errno, "Error seeking in FLAC file");
return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR;
}
return FLAC__STREAM_DECODER_SEEK_STATUS_OK;
}
// Standard callback function more or less copied from FLAC docs.
static FLAC__StreamDecoderTellStatus
tell_cb(const FLAC__StreamDecoder *decoder,
FLAC__uint64 *absolute_byte_offset,
void *handle)
{
DecoderRecord *drec = (DecoderRecord *) handle;
JNIEnv *env = drec->env;
if ((*env)->ExceptionCheck(env)) { return FLAC__STREAM_DECODER_TELL_STATUS_ERROR; }
off_t pos;
if ((pos = ftello(drec->file)) < 0) {
raiseErrorCodeException(env, ERRNO_OFF+errno, "Error getting position in FLAC file");
return FLAC__STREAM_DECODER_TELL_STATUS_ERROR;
}
*absolute_byte_offset = (FLAC__uint64) pos;
return FLAC__STREAM_DECODER_TELL_STATUS_OK;
}
// Standard callback function more or less copied from FLAC docs.
static FLAC__StreamDecoderLengthStatus
length_cb(const FLAC__StreamDecoder *decoder,
FLAC__uint64 *stream_length,
void *handle)
{
struct stat filestats;
DecoderRecord *drec = (DecoderRecord *) handle;
JNIEnv *env = drec->env;
if ((*env)->ExceptionCheck(env)) { return FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR; }
if (fstat(fileno(drec->file), &filestats) != 0) {
raiseErrorCodeException(env, ERRNO_OFF+errno, "Error getting file stats of FLAC file");
return FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR;
}
*stream_length = (FLAC__uint64) filestats.st_size;
return FLAC__STREAM_DECODER_LENGTH_STATUS_OK;
}
// Standard callback function more or less copied from FLAC docs.
static FLAC__bool
eof_cb(const FLAC__StreamDecoder *decoder, void *handle) {
DecoderRecord *drec = (DecoderRecord *) handle;
return feof(drec->file) ? true : false;
}
// -------------------------------------------------------------------------------------------------
// Native FlacDecoder methods
// Helper for create and rewind. Returns 0 on success, throws and returns -1 on failure.
static int
init(JNIEnv *env, DecoderRecord *drec, bool pictures) {
if (pictures) { FLAC__stream_decoder_set_metadata_respond(drec->decoder, FLAC__METADATA_TYPE_PICTURE); }
else { FLAC__stream_decoder_set_metadata_ignore(drec->decoder, FLAC__METADATA_TYPE_PICTURE); }
FLAC__StreamDecoderInitStatus status = (drec->ogg ? FLAC__stream_decoder_init_ogg_stream : FLAC__stream_decoder_init_stream)
(drec->decoder,
read_cb, seek_cb, tell_cb, length_cb, eof_cb,
write_cb, metadata_cb, error_cb,
drec);
if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
raiseErrorCodeException(env, INITSTAT_OFF+status, (char *) FLAC__StreamDecoderInitStatusString[status]);
return -1;
} else {
return 0;
}
}
#define METHOD(name) JNICALL Java_net_avadeaux_klipspringer_codec_FlacDecoder_ ## name
// Opens file, creates and initializes decoder.
JNIEXPORT jlong
METHOD(create) (JNIEnv *env,
jobject jthis,
jbyteArray jfnam,
jboolean jogg)
{
DecoderRecord *drec = malloc(sizeof *drec);
if (drec == NULL) { return fail(env, "Failed to allocate decoder record", drec); }
drec->ogg = jogg;
// Null pointers so that free_drec can handle partially initialized record.
drec->decoder = NULL;
drec->this = NULL;
drec->file = NULL;
drec->buffer = NULL;
if ((drec->decoder = FLAC__stream_decoder_new()) == NULL) {
return fail(env, "Failed to create FLAC decoder", drec);
}
if ((drec->this = (*env)->NewGlobalRef(env, jthis)) == NULL) {
return fail(env, "Failed to get global decoder reference", drec);
}
jclass ecls = (*env)->GetObjectClass(env, jthis);
if (ecls == NULL) { return fail(env, "Failed to get encoder class", drec); }
if ((drec->metadataMid = (*env)->GetMethodID(env, ecls, "metadata", "(IIIILjava/nio/ByteOrder;J)V")) == NULL) {
return fail(env, "Failed to get metadata method ID", drec);
}
if ((drec->writeMid = (*env)->GetMethodID(env, ecls, "write", "(Ljava/nio/ByteBuffer;)Z")) == NULL) {
return fail(env, "Failed to get write method ID", drec);
}
jbyte *fnam = (*env)->GetByteArrayElements(env, jfnam, NULL);
if (fnam == NULL) { return fail(env, "Failed to allocate filename string", drec); }
drec->file = fopen((char *) fnam, "r");
(*env)->ReleaseByteArrayElements(env, jfnam, fnam, JNI_ABORT);
if (drec->file == NULL) { return fail(env, raiseFileNotFound(env, strerror(errno)), drec); }
if (init(env, drec, accepts_picture(env, jthis))) { free_drec(env, drec); return 0; }
return (intptr_t) drec;
}
// Expects file to be still open and decoder to be in uninitialized state (after
// FLAC__stream_decoder_finish was called). Rewinds file, initializes decoder, and processes
// metadata (which does not send it on to the Java side), and leaves the decoder ready for the next
// nativeDecodeAll.
JNIEXPORT void
METHOD (rewind) (JNIEnv *env,
jobject jthis,
jlong jdrec)
{
DecoderRecord *drec = (DecoderRecord *) (intptr_t) jdrec;
if (FLAC__stream_decoder_get_state(drec->decoder) != FLAC__STREAM_DECODER_UNINITIALIZED) {
FLAC__stream_decoder_finish(drec->decoder);
}
errno = 0;
rewind(drec->file);
if (errno != 0) {
raiseErrorCodeException(env, ERRNO_OFF+errno, strerror(errno));
} else if (init(env, drec, false) == 0 && !FLAC__stream_decoder_process_until_end_of_metadata(drec->decoder)) {
raiseErrorCodeException(env, ERRNO_OFF+errno, "Failed to process metadata in rewind");
}
}
// Calls free_drec to close file and delete decoder.
JNIEXPORT void
METHOD (free) (JNIEnv *env,
jobject jthis,
jlong jdrec)
{
free_drec(env, (DecoderRecord *) (intptr_t) jdrec);
}
// Absolute seek.
JNIEXPORT void
METHOD(nativeSeek) (JNIEnv *env,
jobject jthis,
jlong jdrec,
jlong jpos)
{
DecoderRecord *drec = (DecoderRecord *) (intptr_t) jdrec;
drec->env = env;
if (!FLAC__stream_decoder_seek_absolute(drec->decoder, jpos)) {
raiseIOException(env, "Failed to seek in FLAC file (possible invalid position for file)");
}
}
// Expected to be called as the first thing after create to send metadata to the java side.
JNIEXPORT void
METHOD(nativeDecodeMetadata) (JNIEnv *env,
jobject jthis,
jlong jdrec)
{
DecoderRecord *drec = (DecoderRecord *) (intptr_t) jdrec;
drec->env = env;
if (!FLAC__stream_decoder_process_until_end_of_metadata(drec->decoder)) {
raiseIOException(env, "Failed to decode metadata");
}
}
// Expected to be called after processing metadata.
JNIEXPORT jboolean
METHOD(nativeDecodeAll) (JNIEnv *env,
jobject jthis,
jlong jdrec)
{
DecoderRecord *drec = (DecoderRecord *) (intptr_t) jdrec;
drec->env = env;
jboolean r = JNI_TRUE;
if (!FLAC__stream_decoder_process_until_end_of_stream(drec->decoder)) {
FLAC__StreamDecoderState state = FLAC__stream_decoder_get_state(drec->decoder);
if (state != FLAC__STREAM_DECODER_ABORTED) {
raiseErrorCodeException(env, DECSTAT_OFF+state, (char *) FLAC__StreamDecoderStateString[state]);
}
r = JNI_FALSE;
}
FLAC__stream_decoder_finish(drec->decoder);
return r;
}
Version: v4.3.2.2 (2026-05-16T17:03:34+02:00)
Raw file
Source code overview
Klipspringer home