Java教程是为JDK 8编写的。本页面描述的示例和实践不利用后续版本中引入的改进,可能使用已不再可用的技术。
有关Java SE 9及后续版本中更新的语言功能的摘要,请参阅Java语言更改。
有关所有JDK版本的新功能、增强功能以及已删除或弃用选项的信息,请参阅JDK发布说明。
Service Provider Interfaces简介解释了javax.sound.sampled.spi
和javax.sound.midi.spi
包定义了开发声音服务的抽象类。通过实现这些抽象类的子类,服务提供商可以创建一个扩展运行时系统功能的新服务。前一节介绍了如何使用javax.sound.sampled.spi
包。本节讨论如何使用javax.sound.midi.spi
包提供处理MIDI设备和文件的新服务。
javax.sound.midi.spi
包中有四个抽象类,代表了可以为MIDI系统提供的四种不同类型的服务:
MidiFileWriter
提供了MIDI文件写入服务。这些服务使得应用程序能够将自己生成或处理的MIDI Sequence
保存到MIDI文件中。MidiFileReader
提供了文件读取服务,从MIDI文件中返回一个MIDI Sequence
供应用程序使用。MidiDeviceProvider
提供一个或多个特定类型的MIDI设备实例,可能包括硬件设备。SoundbankReader
提供了声音库文件读取服务。SoundbankReader
的具体子类解析给定的声音库文件,生成一个可以加载到Synthesizer
中的Soundbank
对象。应用程序不会直接创建服务对象的实例,无论是提供者对象,如MidiDeviceProvider
,还是由提供者对象提供的对象,如Synthesizer
。程序也不会直接引用SPI类。相反,应用程序通过javax.sound.midi
包中的MidiSystem
对象发出请求,MidiSystem
再使用javax.sound.midi.spi
类的具体子类来处理这些请求。
有三种标准的MIDI文件格式,Java Sound API的实现可以支持:Type 0、Type 1和Type 2。这些文件格式在文件中的MIDI序列数据的内部表示上有所不同,并且适用于不同类型的序列。如果一个实现本身不支持所有三种类型,服务提供商可以提供对未实现类型的支持。还有一些标准MIDI文件格式的变种,其中一些是专有的,同样可以由第三方供应商提供支持。
编写MIDI文件的能力由MidiFileWriter
的具体子类提供。这个抽象类与javax.sampled.spi.AudioFileWriter
直接对应。同样,这些方法被分组为查询方法和实际写入文件的方法。与AudioFileWriter
一样,其中两个查询方法是具体的:
boolean isFileTypeSupported(int fileType) boolean isFileTypeSupported(int fileType, Sequence sequence)
第一个方法提供了关于文件写入程序是否可以写入指定类型的MIDI文件类型的一般信息。第二个方法更具体:它询问是否可以将特定的Sequence写入指定类型的MIDI文件。通常情况下,您不需要覆盖这两个具体方法中的任何一个。在默认实现中,每个方法调用了两个其他相应的查询方法之一,并迭代返回的结果。作为抽象方法,这另外两个查询方法需要在子类中实现:
abstract int[] getMidiFileTypes() abstract int[] getMidiFileTypes(Sequence sequence)
第一个方法返回支持的所有文件类型的数组。典型的实现可能会在文件写入程序的构造函数中初始化数组,并从该方法返回数组。从文件类型集合中,第二个方法找到文件写入程序可以将给定的Sequence写入的子集。根据MIDI规范,不是所有类型的序列都可以写入所有类型的MIDI文件。
MidiFileWriter
子类的write
方法将给定的Sequence
中的数据编码为请求的MIDI文件类型的正确数据格式,并将编码的流写入文件或输出流:
abstract int write(Sequence in, int fileType, java.io.File out) abstract int write(Sequence in, int fileType, java.io.OutputStream out)
为了做到这一点,write
方法必须通过迭代其轨道来解析Sequence
,构建适当的文件头,并将文件头和轨道写入输出。当然,MIDI文件的头格式是由MIDI规范定义的。它包括一些信息,如一个标识此文件为MIDI文件的"魔数",头的长度,轨道数和序列的定时信息(分割类型和分辨率)。MIDI文件的其余部分由MIDI规范定义的格式的轨道数据组成。
让我们简要地看一下应用程序、MIDI系统和服务提供商在编写MIDI文件时如何协作。在典型情况下,应用程序有一个特定的MIDI Sequence
要保存到文件中。程序在尝试写入文件之前,会查询MidiSystem
对象,查看是否支持特定Sequence
的任何MIDI文件格式。 MidiSystem.getMidiFileTypes(Sequence)
方法返回一个数组,该数组包含系统可以写入特定序列的所有MIDI文件类型。它通过调用每个已安装的MidiFileWriter
服务的相应getMidiFileTypes
方法来实现,然后收集并返回一个整数数组,可以将其视为与给定Sequence
兼容的所有文件类型的主列表。当要将Sequence
写入文件时,调用MidiSystem.write
时传递一个表示文件类型的整数,以及要写入和输出文件的Sequence
;MidiSystem
使用提供的类型来决定哪个已安装的MidiFileWriter
应处理写入请求,并将相应的write
分派给适当的MidiFileWriter
。
MidiFileReader
抽象类与javax.sampled.spi.AudioFileReader
类直接对应。两者都包含两个重载方法,每个方法都可以接受File
、URL
或InputStream
参数。重载方法中的第一个方法返回指定文件的文件格式。对于MidiFileReader
,API如下:
abstract MidiFileFormat getMidiFileFormat(java.io.File file) abstract MidiFileFormat getMidiFileFormat( java.io.InputStream stream) abstract MidiFileFormat getMidiFileFormat(java.net.URL url)
具体的子类必须实现这些方法,以返回一个填充完整的MidiFileFormat
对象,描述指定的MIDI文件(或流或URL)的格式,假设该文件是文件读取器支持的类型,并且包含有效的头信息。否则,应抛出InvalidMidiDataException
异常。
另一个重载方法从给定的文件、流或URL返回一个MIDI Sequence
:
abstract Sequence getSequence(java.io.File file) abstract Sequence getSequence(java.io.InputStream stream) abstract Sequence getSequence(java.net.URL url)
getSequence
方法执行实际的工作,解析MIDI输入文件中的字节并构建相应的Sequence
对象。这基本上是MidiFileWriter.write
使用的过程的逆过程。由于根据MIDI规范定义的MIDI文件的内容与Java Sound API定义的Sequence
对象之间存在一对一的对应关系,解析的细节是直接的。如果传递给getSequence
的文件包含文件读取器无法解析的数据(例如,因为文件已损坏或不符合MIDI规范),应抛出InvalidMidiDataException
异常。
一个MidiDeviceProvider可以被视为提供一个或多个特定类型的MIDI设备的工厂。该类由一个方法组成,该方法返回一个MIDI设备的实例,以及查询方法来了解该提供程序可以提供哪些类型的设备。
与其他javax.sound.midi.spi服务一样,应用程序开发人员通过调用MidiSystem方法间接访问MidiDeviceProvider服务,在这种情况下是MidiSystem.getMidiDevice和MidiSystem.getMidiDeviceInfo。子类化MidiDeviceProvider的目的是提供一种新的设备,因此服务开发人员还必须为返回的设备创建一个相应的类,就像我们在javax.sound.sampled.spi包中看到的MixerProvider一样。在那里,返回的设备类实现了javax.sound.sampled.Mixer接口;在这里,它实现了javax.sound.midi.MidiDevice接口。它还可以实现MidiDevice的子接口,如Synthesizer或Sequencer。
因为MidiDeviceProvider的单个子类可以提供多种类型的MidiDevice,所以类的getDeviceInfo方法返回一个枚举不同MidiDevices的MidiDevice.Info对象数组:
abstract MidiDevice.Info[] getDeviceInfo()
返回的数组当然可以包含一个元素。提供程序的典型实现可能会在其构造函数中初始化一个数组,并在这里返回它。这样,MidiSystem可以迭代所有已安装的MidiDeviceProvider来构建所有已安装设备的列表。然后,MidiSystem可以将此列表(MidiDevice.Info[]数组)返回给应用程序。
MidiDeviceProvider还包括一个具体的查询方法:
boolean isDeviceSupported(MidiDevice.Info info)
此方法允许系统查询提供程序关于特定类型的设备的信息。通常情况下,您不需要重写此便利方法。默认实现会遍历由getDeviceInfo返回的数组,并将参数与每个元素进行比较。
第三个也是最后一个MidiDeviceProvider方法返回请求的设备:
abstract MidiDevice getDevice(MidiDevice.Info info)
此方法应首先测试参数,确保它描述的是此提供程序可以提供的设备。如果不是,它应该抛出一个IllegalArgumentException异常。否则,它将返回该设备。
SoundBank
是一组可以加载到 Synthesizer
中的 Instruments
。 Instrument
是一个声音合成算法的实现,用于产生特定类型的声音,并包含相应的名称和信息字符串。 SoundBank
大致对应于MIDI规范中的银行,但它是一个更广泛和可寻址的集合;可以将其视为MIDI银行的集合。
SoundbankReader
包含一个重载的方法,系统调用该方法从音频文件中读取 Soundbank
对象:
abstract Soundbank getSoundbank(java.io.File file) abstract Soundbank getSoundbank(java.io.InputStream stream) abstract Soundbank getSoundbank(java.net.URL url)
SoundbankReader
的具体子类将与特定提供者定义的 SoundBank
、Instrument
和 Synthesizer
实现配合工作,以允许系统从文件中加载 SoundBank
到特定 Synthesizer
类的实例中。合成技术可能因一个 Synthesizer
而异,因此存储在 Instrument
或 SoundBank
中的控制或规范数据的形式可以各不相同。一种合成技术可能只需要几个字节的参数数据,而另一种则基于大量的声音样本。一个 SoundBank
中的资源将取决于它们加载到的 Synthesizer
的性质,因此 SoundbankReader
子类的 getSoundbank
方法的实现可以访问特定类型的 SoundBank
的相关信息。此外,SoundbankReader
的特定子类了解存储 SoundBank
数据的特定文件格式。该文件格式可能是特定供应商的专有格式。
SoundBank
只是一个接口,对 SoundBank
对象的内容只有弱约束。要实现此接口,对象必须支持的方法(getResources
、getInstruments
、getVendor
、getName
等)对对象包含的数据有宽松的要求。例如,getResources
和 getInstruments
可以返回空数组。子类化的 SoundBank
对象的实际内容,特别是其乐器和非乐器资源,由服务提供商定义。因此,解析音频文件的机制完全取决于该特定类型的音频文件的规范。
声音库文件是在Java Sound API之外创建的,通常由能够加载此类声音库的合成器供应商创建。一些供应商可能会提供用于创建这些文件的最终用户工具。