Что ж, самый простой ответ заключается в том, что в настоящий момент Java не может создавать образцы данных для программиста.
Это цитата из официального руководства:
Есть два способа применить обработку сигнала:
Вы можете использовать любую обработку, поддерживаемую микшером или его составными строками, запрашивая Control
объекты и затем устанавливая элементы управления по желанию пользователя. Типичные элементы управления, поддерживаемые микшерами и линиями, включают элементы управления усилением, панорамированием и реверберацией.
Если необходимая вам обработка не обеспечивается микшером или его строками, ваша программа может работать непосредственно с байтами звука, манипулируя ими по своему усмотрению.
На этой странице более подробно обсуждается первый метод, поскольку для второго метода не существует специального API.
Воспроизведение с javax.sound.sampled
в значительной степени действует как мост между файлом и аудиоустройством. Байты считываются из файла и отправляются.
Не думайте, что байты являются значимыми аудиосэмплами! Если у вас нет 8-битного файла AIFF, его нет. (С другой стороны, если образцы определенно имеют 8-битную подпись, вы можете выполнять с ними арифметические операции. Использование 8-битных кодов - один из способов избежать описанной здесь сложности. , если вы просто играете.)
Поэтому вместо этого я перечислю типы AudioFormat.Encoding
и опишите, как их декодировать самостоятельно. В этом ответе не описано, как их кодировать, но он включен в полный пример кода внизу. Кодирование - это в основном процесс декодирования в обратном порядке.
Это длинный ответ, но я хотел дать подробный обзор.
Немного о цифровом аудио
Обычно при объяснении цифрового звука мы имеем в виду линейную импульсно-кодовую модуляцию (LPCM ).
Сплошная звуковая волна дискретизируется через равные промежутки времени, а амплитуды квантуются до целых чисел некоторого масштаба.
Здесь показана синусоидальная волна, дискретизированная и квантованная до 4-х разряда:
(Обратите внимание, что наиболее положительное значение в представлении дополнение до двух на 1 меньше самого отрицательного значения . Это второстепенная деталь, о которой следует помнить. Например, если вы обрезаете аудио и забываете об этом, положительные клипы будут переполняться.)
Когда у нас есть звук на компьютере, у нас есть массив этих образцов. Образец массива - это то, во что мы хотим превратить массив byte
.
Чтобы декодировать сэмплы PCM, мы не особо заботимся о частоте дискретизации или количестве каналов, поэтому я не буду много говорить о них здесь. Каналы обычно чередуются, поэтому, если бы у нас был их массив, они бы хранились следующим образом:
Index 0: Sample 0 (Left Channel)
Index 1: Sample 0 (Right Channel)
Index 2: Sample 1 (Left Channel)
Index 3: Sample 1 (Right Channel)
Index 4: Sample 2 (Left Channel)
Index 5: Sample 2 (Right Channel)
...
Другими словами, для стерео образцы в массиве просто чередуются между левым и правым.
Некоторые предположения
Все примеры кода предполагают следующие объявления:
byte[] bytes;
Массив byte
, считанный из AudioInputStream
.
float[] samples;
Выходной массив сэмплов, который мы собираемся заполнить.
float sample;
Образец, над которым мы сейчас работаем.
long temp;
Промежуточное значение, используемое для общих манипуляций.
int i;
Позиция в массиве byte
, с которой начинаются данные текущей выборки.
Мы нормализуем все образцы в нашем float[]
массиве до диапазона -1f <= sample <= 1f
. Все звуки с плавающей запятой, которые я видел, поступают таким образом, и это довольно удобно.
Если наш исходный звук еще не поступил таким образом (как, например, для целочисленных сэмплов), мы можем нормализовать их сами, используя следующее:
sample = sample / fullScale(bitsPerSample);
Где fullScale
- это 2 bitsPerSample - 1, то есть Math.pow(2, bitsPerSample-1)
.
Как мне привести массив byte
к значимым данным?
Массив byte
содержит образцы кадров, разделенных на одну строку. На самом деле это очень просто, за исключением того, что называется endianness, то есть порядка byte
в каждый образец пакета.
Вот диаграмма. Этот образец (упакованный в массив byte
) содержит десятичное значение 9999:
24-bit sample as big-endian:
bytes[i] bytes[i + 1] bytes[i + 2]
┌──────┐ ┌──────┐ ┌──────┐
00000000 00100111 00001111
24-bit sample as little-endian:
bytes[i] bytes[i + 1] bytes[i + 2]
┌──────┐ ┌──────┐ ┌──────┐
00001111 00100111 00000000
Они содержат одинаковые двоичные значения; однако byte
заказы отменяются.
- При обратном порядке байтов более значимые
byte
идут перед менее значимыми byte
.
- В little-endian менее значимые
byte
идут перед более значимыми bytes
.
Файлы WAV хранятся в обратном порядке и Файлы AIFF хранятся с прямым порядком байтов. Порядок байтов можно получить из AudioFormat.isBigEndian
< / а>.
Чтобы объединить byte
s и поместить их в нашу переменную long temp
, мы:
- Побитовое И каждый
byte
с маской 0xFF
(которая равна 0b1111_1111
), чтобы избежать расширения знака, когда byte
автоматически продвигается. (char
, byte
и short
повышаются до int
, когда над ними выполняются арифметические операции.) См. Также Что делает value & 0xff
в Java?
- Битовый сдвиг каждого
byte
в позицию.
- Поразрядное ИЛИ
byte
вместе.
Вот 24-битный пример:
long temp;
if (isBigEndian) {
temp = (
((bytes[i ] & 0xffL) << 16)
| ((bytes[i + 1] & 0xffL) << 8)
| (bytes[i + 2] & 0xffL)
);
} else {
temp = (
(bytes[i ] & 0xffL)
| ((bytes[i + 1] & 0xffL) << 8)
| ((bytes[i + 2] & 0xffL) << 16)
);
}
Обратите внимание, что порядок сдвига меняется на обратный в зависимости от порядка байтов.
Это также можно обобщить до цикла, который можно увидеть в полном коде внизу этого ответа. (См. Методы unpackAnyBit
и packAnyBit
.)
Теперь, когда у нас есть byte
, объединенные вместе, мы можем сделать еще несколько шагов, чтобы превратить их в образец. Следующие шаги зависят от фактического кодирования.
Как мне расшифровать Encoding.PCM_SIGNED
?
Знак дополнения до двух должен быть расширен. Это означает, что если старший бит (MSB) установлен в 1, мы заполняем все биты над ним единицами. Арифметический сдвиг вправо (>>
) выполнит заполнение автоматически, если бит знака установлен, поэтому я обычно делаю это следующим образом:
int bitsToExtend = Long.SIZE - bitsPerSample;
float sample = (temp << bitsToExtend) >> bitsToExtend.
(Где Long.SIZE
равно 64. Если бы наша переменная temp
не была long
, мы бы использовали что-то другое. Если бы мы использовали, например, int temp
вместо этого, мы бы использовали 32.)
Чтобы понять, как это работает, вот диаграмма расширения знака с 8-битного на 16-битный:
11111111 is the byte value -1, but the upper bits of the short are 0.
Shift the byte's MSB in to the MSB position of the short.
0000 0000 1111 1111
<< 8
───────────────────
1111 1111 0000 0000
Shift it back and the right-shift fills all the upper bits with 1s.
We now have the short value of -1.
1111 1111 0000 0000
>> 8
───────────────────
1111 1111 1111 1111
Положительные значения (которые имели 0 в MSB) остаются без изменений. Это хорошее свойство арифметического сдвига вправо.
Затем нормализуйте образец, как описано в разделе Некоторые предположения.
Возможно, вам не понадобится писать явное расширение знака, если ваш код простой
Java автоматически расширяет знак при преобразовании одного целочисленного типа в более крупный, например byte
в int
. Если вы знаете, что ваш формат ввода и вывода всегда подписан, вы можете использовать автоматическое расширение знака при объединении байтов на предыдущем шаге.
Вспомните из раздела выше (Как мне преобразовать массив байтов в осмысленные данные?), что мы использовали b & 0xFF
, чтобы предотвратить появление расширения знака. Если вы просто удалите & 0xFF
из самого высокого byte
, расширение знака произойдет автоматически.
Например, следующий код декодирует 16-битные выборки с прямым порядком байтов со знаком:
for (int i = 0; i < bytes.length; i++) {
int sample = (bytes[i] << 8) // high byte is sign-extended
| (bytes[i + 1] & 0xFF); // low byte is not
// ...
}
Как мне расшифровать Encoding.PCM_UNSIGNED
?
Превращаем его в число со знаком. Беззнаковые образцы просто смещаются так, например, что:
- Беззнаковое значение 0 соответствует самому отрицательному значению со знаком.
- Беззнаковое значение 2 bitsPerSample - 1 соответствует значению со знаком 0.
- Беззнаковое значение 2 bitsPerSample соответствует наиболее положительному значению со знаком.
Оказывается, это довольно просто. Просто вычтите смещение:
float sample = temp - fullScale(bitsPerSample);
Затем нормализуйте образец, как описано в разделе Некоторые предположения.
Как мне расшифровать Encoding.PCM_FLOAT
?
Это новое, начиная с Java 7.
На практике PCM с плавающей запятой обычно является либо 32-битным IEEE, либо 64-битным IEEE и уже нормализован до диапазона ±1.0
. Примеры можно получить с помощью служебных методов Float#intBitsToFloat
и Double#longBitsToDouble
.
// IEEE 32-bit
float sample = Float.intBitsToFloat((int) temp);
// IEEE 64-bit
double sampleAsDouble = Double.longBitsToDouble(temp);
float sample = (float) sampleAsDouble; // or just use double for arithmetic
Как мне декодировать Encoding.ULAW
и Encoding.ALAW
?
Это кодеки сжатия companding, которые более распространены в телефонах и т. Д. Я полагаю, что они поддерживаются javax.sound.sampled
, потому что они используются в формате Au от Sun. (Однако это не ограничивается только этим типом контейнера. Например, WAV может содержать эти кодировки.)
Вы можете концептуализировать A-law и - law, как будто они являются форматом с плавающей запятой. Это форматы PCM, но диапазон значений нелинейный.
Расшифровать их можно двумя способами. Я покажу способ использования математической формулы. Вы также можете декодировать их, напрямую управляя двоичным файлом: описан в этом сообщении блога, но выглядит более эзотерическим.
Для обоих сжатые данные 8-битные. Стандартно A-law составляет 13 бит при декодировании, а -law - 14 бит при декодировании; однако применение формулы дает диапазон ±1.0
.
Прежде чем применять формулу, необходимо сделать три вещи:
- Некоторые биты обычно инвертируются для хранения по причинам, связанным с целостностью данных.
- Они хранятся как знак и величина (а не как дополнение до двух).
- Формула также предполагает диапазон
±1.0
, поэтому необходимо масштабировать 8-битное значение.
Для -law все биты инвертируются, поэтому:
temp ^= 0xffL; // 0xff == 0b1111_1111
(Обратите внимание, что мы не можем использовать ~
, потому что мы не хотим инвертировать старшие биты long
.)
Для A-закона все остальные биты инвертируются, поэтому:
temp ^= 0x55L; // 0x55 == 0b0101_0101
(Для инверсии можно использовать XOR. См. Как установить, сбросить и переключить бит? а>)
Чтобы преобразовать знак и величину в дополнение до двух, мы:
- Проверьте, установлен ли бит знака.
- Если это так, очистите бит знака и инвертируйте число.
// 0x80 == 0b1000_0000
if ((temp & 0x80L) != 0) {
temp ^= 0x80L;
temp = -temp;
}
Затем масштабируйте закодированные числа так же, как описано в Некоторые предположения:
sample = temp / fullScale(8);
Теперь мы можем применить расширение.
Тогда формула -law, переведенная на Java, выглядит так:
sample = (float) (
signum(sample)
*
(1.0 / 255.0)
*
(pow(256.0, abs(sample)) - 1.0)
);
Тогда формула A-закона, переведенная на Java, выглядит так:
float signum = signum(sample);
sample = abs(sample);
if (sample < (1.0 / (1.0 + log(87.7)))) {
sample = (float) (
sample * ((1.0 + log(87.7)) / 87.7)
);
} else {
sample = (float) (
exp((sample * (1.0 + log(87.7))) - 1.0) / 87.7
);
}
sample = signum * sample;
Вот полный пример кода для класса SimpleAudioConversion
.
package mcve.audio;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioFormat.Encoding;
import static java.lang.Math.*;
/**
* <p>Performs simple audio format conversion.</p>
*
* <p>Example usage:</p>
*
* <pre>{@code AudioInputStream ais = ... ;
* SourceDataLine line = ... ;
* AudioFormat fmt = ... ;
*
* // do setup
*
* for (int blen = 0; (blen = ais.read(bytes)) > -1;) {
* int slen;
* slen = SimpleAudioConversion.decode(bytes, samples, blen, fmt);
*
* // do something with samples
*
* blen = SimpleAudioConversion.encode(samples, bytes, slen, fmt);
* line.write(bytes, 0, blen);
* }}</pre>
*
* @author Radiodef
* @see <a href="http://stackoverflow.com/a/26824664/2891664">Overview on Stack Overflow</a>
*/
public final class SimpleAudioConversion {
private SimpleAudioConversion() {}
/**
* Converts from a byte array to an audio sample float array.
*
* @param bytes the byte array, filled by the AudioInputStream
* @param samples an array to fill up with audio samples
* @param blen the return value of AudioInputStream.read
* @param fmt the source AudioFormat
*
* @return the number of valid audio samples converted
*
* @throws NullPointerException if bytes, samples or fmt is null
* @throws ArrayIndexOutOfBoundsException
* if bytes.length is less than blen or
* if samples.length is less than blen / bytesPerSample(fmt.getSampleSizeInBits())
*/
public static int decode(byte[] bytes,
float[] samples,
int blen,
AudioFormat fmt) {
int bitsPerSample = fmt.getSampleSizeInBits();
int bytesPerSample = bytesPerSample(bitsPerSample);
boolean isBigEndian = fmt.isBigEndian();
Encoding encoding = fmt.getEncoding();
double fullScale = fullScale(bitsPerSample);
int i = 0;
int s = 0;
while (i < blen) {
long temp = unpackBits(bytes, i, isBigEndian, bytesPerSample);
float sample = 0f;
if (encoding == Encoding.PCM_SIGNED) {
temp = extendSign(temp, bitsPerSample);
sample = (float) (temp / fullScale);
} else if (encoding == Encoding.PCM_UNSIGNED) {
temp = unsignedToSigned(temp, bitsPerSample);
sample = (float) (temp / fullScale);
} else if (encoding == Encoding.PCM_FLOAT) {
if (bitsPerSample == 32) {
sample = Float.intBitsToFloat((int) temp);
} else if (bitsPerSample == 64) {
sample = (float) Double.longBitsToDouble(temp);
}
} else if (encoding == Encoding.ULAW) {
sample = bitsToMuLaw(temp);
} else if (encoding == Encoding.ALAW) {
sample = bitsToALaw(temp);
}
samples[s] = sample;
i += bytesPerSample;
s++;
}
return s;
}
/**
* Converts from an audio sample float array to a byte array.
*
* @param samples an array of audio samples to encode
* @param bytes an array to fill up with bytes
* @param slen the return value of the decode method
* @param fmt the destination AudioFormat
*
* @return the number of valid bytes converted
*
* @throws NullPointerException if samples, bytes or fmt is null
* @throws ArrayIndexOutOfBoundsException
* if samples.length is less than slen or
* if bytes.length is less than slen * bytesPerSample(fmt.getSampleSizeInBits())
*/
public static int encode(float[] samples,
byte[] bytes,
int slen,
AudioFormat fmt) {
int bitsPerSample = fmt.getSampleSizeInBits();
int bytesPerSample = bytesPerSample(bitsPerSample);
boolean isBigEndian = fmt.isBigEndian();
Encoding encoding = fmt.getEncoding();
double fullScale = fullScale(bitsPerSample);
int i = 0;
int s = 0;
while (s < slen) {
float sample = samples[s];
long temp = 0L;
if (encoding == Encoding.PCM_SIGNED) {
temp = (long) (sample * fullScale);
} else if (encoding == Encoding.PCM_UNSIGNED) {
temp = (long) (sample * fullScale);
temp = signedToUnsigned(temp, bitsPerSample);
} else if (encoding == Encoding.PCM_FLOAT) {
if (bitsPerSample == 32) {
temp = Float.floatToRawIntBits(sample);
} else if (bitsPerSample == 64) {
temp = Double.doubleToRawLongBits(sample);
}
} else if (encoding == Encoding.ULAW) {
temp = muLawToBits(sample);
} else if (encoding == Encoding.ALAW) {
temp = aLawToBits(sample);
}
packBits(bytes, i, temp, isBigEndian, bytesPerSample);
i += bytesPerSample;
s++;
}
return i;
}
/**
* Computes the block-aligned bytes per sample of the audio format,
* using Math.ceil(bitsPerSample / 8.0).
* <p>
* Round towards the ceiling because formats that allow bit depths
* in non-integral multiples of 8 typically pad up to the nearest
* integral multiple of 8. So for example, a 31-bit AIFF file will
* actually store 32-bit blocks.
*
* @param bitsPerSample the return value of AudioFormat.getSampleSizeInBits
* @return The block-aligned bytes per sample of the audio format.
*/
public static int bytesPerSample(int bitsPerSample) {
return (int) ceil(bitsPerSample / 8.0); // optimization: ((bitsPerSample + 7) >>> 3)
}
/**
* Computes the largest magnitude representable by the audio format,
* using Math.pow(2.0, bitsPerSample - 1). Note that for two's complement
* audio, the largest positive value is one less than the return value of
* this method.
* <p>
* The result is returned as a double because in the case that
* bitsPerSample is 64, a long would overflow.
*
* @param bitsPerSample the return value of AudioFormat.getBitsPerSample
* @return the largest magnitude representable by the audio format
*/
public static double fullScale(int bitsPerSample) {
return pow(2.0, bitsPerSample - 1); // optimization: (1L << (bitsPerSample - 1))
}
private static long unpackBits(byte[] bytes,
int i,
boolean isBigEndian,
int bytesPerSample) {
switch (bytesPerSample) {
case 1: return unpack8Bit(bytes, i);
case 2: return unpack16Bit(bytes, i, isBigEndian);
case 3: return unpack24Bit(bytes, i, isBigEndian);
default: return unpackAnyBit(bytes, i, isBigEndian, bytesPerSample);
}
}
private static long unpack8Bit(byte[] bytes, int i) {
return bytes[i] & 0xffL;
}
private static long unpack16Bit(byte[] bytes,
int i,
boolean isBigEndian) {
if (isBigEndian) {
return (
((bytes[i ] & 0xffL) << 8)
| (bytes[i + 1] & 0xffL)
);
} else {
return (
(bytes[i ] & 0xffL)
| ((bytes[i + 1] & 0xffL) << 8)
);
}
}
private static long unpack24Bit(byte[] bytes,
int i,
boolean isBigEndian) {
if (isBigEndian) {
return (
((bytes[i ] & 0xffL) << 16)
| ((bytes[i + 1] & 0xffL) << 8)
| (bytes[i + 2] & 0xffL)
);
} else {
return (
(bytes[i ] & 0xffL)
| ((bytes[i + 1] & 0xffL) << 8)
| ((bytes[i + 2] & 0xffL) << 16)
);
}
}
private static long unpackAnyBit(byte[] bytes,
int i,
boolean isBigEndian,
int bytesPerSample) {
long temp = 0;
if (isBigEndian) {
for (int b = 0; b < bytesPerSample; b++) {
temp |= (bytes[i + b] & 0xffL) << (
8 * (bytesPerSample - b - 1)
);
}
} else {
for (int b = 0; b < bytesPerSample; b++) {
temp |= (bytes[i + b] & 0xffL) << (8 * b);
}
}
return temp;
}
private static void packBits(byte[] bytes,
int i,
long temp,
boolean isBigEndian,
int bytesPerSample) {
switch (bytesPerSample) {
case 1: pack8Bit(bytes, i, temp);
break;
case 2: pack16Bit(bytes, i, temp, isBigEndian);
break;
case 3: pack24Bit(bytes, i, temp, isBigEndian);
break;
default: packAnyBit(bytes, i, temp, isBigEndian, bytesPerSample);
break;
}
}
private static void pack8Bit(byte[] bytes, int i, long temp) {
bytes[i] = (byte) (temp & 0xffL);
}
private static void pack16Bit(byte[] bytes,
int i,
long temp,
boolean isBigEndian) {
if (isBigEndian) {
bytes[i ] = (byte) ((temp >>> 8) & 0xffL);
bytes[i + 1] = (byte) ( temp & 0xffL);
} else {
bytes[i ] = (byte) ( temp & 0xffL);
bytes[i + 1] = (byte) ((temp >>> 8) & 0xffL);
}
}
private static void pack24Bit(byte[] bytes,
int i,
long temp,
boolean isBigEndian) {
if (isBigEndian) {
bytes[i ] = (byte) ((temp >>> 16) & 0xffL);
bytes[i + 1] = (byte) ((temp >>> 8) & 0xffL);
bytes[i + 2] = (byte) ( temp & 0xffL);
} else {
bytes[i ] = (byte) ( temp & 0xffL);
bytes[i + 1] = (byte) ((temp >>> 8) & 0xffL);
bytes[i + 2] = (byte) ((temp >>> 16) & 0xffL);
}
}
private static void packAnyBit(byte[] bytes,
int i,
long temp,
boolean isBigEndian,
int bytesPerSample) {
if (isBigEndian) {
for (int b = 0; b < bytesPerSample; b++) {
bytes[i + b] = (byte) (
(temp >>> (8 * (bytesPerSample - b - 1))) & 0xffL
);
}
} else {
for (int b = 0; b < bytesPerSample; b++) {
bytes[i + b] = (byte) ((temp >>> (8 * b)) & 0xffL);
}
}
}
private static long extendSign(long temp, int bitsPerSample) {
int bitsToExtend = Long.SIZE - bitsPerSample;
return (temp << bitsToExtend) >> bitsToExtend;
}
private static long unsignedToSigned(long temp, int bitsPerSample) {
return temp - (long) fullScale(bitsPerSample);
}
private static long signedToUnsigned(long temp, int bitsPerSample) {
return temp + (long) fullScale(bitsPerSample);
}
// mu-law constant
private static final double MU = 255.0;
// A-law constant
private static final double A = 87.7;
// natural logarithm of A
private static final double LN_A = log(A);
private static float bitsToMuLaw(long temp) {
temp ^= 0xffL;
if ((temp & 0x80L) != 0) {
temp = -(temp ^ 0x80L);
}
float sample = (float) (temp / fullScale(8));
return (float) (
signum(sample)
*
(1.0 / MU)
*
(pow(1.0 + MU, abs(sample)) - 1.0)
);
}
private static long muLawToBits(float sample) {
double sign = signum(sample);
sample = abs(sample);
sample = (float) (
sign * (log(1.0 + (MU * sample)) / log(1.0 + MU))
);
long temp = (long) (sample * fullScale(8));
if (temp < 0) {
temp = -temp ^ 0x80L;
}
return temp ^ 0xffL;
}
private static float bitsToALaw(long temp) {
temp ^= 0x55L;
if ((temp & 0x80L) != 0) {
temp = -(temp ^ 0x80L);
}
float sample = (float) (temp / fullScale(8));
float sign = signum(sample);
sample = abs(sample);
if (sample < (1.0 / (1.0 + LN_A))) {
sample = (float) (sample * ((1.0 + LN_A) / A));
} else {
sample = (float) (exp((sample * (1.0 + LN_A)) - 1.0) / A);
}
return sign * sample;
}
private static long aLawToBits(float sample) {
double sign = signum(sample);
sample = abs(sample);
if (sample < (1.0 / A)) {
sample = (float) ((A * sample) / (1.0 + LN_A));
} else {
sample = (float) ((1.0 + log(A * sample)) / (1.0 + LN_A));
}
sample *= sign;
long temp = (long) (sample * fullScale(8));
if (temp < 0) {
temp = -temp ^ 0x80L;
}
return temp ^ 0x55L;
}
}
person
Radiodef
schedule
09.11.2014