// 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 "FLAC/stream_encoder.h"
#include "Library.h"
#include "flac_error.h"
#include "net_avadeaux_klipspringer_codec_FlacStream.h" // generated by javac -h
// A pointer to this struct is cast to an integer and used for interaction with the Java side.
typedef struct {
// Members set in create.
FLAC__StreamEncoder *encoder; // FLAC interface
FLAC__int32 **restrict enc_buffer; // One array per channel
jobject receiver; // receiver of encoded data
jmethodID receiveMid; // receciver.receive method
unsigned channels; // number of channels
unsigned ss; // bytes per sample, 1, 2, or 4
// Members set in each process call for passing through to FLAC callbacks
JNIEnv *env; // process call
} EncoderRecord;
// -------------------------------------------------------------------------------------------------
// Helper functions used on errors and by free. The policy is that erec is freed on error in create,
// but in other cases the caller should catch IOException and call free.
// Frees/deletes everything.
static void
free_erec(JNIEnv *env, EncoderRecord *erec) {
if (erec != NULL) {
FLAC__StreamEncoder *e = erec->encoder;
if (e) {
// It seems that a stream_encoder_finish call is necessary to make sure that the encoder
// does not call the write callback later, even after it is deleted. In any case,
// putting this call here got rid of some otherwise inexplicable crashes that have
// something to do with malloc/free confusion.
erec->encoder = NULL; // prevent write_cb from writing
if (FLAC__stream_encoder_get_state(e) == FLAC__STREAM_ENCODER_OK) { FLAC__stream_encoder_finish(e); }
FLAC__stream_encoder_delete(e);
}
if (erec->receiver) { (*env)->DeleteGlobalRef(env, erec->receiver); }
if (erec->enc_buffer) {
for (int j = 0; j < erec->channels; j++) { free(erec->enc_buffer[j]); }
free((void *) erec->enc_buffer);
}
free(erec);
}
}
// Throws Error unless already thrown, frees drec, and returns 0.
static jlong
fail(JNIEnv *env, const char *message, EncoderRecord *erec) {
raiseError(env, message);
free_erec(env, erec);
return 0;
}
// -------------------------------------------------------------------------------------------------
// FLAC callback functions
// Sends data to receiver.
static FLAC__StreamEncoderWriteStatus
write_cb(const FLAC__StreamEncoder *encoder,
const FLAC__byte buffer[],
size_t bytes,
uint32_t frames,
uint32_t current_frame,
void *jerec)
{
EncoderRecord *erec = (EncoderRecord *) jerec;
// This happens if write_cb is invoked by stream_encoder_finish while the encoder is being
// destroyed (when free_erec calls stream_encoder_finish).
if (erec->encoder == NULL) { return FLAC__STREAM_ENCODER_WRITE_STATUS_FATAL_ERROR; }
JNIEnv *env = erec->env;
if ((*env)->ExceptionCheck(env)) { return FLAC__STREAM_ENCODER_WRITE_STATUS_FATAL_ERROR; }
// Wrap the buffer as a DirectByteBuffer.
jobject bb = wrapByteBuffer(env, (void *) buffer, bytes, platform_bigend());
if (bb == NULL) { return FLAC__STREAM_ENCODER_WRITE_STATUS_FATAL_ERROR; }
(*env)->CallVoidMethod(env, erec->receiver, erec->receiveMid, bb, frames);
if ((*env)->ExceptionCheck(env)) { return FLAC__STREAM_ENCODER_WRITE_STATUS_FATAL_ERROR; }
return FLAC__STREAM_ENCODER_WRITE_STATUS_OK;
}
static FLAC__StreamEncoderWriteStatus
testogg_write_cb(const FLAC__StreamEncoder *encoder,
const FLAC__byte buffer[],
size_t bytes,
uint32_t samples,
uint32_t current_frame,
void *outsamples)
{
*((long *) outsamples) += samples;
return FLAC__STREAM_ENCODER_WRITE_STATUS_OK;
}
// -------------------------------------------------------------------------------------------------
// Native FlacStream methods
#define METHOD(name) JNICALL Java_net_avadeaux_klipspringer_codec_FlacStream_ ## name
JNIEXPORT jlong
METHOD(create) (JNIEnv *env,
jclass jeclass,
jbyteArray jfnam,
jboolean ogg,
jint jrate,
jint jbips,
jint jchannels,
jint jss,
jboolean jsigned,
jboolean jbigend,
jint jcomprlevel,
jint jbufferFrames,
jobject jreceiver)
{
if (jss != 1 && jss != 2 && jss != 4) { return fail(env, "Illegal frame size", NULL); }
EncoderRecord *erec = malloc(sizeof *erec);
if (erec == NULL) { return fail(env, "Failed to allocate encoder record", erec); }
// Null pointers so that free_erec can handle partially initialized record.
erec->encoder = NULL;
erec->enc_buffer = NULL;
erec->receiver = NULL;
// Remember format.
erec->channels = jchannels;
erec->ss = jss;
// Global reference to receiver.
erec->receiver = (*env)->NewGlobalRef(env, jreceiver);
if (erec->receiver == NULL) { return fail(env, "Failed to get global reference", erec); }
erec->receiveMid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, jreceiver), "receive", "(Ljava/nio/ByteBuffer;I)V");
if (erec->receiveMid == NULL) { return fail(env, "Failed to get receive method", erec); }
// Set up encoder.
if ((erec->encoder = FLAC__stream_encoder_new()) == NULL) {
return fail(env, "Failed to create FLAC encoder", erec);
}
if (!(FLAC__stream_encoder_set_streamable_subset(erec->encoder, true)
&& FLAC__stream_encoder_set_compression_level(erec->encoder, jcomprlevel)
&& FLAC__stream_encoder_set_channels(erec->encoder, jchannels)
&& FLAC__stream_encoder_set_bits_per_sample(erec->encoder, jbips)
&& FLAC__stream_encoder_set_sample_rate(erec->encoder, jrate))) {
return fail(env, raiseIOException(env, "Failed to initialize FLAC encoder"), erec);
}
erec->env = env; // because FLAC init calls write_cb
FLAC__StreamEncoderInitStatus status;
if (jfnam != NULL) {
jbyte *fnam = (*env)->GetByteArrayElements(env, jfnam, NULL);
if (fnam == NULL) { return fail(env, "Failed to allocate filename string", erec); }
status = (ogg ? FLAC__stream_encoder_init_ogg_file : FLAC__stream_encoder_init_file)(erec->encoder, (char *) fnam, NULL, NULL);
(*env)->ReleaseByteArrayElements(env, jfnam, fnam, JNI_ABORT);
} else {
status = ogg
? FLAC__stream_encoder_init_ogg_stream(erec->encoder, NULL, write_cb, NULL, NULL, NULL, erec)
: FLAC__stream_encoder_init_stream(erec->encoder, write_cb, NULL, NULL, NULL, erec);
}
if (status != FLAC__STREAM_ENCODER_INIT_STATUS_OK) {
return fail(env, raiseErrorCodeException(env, INITSTAT_OFF+status, (char *) FLAC__StreamEncoderInitStatusString[status]), erec);
}
// Allocate FLAC encoder PCM buffer (channel separated).
FLAC__int32 **enc_buffer = malloc(jchannels * sizeof(FLAC__int32 *));
if (enc_buffer == NULL) { return fail(env, "Failed to allocate buffer", erec); }
for (jsize j = 0; j < jchannels; j++) {
enc_buffer[j] = malloc(jbufferFrames * sizeof **enc_buffer);
if (enc_buffer[j] == NULL) { return fail(env, "Failed to allocate buffer", erec); }
}
erec->enc_buffer = enc_buffer;
return (intptr_t) erec;
}
JNIEXPORT void
METHOD(write) (JNIEnv *env,
jobject jthis,
jlong jerec,
jobject jdata,
jint jpos,
jint jframes)
{
EncoderRecord *erec = (EncoderRecord *) (intptr_t) jerec;
void *data = (*env)->GetDirectBufferAddress(env, jdata);
if (data == NULL) { raiseError(env, "Cannot get data byte buffer address"); return; }
data = ((char *) data) + jpos;
FLAC__int32 * const *restrict buffer = erec->enc_buffer;
unsigned channels = erec->channels;
# define COPY_LOOP(b) { \
int ## b ## _t *r = data; \
for (int i = 0; i < jframes; i++) { \
for (int j = 0; j < channels; j++) { \
buffer[j][i] = *r++; \
} \
} \
}
# define SS_CASES \
switch (erec->ss) { \
case 1: COPY_LOOP(8) break; \
case 2: COPY_LOOP(16) break; \
case 4: COPY_LOOP(32) break; \
default: \
raiseIOException(env, "Invalid frame size"); \
return; \
}
switch (channels) {
case 1: SS_CASES break;
case 2: SS_CASES break;
case 4: SS_CASES break;
default: SS_CASES;
}
// Encode.
erec->env = env;
if (!FLAC__stream_encoder_process(erec->encoder, (const FLAC__int32 *const *) buffer, jframes)) {
FLAC__StreamEncoderState state = FLAC__stream_encoder_get_state(erec->encoder);
raiseErrorCodeException(env, ENCSTAT_OFF+state, (char *) FLAC__StreamEncoderStateString[state]);
}
}
JNIEXPORT void
METHOD(finish) (JNIEnv *env,
jobject jthis,
jlong jerec)
{
EncoderRecord *erec = (EncoderRecord *) (intptr_t) jerec;
erec->env = env;
if (!FLAC__stream_encoder_finish(erec->encoder)) {
FLAC__StreamEncoderState state = FLAC__stream_encoder_get_state(erec->encoder);
raiseErrorCodeException(env, ENCSTAT_OFF+state, (char *) FLAC__StreamEncoderStateString[state]);
}
}
JNIEXPORT void
METHOD(free) (JNIEnv *env,
jobject jthis,
jlong jerec)
{
free_erec(env, (EncoderRecord *) (intptr_t) jerec);
}
JNIEXPORT jboolean
METHOD(oggSupported) (JNIEnv *env,
jclass jeclass)
{
const int samples = 100;
FLAC__int32 **buffer = malloc(2 * sizeof(FLAC__int32 *));
if (buffer == NULL) {
raiseError(env, "Failed to allocate buffer for OGG FLAC test");
return false;
}
for (int c = 0; c < 2; c++) {
if ((buffer[c] = malloc(samples * sizeof **buffer)) == NULL) {
raiseError(env, "Failed to allocate buffer for OGG FLAC test");
return false;
}
for (int i = 0; i < samples; i++) {
buffer[c][i] = 0;
}
}
FLAC__StreamEncoder *encoder = FLAC__stream_encoder_new();
if (encoder == NULL) {
raiseError(env, "Failed to allocate encoder for OGG FLAC test");
return false;
}
if (!(FLAC__stream_encoder_set_streamable_subset(encoder, true) &&
FLAC__stream_encoder_set_compression_level(encoder, 5) &&
FLAC__stream_encoder_set_channels(encoder, 2) &&
FLAC__stream_encoder_set_bits_per_sample(encoder, 16) &&
FLAC__stream_encoder_set_sample_rate(encoder, 44100))) {
raiseError(env, "Failed to initialize encoder for OGG FLAC test");
return false;
}
int outsamples = 0;
FLAC__StreamEncoderInitStatus status =
FLAC__stream_encoder_init_ogg_stream(encoder, NULL, testogg_write_cb, NULL, NULL, NULL, &outsamples);
if (status != FLAC__STREAM_ENCODER_INIT_STATUS_OK) {
raiseError(env, "Failed to initialize stream for OGG FLAC test");
return false;
}
if (!FLAC__stream_encoder_process(encoder, (const FLAC__int32 *const *) buffer, samples)
|| !FLAC__stream_encoder_finish(encoder)) {
raiseError(env, "Failed to perform OGG FLAC test");
return false;
}
for (int c = 0; c < 2; c++) { free(buffer[c]); }
free(buffer);
return outsamples == 100;
}
Version: v4.3.2.2 (2026-05-16T17:03:34+02:00)
Raw file
Source code overview
Klipspringer home