在Java程序中,将音频文件(WAV文件)读取到数字数组(float[]
,short[]
,...)的最佳方法是什么,以及从数组中写出一个WAV文件?
答案 0 :(得分:9)
我通过AudioInputStream
阅读了WAV文件。 Java Sound Tutorials的以下代码段效果很好。
int totalFramesRead = 0;
File fileIn = new File(somePathName);
// somePathName is a pre-existing string whose value was
// based on a user selection.
try {
AudioInputStream audioInputStream =
AudioSystem.getAudioInputStream(fileIn);
int bytesPerFrame =
audioInputStream.getFormat().getFrameSize();
if (bytesPerFrame == AudioSystem.NOT_SPECIFIED) {
// some audio formats may have unspecified frame size
// in that case we may read any amount of bytes
bytesPerFrame = 1;
}
// Set an arbitrary buffer size of 1024 frames.
int numBytes = 1024 * bytesPerFrame;
byte[] audioBytes = new byte[numBytes];
try {
int numBytesRead = 0;
int numFramesRead = 0;
// Try to read numBytes bytes from the file.
while ((numBytesRead =
audioInputStream.read(audioBytes)) != -1) {
// Calculate the number of frames actually read.
numFramesRead = numBytesRead / bytesPerFrame;
totalFramesRead += numFramesRead;
// Here, do something useful with the audio data that's
// now in the audioBytes array...
}
} catch (Exception ex) {
// Handle the error...
}
} catch (Exception e) {
// Handle the error...
}
要编写WAV,我发现这很棘手。从表面上看,这似乎是一个循环问题,写入的命令依赖于AudioInputStream
作为参数。
但是如何将字节写入AudioInputStream
?不应该有AudioOutputStream
吗?
我发现可以定义一个可以访问原始音频字节数据的对象来实现TargetDataLine
。
这需要实现许多方法,但大多数方法都可以保持虚拟形式,因为它们不是将数据写入文件所必需的。实施的关键方法是read(byte[] buffer, int bufferoffset, int numberofbytestoread)
。
由于此方法可能会被多次调用,因此还应该有一个实例变量来指示数据进展的程度,并将其作为上述read
方法的一部分进行更新。
当您实施此方法后,您的对象可用于创建新的AudioInputStream
,而AudioSystem.write(yourAudioInputStream, AudioFileFormat.WAV, yourFileDestination)
又可用于:
AudioInputStream
提醒一下,可以使用
TargetDataLine
作为来源创建audioBytes
。
关于直接操作数据,我在上面的代码片段volume
的最里面的循环中对缓冲区中的数据进行了很好的成功。
当您处于该内部循环中时,您可以将字节转换为整数或浮点数并乘以0.0
值(范围从1.0
到TargetDataLine
),然后将它们转换回小端字节。
我相信,由于您可以访问该缓冲区中的一系列样本,因此您还可以在该阶段使用各种形式的DSP过滤算法。根据我的经验,我发现最好直接对此缓冲区中的数据进行音量更改,因为这样可以实现最小的增量:每个样本增加一个delta,从而最大限度地减少因音量引起的不连续性而导致的点击几率。
我找到"控制线"由Java提供的卷倾向于卷的跳跃会导致点击,我相信这是因为增量只能在单个缓冲区读取的粒度上实现(通常在每1024个样本一个更改的范围内)而不是将更改分成更小的部分,并将每个样本添加一个。但是我并不知道音量控制是如何实现的,所以请把这个猜想搞得一团糟。
总而言之,Java.Sound一直是一个令人头痛的问题。我错误的教程没有包括直接从字节写文件的明确示例。我错过了教程,以便在"如何转换......"中掩埋播放文件编码的最佳示例。部分。但是,该教程中有很多有价值的免费信息。
编辑:12/13/17
我使用以下代码在我自己的项目中从PCM文件中写入音频。可以扩展InputStream
而不是实现AudioInputStream.write
,并将其用作public class StereoPcmInputStream extends InputStream
{
private float[] dataFrames;
private int framesCounter;
private int cursor;
private int[] pcmOut = new int[2];
private int[] frameBytes = new int[4];
private int idx;
private int framesToRead;
public void setDataFrames(float[] dataFrames)
{
this.dataFrames = dataFrames;
framesToRead = dataFrames.length / 2;
}
@Override
public int read() throws IOException
{
while(available() > 0)
{
idx &= 3;
if (idx == 0) // set up next frame's worth of data
{
framesCounter++; // count elapsing frames
// scale to 16 bits
pcmOut[0] = (int)(dataFrames[cursor++] * Short.MAX_VALUE);
pcmOut[1] = (int)(dataFrames[cursor++] * Short.MAX_VALUE);
// output as unsigned bytes, in range [0..255]
frameBytes[0] = (char)pcmOut[0];
frameBytes[1] = (char)(pcmOut[0] >> 8);
frameBytes[2] = (char)pcmOut[1];
frameBytes[3] = (char)(pcmOut[1] >> 8);
}
return frameBytes[idx++];
}
return -1;
}
@Override
public int available()
{
// NOTE: not concurrency safe.
// 1st half of sum: there are 4 reads available per frame to be read
// 2nd half of sum: the # of bytes of the current frame that remain to be read
return 4 * ((framesToRead - 1) - framesCounter)
+ (4 - (idx % 4));
}
@Override
public void reset()
{
cursor = 0;
framesCounter = 0;
idx = 0;
}
@Override
public void close()
{
System.out.println(
"StereoPcmInputStream stopped after reading frames:"
+ framesCounter);
}
}
方法的参数。
skip
此处要导出的源数据采用立体声浮点数的形式,范围从-1到1.结果流的格式为16位,立体声,小端。
我为我的特定应用省略了markSupported
和{{1}}方法。但如果需要的话,添加它们并不困难。
答案 1 :(得分:8)
有关您希望实现的内容的更多细节将会有所帮助。如果原始WAV数据适合您,只需使用FileInputStream和可能的扫描仪将其转换为数字。但是,让我试着给你一些有意义的示例代码,以帮助你入门:
为此目的,有一个名为com.sun.media.sound.WaveFileWriter的类。
InputStream in = ...;
OutputStream out = ...;
AudioInputStream in = AudioSystem.getAudioInputStream(in);
WaveFileWriter writer = new WaveFileWriter();
writer.write(in, AudioFileFormat.Type.WAVE, outStream);
您可以实现自己的AudioInputStream,它可以执行任何伏都教操作,将您的数字数组转换为音频数据。
writer.write(new VoodooAudioInputStream(numbers), AudioFileFormat.Type.WAVE, outStream);
如@stacker所述,您当然应该熟悉API。
答案 2 :(得分:6)
如果您需要访问实际样本值,则javax.sound.sample包不适合处理WAV文件。该软件包允许您更改音量,采样率等,但如果您想要其他效果(例如,添加回声),则您可以自己动手。 (Java教程暗示应该可以直接处理样本值,但技术作者过度表达。)
此网站有一个处理WAV文件的简单类:http://www.labbookpages.co.uk/audio/javaWavFiles.html
答案 3 :(得分:5)
WAV文件规范 https://ccrma.stanford.edu/courses/422/projects/WaveFormat/
有一个API可供您使用 http://code.google.com/p/musicg/
答案 4 :(得分:4)
这是直接写入wav文件的源代码。 你只需要知道数学和声音工程就可以产生你想要的声音。 在此示例中,等式计算双耳节拍。
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
public class Example
{
public static void main(String[] args) throws IOException {
double sampleRate = 44100.0;
double frequency = 440;
double frequency2 = 90;
double amplitude = 1.0;
double seconds = 2.0;
double twoPiF = 2 * Math.PI * frequency;
double piF = Math.PI * frequency2;
float[] buffer = new float[(int) (seconds * sampleRate)];
for (int sample = 0; sample < buffer.length; sample++)
{
double time = sample / sampleRate;
buffer[sample] = (float) (amplitude * Math.cos((double)piF *time)* Math.sin(twoPiF * time));
}
final byte[] byteBuffer = new byte[buffer.length * 2];
int bufferIndex = 0;
for (int i = 0; i < byteBuffer.length; i++) {
final int x = (int) (buffer[bufferIndex++] * 32767.0);
byteBuffer[i] = (byte) x;
i++;
byteBuffer[i] = (byte) (x >>> 8);
}
File out = new File("out10.wav");
boolean bigEndian = false;
boolean signed = true;
int bits = 16;
int channels = 1;
AudioFormat format;
format = new AudioFormat((float)sampleRate, bits, channels, signed, bigEndian);
ByteArrayInputStream bais = new ByteArrayInputStream(byteBuffer);
AudioInputStream audioInputStream;
audioInputStream = new AudioInputStream(bais, format,buffer.length);
AudioSystem.write(audioInputStream, AudioFileFormat.Type.WAVE, out);
audioInputStream.close();
}
}
如果你可以修改它来创建一个很酷的嘻哈次低音,因为这是我正在尝试修改这个程序的目的。
答案 5 :(得分:2)
答案 6 :(得分:2)
答案 7 :(得分:0)
如果仍然有人需要它,那么我正在开发一个音频框架,旨在解决该问题和类似问题。虽然在科特林上。您可以在GitHub上找到它:https://github.com/WaveBeans/wavebeans
它看起来像这样:
wave("file:///path/to/file.wav")
.map { it.asInt() } // here it as Sample type, need to convert it to desired type
.asSequence(44100.0f) // framework processes everything as sequence/stream
.toList() // read fully
.toTypedArray() // convert to array
它不依赖于Java Audio。
答案 8 :(得分:-1)
我使用FileInputStream
加上一些魔法:
byte[] byteInput = new byte[(int)file.length() - 44];
short[] input = new short[(int)(byteInput.length / 2f)];
try{
FileInputStream fis = new FileInputStream(file);
fis.read(byteInput, 44, byteInput.length - 45);
ByteBuffer.wrap(byteInput).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(input);
}catch(Exception e ){
e.printStackTrace();
}
您的样本值在short[] input
!