【问题标题】:Java: Combining 4 separate audio byte arrays into single wav audio fileJava:将 4 个单独的音频字节数组组合成单个 wav 音频文件
【发布时间】:2020-06-26 13:28:03
【问题描述】:

我尝试将 4 个单独的字节数组组合到一个文件中,但我只得到空指针异常,我不知道为什么。我的音频格式是 16 位 PCM 签名的,我知道我应该使用 short 而不是字节,但老实说,我完全迷失了。

private short[] mixByteBuffers(byte[] bufferA, byte[] bufferB) {
    short[] first_array = new short[bufferA.length/2];
    short[] second_array = new short [bufferB.length/2];
    short[] final_array = null;

    if(first_array.length > second_array.length) {
        short[] temp_array = new short[bufferA.length];

        for (int i = 0; i < temp_array.length; i++) {
            int mixed=(int)first_array[i] + (int)second_array[i];
            if (mixed>32767) mixed=32767;
            if (mixed<-32768) mixed=-32768;
            temp_array[i] = (short)mixed;
            final_array = temp_array;
        }
    }
    else {
        short[] temp_array = new short[bufferB.length];

        for (int i = 0; i < temp_array.length; i++) {
            int mixed=(int)first_array[i] + (int)second_array[i];
            if (mixed>32767) mixed=32767;
            if (mixed<-32768) mixed=-32768;
            temp_array[i] = (short)mixed;
            final_array = temp_array;
        }        
    }
    return final_array;
}

这是我目前正在尝试的,但它会以java.lang.ArrayIndexOutOfBoundsException: 0 在线返回

int mixed = (int)first_array[i] + (int)second_array[i];

我的数组长度不一样,这就是我调用函数的方式:

public void combineAudio() {
    short[] combinationOne = mixByteBuffers(tempByteArray1, tempByteArray2);
    short[] combinationTwo = mixByteBuffers(tempByteArray3, tempByteArray4);
    short[] channelsCombinedAll = mixShortBuffers(combinationOne, combinationTwo);
    byte[] bytesCombined = new byte[channelsCombinedAll.length * 2];
    ByteBuffer.wrap(bytesCombined).order(ByteOrder.LITTLE_ENDIAN)
        .asShortBuffer().put(channelsCombinedAll);

    mixedByteArray = bytesCombined;
}

一定有比我目前正在做的更好的方法,这让我快疯了。

【问题讨论】:

标签: java arrays audio nullpointerexception javasound


【解决方案1】:

要将两个 byte 数组与 16 位声音样本混合,您应该首先将这些数组转换为 int 数组,即基于样本的数组,然后将它们相加(以混合),然后再转换回字节数组。从byte 数组转换为int 数组时,需要确保使用正确的endianness (byte order)

这里有一些代码可以让您混合两个数组。最后有一些示例代码(使用正弦波)演示了该方法。请注意,这可能不是编码的理想方式,而是演示该概念的工作示例。使用流或线,因为Phil recommends 可能是更聪明的整体方法。

祝你好运!

import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;

public class MixDemo {

    public static byte[] mix(final byte[] a, final byte[] b, final boolean bigEndian) {
        final byte[] aa;
        final byte[] bb;

        final int length = Math.max(a.length, b.length);
        // ensure same lengths
        if (a.length != b.length) {
            aa = new byte[length];
            bb = new byte[length];
            System.arraycopy(a, 0, aa, 0, a.length);
            System.arraycopy(b, 0, bb, 0, b.length);
        } else {
            aa = a;
            bb = b;
        }

        // convert to samples
        final int[] aSamples = toSamples(aa, bigEndian);
        final int[] bSamples = toSamples(bb, bigEndian);

        // mix by adding
        final int[] mix = new int[aSamples.length];
        for (int i=0; i<mix.length; i++) {
            mix[i] = aSamples[i] + bSamples[i];
            // enforce min and max (may introduce clipping)
            mix[i] = Math.min(Short.MAX_VALUE, mix[i]);
            mix[i] = Math.max(Short.MIN_VALUE, mix[i]);
        }

        // convert back to bytes
        return toBytes(mix, bigEndian);
    }

    private static int[] toSamples(final byte[] byteSamples, final boolean bigEndian) {
        final int bytesPerChannel = 2;
        final int length = byteSamples.length / bytesPerChannel;
        if ((length % 2) != 0) throw new IllegalArgumentException("For 16 bit audio, length must be even: " + length);
        final int[] samples = new int[length];
        for (int sampleNumber = 0; sampleNumber < length; sampleNumber++) {
            final int sampleOffset = sampleNumber * bytesPerChannel;
            final int sample = bigEndian
                    ? byteToIntBigEndian(byteSamples, sampleOffset, bytesPerChannel)
                    : byteToIntLittleEndian(byteSamples, sampleOffset, bytesPerChannel);
            samples[sampleNumber] = sample;
        }
        return samples;
    }

    private static byte[] toBytes(final int[] intSamples, final boolean bigEndian) {
        final int bytesPerChannel = 2;
        final int length = intSamples.length * bytesPerChannel;
        final byte[] bytes = new byte[length];
        for (int sampleNumber = 0; sampleNumber < intSamples.length; sampleNumber++) {
            final byte[] b = bigEndian
                    ? intToByteBigEndian(intSamples[sampleNumber], bytesPerChannel)
                    : intToByteLittleEndian(intSamples[sampleNumber], bytesPerChannel);
            System.arraycopy(b, 0, bytes, sampleNumber * bytesPerChannel, bytesPerChannel);
        }
        return bytes;
    }

    // from https://github.com/hendriks73/jipes/blob/master/src/main/java/com/tagtraum/jipes/audio/AudioSignalSource.java#L238
    private static int byteToIntLittleEndian(final byte[] buf, final int offset, final int bytesPerSample) {
        int sample = 0;
        for (int byteIndex = 0; byteIndex < bytesPerSample; byteIndex++) {
            final int aByte = buf[offset + byteIndex] & 0xff;
            sample += aByte << 8 * (byteIndex);
        }
        return (short)sample;
    }

    // from https://github.com/hendriks73/jipes/blob/master/src/main/java/com/tagtraum/jipes/audio/AudioSignalSource.java#L247
    private static int byteToIntBigEndian(final byte[] buf, final int offset, final int bytesPerSample) {
        int sample = 0;
        for (int byteIndex = 0; byteIndex < bytesPerSample; byteIndex++) {
            final int aByte = buf[offset + byteIndex] & 0xff;
            sample += aByte << (8 * (bytesPerSample - byteIndex - 1));
        }
        return (short)sample;
    }

    private static byte[] intToByteLittleEndian(final int sample, final int bytesPerSample) {
        byte[] buf = new byte[bytesPerSample];
        for (int byteIndex = 0; byteIndex < bytesPerSample; byteIndex++) {
            buf[byteIndex] = (byte)((sample >>> (8 * byteIndex)) & 0xFF);
        }
        return buf;
    }

    private static byte[] intToByteBigEndian(final int sample, final int bytesPerSample) {
        byte[] buf = new byte[bytesPerSample];
        for (int byteIndex = 0; byteIndex < bytesPerSample; byteIndex++) {
            buf[byteIndex] = (byte)((sample >>> (8 * (bytesPerSample - byteIndex - 1))) & 0xFF);
        }
        return buf;
    }

    public static void main(final String[] args) throws IOException {
        final int sampleRate = 44100;
        final boolean bigEndian = true;
        final int sampleSizeInBits = 16;
        final int channels = 1;
        final boolean signed = true;
        final AudioFormat targetAudioFormat = new AudioFormat(sampleRate, sampleSizeInBits, channels, signed, bigEndian);

        final byte[] a = new byte[sampleRate * 10];
        final byte[] b = new byte[sampleRate * 5];

        // create sine waves
        for (int i=0; i<a.length/2; i++) {
            System.arraycopy(intToByteBigEndian((int)(30000*Math.sin(i*0.5)),2), 0, a, i*2, 2);
        }
        for (int i=0; i<b.length/2; i++) {
            System.arraycopy(intToByteBigEndian((int)(30000*Math.sin(i*0.1)),2), 0, b, i*2, 2);
        }

        final File aFile = new File("a.wav");
        AudioSystem.write(new AudioInputStream(new ByteArrayInputStream(a), targetAudioFormat, a.length),
                AudioFileFormat.Type.WAVE, aFile);
        final File bFile = new File("b.wav");
        AudioSystem.write(new AudioInputStream(new ByteArrayInputStream(b), targetAudioFormat, b.length),
                AudioFileFormat.Type.WAVE, bFile);

        // mix a and b
        final byte[] mixed = mix(a, b, bigEndian);
        final File outFile = new File("out.wav");
        AudioSystem.write(new AudioInputStream(new ByteArrayInputStream(mixed), targetAudioFormat, mixed.length),
                AudioFileFormat.Type.WAVE, outFile);
    }
}

【讨论】:

  • 我尝试使用此实现来混合两个音频样本,但在尝试混合它们时我得到了噪音。两个音频样本是每个样本 16 位的立体声 PCM。无法从中消除噪音。请在这里提出任何建议。 TIA。
  • 上面的代码是单声道的。您必须对其进行适当修改以适应立体声。通常,PCM立体声信号中的样本是交错的,即LRLRLR ...您需要将Ls与Ls混合,将Rs与Rs混合。祝你好运!
  • 我对音频处理了解不多,但根据我目前阅读的内容,对于 16 位立体声样本,每个通道将是 2 个字节。所以上面的算法应该适用于立体声样本。如果我错了,请纠正我。
  • 您仍然需要调整频道数,正确读取您的数据等。我建议您提出一个新问题,准确显示您做了什么以及如何做 (minimal reproducible example)。
  • 我在这里添加了一个新问题。 stackoverflow.com/questions/65662025/…
【解决方案2】:

else 子句for 循环中的temp_array.length 值为bufferB.length。但是if 子句中的值是bufferA.length/2。您是否忽略了 else 子句中的除以 2?

无论如何,通常只将音频数据(信号)作为流处理。打开每一行,从每行获取预定义缓冲区的字节值,足以从每行获取相同数量的 PCM 值。如果一行在其他行之前用完,则可以用 0 值填写该行。

除非有非常充分的理由添加长度不等的数组,否则我认为最好避免这样做。相反,使用指针(如果您从数组中绘制)或渐进式 read() 方法(如果来自 AudioInput 行)在每次循环迭代中获取固定数量的 PCM 值。否则,我认为您是在自找麻烦,使事情变得不必要地复杂化。

我见过一些可行的解决方案,其中一次只从每个源处理一个 PCM 值,甚至更多,例如 1000 甚至整半秒(如果在 44100 fps 时为 22,050)。主要是在每次迭代时从每个源中获取相同数量的 PCM,如果源用完数据,则用 0 填充。

【讨论】:

    猜你喜欢
    • 2011-10-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多