有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

Java:将4个独立的音频字节数组组合成单个wav音频文件

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

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

一定有比我现在所做的更好的方法,这让我发疯


共 (2) 个答案

  1. # 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);
        }
    }
    
  2. # 2 楼答案

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

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

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

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