提供音频采样服务

如您所知,Java Sound API 包括两个包javax.sound.sampled.spijavax.sound.midi.spi,它们定义了供声音服务开发人员使用的抽象类。通过实现和安装这些抽象类之一的子类,服务提供者可以注册新服务,从而扩展了运行时系统的功能。本页告诉您如何使用javax.sound.sampled.spi包提供新的服务来处理采样的音频。

javax.sound.sampled.spi程序包中有四个抽象类,它们代表可以为采样音频系统提供的四种不同类型的服务:

  • AudioFileWriter提供声音文件编写服务。这些服务使应用程序可以将音频数据流写入特定类型的文件。

  • AudioFileReader提供文件读取服务。这些服务使应用程序能够确定声音文件的特性,并获得可从中读取文件的音频数据的流。

  • FormatConversionProvider提供用于转换音频数据格式的服务。这些服务允许应用程序将音频流从一种数据格式转换为另一种数据格式。

  • MixerProvider提供特定类型混音器的 管理。这种机制允许应用程序获取有关给定类型的混合器的信息并访问其实例。

为了概括前面的讨论,服务提供商可以扩展运行时系统的功能。典型的 SPI 类具有两种类型的方法:一种响应特定提供商提供的服务类型的查询,另一种则直接执行新服务或返回实际提供服务的对象实例。运行时环境的服务提供者机制为音频系统安装的服务提供“注册”,并为新服务提供者类提供“管理”。

本质上,服务实例与应用程序开发人员存在 Double 重隔离。应用程序永远不会直接创建其音频处理任务所需的服务对象实例,例如混合器或格式转换器。该程序甚至也不直接从 管理 它们的 SPI 类中直接请求这些对象。应用程序向javax.sound.sampled包中的AudioSystem对象发出请求,而AudioSystem依次使用 SPI 对象处理这些查询和服务请求。

新音频服务的存在可能对用户和应用程序程序员都是完全透明的。所有应用程序引用都是通过javax.sound.sampled包的标准对象(主要是AudioSystem)进行的,新服务可能提供的特殊处理通常被完全隐藏。

在此讨论中,我们将 continue 以前的约定,即使用诸如AcmeMixerAcmeMixerProvider之类的名称引用新的 SPI 子类。

提供音频文件编写服务

让我们从AudioFileWriter开始,它是较简单的 SPI 类之一。

实现AudioFileWriter的方法的子类必须提供一组方法的实现,以处理有关该类支持的文件格式和文件类型的查询,并提供将提供的音频数据流实际写出到FileOutputStream

AudioFileWriter包括两个在 Base Class 中具有具体实现的方法:

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类由您的子类实际需要实现的六个抽象方法组成,两个不同的重载方法,每个方法都可以带有FileURLInputStream参数。这些重载方法中的第一个方法接受有关指定文件的文件格式的查询:

abstract AudioFileFormat getAudioFileFormat(java.io.File file) 
abstract AudioFileFormat getAudioFileFormat(java.io.InputStream stream) 
abstract AudioFileFormat getAudioFileFormat(java.net.URL url)

getAudioFileFormat方法的典型实现是读取并解析声音文件的 Headers,以确定其文件格式。请参阅 AudioFileFormat 类的描述,以了解需要从 Headers 读取哪些字段,并参考特定文件类型的规范以了解如何解析 Headers。

因为提供流作为此方法的参数的调用者希望该方法不改变该流,所以文件读取器通常应从标记该流开始。读取到 Headers 的末尾后,应将流重置为其原始位置。

另一种重载的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伤口返回到文件数据块的开头(在 Headers 之后),以备读取。但是,可以想象,文件读取器返回AudioInputStream,其音频格式表示从文件中包含的内容以某种方式解码的数据流。重要的是该方法返回一个格式化的流,可以从中读取文件中包含的音频数据。封装在返回的AudioInputStream对象中的AudioFormat将告知调用方流的数据格式,该格式通常但不一定与文件本身中的数据格式相同。

通常,返回的流是AudioInputStream的实例;您不太可能需要继承AudioInputStream

提供格式转换服务

FormatConversionProvider子类将具有一种音频数据格式的AudioInputStream转换为具有另一种格式的AudioInputStream。前一个(Importing)流称为“源”流,后一个(输出)流称为“目标”流。回想一下AudioInputStream包含AudioFormat,而AudioFormat则包含一种特定类型的数据编码,由AudioFormat.Encoding对象表示。源流中的格式和编码称为源格式和源编码,目标流中的格式和编码也称为目标格式和目标编码。

转换工作是在FormatConversionProvider的重载抽象方法getAudioInputStream中执行的。该类还具有抽象查询方法,用于了解所有受支持的目标和源格式以及编码。有一些用于查询特定转换的具体包装方法。

getAudioInputStream的两个变体是:

abstract AudioInputStream getAudioInputStream(AudioFormat.Encoding targetEncoding, 
     AudioInputStream sourceStream)

and

abstract AudioInputStream getAudioInputStream(AudioFormat targetFormat, 
     AudioInputStream sourceStream)

根据调用方是指定完整目标格式还是仅格式的编码,这些参数在第一个参数中有所不同。

getAudioInputStream的典型实现是通过返回AudioInputStream的新子类来工作的,该子类将package原始(源)AudioInputStream并在调用read方法时对其数据应用数据格式转换。例如,考虑一个名为AcmeCodec的新FormatConversionProvider子类的情况,该子类与一个名为AcmeCodecStream的新AudioInputStream子类一起工作。

AcmeCodec's第二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(AudioInputStream的子类)的新read方法中。同样,访问此返回的AcmeCodecStream的应用程序只需将其作为AudioInputStream对其进行操作,而无需了解其实现的详细信息。

FormatConversionProvider的其他方法都允许查询有关对象支持的 Importing 和输出编码以及格式。需要实现以下四种抽象方法:

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对象的工厂。当然,仅当还定义了Mixerinterface的一个或多个新实现时,定义新的MixerProvider才有意义。就像上面的FormatConversionProvider示例一样,我们的getAudioInputStream方法返回了执行转换的AudioInputStream的子类,我们的新类AcmeMixerProvider的方法getMixer返回了另一个实现Mixerinterface的新类的实例。我们将后者称为AcmeMixer。特别是如果混合器是通过硬件实现的,则提供程序可能仅支持所请求设备的一个静态实例。如果是这样,它应该响应getMixer的每次调用而返回此静态实例。

由于AcmeMixer支持Mixerinterface,因此应用程序不需要任何其他信息即可访问其基本功能。但是,如果AcmeMixer支持未在Mixerinterface中定义的功能,并且供应商希望使此扩展功能可被应用程序访问,则混音器当然应定义为带有附加的,记录良好的公共方法的公共类,以便希望利用此扩展功能的程序可以导入AcmeMixer并将getMixer返回的对象转换为该类型。

MixerProvider的其他两种方法是:

abstract Mixer.Info[] getMixerInfo()

and

boolean isMixerSupported(Mixer.Info info)

这些方法允许音频系统确定此特定提供程序类是否可以产生应用程序需要的设备。换句话说,AudioSystem对象可以遍历所有已安装的MixerProviders,以查看哪些对象可以提供应用程序请求的AudioSystem的设备。 getMixerInfo方法返回一个对象数组,其中包含有关此提供程序对象可用的混合器类型的信息。系统可以将这些信息对象以及其他提供程序中的信息对象传递给应用程序。

一个MixerProvider可以提供多种混合器。当系统调用MixerProvider's getMixerInfo方法时,它将获取信息对象列表,这些信息对象标识此提供程序支持的各种混合器。然后,系统可以调用MixerProvider.getMixer(Mixer.Info)以获得每个感兴趣的混合器。

您的子类需要实现getMixerInfo,因为它很抽象。 isMixerSupported方法是具体的,通常不需要重写。默认实现只是将提供的Mixer.InfogetMixerInfo返回的数组中的每个进行比较。