// 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 "choose_format.h"
#include "Library.h"
#include "net_avadeaux_klipspringer_alsa_AlsaDevice.h" // generated by javac -h
static snd_ctl_card_info_t *card_info;
static snd_pcm_info_t *pcm_info;
static snd_pcm_hw_params_t *hw_params = NULL;
static const unsigned rates[] = { 5512, 8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000, 88200, 96000, 176400, 192000, 352800, 384000 };
// -------------------------------------------------------------------------------------------------
// Helper methods
typedef struct {
jobject devices;
jmethodID addDevicesMid;
} DeviceCollector;
static int report_device(JNIEnv *env, DeviceCollector *coll, jstring jcard_name, const char *dev_name, int card, int dev, int sub) {
jstring jdev_name = (*env)->NewStringUTF(env, dev_name);
if (jdev_name == NULL) { raiseError(env, "Failed to allocate string"); return 1; }
(*env)->CallVoidMethod(env, coll->devices, coll->addDevicesMid, jcard_name, jdev_name, card, dev, sub);
return (*env)->ExceptionCheck(env) ? 1 : 0;
}
static int collect_devices(JNIEnv *env, DeviceCollector *coll, snd_ctl_t *ctl, int card, jboolean input) {
int err, first_dev = -1;
if ((err = snd_ctl_pcm_next_device(ctl, &first_dev)) < 0) { return err; }
if (first_dev == -1) { return 0; }
if ((err = snd_ctl_card_info(ctl, card_info)) < 0) { return err; }
jstring jcard_name = (*env)->NewStringUTF(env, snd_ctl_card_info_get_name(card_info));
if (jcard_name == NULL) { raiseError(env, "Failed to allocate string"); return 1; }
for (int dev = first_dev, next_dev; dev != -1; dev = next_dev) {
next_dev = dev;
if ((err = snd_ctl_pcm_next_device(ctl, &next_dev)) < 0) { return err; }
memset(pcm_info, 0, snd_pcm_info_sizeof());
snd_pcm_info_set_device(pcm_info, dev);
snd_pcm_info_set_stream(pcm_info, input ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK);
snd_pcm_info_set_subdevice(pcm_info, 0);
if ((err = snd_ctl_pcm_info(ctl, pcm_info)) < 0) { continue; }
int sub = 0, sub_count = snd_pcm_info_get_subdevices_count(pcm_info);
while (sub < sub_count) {
const char *dev_name = snd_pcm_info_get_name(pcm_info);
err = sub_count < 2 ?
(dev == first_dev && next_dev == -1 ?
report_device(env, coll, jcard_name, dev_name, card, -1, -1)
: report_device(env, coll, jcard_name, dev_name, card, dev, -1))
: report_device(env, coll, jcard_name, dev_name, card, dev, sub);
if (err) { return err; }
while (++sub < sub_count) { // look for next valid subdevice
snd_pcm_info_set_subdevice(pcm_info, sub);
if (!snd_ctl_pcm_info(ctl, pcm_info)) { break; }
}
}
}
return 0;
}
typedef struct {
jobject device;
jmethodID addFormatMid, addRatesMid, addChannelsMid, setPausableMid;
} FormatCollector;
static int report_format(JNIEnv *env, FormatCollector *coll, jboolean s, jint b, jint ss, jboolean be) {
(*env)->CallVoidMethod(env, coll->device, coll->addFormatMid, s, b, ss, be);
return (*env)->ExceptionCheck(env) ? 1 : 0;
}
static int report_channels(JNIEnv *env, FormatCollector *coll, int lo, int hi) {
(*env)->CallVoidMethod(env, coll->device, coll->addChannelsMid, lo, hi);
return (*env)->ExceptionCheck(env) ? 1 : 0;
}
static int report_rates(JNIEnv *env, FormatCollector *coll, int lo, int hi) {
(*env)->CallVoidMethod(env, coll->device, coll->addRatesMid, lo, hi);
return (*env)->ExceptionCheck(env) ? 1 : 0;
}
static int report_pausable(JNIEnv *env, FormatCollector *coll, jboolean pausable) {
(*env)->CallVoidMethod(env, coll->device, coll->setPausableMid, pausable);
return (*env)->ExceptionCheck(env) ? 1 : 0;
}
static int collect_formats(JNIEnv *env, FormatCollector *coll, snd_pcm_t *pcm, jboolean jinput) {
int err;
if ((err = snd_pcm_hw_free(pcm)) < 0
|| (err = snd_pcm_hw_params_any(pcm, hw_params)) < 0) { return err; }
if (snd_pcm_hw_params_test_access(pcm, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) { return 0; }
// Check available formats.
for (snd_pcm_format_t f = 0; f < SND_PCM_FORMAT_LAST; f++) {
if (snd_pcm_hw_params_test_format(pcm, hw_params, f)) { continue; }
bool signd, bigend;
unsigned bips, byps;
if (!split_format(f, &signd, &bips, &byps, &bigend)) { continue; }
if ((err = report_format(env, coll, signd, bips, byps, bigend)) != 0) { return err; }
}
// Check available rates.
unsigned minr, maxr;
if ((err = snd_pcm_hw_params_get_rate_min(hw_params, &minr, NULL)) < 0
|| (err = snd_pcm_hw_params_get_rate_max(hw_params, &maxr, NULL)) < 0) { return err; }
if (minr == maxr) {
// One rate only.
if ((err = report_rates(env, coll, minr, maxr)) != 0) { return err; }
} else if (!snd_pcm_hw_params_test_rate(pcm, hw_params, minr+1, 0)) {
// Looks like it supports any rate.
if ((err = report_rates(env, coll, minr, maxr)) != 0) { return err; }
} else {
// Report min.
if ((err = report_rates(env, coll, minr, minr)) != 0) { return err; }
// Test common rates between min and max.
for (int i = 0; i < sizeof rates / sizeof *rates; i++) {
if (rates[i] <= minr) { continue; }
if (rates[i] >= maxr) { break; }
if (!snd_pcm_hw_params_test_rate(pcm, hw_params, rates[i], 0)) {
if ((err = report_rates(env, coll, rates[i], rates[i])) != 0) { return err; }
}
}
// Report max.
if ((err = report_rates(env, coll, maxr, maxr)) != 0) { return err; }
}
// Check available channels.
unsigned minc, maxc;
if ((err = snd_pcm_hw_params_get_channels_min(hw_params, &minc)) < 0
|| (err = snd_pcm_hw_params_get_channels_max(hw_params, &maxc)) < 0) { return err; }
if (minc == maxc) {
// One channel option only.
if ((err =report_channels(env, coll, minc, maxc)) != 0) { return err; }
} else {
// Loop between min and max to check for gaps, but cut short if max is very large.
int lo = minc;
for (unsigned c = minc+1; c < maxc; c++) {
if (snd_pcm_hw_params_test_channels(pcm, hw_params, c)) {
// c not available, report [lo, c) if lo has value
if (lo > -1) {
if ((err = report_channels(env, coll, lo, c-1)) != 0) { return err; }
lo = -1;
}
} else {
// c available, set lo unless it already has value
if (lo == -1) { lo = c; }
if (c == 10) { break; } // avoid lengthy loop: assume the rest work
}
}
if ((err = report_channels(env, coll, lo > -1 ? lo : maxc, maxc)) != 0) { return err; }
}
// Check if pause supported.
report_pausable(env, coll, report_pausable(env, coll, snd_pcm_hw_params_can_pause(hw_params)));
return 0;
}
// -------------------------------------------------------------------------------------------------
// Alsa initialization method.
#define AMETHOD(name) JNICALL Java_net_avadeaux_klipspringer_alsa_Alsa_ ## name
JNIEXPORT void
AMETHOD(alsaInit) (JNIEnv *env,
jclass jthisClass)
{
if (snd_ctl_card_info_malloc(&card_info)) { init_error("failed to allocate card info record"); }
if (snd_pcm_info_malloc(&pcm_info)) { init_error("failed to allocate PCM info record"); }
if (snd_pcm_hw_params_malloc(&hw_params)) { init_error("failed to allocate hardware params record"); }
}
// -------------------------------------------------------------------------------------------------
// Native AlsaDevice methods
#define METHOD(name) JNICALL Java_net_avadeaux_klipspringer_alsa_AlsaDevice_ ## name
JNIEXPORT void
METHOD(collectDevices) (JNIEnv *env,
jclass jthisclass,
jobject jdevices,
jboolean jinput)
{
DeviceCollector coll = {
jdevices,
(*env)->GetMethodID(env, (*env)->GetObjectClass(env, jdevices), "addDevices", "(Ljava/lang/String;Ljava/lang/String;III)V")
};
if (coll.addDevicesMid == NULL) {
raiseError(env, "failed to get addDevices method");
return;
}
int err, card = -1;
while (true) {
snd_ctl_t *ctl;
if ((err = snd_card_next(&card)) < 0) { raiseError(env, snd_strerror(err)); return; }
if (card == -1) { break; }
char card_id[32];
sprintf(card_id, "hw:%d", card);
if ((err = snd_ctl_open(&ctl, card_id, 0)) < 0) { raiseError(env, snd_strerror(err)); return; }
int err = collect_devices(env, &coll, ctl, card, jinput);
snd_ctl_close(ctl);
if (err < 0) { raiseError(env, snd_strerror(err)); }
if (err) { return; } // exception has been thrown
}
}
JNIEXPORT void
METHOD(collectFormats) (JNIEnv *env,
jclass jthisclass,
jbyteArray jdevname,
jobject jdevice,
jboolean jinput)
{
FormatCollector coll = {
jdevice,
(*env)->GetMethodID(env, (*env)->GetObjectClass(env, jdevice), "addFormat", "(ZIIZ)V"),
(*env)->GetMethodID(env, (*env)->GetObjectClass(env, jdevice), "addRates", "(II)V"),
(*env)->GetMethodID(env, (*env)->GetObjectClass(env, jdevice), "addChannels", "(II)V"),
(*env)->GetMethodID(env, (*env)->GetObjectClass(env, jdevice), "setPausable", "(Z)V")
};
if (coll.addFormatMid == NULL || coll.addRatesMid == NULL || coll.addChannelsMid == NULL || coll.setPausableMid == NULL) {
raiseError(env, "failed to get device methods");
return;
}
jbyte *devname = (*env)->GetByteArrayElements(env, jdevname, NULL);
if (devname == NULL) { raiseError(env, "Failed to allocate device name string"); return; }
snd_pcm_t *pcm;
int err;
if ((err = snd_pcm_open(&pcm, (const char *) devname, jinput ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
raiseErrorCodeException(env, err, snd_strerror(err));
return;
}
err = collect_formats(env, &coll, pcm, jinput);
snd_pcm_close(pcm);
if (err < 0) { raiseErrorCodeException(env, err, snd_strerror(err)); }
if (err) { return; } // exception has been thrown
}
Version: v4.3.2.2 (2026-05-16T17:03:34+02:00)
Raw file
Source code overview
Klipspringer home