// Copyright 2014-2025 Jesper Larsson
//
// This file is part of Klipspringer, <https://klipspringer.eavadeaux.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/>.
package net.avadeaux.klipspringer.alsa;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;
import javax.sound.sampled.*;
import net.avadeaux.klipspringer.codec.*;
class AlsaDevice implements Device {
private static class FormatDetails {
final boolean signed, bigend;
final int ss;
FormatDetails(boolean signed, int ss, boolean bigend) {
this.signed = signed; this.ss = ss; this.bigend = bigend;
}
public String toString() {
return (signed ? "signed, " : "unsigned, ")
+ (bigend ? "big endian, " : "little endian, ")
+ ss + " bytes/sample";
}
}
private static class FormatList extends LinkedList<FormatDetails> { }
private static class DepthMap extends TreeMap<Integer, FormatList> { }
final String name;
private final String description;
private final boolean input;
private final DepthMap depths = new DepthMap();
private final List<String> rates = new ArrayList<String>();
private final List<String> channels = new ArrayList<String>();
private boolean pausable;
private AlsaDevice(String name, String description, boolean input) {
this.name = name;
this.description = description;
this.input = input;
}
boolean pausable() { return pausable; }
public String name() {
return "\"klipalsa:"+name+"\"";
}
public String toString() {
return name()+" ("+description+")";
}
private void addFormat(boolean signed, int bips, int ss, boolean bigend) {
FormatList formats = depths.get(bips);
if (formats == null) { depths.put(bips, (formats = new FormatList())); }
formats.add(new FormatDetails(signed, ss, bigend));
}
private void addRates(int lo, int hi) { rates.add(lo == hi ? lo+"" : lo+"-"+((long) hi & 0xffffffffL)); }
private void addChannels(int lo, int hi) {
if (lo == hi) { channels.add(lo == 1 ? "mono" : "stereo"); }
else if (hi == 2) { channels.add("mono/stereo"); }
else { channels.add(lo+"-"+((long) hi & 0xffffffffL)); }
}
private void setPausable(boolean pausable) { this.pausable = pausable; }
// To allow outputs to scale up bit depth.
int scaledBips(int bips) {
if (!input && depths.get(bips) == null) {
if (bips > 16 && bips < 24 && depths.get(24) != null) { return 24; }
if (bips > 16 && depths.get(32) != null) { return 32; }
if (bips < 8 && depths.get(8) != null) { return 8; }
if (bips < 16 && depths.get(16) != null) { return 16; }
}
return bips;
}
public Device.BitLayout layout(PcmFormat format) {
return scaledBips(format.bips()) > format.bips() ? Device.BitLayout.MSB : Device.BitLayout.LSB;
}
public PcmFormat selectFormat(AudioFormat format) throws UnsupportedFormatException {
boolean signed = format.getEncoding() == AudioFormat.Encoding.PCM_SIGNED;
if (!signed && format.getEncoding() != AudioFormat.Encoding.PCM_UNSIGNED) {
throw new UnsupportedFormatException("Non-PCM format not supported: "+format);
}
int bips = format.getSampleSizeInBits();
int sbips = format instanceof PcmFormat ? ((PcmFormat) format).significantBips() : format.getSampleSizeInBits();
int channels = format.getChannels();
int ss = format.getFrameSize() / channels;
FormatList formats = depths.get(scaledBips(bips));
if (formats == null) { throw new UnsupportedFormatException("Format not supported: "+format); }
FormatDetails r = null;
for (FormatDetails f : formats) {
if (r == null
|| r.ss != ss && f.ss == ss
|| r.signed != signed && f.signed == signed
|| r.bigend != format.isBigEndian() && f.bigend == format.isBigEndian())
{ r = f; }
}
if (r == null) { throw new UnsupportedFormatException("Format not supported: "+format); }
return new PcmFormat((int) format.getSampleRate(), bips, channels, r.signed, channels*r.ss, r.bigend ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN, sbips);
}
public void printAvailableFormats(PrintStream out) {
if (depths.size() > 0) {
out.print(depths.size() == 1 ? " Bit depth: " : " Bit depths: ");
out.println(depths.keySet().stream().map(i -> i+"").collect(Collectors.joining(", ")));
}
if (rates.size() > 0) {
out.print(rates.size() == 1 ? " Sample rate: " : " Sample rates: ");
out.print(rates.stream().collect(Collectors.joining(", ")));
out.println(" Hz");
}
if (channels.size() > 0) {
out.print(" Channels: ");
out.println(channels.stream().collect(Collectors.joining(", ")));
}
}
private static class DeviceCollector {
final Map<String, Device> devices = new LinkedHashMap<String, Device>();
final boolean input;
DeviceCollector(boolean input) { this.input = input; }
void addDevices(String cardName, String devName, int card, int dev, int subdev) {
String devId = dev == -1
? ""+card
: (subdev == -1 ? card+","+dev
: card+","+dev+","+subdev);
String name = cardName+", "+devName;
devices.put("klipalsa:hw:"+devId, new AlsaDevice("hw:"+devId, name, input));
devices.put("klipalsa:plughw:"+devId, new AlsaDevice("plughw:"+devId, name, input));
}
}
static Map<String, Device> collectDevices(boolean input) {
if (!Library.alsaSupported()) { return Collections.emptyMap(); }
DeviceCollector devs = new DeviceCollector(input);
collectDevices(devs, input);
for (var dev : devs.devices.values()) {
AlsaDevice adev = (AlsaDevice) dev;
byte[] devBytes = adev.name.getBytes(StandardCharsets.UTF_8);
try {
collectFormats(Arrays.copyOf(devBytes, devBytes.length+1), adev, input);
} catch (IOException ex) {
// Ignore if cannot open.
if (System.getProperty("klipspringer.debug") != null) { ex.printStackTrace(); }
}
}
return devs.devices;
}
private static native void collectDevices(DeviceCollector devices, boolean input);
private static native void collectFormats(byte[] devName, AlsaDevice device, boolean input) throws IOException;
}
Version: v4.3.2.2 (2026-05-16T17:03:34+02:00)
Raw file
Source code overview
Klipspringer home