我第一次写 Java MIDI 设备选择时,容易把“设备信息”与“设备实例”混为一谈。实际的枚举入口很简单:先调用 MidiSystem.getMidiDeviceInfo() 得到 MidiDevice.Info[],把每个信息对象展示给用户;确定目标后,再把它交给 MidiSystem.getMidiDevice(info)。这样既能先统计候选数量,也能在真正需要时再判断设备能力。
官方资料:https://docs.oracle.com/en/java/javase/25/docs/api/java.desktop/javax/sound/midi/MidiSystem.html
枚举 MIDI 设备不是直接遍历MidiDevice实例,而是先遍历MidiDevice.Info目录。返回数组长度为 0 时,表示当前 Java MIDI 系统没有可见设备;拿到信息对象后,才用getMidiDevice定位具体设备。
先把候选数量和设备信息分开
这篇文章采用三个可观察基线:候选设备数量、四项元数据是否完整、设备能提供的接收器与发送器上限。它们是 API 字段,不是我在某台机器上测出的固定结果,因此不会把“有几个设备”写成所有环境都一样的结论。
import javax.sound.midi.MidiDevice;
import javax.sound.midi.MidiSystem;
public class MidiDeviceCatalog {
public static void main(String[] args) {
// 先取得信息目录,不在枚举阶段打开任何设备。
MidiDevice.Info[] infos = MidiSystem.getMidiDeviceInfo();
if (infos.length == 0) {
// 空数组是环境结果,不能简单当作 API 调用失败。
System.out.println("没有发现可用的 MIDI 设备");
return;
}
// 每个 Info 代表一个可供 MidiSystem 定位的设备条目。
for (int index = 0; index < infos.length; index++) {
MidiDevice.Info info = infos[index];
System.out.printf("[%d] %s%n", index, info.getName());
}
}
}
这里的关键判断是 infos.length。如果它为零,应先检查操作系统是否安装了 MIDI 设备、驱动是否可用以及当前 Java 运行环境能否看到对应设备,而不是继续对空数组取下标。
读取 Info 的四项元数据
MidiDevice.Info 的职责是描述设备。名称适合做列表主标题,厂商便于区分同名设备,版本和描述用于辅助选择。把这些字段一并展示,往往比只打印一个名称更容易定位“选错设备”的问题。
for (MidiDevice.Info info : infos) {
// 这些字段来自设备信息对象,不代表设备已经打开。
String name = info.getName();
String vendor = info.getVendor();
String version = info.getVersion();
String description = info.getDescription();
// 组合成可记录的目录行,便于后续让用户选择。
System.out.printf("%s | %s | %s | %s%n",
name, vendor, version, description);
}
如果应用要保存用户选择,建议保存稳定的业务标识或让用户在启动时重新确认,而不要只依赖数组下标。设备插拔、驱动变化都可能让枚举顺序发生变化。
从 Info 定位 MidiDevice,再看能力
选择某条目录记录后,再调用 MidiSystem.getMidiDevice(info)。此时得到的是 MidiDevice 实例,可以读取设备信息和能力上限。正如第二张图所示,信息对象承担定位职责,设备实例承担能力查询职责,两者不是同一个层次。
import javax.sound.midi.MidiDevice;
import javax.sound.midi.MidiUnavailableException;
static void printCapabilities(MidiDevice.Info info) {
try {
// 用用户选中的 Info 定位设备实例;资源不足时会抛出异常。
MidiDevice device = MidiSystem.getMidiDevice(info);
// -1 通常表示没有固定上限,不能当成“支持 -1 个”。
System.out.printf("%s: receivers=%d, transmitters=%d%n",
device.getDeviceInfo().getName(),
device.getMaxReceivers(),
device.getMaxTransmitters());
} catch (MidiUnavailableException ex) {
// 设备条目存在不等于此刻一定能取得设备资源。
System.err.println("设备当前不可用:" + ex.getMessage());
}
}
这里不要把 getMaxReceivers() 或 getMaxTransmitters() 当成当前已占用数量;它们描述设备支持的最大连接能力,具体语义要以 MidiDevice 的接口约定为准。拿到实例后,如果后续调用会打开设备或取得接收器、发送器,还要设计对应的关闭责任。
三个边界决定枚举是否可用
我会把结果分成三种状态记录:第一,信息数组为空,说明当前环境没有可见候选;第二,信息对象存在但 getMidiDevice 抛出 MidiUnavailableException,说明设备资源在此刻不可取得;第三,设备存在但接收器或发送器能力不符合业务要求,此时应回到候选清单换设备,而不是把异常吞掉。
| 现象 | 判断 | 处理建议 |
|---|---|---|
infos.length == 0 | 没有可见设备 | 检查设备、驱动和运行环境 |
定位时抛出 MidiUnavailableException | 设备暂时不可取得 | 记录原因并允许重试或换设备 |
| 能力上限不满足 | 候选与业务不匹配 | 按接收器/发送器要求重新筛选 |
因此,最小可靠流程就是“先枚举信息、再显示元数据、最后定位实例”。如果只是做设备选择页面,停在 MidiDevice.Info 层就够了;只有需要连接 MIDI 数据流时,才继续管理 MidiDevice 的打开和关闭。
相关问题
为什么不直接保存 MidiDevice 对象?
设备实例与资源状态有关,插拔或占用变化后可能失效。保存信息用于重新定位更适合启动时的设备选择。
为什么枚举结果为空却没有异常?
getMidiDeviceInfo() 返回空数组是“没有安装或没有被当前环境识别的设备”,与调用本身抛出资源异常是两种情况。
什么时候才需要打开设备?
仅展示设备目录时不需要打开;要取得接收器、发送器或进行实际 MIDI 通信时,再按生命周期打开并关闭相关资源。