Java: объединение 4 отдельных массивов аудиобайтов в один аудиофайл wav - PullRequest
1 голос
/ 15 марта 2020

Я пытался объединить 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;
}

Должен быть способ лучше, чем то, что я делаю сейчас, это сводит меня с ума.

Ответы [ 2 ]

1 голос
/ 17 марта 2020

Чтобы смешать два byte массива с 16-битными звуковыми сэмплами, вы должны сначала преобразовать эти массивы в int массивы, т. Е. Массивы на основе семплов, затем добавить их (для микширования) и затем преобразовать обратно в байтовые массивы. При преобразовании массива byte в массив int необходимо убедиться, что вы используете правильный порядковый номер (порядок байтов) .

Вот некоторый код, который позволяет смешивать два массива. В конце приведен пример кода (с использованием синусоидальных волн), который демонстрирует подход. Обратите внимание, что это, возможно, не идеальный способ кодирования, а рабочий пример для демонстрации концепции. Использование потоков или линий, как Фил рекомендует , вероятно, самый разумный общий подход.

Удачи!

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);
    }
}
1 голос
/ 16 марта 2020

Значение temp_array.length в предложении else for l oop равно bufferB.length. Но значение в предложении if равно bufferA.length/2. Вы пропустили деление на 2 в предложении else?

В любом случае, обычно аудиоданные (сигналы) обрабатываются как потоки. С каждой открытой строкой извлекайте значения байтов из каждого предопределенного буфера, достаточные для получения одинакового количества значений PCM из каждой строки. Если одна строка заканчивается перед другими, вы можете заполнить эту строку значениями 0.

Если нет действительно веской причины для добавления массивов неравной длины, я думаю, что лучше избегать этого. Вместо этого используйте указатели (если вы рисуете из массивов) или методы прогрессивного чтения () (если из строк AudioInput), чтобы получать фиксированное количество значений PCM при каждой итерации l oop. В противном случае, я думаю, что вы напрашиваетесь на неприятности, без необходимости усложняющие вещи.

Я видел работоспособные решения, где одновременно обрабатывается только одно значение PCM из каждого источника, и даже больше, например 1000 или даже полное полсекунды (22 050 при скорости 44100 кадров в секунду). Главное, получить одинаковое количество PCM из каждого источника на каждой итерации и заполнить 0, если в источнике не хватает данных.

...