Сгенерированный звук, воспроизводимый с помощью 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-разрядном не слышна, если она не была накоплена.)