Ответ 1
Ну, самый простой ответ заключается в том, что на данный момент Java не может производить выборочные данные для программиста. Воспроизведение с помощью javax.sound.sampled
в значительной степени действует как мост между файлом и звуковым устройством. Байты считываются из файла и отправляются.
Не предполагайте, что байты являются содержательными образцами аудио! Если у вас нет 8-битного файла AIFF, это не так. (С другой стороны, если образцы определенно 8-битные, вы можете сделать арифметику с ними.)
Поэтому вместо этого перечисляю типы AudioFormat.Encoding
и описываю, как их декодировать самостоятельно. Этот ответ не будет охватывать, как их кодировать, но он включен в полный пример кода внизу. Кодирование - это в основном процесс декодирования в обратном порядке.
Это очень длинный ответ, но я хотел дать как можно более полное описание.
Немного о цифровом аудио
Как правило, когда объясняется цифровое аудио, мы имеем в виду Линейная импульсно-кодовая модуляция (LPCM).
Проводится непрерывная звуковая волна с регулярными интервалами, а амплитуды квантуются целыми числами некоторого масштаба.
Здесь показана синусоидальная волна, отбираемая и квантуемая до 4 бит:
Обратите внимание, что наиболее положительное значение в двух дополнении составляет 1 меньше самого отрицательного значения. Это небольшая деталь, о которой нужно знать. Например, если вы обрезаете форму волны и забудете об этом, положительные клипы будут переполняться.
Когда у нас есть звук на компьютере, у нас есть массив этих образцов. Это то, к чему мы хотим превратить массив байтов в. Чтобы декодировать PCM, мы не слишком заботимся о частоте дискретизации или количестве каналов, поэтому я не буду освещать это здесь.
Некоторые предположения
Все примеры кода будут принимать следующие объявления:
-
byte[] bytes;
Байт-массив, считанный из InputStream. -
float sample;
Образец, над которым мы работаем. -
long temp;
Временное значение, используемое для общей манипуляции. -
int i;
Позиция в массиве байтов в каждом образце.
Все кодировки будут масштабироваться в массиве float[]
до диапазона -1f <= sample <= 1f
. Все форматы с плавающей запятой, которые я видел, приходят именно так, и это также самое полезное.
Масштабирование просто, просто:
sample = sample / fullScale(bitsPerSample);
Где fullScale
- 2 bitsPerSample - 1.
Как я могу принудить массив байтов к значимым данным?
Байт-массив содержит разделяемые выборки кадров и все в строке. Это на самом деле очень прямолинейно, за исключением того, что называется endianness, что является упорядочением байтов в каждом пакете.
Вот диаграмма. Этот пакет содержит десятичное значение 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
Они содержат одни и те же двоичные значения; однако, порядки байтов меняются на противоположные.
- В биг-эндиантах более значимые байты появляются перед менее значительными байтами.
- В little-endian младшие байты перед более значительными байтами.
WAV файлы хранятся в порядке младшего байта и Файлы AIFF хранятся в байтовом порядке большого числа. Endianness можно получить из AudioFormat
.
Чтобы объединить байты и вставить их в нашу переменную temp
, мы:
- Побитовый И каждый байт с маской
0xFF
(которая0b1111_1111
), чтобы избежать расширения знака, когда байт автоматически продвигается. (char, байт и короткий передаются на int, когда на них выполняется арифметика.) - Бит сдвигает каждый байт в позицию.
- Побитовое ИЛИ байты вместе.
Вот пример из 24 бит:
if (isBigEndian) {
temp = (
((bytes[i ] & 0xffL) << 16L)
| ((bytes[i + 1] & 0xffL) << 8L)
| (bytes[i + 2] & 0xffL)
);
} else {
temp = (
(bytes[i ] & 0xffL)
| ((bytes[i + 1] & 0xffL) << 8L)
| ((bytes[i + 2] & 0xffL) << 16L)
);
}
Обратите внимание на то, что порядок сдвига имеет значение для endianness.
Этот процесс также можно обобщить на цикл (который включен в полный код), хотя он гораздо более эзотерический.
Теперь, когда мы объединим байты вместе, мы можем включить их в образец.
Как декодировать Encoding.PCM_SIGNED
?
Знак двух дополнений должен быть расширен. Это означает, что если старший бит (MSB) установлен в 1, мы заполняем все биты над ним равным 1. Арифметический сдвиг вправо (>>
) будет выполнять заполнение для нас автоматически, если бит знака установлен, поэтому я обычно делаю это так:
int extensionBits = bitsPerLong - bitsPerSample;
sample = (temp << extensionBits) >> extensionBits.
(Где bitsPerLong
равно 64.)
Чтобы понять, как это работает, вот диаграмма с расширением 8-бит до 16 бит:
This is the byte value -1 but the upper bits of the short are 0. Shift the byte 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 a 1. We now have the short value of -1. 1111 1111 0000 0000 >> 8 ─────────────────── 1111 1111 1111 1111
Положительные значения (с 0 в MSB) остаются неизменными. Это приятное свойство арифметического сдвига вправо.
Затем масштабируйте его.
Как декодировать Encoding.PCM_UNSIGNED
?
Мы превращаем его в число, подписанное. Беззнаковые выборки просто смещаются так, что, например:
- Беззнаковое значение 0 соответствует подписанному самому отрицательному значению.
- Беззнаковое значение 2 bitsPerSample - 1 соответствует значению 0.
- Беззнаковое значение 2 bitsPerSample соответствует подписанному самому положительному значению.
Итак, это оказывается довольно простым, просто вычтите смещение:
sample = temp - fullScale(bitsPerSample);
Затем масштабируйте его.
Как декодировать Encoding.PCM_FLOAT
?
Это новое, поскольку Java 7.
На практике PCM с плавающей запятой неизменно либо IEEE 32-bit, либо IEEE 64-bit и уже масштабируется до диапазона ±1.0
. Образцы могут быть получены с помощью методов утилиты Float#intBitsToFloat
и Double#longBitsToDouble
.
// IEEE 32-bit
sample = Float.intBitsToFloat((int) temp);
// IEEE 64-bit
sample = (float) Double.longBitsToDouble(temp);
Как декодировать Encoding.ULAW
и Encoding.ALAW
?
Это companding кодеки сжатия, которые чаще встречаются в телефонах и т.д. Они поддерживаются javax.sound.sampled
Я предполагаю, потому что они используются Sun Au format. (Хотя это не ограничивается только этим типом контейнера, например, WAV может содержать эти кодировки.)
Вы можете концептуализировать A-law и & mu; -law как и формат с плавающей запятой. Это форматы PCM, но диапазон значений нелинейный.
Есть два способа их декодирования. Я покажу математическое уравнение. Вы также можете декодировать их, манипулируя двоичным файлом, который является описанным в этом сообщении в блоге, но немного более эзотерическим.
Для обоих сжатых данных 8 бит. Стандартно А-закон является 13-битным при декодировании и & mu; -law является 14-бит при декодировании; однако применение этого уравнения дает диапазон ±1.0
.
Прежде чем вы сможете применить уравнение, вам нужно сделать три вещи:
- Некоторые из битов стандартно инвертируются для хранения из-за некоторой архаической причины, связанной с целостностью данных.
- Они хранятся как знак и величина, а не два дополнения.
- Уравнение также ожидает диапазон
±1.0
, поэтому необходимо масштабировать 8-битное значение.
Для & mu; -law все биты инвертируются так:
temp = temp ^ 0xffL; // 0xff == 0b1111_1111
Для закона А каждый другой бит инвертируется так:
temp = temp ^ 0x55L; // 0x55 == 0b0101_0101
(XOR можно использовать для инверсии. См. "Как вы устанавливаете, очищаете и переключаете бит?" )
Чтобы преобразовать из знака и величины в два дополнения, мы:
- Проверьте, установлен ли бит знака.
- Если это так, очистите бит знака и отрицайте число.
// 0x80 == 0b1000_0000
if ((temp & 0x80L) == 0x80L) {
temp = temp ^ 0x80L;
temp = -temp;
}
Затем масштабируйте закодированные числа так же, как описано ранее:
sample = temp / fullScale(8);
Теперь мы можем применить разложение.
Уравнение & mu; -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.ceil;
import static java.lang.Math.pow;
import static java.lang.Math.signum;
import static java.lang.Math.abs;
import static java.lang.Math.log;
import static java.lang.Math.exp;
/**
* Performs rudimentary audio format conversion.
* <p>
* Example usage:
*
* <pre>{@code
* AudioInputStream ais = ... ;
* SourceDataLine line = ... ;
* AudioFormat fmt = ... ;
*
* // do prep
*
* for (int blen = 0; (blen = ais.read(bytes)) > -1;) {
* int slen;
* slen = SimpleAudioConversion.unpack(bytes, samples, blen, fmt);
*
* // do something with samples
*
* blen = SimpleAudioConversion.pack(samples, bytes, slen, fmt);
* line.write(bytes, 0, blen);
* }
* }</pre>
*
* @author Radiodef
* @see <a href="http://stackoverflow.com/a/26824664/2891664">Overview on StackOverflow.com</a>
*/
public final class SimpleAudioConversion {
private SimpleAudioConversion() {}
/**
* Converts:
* <ul>
* <li>from a byte array ({@code byte[]})
* <li>to an audio sample array ({@code float[]}).
* </ul>
*
* @param bytes the byte array, filled by the {@code InputStream}.
* @param samples an array to fill up with audio samples.
* @param blen the return value of {@code InputStream.read}.
* @param fmt the source {@code AudioFormat}.
*
* @return the number of valid audio samples converted.
*
* @throws NullPointerException
* if {@code bytes}, {@code samples} or {@code fmt} is {@code null}
* @throws ArrayIndexOutOfBoundsException
* if {@code (bytes.length < blen)}
* or {@code (samples.length < blen / bytesPerSample(fmt.getBitsPerSample()))}.
*/
public static int unpack(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 = signUnsigned(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:
* <ul>
* <li>from an audio sample array ({@code float[]})
* <li>to a byte array ({@code byte[]}).
* </ul>
*
* @param samples an array of audio samples to encode.
* @param bytes an array to fill up with bytes.
* @param slen the return value of {@code unpack}.
* @param fmt the destination {@code AudioFormat}.
*
* @return the number of valid bytes converted.
*
* @throws NullPointerException
* if {@code samples}, {@code bytes} or {@code fmt} is {@code null}
* @throws ArrayIndexOutOfBoundsException
* if {@code(samples.length < slen)}
* or {@code (bytes.length < slen * bytesPerSample(fmt.getSampleSizeInBits()))}
*/
public static int pack(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 = unsignSigned(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,
* with {@code (int) ceil(bitsPerSample / 8.0)}.
* <p>
* This is generally equivalent to the optimization
* {@code ((bitsPerSample + 7) >>> 3)}. (Except for
* the invalid argument {@code bitsPerSample <= 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 {@code 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);
}
/**
* Computes the largest magnitude representable by the audio format,
* with {@code pow(2.0, bitsPerSample - 1)}.
* <p>
* For {@code bitsPerSample < 64}, this is generally equivalent to
* the optimization {@code (1L << (bitsPerSample - 1L))}. (Except for
* the invalid argument {@code bitsPerSample <= 0}.)
* <p>
* The result is returned as a {@code double} because, in the case that
* {@code bitsPerSample == 64}, a {@code long} would overflow.
*
* @param bitsPerSample the return value of {@code AudioFormat.getBitsPerSample}.
* @return the largest magnitude representable by the audio format.
*/
public static double fullScale(int bitsPerSample) {
return pow(2.0, 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) << 8L)
| (bytes[i + 1] & 0xffL)
);
} else {
return (
(bytes[i ] & 0xffL)
| ((bytes[i + 1] & 0xffL) << 8L)
);
}
}
private static long unpack24Bit(byte[] bytes,
int i,
boolean isBigEndian) {
if (isBigEndian) {
return (
((bytes[i ] & 0xffL) << 16L)
| ((bytes[i + 1] & 0xffL) << 8L)
| (bytes[i + 2] & 0xffL)
);
} else {
return (
(bytes[i ] & 0xffL)
| ((bytes[i + 1] & 0xffL) << 8L)
| ((bytes[i + 2] & 0xffL) << 16L)
);
}
}
private static long unpackAnyBit(byte[] bytes,
int i,
boolean isBigEndian,
int bytesPerSample) {
long temp = 0L;
if (isBigEndian) {
for (int b = 0; b < bytesPerSample; b++) {
temp |= (bytes[i + b] & 0xffL) << (
8L * (bytesPerSample - b - 1L)
);
}
} else {
for (int b = 0; b < bytesPerSample; b++) {
temp |= (bytes[i + b] & 0xffL) << (8L * 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 >>> 8L) & 0xffL);
bytes[i + 1] = (byte) ( temp & 0xffL);
} else {
bytes[i ] = (byte) ( temp & 0xffL);
bytes[i + 1] = (byte) ((temp >>> 8L) & 0xffL);
}
}
private static void pack24Bit(byte[] bytes,
int i,
long temp,
boolean isBigEndian) {
if (isBigEndian) {
bytes[i ] = (byte) ((temp >>> 16L) & 0xffL);
bytes[i + 1] = (byte) ((temp >>> 8L) & 0xffL);
bytes[i + 2] = (byte) ( temp & 0xffL);
} else {
bytes[i ] = (byte) ( temp & 0xffL);
bytes[i + 1] = (byte) ((temp >>> 8L) & 0xffL);
bytes[i + 2] = (byte) ((temp >>> 16L) & 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 >>> (8L * (bytesPerSample - b - 1L))) & 0xffL
);
}
} else {
for (int b = 0; b < bytesPerSample; b++) {
bytes[i + b] = (byte) ((temp >>> (8L * b)) & 0xffL);
}
}
}
private static long extendSign(long temp, int bitsPerSample) {
int extensionBits = 64 - bitsPerSample;
return (temp << extensionBits) >> extensionBits;
}
private static long signUnsigned(long temp, int bitsPerSample) {
return temp - (long) fullScale(bitsPerSample);
}
private static long unsignSigned(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;
// reciprocal of A
private static final double RE_A = 1.0 / A;
// natural logarithm of A
private static final double LN_A = log(A);
// if values are below this, the A-law exponent is 0
private static final double EXP_0 = 1.0 / (1.0 + LN_A);
private static float bitsToMuLaw(long temp) {
temp ^= 0xffL;
if ((temp & 0x80L) == 0x80L) {
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 < 0L) {
temp = -temp ^ 0x80L;
}
return temp ^ 0xffL;
}
private static float bitsToALaw(long temp) {
temp ^= 0x55L;
if ((temp & 0x80L) == 0x80L) {
temp = -(temp ^ 0x80L);
}
float sample = (float) (temp / fullScale(8));
float sign = signum(sample);
sample = abs(sample);
if (sample < EXP_0) {
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 < RE_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 < 0L) {
temp = -temp ^ 0x80L;
}
return temp ^ 0x55L;
}
}