Java - 读取、操作和写入 WAV 文件
在 Java 程序中,将音频文件(WAV 文件)读取到数字数组(float[]
、short[]
、...),并从数字数组写入 WAV 文件?
In a Java program, what is the best way to read an audio file (WAV file) to an array of numbers (float[]
, short[]
, ...), and to write a WAV file from an array of numbers?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
我通过 AudioInputStream 读取 WAV 文件。 Java 声音教程 中的以下代码片段效果很好。
我发现编写 WAV 相当棘手。从表面上看,这似乎是一个循环问题,写入的命令依赖于
AudioInputStream
作为参数。但是如何将字节写入
AudioInputStream
呢?难道不应该有一个 AudioOutputStream 吗?我发现可以定义一个可以访问原始音频字节数据的对象来实现
TargetDataLine
。这需要实现许多方法,但大多数方法可以保留虚拟形式,因为将数据写入文件不需要它们。实现的关键方法是
read(byte[] buffer, int bufferoffset, int numberofbytestoread)
。由于此方法可能会被多次调用,因此还应该有一个实例变量来指示数据的处理进度,并将其更新为上述 read 方法的一部分。
当您实现此方法后,您的对象可用于创建一个新的 AudioInputStream ,该对象又可用于:
至于直接操作数据,我在上面代码片段示例的最内层循环中对缓冲区中的数据进行了很好的操作,
audioBytes
。当您处于内部循环中时,您可以将字节转换为整数或浮点数,并乘以
volume
值(范围从0.0
到1.0
)然后将它们转换回小端字节。我相信,既然您可以访问该缓冲区中的一系列样本,您还可以在该阶段使用各种形式的 DSP 滤波算法。根据我的经验,我发现最好直接对此缓冲区中的数据进行音量更改,因为这样您就可以进行尽可能小的增量:每个样本一个增量,从而最大限度地减少由于音量引起的不连续性而导致点击的机会。
我发现 Java 提供的音量“控制线”倾向于出现音量跳跃会导致点击的情况,我相信这是因为增量仅在单个缓冲区读取的粒度上实现(通常在 1 的范围内)每 1024 个样本的变化),而不是将变化分成更小的部分并为每个样本添加一个。但我不知道音量控制是如何实现的,所以请对这个猜想持保留态度。
总而言之,Java.Sound 确实是一个令人头疼的问题。我责怪教程没有包含直接从字节写入文件的明确示例。我责备教程将播放文件编码的最佳示例隐藏在“如何转换...”部分中。然而,该教程中有很多有价值的免费信息。
编辑:2017 年 12 月 13 日
我已经使用以下代码在我自己的项目中从 PCM 文件写入音频。您可以扩展
InputStream
并将其用作AudioSystem.write
方法的参数,而不是实现TargetDataLine
。此处要导出的源数据采用立体声浮点形式,范围从 -1 到 1。生成的流的格式为 16 位、立体声、小端。
对于我的特定应用程序,我省略了
skip
和markSupported
方法。但如果需要的话添加它们应该不难。I read WAV files via an
AudioInputStream
. The following snippet from the Java Sound Tutorials works well.To write a WAV, I found that quite tricky. On the surface it seems like a circular problem, the command that writes relies on an
AudioInputStream
as a parameter.But how do you write bytes to an
AudioInputStream
? Shouldn't there be anAudioOutputStream
?What I found was that one can define an object that has access to the raw audio byte data to implement
TargetDataLine
.This requires a lot of methods be implemented, but most can stay in dummy form as they are not required for writing data to a file. The key method to implement is
read(byte[] buffer, int bufferoffset, int numberofbytestoread)
.As this method will probably be called multiple times, there should also be an instance variable that indicates how far through the data one has progressed, and update that as part of the above
read
method.When you have implemented this method, then your object can be used in to create a new
AudioInputStream
which in turn can be used with:As to the direct manipulating the data, I have had good success acting on the data in the buffer in the innermost loop of the snippet example above,
audioBytes
.While you are in that inner loop, you can convert the bytes to integers or floats and multiply a
volume
value (ranging from0.0
to1.0
) and then convert them back to little endian bytes.I believe since you have access to a series of samples in that buffer you can also engage various forms of DSP filtering algorithms at that stage. In my experience I have found that it is better to do volume changes directly on data in this buffer because then you can make the smallest possible increment: one delta per sample, minimizing the chance of clicks due to volume-induced discontinuities.
I find the "control lines" for volume provided by Java tend to situations where the jumps in volume will cause clicks, and I believe this is because the deltas are only implemented at the granularity of a single buffer read (often in the range of one change per 1024 samples) rather than dividing the change into smaller pieces and adding them one per sample. But I'm not privy to how the Volume Controls were implemented, so please take that conjecture with a grain of salt.
All and all, Java.Sound has been a real headache to figure out. I fault the Tutorial for not including an explicit example of writing a file directly from bytes. I fault the Tutorial for burying the best example of Play a File coding in the "How to Convert..." section. However, there's a LOT of valuable FREE info in that tutorial.
EDIT: 12/13/17
I've since used the following code to write audio from a PCM file in my own projects. Instead of implementing
TargetDataLine
one can extendInputStream
and use that as a parameter to theAudioSystem.write
method.The source data to be exported here is in the form of stereo floats ranging from -1 to 1. The format of the resulting stream is 16-bit, stereo, little-endian.
I omitted
skip
andmarkSupported
methods for my particular application. But it shouldn't be difficult to add them if they are needed.这是直接写入 wav 文件的源代码。
您只需要了解数学和声音工程即可产生您想要的声音。
在此示例中,方程计算双耳节拍。
This is the source code to write directly to a wav file.
You just need to know the mathematics and sound engineering to produce the sound you want.
In this example the equation calculates a binaural beat.
有关您想要实现的目标的更多详细信息将会有所帮助。如果原始 WAV 数据适合您,只需使用 FileInputStream 和扫描仪将其转换为数字即可。但让我尝试为您提供一些有意义的示例代码来帮助您入门:
有一个名为 com.sun.media.sound.WaveFileWriter 的类用于此目的。
您可以实现自己的 AudioInputStream,它可以执行任何巫术将数字数组转换为音频数据。
正如 @stacker 提到的,您当然应该熟悉 API。
Some more detail on what you'd like to achieve would be helpful. If raw WAV data is okay for you, simply use a FileInputStream and probably a Scanner to turn it into numbers. But let me try to give you some meaningful sample code to get you started:
There is a class called com.sun.media.sound.WaveFileWriter for this purpose.
You could implement your own AudioInputStream that does whatever voodoo to turn your number arrays into audio data.
As @stacker mentioned, you should get yourself familiar with the API of course.
如果您需要访问实际示例值,则 javax.sound.sample 包不适合处理 WAV 文件。该软件包可让您更改音量、采样率等,但如果您想要其他效果(例如添加回声),则只能靠您自己了。 (Java 教程暗示应该可以直接处理示例值,但技术作者夸大了。)
该站点有一个用于处理 WAV 文件的简单类:http://www.labbookpages.co.uk/audio/javaWavFiles.html
The javax.sound.sample package is not suitable for processing WAV files if you need to have access to the actual sample values. The package lets you change volume, sample rate, etc., but if you want other effects (say, adding an echo), you are on your own. (The Java tutorial hints that it should be possible to process the sample values directly, but the tech writer overpromised.)
This site has a simple class for processing WAV files: http://www.labbookpages.co.uk/audio/javaWavFiles.html
WAV 文件规范
https://ccrma.stanford.edu/courses/422/projects/WaveFormat/
有一个 API 适合您的目的
http://code.google.com/p/musicg/
WAV File Specification
https://ccrma.stanford.edu/courses/422/projects/WaveFormat/
There is an API for your purpose
http://code.google.com/p/musicg/
首先,您可能需要了解 WAVE 结构的标题和数据位置,您可以找到规范 此处。
请注意,数据是小端字节序。
有一个 API 可以帮助您实现目标。
First of all, you may need to know the headers and data positions of a WAVE structure, you can find the spec here.
Be aware that the data are little endian.
There's an API which may helps you to achieve your goal.
Wave 文件由 javax.sound.sample package
由于这不是一个简单的 API,因此您应该阅读一篇介绍 API 的文章/教程,例如
Java 声音简介
Wave files are supported by the javax.sound.sample package
Since isn't a trivial API you should read an article / tutorial which introduces the API like
Java Sound, An Introduction
我使用
FileInputStream
进行了一些魔法:您的示例值位于
short[] input
中!I use
FileInputStream
with some magic:Your sample values are in
short[] input
!如果有人仍然需要它,我正在开发一个音频框架,旨在解决这个问题和类似的问题。虽然它是在 Kotlin 上。你可以在 GitHub 上找到它: https://github.com/WaveBeans/wavebeans
它看起来像this:
并且它不依赖于 Java Audio。
If anyone still can find it required, there is an audio framework I'm working on that aimed to solve that and similar issues. Though it's on Kotlin. You can find it on GitHub: https://github.com/WaveBeans/wavebeans
It would look like this:
And it's not dependent on Java Audio.