Java合并具有延迟和重叠的音频文件



我的意图是NOT录制我的系统音频输出或简单地合并音频文件。

我需要一个类"AudioMerger"。它应该有一个给定总长度的方法merge。然后它应该创建一个该长度的无声wav文件,并将指定的声音添加到其中。添加的声音可以重叠,并且可以有偏移

示例:声音长度.wav为3秒

Merger merger = new Merger();
merger.add("sound.wav", 2);
merger.add("sound.wav", 6);
merger.add("sound.wav", 7);
//creates a 10 seconds wav file with the contents of sound.wav inserted at the specific seconds
merger.merge(10);
merger.saveToFile(new File("out.wav"));

感谢https://stackoverflow.com/users/740553/mike-pomax-kamermans我现在有了工作代码。

口哨.wav:https://voca.ro/1iqDr3yVZ6uG
out.wav:https://voca.ro/1jxlHkNUuH9r

市长的问题是创建一个空的wav文件。为了做到这一点,我需要在开头写一个合适的标题。您可以在此处阅读有关.wav标头的详细信息:http://www.topherlee.com/software/pcm-tut-wavformat.html
当我实现这一点时,我很难在Little/Bigh Endian中读取和写入字节。基本上,这些指定了数字存储的方向。Big Endian存储它们就像我们现在和Java一样(从左到右(,Little Endian反向存储它们(从右到左(。一个wav文件期望它的所有数字都在小恩迪亚。因此,当我们从wav文件加载数字时,我们需要将它们转换为Big Endian(我们的Java数字(,而当我们编写文件时,我们则需要将它们重新转换为Little Endian。要做到这一点,我们可以使用Integer.reverseBytes((和Short.rereverseBBytes((方法。

OneHundredTwo:
大恩迪亚:102
小恩迪亚:201

我遇到的另一个困难是合并音频字节数组。我把数组的每一位加在一起,计算出平均值。然而,我的SampleSize是16位的,所以我需要计算每两个字节的平均值,而不是每一个。

当我第一次开始工作时,在我插入的音频播放之前,总是会有一种奇怪的噪音。我不小心用文件内容填充了字节数组。在合并我的程序时,还合并了标题数据,并将它们解释为产生这种噪音的声音数据。剪掉标题后,我的音频听起来不错。

然而,当我的流重叠时,它们会产生大量的前景噪音。在计算平均值时,我没有将除数转换为浮点,所以它截断了一些音频数据。3/2变为1,而不是1.5四舍五入为两个

实际上,我做的正确的事情是确保我的音频只能插入一个可以除以二的偏移量。否则,它将把前一幅度的第一个字节与下一幅度的最后一个字节合并。

import java.io.File;
import java.io.IOException;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
public class Main {
public static void main(String[] args) throws IOException, UnsupportedAudioFileException, LineUnavailableException {
AudioMerger merger = new AudioMerger();
MergeSound sound = new MergeSound(new File("whistle.wav"));
merger.addSound(2, sound);
merger.addSound(5, sound);
merger.addSound(5.5, sound);
merger.merge(10);
merger.saveToFile(new File("out.wav"));
}
}
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
public class MergeSound {
private short audioFormat;
private int sampleRate;
private short sampleSize;
private short channels;
private ByteBuffer buffer;
public MergeSound(File file) throws IOException {
DataInputStream in = new DataInputStream(new FileInputStream(file));
byte[] sound = new byte[in.available() - 44];
// read header data
in.skipNBytes(20);
audioFormat = Short.reverseBytes(in.readShort());
channels = Short.reverseBytes(in.readShort());
sampleRate = Integer.reverseBytes(in.readInt());
in.skipNBytes(6);
sampleSize = Short.reverseBytes(in.readShort());
in.skipNBytes(8);// make sure to cut the full header of else there will be strange noise
in.read(sound);
buffer = ByteBuffer.wrap(sound);
}
public ByteBuffer getBuffer() {
return buffer;
}
public short getAudioFormat() {
return audioFormat;
}
public void setAudioFormat(short audioFormat) {
this.audioFormat = audioFormat;
}
public int getSampleRate() {
return sampleRate;
}
public void setSampleRate(int sampleRate) {
this.sampleRate = sampleRate;
}
public short getSampleSize() {
return sampleSize;
}
public void setSampleSize(short sampleSize) {
this.sampleSize = sampleSize;
}
public short getChannels() {
return channels;
}
public void setChannels(short channels) {
this.channels = channels;
}
}

import static java.lang.Math.ceil;
import static java.lang.Math.round;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
public class AudioMerger {
private short audioFormat = 1;
private int sampleRate = 44100;
private short sampleSize = 16;
private short channels = 2;
private short blockAlign = (short) (sampleSize * channels / 8);
private int byteRate = sampleRate * sampleSize * channels / 8;
private ByteBuffer audioBuffer;
private ArrayList<MergeSound> sounds = new ArrayList<MergeSound>();
private ArrayList<Integer> offsets = new ArrayList<Integer>();
public void addSound(double offsetInSeconds, MergeSound sound) {
if (sound.getAudioFormat() != audioFormat)
new RuntimeException("Incompatible AudioFormat");
if (sound.getSampleRate() != sampleRate)
new RuntimeException("Incompatible SampleRate");
if (sound.getSampleSize() != sampleSize)
new RuntimeException("Incompatible SampleSize");
if (sound.getChannels() != channels)
new RuntimeException("Incompatible amount of Channels");
int offset = secondsToByte(offsetInSeconds);
offset = offset % 2 == 0 ? offset : offset + 1;// ensure we start at short when merging
sounds.add(sound);
offsets.add(offset);
}
public void merge(double durationInSeconds) {
audioBuffer = ByteBuffer.allocate(secondsToByte(durationInSeconds));
for (int i = 0; i < sounds.size(); i++) {
ByteBuffer buffer = sounds.get(i).getBuffer();
int offset1 = offsets.get(i);
// iterate over all sound data to append it
while (buffer.hasRemaining()) {
int position = offset1 + buffer.position();// the global position in audioBuffer
// exit if audio plays after end
if (position >= audioBuffer.capacity())
return;
// add the audio data to the vars
short sum = Short.reverseBytes(buffer.getShort());
int matches = 1;
// make sure later entries dont override the previsously merged
//continue only if theres empty audio data
if (audioBuffer.getShort(position) == 0) {
// iterate over the other sounds and check if the need to be merged
for (int j = i + 1; j < sounds.size(); j++) {// set j to i+1 to avoid all previous
ByteBuffer mergeBuffer = sounds.get(j).getBuffer();
int mergeOffset = offsets.get(j);
// check if this soundfile contains data that has to be merged
if (position >= mergeOffset && position < mergeOffset + mergeBuffer.capacity()) {
sum += Short.reverseBytes(mergeBuffer.getShort(position - mergeOffset));
matches++;
}
}
//make sure to cast to float 3/1=1 BUT round(3/1f)=2 for example
audioBuffer.putShort(position, Short.reverseBytes((short) round(sum / (float) matches)));
}
}
buffer.rewind();// So the sound can be added again
}
}
private int secondsToByte(double seconds) {
return (int) ceil(seconds * byteRate);
}
public void saveToFile(File file) throws IOException {
byte[] audioData = audioBuffer.array();
int audioSize = audioData.length;
int fileSize = audioSize + 44;
// The stream that writes the audio file to the disk
DataOutputStream out = new DataOutputStream(new FileOutputStream(file));
// Write Header
out.writeBytes("RIFF");// 0-4 ChunkId always RIFF
out.writeInt(Integer.reverseBytes(fileSize));// 5-8 ChunkSize always audio-length +header-length(44)
out.writeBytes("WAVE");// 9-12 Format always WAVE
out.writeBytes("fmt ");// 13-16 Subchunk1 ID always "fmt " with trailing whitespace
out.writeInt(Integer.reverseBytes(16)); // 17-20 Subchunk1 Size always 16
out.writeShort(Short.reverseBytes(audioFormat));// 21-22 Audio-Format 1 for PCM PulseAudio
out.writeShort(Short.reverseBytes(channels));// 23-24 Num-Channels 1 for mono, 2 for stereo
out.writeInt(Integer.reverseBytes(sampleRate));// 25-28 Sample-Rate
out.writeInt(Integer.reverseBytes(byteRate));// 29-32 Byte Rate
out.writeShort(Short.reverseBytes(blockAlign));// 33-34 Block Align
out.writeShort(Short.reverseBytes(sampleSize));// 35-36 Bits-Per-Sample
out.writeBytes("data");// 37-40 Subchunk2 ID always data
out.writeInt(Integer.reverseBytes(audioSize));// 41-44 Subchunk 2 Size audio-length
out.write(audioData);// append the merged data
out.close();// close the stream properly
}
}

最新更新