// 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 "vorbis/codec.h"
#include "vorbis/vorbisfile.h"
#include "vorbis_error.h"
#include "Library.h"
#include "net_avadeaux_klipspringer_codec_VorbisDecoder.h" // generated by javac -h
#define VORBIS_BYPS 2
#define VORBIS_BIGEND 0
#define VORBIS_SIGNED 1
// A pointer to this struct is cast to an integer and used for interaction with the Java side.
typedef struct {
OggVorbis_File vf; // Vorbisfile interface
bool vf_open; // vf has been opened and not cleared
jobject this; // VorbisDecoder object
jmethodID metadataMid; // this metadata callback method
jmethodID writeMid; // this write callback method
FILE *file; // set in create, kept open until delete
} 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->vf_open) { ov_clear(&drec->vf); }
if (drec->this) { (*env)->DeleteGlobalRef(env, drec->this); }
if (drec->file) { fclose(drec->file); }
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;
}
// -------------------------------------------------------------------------------------------------
// Native VorbisDecoder methods
// Helper for create and rewind. Returns 0 on success, throws and returns -1 on failure.
static int
init(JNIEnv *env, DecoderRecord *drec) {
int err = ov_open_callbacks(drec->file, &drec->vf, NULL, 0, OV_CALLBACKS_NOCLOSE);
if (err < 0) {
raiseErrorCodeException(env, err, vorbis_strerror(err));
return -1;
}
drec->vf_open = true;
return 0;
}
#define METHOD(name) JNICALL Java_net_avadeaux_klipspringer_codec_VorbisDecoder_ ## name
// Opens file, creates and initializes decoder.
JNIEXPORT jlong
METHOD(create) (JNIEnv *env,
jobject jthis,
jbyteArray jfnam)
{
DecoderRecord *drec = malloc(sizeof *drec);
if (drec == NULL) { return fail(env, "Failed to allocate decoder record", drec); }
drec->vf_open = false;
// Null pointers so that free_drec can handle partially initialized record.
drec->this = NULL;
drec->file = NULL;
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)) { free_drec(env, drec); return 0; }
return (intptr_t) drec;
}
// Expects file to be still open and vf not cleared. Rewinds file, clears and reinitializes vf, 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;
ov_clear(&drec->vf);
drec->vf_open = false;
errno = 0;
rewind(drec->file);
if (errno != 0) {
raiseErrorCodeException(env, errno, strerror(errno));
return;
}
init(env, drec);
}
// Calls free_drec to close file and clear vf.
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;
int err = ov_pcm_seek(&drec->vf, jpos);
if (err) { raiseErrorCodeException(env, err, vorbis_strerror(err)); }
}
// 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;
vorbis_info *vi = ov_info(&drec->vf, -1);
ogg_int64_t samples = ov_pcm_total(&drec->vf, -1);
(*env)->CallVoidMethod(env, drec->this, drec->metadataMid, vi->rate, VORBIS_BYPS*8, VORBIS_BYPS, vi->channels, VORBIS_BIGEND, samples);
}
// Expected to be called after processing metadata.
JNIEXPORT jboolean
METHOD(nativeDecodeAll) (JNIEnv *env,
jobject jthis,
jlong jdrec)
{
DecoderRecord *drec = (DecoderRecord *) (intptr_t) jdrec;
char buffer[4096];
jobject bb = wrapByteBuffer(env, buffer, sizeof buffer, VORBIS_BIGEND);
if (bb == NULL) { return JNI_FALSE; }
int section;
while (true) {
long n = ov_read(&drec->vf, buffer, sizeof buffer, VORBIS_BIGEND, VORBIS_BYPS, VORBIS_SIGNED, §ion);
if (n == 0) { return JNI_TRUE; }
if (n < 0) {
raiseErrorCodeException(env, n, vorbis_strerror(n));
return JNI_FALSE;
}
if (!byteBufferClear(env, bb)
|| !byteBufferLimit(env, bb, n)
|| !(*env)->CallBooleanMethod(env, jthis, drec->writeMid, bb)) { fflush(stdout);return JNI_FALSE; }
}
}
Version: v4.3.2.2 (2026-05-16T17:03:34+02:00)
Raw file
Source code overview
Klipspringer home