// 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