JavaSoundCaptureDeviceDiscovery.java
package org.hammer.audio;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Mixer;
import org.hammer.audio.acquisition.CaptureDeviceDescriptor;
/** Discovers JavaSound input mixers without leaking JavaSound types into the acquisition domain. */
public final class JavaSoundCaptureDeviceDiscovery {
private final MixerCatalog catalog;
/** Create discovery backed by the local JavaSound runtime. */
public JavaSoundCaptureDeviceDiscovery() {
this(new SystemMixerCatalog());
}
JavaSoundCaptureDeviceDiscovery(MixerCatalog catalog) {
this.catalog = Objects.requireNonNull(catalog, "catalog");
}
/** Return input-capable devices in deterministic descriptor order. */
public List<CaptureDeviceDescriptor> discover() {
List<CaptureDeviceDescriptor> devices = new ArrayList<>();
for (Mixer.Info info : catalog.infos()) {
if (catalog.inputCapable(info)) {
devices.add(descriptor(info));
}
}
devices.sort(Comparator.comparing(CaptureDeviceDescriptor::deviceId));
return List.copyOf(devices);
}
/** Resolve one previously discovered device id to its JavaSound mixer identity. */
public Optional<Mixer.Info> findMixerInfo(String deviceId) {
if (deviceId == null || deviceId.isBlank()) {
return Optional.empty();
}
for (Mixer.Info info : catalog.infos()) {
if (deviceId.equals(descriptor(info).deviceId()) && catalog.inputCapable(info)) {
return Optional.of(info);
}
}
return Optional.empty();
}
private static CaptureDeviceDescriptor descriptor(Mixer.Info info) {
return new CaptureDeviceDescriptor(
stableId(info), info.getName(), info.getVendor(), info.getDescription(), info.getVersion());
}
private static String stableId(Mixer.Info info) {
String identity =
String.join(
"\u0000", info.getName(), info.getVendor(), info.getDescription(), info.getVersion());
String encoded =
Base64.getUrlEncoder()
.withoutPadding()
.encodeToString(identity.getBytes(StandardCharsets.UTF_8));
return "java-sound:" + encoded;
}
interface MixerCatalog {
List<Mixer.Info> infos();
boolean inputCapable(Mixer.Info info);
}
private static final class SystemMixerCatalog implements MixerCatalog {
@Override
public List<Mixer.Info> infos() {
return List.of(AudioSystem.getMixerInfo());
}
@Override
public boolean inputCapable(Mixer.Info info) {
return AudioSystem.getMixer(info).getTargetLineInfo().length > 0;
}
}
}