4

我有一个 java 应用程序,它记录来自混音器的音频并将其存储在字节数组中,或将其保存到文件中。我需要的是同时从两个混音器中获取音频,并将其保存到音频文件中(我正在尝试使用 .wav)。问题是我可以获得两个字节数组,但不知道如何合并它们(通过“合并”我不是指连接)。具体来说,它是一个通过 USB 调制解调器处理对话的应用程序,我需要记录它们(流是每个说话人的声音,已经准备好单独记录它们)。

关于如何做的任何线索?

这是我的代码:

import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.Path;

public class FileMixer {

    Path path1 = Paths.get("/file1.wav");
    Path path2 = Paths.get("/file2.wav");
    byte[] byte1 = Files.readAllBytes(path1);
    byte[] byte2 = Files.readAllBytes(path2);
    byte[] out = new byte[byte1.length];

    public FileMixer() {

        byte[] byte1 = Files.readAllBytes(path1);
        byte[] byte2 = Files.readAllBytes(path2);

        for (int i=0; i<byte1.Length; i++)
            out[i] = (byte1[i] + byte2[i]) >> 1;

    }
}

提前致谢

4

2 回答 2

6

要以数字方式混合声波,您可以将两个文件中的每个相应数据点添加在一起。

for (int i=0; i<source1.length; i++)
    result[i] = (source1[i] + source2[i]) >> 1;

换句话说,您从字节数组 1 中取出第 0 项,从字节数组 2 中取出第 0 项,将它们相加,然后将结果数字放入结果数组的第 0 项中。对剩余的值重复。为防止过载,您可能需要将每个结果值除以二。

于 2013-08-26T21:51:26.157 回答
0

确保合并幅度数据而不仅仅是字节数据。如果您的 SampleRate 为 8:一个字节等于一个幅度数据。但如果是 16,则需要将两个字节添加到一个短字节中并将它们合并。

目前你像这样加载你的文件

byte[] byte1 = Files.readAllBytes(path1);

这也会将您的 .wav 文件头加载到字节数组中,但您只想合并实际的音频数据。像这样加载它:

public static ByteBuffer loadFile(File file) throws IOException {
        DataInputStream in = new DataInputStream(new FileInputStream(file));
        byte[] sound = new byte[in.available() - 44];
        in.skipNBytes(44); // skip the header
        in.read(sound);
        return ByteBuffer.wrap(sound);
    }

然后,您可以根据您的样本大小合并这些缓冲区的每个字节或每两个字节。我将使用 16 作为它更常见的。

public static ByteBuffer mergeAudio(ByteBuffer smaller, ByteBuffer larger) {
        // When we merge we will get problems with LittleEndian/BigEndian
        // Actually the amplitude data is stored reverse in the .wav fille
        // When we extract the amplitude value we need to reverse it to get the actuall
        // value
        // We can then add up all the amplitude data and divide it by their amount to
        // get the mean
        // When we save the value we need to reverse it again

        // The result will have the size of the larger audio file. In my case its file2
        ByteBuffer result = ByteBuffer.allocate(larger.capacity());

        while (larger.hasRemaining()) {

            // getShort() for SampleSize 16bit get() for 8 bit.
            // Reverse the short because of LittleEndian/BigEndian
            short sum = Short.reverseBytes(larger.getShort());
            int matches = 1;

            // check if the smaller file still has content so it needs to merge
            if (smaller.hasRemaining()) {
                // getShort() for SampleSize 16bit get() for 8 bit

                // Reverse the short because of LittleEndian/BigEndian
                sum += Short.reverseBytes(smaller.getShort());
                matches++;
            }

            // append the mean of all merged values
            // reverse again
            result.putShort(Short.reverseBytes((short) (sum / (float) matches)));

        }

        return result;
    }

我们现在需要创建自己的 .wav 文件头并附加我们的合并数据。最后,我们可以将更改写入磁盘。

public static void saveToFile(File file, byte[] audioData) throws IOException {

        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
    }

重要的是要合并的两个文件具有相同的
Channels、SampleSize、SampleRate、AudioFormat
这是计算标题数据的方式:

private static short audioFormat = 1;
private static int sampleRate = 44100;
private static short sampleSize = 16;
private static short channels = 2;
private static short blockAlign = (short) (sampleSize * channels / 8);
private static int byteRate = sampleRate * sampleSize * channels / 8;

这是您的工作示例,我将所有内容放在一起:


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(secondsToByte(offsetInSeconds));
    }

    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

                // 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
    }

}
于 2021-12-30T22:18:43.930 回答