// 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 "Library.h"
#include "choose_format.h"
#include "net_avadeaux_klipspringer_alsa_Alsa.h" // generated by javac -h

// -------------------------------------------------------------------------------------------------
// Helper functions used on errors and by free. The policy is that resources are freed on error in
// create, but in other cases the caller should catch IOException and call free.

// Throws exception and closes pcm. Returns 0 so it can be used with return.
static jlong
fail(JNIEnv *env, const char *message, snd_pcm_t *pcm, snd_pcm_hw_params_t *hw_params) {
    raiseError(env, message);
    if (hw_params != NULL) { snd_pcm_hw_params_free(hw_params); }
    if (pcm) { snd_pcm_close(pcm); }
    return 0;
}

// Conditionally attempts recovery after underrun/overrun.
static bool recover_xrun(JNIEnv *env, snd_pcm_t *pcm, int err, int attempts, int rem_attempts) {
    if (err == -EPIPE && attempts) {
        if (!rem_attempts) {
            raiseIOException(env, "Too many attempts to recover from EPIPE");
            return false;
        }
        if (snd_pcm_prepare(pcm) < 0) {
            raiseIOException(env, "Cannot recover from ALSA overrun/underrun");
            return false;
        }
        return true;
    } else {
        raiseErrorCodeException(env, err, snd_strerror(err));
        return false;
    }
}

// -------------------------------------------------------------------------------------------------
// Native Alsa methods

#define METHOD(name) JNICALL Java_net_avadeaux_klipspringer_alsa_Alsa_ ## name

// The alsaInit method is defined in AlsaDevice.c because it initializes static globals there.

JNIEXPORT jlong
METHOD(open) (JNIEnv *env,
              jobject jthis,
              jbyteArray jdevname,
              jboolean input,
              jint jbufferMicros,
              jboolean jsigned,
              jint jrate,
              jint jbips,
              jint jchannels,
              jint jfs,
              jboolean jbigend)
{
    int err;
    snd_pcm_t *pcm = NULL;
    snd_pcm_hw_params_t *hw_params = NULL;

    jbyte *devname = (*env)->GetByteArrayElements(env, jdevname, NULL);
    if (devname == NULL) { return fail(env, "Failed to allocate device name string", pcm, hw_params); }

    if (snd_pcm_hw_params_malloc(&hw_params)) { return fail(env, "Failed to allocate hardware params record", pcm, NULL); }

    unsigned bufferMicros = jbufferMicros;
    int dir = 0;
    if ((err = snd_pcm_open(&pcm, (const char *) devname, input ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK, 0)) < 0
        || (err = snd_pcm_hw_params_any(pcm, hw_params)) < 0
        || (err = snd_pcm_hw_params_set_access(pcm, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0
        || (err = snd_pcm_hw_params_set_format(pcm, hw_params, get_format(jsigned, jbips, jfs/jchannels, jbigend))) < 0
        || (err = snd_pcm_hw_params_set_rate(pcm, hw_params, jrate, 0)) < 0
        || (err = snd_pcm_hw_params_set_channels(pcm, hw_params, jchannels)) < 0
        || (err = snd_pcm_hw_params_set_buffer_time_near(pcm, hw_params, &bufferMicros, &dir)) < 0
        || (err = snd_pcm_hw_params(pcm, hw_params)) < 0
        || (err = snd_pcm_nonblock(pcm, 0)) < 0
        || (err = snd_pcm_prepare(pcm)) < 0) {
        return fail(env, raiseErrorCodeException(env, err, snd_strerror(err)), pcm, hw_params);
    }
    snd_pcm_hw_params_free(hw_params);
    if (input && (err = snd_pcm_start(pcm)) < 0) {
        return fail(env, raiseErrorCodeException(env, err, snd_strerror(err)), pcm, NULL);
    }
    return (intptr_t) pcm;
}

JNIEXPORT void
METHOD(close) (JNIEnv *env,
               jobject jthis,
               jlong jpcm)
{
    snd_pcm_close((snd_pcm_t *) (intptr_t) jpcm);
}

JNIEXPORT jint
METHOD(write) (JNIEnv *env,
               jobject jthis,
               jlong jpcm,
               jobject jbbuf,
               jint joff,
               jint jframes,
               jint jrecoAttempts)
{
    void *buf = (*env)->GetDirectBufferAddress(env, jbbuf);
    if (buf == NULL) { raiseError(env, "Failed to get buffer address"); return 0; }
    snd_pcm_t *pcm = (snd_pcm_t *) (intptr_t) jpcm;
    int ecount = jrecoAttempts;
    while (true) {
        jint n = snd_pcm_writei(pcm, ((char *) buf)+joff, jframes);
        if (n >= 0) { return n; }
        if (!recover_xrun(env, pcm, n, jrecoAttempts, ecount--)) { return 0; }
    }
}

JNIEXPORT jint
METHOD(read) (JNIEnv *env,
              jobject jthis,
              jlong jpcm,
              jobject jbbuf,
              jint joff,
              jint jframes,
              jint jrecoAttempts)
{
    void *buf = (*env)->GetDirectBufferAddress(env, jbbuf);
    if (buf == NULL) { raiseError(env, "Failed to get buffer address"); return 0; }
    snd_pcm_t *pcm = (snd_pcm_t *) (intptr_t) jpcm;
    int ecount = jrecoAttempts;
    while (true) {
        jint n = snd_pcm_readi(pcm, ((char *) buf)+joff, jframes);
        if (n >= 0) { return n; }
        if (!recover_xrun(env, pcm, n, jrecoAttempts, ecount--)) { return 0; }
    }
}

JNIEXPORT void
METHOD(pause) (JNIEnv *env,
               jobject jthis,
               jlong jpcm,
               jint enable)
{
    int err = snd_pcm_pause((snd_pcm_t *) (intptr_t) jpcm, enable);
    if (err < 0) { raiseErrorCodeException(env, err, snd_strerror(err)); }
}

JNIEXPORT void
METHOD(drain) (JNIEnv *env,
               jobject jthis,
               jlong jpcm)
{
    int err = snd_pcm_drain((snd_pcm_t *) (intptr_t) jpcm);
    if (err < 0) { raiseErrorCodeException(env, err, snd_strerror(err)); }
}

JNIEXPORT jint
METHOD(availableFrames) (JNIEnv *env,
                         jobject jthis,
                         jlong jpcm,
                         jint jrecoAttempts)
{
    snd_pcm_t *pcm = (snd_pcm_t *) (intptr_t) jpcm;
    int ecount = jrecoAttempts;
    while (true) {
        jint a = snd_pcm_avail(pcm);
        if (a >= 0) { return a; }
        if (!recover_xrun(env, pcm, a, jrecoAttempts, ecount--)) { return 0; }
    }
}

JNIEXPORT jint
METHOD(bufferFrames) (JNIEnv *env,
                      jobject jthis,
                      jlong jpcm)
{
    snd_pcm_uframes_t ringbufsamples, persz;
    int err = snd_pcm_get_params((snd_pcm_t *) (intptr_t) jpcm, &ringbufsamples, &persz);
    if (err < 0) { raiseErrorCodeException(env, err, snd_strerror(err)); }
    return ringbufsamples;
}

Version: v4.3.2.2 (2026-05-16T17:03:34+02:00)
Raw file
Source code overview
Klipspringer home