# 采样包概述

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

[`javax.sound.sampled`](https://docs.oracle.com/javase/8/docs/api/javax/sound/sampled/package-summary.html) 软件包基本上与音频传输有关 - 换句话说，Java Sound API 专注于播放和捕获。 Java Sound API 解决的中心任务是如何将格式化音频数据的字节移入和移出系统。此任务涉及打开音频输入和输出设备以及管理充满实时声音数据的缓冲区。它还可以涉及将多个音频流混合到一个流中（无论是输入还是输出）。当用户请求启动，暂停，恢复或停止声音流时，必须正确地处理声音进出系统的传输。

为了支持对基本音频输入和输出的关注，Java Sound API 提供了在各种音频数据格式之间进行转换以及读取和编写常见类型的声音文件的方法。但是，它并不试图成为一个全面的声音文件工具包。 Java Sound API 的特定实现不需要支持大量的文件类型或数据格式转换。第三方服务提供商可以提供“插入”现有实现的模块，以支持其他文件类型和转换。

Java Sound API 可以以流，缓冲方式和内存中无缓冲方式处理音频传输。这里使用“流”来概括地指代音频字节的实时处理;它并不是指以特定格式通过 Internet 发送音频的具体，众所周知的情况。换句话说，音频流只是一组连续的音频字节，它们或多或少地以与它们要处理（播放，记录等）相同的速率到达。在所有数据到达之前开始对字节的操作。在流媒体模型中，特别是在音频输入而不是音频输出的情况下，您不一定事先知道声音的持续时间以及声音何时到达。您只需一次处理一个音频数据缓冲区，直到操作停止。在音频输出（播放）的情况下，如果您要播放的声音太大而无法一次装入内存，您还需要缓冲数据。换句话说，您将音频字节以块的形式传送到声音引擎，并负责在适当的时间播放每个样本。提供了一些机制，可以很容易地知道每个块中要传递多少数据。

Java Sound API 还允许在仅播放的情况下进行无缓冲传输，假设您已经掌握了所有音频数据，并且它不会太大而无法放入内存中。在这种情况下，应用程序不需要缓冲音频，尽管如果需要，缓冲的实时方法仍然可用。相反，可以将整个声音立即预加载到存储器中以供后续播放。由于所有声音数据都是预先加载的，因此可以立即开始播放 - 例如，只要用户单击“开始”按钮即可。与缓冲模型相比，这可能是一个优点，其中回放必须等待第一个缓冲区填充。此外，内存中的无缓冲模型允许声音轻松循​​环（循环）或设置为数据中的任意位置。

要使用 Java Sound API 播放或捕获声音，您至少需要三件事：格式化音频数据，混音器和线路。以下概述了这些概念。

格式化音频数据是指多种标准格式中的任何一种的声音。 Java Sound API 区分 _ 数据格式 _ 和 _ 文件格式 _。

### 数据格式

数据格式告诉您如何解释“原始”采样音频数据的一系列字节，例如已经从声音文件读取的样本，或者从麦克风输入捕获的样本。例如，您可能需要知道一个样本有多少位（声音最短时刻的表示），同样您可能需要知道声音的采样率（样本应该跟随彼此的速度）。设置播放或捕捉时，您可以指定要捕捉或播放的声音的数据格式。

在 Java Sound API 中，数据格式由 [`AudioFormat`](https://docs.oracle.com/javase/8/docs/api/javax/sound/sampled/AudioFormat.html) 对象表示，其中包含以下属性：

*   编码技术，通常是脉冲编码调制（PCM）
*   通道数量（1 为单声道，2 为立体声等）
*   采样率（每通道每秒采样数）
*   每个样本的位数（每个通道）
*   帧率
*   帧大小，以字节为单位
*   字节顺序（big-endian 或 little-endian）

PCM 是声音波形的一种编码。 Java Sound API 包括两个使用线性量化幅度的 PCM 编码，以及有符号或无符号整数值。线性量化意味着存储在每个样本中的数字与该时刻的原始声压成正比（除了任何失真），并且类似地与在该瞬间随声音振动的扬声器或鼓膜的位移成比例。例如，光盘使用线性 PCM 编码声音。 Mu-law 编码和 a-law 编码是常见的非线性编码，它们提供更加压缩的音频数据版本;这些编码通常用于电话或语音录音。非线性编码使用非线性函数将原始声音的幅度映射到存储值，该非线性函数可以设计为对于安静的声音而不是对响亮的声音给出更大的幅度分辨率。

一个帧包含特定时间内所有通道的数据。对于 PCM 编码数据，帧是所有通道中同时采样的集合，对于给定的时刻，没有任何附加信息。在这种情况下，帧速率等于采样速率，帧大小（以字节为单位）是通道数乘以采样大小（以位为单位）除以字节中的位数。

对于其他类型的编码，帧可能包含除样本之外的其他信息，帧速率可能与采样率完全不同。例如，考虑 MP3（MPEG-1 Audio Layer 3）编码，该编码在当前版本的 Java Sound API 中未明确提及，但可由 Java Sound API 的实现或第三方支持服务提供者。在 MP3 中，每帧包含一系列压缩数据，用于一系列样本，而不是每个通道一个样本。由于每个帧封装了一系列样本，因此帧速率低于采样率。该框架还包含一个标题。尽管有标题，但是以字节为单位的帧大小小于相等数量的 PCM 帧的字节大小。 （毕竟，MP3 的目的是比 PCM 数据更紧凑。）对于这样的编码，采样率和样本大小是指编码声音在被传送到数字之前最终将被转换成的 PCM 数据 - 模拟转换器（DAC）。

### 文件格式

文件格式指定声音文件的结构，不仅包括文件中原始音频数据的格式，还包括可以存储在文件中的其他信息。声音文件有各种标准类型，例如 WAVE（也称为 WAV，通常与 PC 相关），AIFF（通常与 Macintoshes 相关联）和 AU（通常与 UNIX 系统相关联）。不同类型的声音文件具有不同的结构。例如，它们可能在文件的“标题”中具有不同的数据排列。标题包含通常在文件的实际音频样本之前的描述性信息，尽管某些文件格式允许描述性和音频数据的连续“块”。标题包括用于在声音文件中存储音频的数据格式的规范。这些类型的声音文件中的任何一种都可以包含各种数据格式（尽管通常在给定文件中只有一种数据格式），并且可以在具有不同文件格式的文件中使用相同的数据格式。

在 Java Sound API 中，文件格式由 [`AudioFileFormat`](https://docs.oracle.com/javase/8/docs/api/javax/sound/sampled/AudioFileFormat.html) 对象表示，其中包含：

*   文件类型（WAVE，AIFF 等）
*   文件的长度（以字节为单位）
*   文件中包含的音频数据的长度（以帧为单位）
*   一个 AudioFormat 对象，它指定文件中包含的音频数据的数据格式

[`AudioSystem`](https://docs.oracle.com/javase/8/docs/api/javax/sound/sampled/AudioSystem.html) 类提供了以不同文件格式读取和写入声音以及在不同数据格式之间进行转换的方法。有些方法允许您通过一种称为 [`AudioInputStream`](https://docs.oracle.com/javase/8/docs/api/javax/sound/sampled/AudioInputStream.html) 的流来访问文件的内容。 `AudioInputStream`是 [`InputStream`](https://docs.oracle.com/javase/8/docs/api/java/io/InputStream.html) 类的子类，它封装了一系列可以顺序读取的字节。对于它的超类，`AudioInputStream`类添加了字节'音频数据格式的知识（由`AudioFormat`对象表示）。通过将声音文件作为`AudioInputStream`读取，您可以立即访问样本，而无需担心声音文件的结构（其标题，块等）。单个方法调用为您提供有关数据格式和文件类型的所有信息。

## 什么是搅拌机？

许多声音应用程序编程接口（API）都使用了音频 _ 设备 _ 的概念。设备通常是物理输入/输出设备的软件接口。例如，声音输入设备可以表示声卡的输入能力，包括麦克风输入，线路级模拟输入以及可能的数字音频输入。

在 Java Sound API 中，设备由 [`Mixer`](https://docs.oracle.com/javase/8/docs/api/javax/sound/sampled/Mixer.html) 对象表示。混频器的目的是处理一个或多个音频输入流和一个或多个音频输出流。在典型情况下，它实际上将多个传入流混合到一个传出流中。 `Mixer`对象可以表示物理设备（例如声卡）的声音混合功能，其可能需要将来自各种输入的来自计算机的声音混合，或者来自应用程序并进入输出的声音。

或者，`Mixer`对象可以表示完全用软件实现的混音功能，而不需要任何物理设备的固有接口。

在 Java Sound API 中，声卡上的麦克风输入等组件本身不被视为设备 - 即混音器 - 而是 _ 端口 _ 进出的搅拌机。端口通常将单个音频流输入或输出混音器（尽管该流可以是多声道的，例如立体声）。混音器可能有几个这样的端口。例如，表示声卡输出能力的混音器可能将几个音频流混合在一起，然后将混合信号发送到连接到混音器的各个输出端口中的任何一个或全部。这些输出端口可以是（例如）耳机插孔，内置扬声器或线路电平输出。

为了理解 Java Sound API 中混音器的概念，它有助于可视化物理混音控制台，例如现场音乐会和录音棚中使用的控制台。

<center>![The following context describes this figure.](img/cc66c021ba6fd4b7845c08dd34f66ccb.jpg)

物理混合控制台

</center>

物理混合器具有“条带”（也称为“条带”），每个条带表示单个音频信号通过其进入混合器以进行处理的路径。条带有旋钮和其他控件，您可以通过它们控制该条带中的信号的音量和平移（放置在立体图像中）。此外，混音器可能有一个独立的总线用于混响等效果，此总线可以连接到内部或外部混响单元。每个条带都有一个电位计，可以控制条带信号进入混响混音的程度。然后将混响（“湿”）混合物与来自条带的“干”信号混合。物理混合器将该最终混合物发送到输出总线，该输出总线通常进入磁带录音机（或基于磁盘的记录系统）和/或扬声器。

想象一下正在以立体声录制的现场音乐会。来自舞台上许多麦克风和电子乐器的电缆（或无线连接）插入调音台的输入端。如图所示，每个输入都进入混合器的单独条带。音响工程师决定增益，声像和混响控制的设置。所有条带和混响单元的输出混合在一起成为两个通道。这两个通道连接到调音台上的两个输出，插入的电缆连接到立体声录音机的输入。这两个声道也可能通过放大器发送到大厅的扬声器，具体取决于音乐的类型和大厅的大小。

现在想象一下录音室，其中每个乐器或歌手都被录制到多轨录音机的单独轨道上。在录制了乐器和歌手之后，录音工程师执行“混音”以将所有录音轨道组合成可以分布在光盘上的双声道（立体声）录音。在这种情况下，每个调音台条带的输入不是麦克风，而是多轨录音的一个轨道。再一次，工程师可以使用条带上的控件来确定每个音轨的音量，声像和混响量。调音台的输出再次输入立体声录音机和立体声扬声器，如现场音乐会的例子。

这两个例子说明了混音器的两种不同用途：捕捉多个输入通道，将它们组合成更少的音轨，保存混音，或播放多个音轨，同时将它们混合到更少的音轨。

在 Java Sound API 中，混音器可以类似地用于输入（捕获音频）或输出（播放音频）。在输入的情况下，混合器从其获得用于混合的音频的 _ 源 _ 是一个或多个输入端口。混合器将捕获的和混合的音频流发送到其 _ 目标 _，该目标是具有缓冲区的对象，应用程序可以从该缓冲区检索该混合音频数据。在音频输出的情况下，情况是相反的。混音器的音频源是一个或多个包含缓冲区的对象，一个或多个应用程序将声音数据写入其中;混音器的目标是一个或多个输出端口。

## 什么是线？

物理混音控制台的比喻对于理解 Java Sound API 的 _ 行 _ 概念也很有用。

线是数字音频“流水线”的一个元素，即用于将音频移入或移出系统的路径。通常，线路是进出混频器的路径（虽然从技术上讲，混频器本身也是一种线路）。

音频输入和输出端口是线路。这些类似于连接到物理调音台的麦克风和扬声器。另一种线是数据路径，应用程序可以通过该数据路径从混频器获得输入音频或将输出音频发送到混频器。这些数据路径类似于连接到物理混音控制台的多轨录音机的音轨。

Java Sound API 中的行与物理混音器中的行之间的一个区别是，流经 Java Sound API 中的行的音频数据可以是单声道或多声道（例如，立体声）。相比之下，物理混音器的每个输入和输出通常是单声道声音。为了从物理混频器获得两个或更多通道的输出，通常使用两个或更多个物理输出（至少在模拟声音的情况下;数字输出插孔通常是多通道的）。在 Java Sound API 中，行中的通道数由当前流经该行的数据的 [`AudioFormat`](https://docs.oracle.com/javase/8/docs/api/javax/sound/sampled/AudioFormat.html) 指定。

现在让我们来看看一些特定种类的线条和混音器。下图显示了简单音频输出系统中不同类型的行，它们可以是 Java Sound API 实现的一部分：

<center>![The following context describes this figure.](img/1ce65a06de41ed2e8567883ef93424aa.jpg)

音频输出线的可能配置

</center>

在此示例中，应用程序可以访问音频输入混音器的一些可用输入：一个或多个 _ 剪辑 _ 和 _ 源数据行 _。剪辑是一种混音器输入（一种线路），您可以在播放前加载音频数据;源数据线是接收实时音频数据流的混合器输入。应用程序将声音文件中的音频数据预加载到剪辑中。然后它将其他音频数据推送到源数据线，一次一个缓冲区。混音器从所有这些线路读取数据，每个线路可能有自己的混响，增益和声像控制，并将干音频信号与湿（混响）混音混合。调音台将其最终输出传送到一个或多个输出端口，例如扬声器，耳机插孔和线路输出插孔。

虽然图中各条线被描绘为单独的矩形，但它们全部由混合器“拥有”，并且可以被认为是混合器的组成部分。混响，增益和平移矩形表示混频器可以应用于流经线路的数据的处理控制（而不是线路）。

请注意，这只是 API 支持的混音器的一个示例。并非所有音频配置都具有所示的所有功能。单个源数据行可能不支持平移，混音器可能不支持实现混响，等等。

简单的音频输入系统可能类似：

<center>![The following context describes this figure](img/4cb4e85642a1adb1b468606ba7f97b4b.jpg)

音频输入线的可能配置

</center>

这里，数据从一个或多个输入端口流入混频器，通常是麦克风或线路输入插孔。应用增益和平移，混音器通过调音台的目标数据线将捕获的数据传送到应用程序。目标数据线是混合器输出，包含流输入声音的混合。最简单的混频器只有一条目标数据线，但有些混频器可以同时将捕获的数据传送到多条目标数据线。

现在我们已经看到了一些关于线和混音器的功能图片，让我们从稍微程序化的角度来讨论它们。几种类型的线由基本 [`Line`](https://docs.oracle.com/javase/8/docs/api/javax/sound/sampled/Line.html) 接口的子接口定义。接口层次结构如下所示。

<center>![The following context describes this figure](img/067b588c12115e5aed0badd19b17f30c.jpg)

线路接口层次结构

</center>

基本接口 [`Line`](https://docs.oracle.com/javase/8/docs/api/javax/sound/sampled/Line.html) 描述了所有行共有的最小功能：

*   控件 - 数据线和端口通常有一组控件，这些控件会影响通过线路的音频信号。 Java Sound API 指定可用于操纵声音方面的控制类，例如：增益（以分贝为单位影响信号的音量），平移（影响声音的左右定位，混响（增加声音的混响）模拟不同类型的室内声学）和采样率（影响播放速率以及声音的音高）。
*   打开或关闭状态 - 成功打开一行确保资源已分配给该行。混音器具有有限数量的线，因此在某些时候，多个应用程序（或相同的）可能会争夺混音器线的使用。关闭一行表示现在可以释放该行使用的任何资源。
*   Events – A line generates events when it opens or closes. Subinterfaces of `Line` can introduce other types of events. When a line generates an event, the event is sent to all objects that have registered to "listen" for events on that line. An application program can create these objects, register them to listen for line events, and react to the events as desired.

我们现在将检查`Line`接口的子接口。

[`Ports`](https://docs.oracle.com/javase/8/docs/api/javax/sound/sampled/Port.html) 是用于向音频设备输入音频或从音频设备输出音频的简单线路。如前所述，一些常见类型的端口是麦克风，线路输入，CD-ROM 驱动器，扬声器，耳机和线路输出。

[`Mixer`](https://docs.oracle.com/javase/8/docs/api/javax/sound/sampled/Mixer.html) 接口代表一个混音器，当然，正如我们所见，它代表硬件或软件设备。 `Mixer`接口提供了获取调音台线路的方法。这些包括将音频馈送到混音器的源线和目标线，混音器向其传送混合音频。对于音频输入混音器，源线是输入端口，例如麦克风输入，目标线是 [`TargetDataLines`](https://docs.oracle.com/javase/8/docs/api/javax/sound/sampled/TargetDataLine.html) （如下所述），它们将音频传送到应用程序。另一方面，对于音频输出混合器，源线是 [`Clips`](https://docs.oracle.com/javase/8/docs/api/javax/sound/sampled/Clip.html) 或 [`SourceDataLines`](https://docs.oracle.com/javase/8/docs/api/javax/sound/sampled/SourceDataLine.html) （如下所述），应用程序向其提供音频数据，以及目标线是输出端口，例如扬声器。

A `Mixer`被定义为具有一个或多个源线和一个或多个目标线。注意，这个定义意味着混音器不需要实际混合数据;它可能只有一个源代码行。 `Mixer` API 旨在涵盖各种设备，但典型案例支持混合。

`Mixer`接口支持同步;也就是说，您可以指定将两个或多个混音器的行视为同步组。然后，您可以通过向组中的任何行发送单个消息来启动，停止或关闭所有这些数据行，而不必单独控制每一行。使用支持此功能的混音器，您可以获得线之间的样本精确同步。

通用`Line`接口不提供启动和停止播放或录制的方法。为此，您需要一条数据线。 [`DataLine`](https://docs.oracle.com/javase/8/docs/api/javax/sound/sampled/DataLine.html) 接口提供以下与`Line`相关的其他媒体相关功能：

*   音频格式 - 每条数据线都有一个与其数据流相关的音频格式。

*   媒体位置 - 数据行可以报告其在媒体中的当前位置，以样本帧表示。这表示自打开以来数据线捕获或呈现的样本帧数。

*   缓冲区大小 - 这是数据行内部缓冲区的大小（以字节为单位）。对于源数据行，内部缓冲区是可以写入数据的内部缓冲区，对于目标数据线，它是可以从中读取数据的缓冲区。

*   电平（音频信号的当前幅度）

*   开始和停止播放或捕获

*   暂停并恢复播放或捕捉

*   刷新（丢弃队列中未处理的数据）

*   排空（阻塞直到所有未处理的数据从队列中排出，数据行的缓冲区变空）

*   活动状态 - 如果数据线用于激活演示或从混音器捕获音频数据，则认为数据线是活动的。

*   事件 - 当数据线的有效表示或从数据线捕获数据开始或停止时，会产生`START`和`STOP`事件。

A `TargetDataLine`从混音器接收音频数据。通常，混音器从诸如麦克风的端口捕获音频数据;在将数据放入目标数据行的缓冲区之前，它可能会处理或混合此捕获的音频。 `TargetDataLine`接口提供了从目标数据线的缓冲区读取数据以及确定当前可用于读取的数据量的方法。

A `SourceDataLine`接收用于播放的音频数据。它提供了将数据写入源数据行的缓冲区以进行回放的方法，以及用于确定线路准备接收多少数据而不会阻塞的方法。

A `Clip`是一个数据线，可以在播放前加载音频数据。由于数据是预加载而不是流式传输，因此剪辑的持续时间在播放前是已知的，您可以选择媒体中的任何起始位置。剪辑可以循环，这意味着在回放时，两个指定循环点之间的所有数据将重复指定的次数或无限期。

本节介绍了采样音频 API 的大多数重要接口和类。后续部分将介绍如何在应用程序中访问和使用这些对象。