Java教程是针对JDK 8编写的。本页中描述的示例和实践不利用后续版本中引入的改进,并可能使用不再可用的技术。
有关Java SE 9及后续版本中更新的语言功能的概述,请参见Java语言更改。
有关所有JDK版本的新功能、增强功能以及已删除或已弃用选项的信息,请参见JDK发行说明。
正如您所知,Java音频API包括两个包:javax.sound.sampled.spi和javax.sound.midi.spi,它们定义了开发声音服务的抽象类。通过实现和安装这些抽象类的子类,服务提供者可以注册新的服务,扩展运行时系统的功能。本页告诉您如何使用javax.sound.sampled.spi包提供处理采样音频的新服务。
javax.sound.sampled.spi包中有四个抽象类,代表您可以为采样音频系统提供的四种不同类型的服务:
AudioFileWriter提供了音频文件写入服务。这些服务使应用程序能够将音频数据流写入特定类型的文件。AudioFileReader提供了文件读取服务。这些服务使应用程序能够确定音频文件的特性,并从中获取音频数据流。FormatConversionProvider提供了音频数据格式转换服务。这些服务允许应用程序将音频流从一种数据格式转换为另一种。MixerProvider提供了特定类型混音器的管理。这个机制允许应用程序获取有关给定类型混音器的信息,并访问相应的实例。
回顾之前的讨论,服务提供者可以扩展运行时系统的功能。典型的SPI类具有两种类型的方法:一种是响应有关特定提供者可用服务类型的查询的方法,另一种是直接执行新服务或返回实际提供服务的对象实例的方法。运行时环境的服务提供者机制提供了已安装服务与音频系统的注册,并管理新的服务提供者类。
本质上,服务实例与应用程序开发人员之间存在双重隔离。应用程序不会直接创建服务对象的实例,例如混音器或格式转换器,以用于其音频处理任务。程序甚至不会直接从管理它们的SPI类请求这些对象。应用程序向javax.sound.sampled包中的AudioSystem对象发送请求,AudioSystem再使用SPI对象来处理这些查询和服务请求。
新音频服务的存在对用户和应用程序员来说可能是完全透明的。所有应用引用都通过javax.sound.sampled包中的标准对象进行,主要是AudioSystem,而新服务可能提供的特殊处理通常是完全隐藏的。
在本讨论中,我们将继续使用前面的约定,用AcmeMixer和AcmeMixerProvider这样的名称来引用新的SPI子类。
让我们从AudioFileWriter开始,这是一个比较简单的SPI类。
实现AudioFileWriter方法的子类必须提供一组方法的实现,用于处理关于该类支持的文件格式和文件类型的查询,以及提供将提供的音频数据流写入File或OutputStream的方法。
AudioFileWriter包括两个在基类中具有具体实现的方法:
boolean isFileTypeSupported(AudioFileFormat.Type fileType) boolean isFileTypeSupported(AudioFileFormat.Type fileType, AudioInputStream stream)
这两个方法中的第一个方法通知调用者,该文件写入器是否可以写入指定类型的声音文件。该方法是一个通用的查询,如果文件写入器可以写入该类型的文件,假设文件写入器被提供适当的音频数据,它将返回true。然而,写入文件的能力可能取决于被传递给文件写入器的具体音频数据的格式。文件写入器可能不支持每种音频数据格式,或者约束可能是由文件格式本身施加的。(并非所有类型的音频数据都可以写入所有类型的声音文件。)第二个方法更具体,它询问特定的AudioInputStream是否可以写入到特定类型的文件。
通常,您不需要重写这两个具体方法。每个方法只是一个调用两个其他查询方法并迭代返回结果的包装器。这另外两个查询方法是抽象的,因此需要在子类中实现:
abstract AudioFileFormat.Type[] getAudioFileTypes() abstract AudioFileFormat.Type[] getAudioFileTypes(AudioInputStream stream)
这些方法直接对应于前面的两个方法。每个方法都返回支持的文件类型的数组-对于第一个方法,返回通常支持的所有类型,对于第二个方法,返回特定音频流支持的所有类型。第一个方法的典型实现可能只是返回由文件写入器的构造函数初始化的数组。第二个方法的实现可能会测试流的AudioFormat对象,以查看它是否是请求的文件类型支持的数据格式。
AudioFileWriter 的最后两个方法执行实际的文件写入工作:
abstract int write(AudioInputStream stream,
AudioFileFormat.Type fileType, java.io.File out)
abstract int write(AudioInputStream stream,
AudioFileFormat.Type fileType, java.io.OutputStream out)
这些方法将表示音频数据的字节流写入到由第三个参数指定的流或文件中。具体的实现方式取决于指定类型文件的结构。 write 方法必须按照该格式的声音文件(无论是标准类型的声音文件还是可能是专有的新文件)的要求,写入文件的头部和音频数据。
AudioFileReader 类由六个抽象方法组成,您的子类需要实现这些方法,实际上有两个不同的重载方法,每个方法都可以接受 File、URL 或 InputStream 参数。这两个重载方法中的第一个接受关于指定文件的文件格式的查询:
abstract AudioFileFormat getAudioFileFormat(java.io.File file) abstract AudioFileFormat getAudioFileFormat(java.io.InputStream stream) abstract AudioFileFormat getAudioFileFormat(java.net.URL url)
getAudioFileFormat 方法的典型实现是读取并解析声音文件的头部,以确定其文件格式。请参阅 AudioFileFormat 类的描述,了解需要从头部读取哪些字段,并参考特定文件类型的规范,以了解如何解析头部。
因为调用者将流作为该方法的参数提供,并希望该方法不对流进行更改,因此文件读取器通常应该从标记流开始。在读取完头部后,它应该将流重置为其原始位置。
另一个重载的 AudioFileReader 方法提供文件读取服务,通过返回一个可以从中读取文件音频数据的 AudioInputStream:
abstract AudioInputStream getAudioInputStream(java.io.File file) abstract AudioInputStream getAudioInputStream(java.io.InputStream stream) abstract AudioInputStream getAudioInputStream(java.net.URL url)
通常,getAudioInputStream 方法的实现将返回一个定位在文件数据块(头部之后)开头的 AudioInputStream,准备好进行读取。不过,文件读取器也可以返回一个表示从文件中所包含的内容解码的数据流的 AudioInputStream。重要的是,该方法返回一个格式化的流,可以从中读取文件中包含的音频数据。返回的 AudioInputStream 对象中封装的 AudioFormat 将向调用者提供有关流的数据格式的信息,这通常与文件本身的数据格式相同,但不一定相同。
一般来说,返回的流是AudioInputStream的实例;你很少需要对AudioInputStream进行子类化。
FormatConversionProvider子类将具有一个音频数据格式的AudioInputStream转换为具有另一种格式的AudioInputStream。前者(输入)流被称为源流,后者(输出)流被称为目标流。回想一下,AudioInputStream包含一个AudioFormat,而AudioFormat又包含一种特定类型的数据编码,由AudioFormat.Encoding对象表示。源流中的格式和编码称为源格式和源编码,目标流中的格式和编码也是如此,称为目标格式和目标编码。
转换的工作是在FormatConversionProvider的一个重载抽象方法getAudioInputStream中完成的。该类还有用于了解所有支持的目标和源格式和编码的抽象查询方法。有具体的包装方法用于查询特定的转换。
abstract AudioInputStream getAudioInputStream(AudioFormat.Encoding targetEncoding,
AudioInputStream sourceStream)
和
abstract AudioInputStream getAudioInputStream(AudioFormat targetFormat,
AudioInputStream sourceStream)
这两个方法的第一个参数不同,根据调用者是指定完整的目标格式还是只指定格式的编码。
getAudioInputStream的一个典型实现是返回一个新的AudioInputStream子类,它包装原始(源)AudioInputStream,并在每次调用read方法时对其数据应用数据格式转换。例如,考虑一个名为AcmeCodec的新的FormatConversionProvider子类,它与一个名为AcmeCodecStream的新的AudioInputStream子类一起工作。
AcmeCodec的第二个getAudioInputStream方法的实现可能是:
public AudioInputStream getAudioInputStream
(AudioFormat outputFormat, AudioInputStream stream) {
AudioInputStream cs = null;
AudioFormat inputFormat = stream.getFormat();
if (inputFormat.matches(outputFormat) ) {
cs = stream;
} else {
cs = (AudioInputStream)
(new AcmeCodecStream(stream, outputFormat));
tempBuffer = new byte[tempBufferSize];
}
return cs;
}
实际的格式转换是在返回的AcmeCodecStream的新read方法中进行的,AcmeCodecStream是AudioInputStream的子类。同样,访问返回的AcmeCodecStream的应用程序只需将其作为AudioInputStream操作,无需了解其实现的详细信息。
FormatConversionProvider的其他方法允许查询对象支持的输入和输出编码和格式。以下四个方法需要被实现:
abstract AudioFormat.Encoding[] getSourceEncodings()
abstract AudioFormat.Encoding[] getTargetEncodings()
abstract AudioFormat.Encoding[] getTargetEncodings(
AudioFormat sourceFormat)
abstract AudioFormat[] getTargetFormats(
AudioFormat.Encoding targetEncoding,
AudioFormat sourceFormat)
与上面讨论的AudioFileReader类的查询方法一样,这些查询通常通过检查对象的私有数据来处理,并且对于后两个方法,将它们与参数进行比较。
剩下的四个FormatConversionProvider方法是具体的,通常不需要重写:
boolean isConversionSupported(
AudioFormat.Encoding targetEncoding,
AudioFormat sourceFormat)
boolean isConversionSupported(AudioFormat targetFormat,
AudioFormat sourceFormat)
boolean isSourceEncodingSupported(
AudioFormat.Encoding sourceEncoding)
boolean isTargetEncodingSupported(
AudioFormat.Encoding targetEncoding)
与AudioFileWriter.isFileTypeSupported()一样,这些方法的默认实现本质上是一个包装器,调用其中一个查询方法并迭代返回的结果。
如其名称所示,MixerProvider提供混音器的实例。每个具体的MixerProvider子类都充当应用程序使用的Mixer对象的工厂。当然,只有在定义了一个或多个Mixer接口的新实现时,定义新的MixerProvider才有意义。就像上面的FormatConversionProvider示例中,我们的getAudioInputStream方法返回了一个执行转换的AudioInputStream子类一样,我们的新类AcmeMixerProvider有一个getMixer方法,返回实现Mixer接口的另一个新类的实例。我们将后者称为AcmeMixer。特别是如果混音器是在硬件中实现的,提供者可能只支持请求设备的一个静态实例。如果是这样,它应该在每次调用getMixer时返回该静态实例。
由于AcmeMixer支持Mixer接口,应用程序不需要任何其他信息来访问其基本功能。然而,如果AcmeMixer支持Mixer接口中未定义的功能,并且供应商希望将此扩展功能提供给应用程序,那么混音器当然应该被定义为一个公共类,并具有额外的、良好文档化的公共方法,以便希望使用此扩展功能的程序可以导入AcmeMixer并将getMixer返回的对象转换为此类型。
abstract Mixer.Info[] getMixerInfo()
和
boolean isMixerSupported(Mixer.Info info)
这些方法允许音频系统确定此特定提供程序类是否可以生成应用程序需要的设备。换句话说,AudioSystem对象可以迭代所有已安装的MixerProviders,查看哪些(如果有)可以提供应用程序所请求的AudioSystem设备。 getMixerInfo方法返回一个包含有关此提供程序对象可用的混音器类型的信息对象数组。系统可以将这些信息对象与其他提供程序的信息对象一起传递给应用程序。
单个MixerProvider可以提供多种类型的混音器。当系统调用MixerProvider的getMixerInfo方法时,它会得到一个标识此提供程序支持的不同类型混音器的信息对象列表。然后,系统可以调用MixerProvider.getMixer(Mixer.Info)来获取每个感兴趣的混音器。
您的子类需要实现getMixerInfo,因为它是抽象的。 isMixerSupported方法是具体的,通常不需要覆盖。默认实现只是将提供的Mixer.Info与getMixerInfo返回的数组中的每个对象进行比较。