Сгенерированный звук, воспроизводимый с помощью SouceDataLine, нечеткий

Я пытаюсь создать набор одновременных тонов в реальном времени. Но все звуки, производимые программой, являются "нечеткими", или "статичными", или даже звучат как "визг" на заднем плане. Это особенно заметно в более низких звуках. Вот код:

static final long bufferLength = 44100;
static final AudioFormat af = new AudioFormat(bufferLength, 8, 1, true, false);
static boolean go = true; //to be changed somewhere else

static void startSound(double[] hertz) {
    if (hertz.length == 0) {return;}
    try {
        SourceDataLine sdl = AudioSystem.getSourceDataLine(af);
        sdl.open();
        sdl.start();
        int i = 0;
        //iterate as long as the sound must play
        do {
            //create a new buffer
            double[] buf = new double[128]; //arbitrary number
            final int startI = i;
            //iterate through each of the tones
            for (int k = 0; k < hertz.length; k++) {
                i = startI;
                //iterate through each of the samples for this buffer
                for (int j = 0; j < buf.length; j++) {
                    double x = (double)i/bufferLength*hertz[k]*2*Math.PI;
                    double wave1 = Math.sin(x);
                    //decrease volume with increasing pitch
                    double volume = Math.min(Math.max(300 - hertz[k], 50d), 126d);
                    buf[j] += wave1*volume;
                    i++;
                    if (i == 9999999) { //prevent i from getting too big
                        i = 0;
                    }
                }
            }

            final byte[] finalBuffer = new byte[buf.length];
            //copy the double buffer into byte buffer
            for (int j = 0; j < buf.length; j++) {
                //divide by hertz.length to prevent simultaneous sounds
                //    from becoming too loud
                finalBuffer[j] = (byte)(buf[j]/hertz.length);
            }

            //play the sound
            sdl.write(finalBuffer, 0, finalBuffer.length);
        } while (go);
        sdl.flush();
        sdl.stop();
    } catch (LineUnavailableException e) {
        e.printStackTrace();
    }
}

//play some deep example tones
startSound(new double[]{65.4064, 58.2705, 48.9995});

Я попытался записать звук, выводимый из этой программы, и волны кажутся слегка неровными. Но когда я распечатываю сгенерированные волны прямо из программы, они кажутся совершенно гладкими. Звук, который я генерирую, просто не совпадает со звуком, исходящим из динамиков. Может кто-нибудь уловить, что я делаю не так?

1 ответ

Решение

Согласно моему комментарию, я думаю, что вы слышите ошибку квантования из-за 8-битного звука, и вы должны переключиться на 16-битный. Ошибка квантования иногда упоминается как шум, но является типом искажения квадратной гармоники и является источником тонких обертонов, которые вы слышите.

8-битный иногда приемлем для таких вещей, как речь, где он будет звучать больше как шум. Искажение более заметно с чистыми тонами.

Я передал твой код грубому MCVE, чтобы продемонстрировать разницу.

class SoundTest {
    static final int bufferLength = 44100;
    static final AudioFormat af8 = new AudioFormat(bufferLength, 8, 1, true, false);
    static final AudioFormat af16 = new AudioFormat(bufferLength, 16, 1, true, false);
    static volatile boolean go = true; //to be changed somewhere else

    static void startSound8(double[] hertz) {
        if (hertz.length == 0) {return;}
        try {
            SourceDataLine sdl = AudioSystem.getSourceDataLine(af8);
            sdl.open();
            sdl.start();
            int i = 0;
            //iterate as long as the sound must play
            do {
                //create a new buffer
                double[] buf = new double[128]; //arbitrary number
                final int startI = i;
                //iterate through each of the tones
                for (int k = 0; k < hertz.length; k++) {
                    i = startI;
                    //iterate through each of the samples for this buffer
                    for (int j = 0; j < buf.length; j++) {
                        double x = (double)i/bufferLength*hertz[k]*2*Math.PI;
                        double wave1 = Math.sin(x);
                        //decrease volume with increasing pitch
//                        double volume = Math.min(Math.max(300 - hertz[k], 50d), 126d);
                        double volume = 64;
                        buf[j] += wave1*volume;
                        i++;
                        if (i == 9999999) { //prevent i from getting too big
                            i = 0;
                        }
                    }
                }

                final byte[] finalBuffer = new byte[buf.length];
                //copy the double buffer into byte buffer
                for (int j = 0; j < buf.length; j++) {
                    //divide by hertz.length to prevent simultaneous sounds
                    //    from becoming too loud
                    finalBuffer[j] = (byte)(buf[j]/hertz.length);
                }

                //play the sound
                sdl.write(finalBuffer, 0, finalBuffer.length);
            } while (go);
            sdl.flush();
            sdl.stop();
            synchronized (SoundTest.class) {
                SoundTest.class.notifyAll();
            }
        } catch (LineUnavailableException e) {
            e.printStackTrace();
        }
    }

    static void startSound16(double[] hertz) {
        if (hertz.length == 0) {return;}
        try {
            SourceDataLine sdl = AudioSystem.getSourceDataLine(af16);
            sdl.open();
            sdl.start();
            int i = 0;
            //iterate as long as the sound must play
            do {
                //create a new buffer
                double[] buf = new double[128]; //arbitrary number
                final int startI = i;
                //iterate through each of the tones
                for (int k = 0; k < hertz.length; k++) {
                    i = startI;
                    //iterate through each of the samples for this buffer
                    for (int j = 0; j < buf.length; j++) {
                        double x = (double)i/bufferLength*hertz[k]*2*Math.PI;
                        double wave1 = Math.sin(x);
                        //decrease volume with increasing pitch
                        // double volume = Math.min(Math.max(300 - hertz[k], 50d), 126d);
                        double volume = 16384;
                        buf[j] += wave1*volume;
                        i++;
                        if (i == 9999999) { //prevent i from getting too big
                            i = 0;
                        }
                    }
                }

                final byte[] finalBuffer = new byte[buf.length * 2];

                //copy the double buffer into byte buffer
                for (int j = 0; j < buf.length; j++) {
                    //divide by hertz.length to prevent simultaneous sounds
                    //    from becoming too loud

                    int a = (int) (buf[j] / hertz.length);
                    finalBuffer[j * 2] = (byte) a;
                    finalBuffer[(j * 2) + 1] = (byte) (a >>> 8);
                }

                //play the sound
                sdl.write(finalBuffer, 0, finalBuffer.length);
            } while (go);
            sdl.flush();
            sdl.stop();
            synchronized (SoundTest.class) {
                SoundTest.class.notifyAll();
            }
        } catch (LineUnavailableException e) {
            e.printStackTrace();
        }
    }

    static void playTone(final double hz, final boolean fewBits) {
        go = true;
        new Thread() {
            @Override
            public void run() {
                if (fewBits) {
                    startSound8(new double[] {hz});
                } else {
                    startSound16(new double[] {hz});
                }
            }
        }.start();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException x) {
            x.printStackTrace();
        } finally {
            go = false;
            synchronized (SoundTest.class) {
                try {
                    SoundTest.class.wait();
                } catch (InterruptedException x) {
                    x.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        playTone(220, true);
        playTone(220, false);
    }
}

Я обсуждаю концепции для битовых операций, которые я использовал для упаковки 16-битного байтового массива, и здесь приведен пример кода.

Также стоит упомянуть, что если профессиональному приложению по какой-то причине необходимо использовать 8-битный код, оно, вероятно, добавит дизеринг перед квантованием, что звучит лучше, чем чистая ошибка квантования. (То же самое относится и к 16-разрядному, но ошибка квантования в 16-разрядном не слышна, если она не была накоплена.)

Другие вопросы по тегам