回放有时被称为演示或渲染。这些是适用于声音以外的其他媒体的通用术语。其基本特点是将一系列数据传递到某个位置,最终由用户感知。如果数据是基于时间的,比如声音,那么必须以正确的速率传递。尤其是对于声音而言,保持数据流的速率非常重要,因为声音播放中断通常会产生大声的点击声或刺耳的失真声。Java Sound API旨在帮助应用程序平稳连续地播放声音,即使是非常长的声音。
之前你看到了如何从音频系统或混音器中获取线路。在这里,你将学习如何通过线路播放声音。
正如你所知,有两种线路可以用来播放声音:一个是,另一个是。两者之间的主要区别在于,使用时你在播放之前一次性指定所有的声音数据,而使用时你在播放期间持续不断地写入新的数据缓冲区。尽管有许多情况可以使用或,以下标准有助于确定哪种线路更适合特定的情况:
- 当你有可以预加载到内存中的非实时声音数据时,使用。
例如,你可以将一个短音频文件读入到一个剪辑中。如果你希望声音循环播放多次,比更方便,尤其是如果你希望播放循环(反复通过声音的全部或部分)。如果你需要在声音的任意位置开始播放,剪辑接口提供了一个简单的方法来实现。最后,与的缓冲播放相比,剪辑播放通常具有更低的延迟。换句话说,由于声音预加载到剪辑中,播放可以立即开始,而无需等待缓冲区填充。
- 对于流式数据,如无法一次性将所有数据装入内存的长音频文件,或者无法在播放之前知道数据的声音,使用。
作为后一种情况的示例,假设你正在监视音频输入,也就是在捕获音频时进行回放。如果你没有一个可以将输入音频直接发送到输出端口的混音器,你的应用程序将需要将捕获的数据发送到音频输出混音器。在这种情况下,比更合适。另一个无法预先知道的声音的示例是在响应用户输入时交互地合成或处理声音数据。例如,想象一个通过鼠标移动来“变形”从一个声音到另一个声音的游戏。声音转换的动态性要求应用程序在播放过程中连续更新声音数据,而不是在播放开始之前提供所有数据。
您可以按照之前描述的方法获取Clip;构造一个带有Clip.class的DataLine.Info对象作为第一个参数,并将此DataLine.Info作为参数传递给AudioSystem或Mixer的getLine方法。
获取一个line只意味着您已经得到了一个引用的方式;getLine不会真正为您保留该line。由于mixer可能只有有限数量的所需类型的line可用,因此在您调用getLine获取clip后,可能会有另一个应用程序在您准备开始播放之前抢走clip。要实际使用clip,您需要通过调用以下Clip方法之一将其保留给您的程序的独占使用:
尽管上述第二个open方法中有一个bufferSize参数,但是Clip(与SourceDataLine不同)不包括用于向缓冲区写入新数据的方法。这里的bufferSize参数只是指定要加载到clip中的字节数组的大小。它不是一个您可以随后加载更多数据的缓冲区,就像SourceDataLine的缓冲区一样。
打开clip后,您可以使用Clip的setFramePosition或setMicroSecondPosition方法指定数据在何处开始播放。否则,它将从开头开始。您还可以使用setLoopPoints方法配置重复循环播放。
当您准备好开始播放时,只需调用start方法。要停止或暂停clip,调用stop方法,要恢复播放,再次调用start方法。clip会记住停止播放的媒体位置,因此不需要显式的暂停和恢复方法。如果您不希望它从离开的地方恢复,可以使用上面提到的帧或微秒定位方法将clip“倒回”到开头(或任何其他位置)。
通过调用DataLine的getLevel和isActive方法,可以监视Clip的音量级别和活动状态(活动与非活动)。活动的Clip是当前正在播放声音的Clip。
获取SourceDataLine类似于获取Clip。打开SourceDataLine也类似于打开Clip,目的是再次保留该line。但是,您使用继承自DataLine的不同方法:
注意,当你打开一个时,还没有将任何声音数据与该行关联,与打开不同。相反,你只需指定要播放的音频数据的格式。系统会选择一个默认的缓冲区长度。
你还可以使用以下变体指定特定的缓冲区长度(以字节为单位):
为了与类似方法保持一致,参数以字节表示,但它必须对应于整数个帧。
除了使用上述描述的open方法外,还可以使用的方法打开,不带参数。在这种情况下,行将以其默认音频格式和缓冲区大小打开。然而,您不能以后更改这些值。如果您想了解行的默认音频格式和缓冲区大小,即使在行尚未打开之前,您也可以调用的和方法。
一旦打开,您就可以开始播放声音。您可以通过调用的start方法来实现,然后重复将数据写入行的播放缓冲区。
start方法允许行在缓冲区中有任何数据时开始播放声音。通过以下方法将数据放入缓冲区:
数组中的偏移量以字节为单位表示,数组的长度也是如此。
行会尽快将数据发送给混音器。当混音器将数据传递给其目标时,会生成一个事件。(在Java Sound API的典型实现中,源行将数据传递给混音器和混音器将数据传递给其目标之间的延迟可以忽略不计,即远远小于一个样本的时间。)此事件会发送到行的监听器,如下面的监视线路状态中所解释的。此时行被认为是活动的,因此的方法将返回。请注意,所有这些只有在缓冲区包含要播放的数据时才会发生,不一定是在调用start方法的时候立即发生。如果你在一个新的上调用了,但从未向缓冲区写入数据,那么该行将永远不会被激活,也不会发送事件。(但在这种情况下,的方法将返回。)
那么,你如何知道要向缓冲区写入多少数据,并在何时发送第二批数据呢?幸运的是,你不需要计时第二次调用write以与第一个缓冲区的结束同步!相反,你可以利用write方法的阻塞行为:
- 该方法在数据写入缓冲区后立即返回。它不会等到缓冲区中的所有数据都播放完毕才返回。(如果这样做,你可能没有时间写入下一个缓冲区,从而在音频中产生不连续性。)
- 尝试写入的数据量超过缓冲区的容量是可以的。在这种情况下,该方法会阻塞(不返回),直到你请求的所有数据实际上都被放入缓冲区。换句话说,一次只会写入缓冲区的一个缓冲区的数据并播放,直到剩余的数据都适应缓冲区为止,此时该方法才返回。无论方法是否阻塞,它都会在此次调用的最后一个缓冲区的数据可以被写入时返回。再次强调,这意味着在最后一个缓冲区的数据播放完毕之前,你的代码很可能会重新获得控制。
- 在许多情况下,写入的数据量超过缓冲区的容量都是可以的,但如果你想确保下一次写入不会阻塞,你可以将要写入的字节数限制为DataLine的available方法返回的字节数。
以下是一个示例,通过从流中读取的数据块进行迭代,逐个将数据块写入SourceDataLine进行播放:
如果你不希望write方法阻塞,可以在循环内首先调用available方法,以查找可以无阻塞写入的字节数,然后将numBytesToRead变量限制为此数字,然后再从流中读取。然而,在给定的示例中,阻塞不会对结果产生太大影响,因为write方法在最后一个循环迭代中被调用,直到最后一个缓冲区被写入才会完成循环。无论是否使用阻塞技术,你可能希望将该播放循环在应用程序的其余部分中的一个单独线程中调用,这样在播放长音频时,你的程序不会出现冻结的情况。在循环的每次迭代中,你可以测试用户是否请求停止播放。这样的请求需要将上面的代码中的stopped布尔值设置为true。
由于在所有数据完成播放之前返回,你如何知道播放实际上已经完成了呢?一种方法是在写入最后一个缓冲区的数据后调用的方法。这个方法会阻塞直到所有数据都被播放完。当控制权返回到你的程序时,你可以释放该线路,如果需要的话,而不必担心提前中断任何音频样本的播放:
当然,你也可以故意提前停止播放。例如,应用程序可能为用户提供了一个停止按钮。调用的方法可以立即停止播放,即使在缓冲区的中间。这将保留缓冲区中未播放的数据,所以如果随后调用,播放将从中断的地方恢复。如果这不是你想要发生的,你可以通过调用来丢弃缓冲区中剩余的数据。
当数据流的流动被停止时,会生成一个事件,无论是通过方法、方法、方法,还是因为应用程序在调用之前到达了播放缓冲区的末尾以提供新的数据。一个事件并不一定意味着方法被调用了,也并不一定意味着随后调用将返回。然而,它确实意味着将返回。(当调用了方法后,方法将返回,即使生成了一个事件,只有在调用方法后,方法才会开始返回。)重要的是要意识到和事件对应的是,而不是。
一旦你开始播放声音,你如何知道它何时完成?我们在上面看到了一种解决方案,即在写入最后一个数据缓冲区后调用方法,但这种方法只适用于。另一种适用于和的方法是注册接收线路状态变化时的通知。这些通知以对象的形式生成,有四种类型:、、和。
你的程序中任何实现了接口的对象都可以注册接收这些通知。要实现接口,该对象只需要一个接受参数的更新方法。要将该对象注册为线路的监听器之一,你可以调用以下方法:
每当线路打开、关闭、开始或停止时,它会向所有的监听器发送一个消息。您的对象可以查询接收到的。首先,您可以调用来确保停止的线路是您关心的线路。在我们讨论的情况下,您想知道音频是否已经结束,所以您可以检查的类型是否为。如果是,您可以检查音频的当前位置,它也存储在对象中,并将其与音频的长度(如果已知)进行比较,以确定它是否已经到达了结束,而不是被其他方式停止(比如用户点击了停止按钮,尽管您可能在代码的其他地方能够确定这个原因)。
同样,如果您需要知道线路何时打开、关闭或开始,您可以使用相同的机制。由不同类型的线路生成,不仅仅是和。然而,在的情况下,您不能指望得到一个事件来了解线路的打开或关闭状态。例如,当创建一个时,它可能是初始打开的,所以您不会调用方法,而且也不会生成事件。(请参阅之前对的讨论。)
如果您同时播放多个音轨,您可能希望它们都在完全相同的时间开始和结束。一些混音器通过其方法来实现这种行为,该方法允许您对一组数据线路应用、、和等操作,而不是需要单独控制每个线路。此外,可以控制将操作应用于线路的精度。
要了解特定混音器是否为指定的数据线路组提供此功能,请调用接口的方法:
第一个参数指定了一组特定的数据线路,第二个参数指示必须维护同步的精确度。如果第二个参数为,则查询是询问混音器是否能够始终在指定线路上以样本精确的精度进行控制;否则,在播放过程中只需要在开始和停止操作期间要求精确同步。
某些源数据线具有信号处理控件,例如增益、平移、混响和采样率控件。输出端口上也可能存在类似的控件,尤其是增益控件。有关如何确定线路是否具有这些控件以及如何使用它们的更多信息,请参见。
版权声明:
本文来源网络,所有图片文章版权属于原作者,如有侵权,联系删除。
本文网址:https://www.bianchenghao6.com/java-jiao-cheng/5001.html