# 提供采样音频服务

> 原文： [https://docs.oracle.com/javase/tutorial/sound/SPI-providing-sampled.html](https://docs.oracle.com/javase/tutorial/sound/SPI-providing-sampled.html)

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

`javax.sound.sampled.spi`包中有四个抽象类，表示可以为采样音频系统提供的四种不同类型的服务：

*   [`AudioFileWriter`](https://docs.oracle.com/javase/8/docs/api/javax/sound/sampled/spi/AudioFileWriter.html) 提供声音文件写入服务。这些服务使应用程序可以将音频数据流写入特定类型的文件。
*   [`AudioFileReader`](https://docs.oracle.com/javase/8/docs/api/javax/sound/sampled/spi/AudioFileReader.html) 提供文件读取服务。这些服务使应用程序能够确定声音文件的特征，并获得可以从中读取文件音频数据的流。
*   [`FormatConversionProvider`](https://docs.oracle.com/javase/8/docs/api/javax/sound/sampled/spi/FormatConversionProvider.html) 提供转换音频数据格式的服务。这些服务允许应用程序将音频流从一种数据格式转换为另一种数据格式。
*   [`MixerProvider`](https://docs.oracle.com/javase/8/docs/api/javax/sound/sampled/spi/MixerProvider.html) provides management of a particular kind of mixer. This mechanism allows an application program to obtain information about, and access instances of, a given kind of mixer.

为了概括早期的讨论，服务提供商可以扩展运行时系统的功能。典型的 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`。

## 提供格式转换服务

A `FormatConversionProvider`子类将具有一种音频数据格式的`AudioInputStream`转换为具有另一种格式的格式。前（输入）流被称为 _ 源 _ 流，后者（输出）流被称为 _ 目标 _ 流。回想一下`AudioInputStream`包含`AudioFormat`，而`AudioFormat`又包含特定类型的数据编码，由`AudioFormat.Encoding`对象表示。源流中的格式和编码称为源格式和源编码，目标流中的格式和编码同样称为目标格式和目标编码。

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

`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'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`的新`read`方法中，`AudioInputStream`是`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`返回的对象转换为此类型。

`MixerProvider`的另外两种方法是：

```
abstract Mixer.Info[] getMixerInfo() 

```

和

```
boolean isMixerSupported(Mixer.Info info) 

```

这些方法允许音频系统确定此特定提供程序类是否可以生成应用程序所需的设备。换句话说，`AudioSystem`对象可以迭代所有已安装的`MixerProviders`，以查看哪些（如果有的话）可以为应用程序请求`AudioSystem`的设备供电。 `getMixerInfo`方法返回一个对象数组，其中包含有关此提供程序对象可用的混合器类型的信息。系统可以将这些信息对象以及来自其他提供者的信息对象传递给应用程序。

单个`MixerProvider`可以提供多种混合器。当系统调用`MixerProvider's getMixerInfo`方法时，它会获得一个信息对象列表，用于标识此提供程序支持的不同类型的混合器。然后系统可以调用`MixerProvider.getMixer(Mixer.Info)`以获得每个感兴趣的混频器。

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