// 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 "opusfile.h"
#include "opus_error.h"
#include "Library.h"
#include "net_avadeaux_klipspringer_codec_OpusDecoder.h" // generated by javac -h
#define PCM_BUFFER_FRAMES 8192 // opus doc recommends at least 5760
// A pointer to this struct is cast to an integer and used for interaction with the Java side.
typedef struct {
OggOpusFile *of; // Opusfile interface
jmethodID metadataMid; // metadata callback method
jmethodID writeMid; // write callback method
FILE *file; // set in create, kept open until free
} DecoderRecord;
// -------------------------------------------------------------------------------------------------
// Callback function stuff. We dare not cast stdio functions for seek and tell directly to the
// opusfile types, in case the size of the offset type is different.
int file_read(void *f, unsigned char *p, int n) {
return fread(p, 1, n, (FILE *) f);
}
static int file_seek(void *f, opus_int64 offset, int whence) {
return fseeko((FILE *) f, offset, whence);
}
static opus_int64 file_tell(void *f) {
return ftello(f);
}
OpusFileCallbacks file_callbacks = { file_read, file_seek, file_tell, NULL };
// -------------------------------------------------------------------------------------------------
// 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->of) { op_free(drec->of); }
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 OpusDecoder 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;
drec->of = op_open_callbacks(drec->file, &file_callbacks, NULL, 0, &err);
if (drec->of == NULL) {
raiseErrorCodeException(env, err, opusfile_strerror(err));
return -1;
}
op_set_dither_enabled(drec->of, true);
return 0;
}
#define METHOD(name) JNICALL Java_net_avadeaux_klipspringer_codec_OpusDecoder_ ## name
// Opens file, creates and initializes decoder.
JNIEXPORT jlong
METHOD(create) (JNIEnv *env,
jobject jthis,
jbyteArray jfnam)
{
if (sizeof(opus_int16) != 2) {
raiseIOException(env, "Integer size mismatch for Opus decoding");
return 0;
}
DecoderRecord *drec = malloc(sizeof *drec);
if (drec == NULL) { return fail(env, "Failed to allocate decoder record", drec); }
// Null pointers so that free_drec can handle partially initialized record.
drec->of = NULL;
drec->file = NULL;
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 of either NULL or not freed. 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;
if (drec->of) {
op_free(drec->of);
drec->of = NULL;
}
errno = 0;
rewind(drec->file);
if (errno != 0) {
raiseErrorCodeException(env, errno, strerror(errno));
return;
}
init(env, drec);
}
// Calls free_drec to close file and free of.
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 = op_pcm_seek(drec->of, jpos);
if (err) { raiseErrorCodeException(env, err, opusfile_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;
ogg_int64_t samples = op_pcm_total(drec->of, -1);
(*env)->CallVoidMethod(env, jthis, drec->metadataMid, 48000, 16, 2, op_channel_count(drec->of, -1), platform_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;
int channels = op_channel_count(drec->of, -1);
int buf_bytes = PCM_BUFFER_FRAMES * channels * 2;
opus_int16 *buf = malloc(buf_bytes);
if (buf == NULL) {
raiseIOException(env, "cannot allocate decoding buffer");
return JNI_FALSE;
}
jobject bb = wrapByteBuffer(env, buf, buf_bytes, platform_bigend());
jboolean ok = bb != NULL;
while (ok) {
long n = op_read(drec->of, buf, PCM_BUFFER_FRAMES * channels, NULL);
if (n == 0) { break; }
if (n < 0) {
raiseErrorCodeException(env, n, opusfile_strerror(n));
ok = JNI_FALSE;
}
if (!byteBufferClear(env, bb)
|| !byteBufferLimit(env, bb, n * channels * 2)
|| !(*env)->CallBooleanMethod(env, jthis, drec->writeMid, bb)) {
ok = JNI_FALSE;
}
}
free(buf);
return ok;
}
Version: v4.3.2.2 (2026-05-16T17:03:34+02:00)
Raw file
Source code overview
Klipspringer home